Skip to content
quarry Artifact [f9e1805577]
Login

Artifact [f9e1805577]

Artifact f9e1805577f4d4e8f4362cd2a9fcbf58d9449a7fb534142e70b239a7e7e28ec4:


package obs

import (
	"fmt"
	"hash/fnv"
	"net/http"
	"net/netip"
	"sort"
	"strings"
)

// PrefixOf is an address's network range: the /24 of an IPv4 address,
// the /48 of an IPv6 one, "" for anything that does not parse. A /48 is
// what one site is given; a /24 is the smallest block routed alone.
func PrefixOf(addr string) string {
	a, err := netip.ParseAddr(addr)
	if err != nil {
		return ""
	}
	a = a.Unmap()
	bits := 24
	if a.Is6() {
		bits = 48
	}
	p, err := a.Prefix(bits)
	if err != nil {
		return ""
	}
	return p.String()
}

// browserHints are the headers a current browser sends with a page
// load, by the short name the fingerprint gives each. A script that
// borrows a browser's user agent rarely sends them, or sends them in a
// combination no browser does.
var browserHints = []struct{ header, short string }{
	{"Sec-Ch-Ua", "ch"},
	{"Sec-Ch-Ua-Mobile", "chm"},
	{"Sec-Ch-Ua-Platform", "chp"},
	{"Sec-Fetch-Site", "fs"},
	{"Sec-Fetch-Mode", "fm"},
	{"Sec-Fetch-Dest", "fd"},
	{"Sec-Fetch-User", "fu"},
	{"Upgrade-Insecure-Requests", "uir"},
	{"Accept-Language", "al"},
	{"Accept-Encoding", "ae"},
	{"Cookie", "ck"},
	{"Priority", "pri"},
}

// edgeHeaders are added by what stands in front of the forge, so they
// say nothing about the client.
func edgeHeader(name string) bool {
	n := strings.ToLower(name)
	return strings.HasPrefix(n, "cf-") || strings.HasPrefix(n, "x-forwarded-") || n == "x-real-ip" ||
		n == "cdn-loop" || n == "true-client-ip" || n == "via"
}

// Fingerprint describes how a request presented itself. hints names the
// browser headers present, in a fixed order, with Sec-Fetch-Mode's value
// (navigate, cors...) since a page load says navigate; lang is the first
// language asked for; headset is a digest of the set of header names,
// the edge's own taken out.
func Fingerprint(h http.Header) (hints, lang, headset string) {
	var on []string
	for _, b := range browserHints {
		if _, ok := h[b.header]; ok {
			if b.short == "fm" {
				on = append(on, "fm="+strings.ToLower(h.Get(b.header)))
				continue
			}
			on = append(on, b.short)
		}
	}
	hints = strings.Join(on, " ")
	if al := h.Get("Accept-Language"); al != "" {
		lang, _, _ = strings.Cut(al, ",")
		lang, _, _ = strings.Cut(lang, ";")
		lang = strings.TrimSpace(lang)
		if len(lang) > 16 {
			lang = lang[:16]
		}
	}
	names := make([]string, 0, len(h))
	for k := range h {
		if !edgeHeader(k) {
			names = append(names, strings.ToLower(k))
		}
	}
	sort.Strings(names)
	f := fnv.New64a()
	f.Write([]byte(strings.Join(names, ",")))
	return hints, lang, fmt.Sprintf("%016x", f.Sum64())
}