pax_global_header00006660000000000000000000000064124037540210014510gustar00rootroot0000000000000052 comment=0a5b8bdb09049ae4ae68e75437ded0b7a2e324e3 goproxy-1.0/000077500000000000000000000000001240375402100130575ustar00rootroot00000000000000goproxy-1.0/.gitignore000066400000000000000000000000121240375402100150400ustar00rootroot00000000000000bin *.swp goproxy-1.0/LICENSE000066400000000000000000000027171240375402100140730ustar00rootroot00000000000000Copyright (c) 2012 Elazar Leibovich. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Elazar Leibovich. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. goproxy-1.0/README.md000066400000000000000000000102771240375402100143450ustar00rootroot00000000000000# Introduction Package goproxy provides a customizable HTTP proxy library for Go (golang), It supports regular HTTP proxy, HTTPS through CONNECT, and "hijacking" HTTPS connection using "Man in the Middle" style attack. The intent of the proxy, is to be usable with reasonable amount of traffic yet, customizable and programable. The proxy itself is simply a `net/http` handler. In order to use goproxy, one should set his browser to use goproxy as an HTTP proxy. Here is how you do that [in Chrome](https://support.google.com/chrome/answer/96815?hl=en) and [in Firefox](http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox). For example, the URL you should use as proxy when running `./bin/basic` is `localhost:8080`, as this is the default binding for the basic proxy. ## Mailing List New features would be discussed on the [mailing list](https://groups.google.com/forum/#!forum/goproxy-dev) before their development. ## Latest Stable Release Get the latest goproxy from `gopkg.in/elazarl/goproxy.v1`. # Why not Fiddler2? Fiddler is an excellent software with similar intent. However, Fiddler is not as customable as goproxy intend to be. The main difference is, Fiddler is not intended to be used as a real proxy. A possible use case that suits goproxy but not Fiddler, is, gathering statisitics on page load times for a certain website over a week. With goproxy you could ask all your users to set their proxy to a dedicated machine running a goproxy server. Fiddler is a GUI app not designed to be ran like a server for multiple users. # A taste of goproxy To get a taste of `goproxy`, a basic HTTP/HTTPS transparent proxy import ( "github.com/elazarl/goproxy" "log" "net/http" ) func main() { proxy := goproxy.NewProxyHttpServer() proxy.Verbose = true log.Fatal(http.ListenAndServe(":8080", proxy)) } This line will add `X-GoProxy: yxorPoG-X` header to all requests sent through the proxy proxy.OnRequest().DoFunc( func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { r.Header.Set("X-GoProxy","yxorPoG-X") return r,nil }) `DoFunc` will process all incoming requests to the proxy. It will add a header to the request and return it. The proxy will send the modified request. Note that we returned nil value as the response. Have we returned a response, goproxy would have discarded the request and sent the new response to the client. In order to refuse connections to reddit at work time proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc( func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 { return r,goproxy.NewResponse(r, goproxy.ContentTypeText,http.StatusForbidden, "Don't waste your time!") } return r,nil }) `DstHostIs` returns a `ReqCondition`, that is a function receiving a `Request` and returning a boolean we will only process requests that matches the condition. `DstHostIs("www.reddit.com")` will return a `ReqCondition` accepting only requests directed to "www.reddit.com". `DoFunc` will recieve a function that will preprocess the request. We can change the request, or return a response. If the time is between 8:00am and 17:00pm, we will neglect the request, and return a precanned text response saying "do not waste your time". See additional examples in the examples directory. # What's New 1. Ability to `Hijack` CONNECT requests. See [the eavesdropper example](https://github.com/elazarl/goproxy/blob/master/examples/eavesdropper/main.go#L17) # License I put the software temporarily under the Go-compatible BSD license, if this prevents someone from using the software, do let mee know and I'll consider changing it. At any rate, user feedback is very important for me, so I'll be delighted to know if you're using this package. # Beta Software I've received a positive feedback from a few people who use goproxy in production settings. I believe it is good enough for usage. I'll try to keep reasonable backwards compatability. In case of a major API change, I'll change the import path. goproxy-1.0/actions.go000066400000000000000000000050311240375402100150450ustar00rootroot00000000000000package goproxy import "net/http" // ReqHandler will "tamper" with the request coming to the proxy server // If Handle returns req,nil the proxy will send the returned request // to the destination server. If it returns nil,resp the proxy will // skip sending any requests, and will simply return the response `resp` // to the client. type ReqHandler interface { Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) } // A wrapper that would convert a function to a ReqHandler interface type type FuncReqHandler func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) // FuncReqHandler.Handle(req,ctx) <=> FuncReqHandler(req,ctx) func (f FuncReqHandler) Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { return f(req, ctx) } // after the proxy have sent the request to the destination server, it will // "filter" the response through the RespHandlers it has. // The proxy server will send to the client the response returned by the RespHandler. // In case of error, resp will be nil, and ctx.RoundTrip.Error will contain the error type RespHandler interface { Handle(resp *http.Response, ctx *ProxyCtx) *http.Response } // A wrapper that would convert a function to a RespHandler interface type type FuncRespHandler func(resp *http.Response, ctx *ProxyCtx) *http.Response // FuncRespHandler.Handle(req,ctx) <=> FuncRespHandler(req,ctx) func (f FuncRespHandler) Handle(resp *http.Response, ctx *ProxyCtx) *http.Response { return f(resp, ctx) } // When a client send a CONNECT request to a host, the request is filtered through // all the HttpsHandlers the proxy has, and if one returns true, the connection is // sniffed using Man in the Middle attack. // That is, the proxy will create a TLS connection with the client, another TLS // connection with the destination the client wished to connect to, and would // send back and forth all messages from the server to the client and vice versa. // The request and responses sent in this Man In the Middle channel are filtered // through the usual flow (request and response filtered through the ReqHandlers // and RespHandlers) type HttpsHandler interface { HandleConnect(req string, ctx *ProxyCtx) (*ConnectAction, string) } // A wrapper that would convert a function to a HttpsHandler interface type type FuncHttpsHandler func(host string, ctx *ProxyCtx) (*ConnectAction, string) // FuncHttpsHandler should implement the RespHandler interface func (f FuncHttpsHandler) HandleConnect(host string, ctx *ProxyCtx) (*ConnectAction, string) { return f(host, ctx) } goproxy-1.0/all.bash000077500000000000000000000005051240375402100144710ustar00rootroot00000000000000#!/bin/bash go test || exit for action in $@; do go $action; done mkdir -p bin find regretable examples/* ext/* -maxdepth 0 -type d | while read d; do (cd $d go build -o ../../bin/$(basename $d) find *_test.go -maxdepth 0 2>/dev/null|while read f;do for action in $@; do go $action; done go test break done) done goproxy-1.0/ca.pem000066400000000000000000000015271240375402100141520ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICSjCCAbWgAwIBAgIBADALBgkqhkiG9w0BAQUwSjEjMCEGA1UEChMaZ2l0aHVi LmNvbS9lbGF6YXJsL2dvcHJveHkxIzAhBgNVBAMTGmdpdGh1Yi5jb20vZWxhemFy bC9nb3Byb3h5MB4XDTAwMDEwMTAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowSjEjMCEG A1UEChMaZ2l0aHViLmNvbS9lbGF6YXJsL2dvcHJveHkxIzAhBgNVBAMTGmdpdGh1 Yi5jb20vZWxhemFybC9nb3Byb3h5MIGdMAsGCSqGSIb3DQEBAQOBjQAwgYkCgYEA vz9BbCaJjxs73Tvcq3leP32hAGerQ1RgvlZ68Z4nZmoVHfl+2Nr/m0dmW+GdOfpT cs/KzfJjYGr/84x524fiuR8GdZ0HOtXJzyF5seoWnbBIuyr1PbEpgRhGQMqqOUuj YExeLbfNHPIoJ8XZ1Vzyv3YxjbmjWA+S/uOe9HWtDbMCAwEAAaNGMEQwDgYDVR0P AQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w DAYDVR0RBAUwA4IBKjALBgkqhkiG9w0BAQUDgYEAIcL8huSmGMompNujsvePTUnM oEUKtX4Eh/+s+DSfV/TyI0I+3GiPpLplEgFWuoBIJGios0r1dKh5N0TGjxX/RmGm qo7E4jjJuo8Gs5U8/fgThZmshax2lwLtbRNwhvUVr65GdahLsZz8I+hySLuatVvR qHHq/FQORIiNyNpq/Hg= -----END CERTIFICATE----- goproxy-1.0/certs.go000066400000000000000000000044021240375402100145260ustar00rootroot00000000000000package goproxy import ( "crypto/tls" "crypto/x509" ) func init() { if goproxyCaErr != nil { panic("Error parsing builtin CA " + goproxyCaErr.Error()) } var err error if GoproxyCa.Leaf, err = x509.ParseCertificate(GoproxyCa.Certificate[0]); err != nil { panic("Error parsing builtin CA " + err.Error()) } } var tlsClientSkipVerify = &tls.Config{InsecureSkipVerify: true} var defaultTlsConfig = &tls.Config{ InsecureSkipVerify: true, } var CA_CERT = []byte(`-----BEGIN CERTIFICATE----- MIICSjCCAbWgAwIBAgIBADALBgkqhkiG9w0BAQUwSjEjMCEGA1UEChMaZ2l0aHVi LmNvbS9lbGF6YXJsL2dvcHJveHkxIzAhBgNVBAMTGmdpdGh1Yi5jb20vZWxhemFy bC9nb3Byb3h5MB4XDTAwMDEwMTAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowSjEjMCEG A1UEChMaZ2l0aHViLmNvbS9lbGF6YXJsL2dvcHJveHkxIzAhBgNVBAMTGmdpdGh1 Yi5jb20vZWxhemFybC9nb3Byb3h5MIGdMAsGCSqGSIb3DQEBAQOBjQAwgYkCgYEA vz9BbCaJjxs73Tvcq3leP32hAGerQ1RgvlZ68Z4nZmoVHfl+2Nr/m0dmW+GdOfpT cs/KzfJjYGr/84x524fiuR8GdZ0HOtXJzyF5seoWnbBIuyr1PbEpgRhGQMqqOUuj YExeLbfNHPIoJ8XZ1Vzyv3YxjbmjWA+S/uOe9HWtDbMCAwEAAaNGMEQwDgYDVR0P AQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w DAYDVR0RBAUwA4IBKjALBgkqhkiG9w0BAQUDgYEAIcL8huSmGMompNujsvePTUnM oEUKtX4Eh/+s+DSfV/TyI0I+3GiPpLplEgFWuoBIJGios0r1dKh5N0TGjxX/RmGm qo7E4jjJuo8Gs5U8/fgThZmshax2lwLtbRNwhvUVr65GdahLsZz8I+hySLuatVvR qHHq/FQORIiNyNpq/Hg= -----END CERTIFICATE-----`) var CA_KEY = []byte(`-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQC/P0FsJomPGzvdO9yreV4/faEAZ6tDVGC+VnrxnidmahUd+X7Y 2v+bR2Zb4Z05+lNyz8rN8mNgav/zjHnbh+K5HwZ1nQc61cnPIXmx6hadsEi7KvU9 sSmBGEZAyqo5S6NgTF4tt80c8ignxdnVXPK/djGNuaNYD5L+4570da0NswIDAQAB AoGBALzIv1b4D7ARTR3NOr6V9wArjiOtMjUrdLhO+9vIp9IEA8ZsA9gjDlCEwbkP VDnoLjnWfraff5Os6+3JjHy1fYpUiCdnk2XA6iJSL1XWKQZPt3wOunxP4lalDgED QTRReFbA/y/Z4kSfTXpVj68ytcvSRW/N7q5/qRtbN9804jpBAkEA0s6lvH2btSLA mcEdwhs7zAslLbdld7rvfUeP82gPPk0S6yUqTNyikqshM9AwAktHY7WvYdKl+ghZ HTxKVC4DoQJBAOg/IAW5RbXknP+Lf7AVtBgw3E+Yfa3mcdLySe8hjxxyZq825Zmu Rt5Qj4Lw6ifSFNy4kiiSpE/ZCukYvUXGENMCQFkPxSWlS6tzSzuqQxBGwTSrYMG3 wb6b06JyIXcMd6Qym9OMmBpw/J5KfnSNeDr/4uFVWQtTG5xO+pdHaX+3EQECQQDl qcbY4iX1gWVfr2tNjajSYz751yoxVbkpiT9joiQLVXYFvpu+JYEfRzsjmWl0h2Lq AftG8/xYmaEYcMZ6wSrRAkBUwiom98/8wZVlB6qbwhU1EKDFANvICGSWMIhPx3v7 MJqTIj4uJhte2/uyVvZ6DC6noWYgy+kLgqG0S97tUEG8 -----END RSA PRIVATE KEY-----`) var GoproxyCa, goproxyCaErr = tls.X509KeyPair(CA_CERT, CA_KEY) goproxy-1.0/chunked.go000066400000000000000000000032621240375402100150320ustar00rootroot00000000000000// Taken from $GOROOT/src/pkg/net/http/chunked // needed to write https responses to client. package goproxy import ( "io" "strconv" ) // newChunkedWriter returns a new chunkedWriter that translates writes into HTTP // "chunked" format before writing them to w. Closing the returned chunkedWriter // sends the final 0-length chunk that marks the end of the stream. // // newChunkedWriter is not needed by normal applications. The http // package adds chunking automatically if handlers don't set a // Content-Length header. Using newChunkedWriter inside a handler // would result in double chunking or chunking with a Content-Length // length, both of which are wrong. func newChunkedWriter(w io.Writer) io.WriteCloser { return &chunkedWriter{w} } // Writing to chunkedWriter translates to writing in HTTP chunked Transfer // Encoding wire format to the underlying Wire chunkedWriter. type chunkedWriter struct { Wire io.Writer } // Write the contents of data as one chunk to Wire. // NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has // a bug since it does not check for success of io.WriteString func (cw *chunkedWriter) Write(data []byte) (n int, err error) { // Don't send 0-length data. It looks like EOF for chunked encoding. if len(data) == 0 { return 0, nil } head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" if _, err = io.WriteString(cw.Wire, head); err != nil { return 0, err } if n, err = cw.Wire.Write(data); err != nil { return } if n != len(data) { err = io.ErrShortWrite return } _, err = io.WriteString(cw.Wire, "\r\n") return } func (cw *chunkedWriter) Close() error { _, err := io.WriteString(cw.Wire, "0\r\n") return err } goproxy-1.0/counterecryptor.go000066400000000000000000000025361240375402100166630ustar00rootroot00000000000000package goproxy import ( "crypto/aes" "crypto/cipher" "crypto/rsa" "crypto/sha256" "crypto/x509" "errors" ) type CounterEncryptorRand struct { cipher cipher.Block counter []byte rand []byte ix int } func NewCounterEncryptorRandFromKey(key interface{}, seed []byte) (r CounterEncryptorRand, err error) { var keyBytes []byte switch key := key.(type) { case *rsa.PrivateKey: keyBytes = x509.MarshalPKCS1PrivateKey(key) default: err = errors.New("only RSA keys supported") return } h := sha256.New() if r.cipher, err = aes.NewCipher(h.Sum(keyBytes)[:aes.BlockSize]); err != nil { return } r.counter = make([]byte, r.cipher.BlockSize()) if seed != nil { copy(r.counter, h.Sum(seed)[:r.cipher.BlockSize()]) } r.rand = make([]byte, r.cipher.BlockSize()) r.ix = len(r.rand) return } func (c *CounterEncryptorRand) Seed(b []byte) { if len(b) != len(c.counter) { panic("SetCounter: wrong counter size") } copy(c.counter, b) } func (c *CounterEncryptorRand) refill() { c.cipher.Encrypt(c.rand, c.counter) for i := 0; i < len(c.counter); i++ { if c.counter[i]++; c.counter[i] != 0 { break } } c.ix = 0 } func (c *CounterEncryptorRand) Read(b []byte) (n int, err error) { if c.ix == len(c.rand) { c.refill() } if n = len(c.rand) - c.ix; n > len(b) { n = len(b) } copy(b, c.rand[c.ix:c.ix+n]) c.ix += n return } goproxy-1.0/counterecryptor_test.go000066400000000000000000000055461240375402100177260ustar00rootroot00000000000000package goproxy_test import ( "bytes" "crypto/rsa" "encoding/binary" "github.com/elazarl/goproxy" "io" "math" "math/rand" "testing" ) type RandSeedReader struct { r rand.Rand } func (r *RandSeedReader) Read(b []byte) (n int, err error) { for i := range b { b[i] = byte(r.r.Int() & 0xFF) } return len(b), nil } func TestCounterEncDifferentConsecutive(t *testing.T) { k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128) fatalOnErr(err, "rsa.GenerateKey", t) c, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog")) fatalOnErr(err, "NewCounterEncryptorRandFromKey", t) for i := 0; i < 100*1000; i++ { var a, b int64 binary.Read(&c, binary.BigEndian, &a) binary.Read(&c, binary.BigEndian, &b) if a == b { t.Fatal("two consecutive equal int64", a, b) } } } func TestCounterEncIdenticalStreams(t *testing.T) { k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128) fatalOnErr(err, "rsa.GenerateKey", t) c1, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog")) fatalOnErr(err, "NewCounterEncryptorRandFromKey", t) c2, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog")) fatalOnErr(err, "NewCounterEncryptorRandFromKey", t) nout := 1000 out1, out2 := make([]byte, nout), make([]byte, nout) io.ReadFull(&c1, out1) tmp := out2[:] rand.Seed(0xFF43109) for len(tmp) > 0 { n := 1 + rand.Intn(256) if n > len(tmp) { n = len(tmp) } n, err := c2.Read(tmp[:n]) fatalOnErr(err, "CounterEncryptorRand.Read", t) tmp = tmp[n:] } if !bytes.Equal(out1, out2) { t.Error("identical CSPRNG does not produce the same output") } } func stddev(data []int) float64 { var sum, sum_sqr float64 = 0, 0 for _, h := range data { sum += float64(h) sum_sqr += float64(h) * float64(h) } n := float64(len(data)) variance := (sum_sqr - ((sum * sum) / n)) / (n - 1) return math.Sqrt(variance) } func TestCounterEncStreamHistogram(t *testing.T) { k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128) fatalOnErr(err, "rsa.GenerateKey", t) c, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog")) fatalOnErr(err, "NewCounterEncryptorRandFromKey", t) nout := 100 * 1000 out := make([]byte, nout) io.ReadFull(&c, out) refhist := make([]int, 256) for i := 0; i < nout; i++ { refhist[rand.Intn(256)]++ } hist := make([]int, 256) for _, b := range out { hist[int(b)]++ } refstddev, stddev := stddev(refhist), stddev(hist) // due to lack of time, I guestimate t.Logf("ref:%v - act:%v = %v", refstddev, stddev, math.Abs(refstddev-stddev)) if math.Abs(refstddev-stddev) >= 1 { t.Errorf("stddev of ref histogram different than regular PRNG: %v %v", refstddev, stddev) } } goproxy-1.0/ctx.go000066400000000000000000000055171240375402100142140ustar00rootroot00000000000000package goproxy import ( "net/http" "regexp" ) // ProxyCtx is the Proxy context, contains useful information about every request. It is passed to // every user function. Also used as a logger. type ProxyCtx struct { // Will contain the client request from the proxy Req *http.Request // Will contain the remote server's response (if available. nil if the request wasn't send yet) Resp *http.Response RoundTripper RoundTripper // will contain the recent error that occured while trying to send receive or parse traffic Error error // A handle for the user to keep data in the context, from the call of ReqHandler to the // call of RespHandler UserData interface{} // Will connect a request to a response Session int64 proxy *ProxyHttpServer } type RoundTripper interface { RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) } type RoundTripperFunc func(req *http.Request, ctx *ProxyCtx) (*http.Response, error) func (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) { return f(req, ctx) } func (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, error) { if ctx.RoundTripper != nil { return ctx.RoundTripper.RoundTrip(req, ctx) } return ctx.proxy.Tr.RoundTrip(req) } func (ctx *ProxyCtx) printf(msg string, argv ...interface{}) { ctx.proxy.Logger.Printf("[%03d] "+msg+"\n", append([]interface{}{ctx.Session & 0xFF}, argv...)...) } // Logf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter // This message will be printed only if the Verbose field of the ProxyHttpServer is set to true // // proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) *http.Request{ // nr := atomic.AddInt32(&counter,1) // ctx.Printf("So far %d requests",nr) // return r // }) func (ctx *ProxyCtx) Logf(msg string, argv ...interface{}) { if ctx.proxy.Verbose { ctx.printf("INFO: "+msg, argv...) } } // Warnf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter // This message will always be printed. // // proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) *http.Request{ // f,err := os.OpenFile(cachedContent) // if err != nil { // ctx.Warnf("error open file %v: %v",cachedContent,err) // return r // } // return r // }) func (ctx *ProxyCtx) Warnf(msg string, argv ...interface{}) { ctx.printf("WARN: "+msg, argv...) } var charsetFinder = regexp.MustCompile("charset=([^ ;]*)") // Will try to infer the character set of the request from the headers. // Returns the empty string if we don't know which character set it used. // Currently it will look for charset= in the Content-Type header of the request. func (ctx *ProxyCtx) Charset() string { charsets := charsetFinder.FindStringSubmatch(ctx.Resp.Header.Get("Content-Type")) if charsets == nil { return "" } return charsets[1] } goproxy-1.0/dispatcher.go000066400000000000000000000301221240375402100155320ustar00rootroot00000000000000package goproxy import ( "bytes" "io/ioutil" "net" "net/http" "regexp" "strings" ) // ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request // before sending it to the remote server type ReqCondition interface { RespCondition HandleReq(req *http.Request, ctx *ProxyCtx) bool } // RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response // before sending it to the proxy client. Note that resp might be nil, in case there was an // error sending the request. type RespCondition interface { HandleResp(resp *http.Response, ctx *ProxyCtx) bool } // ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx) type ReqConditionFunc func(req *http.Request, ctx *ProxyCtx) bool // RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx) type RespConditionFunc func(resp *http.Response, ctx *ProxyCtx) bool func (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) bool { return c(req, ctx) } // ReqConditionFunc cannot test responses. It only satisfies RespCondition interface so that // to be usable as RespCondition. func (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { return c(ctx.Req, ctx) } func (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { return c(resp, ctx) } // UrlHasPrefix returns a ReqCondtion checking wether the destination URL the proxy client has requested // has the given prefix, with or without the host. // For example UrlHasPrefix("host/x") will match requests of the form 'GET host/x', and will match // requests to url 'http://host/x' func UrlHasPrefix(prefix string) ReqConditionFunc { return func(req *http.Request, ctx *ProxyCtx) bool { return strings.HasPrefix(req.URL.Path, prefix) || strings.HasPrefix(req.URL.Host+"/"+req.URL.Path, prefix) || strings.HasPrefix(req.URL.Scheme+req.URL.Host+req.URL.Path, prefix) } } // UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings // with or without the host prefix. // UrlIs("google.com/","foo") will match requests 'GET /' to 'google.com', requests `'GET google.com/' to // any host, and requests of the form 'GET foo'. func UrlIs(urls ...string) ReqConditionFunc { urlSet := make(map[string]bool) for _, u := range urls { urlSet[u] = true } return func(req *http.Request, ctx *ProxyCtx) bool { _, pathOk := urlSet[req.URL.Path] _, hostAndOk := urlSet[req.URL.Host+req.URL.Path] return pathOk || hostAndOk } } // ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches // any of the given regular expressions. func ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc { return func(req *http.Request, ctx *ProxyCtx) bool { for _, re := range regexps { if re.MatchString(req.Host) { return true } } return false } } // ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal // to one of the given strings func ReqHostIs(hosts ...string) ReqConditionFunc { hostSet := make(map[string]bool) for _, h := range hosts { hostSet[h] = true } return func(req *http.Request, ctx *ProxyCtx) bool { _, ok := hostSet[req.URL.Host] return ok } } var localHostIpv4 = regexp.MustCompile(`127\.0\.0\.\d+`) // IsLocalHost checks whether the destination host is explicitly local host // (buggy, there can be IPv6 addresses it doesn't catch) var IsLocalHost ReqConditionFunc = func(req *http.Request, ctx *ProxyCtx) bool { return req.URL.Host == "::1" || req.URL.Host == "0:0:0:0:0:0:0:1" || localHostIpv4.MatchString(req.URL.Host) || req.URL.Host == "localhost" } // UrlMatches returns a ReqCondition testing whether the destination URL // of the request matches the given regexp, with or without prefix func UrlMatches(re *regexp.Regexp) ReqConditionFunc { return func(req *http.Request, ctx *ProxyCtx) bool { return re.MatchString(req.URL.Path) || re.MatchString(req.URL.Host+req.URL.Path) } } // DstHostIs returns a ReqCondtion testing wether the host in the request url is the given string func DstHostIs(host string) ReqConditionFunc { return func(req *http.Request, ctx *ProxyCtx) bool { return req.URL.Host == host } } // SrcIpIs returns a ReqCondtion testing wether the source IP of the request is the given string func SrcIpIs(ip string) ReqCondition { return ReqConditionFunc(func(req *http.Request, ctx *ProxyCtx) bool { return strings.HasPrefix(req.RemoteAddr, ip+":") }) } // Not returns a ReqCondtion negating the given ReqCondition func Not(r ReqCondition) ReqConditionFunc { return func(req *http.Request, ctx *ProxyCtx) bool { return !r.HandleReq(req, ctx) } } // ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal // to one of the given strings. func ContentTypeIs(typ string, types ...string) RespCondition { types = append(types, typ) return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool { if resp == nil { return false } contentType := resp.Header.Get("Content-Type") for _, typ := range types { if contentType == typ || strings.HasPrefix(contentType, typ+";") { return true } } return false }) } // ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions. // You will use the ReqProxyConds struct to register a ReqHandler, that would filter // the request, only if all the given ReqCondition matched. // Typical usage: // proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...) func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds { return &ReqProxyConds{proxy, conds} } // ReqProxyConds aggregate ReqConditions for a ProxyHttpServer. Upon calling Do, it will register a ReqHandler that would // handle the request if all conditions on the HTTP request are met. type ReqProxyConds struct { proxy *ProxyHttpServer reqConds []ReqCondition } // DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f)) func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)) { pcond.Do(FuncReqHandler(f)) } // ReqProxyConds.Do will register the ReqHandler on the proxy, // the ReqHandler will handle the HTTP request if all the conditions // aggregated in the ReqProxyConds are met. Typical usage: // proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy // proxy.OnRequest(cond1,cond2).Do(handler) // // given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true // // if they are, will call handler.Handle(req,ctx) func (pcond *ReqProxyConds) Do(h ReqHandler) { pcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers, FuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { for _, cond := range pcond.reqConds { if !cond.HandleReq(r, ctx) { return r, nil } } return h.Handle(r, ctx) })) } // HandleConnect is used when proxy receives an HTTP CONNECT request, // it'll then use the HttpsHandler to determine what should it // do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction // struct returned will determine what to do with this request. ConnectAccept will simply accept the request // forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the // client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man // in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped // connection. // The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy // will use the default tls configuration. // proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) { pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { for _, cond := range pcond.reqConds { if !cond.HandleReq(ctx.Req, ctx) { return nil, "" } } return h.HandleConnect(host, ctx) })) } // HandleConnectFunc is equivalent to HandleConnect, // for example, accepting CONNECT request if they contain a password in header // io.WriteString(h,password) // passHash := h.Sum(nil) // proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { // c := sha1.New() // io.WriteString(c,ctx.Req.Header.Get("X-GoProxy-Auth")) // if c.Sum(nil) == passHash { // return OkConnect, host // } // return RejectConnect, host // }) func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string)) { pcond.HandleConnect(FuncHttpsHandler(f)) } func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) { pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { for _, cond := range pcond.reqConds { if !cond.HandleReq(ctx.Req, ctx) { return nil, "" } } return &ConnectAction{Action: ConnectHijack, Hijack: f}, host })) } // ProxyConds is used to aggregate RespConditions for a ProxyHttpServer. // Upon calling ProxyConds.Do, it will register a RespHandler that would // handle the HTTP response from remote server if all conditions on the HTTP response are met. type ProxyConds struct { proxy *ProxyHttpServer reqConds []ReqCondition respCond []RespCondition } // ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f)) func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response) { pcond.Do(FuncRespHandler(f)) } // ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every // request that matches the conditions aggregated in pcond. func (pcond *ProxyConds) Do(h RespHandler) { pcond.proxy.respHandlers = append(pcond.proxy.respHandlers, FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { for _, cond := range pcond.reqConds { if !cond.HandleReq(ctx.Req, ctx) { return resp } } for _, cond := range pcond.respCond { if !cond.HandleResp(resp, ctx) { return resp } } return h.Handle(resp, ctx) })) } // OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is // proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used // // if cond1.HandleResp(resp) && cond2.HandleResp(resp) func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds { return &ProxyConds{proxy, make([]ReqCondition, 0), conds} } // AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to // eavesdrop all https connections to www.google.com, we can use // proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm) var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { return MitmConnect, host } // AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow // connections to hosts on any other port than 443 // proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))). // HandleConnect(goproxy.AlwaysReject) var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { return RejectConnect, host } // HandleBytes will return a RespHandler that read the entire body of the request // to a byte array in memory, would run the user supplied f function on the byte arra, // and will replace the body of the original response with the resulting byte array. func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler { return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { b, err := ioutil.ReadAll(resp.Body) if err != nil { ctx.Warnf("Cannot read response %s", err) return resp } resp.Body.Close() resp.Body = ioutil.NopCloser(bytes.NewBuffer(f(b, ctx))) return resp }) } goproxy-1.0/doc.go000066400000000000000000000070441240375402100141600ustar00rootroot00000000000000/* Package goproxy provides a customizable HTTP proxy, supporting hijacking HTTPS connection. The intent of the proxy, is to be usable with reasonable amount of traffic yet, customizable and programable. The proxy itself is simply an `net/http` handler. Typical usage is proxy := goproxy.NewProxyHttpServer() proxy.OnRequest(..conditions..).Do(..requesthandler..) proxy.OnRequest(..conditions..).DoFunc(..requesthandlerFunction..) proxy.OnResponse(..conditions..).Do(..responesHandler..) proxy.OnResponse(..conditions..).DoFunc(..responesHandlerFunction..) http.ListenAndServe(":8080", proxy) Adding a header to each request proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx)*http.Request{ r.Header.Set("X-GoProxy","1") return r }) Note that the function is called before the proxy sends the request to the server For printing the content type of all incoming responses proxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{ println(ctx.Req.Host,"->",r.Header.Get("Content-Type")) return r }) note that we used the ProxyCtx context variable here. It contains the request and the response (Req and Resp, Resp is nil if unavailable) of this specific client interaction with the proxy. To print the content type of all responses from a certain url, we'll add a ReqCondition to the OnResponse function: proxy.OnResponse(goproxy.UrlIs("golang.org/pkg")).DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{ println(ctx.Req.Host,"->",r.Header.Get("Content-Type")) return r }) We can write the condition ourselves, conditions can be set on request and on response var random = ReqConditionFunc(func(r *http.Request) bool { return rand.Intn(1) == 0 }) var hasGoProxyHeader = RespConditionFunc(func(resp *http.Response,req *http.Request)bool { return resp.Header.Get("X-GoProxy") != "" }) Caution! If you give a RespCondition to the OnRequest function, you'll get a run time panic! It doesn't make sense to read the response, if you still haven't got it! Finally, we have convenience function to throw a quick response proxy.OnResponse(hasGoProxyHeader).DoFunc(func(r*http.Response,ctx *goproxy.ProxyCtx)*http.Response { r.Body.Close() return goproxy.ForbiddenTextResponse(ctx.Req,"Can't see response with X-GoProxy header!") }) we close the body of the original repsonse, and return a new 403 response with a short message. Example use cases: 1. https://github.com/elazarl/goproxy/tree/master/examples/avgSize To measure the average size of an Html served in your site. One can ask all the QA team to access the website by a proxy, and the proxy will measure the average size of all text/html responses from your host. 2. [not yet implemented] All requests to your web servers should be directed through the proxy, when the proxy will detect html pieces sent as a response to AJAX request, it'll send a warning email. 3. https://github.com/elazarl/goproxy/blob/master/examples/httpdump/ Generate a real traffic to your website by real users using through proxy. Record the traffic, and try it again for more real load testing. 4. https://github.com/elazarl/goproxy/tree/master/examples/noRedditAtWorktime Will allow browsing to reddit.com between 8:00am and 17:00pm 5. https://github.com/elazarl/goproxy/tree/master/examples/jqueryVersion Will warn if multiple versions of jquery are used in the same domain. 6. https://github.com/elazarl/goproxy/blob/master/examples/upside-down-ternet/ Modifies image files in an HTTP response via goproxy's image extension found in ext/. */ package goproxy goproxy-1.0/examples/000077500000000000000000000000001240375402100146755ustar00rootroot00000000000000goproxy-1.0/examples/avgSize/000077500000000000000000000000001240375402100163055ustar00rootroot00000000000000goproxy-1.0/examples/avgSize/main.go000066400000000000000000000022021240375402100175540ustar00rootroot00000000000000package main import ( "github.com/elazarl/goproxy" "github.com/elazarl/goproxy/ext/html" "io" "log" . "net/http" "time" ) type Count struct { Id string Count int64 } type CountReadCloser struct { Id string R io.ReadCloser ch chan<- Count nr int64 } func (c *CountReadCloser) Read(b []byte) (n int, err error) { n, err = c.R.Read(b) c.nr += int64(n) return } func (c CountReadCloser) Close() error { c.ch <- Count{c.Id, c.nr} return c.R.Close() } func main() { proxy := goproxy.NewProxyHttpServer() //proxy.Verbose = true timer := make(chan bool) ch := make(chan Count, 10) go func() { for { time.Sleep(time.Minute * 2) timer <- true } }() go func() { m := make(map[string]int64) for { select { case c := <-ch: m[c.Id] = m[c.Id] + c.Count case <-timer: println("statistics") for k, v := range m { println(k, "->", v) } } } }() proxy.OnResponse(goproxy_html.IsWebRelatedText).DoFunc(func(resp *Response, ctx *goproxy.ProxyCtx) *Response { resp.Body = &CountReadCloser{ctx.Req.URL.String(), resp.Body, ch, 0} return resp }) log.Fatal(ListenAndServe(":8080", proxy)) } goproxy-1.0/examples/basic/000077500000000000000000000000001240375402100157565ustar00rootroot00000000000000goproxy-1.0/examples/basic/main.go000066400000000000000000000005621240375402100172340ustar00rootroot00000000000000package main import ( "github.com/elazarl/goproxy" "log" "flag" "net/http" ) func main() { verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") addr := flag.String("addr", ":8080", "proxy listen address") flag.Parse() proxy := goproxy.NewProxyHttpServer() proxy.Verbose = *verbose log.Fatal(http.ListenAndServe(*addr, proxy)) } goproxy-1.0/examples/eavesdropper/000077500000000000000000000000001240375402100173745ustar00rootroot00000000000000goproxy-1.0/examples/eavesdropper/main.go000066400000000000000000000026331240375402100206530ustar00rootroot00000000000000package main import ( "bufio" "log" "net" "net/http" "regexp" "github.com/elazarl/goproxy" ) func orPanic(err error) { if err != nil { panic(err) } } func main() { proxy := goproxy.NewProxyHttpServer() proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*baidu.com$"))). HandleConnect(goproxy.AlwaysReject) proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))). HandleConnect(goproxy.AlwaysMitm) // enable curl -p for all hosts on port 80 proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:80$"))). HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) { defer func() { if e := recover(); e != nil { ctx.Logf("error connecting to remote: %v", e) client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n")) } client.Close() }() clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client)) remote, err := net.Dial("tcp", req.URL.Host) orPanic(err) remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote)) for { req, err := http.ReadRequest(clientBuf.Reader) orPanic(err) orPanic(req.Write(remoteBuf)) orPanic(remoteBuf.Flush()) resp, err := http.ReadResponse(remoteBuf.Reader, req) orPanic(err) orPanic(resp.Write(clientBuf.Writer)) orPanic(clientBuf.Flush()) } }) proxy.Verbose = true log.Fatal(http.ListenAndServe(":8080", proxy)) } goproxy-1.0/examples/httpdump/000077500000000000000000000000001240375402100165425ustar00rootroot00000000000000goproxy-1.0/examples/httpdump/httpdump.go000066400000000000000000000134521240375402100207430ustar00rootroot00000000000000package main import ( "errors" "flag" "fmt" "io" "log" "net" "net/http" "net/http/httputil" "os" "os/signal" "path" "sync" "time" "github.com/elazarl/goproxy" "github.com/elazarl/goproxy/transport" ) type FileStream struct { path string f *os.File } func NewFileStream(path string) *FileStream { return &FileStream{path, nil} } func (fs *FileStream) Write(b []byte) (nr int, err error) { if fs.f == nil { fs.f, err = os.Create(fs.path) if err != nil { return 0, err } } return fs.f.Write(b) } func (fs *FileStream) Close() error { fmt.Println("Close", fs.path) if fs.f == nil { return errors.New("FileStream was never written into") } return fs.f.Close() } type Meta struct { req *http.Request resp *http.Response err error t time.Time sess int64 bodyPath string from string } func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...interface{}) { if *err != nil { return } var n int n, *err = fmt.Fprintf(w, pat, a...) *nr += int64(n) } func write(nr *int64, err *error, w io.Writer, b []byte) { if *err != nil { return } var n int n, *err = w.Write(b) *nr += int64(n) } func (m *Meta) WriteTo(w io.Writer) (nr int64, err error) { if m.req != nil { fprintf(&nr, &err, w, "Type: request\r\n") } else if m.resp != nil { fprintf(&nr, &err, w, "Type: response\r\n") } fprintf(&nr, &err, w, "ReceivedAt: %v\r\n", m.t) fprintf(&nr, &err, w, "Session: %d\r\n", m.sess) fprintf(&nr, &err, w, "From: %v\r\n", m.from) if m.err != nil { // note the empty response fprintf(&nr, &err, w, "Error: %v\r\n\r\n\r\n\r\n", m.err) } else if m.req != nil { fprintf(&nr, &err, w, "\r\n") buf, err2 := httputil.DumpRequest(m.req, false) if err2 != nil { return nr, err2 } write(&nr, &err, w, buf) } else if m.resp != nil { fprintf(&nr, &err, w, "\r\n") buf, err2 := httputil.DumpResponse(m.resp, false) if err2 != nil { return nr, err2 } write(&nr, &err, w, buf) } return } type HttpLogger struct { path string c chan *Meta errch chan error } func NewLogger(basepath string) (*HttpLogger, error) { f, err := os.Create(path.Join(basepath, "log")) if err != nil { return nil, err } logger := &HttpLogger{basepath, make(chan *Meta), make(chan error)} go func() { for m := range logger.c { if _, err := m.WriteTo(f); err != nil { log.Println("Can't write meta", err) } } logger.errch <- f.Close() }() return logger, nil } func (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.ProxyCtx) { body := path.Join(logger.path, fmt.Sprintf("%d_resp", ctx.Session)) from := "" if ctx.UserData != nil { from = ctx.UserData.(*transport.RoundTripDetails).TCPAddr.String() } if resp == nil { resp = emptyResp } else { resp.Body = NewTeeReadCloser(resp.Body, NewFileStream(body)) } logger.LogMeta(&Meta{ resp: resp, err: ctx.Error, t: time.Now(), sess: ctx.Session, from: from}) } var emptyResp = &http.Response{} var emptyReq = &http.Request{} func (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.ProxyCtx) { body := path.Join(logger.path, fmt.Sprintf("%d_req", ctx.Session)) if req == nil { req = emptyReq } else { req.Body = NewTeeReadCloser(req.Body, NewFileStream(body)) } logger.LogMeta(&Meta{ req: req, err: ctx.Error, t: time.Now(), sess: ctx.Session, from: req.RemoteAddr}) } func (logger *HttpLogger) LogMeta(m *Meta) { logger.c <- m } func (logger *HttpLogger) Close() error { close(logger.c) return <-logger.errch } type TeeReadCloser struct { r io.Reader w io.WriteCloser c io.Closer } func NewTeeReadCloser(r io.ReadCloser, w io.WriteCloser) io.ReadCloser { return &TeeReadCloser{io.TeeReader(r, w), w, r} } func (t *TeeReadCloser) Read(b []byte) (int, error) { return t.r.Read(b) } func (t *TeeReadCloser) Close() error { err1 := t.c.Close() err2 := t.w.Close() if err1 == nil && err2 == nil { return nil } if err1 != nil { return err2 } return err1 } type stoppableListener struct { net.Listener sync.WaitGroup } type stoppableConn struct { net.Conn wg *sync.WaitGroup } func newStoppableListener(l net.Listener) *stoppableListener { return &stoppableListener{l, sync.WaitGroup{}} } func (sl *stoppableListener) Accept() (net.Conn, error) { c, err := sl.Listener.Accept() if err != nil { return c, err } sl.Add(1) return &stoppableConn{c, &sl.WaitGroup}, nil } func (sc *stoppableConn) Close() error { sc.wg.Done() return sc.Conn.Close() } func main() { verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") addr := flag.String("l", ":8080", "on which address should the proxy listen") flag.Parse() proxy := goproxy.NewProxyHttpServer() proxy.Verbose = *verbose if err := os.MkdirAll("db", 0755); err != nil { log.Fatal("Can't create dir", err) } logger, err := NewLogger("db") if err != nil { log.Fatal("can't open log file", err) } tr := transport.Transport{Proxy: transport.ProxyFromEnvironment} proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { ctx.RoundTripper = goproxy.RoundTripperFunc(func (req *http.Request, ctx *goproxy.ProxyCtx) (resp *http.Response, err error) { ctx.UserData, resp, err = tr.DetailedRoundTrip(req) return }) logger.LogReq(req, ctx) return req, nil }) proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { logger.LogResp(resp, ctx) return resp }) l, err := net.Listen("tcp", *addr) if err != nil { log.Fatal("listen:", err) } sl := newStoppableListener(l) ch := make(chan os.Signal) signal.Notify(ch, os.Interrupt) go func() { <-ch log.Println("Got SIGINT exiting") sl.Add(1) sl.Close() logger.Close() sl.Done() }() log.Println("Starting Proxy") http.Serve(sl, proxy) sl.Wait() log.Println("All connections closed - exit") } goproxy-1.0/examples/jqueryVersion/000077500000000000000000000000001240375402100175625ustar00rootroot00000000000000goproxy-1.0/examples/jqueryVersion/jquery1.html000066400000000000000000000001351240375402100220470ustar00rootroot00000000000000 goproxy-1.0/examples/jqueryVersion/jquery2.html000066400000000000000000000001351240375402100220500ustar00rootroot00000000000000 goproxy-1.0/examples/jqueryVersion/jquery_homepage.html000066400000000000000000000322431240375402100236400ustar00rootroot00000000000000 jQuery: The Write Less, Do More, JavaScript Library

jQuery is a new kind of JavaScript Library.

jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.

Grab the latest version!

Choose your compression level:
jquery-1.7.2.min.js jquery-1.7.2.js

Current Release: v1.7.2

Learn jQuery Now!

What does jQuery code look like? Here's the quick and dirty:

$("p.neat").addClass("ohmy").show("slow");
Run Code

Congratulations! You just ran a snippet of jQuery code. Wasn't that easy? There's lots of example code throughout the documentation on this site. Be sure to give all the code a test run to see what happens.

Books About jQuery

goproxy-1.0/examples/jqueryVersion/jquery_test.go000066400000000000000000000065671240375402100225050ustar00rootroot00000000000000package main import ( "bytes" "io/ioutil" "log" "net/http" "net/http/httptest" "net/url" "strings" "testing" ) func equal(u, v []string) bool { if len(u) != len(v) { return false } for i, _ := range u { if u[i] != v[i] { return false } } return true } func readFile(fname string, t *testing.T) string { b, err := ioutil.ReadFile(fname) if err != nil { t.Fatal("readFile", err) } return string(b) } func TestDefectiveScriptParser(t *testing.T) { if l := len(findScriptSrc(` `)); l != 0 { t.Fail() } urls := findScriptSrc(readFile("w3schools.html", t)) if !equal(urls, []string{"http://partner.googleadservices.com/gampad/google_service.js", "//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"}) { t.Error("w3schools.html", "src scripts are not recognized", urls) } urls = findScriptSrc(readFile("jquery_homepage.html", t)) if !equal(urls, []string{"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js", "http://code.jquery.com/jquery-1.4.2.min.js", "http://static.jquery.com/files/rocker/scripts/custom.js", "http://static.jquery.com/donate/donate.js"}) { t.Error("jquery_homepage.html", "src scripts are not recognized", urls) } } func get(url string, t *testing.T) { } func proxyWithLog() (*http.Client, *bytes.Buffer) { proxy := NewJqueryVersionProxy() proxyServer := httptest.NewServer(proxy) buf := new(bytes.Buffer) proxy.Logger = log.New(buf, "", 0) proxyUrl, _ := url.Parse(proxyServer.URL) tr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)} client := &http.Client{Transport: tr} return client, buf } func TestProxyServiceTwoVersions(t *testing.T) { var fs = httptest.NewServer(http.FileServer(http.Dir("."))) defer fs.Close() client, buf := proxyWithLog() get := func(url string) { resp, err := client.Get(fs.URL + url) if err != nil { t.Fatal("Cannot get proxy", err) } ioutil.ReadAll(resp.Body) resp.Body.Close() } get("/w3schools.html") get("/php_man.html") if buf.String() != "" { t.Error("shouldn't warn on a single URL", buf.String()) } get("/jquery1.html") warnings := buf.String() if !strings.Contains(warnings, "http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js") || !strings.Contains(warnings, "jquery.1.4.js") || !strings.Contains(warnings, "Contradicting") { t.Error("contadicting jquery versions (php_man.html, w3schools.html) does not issue warning", warnings) } } func TestProxyService(t *testing.T) { var fs = httptest.NewServer(http.FileServer(http.Dir("."))) defer fs.Close() client, buf := proxyWithLog() get := func(url string) { resp, err := client.Get(fs.URL + url) if err != nil { t.Fatal("Cannot get proxy", err) } ioutil.ReadAll(resp.Body) resp.Body.Close() } get("/jquery_homepage.html") warnings := buf.String() if !strings.Contains(warnings, "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js") || !strings.Contains(warnings, "http://code.jquery.com/jquery-1.4.2.min.js") || !strings.Contains(warnings, "Contradicting") { t.Error("contadicting jquery versions does not issue warning") } } goproxy-1.0/examples/jqueryVersion/main.go000066400000000000000000000027721240375402100210450ustar00rootroot00000000000000package main import ( "github.com/elazarl/goproxy" "github.com/elazarl/goproxy/ext/html" "log" "net/http" "regexp" ) func findScriptSrc(html string) []string { // who said we can't parse HTML with regexp? scriptMatcher := regexp.MustCompile(`(?i:]*\ssrc=["']([^"']*)["'])`) srcs := make([]string, 0) matches := scriptMatcher.FindAllStringIndex(html, -1) for _, match := range matches { //println("Match",html[match[0]:match[1]]) // -1 to capture the whitespace at the end of the script tag srcMatch := srcAttrMatcher.FindStringSubmatch(html[match[1]-1:]) if srcMatch != nil { srcs = append(srcs, srcMatch[1]) } } return srcs } func NewJqueryVersionProxy() *goproxy.ProxyHttpServer { proxy := goproxy.NewProxyHttpServer() m := make(map[string]string) jqueryMatcher := regexp.MustCompile(`(?i:jquery\.)`) proxy.OnResponse(goproxy_html.IsHtml).Do(goproxy_html.HandleString( func(s string, ctx *goproxy.ProxyCtx) string { //ctx.Warnf("Charset %v by %v",ctx.Charset(),ctx.Req.Header["Content-Type"]) for _, src := range findScriptSrc(s) { if jqueryMatcher.MatchString(src) { prev, ok := m[ctx.Req.Host] if ok && prev != src { ctx.Warnf("In %v, Contradicting jqueries %v %v", ctx.Req.URL, prev, src) break } m[ctx.Req.Host] = src } } return s })) return proxy } func main() { proxy := NewJqueryVersionProxy() //proxy.Verbose = true log.Fatal(http.ListenAndServe(":8080", proxy)) } goproxy-1.0/examples/jqueryVersion/php_man.html000066400000000000000000000502461240375402100221010ustar00rootroot00000000000000 PHP: PHP Manual - Manual

search for in the

 
[edit] Last updated: Fri, 23 Mar 2012

view this page in

PHP Manual

by:
Mehdi Achour
Friedhelm Betz
Antony Dovgal
Nuno Lopes
Hannes Magnusson
Georg Richter
Damien Seguy
Jakub Vrana
2012-03-23
Edited By: Philip Olson


add a note add a note User Contributed Notes PHP Manual
There are no user contributed notes for this page.

 
goproxy-1.0/examples/jqueryVersion/w3schools.html000066400000000000000000001046041240375402100224010ustar00rootroot00000000000000 HTML5 Tutorial
W3Schools.com

HTML5 Tutorial


HTML5 is The New HTML Standard

HTML5

  • New Elements
  • New Attributes
  • Full CSS3 Support
  • Video and Audio
  • 2D/3D Graphics
  • Local Storage
  • Local SQL Database
  • Web Applications

Examples in Each Chapter

With our HTML editor, you can edit the HTML, and click on a button to view the result.

Example

<!DOCTYPE HTML>
<html>
<body>

<video width="320" height="240" controls="controls">
  <source src="movie.mp4" type="video/mp4" />
  <source src="movie.ogg" type="video/ogg" />
  <source src="movie.webm" type="video/webm" />
Your browser does not support the video tag.
</video>

</body>
</html>

Try it yourself »

Click on the "Try it yourself" button to see how it works

Start learning HTML5 now!

HTML5 References

At W3Schools you will find complete references about tags, global attributes, standard events, and more.

HTML5 Tag Reference


WEB HOSTING
Best Web Hosting
PHP MySQL Hosting
Best Hosting Coupons
UK Reseller Hosting
Cloud Hosting
Top Web Hosting
$3.98 Unlimited Hosting
Premium Website Design
WEB BUILDING
Download XML Editor
FREE Website BUILDER
Free Website Templates Free CSS Templates
CREATE HTML Websites
W3SCHOOLS EXAMS
Get Certified in:
HTML, CSS, JavaScript, XML, PHP, and ASP
W3SCHOOLS BOOKS
New Books:
HTML, CSS
JavaScript, and Ajax
STATISTICS
Browser Statistics
Browser OS
Browser Display
SHARE THIS PAGE


goproxy-1.0/examples/noRedditAtWorktime/000077500000000000000000000000001240375402100204545ustar00rootroot00000000000000goproxy-1.0/examples/noRedditAtWorktime/noRedditAtWorktime.go000066400000000000000000000010271240375402100245620ustar00rootroot00000000000000package main import ( "github.com/elazarl/goproxy" "log" "net/http" "time" ) func main() { proxy := goproxy.NewProxyHttpServer() proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc( func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { if h, _, _ := time.Now().Clock(); h >= 8 && h <= 17 { return r, goproxy.NewResponse(r, goproxy.ContentTypeText, http.StatusForbidden, "Don't waste your time!") } return r, nil }) log.Fatalln(http.ListenAndServe(":8080", proxy)) } goproxy-1.0/examples/sokeepalive/000077500000000000000000000000001240375402100172045ustar00rootroot00000000000000goproxy-1.0/examples/sokeepalive/sokeepalive.go000066400000000000000000000010751240375402100220450ustar00rootroot00000000000000package main import ( "github.com/elazarl/goproxy" "log" "flag" "net" "net/http" ) func main() { verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") addr := flag.String("addr", ":8080", "proxy listen address") flag.Parse() proxy := goproxy.NewProxyHttpServer() proxy.Tr.Dial = func(network, addr string) (c net.Conn, err error) { c, err = net.Dial(network, addr) if c, ok := c.(*net.TCPConn); err != nil && ok { c.SetKeepAlive(true) } return } proxy.Verbose = *verbose log.Fatal(http.ListenAndServe(*addr, proxy)) } goproxy-1.0/examples/sslstrip/000077500000000000000000000000001240375402100165605ustar00rootroot00000000000000goproxy-1.0/examples/sslstrip/sslstrip.go000066400000000000000000000011511240375402100207700ustar00rootroot00000000000000package main import ( "github.com/elazarl/goproxy" "log" "flag" "net/http" ) func main() { verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") addr := flag.String("addr", ":8080", "proxy listen address") flag.Parse() proxy := goproxy.NewProxyHttpServer() proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) proxy.OnRequest().DoFunc(func (req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { if req.URL.Scheme == "https" { req.URL.Scheme = "http" } return req, nil }) proxy.Verbose = *verbose log.Fatal(http.ListenAndServe(*addr, proxy)) } goproxy-1.0/examples/upside-down-ternet/000077500000000000000000000000001240375402100204325ustar00rootroot00000000000000goproxy-1.0/examples/upside-down-ternet/main.go000066400000000000000000000010711240375402100217040ustar00rootroot00000000000000package main import ( "github.com/elazarl/goproxy" "github.com/elazarl/goproxy/ext/image" "image" "log" "net/http" ) func main() { proxy := goproxy.NewProxyHttpServer() proxy.OnResponse().Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image { dx, dy := img.Bounds().Dx(), img.Bounds().Dy() nimg := image.NewRGBA(img.Bounds()) for i := 0; i < dx; i++ { for j := 0; j <= dy; j++ { nimg.Set(i, j, img.At(i, dy-j-1)) } } return nimg })) proxy.Verbose = true log.Fatal(http.ListenAndServe(":8080", proxy)) } goproxy-1.0/examples/yui-minify/000077500000000000000000000000001240375402100167745ustar00rootroot00000000000000goproxy-1.0/examples/yui-minify/yui.go000066400000000000000000000064051240375402100201360ustar00rootroot00000000000000// This example would minify standalone Javascript files (identified by their content type) // using the command line utility YUI compressor http://yui.github.io/yuicompressor/ // Example usage: // // ./yui -java /usr/local/bin/java -yuicompressor ~/Downloads/yuicompressor-2.4.8.jar // $ curl -vx localhost:8080 http://golang.org/lib/godoc/godocs.js // (function(){function g(){var u=$("#search");if(u.length===0){return}function t(){if(.... // $ curl http://golang.org/lib/godoc/godocs.js | head -n 3 // // Copyright 2012 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 main import ( "flag" "io" "io/ioutil" "log" "net/http" "os" "os/exec" "path" "strings" "github.com/elazarl/goproxy" ) func main() { verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") addr := flag.String("addr", ":8080", "proxy listen address") java := flag.String("javapath", "java", "where the Java executable is located") yuicompressor := flag.String("yuicompressor", "", "where the yuicompressor is located, assumed to be in CWD") yuicompressordir := flag.String("yuicompressordir", ".", "a folder to search yuicompressor in, will be ignored if yuicompressor is set") flag.Parse() if *yuicompressor == "" { files, err := ioutil.ReadDir(*yuicompressordir) if err != nil { log.Fatal("Cannot find yuicompressor jar") } for _, file := range files { if strings.HasPrefix(file.Name(), "yuicompressor") && strings.HasSuffix(file.Name(), ".jar") { c := path.Join(*yuicompressordir, file.Name()) yuicompressor = &c break } } } if *yuicompressor == "" { log.Fatal("Can't find yuicompressor jar, searched yuicompressor*.jar in dir ", *yuicompressordir) } if _, err := os.Stat(*yuicompressor); os.IsNotExist(err) { log.Fatal("Can't find yuicompressor jar specified ", *yuicompressor) } proxy := goproxy.NewProxyHttpServer() proxy.Verbose = *verbose proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { contentType := resp.Header.Get("Content-Type") if contentType == "application/javascript" || contentType == "application/x-javascript" { // in real code, response should be streamed as well var err error cmd := exec.Command(*java, "-jar", *yuicompressor, "--type", "js") cmd.Stdin = resp.Body resp.Body, err = cmd.StdoutPipe() if err != nil { ctx.Warnf("Cannot minify content in %v: %v", ctx.Req.URL, err) return goproxy.TextResponse(ctx.Req, "Error getting stdout pipe") } stderr, err := cmd.StderrPipe() if err != nil { ctx.Logf("Error obtaining stderr from yuicompress: %s", err) return goproxy.TextResponse(ctx.Req, "Error getting stderr pipe") } if err := cmd.Start(); err != nil { ctx.Warnf("Cannot minify content in %v: %v", ctx.Req.URL, err) } go func() { defer stderr.Close() const kb = 1024 msg, err := ioutil.ReadAll(&io.LimitedReader{stderr, 50 * kb}) if len(msg) != 0 { ctx.Logf("Error executing yuicompress: %s", string(msg)) } if err != nil { ctx.Logf("Error reading stderr from yuicompress: %s", string(msg)) } }() } return resp }) log.Fatal(http.ListenAndServe(*addr, proxy)) } goproxy-1.0/ext/000077500000000000000000000000001240375402100136575ustar00rootroot00000000000000goproxy-1.0/ext/auth/000077500000000000000000000000001240375402100146205ustar00rootroot00000000000000goproxy-1.0/ext/auth/basic.go000066400000000000000000000046351240375402100162400ustar00rootroot00000000000000package auth import ( "bytes" "encoding/base64" "io/ioutil" "net/http" "strings" "github.com/elazarl/goproxy" ) var unauthorizedMsg = []byte("407 Proxy Authentication Required") func BasicUnauthorized(req *http.Request, realm string) *http.Response { // TODO(elazar): verify realm is well formed return &http.Response{ StatusCode: 407, ProtoMajor: 1, ProtoMinor: 1, Request: req, Header: http.Header{"Proxy-Authenticate": []string{"Basic realm=" + realm}}, Body: ioutil.NopCloser(bytes.NewBuffer(unauthorizedMsg)), ContentLength: int64(len(unauthorizedMsg)), } } var proxyAuthorizatonHeader = "Proxy-Authorization" func auth(req *http.Request, f func(user, passwd string) bool) bool { authheader := strings.SplitN(req.Header.Get(proxyAuthorizatonHeader), " ", 2) req.Header.Del(proxyAuthorizatonHeader) if len(authheader) != 2 || authheader[0] != "Basic" { return false } userpassraw, err := base64.StdEncoding.DecodeString(authheader[1]) if err != nil { return false } userpass := strings.SplitN(string(userpassraw), ":", 2) if len(userpass) != 2 { return false } return f(userpass[0], userpass[1]) } // Basic returns a basic HTTP authentication handler for requests // // You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities func Basic(realm string, f func(user, passwd string) bool) goproxy.ReqHandler { return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { if !auth(req, f) { return nil, BasicUnauthorized(req, realm) } return req, nil }) } // BasicConnect returns a basic HTTP authentication handler for CONNECT requests // // You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities func BasicConnect(realm string, f func(user, passwd string) bool) goproxy.HttpsHandler { return goproxy.FuncHttpsHandler(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { if !auth(ctx.Req, f) { ctx.Resp = BasicUnauthorized(ctx.Req, realm) return goproxy.RejectConnect, host } return goproxy.OkConnect, host }) } // ProxyBasic will force HTTP authentication before any request to the proxy is processed func ProxyBasic(proxy *goproxy.ProxyHttpServer, realm string, f func(user, passwd string) bool) { proxy.OnRequest().Do(Basic(realm, f)) proxy.OnRequest().HandleConnect(BasicConnect(realm, f)) } goproxy-1.0/ext/auth/basic_test.go000066400000000000000000000107221240375402100172710ustar00rootroot00000000000000package auth_test import ( "encoding/base64" "io" "io/ioutil" "net" "net/http" "net/http/httptest" "net/url" "os" "os/exec" "os/signal" "sync/atomic" "testing" "github.com/elazarl/goproxy" "github.com/elazarl/goproxy/ext/auth" ) type ConstantHanlder string func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) { io.WriteString(w, string(h)) } func oneShotProxy() (client *http.Client, proxy *goproxy.ProxyHttpServer, s *httptest.Server) { proxy = goproxy.NewProxyHttpServer() s = httptest.NewServer(proxy) proxyUrl, _ := url.Parse(s.URL) tr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)} client = &http.Client{Transport: tr} return } func times(n int, s string) string { r := make([]byte, 0, n*len(s)) for i := 0; i < n; i++ { r = append(r, s...) } return string(r) } func TestBasicConnectAuthWithCurl(t *testing.T) { expected := ":c>" background := httptest.NewTLSServer(ConstantHanlder(expected)) defer background.Close() _, proxy, proxyserver := oneShotProxy() defer proxyserver.Close() proxy.OnRequest().HandleConnect(auth.BasicConnect("my_realm", func(user, passwd string) bool { return user == "user" && passwd == "open sesame" })) cmd := exec.Command("curl", "--silent", "--show-error", "--insecure", "-x", proxyserver.URL, "-U", "user:open sesame", "-p", "--url", background.URL+"/[1-3]", ) out, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr if err != nil { t.Fatal(err, string(out)) } finalexpected := times(3, expected) if string(out) != finalexpected { t.Error("Expected", finalexpected, "got", string(out)) } } func TestBasicAuthWithCurl(t *testing.T) { expected := ":c>" background := httptest.NewServer(ConstantHanlder(expected)) defer background.Close() _, proxy, proxyserver := oneShotProxy() defer proxyserver.Close() proxy.OnRequest().Do(auth.Basic("my_realm", func(user, passwd string) bool { return user == "user" && passwd == "open sesame" })) cmd := exec.Command("curl", "--silent", "--show-error", "-x", proxyserver.URL, "-U", "user:open sesame", "--url", background.URL+"/[1-3]", ) out, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr if err != nil { t.Fatal(err, string(out)) } finalexpected := times(3, expected) if string(out) != finalexpected { t.Error("Expected", finalexpected, "got", string(out)) } } func TestBasicAuth(t *testing.T) { expected := "hello" background := httptest.NewServer(ConstantHanlder(expected)) defer background.Close() client, proxy, proxyserver := oneShotProxy() defer proxyserver.Close() proxy.OnRequest().Do(auth.Basic("my_realm", func(user, passwd string) bool { return user == "user" && passwd == "open sesame" })) // without auth resp, err := client.Get(background.URL) if err != nil { t.Fatal(err) } if resp.Header.Get("Proxy-Authenticate") != "Basic realm=my_realm" { t.Error("Expected Proxy-Authenticate header got", resp.Header.Get("Proxy-Authenticate")) } if resp.StatusCode != 407 { t.Error("Expected status 407 Proxy Authentication Required, got", resp.Status) } // with auth req, err := http.NewRequest("GET", background.URL, nil) if err != nil { t.Fatal(err) } req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("user:open sesame"))) resp, err = client.Do(req) if err != nil { t.Fatal(err) } if resp.StatusCode != 200 { t.Error("Expected status 200 OK, got", resp.Status) } msg, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal(err) } if string(msg) != "hello" { t.Errorf("Expected '%s', actual '%s'", expected, string(msg)) } } func TestWithBrowser(t *testing.T) { // an easy way to check if auth works with webserver // to test, run with // $ go test -run TestWithBrowser -- server // configure a browser to use the printed proxy address, use the proxy // and exit with Ctrl-C. It will throw error if your haven't acutally used the proxy if os.Args[len(os.Args)-1] != "server" { return } proxy := goproxy.NewProxyHttpServer() println("proxy localhost port 8082") access := int32(0) proxy.OnRequest().Do(auth.Basic("my_realm", func(user, passwd string) bool { atomic.AddInt32(&access, 1) return user == "user" && passwd == "1234" })) l, err := net.Listen("tcp", "localhost:8082") if err != nil { t.Fatal(err) } ch := make(chan os.Signal) signal.Notify(ch, os.Interrupt) go func() { <-ch l.Close() }() http.Serve(l, proxy) if access <= 0 { t.Error("No one accessed the proxy") } } goproxy-1.0/ext/html/000077500000000000000000000000001240375402100146235ustar00rootroot00000000000000goproxy-1.0/ext/html/cp1255.html000066400000000000000000000662371240375402100164460ustar00rootroot00000000000000 "
rss
   2012 . " . (4.3.12)
goproxy-1.0/ext/html/cp1255.txt000066400000000000000000000000021240375402100162730ustar00rootroot00000000000000goproxy-1.0/ext/html/html.go000066400000000000000000000057751240375402100161340ustar00rootroot00000000000000// extension to goproxy that will allow you to easily filter web browser related content. package goproxy_html import ( "bytes" "code.google.com/p/go-charset/charset" _ "code.google.com/p/go-charset/data" "errors" "github.com/elazarl/goproxy" "io" "io/ioutil" "net/http" "strings" ) var IsHtml goproxy.RespCondition = goproxy.ContentTypeIs("text/html") var IsCss goproxy.RespCondition = goproxy.ContentTypeIs("text/css") var IsJavaScript goproxy.RespCondition = goproxy.ContentTypeIs("text/javascript", "application/javascript") var IsJson goproxy.RespCondition = goproxy.ContentTypeIs("text/json") var IsXml goproxy.RespCondition = goproxy.ContentTypeIs("text/xml") var IsWebRelatedText goproxy.RespCondition = goproxy.ContentTypeIs("text/html", "text/css", "text/javascript", "application/javascript", "text/xml", "text/json") // HandleString will recieve a function that filters a string, and will convert the // request body to a utf8 string, according to the charset specified in the Content-Type // header. // guessing Html charset encoding from the tags is not yet implemented. func HandleString(f func(s string, ctx *goproxy.ProxyCtx) string) goproxy.RespHandler { return HandleStringReader(func(r io.Reader, ctx *goproxy.ProxyCtx) io.Reader { b, err := ioutil.ReadAll(r) if err != nil { ctx.Warnf("Cannot read string from resp body: %v", err) return r } return bytes.NewBufferString(f(string(b), ctx)) }) } // Will recieve an input stream which would convert the response to utf-8 // The given function must close the reader r, in order to close the response body. func HandleStringReader(f func(r io.Reader, ctx *goproxy.ProxyCtx) io.Reader) goproxy.RespHandler { return goproxy.FuncRespHandler(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { charsetName := ctx.Charset() if charsetName == "" { charsetName = "utf-8" } if strings.ToLower(charsetName) != "utf-8" { r, err := charset.NewReader(charsetName, resp.Body) if err != nil { ctx.Warnf("Cannot convert from %v to utf-8: %v", charsetName, err) return resp } tr, err := charset.TranslatorTo(charsetName) if err != nil { ctx.Warnf("Can't translate to %v from utf-8: %v", charsetName, err) return resp } if err != nil { ctx.Warnf("Cannot translate to %v: %v", charsetName, err) return resp } newr := charset.NewTranslatingReader(f(r, ctx), tr) resp.Body = &readFirstCloseBoth{ioutil.NopCloser(newr), resp.Body} } else { //no translation is needed, already at utf-8 resp.Body = &readFirstCloseBoth{ioutil.NopCloser(f(resp.Body, ctx)), resp.Body} } return resp }) } type readFirstCloseBoth struct { r io.ReadCloser c io.Closer } func (rfcb *readFirstCloseBoth) Read(b []byte) (nr int, err error) { return rfcb.r.Read(b) } func (rfcb *readFirstCloseBoth) Close() error { err1 := rfcb.r.Close() err2 := rfcb.c.Close() if err1 != nil && err2 != nil { return errors.New(err1.Error() + ", " + err2.Error()) } if err1 != nil { return err1 } return err2 } goproxy-1.0/ext/html/html_test.go000066400000000000000000000026741240375402100171660ustar00rootroot00000000000000package goproxy_html_test import ( "github.com/elazarl/goproxy" "github.com/elazarl/goproxy/ext/html" "io/ioutil" "net/http" "net/http/httptest" "net/url" "testing" ) type ConstantServer int func (s ConstantServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=iso-8859-8") //w.Header().Set("Content-Type","text/plain; charset=cp-1255") w.Write([]byte{0xe3, 0xf3}) } func TestCharset(t *testing.T) { s := httptest.NewServer(ConstantServer(1)) defer s.Close() ch := make(chan string, 2) proxy := goproxy.NewProxyHttpServer() proxy.OnResponse().Do(goproxy_html.HandleString( func(s string, ctx *goproxy.ProxyCtx) string { ch <- s return s })) proxyServer := httptest.NewServer(proxy) defer proxyServer.Close() proxyUrl, _ := url.Parse(proxyServer.URL) client := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl)}} resp, err := client.Get(s.URL + "/cp1255.txt") if err != nil { t.Fatal("GET:", err) } b, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal("readAll:", err) } resp.Body.Close() inHandleString := "" select { case inHandleString = <-ch: default: } if len(b) != 2 || b[0] != 0xe3 || b[1] != 0xf3 { t.Error("Did not translate back to 0xe3,0xf3, instead", b) } if inHandleString != "דף" { t.Error("HandleString did not convert DALET & PEH SOFIT (דף) from ISO-8859-8 to utf-8, got", []byte(inHandleString)) } } goproxy-1.0/ext/image/000077500000000000000000000000001240375402100147415ustar00rootroot00000000000000goproxy-1.0/ext/image/image.go000066400000000000000000000043451240375402100163600ustar00rootroot00000000000000package goproxy_image import ( "bytes" . "github.com/elazarl/goproxy" "github.com/elazarl/goproxy/regretable" "image" _ "image/gif" "image/jpeg" "image/png" "io/ioutil" "net/http" ) var RespIsImage = ContentTypeIs("image/gif", "image/jpeg", "image/pjpeg", "application/octet-stream", "image/png") // "image/tiff" tiff support is in external package, and rarely used, so we omitted it func HandleImage(f func(img image.Image, ctx *ProxyCtx) image.Image) RespHandler { return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { if !RespIsImage.HandleResp(resp, ctx) { return resp } if resp.StatusCode != 200 { // we might get 304 - not modified response without data return resp } contentType := resp.Header.Get("Content-Type") regret := regretable.NewRegretableReaderCloser(resp.Body) resp.Body = regret img, imgType, err := image.Decode(resp.Body) if err != nil { regret.Regret() ctx.Warnf("%s: %s", ctx.Req.Method+" "+ctx.Req.URL.String()+" Image from "+ctx.Req.RequestURI+"content type"+ contentType+"cannot be decoded returning original image", err) return resp } result := f(img, ctx) buf := bytes.NewBuffer([]byte{}) switch contentType { // No gif image encoder in go - convert to png case "image/gif", "image/png": if err := png.Encode(buf, result); err != nil { ctx.Warnf("Cannot encode image, returning orig %v %v", ctx.Req.URL.String(), err) return resp } resp.Header.Set("Content-Type", "image/png") case "image/jpeg", "image/pjpeg": if err := jpeg.Encode(buf, result, nil); err != nil { ctx.Warnf("Cannot encode image, returning orig %v %v", ctx.Req.URL.String(), err) return resp } case "application/octet-stream": switch imgType { case "jpeg": if err := jpeg.Encode(buf, result, nil); err != nil { ctx.Warnf("Cannot encode image as jpeg, returning orig %v %v", ctx.Req.URL.String(), err) return resp } case "png", "gif": if err := png.Encode(buf, result); err != nil { ctx.Warnf("Cannot encode image as png, returning orig %v %v", ctx.Req.URL.String(), err) return resp } } default: panic("unhandlable type" + contentType) } resp.Body = ioutil.NopCloser(buf) return resp }) } goproxy-1.0/https.go000066400000000000000000000235251240375402100145570ustar00rootroot00000000000000package goproxy import ( "bufio" "crypto/tls" "errors" "io" "io/ioutil" "net" "net/http" "net/url" "os" "strconv" "strings" "sync/atomic" ) type ConnectActionLiteral int const ( ConnectAccept = iota ConnectReject ConnectMitm ConnectHijack ConnectHTTPMitm ) var ( OkConnect = &ConnectAction{Action: ConnectAccept} MitmConnect = &ConnectAction{Action: ConnectMitm} HTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm} RejectConnect = &ConnectAction{Action: ConnectReject} ) type ConnectAction struct { Action ConnectActionLiteral TlsConfig *tls.Config Ca *tls.Certificate Hijack func(req *http.Request, client net.Conn, ctx *ProxyCtx) } func stripPort(s string) string { ix := strings.IndexRune(s, ':') if ix == -1 { return s } return s[:ix] } func (proxy *ProxyHttpServer) dial(network, addr string) (c net.Conn, err error) { if proxy.Tr.Dial != nil { return proxy.Tr.Dial(network, addr) } return net.Dial(network, addr) } func (proxy *ProxyHttpServer) connectDial(network, addr string) (c net.Conn, err error) { if proxy.ConnectDial == nil { return proxy.dial(network, addr) } return proxy.ConnectDial(network, addr) } func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) { ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy} hij, ok := w.(http.Hijacker) if !ok { panic("httpserver does not support hijacking") } proxyClient, _, e := hij.Hijack() if e != nil { panic("Cannot hijack connection " + e.Error()) } ctx.Logf("Running %d CONNECT handlers", len(proxy.httpsHandlers)) todo, host := OkConnect, r.URL.Host ctx.Req = r for _, h := range proxy.httpsHandlers { newtodo, newhost := h.HandleConnect(host, ctx) if newtodo != nil { todo, host = newtodo, newhost } ctx.Logf("handler: %v %s", todo, host) } switch todo.Action { case ConnectAccept: if !hasPort.MatchString(host) { host += ":80" } targetSiteCon, err := proxy.connectDial("tcp", host) if err != nil { httpError(proxyClient, ctx, err) return } ctx.Logf("Accepting CONNECT to %s", host) proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) go copyAndClose(ctx, targetSiteCon, proxyClient) go copyAndClose(ctx, proxyClient, targetSiteCon) case ConnectHijack: ctx.Logf("Hijacking CONNECT to %s", host) proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) todo.Hijack(r, proxyClient, ctx) case ConnectHTTPMitm: proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) ctx.Logf("Assuming CONNECT is plain HTTP tunneling, mitm proxying it") targetSiteCon, err := proxy.connectDial("tcp", host) if err != nil { ctx.Warnf("Error dialing to %s: %s", host, err.Error()) return } for { client := bufio.NewReader(proxyClient) remote := bufio.NewReader(targetSiteCon) req, err := http.ReadRequest(client) if err != nil && err != io.EOF { ctx.Warnf("cannot read request of MITM HTTP client: %+#v", err) } if err != nil { return } req, resp := proxy.filterRequest(req, ctx) if resp == nil { if err := req.Write(targetSiteCon); err != nil { httpError(proxyClient, ctx, err) return } resp, err = http.ReadResponse(remote, req) if err != nil { httpError(proxyClient, ctx, err) return } } resp = proxy.filterResponse(resp, ctx) if err := resp.Write(proxyClient); err != nil { httpError(proxyClient, ctx, err) return } } case ConnectMitm: proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) ctx.Logf("Assuming CONNECT is TLS, mitm proxying it") // this goes in a separate goroutine, so that the net/http server won't think we're // still handling the request even after hijacking the connection. Those HTTP CONNECT // request can take forever, and the server will be stuck when "closed". // TODO: Allow Server.Close() mechanism to shut down this connection as nicely as possible ca := todo.Ca if ca == nil { ca = &GoproxyCa } cert, err := signHost(*ca, []string{stripPort(host)}) if err != nil { ctx.Warnf("Cannot sign host certificate with provided CA: %s", err) return } tlsConfig := tls.Config{} if todo.TlsConfig != nil { tlsConfig = *todo.TlsConfig } else { tlsConfig = *defaultTlsConfig } tlsConfig.Certificates = append(tlsConfig.Certificates, cert) go func() { //TODO: cache connections to the remote website rawClientTls := tls.Server(proxyClient, &tlsConfig) if err := rawClientTls.Handshake(); err != nil { ctx.Warnf("Cannot handshake client %v %v", r.Host, err) return } defer rawClientTls.Close() clientTlsReader := bufio.NewReader(rawClientTls) for !isEof(clientTlsReader) { req, err := http.ReadRequest(clientTlsReader) if err != nil && err != io.EOF { return } if err != nil { ctx.Warnf("Cannot read TLS request from mitm'd client %v %v", r.Host, err) return } ctx.Logf("req %v", r.Host) req.URL, err = url.Parse("https://" + r.Host + req.URL.String()) req, resp := proxy.filterRequest(req, ctx) if resp == nil { if err != nil { ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path) return } removeProxyHeaders(ctx, req) resp, err = ctx.RoundTrip(req) if err != nil { ctx.Warnf("Cannot read TLS response from mitm'd server %v", err) return } ctx.Logf("resp %v", resp.Status) } resp = proxy.filterResponse(resp, ctx) text := resp.Status statusCode := strconv.Itoa(resp.StatusCode) + " " if strings.HasPrefix(text, statusCode) { text = text[len(statusCode):] } // always use 1.1 to support chunked encoding if _, err := io.WriteString(rawClientTls, "HTTP/1.1"+" "+statusCode+text+"\r\n"); err != nil { ctx.Warnf("Cannot write TLS response HTTP status from mitm'd client: %v", err) return } // Since we don't know the length of resp, return chunked encoded response // TODO: use a more reasonable scheme resp.Header.Del("Content-Length") resp.Header.Set("Transfer-Encoding", "chunked") if err := resp.Header.Write(rawClientTls); err != nil { ctx.Warnf("Cannot write TLS response header from mitm'd client: %v", err) return } if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { ctx.Warnf("Cannot write TLS response header end from mitm'd client: %v", err) return } chunked := newChunkedWriter(rawClientTls) if _, err := io.Copy(chunked, resp.Body); err != nil { ctx.Warnf("Cannot write TLS response body from mitm'd client: %v", err) return } if err := chunked.Close(); err != nil { ctx.Warnf("Cannot write TLS chunked EOF from mitm'd client: %v", err) return } if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { ctx.Warnf("Cannot write TLS response chunked trailer from mitm'd client: %v", err) return } } ctx.Logf("Exiting on EOF") }() case ConnectReject: if ctx.Resp != nil { if err := ctx.Resp.Write(proxyClient); err != nil { ctx.Warnf("Cannot write response that reject http CONNECT: %v", err) } } proxyClient.Close() } } func httpError(w io.WriteCloser, ctx *ProxyCtx, err error) { if _, err := io.WriteString(w, "HTTP/1.1 502 Bad Gateway\r\n\r\n"); err != nil { ctx.Warnf("Error responding to client: %s", err) } if err := w.Close(); err != nil { ctx.Warnf("Error closing client connection: %s", err) } } func copyAndClose(ctx *ProxyCtx, w, r net.Conn) { connOk := true if _, err := io.Copy(w, r); err != nil { connOk = false ctx.Warnf("Error copying to client: %s", err) } if err := r.Close(); err != nil && connOk { ctx.Warnf("Error closing: %s", err) } } func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn, error) { https_proxy := os.Getenv("HTTPS_PROXY") if https_proxy == "" { https_proxy = os.Getenv("https_proxy") } if https_proxy == "" { return nil } return proxy.NewConnectDialToProxy(https_proxy) } func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(network, addr string) (net.Conn, error) { u, err := url.Parse(https_proxy) if err != nil { return nil } if u.Scheme == "" || u.Scheme == "http" { if strings.IndexRune(u.Host, ':') == -1 { u.Host += ":80" } return func(network, addr string) (net.Conn, error) { connectReq := &http.Request{ Method: "CONNECT", URL: &url.URL{Opaque: addr}, Host: addr, Header: make(http.Header), } c, err := proxy.dial(network, u.Host) if err != nil { return nil, err } connectReq.Write(c) // Read response. // Okay to use and discard buffered reader here, because // TLS server will not speak until spoken to. br := bufio.NewReader(c) resp, err := http.ReadResponse(br, connectReq) if err != nil { c.Close() return nil, err } if resp.StatusCode != 200 { resp, _ := ioutil.ReadAll(resp.Body) c.Close() return nil, errors.New("proxy refused connection" + string(resp)) } return c, nil } } if u.Scheme == "https" { if strings.IndexRune(u.Host, ':') == -1 { u.Host += ":443" } return func(network, addr string) (net.Conn, error) { c, err := proxy.dial(network, u.Host) if err != nil { return nil, err } c = tls.Client(c, proxy.Tr.TLSClientConfig) connectReq := &http.Request{ Method: "CONNECT", URL: &url.URL{Opaque: addr}, Host: addr, Header: make(http.Header), } connectReq.Write(c) // Read response. // Okay to use and discard buffered reader here, because // TLS server will not speak until spoken to. br := bufio.NewReader(c) resp, err := http.ReadResponse(br, connectReq) if err != nil { c.Close() return nil, err } if resp.StatusCode != 200 { body, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 500)) resp.Body.Close() c.Close() return nil, errors.New("proxy refused connection" + string(body)) } return c, nil } } return nil } goproxy-1.0/key.pem000066400000000000000000000015671240375402100143630ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQC/P0FsJomPGzvdO9yreV4/faEAZ6tDVGC+VnrxnidmahUd+X7Y 2v+bR2Zb4Z05+lNyz8rN8mNgav/zjHnbh+K5HwZ1nQc61cnPIXmx6hadsEi7KvU9 sSmBGEZAyqo5S6NgTF4tt80c8ignxdnVXPK/djGNuaNYD5L+4570da0NswIDAQAB AoGBALzIv1b4D7ARTR3NOr6V9wArjiOtMjUrdLhO+9vIp9IEA8ZsA9gjDlCEwbkP VDnoLjnWfraff5Os6+3JjHy1fYpUiCdnk2XA6iJSL1XWKQZPt3wOunxP4lalDgED QTRReFbA/y/Z4kSfTXpVj68ytcvSRW/N7q5/qRtbN9804jpBAkEA0s6lvH2btSLA mcEdwhs7zAslLbdld7rvfUeP82gPPk0S6yUqTNyikqshM9AwAktHY7WvYdKl+ghZ HTxKVC4DoQJBAOg/IAW5RbXknP+Lf7AVtBgw3E+Yfa3mcdLySe8hjxxyZq825Zmu Rt5Qj4Lw6ifSFNy4kiiSpE/ZCukYvUXGENMCQFkPxSWlS6tzSzuqQxBGwTSrYMG3 wb6b06JyIXcMd6Qym9OMmBpw/J5KfnSNeDr/4uFVWQtTG5xO+pdHaX+3EQECQQDl qcbY4iX1gWVfr2tNjajSYz751yoxVbkpiT9joiQLVXYFvpu+JYEfRzsjmWl0h2Lq AftG8/xYmaEYcMZ6wSrRAkBUwiom98/8wZVlB6qbwhU1EKDFANvICGSWMIhPx3v7 MJqTIj4uJhte2/uyVvZ6DC6noWYgy+kLgqG0S97tUEG8 -----END RSA PRIVATE KEY----- goproxy-1.0/proxy.go000066400000000000000000000115531240375402100145740ustar00rootroot00000000000000package goproxy import ( "bufio" "io" "log" "net" "net/http" "os" "regexp" "sync/atomic" ) // The basic proxy type. Implements http.Handler. type ProxyHttpServer struct { // session variable must be aligned in i386 // see http://golang.org/src/pkg/sync/atomic/doc.go#L41 sess int64 // setting Verbose to true will log information on each request sent to the proxy Verbose bool Logger *log.Logger NonproxyHandler http.Handler reqHandlers []ReqHandler respHandlers []RespHandler httpsHandlers []HttpsHandler Tr *http.Transport // ConnectDial will be used to create TCP connections for CONNECT requests // if nil Tr.Dial will be used ConnectDial func(network string, addr string) (net.Conn, error) } var hasPort = regexp.MustCompile(`:\d+$`) func copyHeaders(dst, src http.Header) { for k, _ := range dst { dst.Del(k) } for k, vs := range src { for _, v := range vs { dst.Add(k, v) } } } func isEof(r *bufio.Reader) bool { _, err := r.Peek(1) if err == io.EOF { return true } return false } func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) { req = r for _, h := range proxy.reqHandlers { req, resp = h.Handle(r, ctx) // non-nil resp means the handler decided to skip sending the request // and return canned response instead. if resp != nil { break } } return } func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) { resp = respOrig for _, h := range proxy.respHandlers { ctx.Resp = resp resp = h.Handle(resp, ctx) } return } func removeProxyHeaders(ctx *ProxyCtx, r *http.Request) { r.RequestURI = "" // this must be reset when serving a request with the client ctx.Logf("Sending request %v %v", r.Method, r.URL.String()) // If no Accept-Encoding header exists, Transport will add the headers it can accept // and would wrap the response body with the relevant reader. r.Header.Del("Accept-Encoding") // curl can add that, see // http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/web-proxy-connection-header.html r.Header.Del("Proxy-Connection") // Connection is single hop Header: // http://www.w3.org/Protocols/rfc2616/rfc2616.txt // 14.10 Connection // The Connection general-header field allows the sender to specify // options that are desired for that particular connection and MUST NOT // be communicated by proxies over further connections. r.Header.Del("Connection") } // Standard net/http function. Shouldn't be used directly, http.Serve will use it. func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { //r.Header["X-Forwarded-For"] = w.RemoteAddr() if r.Method == "CONNECT" { proxy.handleHttps(w, r) } else { ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy} var err error ctx.Logf("Got request %v %v %v %v", r.URL.Path, r.Host, r.Method, r.URL.String()) if !r.URL.IsAbs() { proxy.NonproxyHandler.ServeHTTP(w, r) return } r, resp := proxy.filterRequest(r, ctx) if resp == nil { removeProxyHeaders(ctx, r) resp, err = ctx.RoundTrip(r) if err != nil { ctx.Error = err resp = proxy.filterResponse(nil, ctx) if resp == nil { ctx.Logf("error read response %v %v:", r.URL.Host, err.Error()) http.Error(w, err.Error(), 500) return } } ctx.Logf("Received response %v", resp.Status) } origBody := resp.Body resp = proxy.filterResponse(resp, ctx) ctx.Logf("Copying response to client %v [%d]", resp.Status, resp.StatusCode) // http.ResponseWriter will take care of filling the correct response length // Setting it now, might impose wrong value, contradicting the actual new // body the user returned. // We keep the original body to remove the header only if things changed. // This will prevent problems with HEAD requests where there's no body, yet, // the Content-Length header should be set. if origBody != resp.Body { resp.Header.Del("Content-Length") } copyHeaders(w.Header(), resp.Header) w.WriteHeader(resp.StatusCode) nr, err := io.Copy(w, resp.Body) if err := resp.Body.Close(); err != nil { ctx.Warnf("Can't close response body %v", err) } ctx.Logf("Copied %v bytes to client error=%v", nr, err) } } // New proxy server, logs to StdErr by default func NewProxyHttpServer() *ProxyHttpServer { proxy := ProxyHttpServer{ Logger: log.New(os.Stderr, "", log.LstdFlags), reqHandlers: []ReqHandler{}, respHandlers: []RespHandler{}, httpsHandlers: []HttpsHandler{}, NonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { http.Error(w, "This is a proxy server, does not response to non-proxy requests", 500) }), Tr: &http.Transport{TLSClientConfig: tlsClientSkipVerify, Proxy: http.ProxyFromEnvironment}, } proxy.ConnectDial = dialerFromEnv(&proxy) return &proxy } goproxy-1.0/proxy_test.go000066400000000000000000000513501240375402100156320ustar00rootroot00000000000000package goproxy_test import ( "bufio" "bytes" "crypto/tls" "encoding/base64" "image" "io" "io/ioutil" "net" "net/http" "net/http/httptest" "net/url" "os" "os/exec" "strings" "testing" "github.com/elazarl/goproxy" "github.com/elazarl/goproxy/ext/image" ) var acceptAllCerts = &tls.Config{InsecureSkipVerify: true} var noProxyClient = &http.Client{Transport: &http.Transport{TLSClientConfig: acceptAllCerts}} var https = httptest.NewTLSServer(nil) var srv = httptest.NewServer(nil) var fs = httptest.NewServer(http.FileServer(http.Dir("."))) type QueryHandler struct{} func (QueryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { if err := req.ParseForm(); err != nil { panic(err) } io.WriteString(w, req.Form.Get("result")) } func init() { http.DefaultServeMux.Handle("/bobo", ConstantHanlder("bobo")) http.DefaultServeMux.Handle("/query", QueryHandler{}) } type ConstantHanlder string func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) { io.WriteString(w, string(h)) } func get(url string, client *http.Client) ([]byte, error) { resp, err := client.Get(url) if err != nil { return nil, err } txt, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() if err != nil { return nil, err } return txt, nil } func getOrFail(url string, client *http.Client, t *testing.T) []byte { txt, err := get(url, client) if err != nil { t.Fatal("Can't fetch url", url, err) } return txt } func localFile(url string) string { return fs.URL + "/" + url } func localTls(url string) string { return https.URL + url } func TestSimpleHttpReqWithProxy(t *testing.T) { client, _, s := oneShotProxy(t) defer s.Close() if r := string(getOrFail(srv.URL+"/bobo", client, t)); r != "bobo" { t.Error("proxy server does not serve constant handlers", r) } if r := string(getOrFail(srv.URL+"/bobo", client, t)); r != "bobo" { t.Error("proxy server does not serve constant handlers", r) } if string(getOrFail(https.URL+"/bobo", client, t)) != "bobo" { t.Error("TLS server does not serve constant handlers, when proxy is used") } } func oneShotProxy(t *testing.T) (client *http.Client, proxy *goproxy.ProxyHttpServer, s *httptest.Server) { proxy = goproxy.NewProxyHttpServer() s = httptest.NewServer(proxy) proxyUrl, _ := url.Parse(s.URL) tr := &http.Transport{TLSClientConfig: acceptAllCerts, Proxy: http.ProxyURL(proxyUrl)} client = &http.Client{Transport: tr} return } func TestSimpleHook(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnRequest(goproxy.SrcIpIs("127.0.0.1")).DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { req.URL.Path = "/bobo" return req, nil }) if result := string(getOrFail(srv.URL+("/momo"), client, t)); result != "bobo" { t.Error("Redirecting all requests from 127.0.0.1 to bobo, didn't work." + " (Might break if Go's client sets RemoteAddr to IPv6 address). Got: " + result) } } func TestAlwaysHook(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { req.URL.Path = "/bobo" return req, nil }) if result := string(getOrFail(srv.URL+("/momo"), client, t)); result != "bobo" { t.Error("Redirecting all requests from 127.0.0.1 to bobo, didn't work." + " (Might break if Go's client sets RemoteAddr to IPv6 address). Got: " + result) } } func TestReplaceResponse(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { resp.StatusCode = http.StatusOK resp.Body = ioutil.NopCloser(bytes.NewBufferString("chico")) return resp }) if result := string(getOrFail(srv.URL+("/momo"), client, t)); result != "chico" { t.Error("hooked response, should be chico, instead:", result) } } func TestReplaceReponseForUrl(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnResponse(goproxy.UrlIs("/koko")).DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { resp.StatusCode = http.StatusOK resp.Body = ioutil.NopCloser(bytes.NewBufferString("chico")) return resp }) if result := string(getOrFail(srv.URL+("/koko"), client, t)); result != "chico" { t.Error("hooked 'koko', should be chico, instead:", result) } if result := string(getOrFail(srv.URL+("/bobo"), client, t)); result != "bobo" { t.Error("still, bobo should stay as usual, instead:", result) } } func TestOneShotFileServer(t *testing.T) { client, _, l := oneShotProxy(t) defer l.Close() file := "test_data/panda.png" info, err := os.Stat(file) if err != nil { t.Fatal("Cannot find", file) } if resp, err := client.Get(fs.URL + "/" + file); err == nil { b, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal("got", string(b)) } if int64(len(b)) != info.Size() { t.Error("Expected Length", file, info.Size(), "actually", len(b), "starts", string(b[:10])) } } else { t.Fatal("Cannot read from fs server", err) } } func TestContentType(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnResponse(goproxy.ContentTypeIs("image/png")).DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { resp.Header.Set("X-Shmoopi", "1") return resp }) for _, file := range []string{"test_data/panda.png", "test_data/football.png"} { if resp, err := client.Get(localFile(file)); err != nil || resp.Header.Get("X-Shmoopi") != "1" { if err == nil { t.Error("pngs should have X-Shmoopi header = 1, actually", resp.Header.Get("X-Shmoopi")) } else { t.Error("error reading png", err) } } } file := "baby.jpg" if resp, err := client.Get(localFile(file)); err != nil || resp.Header.Get("X-Shmoopi") != "" { if err == nil { t.Error("Non png images should NOT have X-Shmoopi header at all", resp.Header.Get("X-Shmoopi")) } else { t.Error("error reading png", err) } } } func getImage(file string, t *testing.T) image.Image { newimage, err := ioutil.ReadFile(file) if err != nil { t.Fatal("Cannot read file", file, err) } img, _, err := image.Decode(bytes.NewReader(newimage)) if err != nil { t.Fatal("Cannot decode image", file, err) } return img } func readAll(r io.Reader, t *testing.T) []byte { b, err := ioutil.ReadAll(r) if err != nil { t.Fatal("Cannot read", err) } return b } func readFile(file string, t *testing.T) []byte { b, err := ioutil.ReadFile(file) if err != nil { t.Fatal("Cannot read", err) } return b } func fatalOnErr(err error, msg string, t *testing.T) { if err != nil { t.Fatal(msg, err) } } func panicOnErr(err error, msg string) { if err != nil { println(err.Error() + ":-" + msg) os.Exit(-1) } } func compareImage(eImg, aImg image.Image, t *testing.T) { if eImg.Bounds().Dx() != aImg.Bounds().Dx() || eImg.Bounds().Dy() != aImg.Bounds().Dy() { t.Error("image sizes different") return } for i := 0; i < eImg.Bounds().Dx(); i++ { for j := 0; j < eImg.Bounds().Dy(); j++ { er, eg, eb, ea := eImg.At(i, j).RGBA() ar, ag, ab, aa := aImg.At(i, j).RGBA() if er != ar || eg != ag || eb != ab || ea != aa { t.Error("images different at", i, j, "vals\n", er, eg, eb, ea, "\n", ar, ag, ab, aa, aa) return } } } } func TestConstantImageHandler(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() //panda := getImage("panda.png", t) football := getImage("test_data/football.png", t) proxy.OnResponse().Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image { return football })) resp, err := client.Get(localFile("test_data/panda.png")) if err != nil { t.Fatal("Cannot get panda.png", err) } img, _, err := image.Decode(resp.Body) if err != nil { t.Error("decode", err) } else { compareImage(football, img, t) } } func TestImageHandler(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() football := getImage("test_data/football.png", t) proxy.OnResponse(goproxy.UrlIs("/test_data/panda.png")).Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image { return football })) resp, err := client.Get(localFile("test_data/panda.png")) if err != nil { t.Fatal("Cannot get panda.png", err) } img, _, err := image.Decode(resp.Body) if err != nil { t.Error("decode", err) } else { compareImage(football, img, t) } // and again resp, err = client.Get(localFile("test_data/panda.png")) if err != nil { t.Fatal("Cannot get panda.png", err) } img, _, err = image.Decode(resp.Body) if err != nil { t.Error("decode", err) } else { compareImage(football, img, t) } } func TestChangeResp(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { resp.Body.Read([]byte{0}) resp.Body = ioutil.NopCloser(new(bytes.Buffer)) return resp }) resp, err := client.Get(localFile("test_data/panda.png")) if err != nil { t.Fatal(err) } ioutil.ReadAll(resp.Body) _, err = client.Get(localFile("/bobo")) if err != nil { t.Fatal(err) } } func TestReplaceImage(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() panda := getImage("test_data/panda.png", t) football := getImage("test_data/football.png", t) proxy.OnResponse(goproxy.UrlIs("/test_data/panda.png")).Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image { return football })) proxy.OnResponse(goproxy.UrlIs("/test_data/football.png")).Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image { return panda })) imgByPandaReq, _, err := image.Decode(bytes.NewReader(getOrFail(localFile("test_data/panda.png"), client, t))) fatalOnErr(err, "decode panda", t) compareImage(football, imgByPandaReq, t) imgByFootballReq, _, err := image.Decode(bytes.NewReader(getOrFail(localFile("test_data/football.png"), client, t))) fatalOnErr(err, "decode football", t) compareImage(panda, imgByFootballReq, t) } func getCert(c *tls.Conn, t *testing.T) []byte { if err := c.Handshake(); err != nil { t.Fatal("cannot handshake", err) } return c.ConnectionState().PeerCertificates[0].Raw } func TestSimpleMitm(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnRequest(goproxy.ReqHostIs(https.Listener.Addr().String())).HandleConnect(goproxy.AlwaysMitm) proxy.OnRequest(goproxy.ReqHostIs("no such host exists")).HandleConnect(goproxy.AlwaysMitm) c, err := tls.Dial("tcp", https.Listener.Addr().String(), &tls.Config{InsecureSkipVerify: true}) if err != nil { t.Fatal("cannot dial to tcp server", err) } origCert := getCert(c, t) c.Close() c2, err := net.Dial("tcp", l.Listener.Addr().String()) if err != nil { t.Fatal("dialing to proxy", err) } creq, err := http.NewRequest("CONNECT", https.URL, nil) //creq,err := http.NewRequest("CONNECT","https://google.com:443",nil) if err != nil { t.Fatal("create new request", creq) } creq.Write(c2) c2buf := bufio.NewReader(c2) resp, err := http.ReadResponse(c2buf, creq) if err != nil || resp.StatusCode != 200 { t.Fatal("Cannot CONNECT through proxy", err) } c2tls := tls.Client(c2, &tls.Config{InsecureSkipVerify: true}) proxyCert := getCert(c2tls, t) if bytes.Equal(proxyCert, origCert) { t.Errorf("Certificate after mitm is not different\n%v\n%v", base64.StdEncoding.EncodeToString(origCert), base64.StdEncoding.EncodeToString(proxyCert)) } if resp := string(getOrFail(https.URL+"/bobo", client, t)); resp != "bobo" { t.Error("Wrong response when mitm", resp, "expected bobo") } if resp := string(getOrFail(https.URL+"/query?result=bar", client, t)); resp != "bar" { t.Error("Wrong response when mitm", resp, "expected bar") } } func TestConnectHandler(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() althttps := httptest.NewTLSServer(ConstantHanlder("althttps")) proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { u, _ := url.Parse(althttps.URL) return goproxy.OkConnect, u.Host }) if resp := string(getOrFail(https.URL+"/alturl", client, t)); resp != "althttps" { t.Error("Proxy should redirect CONNECT requests to local althttps server, expected 'althttps' got ", resp) } } func TestMitmIsFiltered(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() //proxy.Verbose = true proxy.OnRequest(goproxy.ReqHostIs(https.Listener.Addr().String())).HandleConnect(goproxy.AlwaysMitm) proxy.OnRequest(goproxy.UrlIs("/momo")).DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { return nil, goproxy.TextResponse(req, "koko") }) if resp := string(getOrFail(https.URL+"/momo", client, t)); resp != "koko" { t.Error("Proxy should capture /momo to be koko and not", resp) } if resp := string(getOrFail(https.URL+"/bobo", client, t)); resp != "bobo" { t.Error("But still /bobo should be bobo and not", resp) } } func TestFirstHandlerMatches(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { return nil, goproxy.TextResponse(req, "koko") }) proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { panic("should never get here, previous response is no null") return nil, nil }) if resp := string(getOrFail(srv.URL+"/", client, t)); resp != "koko" { t.Error("should return always koko and not", resp) } } func constantHttpServer(content []byte) (addr string) { l, err := net.Listen("tcp", "localhost:0") panicOnErr(err, "listen") go func() { c, err := l.Accept() panicOnErr(err, "accept") buf := bufio.NewReader(c) _, err = http.ReadRequest(buf) panicOnErr(err, "readReq") c.Write(content) c.Close() l.Close() }() return l.Addr().String() } func TestIcyResponse(t *testing.T) { // TODO: fix this test return // skip for now s := constantHttpServer([]byte("ICY 200 OK\r\n\r\nblablabla")) _, proxy, l := oneShotProxy(t) proxy.Verbose = true defer l.Close() req, err := http.NewRequest("GET", "http://"+s, nil) panicOnErr(err, "newReq") proxyip := l.URL[len("http://"):] println("got ip: " + proxyip) c, err := net.Dial("tcp", proxyip) panicOnErr(err, "dial") defer c.Close() req.WriteProxy(c) raw, err := ioutil.ReadAll(c) panicOnErr(err, "readAll") if string(raw) != "ICY 200 OK\r\n\r\nblablabla" { t.Error("Proxy did not send the malformed response received") } } type VerifyNoProxyHeaders struct { *testing.T } func (v VerifyNoProxyHeaders) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Connection") != "" || r.Header.Get("Proxy-Connection") != "" { v.Error("Got Connection header from goproxy", r.Header) } } func TestNoProxyHeaders(t *testing.T) { s := httptest.NewServer(VerifyNoProxyHeaders{t}) client, _, l := oneShotProxy(t) defer l.Close() req, err := http.NewRequest("GET", s.URL, nil) panicOnErr(err, "bad request") req.Header.Add("Connection", "close") req.Header.Add("Proxy-Connection", "close") client.Do(req) } func TestNoProxyHeadersHttps(t *testing.T) { s := httptest.NewTLSServer(VerifyNoProxyHeaders{t}) client, proxy, l := oneShotProxy(t) proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) defer l.Close() req, err := http.NewRequest("GET", s.URL, nil) panicOnErr(err, "bad request") req.Header.Add("Connection", "close") req.Header.Add("Proxy-Connection", "close") client.Do(req) } func TestHeadReqHasContentLength(t *testing.T) { client, _, l := oneShotProxy(t) defer l.Close() resp, err := client.Head(localFile("test_data/panda.png")) panicOnErr(err, "resp to HEAD") if resp.Header.Get("Content-Length") == "" { t.Error("Content-Length should exist on HEAD requests") } } func TestChunkedResponse(t *testing.T) { l, err := net.Listen("tcp", ":10234") panicOnErr(err, "listen") defer l.Close() go func() { for i := 0; i < 2; i++ { c, err := l.Accept() panicOnErr(err, "accept") _, err = http.ReadRequest(bufio.NewReader(c)) panicOnErr(err, "readrequest") io.WriteString(c, "HTTP/1.1 200 OK\r\n"+ "Content-Type: text/plain\r\n"+ "Transfer-Encoding: chunked\r\n\r\n"+ "25\r\n"+ "This is the data in the first chunk\r\n\r\n"+ "1C\r\n"+ "and this is the second one\r\n\r\n"+ "3\r\n"+ "con\r\n"+ "8\r\n"+ "sequence\r\n0\r\n\r\n") c.Close() } }() c, err := net.Dial("tcp", "localhost:10234") panicOnErr(err, "dial") defer c.Close() req, _ := http.NewRequest("GET", "/", nil) req.Write(c) resp, err := http.ReadResponse(bufio.NewReader(c), req) panicOnErr(err, "readresp") b, err := ioutil.ReadAll(resp.Body) panicOnErr(err, "readall") expected := "This is the data in the first chunk\r\nand this is the second one\r\nconsequence" if string(b) != expected { t.Errorf("Got `%v` expected `%v`", string(b), expected) } client, proxy, s := oneShotProxy(t) defer s.Close() proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { panicOnErr(ctx.Error, "error reading output") b, err := ioutil.ReadAll(resp.Body) resp.Body.Close() panicOnErr(err, "readall onresp") if enc := resp.Header.Get("Transfer-Encoding"); enc != "" { t.Fatal("Chunked response should be received as plaintext", enc) } resp.Body = ioutil.NopCloser(bytes.NewBufferString(strings.Replace(string(b), "e", "E", -1))) return resp }) resp, err = client.Get("http://localhost:10234/") panicOnErr(err, "client.Get") b, err = ioutil.ReadAll(resp.Body) panicOnErr(err, "readall proxy") if string(b) != strings.Replace(expected, "e", "E", -1) { t.Error("expected", expected, "w/ e->E. Got", string(b)) } } func TestGoproxyThroughProxy(t *testing.T) { _, proxy, l := oneShotProxy(t) defer l.Close() client, proxy2, l2 := oneShotProxy(t) defer l2.Close() doubleString := func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { b, err := ioutil.ReadAll(resp.Body) panicOnErr(err, "readAll resp") resp.Body = ioutil.NopCloser(bytes.NewBufferString(string(b) + " " + string(b))) return resp } proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) proxy.OnResponse().DoFunc(doubleString) proxy2.ConnectDial = proxy2.NewConnectDialToProxy(l.URL) if r := string(getOrFail(https.URL+"/bobo", client, t)); r != "bobo bobo" { t.Error("Expected bobo doubled twice, got", r) } } func TestGoproxyHijackConnect(t *testing.T) { client, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnRequest(goproxy.ReqHostIs(srv.Listener.Addr().String())). HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) { t.Logf("URL %+#v\nSTR %s", req.URL, req.URL.String()) resp, err := http.Get("http:" + req.URL.String() + "/bobo") panicOnErr(err, "http.Get(CONNECT url)") panicOnErr(resp.Write(client), "resp.Write(client)") resp.Body.Close() client.Close() }) proxyAddr := l.Listener.Addr().String() conn, err := net.Dial("tcp", proxyAddr) panicOnErr(err, "conn "+proxyAddr) buf := bufio.NewReader(conn) writeConnect(conn) readConnectResponse(buf) if txt := readResponse(buf); txt != "bobo" { t.Error("Expected bobo for CONNECT /foo, got", txt) } if r := string(getOrFail(https.URL+"/bobo", client, t)); r != "bobo" { t.Error("Expected bobo would keep working with CONNECT", r) } } func readResponse(buf *bufio.Reader) string { req, err := http.NewRequest("GET", srv.URL, nil) panicOnErr(err, "NewRequest") resp, err := http.ReadResponse(buf, req) panicOnErr(err, "resp.Read") defer resp.Body.Close() txt, err := ioutil.ReadAll(resp.Body) panicOnErr(err, "resp.Read") return string(txt) } func writeConnect(w io.Writer) { req, err := http.NewRequest("CONNECT", srv.URL[len("http://"):], nil) panicOnErr(err, "NewRequest") req.Write(w) panicOnErr(err, "req(CONNECT).Write") } func readConnectResponse(buf *bufio.Reader) { _, err := buf.ReadString('\n') panicOnErr(err, "resp.Read connect resp") _, err = buf.ReadString('\n') panicOnErr(err, "resp.Read connect resp") } func TestCurlMinusP(t *testing.T) { _, proxy, l := oneShotProxy(t) defer l.Close() proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { return goproxy.HTTPMitmConnect, host }) called := false proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { called = true return req, nil }) cmd := exec.Command("curl", "-p", "-sS", "--proxy", l.URL, srv.URL+"/bobo") output, err := cmd.CombinedOutput() if err != nil { t.Fatal(err) } if string(output) != "bobo" { t.Error("Expected bobo, got", string(output)) } if !called { t.Error("handler not called") } } func TestSelfRequest(t *testing.T) { _, proxy, l := oneShotProxy(t) proxy.Verbose = true defer l.Close() if !strings.Contains(string(getOrFail(l.URL, http.DefaultClient, t)), "non-proxy") { t.Fatal("non proxy requests should fail") } } goproxy-1.0/regretable/000077500000000000000000000000001240375402100151735ustar00rootroot00000000000000goproxy-1.0/regretable/regretreader.go000066400000000000000000000052441240375402100202020ustar00rootroot00000000000000package regretable import ( "io" ) // A RegretableReader will allow you to read from a reader, and then // to "regret" reading it, and push back everything you've read. // For example, // rb := NewRegretableReader(bytes.NewBuffer([]byte{1,2,3})) // var b = make([]byte,1) // rb.Read(b) // b[0] = 1 // rb.Regret() // ioutil.ReadAll(rb.Read) // returns []byte{1,2,3},nil type RegretableReader struct { reader io.Reader overflow bool r, w int buf []byte } var defaultBufferSize = 500 // Same as RegretableReader, but allows closing the underlying reader type RegretableReaderCloser struct { RegretableReader c io.Closer } // Closes the underlying readCloser, you cannot regret after closing the stream func (rbc *RegretableReaderCloser) Close() error { return rbc.c.Close() } // initialize a RegretableReaderCloser with underlying readCloser rc func NewRegretableReaderCloser(rc io.ReadCloser) *RegretableReaderCloser { return &RegretableReaderCloser{*NewRegretableReader(rc), rc} } // initialize a RegretableReaderCloser with underlying readCloser rc func NewRegretableReaderCloserSize(rc io.ReadCloser, size int) *RegretableReaderCloser { return &RegretableReaderCloser{*NewRegretableReaderSize(rc, size), rc} } // The next read from the RegretableReader will be as if the underlying reader // was never read (or from the last point forget is called). func (rb *RegretableReader) Regret() { if rb.overflow { panic("regretting after overflow makes no sense") } rb.r = 0 } // Will "forget" everything read so far. // rb := NewRegretableReader(bytes.NewBuffer([]byte{1,2,3})) // var b = make([]byte,1) // rb.Read(b) // b[0] = 1 // rb.Forget() // rb.Read(b) // b[0] = 2 // rb.Regret() // ioutil.ReadAll(rb.Read) // returns []byte{2,3},nil func (rb *RegretableReader) Forget() { if rb.overflow { panic("forgetting after overflow makes no sense") } rb.r = 0 rb.w = 0 } // initialize a RegretableReader with underlying reader r, whose buffer is size bytes long func NewRegretableReaderSize(r io.Reader, size int) *RegretableReader { return &RegretableReader{reader: r, buf: make([]byte, size) } } // initialize a RegretableReader with underlying reader r func NewRegretableReader(r io.Reader) *RegretableReader { return NewRegretableReaderSize(r, defaultBufferSize) } // reads from the underlying reader. Will buffer all input until Regret is called. func (rb *RegretableReader) Read(p []byte) (n int, err error) { if rb.overflow { return rb.reader.Read(p) } if rb.r < rb.w { n = copy(p, rb.buf[rb.r:rb.w]) rb.r += n return } n, err = rb.reader.Read(p) bn := copy(rb.buf[rb.w:], p[:n]) rb.w, rb.r = rb.w + bn, rb.w + n if bn < n { rb.overflow = true } return } goproxy-1.0/regretable/regretreader_test.go000066400000000000000000000073121240375402100212370ustar00rootroot00000000000000package regretable_test import ( . "github.com/elazarl/goproxy/regretable" "bytes" "io" "io/ioutil" "strings" "testing" ) func TestRegretableReader(t *testing.T) { buf := new(bytes.Buffer) mb := NewRegretableReader(buf) word := "12345678" buf.WriteString(word) fivebytes := make([]byte, 5) mb.Read(fivebytes) mb.Regret() s, _ := ioutil.ReadAll(mb) if string(s) != word { t.Errorf("Uncommited read is gone, [%d,%d] actual '%v' expected '%v'\n", len(s), len(word), string(s), word) } } func TestRegretableEmptyRead(t *testing.T) { buf := new(bytes.Buffer) mb := NewRegretableReader(buf) word := "12345678" buf.WriteString(word) zero := make([]byte, 0) mb.Read(zero) mb.Regret() s, err := ioutil.ReadAll(mb) if string(s) != word { t.Error("Uncommited read is gone, actual:", string(s), "expected:", word, "err:", err) } } func TestRegretableAlsoEmptyRead(t *testing.T) { buf := new(bytes.Buffer) mb := NewRegretableReader(buf) word := "12345678" buf.WriteString(word) one := make([]byte, 1) zero := make([]byte, 0) five := make([]byte, 5) mb.Read(one) mb.Read(zero) mb.Read(five) mb.Regret() s, _ := ioutil.ReadAll(mb) if string(s) != word { t.Error("Uncommited read is gone", string(s), "expected", word) } } func TestRegretableRegretBeforeRead(t *testing.T) { buf := new(bytes.Buffer) mb := NewRegretableReader(buf) word := "12345678" buf.WriteString(word) five := make([]byte, 5) mb.Regret() mb.Read(five) s, err := ioutil.ReadAll(mb) if string(s) != "678" { t.Error("Uncommited read is gone", string(s), len(string(s)), "expected", "678", len("678"), "err:", err) } } func TestRegretableFullRead(t *testing.T) { buf := new(bytes.Buffer) mb := NewRegretableReader(buf) word := "12345678" buf.WriteString(word) twenty := make([]byte, 20) mb.Read(twenty) mb.Regret() s, _ := ioutil.ReadAll(mb) if string(s) != word { t.Error("Uncommited read is gone", string(s), len(string(s)), "expected", word, len(word)) } } func assertEqual(t *testing.T, expected, actual string) { if expected!=actual { t.Fatal("Expected", expected, "actual", actual) } } func assertReadAll(t *testing.T, r io.Reader) string { s, err := ioutil.ReadAll(r) if err!=nil { t.Fatal("error when reading", err) } return string(s) } func TestRegretableRegretTwice(t *testing.T) { buf := new(bytes.Buffer) mb := NewRegretableReader(buf) word := "12345678" buf.WriteString(word) assertEqual(t, word, assertReadAll(t, mb)) mb.Regret() assertEqual(t, word, assertReadAll(t, mb)) mb.Regret() assertEqual(t, word, assertReadAll(t, mb)) } type CloseCounter struct { r io.Reader closed int } func (cc *CloseCounter) Read(b []byte) (int, error) { return cc.r.Read(b) } func (cc *CloseCounter) Close() error { cc.closed++ return nil } func assert(t *testing.T, b bool, msg string) { if !b { t.Errorf("Assertion Error: %s", msg) } } func TestRegretableCloserSizeRegrets(t *testing.T) { defer func() { if r := recover(); r == nil || !strings.Contains(r.(string), "regret") { t.Error("Did not panic when regretting overread buffer:", r) } }() buf := new(bytes.Buffer) buf.WriteString("123456") mb := NewRegretableReaderCloserSize(ioutil.NopCloser(buf), 3) mb.Read(make([]byte, 4)) mb.Regret() } func TestRegretableCloserRegretsClose(t *testing.T) { buf := new(bytes.Buffer) cc := &CloseCounter{buf, 0} mb := NewRegretableReaderCloser(cc) word := "12345678" buf.WriteString(word) mb.Read([]byte{0}) mb.Close() if cc.closed != 1 { t.Error("RegretableReaderCloser ignores Close") } mb.Regret() mb.Close() if cc.closed != 2 { t.Error("RegretableReaderCloser does ignore Close after regret") } // TODO(elazar): return an error if client issues Close more than once after regret } goproxy-1.0/responses.go000066400000000000000000000023531240375402100154320ustar00rootroot00000000000000package goproxy import ( "bytes" "io/ioutil" "net/http" ) // Will generate a valid http response to the given request the response will have // the given contentType, and http status. // Typical usage, refuse to process requests to local addresses: // // proxy.OnRequest(IsLocalHost()).DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request,*http.Response) { // return nil,NewResponse(r,goproxy.ContentTypeHtml,http.StatusUnauthorized, // `Can't use proxy for local addresses`) // }) func NewResponse(r *http.Request, contentType string, status int, body string) *http.Response { resp := &http.Response{} resp.Request = r resp.TransferEncoding = r.TransferEncoding resp.Header = make(http.Header) resp.Header.Add("Content-Type", contentType) resp.StatusCode = status buf := bytes.NewBufferString(body) resp.ContentLength = int64(buf.Len()) resp.Body = ioutil.NopCloser(buf) return resp } const ( ContentTypeText = "text/plain" ContentTypeHtml = "text/html" ) // Alias for NewResponse(r,ContentTypeText,http.StatusAccepted,text) func TextResponse(r *http.Request, text string) *http.Response { return NewResponse(r, ContentTypeText, http.StatusAccepted, text) } goproxy-1.0/signer.go000066400000000000000000000041471240375402100147030ustar00rootroot00000000000000package goproxy import ( "crypto/rsa" "crypto/sha1" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "math/big" "net" "runtime" "sort" "time" ) func hashSorted(lst []string) []byte { c := make([]string, len(lst)) copy(c, lst) sort.Strings(c) h := sha1.New() for _, s := range c { h.Write([]byte(s + ",")) } return h.Sum(nil) } func hashSortedBigInt(lst []string) *big.Int { rv := new(big.Int) rv.SetBytes(hashSorted(lst)) return rv } var goproxySignerVersion = ":goroxy1" func signHost(ca tls.Certificate, hosts []string) (cert tls.Certificate, err error) { var x509ca *x509.Certificate if x509ca, err = x509.ParseCertificate(GoproxyCa.Certificate[0]); err != nil { return } start := time.Unix(0, 0) end, err := time.Parse("2006-01-02", "2049-12-31") if err != nil { panic(err) } hash := hashSorted(append(hosts, goproxySignerVersion, ":"+runtime.Version())) serial := new(big.Int) serial.SetBytes(hash) template := x509.Certificate{ // TODO(elazar): instead of this ugly hack, just encode the certificate and hash the binary form. SerialNumber: serial, Issuer: x509ca.Subject, Subject: pkix.Name{ Organization: []string{"GoProxy untrusted MITM proxy Inc"}, }, NotBefore: start, NotAfter: end, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } for _, h := range hosts { if ip := net.ParseIP(h); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else { template.DNSNames = append(template.DNSNames, h) } } var csprng CounterEncryptorRand if csprng, err = NewCounterEncryptorRandFromKey(ca.PrivateKey, hash); err != nil { return } var certpriv *rsa.PrivateKey if certpriv, err = rsa.GenerateKey(&csprng, 1024); err != nil { return } var derBytes []byte if derBytes, err = x509.CreateCertificate(&csprng, &template, x509ca, &certpriv.PublicKey, ca.PrivateKey); err != nil { return } return tls.Certificate{ Certificate: [][]byte{derBytes, ca.Certificate[0]}, PrivateKey: certpriv, }, nil } goproxy-1.0/signer_test.go000066400000000000000000000044721240375402100157430ustar00rootroot00000000000000package goproxy import ( "crypto/tls" "crypto/x509" "io/ioutil" "net/http" "net/http/httptest" "os" "os/exec" "strings" "testing" "time" ) func orFatal(msg string, err error, t *testing.T) { if err != nil { t.Fatal(msg, err) } } type ConstantHanlder string func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte(h)) } func getBrowser(args []string) string { for i, arg := range args { if arg == "-browser" && i+1 < len(arg) { return args[i+1] } if strings.HasPrefix(arg, "-browser=") { return arg[len("-browser="):] } } return "" } func TestSingerTls(t *testing.T) { cert, err := signHost(GoproxyCa, []string{"example.com", "1.1.1.1", "localhost"}) orFatal("singHost", err, t) cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) orFatal("ParseCertificate", err, t) expected := "key verifies with Go" server := httptest.NewUnstartedServer(ConstantHanlder(expected)) defer server.Close() server.TLS = &tls.Config{Certificates: []tls.Certificate{cert, GoproxyCa}} server.TLS.BuildNameToCertificate() server.StartTLS() certpool := x509.NewCertPool() certpool.AddCert(GoproxyCa.Leaf) tr := &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: certpool}, } asLocalhost := strings.Replace(server.URL, "127.0.0.1", "localhost", -1) req, err := http.NewRequest("GET", asLocalhost, nil) orFatal("NewRequest", err, t) resp, err := tr.RoundTrip(req) orFatal("RoundTrip", err, t) txt, err := ioutil.ReadAll(resp.Body) orFatal("ioutil.ReadAll", err, t) if string(txt) != expected { t.Errorf("Expected '%s' got '%s'", expected, string(txt)) } browser := getBrowser(os.Args) if browser != "" { exec.Command(browser, asLocalhost).Run() time.Sleep(10 * time.Second) } } func TestSingerX509(t *testing.T) { cert, err := signHost(GoproxyCa, []string{"example.com", "1.1.1.1", "localhost"}) orFatal("singHost", err, t) cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) orFatal("ParseCertificate", err, t) certpool := x509.NewCertPool() certpool.AddCert(GoproxyCa.Leaf) orFatal("VerifyHostname", cert.Leaf.VerifyHostname("example.com"), t) orFatal("CheckSignatureFrom", cert.Leaf.CheckSignatureFrom(GoproxyCa.Leaf), t) _, err = cert.Leaf.Verify(x509.VerifyOptions{ DNSName: "example.com", Roots: certpool, }) orFatal("Verify", err, t) } goproxy-1.0/test_data/000077500000000000000000000000001240375402100150275ustar00rootroot00000000000000goproxy-1.0/test_data/baby.jpg000066400000000000000000000050131240375402100164450ustar00rootroot00000000000000JFIF;CREATOR: gd-jpeg v1.0 (using IJG JPEG v80), quality = 95 C     C  22" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?Bim r:W# iZ[)|!wN=z|0|S.b6Ki;|S4j:<!aѤ* }rk0xz2 N׷[eBe %{_/>3~xCxY\aPOd_~(naƯ[i`r7G= }M|z~=s-^k[[u=w+z_'wS8P0ҵ9<黧{vz9tR6jF|@G'H, dF@888=;\?7fGUT+$2сQAXm M:݃f%i235c8ܺ Sw>&Y_76{7DQqӂG;x 5[G{-~(nݢ%lGsE|W{gK#_h`1¾+ߊş|5|7kkX#^ uA5K>H&`c29+U}/Pjڙg"D%~_SeUjz[Ecd^$n`/"k\^XaƇ=GaŞ;oaȹveY6ynsY^5$: KµWW${yof] QfoFx_i\ 2hNu+(̄FH,Gҳ!G;.;W ڌ[(62rpqі_]έ$IrY-譻뷃=x{MktAǔ,g2-;H1[*@*+mU0()_FϜ} mFеA̭WM"[x%*If߉Yi42@3|~ᏇZ杪 [LQlpĞ)Z46kg0Xaqާ93ͧc4F@3ϓ~rG$WIS_c.(Ln,}k?>>i%MmNፗk0gU#ƍI+Ztc 8hQV}+m̨ Kn|_+1]/QIX4==̐ldݸW~u hvg58u ÎHf݂8HiT_4{ÿ 5 +TK}JmwH&f8pC|IC7xYޱBew嚕G哸I$X>/C/sWɊ播BWoG=Q]0x"UdhK $>0b&>]v,S>?h[xm%۠2tj_NKBHЩe8'ݣnoR/~5[ iup\\ %3kOXY;H(1*0hGoj(2Ž~;q_#C6VziEqC H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxYUofLg5qE5(i(*`>I"jD.<QL\PNLi\t?5Ujzz=>g.Xq¯؆؎ݭř` LlGPA\5j9ơ x Vv~r,° n=m ~La=F ~,D\[AbS p6.`N+K.co cq!6+x #@*;C,V4@BQXmFÓ=P>_X 3_(i3l nk}ܦowǵ4ɟ x'f½ܺ 9'O,-ւ7seuεn2g"2S7,в~8n>1HeKn8J#Z[o_rE;`hU[ U~X܊[] &n]TƎpHyl΋QICeb Ҟa =-Jl1U⢺8p,eXܩ 7pboI/H&j'erW_ {{ X =3t+g3m,B-Lƃv^6g-Ea0 %xb*F\PPi0Dj#+}0 T:8BZ'Ea&hC|d v][htk8YqM.[Voyt!p6. o,;&)xtSO6@ qa+2((((((((((Q3xYSZIENDB`goproxy-1.0/test_data/panda.png000066400000000000000000000257321240375402100166310ustar00rootroot00000000000000PNG  IHDR@@iq pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F!IDATx{iuwo_f:3 @$E"ͤ(Y$TE.)rlW*vJ~8+^ʒX%ɴd(Ő `ַz=ETyU]>{w513~ 3C-֬^ F= X(bffE\BB#0"Qñ042P*զ(*3g dd V?Sop]YkaCe"I$Ic *5|jjr.yAUN{jcԛ}8KW섟$ RJHD謋9x?Y*Wv[+%[W5\pM7T$}֭b!zsz{vx)[R(u>`@HrXM+j~nf JG|-m+\ V J)dY뮽:uK/']sDjsS=Fm0FFG{捿}6h`q5fz=PRoʕ !]]; ȀJA.W"!vbͥ%efz?}뮻V ]hH+O|ΡZm-a6>z(^}5h̙3X~6^7mm kOR Rڱ3@D`LjccHAVZm=/Ǒ&m\}rS{ݮK/#2T+X(c}w!ZC* v ! .BB xZ#=A@qc۶ilc\ˀZ\'( ڝ.eƢ!|W?-W_M,Gk' <03vŵ-B)%FIoݹӭ\1=zp+N>pY !?v:֭~AZv; he4;X,1mcV0se;nX^wkN7P(: hػgcJJu{= i5!~[>/忝Z?u2%E0oa8 AD|9KDR0D2c B>_@V} kO/\txY,uW]K|B!zÁ#52cy$HJeJ; !Z?6>~ͪի^֮Y}YYuS()G xZ}8k=-2)b\!4AFxWh~~X*J}<$]a3>_FdPټ<߃ e;0!.eSJ"p &3xL#jFNg+/%% @[: v.'9WY9MbZ obX3gN{⋈j'sN1T8|0B7bIH%!BT*~!'@ԯR1N:ٹt:?3sy(*ufZ?(1u~4G_*^wG IVŸ_RZ.yhOaj:t;]e|+OϢZ5i'Vߒk3?¡CG~ \qemkmoʬsRzpš>ROc-^.%1cll0T+C%Tu-Qvu pi%XS.8?lhx$}BI%BHAR>o9{Il^=3hZ)G~铿{N׈ضڭ C xX1-7O?ueQczX,\m)% *ybj~o| rD 0-7H"ͰwnXkqucxMR < AzsmZZLfp39kMy ?~,Ȳ&G1(BFcV r ~ Z%?Rn|| Kpl1,b:iDȲ!K3޽©ӧnw[yr-8s 0B\"| /yeTxp>B)%,=jj[RDQ4K߶ ݧ7=_[mo|3TJRLRP h "b=G$:REHϗp΢}݇M7駞E|7?>;vKKK}JGGvmټ%ItV!e* O8qɷr|r!u)5BJ3??ogfڵ. $E9'5$ `0;dYU+WaW_6?Pp)<DZ{ZcZfU׶}Ͻ}ߟ`F<::y^( '~ Y!M AZkredn~~^:u6blli 2dYgQNK<KK zGp9Y<я~ĝn:kVyZsT ( p+Wm^g&ZxӦ]|7s AധEPJJl6c(cc`g!`&aDL^vNF;N{zkV@ׇ'ؽkZ ,̈́1 䱱1^ۏC96BQ$@`J LaZN 2Qf(J2y̞+/ 6`hhPadtvF!%fpadi,K+M$v:A)%Lbaǃ>(fgfyfvV;kRDQȎs\L`h~17H`80ĀPJ@H*_!(J`v}h6ۈ>ꫯ=x?Tj XJ1¡z%+|ۉRY^k9ssVZc>裂FGGX뤒Bc"0:#ޛ c,Ȳ fjsA.|^jBIX, n^.RJiyC*!;~Z*S`VR AZ#?C14zU&Ib4Qf;J)b5|v&@.8iR1l^z3#?_gyZqKu%) |wczf~WgnD`f{u t L({&5Zm&p֒R* -) AӤ΂AϡnP,0::u͛6Q^jYO$3?fFf `0=={Dsidj,&&?t?8` K lYU>U%@ܧ.%rؘ.[I"<`'A*)u'1T(TRڇq$bùAxa1)4lA!zb|IOo0R c @G(~;w=~|z+_fΟ#y٭'N hdfpML PCE>P4˳8]/ $ 4apAt{2kzavvvɝN^HfiP(λ]e@~3(0RJXk0;;o~o5u[ K ?Y\\Lƾ86:~vl߮~W4s_sŦWZ@:Pa{Es\T s~#R # Hr9hO#} x[PȲdw.IB+WĊ v Pa1HgϞ IDRHr P,VʓJ)S彻[߆}/Պb6UB ؐW`J\7:s2I/m *2vi G u;!<<[fĴRt,%nՊm⾟;oډr p}%P/h6`P̯c7BбEV&8u>AP <=G!&(F!_@!OX~=Mann](A)f 7}AYh4! ԗqX  @kzM8kQZ*8K@A,@P ǞK ȃQ\ͤfe$.ȲHӌIr-.6w^qi| ³>??}Af`vR/w&ˠp`ő#GH$I B|O MC7ujaSB$RHe`8\(+k,ĉNN6]O/ow܉),-."|q>o׾Hdyov9@Mpd9( HBp$)H,c&ˢSgNChՂxUdD@> HL$A d,˸aii܉w LNL" #!a|lO" #4B@H yow  ~bnJud0Ơl8n/.-B* gΡ4PADNA8b `ǼeIJIbH;2B^zN|? 3brfDj DZ+V M>OH$4E$ ""Dv= Nh)Vbc;.,Td™ӧqSk3%8I0`(l  Q9D& ))RabYf(+`xh3"r~VNZ?f8=4HvN|0 j4qA裏R(ZiH)`m'IťUaS<GCG{iZ.y>Y [ǎ-9 $ ͦi:*g֐Il)coAuQ48EXHh0>>! $|`>[ČsWjc=ؽk8u0A).Pa&3]^αC;oYᅽ{]/K?K~u[ Al ';8fH0R" t4[K\C$pQnľ};o֯gk !dTFv33C,"&ڭN:ln|.X.[΁!24[-\ĉ˿_O=B֣\֌Cbkl?HdFoH'ۭsJ AwIy~n>ظqduVj+_a~aN?>cZjj͆(|ݞ ð ^ eKRQRI_IU&"O 't WR+c ;}iZP%f **нP- so,-CC:$&3aɧ‡>/Ej}`/6ĥ&u2;LV ϓ43[v`ǶIJfPٱy!DUi9^텽3|HuF& 2S02!@P3 N2(fXv:* ;A<=~ПA:ݮhV>ˌ RoundTripper // TODO: tunable on global max cached connections // TODO: tunable on timeout on cached connections // TODO: optional pipelining // Proxy specifies a function to return a proxy for a given // Request. If the function returns a non-nil error, the // request is aborted with the provided error. // If Proxy is nil or returns a nil *URL, no proxy is used. Proxy func(*http.Request) (*url.URL, error) // Dial specifies the dial function for creating TCP // connections. // If Dial is nil, net.Dial is used. Dial func(net, addr string) (c net.Conn, err error) // TLSClientConfig specifies the TLS configuration to use with // tls.Client. If nil, the default configuration is used. TLSClientConfig *tls.Config DisableKeepAlives bool DisableCompression bool // MaxIdleConnsPerHost, if non-zero, controls the maximum idle // (keep-alive) to keep to keep per-host. If zero, // DefaultMaxIdleConnsPerHost is used. MaxIdleConnsPerHost int } // ProxyFromEnvironment returns the URL of the proxy to use for a // given request, as indicated by the environment variables // $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy). // An error is returned if the proxy environment is invalid. // A nil URL and nil error are returned if no proxy is defined in the // environment, or a proxy should not be used for the given request. func ProxyFromEnvironment(req *http.Request) (*url.URL, error) { proxy := getenvEitherCase("HTTP_PROXY") if proxy == "" { return nil, nil } if !useProxy(canonicalAddr(req.URL)) { return nil, nil } proxyURL, err := url.Parse(proxy) if err != nil || proxyURL.Scheme == "" { if u, err := url.Parse("http://" + proxy); err == nil { proxyURL = u err = nil } } if err != nil { return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err) } return proxyURL, nil } // ProxyURL returns a proxy function (for use in a Transport) // that always returns the same URL. func ProxyURL(fixedURL *url.URL) func(*http.Request) (*url.URL, error) { return func(*http.Request) (*url.URL, error) { return fixedURL, nil } } // transportRequest is a wrapper around a *Request that adds // optional extra headers to write. type transportRequest struct { *http.Request // original request, not to be mutated extra http.Header // extra headers to write, or nil } func (tr *transportRequest) extraHeaders() http.Header { if tr.extra == nil { tr.extra = make(http.Header) } return tr.extra } type RoundTripDetails struct { Host string TCPAddr *net.TCPAddr IsProxy bool Error error } func (t *Transport) DetailedRoundTrip(req *http.Request) (details *RoundTripDetails, resp *http.Response, err error) { if req.URL == nil { return nil, nil, errors.New("http: nil Request.URL") } if req.Header == nil { return nil, nil, errors.New("http: nil Request.Header") } if req.URL.Scheme != "http" && req.URL.Scheme != "https" { t.lk.Lock() var rt RoundTripper if t.altProto != nil { rt = t.altProto[req.URL.Scheme] } t.lk.Unlock() if rt == nil { return nil, nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme} } return rt.DetailedRoundTrip(req) } treq := &transportRequest{Request: req} cm, err := t.connectMethodForRequest(treq) if err != nil { return nil, nil, err } // Get the cached or newly-created connection to either the // host (for http or https), the http proxy, or the http proxy // pre-CONNECTed to https server. In any case, we'll be ready // to send it requests. pconn, err := t.getConn(cm) if err != nil { return nil, nil, err } resp, err = pconn.roundTrip(treq) return &RoundTripDetails{pconn.host, pconn.ip, pconn.isProxy, err}, resp, err } // RoundTrip implements the RoundTripper interface. func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { _, resp, err = t.DetailedRoundTrip(req) return } // RegisterProtocol registers a new protocol with scheme. // The Transport will pass requests using the given scheme to rt. // It is rt's responsibility to simulate HTTP request semantics. // // RegisterProtocol can be used by other packages to provide // implementations of protocol schemes like "ftp" or "file". func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) { if scheme == "http" || scheme == "https" { panic("protocol " + scheme + " already registered") } t.lk.Lock() defer t.lk.Unlock() if t.altProto == nil { t.altProto = make(map[string]RoundTripper) } if _, exists := t.altProto[scheme]; exists { panic("protocol " + scheme + " already registered") } t.altProto[scheme] = rt } // CloseIdleConnections closes any connections which were previously // connected from previous requests but are now sitting idle in // a "keep-alive" state. It does not interrupt any connections currently // in use. func (t *Transport) CloseIdleConnections() { t.lk.Lock() defer t.lk.Unlock() if t.idleConn == nil { return } for _, conns := range t.idleConn { for _, pconn := range conns { pconn.close() } } t.idleConn = make(map[string][]*persistConn) } // // Private implementation past this point. // func getenvEitherCase(k string) string { if v := os.Getenv(strings.ToUpper(k)); v != "" { return v } return os.Getenv(strings.ToLower(k)) } func (t *Transport) connectMethodForRequest(treq *transportRequest) (*connectMethod, error) { cm := &connectMethod{ targetScheme: treq.URL.Scheme, targetAddr: canonicalAddr(treq.URL), } if t.Proxy != nil { var err error cm.proxyURL, err = t.Proxy(treq.Request) if err != nil { return nil, err } } return cm, nil } // proxyAuth returns the Proxy-Authorization header to set // on requests, if applicable. func (cm *connectMethod) proxyAuth() string { if cm.proxyURL == nil { return "" } if u := cm.proxyURL.User; u != nil { return "Basic " + base64.URLEncoding.EncodeToString([]byte(u.String())) } return "" } // putIdleConn adds pconn to the list of idle persistent connections awaiting // a new request. // If pconn is no longer needed or not in a good state, putIdleConn // returns false. func (t *Transport) putIdleConn(pconn *persistConn) bool { t.lk.Lock() defer t.lk.Unlock() if t.DisableKeepAlives || t.MaxIdleConnsPerHost < 0 { pconn.close() return false } if pconn.isBroken() { return false } key := pconn.cacheKey max := t.MaxIdleConnsPerHost if max == 0 { max = DefaultMaxIdleConnsPerHost } if len(t.idleConn[key]) >= max { pconn.close() return false } t.idleConn[key] = append(t.idleConn[key], pconn) return true } func (t *Transport) getIdleConn(cm *connectMethod) (pconn *persistConn) { t.lk.Lock() defer t.lk.Unlock() if t.idleConn == nil { t.idleConn = make(map[string][]*persistConn) } key := cm.String() for { pconns, ok := t.idleConn[key] if !ok { return nil } if len(pconns) == 1 { pconn = pconns[0] delete(t.idleConn, key) } else { // 2 or more cached connections; pop last // TODO: queue? pconn = pconns[len(pconns)-1] t.idleConn[key] = pconns[0 : len(pconns)-1] } if !pconn.isBroken() { return } } return } func (t *Transport) dial(network, addr string) (c net.Conn, raddr string, ip *net.TCPAddr, err error) { if t.Dial != nil { ip, err = net.ResolveTCPAddr("tcp", addr) if err!=nil { return } c, err = t.Dial(network, addr) raddr = addr return } addri, err := net.ResolveTCPAddr("tcp", addr) if err!=nil { return } c, err = net.DialTCP("tcp", nil, addri) raddr = addr ip = addri return } // getConn dials and creates a new persistConn to the target as // specified in the connectMethod. This includes doing a proxy CONNECT // and/or setting up TLS. If this doesn't return an error, the persistConn // is ready to write requests to. func (t *Transport) getConn(cm *connectMethod) (*persistConn, error) { if pc := t.getIdleConn(cm); pc != nil { return pc, nil } conn, raddr, ip, err := t.dial("tcp", cm.addr()) if err != nil { if cm.proxyURL != nil { err = fmt.Errorf("http: error connecting to proxy %s: %v", cm.proxyURL, err) } return nil, err } pa := cm.proxyAuth() pconn := &persistConn{ t: t, cacheKey: cm.String(), conn: conn, reqch: make(chan requestAndChan, 50), host: raddr, ip: ip, } switch { case cm.proxyURL == nil: // Do nothing. case cm.targetScheme == "http": pconn.isProxy = true if pa != "" { pconn.mutateHeaderFunc = func(h http.Header) { h.Set("Proxy-Authorization", pa) } } case cm.targetScheme == "https": connectReq := &http.Request{ Method: "CONNECT", URL: &url.URL{Opaque: cm.targetAddr}, Host: cm.targetAddr, Header: make(http.Header), } if pa != "" { connectReq.Header.Set("Proxy-Authorization", pa) } connectReq.Write(conn) // 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 := http.ReadResponse(br, connectReq) if err != nil { conn.Close() return nil, err } if resp.StatusCode != 200 { f := strings.SplitN(resp.Status, " ", 2) conn.Close() return nil, errors.New(f[1]) } } if cm.targetScheme == "https" { // Initiate TLS and check remote host name against certificate. conn = tls.Client(conn, t.TLSClientConfig) if err = conn.(*tls.Conn).Handshake(); err != nil { return nil, err } if t.TLSClientConfig == nil || !t.TLSClientConfig.InsecureSkipVerify { if err = conn.(*tls.Conn).VerifyHostname(cm.tlsHost()); err != nil { return nil, err } } pconn.conn = conn } pconn.br = bufio.NewReader(pconn.conn) pconn.bw = bufio.NewWriter(pconn.conn) go pconn.readLoop() return pconn, nil } // useProxy returns true if requests to addr should use a proxy, // according to the NO_PROXY or no_proxy environment variable. // addr is always a canonicalAddr with a host and port. func useProxy(addr string) bool { if len(addr) == 0 { return true } host, _, err := net.SplitHostPort(addr) if err != nil { return false } if host == "localhost" { return false } if ip := net.ParseIP(host); ip != nil { if ip.IsLoopback() { return false } } no_proxy := getenvEitherCase("NO_PROXY") if no_proxy == "*" { return false } addr = strings.ToLower(strings.TrimSpace(addr)) if hasPort(addr) { addr = addr[:strings.LastIndex(addr, ":")] } for _, p := range strings.Split(no_proxy, ",") { 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 false } } return true } // connectMethod is the map key (in its String form) for keeping persistent // TCP connections alive for subsequent HTTP requests. // // A connect method may be of the following types: // // Cache key form Description // ----------------- ------------------------- // ||http|foo.com http directly to server, no proxy // ||https|foo.com https directly to server, no proxy // http://proxy.com|https|foo.com http to proxy, then CONNECT to foo.com // http://proxy.com|http http to proxy, http to anywhere after that // // Note: no support to https to the proxy yet. // type connectMethod struct { proxyURL *url.URL // nil for no proxy, else full proxy URL targetScheme string // "http" or "https" targetAddr string // Not used if proxy + http targetScheme (4th example in table) } func (ck *connectMethod) String() string { proxyStr := "" if ck.proxyURL != nil { proxyStr = ck.proxyURL.String() } return strings.Join([]string{proxyStr, ck.targetScheme, ck.targetAddr}, "|") } // addr returns the first hop "host:port" to which we need to TCP connect. func (cm *connectMethod) addr() string { if cm.proxyURL != nil { return canonicalAddr(cm.proxyURL) } return cm.targetAddr } // tlsHost returns the host name to match against the peer's // TLS certificate. func (cm *connectMethod) tlsHost() string { h := cm.targetAddr if hasPort(h) { h = h[:strings.LastIndex(h, ":")] } return h } // persistConn wraps a connection, usually a persistent one // (but may be used for non-keep-alive requests as well) type persistConn struct { t *Transport cacheKey string // its connectMethod.String() conn net.Conn br *bufio.Reader // from conn bw *bufio.Writer // to conn reqch chan requestAndChan // written by roundTrip(); read by readLoop() isProxy bool // mutateHeaderFunc is an optional func to modify extra // headers on each outbound request before it's written. (the // original Request given to RoundTrip is not modified) mutateHeaderFunc func(http.Header) lk sync.Mutex // guards numExpectedResponses and broken numExpectedResponses int broken bool // an error has happened on this connection; marked broken so it's not reused. host string ip *net.TCPAddr } func (pc *persistConn) isBroken() bool { pc.lk.Lock() defer pc.lk.Unlock() return pc.broken } var remoteSideClosedFunc func(error) bool // or nil to use default func remoteSideClosed(err error) bool { if err == io.EOF { return true } if remoteSideClosedFunc != nil { return remoteSideClosedFunc(err) } return false } func (pc *persistConn) readLoop() { alive := true var lastbody io.ReadCloser // last response body, if any, read on this connection for alive { pb, err := pc.br.Peek(1) pc.lk.Lock() if pc.numExpectedResponses == 0 { pc.closeLocked() pc.lk.Unlock() if len(pb) > 0 { log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", string(pb), err) } return } pc.lk.Unlock() rc := <-pc.reqch // Advance past the previous response's body, if the // caller hasn't done so. if lastbody != nil { lastbody.Close() // assumed idempotent lastbody = nil } resp, err := http.ReadResponse(pc.br, rc.req) if err != nil { pc.close() } else { hasBody := rc.req.Method != "HEAD" && resp.ContentLength != 0 if rc.addedGzip && hasBody && resp.Header.Get("Content-Encoding") == "gzip" { resp.Header.Del("Content-Encoding") resp.Header.Del("Content-Length") resp.ContentLength = -1 gzReader, zerr := gzip.NewReader(resp.Body) if zerr != nil { pc.close() err = zerr } else { resp.Body = &readFirstCloseBoth{&discardOnCloseReadCloser{gzReader}, resp.Body} } } resp.Body = &bodyEOFSignal{body: resp.Body} } if err != nil || resp.Close || rc.req.Close { alive = false } hasBody := resp != nil && resp.ContentLength != 0 var waitForBodyRead chan bool if alive { if hasBody { lastbody = resp.Body waitForBodyRead = make(chan bool) resp.Body.(*bodyEOFSignal).fn = func() { if !pc.t.putIdleConn(pc) { alive = false } waitForBodyRead <- true } } else { // When there's no response body, we immediately // reuse the TCP connection (putIdleConn), but // we need to prevent ClientConn.Read from // closing the Response.Body on the next // loop, otherwise it might close the body // before the client code has had a chance to // read it (even though it'll just be 0, EOF). lastbody = nil if !pc.t.putIdleConn(pc) { alive = false } } } rc.ch <- responseAndError{resp, err} // Wait for the just-returned response body to be fully consumed // before we race and peek on the underlying bufio reader. if waitForBodyRead != nil { <-waitForBodyRead } } } type responseAndError struct { res *http.Response err error } type requestAndChan struct { req *http.Request ch chan responseAndError // did the Transport (as opposed to the client code) add an // Accept-Encoding gzip header? only if it we set it do // we transparently decode the gzip. addedGzip bool } func (pc *persistConn) roundTrip(req *transportRequest) (resp *http.Response, err error) { if pc.mutateHeaderFunc != nil { panic("mutateHeaderFunc not supported in modified Transport") pc.mutateHeaderFunc(req.extraHeaders()) } // Ask for a compressed version if the caller didn't set their // own value for Accept-Encoding. We only attempted to // uncompress the gzip stream if we were the layer that // requested it. requestedGzip := false if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" { // Request gzip only, not deflate. Deflate is ambiguous and // not as universally supported anyway. // See: http://www.gzip.org/zlib/zlib_faq.html#faq38 requestedGzip = true req.extraHeaders().Set("Accept-Encoding", "gzip") } pc.lk.Lock() pc.numExpectedResponses++ pc.lk.Unlock() // orig: err = req.Request.write(pc.bw, pc.isProxy, req.extra) if pc.isProxy { err = req.Request.WriteProxy(pc.bw) } else { err = req.Request.Write(pc.bw) } if err != nil { pc.close() return } pc.bw.Flush() ch := make(chan responseAndError, 1) pc.reqch <- requestAndChan{req.Request, ch, requestedGzip} re := <-ch pc.lk.Lock() pc.numExpectedResponses-- pc.lk.Unlock() return re.res, re.err } func (pc *persistConn) close() { pc.lk.Lock() defer pc.lk.Unlock() pc.closeLocked() } func (pc *persistConn) closeLocked() { pc.broken = true pc.conn.Close() pc.mutateHeaderFunc = nil } var portMap = map[string]string{ "http": "80", "https": "443", } // canonicalAddr returns url.Host but always with a ":port" suffix func canonicalAddr(url *url.URL) string { addr := url.Host if !hasPort(addr) { return addr + ":" + portMap[url.Scheme] } return addr } func responseIsKeepAlive(res *http.Response) bool { // TODO: implement. for now just always shutting down the connection. return false } // bodyEOFSignal wraps a ReadCloser but runs fn (if non-nil) at most // once, right before the final Read() or Close() call returns, but after // EOF has been seen. type bodyEOFSignal struct { body io.ReadCloser fn func() isClosed bool } func (es *bodyEOFSignal) Read(p []byte) (n int, err error) { n, err = es.body.Read(p) if es.isClosed && n > 0 { panic("http: unexpected bodyEOFSignal Read after Close; see issue 1725") } if err == io.EOF && es.fn != nil { es.fn() es.fn = nil } return } func (es *bodyEOFSignal) Close() (err error) { if es.isClosed { return nil } es.isClosed = true err = es.body.Close() if err == nil && es.fn != nil { es.fn() es.fn = nil } return } type readFirstCloseBoth struct { io.ReadCloser io.Closer } func (r *readFirstCloseBoth) Close() error { if err := r.ReadCloser.Close(); err != nil { r.Closer.Close() return err } if err := r.Closer.Close(); err != nil { return err } return nil } // discardOnCloseReadCloser consumes all its input on Close. type discardOnCloseReadCloser struct { io.ReadCloser } func (d *discardOnCloseReadCloser) Close() error { io.Copy(ioutil.Discard, d.ReadCloser) // ignore errors; likely invalid or already closed return d.ReadCloser.Close() } goproxy-1.0/transport/util.go000066400000000000000000000004401240375402100164150ustar00rootroot00000000000000package transport import ( "fmt" "strings" ) type badStringError struct { what string str string } func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }