99 "net/http"
1010 "net/http/httptest"
1111 "net/url"
12+ "os"
13+ "path/filepath"
1214 "strings"
1315 "sync"
1416 "testing"
@@ -129,29 +131,57 @@ func startTargetServer(t *testing.T) *httptest.Server {
129131 return srv
130132}
131133
132- func TestWithProxyTransport_HTTPProxy (t * testing.T ) {
134+ func TestWithProxyTransport_UDSProxy (t * testing.T ) {
133135 target := startTargetServer (t )
136+ targetURL , _ := url .Parse (target .URL )
137+
138+ // Use /tmp directly because t.TempDir() paths on macOS could exceed
139+ // the 108-character limit for unix socket addresses.
140+ socketPath := filepath .Join ("/tmp" , fmt .Sprintf ("src-cli-test-%d.sock" , time .Now ().UnixNano ()))
141+ t .Cleanup (func () { os .Remove (socketPath ) })
142+ ln , err := net .Listen ("unix" , socketPath )
143+ if err != nil {
144+ t .Fatalf ("listen unix: %v" , err )
145+ }
146+ t .Cleanup (func () { ln .Close () })
134147
148+ // The UDS proxy path dials the unix socket directly (no CONNECT).
149+ // Simulate a forwarding proxy that copies bytes to the target.
135150 var mu sync.Mutex
136151 var used bool
137- var proto string
138152
139- proxyURL := startProxy (t , proxyOpts {
140- observe : func (r * http.Request ) {
153+ go func () {
154+ for {
155+ conn , err := ln .Accept ()
156+ if err != nil {
157+ return
158+ }
141159 mu .Lock ()
142- defer mu .Unlock ()
143160 used = true
144- proto = r .Proto
145- },
146- })
161+ mu .Unlock ()
162+ go func () {
163+ defer conn .Close ()
164+ dest , err := net .DialTimeout ("tcp" , targetURL .Host , 10 * time .Second )
165+ if err != nil {
166+ return
167+ }
168+ defer dest .Close ()
169+ var wg sync.WaitGroup
170+ wg .Add (2 )
171+ go func () { defer wg .Done (); io .Copy (dest , conn ) }()
172+ go func () { defer wg .Done (); io .Copy (conn , dest ) }()
173+ wg .Wait ()
174+ }()
175+ }
176+ }()
147177
148- transport := withProxyTransport (newTestTransport (), proxyURL , "" )
178+ transport := withProxyTransport (newTestTransport (), nil , socketPath )
149179 t .Cleanup (transport .CloseIdleConnections )
150180 client := & http.Client {Transport : transport , Timeout : 10 * time .Second }
151181
152182 resp , err := client .Get (target .URL )
153183 if err != nil {
154- t .Fatalf ("GET through http proxy: %v" , err )
184+ t .Fatalf ("GET through unix proxy: %v" , err )
155185 }
156186 defer resp .Body .Close ()
157187 body , err := io .ReadAll (resp .Body )
@@ -169,27 +199,21 @@ func TestWithProxyTransport_HTTPProxy(t *testing.T) {
169199 mu .Lock ()
170200 defer mu .Unlock ()
171201 if ! used {
172- t .Fatal ("proxy handler was never invoked" )
173- }
174- if proto != "HTTP/1.1" {
175- t .Errorf ("expected proxy to see HTTP/1.1 CONNECT, got %s" , proto )
202+ t .Fatal ("unix socket proxy was never used" )
176203 }
177204}
178205
179- func TestWithProxyTransport_HTTPSProxy (t * testing.T ) {
206+ func TestWithProxyTransport_HTTPProxy (t * testing.T ) {
180207 target := startTargetServer (t )
181208
182209 var mu sync.Mutex
183210 var used bool
184- var proto string
185211
186212 proxyURL := startProxy (t , proxyOpts {
187- useTLS : true ,
188213 observe : func (r * http.Request ) {
189214 mu .Lock ()
190215 defer mu .Unlock ()
191216 used = true
192- proto = r .Proto
193217 },
194218 })
195219
@@ -199,7 +223,7 @@ func TestWithProxyTransport_HTTPSProxy(t *testing.T) {
199223
200224 resp , err := client .Get (target .URL )
201225 if err != nil {
202- t .Fatalf ("GET through https proxy: %v" , err )
226+ t .Fatalf ("GET through http proxy: %v" , err )
203227 }
204228 defer resp .Body .Close ()
205229 body , err := io .ReadAll (resp .Body )
@@ -219,60 +243,22 @@ func TestWithProxyTransport_HTTPSProxy(t *testing.T) {
219243 if ! used {
220244 t .Fatal ("proxy handler was never invoked" )
221245 }
222- if proto != "HTTP/1.1" {
223- t .Errorf ("expected proxy to see HTTP/1.1 CONNECT, got %s" , proto )
224- }
225246}
226247
227- func TestWithProxyTransport_ProxyAuth (t * testing.T ) {
248+ func TestWithProxyTransport_HTTPSProxy (t * testing.T ) {
228249 target := startTargetServer (t )
229250
230- t .Run ("http proxy with auth" , func (t * testing.T ) {
231- proxyURL := startProxy (t , proxyOpts {username : "user" , password : "pass" })
232- transport := withProxyTransport (newTestTransport (), proxyURL , "" )
233- t .Cleanup (transport .CloseIdleConnections )
234- client := & http.Client {Transport : transport , Timeout : 10 * time .Second }
235-
236- resp , err := client .Get (target .URL )
237- if err != nil {
238- t .Fatalf ("GET through authenticated http proxy: %v" , err )
239- }
240- defer resp .Body .Close ()
241- if _ , err := io .ReadAll (resp .Body ); err != nil {
242- t .Fatalf ("read body: %v" , err )
243- }
244-
245- if resp .StatusCode != http .StatusOK {
246- t .Errorf ("expected 200, got %d" , resp .StatusCode )
247- }
248- })
249-
250- t .Run ("https proxy with auth" , func (t * testing.T ) {
251- proxyURL := startProxy (t , proxyOpts {useTLS : true , username : "user" , password : "s3cret" })
252- transport := withProxyTransport (newTestTransport (), proxyURL , "" )
253- t .Cleanup (transport .CloseIdleConnections )
254- client := & http.Client {Transport : transport , Timeout : 10 * time .Second }
255-
256- resp , err := client .Get (target .URL )
257- if err != nil {
258- t .Fatalf ("GET through authenticated https proxy: %v" , err )
259- }
260- defer resp .Body .Close ()
261- if _ , err := io .ReadAll (resp .Body ); err != nil {
262- t .Fatalf ("read body: %v" , err )
263- }
251+ var mu sync.Mutex
252+ var used bool
264253
265- if resp .StatusCode != http .StatusOK {
266- t .Errorf ("expected 200, got %d" , resp .StatusCode )
267- }
254+ proxyURL := startProxy (t , proxyOpts {
255+ useTLS : true ,
256+ observe : func (r * http.Request ) {
257+ mu .Lock ()
258+ defer mu .Unlock ()
259+ used = true
260+ },
268261 })
269- }
270-
271- func TestWithProxyTransport_HTTPSProxy_HTTP2ToOrigin (t * testing.T ) {
272- // Verify that when tunneling through an HTTPS proxy, the connection to
273- // the origin target still negotiates HTTP/2 (not downgraded to HTTP/1.1).
274- target := startTargetServer (t )
275- proxyURL := startProxy (t , proxyOpts {useTLS : true })
276262
277263 transport := withProxyTransport (newTestTransport (), proxyURL , "" )
278264 t .Cleanup (transport .CloseIdleConnections )
@@ -283,117 +269,21 @@ func TestWithProxyTransport_HTTPSProxy_HTTP2ToOrigin(t *testing.T) {
283269 t .Fatalf ("GET through https proxy: %v" , err )
284270 }
285271 defer resp .Body .Close ()
286- if _ , err := io .ReadAll (resp .Body ); err != nil {
287- t .Fatalf ("read body: %v" , err )
288- }
289-
290- if resp .ProtoMajor != 2 {
291- t .Errorf ("expected HTTP/2 to origin, got %s" , resp .Proto )
292- }
293- }
294-
295- func TestWithProxyTransport_HandshakeFailureClosesConn (t * testing.T ) {
296- // Verify that when the TLS handshake to the origin fails, the underlying
297- // tunnel connection is closed (regression test for tlsConn.Close on error).
298- //
299- // A plain TCP listener acts as the target. The proxy CONNECT succeeds
300- // (TCP-level), but the subsequent TLS handshake fails because the target
301- // is not a TLS server. If handshakeTLS properly closes tlsConn on failure,
302- // the tunnel tears down and the target sees the connection close.
303- connClosed := make (chan struct {})
304- ln , err := net .Listen ("tcp" , "127.0.0.1:0" )
272+ body , err := io .ReadAll (resp .Body )
305273 if err != nil {
306- t .Fatalf ("listen: %v" , err )
307- }
308- defer ln .Close ()
309-
310- go func () {
311- conn , err := ln .Accept ()
312- if err != nil {
313- return
314- }
315- defer conn .Close ()
316- // Send non-TLS bytes so the client handshake fails immediately
317- // rather than waiting for a timeout.
318- conn .Write ([]byte ("not-tls\n " ))
319- // Drain until the remote side closes the tunnel.
320- io .Copy (io .Discard , conn )
321- close (connClosed )
322- }()
323-
324- proxyURL := startProxy (t , proxyOpts {useTLS : true })
325- transport := withProxyTransport (newTestTransport (), proxyURL , "" )
326- t .Cleanup (transport .CloseIdleConnections )
327- client := & http.Client {Transport : transport , Timeout : 5 * time .Second }
328-
329- _ , err = client .Get ("https://" + ln .Addr ().String ())
330- if err == nil {
331- t .Fatal ("expected TLS handshake error, got nil" )
274+ t .Fatalf ("read body: %v" , err )
332275 }
333276
334- select {
335- case <- connClosed :
336- // Connection was properly cleaned up.
337- case <- time .After (5 * time .Second ):
338- t .Fatal ("connection was not closed after TLS handshake failure" )
277+ if resp .StatusCode != http .StatusOK {
278+ t .Errorf ("expected 200, got %d" , resp .StatusCode )
339279 }
340- }
341-
342- func TestWithProxyTransport_ProxyRejectsConnect (t * testing.T ) {
343- tests := []struct {
344- name string
345- statusCode int
346- body string
347- wantErr string
348- }{
349- {"407 proxy auth required" , http .StatusProxyAuthRequired , "proxy auth required" , "Proxy Authentication Required" },
350- {"403 forbidden" , http .StatusForbidden , "access denied by policy" , "Forbidden" },
351- {"502 bad gateway" , http .StatusBadGateway , "upstream unreachable" , "Bad Gateway" },
280+ if got := strings .TrimSpace (string (body )); got != "ok" {
281+ t .Errorf ("expected body 'ok', got %q" , got )
352282 }
353283
354- // Use a local target so we never depend on external DNS.
355- target := startTargetServer (t )
356-
357- for _ , tt := range tests {
358- t .Run ("http proxy/" + tt .name , func (t * testing.T ) {
359- srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
360- http .Error (w , tt .body , tt .statusCode )
361- }))
362- t .Cleanup (srv .Close )
363-
364- proxyURL , _ := url .Parse (srv .URL )
365- transport := withProxyTransport (newTestTransport (), proxyURL , "" )
366- t .Cleanup (transport .CloseIdleConnections )
367- client := & http.Client {Transport : transport , Timeout : 10 * time .Second }
368-
369- _ , err := client .Get (target .URL )
370- if err == nil {
371- t .Fatal ("expected error, got nil" )
372- }
373- if ! strings .Contains (err .Error (), tt .wantErr ) {
374- t .Errorf ("error should contain %q, got: %v" , tt .wantErr , err )
375- }
376- })
377-
378- t .Run ("https proxy/" + tt .name , func (t * testing.T ) {
379- srv := httptest .NewUnstartedServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
380- http .Error (w , tt .body , tt .statusCode )
381- }))
382- srv .StartTLS ()
383- t .Cleanup (srv .Close )
384-
385- proxyURL , _ := url .Parse (srv .URL )
386- transport := withProxyTransport (newTestTransport (), proxyURL , "" )
387- t .Cleanup (transport .CloseIdleConnections )
388- client := & http.Client {Transport : transport , Timeout : 10 * time .Second }
389-
390- _ , err := client .Get (target .URL )
391- if err == nil {
392- t .Fatal ("expected error, got nil" )
393- }
394- if ! strings .Contains (err .Error (), tt .wantErr ) {
395- t .Errorf ("error should contain %q, got: %v" , tt .wantErr , err )
396- }
397- })
284+ mu .Lock ()
285+ defer mu .Unlock ()
286+ if ! used {
287+ t .Fatal ("proxy handler was never invoked" )
398288 }
399289}
0 commit comments