├── .gitignore ├── test_data ├── baby.jpg ├── panda.png └── football.png ├── ext ├── html │ ├── cp1255.html │ ├── cp1255.txt │ ├── html_test.go │ └── html.go ├── go.mod ├── go.sum ├── image │ └── image.go └── auth │ ├── basic.go │ └── basic_test.go ├── logger.go ├── go.mod ├── examples ├── goproxy-jquery-version │ ├── jquery1.html │ ├── jquery2.html │ ├── README.md │ ├── main.go │ ├── jquery_test.go │ ├── jquery_homepage.html │ └── php_man.html ├── goproxy-websockets │ ├── localhost-key.pem │ ├── localhost.pem │ └── main.go ├── go.mod ├── goproxy-basic │ ├── main.go │ └── README.md ├── go.sum ├── goproxy-customca │ ├── main.go │ └── cert.go ├── goproxy-no-reddit-at-worktime │ ├── README.md │ └── noreddit.go ├── goproxy-sokeepalive │ └── sokeepalive.go ├── goproxy-upside-down-ternet │ └── main.go ├── goproxy-sslstrip │ └── sslstrip.go ├── goproxy-httpdump │ ├── README.md │ └── httpdump.go ├── goproxy-transparent │ ├── proxy.sh │ ├── README.md │ └── transparent.go ├── goproxy-stats │ ├── README.md │ └── main.go ├── goproxy-eavesdropper │ └── main.go ├── cascadeproxy │ └── main.go └── goproxy-yui-minify │ └── yui.go ├── certs ├── openssl-gen.sh └── openssl.cnf ├── go.sum ├── transport ├── util.go └── roundtripper.go ├── all.bash ├── responses.go ├── LICENSE ├── counterecryptor.go ├── chunked.go ├── ca.pem ├── actions.go ├── regretable ├── regretreader.go └── regretreader_test.go ├── signer.go ├── key.pem ├── counterecryptor_test.go ├── ctx.go ├── websocket.go ├── doc.go ├── signer_test.go ├── certs.go ├── proxy.go ├── README.md ├── dispatcher.go └── https.go /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | *.swp 3 | -------------------------------------------------------------------------------- /test_data/baby.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ice3man543/goproxy/master/test_data/baby.jpg -------------------------------------------------------------------------------- /ext/html/cp1255.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ice3man543/goproxy/master/ext/html/cp1255.html -------------------------------------------------------------------------------- /ext/html/cp1255.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ice3man543/goproxy/master/ext/html/cp1255.txt -------------------------------------------------------------------------------- /test_data/panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ice3man543/goproxy/master/test_data/panda.png -------------------------------------------------------------------------------- /test_data/football.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ice3man543/goproxy/master/test_data/football.png -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | type Logger interface { 4 | Printf(format string, v ...interface{}) 5 | } 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Ice3man543/goproxy 2 | 3 | require github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 4 | -------------------------------------------------------------------------------- /ext/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/elazarl/goproxy/ext 2 | 3 | require github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4 // indirect 4 | -------------------------------------------------------------------------------- /examples/goproxy-jquery-version/jquery1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/goproxy-jquery-version/jquery2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ext/go.sum: -------------------------------------------------------------------------------- 1 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4 h1:BN/Nyn2nWMoqGRA7G7paDNDqTXE30mXGqzzybrfo05w= 2 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 3 | -------------------------------------------------------------------------------- /examples/goproxy-websockets/localhost-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIC1vu3JK+Z6WtbpaTL4LquNQVcwSha53sVIxEdcLNG6WoAoGCCqGSM49 3 | AwEHoUQDQgAE8OpaVYv567gc00WZ8nSTDvcic+8tW7dCgGffiJogyesaERxDi+fg 4 | 0E/9WiNrF66Pl05I3r4NFD0EoIlcaSbMqw== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/elazarl/goproxy/examples/goproxy-transparent 2 | 3 | require ( 4 | github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a 5 | github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b 6 | ) 7 | 8 | replace github.com/elazarl/goproxy => ../ 9 | -------------------------------------------------------------------------------- /certs/openssl-gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | # generate CA's key 4 | openssl genrsa -aes256 -passout pass:1 -out ca.key.pem 4096 5 | openssl rsa -passin pass:1 -in ca.key.pem -out ca.key.pem.tmp 6 | mv ca.key.pem.tmp ca.key.pem 7 | 8 | openssl req -config openssl.cnf -key ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out ca.pem 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= 2 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 3 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 4 | -------------------------------------------------------------------------------- /transport/util.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type badStringError struct { 9 | what string 10 | str string 11 | } 12 | 13 | func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } 14 | 15 | func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } 16 | -------------------------------------------------------------------------------- /all.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go test || exit 4 | for action in $@; do go $action; done 5 | 6 | mkdir -p bin 7 | find regretable examples/* ext/* -maxdepth 0 -type d | while read d; do 8 | (cd $d 9 | go build -o ../../bin/$(basename $d) 10 | find *_test.go -maxdepth 0 2>/dev/null|while read f;do 11 | for action in $@; do go $action; done 12 | go test 13 | break 14 | done) 15 | done 16 | -------------------------------------------------------------------------------- /examples/goproxy-basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/Ice3man543/goproxy" 9 | ) 10 | 11 | func main() { 12 | verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") 13 | addr := flag.String("addr", ":8080", "proxy listen address") 14 | flag.Parse() 15 | proxy := goproxy.NewProxyHttpServer() 16 | proxy.Verbose = *verbose 17 | log.Fatal(http.ListenAndServe(*addr, proxy)) 18 | } 19 | -------------------------------------------------------------------------------- /examples/go.sum: -------------------------------------------------------------------------------- 1 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 2 | github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b h1:IpLPmn6Re21F0MaV6Zsc5RdSE6KuoFpWmHiUSEs3PrE= 3 | github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU= 4 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 5 | -------------------------------------------------------------------------------- /examples/goproxy-customca/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/Ice3man543/goproxy" 9 | ) 10 | 11 | func main() { 12 | verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") 13 | addr := flag.String("addr", ":8080", "proxy listen address") 14 | flag.Parse() 15 | setCA(caCert, caKey) 16 | proxy := goproxy.NewProxyHttpServer() 17 | proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) 18 | proxy.Verbose = *verbose 19 | log.Fatal(http.ListenAndServe(*addr, proxy)) 20 | } 21 | -------------------------------------------------------------------------------- /examples/goproxy-no-reddit-at-worktime/README.md: -------------------------------------------------------------------------------- 1 | # Request Filtering 2 | 3 | `goproxy-no-reddit-at-work` starts an HTTP proxy on :8080. It denies requests 4 | to "www.reddit.com" made between 8am to 5pm inclusive, local time. 5 | 6 | Start it in one shell: 7 | 8 | ```sh 9 | $ goproxy-no-reddit-at-work 10 | ``` 11 | 12 | Fetch reddit in another: 13 | 14 | ```sh 15 | $ http_proxy=http://127.0.0.1:8080 wget -O - http://www.reddit.com 16 | --2015-04-11 16:59:01-- http://www.reddit.com/ 17 | Connecting to 127.0.0.1:8080... connected. 18 | Proxy request sent, awaiting response... 403 Forbidden 19 | 2015-04-11 16:59:01 ERROR 403: Forbidden. 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /examples/goproxy-sokeepalive/sokeepalive.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/Ice3man543/goproxy" 6 | "log" 7 | "net" 8 | "net/http" 9 | ) 10 | 11 | func main() { 12 | verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") 13 | addr := flag.String("addr", ":8080", "proxy listen address") 14 | flag.Parse() 15 | proxy := goproxy.NewProxyHttpServer() 16 | proxy.Tr.Dial = func(network, addr string) (c net.Conn, err error) { 17 | c, err = net.Dial(network, addr) 18 | if c, ok := c.(*net.TCPConn); err == nil && ok { 19 | c.SetKeepAlive(true) 20 | } 21 | return 22 | } 23 | proxy.Verbose = *verbose 24 | log.Fatal(http.ListenAndServe(*addr, proxy)) 25 | } 26 | -------------------------------------------------------------------------------- /examples/goproxy-upside-down-ternet/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/Ice3man543/goproxy" 9 | goproxy_image "github.com/elazarl/goproxy/ext/image" 10 | ) 11 | 12 | func main() { 13 | proxy := goproxy.NewProxyHttpServer() 14 | proxy.OnResponse().Do(goproxy_image.HandleImage(func(img image.Image, ctx *goproxy.ProxyCtx) image.Image { 15 | dx, dy := img.Bounds().Dx(), img.Bounds().Dy() 16 | 17 | nimg := image.NewRGBA(img.Bounds()) 18 | for i := 0; i < dx; i++ { 19 | for j := 0; j <= dy; j++ { 20 | nimg.Set(i, j, img.At(i, dy-j-1)) 21 | } 22 | } 23 | return nimg 24 | })) 25 | proxy.Verbose = true 26 | log.Fatal(http.ListenAndServe(":8080", proxy)) 27 | } 28 | -------------------------------------------------------------------------------- /examples/goproxy-sslstrip/sslstrip.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/Ice3man543/goproxy" 9 | ) 10 | 11 | func main() { 12 | verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") 13 | addr := flag.String("addr", ":8080", "proxy listen address") 14 | flag.Parse() 15 | proxy := goproxy.NewProxyHttpServer() 16 | proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) 17 | proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 18 | if req.URL.Scheme == "https" { 19 | req.URL.Scheme = "http" 20 | } 21 | return req, nil 22 | }) 23 | proxy.Verbose = *verbose 24 | log.Fatal(http.ListenAndServe(*addr, proxy)) 25 | } 26 | -------------------------------------------------------------------------------- /examples/goproxy-no-reddit-at-worktime/noreddit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/Ice3man543/goproxy" 9 | ) 10 | 11 | func main() { 12 | proxy := goproxy.NewProxyHttpServer() 13 | proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc( 14 | func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 15 | h, _, _ := time.Now().Clock() 16 | if h >= 8 && h <= 17 { 17 | return r, goproxy.NewResponse(r, 18 | goproxy.ContentTypeText, http.StatusForbidden, 19 | "Don't waste your time!") 20 | } else { 21 | ctx.Warnf("clock: %d, you can waste your time...", h) 22 | } 23 | return r, nil 24 | }) 25 | log.Fatalln(http.ListenAndServe(":8080", proxy)) 26 | } 27 | -------------------------------------------------------------------------------- /transport/roundtripper.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import "net/http" 4 | 5 | type RoundTripper interface { 6 | // RoundTrip executes a single HTTP transaction, returning 7 | // the Response for the request req. RoundTrip should not 8 | // attempt to interpret the response. In particular, 9 | // RoundTrip must return err == nil if it obtained a response, 10 | // regardless of the response's HTTP status code. A non-nil 11 | // err should be reserved for failure to obtain a response. 12 | // Similarly, RoundTrip should not attempt to handle 13 | // higher-level protocol details such as redirects, 14 | // authentication, or cookies. 15 | // 16 | // RoundTrip should not modify the request, except for 17 | // consuming the Body. The request's URL and Header fields 18 | // are guaranteed to be initialized. 19 | RoundTrip(*http.Request) (*http.Response, error) 20 | DetailedRoundTrip(*http.Request) (*RoundTripDetails, *http.Response, error) 21 | } 22 | -------------------------------------------------------------------------------- /examples/goproxy-basic/README.md: -------------------------------------------------------------------------------- 1 | # Simple HTTP Proxy 2 | 3 | `goproxy-basic` starts an HTTP proxy on :8080. It only handles explicit CONNECT 4 | requests. 5 | 6 | Start it in one shell: 7 | 8 | ```sh 9 | goproxy-basic -v 10 | ``` 11 | 12 | Fetch goproxy homepage in another: 13 | 14 | ```sh 15 | http_proxy=http://127.0.0.1:8080 wget -O - \ 16 | http://ripper234.com/p/introducing-goproxy-light-http-proxy/ 17 | ``` 18 | 19 | The homepage HTML content should be displayed in the console. The proxy should 20 | have logged the request being processed: 21 | 22 | ```sh 23 | 2015/04/09 18:19:17 [001] INFO: Got request /p/introducing-goproxy-light-http-proxy/ ripper234.com GET http://ripper234.com/p/introducing-goproxy-light-http-proxy/ 24 | 2015/04/09 18:19:17 [001] INFO: Sending request GET http://ripper234.com/p/introducing-goproxy-light-http-proxy/ 25 | 2015/04/09 18:19:18 [001] INFO: Received response 200 OK 26 | 2015/04/09 18:19:18 [001] INFO: Copying response to client 200 OK [200] 27 | 2015/04/09 18:19:18 [001] INFO: Copied 44333 bytes to client error= 28 | ``` 29 | 30 | -------------------------------------------------------------------------------- /examples/goproxy-websockets/localhost.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICozCCAYugAwIBAgIUEdJ4Tu/Hs4HznpHjnb3Akj7uNKgwDQYJKoZIhvcNAQEL 3 | BQAwDTELMAkGA1UEAxMCQ0EwHhcNMTYwNTA1MjIxMjAwWhcNMjEwNTA0MjIxMjAw 4 | WjBGMQswCQYDVQQGEwJVUzEWMBQGA1UECBMNU2FuIEZyYW5jaXNjbzELMAkGA1UE 5 | BxMCQ0ExEjAQBgNVBAMTCWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEH 6 | A0IABPDqWlWL+eu4HNNFmfJ0kw73InPvLVu3QoBn34iaIMnrGhEcQ4vn4NBP/Voj 7 | axeuj5dOSN6+DRQ9BKCJXGkmzKujgYwwgYkwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud 8 | JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFD8hwmfkMLy1 9 | 9iajA7jsiEzBDT6DMB8GA1UdIwQYMBaAFBZgsrPxVqk7nrwIzeqIEl62Nj3IMBQG 10 | A1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAPYJUCwgNCVLm 11 | oITJdYtroJIOt3LegIQJWWYll4J3/WidfmW4FWD4Qumd0YRjqgIhTcgmxHWhhJXB 12 | KFd+hMD+UUs7jdYRQ80Ch/S/r/PzBNLiHkZ+ILklCU2JG25hJjXX48BB5y1eVF7O 13 | 9ds5h6KHJqNwG6B5yfB5iPxpd4/Qk/+6vNYODX5LSQqo9MpGotByjMGywcY4TesF 14 | 5AYT7pPoRkxucAifTRjw/8k653Zm2ZA9HCo+i9GrW+kyk3XP5Xk8z0GuvOCPnBIu 15 | p1jiUOH84pXap2cuR0z3vkp76mPAgsBk64RCBjCJJV8LEQbiPW4W9dQjcvlLkW7X 16 | lP5b19hr6g== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /examples/goproxy-httpdump/README.md: -------------------------------------------------------------------------------- 1 | # Trace HTTP Requests and Responses 2 | 3 | `goproxy-httpdump` starts an HTTP proxy on :8080. It handles explicit CONNECT 4 | requests and traces them in a "db" directory created in the proxy working 5 | directory. Each request type and headers are logged in a "log" file, while 6 | their bodies are dumped in files prefixed with the request session identifier. 7 | 8 | Additionally, the example demonstrates how to: 9 | - Log information asynchronously (see HttpLogger) 10 | - Allow the proxy to be stopped manually while ensuring all pending requests 11 | have been processed (in this case, logged). 12 | 13 | Start it in one shell: 14 | 15 | ```sh 16 | goproxy-httpdump 17 | ``` 18 | 19 | Fetch goproxy homepage in another: 20 | 21 | ```sh 22 | http_proxy=http://127.0.0.1:8080 wget -O - \ 23 | http://ripper234.com/p/introducing-goproxy-light-http-proxy/ 24 | ``` 25 | 26 | A "db" directory should have appeared where you started the proxy, containing 27 | two files: 28 | - log: the request/response traces 29 | - 1\_resp: the first response body 30 | 31 | -------------------------------------------------------------------------------- /examples/goproxy-jquery-version/README.md: -------------------------------------------------------------------------------- 1 | # Content Analysis 2 | 3 | `goproxy-jquery-version` starts an HTTP proxy on :8080. It checks HTML 4 | responses, looks for scripts referencing jQuery library and emits warnings if 5 | different versions of the library are being used for a given host. 6 | 7 | Start it in one shell: 8 | 9 | ```sh 10 | goproxy-jquery-version 11 | ``` 12 | 13 | Fetch goproxy homepage in another: 14 | 15 | ```sh 16 | http_proxy=http://127.0.0.1:8080 wget -O - \ 17 | http://ripper234.com/p/introducing-goproxy-light-http-proxy/ 18 | ``` 19 | 20 | Goproxy homepage uses jQuery and a mix of plugins. First the proxy reports the 21 | first use of jQuery it detects for the domain. Then, because the regular 22 | expression matching the jQuery sources is imprecise, it reports a mismatch with 23 | a plugin reference: 24 | 25 | ```sh 26 | 2015/04/11 11:23:02 [001] WARN: ripper234.com uses //ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js 27 | 2015/04/11 11:23:02 [001] WARN: In http://ripper234.com/p/introducing-goproxy-light-http-proxy/, \ 28 | Contradicting jqueries //ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js \ 29 | http://ripper234.wpengine.netdna-cdn.com/wp-content/plugins/wp-ajax-edit-comments/js/jquery.colorbox.min.js?ver=5.0.36 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /certs/openssl.cnf: -------------------------------------------------------------------------------- 1 | [ ca ] 2 | default_ca = CA_default 3 | [ CA_default ] 4 | default_md = sha256 5 | [ v3_ca ] 6 | subjectKeyIdentifier=hash 7 | authorityKeyIdentifier=keyid:always,issuer 8 | basicConstraints = critical,CA:true 9 | [ req ] 10 | distinguished_name = req_distinguished_name 11 | [ req_distinguished_name ] 12 | countryName = Country Name (2 letter code) 13 | countryName_default = IL 14 | countryName_min = 2 15 | countryName_max = 2 16 | 17 | stateOrProvinceName = State or Province Name (full name) 18 | stateOrProvinceName_default = Center 19 | 20 | localityName = Locality Name (eg, city) 21 | localityName_default = Lod 22 | 23 | 0.organizationName = Organization Name (eg, company) 24 | 0.organizationName_default = GoProxy 25 | 26 | # we can do this but it is not needed normally :-) 27 | #1.organizationName = Second Organization Name (eg, company) 28 | #1.organizationName_default = World Wide Web Pty Ltd 29 | 30 | organizationalUnitName = Organizational Unit Name (eg, section) 31 | organizationalUnitName_default = GoProxy 32 | 33 | commonName = Common Name (e.g. server FQDN or YOUR name) 34 | commonName_default = goproxy.github.io 35 | commonName_max = 64 36 | 37 | emailAddress = Email Address 38 | emailAddress_default = elazarl@gmail.com 39 | emailAddress_max = 64 40 | -------------------------------------------------------------------------------- /examples/goproxy-transparent/proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # goproxy IP 3 | GOPROXY_SERVER="10.10.10.1" 4 | # goproxy port 5 | GOPROXY_PORT="3129" 6 | GOPROXY_PORT_TLS="3128" 7 | # DO NOT MODIFY BELOW 8 | # Load IPTABLES modules for NAT and IP conntrack support 9 | modprobe ip_conntrack 10 | modprobe ip_conntrack_ftp 11 | echo 1 > /proc/sys/net/ipv4/ip_forward 12 | echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter 13 | 14 | # Clean old firewall 15 | iptables -t nat -F 16 | iptables -t nat -X 17 | iptables -t mangle -F 18 | iptables -t mangle -X 19 | 20 | # Write new rules 21 | iptables -t nat -A PREROUTING -s $GOPROXY_SERVER -p tcp --dport $GOPROXY_PORT -j ACCEPT 22 | iptables -t nat -A PREROUTING -s $GOPROXY_SERVER -p tcp --dport $GOPROXY_PORT_TLS -j ACCEPT 23 | iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT 24 | iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT_TLS 25 | # The following line supports using goproxy as an explicit proxy in addition 26 | iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination $GOPROXY_SERVER:$GOPROXY_PORT 27 | iptables -t nat -A POSTROUTING -j MASQUERADE 28 | iptables -t mangle -A PREROUTING -p tcp --dport $GOPROXY_PORT -j DROP 29 | iptables -t mangle -A PREROUTING -p tcp --dport $GOPROXY_PORT_TLS -j DROP 30 | -------------------------------------------------------------------------------- /responses.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "net/http" 7 | ) 8 | 9 | // Will generate a valid http response to the given request the response will have 10 | // the given contentType, and http status. 11 | // Typical usage, refuse to process requests to local addresses: 12 | // 13 | // proxy.OnRequest(IsLocalHost()).DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request,*http.Response) { 14 | // return nil,NewResponse(r,goproxy.ContentTypeHtml,http.StatusUnauthorized, 15 | // `Can't use proxy for local addresses`) 16 | // }) 17 | func NewResponse(r *http.Request, contentType string, status int, body string) *http.Response { 18 | resp := &http.Response{} 19 | resp.Request = r 20 | resp.TransferEncoding = r.TransferEncoding 21 | resp.Header = make(http.Header) 22 | resp.Header.Add("Content-Type", contentType) 23 | resp.StatusCode = status 24 | resp.Status = http.StatusText(status) 25 | buf := bytes.NewBufferString(body) 26 | resp.ContentLength = int64(buf.Len()) 27 | resp.Body = ioutil.NopCloser(buf) 28 | return resp 29 | } 30 | 31 | const ( 32 | ContentTypeText = "text/plain" 33 | ContentTypeHtml = "text/html" 34 | ) 35 | 36 | // Alias for NewResponse(r,ContentTypeText,http.StatusAccepted,text) 37 | func TextResponse(r *http.Request, text string) *http.Response { 38 | return NewResponse(r, ContentTypeText, http.StatusAccepted, text) 39 | } 40 | -------------------------------------------------------------------------------- /examples/goproxy-stats/README.md: -------------------------------------------------------------------------------- 1 | # Gather Browsing Statistics 2 | 3 | `goproxy-stats` starts an HTTP proxy on :8080, counts the bytes received for 4 | web resources and prints the cumulative sum per URL every 20 seconds. 5 | 6 | Start it in one shell: 7 | 8 | ```sh 9 | goproxy-stats 10 | ``` 11 | 12 | Fetch goproxy homepage in another: 13 | 14 | ```sh 15 | mkdir tmp 16 | cd tmp 17 | http_proxy=http://127.0.0.1:8080 wget -r -l 1 -H \ 18 | http://ripper234.com/p/introducing-goproxy-light-http-proxy/ 19 | ``` 20 | 21 | Stop it after a moment. `goproxy-stats` should eventually print: 22 | ```sh 23 | listening on :8080 24 | statistics 25 | http://www.telerik.com/fiddler -> 84335 26 | http://msmvps.com/robots.txt -> 157 27 | http://eli.thegreenplace.net/robots.txt -> 294 28 | http://www.phdcomics.com/robots.txt -> 211 29 | http://resharper.blogspot.com/robots.txt -> 221 30 | http://idanz.blogli.co.il/robots.txt -> 271 31 | http://ripper234.com/p/introducing-goproxy-light-http-proxy/ -> 44407 32 | http://live.gnome.org/robots.txt -> 298 33 | http://ponetium.wordpress.com/robots.txt -> 178 34 | http://pilaheleg.blogli.co.il/robots.txt -> 321 35 | http://pilaheleg.wordpress.com/robots.txt -> 178 36 | http://blogli.co.il/ -> 9165 37 | http://nimrod-code.org/robots.txt -> 289 38 | http://www.joelonsoftware.com/robots.txt -> 1245 39 | http://top-performance.blogspot.com/robots.txt -> 227 40 | http://ooc-lang.org/robots.txt -> 345 41 | http://blogs.jetbrains.com/robots.txt -> 293 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Elazar Leibovich. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Elazar Leibovich. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /examples/goproxy-stats/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | . "net/http" 8 | "time" 9 | 10 | "github.com/Ice3man543/goproxy" 11 | goproxy_html "github.com/elazarl/goproxy/ext/html" 12 | ) 13 | 14 | type Count struct { 15 | Id string 16 | Count int64 17 | } 18 | type CountReadCloser struct { 19 | Id string 20 | R io.ReadCloser 21 | ch chan<- Count 22 | nr int64 23 | } 24 | 25 | func (c *CountReadCloser) Read(b []byte) (n int, err error) { 26 | n, err = c.R.Read(b) 27 | c.nr += int64(n) 28 | return 29 | } 30 | func (c CountReadCloser) Close() error { 31 | c.ch <- Count{c.Id, c.nr} 32 | return c.R.Close() 33 | } 34 | 35 | func main() { 36 | proxy := goproxy.NewProxyHttpServer() 37 | timer := make(chan bool) 38 | ch := make(chan Count, 10) 39 | go func() { 40 | for { 41 | time.Sleep(20 * time.Second) 42 | timer <- true 43 | } 44 | }() 45 | go func() { 46 | m := make(map[string]int64) 47 | for { 48 | select { 49 | case c := <-ch: 50 | m[c.Id] = m[c.Id] + c.Count 51 | case <-timer: 52 | fmt.Printf("statistics\n") 53 | for k, v := range m { 54 | fmt.Printf("%s -> %d\n", k, v) 55 | } 56 | } 57 | } 58 | }() 59 | 60 | // IsWebRelatedText filters on html/javascript/css resources 61 | proxy.OnResponse(goproxy_html.IsWebRelatedText).DoFunc(func(resp *Response, ctx *goproxy.ProxyCtx) *Response { 62 | resp.Body = &CountReadCloser{ctx.Req.URL.String(), resp.Body, ch, 0} 63 | return resp 64 | }) 65 | fmt.Printf("listening on :8080\n") 66 | log.Fatal(ListenAndServe(":8080", proxy)) 67 | } 68 | -------------------------------------------------------------------------------- /ext/html/html_test.go: -------------------------------------------------------------------------------- 1 | package goproxy_html_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "testing" 9 | 10 | "github.com/Ice3man543/goproxy" 11 | goproxy_html "github.com/elazarl/goproxy/ext/html" 12 | ) 13 | 14 | type ConstantServer int 15 | 16 | func (s ConstantServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 17 | w.Header().Set("Content-Type", "text/plain; charset=iso-8859-8") 18 | //w.Header().Set("Content-Type","text/plain; charset=cp-1255") 19 | w.Write([]byte{0xe3, 0xf3}) 20 | } 21 | 22 | func TestCharset(t *testing.T) { 23 | s := httptest.NewServer(ConstantServer(1)) 24 | defer s.Close() 25 | 26 | ch := make(chan string, 2) 27 | proxy := goproxy.NewProxyHttpServer() 28 | proxy.OnResponse().Do(goproxy_html.HandleString( 29 | func(s string, ctx *goproxy.ProxyCtx) string { 30 | ch <- s 31 | return s 32 | })) 33 | proxyServer := httptest.NewServer(proxy) 34 | defer proxyServer.Close() 35 | 36 | proxyUrl, _ := url.Parse(proxyServer.URL) 37 | client := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl)}} 38 | 39 | resp, err := client.Get(s.URL + "/cp1255.txt") 40 | if err != nil { 41 | t.Fatal("GET:", err) 42 | } 43 | b, err := ioutil.ReadAll(resp.Body) 44 | if err != nil { 45 | t.Fatal("readAll:", err) 46 | } 47 | resp.Body.Close() 48 | 49 | inHandleString := "" 50 | select { 51 | case inHandleString = <-ch: 52 | default: 53 | } 54 | 55 | if len(b) != 2 || b[0] != 0xe3 || b[1] != 0xf3 { 56 | t.Error("Did not translate back to 0xe3,0xf3, instead", b) 57 | } 58 | if inHandleString != "דף" { 59 | t.Error("HandleString did not convert DALET & PEH SOFIT (דף) from ISO-8859-8 to utf-8, got", []byte(inHandleString)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/goproxy-transparent/README.md: -------------------------------------------------------------------------------- 1 | # Transparent Proxy 2 | 3 | This transparent example in goproxy is meant to show how to transparenty proxy and hijack all http and https connections while doing a man-in-the-middle to the TLS session. It requires that goproxy sees all the packets traversing out to the internet. Linux iptables rules deal with changing the source/destination IPs to act transparently, but you do need to setup your network configuration so that goproxy is a mandatory stop on the outgoing route. Primarily you can do this by placing the proxy inline. goproxy does not have any WCCP support itself; patches welcome. 4 | 5 | ## Why not explicit? 6 | 7 | Transparent proxies are more difficult to maintain and setup from a server side, but they require no configuration on the client(s) which could be in unmanaged systems or systems that don't support a proxy configuration. See the [eavesdropper example](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-eavesdropper/main.go) if you want to see an explicit proxy example. 8 | 9 | ## Potential Issues 10 | 11 | Support for very old clients using HTTPS will fail. Clients need to send the SNI value in the TLS ClientHello which most modern clients do these days, but old clients will break. 12 | 13 | If you're routing table allows for it, an explicit http request to goproxy will cause it to fail in an endless loop since it will try to request resources from itself repeatedly. This could be solved in the goproxy code by looking up the hostnames, but it adds a delay that is much easier/faster to handle on the routing side. 14 | 15 | ## Routing Rules 16 | 17 | Example routing rules are included in [proxy.sh](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-transparent/proxy.sh) but are best when setup using your distribution's configuration. 18 | -------------------------------------------------------------------------------- /counterecryptor.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "crypto/sha256" 9 | "crypto/x509" 10 | "errors" 11 | ) 12 | 13 | type CounterEncryptorRand struct { 14 | cipher cipher.Block 15 | counter []byte 16 | rand []byte 17 | ix int 18 | } 19 | 20 | func NewCounterEncryptorRandFromKey(key interface{}, seed []byte) (r CounterEncryptorRand, err error) { 21 | var keyBytes []byte 22 | switch key := key.(type) { 23 | case *rsa.PrivateKey: 24 | keyBytes = x509.MarshalPKCS1PrivateKey(key) 25 | case *ecdsa.PrivateKey: 26 | if keyBytes, err = x509.MarshalECPrivateKey(key); err != nil { 27 | return 28 | } 29 | default: 30 | err = errors.New("only RSA and ECDSA keys supported") 31 | return 32 | } 33 | h := sha256.New() 34 | if r.cipher, err = aes.NewCipher(h.Sum(keyBytes)[:aes.BlockSize]); err != nil { 35 | return 36 | } 37 | r.counter = make([]byte, r.cipher.BlockSize()) 38 | if seed != nil { 39 | copy(r.counter, h.Sum(seed)[:r.cipher.BlockSize()]) 40 | } 41 | r.rand = make([]byte, r.cipher.BlockSize()) 42 | r.ix = len(r.rand) 43 | return 44 | } 45 | 46 | func (c *CounterEncryptorRand) Seed(b []byte) { 47 | if len(b) != len(c.counter) { 48 | panic("SetCounter: wrong counter size") 49 | } 50 | copy(c.counter, b) 51 | } 52 | 53 | func (c *CounterEncryptorRand) refill() { 54 | c.cipher.Encrypt(c.rand, c.counter) 55 | for i := 0; i < len(c.counter); i++ { 56 | if c.counter[i]++; c.counter[i] != 0 { 57 | break 58 | } 59 | } 60 | c.ix = 0 61 | } 62 | 63 | func (c *CounterEncryptorRand) Read(b []byte) (n int, err error) { 64 | if c.ix == len(c.rand) { 65 | c.refill() 66 | } 67 | if n = len(c.rand) - c.ix; n > len(b) { 68 | n = len(b) 69 | } 70 | copy(b, c.rand[c.ix:c.ix+n]) 71 | c.ix += n 72 | return 73 | } 74 | -------------------------------------------------------------------------------- /examples/goproxy-eavesdropper/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "log" 7 | "net" 8 | "net/http" 9 | "regexp" 10 | 11 | "github.com/Ice3man543/goproxy" 12 | ) 13 | 14 | func orPanic(err error) { 15 | if err != nil { 16 | panic(err) 17 | } 18 | } 19 | 20 | func main() { 21 | proxy := goproxy.NewProxyHttpServer() 22 | proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*baidu.com$"))). 23 | HandleConnect(goproxy.AlwaysReject) 24 | proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))). 25 | HandleConnect(goproxy.AlwaysMitm) 26 | // enable curl -p for all hosts on port 80 27 | proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:80$"))). 28 | HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) { 29 | defer func() { 30 | if e := recover(); e != nil { 31 | ctx.Logf("error connecting to remote: %v", e) 32 | client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n")) 33 | } 34 | client.Close() 35 | }() 36 | clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client)) 37 | remote, err := net.Dial("tcp", req.URL.Host) 38 | orPanic(err) 39 | remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote)) 40 | for { 41 | req, err := http.ReadRequest(clientBuf.Reader) 42 | orPanic(err) 43 | orPanic(req.Write(remoteBuf)) 44 | orPanic(remoteBuf.Flush()) 45 | resp, err := http.ReadResponse(remoteBuf.Reader, req) 46 | orPanic(err) 47 | orPanic(resp.Write(clientBuf.Writer)) 48 | orPanic(clientBuf.Flush()) 49 | } 50 | }) 51 | verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") 52 | addr := flag.String("addr", ":8080", "proxy listen address") 53 | flag.Parse() 54 | proxy.Verbose = *verbose 55 | log.Fatal(http.ListenAndServe(*addr, proxy)) 56 | } 57 | -------------------------------------------------------------------------------- /chunked.go: -------------------------------------------------------------------------------- 1 | // Taken from $GOROOT/src/pkg/net/http/chunked 2 | // needed to write https responses to client. 3 | package goproxy 4 | 5 | import ( 6 | "io" 7 | "strconv" 8 | ) 9 | 10 | // newChunkedWriter returns a new chunkedWriter that translates writes into HTTP 11 | // "chunked" format before writing them to w. Closing the returned chunkedWriter 12 | // sends the final 0-length chunk that marks the end of the stream. 13 | // 14 | // newChunkedWriter is not needed by normal applications. The http 15 | // package adds chunking automatically if handlers don't set a 16 | // Content-Length header. Using newChunkedWriter inside a handler 17 | // would result in double chunking or chunking with a Content-Length 18 | // length, both of which are wrong. 19 | func newChunkedWriter(w io.Writer) io.WriteCloser { 20 | return &chunkedWriter{w} 21 | } 22 | 23 | // Writing to chunkedWriter translates to writing in HTTP chunked Transfer 24 | // Encoding wire format to the underlying Wire chunkedWriter. 25 | type chunkedWriter struct { 26 | Wire io.Writer 27 | } 28 | 29 | // Write the contents of data as one chunk to Wire. 30 | // NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has 31 | // a bug since it does not check for success of io.WriteString 32 | func (cw *chunkedWriter) Write(data []byte) (n int, err error) { 33 | 34 | // Don't send 0-length data. It looks like EOF for chunked encoding. 35 | if len(data) == 0 { 36 | return 0, nil 37 | } 38 | 39 | head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" 40 | 41 | if _, err = io.WriteString(cw.Wire, head); err != nil { 42 | return 0, err 43 | } 44 | if n, err = cw.Wire.Write(data); err != nil { 45 | return 46 | } 47 | if n != len(data) { 48 | err = io.ErrShortWrite 49 | return 50 | } 51 | _, err = io.WriteString(cw.Wire, "\r\n") 52 | 53 | return 54 | } 55 | 56 | func (cw *chunkedWriter) Close() error { 57 | _, err := io.WriteString(cw.Wire, "0\r\n") 58 | return err 59 | } 60 | -------------------------------------------------------------------------------- /examples/goproxy-jquery-version/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "regexp" 7 | 8 | "github.com/Ice3man543/goproxy" 9 | goproxy_html "github.com/elazarl/goproxy/ext/html" 10 | ) 11 | 12 | var ( 13 | // who said we can't parse HTML with regexp? 14 | scriptMatcher = regexp.MustCompile(`(?i:]*\ssrc=["']([^"']*)["'])`) 16 | ) 17 | 18 | // findScripts returns all sources of HTML script tags found in input text. 19 | func findScriptSrc(html string) []string { 20 | srcs := make([]string, 0) 21 | matches := scriptMatcher.FindAllStringIndex(html, -1) 22 | for _, match := range matches { 23 | // -1 to capture the whitespace at the end of the script tag 24 | srcMatch := srcAttrMatcher.FindStringSubmatch(html[match[1]-1:]) 25 | if srcMatch != nil { 26 | srcs = append(srcs, srcMatch[1]) 27 | } 28 | } 29 | return srcs 30 | } 31 | 32 | // NewJQueryVersionProxy creates a proxy checking responses HTML content, looks 33 | // for scripts referencing jQuery library and emits warnings if different 34 | // versions of the library are being used for a given host. 35 | func NewJqueryVersionProxy() *goproxy.ProxyHttpServer { 36 | proxy := goproxy.NewProxyHttpServer() 37 | m := make(map[string]string) 38 | jqueryMatcher := regexp.MustCompile(`(?i:jquery\.)`) 39 | proxy.OnResponse(goproxy_html.IsHtml).Do(goproxy_html.HandleString( 40 | func(s string, ctx *goproxy.ProxyCtx) string { 41 | for _, src := range findScriptSrc(s) { 42 | if !jqueryMatcher.MatchString(src) { 43 | continue 44 | } 45 | prev, ok := m[ctx.Req.Host] 46 | if ok { 47 | if prev != src { 48 | ctx.Warnf("In %v, Contradicting jqueries %v %v", 49 | ctx.Req.URL, prev, src) 50 | break 51 | } 52 | } else { 53 | ctx.Warnf("%s uses jquery %s", ctx.Req.Host, src) 54 | m[ctx.Req.Host] = src 55 | } 56 | } 57 | return s 58 | })) 59 | return proxy 60 | } 61 | 62 | func main() { 63 | proxy := NewJqueryVersionProxy() 64 | log.Fatal(http.ListenAndServe(":8080", proxy)) 65 | } 66 | -------------------------------------------------------------------------------- /ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD 3 | VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM 4 | B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0 5 | aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0 6 | MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE 7 | CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV 8 | BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI 9 | hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP 10 | ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9 11 | 3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP 12 | sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9 13 | V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh 14 | hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr 15 | lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq 16 | j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo 17 | WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD 18 | fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj 19 | YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh 20 | WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj 21 | UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4 22 | uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB 23 | CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F 24 | AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0 25 | C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3 26 | Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin 27 | o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye 28 | i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr 29 | bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY 30 | VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft 31 | 8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86 32 | NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV 33 | BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA== 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /ext/image/image.go: -------------------------------------------------------------------------------- 1 | package goproxy_image 2 | 3 | import ( 4 | "bytes" 5 | "image" 6 | _ "image/gif" 7 | "image/jpeg" 8 | "image/png" 9 | "io/ioutil" 10 | "net/http" 11 | 12 | . "github.com/Ice3man543/goproxy" 13 | "github.com/elazarl/goproxy/regretable" 14 | ) 15 | 16 | var RespIsImage = ContentTypeIs("image/gif", 17 | "image/jpeg", 18 | "image/pjpeg", 19 | "application/octet-stream", 20 | "image/png") 21 | 22 | // "image/tiff" tiff support is in external package, and rarely used, so we omitted it 23 | 24 | func HandleImage(f func(img image.Image, ctx *ProxyCtx) image.Image) RespHandler { 25 | return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { 26 | if !RespIsImage.HandleResp(resp, ctx) { 27 | return resp 28 | } 29 | if resp.StatusCode != 200 { 30 | // we might get 304 - not modified response without data 31 | return resp 32 | } 33 | contentType := resp.Header.Get("Content-Type") 34 | 35 | const kb = 1024 36 | regret := regretable.NewRegretableReaderCloserSize(resp.Body, 16*kb) 37 | resp.Body = regret 38 | img, imgType, err := image.Decode(resp.Body) 39 | if err != nil { 40 | regret.Regret() 41 | ctx.Warnf("%s: %s", ctx.Req.Method+" "+ctx.Req.URL.String()+" Image from "+ctx.Req.RequestURI+"content type"+ 42 | contentType+"cannot be decoded returning original image", err) 43 | return resp 44 | } 45 | result := f(img, ctx) 46 | buf := bytes.NewBuffer([]byte{}) 47 | switch contentType { 48 | // No gif image encoder in go - convert to png 49 | case "image/gif", "image/png": 50 | if err := png.Encode(buf, result); err != nil { 51 | ctx.Warnf("Cannot encode image, returning orig %v %v", ctx.Req.URL.String(), err) 52 | return resp 53 | } 54 | resp.Header.Set("Content-Type", "image/png") 55 | case "image/jpeg", "image/pjpeg": 56 | if err := jpeg.Encode(buf, result, nil); err != nil { 57 | ctx.Warnf("Cannot encode image, returning orig %v %v", ctx.Req.URL.String(), err) 58 | return resp 59 | } 60 | case "application/octet-stream": 61 | switch imgType { 62 | case "jpeg": 63 | if err := jpeg.Encode(buf, result, nil); err != nil { 64 | ctx.Warnf("Cannot encode image as jpeg, returning orig %v %v", ctx.Req.URL.String(), err) 65 | return resp 66 | } 67 | case "png", "gif": 68 | if err := png.Encode(buf, result); err != nil { 69 | ctx.Warnf("Cannot encode image as png, returning orig %v %v", ctx.Req.URL.String(), err) 70 | return resp 71 | } 72 | } 73 | default: 74 | panic("unhandlable type" + contentType) 75 | } 76 | resp.Body = ioutil.NopCloser(buf) 77 | return resp 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /actions.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import "net/http" 4 | 5 | // ReqHandler will "tamper" with the request coming to the proxy server 6 | // If Handle returns req,nil the proxy will send the returned request 7 | // to the destination server. If it returns nil,resp the proxy will 8 | // skip sending any requests, and will simply return the response `resp` 9 | // to the client. 10 | type ReqHandler interface { 11 | Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) 12 | } 13 | 14 | // A wrapper that would convert a function to a ReqHandler interface type 15 | type FuncReqHandler func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) 16 | 17 | // FuncReqHandler.Handle(req,ctx) <=> FuncReqHandler(req,ctx) 18 | func (f FuncReqHandler) Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { 19 | return f(req, ctx) 20 | } 21 | 22 | // after the proxy have sent the request to the destination server, it will 23 | // "filter" the response through the RespHandlers it has. 24 | // The proxy server will send to the client the response returned by the RespHandler. 25 | // In case of error, resp will be nil, and ctx.RoundTrip.Error will contain the error 26 | type RespHandler interface { 27 | Handle(resp *http.Response, ctx *ProxyCtx) *http.Response 28 | } 29 | 30 | // A wrapper that would convert a function to a RespHandler interface type 31 | type FuncRespHandler func(resp *http.Response, ctx *ProxyCtx) *http.Response 32 | 33 | // FuncRespHandler.Handle(req,ctx) <=> FuncRespHandler(req,ctx) 34 | func (f FuncRespHandler) Handle(resp *http.Response, ctx *ProxyCtx) *http.Response { 35 | return f(resp, ctx) 36 | } 37 | 38 | // When a client send a CONNECT request to a host, the request is filtered through 39 | // all the HttpsHandlers the proxy has, and if one returns true, the connection is 40 | // sniffed using Man in the Middle attack. 41 | // That is, the proxy will create a TLS connection with the client, another TLS 42 | // connection with the destination the client wished to connect to, and would 43 | // send back and forth all messages from the server to the client and vice versa. 44 | // The request and responses sent in this Man In the Middle channel are filtered 45 | // through the usual flow (request and response filtered through the ReqHandlers 46 | // and RespHandlers) 47 | type HttpsHandler interface { 48 | HandleConnect(req string, ctx *ProxyCtx) (*ConnectAction, string) 49 | } 50 | 51 | // A wrapper that would convert a function to a HttpsHandler interface type 52 | type FuncHttpsHandler func(host string, ctx *ProxyCtx) (*ConnectAction, string) 53 | 54 | // FuncHttpsHandler should implement the RespHandler interface 55 | func (f FuncHttpsHandler) HandleConnect(host string, ctx *ProxyCtx) (*ConnectAction, string) { 56 | return f(host, ctx) 57 | } 58 | -------------------------------------------------------------------------------- /ext/auth/basic.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/Ice3man543/goproxy" 11 | ) 12 | 13 | var unauthorizedMsg = []byte("407 Proxy Authentication Required") 14 | 15 | func BasicUnauthorized(req *http.Request, realm string) *http.Response { 16 | // TODO(elazar): verify realm is well formed 17 | return &http.Response{ 18 | StatusCode: 407, 19 | ProtoMajor: 1, 20 | ProtoMinor: 1, 21 | Request: req, 22 | Header: http.Header{ 23 | "Proxy-Authenticate": []string{"Basic realm=" + realm}, 24 | "Proxy-Connection": []string{"close"}, 25 | }, 26 | Body: ioutil.NopCloser(bytes.NewBuffer(unauthorizedMsg)), 27 | ContentLength: int64(len(unauthorizedMsg)), 28 | } 29 | } 30 | 31 | var proxyAuthorizationHeader = "Proxy-Authorization" 32 | 33 | func auth(req *http.Request, f func(user, passwd string) bool) bool { 34 | authheader := strings.SplitN(req.Header.Get(proxyAuthorizationHeader), " ", 2) 35 | req.Header.Del(proxyAuthorizationHeader) 36 | if len(authheader) != 2 || authheader[0] != "Basic" { 37 | return false 38 | } 39 | userpassraw, err := base64.StdEncoding.DecodeString(authheader[1]) 40 | if err != nil { 41 | return false 42 | } 43 | userpass := strings.SplitN(string(userpassraw), ":", 2) 44 | if len(userpass) != 2 { 45 | return false 46 | } 47 | return f(userpass[0], userpass[1]) 48 | } 49 | 50 | // Basic returns a basic HTTP authentication handler for requests 51 | // 52 | // You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities 53 | func Basic(realm string, f func(user, passwd string) bool) goproxy.ReqHandler { 54 | return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 55 | if !auth(req, f) { 56 | return nil, BasicUnauthorized(req, realm) 57 | } 58 | return req, nil 59 | }) 60 | } 61 | 62 | // BasicConnect returns a basic HTTP authentication handler for CONNECT requests 63 | // 64 | // You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities 65 | func BasicConnect(realm string, f func(user, passwd string) bool) goproxy.HttpsHandler { 66 | return goproxy.FuncHttpsHandler(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { 67 | if !auth(ctx.Req, f) { 68 | ctx.Resp = BasicUnauthorized(ctx.Req, realm) 69 | return goproxy.RejectConnect, host 70 | } 71 | return goproxy.OkConnect, host 72 | }) 73 | } 74 | 75 | // ProxyBasic will force HTTP authentication before any request to the proxy is processed 76 | func ProxyBasic(proxy *goproxy.ProxyHttpServer, realm string, f func(user, passwd string) bool) { 77 | proxy.OnRequest().Do(Basic(realm, f)) 78 | proxy.OnRequest().HandleConnect(BasicConnect(realm, f)) 79 | } 80 | -------------------------------------------------------------------------------- /regretable/regretreader.go: -------------------------------------------------------------------------------- 1 | package regretable 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // A RegretableReader will allow you to read from a reader, and then 8 | // to "regret" reading it, and push back everything you've read. 9 | // For example, 10 | // rb := NewRegretableReader(bytes.NewBuffer([]byte{1,2,3})) 11 | // var b = make([]byte,1) 12 | // rb.Read(b) // b[0] = 1 13 | // rb.Regret() 14 | // ioutil.ReadAll(rb.Read) // returns []byte{1,2,3},nil 15 | type RegretableReader struct { 16 | reader io.Reader 17 | overflow bool 18 | r, w int 19 | buf []byte 20 | } 21 | 22 | var defaultBufferSize = 500 23 | 24 | // Same as RegretableReader, but allows closing the underlying reader 25 | type RegretableReaderCloser struct { 26 | RegretableReader 27 | c io.Closer 28 | } 29 | 30 | // Closes the underlying readCloser, you cannot regret after closing the stream 31 | func (rbc *RegretableReaderCloser) Close() error { 32 | return rbc.c.Close() 33 | } 34 | 35 | // initialize a RegretableReaderCloser with underlying readCloser rc 36 | func NewRegretableReaderCloser(rc io.ReadCloser) *RegretableReaderCloser { 37 | return &RegretableReaderCloser{*NewRegretableReader(rc), rc} 38 | } 39 | 40 | // initialize a RegretableReaderCloser with underlying readCloser rc 41 | func NewRegretableReaderCloserSize(rc io.ReadCloser, size int) *RegretableReaderCloser { 42 | return &RegretableReaderCloser{*NewRegretableReaderSize(rc, size), rc} 43 | } 44 | 45 | // The next read from the RegretableReader will be as if the underlying reader 46 | // was never read (or from the last point forget is called). 47 | func (rb *RegretableReader) Regret() { 48 | if rb.overflow { 49 | panic("regretting after overflow makes no sense") 50 | } 51 | rb.r = 0 52 | } 53 | 54 | // Will "forget" everything read so far. 55 | // rb := NewRegretableReader(bytes.NewBuffer([]byte{1,2,3})) 56 | // var b = make([]byte,1) 57 | // rb.Read(b) // b[0] = 1 58 | // rb.Forget() 59 | // rb.Read(b) // b[0] = 2 60 | // rb.Regret() 61 | // ioutil.ReadAll(rb.Read) // returns []byte{2,3},nil 62 | func (rb *RegretableReader) Forget() { 63 | if rb.overflow { 64 | panic("forgetting after overflow makes no sense") 65 | } 66 | rb.r = 0 67 | rb.w = 0 68 | } 69 | 70 | // initialize a RegretableReader with underlying reader r, whose buffer is size bytes long 71 | func NewRegretableReaderSize(r io.Reader, size int) *RegretableReader { 72 | return &RegretableReader{reader: r, buf: make([]byte, size)} 73 | } 74 | 75 | // initialize a RegretableReader with underlying reader r 76 | func NewRegretableReader(r io.Reader) *RegretableReader { 77 | return NewRegretableReaderSize(r, defaultBufferSize) 78 | } 79 | 80 | // reads from the underlying reader. Will buffer all input until Regret is called. 81 | func (rb *RegretableReader) Read(p []byte) (n int, err error) { 82 | if rb.overflow { 83 | return rb.reader.Read(p) 84 | } 85 | if rb.r < rb.w { 86 | n = copy(p, rb.buf[rb.r:rb.w]) 87 | rb.r += n 88 | return 89 | } 90 | n, err = rb.reader.Read(p) 91 | bn := copy(rb.buf[rb.w:], p[:n]) 92 | rb.w, rb.r = rb.w+bn, rb.w+n 93 | if bn < n { 94 | rb.overflow = true 95 | } 96 | return 97 | } 98 | -------------------------------------------------------------------------------- /signer.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ecdsa" 6 | "crypto/elliptic" 7 | "crypto/rsa" 8 | "crypto/sha1" 9 | "crypto/tls" 10 | "crypto/x509" 11 | "crypto/x509/pkix" 12 | "fmt" 13 | "math/big" 14 | "math/rand" 15 | "net" 16 | "runtime" 17 | "sort" 18 | "time" 19 | ) 20 | 21 | func hashSorted(lst []string) []byte { 22 | c := make([]string, len(lst)) 23 | copy(c, lst) 24 | sort.Strings(c) 25 | h := sha1.New() 26 | for _, s := range c { 27 | h.Write([]byte(s + ",")) 28 | } 29 | return h.Sum(nil) 30 | } 31 | 32 | func hashSortedBigInt(lst []string) *big.Int { 33 | rv := new(big.Int) 34 | rv.SetBytes(hashSorted(lst)) 35 | return rv 36 | } 37 | 38 | var goproxySignerVersion = ":goroxy1" 39 | 40 | func signHost(ca tls.Certificate, hosts []string) (cert *tls.Certificate, err error) { 41 | var x509ca *x509.Certificate 42 | 43 | // Use the provided ca and not the global GoproxyCa for certificate generation. 44 | if x509ca, err = x509.ParseCertificate(ca.Certificate[0]); err != nil { 45 | return 46 | } 47 | start := time.Unix(0, 0) 48 | end, err := time.Parse("2006-01-02", "2049-12-31") 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | serial := big.NewInt(rand.Int63()) 54 | template := x509.Certificate{ 55 | // TODO(elazar): instead of this ugly hack, just encode the certificate and hash the binary form. 56 | SerialNumber: serial, 57 | Issuer: x509ca.Subject, 58 | Subject: pkix.Name{ 59 | Organization: []string{"GoProxy untrusted MITM proxy Inc"}, 60 | }, 61 | NotBefore: start, 62 | NotAfter: end, 63 | 64 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 65 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 66 | BasicConstraintsValid: true, 67 | } 68 | for _, h := range hosts { 69 | if ip := net.ParseIP(h); ip != nil { 70 | template.IPAddresses = append(template.IPAddresses, ip) 71 | } else { 72 | template.DNSNames = append(template.DNSNames, h) 73 | template.Subject.CommonName = h 74 | } 75 | } 76 | 77 | hash := hashSorted(append(hosts, goproxySignerVersion, ":"+runtime.Version())) 78 | var csprng CounterEncryptorRand 79 | if csprng, err = NewCounterEncryptorRandFromKey(ca.PrivateKey, hash); err != nil { 80 | return 81 | } 82 | 83 | var certpriv crypto.Signer 84 | switch ca.PrivateKey.(type) { 85 | case *rsa.PrivateKey: 86 | if certpriv, err = rsa.GenerateKey(&csprng, 2048); err != nil { 87 | return 88 | } 89 | case *ecdsa.PrivateKey: 90 | if certpriv, err = ecdsa.GenerateKey(elliptic.P256(), &csprng); err != nil { 91 | return 92 | } 93 | default: 94 | err = fmt.Errorf("unsupported key type %T", ca.PrivateKey) 95 | } 96 | 97 | var derBytes []byte 98 | if derBytes, err = x509.CreateCertificate(&csprng, &template, x509ca, certpriv.Public(), ca.PrivateKey); err != nil { 99 | return 100 | } 101 | return &tls.Certificate{ 102 | Certificate: [][]byte{derBytes, ca.Certificate[0]}, 103 | PrivateKey: certpriv, 104 | }, nil 105 | } 106 | 107 | func init() { 108 | // Avoid deterministic random numbers 109 | rand.Seed(time.Now().UnixNano()) 110 | } 111 | -------------------------------------------------------------------------------- /examples/cascadeproxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | "time" 12 | 13 | "github.com/elazarl/goproxy/ext/auth" 14 | 15 | "github.com/Ice3man543/goproxy" 16 | ) 17 | 18 | const ( 19 | ProxyAuthHeader = "Proxy-Authorization" 20 | ) 21 | 22 | func SetBasicAuth(username, password string, req *http.Request) { 23 | req.Header.Set(ProxyAuthHeader, fmt.Sprintf("Basic %s", basicAuth(username, password))) 24 | } 25 | 26 | func basicAuth(username, password string) string { 27 | return base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) 28 | } 29 | 30 | func GetBasicAuth(req *http.Request) (username, password string, ok bool) { 31 | auth := req.Header.Get(ProxyAuthHeader) 32 | if auth == "" { 33 | return 34 | } 35 | 36 | const prefix = "Basic " 37 | if !strings.HasPrefix(auth, prefix) { 38 | return 39 | } 40 | c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) 41 | if err != nil { 42 | return 43 | } 44 | cs := string(c) 45 | s := strings.IndexByte(cs, ':') 46 | if s < 0 { 47 | return 48 | } 49 | return cs[:s], cs[s+1:], true 50 | } 51 | 52 | func main() { 53 | username, password := "foo", "bar" 54 | 55 | // start end proxy server 56 | endProxy := goproxy.NewProxyHttpServer() 57 | endProxy.Verbose = true 58 | auth.ProxyBasic(endProxy, "my_realm", func(user, pwd string) bool { 59 | return user == username && password == pwd 60 | }) 61 | log.Println("serving end proxy server at localhost:8082") 62 | go http.ListenAndServe("localhost:8082", endProxy) 63 | 64 | // start middle proxy server 65 | middleProxy := goproxy.NewProxyHttpServer() 66 | middleProxy.Verbose = true 67 | middleProxy.Tr.Proxy = func(req *http.Request) (*url.URL, error) { 68 | return url.Parse("http://localhost:8082") 69 | } 70 | connectReqHandler := func(req *http.Request) { 71 | SetBasicAuth(username, password, req) 72 | } 73 | middleProxy.ConnectDial = middleProxy.NewConnectDialToProxyWithHandler("http://localhost:8082", connectReqHandler) 74 | middleProxy.OnRequest().Do(goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 75 | SetBasicAuth(username, password, req) 76 | return req, nil 77 | })) 78 | log.Println("serving middle proxy server at localhost:8081") 79 | go http.ListenAndServe("localhost:8081", middleProxy) 80 | 81 | time.Sleep(1 * time.Second) 82 | 83 | // fire a http request: client --> middle proxy --> end proxy --> internet 84 | proxyUrl := "http://localhost:8081" 85 | request, err := http.NewRequest("GET", "https://ip.cn", nil) 86 | if err != nil { 87 | log.Fatalf("new request failed:%v", err) 88 | } 89 | tr := &http.Transport{Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse(proxyUrl) }} 90 | client := &http.Client{Transport: tr} 91 | rsp, err := client.Do(request) 92 | if err != nil { 93 | log.Fatalf("get rsp failed:%v", err) 94 | 95 | } 96 | defer rsp.Body.Close() 97 | data, _ := ioutil.ReadAll(rsp.Body) 98 | 99 | if rsp.StatusCode != http.StatusOK { 100 | log.Fatalf("status %d, data %s", rsp.StatusCode, data) 101 | } 102 | 103 | log.Printf("rsp:%s", data) 104 | } 105 | -------------------------------------------------------------------------------- /key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF 3 | 0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw 4 | HM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf 5 | m31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+ 6 | qiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ 7 | 0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I 8 | yeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq 9 | AmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU 10 | BIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK 11 | 0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic 12 | geFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA 13 | AQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR 14 | kPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3 15 | lVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt 16 | zCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7 17 | +68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ 18 | 3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf 19 | pjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U 20 | C440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4 21 | Ood3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3 22 | 4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm 23 | V6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9 24 | jZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag 25 | /1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6 26 | eDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw 27 | +LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ 28 | ZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt 29 | FQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC 30 | 06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7 31 | OSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9 32 | 7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf 33 | KR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt 34 | sjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB 35 | N/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa 36 | QbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv 37 | 5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W 38 | t3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF 39 | 540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru 40 | sIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi 41 | L0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um 42 | YJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi 43 | 9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe 44 | yKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ 45 | QBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c 46 | ue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH 47 | 759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh 48 | pmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1 49 | cj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88 50 | 4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w= 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /counterecryptor_test.go: -------------------------------------------------------------------------------- 1 | package goproxy_test 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rsa" 6 | "encoding/binary" 7 | "io" 8 | "math" 9 | "math/rand" 10 | "testing" 11 | 12 | "github.com/Ice3man543/goproxy" 13 | ) 14 | 15 | type RandSeedReader struct { 16 | r rand.Rand 17 | } 18 | 19 | func (r *RandSeedReader) Read(b []byte) (n int, err error) { 20 | for i := range b { 21 | b[i] = byte(r.r.Int() & 0xFF) 22 | } 23 | return len(b), nil 24 | } 25 | 26 | func TestCounterEncDifferentConsecutive(t *testing.T) { 27 | k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128) 28 | fatalOnErr(err, "rsa.GenerateKey", t) 29 | c, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog")) 30 | fatalOnErr(err, "NewCounterEncryptorRandFromKey", t) 31 | for i := 0; i < 100*1000; i++ { 32 | var a, b int64 33 | binary.Read(&c, binary.BigEndian, &a) 34 | binary.Read(&c, binary.BigEndian, &b) 35 | if a == b { 36 | t.Fatal("two consecutive equal int64", a, b) 37 | } 38 | } 39 | } 40 | 41 | func TestCounterEncIdenticalStreams(t *testing.T) { 42 | k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128) 43 | fatalOnErr(err, "rsa.GenerateKey", t) 44 | c1, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog")) 45 | fatalOnErr(err, "NewCounterEncryptorRandFromKey", t) 46 | c2, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog")) 47 | fatalOnErr(err, "NewCounterEncryptorRandFromKey", t) 48 | nout := 1000 49 | out1, out2 := make([]byte, nout), make([]byte, nout) 50 | io.ReadFull(&c1, out1) 51 | tmp := out2[:] 52 | rand.Seed(0xFF43109) 53 | for len(tmp) > 0 { 54 | n := 1 + rand.Intn(256) 55 | if n > len(tmp) { 56 | n = len(tmp) 57 | } 58 | n, err := c2.Read(tmp[:n]) 59 | fatalOnErr(err, "CounterEncryptorRand.Read", t) 60 | tmp = tmp[n:] 61 | } 62 | if !bytes.Equal(out1, out2) { 63 | t.Error("identical CSPRNG does not produce the same output") 64 | } 65 | } 66 | 67 | func stddev(data []int) float64 { 68 | var sum, sum_sqr float64 = 0, 0 69 | for _, h := range data { 70 | sum += float64(h) 71 | sum_sqr += float64(h) * float64(h) 72 | } 73 | n := float64(len(data)) 74 | variance := (sum_sqr - ((sum * sum) / n)) / (n - 1) 75 | return math.Sqrt(variance) 76 | } 77 | 78 | func TestCounterEncStreamHistogram(t *testing.T) { 79 | k, err := rsa.GenerateKey(&RandSeedReader{*rand.New(rand.NewSource(0xFF43109))}, 128) 80 | fatalOnErr(err, "rsa.GenerateKey", t) 81 | c, err := goproxy.NewCounterEncryptorRandFromKey(k, []byte("the quick brown fox run over the lazy dog")) 82 | fatalOnErr(err, "NewCounterEncryptorRandFromKey", t) 83 | nout := 100 * 1000 84 | out := make([]byte, nout) 85 | io.ReadFull(&c, out) 86 | refhist := make([]int, 512) 87 | for i := 0; i < nout; i++ { 88 | refhist[rand.Intn(256)]++ 89 | } 90 | hist := make([]int, 512) 91 | for _, b := range out { 92 | hist[int(b)]++ 93 | } 94 | refstddev, stddev := stddev(refhist), stddev(hist) 95 | // due to lack of time, I guestimate 96 | t.Logf("ref:%v - act:%v = %v", refstddev, stddev, math.Abs(refstddev-stddev)) 97 | if math.Abs(refstddev-stddev) >= 1 { 98 | t.Errorf("stddev of ref histogram different than regular PRNG: %v %v", refstddev, stddev) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ctx.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/http" 6 | "regexp" 7 | ) 8 | 9 | // ProxyCtx is the Proxy context, contains useful information about every request. It is passed to 10 | // every user function. Also used as a logger. 11 | type ProxyCtx struct { 12 | // Will contain the client request from the proxy 13 | Req *http.Request 14 | // Will contain the remote server's response (if available. nil if the request wasn't send yet) 15 | Resp *http.Response 16 | RoundTripper RoundTripper 17 | // will contain the recent error that occurred while trying to send receive or parse traffic 18 | Error error 19 | // A handle for the user to keep data in the context, from the call of ReqHandler to the 20 | // call of RespHandler 21 | UserData interface{} 22 | // Will connect a request to a response 23 | Session int64 24 | certStore CertStorage 25 | Proxy *ProxyHttpServer 26 | } 27 | 28 | type RoundTripper interface { 29 | RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) 30 | } 31 | 32 | type CertStorage interface { 33 | Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) 34 | } 35 | 36 | type RoundTripperFunc func(req *http.Request, ctx *ProxyCtx) (*http.Response, error) 37 | 38 | func (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) { 39 | return f(req, ctx) 40 | } 41 | 42 | func (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, error) { 43 | if ctx.RoundTripper != nil { 44 | return ctx.RoundTripper.RoundTrip(req, ctx) 45 | } 46 | return ctx.Proxy.Tr.RoundTrip(req) 47 | } 48 | 49 | func (ctx *ProxyCtx) printf(msg string, argv ...interface{}) { 50 | ctx.Proxy.Logger.Printf("[%03d] "+msg+"\n", append([]interface{}{ctx.Session & 0xFF}, argv...)...) 51 | } 52 | 53 | // Logf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter 54 | // This message will be printed only if the Verbose field of the ProxyHttpServer is set to true 55 | // 56 | // proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){ 57 | // nr := atomic.AddInt32(&counter,1) 58 | // ctx.Printf("So far %d requests",nr) 59 | // return r, nil 60 | // }) 61 | func (ctx *ProxyCtx) Logf(msg string, argv ...interface{}) { 62 | if ctx.Proxy.Verbose { 63 | ctx.printf("INFO: "+msg, argv...) 64 | } 65 | } 66 | 67 | // Warnf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter 68 | // This message will always be printed. 69 | // 70 | // proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){ 71 | // f,err := os.OpenFile(cachedContent) 72 | // if err != nil { 73 | // ctx.Warnf("error open file %v: %v",cachedContent,err) 74 | // return r, nil 75 | // } 76 | // return r, nil 77 | // }) 78 | func (ctx *ProxyCtx) Warnf(msg string, argv ...interface{}) { 79 | ctx.printf("WARN: "+msg, argv...) 80 | } 81 | 82 | var charsetFinder = regexp.MustCompile("charset=([^ ;]*)") 83 | 84 | // Will try to infer the character set of the request from the headers. 85 | // Returns the empty string if we don't know which character set it used. 86 | // Currently it will look for charset= in the Content-Type header of the request. 87 | func (ctx *ProxyCtx) Charset() string { 88 | charsets := charsetFinder.FindStringSubmatch(ctx.Resp.Header.Get("Content-Type")) 89 | if charsets == nil { 90 | return "" 91 | } 92 | return charsets[1] 93 | } 94 | -------------------------------------------------------------------------------- /examples/goproxy-websockets/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "github.com/ecordell/goproxy" 6 | "github.com/gorilla/websocket" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "os/signal" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | var upgrader = websocket.Upgrader{} // use default options 17 | 18 | func echo(w http.ResponseWriter, r *http.Request) { 19 | c, err := upgrader.Upgrade(w, r, nil) 20 | if err != nil { 21 | log.Print("upgrade:", err) 22 | return 23 | } 24 | defer c.Close() 25 | for { 26 | mt, message, err := c.ReadMessage() 27 | if err != nil { 28 | log.Println("read:", err) 29 | break 30 | } 31 | log.Printf("recv: %s", message) 32 | err = c.WriteMessage(mt, message) 33 | if err != nil { 34 | log.Println("write:", err) 35 | break 36 | } 37 | } 38 | } 39 | 40 | func StartEchoServer(wg *sync.WaitGroup) { 41 | log.Println("Starting echo server") 42 | wg.Add(1) 43 | go func() { 44 | http.HandleFunc("/", echo) 45 | err := http.ListenAndServeTLS(":12345", "localhost.pem", "localhost-key.pem", nil) 46 | if err != nil { 47 | panic("ListenAndServe: " + err.Error()) 48 | } 49 | wg.Done() 50 | }() 51 | } 52 | 53 | func StartProxy(wg *sync.WaitGroup) { 54 | log.Println("Starting proxy server") 55 | wg.Add(1) 56 | go func() { 57 | proxy := goproxy.NewProxyHttpServer() 58 | proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) 59 | proxy.Verbose = true 60 | 61 | err := http.ListenAndServe(":54321", proxy) 62 | if err != nil { 63 | log.Fatal(err.Error()) 64 | } 65 | wg.Done() 66 | }() 67 | } 68 | 69 | func main() { 70 | interrupt := make(chan os.Signal, 1) 71 | signal.Notify(interrupt, os.Interrupt) 72 | wg := &sync.WaitGroup{} 73 | StartEchoServer(wg) 74 | StartProxy(wg) 75 | 76 | endpointUrl := "wss://localhost:12345" 77 | proxyUrl := "wss://localhost:54321" 78 | 79 | surl, _ := url.Parse(proxyUrl) 80 | dialer := websocket.Dialer{ 81 | Subprotocols: []string{"p1"}, 82 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 83 | Proxy: http.ProxyURL(surl), 84 | } 85 | 86 | c, _, err := dialer.Dial(endpointUrl, nil) 87 | if err != nil { 88 | log.Fatal("dial:", err) 89 | } 90 | defer c.Close() 91 | 92 | done := make(chan struct{}) 93 | 94 | go func() { 95 | defer c.Close() 96 | defer close(done) 97 | for { 98 | _, message, err := c.ReadMessage() 99 | if err != nil { 100 | log.Println("read:", err) 101 | return 102 | } 103 | log.Printf("recv: %s", message) 104 | } 105 | }() 106 | 107 | ticker := time.NewTicker(time.Second) 108 | defer ticker.Stop() 109 | 110 | for { 111 | select { 112 | case t := <-ticker.C: 113 | err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) 114 | if err != nil { 115 | log.Println("write:", err) 116 | return 117 | } 118 | case <-interrupt: 119 | log.Println("interrupt") 120 | // To cleanly close a connection, a client should send a close 121 | // frame and wait for the server to close the connection. 122 | err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 123 | if err != nil { 124 | log.Println("write close:", err) 125 | return 126 | } 127 | select { 128 | case <-done: 129 | case <-time.After(time.Second): 130 | } 131 | c.Close() 132 | return 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /ext/html/html.go: -------------------------------------------------------------------------------- 1 | // extension to goproxy that will allow you to easily filter web browser related content. 2 | package goproxy_html 3 | 4 | import ( 5 | "bytes" 6 | "errors" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | 12 | "github.com/Ice3man543/goproxy" 13 | "github.com/rogpeppe/go-charset/charset" 14 | _ "github.com/rogpeppe/go-charset/data" 15 | ) 16 | 17 | var IsHtml goproxy.RespCondition = goproxy.ContentTypeIs("text/html") 18 | 19 | var IsCss goproxy.RespCondition = goproxy.ContentTypeIs("text/css") 20 | 21 | var IsJavaScript goproxy.RespCondition = goproxy.ContentTypeIs("text/javascript", 22 | "application/javascript") 23 | 24 | var IsJson goproxy.RespCondition = goproxy.ContentTypeIs("text/json") 25 | 26 | var IsXml goproxy.RespCondition = goproxy.ContentTypeIs("text/xml") 27 | 28 | var IsWebRelatedText goproxy.RespCondition = goproxy.ContentTypeIs("text/html", 29 | "text/css", 30 | "text/javascript", "application/javascript", 31 | "text/xml", 32 | "text/json") 33 | 34 | // HandleString will receive a function that filters a string, and will convert the 35 | // request body to a utf8 string, according to the charset specified in the Content-Type 36 | // header. 37 | // guessing Html charset encoding from the tags is not yet implemented. 38 | func HandleString(f func(s string, ctx *goproxy.ProxyCtx) string) goproxy.RespHandler { 39 | return HandleStringReader(func(r io.Reader, ctx *goproxy.ProxyCtx) io.Reader { 40 | b, err := ioutil.ReadAll(r) 41 | if err != nil { 42 | ctx.Warnf("Cannot read string from resp body: %v", err) 43 | return r 44 | } 45 | return bytes.NewBufferString(f(string(b), ctx)) 46 | }) 47 | } 48 | 49 | // Will receive an input stream which would convert the response to utf-8 50 | // The given function must close the reader r, in order to close the response body. 51 | func HandleStringReader(f func(r io.Reader, ctx *goproxy.ProxyCtx) io.Reader) goproxy.RespHandler { 52 | return goproxy.FuncRespHandler(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { 53 | if ctx.Error != nil { 54 | return nil 55 | } 56 | charsetName := ctx.Charset() 57 | if charsetName == "" { 58 | charsetName = "utf-8" 59 | } 60 | 61 | if strings.ToLower(charsetName) != "utf-8" { 62 | r, err := charset.NewReader(charsetName, resp.Body) 63 | if err != nil { 64 | ctx.Warnf("Cannot convert from %v to utf-8: %v", charsetName, err) 65 | return resp 66 | } 67 | tr, err := charset.TranslatorTo(charsetName) 68 | if err != nil { 69 | ctx.Warnf("Can't translate to %v from utf-8: %v", charsetName, err) 70 | return resp 71 | } 72 | if err != nil { 73 | ctx.Warnf("Cannot translate to %v: %v", charsetName, err) 74 | return resp 75 | } 76 | newr := charset.NewTranslatingReader(f(r, ctx), tr) 77 | resp.Body = &readFirstCloseBoth{ioutil.NopCloser(newr), resp.Body} 78 | } else { 79 | //no translation is needed, already at utf-8 80 | resp.Body = &readFirstCloseBoth{ioutil.NopCloser(f(resp.Body, ctx)), resp.Body} 81 | } 82 | return resp 83 | }) 84 | } 85 | 86 | type readFirstCloseBoth struct { 87 | r io.ReadCloser 88 | c io.Closer 89 | } 90 | 91 | func (rfcb *readFirstCloseBoth) Read(b []byte) (nr int, err error) { 92 | return rfcb.r.Read(b) 93 | } 94 | func (rfcb *readFirstCloseBoth) Close() error { 95 | err1 := rfcb.r.Close() 96 | err2 := rfcb.c.Close() 97 | if err1 != nil && err2 != nil { 98 | return errors.New(err1.Error() + ", " + err2.Error()) 99 | } 100 | if err1 != nil { 101 | return err1 102 | } 103 | return err2 104 | } 105 | -------------------------------------------------------------------------------- /examples/goproxy-yui-minify/yui.go: -------------------------------------------------------------------------------- 1 | // This example would minify standalone Javascript files (identified by their content type) 2 | // using the command line utility YUI compressor http://yui.github.io/yuicompressor/ 3 | // Example usage: 4 | // 5 | // ./yui -java /usr/local/bin/java -yuicompressor ~/Downloads/yuicompressor-2.4.8.jar 6 | // $ curl -vx localhost:8080 http://golang.org/lib/godoc/godocs.js 7 | // (function(){function g(){var u=$("#search");if(u.length===0){return}function t(){if(.... 8 | // $ curl http://golang.org/lib/godoc/godocs.js | head -n 3 9 | // // Copyright 2012 The Go Authors. All rights reserved. 10 | // // Use of this source code is governed by a BSD-style 11 | // // license that can be found in the LICENSE file. 12 | package main 13 | 14 | import ( 15 | "flag" 16 | "io" 17 | "io/ioutil" 18 | "log" 19 | "net/http" 20 | "os" 21 | "os/exec" 22 | "path" 23 | "strings" 24 | 25 | "github.com/Ice3man543/goproxy" 26 | ) 27 | 28 | func main() { 29 | verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") 30 | addr := flag.String("addr", ":8080", "proxy listen address") 31 | java := flag.String("javapath", "java", "where the Java executable is located") 32 | yuicompressor := flag.String("yuicompressor", "", "where the yuicompressor is located, assumed to be in CWD") 33 | yuicompressordir := flag.String("yuicompressordir", ".", "a folder to search yuicompressor in, will be ignored if yuicompressor is set") 34 | flag.Parse() 35 | if *yuicompressor == "" { 36 | files, err := ioutil.ReadDir(*yuicompressordir) 37 | if err != nil { 38 | log.Fatal("Cannot find yuicompressor jar") 39 | } 40 | for _, file := range files { 41 | if strings.HasPrefix(file.Name(), "yuicompressor") && strings.HasSuffix(file.Name(), ".jar") { 42 | c := path.Join(*yuicompressordir, file.Name()) 43 | yuicompressor = &c 44 | break 45 | } 46 | } 47 | } 48 | if *yuicompressor == "" { 49 | log.Fatal("Can't find yuicompressor jar, searched yuicompressor*.jar in dir ", *yuicompressordir) 50 | } 51 | if _, err := os.Stat(*yuicompressor); os.IsNotExist(err) { 52 | log.Fatal("Can't find yuicompressor jar specified ", *yuicompressor) 53 | } 54 | proxy := goproxy.NewProxyHttpServer() 55 | proxy.Verbose = *verbose 56 | proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { 57 | contentType := resp.Header.Get("Content-Type") 58 | if contentType == "application/javascript" || contentType == "application/x-javascript" { 59 | // in real code, response should be streamed as well 60 | var err error 61 | cmd := exec.Command(*java, "-jar", *yuicompressor, "--type", "js") 62 | cmd.Stdin = resp.Body 63 | resp.Body, err = cmd.StdoutPipe() 64 | if err != nil { 65 | ctx.Warnf("Cannot minify content in %v: %v", ctx.Req.URL, err) 66 | return goproxy.TextResponse(ctx.Req, "Error getting stdout pipe") 67 | } 68 | stderr, err := cmd.StderrPipe() 69 | if err != nil { 70 | ctx.Logf("Error obtaining stderr from yuicompress: %s", err) 71 | return goproxy.TextResponse(ctx.Req, "Error getting stderr pipe") 72 | } 73 | if err := cmd.Start(); err != nil { 74 | ctx.Warnf("Cannot minify content in %v: %v", ctx.Req.URL, err) 75 | } 76 | go func() { 77 | defer stderr.Close() 78 | const kb = 1024 79 | msg, err := ioutil.ReadAll(&io.LimitedReader{stderr, 50 * kb}) 80 | if len(msg) != 0 { 81 | ctx.Logf("Error executing yuicompress: %s", string(msg)) 82 | } 83 | if err != nil { 84 | ctx.Logf("Error reading stderr from yuicompress: %s", string(msg)) 85 | } 86 | }() 87 | } 88 | return resp 89 | }) 90 | log.Fatal(http.ListenAndServe(*addr, proxy)) 91 | } 92 | -------------------------------------------------------------------------------- /websocket.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | ) 11 | 12 | func headerContains(header http.Header, name string, value string) bool { 13 | for _, v := range header[name] { 14 | for _, s := range strings.Split(v, ",") { 15 | if strings.EqualFold(value, strings.TrimSpace(s)) { 16 | return true 17 | } 18 | } 19 | } 20 | return false 21 | } 22 | 23 | func isWebSocketRequest(r *http.Request) bool { 24 | return headerContains(r.Header, "Connection", "upgrade") && 25 | headerContains(r.Header, "Upgrade", "websocket") 26 | } 27 | 28 | func (proxy *ProxyHttpServer) serveWebsocketTLS(ctx *ProxyCtx, w http.ResponseWriter, req *http.Request, tlsConfig *tls.Config, clientConn *tls.Conn) { 29 | targetURL := url.URL{Scheme: "wss", Host: req.URL.Host, Path: req.URL.Path} 30 | 31 | // Connect to upstream 32 | targetConn, err := tls.Dial("tcp", targetURL.Host, tlsConfig) 33 | if err != nil { 34 | ctx.Warnf("Error dialing target site: %v", err) 35 | return 36 | } 37 | defer targetConn.Close() 38 | 39 | // Perform handshake 40 | if err := proxy.websocketHandshake(ctx, req, targetConn, clientConn); err != nil { 41 | ctx.Warnf("Websocket handshake error: %v", err) 42 | return 43 | } 44 | 45 | // Proxy wss connection 46 | proxy.proxyWebsocket(ctx, targetConn, clientConn) 47 | } 48 | 49 | func (proxy *ProxyHttpServer) serveWebsocket(ctx *ProxyCtx, w http.ResponseWriter, req *http.Request) { 50 | targetURL := url.URL{Scheme: "ws", Host: req.URL.Host, Path: req.URL.Path} 51 | 52 | targetConn, err := proxy.connectDial("tcp", targetURL.Host) 53 | if err != nil { 54 | ctx.Warnf("Error dialing target site: %v", err) 55 | return 56 | } 57 | defer targetConn.Close() 58 | 59 | // Connect to Client 60 | hj, ok := w.(http.Hijacker) 61 | if !ok { 62 | panic("httpserver does not support hijacking") 63 | } 64 | clientConn, _, err := hj.Hijack() 65 | if err != nil { 66 | ctx.Warnf("Hijack error: %v", err) 67 | return 68 | } 69 | 70 | // Perform handshake 71 | if err := proxy.websocketHandshake(ctx, req, targetConn, clientConn); err != nil { 72 | ctx.Warnf("Websocket handshake error: %v", err) 73 | return 74 | } 75 | 76 | // Proxy ws connection 77 | proxy.proxyWebsocket(ctx, targetConn, clientConn) 78 | } 79 | 80 | func (proxy *ProxyHttpServer) websocketHandshake(ctx *ProxyCtx, req *http.Request, targetSiteConn io.ReadWriter, clientConn io.ReadWriter) error { 81 | // write handshake request to target 82 | err := req.Write(targetSiteConn) 83 | if err != nil { 84 | ctx.Warnf("Error writing upgrade request: %v", err) 85 | return err 86 | } 87 | 88 | targetTLSReader := bufio.NewReader(targetSiteConn) 89 | 90 | // Read handshake response from target 91 | resp, err := http.ReadResponse(targetTLSReader, req) 92 | if err != nil { 93 | ctx.Warnf("Error reading handhsake response %v", err) 94 | return err 95 | } 96 | 97 | // Run response through handlers 98 | resp = proxy.filterResponse(resp, ctx) 99 | 100 | // Proxy handshake back to client 101 | err = resp.Write(clientConn) 102 | if err != nil { 103 | ctx.Warnf("Error writing handshake response: %v", err) 104 | return err 105 | } 106 | return nil 107 | } 108 | 109 | func (proxy *ProxyHttpServer) proxyWebsocket(ctx *ProxyCtx, dest io.ReadWriter, source io.ReadWriter) { 110 | errChan := make(chan error, 2) 111 | cp := func(dst io.Writer, src io.Reader) { 112 | _, err := io.Copy(dst, src) 113 | ctx.Warnf("Websocket error: %v", err) 114 | errChan <- err 115 | } 116 | 117 | // Start proxying websocket data 118 | go cp(dest, source) 119 | go cp(source, dest) 120 | <-errChan 121 | } 122 | -------------------------------------------------------------------------------- /examples/goproxy-jquery-version/jquery_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "net/http/httptest" 9 | "net/url" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func equal(u, v []string) bool { 15 | if len(u) != len(v) { 16 | return false 17 | } 18 | for i, _ := range u { 19 | if u[i] != v[i] { 20 | return false 21 | } 22 | } 23 | return true 24 | } 25 | 26 | func readFile(fname string, t *testing.T) string { 27 | b, err := ioutil.ReadFile(fname) 28 | if err != nil { 29 | t.Fatal("readFile", err) 30 | } 31 | return string(b) 32 | } 33 | 34 | func TestDefectiveScriptParser(t *testing.T) { 35 | if l := len(findScriptSrc(` 36 | 37 | 38 | 39 | 45 | 46 | 47 | `)); l != 0 { 48 | t.Fail() 49 | } 50 | urls := findScriptSrc(readFile("w3schools.html", t)) 51 | if !equal(urls, []string{"http://partner.googleadservices.com/gampad/google_service.js", 52 | "//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"}) { 53 | t.Error("w3schools.html", "src scripts are not recognized", urls) 54 | } 55 | urls = findScriptSrc(readFile("jquery_homepage.html", t)) 56 | if !equal(urls, []string{"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js", 57 | "http://code.jquery.com/jquery-1.4.2.min.js", 58 | "http://static.jquery.com/files/rocker/scripts/custom.js", 59 | "http://static.jquery.com/donate/donate.js"}) { 60 | t.Error("jquery_homepage.html", "src scripts are not recognized", urls) 61 | } 62 | } 63 | 64 | func proxyWithLog() (*http.Client, *bytes.Buffer) { 65 | proxy := NewJqueryVersionProxy() 66 | proxyServer := httptest.NewServer(proxy) 67 | buf := new(bytes.Buffer) 68 | proxy.Logger = log.New(buf, "", 0) 69 | proxyUrl, _ := url.Parse(proxyServer.URL) 70 | tr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)} 71 | client := &http.Client{Transport: tr} 72 | return client, buf 73 | } 74 | 75 | func get(t *testing.T, server *httptest.Server, client *http.Client, url string) { 76 | resp, err := client.Get(server.URL + url) 77 | if err != nil { 78 | t.Fatal("cannot get proxy", err) 79 | } 80 | ioutil.ReadAll(resp.Body) 81 | resp.Body.Close() 82 | } 83 | 84 | func TestProxyServiceTwoVersions(t *testing.T) { 85 | var fs = httptest.NewServer(http.FileServer(http.Dir("."))) 86 | defer fs.Close() 87 | 88 | client, buf := proxyWithLog() 89 | 90 | get(t, fs, client, "/w3schools.html") 91 | get(t, fs, client, "/php_man.html") 92 | if buf.String() != "" && 93 | !strings.Contains(buf.String(), " uses jquery ") { 94 | t.Error("shouldn't warn on a single URL", buf.String()) 95 | } 96 | get(t, fs, client, "/jquery1.html") 97 | warnings := buf.String() 98 | if !strings.Contains(warnings, "http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js") || 99 | !strings.Contains(warnings, "jquery.1.4.js") || 100 | !strings.Contains(warnings, "Contradicting") { 101 | t.Error("contradicting jquery versions (php_man.html, w3schools.html) does not issue warning", warnings) 102 | } 103 | } 104 | 105 | func TestProxyService(t *testing.T) { 106 | var fs = httptest.NewServer(http.FileServer(http.Dir("."))) 107 | defer fs.Close() 108 | 109 | client, buf := proxyWithLog() 110 | 111 | get(t, fs, client, "/jquery_homepage.html") 112 | warnings := buf.String() 113 | if !strings.Contains(warnings, "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js") || 114 | !strings.Contains(warnings, "http://code.jquery.com/jquery-1.4.2.min.js") || 115 | !strings.Contains(warnings, "Contradicting") { 116 | t.Error("contradicting jquery versions does not issue warning") 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /examples/goproxy-customca/cert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | 7 | "github.com/Ice3man543/goproxy" 8 | ) 9 | 10 | var caCert = []byte(`-----BEGIN CERTIFICATE----- 11 | MIIDkzCCAnugAwIBAgIJAKe/ZGdfcHdPMA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV 12 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX 13 | aWRnaXRzIFB0eSBMdGQxGTAXBgNVBAMMEGRlbW8gZm9yIGdvcHJveHkwHhcNMTYw 14 | OTI3MTQzNzQ3WhcNMTkwOTI3MTQzNzQ3WjBgMQswCQYDVQQGEwJBVTETMBEGA1UE 15 | CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk 16 | MRkwFwYDVQQDDBBkZW1vIGZvciBnb3Byb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOC 17 | AQ8AMIIBCgKCAQEA2+W48YZoch72zj0a+ZlyFVY2q2MWmqsEY9f/u53fAeTxvPE6 18 | 1/DnqsydnA3FnGvxw9Dz0oZO6xG+PZvp+lhN07NZbuXK1nie8IpxCa342axpu4C0 19 | 69lZwxikpGyJO4IL5ywp/qfb5a2DxPTAyQOQ8ROAaydoEmktRp25yicnQ2yeZW// 20 | 1SIQxt7gRxQIGmuOQ/Gqr/XN/z2cZdbGJVRUvQXk7N6NhQiCX1zlmp1hzUW9jwC+ 21 | JEKKF1XVpQbc94Bo5supxhkKJ70CREPy8TH9mAUcQUZQRohnPvvt/lKneYAGhjHK 22 | vhpajwlbMMSocVXFvY7o/IqIE/+ZUeQTs1SUwQIDAQABo1AwTjAdBgNVHQ4EFgQU 23 | GnlWcIbfsWJW7GId+6xZIK8YlFEwHwYDVR0jBBgwFoAUGnlWcIbfsWJW7GId+6xZ 24 | IK8YlFEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAoFUjSD15rKlY 25 | xudzyVlr6n0fRNhITkiZMX3JlFOvtHNYif8RfK4TH/oHNBTmle69AgixjMgy8GGd 26 | H90prytGQ5zCs1tKcCFsN5gRSgdAkc2PpRFOK6u8HwOITV5lV7sjucsddXJcOJbQ 27 | 4fyVe47V9TTxI+A7lRnUP2HYTR1Bd0R/IgRAH57d1ZHs7omHIuQ+Ea8ph2ppXMnP 28 | DXVOlZ9zfczSnPnQoomqULOU9Fq2ycyi8Y/ROtAHP6O7wCFbYHXhxojdaHSdhkcd 29 | troTflFMD2/4O6MtBKbHxSmEG6H0FBYz5xUZhZq7WUH24V3xYsfge29/lOCd5/Xf 30 | A+j0RJc/lQ== 31 | -----END CERTIFICATE-----`) 32 | 33 | var caKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 34 | MIIEowIBAAKCAQEA2+W48YZoch72zj0a+ZlyFVY2q2MWmqsEY9f/u53fAeTxvPE6 35 | 1/DnqsydnA3FnGvxw9Dz0oZO6xG+PZvp+lhN07NZbuXK1nie8IpxCa342axpu4C0 36 | 69lZwxikpGyJO4IL5ywp/qfb5a2DxPTAyQOQ8ROAaydoEmktRp25yicnQ2yeZW// 37 | 1SIQxt7gRxQIGmuOQ/Gqr/XN/z2cZdbGJVRUvQXk7N6NhQiCX1zlmp1hzUW9jwC+ 38 | JEKKF1XVpQbc94Bo5supxhkKJ70CREPy8TH9mAUcQUZQRohnPvvt/lKneYAGhjHK 39 | vhpajwlbMMSocVXFvY7o/IqIE/+ZUeQTs1SUwQIDAQABAoIBAHK94ww8W0G5QIWL 40 | Qwkc9XeGvg4eLUxVknva2Ll4fkZJxY4WveKx9OCd1lv4n7WoacYIwUGIDaQBZShW 41 | s/eKnkmqGy+PvpC87gqL4sHvQpuqqJ1LYpxylLEFqduWOuGPUVC2Lc+QnWCycsCS 42 | CgqZzsbMq0S+kkKRGSvw32JJneZCzqLgLNssQNVk+Gm6SI3s4jJsGPesjhnvoPaa 43 | xZK14uFpltaA05GSTDaQeZJFEdnnb3f/eNPc2xMEfi0S2ZlJ6Q92WJEOepAetDlR 44 | cRFi004bNyTb4Bphg8s4+9Cti5is199aFkGCRDWxeqEnc6aMY3Ezu9Qg3uttLVUd 45 | uy830GUCgYEA7qS0X+9UH1R02L3aoANyADVbFt2ZpUwQGauw9WM92pH52xeHAw1S 46 | ohus6FI3OC8xQq2CN525tGLUbFDZnNZ3YQHqFsfgevfnTs1//gbKXomitev0oFKh 47 | VT+WYS4lkgYtPlXzhdGuk32q99T/wIocAguvCUY3PiA7yBz93ReyausCgYEA6+P8 48 | bugMqT8qjoiz1q/YCfxsw9bAGWjlVqme2xmp256AKtxvCf1BPsToAaJU3nFi3vkw 49 | ICLxUWAYoMBODJ3YnbOsIZOavdXZwYHv54JqwqFealC3DG0Du6fZYZdiY8pK+E6m 50 | 3fiYzP1WoVK5tU4bH8ibuIQvpcI8j7Gy0cV6/AMCgYBHl7fZNAZro72uLD7DVGVF 51 | 9LvP/0kR0uDdoqli5JPw12w6szM40i1hHqZfyBJy042WsFDpeHL2z9Nkb1jpeVm1 52 | C4r7rJkGqwqElJf6UHUzqVzb8N6hnkhyN7JYkyyIQzwdgFGfaslRzBiXYxoa3BQM 53 | 9Q5c3OjDxY3JuhDa3DoVYwKBgDNqrWJLSD832oHZAEIicBe1IswJKjQfriWWsV6W 54 | mHSbdtpg0/88aZVR/DQm+xLFakSp0jifBTS0momngRu06Dtvp2xmLQuF6oIIXY97 55 | 2ON1owvPbibSOEcWDgb8pWCU/oRjOHIXts6vxctCKeKAFN93raGphm0+Ck9T72NU 56 | BTubAoGBAMEhI/Wy9wAETuXwN84AhmPdQsyCyp37YKt2ZKaqu37x9v2iL8JTbPEz 57 | pdBzkA2Gc0Wdb6ekIzRrTsJQl+c/0m9byFHsRsxXW2HnezfOFX1H4qAmF6KWP0ub 58 | M8aIn6Rab4sNPSrvKGrU6rFpv/6M33eegzldVnV9ku6uPJI1fFTC 59 | -----END RSA PRIVATE KEY-----`) 60 | 61 | func setCA(caCert, caKey []byte) error { 62 | goproxyCa, err := tls.X509KeyPair(caCert, caKey) 63 | if err != nil { 64 | return err 65 | } 66 | if goproxyCa.Leaf, err = x509.ParseCertificate(goproxyCa.Certificate[0]); err != nil { 67 | return err 68 | } 69 | goproxy.GoproxyCa = goproxyCa 70 | goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 71 | goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 72 | goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 73 | goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package goproxy provides a customizable HTTP proxy, 3 | supporting hijacking HTTPS connection. 4 | 5 | The intent of the proxy, is to be usable with reasonable amount of traffic 6 | yet, customizable and programable. 7 | 8 | The proxy itself is simply an `net/http` handler. 9 | 10 | Typical usage is 11 | 12 | proxy := goproxy.NewProxyHttpServer() 13 | proxy.OnRequest(..conditions..).Do(..requesthandler..) 14 | proxy.OnRequest(..conditions..).DoFunc(..requesthandlerFunction..) 15 | proxy.OnResponse(..conditions..).Do(..responesHandler..) 16 | proxy.OnResponse(..conditions..).DoFunc(..responesHandlerFunction..) 17 | http.ListenAndServe(":8080", proxy) 18 | 19 | Adding a header to each request 20 | 21 | proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){ 22 | r.Header.Set("X-GoProxy","1") 23 | return r, nil 24 | }) 25 | 26 | Note that the function is called before the proxy sends the request to the server 27 | 28 | For printing the content type of all incoming responses 29 | 30 | proxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{ 31 | println(ctx.Req.Host,"->",r.Header.Get("Content-Type")) 32 | return r 33 | }) 34 | 35 | note that we used the ProxyCtx context variable here. It contains the request 36 | and the response (Req and Resp, Resp is nil if unavailable) of this specific client 37 | interaction with the proxy. 38 | 39 | To print the content type of all responses from a certain url, we'll add a 40 | ReqCondition to the OnResponse function: 41 | 42 | proxy.OnResponse(goproxy.UrlIs("golang.org/pkg")).DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{ 43 | println(ctx.Req.Host,"->",r.Header.Get("Content-Type")) 44 | return r 45 | }) 46 | 47 | We can write the condition ourselves, conditions can be set on request and on response 48 | 49 | var random = ReqConditionFunc(func(r *http.Request) bool { 50 | return rand.Intn(1) == 0 51 | }) 52 | var hasGoProxyHeader = RespConditionFunc(func(resp *http.Response,req *http.Request)bool { 53 | return resp.Header.Get("X-GoProxy") != "" 54 | }) 55 | 56 | Caution! If you give a RespCondition to the OnRequest function, you'll get a run time panic! It doesn't 57 | make sense to read the response, if you still haven't got it! 58 | 59 | Finally, we have convenience function to throw a quick response 60 | 61 | proxy.OnResponse(hasGoProxyHeader).DoFunc(func(r*http.Response,ctx *goproxy.ProxyCtx)*http.Response { 62 | r.Body.Close() 63 | return goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusForbidden, "Can't see response with X-GoProxy header!") 64 | }) 65 | 66 | we close the body of the original repsonse, and return a new 403 response with a short message. 67 | 68 | Example use cases: 69 | 70 | 1. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-avgsize 71 | 72 | To measure the average size of an Html served in your site. One can ask 73 | all the QA team to access the website by a proxy, and the proxy will 74 | measure the average size of all text/html responses from your host. 75 | 76 | 2. [not yet implemented] 77 | 78 | All requests to your web servers should be directed through the proxy, 79 | when the proxy will detect html pieces sent as a response to AJAX 80 | request, it'll send a warning email. 81 | 82 | 3. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-httpdump/ 83 | 84 | Generate a real traffic to your website by real users using through 85 | proxy. Record the traffic, and try it again for more real load testing. 86 | 87 | 4. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-no-reddit-at-worktime 88 | 89 | Will allow browsing to reddit.com between 8:00am and 17:00pm 90 | 91 | 5. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-jquery-version 92 | 93 | Will warn if multiple versions of jquery are used in the same domain. 94 | 95 | 6. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-upside-down-ternet/ 96 | 97 | Modifies image files in an HTTP response via goproxy's image extension found in ext/. 98 | 99 | */ 100 | package goproxy 101 | -------------------------------------------------------------------------------- /regretable/regretreader_test.go: -------------------------------------------------------------------------------- 1 | package regretable_test 2 | 3 | import ( 4 | "bytes" 5 | . "github.com/elazarl/goproxy/regretable" 6 | "io" 7 | "io/ioutil" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestRegretableReader(t *testing.T) { 13 | buf := new(bytes.Buffer) 14 | mb := NewRegretableReader(buf) 15 | word := "12345678" 16 | buf.WriteString(word) 17 | 18 | fivebytes := make([]byte, 5) 19 | mb.Read(fivebytes) 20 | mb.Regret() 21 | 22 | s, _ := ioutil.ReadAll(mb) 23 | if string(s) != word { 24 | t.Errorf("Uncommited read is gone, [%d,%d] actual '%v' expected '%v'\n", len(s), len(word), string(s), word) 25 | } 26 | } 27 | 28 | func TestRegretableEmptyRead(t *testing.T) { 29 | buf := new(bytes.Buffer) 30 | mb := NewRegretableReader(buf) 31 | word := "12345678" 32 | buf.WriteString(word) 33 | 34 | zero := make([]byte, 0) 35 | mb.Read(zero) 36 | mb.Regret() 37 | 38 | s, err := ioutil.ReadAll(mb) 39 | if string(s) != word { 40 | t.Error("Uncommited read is gone, actual:", string(s), "expected:", word, "err:", err) 41 | } 42 | } 43 | 44 | func TestRegretableAlsoEmptyRead(t *testing.T) { 45 | buf := new(bytes.Buffer) 46 | mb := NewRegretableReader(buf) 47 | word := "12345678" 48 | buf.WriteString(word) 49 | 50 | one := make([]byte, 1) 51 | zero := make([]byte, 0) 52 | five := make([]byte, 5) 53 | mb.Read(one) 54 | mb.Read(zero) 55 | mb.Read(five) 56 | mb.Regret() 57 | 58 | s, _ := ioutil.ReadAll(mb) 59 | if string(s) != word { 60 | t.Error("Uncommited read is gone", string(s), "expected", word) 61 | } 62 | } 63 | 64 | func TestRegretableRegretBeforeRead(t *testing.T) { 65 | buf := new(bytes.Buffer) 66 | mb := NewRegretableReader(buf) 67 | word := "12345678" 68 | buf.WriteString(word) 69 | 70 | five := make([]byte, 5) 71 | mb.Regret() 72 | mb.Read(five) 73 | 74 | s, err := ioutil.ReadAll(mb) 75 | if string(s) != "678" { 76 | t.Error("Uncommited read is gone", string(s), len(string(s)), "expected", "678", len("678"), "err:", err) 77 | } 78 | } 79 | 80 | func TestRegretableFullRead(t *testing.T) { 81 | buf := new(bytes.Buffer) 82 | mb := NewRegretableReader(buf) 83 | word := "12345678" 84 | buf.WriteString(word) 85 | 86 | twenty := make([]byte, 20) 87 | mb.Read(twenty) 88 | mb.Regret() 89 | 90 | s, _ := ioutil.ReadAll(mb) 91 | if string(s) != word { 92 | t.Error("Uncommited read is gone", string(s), len(string(s)), "expected", word, len(word)) 93 | } 94 | } 95 | 96 | func assertEqual(t *testing.T, expected, actual string) { 97 | if expected != actual { 98 | t.Fatal("Expected", expected, "actual", actual) 99 | } 100 | } 101 | 102 | func assertReadAll(t *testing.T, r io.Reader) string { 103 | s, err := ioutil.ReadAll(r) 104 | if err != nil { 105 | t.Fatal("error when reading", err) 106 | } 107 | return string(s) 108 | } 109 | 110 | func TestRegretableRegretTwice(t *testing.T) { 111 | buf := new(bytes.Buffer) 112 | mb := NewRegretableReader(buf) 113 | word := "12345678" 114 | buf.WriteString(word) 115 | 116 | assertEqual(t, word, assertReadAll(t, mb)) 117 | mb.Regret() 118 | assertEqual(t, word, assertReadAll(t, mb)) 119 | mb.Regret() 120 | assertEqual(t, word, assertReadAll(t, mb)) 121 | } 122 | 123 | type CloseCounter struct { 124 | r io.Reader 125 | closed int 126 | } 127 | 128 | func (cc *CloseCounter) Read(b []byte) (int, error) { 129 | return cc.r.Read(b) 130 | } 131 | 132 | func (cc *CloseCounter) Close() error { 133 | cc.closed++ 134 | return nil 135 | } 136 | 137 | func assert(t *testing.T, b bool, msg string) { 138 | if !b { 139 | t.Errorf("Assertion Error: %s", msg) 140 | } 141 | } 142 | 143 | func TestRegretableCloserSizeRegrets(t *testing.T) { 144 | defer func() { 145 | if r := recover(); r == nil || !strings.Contains(r.(string), "regret") { 146 | t.Error("Did not panic when regretting overread buffer:", r) 147 | } 148 | }() 149 | buf := new(bytes.Buffer) 150 | buf.WriteString("123456") 151 | mb := NewRegretableReaderCloserSize(ioutil.NopCloser(buf), 3) 152 | mb.Read(make([]byte, 4)) 153 | mb.Regret() 154 | } 155 | 156 | func TestRegretableCloserRegretsClose(t *testing.T) { 157 | buf := new(bytes.Buffer) 158 | cc := &CloseCounter{buf, 0} 159 | mb := NewRegretableReaderCloser(cc) 160 | word := "12345678" 161 | buf.WriteString(word) 162 | 163 | mb.Read([]byte{0}) 164 | mb.Close() 165 | if cc.closed != 1 { 166 | t.Error("RegretableReaderCloser ignores Close") 167 | } 168 | mb.Regret() 169 | mb.Close() 170 | if cc.closed != 2 { 171 | t.Error("RegretableReaderCloser does ignore Close after regret") 172 | } 173 | // TODO(elazar): return an error if client issues Close more than once after regret 174 | } 175 | -------------------------------------------------------------------------------- /examples/goproxy-transparent/transparent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net" 10 | "net/http" 11 | "net/url" 12 | "regexp" 13 | 14 | "github.com/Ice3man543/goproxy" 15 | "github.com/inconshreveable/go-vhost" 16 | ) 17 | 18 | func orPanic(err error) { 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | 24 | func main() { 25 | verbose := flag.Bool("v", true, "should every proxy request be logged to stdout") 26 | http_addr := flag.String("httpaddr", ":3129", "proxy http listen address") 27 | https_addr := flag.String("httpsaddr", ":3128", "proxy https listen address") 28 | flag.Parse() 29 | 30 | proxy := goproxy.NewProxyHttpServer() 31 | proxy.Verbose = *verbose 32 | if proxy.Verbose { 33 | log.Printf("Server starting up! - configured to listen on http interface %s and https interface %s", *http_addr, *https_addr) 34 | } 35 | 36 | proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 37 | if req.Host == "" { 38 | fmt.Fprintln(w, "Cannot handle requests without Host header, e.g., HTTP 1.0") 39 | return 40 | } 41 | req.URL.Scheme = "http" 42 | req.URL.Host = req.Host 43 | proxy.ServeHTTP(w, req) 44 | }) 45 | proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))). 46 | HandleConnect(goproxy.AlwaysMitm) 47 | proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:80$"))). 48 | HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) { 49 | defer func() { 50 | if e := recover(); e != nil { 51 | ctx.Logf("error connecting to remote: %v", e) 52 | client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n")) 53 | } 54 | client.Close() 55 | }() 56 | clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client)) 57 | remote, err := connectDial(proxy, "tcp", req.URL.Host) 58 | orPanic(err) 59 | remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote)) 60 | for { 61 | req, err := http.ReadRequest(clientBuf.Reader) 62 | orPanic(err) 63 | orPanic(req.Write(remoteBuf)) 64 | orPanic(remoteBuf.Flush()) 65 | resp, err := http.ReadResponse(remoteBuf.Reader, req) 66 | orPanic(err) 67 | orPanic(resp.Write(clientBuf.Writer)) 68 | orPanic(clientBuf.Flush()) 69 | } 70 | }) 71 | 72 | go func() { 73 | log.Fatalln(http.ListenAndServe(*http_addr, proxy)) 74 | }() 75 | 76 | // listen to the TLS ClientHello but make it a CONNECT request instead 77 | ln, err := net.Listen("tcp", *https_addr) 78 | if err != nil { 79 | log.Fatalf("Error listening for https connections - %v", err) 80 | } 81 | for { 82 | c, err := ln.Accept() 83 | if err != nil { 84 | log.Printf("Error accepting new connection - %v", err) 85 | continue 86 | } 87 | go func(c net.Conn) { 88 | tlsConn, err := vhost.TLS(c) 89 | if err != nil { 90 | log.Printf("Error accepting new connection - %v", err) 91 | } 92 | if tlsConn.Host() == "" { 93 | log.Printf("Cannot support non-SNI enabled clients") 94 | return 95 | } 96 | connectReq := &http.Request{ 97 | Method: "CONNECT", 98 | URL: &url.URL{ 99 | Opaque: tlsConn.Host(), 100 | Host: net.JoinHostPort(tlsConn.Host(), "443"), 101 | }, 102 | Host: tlsConn.Host(), 103 | Header: make(http.Header), 104 | RemoteAddr: c.RemoteAddr().String(), 105 | } 106 | resp := dumbResponseWriter{tlsConn} 107 | proxy.ServeHTTP(resp, connectReq) 108 | }(c) 109 | } 110 | } 111 | 112 | // copied/converted from https.go 113 | func dial(proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) { 114 | if proxy.Tr.Dial != nil { 115 | return proxy.Tr.Dial(network, addr) 116 | } 117 | return net.Dial(network, addr) 118 | } 119 | 120 | // copied/converted from https.go 121 | func connectDial(proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) { 122 | if proxy.ConnectDial == nil { 123 | return dial(proxy, network, addr) 124 | } 125 | return proxy.ConnectDial(network, addr) 126 | } 127 | 128 | type dumbResponseWriter struct { 129 | net.Conn 130 | } 131 | 132 | func (dumb dumbResponseWriter) Header() http.Header { 133 | panic("Header() should not be called on this ResponseWriter") 134 | } 135 | 136 | func (dumb dumbResponseWriter) Write(buf []byte) (int, error) { 137 | if bytes.Equal(buf, []byte("HTTP/1.0 200 OK\r\n\r\n")) { 138 | return len(buf), nil // throw away the HTTP OK response from the faux CONNECT request 139 | } 140 | return dumb.Conn.Write(buf) 141 | } 142 | 143 | func (dumb dumbResponseWriter) WriteHeader(code int) { 144 | panic("WriteHeader() should not be called on this ResponseWriter") 145 | } 146 | 147 | func (dumb dumbResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 148 | return dumb, bufio.NewReadWriter(bufio.NewReader(dumb), bufio.NewWriter(dumb)), nil 149 | } 150 | -------------------------------------------------------------------------------- /signer_test.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "os" 10 | "os/exec" 11 | "strings" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | func orFatal(msg string, err error, t *testing.T) { 17 | if err != nil { 18 | t.Fatal(msg, err) 19 | } 20 | } 21 | 22 | type ConstantHanlder string 23 | 24 | func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) { 25 | w.Write([]byte(h)) 26 | } 27 | 28 | func getBrowser(args []string) string { 29 | for i, arg := range args { 30 | if arg == "-browser" && i+1 < len(arg) { 31 | return args[i+1] 32 | } 33 | if strings.HasPrefix(arg, "-browser=") { 34 | return arg[len("-browser="):] 35 | } 36 | } 37 | return "" 38 | } 39 | 40 | func testSignerX509(t *testing.T, ca tls.Certificate) { 41 | cert, err := signHost(ca, []string{"example.com", "1.1.1.1", "localhost"}) 42 | orFatal("singHost", err, t) 43 | cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) 44 | orFatal("ParseCertificate", err, t) 45 | certpool := x509.NewCertPool() 46 | certpool.AddCert(ca.Leaf) 47 | orFatal("VerifyHostname", cert.Leaf.VerifyHostname("example.com"), t) 48 | orFatal("CheckSignatureFrom", cert.Leaf.CheckSignatureFrom(ca.Leaf), t) 49 | _, err = cert.Leaf.Verify(x509.VerifyOptions{ 50 | DNSName: "example.com", 51 | Roots: certpool, 52 | }) 53 | orFatal("Verify", err, t) 54 | } 55 | 56 | func testSignerTls(t *testing.T, ca tls.Certificate) { 57 | cert, err := signHost(ca, []string{"example.com", "1.1.1.1", "localhost"}) 58 | orFatal("singHost", err, t) 59 | cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) 60 | orFatal("ParseCertificate", err, t) 61 | expected := "key verifies with Go" 62 | server := httptest.NewUnstartedServer(ConstantHanlder(expected)) 63 | defer server.Close() 64 | server.TLS = &tls.Config{Certificates: []tls.Certificate{*cert, ca}} 65 | server.TLS.BuildNameToCertificate() 66 | server.StartTLS() 67 | certpool := x509.NewCertPool() 68 | certpool.AddCert(ca.Leaf) 69 | tr := &http.Transport{ 70 | TLSClientConfig: &tls.Config{RootCAs: certpool}, 71 | } 72 | asLocalhost := strings.Replace(server.URL, "127.0.0.1", "localhost", -1) 73 | req, err := http.NewRequest("GET", asLocalhost, nil) 74 | orFatal("NewRequest", err, t) 75 | resp, err := tr.RoundTrip(req) 76 | orFatal("RoundTrip", err, t) 77 | txt, err := ioutil.ReadAll(resp.Body) 78 | orFatal("ioutil.ReadAll", err, t) 79 | if string(txt) != expected { 80 | t.Errorf("Expected '%s' got '%s'", expected, string(txt)) 81 | } 82 | browser := getBrowser(os.Args) 83 | if browser != "" { 84 | exec.Command(browser, asLocalhost).Run() 85 | time.Sleep(10 * time.Second) 86 | } 87 | } 88 | 89 | func TestSignerRsaTls(t *testing.T) { 90 | testSignerTls(t, GoproxyCa) 91 | } 92 | 93 | func TestSignerRsaX509(t *testing.T) { 94 | testSignerX509(t, GoproxyCa) 95 | } 96 | 97 | func TestSignerEcdsaTls(t *testing.T) { 98 | testSignerTls(t, EcdsaCa) 99 | } 100 | 101 | func TestSignerEcdsaX509(t *testing.T) { 102 | testSignerX509(t, EcdsaCa) 103 | } 104 | 105 | var c *tls.Certificate 106 | var e error 107 | 108 | func BenchmarkSignRsa(b *testing.B) { 109 | var cert *tls.Certificate 110 | var err error 111 | for n := 0; n < b.N; n++ { 112 | cert, err = signHost(GoproxyCa, []string{"example.com", "1.1.1.1", "localhost"}) 113 | 114 | } 115 | c = cert 116 | e = err 117 | } 118 | 119 | func BenchmarkSignEcdsa(b *testing.B) { 120 | var cert *tls.Certificate 121 | var err error 122 | for n := 0; n < b.N; n++ { 123 | cert, err = signHost(EcdsaCa, []string{"example.com", "1.1.1.1", "localhost"}) 124 | 125 | } 126 | c = cert 127 | e = err 128 | } 129 | 130 | // 131 | // Eliptic Curve certificate and key for testing 132 | // 133 | 134 | var ECDSA_CA_CERT = []byte(`-----BEGIN CERTIFICATE----- 135 | MIICGDCCAb8CFEkSgqYhlT0+Yyr9anQNJgtclTL0MAoGCCqGSM49BAMDMIGOMQsw 136 | CQYDVQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNV 137 | BAoMB0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHku 138 | Z2l0aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0x 139 | OTA1MDcxMTUwMThaFw0zOTA1MDIxMTUwMThaMIGOMQswCQYDVQQGEwJJTDEPMA0G 140 | A1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAO 141 | BgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJ 142 | KoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 143 | AwEHA0IABDlH4YrdukPFAjbO8x+gR9F8ID7eCU8Orhba/MIblSRrRVedpj08lK+2 144 | svyoAcrcDsynClO9aQtsC9ivZ+Pmr3MwCgYIKoZIzj0EAwMDRwAwRAIgGRSSJVSE 145 | 1b1KVU0+w+SRtnR5Wb7jkwnaDNxQ3c3FXoICIBJV/l1hFM7mbd68Oi5zLq/4ZsrL 146 | 98Bb3nddk2xys6a9 147 | -----END CERTIFICATE-----`) 148 | 149 | var ECDSA_CA_KEY = []byte(`-----BEGIN PRIVATE KEY----- 150 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEsc8m+2aZfagnesg 151 | qMgXe8ph4LtVu2VOUYhHttuEDsChRANCAAQ5R+GK3bpDxQI2zvMfoEfRfCA+3glP 152 | Dq4W2vzCG5Uka0VXnaY9PJSvtrL8qAHK3A7MpwpTvWkLbAvYr2fj5q9z 153 | -----END PRIVATE KEY-----`) 154 | 155 | var EcdsaCa, ecdsaCaErr = tls.X509KeyPair(ECDSA_CA_CERT, ECDSA_CA_KEY) 156 | 157 | func init() { 158 | if ecdsaCaErr != nil { 159 | panic("Error parsing ecdsa CA " + ecdsaCaErr.Error()) 160 | } 161 | var err error 162 | if EcdsaCa.Leaf, err = x509.ParseCertificate(EcdsaCa.Certificate[0]); err != nil { 163 | panic("Error parsing ecdsa CA " + err.Error()) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /ext/auth/basic_test.go: -------------------------------------------------------------------------------- 1 | package auth_test 2 | 3 | import ( 4 | "encoding/base64" 5 | "io" 6 | "io/ioutil" 7 | "net" 8 | "net/http" 9 | "net/http/httptest" 10 | "net/url" 11 | "os" 12 | "os/exec" 13 | "os/signal" 14 | "sync/atomic" 15 | "testing" 16 | 17 | "github.com/Ice3man543/goproxy" 18 | "github.com/elazarl/goproxy/ext/auth" 19 | ) 20 | 21 | type ConstantHanlder string 22 | 23 | func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) { 24 | io.WriteString(w, string(h)) 25 | } 26 | 27 | func oneShotProxy(proxy *goproxy.ProxyHttpServer) (client *http.Client, s *httptest.Server) { 28 | s = httptest.NewServer(proxy) 29 | 30 | proxyUrl, _ := url.Parse(s.URL) 31 | tr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)} 32 | client = &http.Client{Transport: tr} 33 | return 34 | } 35 | 36 | func times(n int, s string) string { 37 | r := make([]byte, 0, n*len(s)) 38 | for i := 0; i < n; i++ { 39 | r = append(r, s...) 40 | } 41 | return string(r) 42 | } 43 | 44 | func TestBasicConnectAuthWithCurl(t *testing.T) { 45 | expected := ":c>" 46 | background := httptest.NewTLSServer(ConstantHanlder(expected)) 47 | defer background.Close() 48 | proxy := goproxy.NewProxyHttpServer() 49 | proxy.OnRequest().HandleConnect(auth.BasicConnect("my_realm", func(user, passwd string) bool { 50 | return user == "user" && passwd == "open sesame" 51 | })) 52 | _, proxyserver := oneShotProxy(proxy) 53 | defer proxyserver.Close() 54 | 55 | cmd := exec.Command("curl", 56 | "--silent", "--show-error", "--insecure", 57 | "-x", proxyserver.URL, 58 | "-U", "user:open sesame", 59 | "-p", 60 | "--url", background.URL+"/[1-3]", 61 | ) 62 | out, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr 63 | if err != nil { 64 | t.Fatal(err, string(out)) 65 | } 66 | finalexpected := times(3, expected) 67 | if string(out) != finalexpected { 68 | t.Error("Expected", finalexpected, "got", string(out)) 69 | } 70 | } 71 | 72 | func TestBasicAuthWithCurl(t *testing.T) { 73 | expected := ":c>" 74 | background := httptest.NewServer(ConstantHanlder(expected)) 75 | defer background.Close() 76 | proxy := goproxy.NewProxyHttpServer() 77 | proxy.OnRequest().Do(auth.Basic("my_realm", func(user, passwd string) bool { 78 | return user == "user" && passwd == "open sesame" 79 | })) 80 | _, proxyserver := oneShotProxy(proxy) 81 | defer proxyserver.Close() 82 | 83 | cmd := exec.Command("curl", 84 | "--silent", "--show-error", 85 | "-x", proxyserver.URL, 86 | "-U", "user:open sesame", 87 | "--url", background.URL+"/[1-3]", 88 | ) 89 | out, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr 90 | if err != nil { 91 | t.Fatal(err, string(out)) 92 | } 93 | finalexpected := times(3, expected) 94 | if string(out) != finalexpected { 95 | t.Error("Expected", finalexpected, "got", string(out)) 96 | } 97 | } 98 | 99 | func TestBasicAuth(t *testing.T) { 100 | expected := "hello" 101 | background := httptest.NewServer(ConstantHanlder(expected)) 102 | defer background.Close() 103 | proxy := goproxy.NewProxyHttpServer() 104 | proxy.OnRequest().Do(auth.Basic("my_realm", func(user, passwd string) bool { 105 | return user == "user" && passwd == "open sesame" 106 | })) 107 | client, proxyserver := oneShotProxy(proxy) 108 | defer proxyserver.Close() 109 | 110 | // without auth 111 | resp, err := client.Get(background.URL) 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | if resp.Header.Get("Proxy-Authenticate") != "Basic realm=my_realm" { 116 | t.Error("Expected Proxy-Authenticate header got", resp.Header.Get("Proxy-Authenticate")) 117 | } 118 | if resp.StatusCode != 407 { 119 | t.Error("Expected status 407 Proxy Authentication Required, got", resp.Status) 120 | } 121 | 122 | // with auth 123 | req, err := http.NewRequest("GET", background.URL, nil) 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | req.Header.Set("Proxy-Authorization", 128 | "Basic "+base64.StdEncoding.EncodeToString([]byte("user:open sesame"))) 129 | resp, err = client.Do(req) 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | if resp.StatusCode != 200 { 134 | t.Error("Expected status 200 OK, got", resp.Status) 135 | } 136 | msg, err := ioutil.ReadAll(resp.Body) 137 | if err != nil { 138 | t.Fatal(err) 139 | } 140 | if string(msg) != "hello" { 141 | t.Errorf("Expected '%s', actual '%s'", expected, string(msg)) 142 | } 143 | } 144 | 145 | func TestWithBrowser(t *testing.T) { 146 | // an easy way to check if auth works with webserver 147 | // to test, run with 148 | // $ go test -run TestWithBrowser -- server 149 | // configure a browser to use the printed proxy address, use the proxy 150 | // and exit with Ctrl-C. It will throw error if your haven't acutally used the proxy 151 | if os.Args[len(os.Args)-1] != "server" { 152 | return 153 | } 154 | proxy := goproxy.NewProxyHttpServer() 155 | println("proxy localhost port 8082") 156 | access := int32(0) 157 | proxy.OnRequest().Do(auth.Basic("my_realm", func(user, passwd string) bool { 158 | atomic.AddInt32(&access, 1) 159 | return user == "user" && passwd == "1234" 160 | })) 161 | l, err := net.Listen("tcp", "localhost:8082") 162 | if err != nil { 163 | t.Fatal(err) 164 | } 165 | ch := make(chan os.Signal) 166 | signal.Notify(ch, os.Interrupt) 167 | go func() { 168 | <-ch 169 | l.Close() 170 | }() 171 | http.Serve(l, proxy) 172 | if access <= 0 { 173 | t.Error("No one accessed the proxy") 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /certs.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | ) 7 | 8 | func init() { 9 | if goproxyCaErr != nil { 10 | panic("Error parsing builtin CA " + goproxyCaErr.Error()) 11 | } 12 | var err error 13 | if GoproxyCa.Leaf, err = x509.ParseCertificate(GoproxyCa.Certificate[0]); err != nil { 14 | panic("Error parsing builtin CA " + err.Error()) 15 | } 16 | } 17 | 18 | var tlsClientSkipVerify = &tls.Config{InsecureSkipVerify: true} 19 | 20 | var defaultTLSConfig = &tls.Config{ 21 | InsecureSkipVerify: true, 22 | } 23 | 24 | var CA_CERT = []byte(`-----BEGIN CERTIFICATE----- 25 | MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD 26 | VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM 27 | B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0 28 | aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0 29 | MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE 30 | CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV 31 | BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI 32 | hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP 33 | ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9 34 | 3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP 35 | sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9 36 | V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh 37 | hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr 38 | lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq 39 | j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo 40 | WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD 41 | fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj 42 | YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh 43 | WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj 44 | UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4 45 | uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB 46 | CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F 47 | AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0 48 | C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3 49 | Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin 50 | o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye 51 | i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr 52 | bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY 53 | VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft 54 | 8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86 55 | NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV 56 | BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA== 57 | -----END CERTIFICATE-----`) 58 | 59 | var CA_KEY = []byte(`-----BEGIN RSA PRIVATE KEY----- 60 | MIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF 61 | 0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw 62 | HM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf 63 | m31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+ 64 | qiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ 65 | 0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I 66 | yeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq 67 | AmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU 68 | BIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK 69 | 0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic 70 | geFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA 71 | AQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR 72 | kPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3 73 | lVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt 74 | zCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7 75 | +68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ 76 | 3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf 77 | pjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U 78 | C440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4 79 | Ood3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3 80 | 4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm 81 | V6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9 82 | jZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag 83 | /1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6 84 | eDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw 85 | +LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ 86 | ZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt 87 | FQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC 88 | 06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7 89 | OSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9 90 | 7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf 91 | KR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt 92 | sjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB 93 | N/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa 94 | QbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv 95 | 5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W 96 | t3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF 97 | 540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru 98 | sIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi 99 | L0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um 100 | YJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi 101 | 9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe 102 | yKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ 103 | QBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c 104 | ue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH 105 | 759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh 106 | pmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1 107 | cj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88 108 | 4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w= 109 | -----END RSA PRIVATE KEY-----`) 110 | 111 | var GoproxyCa, goproxyCaErr = tls.X509KeyPair(CA_CERT, CA_KEY) 112 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "log" 7 | "net" 8 | "net/http" 9 | "os" 10 | "regexp" 11 | "sync/atomic" 12 | ) 13 | 14 | // The basic proxy type. Implements http.Handler. 15 | type ProxyHttpServer struct { 16 | // session variable must be aligned in i386 17 | // see http://golang.org/src/pkg/sync/atomic/doc.go#L41 18 | sess int64 19 | // KeepDestinationHeaders indicates the proxy should retain any headers present in the http.Response before proxying 20 | KeepDestinationHeaders bool 21 | // setting Verbose to true will log information on each request sent to the proxy 22 | Verbose bool 23 | Logger Logger 24 | NonproxyHandler http.Handler 25 | reqHandlers []ReqHandler 26 | respHandlers []RespHandler 27 | httpsHandlers []HttpsHandler 28 | Tr *http.Transport 29 | // ConnectDial will be used to create TCP connections for CONNECT requests 30 | // if nil Tr.Dial will be used 31 | ConnectDial func(network string, addr string) (net.Conn, error) 32 | CertStore CertStorage 33 | } 34 | 35 | var hasPort = regexp.MustCompile(`:\d+$`) 36 | 37 | func copyHeaders(dst, src http.Header, keepDestHeaders bool) { 38 | if !keepDestHeaders { 39 | for k := range dst { 40 | dst.Del(k) 41 | } 42 | } 43 | for k, vs := range src { 44 | for _, v := range vs { 45 | dst.Add(k, v) 46 | } 47 | } 48 | } 49 | 50 | func isEof(r *bufio.Reader) bool { 51 | _, err := r.Peek(1) 52 | if err == io.EOF { 53 | return true 54 | } 55 | return false 56 | } 57 | 58 | func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) { 59 | req = r 60 | for _, h := range proxy.reqHandlers { 61 | req, resp = h.Handle(r, ctx) 62 | // non-nil resp means the handler decided to skip sending the request 63 | // and return canned response instead. 64 | if resp != nil { 65 | break 66 | } 67 | } 68 | return 69 | } 70 | func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) { 71 | resp = respOrig 72 | for _, h := range proxy.respHandlers { 73 | ctx.Resp = resp 74 | resp = h.Handle(resp, ctx) 75 | } 76 | return 77 | } 78 | 79 | func removeProxyHeaders(ctx *ProxyCtx, r *http.Request) { 80 | r.RequestURI = "" // this must be reset when serving a request with the client 81 | ctx.Logf("Sending request %v %v", r.Method, r.URL.String()) 82 | // If no Accept-Encoding header exists, Transport will add the headers it can accept 83 | // and would wrap the response body with the relevant reader. 84 | r.Header.Del("Accept-Encoding") 85 | // curl can add that, see 86 | // https://jdebp.eu./FGA/web-proxy-connection-header.html 87 | r.Header.Del("Proxy-Connection") 88 | r.Header.Del("Proxy-Authenticate") 89 | r.Header.Del("Proxy-Authorization") 90 | // Connection, Authenticate and Authorization are single hop Header: 91 | // http://www.w3.org/Protocols/rfc2616/rfc2616.txt 92 | // 14.10 Connection 93 | // The Connection general-header field allows the sender to specify 94 | // options that are desired for that particular connection and MUST NOT 95 | // be communicated by proxies over further connections. 96 | r.Header.Del("Connection") 97 | } 98 | 99 | // Standard net/http function. Shouldn't be used directly, http.Serve will use it. 100 | func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 101 | //r.Header["X-Forwarded-For"] = w.RemoteAddr() 102 | if r.Method == "CONNECT" { 103 | proxy.handleHttps(w, r) 104 | } else { 105 | ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy} 106 | 107 | var err error 108 | ctx.Logf("Got request %v %v %v %v", r.URL.Path, r.Host, r.Method, r.URL.String()) 109 | if !r.URL.IsAbs() { 110 | proxy.NonproxyHandler.ServeHTTP(w, r) 111 | return 112 | } 113 | r, resp := proxy.filterRequest(r, ctx) 114 | 115 | if resp == nil { 116 | if isWebSocketRequest(r) { 117 | ctx.Logf("Request looks like websocket upgrade.") 118 | proxy.serveWebsocket(ctx, w, r) 119 | } 120 | 121 | removeProxyHeaders(ctx, r) 122 | resp, err = ctx.RoundTrip(r) 123 | if err != nil { 124 | ctx.Error = err 125 | resp = proxy.filterResponse(nil, ctx) 126 | 127 | } 128 | if resp != nil { 129 | ctx.Logf("Received response %v", resp.Status) 130 | } 131 | } 132 | resp = proxy.filterResponse(resp, ctx) 133 | 134 | if resp == nil { 135 | var errorString string 136 | if ctx.Error != nil { 137 | errorString = "error read response " + r.URL.Host + " : " + ctx.Error.Error() 138 | ctx.Logf(errorString) 139 | http.Error(w, ctx.Error.Error(), 500) 140 | } else { 141 | errorString = "error read response " + r.URL.Host 142 | ctx.Logf(errorString) 143 | http.Error(w, errorString, 500) 144 | } 145 | return 146 | } 147 | origBody := resp.Body 148 | defer origBody.Close() 149 | ctx.Logf("Copying response to client %v [%d]", resp.Status, resp.StatusCode) 150 | // http.ResponseWriter will take care of filling the correct response length 151 | // Setting it now, might impose wrong value, contradicting the actual new 152 | // body the user returned. 153 | // We keep the original body to remove the header only if things changed. 154 | // This will prevent problems with HEAD requests where there's no body, yet, 155 | // the Content-Length header should be set. 156 | if origBody != resp.Body { 157 | resp.Header.Del("Content-Length") 158 | } 159 | copyHeaders(w.Header(), resp.Header, proxy.KeepDestinationHeaders) 160 | w.WriteHeader(resp.StatusCode) 161 | nr, err := io.Copy(w, resp.Body) 162 | if err := resp.Body.Close(); err != nil { 163 | ctx.Warnf("Can't close response body %v", err) 164 | } 165 | ctx.Logf("Copied %v bytes to client error=%v", nr, err) 166 | } 167 | } 168 | 169 | // NewProxyHttpServer creates and returns a proxy server, logging to stderr by default 170 | func NewProxyHttpServer() *ProxyHttpServer { 171 | proxy := ProxyHttpServer{ 172 | Logger: log.New(os.Stderr, "", log.LstdFlags), 173 | reqHandlers: []ReqHandler{}, 174 | respHandlers: []RespHandler{}, 175 | httpsHandlers: []HttpsHandler{}, 176 | NonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 177 | http.Error(w, "This is a proxy server. Does not respond to non-proxy requests.", 500) 178 | }), 179 | Tr: &http.Transport{TLSClientConfig: tlsClientSkipVerify, Proxy: http.ProxyFromEnvironment}, 180 | } 181 | proxy.ConnectDial = dialerFromEnv(&proxy) 182 | 183 | return &proxy 184 | } 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | [![GoDoc](https://godoc.org/github.com/elazarl/goproxy?status.svg)](https://godoc.org/github.com/elazarl/goproxy) 4 | [![Join the chat at https://gitter.im/elazarl/goproxy](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/elazarl/goproxy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | Package goproxy provides a customizable HTTP proxy library for Go (golang), 7 | 8 | It supports regular HTTP proxy, HTTPS through CONNECT, and "hijacking" HTTPS 9 | connection using "Man in the Middle" style attack. 10 | 11 | The intent of the proxy is to be usable with reasonable amount of traffic, 12 | yet customizable and programmable. 13 | 14 | The proxy itself is simply a `net/http` handler. 15 | 16 | In order to use goproxy, one should set their browser to use goproxy as an HTTP 17 | proxy. Here is how you do that [in Chrome](https://support.google.com/chrome/answer/96815?hl=en) 18 | and [in Firefox](http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox). 19 | 20 | For example, the URL you should use as proxy when running `./bin/basic` is 21 | `localhost:8080`, as this is the default binding for the basic proxy. 22 | 23 | ## Mailing List 24 | 25 | New features will be discussed on the [mailing list](https://groups.google.com/forum/#!forum/goproxy-dev) 26 | before their development. 27 | 28 | ## Latest Stable Release 29 | 30 | Get the latest goproxy from `gopkg.in/elazarl/goproxy.v1`. 31 | 32 | # Why not Fiddler2? 33 | 34 | Fiddler is an excellent software with similar intent. However, Fiddler is not 35 | as customizable as goproxy intends to be. The main difference is, Fiddler is not 36 | intended to be used as a real proxy. 37 | 38 | A possible use case that suits goproxy but 39 | not Fiddler, is gathering statistics on page load times for a certain website over a week. 40 | With goproxy you could ask all your users to set their proxy to a dedicated machine running a 41 | goproxy server. Fiddler is a GUI app not designed to be run like a server for multiple users. 42 | 43 | # A taste of goproxy 44 | 45 | To get a taste of `goproxy`, a basic HTTP/HTTPS transparent proxy 46 | 47 | ```go 48 | package main 49 | 50 | import ( 51 | "github.com/Ice3man543/goproxy" 52 | "log" 53 | "net/http" 54 | ) 55 | 56 | func main() { 57 | proxy := goproxy.NewProxyHttpServer() 58 | proxy.Verbose = true 59 | log.Fatal(http.ListenAndServe(":8080", proxy)) 60 | } 61 | ``` 62 | 63 | This line will add `X-GoProxy: yxorPoG-X` header to all requests sent through the proxy 64 | 65 | ```go 66 | proxy.OnRequest().DoFunc( 67 | func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { 68 | r.Header.Set("X-GoProxy","yxorPoG-X") 69 | return r,nil 70 | }) 71 | ``` 72 | 73 | `DoFunc` will process all incoming requests to the proxy. It will add a header to the request 74 | and return it. The proxy will send the modified request. 75 | 76 | Note that we returned nil value as the response. Had we returned a response, goproxy would 77 | have discarded the request and sent the new response to the client. 78 | 79 | In order to refuse connections to reddit at work time 80 | 81 | ```go 82 | proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc( 83 | func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { 84 | if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 { 85 | return r,goproxy.NewResponse(r, 86 | goproxy.ContentTypeText,http.StatusForbidden, 87 | "Don't waste your time!") 88 | } 89 | return r,nil 90 | }) 91 | ``` 92 | 93 | `DstHostIs` returns a `ReqCondition`, that is a function receiving a `Request` and returning a boolean. 94 | We will only process requests that match the condition. `DstHostIs("www.reddit.com")` will return 95 | a `ReqCondition` accepting only requests directed to "www.reddit.com". 96 | 97 | `DoFunc` will receive a function that will preprocess the request. We can change the request, or 98 | return a response. If the time is between 8:00am and 17:00pm, we will reject the request, and 99 | return a precanned text response saying "do not waste your time". 100 | 101 | See additional examples in the examples directory. 102 | 103 | 104 | # Type of handlers for manipulating connect/req/resp behavior 105 | 106 | There are 3 kinds of useful handlers to manipulate the behavior, as follows: 107 | 108 | ```go 109 | // handler called after receiving HTTP CONNECT from the client, and before proxy establish connection 110 | // with destination host 111 | httpsHandlers []HttpsHandler 112 | 113 | // handler called before proxy send HTTP request to destination host 114 | reqHandlers []ReqHandler 115 | 116 | // handler called after proxy receives HTTP Response from destination host, and before proxy forward 117 | // the Response to the client. 118 | respHandlers []RespHandler 119 | ``` 120 | 121 | Depending on what you want to manipulate, the ways to add handlers to each handler list are: 122 | 123 | ```go 124 | // Add handlers to httpsHandlers 125 | proxy.OnRequest(Some ReqConditions).HandleConnect(YourHandlerFunc()) 126 | 127 | // Add handlers to reqHandlers 128 | proxy.OnRequest(Some ReqConditions).Do(YourReqHandlerFunc()) 129 | 130 | // Add handlers to respHandlers 131 | proxy.OnResponse(Some RespConditions).Do(YourRespHandlerFunc()) 132 | ``` 133 | 134 | For example: 135 | 136 | ```go 137 | // This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase 138 | proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("reddit.*:443$"))).HandleConnect(goproxy.RejectConnect) 139 | 140 | // This will NOT reject the HTTPS request with URL ending with gif, due to the fact that proxy 141 | // only got the URL.Hostname and URL.Port during the HTTP CONNECT phase if the scheme is HTTPS, which is 142 | // quiet common these days. 143 | proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.RejectConnect) 144 | 145 | // The correct way to manipulate the HTTP request using URL.Path as condition is: 146 | proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc()) 147 | ``` 148 | 149 | # What's New 150 | 151 | 1. Ability to `Hijack` CONNECT requests. See 152 | [the eavesdropper example](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-eavesdropper/main.go#L27) 153 | 2. Transparent proxy support for http/https including MITM certificate generation for TLS. See the [transparent example.](https://github.com/elazarl/goproxy/tree/master/examples/goproxy-transparent) 154 | 155 | # License 156 | 157 | I put the software temporarily under the Go-compatible BSD license. 158 | If this prevents someone from using the software, do let me know and I'll consider changing it. 159 | 160 | At any rate, user feedback is very important for me, so I'll be delighted to know if you're using this package. 161 | 162 | # Beta Software 163 | 164 | I've received positive feedback from a few people who use goproxy in production settings. 165 | I believe it is good enough for usage. 166 | 167 | I'll try to keep reasonable backwards compatibility. In case of a major API change, 168 | I'll change the import path. 169 | -------------------------------------------------------------------------------- /examples/goproxy-httpdump/httpdump.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "net/http" 11 | "net/http/httputil" 12 | "os" 13 | "os/signal" 14 | "path" 15 | "sync" 16 | "time" 17 | 18 | "github.com/Ice3man543/goproxy" 19 | "github.com/elazarl/goproxy/transport" 20 | ) 21 | 22 | type FileStream struct { 23 | path string 24 | f *os.File 25 | } 26 | 27 | func NewFileStream(path string) *FileStream { 28 | return &FileStream{path, nil} 29 | } 30 | 31 | func (fs *FileStream) Write(b []byte) (nr int, err error) { 32 | if fs.f == nil { 33 | fs.f, err = os.Create(fs.path) 34 | if err != nil { 35 | return 0, err 36 | } 37 | } 38 | return fs.f.Write(b) 39 | } 40 | 41 | func (fs *FileStream) Close() error { 42 | fmt.Println("Close", fs.path) 43 | if fs.f == nil { 44 | return errors.New("FileStream was never written into") 45 | } 46 | return fs.f.Close() 47 | } 48 | 49 | type Meta struct { 50 | req *http.Request 51 | resp *http.Response 52 | err error 53 | t time.Time 54 | sess int64 55 | bodyPath string 56 | from string 57 | } 58 | 59 | func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...interface{}) { 60 | if *err != nil { 61 | return 62 | } 63 | var n int 64 | n, *err = fmt.Fprintf(w, pat, a...) 65 | *nr += int64(n) 66 | } 67 | 68 | func write(nr *int64, err *error, w io.Writer, b []byte) { 69 | if *err != nil { 70 | return 71 | } 72 | var n int 73 | n, *err = w.Write(b) 74 | *nr += int64(n) 75 | } 76 | 77 | func (m *Meta) WriteTo(w io.Writer) (nr int64, err error) { 78 | if m.req != nil { 79 | fprintf(&nr, &err, w, "Type: request\r\n") 80 | } else if m.resp != nil { 81 | fprintf(&nr, &err, w, "Type: response\r\n") 82 | } 83 | fprintf(&nr, &err, w, "ReceivedAt: %v\r\n", m.t) 84 | fprintf(&nr, &err, w, "Session: %d\r\n", m.sess) 85 | fprintf(&nr, &err, w, "From: %v\r\n", m.from) 86 | if m.err != nil { 87 | // note the empty response 88 | fprintf(&nr, &err, w, "Error: %v\r\n\r\n\r\n\r\n", m.err) 89 | } else if m.req != nil { 90 | fprintf(&nr, &err, w, "\r\n") 91 | buf, err2 := httputil.DumpRequest(m.req, false) 92 | if err2 != nil { 93 | return nr, err2 94 | } 95 | write(&nr, &err, w, buf) 96 | } else if m.resp != nil { 97 | fprintf(&nr, &err, w, "\r\n") 98 | buf, err2 := httputil.DumpResponse(m.resp, false) 99 | if err2 != nil { 100 | return nr, err2 101 | } 102 | write(&nr, &err, w, buf) 103 | } 104 | return 105 | } 106 | 107 | // HttpLogger is an asynchronous HTTP request/response logger. It traces 108 | // requests and responses headers in a "log" file in logger directory and dumps 109 | // their bodies in files prefixed with the session identifiers. 110 | // Close it to ensure pending items are correctly logged. 111 | type HttpLogger struct { 112 | path string 113 | c chan *Meta 114 | errch chan error 115 | } 116 | 117 | func NewLogger(basepath string) (*HttpLogger, error) { 118 | f, err := os.Create(path.Join(basepath, "log")) 119 | if err != nil { 120 | return nil, err 121 | } 122 | logger := &HttpLogger{basepath, make(chan *Meta), make(chan error)} 123 | go func() { 124 | for m := range logger.c { 125 | if _, err := m.WriteTo(f); err != nil { 126 | log.Println("Can't write meta", err) 127 | } 128 | } 129 | logger.errch <- f.Close() 130 | }() 131 | return logger, nil 132 | } 133 | 134 | func (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.ProxyCtx) { 135 | body := path.Join(logger.path, fmt.Sprintf("%d_resp", ctx.Session)) 136 | from := "" 137 | if ctx.UserData != nil { 138 | from = ctx.UserData.(*transport.RoundTripDetails).TCPAddr.String() 139 | } 140 | if resp == nil { 141 | resp = emptyResp 142 | } else { 143 | resp.Body = NewTeeReadCloser(resp.Body, NewFileStream(body)) 144 | } 145 | logger.LogMeta(&Meta{ 146 | resp: resp, 147 | err: ctx.Error, 148 | t: time.Now(), 149 | sess: ctx.Session, 150 | from: from}) 151 | } 152 | 153 | var emptyResp = &http.Response{} 154 | var emptyReq = &http.Request{} 155 | 156 | func (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.ProxyCtx) { 157 | body := path.Join(logger.path, fmt.Sprintf("%d_req", ctx.Session)) 158 | if req == nil { 159 | req = emptyReq 160 | } else { 161 | req.Body = NewTeeReadCloser(req.Body, NewFileStream(body)) 162 | } 163 | logger.LogMeta(&Meta{ 164 | req: req, 165 | err: ctx.Error, 166 | t: time.Now(), 167 | sess: ctx.Session, 168 | from: req.RemoteAddr}) 169 | } 170 | 171 | func (logger *HttpLogger) LogMeta(m *Meta) { 172 | logger.c <- m 173 | } 174 | 175 | func (logger *HttpLogger) Close() error { 176 | close(logger.c) 177 | return <-logger.errch 178 | } 179 | 180 | // TeeReadCloser extends io.TeeReader by allowing reader and writer to be 181 | // closed. 182 | type TeeReadCloser struct { 183 | r io.Reader 184 | w io.WriteCloser 185 | c io.Closer 186 | } 187 | 188 | func NewTeeReadCloser(r io.ReadCloser, w io.WriteCloser) io.ReadCloser { 189 | return &TeeReadCloser{io.TeeReader(r, w), w, r} 190 | } 191 | 192 | func (t *TeeReadCloser) Read(b []byte) (int, error) { 193 | return t.r.Read(b) 194 | } 195 | 196 | // Close attempts to close the reader and write. It returns an error if both 197 | // failed to Close. 198 | func (t *TeeReadCloser) Close() error { 199 | err1 := t.c.Close() 200 | err2 := t.w.Close() 201 | if err1 != nil { 202 | return err1 203 | } 204 | return err2 205 | } 206 | 207 | // stoppableListener serves stoppableConn and tracks their lifetime to notify 208 | // when it is safe to terminate the application. 209 | type stoppableListener struct { 210 | net.Listener 211 | sync.WaitGroup 212 | } 213 | 214 | type stoppableConn struct { 215 | net.Conn 216 | wg *sync.WaitGroup 217 | } 218 | 219 | func newStoppableListener(l net.Listener) *stoppableListener { 220 | return &stoppableListener{l, sync.WaitGroup{}} 221 | } 222 | 223 | func (sl *stoppableListener) Accept() (net.Conn, error) { 224 | c, err := sl.Listener.Accept() 225 | if err != nil { 226 | return c, err 227 | } 228 | sl.Add(1) 229 | return &stoppableConn{c, &sl.WaitGroup}, nil 230 | } 231 | 232 | func (sc *stoppableConn) Close() error { 233 | sc.wg.Done() 234 | return sc.Conn.Close() 235 | } 236 | 237 | func main() { 238 | verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") 239 | addr := flag.String("l", ":8080", "on which address should the proxy listen") 240 | flag.Parse() 241 | proxy := goproxy.NewProxyHttpServer() 242 | proxy.Verbose = *verbose 243 | if err := os.MkdirAll("db", 0755); err != nil { 244 | log.Fatal("Can't create dir", err) 245 | } 246 | logger, err := NewLogger("db") 247 | if err != nil { 248 | log.Fatal("can't open log file", err) 249 | } 250 | tr := transport.Transport{Proxy: transport.ProxyFromEnvironment} 251 | // For every incoming request, override the RoundTripper to extract 252 | // connection information. Store it is session context log it after 253 | // handling the response. 254 | proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 255 | ctx.RoundTripper = goproxy.RoundTripperFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (resp *http.Response, err error) { 256 | ctx.UserData, resp, err = tr.DetailedRoundTrip(req) 257 | return 258 | }) 259 | logger.LogReq(req, ctx) 260 | return req, nil 261 | }) 262 | proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { 263 | logger.LogResp(resp, ctx) 264 | return resp 265 | }) 266 | l, err := net.Listen("tcp", *addr) 267 | if err != nil { 268 | log.Fatal("listen:", err) 269 | } 270 | sl := newStoppableListener(l) 271 | ch := make(chan os.Signal) 272 | signal.Notify(ch, os.Interrupt) 273 | go func() { 274 | <-ch 275 | log.Println("Got SIGINT exiting") 276 | sl.Add(1) 277 | sl.Close() 278 | logger.Close() 279 | sl.Done() 280 | }() 281 | log.Println("Starting Proxy") 282 | http.Serve(sl, proxy) 283 | sl.Wait() 284 | log.Println("All connections closed - exit") 285 | } 286 | -------------------------------------------------------------------------------- /dispatcher.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "net" 7 | "net/http" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | // ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request 13 | // before sending it to the remote server 14 | type ReqCondition interface { 15 | RespCondition 16 | HandleReq(req *http.Request, ctx *ProxyCtx) bool 17 | } 18 | 19 | // RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response 20 | // before sending it to the proxy client. Note that resp might be nil, in case there was an 21 | // error sending the request. 22 | type RespCondition interface { 23 | HandleResp(resp *http.Response, ctx *ProxyCtx) bool 24 | } 25 | 26 | // ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx) 27 | type ReqConditionFunc func(req *http.Request, ctx *ProxyCtx) bool 28 | 29 | // RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx) 30 | type RespConditionFunc func(resp *http.Response, ctx *ProxyCtx) bool 31 | 32 | func (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) bool { 33 | return c(req, ctx) 34 | } 35 | 36 | // ReqConditionFunc cannot test responses. It only satisfies RespCondition interface so that 37 | // to be usable as RespCondition. 38 | func (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { 39 | return c(ctx.Req, ctx) 40 | } 41 | 42 | func (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { 43 | return c(resp, ctx) 44 | } 45 | 46 | // UrlHasPrefix returns a ReqCondition checking wether the destination URL the proxy client has requested 47 | // has the given prefix, with or without the host. 48 | // For example UrlHasPrefix("host/x") will match requests of the form 'GET host/x', and will match 49 | // requests to url 'http://host/x' 50 | func UrlHasPrefix(prefix string) ReqConditionFunc { 51 | return func(req *http.Request, ctx *ProxyCtx) bool { 52 | return strings.HasPrefix(req.URL.Path, prefix) || 53 | strings.HasPrefix(req.URL.Host+req.URL.Path, prefix) || 54 | strings.HasPrefix(req.URL.Scheme+req.URL.Host+req.URL.Path, prefix) 55 | } 56 | } 57 | 58 | // UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings 59 | // with or without the host prefix. 60 | // UrlIs("google.com/","foo") will match requests 'GET /' to 'google.com', requests `'GET google.com/' to 61 | // any host, and requests of the form 'GET foo'. 62 | func UrlIs(urls ...string) ReqConditionFunc { 63 | urlSet := make(map[string]bool) 64 | for _, u := range urls { 65 | urlSet[u] = true 66 | } 67 | return func(req *http.Request, ctx *ProxyCtx) bool { 68 | _, pathOk := urlSet[req.URL.Path] 69 | _, hostAndOk := urlSet[req.URL.Host+req.URL.Path] 70 | return pathOk || hostAndOk 71 | } 72 | } 73 | 74 | // ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches 75 | // any of the given regular expressions. 76 | func ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc { 77 | return func(req *http.Request, ctx *ProxyCtx) bool { 78 | for _, re := range regexps { 79 | if re.MatchString(req.Host) { 80 | return true 81 | } 82 | } 83 | return false 84 | } 85 | } 86 | 87 | // ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal 88 | // to one of the given strings 89 | func ReqHostIs(hosts ...string) ReqConditionFunc { 90 | hostSet := make(map[string]bool) 91 | for _, h := range hosts { 92 | hostSet[h] = true 93 | } 94 | return func(req *http.Request, ctx *ProxyCtx) bool { 95 | _, ok := hostSet[req.URL.Host] 96 | return ok 97 | } 98 | } 99 | 100 | var localHostIpv4 = regexp.MustCompile(`127\.0\.0\.\d+`) 101 | 102 | // IsLocalHost checks whether the destination host is explicitly local host 103 | // (buggy, there can be IPv6 addresses it doesn't catch) 104 | var IsLocalHost ReqConditionFunc = func(req *http.Request, ctx *ProxyCtx) bool { 105 | return req.URL.Host == "::1" || 106 | req.URL.Host == "0:0:0:0:0:0:0:1" || 107 | localHostIpv4.MatchString(req.URL.Host) || 108 | req.URL.Host == "localhost" 109 | } 110 | 111 | // UrlMatches returns a ReqCondition testing whether the destination URL 112 | // of the request matches the given regexp, with or without prefix 113 | func UrlMatches(re *regexp.Regexp) ReqConditionFunc { 114 | return func(req *http.Request, ctx *ProxyCtx) bool { 115 | return re.MatchString(req.URL.Path) || 116 | re.MatchString(req.URL.Host+req.URL.Path) 117 | } 118 | } 119 | 120 | // DstHostIs returns a ReqCondition testing wether the host in the request url is the given string 121 | func DstHostIs(host string) ReqConditionFunc { 122 | return func(req *http.Request, ctx *ProxyCtx) bool { 123 | return req.URL.Host == host 124 | } 125 | } 126 | 127 | // SrcIpIs returns a ReqCondition testing whether the source IP of the request is one of the given strings 128 | func SrcIpIs(ips ...string) ReqCondition { 129 | return ReqConditionFunc(func(req *http.Request, ctx *ProxyCtx) bool { 130 | for _, ip := range ips { 131 | if strings.HasPrefix(req.RemoteAddr, ip+":") { 132 | return true 133 | } 134 | } 135 | return false 136 | }) 137 | } 138 | 139 | // Not returns a ReqCondition negating the given ReqCondition 140 | func Not(r ReqCondition) ReqConditionFunc { 141 | return func(req *http.Request, ctx *ProxyCtx) bool { 142 | return !r.HandleReq(req, ctx) 143 | } 144 | } 145 | 146 | // ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal 147 | // to one of the given strings. 148 | func ContentTypeIs(typ string, types ...string) RespCondition { 149 | types = append(types, typ) 150 | return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool { 151 | if resp == nil { 152 | return false 153 | } 154 | contentType := resp.Header.Get("Content-Type") 155 | for _, typ := range types { 156 | if contentType == typ || strings.HasPrefix(contentType, typ+";") { 157 | return true 158 | } 159 | } 160 | return false 161 | }) 162 | } 163 | 164 | // ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions. 165 | // You will use the ReqProxyConds struct to register a ReqHandler, that would filter 166 | // the request, only if all the given ReqCondition matched. 167 | // Typical usage: 168 | // proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...) 169 | func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds { 170 | return &ReqProxyConds{proxy, conds} 171 | } 172 | 173 | // ReqProxyConds aggregate ReqConditions for a ProxyHttpServer. Upon calling Do, it will register a ReqHandler that would 174 | // handle the request if all conditions on the HTTP request are met. 175 | type ReqProxyConds struct { 176 | proxy *ProxyHttpServer 177 | reqConds []ReqCondition 178 | } 179 | 180 | // DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f)) 181 | func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)) { 182 | pcond.Do(FuncReqHandler(f)) 183 | } 184 | 185 | // ReqProxyConds.Do will register the ReqHandler on the proxy, 186 | // the ReqHandler will handle the HTTP request if all the conditions 187 | // aggregated in the ReqProxyConds are met. Typical usage: 188 | // proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy 189 | // proxy.OnRequest(cond1,cond2).Do(handler) 190 | // // given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true 191 | // // if they are, will call handler.Handle(req,ctx) 192 | func (pcond *ReqProxyConds) Do(h ReqHandler) { 193 | pcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers, 194 | FuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { 195 | for _, cond := range pcond.reqConds { 196 | if !cond.HandleReq(r, ctx) { 197 | return r, nil 198 | } 199 | } 200 | return h.Handle(r, ctx) 201 | })) 202 | } 203 | 204 | // HandleConnect is used when proxy receives an HTTP CONNECT request, 205 | // it'll then use the HttpsHandler to determine what should it 206 | // do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction 207 | // struct returned will determine what to do with this request. ConnectAccept will simply accept the request 208 | // forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the 209 | // client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man 210 | // in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped 211 | // connection. 212 | // The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy 213 | // will use the default tls configuration. 214 | // proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests 215 | func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) { 216 | pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, 217 | FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 218 | for _, cond := range pcond.reqConds { 219 | if !cond.HandleReq(ctx.Req, ctx) { 220 | return nil, "" 221 | } 222 | } 223 | return h.HandleConnect(host, ctx) 224 | })) 225 | } 226 | 227 | // HandleConnectFunc is equivalent to HandleConnect, 228 | // for example, accepting CONNECT request if they contain a password in header 229 | // io.WriteString(h,password) 230 | // passHash := h.Sum(nil) 231 | // proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 232 | // c := sha1.New() 233 | // io.WriteString(c,ctx.Req.Header.Get("X-GoProxy-Auth")) 234 | // if c.Sum(nil) == passHash { 235 | // return OkConnect, host 236 | // } 237 | // return RejectConnect, host 238 | // }) 239 | func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string)) { 240 | pcond.HandleConnect(FuncHttpsHandler(f)) 241 | } 242 | 243 | func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) { 244 | pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, 245 | FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 246 | for _, cond := range pcond.reqConds { 247 | if !cond.HandleReq(ctx.Req, ctx) { 248 | return nil, "" 249 | } 250 | } 251 | return &ConnectAction{Action: ConnectHijack, Hijack: f}, host 252 | })) 253 | } 254 | 255 | // ProxyConds is used to aggregate RespConditions for a ProxyHttpServer. 256 | // Upon calling ProxyConds.Do, it will register a RespHandler that would 257 | // handle the HTTP response from remote server if all conditions on the HTTP response are met. 258 | type ProxyConds struct { 259 | proxy *ProxyHttpServer 260 | reqConds []ReqCondition 261 | respCond []RespCondition 262 | } 263 | 264 | // ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f)) 265 | func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response) { 266 | pcond.Do(FuncRespHandler(f)) 267 | } 268 | 269 | // ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every 270 | // request that matches the conditions aggregated in pcond. 271 | func (pcond *ProxyConds) Do(h RespHandler) { 272 | pcond.proxy.respHandlers = append(pcond.proxy.respHandlers, 273 | FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { 274 | for _, cond := range pcond.reqConds { 275 | if !cond.HandleReq(ctx.Req, ctx) { 276 | return resp 277 | } 278 | } 279 | for _, cond := range pcond.respCond { 280 | if !cond.HandleResp(resp, ctx) { 281 | return resp 282 | } 283 | } 284 | return h.Handle(resp, ctx) 285 | })) 286 | } 287 | 288 | // OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is 289 | // proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used 290 | // // if cond1.HandleResp(resp) && cond2.HandleResp(resp) 291 | func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds { 292 | return &ProxyConds{proxy, make([]ReqCondition, 0), conds} 293 | } 294 | 295 | // AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to 296 | // eavesdrop all https connections to www.google.com, we can use 297 | // proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm) 298 | var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 299 | return MitmConnect, host 300 | } 301 | 302 | // AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow 303 | // connections to hosts on any other port than 443 304 | // proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))). 305 | // HandleConnect(goproxy.AlwaysReject) 306 | var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 307 | return RejectConnect, host 308 | } 309 | 310 | // HandleBytes will return a RespHandler that read the entire body of the request 311 | // to a byte array in memory, would run the user supplied f function on the byte arra, 312 | // and will replace the body of the original response with the resulting byte array. 313 | func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler { 314 | return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { 315 | b, err := ioutil.ReadAll(resp.Body) 316 | if err != nil { 317 | ctx.Warnf("Cannot read response %s", err) 318 | return resp 319 | } 320 | resp.Body.Close() 321 | 322 | resp.Body = ioutil.NopCloser(bytes.NewBuffer(f(b, ctx))) 323 | return resp 324 | }) 325 | } 326 | -------------------------------------------------------------------------------- /examples/goproxy-jquery-version/jquery_homepage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery: The Write Less, Do More, JavaScript Library 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 |
20 | 31 |
32 | 33 |
34 | 42 |
43 | 44 | 45 | 46 |
47 | 48 |
49 | 50 |
51 |

jQuery is a new kind of JavaScript Library.

52 |

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.

53 |
    54 |
  • Lightweight Footprint 55 |
    56 | 57 |

    About 31KB in size (Minified and Gzipped)

    58 |
    59 |
  • 60 |
  • CSS3 Compliant 61 |
    62 |

    Supports CSS 1-3 selectors and more!

    63 |
    64 | 65 |
  • 66 |
  • Cross-browser 67 |
    68 |

    IE 6.0+, FF 3.6+, Safari 5.0+, Opera, Chrome

    69 |
    70 |
  • 71 |
72 |
73 | 74 |
75 |

Grab the latest version!

76 |
77 |
78 | Choose your compression level: 79 |
80 | 81 | jquery-1.7.2.min.js 82 | 83 | 84 | jquery-1.7.2.js 85 | 86 |
87 | 88 |

Current Release: v1.7.2

89 |
90 |
91 | 97 |
98 | 99 |
100 |

Who's using jQuery?

101 | 115 |
116 | 117 | 118 |
119 | 120 |
121 |

Learn jQuery Now!

122 |

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

123 |
124 |
$("p.neat").addClass("ohmy").show("slow");
125 | Run Code 126 | 127 |

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.

128 |
129 |
130 | 131 | 132 | 133 |
134 |

jQuery Resources

135 | 136 |
137 |

Getting Started With jQuery

138 | 145 |
146 |
147 |

Developer Resources

148 | 155 |
156 |
157 | 158 |
159 | 160 |
161 |

Books About jQuery

162 | 163 | 197 | 198 |
199 | 200 | 201 | 202 |
203 | 204 | 205 | 224 |
225 | 226 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /https.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "errors" 7 | "io" 8 | "io/ioutil" 9 | "net" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "regexp" 14 | "strconv" 15 | "strings" 16 | "sync" 17 | "sync/atomic" 18 | ) 19 | 20 | type ConnectActionLiteral int 21 | 22 | const ( 23 | ConnectAccept = iota 24 | ConnectReject 25 | ConnectMitm 26 | ConnectHijack 27 | ConnectHTTPMitm 28 | ConnectProxyAuthHijack 29 | ) 30 | 31 | var ( 32 | OkConnect = &ConnectAction{Action: ConnectAccept, TLSConfig: TLSConfigFromCA(&GoproxyCa)} 33 | MitmConnect = &ConnectAction{Action: ConnectMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)} 34 | HTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)} 35 | RejectConnect = &ConnectAction{Action: ConnectReject, TLSConfig: TLSConfigFromCA(&GoproxyCa)} 36 | httpsRegexp = regexp.MustCompile(`^https:\/\/`) 37 | ) 38 | 39 | type ConnectAction struct { 40 | Action ConnectActionLiteral 41 | Hijack func(req *http.Request, client net.Conn, ctx *ProxyCtx) 42 | TLSConfig func(host string, ctx *ProxyCtx) (*tls.Config, error) 43 | } 44 | 45 | func stripPort(s string) string { 46 | ix := strings.IndexRune(s, ':') 47 | if ix == -1 { 48 | return s 49 | } 50 | return s[:ix] 51 | } 52 | 53 | func (proxy *ProxyHttpServer) dial(network, addr string) (c net.Conn, err error) { 54 | if proxy.Tr.Dial != nil { 55 | return proxy.Tr.Dial(network, addr) 56 | } 57 | return net.Dial(network, addr) 58 | } 59 | 60 | func (proxy *ProxyHttpServer) connectDial(network, addr string) (c net.Conn, err error) { 61 | if proxy.ConnectDial == nil { 62 | return proxy.dial(network, addr) 63 | } 64 | return proxy.ConnectDial(network, addr) 65 | } 66 | 67 | type halfClosable interface { 68 | net.Conn 69 | CloseWrite() error 70 | CloseRead() error 71 | } 72 | 73 | var _ halfClosable = (*net.TCPConn)(nil) 74 | 75 | func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) { 76 | ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, certStore: proxy.CertStore} 77 | 78 | hij, ok := w.(http.Hijacker) 79 | if !ok { 80 | panic("httpserver does not support hijacking") 81 | } 82 | 83 | proxyClient, _, e := hij.Hijack() 84 | if e != nil { 85 | panic("Cannot hijack connection " + e.Error()) 86 | } 87 | 88 | ctx.Logf("Running %d CONNECT handlers", len(proxy.httpsHandlers)) 89 | todo, host := OkConnect, r.URL.Host 90 | for i, h := range proxy.httpsHandlers { 91 | newtodo, newhost := h.HandleConnect(host, ctx) 92 | 93 | // If found a result, break the loop immediately 94 | if newtodo != nil { 95 | todo, host = newtodo, newhost 96 | ctx.Logf("on %dth handler: %v %s", i, todo, host) 97 | break 98 | } 99 | } 100 | switch todo.Action { 101 | case ConnectAccept: 102 | if !hasPort.MatchString(host) { 103 | host += ":80" 104 | } 105 | targetSiteCon, err := proxy.connectDial("tcp", host) 106 | if err != nil { 107 | httpError(proxyClient, ctx, err) 108 | return 109 | } 110 | ctx.Logf("Accepting CONNECT to %s", host) 111 | proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) 112 | 113 | targetTCP, targetOK := targetSiteCon.(halfClosable) 114 | proxyClientTCP, clientOK := proxyClient.(halfClosable) 115 | if targetOK && clientOK { 116 | go copyAndClose(ctx, targetTCP, proxyClientTCP) 117 | go copyAndClose(ctx, proxyClientTCP, targetTCP) 118 | } else { 119 | go func() { 120 | var wg sync.WaitGroup 121 | wg.Add(2) 122 | go copyOrWarn(ctx, targetSiteCon, proxyClient, &wg) 123 | go copyOrWarn(ctx, proxyClient, targetSiteCon, &wg) 124 | wg.Wait() 125 | proxyClient.Close() 126 | targetSiteCon.Close() 127 | 128 | }() 129 | } 130 | 131 | case ConnectHijack: 132 | ctx.Logf("Hijacking CONNECT to %s", host) 133 | proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) 134 | todo.Hijack(r, proxyClient, ctx) 135 | case ConnectHTTPMitm: 136 | proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) 137 | ctx.Logf("Assuming CONNECT is plain HTTP tunneling, mitm proxying it") 138 | targetSiteCon, err := proxy.connectDial("tcp", host) 139 | if err != nil { 140 | ctx.Warnf("Error dialing to %s: %s", host, err.Error()) 141 | return 142 | } 143 | for { 144 | client := bufio.NewReader(proxyClient) 145 | remote := bufio.NewReader(targetSiteCon) 146 | req, err := http.ReadRequest(client) 147 | if err != nil && err != io.EOF { 148 | ctx.Warnf("cannot read request of MITM HTTP client: %+#v", err) 149 | } 150 | if err != nil { 151 | return 152 | } 153 | req, resp := proxy.filterRequest(req, ctx) 154 | if resp == nil { 155 | if err := req.Write(targetSiteCon); err != nil { 156 | httpError(proxyClient, ctx, err) 157 | return 158 | } 159 | resp, err = http.ReadResponse(remote, req) 160 | if err != nil { 161 | httpError(proxyClient, ctx, err) 162 | return 163 | } 164 | defer resp.Body.Close() 165 | } 166 | resp = proxy.filterResponse(resp, ctx) 167 | if err := resp.Write(proxyClient); err != nil { 168 | httpError(proxyClient, ctx, err) 169 | return 170 | } 171 | } 172 | case ConnectMitm: 173 | proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) 174 | ctx.Logf("Assuming CONNECT is TLS, mitm proxying it") 175 | // this goes in a separate goroutine, so that the net/http server won't think we're 176 | // still handling the request even after hijacking the connection. Those HTTP CONNECT 177 | // request can take forever, and the server will be stuck when "closed". 178 | // TODO: Allow Server.Close() mechanism to shut down this connection as nicely as possible 179 | tlsConfig := defaultTLSConfig 180 | if todo.TLSConfig != nil { 181 | var err error 182 | tlsConfig, err = todo.TLSConfig(host, ctx) 183 | if err != nil { 184 | httpError(proxyClient, ctx, err) 185 | return 186 | } 187 | } 188 | go func() { 189 | //TODO: cache connections to the remote website 190 | rawClientTls := tls.Server(proxyClient, tlsConfig) 191 | if err := rawClientTls.Handshake(); err != nil { 192 | ctx.Warnf("Cannot handshake client %v %v", r.Host, err) 193 | return 194 | } 195 | defer rawClientTls.Close() 196 | clientTlsReader := bufio.NewReader(rawClientTls) 197 | for !isEof(clientTlsReader) { 198 | req, err := http.ReadRequest(clientTlsReader) 199 | var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, UserData: ctx.UserData} 200 | if err != nil && err != io.EOF { 201 | return 202 | } 203 | if err != nil { 204 | ctx.Warnf("Cannot read TLS request from mitm'd client %v %v", r.Host, err) 205 | return 206 | } 207 | req.RemoteAddr = r.RemoteAddr // since we're converting the request, need to carry over the original connecting IP as well 208 | ctx.Logf("req %v", r.Host) 209 | 210 | if !httpsRegexp.MatchString(req.URL.String()) { 211 | req.URL, err = url.Parse("https://" + r.Host + req.URL.String()) 212 | } 213 | 214 | // Bug fix which goproxy fails to provide request 215 | // information URL in the context when does HTTPS MITM 216 | ctx.Req = req 217 | 218 | req, resp := proxy.filterRequest(req, ctx) 219 | if resp == nil { 220 | if isWebSocketRequest(req) { 221 | ctx.Logf("Request looks like websocket upgrade.") 222 | proxy.serveWebsocketTLS(ctx, w, req, tlsConfig, rawClientTls) 223 | return 224 | } 225 | if err != nil { 226 | ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path) 227 | return 228 | } 229 | removeProxyHeaders(ctx, req) 230 | resp, err = ctx.RoundTrip(req) 231 | if err != nil { 232 | ctx.Warnf("Cannot read TLS response from mitm'd server %v", err) 233 | return 234 | } 235 | ctx.Logf("resp %v", resp.Status) 236 | } 237 | resp = proxy.filterResponse(resp, ctx) 238 | defer resp.Body.Close() 239 | 240 | text := resp.Status 241 | statusCode := strconv.Itoa(resp.StatusCode) + " " 242 | if strings.HasPrefix(text, statusCode) { 243 | text = text[len(statusCode):] 244 | } 245 | // always use 1.1 to support chunked encoding 246 | if _, err := io.WriteString(rawClientTls, "HTTP/1.1"+" "+statusCode+text+"\r\n"); err != nil { 247 | ctx.Warnf("Cannot write TLS response HTTP status from mitm'd client: %v", err) 248 | return 249 | } 250 | // Since we don't know the length of resp, return chunked encoded response 251 | // TODO: use a more reasonable scheme 252 | resp.Header.Del("Content-Length") 253 | resp.Header.Set("Transfer-Encoding", "chunked") 254 | // Force connection close otherwise chrome will keep CONNECT tunnel open forever 255 | resp.Header.Set("Connection", "close") 256 | if err := resp.Header.Write(rawClientTls); err != nil { 257 | ctx.Warnf("Cannot write TLS response header from mitm'd client: %v", err) 258 | return 259 | } 260 | if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { 261 | ctx.Warnf("Cannot write TLS response header end from mitm'd client: %v", err) 262 | return 263 | } 264 | chunked := newChunkedWriter(rawClientTls) 265 | if _, err := io.Copy(chunked, resp.Body); err != nil { 266 | ctx.Warnf("Cannot write TLS response body from mitm'd client: %v", err) 267 | return 268 | } 269 | if err := chunked.Close(); err != nil { 270 | ctx.Warnf("Cannot write TLS chunked EOF from mitm'd client: %v", err) 271 | return 272 | } 273 | if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { 274 | ctx.Warnf("Cannot write TLS response chunked trailer from mitm'd client: %v", err) 275 | return 276 | } 277 | } 278 | ctx.Logf("Exiting on EOF") 279 | }() 280 | case ConnectProxyAuthHijack: 281 | proxyClient.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n")) 282 | todo.Hijack(r, proxyClient, ctx) 283 | case ConnectReject: 284 | if ctx.Resp != nil { 285 | if err := ctx.Resp.Write(proxyClient); err != nil { 286 | ctx.Warnf("Cannot write response that reject http CONNECT: %v", err) 287 | } 288 | } 289 | proxyClient.Close() 290 | } 291 | } 292 | 293 | func httpError(w io.WriteCloser, ctx *ProxyCtx, err error) { 294 | if _, err := io.WriteString(w, "HTTP/1.1 502 Bad Gateway\r\n\r\n"); err != nil { 295 | ctx.Warnf("Error responding to client: %s", err) 296 | } 297 | if err := w.Close(); err != nil { 298 | ctx.Warnf("Error closing client connection: %s", err) 299 | } 300 | } 301 | 302 | func copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader, wg *sync.WaitGroup) { 303 | if _, err := io.Copy(dst, src); err != nil { 304 | ctx.Warnf("Error copying to client: %s", err) 305 | } 306 | wg.Done() 307 | } 308 | 309 | func copyAndClose(ctx *ProxyCtx, dst, src halfClosable) { 310 | if _, err := io.Copy(dst, src); err != nil { 311 | ctx.Warnf("Error copying to client: %s", err) 312 | } 313 | 314 | dst.CloseWrite() 315 | src.CloseRead() 316 | } 317 | 318 | func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn, error) { 319 | https_proxy := os.Getenv("HTTPS_PROXY") 320 | if https_proxy == "" { 321 | https_proxy = os.Getenv("https_proxy") 322 | } 323 | if https_proxy == "" { 324 | return nil 325 | } 326 | return proxy.NewConnectDialToProxy(https_proxy) 327 | } 328 | 329 | func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(network, addr string) (net.Conn, error) { 330 | return proxy.NewConnectDialToProxyWithHandler(https_proxy, nil) 331 | } 332 | 333 | func (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(https_proxy string, connectReqHandler func(req *http.Request)) func(network, addr string) (net.Conn, error) { 334 | u, err := url.Parse(https_proxy) 335 | if err != nil { 336 | return nil 337 | } 338 | if u.Scheme == "" || u.Scheme == "http" { 339 | if strings.IndexRune(u.Host, ':') == -1 { 340 | u.Host += ":80" 341 | } 342 | return func(network, addr string) (net.Conn, error) { 343 | connectReq := &http.Request{ 344 | Method: "CONNECT", 345 | URL: &url.URL{Opaque: addr}, 346 | Host: addr, 347 | Header: make(http.Header), 348 | } 349 | if connectReqHandler != nil { 350 | connectReqHandler(connectReq) 351 | } 352 | c, err := proxy.dial(network, u.Host) 353 | if err != nil { 354 | return nil, err 355 | } 356 | connectReq.Write(c) 357 | // Read response. 358 | // Okay to use and discard buffered reader here, because 359 | // TLS server will not speak until spoken to. 360 | br := bufio.NewReader(c) 361 | resp, err := http.ReadResponse(br, connectReq) 362 | if err != nil { 363 | c.Close() 364 | return nil, err 365 | } 366 | defer resp.Body.Close() 367 | if resp.StatusCode != 200 { 368 | resp, err := ioutil.ReadAll(resp.Body) 369 | if err != nil { 370 | return nil, err 371 | } 372 | c.Close() 373 | return nil, errors.New("proxy refused connection" + string(resp)) 374 | } 375 | return c, nil 376 | } 377 | } 378 | if u.Scheme == "https" || u.Scheme == "wss" { 379 | if strings.IndexRune(u.Host, ':') == -1 { 380 | u.Host += ":443" 381 | } 382 | return func(network, addr string) (net.Conn, error) { 383 | c, err := proxy.dial(network, u.Host) 384 | if err != nil { 385 | return nil, err 386 | } 387 | c = tls.Client(c, proxy.Tr.TLSClientConfig) 388 | connectReq := &http.Request{ 389 | Method: "CONNECT", 390 | URL: &url.URL{Opaque: addr}, 391 | Host: addr, 392 | Header: make(http.Header), 393 | } 394 | if connectReqHandler != nil { 395 | connectReqHandler(connectReq) 396 | } 397 | connectReq.Write(c) 398 | // Read response. 399 | // Okay to use and discard buffered reader here, because 400 | // TLS server will not speak until spoken to. 401 | br := bufio.NewReader(c) 402 | resp, err := http.ReadResponse(br, connectReq) 403 | if err != nil { 404 | c.Close() 405 | return nil, err 406 | } 407 | defer resp.Body.Close() 408 | if resp.StatusCode != 200 { 409 | body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 500)) 410 | if err != nil { 411 | return nil, err 412 | } 413 | c.Close() 414 | return nil, errors.New("proxy refused connection" + string(body)) 415 | } 416 | return c, nil 417 | } 418 | } 419 | return nil 420 | } 421 | 422 | func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error) { 423 | return func(host string, ctx *ProxyCtx) (*tls.Config, error) { 424 | var err error 425 | var cert *tls.Certificate 426 | 427 | hostname := stripPort(host) 428 | config := defaultTLSConfig.Clone() 429 | ctx.Logf("signing for %s", stripPort(host)) 430 | 431 | genCert := func() (*tls.Certificate, error) { 432 | return signHost(*ca, []string{hostname}) 433 | } 434 | if ctx.certStore != nil { 435 | cert, err = ctx.certStore.Fetch(hostname, genCert) 436 | } else { 437 | cert, err = genCert() 438 | } 439 | 440 | if err != nil { 441 | ctx.Warnf("Cannot sign host certificate with provided CA: %s", err) 442 | return nil, err 443 | } 444 | 445 | config.Certificates = append(config.Certificates, *cert) 446 | return config, nil 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /examples/goproxy-jquery-version/php_man.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | PHP: PHP Manual - Manual 6 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 66 | 67 | 68 | 69 | 70 |
71 | 73 |
74 | downloads | 75 | documentation | 76 | faq | 77 | getting help | 78 | mailing lists | 79 | licenses | 80 | wiki | 81 | reporting bugs | 82 | php.net sites | 83 | links | 84 | conferences | 85 | my php.net 86 |
87 |
88 | 89 |
90 |
91 |

92 | 93 | search for 94 | 95 | 96 | in the 97 | 112 | 115 | 116 |

117 |
118 |
119 | 120 |
121 |
122 | 123 | 126 | 127 |
128 |
129 | 130 |
131 | 132 | 133 | 134 |   135 | 136 |
137 | [edit] Last updated: Fri, 23 Mar 2012 138 |
139 |
140 |

view this page in

154 | 155 |
156 |
157 |
158 | 159 | 160 |
161 |

PHP Manual

162 | 163 | 164 | 165 |
166 |
167 | 168 |
by:
169 | 170 | Mehdi Achour 171 | 172 |
173 | 174 | 175 |
176 | 177 | Friedhelm Betz 178 | 179 |
180 | 181 | 182 |
183 | 184 | Antony Dovgal 185 | 186 |
187 | 188 | 189 |
190 | 191 | Nuno Lopes 192 | 193 |
194 | 195 | 196 |
197 | 198 | Hannes Magnusson 199 | 200 |
201 | 202 | 203 |
204 | 205 | Georg Richter 206 | 207 |
208 | 209 | 210 |
211 | 212 | Damien Seguy 213 | 214 |
215 | 216 | 217 |
218 | 219 | Jakub Vrana 220 | 221 |
222 | 223 | 224 | 225 |
226 | 227 | 228 | And several others 229 | 230 | 231 |
232 | 233 |
234 |
2012-03-23
235 | 236 |
237 |
Edited By: 238 | 239 | Philip Olson 240 | 241 |
242 | 243 |
244 | 245 | 249 | 250 | 251 | 252 |
253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 |


280 |
281 |
282 | add a note add a note 283 | User Contributed Notes 284 | PHP Manual 285 |
286 |
There are no user contributed notes for this page.

287 |
288 |
 
289 |
290 | 291 |
292 | show source | 293 | credits | 294 | stats | 295 | sitemap | 296 | contact | 297 | advertising | 298 | mirror sites 299 |
300 | 301 | 313 | 322 | 323 | --------------------------------------------------------------------------------