From f524fffe6caacfd4e56477d2ef3c403be16949ab Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:25:20 +0800 Subject: [PATCH 01/10] SetTLSFingerprint can uTLSConnApply --- client.go | 7 ++++++- client_test.go | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index e9db4b8..172f4a4 100644 --- a/client.go +++ b/client.go @@ -1224,7 +1224,7 @@ func (conn *uTLSConn) ConnectionState() tls.ConnectionState { // (https://github.com/refraction-networking/utls) to perform the tls handshake, // which uses the specified clientHelloID to simulate the tls fingerprint. // Note this is valid for HTTP1 and HTTP2, not HTTP3. -func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID) *Client { +func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID, uTLSConnApply ...func(*uTLSConn) error) *Client { fn := func(ctx context.Context, addr string, plainConn net.Conn) (conn net.Conn, tlsState *tls.ConnectionState, err error) { colonPos := strings.LastIndex(addr, ":") if colonPos == -1 { @@ -1248,6 +1248,11 @@ func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID) *Client { KeyLogWriter: tlsConfig.KeyLogWriter, } uconn := &uTLSConn{utls.UClient(plainConn, utlsConfig, clientHelloID)} + for _, fnApply := range uTLSConnApply { + if err = fnApply(uconn); err != nil { + return + } + } err = uconn.HandshakeContext(ctx) if err != nil { return diff --git a/client_test.go b/client_test.go index 8f7bc9b..cfd8483 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" ) @@ -720,3 +721,21 @@ func TestCloneCookieJar(t *testing.T) { tests.AssertEqual(t, true, c2.cookiejarFactory == nil) tests.AssertEqual(t, true, c2.httpClient.Jar == nil) } +func TestUTLSConnApply(t *testing.T) { + c1 := C() + + c1.SetTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { + tt, _ := utls.UTLSIdToSpec(utls.HelloQQ_Auto) + //cycletls.StringToSpec("771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0", false) + err := conn.ApplyPreset(&tt) + return err + }) + + //c1.SetTLSFingerprintQQ() + bodyJson := &struct { + Ja3NText string `json:"ja3n_text"` + }{} + _ = c1.Get("https://tls.browserleaks.com/json").Do().Into(bodyJson) + //println(string(bodyJson.Ja3NText)) + tests.AssertEqual(t, true, bodyJson.Ja3NText == "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0") +} From fc4d80439c6ae3e08242c5d0d42a6ebcd09cba61 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:25:59 +0800 Subject: [PATCH 02/10] SetTLSFingerprint can uTLSConnApply --- client_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client_test.go b/client_test.go index cfd8483..6cb44ec 100644 --- a/client_test.go +++ b/client_test.go @@ -726,6 +726,7 @@ func TestUTLSConnApply(t *testing.T) { c1.SetTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { tt, _ := utls.UTLSIdToSpec(utls.HelloQQ_Auto) + //"github.com/Danny-Dasilva/CycleTLS/cycletls" //cycletls.StringToSpec("771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0", false) err := conn.ApplyPreset(&tt) return err From de2a62b357ca3645e26caf783c9852ac5a69ba95 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:32:52 +0800 Subject: [PATCH 03/10] SetTLSFingerprint can uTLSConnApply --- client.go | 7 +++++++ client_test.go | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/client.go b/client.go index 172f4a4..571a139 100644 --- a/client.go +++ b/client.go @@ -1278,6 +1278,13 @@ func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID, uTLSConnApp c.Transport.SetTLSHandshake(fn) return c } +func (c *Client) SetTLSFingerprintSpec(clientHelloID *utls.ClientHelloSpec) *Client { + c.SetTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { + err := conn.ApplyPreset(clientHelloID) + return err + }) + return c +} // SetTLSHandshake set the custom tls handshake function, only valid for HTTP1 and HTTP2, not HTTP3, // it specifies an optional dial function for tls handshake, it works even if a proxy is set, can be diff --git a/client_test.go b/client_test.go index 6cb44ec..0402c1b 100644 --- a/client_test.go +++ b/client_test.go @@ -740,3 +740,14 @@ func TestUTLSConnApply(t *testing.T) { //println(string(bodyJson.Ja3NText)) tests.AssertEqual(t, true, bodyJson.Ja3NText == "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0") } +func TestSetTLSFingerprintSpec(t *testing.T) { + c1 := C() + tt, _ := utls.UTLSIdToSpec(utls.HelloQQ_Auto) + c1.SetTLSFingerprintSpec(&tt) + bodyJson := &struct { + Ja3NText string `json:"ja3n_text"` + }{} + _ = c1.Get("https://tls.browserleaks.com/json").Do().Into(bodyJson) + //println(string(bodyJson.Ja3NText)) + tests.AssertEqual(t, true, bodyJson.Ja3NText == "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0") +} From ddf9504d05540048afac22dcf4e3b0c36ff8b2cf Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:33:25 +0800 Subject: [PATCH 04/10] SetTLSFingerprint can uTLSConnApply --- client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client.go b/client.go index 571a139..89a687a 100644 --- a/client.go +++ b/client.go @@ -1278,6 +1278,10 @@ func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID, uTLSConnApp c.Transport.SetTLSHandshake(fn) return c } + +// SetTLSFingerprintSpec set the tls fingerprint for tls handshake, will use utls +// (https://github.com/refraction-networking/utls) to perform the tls handshake, +// which uses the specified clientHelloID to simulate the tls fingerprint. func (c *Client) SetTLSFingerprintSpec(clientHelloID *utls.ClientHelloSpec) *Client { c.SetTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { err := conn.ApplyPreset(clientHelloID) From d6dc3399d4933052f31789f0afa507567e927f33 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:40:21 +0800 Subject: [PATCH 05/10] SetTLSFingerprint can uTLSConnApply --- client.go | 12 ++++++++---- client_test.go | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/client.go b/client.go index 89a687a..21accd0 100644 --- a/client.go +++ b/client.go @@ -1224,7 +1224,11 @@ func (conn *uTLSConn) ConnectionState() tls.ConnectionState { // (https://github.com/refraction-networking/utls) to perform the tls handshake, // which uses the specified clientHelloID to simulate the tls fingerprint. // Note this is valid for HTTP1 and HTTP2, not HTTP3. -func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID, uTLSConnApply ...func(*uTLSConn) error) *Client { +func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID) *Client { + c.setTLSFingerprint(clientHelloID, nil) + return c +} +func (c *Client) setTLSFingerprint(clientHelloID utls.ClientHelloID, uTLSConnApply func(*uTLSConn) error) *Client { fn := func(ctx context.Context, addr string, plainConn net.Conn) (conn net.Conn, tlsState *tls.ConnectionState, err error) { colonPos := strings.LastIndex(addr, ":") if colonPos == -1 { @@ -1248,8 +1252,8 @@ func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID, uTLSConnApp KeyLogWriter: tlsConfig.KeyLogWriter, } uconn := &uTLSConn{utls.UClient(plainConn, utlsConfig, clientHelloID)} - for _, fnApply := range uTLSConnApply { - if err = fnApply(uconn); err != nil { + if uTLSConnApply != nil { + if err = uTLSConnApply(uconn); err != nil { return } } @@ -1283,7 +1287,7 @@ func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID, uTLSConnApp // (https://github.com/refraction-networking/utls) to perform the tls handshake, // which uses the specified clientHelloID to simulate the tls fingerprint. func (c *Client) SetTLSFingerprintSpec(clientHelloID *utls.ClientHelloSpec) *Client { - c.SetTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { + c.setTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { err := conn.ApplyPreset(clientHelloID) return err }) diff --git a/client_test.go b/client_test.go index 0402c1b..de3f000 100644 --- a/client_test.go +++ b/client_test.go @@ -724,7 +724,7 @@ func TestCloneCookieJar(t *testing.T) { func TestUTLSConnApply(t *testing.T) { c1 := C() - c1.SetTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { + c1.setTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { tt, _ := utls.UTLSIdToSpec(utls.HelloQQ_Auto) //"github.com/Danny-Dasilva/CycleTLS/cycletls" //cycletls.StringToSpec("771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0", false) From 8bdfc53b2caf5de23b9f23d3554305379acecd71 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:19:13 +0800 Subject: [PATCH 06/10] ADD JA3 --- client.go | 10 ++++++++++ client_test.go | 20 +++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 21accd0..a41548c 100644 --- a/client.go +++ b/client.go @@ -18,6 +18,7 @@ import ( "strings" "time" + "github.com/Danny-Dasilva/CycleTLS/cycletls" utls "github.com/refraction-networking/utls" "golang.org/x/net/publicsuffix" @@ -1294,6 +1295,15 @@ func (c *Client) SetTLSFingerprintSpec(clientHelloID *utls.ClientHelloSpec) *Cli return c } +func (c *Client) SetTLSFingerprintJA3(ja3 string, userAgent string, forceHTTP1 bool) *Client { + c.setTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { + clientHelloID, err := cycletls.StringToSpec(ja3, userAgent, forceHTTP1) + err = conn.ApplyPreset(clientHelloID) + return err + }) + return c +} + // SetTLSHandshake set the custom tls handshake function, only valid for HTTP1 and HTTP2, not HTTP3, // it specifies an optional dial function for tls handshake, it works even if a proxy is set, can be // used to customize the tls fingerprint. diff --git a/client_test.go b/client_test.go index de3f000..3eb31c3 100644 --- a/client_test.go +++ b/client_test.go @@ -17,6 +17,7 @@ import ( "testing" "time" + "github.com/Danny-Dasilva/CycleTLS/cycletls" "github.com/imroc/req/v3/internal/header" "github.com/imroc/req/v3/internal/tests" utls "github.com/refraction-networking/utls" @@ -725,10 +726,10 @@ func TestUTLSConnApply(t *testing.T) { c1 := C() c1.setTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { - tt, _ := utls.UTLSIdToSpec(utls.HelloQQ_Auto) + //tt, _ := utls.UTLSIdToSpec(utls.HelloQQ_Auto) //"github.com/Danny-Dasilva/CycleTLS/cycletls" - //cycletls.StringToSpec("771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0", false) - err := conn.ApplyPreset(&tt) + tt, _ := cycletls.StringToSpec("771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0", false) + err := conn.ApplyPreset(tt) return err }) @@ -751,3 +752,16 @@ func TestSetTLSFingerprintSpec(t *testing.T) { //println(string(bodyJson.Ja3NText)) tests.AssertEqual(t, true, bodyJson.Ja3NText == "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0") } +func TestSetTLSFingerprintJA3(t *testing.T) { + c1 := C() + ja3 := "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0" + c1.SetTLSFingerprintJA3(ja3, "", false) + bodyJson := &struct { + Ja3NText string `json:"ja3n_text"` + }{} + _ = c1.Get("https://tls.browserleaks.com/json").Do().Into(bodyJson) + + //println(string(bodyJson.Ja3NText)) + tests.AssertEqual(t, true, bodyJson.Ja3NText == ja3) + +} From 8f5798fc3f6ec2eed91c1c0fcde1136c210ce79e Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:19:25 +0800 Subject: [PATCH 07/10] ADD JA3 --- go.mod | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/go.mod b/go.mod index ae176c1..b6dddda 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,24 @@ require ( ) require ( + github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.30 // indirect + github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1 // indirect + github.com/gaukas/clienthellod v0.4.2 // indirect + github.com/gaukas/godicttls v0.0.4 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/onsi/ginkgo/v2 v2.23.4 // indirect + github.com/refraction-networking/uquic v0.0.6 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/mock v0.6.0 // indirect golang.org/x/crypto v0.44.0 // indirect + golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect + golang.org/x/tools v0.38.0 // indirect + h12.io/socks v1.0.3 // indirect ) From c54a1f342fea94b4ee04801e35e431602d47de32 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:16:11 +0800 Subject: [PATCH 08/10] ADD JA3 --- go.mod | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/go.mod b/go.mod index b6dddda..ad4e49f 100644 --- a/go.mod +++ b/go.mod @@ -1,38 +1,10 @@ module github.com/imroc/req/v3 go 1.24.0 - -require ( - github.com/andybalholm/brotli v1.2.0 - github.com/google/go-querystring v1.1.0 - github.com/icholy/digest v1.1.0 - github.com/klauspost/compress v1.18.1 - github.com/quic-go/qpack v0.5.1 - github.com/quic-go/quic-go v0.56.0 - github.com/refraction-networking/utls v1.8.1 - golang.org/x/net v0.47.0 - golang.org/x/text v0.31.0 -) - require ( github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.30 // indirect - github.com/Danny-Dasilva/fhttp v0.0.0-20240217042913-eeeb0b347ce1 // indirect - github.com/gaukas/clienthellod v0.4.2 // indirect - github.com/gaukas/godicttls v0.0.4 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect - github.com/gorilla/websocket v1.5.1 // indirect - github.com/onsi/ginkgo/v2 v2.23.4 // indirect - github.com/refraction-networking/uquic v0.0.6 // indirect - go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/mock v0.6.0 // indirect golang.org/x/crypto v0.44.0 // indirect - golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect - golang.org/x/tools v0.38.0 // indirect - h12.io/socks v1.0.3 // indirect ) From 268a007c817eb0491b2a2e661d4cfffec3561aa2 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:17:17 +0800 Subject: [PATCH 09/10] ADD JA3 --- go.mod | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/go.mod b/go.mod index ad4e49f..0122f18 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,21 @@ module github.com/imroc/req/v3 go 1.24.0 + require ( github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.30 // indirect + github.com/andybalholm/brotli v1.2.0 + github.com/google/go-querystring v1.1.0 + github.com/icholy/digest v1.1.0 + github.com/klauspost/compress v1.18.1 + github.com/quic-go/qpack v0.5.1 + github.com/quic-go/quic-go v0.56.0 + github.com/refraction-networking/utls v1.8.1 + golang.org/x/net v0.47.0 + golang.org/x/text v0.31.0 +) + +require ( github.com/google/go-cmp v0.7.0 // indirect go.uber.org/mock v0.6.0 // indirect golang.org/x/crypto v0.44.0 // indirect From 1ed6b7131efff8cb204aa3771111f28776b3d692 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+Shinku-Chen@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:48:39 +0800 Subject: [PATCH 10/10] ADD JA3 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0122f18..6c2b412 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/imroc/req/v3 go 1.24.0 require ( - github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.30 // indirect + github.com/Danny-Dasilva/CycleTLS/cycletls v1.0.30 github.com/andybalholm/brotli v1.2.0 github.com/google/go-querystring v1.1.0 github.com/icholy/digest v1.1.0