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 }