// This file is part of the happyDomain (R) project. // Copyright (c) 2020-2026 happyDomain // Authors: Pierre-Olivier Mercier, et al. package checker import ( "context" "fmt" "net/url" "strings" sdk "git.happydns.org/checker-sdk-go/checker" ) func init() { RegisterRule(&httpsRedirectRule{}) } // httpsRedirectRule verifies that plain HTTP either redirects to HTTPS or // fails (which is acceptable when HTTP is intentionally not served). type httpsRedirectRule struct{} func (r *httpsRedirectRule) Name() string { return "http.https_redirect" } func (r *httpsRedirectRule) Description() string { return "Plain HTTP responses must redirect to an HTTPS URL on the same host." } func (r *httpsRedirectRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState { data, errSt := loadHTTPData(ctx, obs) if errSt != nil { return []sdk.CheckState{*errSt} } require := sdk.GetBoolOption(opts, OptionRequireHTTPS, true) const okMsg = "HTTP redirects to HTTPS on every reachable IP." return EvalAggregateByScheme(data, "http", "http.https_redirect", okMsg, func(p HTTPProbe, emit func(sdk.CheckState)) { if !p.TCPConnected || p.StatusCode == 0 { // Reachability rule handles this; an HTTP server that is // simply not running is fine for redirect-purposes. return } final := p.FinalURL if final == "" && len(p.RedirectChain) > 0 { final = p.RedirectChain[len(p.RedirectChain)-1].To } isHTTPS := false if u, err := url.Parse(final); err == nil { isHTTPS = strings.EqualFold(u.Scheme, "https") } switch { case isHTTPS: // Good. Aggregated below as a single OK. case require: emit(sdk.CheckState{ Status: sdk.StatusWarn, Code: "http.no_https_redirect", Subject: p.Address, Message: fmt.Sprintf("HTTP response on %s did not redirect to HTTPS (final URL: %s, status %d)", p.Address, final, p.StatusCode), Meta: map[string]any{"fix": "Configure your web server to redirect every plain-HTTP request to https://."}, }) default: emit(sdk.CheckState{ Status: sdk.StatusInfo, Code: "http.plain_http_served", Subject: p.Address, Message: fmt.Sprintf("HTTP responded directly without redirect (status %d)", p.StatusCode), }) } }) }