fiber/middleware/csrf/helpers.go
2025-08-21 08:35:59 +02:00

85 lines
2.5 KiB
Go

package csrf
import (
"crypto/subtle"
"net/url"
"strings"
)
func compareTokens(a, b []byte) bool {
return subtle.ConstantTimeCompare(a, b) == 1
}
func compareStrings(a, b string) bool {
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}
// normalizeOrigin checks if the provided origin is in a correct format
// and normalizes it by removing any path or trailing slash.
// It returns a boolean indicating whether the origin is valid
// and the normalized origin.
func normalizeOrigin(origin string) (bool, string) {
parsedOrigin, err := url.Parse(origin)
if err != nil {
return false, ""
}
// Validate the scheme is either http or https
if parsedOrigin.Scheme != "http" && parsedOrigin.Scheme != "https" {
return false, ""
}
// Don't allow a wildcard with a protocol
// wildcards cannot be used within any other value. For example, the following header is not valid:
// Access-Control-Allow-Origin: https://*
if strings.Contains(parsedOrigin.Host, "*") {
return false, ""
}
// Validate there is a host present. The presence of a path, query, or fragment components
// is checked, but a trailing "/" (indicative of the root) is allowed for the path and will be normalized
if parsedOrigin.Host == "" || (parsedOrigin.Path != "" && parsedOrigin.Path != "/") || parsedOrigin.RawQuery != "" || parsedOrigin.Fragment != "" {
return false, ""
}
// Normalize the origin by constructing it from the scheme and host.
// The path or trailing slash is not included in the normalized origin.
return true, strings.ToLower(parsedOrigin.Scheme) + "://" + strings.ToLower(parsedOrigin.Host)
}
type subdomain struct {
prefix string
suffix string
}
func (s subdomain) match(o string) bool {
// Not a subdomain if not long enough for a dot separator.
if len(o) < len(s.prefix)+len(s.suffix)+1 {
return false
}
if !strings.HasPrefix(o, s.prefix) || !strings.HasSuffix(o, s.suffix) {
return false
}
// Check for the dot separator and validate that there is at least one
// non-empty label between prefix and suffix. Empty labels like
// "https://.example.com" or "https://..example.com" should not match.
suffixStartIndex := len(o) - len(s.suffix)
if suffixStartIndex <= len(s.prefix) {
return false
}
if o[suffixStartIndex-1] != '.' {
return false
}
// Extract the subdomain part (without the trailing dot) and ensure it
// doesn't contain empty labels.
sub := o[len(s.prefix) : suffixStartIndex-1]
if sub == "" || strings.HasPrefix(sub, ".") || strings.Contains(sub, "..") {
return false
}
return true
}