]> Cypherpunks repositories - gostls13.git/commitdiff
http: add proxy support
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Wed, 16 Feb 2011 19:06:50 +0000 (14:06 -0500)
committerAdam Langley <agl@golang.org>
Wed, 16 Feb 2011 19:06:50 +0000 (14:06 -0500)
Fixes #53.

R=agl1, jacek.masiulaniec, adg, rsc, agl
CC=golang-dev
https://golang.org/cl/3794041

src/pkg/http/client.go
src/pkg/http/proxy_test.go [new file with mode: 0644]

index 022f4f124a86ed9e3ff4013631590e5909735e2c..ae37879ae984612953f8b437661e59e2c3510eb8 100644 (file)
@@ -31,6 +31,40 @@ type readClose struct {
        io.Closer
 }
 
+// matchNoProxy returns true if requests to addr should not use a proxy,
+// according to the NO_PROXY or no_proxy environment variable.
+func matchNoProxy(addr string) bool {
+       if len(addr) == 0 {
+               return false
+       }
+       no_proxy := os.Getenv("NO_PROXY")
+       if len(no_proxy) == 0 {
+               no_proxy = os.Getenv("no_proxy")
+       }
+       if no_proxy == "*" {
+               return true
+       }
+
+       addr = strings.ToLower(strings.TrimSpace(addr))
+       if hasPort(addr) {
+               addr = addr[:strings.LastIndex(addr, ":")]
+       }
+
+       for _, p := range strings.Split(no_proxy, ",", -1) {
+               p = strings.ToLower(strings.TrimSpace(p))
+               if len(p) == 0 {
+                       continue
+               }
+               if hasPort(p) {
+                       p = p[:strings.LastIndex(p, ":")]
+               }
+               if addr == p || (p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:])) {
+                       return true
+               }
+       }
+       return false
+}
+
 // Send issues an HTTP request.  Caller should close resp.Body when done reading it.
 //
 // TODO: support persistent connections (multiple requests on a single connection).
@@ -56,22 +90,81 @@ func send(req *Request) (resp *Response, err os.Error) {
                req.Header["Authorization"] = "Basic " + string(encoded)
        }
 
-       var conn io.ReadWriteCloser
-       if req.URL.Scheme == "http" {
-               conn, err = net.Dial("tcp", "", addr)
+       var proxyURL *URL
+       proxyAuth := ""
+       proxy := os.Getenv("HTTP_PROXY")
+       if proxy == "" {
+               proxy = os.Getenv("http_proxy")
+       }
+       if matchNoProxy(addr) {
+               proxy = ""
+       }
+
+       if proxy != "" {
+               proxyURL, err = ParseURL(proxy)
                if err != nil {
-                       return nil, err
+                       return nil, os.ErrorString("invalid proxy address")
+               }
+               addr = proxyURL.Host
+               proxyInfo := proxyURL.RawUserinfo
+               if proxyInfo != "" {
+                       enc := base64.URLEncoding
+                       encoded := make([]byte, enc.EncodedLen(len(proxyInfo)))
+                       enc.Encode(encoded, []byte(proxyInfo))
+                       proxyAuth = "Basic " + string(encoded)
+               }
+       }
+
+       // Connect to server or proxy.
+       conn, err := net.Dial("tcp", "", addr)
+       if err != nil {
+               return nil, err
+       }
+
+       if req.URL.Scheme == "http" {
+               // Include proxy http header if needed.
+               if proxyAuth != "" {
+                       req.Header["Proxy-Authorization"] = proxyAuth
                }
        } else { // https
-               conn, err = tls.Dial("tcp", "", addr, nil)
-               if err != nil {
+               if proxyURL != nil {
+                       // Ask proxy for direct connection to server.
+                       // addr defaults above to ":https" but we need to use numbers
+                       addr = req.URL.Host
+                       if !hasPort(addr) {
+                               addr += ":443"
+                       }
+                       fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\n", addr)
+                       fmt.Fprintf(conn, "Host: %s\r\n", addr)
+                       if proxyAuth != "" {
+                               fmt.Fprintf(conn, "Proxy-Authorization: %s\r\n", proxyAuth)
+                       }
+                       fmt.Fprintf(conn, "\r\n")
+
+                       // Read response.
+                       // Okay to use and discard buffered reader here, because
+                       // TLS server will not speak until spoken to.
+                       br := bufio.NewReader(conn)
+                       resp, err := ReadResponse(br, "CONNECT")
+                       if err != nil {
+                               return nil, err
+                       }
+                       if resp.StatusCode != 200 {
+                               f := strings.Split(resp.Status, " ", 2)
+                               return nil, os.ErrorString(f[1])
+                       }
+               }
+
+               // Initiate TLS and check remote host name against certificate.
+               conn = tls.Client(conn, nil)
+               if err = conn.(*tls.Conn).Handshake(); err != nil {
                        return nil, err
                }
                h := req.URL.Host
                if hasPort(h) {
-                       h = h[0:strings.LastIndex(h, ":")]
+                       h = h[:strings.LastIndex(h, ":")]
                }
-               if err := conn.(*tls.Conn).VerifyHostname(h); err != nil {
+               if err = conn.(*tls.Conn).VerifyHostname(h); err != nil {
                        return nil, err
                }
        }
diff --git a/src/pkg/http/proxy_test.go b/src/pkg/http/proxy_test.go
new file mode 100644 (file)
index 0000000..0f2ca45
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package http
+
+import (
+       "os"
+       "testing"
+)
+
+// TODO(mattn):
+//     test ProxyAuth
+
+var MatchNoProxyTests = []struct {
+       host  string
+       match bool
+}{
+       {"localhost", true},        // match completely
+       {"barbaz.net", true},       // match as .barbaz.net
+       {"foobar.com:443", true},   // have a port but match 
+       {"foofoobar.com", false},   // not match as a part of foobar.com
+       {"baz.com", false},         // not match as a part of barbaz.com
+       {"localhost.net", false},   // not match as suffix of address
+       {"local.localhost", false}, // not match as prefix as address
+       {"barbarbaz.net", false},   // not match because NO_PROXY have a '.'
+       {"www.foobar.com", false},  // not match because NO_PROXY is not .foobar.com
+}
+
+func TestMatchNoProxy(t *testing.T) {
+       oldenv := os.Getenv("NO_PROXY")
+       no_proxy := "foobar.com, .barbaz.net   , localhost"
+       os.Setenv("NO_PROXY", no_proxy)
+       defer os.Setenv("NO_PROXY", oldenv)
+
+       for _, test := range MatchNoProxyTests {
+               if matchNoProxy(test.host) != test.match {
+                       if test.match {
+                               t.Errorf("matchNoProxy(%v) = %v, want %v", test.host, !test.match, test.match)
+                       } else {
+                               t.Errorf("not expected: '%s' shouldn't match as '%s'", test.host, no_proxy)
+                       }
+               }
+       }
+}