├── .gitignore ├── AUTHORS ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── ca_cert.pem ├── ca_key.pem ├── casigner.go ├── connresponsewriter.go ├── context.go ├── error.go ├── examples ├── go-httpproxy-demo │ ├── .gitignore │ └── main.go └── go-httpproxy-simple │ ├── .gitignore │ └── main.go ├── httpproxy.go ├── proxy.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.old 3 | *~ 4 | *.cache 5 | *.swap 6 | *.swp 7 | *.temp 8 | *.tmp 9 | 10 | *.o 11 | *.a 12 | *.so 13 | *.exe 14 | *.dll 15 | *.py[co] 16 | 17 | .DS_Store 18 | 19 | *.project 20 | *.includepath 21 | *.settings 22 | *.sublime-project 23 | *.sublime-workspace 24 | *.idea 25 | *.vscode 26 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | 4 | # Names should be added to this file as one of 5 | # Organization's name 6 | # Individual's name 7 | # Individual's name 8 | 9 | # Please keep the list sorted. 10 | 11 | Orkun Karaduman 12 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who can contribute 2 | # (and typically have contributed) code to this repository. 3 | # The AUTHORS file lists the copyright holders; this file 4 | # lists people. 5 | 6 | # Names should be added to this file like so: 7 | # Individual's name 8 | # Individual's name 9 | 10 | # Please keep the list sorted. 11 | 12 | Orkun Karaduman 13 | Yasin Özel 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, "httpproxy" authors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the "httpproxy" authors nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go HTTP proxy server library 2 | 3 | [![GoDoc](https://godoc.org/github.com/go-httpproxy/httpproxy?status.svg)](https://godoc.org/github.com/go-httpproxy/httpproxy) 4 | 5 | Package httpproxy provides a customizable HTTP proxy; supports HTTP, HTTPS through 6 | CONNECT. And also provides HTTPS connection using "Man in the Middle" style 7 | attack. 8 | 9 | It's easy to use. `httpproxy.Proxy` implements `Handler` interface of `net/http` 10 | package to offer `http.ListenAndServe` function. 11 | 12 | ## Installing 13 | 14 | ```sh 15 | go get -u github.com/go-httpproxy/httpproxy 16 | # or 17 | go get -u gopkg.in/httpproxy.v1 18 | ``` 19 | 20 | ## Usage 21 | 22 | Library has two significant structs: Proxy and Context. 23 | 24 | ### Proxy struct 25 | 26 | ```go 27 | // Proxy defines parameters for running an HTTP Proxy. It implements 28 | // http.Handler interface for ListenAndServe function. If you need, you must 29 | // set Proxy struct before handling requests. 30 | type Proxy struct { 31 | // Session number of last proxy request. 32 | SessionNo int64 33 | 34 | // RoundTripper interface to obtain remote response. 35 | // By default, it uses &http.Transport{}. 36 | Rt http.RoundTripper 37 | 38 | // Certificate key pair. 39 | Ca tls.Certificate 40 | 41 | // User data to use free. 42 | UserData interface{} 43 | 44 | // Error callback. 45 | OnError func(ctx *Context, where string, err *Error, opErr error) 46 | 47 | // Accept callback. It greets proxy request like ServeHTTP function of 48 | // http.Handler. 49 | // If it returns true, stops processing proxy request. 50 | OnAccept func(ctx *Context, w http.ResponseWriter, r *http.Request) bool 51 | 52 | // Auth callback. If you need authentication, set this callback. 53 | // If it returns true, authentication succeeded. 54 | OnAuth func(ctx *Context, authType string, user string, pass string) bool 55 | 56 | // Connect callback. It sets connect action and new host. 57 | // If len(newhost) > 0, host changes. 58 | OnConnect func(ctx *Context, host string) (ConnectAction ConnectAction, 59 | newHost string) 60 | 61 | // Request callback. It greets remote request. 62 | // If it returns non-nil response, stops processing remote request. 63 | OnRequest func(ctx *Context, req *http.Request) (resp *http.Response) 64 | 65 | // Response callback. It greets remote response. 66 | // Remote response sends after this callback. 67 | OnResponse func(ctx *Context, req *http.Request, resp *http.Response) 68 | 69 | // If ConnectAction is ConnectMitm, it sets chunked to Transfer-Encoding. 70 | // By default, true. 71 | MitmChunked bool 72 | 73 | // HTTP Authentication type. If it's not specified (""), uses "Basic". 74 | // By default, "". 75 | AuthType string 76 | } 77 | ``` 78 | 79 | ### Context struct 80 | 81 | ```go 82 | // Context keeps context of each proxy request. 83 | type Context struct { 84 | // Pointer of Proxy struct handled this context. 85 | // It's using internally. Don't change in Context struct! 86 | Prx *Proxy 87 | 88 | // Session number of this context obtained from Proxy struct. 89 | SessionNo int64 90 | 91 | // Sub session number of processing remote connection. 92 | SubSessionNo int64 93 | 94 | // Original Proxy request. 95 | // It's using internally. Don't change in Context struct! 96 | Req *http.Request 97 | 98 | // Original Proxy request, if proxy request method is CONNECT. 99 | // It's using internally. Don't change in Context struct! 100 | ConnectReq *http.Request 101 | 102 | // Action of after the CONNECT, if proxy request method is CONNECT. 103 | // It's using internally. Don't change in Context struct! 104 | ConnectAction ConnectAction 105 | 106 | // Remote host, if proxy request method is CONNECT. 107 | // It's using internally. Don't change in Context struct! 108 | ConnectHost string 109 | 110 | // User data to use free. 111 | UserData interface{} 112 | } 113 | ``` 114 | 115 | ## Examples 116 | 117 | For more examples, examples/ 118 | 119 | ### examples/go-httpproxy-simple 120 | 121 | ```go 122 | package main 123 | 124 | import ( 125 | "log" 126 | "net/http" 127 | 128 | "github.com/go-httpproxy/httpproxy" 129 | ) 130 | 131 | func OnError(ctx *httpproxy.Context, where string, 132 | err *httpproxy.Error, opErr error) { 133 | // Log errors. 134 | log.Printf("ERR: %s: %s [%s]", where, err, opErr) 135 | } 136 | 137 | func OnAccept(ctx *httpproxy.Context, w http.ResponseWriter, 138 | r *http.Request) bool { 139 | // Handle local request has path "/info" 140 | if r.Method == "GET" && !r.URL.IsAbs() && r.URL.Path == "/info" { 141 | w.Write([]byte("This is go-httpproxy.")) 142 | return true 143 | } 144 | return false 145 | } 146 | 147 | func OnAuth(ctx *httpproxy.Context, authType string, user string, pass string) bool { 148 | // Auth test user. 149 | if user == "test" && pass == "1234" { 150 | return true 151 | } 152 | return false 153 | } 154 | 155 | func OnConnect(ctx *httpproxy.Context, host string) ( 156 | ConnectAction httpproxy.ConnectAction, newHost string) { 157 | // Apply "Man in the Middle" to all ssl connections. Never change host. 158 | return httpproxy.ConnectMitm, host 159 | } 160 | 161 | func OnRequest(ctx *httpproxy.Context, req *http.Request) ( 162 | resp *http.Response) { 163 | // Log proxying requests. 164 | log.Printf("INFO: Proxy: %s %s", req.Method, req.URL.String()) 165 | return 166 | } 167 | 168 | func OnResponse(ctx *httpproxy.Context, req *http.Request, 169 | resp *http.Response) { 170 | // Add header "Via: go-httpproxy". 171 | resp.Header.Add("Via", "go-httpproxy") 172 | } 173 | 174 | func main() { 175 | // Create a new proxy with default certificate pair. 176 | prx, _ := httpproxy.NewProxy() 177 | 178 | // Set handlers. 179 | prx.OnError = OnError 180 | prx.OnAccept = OnAccept 181 | prx.OnAuth = OnAuth 182 | prx.OnConnect = OnConnect 183 | prx.OnRequest = OnRequest 184 | prx.OnResponse = OnResponse 185 | 186 | // Listen... 187 | http.ListenAndServe(":8080", prx) 188 | } 189 | ``` 190 | -------------------------------------------------------------------------------- /ca_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /ca_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /casigner.go: -------------------------------------------------------------------------------- 1 | package httpproxy 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/sha1" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "math/big" 9 | mrand "math/rand" 10 | "net" 11 | "sort" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // CaSigner is a certificate signer by CA certificate. It supports caching. 17 | type CaSigner struct { 18 | // Ca specifies CA certificate. You must set before using. 19 | Ca *tls.Certificate 20 | 21 | mu sync.RWMutex 22 | certMap map[string]*tls.Certificate 23 | certList []string 24 | certIndex int 25 | certMax int 26 | } 27 | 28 | // NewCaSigner returns a new CaSigner without caching. 29 | func NewCaSigner() *CaSigner { 30 | return NewCaSignerCache(0) 31 | } 32 | 33 | // NewCaSignerCache returns a new CaSigner with caching given max. 34 | func NewCaSignerCache(max int) *CaSigner { 35 | if max < 0 { 36 | max = 0 37 | } 38 | return &CaSigner{ 39 | certMap: make(map[string]*tls.Certificate), 40 | certList: make([]string, max), 41 | certIndex: 0, 42 | certMax: max, 43 | } 44 | } 45 | 46 | // SignHost generates TLS certificate given single host, signed by CA certificate. 47 | func (c *CaSigner) SignHost(host string) (cert *tls.Certificate) { 48 | if host == "" { 49 | return 50 | } 51 | if c.certMax <= 0 { 52 | crt, err := SignHosts(*c.Ca, []string{host}) 53 | if err != nil { 54 | return nil 55 | } 56 | cert = crt 57 | return 58 | } 59 | func() { 60 | c.mu.RLock() 61 | defer c.mu.RUnlock() 62 | cert = c.certMap[host] 63 | }() 64 | if cert != nil { 65 | return 66 | } 67 | c.mu.Lock() 68 | defer c.mu.Unlock() 69 | cert = c.certMap[host] 70 | if cert != nil { 71 | return 72 | } 73 | crt, err := SignHosts(*c.Ca, []string{host}) 74 | if err != nil { 75 | return nil 76 | } 77 | cert = crt 78 | if len(c.certMap) >= c.certMax { 79 | delete(c.certMap, c.certList[c.certIndex]) 80 | } 81 | c.certMap[host] = cert 82 | c.certList[c.certIndex] = host 83 | c.certIndex++ 84 | if c.certIndex >= c.certMax { 85 | c.certIndex = 0 86 | } 87 | return 88 | } 89 | 90 | // SignHosts generates TLS certificate given hosts, signed by CA certificate. 91 | func SignHosts(ca tls.Certificate, hosts []string) (*tls.Certificate, error) { 92 | x509ca, err := x509.ParseCertificate(ca.Certificate[0]) 93 | if err != nil { 94 | return nil, err 95 | } 96 | start := time.Unix(0, 0) 97 | end, _ := time.Parse("2006-01-02", "2038-01-19") 98 | serial := hashSortedBigInt(append(hosts, "1")) 99 | template := x509.Certificate{ 100 | SerialNumber: serial, 101 | Issuer: x509ca.Subject, 102 | Subject: x509ca.Subject, 103 | NotBefore: start, 104 | NotAfter: end, 105 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 106 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 107 | BasicConstraintsValid: true, 108 | } 109 | for _, h := range hosts { 110 | h = stripPort(h) 111 | if ip := net.ParseIP(h); ip != nil { 112 | template.IPAddresses = append(template.IPAddresses, ip) 113 | } else { 114 | template.DNSNames = append(template.DNSNames, h) 115 | } 116 | } 117 | rnd := mrand.New(mrand.NewSource(serial.Int64())) 118 | certPriv, err := rsa.GenerateKey(rnd, 1024) 119 | if err != nil { 120 | return nil, err 121 | } 122 | derBytes, err := x509.CreateCertificate(rnd, &template, x509ca, &certPriv.PublicKey, ca.PrivateKey) 123 | if err != nil { 124 | return nil, err 125 | } 126 | return &tls.Certificate{ 127 | Certificate: [][]byte{derBytes, ca.Certificate[0]}, 128 | PrivateKey: certPriv, 129 | }, nil 130 | } 131 | 132 | func hashSorted(lst []string) []byte { 133 | c := make([]string, len(lst)) 134 | copy(c, lst) 135 | sort.Strings(c) 136 | h := sha1.New() 137 | for _, s := range c { 138 | h.Write([]byte(s + ",")) 139 | } 140 | return h.Sum(nil) 141 | } 142 | 143 | func hashSortedBigInt(lst []string) *big.Int { 144 | rv := new(big.Int) 145 | rv.SetBytes(hashSorted(lst)) 146 | return rv 147 | } 148 | -------------------------------------------------------------------------------- /connresponsewriter.go: -------------------------------------------------------------------------------- 1 | package httpproxy 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "net/http" 8 | "sync" 9 | ) 10 | 11 | // ConnResponseWriter implements http.ResponseWriter interface to use hijacked 12 | // HTTP connection. 13 | type ConnResponseWriter struct { 14 | Conn net.Conn 15 | mu sync.Mutex 16 | err error 17 | header http.Header 18 | headersSent bool 19 | } 20 | 21 | // NewConnResponseWriter returns a new ConnResponseWriter. 22 | func NewConnResponseWriter(conn net.Conn) *ConnResponseWriter { 23 | return &ConnResponseWriter{Conn: conn, header: make(http.Header)} 24 | } 25 | 26 | // Header returns the header map that will be sent by WriteHeader. 27 | func (c *ConnResponseWriter) Header() http.Header { 28 | return c.header 29 | } 30 | 31 | // Write writes the data to the connection as part of an HTTP reply. 32 | func (c *ConnResponseWriter) Write(body []byte) (int, error) { 33 | c.mu.Lock() 34 | defer c.mu.Unlock() 35 | c.writeHeader(http.StatusOK) 36 | if c.err != nil { 37 | return 0, c.err 38 | } 39 | n, err := c.Conn.Write(body) 40 | if err != nil { 41 | c.err = err 42 | } 43 | return n, err 44 | } 45 | 46 | // WriteHeader sends an HTTP response header with status code. 47 | func (c *ConnResponseWriter) WriteHeader(statusCode int) { 48 | c.mu.Lock() 49 | defer c.mu.Unlock() 50 | c.writeHeader(statusCode) 51 | } 52 | 53 | // Close closes network connection. 54 | func (c *ConnResponseWriter) Close() error { 55 | return c.Conn.Close() 56 | } 57 | 58 | func (c *ConnResponseWriter) writeHeader(statusCode int) { 59 | if c.err != nil { 60 | return 61 | } 62 | if c.headersSent { 63 | return 64 | } 65 | st := http.StatusText(statusCode) 66 | if st != "" { 67 | st = " " + st 68 | } 69 | if _, err := io.WriteString(c.Conn, fmt.Sprintf("HTTP/1.1 %d%s\r\n", statusCode, st)); err != nil { 70 | c.err = err 71 | return 72 | } 73 | if err := c.header.Write(c.Conn); err != nil { 74 | c.err = err 75 | return 76 | } 77 | if _, err := io.WriteString(c.Conn, "\r\n"); err != nil { 78 | c.err = err 79 | return 80 | } 81 | c.headersSent = true 82 | } 83 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package httpproxy 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "crypto/tls" 7 | "encoding/base64" 8 | "io" 9 | "net" 10 | "net/http" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | // Context keeps context of each proxy request. 16 | type Context struct { 17 | // Pointer of Proxy struct handled this context. 18 | // It's using internally. Don't change in Context struct! 19 | Prx *Proxy 20 | 21 | // Session number of this context obtained from Proxy struct. 22 | SessionNo int64 23 | 24 | // Sub session number of processing remote connection. 25 | SubSessionNo int64 26 | 27 | // Original Proxy request. 28 | // It's using internally. Don't change in Context struct! 29 | Req *http.Request 30 | 31 | // Original Proxy request, if proxy request method is CONNECT. 32 | // It's using internally. Don't change in Context struct! 33 | ConnectReq *http.Request 34 | 35 | // Action of after the CONNECT, if proxy request method is CONNECT. 36 | // It's using internally. Don't change in Context struct! 37 | ConnectAction ConnectAction 38 | 39 | // Remote host, if proxy request method is CONNECT. 40 | // It's using internally. Don't change in Context struct! 41 | ConnectHost string 42 | 43 | // User data to use free. 44 | UserData interface{} 45 | 46 | hijTLSConn *tls.Conn 47 | hijTLSReader *bufio.Reader 48 | } 49 | 50 | func (ctx *Context) onAccept(w http.ResponseWriter, r *http.Request) bool { 51 | defer func() { 52 | if err, ok := recover().(error); ok { 53 | ctx.doError("Accept", ErrPanic, err) 54 | } 55 | }() 56 | return ctx.Prx.OnAccept(ctx, w, r) 57 | } 58 | 59 | func (ctx *Context) onAuth(authType string, user string, pass string) bool { 60 | defer func() { 61 | if err, ok := recover().(error); ok { 62 | ctx.doError("Auth", ErrPanic, err) 63 | } 64 | }() 65 | return ctx.Prx.OnAuth(ctx, authType, user, pass) 66 | } 67 | 68 | func (ctx *Context) onConnect(host string) (ConnectAction ConnectAction, 69 | newHost string) { 70 | defer func() { 71 | if err, ok := recover().(error); ok { 72 | ctx.doError("Connect", ErrPanic, err) 73 | } 74 | }() 75 | return ctx.Prx.OnConnect(ctx, host) 76 | } 77 | 78 | func (ctx *Context) onRequest(req *http.Request) (resp *http.Response) { 79 | defer func() { 80 | if err, ok := recover().(error); ok { 81 | ctx.doError("Request", ErrPanic, err) 82 | } 83 | }() 84 | return ctx.Prx.OnRequest(ctx, req) 85 | } 86 | 87 | func (ctx *Context) onResponse(req *http.Request, resp *http.Response) { 88 | defer func() { 89 | if err, ok := recover().(error); ok { 90 | ctx.doError("Response", ErrPanic, err) 91 | } 92 | }() 93 | ctx.Prx.OnResponse(ctx, req, resp) 94 | } 95 | 96 | func (ctx *Context) doError(where string, err *Error, opErr error) { 97 | if ctx.Prx.OnError == nil { 98 | return 99 | } 100 | ctx.Prx.OnError(ctx, where, err, opErr) 101 | } 102 | 103 | func (ctx *Context) doAccept(w http.ResponseWriter, r *http.Request) bool { 104 | ctx.Req = r 105 | if !r.ProtoAtLeast(1, 0) || r.ProtoAtLeast(2, 0) { 106 | if r.Body != nil { 107 | defer r.Body.Close() 108 | } 109 | ctx.doError("Accept", ErrNotSupportHTTPVer, nil) 110 | return true 111 | } 112 | if ctx.Prx.OnAccept != nil && ctx.onAccept(w, r) { 113 | if r.Body != nil { 114 | defer r.Body.Close() 115 | } 116 | return true 117 | } 118 | return false 119 | } 120 | 121 | func (ctx *Context) doAuth(w http.ResponseWriter, r *http.Request) bool { 122 | if r.Method != "CONNECT" && !r.URL.IsAbs() { 123 | return false 124 | } 125 | if ctx.Prx.OnAuth == nil { 126 | return false 127 | } 128 | prxAuthType := ctx.Prx.AuthType 129 | if prxAuthType == "" { 130 | prxAuthType = "Basic" 131 | } 132 | unauthorized := false 133 | authParts := strings.SplitN(r.Header.Get("Proxy-Authorization"), " ", 2) 134 | if len(authParts) >= 2 { 135 | authType := authParts[0] 136 | authData := authParts[1] 137 | if prxAuthType == authType { 138 | unauthorized = true 139 | switch authType { 140 | case "Basic": 141 | userpassraw, err := base64.StdEncoding.DecodeString(authData) 142 | if err == nil { 143 | userpass := strings.SplitN(string(userpassraw), ":", 2) 144 | if len(userpass) >= 2 && ctx.onAuth(authType, userpass[0], userpass[1]) { 145 | return false 146 | } 147 | } 148 | default: 149 | unauthorized = false 150 | } 151 | } 152 | } 153 | if r.Body != nil { 154 | defer r.Body.Close() 155 | } 156 | respCode := 407 157 | respBody := "Proxy Authentication Required" 158 | if unauthorized { 159 | respBody += " [Unauthorized]" 160 | } 161 | err := ServeInMemory(w, respCode, map[string][]string{"Proxy-Authenticate": {prxAuthType}}, 162 | []byte(respBody)) 163 | if err != nil && !isConnectionClosed(err) { 164 | ctx.doError("Auth", ErrResponseWrite, err) 165 | } 166 | return true 167 | } 168 | 169 | func (ctx *Context) doConnect(w http.ResponseWriter, r *http.Request) (b bool) { 170 | b = true 171 | if r.Method != "CONNECT" { 172 | b = false 173 | return 174 | } 175 | hij, ok := w.(http.Hijacker) 176 | if !ok { 177 | if r.Body != nil { 178 | defer r.Body.Close() 179 | } 180 | ctx.doError("Connect", ErrNotSupportHijacking, nil) 181 | return 182 | } 183 | conn, _, err := hij.Hijack() 184 | if err != nil { 185 | if r.Body != nil { 186 | defer r.Body.Close() 187 | } 188 | ctx.doError("Connect", ErrNotSupportHijacking, err) 189 | return 190 | } 191 | hijConn := conn 192 | ctx.ConnectReq = r 193 | ctx.ConnectAction = ConnectProxy 194 | host := r.URL.Host 195 | if !hasPort.MatchString(host) { 196 | host += ":80" 197 | } 198 | if ctx.Prx.OnConnect != nil { 199 | var newHost string 200 | ctx.ConnectAction, newHost = ctx.onConnect(host) 201 | if newHost != "" { 202 | host = newHost 203 | } 204 | } 205 | if !hasPort.MatchString(host) { 206 | host += ":80" 207 | } 208 | ctx.ConnectHost = host 209 | switch ctx.ConnectAction { 210 | case ConnectProxy: 211 | conn, err := net.Dial("tcp", host) 212 | if err != nil { 213 | hijConn.Write([]byte("HTTP/1.1 404 Not Found\r\n\r\n")) 214 | hijConn.Close() 215 | ctx.doError("Connect", ErrRemoteConnect, err) 216 | return 217 | } 218 | remoteConn := conn.(*net.TCPConn) 219 | if _, err := hijConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")); err != nil { 220 | hijConn.Close() 221 | remoteConn.Close() 222 | if !isConnectionClosed(err) { 223 | ctx.doError("Connect", ErrResponseWrite, err) 224 | } 225 | return 226 | } 227 | var wg sync.WaitGroup 228 | wg.Add(2) 229 | go func() { 230 | defer wg.Done() 231 | defer func() { 232 | e := recover() 233 | err, ok := e.(error) 234 | if !ok { 235 | return 236 | } 237 | hijConn.Close() 238 | remoteConn.Close() 239 | if !isConnectionClosed(err) { 240 | ctx.doError("Connect", ErrRequestRead, err) 241 | } 242 | }() 243 | _, err := io.Copy(remoteConn, hijConn) 244 | if err != nil { 245 | panic(err) 246 | } 247 | remoteConn.CloseWrite() 248 | if c, ok := hijConn.(*net.TCPConn); ok { 249 | c.CloseRead() 250 | } 251 | }() 252 | go func() { 253 | defer wg.Done() 254 | defer func() { 255 | e := recover() 256 | err, ok := e.(error) 257 | if !ok { 258 | return 259 | } 260 | hijConn.Close() 261 | remoteConn.Close() 262 | if !isConnectionClosed(err) { 263 | ctx.doError("Connect", ErrResponseWrite, err) 264 | } 265 | }() 266 | _, err := io.Copy(hijConn, remoteConn) 267 | if err != nil { 268 | panic(err) 269 | } 270 | remoteConn.CloseRead() 271 | if c, ok := hijConn.(*net.TCPConn); ok { 272 | c.CloseWrite() 273 | } 274 | }() 275 | wg.Wait() 276 | hijConn.Close() 277 | remoteConn.Close() 278 | case ConnectMitm: 279 | tlsConfig := &tls.Config{} 280 | cert := ctx.Prx.signer.SignHost(host) 281 | if cert == nil { 282 | hijConn.Close() 283 | ctx.doError("Connect", ErrTLSSignHost, err) 284 | return 285 | } 286 | tlsConfig.Certificates = append(tlsConfig.Certificates, *cert) 287 | if _, err := hijConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")); err != nil { 288 | hijConn.Close() 289 | if !isConnectionClosed(err) { 290 | ctx.doError("Connect", ErrResponseWrite, err) 291 | } 292 | return 293 | } 294 | ctx.hijTLSConn = tls.Server(hijConn, tlsConfig) 295 | if err := ctx.hijTLSConn.Handshake(); err != nil { 296 | ctx.hijTLSConn.Close() 297 | if !isConnectionClosed(err) { 298 | ctx.doError("Connect", ErrTLSHandshake, err) 299 | } 300 | return 301 | } 302 | ctx.hijTLSReader = bufio.NewReader(ctx.hijTLSConn) 303 | b = false 304 | default: 305 | hijConn.Close() 306 | } 307 | return 308 | } 309 | 310 | func (ctx *Context) doMitm() (w http.ResponseWriter, r *http.Request) { 311 | req, err := http.ReadRequest(ctx.hijTLSReader) 312 | if err != nil { 313 | if !isConnectionClosed(err) { 314 | ctx.doError("Request", ErrRequestRead, err) 315 | } 316 | return 317 | } 318 | req.RemoteAddr = ctx.ConnectReq.RemoteAddr 319 | if req.URL.IsAbs() { 320 | ctx.doError("Request", ErrAbsURLAfterCONNECT, nil) 321 | return 322 | } 323 | req.URL.Scheme = "https" 324 | req.URL.Host = ctx.ConnectHost 325 | w = NewConnResponseWriter(ctx.hijTLSConn) 326 | r = req 327 | return 328 | } 329 | 330 | func (ctx *Context) doRequest(w http.ResponseWriter, r *http.Request) (bool, error) { 331 | if !r.URL.IsAbs() { 332 | if r.Body != nil { 333 | defer r.Body.Close() 334 | } 335 | err := ServeInMemory(w, 500, nil, []byte("This is a proxy server. Does not respond to non-proxy requests.")) 336 | if err != nil && !isConnectionClosed(err) { 337 | ctx.doError("Request", ErrResponseWrite, err) 338 | } 339 | return true, err 340 | } 341 | r.RequestURI = r.URL.String() 342 | if ctx.Prx.OnRequest == nil { 343 | return false, nil 344 | } 345 | resp := ctx.onRequest(r) 346 | if resp == nil { 347 | return false, nil 348 | } 349 | if r.Body != nil { 350 | defer r.Body.Close() 351 | } 352 | resp.Request = r 353 | resp.TransferEncoding = nil 354 | if ctx.ConnectAction == ConnectMitm && ctx.Prx.MitmChunked { 355 | resp.TransferEncoding = []string{"chunked"} 356 | } 357 | err := ServeResponse(w, resp) 358 | if err != nil && !isConnectionClosed(err) { 359 | ctx.doError("Request", ErrResponseWrite, err) 360 | } 361 | return true, err 362 | } 363 | 364 | func (ctx *Context) doResponse(w http.ResponseWriter, r *http.Request) error { 365 | if r.Body != nil { 366 | defer r.Body.Close() 367 | } 368 | resp, err := ctx.Prx.Rt.RoundTrip(r) 369 | if err != nil { 370 | if err != context.Canceled && !isConnectionClosed(err) { 371 | ctx.doError("Response", ErrRoundTrip, err) 372 | } 373 | err := ServeInMemory(w, 404, nil, nil) 374 | if err != nil && !isConnectionClosed(err) { 375 | ctx.doError("Response", ErrResponseWrite, err) 376 | } 377 | return err 378 | } 379 | if ctx.Prx.OnResponse != nil { 380 | ctx.onResponse(r, resp) 381 | } 382 | resp.Request = r 383 | resp.TransferEncoding = nil 384 | if ctx.ConnectAction == ConnectMitm && ctx.Prx.MitmChunked { 385 | resp.TransferEncoding = []string{"chunked"} 386 | } 387 | err = ServeResponse(w, resp) 388 | if err != nil && !isConnectionClosed(err) { 389 | ctx.doError("Response", ErrResponseWrite, err) 390 | } 391 | return err 392 | } 393 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package httpproxy 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "os" 7 | "syscall" 8 | ) 9 | 10 | // Library specific errors. 11 | var ( 12 | ErrPanic = NewError("panic") 13 | ErrResponseWrite = NewError("response write") 14 | ErrRequestRead = NewError("request read") 15 | ErrRemoteConnect = NewError("remote connect") 16 | ErrNotSupportHijacking = NewError("hijacking not supported") 17 | ErrTLSSignHost = NewError("TLS sign host") 18 | ErrTLSHandshake = NewError("TLS handshake") 19 | ErrAbsURLAfterCONNECT = NewError("absolute URL after CONNECT") 20 | ErrRoundTrip = NewError("round trip") 21 | ErrUnsupportedTransferEncoding = NewError("unsupported transfer encoding") 22 | ErrNotSupportHTTPVer = NewError("http version not supported") 23 | ) 24 | 25 | // Error struct is base of library specific errors. 26 | type Error struct { 27 | ErrString string 28 | } 29 | 30 | // NewError returns a new Error. 31 | func NewError(errString string) *Error { 32 | return &Error{errString} 33 | } 34 | 35 | // Error implements error interface. 36 | func (e *Error) Error() string { 37 | return e.ErrString 38 | } 39 | 40 | func isConnectionClosed(err error) bool { 41 | if err == nil { 42 | return false 43 | } 44 | if err == io.EOF { 45 | return true 46 | } 47 | i := 0 48 | var newerr = &err 49 | for opError, ok := (*newerr).(*net.OpError); ok && i < 10; { 50 | i++ 51 | newerr = &opError.Err 52 | if syscallError, ok := (*newerr).(*os.SyscallError); ok { 53 | if syscallError.Err == syscall.EPIPE || syscallError.Err == syscall.ECONNRESET || syscallError.Err == syscall.EPROTOTYPE { 54 | return true 55 | } 56 | } 57 | } 58 | return false 59 | } 60 | -------------------------------------------------------------------------------- /examples/go-httpproxy-demo/.gitignore: -------------------------------------------------------------------------------- 1 | /go-httpproxy-demo 2 | -------------------------------------------------------------------------------- /examples/go-httpproxy-demo/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | go-httpproxy-demo is an example for HTTP and HTTPS web proxy. 3 | 4 | Connect through HTTP proxy to HTTP: 5 | curl -x "http://test:1234@localhost:8080" http://httpbin.org/get?a=b 6 | 7 | Connect through HTTP proxy to HTTPS with MITM: 8 | curl --insecure -x "http://test:1234@localhost:8080" https://httpbin.org/get?a=b 9 | 10 | Connect through HTTPS proxy to HTTP: 11 | curl --proxy-insecure -x "https://test:1234@localhost:8443" http://httpbin.org/get?a=b 12 | 13 | Connect through HTTPS proxy to HTTPS with MITM: 14 | curl --proxy-insecure --insecure -x "https://test:1234@localhost:8443" https://httpbin.org/get?a=b 15 | 16 | */ 17 | package main 18 | 19 | import ( 20 | "context" 21 | "crypto/tls" 22 | "log" 23 | "net/http" 24 | "os" 25 | "os/signal" 26 | "sync" 27 | "syscall" 28 | "time" 29 | 30 | "github.com/go-httpproxy/httpproxy" 31 | ) 32 | 33 | var logErr = log.New(os.Stderr, "ERR: ", log.LstdFlags) 34 | 35 | func OnError(ctx *httpproxy.Context, where string, 36 | err *httpproxy.Error, opErr error) { 37 | // Log errors. 38 | logErr.Printf("%s: %s [%s]", where, err, opErr) 39 | } 40 | 41 | func OnAccept(ctx *httpproxy.Context, w http.ResponseWriter, 42 | r *http.Request) bool { 43 | // Handle local request has path "/info" 44 | if r.Method == "GET" && !r.URL.IsAbs() && r.URL.Path == "/info" { 45 | w.Write([]byte("This is go-httpproxy.")) 46 | return true 47 | } 48 | return false 49 | } 50 | 51 | func OnAuth(ctx *httpproxy.Context, authType string, user string, pass string) bool { 52 | // Auth test user. 53 | if user == "test" && pass == "1234" { 54 | return true 55 | } 56 | return false 57 | } 58 | 59 | func OnConnect(ctx *httpproxy.Context, host string) ( 60 | ConnectAction httpproxy.ConnectAction, newHost string) { 61 | // Apply "Man in the Middle" to all ssl connections. Never change host. 62 | return httpproxy.ConnectMitm, host 63 | } 64 | 65 | func OnRequest(ctx *httpproxy.Context, req *http.Request) ( 66 | resp *http.Response) { 67 | // Log proxying requests. 68 | log.Printf("INFO: Proxy %d %d: %s %s", ctx.SessionNo, ctx.SubSessionNo, req.Method, req.URL.String()) 69 | return 70 | } 71 | 72 | func OnResponse(ctx *httpproxy.Context, req *http.Request, 73 | resp *http.Response) { 74 | // Add header "Via: go-httpproxy". 75 | resp.Header.Add("Via", "go-httpproxy") 76 | } 77 | 78 | func main() { 79 | log.SetOutput(os.Stdout) 80 | log.Print("Started") 81 | 82 | sigChan := make(chan os.Signal) 83 | signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGTERM) 84 | 85 | // Create a new proxy with default certificate pair. 86 | prx, _ := httpproxy.NewProxy() 87 | 88 | // Set proxy handlers. 89 | prx.OnError = OnError 90 | prx.OnAccept = OnAccept 91 | prx.OnAuth = OnAuth 92 | prx.OnConnect = OnConnect 93 | prx.OnRequest = OnRequest 94 | prx.OnResponse = OnResponse 95 | //prx.MitmChunked = false 96 | 97 | server := &http.Server{ 98 | Addr: ":8080", 99 | Handler: prx, 100 | TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), 101 | } 102 | listenErrChan := make(chan error) 103 | go func() { 104 | listenErrChan <- server.ListenAndServe() 105 | }() 106 | log.Printf("Listening HTTP %s", server.Addr) 107 | 108 | cert, _ := tls.X509KeyPair(httpproxy.DefaultCaCert, httpproxy.DefaultCaKey) 109 | serverHTTPS := &http.Server{ 110 | Addr: ":8443", 111 | Handler: prx, 112 | TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), 113 | TLSConfig: &tls.Config{ 114 | MinVersion: tls.VersionSSL30, 115 | CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, 116 | PreferServerCipherSuites: true, 117 | CipherSuites: []uint16{ 118 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 119 | tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 120 | tls.TLS_RSA_WITH_AES_256_GCM_SHA384, 121 | tls.TLS_RSA_WITH_AES_256_CBC_SHA, 122 | }, 123 | Certificates: []tls.Certificate{cert}, 124 | }, 125 | } 126 | listenHTTPSErrChan := make(chan error) 127 | go func() { 128 | listenHTTPSErrChan <- serverHTTPS.ListenAndServeTLS("", "") 129 | }() 130 | log.Printf("Listening HTTPS %s", serverHTTPS.Addr) 131 | 132 | mainloop: 133 | for { 134 | select { 135 | case <-sigChan: 136 | break mainloop 137 | case listenErr := <-listenErrChan: 138 | if listenErr != nil && listenErr == http.ErrServerClosed { 139 | break mainloop 140 | } 141 | log.Fatal(listenErr) 142 | case listenErr := <-listenHTTPSErrChan: 143 | if listenErr != nil && listenErr == http.ErrServerClosed { 144 | break mainloop 145 | } 146 | log.Fatal(listenErr) 147 | } 148 | } 149 | 150 | shutdown := func(srv *http.Server, wg *sync.WaitGroup) { 151 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 152 | defer cancel() 153 | srv.SetKeepAlivesEnabled(false) 154 | if err := srv.Shutdown(ctx); err == context.DeadlineExceeded { 155 | log.Printf("Force shutdown %s", srv.Addr) 156 | } else { 157 | log.Printf("Graceful shutdown %s", srv.Addr) 158 | } 159 | wg.Done() 160 | } 161 | 162 | wg := &sync.WaitGroup{} 163 | wg.Add(1) 164 | go shutdown(server, wg) 165 | wg.Add(1) 166 | go shutdown(serverHTTPS, wg) 167 | wg.Wait() 168 | 169 | log.Println("Finished") 170 | } 171 | -------------------------------------------------------------------------------- /examples/go-httpproxy-simple/.gitignore: -------------------------------------------------------------------------------- 1 | /go-httpproxy-simple 2 | -------------------------------------------------------------------------------- /examples/go-httpproxy-simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/go-httpproxy/httpproxy" 8 | ) 9 | 10 | func OnError(ctx *httpproxy.Context, where string, 11 | err *httpproxy.Error, opErr error) { 12 | // Log errors. 13 | log.Printf("ERR: %s: %s [%s]", where, err, opErr) 14 | } 15 | 16 | func OnAccept(ctx *httpproxy.Context, w http.ResponseWriter, 17 | r *http.Request) bool { 18 | // Handle local request has path "/info" 19 | if r.Method == "GET" && !r.URL.IsAbs() && r.URL.Path == "/info" { 20 | w.Write([]byte("This is go-httpproxy.")) 21 | return true 22 | } 23 | return false 24 | } 25 | 26 | func OnAuth(ctx *httpproxy.Context, authType string, user string, pass string) bool { 27 | // Auth test user. 28 | if user == "test" && pass == "1234" { 29 | return true 30 | } 31 | return false 32 | } 33 | 34 | func OnConnect(ctx *httpproxy.Context, host string) ( 35 | ConnectAction httpproxy.ConnectAction, newHost string) { 36 | // Apply "Man in the Middle" to all ssl connections. Never change host. 37 | return httpproxy.ConnectMitm, host 38 | } 39 | 40 | func OnRequest(ctx *httpproxy.Context, req *http.Request) ( 41 | resp *http.Response) { 42 | // Log proxying requests. 43 | log.Printf("INFO: Proxy: %s %s", req.Method, req.URL.String()) 44 | return 45 | } 46 | 47 | func OnResponse(ctx *httpproxy.Context, req *http.Request, 48 | resp *http.Response) { 49 | // Add header "Via: go-httpproxy". 50 | resp.Header.Add("Via", "go-httpproxy") 51 | } 52 | 53 | func main() { 54 | // Create a new proxy with default certificate pair. 55 | prx, _ := httpproxy.NewProxy() 56 | 57 | // Set handlers. 58 | prx.OnError = OnError 59 | prx.OnAccept = OnAccept 60 | prx.OnAuth = OnAuth 61 | prx.OnConnect = OnConnect 62 | prx.OnRequest = OnRequest 63 | prx.OnResponse = OnResponse 64 | 65 | // Listen... 66 | http.ListenAndServe(":8080", prx) 67 | } 68 | -------------------------------------------------------------------------------- /httpproxy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package httpproxy provides a customizable HTTP proxy; 3 | supports HTTP, HTTPS through CONNECT. And also provides HTTPS connection 4 | using "Man in the Middle" style attack. 5 | 6 | It's easy to use. `httpproxy.Proxy` implements `Handler` interface of `net/http` 7 | package to offer `http.ListenAndServe` function. 8 | */ 9 | package httpproxy 10 | 11 | // ConnectAction specifies action of after the CONNECT. 12 | type ConnectAction int 13 | 14 | // Constants of ConnectAction type. 15 | const ( 16 | // ConnectNone specifies that proxy request is not CONNECT. 17 | // If it returned in OnConnect, proxy connection closes immediately. 18 | ConnectNone = ConnectAction(iota) 19 | 20 | // ConnectProxy specifies directly socket proxy after the CONNECT. 21 | ConnectProxy 22 | 23 | // ConnectMitm specifies proxy "Man in the Middle" style attack 24 | // after the CONNECT. 25 | ConnectMitm 26 | ) 27 | 28 | // DefaultCaCert provides default CA certificate. 29 | var DefaultCaCert = []byte(`-----BEGIN CERTIFICATE----- 30 | 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 60 | -----END CERTIFICATE-----`) 61 | 62 | // DefaultCaKey provides default CA key. 63 | var DefaultCaKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 64 | 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 113 | -----END RSA PRIVATE KEY-----`) 114 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package httpproxy 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/http" 6 | "sync/atomic" 7 | ) 8 | 9 | // Proxy defines parameters for running an HTTP Proxy. It implements 10 | // http.Handler interface for ListenAndServe function. If you need, you must 11 | // set Proxy struct before handling requests. 12 | type Proxy struct { 13 | // Session number of last proxy request. 14 | SessionNo int64 15 | 16 | // RoundTripper interface to obtain remote response. 17 | // By default, it uses &http.Transport{}. 18 | Rt http.RoundTripper 19 | 20 | // Certificate key pair. 21 | Ca tls.Certificate 22 | 23 | // User data to use free. 24 | UserData interface{} 25 | 26 | // Error callback. 27 | OnError func(ctx *Context, where string, err *Error, opErr error) 28 | 29 | // Accept callback. It greets proxy request like ServeHTTP function of 30 | // http.Handler. 31 | // If it returns true, stops processing proxy request. 32 | OnAccept func(ctx *Context, w http.ResponseWriter, r *http.Request) bool 33 | 34 | // Auth callback. If you need authentication, set this callback. 35 | // If it returns true, authentication succeeded. 36 | OnAuth func(ctx *Context, authType string, user string, pass string) bool 37 | 38 | // Connect callback. It sets connect action and new host. 39 | // If len(newhost) > 0, host changes. 40 | OnConnect func(ctx *Context, host string) (ConnectAction ConnectAction, 41 | newHost string) 42 | 43 | // Request callback. It greets remote request. 44 | // If it returns non-nil response, stops processing remote request. 45 | OnRequest func(ctx *Context, req *http.Request) (resp *http.Response) 46 | 47 | // Response callback. It greets remote response. 48 | // Remote response sends after this callback. 49 | OnResponse func(ctx *Context, req *http.Request, resp *http.Response) 50 | 51 | // If ConnectAction is ConnectMitm, it sets chunked to Transfer-Encoding. 52 | // By default, true. 53 | MitmChunked bool 54 | 55 | // HTTP Authentication type. If it's not specified (""), uses "Basic". 56 | // By default, "". 57 | AuthType string 58 | 59 | signer *CaSigner 60 | } 61 | 62 | // NewProxy returns a new Proxy has default CA certificate and key. 63 | func NewProxy() (*Proxy, error) { 64 | return NewProxyCert(nil, nil) 65 | } 66 | 67 | // NewProxyCert returns a new Proxy given CA certificate and key. 68 | func NewProxyCert(caCert, caKey []byte) (*Proxy, error) { 69 | prx := &Proxy{ 70 | Rt: &http.Transport{TLSClientConfig: &tls.Config{}, 71 | Proxy: http.ProxyFromEnvironment}, 72 | MitmChunked: true, 73 | signer: NewCaSignerCache(1024), 74 | } 75 | prx.signer.Ca = &prx.Ca 76 | if caCert == nil { 77 | caCert = DefaultCaCert 78 | } 79 | if caKey == nil { 80 | caKey = DefaultCaKey 81 | } 82 | var err error 83 | prx.Ca, err = tls.X509KeyPair(caCert, caKey) 84 | if err != nil { 85 | return nil, err 86 | } 87 | return prx, nil 88 | } 89 | 90 | // ServeHTTP implements http.Handler. 91 | func (prx *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { 92 | ctx := &Context{Prx: prx, SessionNo: atomic.AddInt64(&prx.SessionNo, 1)} 93 | 94 | defer func() { 95 | rec := recover() 96 | if rec != nil { 97 | if err, ok := rec.(error); ok && prx.OnError != nil { 98 | prx.OnError(ctx, "ServeHTTP", ErrPanic, err) 99 | } 100 | panic(rec) 101 | } 102 | }() 103 | 104 | if ctx.doAccept(w, r) { 105 | return 106 | } 107 | 108 | if ctx.doAuth(w, r) { 109 | return 110 | } 111 | r.Header.Del("Proxy-Connection") 112 | r.Header.Del("Proxy-Authenticate") 113 | r.Header.Del("Proxy-Authorization") 114 | 115 | if b := ctx.doConnect(w, r); b { 116 | return 117 | } 118 | 119 | for { 120 | var w2 = w 121 | var r2 = r 122 | var cyclic = false 123 | switch ctx.ConnectAction { 124 | case ConnectMitm: 125 | if prx.MitmChunked { 126 | cyclic = true 127 | } 128 | w2, r2 = ctx.doMitm() 129 | } 130 | if w2 == nil || r2 == nil { 131 | break 132 | } 133 | //r.Header.Del("Accept-Encoding") 134 | //r.Header.Del("Connection") 135 | ctx.SubSessionNo++ 136 | if b, err := ctx.doRequest(w2, r2); err != nil { 137 | break 138 | } else { 139 | if b { 140 | if !cyclic { 141 | break 142 | } else { 143 | continue 144 | } 145 | } 146 | } 147 | if err := ctx.doResponse(w2, r2); err != nil || !cyclic { 148 | break 149 | } 150 | } 151 | 152 | if ctx.hijTLSConn != nil { 153 | ctx.hijTLSConn.Close() 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package httpproxy 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "net/http/httputil" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | // InMemoryResponse creates new HTTP response given arguments. 17 | func InMemoryResponse(code int, header http.Header, body []byte) *http.Response { 18 | if header == nil { 19 | header = make(http.Header) 20 | } 21 | st := http.StatusText(code) 22 | if st != "" { 23 | st = " " + st 24 | } 25 | var bodyReadCloser io.ReadCloser 26 | var bodyContentLength = int64(0) 27 | if body != nil { 28 | bodyReadCloser = ioutil.NopCloser(bytes.NewBuffer(body)) 29 | bodyContentLength = int64(len(body)) 30 | } 31 | return &http.Response{ 32 | Status: fmt.Sprintf("%d%s", code, st), 33 | StatusCode: code, 34 | Proto: "HTTP/1.1", 35 | ProtoMajor: 1, 36 | ProtoMinor: 1, 37 | Header: header, 38 | Body: bodyReadCloser, 39 | ContentLength: bodyContentLength, 40 | } 41 | } 42 | 43 | // ServeResponse serves HTTP response to http.ResponseWriter. 44 | func ServeResponse(w http.ResponseWriter, resp *http.Response) error { 45 | if resp.Body != nil { 46 | defer resp.Body.Close() 47 | } 48 | h := w.Header() 49 | for k, v := range resp.Header { 50 | for _, v1 := range v { 51 | h.Add(k, v1) 52 | } 53 | } 54 | if h.Get("Date") == "" { 55 | h.Set("Date", time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05")+" GMT") 56 | } 57 | if h.Get("Content-Type") == "" && resp.ContentLength != 0 { 58 | h.Set("Content-Type", "text/plain; charset=utf-8") 59 | } 60 | if resp.ContentLength >= 0 { 61 | h.Set("Content-Length", strconv.FormatInt(resp.ContentLength, 10)) 62 | } else { 63 | h.Del("Content-Length") 64 | } 65 | h.Del("Transfer-Encoding") 66 | te := "" 67 | if len(resp.TransferEncoding) > 0 { 68 | if len(resp.TransferEncoding) > 1 { 69 | return ErrUnsupportedTransferEncoding 70 | } 71 | te = resp.TransferEncoding[0] 72 | } 73 | h.Del("Connection") 74 | clientConnection := "" 75 | if resp.Request != nil { 76 | clientConnection = resp.Request.Header.Get("Connection") 77 | } 78 | switch clientConnection { 79 | case "close": 80 | h.Set("Connection", "close") 81 | case "keep-alive": 82 | if h.Get("Content-Length") != "" || te == "chunked" { 83 | h.Set("Connection", "keep-alive") 84 | } else { 85 | h.Set("Connection", "close") 86 | } 87 | default: 88 | if te == "chunked" { 89 | h.Set("Connection", "close") 90 | } 91 | } 92 | switch te { 93 | case "": 94 | w.WriteHeader(resp.StatusCode) 95 | if resp.Body != nil { 96 | if _, err := io.Copy(w, resp.Body); err != nil { 97 | return err 98 | } 99 | } 100 | case "chunked": 101 | h.Set("Transfer-Encoding", "chunked") 102 | w.WriteHeader(resp.StatusCode) 103 | w2 := httputil.NewChunkedWriter(w) 104 | if resp.Body != nil { 105 | if _, err := io.Copy(w2, resp.Body); err != nil { 106 | return err 107 | } 108 | } 109 | if err := w2.Close(); err != nil { 110 | return err 111 | } 112 | if _, err := w.Write([]byte("\r\n")); err != nil { 113 | return err 114 | } 115 | default: 116 | return ErrUnsupportedTransferEncoding 117 | } 118 | return nil 119 | } 120 | 121 | // ServeInMemory serves HTTP response given arguments to http.ResponseWriter. 122 | func ServeInMemory(w http.ResponseWriter, code int, header http.Header, body []byte) error { 123 | return ServeResponse(w, InMemoryResponse(code, header, body)) 124 | } 125 | 126 | var hasPort = regexp.MustCompile(`:\d+$`) 127 | 128 | func stripPort(s string) string { 129 | ix := strings.IndexRune(s, ':') 130 | if ix == -1 { 131 | return s 132 | } 133 | return s[:ix] 134 | } 135 | --------------------------------------------------------------------------------