ソースを参照

Fix: HTTP proxy should not follow redirects

* URL proxy uses http.Client in order to handle
  cookies and redirects.
* HTTP proxy now reverted to simply use http.Transport,
  as using http.Client in this case was causing
  browsing issues (a standard proxy shouldn't follow
  redirects).
Rod Hynes 10 年 前
コミット
c0584893ec
1 ファイル変更55 行追加26 行削除
  1. 55 26
      psiphon/httpProxy.go

+ 55 - 26
psiphon/httpProxy.go

@@ -56,10 +56,11 @@ type HttpProxy struct {
 	tunneler               Tunneler
 	tunneler               Tunneler
 	listener               net.Listener
 	listener               net.Listener
 	serveWaitGroup         *sync.WaitGroup
 	serveWaitGroup         *sync.WaitGroup
-	httpTunneledRelay      *http.Transport
-	httpTunneledClient     *http.Client
-	httpDirectRelay        *http.Transport
-	httpDirectClient       *http.Client
+	httpProxyTunneledRelay *http.Transport
+	urlProxyTunneledRelay  *http.Transport
+	urlProxyTunneledClient *http.Client
+	urlProxyDirectRelay    *http.Transport
+	urlProxyDirectClient   *http.Client
 	openConns              *Conns
 	openConns              *Conns
 	stopListeningBroadcast chan struct{}
 	stopListeningBroadcast chan struct{}
 }
 }
@@ -86,29 +87,42 @@ func NewHttpProxy(
 		// TODO: connect timeout?
 		// TODO: connect timeout?
 		return tunneler.Dial(addr, false, nil)
 		return tunneler.Dial(addr, false, nil)
 	}
 	}
-	httpTunneledRelay := &http.Transport{
+	directDialer := func(_, addr string) (conn net.Conn, err error) {
+		return DialTCP(addr, untunneledDialConfig)
+	}
+
+	// TODO: could HTTP proxy share a tunneled transort with URL proxy?
+	// For now, keeping them distinct just to be conservative.
+	httpProxyTunneledRelay := &http.Transport{
 		Dial:                  tunneledDialer,
 		Dial:                  tunneledDialer,
 		MaxIdleConnsPerHost:   HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST,
 		MaxIdleConnsPerHost:   HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST,
 		ResponseHeaderTimeout: HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
 		ResponseHeaderTimeout: HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
 	}
 	}
-	httpTunneledClient := &http.Client{
-		Transport: httpTunneledRelay,
+
+	// Note: URL proxy relays use http.Client for upstream requests, so
+	// redirects will be followed. HTTP proxy should not follow redirects
+	// and simply uses http.Transport directly.
+
+	urlProxyTunneledRelay := &http.Transport{
+		Dial:                  tunneledDialer,
+		MaxIdleConnsPerHost:   HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST,
+		ResponseHeaderTimeout: HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
+	}
+	urlProxyTunneledClient := &http.Client{
+		Transport: urlProxyTunneledRelay,
 		Jar:       nil, // TODO: cookie support for URL proxy?
 		Jar:       nil, // TODO: cookie support for URL proxy?
 
 
 		// Note: don't use this timeout -- it interrupts downloads of large response bodies
 		// Note: don't use this timeout -- it interrupts downloads of large response bodies
 		//Timeout:   HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
 		//Timeout:   HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
 	}
 	}
 
 
-	directDialer := func(_, addr string) (conn net.Conn, err error) {
-		return DialTCP(addr, untunneledDialConfig)
-	}
-	httpDirectRelay := &http.Transport{
+	urlProxyDirectRelay := &http.Transport{
 		Dial:                  directDialer,
 		Dial:                  directDialer,
 		MaxIdleConnsPerHost:   HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST,
 		MaxIdleConnsPerHost:   HTTP_PROXY_MAX_IDLE_CONNECTIONS_PER_HOST,
 		ResponseHeaderTimeout: HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
 		ResponseHeaderTimeout: HTTP_PROXY_ORIGIN_SERVER_TIMEOUT,
 	}
 	}
-	httpDirectClient := &http.Client{
-		Transport: httpDirectRelay,
+	urlProxyDirectClient := &http.Client{
+		Transport: urlProxyDirectRelay,
 		Jar:       nil,
 		Jar:       nil,
 	}
 	}
 
 
@@ -116,10 +130,11 @@ func NewHttpProxy(
 		tunneler:               tunneler,
 		tunneler:               tunneler,
 		listener:               listener,
 		listener:               listener,
 		serveWaitGroup:         new(sync.WaitGroup),
 		serveWaitGroup:         new(sync.WaitGroup),
-		httpTunneledRelay:      httpTunneledRelay,
-		httpTunneledClient:     httpTunneledClient,
-		httpDirectRelay:        httpDirectRelay,
-		httpDirectClient:       httpDirectClient,
+		httpProxyTunneledRelay: httpProxyTunneledRelay,
+		urlProxyTunneledRelay:  urlProxyTunneledRelay,
+		urlProxyTunneledClient: urlProxyTunneledClient,
+		urlProxyDirectRelay:    urlProxyDirectRelay,
+		urlProxyDirectClient:   urlProxyDirectClient,
 		openConns:              new(Conns),
 		openConns:              new(Conns),
 		stopListeningBroadcast: make(chan struct{}),
 		stopListeningBroadcast: make(chan struct{}),
 	}
 	}
@@ -138,8 +153,9 @@ func (proxy *HttpProxy) Close() {
 	proxy.openConns.CloseAll()
 	proxy.openConns.CloseAll()
 	// Close idle proxy->origin persistent connections
 	// Close idle proxy->origin persistent connections
 	// TODO: also close active connections
 	// TODO: also close active connections
-	proxy.httpTunneledRelay.CloseIdleConnections()
-	proxy.httpDirectRelay.CloseIdleConnections()
+	proxy.httpProxyTunneledRelay.CloseIdleConnections()
+	proxy.urlProxyTunneledRelay.CloseIdleConnections()
+	proxy.urlProxyDirectRelay.CloseIdleConnections()
 }
 }
 
 
 // ServeHTTP receives HTTP requests and proxies them. CONNECT requests
 // ServeHTTP receives HTTP requests and proxies them. CONNECT requests
@@ -201,7 +217,7 @@ func (proxy *HttpProxy) httpConnectHandler(localConn net.Conn, target string) (e
 }
 }
 
 
 func (proxy *HttpProxy) httpProxyHandler(responseWriter http.ResponseWriter, request *http.Request) {
 func (proxy *HttpProxy) httpProxyHandler(responseWriter http.ResponseWriter, request *http.Request) {
-	relayHttpRequest(proxy.httpTunneledClient, request, responseWriter)
+	relayHttpRequest(nil, proxy.httpProxyTunneledRelay, request, responseWriter)
 }
 }
 
 
 const (
 const (
@@ -220,10 +236,10 @@ func (proxy *HttpProxy) urlProxyHandler(responseWriter http.ResponseWriter, requ
 	switch {
 	switch {
 	case strings.HasPrefix(request.URL.Path, URL_PROXY_TUNNELED_REQUEST_PATH):
 	case strings.HasPrefix(request.URL.Path, URL_PROXY_TUNNELED_REQUEST_PATH):
 		originUrl, err = url.QueryUnescape(request.URL.Path[len(URL_PROXY_TUNNELED_REQUEST_PATH):])
 		originUrl, err = url.QueryUnescape(request.URL.Path[len(URL_PROXY_TUNNELED_REQUEST_PATH):])
-		client = proxy.httpTunneledClient
+		client = proxy.urlProxyTunneledClient
 	case strings.HasPrefix(request.URL.Path, URL_PROXY_DIRECT_REQUEST_PATH):
 	case strings.HasPrefix(request.URL.Path, URL_PROXY_DIRECT_REQUEST_PATH):
 		originUrl, err = url.QueryUnescape(request.URL.Path[len(URL_PROXY_DIRECT_REQUEST_PATH):])
 		originUrl, err = url.QueryUnescape(request.URL.Path[len(URL_PROXY_DIRECT_REQUEST_PATH):])
-		client = proxy.httpDirectClient
+		client = proxy.urlProxyDirectClient
 	default:
 	default:
 		err = errors.New("missing origin URL")
 		err = errors.New("missing origin URL")
 	}
 	}
@@ -250,10 +266,14 @@ func (proxy *HttpProxy) urlProxyHandler(responseWriter http.ResponseWriter, requ
 	request.Host = url.Host
 	request.Host = url.Host
 	request.URL = url
 	request.URL = url
 
 
-	relayHttpRequest(client, request, responseWriter)
+	relayHttpRequest(client, nil, request, responseWriter)
 }
 }
 
 
-func relayHttpRequest(client *http.Client, request *http.Request, responseWriter http.ResponseWriter) {
+func relayHttpRequest(
+	client *http.Client,
+	transport *http.Transport,
+	request *http.Request,
+	responseWriter http.ResponseWriter) {
 
 
 	// Transform received request struct before using as input to relayed request
 	// Transform received request struct before using as input to relayed request
 	request.Close = false
 	request.Close = false
@@ -262,8 +282,17 @@ func relayHttpRequest(client *http.Client, request *http.Request, responseWriter
 		request.Header.Del(key)
 		request.Header.Del(key)
 	}
 	}
 
 
-	// Relay the HTTP request and get the response
-	response, err := client.Do(request)
+	// Relay the HTTP request and get the response. Use a client when supplied,
+	// otherwise a transport. A client handles cookies and redirects, and a
+	// transport does not.
+	var response *http.Response
+	var err error
+	if client != nil {
+		response, err = client.Do(request)
+	} else {
+		response, err = transport.RoundTrip(request)
+	}
+
 	if err != nil {
 	if err != nil {
 		NoticeAlert("%s", ContextError(FilterUrlError(err)))
 		NoticeAlert("%s", ContextError(FilterUrlError(err)))
 		forceClose(responseWriter)
 		forceClose(responseWriter)