package models import ( "errors" "fmt" "net/http" "strings" ) // CheckHeader provides functionality. type CheckHeader struct { Key string `json:"key"` Value string `json:"value"` } // CheckSettings provides functionality. type CheckSettings struct { // HTTP Basic Auth Username HTTPUsername string `json:"http_username,omitempty"` // HTTP Basic Auth Password HTTPPassword string `json:"http_password,omitempty"` // ExpectedAnswer (default, redirect, custom) ExpectedAnswer string `json:"expected_answer,omitempty"` // Expected redirect location ExpectedLocation string `json:"expected_location,omitempty"` // Expected HTTP status codes for custom ExpectedHTTPCode int `json:"expected_http_code,omitempty"` // Keyword search type (off, present or absent) KeywordType string `json:"keyword_type,omitempty"` // Keyword to search for KeywordValue string `json:"keyword_value,omitempty"` SlowTime int `json:"slow_time"` Timeout int `json:"timeout"` // Request Settings CheckIp bool `json:"checkip"` //nolint:revive // accepted lint exception CheckIPv6 bool `json:"checkipv6"` RequestHeader []CheckHeader `json:"request_headers"` RequestMethod string `json:"request_method"` RequestType string `json:"request_type"` RequestContent string `json:"request_content"` // SSH Settings Port string `json:"port"` // Host optionally overrides the monitor host for checks that do // not naturally target the Monitor.Host (e.g. an ICMP/TCP/UDP // probe to a separate machine, or a different IP family). Used by // cping/ctcp/cudp. Host string `json:"host,omitempty"` // Count is the per-check packet count for ping. Defaults to 1 in // cping when zero or negative. Count int `json:"count,omitempty"` // PacketSize is the ICMP payload size for ping (bytes). Defaults // to 56 in cping when zero or negative. PacketSize int `json:"packet_size,omitempty"` // Distributed marks this check as eligible for execution on the // distributed worker pool (multi-region, multi-worker) instead of // only the in-process scheduler. Reserved for paid plans; the // controller layer enforces the plan check. Distributed bool `json:"distributed"` } // redirectCodes contains all HTTP redirect status codes. var redirectCodes = []int{300, 301, 302, 303, 307, 308} // CheckKeyword provides functionality. func (s *CheckSettings) CheckKeyword(body []byte, warns []string) ([]string, error) { var err error switch s.KeywordType { case "", "off": return warns, nil case "present": if !strings.Contains(string(body), s.KeywordValue) { err = errors.New("expected keyword " + s.KeywordValue + " not found") } case "absent": if strings.Contains(string(body), s.KeywordValue) { err = errors.New("unexpected keyword " + s.KeywordValue + " found") } } return warns, err } // CheckAnswer provides functionality. func (s *CheckSettings) CheckAnswer(resp *http.Response, body []byte) ([]string, error) { // log.Println("check answer, settings:") // spew.Dump(s) isRedirect := false for _, rc := range redirectCodes { if resp.StatusCode == rc { isRedirect = true } } location := resp.Header.Get("location") // log.Println("redirect?", isRedirect, location) switch s.ExpectedAnswer { case "", "default": if isRedirect { return s.CheckKeyword(body, []string{"redirect: " + location}) } else if resp.StatusCode != 200 { return []string{}, fmt.Errorf("bad status code: %d", resp.StatusCode) } case "redirect": if !isRedirect { return []string{}, fmt.Errorf("bad status code: %d (expected redirect)", resp.StatusCode) } if s.ExpectedLocation != "" { if location != s.ExpectedLocation { return []string{}, fmt.Errorf( "bad location: %s (expected %s)", location, s.ExpectedLocation, ) } } case "custom": if s.ExpectedHTTPCode == 0 { s.ExpectedHTTPCode = 200 } if resp.StatusCode != s.ExpectedHTTPCode { return []string{}, fmt.Errorf("bad status code: %d (expected %d)", resp.StatusCode, s.ExpectedHTTPCode) } if s.ExpectedLocation != "" { if location != s.ExpectedLocation { return []string{}, fmt.Errorf( "bad location: %s (expected %s)", location, s.ExpectedLocation, ) } } default: panic("bad expectedAnswer") } // return []string{}, nil return s.CheckKeyword(body, []string{}) }