From 8e4fc75958822e6839dabbc98013d12cf8349dc1 Mon Sep 17 00:00:00 2001 From: 1992w Date: Tue, 14 Jul 2026 21:47:52 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=20SetTLSFingerprintS?= =?UTF-8?q?pec=20=E8=BF=9E=E7=BB=AD=E6=8F=A1=E6=89=8B=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.go | 5 +-- client_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 81132354..7053a623 100644 --- a/client.go +++ b/client.go @@ -1288,9 +1288,10 @@ func (c *Client) setTLSFingerprint(clientHelloID utls.ClientHelloID, uTLSConnApp // (e.g. for JA3/JA4 customization). Uses utls // (https://github.com/refraction-networking/utls) to perform the tls handshake. // Note this is valid for HTTP1 and HTTP2, not HTTP3. -func (c *Client) SetTLSFingerprintSpec(clientHelloID *utls.ClientHelloSpec) *Client { +func (c *Client) SetTLSFingerprintSpec(fn func() utls.ClientHelloSpec) *Client { c.setTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { - return conn.ApplyPreset(clientHelloID) + spec := fn() + return conn.ApplyPreset(&spec) }) return c } diff --git a/client_test.go b/client_test.go index 59b38274..b00eff03 100644 --- a/client_test.go +++ b/client_test.go @@ -19,6 +19,7 @@ import ( "github.com/imroc/req/v3/internal/header" "github.com/imroc/req/v3/internal/tests" + utls "github.com/refraction-networking/utls" "golang.org/x/net/publicsuffix" ) @@ -752,3 +753,86 @@ func TestCloneCookieJar(t *testing.T) { tests.AssertEqual(t, true, c2.cookiejarFactory == nil) tests.AssertEqual(t, true, c2.httpClient.Jar == nil) } + +func TestSetTLSFingerprintSpec(t *testing.T) { + c := tc().SetTLSFingerprintSpec(func() utls.ClientHelloSpec { + return utls.ClientHelloSpec{ + CipherSuites: []uint16{ + utls.GREASE_PLACEHOLDER, + utls.TLS_AES_128_GCM_SHA256, + utls.TLS_AES_256_GCM_SHA384, + utls.TLS_CHACHA20_POLY1305_SHA256, + utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + utls.TLS_RSA_WITH_AES_128_GCM_SHA256, + utls.TLS_RSA_WITH_AES_256_GCM_SHA384, + utls.TLS_RSA_WITH_AES_128_CBC_SHA, + utls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, + }, + Extensions: utls.ShuffleChromeTLSExtensions([]utls.TLSExtension{ + &utls.UtlsGREASEExtension{}, + &utls.SNIExtension{}, + &utls.ExtendedMasterSecretExtension{}, + &utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient}, + &utls.SupportedCurvesExtension{[]utls.CurveID{ + utls.GREASE_PLACEHOLDER, + utls.X25519MLKEM768, + utls.X25519, + utls.CurveP256, + utls.CurveP384, + }}, + &utls.SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, + }}, + &utls.SessionTicketExtension{}, + &utls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &utls.StatusRequestExtension{}, + &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{ + utls.ECDSAWithP256AndSHA256, + utls.PSSWithSHA256, + utls.PKCS1WithSHA256, + utls.ECDSAWithP384AndSHA384, + utls.PSSWithSHA384, + utls.PKCS1WithSHA384, + utls.PSSWithSHA512, + utls.PKCS1WithSHA512, + }}, + &utls.SCTExtension{}, + &utls.KeyShareExtension{[]utls.KeyShare{ + {Group: utls.CurveID(utls.GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: utls.X25519MLKEM768}, + {Group: utls.X25519}, + }}, + &utls.PSKKeyExchangeModesExtension{[]uint8{ + utls.PskModeDHE, + }}, + &utls.SupportedVersionsExtension{[]uint16{ + utls.GREASE_PLACEHOLDER, + utls.VersionTLS13, + utls.VersionTLS12, + }}, + &utls.UtlsCompressCertExtension{[]utls.CertCompressionAlgo{ + utls.CertCompressionBrotli, + }}, + &utls.ApplicationSettingsExtensionNew{SupportedProtocols: []string{"h2"}}, + utls.BoringGREASEECH(), + &utls.UtlsGREASEExtension{}, + }), + } + }) + if _, err := c.R().Get("https://tls.browserleaks.com/json"); err != nil { + t.Errorf("TestSetTLSFingerprintSpec failed: %v", err) + } + if _, err := c.R().Get("https://tools.scrapfly.io/api/fp/ja3"); err != nil { + t.Errorf("TestSetTLSFingerprintSpec failed: %v", err) + } +} From 8bd83b5790946d2cd2d2607fb3209861a82a45b5 Mon Sep 17 00:00:00 2001 From: 1992w Date: Wed, 22 Jul 2026 23:58:58 +0800 Subject: [PATCH 2/2] fix: refactor TestSetTLSFingerprintSpec to use local test servers and fix go vet --- client_test.go | 73 +++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 55 deletions(-) diff --git a/client_test.go b/client_test.go index b00eff03..c343b21d 100644 --- a/client_test.go +++ b/client_test.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "net/http/cookiejar" + "net/http/httptest" "net/url" "os" "regexp" @@ -755,84 +756,46 @@ func TestCloneCookieJar(t *testing.T) { } func TestSetTLSFingerprintSpec(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + } + server1 := httptest.NewTLSServer(http.HandlerFunc(handler)) + defer server1.Close() + c := tc().SetTLSFingerprintSpec(func() utls.ClientHelloSpec { return utls.ClientHelloSpec{ CipherSuites: []uint16{ - utls.GREASE_PLACEHOLDER, utls.TLS_AES_128_GCM_SHA256, - utls.TLS_AES_256_GCM_SHA384, - utls.TLS_CHACHA20_POLY1305_SHA256, - utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - utls.TLS_RSA_WITH_AES_128_GCM_SHA256, - utls.TLS_RSA_WITH_AES_256_GCM_SHA384, - utls.TLS_RSA_WITH_AES_128_CBC_SHA, - utls.TLS_RSA_WITH_AES_256_CBC_SHA, }, - CompressionMethods: []byte{ - 0x00, - }, - Extensions: utls.ShuffleChromeTLSExtensions([]utls.TLSExtension{ - &utls.UtlsGREASEExtension{}, + CompressionMethods: []byte{0x00}, + Extensions: []utls.TLSExtension{ &utls.SNIExtension{}, - &utls.ExtendedMasterSecretExtension{}, - &utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient}, - &utls.SupportedCurvesExtension{[]utls.CurveID{ - utls.GREASE_PLACEHOLDER, - utls.X25519MLKEM768, + &utls.SupportedCurvesExtension{Curves: []utls.CurveID{ utls.X25519, utls.CurveP256, - utls.CurveP384, - }}, - &utls.SupportedPointsExtension{SupportedPoints: []byte{ - 0x00, }}, - &utls.SessionTicketExtension{}, + &utls.SupportedPointsExtension{SupportedPoints: []byte{0x00}}, &utls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, - &utls.StatusRequestExtension{}, &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{ utls.ECDSAWithP256AndSHA256, utls.PSSWithSHA256, utls.PKCS1WithSHA256, - utls.ECDSAWithP384AndSHA384, - utls.PSSWithSHA384, - utls.PKCS1WithSHA384, - utls.PSSWithSHA512, - utls.PKCS1WithSHA512, }}, - &utls.SCTExtension{}, - &utls.KeyShareExtension{[]utls.KeyShare{ - {Group: utls.CurveID(utls.GREASE_PLACEHOLDER), Data: []byte{0}}, - {Group: utls.X25519MLKEM768}, + &utls.KeyShareExtension{KeyShares: []utls.KeyShare{ {Group: utls.X25519}, }}, - &utls.PSKKeyExchangeModesExtension{[]uint8{ - utls.PskModeDHE, - }}, - &utls.SupportedVersionsExtension{[]uint16{ - utls.GREASE_PLACEHOLDER, + &utls.SupportedVersionsExtension{Versions: []uint16{ utls.VersionTLS13, utls.VersionTLS12, }}, - &utls.UtlsCompressCertExtension{[]utls.CertCompressionAlgo{ - utls.CertCompressionBrotli, - }}, - &utls.ApplicationSettingsExtensionNew{SupportedProtocols: []string{"h2"}}, - utls.BoringGREASEECH(), - &utls.UtlsGREASEExtension{}, - }), + }, } }) - if _, err := c.R().Get("https://tls.browserleaks.com/json"); err != nil { - t.Errorf("TestSetTLSFingerprintSpec failed: %v", err) + if _, err := c.R().Get("/"); err != nil { + t.Errorf("TestSetTLSFingerprintSpec failed on first handshake: %v", err) } - if _, err := c.R().Get("https://tools.scrapfly.io/api/fp/ja3"); err != nil { - t.Errorf("TestSetTLSFingerprintSpec failed: %v", err) + if _, err := c.R().Get(server1.URL); err != nil { + t.Errorf("TestSetTLSFingerprintSpec failed on consecutive handshake to different host: %v", err) } }