1/* 2 * Copyright (c) 2014, Yawning Angel <yawning at schwanenlied dot me> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * * Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 11 * * Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 * POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28package main 29 30import ( 31 "bufio" 32 "encoding/base64" 33 "fmt" 34 "net" 35 "net/http" 36 "net/http/httputil" 37 "net/url" 38 "time" 39 40 "golang.org/x/net/proxy" 41) 42 43// httpProxy is a HTTP connect proxy. 44type httpProxy struct { 45 hostPort string 46 haveAuth bool 47 username string 48 password string 49 forward proxy.Dialer 50} 51 52func newHTTP(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { 53 s := new(httpProxy) 54 s.hostPort = uri.Host 55 s.forward = forward 56 if uri.User != nil { 57 s.haveAuth = true 58 s.username = uri.User.Username() 59 s.password, _ = uri.User.Password() 60 } 61 62 return s, nil 63} 64 65func (s *httpProxy) Dial(network, addr string) (net.Conn, error) { 66 // Dial and create the http client connection. 67 c, err := s.forward.Dial("tcp", s.hostPort) 68 if err != nil { 69 return nil, err 70 } 71 conn := new(httpConn) 72 conn.httpConn = httputil.NewClientConn(c, nil) // nolint: staticcheck 73 conn.remoteAddr, err = net.ResolveTCPAddr(network, addr) 74 if err != nil { 75 conn.httpConn.Close() 76 return nil, err 77 } 78 79 // HACK HACK HACK HACK. http.ReadRequest also does this. 80 reqURL, err := url.Parse("http://" + addr) 81 if err != nil { 82 conn.httpConn.Close() 83 return nil, err 84 } 85 reqURL.Scheme = "" 86 87 req, err := http.NewRequest("CONNECT", reqURL.String(), nil) 88 if err != nil { 89 conn.httpConn.Close() 90 return nil, err 91 } 92 req.Close = false 93 if s.haveAuth { 94 // SetBasicAuth doesn't quite do what is appropriate, because 95 // the correct header is `Proxy-Authorization`. 96 req.Header.Set("Proxy-Authorization", "Basic " + base64.StdEncoding.EncodeToString([]byte(s.username+":"+s.password))) 97 } 98 req.Header.Set("User-Agent", "") 99 100 resp, err := conn.httpConn.Do(req) 101 if err != nil && err != httputil.ErrPersistEOF { // nolint: staticcheck 102 conn.httpConn.Close() 103 return nil, err 104 } 105 if resp.StatusCode != 200 { 106 conn.httpConn.Close() 107 return nil, fmt.Errorf("proxy error: %s", resp.Status) 108 } 109 110 conn.hijackedConn, conn.staleReader = conn.httpConn.Hijack() 111 return conn, nil 112} 113 114type httpConn struct { 115 remoteAddr *net.TCPAddr 116 httpConn *httputil.ClientConn // nolint: staticcheck 117 hijackedConn net.Conn 118 staleReader *bufio.Reader 119} 120 121func (c *httpConn) Read(b []byte) (int, error) { 122 if c.staleReader != nil { 123 if c.staleReader.Buffered() > 0 { 124 return c.staleReader.Read(b) 125 } 126 c.staleReader = nil 127 } 128 return c.hijackedConn.Read(b) 129} 130 131func (c *httpConn) Write(b []byte) (int, error) { 132 return c.hijackedConn.Write(b) 133} 134 135func (c *httpConn) Close() error { 136 return c.hijackedConn.Close() 137} 138 139func (c *httpConn) LocalAddr() net.Addr { 140 return c.hijackedConn.LocalAddr() 141} 142 143func (c *httpConn) RemoteAddr() net.Addr { 144 return c.remoteAddr 145} 146 147func (c *httpConn) SetDeadline(t time.Time) error { 148 return c.hijackedConn.SetDeadline(t) 149} 150 151func (c *httpConn) SetReadDeadline(t time.Time) error { 152 return c.hijackedConn.SetReadDeadline(t) 153} 154 155func (c *httpConn) SetWriteDeadline(t time.Time) error { 156 return c.hijackedConn.SetWriteDeadline(t) 157} 158 159func init() { 160 proxy.RegisterDialerType("http", newHTTP) 161} 162