@@ -10,10 +10,10 @@ import (
1010 "net"
1111 "net/http"
1212 "strings"
13+ "sync"
1314 "sync/atomic"
1415 "testing"
1516
16- "github.com/elazarl/goproxy"
1717 fixtures "github.com/go-git/go-git-fixtures/v5"
1818 "github.com/stretchr/testify/assert"
1919 "github.com/stretchr/testify/require"
@@ -39,9 +39,7 @@ type ProxySuite struct {
3939func (s * ProxySuite ) TestAdvertisedReferencesHTTP () {
4040 var proxiedRequests int32
4141
42- proxy := goproxy .NewProxyHttpServer ()
43-
44- setupHTTPProxy (proxy , & proxiedRequests )
42+ proxy := newTestProxy (& proxiedRequests )
4543
4644 httpProxyAddr := setupProxyServer (s .T (), proxy , false , true )
4745
@@ -76,8 +74,7 @@ func (s *ProxySuite) TestAdvertisedReferencesHTTP() {
7674func (s * ProxySuite ) TestAdvertisedReferencesHTTPS () {
7775 var proxiedRequests int32
7876
79- proxy := goproxy .NewProxyHttpServer ()
80- setupHTTPSProxy (proxy , & proxiedRequests )
77+ proxy := newTestProxy (& proxiedRequests )
8178
8279 httpsProxyAddr := setupProxyServer (s .T (), proxy , true , true )
8380
@@ -179,37 +176,130 @@ func setupProxyServer(t testing.TB, handler http.Handler, isTLS, schemaAddr bool
179176 return httpProxyAddr
180177}
181178
182- func setupHTTPProxy (proxy * goproxy.ProxyHttpServer , proxiedRequests * int32 ) {
183- // The request is being forwarded to the local test git server in this handler.
184- var proxyHandler goproxy.FuncReqHandler = func (req * http.Request , _ * goproxy.ProxyCtx ) (* http.Request , * http.Response ) {
185- if strings .Contains (req .Host , "localhost" ) {
186- user , pass , _ := parseBasicAuth (req .Header .Get ("Proxy-Authorization" ))
187- if user != "user" || pass != "pass" {
188- return req , goproxy .NewResponse (req , goproxy .ContentTypeText , http .StatusUnauthorized , "" )
189- }
190- atomic .AddInt32 (proxiedRequests , 1 )
191- return req , nil
192- }
193- // Reject if it isn't our request.
194- return req , goproxy .NewResponse (req , goproxy .ContentTypeText , http .StatusForbidden , "" )
179+ // testProxy is a minimal HTTP/HTTPS proxy for testing.
180+ type testProxy struct {
181+ proxiedRequests * int32
182+ }
183+
184+ func newTestProxy (proxiedRequests * int32 ) * testProxy {
185+ return & testProxy {proxiedRequests : proxiedRequests }
186+ }
187+
188+ func (p * testProxy ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
189+ // Check proxy authentication
190+ user , pass , _ := parseBasicAuth (r .Header .Get ("Proxy-Authorization" ))
191+ if user != "user" || pass != "pass" {
192+ http .Error (w , "Proxy Authentication Required" , http .StatusProxyAuthRequired )
193+ return
194+ }
195+
196+ if r .Method == http .MethodConnect {
197+ // HTTPS proxy: handle CONNECT requests
198+ p .handleConnect (w , r )
199+ } else {
200+ // HTTP proxy: forward the request
201+ p .handleHTTP (w , r )
195202 }
196- proxy .OnRequest ().Do (proxyHandler )
197203}
198204
199- func setupHTTPSProxy (proxy * goproxy.ProxyHttpServer , proxiedRequests * int32 ) {
200- var proxyHandler goproxy.FuncHttpsHandler = func (host string , ctx * goproxy.ProxyCtx ) (* goproxy.ConnectAction , string ) {
201- if strings .Contains (host , "github.com" ) {
202- user , pass , _ := parseBasicAuth (ctx .Req .Header .Get ("Proxy-Authorization" ))
203- if user != "user" || pass != "pass" {
204- return goproxy .RejectConnect , host
205- }
206- atomic .AddInt32 (proxiedRequests , 1 )
207- return goproxy .OkConnect , host
205+ func (p * testProxy ) handleConnect (w http.ResponseWriter , r * http.Request ) {
206+ // Only allow connections to github.com for HTTPS tests
207+ if ! strings .Contains (r .Host , "github.com" ) && ! strings .Contains (r .Host , "localhost" ) {
208+ http .Error (w , "Forbidden" , http .StatusForbidden )
209+ return
210+ }
211+
212+ atomic .AddInt32 (p .proxiedRequests , 1 )
213+
214+ // Establish connection to the target
215+ targetConn , err := net .Dial ("tcp" , r .Host )
216+ if err != nil {
217+ http .Error (w , err .Error (), http .StatusServiceUnavailable )
218+ return
219+ }
220+
221+ // Hijack the connection first
222+ hijacker , ok := w .(http.Hijacker )
223+ if ! ok {
224+ http .Error (w , "Hijacking not supported" , http .StatusInternalServerError )
225+ targetConn .Close ()
226+ return
227+ }
228+
229+ clientConn , _ , err := hijacker .Hijack ()
230+ if err != nil {
231+ http .Error (w , err .Error (), http .StatusServiceUnavailable )
232+ targetConn .Close ()
233+ return
234+ }
235+
236+ // Send 200 Connection Established response manually after hijacking
237+ _ , err = clientConn .Write ([]byte ("HTTP/1.1 200 Connection Established\r \n \r \n " ))
238+ if err != nil {
239+ targetConn .Close ()
240+ clientConn .Close ()
241+ return
242+ }
243+
244+ // Tunnel data between client and target.
245+ // Use one goroutine for client->target, current goroutine for target->client.
246+ var wg sync.WaitGroup
247+ wg .Add (1 )
248+ go func () {
249+ defer wg .Done ()
250+ _ , _ = io .Copy (targetConn , clientConn )
251+ targetConn .Close () // Signal EOF to the other direction
252+ }()
253+
254+ _ , _ = io .Copy (clientConn , targetConn )
255+ clientConn .Close () // Signal EOF to the other direction
256+
257+ wg .Wait ()
258+ }
259+
260+ func (p * testProxy ) handleHTTP (w http.ResponseWriter , r * http.Request ) {
261+ // Only allow requests to localhost for HTTP tests
262+ if ! strings .Contains (r .Host , "localhost" ) {
263+ http .Error (w , "Forbidden" , http .StatusForbidden )
264+ return
265+ }
266+
267+ atomic .AddInt32 (p .proxiedRequests , 1 )
268+
269+ // Create a new request to the target
270+ outReq := r .Clone (r .Context ())
271+ outReq .RequestURI = ""
272+
273+ // Remove hop-by-hop headers
274+ hopHeaders := []string {
275+ "Connection" ,
276+ "Proxy-Connection" , // non-standard but still sent by some proxies
277+ "Keep-Alive" ,
278+ "Proxy-Authorization" ,
279+ "TE" ,
280+ "Trailer" ,
281+ "Transfer-Encoding" ,
282+ "Upgrade" ,
283+ }
284+ for _ , h := range hopHeaders {
285+ outReq .Header .Del (h )
286+ }
287+
288+ resp , err := http .DefaultTransport .RoundTrip (outReq )
289+ if err != nil {
290+ http .Error (w , err .Error (), http .StatusBadGateway )
291+ return
292+ }
293+ defer resp .Body .Close ()
294+
295+ // Copy response headers
296+ for key , values := range resp .Header {
297+ for _ , value := range values {
298+ w .Header ().Add (key , value )
208299 }
209- // Reject if it isn't our request.
210- return goproxy .RejectConnect , host
211300 }
212- proxy .OnRequest ().HandleConnect (proxyHandler )
301+ w .WriteHeader (resp .StatusCode )
302+ _ , _ = io .Copy (w , resp .Body )
213303}
214304
215305// adapted from https://github.com/golang/go/blob/2ef70d9d0f98832c8103a7968b195e560a8bb262/src/net/http/request.go#L959
0 commit comments