package config import ( "errors" "reflect" "strings" "time" ) // JSCKServerHeaders is the server headers section's configuration key. const JSCKServerHeaders string = "headers" // JSCServerHeaders configures the server's request checks and security // response headers. AllowedHosts restricts the Host header, and supports // "*.example.com"; empty disables the check. Each policy header is sent with // its configured value; empty means it isn't sent. ExtraResponseHeaders are // set on every response, and RemoveResponseHeaders stripped from every // response before it is sent; empty means none. type JSCServerHeaders struct { AllowedHosts []string `cfg:"allowed_hosts"` ContentSecurityPolicy string `cfg:"content_security_policy"` FrameOptions string `cfg:"frame_options"` ReferrerPolicy string `cfg:"referrer_policy"` PermissionsPolicy string `cfg:"permissions_policy"` CrossOriginOpenerPolicy string `cfg:"cross_origin_opener_policy"` CrossOriginResourcePolicy string `cfg:"cross_origin_resource_policy"` HSTS JSCServerHeadersHSTS `cfg:"hsts"` ExtraResponseHeaders []JSCServerHeadersExtra `cfg:"extra_response_headers"` RemoveResponseHeaders []string `cfg:"remove_response_headers"` } // ---------- Section methods (see Section) ---------- // Map implements Section. func (c JSCServerHeaders) Map() map[string]any { return mapOf(c) } // Flatten implements Section. func (c JSCServerHeaders) Flatten() map[string]any { return flattenOf(JSCKServerHeaders, c) } // TypeOf implements Section. func (c JSCServerHeaders) TypeOf(key string) (reflect.Kind, error) { return typeOf(c, key) } // ValueOf implements Section. func (c JSCServerHeaders) ValueOf(key string) (any, error) { return valueOf(c, key) } // SetValue implements Section. func (c *JSCServerHeaders) SetValue(key string, value any) error { return setValue(c, key, value) } // ========== HSTS Headers ========== // JSCKServerHeadersHSTS is the HSTS section's configuration key. const JSCKServerHeadersHSTS string = "hsts" // JSCServerHeadersHSTS configures the Strict-Transport-Security header, sent // on HTTPS requests when enabled. A MaxAge of 0 or less means the header // isn't sent. type JSCServerHeadersHSTS struct { Enabled bool `cfg:"enabled" default:"false"` MaxAge time.Duration `cfg:"max_age" default:"8760h"` IncludeSubdomains bool `cfg:"include_subdomains" default:"true"` Preload bool `cfg:"preload" default:"false"` } // ---------- Section methods (see Section) ---------- // Map implements Section. func (c JSCServerHeadersHSTS) Map() map[string]any { return mapOf(c) } // Flatten implements Section. func (c JSCServerHeadersHSTS) Flatten() map[string]any { return flattenOf(JSCKServerHeadersHSTS, c) } // TypeOf implements Section. func (c JSCServerHeadersHSTS) TypeOf(key string) (reflect.Kind, error) { return typeOf(c, key) } // ValueOf implements Section. func (c JSCServerHeadersHSTS) ValueOf(key string) (any, error) { return valueOf(c, key) } // SetValue implements Section. func (c *JSCServerHeadersHSTS) SetValue(key string, value any) error { return setValue(c, key, value) } // ========== Extra Headers ========== // JSCServerHeadersExtra configures one extra response header. It is used as // the items of the extra_response_headers list, so it has no JSCK* constant // and its Flatten keys are not prefixed. Header must be a valid header name, // and Value must not hold a line break (see validate); an empty Value means // the header isn't sent. type JSCServerHeadersExtra struct { Header string `cfg:"header"` Value string `cfg:"value"` } // ---------- Section methods (see Section) ---------- // Map implements Section. func (c JSCServerHeadersExtra) Map() map[string]any { return mapOf(c) } // Flatten implements Section. func (c JSCServerHeadersExtra) Flatten() map[string]any { return flattenOf("", c) } // TypeOf implements Section. func (c JSCServerHeadersExtra) TypeOf(key string) (reflect.Kind, error) { return typeOf(c, key) } // ValueOf implements Section. func (c JSCServerHeadersExtra) ValueOf(key string) (any, error) { return valueOf(c, key) } // SetValue implements Section. func (c *JSCServerHeadersExtra) SetValue(key string, value any) error { return setValue(c, key, value) } // ---------- Section specific methods ---------- // validate checks values before they are stored (see validator). header must // be a valid header field name (an RFC 9110 token) and value must not hold a // line break, so neither can break the response's header block. func (c *JSCServerHeadersExtra) validate(key string, value any) error { switch key { case "header": if !isHeaderToken(value.(string)) { return errors.New( "config: invalid header name: must be a non-empty token such as X-Robots-Tag", ) } case "value": if strings.ContainsAny(value.(string), "\r\n") { return errors.New( "config: invalid header value: must not contain a line break", ) } } return nil } // isHeaderToken reports whether name is a non-empty RFC 9110 token, the syntax // of a header field name. func isHeaderToken(name string) bool { if name == "" { return false } for _, char := range name { switch { case char >= 'a' && char <= 'z', char >= 'A' && char <= 'Z', char >= '0' && char <= '9': case strings.ContainsRune("!#$%&'*+-.^_`|~", char): default: return false } } return true }