Files
creator-hub/internal/environment/fingerprint.go
T

136 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package environment
import (
"errors"
"net/url"
"regexp"
"strconv"
"strings"
)
// Fingerprint 是 fingerprint-chromium 的结构化启动参数集合。
// 参数契约来源:https://github.com/adryfish/fingerprint-chromium(BSD-3-clause)。
// 零值字段表示不传递该命令行开关。
type Fingerprint struct {
Seed int64 `json:"seed"`
Platform string `json:"platform,omitempty"`
PlatformVersion string `json:"platform_version,omitempty"`
Brand string `json:"brand,omitempty"`
BrandVersion string `json:"brand_version,omitempty"`
HardwareConcurrency int64 `json:"hardware_concurrency,omitempty"`
Lang string `json:"lang,omitempty"`
AcceptLang string `json:"accept_lang,omitempty"`
Timezone string `json:"timezone,omitempty"`
ProxyServer string `json:"proxy_server,omitempty"`
DisableNonProxiedUDP bool `json:"disable_non_proxied_udp,omitempty"`
DisableSpoofing string `json:"disable_spoofing,omitempty"`
}
var (
platforms = map[string]bool{"windows": true, "linux": true, "macos": true}
brands = map[string]bool{"Chrome": true, "Edge": true, "Opera": true, "Vivaldi": true}
spoofings = map[string]bool{"font": true, "audio": true, "canvas": true, "clientrects": true, "gpu": true}
langPattern = regexp.MustCompile(`^[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})?$`)
acceptLangPattern = regexp.MustCompile(
`^[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})?(, ?[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*){0,7}$`)
timezonePattern = regexp.MustCompile(`^[A-Za-z0-9_+\-/]{1,64}$`)
shortVersionPatten = regexp.MustCompile(`^[A-Za-z0-9._+~-]{1,32}$`)
)
// Validate 校验全量指纹参数;只做值域校验,参数作为独立 argv 传入容器,无 shell 注入面。
func (f Fingerprint) Validate() error {
if f.Seed < 1 || f.Seed > 2147483647 {
return errors.New("seed must be 1..2147483647")
}
if !optionalIn(f.Platform, platforms) {
return errors.New("platform must be one of windows, linux, macos")
}
if !optionalIn(f.Brand, brands) {
return errors.New("brand must be one of Chrome, Edge, Opera, Vivaldi")
}
if !optionalMatch(f.PlatformVersion, shortVersionPatten) {
return errors.New("platform_version must be a short version like 11.0.0")
}
if !optionalMatch(f.BrandVersion, shortVersionPatten) {
return errors.New("brand_version must be a short version like 132.0.6834.159")
}
if f.HardwareConcurrency < 0 || f.HardwareConcurrency > 128 {
return errors.New("hardware_concurrency must be 0..128, 0 omits the flag")
}
if !optionalMatch(f.Lang, langPattern) {
return errors.New("lang must be a language code like zh-CN")
}
if !optionalMatch(f.AcceptLang, acceptLangPattern) {
return errors.New("accept_lang must be a comma separated language list like zh-CN,en-US")
}
if !optionalMatch(f.Timezone, timezonePattern) {
return errors.New("timezone must be an IANA timezone like Asia/Shanghai")
}
if f.ProxyServer != "" && !validProxyURL(f.ProxyServer) {
return errors.New("proxy_server must be an http/https/socks4/socks5 URL with a host")
}
if f.DisableSpoofing != "" && !validDisableSpoofing(f.DisableSpoofing) {
return errors.New("disable_spoofing must be a comma separated subset of font,audio,canvas,clientrects,gpu")
}
return nil
}
// Args 生成 fingerprint-chromium 命令行参数(不含 URL 尾参)。
func (f Fingerprint) Args() []string {
args := []string{"--fingerprint=" + strconv.FormatInt(f.Seed, 10)}
value := func(flag, setting string) {
if setting != "" {
args = append(args, "--"+flag+"="+setting)
}
}
value("fingerprint-platform", f.Platform)
value("fingerprint-platform-version", f.PlatformVersion)
value("fingerprint-brand", f.Brand)
value("fingerprint-brand-version", f.BrandVersion)
if f.HardwareConcurrency > 0 {
value("fingerprint-hardware-concurrency", strconv.FormatInt(f.HardwareConcurrency, 10))
}
value("lang", f.Lang)
value("accept-lang", f.AcceptLang)
value("timezone", f.Timezone)
value("proxy-server", f.ProxyServer)
if f.DisableNonProxiedUDP {
args = append(args, "--disable-non-proxied-udp")
}
value("disable-spoofing", f.DisableSpoofing)
return args
}
func optionalIn(value string, allowed map[string]bool) bool {
return value == "" || allowed[value]
}
func optionalMatch(value string, pattern *regexp.Regexp) bool {
return value == "" || pattern.MatchString(value)
}
func validProxyURL(raw string) bool {
parsed, err := url.Parse(raw)
if err != nil || parsed.Host == "" || parsed.User != nil {
return false
}
switch parsed.Scheme {
case "http", "https", "socks4", "socks5":
return true
default:
return false
}
}
func validDisableSpoofing(value string) bool {
seen := make(map[string]bool, len(spoofings))
for _, part := range strings.Split(value, ",") {
part = strings.TrimSpace(part)
if !spoofings[part] || seen[part] {
return false
}
seen[part] = true
}
return true
}