Initial commit
This commit is contained in:
commit
542ebdea34
40 changed files with 4592 additions and 0 deletions
130
checker/headers.go
Normal file
130
checker/headers.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2026 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
|
||||
package checker
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HSTSDirectives is the parsed form of a Strict-Transport-Security header
|
||||
// (RFC 6797 §6.1).
|
||||
type HSTSDirectives struct {
|
||||
MaxAge int64
|
||||
IncludeSub bool
|
||||
Preload bool
|
||||
}
|
||||
|
||||
// ParseHSTS pulls max-age, includeSubDomains and preload out of an HSTS
|
||||
// value. Returns nil for an empty value so callers can distinguish "header
|
||||
// absent" from "header present with max-age=0".
|
||||
func ParseHSTS(v string) *HSTSDirectives {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
h := &HSTSDirectives{}
|
||||
for _, part := range strings.Split(v, ";") {
|
||||
part = strings.TrimSpace(part)
|
||||
switch {
|
||||
case strings.HasPrefix(strings.ToLower(part), "max-age="):
|
||||
val := strings.Trim(part[len("max-age="):], "\"")
|
||||
if n, err := strconv.ParseInt(val, 10, 64); err == nil {
|
||||
h.MaxAge = n
|
||||
}
|
||||
case strings.EqualFold(part, "includeSubDomains"):
|
||||
h.IncludeSub = true
|
||||
case strings.EqualFold(part, "preload"):
|
||||
h.Preload = true
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// CSPDirectives is the parsed form of a Content-Security-Policy header
|
||||
// (W3C CSP3). Directive names are lowercased; source tokens keep their
|
||||
// original casing because keywords like 'unsafe-inline' must round-trip
|
||||
// verbatim when reported back to the user.
|
||||
type CSPDirectives struct {
|
||||
Raw string
|
||||
Directives map[string][]string
|
||||
}
|
||||
|
||||
// ParseCSP splits a CSP header into its directive → sources map.
|
||||
func ParseCSP(v string) *CSPDirectives {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
c := &CSPDirectives{Raw: v, Directives: map[string][]string{}}
|
||||
for _, d := range strings.Split(v, ";") {
|
||||
d = strings.TrimSpace(d)
|
||||
if d == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(d)
|
||||
name := strings.ToLower(fields[0])
|
||||
c.Directives[name] = fields[1:]
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// HasDirective reports whether the named directive is declared at all.
|
||||
func (c *CSPDirectives) HasDirective(name string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := c.Directives[strings.ToLower(name)]
|
||||
return ok
|
||||
}
|
||||
|
||||
// HasSource reports whether the named directive lists the given source
|
||||
// token (case-insensitive comparison; pass keywords with their quotes,
|
||||
// e.g. "'unsafe-inline'").
|
||||
func (c *CSPDirectives) HasSource(directive, source string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
for _, s := range c.Directives[strings.ToLower(directive)] {
|
||||
if strings.EqualFold(s, source) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasUnsafe reports whether any directive uses 'unsafe-inline' or
|
||||
// 'unsafe-eval' — the two keywords that nullify most of CSP's value.
|
||||
func (c *CSPDirectives) HasUnsafe() bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
for _, sources := range c.Directives {
|
||||
for _, s := range sources {
|
||||
ls := strings.ToLower(s)
|
||||
if ls == "'unsafe-inline'" || ls == "'unsafe-eval'" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ParsedHeaders bundles the structured headers we parse repeatedly. Fields
|
||||
// are nil when the underlying header is absent on the probe; rules can
|
||||
// nil-check or rely on the typed accessors which already handle nil.
|
||||
type ParsedHeaders struct {
|
||||
HSTS *HSTSDirectives
|
||||
CSP *CSPDirectives
|
||||
}
|
||||
|
||||
// ParseHeaders builds a ParsedHeaders from a probe's raw header map.
|
||||
// Header lookups use the lowercase keys produced by the collector.
|
||||
func ParseHeaders(p HTTPProbe) ParsedHeaders {
|
||||
return ParsedHeaders{
|
||||
HSTS: ParseHSTS(p.Headers["strict-transport-security"]),
|
||||
CSP: ParseCSP(p.Headers["content-security-policy"]),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue