├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── auth.go ├── auth_test.go ├── basic_auth.go ├── basic_auth_test.go ├── config.go ├── config_test.go ├── conn.go ├── digest_auth.go ├── go.mod ├── go.sum ├── headers_test.go ├── log.go ├── microproxy.go ├── microproxy.toml ├── proxy-digest-auth-test.py ├── proxy-digest-auth-test.py.bak └── vendor ├── github.com ├── BurntSushi │ └── toml │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── COMPATIBLE │ │ ├── COPYING │ │ ├── Makefile │ │ ├── README.md │ │ ├── decode.go │ │ ├── decode_meta.go │ │ ├── doc.go │ │ ├── encode.go │ │ ├── encoding_types.go │ │ ├── encoding_types_1.1.go │ │ ├── lex.go │ │ ├── parse.go │ │ ├── session.vim │ │ ├── type_check.go │ │ └── type_fields.go └── elazarl │ └── goproxy │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── actions.go │ ├── all.bash │ ├── ca.pem │ ├── certs.go │ ├── chunked.go │ ├── counterecryptor.go │ ├── ctx.go │ ├── dispatcher.go │ ├── doc.go │ ├── go.mod │ ├── go.sum │ ├── https.go │ ├── key.pem │ ├── logger.go │ ├── proxy.go │ ├── responses.go │ ├── signer.go │ └── websocket.go └── modules.txt /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.17 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | microproxy 2 | .tags* 3 | *.sublime* 4 | *.swp 5 | bin/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Konstantin Sorokin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | `microproxy` is a lightweight non-caching HTTP/HTTPS proxy server. 3 | 4 | ## Main features 5 | * Single executable with no external dependencies. 6 | * Single simple configuration file in TOML format. 7 | * Basic and Digest access authentication methods. 8 | * IP-based black and white access lists. 9 | * Ability to log all requests. 10 | * Ability to tweak X-Forwarded-For header. 11 | * Ability to specify IP address for outgoing connections. 12 | * Ability to forward requests to upstream proxy. 13 | * Reasonable memory usage. 14 | 15 | ## Installing 16 | This project is written in the [Go](http://golang.org/) programming language and to build it you need to install Go compiler and set some enviroment variables. [Here is instructions on how to do it](http://golang.org/doc/install). After you've done it, run the following command in your shell: 17 | ``` 18 | $ go get github.com/thekvs/microproxy 19 | ``` 20 | and this will build the binary in `$GOPATH/bin`. 21 | 22 | Run `go build -mod=vendor` for local build. 23 | 24 | ## Configuration file options 25 | `microproxy` uses [TOML](https://github.com/toml-lang/toml) format for configuration file. Below is a list of supported configuration options. 26 | 27 | * `listen="ip:port"` -- ip address and port where to listen for incoming proxy request. Default: `127.0.0.1:3128` 28 | * `access_log="path"` -- path to a file where to write requested through proxy urls. 29 | * `activity_log="path"` -- path to a file where to write debug and auxiliary information. 30 | * `allowed_connect_ports=[port1, port2, ...]` -- list of allowed port to CONNECT to. Default: `[443]` 31 | * `auth_file="path"` -- path to a file with users' passwords. If you use `digest` auth. scheme this file has to be in the format used by Apache's [htdigest](http://httpd.apache.org/docs/2.4/programs/htdigest.html) utility, for `basic` scheme it has to be in the format used by Apache's [htpasswd](http://httpd.apache.org/docs/2.4/programs/htpasswd.html) utility with -p option, i.e. created as `$ htpasswd -c -p auth.txt username`. 32 | * `auth_type="type"` -- authentication scheme type. Available options are: 33 | * `"basic"` -- use Basic authentication scheme. 34 | * `"digest"` -- use Digest authentication scheme. 35 | * `auth_realm="realmstring"` -- realm name which is to be reported to the client for the proxy authentication scheme. 36 | * `forwarded_for_header="action"` -- specifies how to handle `X-Forwarded-For` HTTP protocol header. Available options are: 37 | * `"on"` -- set `X-Forwarded-For` header with client's IP address, this is a default choice. 38 | * `"off"` -- do nothing, i.e. leave headear as is. 39 | * `"delete"` -- delete `X-Forwarded-For` header, this turns on stealth mode. 40 | * `"truncate"` -- delete all old `X-Forwarded-For` headers and insert a new one with client's IP address. 41 | * `via_header="action"` -- specifies how to handle `Via` HTTP protocol header. Available options are: 42 | * `"on"` -- set `Via` header, this is a default choice. 43 | * `"off"` -- do nothing with `Via` header. 44 | * `"delete"` -- delete `Via` header. 45 | * `via_proxy_name="name"` -- this value will be used as the host name in the `Via` header, by default the server's host name will be used. 46 | * `allowed_networks=["net1", ...]` -- list of whitelisted networks in CIDR format. 47 | * `disallowed_networks=["net1", ...]` -- list of blacklisted networks in CIDR format. 48 | * `bind_ip="ip"` -- specify which IP will be used for outgoing connections. 49 | * `add_headers=[["header1", value1"], ["header2", "value2"]...]` -- adds specified headers to outgoing HTTP requests, this option will not work for HTTPS connections. 50 | * `forward_proxy_url="http://user:password@host:port"` -- specify proxy to forward requests to. Uses basic auth type for the forward proxy. 51 | 52 | ## Usage 53 | 54 | ``` 55 | $ ./microproxy --config microproxy.toml 56 | ``` 57 | 58 | To enable debug mode, add `-v` switch. To only test configuration file correctness add `-t` switch, 59 | i.e. `$ ./microproxy --config microproxy.toml -t` 60 | 61 | ## Signal handling 62 | On `USR1` signal microproxy reopens access and activity log files. 63 | 64 | ## Licensing 65 | All source code included in this distribution is covered by the MIT License found in the LICENSE file. 66 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "regexp" 10 | "strings" 11 | 12 | "github.com/elazarl/goproxy" 13 | ) 14 | 15 | type ( 16 | BasicAuthFunc func(authData *BasicAuthData) *BasicAuthResponse 17 | DigestAuthFunc func(authData *DigestAuthData, op int) *DigestAuthResponse 18 | ) 19 | 20 | var unauthorizedMsg = []byte("407 Proxy Authentication Required") 21 | 22 | const ( 23 | ProxyAuthorizatonHeader = "Proxy-Authorization" 24 | ProxyAuthenticateHeader = "Proxy-Authenticate" 25 | ) 26 | 27 | func basicUnauthorized(req *http.Request, realm string) *http.Response { 28 | header := fmt.Sprintf("Basic realm=\"%s\"", realm) 29 | 30 | return &http.Response{ 31 | StatusCode: 407, 32 | ProtoMajor: 1, 33 | ProtoMinor: 1, 34 | Request: req, 35 | Header: http.Header{ProxyAuthenticateHeader: []string{header}}, 36 | Body: ioutil.NopCloser(bytes.NewBuffer(unauthorizedMsg)), 37 | ContentLength: int64(len(unauthorizedMsg)), 38 | } 39 | } 40 | 41 | func digestUnauthorized(req *http.Request, realm string, authFunc DigestAuthFunc) *http.Response { 42 | authResult := authFunc(nil, getNonce) 43 | nonce := authResult.data 44 | header := fmt.Sprintf("Digest realm=\"%s\", qop=auth, nonce=\"%s\"", realm, nonce) 45 | 46 | return &http.Response{ 47 | StatusCode: 407, 48 | ProtoMajor: 1, 49 | ProtoMinor: 1, 50 | Request: req, 51 | Header: http.Header{ProxyAuthenticateHeader: []string{header}}, 52 | Body: ioutil.NopCloser(bytes.NewBuffer(unauthorizedMsg)), 53 | ContentLength: int64(len(unauthorizedMsg)), 54 | } 55 | } 56 | 57 | func getDigestAuthData(req *http.Request) *DigestAuthData { 58 | authHeader := strings.SplitN(req.Header.Get(ProxyAuthorizatonHeader), " ", 2) 59 | req.Header.Del(ProxyAuthorizatonHeader) 60 | 61 | if len(authHeader) != 2 || authHeader[0] != "Digest" { 62 | return nil 63 | } 64 | 65 | m := make(map[string]string) 66 | 67 | quotedStringsRegexp := regexp.MustCompile("\"(.*?)\"") 68 | commasRegexp := regexp.MustCompile(",") 69 | 70 | h := authHeader[1] 71 | quotes := quotedStringsRegexp.FindAllStringSubmatchIndex(h, -1) 72 | commas := commasRegexp.FindAllStringSubmatchIndex(h, -1) 73 | 74 | separateCommas := make([]int, 0, 8) 75 | var quotedComma bool 76 | 77 | for _, commaIndices := range commas { 78 | commaIndex := commaIndices[0] 79 | quotedComma = false 80 | for _, quoteIndices := range quotes { 81 | if len(quoteIndices) == 4 && commaIndex >= quoteIndices[2] && commaIndex <= quoteIndices[3] { 82 | quotedComma = true 83 | break 84 | } 85 | } 86 | if !quotedComma { 87 | separateCommas = append(separateCommas, commaIndex) 88 | } 89 | } 90 | 91 | tokens := make([]string, 0, 10) 92 | s := 0 93 | 94 | for _, val := range separateCommas { 95 | e := val 96 | tokens = append(tokens, strings.Trim(h[s:e], " ")) 97 | s = e + 1 98 | } 99 | 100 | tokens = append(tokens, strings.Trim(h[s:], " ")) 101 | 102 | for _, token := range tokens { 103 | kv := strings.SplitN(token, "=", 2) 104 | m[kv[0]] = strings.Trim(kv[1], "\"") 105 | } 106 | 107 | var data DigestAuthData 108 | 109 | if v, ok := m["username"]; ok { 110 | data.user = v 111 | } 112 | 113 | if v, ok := m["realm"]; ok { 114 | data.realm = v 115 | } 116 | 117 | if v, ok := m["nonce"]; ok { 118 | data.nonce = v 119 | } 120 | 121 | if v, ok := m["uri"]; ok { 122 | data.uri = v 123 | } 124 | 125 | if v, ok := m["response"]; ok { 126 | data.response = v 127 | } 128 | 129 | if v, ok := m["qop"]; ok { 130 | data.qop = v 131 | } 132 | 133 | if v, ok := m["nc"]; ok { 134 | data.nc = v 135 | } 136 | 137 | if v, ok := m["cnonce"]; ok { 138 | data.cnonce = v 139 | } 140 | 141 | data.method = req.Method 142 | 143 | return &data 144 | } 145 | 146 | func getBasicAuthData(req *http.Request) *BasicAuthData { 147 | authHeader := strings.SplitN(req.Header.Get(ProxyAuthorizatonHeader), " ", 2) 148 | req.Header.Del(ProxyAuthorizatonHeader) 149 | 150 | if len(authHeader) != 2 || authHeader[0] != "Basic" { 151 | return nil 152 | } 153 | 154 | rawUserPassword, err := base64.StdEncoding.DecodeString(authHeader[1]) 155 | if err != nil { 156 | return nil 157 | } 158 | 159 | userPassword := strings.SplitN(string(rawUserPassword), ":", 2) 160 | if len(userPassword) != 2 { 161 | return nil 162 | } 163 | 164 | data := BasicAuthData{user: userPassword[0], password: userPassword[1]} 165 | 166 | return &data 167 | } 168 | 169 | func performBasicAuth(req *http.Request, authFunc BasicAuthFunc) (bool, *BasicAuthData) { 170 | data := getBasicAuthData(req) 171 | if data == nil { 172 | return false, data 173 | } 174 | 175 | resp := authFunc(data) 176 | 177 | return resp.status, data 178 | } 179 | 180 | func performDigestAuth(req *http.Request, authFunc DigestAuthFunc) (bool, *DigestAuthData) { 181 | data := getDigestAuthData(req) 182 | if data == nil { 183 | return false, data 184 | } 185 | 186 | authResponse := authFunc(data, validateUser) 187 | 188 | switch authResponse.status { 189 | case authOk: 190 | return true, data 191 | case authFailed: 192 | return false, data 193 | default: 194 | panic("unreachable point") 195 | } 196 | 197 | // return false, data 198 | } 199 | 200 | func basicAuthReqHandler(realm string, authFunc BasicAuthFunc) goproxy.ReqHandler { 201 | return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 202 | status, data := performBasicAuth(req, authFunc) 203 | if !status { 204 | if data != nil { 205 | ctx.Warnf("failed basic auth. attempt: user=%v, addr=%v", data.user, req.RemoteAddr) 206 | } 207 | return nil, basicUnauthorized(req, realm) 208 | } 209 | 210 | ctx.UserData = data.user 211 | 212 | return req, nil 213 | }) 214 | } 215 | 216 | func digestAuthReqHandler(realm string, authFunc DigestAuthFunc) goproxy.ReqHandler { 217 | return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 218 | status, data := performDigestAuth(req, authFunc) 219 | if !status { 220 | if data != nil { 221 | ctx.Warnf("failed digest auth. attempt: user=%v, realm=%v, addr=%v", data.user, data.realm, req.RemoteAddr) 222 | } 223 | return nil, digestUnauthorized(req, realm, authFunc) 224 | } 225 | 226 | ctx.UserData = data.user 227 | 228 | return req, nil 229 | }) 230 | } 231 | 232 | func basicConnectAuthHandler(realm string, authFunc BasicAuthFunc, logger *ProxyLogger) goproxy.HttpsHandler { 233 | return goproxy.FuncHttpsHandler(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { 234 | status, data := performBasicAuth(ctx.Req, authFunc) 235 | if !status { 236 | if data != nil { 237 | ctx.Warnf("failed basic auth. CONNECT method attempt: user=%v, addr=%v", data.user, ctx.Req.RemoteAddr) 238 | } 239 | ctx.Resp = basicUnauthorized(ctx.Req, realm) 240 | return goproxy.RejectConnect, host 241 | } 242 | 243 | ctx.UserData = data.user 244 | if ctx.Req == nil { 245 | ctx.Req = emptyReq 246 | } 247 | 248 | if logger != nil { 249 | logger.log(ctx) 250 | } 251 | 252 | return goproxy.OkConnect, host 253 | }) 254 | } 255 | 256 | func digestConnectAuthHandler(realm string, authFunc DigestAuthFunc, logger *ProxyLogger) goproxy.HttpsHandler { 257 | return goproxy.FuncHttpsHandler(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { 258 | status, data := performDigestAuth(ctx.Req, authFunc) 259 | if !status { 260 | if data != nil { 261 | ctx.Warnf("failed digest auth. CONNECT method attempt: user=%v, realm=%v, addr=%v", 262 | data.user, data.realm, ctx.Req.RemoteAddr) 263 | } 264 | ctx.Resp = digestUnauthorized(ctx.Req, realm, authFunc) 265 | return goproxy.RejectConnect, host 266 | } 267 | 268 | ctx.UserData = data.user 269 | if ctx.Req == nil { 270 | ctx.Req = emptyReq 271 | } 272 | 273 | if logger != nil { 274 | logger.log(ctx) 275 | } 276 | 277 | return goproxy.OkConnect, host 278 | }) 279 | } 280 | 281 | func setProxyBasicAuth(proxy *goproxy.ProxyHttpServer, realm string, authFunc BasicAuthFunc, logger *ProxyLogger) { 282 | proxy.OnRequest().Do(basicAuthReqHandler(realm, authFunc)) 283 | proxy.OnRequest().HandleConnect(basicConnectAuthHandler(realm, authFunc, logger)) 284 | } 285 | 286 | func setProxyDigestAuth(proxy *goproxy.ProxyHttpServer, realm string, authFunc DigestAuthFunc, logger *ProxyLogger) { 287 | proxy.OnRequest().Do(digestAuthReqHandler(realm, authFunc)) 288 | proxy.OnRequest().HandleConnect(digestConnectAuthHandler(realm, authFunc, logger)) 289 | } 290 | -------------------------------------------------------------------------------- /auth_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "crypto/tls" 7 | "encoding/base64" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "net/http" 12 | "net/http/httptest" 13 | "net/url" 14 | "os/exec" 15 | "regexp" 16 | "strconv" 17 | "strings" 18 | "testing" 19 | 20 | "github.com/elazarl/goproxy" 21 | ) 22 | 23 | // >>> import hashlib 24 | // >>> ha1 = hashlib.md5("user:my_realm:open sesame").hexdigest() 25 | // >>> ha1 26 | // 'e0d80a524f34d30b658136e2e89c1677' 27 | const ( 28 | user = "user" 29 | password = "open sesame" 30 | realm = "my_realm" 31 | ha1 = "e0d80a524f34d30b658136e2e89c1677" 32 | nc = "00000001" 33 | cnonce = "7e1d7e39d76092ea" 34 | uri = "/" 35 | method = "GET" 36 | qop = "auth" 37 | ) 38 | 39 | type ConstantHanlder string 40 | 41 | func (h ConstantHanlder) ServeHTTP(w http.ResponseWriter, r *http.Request) { 42 | _, err := io.WriteString(w, string(h)) 43 | if err != nil { 44 | fmt.Printf("Error: %v", err) 45 | } 46 | } 47 | 48 | func oneShotProxy() (client *http.Client, proxy *goproxy.ProxyHttpServer, s *httptest.Server) { 49 | proxy = goproxy.NewProxyHttpServer() 50 | s = httptest.NewServer(proxy) 51 | 52 | proxyURL, _ := url.Parse(s.URL) 53 | 54 | tr := &http.Transport{ 55 | Proxy: http.ProxyURL(proxyURL), 56 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 57 | } 58 | 59 | client = &http.Client{Transport: tr} 60 | 61 | return client, proxy, s 62 | } 63 | 64 | func times(n int, s string) string { 65 | r := make([]byte, 0, n*len(s)) 66 | 67 | for i := 0; i < n; i++ { 68 | r = append(r, s...) 69 | } 70 | 71 | return string(r) 72 | } 73 | 74 | func TestBasicConnectAuthWithCurl(t *testing.T) { 75 | expected := ":c>" 76 | 77 | background := httptest.NewTLSServer(ConstantHanlder(expected)) 78 | defer background.Close() 79 | 80 | _, proxy, proxyserver := oneShotProxy() 81 | defer proxyserver.Close() 82 | 83 | s := user + ":" + password + "\n" 84 | file := bytes.NewBuffer([]byte(s)) 85 | auth, err := newBasicAuth(file) 86 | if err != nil { 87 | t.Fatalf("couldn't create digest auth structure: %v", err) 88 | } 89 | setProxyBasicAuth(proxy, realm, makeBasicAuthValidator(auth), nil) 90 | 91 | authString := user + ":" + password 92 | cmd := exec.Command("curl", 93 | "--silent", 94 | "--show-error", 95 | "--insecure", 96 | "--proxy", proxyserver.URL, 97 | "--proxy-user", authString, 98 | "--proxytunnel", 99 | "--url", background.URL+"/[1-3]", 100 | ) 101 | 102 | out, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr 103 | if err != nil { 104 | t.Fatal(err, string(out)) 105 | } 106 | 107 | finalexpected := times(3, expected) 108 | if string(out) != finalexpected { 109 | t.Error("Expected", finalexpected, "got", string(out)) 110 | } 111 | } 112 | 113 | func TestBasicAuthWithCurl(t *testing.T) { 114 | expected := ":c>" 115 | 116 | background := httptest.NewServer(ConstantHanlder(expected)) 117 | defer background.Close() 118 | 119 | _, proxy, proxyserver := oneShotProxy() 120 | defer proxyserver.Close() 121 | 122 | s := user + ":" + password + "\n" 123 | file := bytes.NewBuffer([]byte(s)) 124 | auth, err := newBasicAuth(file) 125 | if err != nil { 126 | t.Fatalf("couldn't create digest auth structure: %v", err) 127 | } 128 | setProxyBasicAuth(proxy, realm, makeBasicAuthValidator(auth), nil) 129 | 130 | authString := user + ":" + password 131 | cmd := exec.Command("curl", 132 | "--silent", 133 | "--show-error", 134 | "--proxy", proxyserver.URL, 135 | "--proxy-user", authString, 136 | "--url", background.URL+"/[1-3]", 137 | ) 138 | 139 | out, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr 140 | if err != nil { 141 | t.Fatal(err, string(out)) 142 | } 143 | 144 | finalexpected := times(3, expected) 145 | if string(out) != finalexpected { 146 | t.Error("Expected", finalexpected, "got", string(out)) 147 | } 148 | } 149 | 150 | func TestBasicAuth(t *testing.T) { 151 | expected := "hello" 152 | 153 | background := httptest.NewServer(ConstantHanlder(expected)) 154 | defer background.Close() 155 | 156 | client, proxy, proxyserver := oneShotProxy() 157 | defer proxyserver.Close() 158 | 159 | s := user + ":" + password + "\n" 160 | file := bytes.NewBuffer([]byte(s)) 161 | auth, err := newBasicAuth(file) 162 | if err != nil { 163 | t.Fatalf("couldn't create digest auth structure: %v", err) 164 | } 165 | setProxyBasicAuth(proxy, realm, makeBasicAuthValidator(auth), nil) 166 | 167 | // without auth 168 | resp, err := client.Get(background.URL) 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | expectedProxyAuthenticate := fmt.Sprintf("Basic realm=\"%s\"", realm) 173 | if resp.Header.Get("Proxy-Authenticate") != expectedProxyAuthenticate { 174 | t.Error("Expected Proxy-Authenticate header got", resp.Header.Get("Proxy-Authenticate")) 175 | } 176 | if resp.StatusCode != 407 { 177 | t.Error("Expected status 407 Proxy Authentication Required, got", resp.Status) 178 | } 179 | 180 | // with auth 181 | req, err := http.NewRequest("GET", background.URL, nil) 182 | if err != nil { 183 | t.Fatal(err) 184 | } 185 | authString := user + ":" + password 186 | header := "Basic " + base64.StdEncoding.EncodeToString([]byte(authString)) 187 | req.Header.Set("Proxy-Authorization", header) 188 | resp, err = client.Do(req) 189 | if err != nil { 190 | t.Fatal(err) 191 | } 192 | if resp.StatusCode != 200 { 193 | t.Error("Expected status 200 OK, got", resp.Status) 194 | } 195 | msg, err := ioutil.ReadAll(resp.Body) 196 | if err != nil { 197 | t.Fatal(err) 198 | } 199 | if string(msg) != "hello" { 200 | t.Errorf("Expected '%s', actual '%s'", expected, string(msg)) 201 | } 202 | } 203 | 204 | func TestDigestAuth(t *testing.T) { 205 | expected := "Hello, World!" 206 | 207 | background := httptest.NewServer(ConstantHanlder(expected)) 208 | defer background.Close() 209 | 210 | client, proxy, proxyserver := oneShotProxy() 211 | defer proxyserver.Close() 212 | 213 | s := user + ":" + realm + ":" + ha1 + "\n" 214 | file := bytes.NewBuffer([]byte(s)) 215 | auth, err := newDigestAuth(file) 216 | if err != nil { 217 | t.Fatalf("couldn't create digest auth structure: %v", err) 218 | } 219 | setProxyDigestAuth(proxy, realm, makeDigestAuthValidator(auth), nil) 220 | 221 | // without auth 222 | resp, err := client.Get(background.URL) 223 | if err != nil { 224 | t.Fatal(err) 225 | } 226 | 227 | header := resp.Header.Get("Proxy-Authenticate") 228 | if len(header) == 0 { 229 | t.Error("Couldn't get expected Proxy-Authenticate header") 230 | } 231 | 232 | splitted := strings.SplitN(header, " ", 2) 233 | if splitted[0] != "Digest" { 234 | t.Error("Expected Digest Proxy-Authenticate header got", header) 235 | } 236 | if resp.StatusCode != 407 { 237 | t.Error("Expected status 407 Proxy Authentication Required, got", resp.Status) 238 | } 239 | 240 | nonceRegexp := regexp.MustCompile("nonce=\"(.*?)\"") 241 | nonce := nonceRegexp.FindAllStringSubmatch(splitted[1], -1)[0][1] 242 | 243 | s = method + ":" + uri 244 | ha2 := fmt.Sprintf("%x", md5.Sum([]byte(s))) 245 | s = ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2 246 | response := fmt.Sprintf("%x", md5.Sum([]byte(s))) 247 | 248 | proxyAuthorizationHeader := fmt.Sprintf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", qop=%s, nc=%s, cnonce=\"%s\"", 249 | user, realm, nonce, uri, response, qop, nc, cnonce) 250 | 251 | // with auth 252 | req, err := http.NewRequest("GET", background.URL, nil) 253 | if err != nil { 254 | t.Fatal(err) 255 | } 256 | req.Header.Set("Proxy-Authorization", proxyAuthorizationHeader) 257 | 258 | resp, err = client.Do(req) 259 | if err != nil { 260 | t.Fatal(err) 261 | } 262 | if resp.StatusCode != 200 { 263 | t.Error("Expected status 200 OK, got", resp.Status) 264 | } 265 | 266 | msg, err := ioutil.ReadAll(resp.Body) 267 | if err != nil { 268 | t.Fatal(err) 269 | } 270 | if string(msg) != expected { 271 | t.Errorf("Expected '%s', actual '%s'", expected, string(msg)) 272 | } 273 | } 274 | 275 | func TestDigestAuthWithPython(t *testing.T) { 276 | expected := "Hello, World!" 277 | 278 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 279 | w.Header().Add("Content-Type", "text/plain") 280 | fmt.Fprint(w, expected) 281 | })) 282 | defer ts.Close() 283 | 284 | _, proxy, proxyserver := oneShotProxy() 285 | defer proxyserver.Close() 286 | 287 | s := user + ":" + realm + ":" + ha1 + "\n" 288 | file := bytes.NewBuffer([]byte(s)) 289 | auth, err := newDigestAuth(file) 290 | if err != nil { 291 | t.Fatalf("couldn't create digest auth structure: %v", err) 292 | } 293 | setProxyDigestAuth(proxy, realm, makeDigestAuthValidator(auth), nil) 294 | 295 | cmd := exec.Command("python3", 296 | "proxy-digest-auth-test.py", 297 | "--proxy", proxyserver.URL, 298 | "--user", user, 299 | "--password", password, 300 | "--url", ts.URL, 301 | ) 302 | 303 | out, err := cmd.CombinedOutput() 304 | if err != nil { 305 | t.Fatal(err, string(out)) 306 | } 307 | 308 | // python adds '\n' so we need to remove it 309 | outString := string(out) 310 | result := strings.Trim(outString, "\r\n") 311 | 312 | // output comes in the form b'...', so remove prefix "b'" and suffix "'" 313 | if len(result) <= 3 { 314 | t.Fatal("response is too short") 315 | } 316 | result = result[2 : len(result)-2] 317 | if result != expected { 318 | t.Error("Expected", expected, "got", result) 319 | } 320 | } 321 | 322 | func TestDigestAuthWithCurl(t *testing.T) { 323 | expected := "Hello, World!" 324 | 325 | background := httptest.NewServer(ConstantHanlder(expected)) 326 | defer background.Close() 327 | 328 | _, proxy, proxyserver := oneShotProxy() 329 | defer proxyserver.Close() 330 | 331 | s := user + ":" + realm + ":" + ha1 + "\n" 332 | file := bytes.NewBuffer([]byte(s)) 333 | auth, err := newDigestAuth(file) 334 | if err != nil { 335 | t.Fatalf("couldn't create digest auth structure: %v", err) 336 | } 337 | setProxyDigestAuth(proxy, realm, makeDigestAuthValidator(auth), nil) 338 | 339 | authString := user + ":" + password 340 | cmd := exec.Command("curl", 341 | "--silent", 342 | "--show-error", 343 | "--proxy-digest", 344 | "--proxy", proxyserver.URL, 345 | "--proxy-user", authString, 346 | "--url", background.URL, 347 | ) 348 | 349 | out, err := cmd.CombinedOutput() // if curl got error, it'll show up in stderr 350 | if err != nil { 351 | t.Fatal(err, string(out)) 352 | } 353 | 354 | result := string(out) 355 | 356 | if result != expected { 357 | t.Error("Expected", expected, "got", result) 358 | } 359 | } 360 | 361 | func TestIPBasedAccessDenied(t *testing.T) { 362 | expected := "Hello, World!" 363 | 364 | background := httptest.NewServer(ConstantHanlder(expected)) 365 | defer background.Close() 366 | 367 | client, proxy, proxyserver := oneShotProxy() 368 | defer proxyserver.Close() 369 | 370 | s := "allowed_networks = [\"172.16.11.0/24\"]\n" 371 | conf := newConfiguration(bytes.NewBuffer([]byte(s))) 372 | setAllowedNetworksHandler(conf, proxy) 373 | 374 | resp, err := client.Get(background.URL) 375 | if err != nil { 376 | t.Fatal(err) 377 | } 378 | 379 | if resp.StatusCode != 403 { 380 | t.Error("Expected 403 status code, got", resp.Status) 381 | } 382 | } 383 | 384 | func TestIPBasedAccessAllowed(t *testing.T) { 385 | expected := "Hello, World!" 386 | 387 | background := httptest.NewServer(ConstantHanlder(expected)) 388 | defer background.Close() 389 | 390 | client, proxy, proxyserver := oneShotProxy() 391 | defer proxyserver.Close() 392 | 393 | s := "allowed_networks=[\"127.0.0.1/32\"]\n" 394 | conf := newConfiguration(bytes.NewBuffer([]byte(s))) 395 | setAllowedNetworksHandler(conf, proxy) 396 | 397 | resp, err := client.Get(background.URL) 398 | if err != nil { 399 | t.Fatal(err) 400 | } 401 | 402 | if resp.StatusCode != 200 { 403 | t.Error("Expected 200 status code, got", resp.Status) 404 | } 405 | 406 | msg, err := ioutil.ReadAll(resp.Body) 407 | if err != nil { 408 | t.Fatal(err) 409 | } 410 | 411 | actual := string(msg) 412 | if actual != expected { 413 | t.Errorf("Expected '%s', actual '%s'", expected, actual) 414 | } 415 | } 416 | 417 | func TestHTTPSConnectDenied(t *testing.T) { 418 | expected := "Hello, World!" 419 | 420 | background := httptest.NewTLSServer(ConstantHanlder(expected)) 421 | defer background.Close() 422 | 423 | client, proxy, proxyserver := oneShotProxy() 424 | defer proxyserver.Close() 425 | 426 | // test server'll bind to the port different from 443 427 | s := fmt.Sprintf("allowed_connect_ports=[%d]\n", 443) 428 | conf := newConfiguration(bytes.NewBuffer([]byte(s))) 429 | setAllowedConnectPortsHandler(conf, proxy) 430 | 431 | _, err := client.Get(background.URL) 432 | if err == nil { 433 | t.Fatal(err) 434 | } 435 | } 436 | 437 | func TestHTTPSConnectAllowed(t *testing.T) { 438 | expected := "Hello, World!" 439 | 440 | background := httptest.NewTLSServer(ConstantHanlder(expected)) 441 | defer background.Close() 442 | 443 | parsedURL, err := url.Parse(background.URL) 444 | if err != nil { 445 | t.Fatal(err) 446 | } 447 | 448 | port, err := strconv.ParseUint(strings.Split(parsedURL.Host, ":")[1], 10, 16) 449 | if err != nil { 450 | t.Fatal(err) 451 | } 452 | 453 | client, proxy, proxyserver := oneShotProxy() 454 | defer proxyserver.Close() 455 | 456 | s := fmt.Sprintf("allowed_connect_ports=[%d]\n", port) 457 | conf := newConfiguration(bytes.NewBuffer([]byte(s))) 458 | setAllowedConnectPortsHandler(conf, proxy) 459 | 460 | resp, err := client.Get(background.URL) 461 | if err != nil { 462 | t.Fatal(err) 463 | } 464 | 465 | if resp.StatusCode != 200 { 466 | t.Error("Expected 200 status code, got", resp.Status) 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /basic_auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/csv" 5 | "errors" 6 | "io" 7 | "os" 8 | ) 9 | 10 | type BasicAuthData struct { 11 | user string 12 | password string 13 | } 14 | 15 | type basicAuth struct { 16 | users map[string]string 17 | } 18 | 19 | func newBasicAuthFromFile(path string) (*basicAuth, error) { 20 | r, err := os.Open(path) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | return newBasicAuth(r) 26 | } 27 | 28 | func newBasicAuth(file io.Reader) (*basicAuth, error) { 29 | csvReader := csv.NewReader(file) 30 | csvReader.Comma = ':' 31 | csvReader.Comment = '#' 32 | csvReader.TrimLeadingSpace = true 33 | 34 | records, err := csvReader.ReadAll() 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | h := &basicAuth{users: make(map[string]string)} 40 | 41 | for _, record := range records { 42 | if len(record) != 2 { 43 | return nil, errors.New("invalid basic auth file format") 44 | } 45 | h.users[record[0]] = record[1] 46 | } 47 | 48 | if len(h.users) == 0 { 49 | return nil, errors.New("auth file contains no data") 50 | } 51 | 52 | return h, nil 53 | } 54 | 55 | func (h *basicAuth) validate(authData *BasicAuthData) bool { 56 | realPassword, exists := h.users[authData.user] 57 | if !exists || realPassword != authData.password { 58 | return false 59 | } 60 | 61 | return true 62 | } 63 | -------------------------------------------------------------------------------- /basic_auth_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestBasicAuthFile(t *testing.T) { 9 | file := bytes.NewBuffer([]byte("testuser:asdf\n")) 10 | auth, err := newBasicAuth(file) 11 | if err != nil { 12 | t.Errorf("couldn't create basic auth structure") 13 | } 14 | 15 | if valid := auth.validate(&BasicAuthData{ 16 | user: "testuser", 17 | password: "asdf", 18 | }); !valid { 19 | t.Errorf("password validation failed") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "os" 8 | 9 | "github.com/BurntSushi/toml" 10 | ) 11 | 12 | type Configuration struct { 13 | Listen string `toml:"listen"` 14 | AccessLog string `toml:"access_log"` 15 | ActivityLog string `toml:"activity_log"` 16 | AllowedConnectPorts []int `toml:"allowed_connect_ports"` 17 | AllowedNetworks []string `toml:"allowed_networks"` 18 | DisallowedNetworks []string `toml:"disallowed_networks"` 19 | AuthRealm string `toml:"auth_realm"` 20 | AuthType string `toml:"auth_type"` 21 | AuthFile string `toml:"auth_file"` 22 | ForwardedForHeader string `toml:"forwarded_for_header"` 23 | BindIP string `toml:"bind_ip"` 24 | ViaHeader string `toml:"via_header"` 25 | ViaProxyName string `toml:"via_proxy_name"` 26 | AddHeaders [][]string `toml:"add_headers"` 27 | ForwardProxyURL string `toml:"forward_proxy_url"` 28 | } 29 | 30 | const ( 31 | defaultListenAddress = "127.0.0.1:3128" 32 | defaultAllowedNetwork = "127.0.0.1/32" 33 | defaultAllowedConnectPort = 443 34 | ) 35 | 36 | func validateNetworks(networks []string) { 37 | if len(networks) > 0 { 38 | for i, network := range networks { 39 | _, _, err := net.ParseCIDR(network) 40 | if err != nil { 41 | if ip := net.ParseIP(network); ip != nil { 42 | if ip4 := ip.To4(); ip4 != nil { 43 | networks[i] = network + "/32" 44 | continue 45 | } 46 | if ip16 := ip.To16(); ip16 != nil { 47 | networks[i] = network + "/128" 48 | continue 49 | } 50 | } 51 | log.Fatalf("couldn't parse network %s: %v", network, err) 52 | } 53 | } 54 | } 55 | } 56 | 57 | func validateIP(addr string) { 58 | if addr != "" { 59 | ip := net.ParseIP(addr) 60 | if ip == nil { 61 | log.Fatalf("incorrect IP address %s", addr) 62 | } 63 | } 64 | } 65 | 66 | func validateAuthType(authType string) { 67 | validValues := map[string]bool{ 68 | "": true, 69 | "basic": true, 70 | "digest": true, 71 | } 72 | 73 | _, ok := validValues[authType] 74 | if !ok { 75 | log.Fatalf("Incorrect authentication type '%s'", authType) 76 | } 77 | } 78 | 79 | func validateForwardedForHeaderAction(action string) { 80 | validValues := map[string]bool{ 81 | "on": true, 82 | "off": true, 83 | "delete": true, 84 | "truncate": true, 85 | } 86 | 87 | _, ok := validValues[action] 88 | if !ok { 89 | log.Fatalf("Incorrect 'Forwarded-For' header action '%s'", action) 90 | } 91 | } 92 | 93 | func validateViaHeaderAction(action string) { 94 | validValues := map[string]bool{ 95 | "on": true, 96 | "off": true, 97 | "delete": true, 98 | } 99 | 100 | _, ok := validValues[action] 101 | if !ok { 102 | log.Fatalf("Incorrect 'Via' header action '%s'", action) 103 | } 104 | } 105 | 106 | func newConfigurationFromFile(path string) *Configuration { 107 | file, err := os.Open(path) 108 | if err != nil { 109 | log.Fatal("can't open configuration file: ", err) 110 | } 111 | 112 | conf := newConfiguration(file) 113 | 114 | return conf 115 | } 116 | 117 | func newConfiguration(data io.Reader) *Configuration { 118 | var conf Configuration 119 | if _, err := toml.DecodeReader(data, &conf); err != nil { 120 | log.Fatalf("Couldn't parse configuration file: %v", err) 121 | } 122 | 123 | if conf.Listen == "" { 124 | conf.Listen = defaultListenAddress 125 | } 126 | 127 | // if no auth. enabled allow only from 127.0.0.1/32 if not deliberately specified otherwise 128 | if conf.AllowedNetworks == nil || len(conf.AllowedNetworks) == 0 { 129 | if conf.AuthFile == "" || conf.AuthType == "" { 130 | conf.AllowedNetworks = make([]string, 1) 131 | conf.AllowedNetworks[0] = defaultAllowedNetwork 132 | } 133 | } 134 | 135 | validateNetworks(conf.AllowedNetworks) 136 | validateNetworks(conf.DisallowedNetworks) 137 | validateIP(conf.BindIP) 138 | 139 | // by default allow connect only to the https protocol port 140 | if conf.AllowedConnectPorts == nil || len(conf.AllowedConnectPorts) == 0 { 141 | conf.AllowedConnectPorts = make([]int, 1) 142 | conf.AllowedConnectPorts[0] = defaultAllowedConnectPort 143 | } 144 | 145 | if conf.AuthType == "" && conf.AuthFile != "" { 146 | log.Fatal("missed mandatoty configuration parameter 'auth_type'") 147 | } 148 | 149 | // by default set X-Forwarded-For header 150 | if conf.ForwardedForHeader == "" { 151 | conf.ForwardedForHeader = "on" 152 | } 153 | 154 | if conf.ViaHeader == "" { 155 | conf.ViaHeader = "on" 156 | } 157 | 158 | if conf.ViaProxyName == "" && conf.ViaHeader == "on" { 159 | hostname, err := os.Hostname() 160 | if err != nil { 161 | log.Fatalf("os.Hostname() failed: %v\n", err) 162 | } 163 | conf.ViaProxyName = hostname 164 | } 165 | 166 | validateAuthType(conf.AuthType) 167 | validateForwardedForHeaderAction(conf.ForwardedForHeader) 168 | validateViaHeaderAction(conf.ViaHeader) 169 | 170 | return &conf 171 | } 172 | -------------------------------------------------------------------------------- /config_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func compareSlices(s1, s2 []int) bool { 6 | if len(s1) == len(s2) { 7 | for i, v := range s1 { 8 | if v != s2[i] { 9 | return false 10 | } 11 | } 12 | } else { 13 | return false 14 | } 15 | 16 | return true 17 | } 18 | 19 | func TestConfigFile(t *testing.T) { 20 | expected := Configuration{ 21 | Listen: "127.0.0.1:3129", 22 | AccessLog: "/tmp/microproxy.access.log", 23 | AuthType: "basic", 24 | AuthRealm: "proxy", 25 | AuthFile: "auth.txt", 26 | ForwardedForHeader: "on", 27 | AllowedConnectPorts: make([]int, 2), 28 | } 29 | 30 | expected.AllowedConnectPorts[0] = 443 31 | expected.AllowedConnectPorts[1] = 80 32 | 33 | conf := newConfigurationFromFile("microproxy.toml") 34 | 35 | if conf.Listen != expected.Listen { 36 | t.Errorf("Got %v, expected %v", conf.Listen, expected.Listen) 37 | } 38 | 39 | if conf.AccessLog != expected.AccessLog { 40 | t.Errorf("Got %v, expected %v", conf.AccessLog, expected.AccessLog) 41 | } 42 | 43 | if !compareSlices(conf.AllowedConnectPorts, expected.AllowedConnectPorts) { 44 | t.Errorf("Got %v, expected %v", conf.AllowedConnectPorts, expected.AllowedConnectPorts) 45 | } 46 | 47 | if conf.AuthFile != expected.AuthFile { 48 | t.Errorf("Got %v, expected %v", conf.AuthFile, expected.AuthFile) 49 | } 50 | 51 | if conf.AuthRealm != expected.AuthRealm { 52 | t.Errorf("Got %v, expected %v", conf.AuthRealm, expected.AuthRealm) 53 | } 54 | 55 | if conf.AuthType != expected.AuthType { 56 | t.Errorf("Got %v, expected %v", conf.AuthType, expected.AuthType) 57 | } 58 | 59 | if conf.ForwardedForHeader != expected.ForwardedForHeader { 60 | t.Errorf("Got %v, expected %v", conf.ForwardedForHeader, expected.ForwardedForHeader) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | const ( 9 | DefaultReadTimeout = 15 * time.Minute 10 | DefaultWriteTimeout = 15 * time.Minute 11 | ) 12 | 13 | type TimedConn struct { 14 | net.Conn 15 | readTimeout time.Duration 16 | writeTimeout time.Duration 17 | } 18 | 19 | func (c TimedConn) Read(b []byte) (int, error) { 20 | err := c.Conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 21 | if err != nil { 22 | return 0, err 23 | } 24 | 25 | return c.Conn.Read(b) 26 | } 27 | 28 | func (c TimedConn) Write(b []byte) (int, error) { 29 | err := c.Conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 30 | if err != nil { 31 | return 0, err 32 | } 33 | 34 | return c.Conn.Write(b) 35 | } 36 | -------------------------------------------------------------------------------- /digest_auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/csv" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "math/rand" 10 | "os" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | const ( 16 | chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 17 | maxNonceInactiveInterval = 12 * time.Hour 18 | ) 19 | 20 | type NonceInfo struct { 21 | issued time.Time 22 | lastUsed time.Time 23 | lastNonceCounter uint64 24 | } 25 | 26 | type DigestAuth struct { 27 | users map[string]string 28 | // issued nonce values 29 | nonces map[string](*NonceInfo) 30 | } 31 | 32 | type DigestAuthData struct { 33 | user string 34 | realm string 35 | nonce string 36 | method string 37 | uri string 38 | response string 39 | qop string 40 | nc string 41 | cnonce string 42 | } 43 | 44 | func makeRandomString(l int) string { 45 | b := make([]byte, l) 46 | for i := 0; i < l; i++ { 47 | b[i] = chars[rand.Intn(len(chars))] 48 | } 49 | 50 | return string(b) 51 | } 52 | 53 | func newDigestAuthFromFile(path string) (*DigestAuth, error) { 54 | r, err := os.Open(path) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return newDigestAuth(r) 60 | } 61 | 62 | func newDigestAuth(file io.Reader) (*DigestAuth, error) { 63 | csvReader := csv.NewReader(file) 64 | csvReader.Comma = ':' 65 | csvReader.Comment = '#' 66 | csvReader.TrimLeadingSpace = true 67 | 68 | records, err := csvReader.ReadAll() 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | h := &DigestAuth{users: make(map[string]string), nonces: make(map[string](*NonceInfo))} 74 | 75 | for _, record := range records { 76 | // each record has to be in form: "user:realm:md5hash" 77 | if len(record) != 3 { 78 | return nil, errors.New("invalid htdigest file format") 79 | } 80 | key := record[0] + ":" + record[1] 81 | value := record[2] 82 | h.users[key] = value 83 | } 84 | 85 | rand.Seed(time.Now().UnixNano()) 86 | 87 | return h, nil 88 | } 89 | 90 | func (h *DigestAuth) validate(data *DigestAuthData) bool { 91 | lookupKey := data.user + ":" + data.realm 92 | ha1, exists := h.users[lookupKey] 93 | if !exists { 94 | return false 95 | } 96 | 97 | nonceInfo, nonceExists := h.nonces[data.nonce] 98 | if !nonceExists { 99 | return false 100 | } 101 | 102 | nc, err := strconv.ParseUint(data.nc, 16, 64) 103 | if err != nil { 104 | return false 105 | } 106 | 107 | // reply attack ? 108 | if nc == nonceInfo.lastNonceCounter { 109 | return false 110 | } 111 | 112 | s := data.method + ":" + data.uri 113 | ha2 := fmt.Sprintf("%x", md5.Sum([]byte(s))) 114 | s = ha1 + ":" + data.nonce + ":" + data.nc + ":" + data.cnonce + ":" + data.qop + ":" + ha2 115 | realResponse := fmt.Sprintf("%x", md5.Sum([]byte(s))) 116 | 117 | if data.response == realResponse { 118 | nonceInfo.lastUsed = time.Now() 119 | nonceInfo.lastNonceCounter = nc 120 | return true 121 | } 122 | 123 | return false 124 | } 125 | 126 | func (h *DigestAuth) newNonce() string { 127 | var nonce string 128 | 129 | for { 130 | rs := makeRandomString(100) 131 | nonce = fmt.Sprintf("%x", md5.Sum([]byte(rs))) 132 | _, exists := h.nonces[nonce] 133 | if !exists { 134 | h.addNonce(nonce) 135 | break 136 | } 137 | } 138 | 139 | return nonce 140 | } 141 | 142 | func (h *DigestAuth) addNonce(nonce string) { 143 | h.nonces[nonce] = &NonceInfo{ 144 | issued: time.Now(), 145 | lastUsed: time.Now(), 146 | lastNonceCounter: 0, 147 | } 148 | } 149 | 150 | func (h *DigestAuth) expireNonces() { 151 | currentTime := time.Now() 152 | limit := currentTime.Add(-maxNonceInactiveInterval) 153 | for key, value := range h.nonces { 154 | if value.lastUsed.Before(limit) { 155 | delete(h.nonces, key) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/thekvs/microproxy 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/BurntSushi/toml v0.3.1 7 | github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d h1:rtM8HsT3NG37YPjz8sYSbUSdElP9lUsQENYzJDZDUBE= 4 | github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 5 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= 6 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 7 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 8 | -------------------------------------------------------------------------------- /headers_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | ) 11 | 12 | type myHandler struct { 13 | headers map[string]string 14 | } 15 | 16 | func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 17 | found := false 18 | 19 | for h, v := range h.headers { 20 | found = (r.Header.Get(h) == v) 21 | if !found { 22 | break 23 | } 24 | } 25 | 26 | if found { 27 | fmt.Fprint(w, "OK") 28 | } else { 29 | fmt.Fprintf(w, "FAIL. Headers: %v", r.Header) 30 | } 31 | } 32 | 33 | func TestCustomHeaders(t *testing.T) { 34 | expectedResponse := "OK" 35 | expectedHeaders := map[string]string{ 36 | "X-Custom-Header-1": "Value-1", 37 | "X-Custom-Header-2": "Value-2", 38 | } 39 | 40 | handler := &myHandler{headers: expectedHeaders} 41 | 42 | background := httptest.NewServer(handler) 43 | defer background.Close() 44 | 45 | client, proxy, proxyserver := oneShotProxy() 46 | defer proxyserver.Close() 47 | 48 | s := `add_headers=[["X-Custom-Header-1", "Value-1"], ["X-Custom-Header-2", "Value-2"]]` 49 | conf := newConfiguration(bytes.NewBuffer([]byte(s))) 50 | setAddCustomHeadersHandler(conf, proxy) 51 | 52 | resp, err := client.Get(background.URL) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | 57 | if resp.StatusCode != 200 { 58 | t.Error("Expected 200 status code, got", resp.Status) 59 | } 60 | 61 | msg, err := ioutil.ReadAll(resp.Body) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | 66 | actualResponse := string(msg) 67 | if actualResponse != expectedResponse { 68 | t.Errorf("Expected '%s', actual '%s'", expectedResponse, actualResponse) 69 | } 70 | } 71 | 72 | func TestViaHeaders(t *testing.T) { 73 | expectedResponse := "OK" 74 | expectedHeaders := map[string]string{ 75 | "Via": "1.1 octopus", 76 | } 77 | 78 | handler := &myHandler{headers: expectedHeaders} 79 | 80 | background := httptest.NewServer(handler) 81 | defer background.Close() 82 | 83 | client, proxy, proxyserver := oneShotProxy() 84 | defer proxyserver.Close() 85 | 86 | s := "via_header=\"on\"\nvia_proxy_name=\"octopus\"\n" 87 | conf := newConfiguration(bytes.NewBuffer([]byte(s))) 88 | setViaHeaderHandler(conf, proxy) 89 | 90 | resp, err := client.Get(background.URL) 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | 95 | if resp.StatusCode != 200 { 96 | t.Error("Expected 200 status code, got", resp.Status) 97 | } 98 | 99 | msg, err := ioutil.ReadAll(resp.Body) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | actualResponse := string(msg) 105 | if actualResponse != expectedResponse { 106 | t.Errorf("Expected '%s', actual '%s'", expectedResponse, actualResponse) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "os" 9 | "time" 10 | 11 | "github.com/elazarl/goproxy" 12 | ) 13 | 14 | const ( 15 | AppendLog int = iota 16 | ReopenLog int = iota 17 | ) 18 | 19 | var ( 20 | emptyResp = &http.Response{} 21 | emptyReq = &http.Request{} 22 | ) 23 | 24 | type LogData struct { 25 | action int 26 | req *http.Request 27 | resp *http.Response 28 | user string 29 | err error 30 | time time.Time 31 | } 32 | 33 | type ProxyLogger struct { 34 | path string 35 | logChannel chan *LogData 36 | errorChannel chan error 37 | } 38 | 39 | func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...interface{}) { 40 | if *err != nil { 41 | return 42 | } 43 | var n int 44 | n, *err = fmt.Fprintf(w, pat, a...) 45 | *nr += int64(n) 46 | } 47 | 48 | func getAuthenticatedUserName(ctx *goproxy.ProxyCtx) string { 49 | user, ok := ctx.UserData.(string) 50 | if !ok { 51 | user = "-" 52 | } 53 | 54 | return user 55 | } 56 | 57 | func (m *LogData) writeTo(w io.Writer) (nr int64, err error) { 58 | if m.resp != nil { 59 | if m.resp.Request != nil { 60 | fprintf(&nr, &err, w, 61 | "%v %v %v %v %v %v %v\n", 62 | m.time.Format(time.RFC3339), 63 | m.resp.Request.RemoteAddr, 64 | m.resp.Request.Method, 65 | m.resp.Request.URL, 66 | m.resp.StatusCode, 67 | m.resp.ContentLength, 68 | m.user) 69 | } else { 70 | fprintf(&nr, &err, w, 71 | "%v %v %v %v %v %v %v\n", 72 | m.time.Format(time.RFC3339), 73 | "-", 74 | "-", 75 | "-", 76 | m.resp.StatusCode, 77 | m.resp.ContentLength, 78 | m.user) 79 | } 80 | } else if m.req != nil { 81 | fprintf(&nr, &err, w, 82 | "%v %v %v %v %v %v %v\n", 83 | m.time.Format(time.RFC3339), 84 | m.req.RemoteAddr, 85 | m.req.Method, 86 | m.req.URL, 87 | "-", 88 | "-", 89 | m.user) 90 | } 91 | 92 | return 93 | } 94 | 95 | func newProxyLogger(conf *Configuration) *ProxyLogger { 96 | var fh *os.File 97 | 98 | if conf.AccessLog != "" { 99 | var err error 100 | fh, err = os.OpenFile(conf.AccessLog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600) 101 | if err != nil { 102 | log.Fatalf("Couldn't open log file: %v", err) 103 | } 104 | } 105 | 106 | logger := &ProxyLogger{ 107 | path: conf.AccessLog, 108 | logChannel: make(chan *LogData), 109 | errorChannel: make(chan error), 110 | } 111 | 112 | go func() { 113 | for m := range logger.logChannel { 114 | if fh != nil { 115 | switch m.action { 116 | case AppendLog: 117 | if _, err := m.writeTo(fh); err != nil { 118 | log.Println("Can't write meta", err) 119 | } 120 | case ReopenLog: 121 | err := fh.Close() 122 | if err != nil { 123 | log.Fatal(err) 124 | } 125 | fh, err = os.OpenFile(conf.AccessLog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600) 126 | if err != nil { 127 | log.Fatalf("Couldn't reopen log file: %v", err) 128 | } 129 | } 130 | } 131 | } 132 | logger.errorChannel <- fh.Close() 133 | }() 134 | 135 | return logger 136 | } 137 | 138 | func (logger *ProxyLogger) logResponse(resp *http.Response, ctx *goproxy.ProxyCtx) { 139 | if resp == nil { 140 | resp = emptyResp 141 | } 142 | 143 | logger.writeLogEntry(&LogData{ 144 | action: AppendLog, 145 | resp: resp, 146 | user: getAuthenticatedUserName(ctx), 147 | err: ctx.Error, 148 | time: time.Now(), 149 | }) 150 | } 151 | 152 | func (logger *ProxyLogger) writeLogEntry(data *LogData) { 153 | logger.logChannel <- data 154 | } 155 | 156 | func (logger *ProxyLogger) log(ctx *goproxy.ProxyCtx) { 157 | data := &LogData{ 158 | action: AppendLog, 159 | req: ctx.Req, 160 | resp: ctx.Resp, 161 | user: getAuthenticatedUserName(ctx), 162 | err: ctx.Error, 163 | time: time.Now(), 164 | } 165 | logger.writeLogEntry(data) 166 | } 167 | 168 | func (logger *ProxyLogger) close() error { 169 | close(logger.logChannel) 170 | return <-logger.errorChannel 171 | } 172 | 173 | func (logger *ProxyLogger) reopen() { 174 | logger.writeLogEntry(&LogData{action: ReopenLog}) 175 | } 176 | -------------------------------------------------------------------------------- /microproxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "os/signal" 14 | "regexp" 15 | "strconv" 16 | "strings" 17 | "syscall" 18 | "time" 19 | 20 | "github.com/elazarl/goproxy" 21 | ) 22 | 23 | // Digest auth. operation type 24 | const ( 25 | validateUser int = iota 26 | getNonce int = iota 27 | maintPing int = iota 28 | ) 29 | 30 | // Digest auth. resp status 31 | const ( 32 | authOk int = iota 33 | authFailed int = iota 34 | nonceOk int = iota 35 | maintOk int = iota 36 | ) 37 | 38 | const ( 39 | proxyForwardedForHeader = "X-Forwarded-For" 40 | proxyViaHeader = "Via" 41 | ) 42 | 43 | const tcpKeepAliveInterval = 1 * time.Minute 44 | 45 | type basicAuthRequest struct { 46 | data *BasicAuthData 47 | respChannel chan *BasicAuthResponse 48 | } 49 | 50 | type BasicAuthResponse struct { 51 | status bool 52 | } 53 | 54 | type digestAuthRequest struct { 55 | data *DigestAuthData 56 | op int 57 | respChannel chan *DigestAuthResponse 58 | } 59 | 60 | type DigestAuthResponse struct { 61 | data string 62 | status int 63 | } 64 | 65 | func makeBasicAuthValidator(auth *basicAuth) BasicAuthFunc { 66 | channel := make(chan *basicAuthRequest) 67 | validator := func() { 68 | for e := range channel { 69 | status := auth.validate(e.data) 70 | e.respChannel <- &BasicAuthResponse{status: status} 71 | } 72 | } 73 | 74 | go validator() 75 | 76 | return func(authData *BasicAuthData) *BasicAuthResponse { 77 | request := &basicAuthRequest{ 78 | data: authData, 79 | respChannel: make(chan *BasicAuthResponse), 80 | } 81 | channel <- request 82 | return <-request.respChannel 83 | } 84 | } 85 | 86 | func makeDigestAuthValidator(auth *DigestAuth) DigestAuthFunc { 87 | channel := make(chan *digestAuthRequest) 88 | 89 | processor := func() { 90 | for e := range channel { 91 | var response *DigestAuthResponse 92 | switch e.op { 93 | case validateUser: 94 | status := auth.validate(e.data) 95 | if status { 96 | response = &DigestAuthResponse{status: authOk} 97 | } else { 98 | response = &DigestAuthResponse{status: authFailed} 99 | } 100 | case getNonce: 101 | nonce := auth.newNonce() 102 | response = &DigestAuthResponse{status: nonceOk, data: nonce} 103 | case maintPing: 104 | auth.expireNonces() 105 | response = &DigestAuthResponse{status: maintOk} 106 | default: 107 | panic("unexpected operation type") 108 | } 109 | e.respChannel <- response 110 | } 111 | } 112 | 113 | maintPinger := func() { 114 | for { 115 | request := &digestAuthRequest{op: maintPing, respChannel: make(chan *DigestAuthResponse)} 116 | channel <- request 117 | response := <-request.respChannel 118 | if response.status != maintOk { 119 | log.Fatal("unexpected status") 120 | } 121 | time.Sleep(30 * time.Minute) 122 | } 123 | } 124 | 125 | go processor() 126 | go maintPinger() 127 | 128 | authFunc := func(authData *DigestAuthData, op int) *DigestAuthResponse { 129 | request := &digestAuthRequest{data: authData, op: op, respChannel: make(chan *DigestAuthResponse)} 130 | channel <- request 131 | return <-request.respChannel 132 | } 133 | 134 | return authFunc 135 | } 136 | 137 | func setAllowedNetworksHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) { 138 | if conf.AllowedNetworks != nil && len(conf.AllowedNetworks) > 0 { 139 | proxy.OnRequest(goproxy.Not(sourceIPMatches(conf.AllowedNetworks))).HandleConnect(goproxy.AlwaysReject) 140 | proxy.OnRequest(goproxy.Not(sourceIPMatches(conf.AllowedNetworks))).DoFunc( 141 | func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 142 | return req, goproxy.NewResponse(req, goproxy.ContentTypeHtml, http.StatusForbidden, "Access denied") 143 | }) 144 | } 145 | 146 | if conf.DisallowedNetworks != nil && len(conf.DisallowedNetworks) > 0 { 147 | proxy.OnRequest(sourceIPMatches(conf.DisallowedNetworks)).HandleConnect(goproxy.AlwaysReject) 148 | proxy.OnRequest(sourceIPMatches(conf.DisallowedNetworks)).DoFunc( 149 | func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 150 | return req, goproxy.NewResponse(req, goproxy.ContentTypeHtml, http.StatusForbidden, "Access denied") 151 | }) 152 | } 153 | } 154 | 155 | func sourceIPMatches(networks []string) goproxy.ReqConditionFunc { 156 | cidrs := make([](*net.IPNet), len(networks)) 157 | for idx, network := range networks { 158 | _, cidrnet, _ := net.ParseCIDR(network) 159 | cidrs[idx] = cidrnet 160 | } 161 | 162 | return func(req *http.Request, ctx *goproxy.ProxyCtx) bool { 163 | ip, _, err := net.SplitHostPort(req.RemoteAddr) 164 | if err != nil { 165 | ctx.Warnf("couldn't parse remote address %v: %v", req.RemoteAddr, err) 166 | return false 167 | } 168 | addr := net.ParseIP(ip) 169 | for _, network := range cidrs { 170 | if network.Contains(addr) { 171 | return true 172 | } 173 | } 174 | return false 175 | } 176 | } 177 | 178 | func setAllowedConnectPortsHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) { 179 | if conf.AllowedConnectPorts != nil && len(conf.AllowedConnectPorts) > 0 { 180 | ports := make([]string, len(conf.AllowedConnectPorts)) 181 | for i, v := range conf.AllowedConnectPorts { 182 | ports[i] = ":" + strconv.Itoa(v) 183 | } 184 | rx := "(" + strings.Join(ports, "|") + ")$" 185 | proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(rx)))).HandleConnect(goproxy.AlwaysReject) 186 | } 187 | } 188 | 189 | func setForwardedForHeaderHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) { 190 | f := func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 191 | ip, _, err := net.SplitHostPort(req.RemoteAddr) 192 | if err != nil { 193 | ctx.Warnf("coudn't parse remote address %v: %v", req.RemoteAddr, err) 194 | return req, nil 195 | } 196 | 197 | switch conf.ForwardedForHeader { 198 | case "on": 199 | header := req.Header.Get(proxyForwardedForHeader) 200 | if header == "" { 201 | req.Header.Add(proxyForwardedForHeader, ip) 202 | } else { 203 | header = header + ", " + ip 204 | req.Header.Del(proxyForwardedForHeader) 205 | req.Header.Add(proxyForwardedForHeader, header) 206 | } 207 | case "delete": 208 | req.Header.Del(proxyForwardedForHeader) 209 | case "truncate": 210 | req.Header.Del(proxyForwardedForHeader) 211 | req.Header.Add(proxyForwardedForHeader, ip) 212 | } 213 | 214 | return req, nil 215 | } 216 | 217 | proxy.OnRequest().DoFunc(f) 218 | } 219 | 220 | func setViaHeaderHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) { 221 | handler := func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 222 | switch conf.ViaHeader { 223 | case "on": 224 | header := req.Header.Get(proxyViaHeader) 225 | if header == "" { 226 | header = fmt.Sprintf("1.1 %s", conf.ViaProxyName) 227 | } else { 228 | header = fmt.Sprintf("%s, 1.1 %s", header, conf.ViaProxyName) 229 | } 230 | req.Header.Add(proxyViaHeader, header) 231 | case "delete": 232 | req.Header.Del(proxyViaHeader) 233 | } 234 | return req, nil 235 | } 236 | 237 | proxy.OnRequest().DoFunc(handler) 238 | } 239 | 240 | func setAddCustomHeadersHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer) { 241 | handler := func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 242 | for _, headerData := range conf.AddHeaders { 243 | if len(headerData) == 2 { 244 | header := headerData[0] 245 | value := headerData[1] 246 | if len(header) > 0 && len(value) > 0 { 247 | headerExists := (req.Header.Get(header) != "") 248 | if !headerExists { 249 | req.Header.Add(header, value) 250 | } 251 | } 252 | } 253 | } 254 | return req, nil 255 | } 256 | 257 | if len(conf.AddHeaders) > 0 { 258 | proxy.OnRequest().DoFunc(handler) 259 | } 260 | } 261 | 262 | func makeCustomDialContext(localAddr *net.TCPAddr) func(context.Context, string, string) (net.Conn, error) { 263 | return func(ctx context.Context, network, addr string) (net.Conn, error) { 264 | remoteAddr, err := net.ResolveTCPAddr(network, addr) 265 | if err != nil { 266 | return nil, err 267 | } 268 | 269 | conn, err := net.DialTCP(network, localAddr, remoteAddr) 270 | if err != nil { 271 | return nil, err 272 | } 273 | 274 | err = conn.SetKeepAlive(true) 275 | if err != nil { 276 | return nil, err 277 | } 278 | 279 | err = conn.SetKeepAlivePeriod(tcpKeepAliveInterval) 280 | if err != nil { 281 | return nil, err 282 | } 283 | 284 | c := TimedConn{ 285 | Conn: conn, 286 | readTimeout: DefaultReadTimeout, 287 | writeTimeout: DefaultWriteTimeout, 288 | } 289 | 290 | return c, nil 291 | } 292 | } 293 | 294 | func createProxy(conf *Configuration) *goproxy.ProxyHttpServer { 295 | proxy := goproxy.NewProxyHttpServer() 296 | setActivityLog(conf, proxy) 297 | 298 | var laddr string 299 | 300 | addressOk := true 301 | 302 | if conf.BindIP != "" { 303 | if ip := net.ParseIP(conf.BindIP); ip != nil { 304 | if ip4 := ip.To4(); ip4 != nil { 305 | laddr = conf.BindIP + ":0" 306 | } else if ip16 := ip.To16(); ip16 != nil { 307 | laddr = "[" + conf.BindIP + "]:0" 308 | } else { 309 | proxy.Logger.Printf("WARN: couldn't use \"%v\" as outgoing request address.\n", conf.BindIP) 310 | addressOk = false 311 | } 312 | } 313 | } 314 | 315 | if addressOk { 316 | if laddr != "" { 317 | if addr, err := net.ResolveTCPAddr("tcp", laddr); err == nil { 318 | proxy.Tr.DialContext = makeCustomDialContext(addr) 319 | } else { 320 | proxy.Logger.Printf("WARN: couldn't use \"%v\" as outgoing request address. %v\n", conf.BindIP, err) 321 | } 322 | } else { 323 | proxy.Tr.DialContext = makeCustomDialContext(nil) 324 | } 325 | } 326 | 327 | return proxy 328 | } 329 | 330 | func setActivityLog(conf *Configuration, proxy *goproxy.ProxyHttpServer) { 331 | if conf.ActivityLog != "" { 332 | fh, err := os.OpenFile(conf.ActivityLog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600) 333 | if err != nil { 334 | log.Fatalf("couldn't open activity log file %v: %v", conf.ActivityLog, err) 335 | } 336 | proxy.Logger = log.New(fh, "", log.LstdFlags) 337 | } 338 | } 339 | 340 | func setSignalHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer, logger *ProxyLogger) { 341 | signalChannel := make(chan os.Signal) 342 | signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM, syscall.SIGUSR1) 343 | 344 | signalHandler := func() { 345 | for sig := range signalChannel { 346 | switch sig { 347 | case os.Interrupt, syscall.SIGTERM: 348 | proxy.Logger.Printf("got interrupt signal, exiting\n") 349 | err := logger.close() 350 | if err != nil { 351 | log.Printf("Close error: %v", err) 352 | } 353 | os.Exit(0) 354 | case syscall.SIGUSR1: 355 | proxy.Logger.Printf("got USR1 signal, reopening logs\n") 356 | // reopen access log 357 | logger.reopen() 358 | // reopen activity log 359 | setActivityLog(conf, proxy) 360 | } 361 | } 362 | } 363 | 364 | go signalHandler() 365 | } 366 | 367 | func setAuthenticationHandler(conf *Configuration, proxy *goproxy.ProxyHttpServer, logger *ProxyLogger) { 368 | if conf.AuthFile != "" { 369 | if conf.AuthType == "basic" { 370 | auth, err := newBasicAuthFromFile(conf.AuthFile) 371 | if err != nil { 372 | proxy.Logger.Printf("couldn't create basic auth structure: %v\n", err) 373 | os.Exit(1) 374 | } 375 | setProxyBasicAuth(proxy, conf.AuthRealm, makeBasicAuthValidator(auth), logger) 376 | } else { 377 | auth, err := newDigestAuthFromFile(conf.AuthFile) 378 | if err != nil { 379 | proxy.Logger.Printf("couldn't create digest auth structure: %v\n", err) 380 | os.Exit(1) 381 | } 382 | setProxyDigestAuth(proxy, conf.AuthRealm, makeDigestAuthValidator(auth), logger) 383 | } 384 | } else { 385 | // If there is neither Digest no Basic authentication we still need to setup 386 | // handler to log HTTPS CONNECT requests 387 | setHTTPSLoggingHandler(proxy, logger) 388 | } 389 | } 390 | 391 | func setHTTPSLoggingHandler(proxy *goproxy.ProxyHttpServer, logger *ProxyLogger) { 392 | proxy.OnRequest().HandleConnectFunc( 393 | func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { 394 | if ctx.Req == nil { 395 | ctx.Req = emptyReq 396 | } 397 | 398 | if logger != nil { 399 | logger.log(ctx) 400 | } 401 | 402 | return goproxy.OkConnect, host 403 | }) 404 | } 405 | 406 | func setHTTPLoggingHandler(proxy *goproxy.ProxyHttpServer, logger *ProxyLogger) { 407 | proxy.OnResponse().DoFunc( 408 | func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { 409 | logger.logResponse(resp, ctx) 410 | return resp 411 | }) 412 | } 413 | 414 | func setForwardProxy(conf *Configuration, proxy *goproxy.ProxyHttpServer) { 415 | if len(conf.ForwardProxyURL) == 0 { 416 | return 417 | } 418 | 419 | u, err := url.Parse(conf.ForwardProxyURL) 420 | if err != nil { 421 | proxy.Logger.Printf("can't parse forward proxy URL: %v", err) 422 | os.Exit(1) 423 | } 424 | 425 | proxy.Tr = &http.Transport{Proxy: func(req *http.Request) (*url.URL, error) { 426 | return url.Parse(conf.ForwardProxyURL) 427 | }} 428 | 429 | if len(u.User.String()) > 0 { 430 | connectHandler := func(req *http.Request) { 431 | req.Header.Del(ProxyAuthorizatonHeader) 432 | if len(u.User.Username()) > 0 { 433 | req.Header.Set(ProxyAuthorizatonHeader, "Basic "+base64.StdEncoding.EncodeToString([]byte(u.User.String()))) 434 | } 435 | } 436 | proxy.ConnectDial = proxy.NewConnectDialToProxyWithHandler(conf.ForwardProxyURL, connectHandler) 437 | } else { 438 | proxy.ConnectDial = proxy.NewConnectDialToProxy(conf.ForwardProxyURL) 439 | } 440 | } 441 | 442 | func main() { 443 | configFile := flag.String("config", "microproxy.toml", "proxy configuration file") 444 | testConfigOnly := flag.Bool("t", false, "only test configuration file") 445 | verboseMode := flag.Bool("v", false, "enable verbose debug mode") 446 | 447 | flag.Parse() 448 | 449 | conf := newConfigurationFromFile(*configFile) 450 | 451 | if *testConfigOnly { 452 | fmt.Println("Configuration file seems ok.") 453 | os.Exit(0) 454 | } 455 | 456 | proxy := createProxy(conf) 457 | proxy.Verbose = *verboseMode 458 | 459 | logger := newProxyLogger(conf) 460 | 461 | setHTTPLoggingHandler(proxy, logger) 462 | setForwardProxy(conf, proxy) 463 | setAllowedConnectPortsHandler(conf, proxy) 464 | setAllowedNetworksHandler(conf, proxy) 465 | setForwardedForHeaderHandler(conf, proxy) 466 | setViaHeaderHandler(conf, proxy) 467 | setAddCustomHeadersHandler(conf, proxy) 468 | setSignalHandler(conf, proxy, logger) 469 | 470 | // To be called first while processing handlers' stack, 471 | // has to be placed last in the source code. 472 | setAuthenticationHandler(conf, proxy, logger) 473 | 474 | proxy.Logger.Printf("starting proxy\n") 475 | 476 | log.Fatal(http.ListenAndServe(conf.Listen, proxy)) 477 | } 478 | -------------------------------------------------------------------------------- /microproxy.toml: -------------------------------------------------------------------------------- 1 | listen="127.0.0.1:3129" 2 | access_log="/tmp/microproxy.access.log" 3 | activity_log="/tmp/microproxy.error.log" 4 | allowed_connect_ports=[443, 80] 5 | auth_file="auth.txt" 6 | auth_type="basic" 7 | auth_realm="proxy" 8 | forwarded_for_header="on" 9 | allowed_networks=["127.0.0.1/32"] 10 | add_headers=[ 11 | ["X-Test-Header1", "yes"], 12 | ["X-Test-Header2", "no"] 13 | ] 14 | -------------------------------------------------------------------------------- /proxy-digest-auth-test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import urllib.request, urllib.error, urllib.parse 3 | import argparse 4 | 5 | 6 | def parse_args(): 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("--proxy", type=str, metavar="PROXY", required=True, 9 | help="address of a proxy server") 10 | parser.add_argument("--user", type=str, metavar="USER", required=True, 11 | help="proxy user") 12 | parser.add_argument("--password", type=str, metavar="PASSWORD", required=True, 13 | help="proxt user's password") 14 | parser.add_argument("--url", type=str, metavar="URL", required=True, 15 | help="URL to access") 16 | 17 | args = parser.parse_args() 18 | 19 | return args 20 | 21 | 22 | def main(): 23 | args = parse_args() 24 | 25 | password_manager = urllib.request.HTTPPasswordMgrWithDefaultRealm() 26 | password_manager.add_password(None, args.url, args.user, args.password) 27 | auth_handler = urllib.request.ProxyDigestAuthHandler(password_manager) 28 | proxy_support = urllib.request.ProxyHandler({"http": args.proxy}) 29 | opener = urllib.request.build_opener(proxy_support, auth_handler) 30 | urllib.request.install_opener(opener) 31 | handle = urllib.request.urlopen(args.url) 32 | page = handle.read() 33 | 34 | print((page), end=' ') 35 | 36 | 37 | if __name__ == '__main__': 38 | try: 39 | main() 40 | except Exception as exc: 41 | print(exc) 42 | sys.exit(-1) 43 | -------------------------------------------------------------------------------- /proxy-digest-auth-test.py.bak: -------------------------------------------------------------------------------- 1 | import sys 2 | import urllib2 3 | import argparse 4 | 5 | 6 | def parse_args(): 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("--proxy", type=str, metavar="PROXY", required=True, 9 | help="address of a proxy server") 10 | parser.add_argument("--user", type=str, metavar="USER", required=True, 11 | help="proxy user") 12 | parser.add_argument("--password", type=str, metavar="PASSWORD", required=True, 13 | help="proxt user's password") 14 | parser.add_argument("--url", type=str, metavar="URL", required=True, 15 | help="URL to access") 16 | 17 | args = parser.parse_args() 18 | 19 | return args 20 | 21 | 22 | def main(): 23 | args = parse_args() 24 | 25 | password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() 26 | password_manager.add_password(None, args.url, args.user, args.password) 27 | auth_handler = urllib2.ProxyDigestAuthHandler(password_manager) 28 | proxy_support = urllib2.ProxyHandler({"http": args.proxy}) 29 | opener = urllib2.build_opener(proxy_support, auth_handler) 30 | urllib2.install_opener(opener) 31 | handle = urllib2.urlopen(args.url) 32 | page = handle.read() 33 | 34 | print(page), 35 | 36 | 37 | if __name__ == '__main__': 38 | try: 39 | main() 40 | except Exception as exc: 41 | print(exc) 42 | sys.exit(-1) 43 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/.gitignore: -------------------------------------------------------------------------------- 1 | TAGS 2 | tags 3 | .*.swp 4 | tomlcheck/tomlcheck 5 | toml.test 6 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - 1.2 5 | - 1.3 6 | - 1.4 7 | - 1.5 8 | - 1.6 9 | - tip 10 | install: 11 | - go install ./... 12 | - go get github.com/BurntSushi/toml-test 13 | script: 14 | - export PATH="$PATH:$HOME/gopath/bin" 15 | - make test 16 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/COMPATIBLE: -------------------------------------------------------------------------------- 1 | Compatible with TOML version 2 | [v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md) 3 | 4 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/COPYING: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 TOML authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | go install ./... 3 | 4 | test: install 5 | go test -v 6 | toml-test toml-test-decoder 7 | toml-test -encoder toml-test-encoder 8 | 9 | fmt: 10 | gofmt -w *.go */*.go 11 | colcheck *.go */*.go 12 | 13 | tags: 14 | find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS 15 | 16 | push: 17 | git push origin master 18 | git push github master 19 | 20 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/README.md: -------------------------------------------------------------------------------- 1 | ## TOML parser and encoder for Go with reflection 2 | 3 | TOML stands for Tom's Obvious, Minimal Language. This Go package provides a 4 | reflection interface similar to Go's standard library `json` and `xml` 5 | packages. This package also supports the `encoding.TextUnmarshaler` and 6 | `encoding.TextMarshaler` interfaces so that you can define custom data 7 | representations. (There is an example of this below.) 8 | 9 | Spec: https://github.com/toml-lang/toml 10 | 11 | Compatible with TOML version 12 | [v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) 13 | 14 | Documentation: https://godoc.org/github.com/BurntSushi/toml 15 | 16 | Installation: 17 | 18 | ```bash 19 | go get github.com/BurntSushi/toml 20 | ``` 21 | 22 | Try the toml validator: 23 | 24 | ```bash 25 | go get github.com/BurntSushi/toml/cmd/tomlv 26 | tomlv some-toml-file.toml 27 | ``` 28 | 29 | [![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml) 30 | 31 | ### Testing 32 | 33 | This package passes all tests in 34 | [toml-test](https://github.com/BurntSushi/toml-test) for both the decoder 35 | and the encoder. 36 | 37 | ### Examples 38 | 39 | This package works similarly to how the Go standard library handles `XML` 40 | and `JSON`. Namely, data is loaded into Go values via reflection. 41 | 42 | For the simplest example, consider some TOML file as just a list of keys 43 | and values: 44 | 45 | ```toml 46 | Age = 25 47 | Cats = [ "Cauchy", "Plato" ] 48 | Pi = 3.14 49 | Perfection = [ 6, 28, 496, 8128 ] 50 | DOB = 1987-07-05T05:45:00Z 51 | ``` 52 | 53 | Which could be defined in Go as: 54 | 55 | ```go 56 | type Config struct { 57 | Age int 58 | Cats []string 59 | Pi float64 60 | Perfection []int 61 | DOB time.Time // requires `import time` 62 | } 63 | ``` 64 | 65 | And then decoded with: 66 | 67 | ```go 68 | var conf Config 69 | if _, err := toml.Decode(tomlData, &conf); err != nil { 70 | // handle error 71 | } 72 | ``` 73 | 74 | You can also use struct tags if your struct field name doesn't map to a TOML 75 | key value directly: 76 | 77 | ```toml 78 | some_key_NAME = "wat" 79 | ``` 80 | 81 | ```go 82 | type TOML struct { 83 | ObscureKey string `toml:"some_key_NAME"` 84 | } 85 | ``` 86 | 87 | ### Using the `encoding.TextUnmarshaler` interface 88 | 89 | Here's an example that automatically parses duration strings into 90 | `time.Duration` values: 91 | 92 | ```toml 93 | [[song]] 94 | name = "Thunder Road" 95 | duration = "4m49s" 96 | 97 | [[song]] 98 | name = "Stairway to Heaven" 99 | duration = "8m03s" 100 | ``` 101 | 102 | Which can be decoded with: 103 | 104 | ```go 105 | type song struct { 106 | Name string 107 | Duration duration 108 | } 109 | type songs struct { 110 | Song []song 111 | } 112 | var favorites songs 113 | if _, err := toml.Decode(blob, &favorites); err != nil { 114 | log.Fatal(err) 115 | } 116 | 117 | for _, s := range favorites.Song { 118 | fmt.Printf("%s (%s)\n", s.Name, s.Duration) 119 | } 120 | ``` 121 | 122 | And you'll also need a `duration` type that satisfies the 123 | `encoding.TextUnmarshaler` interface: 124 | 125 | ```go 126 | type duration struct { 127 | time.Duration 128 | } 129 | 130 | func (d *duration) UnmarshalText(text []byte) error { 131 | var err error 132 | d.Duration, err = time.ParseDuration(string(text)) 133 | return err 134 | } 135 | ``` 136 | 137 | ### More complex usage 138 | 139 | Here's an example of how to load the example from the official spec page: 140 | 141 | ```toml 142 | # This is a TOML document. Boom. 143 | 144 | title = "TOML Example" 145 | 146 | [owner] 147 | name = "Tom Preston-Werner" 148 | organization = "GitHub" 149 | bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." 150 | dob = 1979-05-27T07:32:00Z # First class dates? Why not? 151 | 152 | [database] 153 | server = "192.168.1.1" 154 | ports = [ 8001, 8001, 8002 ] 155 | connection_max = 5000 156 | enabled = true 157 | 158 | [servers] 159 | 160 | # You can indent as you please. Tabs or spaces. TOML don't care. 161 | [servers.alpha] 162 | ip = "10.0.0.1" 163 | dc = "eqdc10" 164 | 165 | [servers.beta] 166 | ip = "10.0.0.2" 167 | dc = "eqdc10" 168 | 169 | [clients] 170 | data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it 171 | 172 | # Line breaks are OK when inside arrays 173 | hosts = [ 174 | "alpha", 175 | "omega" 176 | ] 177 | ``` 178 | 179 | And the corresponding Go types are: 180 | 181 | ```go 182 | type tomlConfig struct { 183 | Title string 184 | Owner ownerInfo 185 | DB database `toml:"database"` 186 | Servers map[string]server 187 | Clients clients 188 | } 189 | 190 | type ownerInfo struct { 191 | Name string 192 | Org string `toml:"organization"` 193 | Bio string 194 | DOB time.Time 195 | } 196 | 197 | type database struct { 198 | Server string 199 | Ports []int 200 | ConnMax int `toml:"connection_max"` 201 | Enabled bool 202 | } 203 | 204 | type server struct { 205 | IP string 206 | DC string 207 | } 208 | 209 | type clients struct { 210 | Data [][]interface{} 211 | Hosts []string 212 | } 213 | ``` 214 | 215 | Note that a case insensitive match will be tried if an exact match can't be 216 | found. 217 | 218 | A working example of the above can be found in `_examples/example.{go,toml}`. 219 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/decode.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "math" 8 | "reflect" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func e(format string, args ...interface{}) error { 14 | return fmt.Errorf("toml: "+format, args...) 15 | } 16 | 17 | // Unmarshaler is the interface implemented by objects that can unmarshal a 18 | // TOML description of themselves. 19 | type Unmarshaler interface { 20 | UnmarshalTOML(interface{}) error 21 | } 22 | 23 | // Unmarshal decodes the contents of `p` in TOML format into a pointer `v`. 24 | func Unmarshal(p []byte, v interface{}) error { 25 | _, err := Decode(string(p), v) 26 | return err 27 | } 28 | 29 | // Primitive is a TOML value that hasn't been decoded into a Go value. 30 | // When using the various `Decode*` functions, the type `Primitive` may 31 | // be given to any value, and its decoding will be delayed. 32 | // 33 | // A `Primitive` value can be decoded using the `PrimitiveDecode` function. 34 | // 35 | // The underlying representation of a `Primitive` value is subject to change. 36 | // Do not rely on it. 37 | // 38 | // N.B. Primitive values are still parsed, so using them will only avoid 39 | // the overhead of reflection. They can be useful when you don't know the 40 | // exact type of TOML data until run time. 41 | type Primitive struct { 42 | undecoded interface{} 43 | context Key 44 | } 45 | 46 | // DEPRECATED! 47 | // 48 | // Use MetaData.PrimitiveDecode instead. 49 | func PrimitiveDecode(primValue Primitive, v interface{}) error { 50 | md := MetaData{decoded: make(map[string]bool)} 51 | return md.unify(primValue.undecoded, rvalue(v)) 52 | } 53 | 54 | // PrimitiveDecode is just like the other `Decode*` functions, except it 55 | // decodes a TOML value that has already been parsed. Valid primitive values 56 | // can *only* be obtained from values filled by the decoder functions, 57 | // including this method. (i.e., `v` may contain more `Primitive` 58 | // values.) 59 | // 60 | // Meta data for primitive values is included in the meta data returned by 61 | // the `Decode*` functions with one exception: keys returned by the Undecoded 62 | // method will only reflect keys that were decoded. Namely, any keys hidden 63 | // behind a Primitive will be considered undecoded. Executing this method will 64 | // update the undecoded keys in the meta data. (See the example.) 65 | func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error { 66 | md.context = primValue.context 67 | defer func() { md.context = nil }() 68 | return md.unify(primValue.undecoded, rvalue(v)) 69 | } 70 | 71 | // Decode will decode the contents of `data` in TOML format into a pointer 72 | // `v`. 73 | // 74 | // TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be 75 | // used interchangeably.) 76 | // 77 | // TOML arrays of tables correspond to either a slice of structs or a slice 78 | // of maps. 79 | // 80 | // TOML datetimes correspond to Go `time.Time` values. 81 | // 82 | // All other TOML types (float, string, int, bool and array) correspond 83 | // to the obvious Go types. 84 | // 85 | // An exception to the above rules is if a type implements the 86 | // encoding.TextUnmarshaler interface. In this case, any primitive TOML value 87 | // (floats, strings, integers, booleans and datetimes) will be converted to 88 | // a byte string and given to the value's UnmarshalText method. See the 89 | // Unmarshaler example for a demonstration with time duration strings. 90 | // 91 | // Key mapping 92 | // 93 | // TOML keys can map to either keys in a Go map or field names in a Go 94 | // struct. The special `toml` struct tag may be used to map TOML keys to 95 | // struct fields that don't match the key name exactly. (See the example.) 96 | // A case insensitive match to struct names will be tried if an exact match 97 | // can't be found. 98 | // 99 | // The mapping between TOML values and Go values is loose. That is, there 100 | // may exist TOML values that cannot be placed into your representation, and 101 | // there may be parts of your representation that do not correspond to 102 | // TOML values. This loose mapping can be made stricter by using the IsDefined 103 | // and/or Undecoded methods on the MetaData returned. 104 | // 105 | // This decoder will not handle cyclic types. If a cyclic type is passed, 106 | // `Decode` will not terminate. 107 | func Decode(data string, v interface{}) (MetaData, error) { 108 | rv := reflect.ValueOf(v) 109 | if rv.Kind() != reflect.Ptr { 110 | return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v)) 111 | } 112 | if rv.IsNil() { 113 | return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v)) 114 | } 115 | p, err := parse(data) 116 | if err != nil { 117 | return MetaData{}, err 118 | } 119 | md := MetaData{ 120 | p.mapping, p.types, p.ordered, 121 | make(map[string]bool, len(p.ordered)), nil, 122 | } 123 | return md, md.unify(p.mapping, indirect(rv)) 124 | } 125 | 126 | // DecodeFile is just like Decode, except it will automatically read the 127 | // contents of the file at `fpath` and decode it for you. 128 | func DecodeFile(fpath string, v interface{}) (MetaData, error) { 129 | bs, err := ioutil.ReadFile(fpath) 130 | if err != nil { 131 | return MetaData{}, err 132 | } 133 | return Decode(string(bs), v) 134 | } 135 | 136 | // DecodeReader is just like Decode, except it will consume all bytes 137 | // from the reader and decode it for you. 138 | func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { 139 | bs, err := ioutil.ReadAll(r) 140 | if err != nil { 141 | return MetaData{}, err 142 | } 143 | return Decode(string(bs), v) 144 | } 145 | 146 | // unify performs a sort of type unification based on the structure of `rv`, 147 | // which is the client representation. 148 | // 149 | // Any type mismatch produces an error. Finding a type that we don't know 150 | // how to handle produces an unsupported type error. 151 | func (md *MetaData) unify(data interface{}, rv reflect.Value) error { 152 | 153 | // Special case. Look for a `Primitive` value. 154 | if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { 155 | // Save the undecoded data and the key context into the primitive 156 | // value. 157 | context := make(Key, len(md.context)) 158 | copy(context, md.context) 159 | rv.Set(reflect.ValueOf(Primitive{ 160 | undecoded: data, 161 | context: context, 162 | })) 163 | return nil 164 | } 165 | 166 | // Special case. Unmarshaler Interface support. 167 | if rv.CanAddr() { 168 | if v, ok := rv.Addr().Interface().(Unmarshaler); ok { 169 | return v.UnmarshalTOML(data) 170 | } 171 | } 172 | 173 | // Special case. Handle time.Time values specifically. 174 | // TODO: Remove this code when we decide to drop support for Go 1.1. 175 | // This isn't necessary in Go 1.2 because time.Time satisfies the encoding 176 | // interfaces. 177 | if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) { 178 | return md.unifyDatetime(data, rv) 179 | } 180 | 181 | // Special case. Look for a value satisfying the TextUnmarshaler interface. 182 | if v, ok := rv.Interface().(TextUnmarshaler); ok { 183 | return md.unifyText(data, v) 184 | } 185 | // BUG(burntsushi) 186 | // The behavior here is incorrect whenever a Go type satisfies the 187 | // encoding.TextUnmarshaler interface but also corresponds to a TOML 188 | // hash or array. In particular, the unmarshaler should only be applied 189 | // to primitive TOML values. But at this point, it will be applied to 190 | // all kinds of values and produce an incorrect error whenever those values 191 | // are hashes or arrays (including arrays of tables). 192 | 193 | k := rv.Kind() 194 | 195 | // laziness 196 | if k >= reflect.Int && k <= reflect.Uint64 { 197 | return md.unifyInt(data, rv) 198 | } 199 | switch k { 200 | case reflect.Ptr: 201 | elem := reflect.New(rv.Type().Elem()) 202 | err := md.unify(data, reflect.Indirect(elem)) 203 | if err != nil { 204 | return err 205 | } 206 | rv.Set(elem) 207 | return nil 208 | case reflect.Struct: 209 | return md.unifyStruct(data, rv) 210 | case reflect.Map: 211 | return md.unifyMap(data, rv) 212 | case reflect.Array: 213 | return md.unifyArray(data, rv) 214 | case reflect.Slice: 215 | return md.unifySlice(data, rv) 216 | case reflect.String: 217 | return md.unifyString(data, rv) 218 | case reflect.Bool: 219 | return md.unifyBool(data, rv) 220 | case reflect.Interface: 221 | // we only support empty interfaces. 222 | if rv.NumMethod() > 0 { 223 | return e("unsupported type %s", rv.Type()) 224 | } 225 | return md.unifyAnything(data, rv) 226 | case reflect.Float32: 227 | fallthrough 228 | case reflect.Float64: 229 | return md.unifyFloat64(data, rv) 230 | } 231 | return e("unsupported type %s", rv.Kind()) 232 | } 233 | 234 | func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { 235 | tmap, ok := mapping.(map[string]interface{}) 236 | if !ok { 237 | if mapping == nil { 238 | return nil 239 | } 240 | return e("type mismatch for %s: expected table but found %T", 241 | rv.Type().String(), mapping) 242 | } 243 | 244 | for key, datum := range tmap { 245 | var f *field 246 | fields := cachedTypeFields(rv.Type()) 247 | for i := range fields { 248 | ff := &fields[i] 249 | if ff.name == key { 250 | f = ff 251 | break 252 | } 253 | if f == nil && strings.EqualFold(ff.name, key) { 254 | f = ff 255 | } 256 | } 257 | if f != nil { 258 | subv := rv 259 | for _, i := range f.index { 260 | subv = indirect(subv.Field(i)) 261 | } 262 | if isUnifiable(subv) { 263 | md.decoded[md.context.add(key).String()] = true 264 | md.context = append(md.context, key) 265 | if err := md.unify(datum, subv); err != nil { 266 | return err 267 | } 268 | md.context = md.context[0 : len(md.context)-1] 269 | } else if f.name != "" { 270 | // Bad user! No soup for you! 271 | return e("cannot write unexported field %s.%s", 272 | rv.Type().String(), f.name) 273 | } 274 | } 275 | } 276 | return nil 277 | } 278 | 279 | func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { 280 | tmap, ok := mapping.(map[string]interface{}) 281 | if !ok { 282 | if tmap == nil { 283 | return nil 284 | } 285 | return badtype("map", mapping) 286 | } 287 | if rv.IsNil() { 288 | rv.Set(reflect.MakeMap(rv.Type())) 289 | } 290 | for k, v := range tmap { 291 | md.decoded[md.context.add(k).String()] = true 292 | md.context = append(md.context, k) 293 | 294 | rvkey := indirect(reflect.New(rv.Type().Key())) 295 | rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) 296 | if err := md.unify(v, rvval); err != nil { 297 | return err 298 | } 299 | md.context = md.context[0 : len(md.context)-1] 300 | 301 | rvkey.SetString(k) 302 | rv.SetMapIndex(rvkey, rvval) 303 | } 304 | return nil 305 | } 306 | 307 | func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error { 308 | datav := reflect.ValueOf(data) 309 | if datav.Kind() != reflect.Slice { 310 | if !datav.IsValid() { 311 | return nil 312 | } 313 | return badtype("slice", data) 314 | } 315 | sliceLen := datav.Len() 316 | if sliceLen != rv.Len() { 317 | return e("expected array length %d; got TOML array of length %d", 318 | rv.Len(), sliceLen) 319 | } 320 | return md.unifySliceArray(datav, rv) 321 | } 322 | 323 | func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error { 324 | datav := reflect.ValueOf(data) 325 | if datav.Kind() != reflect.Slice { 326 | if !datav.IsValid() { 327 | return nil 328 | } 329 | return badtype("slice", data) 330 | } 331 | n := datav.Len() 332 | if rv.IsNil() || rv.Cap() < n { 333 | rv.Set(reflect.MakeSlice(rv.Type(), n, n)) 334 | } 335 | rv.SetLen(n) 336 | return md.unifySliceArray(datav, rv) 337 | } 338 | 339 | func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { 340 | sliceLen := data.Len() 341 | for i := 0; i < sliceLen; i++ { 342 | v := data.Index(i).Interface() 343 | sliceval := indirect(rv.Index(i)) 344 | if err := md.unify(v, sliceval); err != nil { 345 | return err 346 | } 347 | } 348 | return nil 349 | } 350 | 351 | func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error { 352 | if _, ok := data.(time.Time); ok { 353 | rv.Set(reflect.ValueOf(data)) 354 | return nil 355 | } 356 | return badtype("time.Time", data) 357 | } 358 | 359 | func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error { 360 | if s, ok := data.(string); ok { 361 | rv.SetString(s) 362 | return nil 363 | } 364 | return badtype("string", data) 365 | } 366 | 367 | func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error { 368 | if num, ok := data.(float64); ok { 369 | switch rv.Kind() { 370 | case reflect.Float32: 371 | fallthrough 372 | case reflect.Float64: 373 | rv.SetFloat(num) 374 | default: 375 | panic("bug") 376 | } 377 | return nil 378 | } 379 | return badtype("float", data) 380 | } 381 | 382 | func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error { 383 | if num, ok := data.(int64); ok { 384 | if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 { 385 | switch rv.Kind() { 386 | case reflect.Int, reflect.Int64: 387 | // No bounds checking necessary. 388 | case reflect.Int8: 389 | if num < math.MinInt8 || num > math.MaxInt8 { 390 | return e("value %d is out of range for int8", num) 391 | } 392 | case reflect.Int16: 393 | if num < math.MinInt16 || num > math.MaxInt16 { 394 | return e("value %d is out of range for int16", num) 395 | } 396 | case reflect.Int32: 397 | if num < math.MinInt32 || num > math.MaxInt32 { 398 | return e("value %d is out of range for int32", num) 399 | } 400 | } 401 | rv.SetInt(num) 402 | } else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 { 403 | unum := uint64(num) 404 | switch rv.Kind() { 405 | case reflect.Uint, reflect.Uint64: 406 | // No bounds checking necessary. 407 | case reflect.Uint8: 408 | if num < 0 || unum > math.MaxUint8 { 409 | return e("value %d is out of range for uint8", num) 410 | } 411 | case reflect.Uint16: 412 | if num < 0 || unum > math.MaxUint16 { 413 | return e("value %d is out of range for uint16", num) 414 | } 415 | case reflect.Uint32: 416 | if num < 0 || unum > math.MaxUint32 { 417 | return e("value %d is out of range for uint32", num) 418 | } 419 | } 420 | rv.SetUint(unum) 421 | } else { 422 | panic("unreachable") 423 | } 424 | return nil 425 | } 426 | return badtype("integer", data) 427 | } 428 | 429 | func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error { 430 | if b, ok := data.(bool); ok { 431 | rv.SetBool(b) 432 | return nil 433 | } 434 | return badtype("boolean", data) 435 | } 436 | 437 | func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error { 438 | rv.Set(reflect.ValueOf(data)) 439 | return nil 440 | } 441 | 442 | func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { 443 | var s string 444 | switch sdata := data.(type) { 445 | case TextMarshaler: 446 | text, err := sdata.MarshalText() 447 | if err != nil { 448 | return err 449 | } 450 | s = string(text) 451 | case fmt.Stringer: 452 | s = sdata.String() 453 | case string: 454 | s = sdata 455 | case bool: 456 | s = fmt.Sprintf("%v", sdata) 457 | case int64: 458 | s = fmt.Sprintf("%d", sdata) 459 | case float64: 460 | s = fmt.Sprintf("%f", sdata) 461 | default: 462 | return badtype("primitive (string-like)", data) 463 | } 464 | if err := v.UnmarshalText([]byte(s)); err != nil { 465 | return err 466 | } 467 | return nil 468 | } 469 | 470 | // rvalue returns a reflect.Value of `v`. All pointers are resolved. 471 | func rvalue(v interface{}) reflect.Value { 472 | return indirect(reflect.ValueOf(v)) 473 | } 474 | 475 | // indirect returns the value pointed to by a pointer. 476 | // Pointers are followed until the value is not a pointer. 477 | // New values are allocated for each nil pointer. 478 | // 479 | // An exception to this rule is if the value satisfies an interface of 480 | // interest to us (like encoding.TextUnmarshaler). 481 | func indirect(v reflect.Value) reflect.Value { 482 | if v.Kind() != reflect.Ptr { 483 | if v.CanSet() { 484 | pv := v.Addr() 485 | if _, ok := pv.Interface().(TextUnmarshaler); ok { 486 | return pv 487 | } 488 | } 489 | return v 490 | } 491 | if v.IsNil() { 492 | v.Set(reflect.New(v.Type().Elem())) 493 | } 494 | return indirect(reflect.Indirect(v)) 495 | } 496 | 497 | func isUnifiable(rv reflect.Value) bool { 498 | if rv.CanSet() { 499 | return true 500 | } 501 | if _, ok := rv.Interface().(TextUnmarshaler); ok { 502 | return true 503 | } 504 | return false 505 | } 506 | 507 | func badtype(expected string, data interface{}) error { 508 | return e("cannot load TOML value of type %T into a Go %s", data, expected) 509 | } 510 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/decode_meta.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import "strings" 4 | 5 | // MetaData allows access to meta information about TOML data that may not 6 | // be inferrable via reflection. In particular, whether a key has been defined 7 | // and the TOML type of a key. 8 | type MetaData struct { 9 | mapping map[string]interface{} 10 | types map[string]tomlType 11 | keys []Key 12 | decoded map[string]bool 13 | context Key // Used only during decoding. 14 | } 15 | 16 | // IsDefined returns true if the key given exists in the TOML data. The key 17 | // should be specified hierarchially. e.g., 18 | // 19 | // // access the TOML key 'a.b.c' 20 | // IsDefined("a", "b", "c") 21 | // 22 | // IsDefined will return false if an empty key given. Keys are case sensitive. 23 | func (md *MetaData) IsDefined(key ...string) bool { 24 | if len(key) == 0 { 25 | return false 26 | } 27 | 28 | var hash map[string]interface{} 29 | var ok bool 30 | var hashOrVal interface{} = md.mapping 31 | for _, k := range key { 32 | if hash, ok = hashOrVal.(map[string]interface{}); !ok { 33 | return false 34 | } 35 | if hashOrVal, ok = hash[k]; !ok { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | 42 | // Type returns a string representation of the type of the key specified. 43 | // 44 | // Type will return the empty string if given an empty key or a key that 45 | // does not exist. Keys are case sensitive. 46 | func (md *MetaData) Type(key ...string) string { 47 | fullkey := strings.Join(key, ".") 48 | if typ, ok := md.types[fullkey]; ok { 49 | return typ.typeString() 50 | } 51 | return "" 52 | } 53 | 54 | // Key is the type of any TOML key, including key groups. Use (MetaData).Keys 55 | // to get values of this type. 56 | type Key []string 57 | 58 | func (k Key) String() string { 59 | return strings.Join(k, ".") 60 | } 61 | 62 | func (k Key) maybeQuotedAll() string { 63 | var ss []string 64 | for i := range k { 65 | ss = append(ss, k.maybeQuoted(i)) 66 | } 67 | return strings.Join(ss, ".") 68 | } 69 | 70 | func (k Key) maybeQuoted(i int) string { 71 | quote := false 72 | for _, c := range k[i] { 73 | if !isBareKeyChar(c) { 74 | quote = true 75 | break 76 | } 77 | } 78 | if quote { 79 | return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\"" 80 | } 81 | return k[i] 82 | } 83 | 84 | func (k Key) add(piece string) Key { 85 | newKey := make(Key, len(k)+1) 86 | copy(newKey, k) 87 | newKey[len(k)] = piece 88 | return newKey 89 | } 90 | 91 | // Keys returns a slice of every key in the TOML data, including key groups. 92 | // Each key is itself a slice, where the first element is the top of the 93 | // hierarchy and the last is the most specific. 94 | // 95 | // The list will have the same order as the keys appeared in the TOML data. 96 | // 97 | // All keys returned are non-empty. 98 | func (md *MetaData) Keys() []Key { 99 | return md.keys 100 | } 101 | 102 | // Undecoded returns all keys that have not been decoded in the order in which 103 | // they appear in the original TOML document. 104 | // 105 | // This includes keys that haven't been decoded because of a Primitive value. 106 | // Once the Primitive value is decoded, the keys will be considered decoded. 107 | // 108 | // Also note that decoding into an empty interface will result in no decoding, 109 | // and so no keys will be considered decoded. 110 | // 111 | // In this sense, the Undecoded keys correspond to keys in the TOML document 112 | // that do not have a concrete type in your representation. 113 | func (md *MetaData) Undecoded() []Key { 114 | undecoded := make([]Key, 0, len(md.keys)) 115 | for _, key := range md.keys { 116 | if !md.decoded[key.String()] { 117 | undecoded = append(undecoded, key) 118 | } 119 | } 120 | return undecoded 121 | } 122 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package toml provides facilities for decoding and encoding TOML configuration 3 | files via reflection. There is also support for delaying decoding with 4 | the Primitive type, and querying the set of keys in a TOML document with the 5 | MetaData type. 6 | 7 | The specification implemented: https://github.com/toml-lang/toml 8 | 9 | The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify 10 | whether a file is a valid TOML document. It can also be used to print the 11 | type of each key in a TOML document. 12 | 13 | Testing 14 | 15 | There are two important types of tests used for this package. The first is 16 | contained inside '*_test.go' files and uses the standard Go unit testing 17 | framework. These tests are primarily devoted to holistically testing the 18 | decoder and encoder. 19 | 20 | The second type of testing is used to verify the implementation's adherence 21 | to the TOML specification. These tests have been factored into their own 22 | project: https://github.com/BurntSushi/toml-test 23 | 24 | The reason the tests are in a separate project is so that they can be used by 25 | any implementation of TOML. Namely, it is language agnostic. 26 | */ 27 | package toml 28 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/encode.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | "sort" 10 | "strconv" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | type tomlEncodeError struct{ error } 16 | 17 | var ( 18 | errArrayMixedElementTypes = errors.New( 19 | "toml: cannot encode array with mixed element types") 20 | errArrayNilElement = errors.New( 21 | "toml: cannot encode array with nil element") 22 | errNonString = errors.New( 23 | "toml: cannot encode a map with non-string key type") 24 | errAnonNonStruct = errors.New( 25 | "toml: cannot encode an anonymous field that is not a struct") 26 | errArrayNoTable = errors.New( 27 | "toml: TOML array element cannot contain a table") 28 | errNoKey = errors.New( 29 | "toml: top-level values must be Go maps or structs") 30 | errAnything = errors.New("") // used in testing 31 | ) 32 | 33 | var quotedReplacer = strings.NewReplacer( 34 | "\t", "\\t", 35 | "\n", "\\n", 36 | "\r", "\\r", 37 | "\"", "\\\"", 38 | "\\", "\\\\", 39 | ) 40 | 41 | // Encoder controls the encoding of Go values to a TOML document to some 42 | // io.Writer. 43 | // 44 | // The indentation level can be controlled with the Indent field. 45 | type Encoder struct { 46 | // A single indentation level. By default it is two spaces. 47 | Indent string 48 | 49 | // hasWritten is whether we have written any output to w yet. 50 | hasWritten bool 51 | w *bufio.Writer 52 | } 53 | 54 | // NewEncoder returns a TOML encoder that encodes Go values to the io.Writer 55 | // given. By default, a single indentation level is 2 spaces. 56 | func NewEncoder(w io.Writer) *Encoder { 57 | return &Encoder{ 58 | w: bufio.NewWriter(w), 59 | Indent: " ", 60 | } 61 | } 62 | 63 | // Encode writes a TOML representation of the Go value to the underlying 64 | // io.Writer. If the value given cannot be encoded to a valid TOML document, 65 | // then an error is returned. 66 | // 67 | // The mapping between Go values and TOML values should be precisely the same 68 | // as for the Decode* functions. Similarly, the TextMarshaler interface is 69 | // supported by encoding the resulting bytes as strings. (If you want to write 70 | // arbitrary binary data then you will need to use something like base64 since 71 | // TOML does not have any binary types.) 72 | // 73 | // When encoding TOML hashes (i.e., Go maps or structs), keys without any 74 | // sub-hashes are encoded first. 75 | // 76 | // If a Go map is encoded, then its keys are sorted alphabetically for 77 | // deterministic output. More control over this behavior may be provided if 78 | // there is demand for it. 79 | // 80 | // Encoding Go values without a corresponding TOML representation---like map 81 | // types with non-string keys---will cause an error to be returned. Similarly 82 | // for mixed arrays/slices, arrays/slices with nil elements, embedded 83 | // non-struct types and nested slices containing maps or structs. 84 | // (e.g., [][]map[string]string is not allowed but []map[string]string is OK 85 | // and so is []map[string][]string.) 86 | func (enc *Encoder) Encode(v interface{}) error { 87 | rv := eindirect(reflect.ValueOf(v)) 88 | if err := enc.safeEncode(Key([]string{}), rv); err != nil { 89 | return err 90 | } 91 | return enc.w.Flush() 92 | } 93 | 94 | func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { 95 | defer func() { 96 | if r := recover(); r != nil { 97 | if terr, ok := r.(tomlEncodeError); ok { 98 | err = terr.error 99 | return 100 | } 101 | panic(r) 102 | } 103 | }() 104 | enc.encode(key, rv) 105 | return nil 106 | } 107 | 108 | func (enc *Encoder) encode(key Key, rv reflect.Value) { 109 | // Special case. Time needs to be in ISO8601 format. 110 | // Special case. If we can marshal the type to text, then we used that. 111 | // Basically, this prevents the encoder for handling these types as 112 | // generic structs (or whatever the underlying type of a TextMarshaler is). 113 | switch rv.Interface().(type) { 114 | case time.Time, TextMarshaler: 115 | enc.keyEqElement(key, rv) 116 | return 117 | } 118 | 119 | k := rv.Kind() 120 | switch k { 121 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 122 | reflect.Int64, 123 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 124 | reflect.Uint64, 125 | reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: 126 | enc.keyEqElement(key, rv) 127 | case reflect.Array, reflect.Slice: 128 | if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { 129 | enc.eArrayOfTables(key, rv) 130 | } else { 131 | enc.keyEqElement(key, rv) 132 | } 133 | case reflect.Interface: 134 | if rv.IsNil() { 135 | return 136 | } 137 | enc.encode(key, rv.Elem()) 138 | case reflect.Map: 139 | if rv.IsNil() { 140 | return 141 | } 142 | enc.eTable(key, rv) 143 | case reflect.Ptr: 144 | if rv.IsNil() { 145 | return 146 | } 147 | enc.encode(key, rv.Elem()) 148 | case reflect.Struct: 149 | enc.eTable(key, rv) 150 | default: 151 | panic(e("unsupported type for key '%s': %s", key, k)) 152 | } 153 | } 154 | 155 | // eElement encodes any value that can be an array element (primitives and 156 | // arrays). 157 | func (enc *Encoder) eElement(rv reflect.Value) { 158 | switch v := rv.Interface().(type) { 159 | case time.Time: 160 | // Special case time.Time as a primitive. Has to come before 161 | // TextMarshaler below because time.Time implements 162 | // encoding.TextMarshaler, but we need to always use UTC. 163 | enc.wf(v.UTC().Format("2006-01-02T15:04:05Z")) 164 | return 165 | case TextMarshaler: 166 | // Special case. Use text marshaler if it's available for this value. 167 | if s, err := v.MarshalText(); err != nil { 168 | encPanic(err) 169 | } else { 170 | enc.writeQuoted(string(s)) 171 | } 172 | return 173 | } 174 | switch rv.Kind() { 175 | case reflect.Bool: 176 | enc.wf(strconv.FormatBool(rv.Bool())) 177 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 178 | reflect.Int64: 179 | enc.wf(strconv.FormatInt(rv.Int(), 10)) 180 | case reflect.Uint, reflect.Uint8, reflect.Uint16, 181 | reflect.Uint32, reflect.Uint64: 182 | enc.wf(strconv.FormatUint(rv.Uint(), 10)) 183 | case reflect.Float32: 184 | enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32))) 185 | case reflect.Float64: 186 | enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64))) 187 | case reflect.Array, reflect.Slice: 188 | enc.eArrayOrSliceElement(rv) 189 | case reflect.Interface: 190 | enc.eElement(rv.Elem()) 191 | case reflect.String: 192 | enc.writeQuoted(rv.String()) 193 | default: 194 | panic(e("unexpected primitive type: %s", rv.Kind())) 195 | } 196 | } 197 | 198 | // By the TOML spec, all floats must have a decimal with at least one 199 | // number on either side. 200 | func floatAddDecimal(fstr string) string { 201 | if !strings.Contains(fstr, ".") { 202 | return fstr + ".0" 203 | } 204 | return fstr 205 | } 206 | 207 | func (enc *Encoder) writeQuoted(s string) { 208 | enc.wf("\"%s\"", quotedReplacer.Replace(s)) 209 | } 210 | 211 | func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { 212 | length := rv.Len() 213 | enc.wf("[") 214 | for i := 0; i < length; i++ { 215 | elem := rv.Index(i) 216 | enc.eElement(elem) 217 | if i != length-1 { 218 | enc.wf(", ") 219 | } 220 | } 221 | enc.wf("]") 222 | } 223 | 224 | func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { 225 | if len(key) == 0 { 226 | encPanic(errNoKey) 227 | } 228 | for i := 0; i < rv.Len(); i++ { 229 | trv := rv.Index(i) 230 | if isNil(trv) { 231 | continue 232 | } 233 | panicIfInvalidKey(key) 234 | enc.newline() 235 | enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) 236 | enc.newline() 237 | enc.eMapOrStruct(key, trv) 238 | } 239 | } 240 | 241 | func (enc *Encoder) eTable(key Key, rv reflect.Value) { 242 | panicIfInvalidKey(key) 243 | if len(key) == 1 { 244 | // Output an extra newline between top-level tables. 245 | // (The newline isn't written if nothing else has been written though.) 246 | enc.newline() 247 | } 248 | if len(key) > 0 { 249 | enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) 250 | enc.newline() 251 | } 252 | enc.eMapOrStruct(key, rv) 253 | } 254 | 255 | func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) { 256 | switch rv := eindirect(rv); rv.Kind() { 257 | case reflect.Map: 258 | enc.eMap(key, rv) 259 | case reflect.Struct: 260 | enc.eStruct(key, rv) 261 | default: 262 | panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) 263 | } 264 | } 265 | 266 | func (enc *Encoder) eMap(key Key, rv reflect.Value) { 267 | rt := rv.Type() 268 | if rt.Key().Kind() != reflect.String { 269 | encPanic(errNonString) 270 | } 271 | 272 | // Sort keys so that we have deterministic output. And write keys directly 273 | // underneath this key first, before writing sub-structs or sub-maps. 274 | var mapKeysDirect, mapKeysSub []string 275 | for _, mapKey := range rv.MapKeys() { 276 | k := mapKey.String() 277 | if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) { 278 | mapKeysSub = append(mapKeysSub, k) 279 | } else { 280 | mapKeysDirect = append(mapKeysDirect, k) 281 | } 282 | } 283 | 284 | var writeMapKeys = func(mapKeys []string) { 285 | sort.Strings(mapKeys) 286 | for _, mapKey := range mapKeys { 287 | mrv := rv.MapIndex(reflect.ValueOf(mapKey)) 288 | if isNil(mrv) { 289 | // Don't write anything for nil fields. 290 | continue 291 | } 292 | enc.encode(key.add(mapKey), mrv) 293 | } 294 | } 295 | writeMapKeys(mapKeysDirect) 296 | writeMapKeys(mapKeysSub) 297 | } 298 | 299 | func (enc *Encoder) eStruct(key Key, rv reflect.Value) { 300 | // Write keys for fields directly under this key first, because if we write 301 | // a field that creates a new table, then all keys under it will be in that 302 | // table (not the one we're writing here). 303 | rt := rv.Type() 304 | var fieldsDirect, fieldsSub [][]int 305 | var addFields func(rt reflect.Type, rv reflect.Value, start []int) 306 | addFields = func(rt reflect.Type, rv reflect.Value, start []int) { 307 | for i := 0; i < rt.NumField(); i++ { 308 | f := rt.Field(i) 309 | // skip unexported fields 310 | if f.PkgPath != "" && !f.Anonymous { 311 | continue 312 | } 313 | frv := rv.Field(i) 314 | if f.Anonymous { 315 | t := f.Type 316 | switch t.Kind() { 317 | case reflect.Struct: 318 | // Treat anonymous struct fields with 319 | // tag names as though they are not 320 | // anonymous, like encoding/json does. 321 | if getOptions(f.Tag).name == "" { 322 | addFields(t, frv, f.Index) 323 | continue 324 | } 325 | case reflect.Ptr: 326 | if t.Elem().Kind() == reflect.Struct && 327 | getOptions(f.Tag).name == "" { 328 | if !frv.IsNil() { 329 | addFields(t.Elem(), frv.Elem(), f.Index) 330 | } 331 | continue 332 | } 333 | // Fall through to the normal field encoding logic below 334 | // for non-struct anonymous fields. 335 | } 336 | } 337 | 338 | if typeIsHash(tomlTypeOfGo(frv)) { 339 | fieldsSub = append(fieldsSub, append(start, f.Index...)) 340 | } else { 341 | fieldsDirect = append(fieldsDirect, append(start, f.Index...)) 342 | } 343 | } 344 | } 345 | addFields(rt, rv, nil) 346 | 347 | var writeFields = func(fields [][]int) { 348 | for _, fieldIndex := range fields { 349 | sft := rt.FieldByIndex(fieldIndex) 350 | sf := rv.FieldByIndex(fieldIndex) 351 | if isNil(sf) { 352 | // Don't write anything for nil fields. 353 | continue 354 | } 355 | 356 | opts := getOptions(sft.Tag) 357 | if opts.skip { 358 | continue 359 | } 360 | keyName := sft.Name 361 | if opts.name != "" { 362 | keyName = opts.name 363 | } 364 | if opts.omitempty && isEmpty(sf) { 365 | continue 366 | } 367 | if opts.omitzero && isZero(sf) { 368 | continue 369 | } 370 | 371 | enc.encode(key.add(keyName), sf) 372 | } 373 | } 374 | writeFields(fieldsDirect) 375 | writeFields(fieldsSub) 376 | } 377 | 378 | // tomlTypeName returns the TOML type name of the Go value's type. It is 379 | // used to determine whether the types of array elements are mixed (which is 380 | // forbidden). If the Go value is nil, then it is illegal for it to be an array 381 | // element, and valueIsNil is returned as true. 382 | 383 | // Returns the TOML type of a Go value. The type may be `nil`, which means 384 | // no concrete TOML type could be found. 385 | func tomlTypeOfGo(rv reflect.Value) tomlType { 386 | if isNil(rv) || !rv.IsValid() { 387 | return nil 388 | } 389 | switch rv.Kind() { 390 | case reflect.Bool: 391 | return tomlBool 392 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 393 | reflect.Int64, 394 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 395 | reflect.Uint64: 396 | return tomlInteger 397 | case reflect.Float32, reflect.Float64: 398 | return tomlFloat 399 | case reflect.Array, reflect.Slice: 400 | if typeEqual(tomlHash, tomlArrayType(rv)) { 401 | return tomlArrayHash 402 | } 403 | return tomlArray 404 | case reflect.Ptr, reflect.Interface: 405 | return tomlTypeOfGo(rv.Elem()) 406 | case reflect.String: 407 | return tomlString 408 | case reflect.Map: 409 | return tomlHash 410 | case reflect.Struct: 411 | switch rv.Interface().(type) { 412 | case time.Time: 413 | return tomlDatetime 414 | case TextMarshaler: 415 | return tomlString 416 | default: 417 | return tomlHash 418 | } 419 | default: 420 | panic("unexpected reflect.Kind: " + rv.Kind().String()) 421 | } 422 | } 423 | 424 | // tomlArrayType returns the element type of a TOML array. The type returned 425 | // may be nil if it cannot be determined (e.g., a nil slice or a zero length 426 | // slize). This function may also panic if it finds a type that cannot be 427 | // expressed in TOML (such as nil elements, heterogeneous arrays or directly 428 | // nested arrays of tables). 429 | func tomlArrayType(rv reflect.Value) tomlType { 430 | if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { 431 | return nil 432 | } 433 | firstType := tomlTypeOfGo(rv.Index(0)) 434 | if firstType == nil { 435 | encPanic(errArrayNilElement) 436 | } 437 | 438 | rvlen := rv.Len() 439 | for i := 1; i < rvlen; i++ { 440 | elem := rv.Index(i) 441 | switch elemType := tomlTypeOfGo(elem); { 442 | case elemType == nil: 443 | encPanic(errArrayNilElement) 444 | case !typeEqual(firstType, elemType): 445 | encPanic(errArrayMixedElementTypes) 446 | } 447 | } 448 | // If we have a nested array, then we must make sure that the nested 449 | // array contains ONLY primitives. 450 | // This checks arbitrarily nested arrays. 451 | if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) { 452 | nest := tomlArrayType(eindirect(rv.Index(0))) 453 | if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) { 454 | encPanic(errArrayNoTable) 455 | } 456 | } 457 | return firstType 458 | } 459 | 460 | type tagOptions struct { 461 | skip bool // "-" 462 | name string 463 | omitempty bool 464 | omitzero bool 465 | } 466 | 467 | func getOptions(tag reflect.StructTag) tagOptions { 468 | t := tag.Get("toml") 469 | if t == "-" { 470 | return tagOptions{skip: true} 471 | } 472 | var opts tagOptions 473 | parts := strings.Split(t, ",") 474 | opts.name = parts[0] 475 | for _, s := range parts[1:] { 476 | switch s { 477 | case "omitempty": 478 | opts.omitempty = true 479 | case "omitzero": 480 | opts.omitzero = true 481 | } 482 | } 483 | return opts 484 | } 485 | 486 | func isZero(rv reflect.Value) bool { 487 | switch rv.Kind() { 488 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 489 | return rv.Int() == 0 490 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 491 | return rv.Uint() == 0 492 | case reflect.Float32, reflect.Float64: 493 | return rv.Float() == 0.0 494 | } 495 | return false 496 | } 497 | 498 | func isEmpty(rv reflect.Value) bool { 499 | switch rv.Kind() { 500 | case reflect.Array, reflect.Slice, reflect.Map, reflect.String: 501 | return rv.Len() == 0 502 | case reflect.Bool: 503 | return !rv.Bool() 504 | } 505 | return false 506 | } 507 | 508 | func (enc *Encoder) newline() { 509 | if enc.hasWritten { 510 | enc.wf("\n") 511 | } 512 | } 513 | 514 | func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { 515 | if len(key) == 0 { 516 | encPanic(errNoKey) 517 | } 518 | panicIfInvalidKey(key) 519 | enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) 520 | enc.eElement(val) 521 | enc.newline() 522 | } 523 | 524 | func (enc *Encoder) wf(format string, v ...interface{}) { 525 | if _, err := fmt.Fprintf(enc.w, format, v...); err != nil { 526 | encPanic(err) 527 | } 528 | enc.hasWritten = true 529 | } 530 | 531 | func (enc *Encoder) indentStr(key Key) string { 532 | return strings.Repeat(enc.Indent, len(key)-1) 533 | } 534 | 535 | func encPanic(err error) { 536 | panic(tomlEncodeError{err}) 537 | } 538 | 539 | func eindirect(v reflect.Value) reflect.Value { 540 | switch v.Kind() { 541 | case reflect.Ptr, reflect.Interface: 542 | return eindirect(v.Elem()) 543 | default: 544 | return v 545 | } 546 | } 547 | 548 | func isNil(rv reflect.Value) bool { 549 | switch rv.Kind() { 550 | case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 551 | return rv.IsNil() 552 | default: 553 | return false 554 | } 555 | } 556 | 557 | func panicIfInvalidKey(key Key) { 558 | for _, k := range key { 559 | if len(k) == 0 { 560 | encPanic(e("Key '%s' is not a valid table name. Key names "+ 561 | "cannot be empty.", key.maybeQuotedAll())) 562 | } 563 | } 564 | } 565 | 566 | func isValidKeyName(s string) bool { 567 | return len(s) != 0 568 | } 569 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/encoding_types.go: -------------------------------------------------------------------------------- 1 | // +build go1.2 2 | 3 | package toml 4 | 5 | // In order to support Go 1.1, we define our own TextMarshaler and 6 | // TextUnmarshaler types. For Go 1.2+, we just alias them with the 7 | // standard library interfaces. 8 | 9 | import ( 10 | "encoding" 11 | ) 12 | 13 | // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here 14 | // so that Go 1.1 can be supported. 15 | type TextMarshaler encoding.TextMarshaler 16 | 17 | // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined 18 | // here so that Go 1.1 can be supported. 19 | type TextUnmarshaler encoding.TextUnmarshaler 20 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/encoding_types_1.1.go: -------------------------------------------------------------------------------- 1 | // +build !go1.2 2 | 3 | package toml 4 | 5 | // These interfaces were introduced in Go 1.2, so we add them manually when 6 | // compiling for Go 1.1. 7 | 8 | // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here 9 | // so that Go 1.1 can be supported. 10 | type TextMarshaler interface { 11 | MarshalText() (text []byte, err error) 12 | } 13 | 14 | // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined 15 | // here so that Go 1.1 can be supported. 16 | type TextUnmarshaler interface { 17 | UnmarshalText(text []byte) error 18 | } 19 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/parse.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "time" 8 | "unicode" 9 | "unicode/utf8" 10 | ) 11 | 12 | type parser struct { 13 | mapping map[string]interface{} 14 | types map[string]tomlType 15 | lx *lexer 16 | 17 | // A list of keys in the order that they appear in the TOML data. 18 | ordered []Key 19 | 20 | // the full key for the current hash in scope 21 | context Key 22 | 23 | // the base key name for everything except hashes 24 | currentKey string 25 | 26 | // rough approximation of line number 27 | approxLine int 28 | 29 | // A map of 'key.group.names' to whether they were created implicitly. 30 | implicits map[string]bool 31 | } 32 | 33 | type parseError string 34 | 35 | func (pe parseError) Error() string { 36 | return string(pe) 37 | } 38 | 39 | func parse(data string) (p *parser, err error) { 40 | defer func() { 41 | if r := recover(); r != nil { 42 | var ok bool 43 | if err, ok = r.(parseError); ok { 44 | return 45 | } 46 | panic(r) 47 | } 48 | }() 49 | 50 | p = &parser{ 51 | mapping: make(map[string]interface{}), 52 | types: make(map[string]tomlType), 53 | lx: lex(data), 54 | ordered: make([]Key, 0), 55 | implicits: make(map[string]bool), 56 | } 57 | for { 58 | item := p.next() 59 | if item.typ == itemEOF { 60 | break 61 | } 62 | p.topLevel(item) 63 | } 64 | 65 | return p, nil 66 | } 67 | 68 | func (p *parser) panicf(format string, v ...interface{}) { 69 | msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s", 70 | p.approxLine, p.current(), fmt.Sprintf(format, v...)) 71 | panic(parseError(msg)) 72 | } 73 | 74 | func (p *parser) next() item { 75 | it := p.lx.nextItem() 76 | if it.typ == itemError { 77 | p.panicf("%s", it.val) 78 | } 79 | return it 80 | } 81 | 82 | func (p *parser) bug(format string, v ...interface{}) { 83 | panic(fmt.Sprintf("BUG: "+format+"\n\n", v...)) 84 | } 85 | 86 | func (p *parser) expect(typ itemType) item { 87 | it := p.next() 88 | p.assertEqual(typ, it.typ) 89 | return it 90 | } 91 | 92 | func (p *parser) assertEqual(expected, got itemType) { 93 | if expected != got { 94 | p.bug("Expected '%s' but got '%s'.", expected, got) 95 | } 96 | } 97 | 98 | func (p *parser) topLevel(item item) { 99 | switch item.typ { 100 | case itemCommentStart: 101 | p.approxLine = item.line 102 | p.expect(itemText) 103 | case itemTableStart: 104 | kg := p.next() 105 | p.approxLine = kg.line 106 | 107 | var key Key 108 | for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() { 109 | key = append(key, p.keyString(kg)) 110 | } 111 | p.assertEqual(itemTableEnd, kg.typ) 112 | 113 | p.establishContext(key, false) 114 | p.setType("", tomlHash) 115 | p.ordered = append(p.ordered, key) 116 | case itemArrayTableStart: 117 | kg := p.next() 118 | p.approxLine = kg.line 119 | 120 | var key Key 121 | for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() { 122 | key = append(key, p.keyString(kg)) 123 | } 124 | p.assertEqual(itemArrayTableEnd, kg.typ) 125 | 126 | p.establishContext(key, true) 127 | p.setType("", tomlArrayHash) 128 | p.ordered = append(p.ordered, key) 129 | case itemKeyStart: 130 | kname := p.next() 131 | p.approxLine = kname.line 132 | p.currentKey = p.keyString(kname) 133 | 134 | val, typ := p.value(p.next()) 135 | p.setValue(p.currentKey, val) 136 | p.setType(p.currentKey, typ) 137 | p.ordered = append(p.ordered, p.context.add(p.currentKey)) 138 | p.currentKey = "" 139 | default: 140 | p.bug("Unexpected type at top level: %s", item.typ) 141 | } 142 | } 143 | 144 | // Gets a string for a key (or part of a key in a table name). 145 | func (p *parser) keyString(it item) string { 146 | switch it.typ { 147 | case itemText: 148 | return it.val 149 | case itemString, itemMultilineString, 150 | itemRawString, itemRawMultilineString: 151 | s, _ := p.value(it) 152 | return s.(string) 153 | default: 154 | p.bug("Unexpected key type: %s", it.typ) 155 | panic("unreachable") 156 | } 157 | } 158 | 159 | // value translates an expected value from the lexer into a Go value wrapped 160 | // as an empty interface. 161 | func (p *parser) value(it item) (interface{}, tomlType) { 162 | switch it.typ { 163 | case itemString: 164 | return p.replaceEscapes(it.val), p.typeOfPrimitive(it) 165 | case itemMultilineString: 166 | trimmed := stripFirstNewline(stripEscapedWhitespace(it.val)) 167 | return p.replaceEscapes(trimmed), p.typeOfPrimitive(it) 168 | case itemRawString: 169 | return it.val, p.typeOfPrimitive(it) 170 | case itemRawMultilineString: 171 | return stripFirstNewline(it.val), p.typeOfPrimitive(it) 172 | case itemBool: 173 | switch it.val { 174 | case "true": 175 | return true, p.typeOfPrimitive(it) 176 | case "false": 177 | return false, p.typeOfPrimitive(it) 178 | } 179 | p.bug("Expected boolean value, but got '%s'.", it.val) 180 | case itemInteger: 181 | if !numUnderscoresOK(it.val) { 182 | p.panicf("Invalid integer %q: underscores must be surrounded by digits", 183 | it.val) 184 | } 185 | val := strings.Replace(it.val, "_", "", -1) 186 | num, err := strconv.ParseInt(val, 10, 64) 187 | if err != nil { 188 | // Distinguish integer values. Normally, it'd be a bug if the lexer 189 | // provides an invalid integer, but it's possible that the number is 190 | // out of range of valid values (which the lexer cannot determine). 191 | // So mark the former as a bug but the latter as a legitimate user 192 | // error. 193 | if e, ok := err.(*strconv.NumError); ok && 194 | e.Err == strconv.ErrRange { 195 | 196 | p.panicf("Integer '%s' is out of the range of 64-bit "+ 197 | "signed integers.", it.val) 198 | } else { 199 | p.bug("Expected integer value, but got '%s'.", it.val) 200 | } 201 | } 202 | return num, p.typeOfPrimitive(it) 203 | case itemFloat: 204 | parts := strings.FieldsFunc(it.val, func(r rune) bool { 205 | switch r { 206 | case '.', 'e', 'E': 207 | return true 208 | } 209 | return false 210 | }) 211 | for _, part := range parts { 212 | if !numUnderscoresOK(part) { 213 | p.panicf("Invalid float %q: underscores must be "+ 214 | "surrounded by digits", it.val) 215 | } 216 | } 217 | if !numPeriodsOK(it.val) { 218 | // As a special case, numbers like '123.' or '1.e2', 219 | // which are valid as far as Go/strconv are concerned, 220 | // must be rejected because TOML says that a fractional 221 | // part consists of '.' followed by 1+ digits. 222 | p.panicf("Invalid float %q: '.' must be followed "+ 223 | "by one or more digits", it.val) 224 | } 225 | val := strings.Replace(it.val, "_", "", -1) 226 | num, err := strconv.ParseFloat(val, 64) 227 | if err != nil { 228 | if e, ok := err.(*strconv.NumError); ok && 229 | e.Err == strconv.ErrRange { 230 | 231 | p.panicf("Float '%s' is out of the range of 64-bit "+ 232 | "IEEE-754 floating-point numbers.", it.val) 233 | } else { 234 | p.panicf("Invalid float value: %q", it.val) 235 | } 236 | } 237 | return num, p.typeOfPrimitive(it) 238 | case itemDatetime: 239 | var t time.Time 240 | var ok bool 241 | var err error 242 | for _, format := range []string{ 243 | "2006-01-02T15:04:05Z07:00", 244 | "2006-01-02T15:04:05", 245 | "2006-01-02", 246 | } { 247 | t, err = time.ParseInLocation(format, it.val, time.Local) 248 | if err == nil { 249 | ok = true 250 | break 251 | } 252 | } 253 | if !ok { 254 | p.panicf("Invalid TOML Datetime: %q.", it.val) 255 | } 256 | return t, p.typeOfPrimitive(it) 257 | case itemArray: 258 | array := make([]interface{}, 0) 259 | types := make([]tomlType, 0) 260 | 261 | for it = p.next(); it.typ != itemArrayEnd; it = p.next() { 262 | if it.typ == itemCommentStart { 263 | p.expect(itemText) 264 | continue 265 | } 266 | 267 | val, typ := p.value(it) 268 | array = append(array, val) 269 | types = append(types, typ) 270 | } 271 | return array, p.typeOfArray(types) 272 | case itemInlineTableStart: 273 | var ( 274 | hash = make(map[string]interface{}) 275 | outerContext = p.context 276 | outerKey = p.currentKey 277 | ) 278 | 279 | p.context = append(p.context, p.currentKey) 280 | p.currentKey = "" 281 | for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { 282 | if it.typ != itemKeyStart { 283 | p.bug("Expected key start but instead found %q, around line %d", 284 | it.val, p.approxLine) 285 | } 286 | if it.typ == itemCommentStart { 287 | p.expect(itemText) 288 | continue 289 | } 290 | 291 | // retrieve key 292 | k := p.next() 293 | p.approxLine = k.line 294 | kname := p.keyString(k) 295 | 296 | // retrieve value 297 | p.currentKey = kname 298 | val, typ := p.value(p.next()) 299 | // make sure we keep metadata up to date 300 | p.setType(kname, typ) 301 | p.ordered = append(p.ordered, p.context.add(p.currentKey)) 302 | hash[kname] = val 303 | } 304 | p.context = outerContext 305 | p.currentKey = outerKey 306 | return hash, tomlHash 307 | } 308 | p.bug("Unexpected value type: %s", it.typ) 309 | panic("unreachable") 310 | } 311 | 312 | // numUnderscoresOK checks whether each underscore in s is surrounded by 313 | // characters that are not underscores. 314 | func numUnderscoresOK(s string) bool { 315 | accept := false 316 | for _, r := range s { 317 | if r == '_' { 318 | if !accept { 319 | return false 320 | } 321 | accept = false 322 | continue 323 | } 324 | accept = true 325 | } 326 | return accept 327 | } 328 | 329 | // numPeriodsOK checks whether every period in s is followed by a digit. 330 | func numPeriodsOK(s string) bool { 331 | period := false 332 | for _, r := range s { 333 | if period && !isDigit(r) { 334 | return false 335 | } 336 | period = r == '.' 337 | } 338 | return !period 339 | } 340 | 341 | // establishContext sets the current context of the parser, 342 | // where the context is either a hash or an array of hashes. Which one is 343 | // set depends on the value of the `array` parameter. 344 | // 345 | // Establishing the context also makes sure that the key isn't a duplicate, and 346 | // will create implicit hashes automatically. 347 | func (p *parser) establishContext(key Key, array bool) { 348 | var ok bool 349 | 350 | // Always start at the top level and drill down for our context. 351 | hashContext := p.mapping 352 | keyContext := make(Key, 0) 353 | 354 | // We only need implicit hashes for key[0:-1] 355 | for _, k := range key[0 : len(key)-1] { 356 | _, ok = hashContext[k] 357 | keyContext = append(keyContext, k) 358 | 359 | // No key? Make an implicit hash and move on. 360 | if !ok { 361 | p.addImplicit(keyContext) 362 | hashContext[k] = make(map[string]interface{}) 363 | } 364 | 365 | // If the hash context is actually an array of tables, then set 366 | // the hash context to the last element in that array. 367 | // 368 | // Otherwise, it better be a table, since this MUST be a key group (by 369 | // virtue of it not being the last element in a key). 370 | switch t := hashContext[k].(type) { 371 | case []map[string]interface{}: 372 | hashContext = t[len(t)-1] 373 | case map[string]interface{}: 374 | hashContext = t 375 | default: 376 | p.panicf("Key '%s' was already created as a hash.", keyContext) 377 | } 378 | } 379 | 380 | p.context = keyContext 381 | if array { 382 | // If this is the first element for this array, then allocate a new 383 | // list of tables for it. 384 | k := key[len(key)-1] 385 | if _, ok := hashContext[k]; !ok { 386 | hashContext[k] = make([]map[string]interface{}, 0, 5) 387 | } 388 | 389 | // Add a new table. But make sure the key hasn't already been used 390 | // for something else. 391 | if hash, ok := hashContext[k].([]map[string]interface{}); ok { 392 | hashContext[k] = append(hash, make(map[string]interface{})) 393 | } else { 394 | p.panicf("Key '%s' was already created and cannot be used as "+ 395 | "an array.", keyContext) 396 | } 397 | } else { 398 | p.setValue(key[len(key)-1], make(map[string]interface{})) 399 | } 400 | p.context = append(p.context, key[len(key)-1]) 401 | } 402 | 403 | // setValue sets the given key to the given value in the current context. 404 | // It will make sure that the key hasn't already been defined, account for 405 | // implicit key groups. 406 | func (p *parser) setValue(key string, value interface{}) { 407 | var tmpHash interface{} 408 | var ok bool 409 | 410 | hash := p.mapping 411 | keyContext := make(Key, 0) 412 | for _, k := range p.context { 413 | keyContext = append(keyContext, k) 414 | if tmpHash, ok = hash[k]; !ok { 415 | p.bug("Context for key '%s' has not been established.", keyContext) 416 | } 417 | switch t := tmpHash.(type) { 418 | case []map[string]interface{}: 419 | // The context is a table of hashes. Pick the most recent table 420 | // defined as the current hash. 421 | hash = t[len(t)-1] 422 | case map[string]interface{}: 423 | hash = t 424 | default: 425 | p.bug("Expected hash to have type 'map[string]interface{}', but "+ 426 | "it has '%T' instead.", tmpHash) 427 | } 428 | } 429 | keyContext = append(keyContext, key) 430 | 431 | if _, ok := hash[key]; ok { 432 | // Typically, if the given key has already been set, then we have 433 | // to raise an error since duplicate keys are disallowed. However, 434 | // it's possible that a key was previously defined implicitly. In this 435 | // case, it is allowed to be redefined concretely. (See the 436 | // `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.) 437 | // 438 | // But we have to make sure to stop marking it as an implicit. (So that 439 | // another redefinition provokes an error.) 440 | // 441 | // Note that since it has already been defined (as a hash), we don't 442 | // want to overwrite it. So our business is done. 443 | if p.isImplicit(keyContext) { 444 | p.removeImplicit(keyContext) 445 | return 446 | } 447 | 448 | // Otherwise, we have a concrete key trying to override a previous 449 | // key, which is *always* wrong. 450 | p.panicf("Key '%s' has already been defined.", keyContext) 451 | } 452 | hash[key] = value 453 | } 454 | 455 | // setType sets the type of a particular value at a given key. 456 | // It should be called immediately AFTER setValue. 457 | // 458 | // Note that if `key` is empty, then the type given will be applied to the 459 | // current context (which is either a table or an array of tables). 460 | func (p *parser) setType(key string, typ tomlType) { 461 | keyContext := make(Key, 0, len(p.context)+1) 462 | for _, k := range p.context { 463 | keyContext = append(keyContext, k) 464 | } 465 | if len(key) > 0 { // allow type setting for hashes 466 | keyContext = append(keyContext, key) 467 | } 468 | p.types[keyContext.String()] = typ 469 | } 470 | 471 | // addImplicit sets the given Key as having been created implicitly. 472 | func (p *parser) addImplicit(key Key) { 473 | p.implicits[key.String()] = true 474 | } 475 | 476 | // removeImplicit stops tagging the given key as having been implicitly 477 | // created. 478 | func (p *parser) removeImplicit(key Key) { 479 | p.implicits[key.String()] = false 480 | } 481 | 482 | // isImplicit returns true if the key group pointed to by the key was created 483 | // implicitly. 484 | func (p *parser) isImplicit(key Key) bool { 485 | return p.implicits[key.String()] 486 | } 487 | 488 | // current returns the full key name of the current context. 489 | func (p *parser) current() string { 490 | if len(p.currentKey) == 0 { 491 | return p.context.String() 492 | } 493 | if len(p.context) == 0 { 494 | return p.currentKey 495 | } 496 | return fmt.Sprintf("%s.%s", p.context, p.currentKey) 497 | } 498 | 499 | func stripFirstNewline(s string) string { 500 | if len(s) == 0 || s[0] != '\n' { 501 | return s 502 | } 503 | return s[1:] 504 | } 505 | 506 | func stripEscapedWhitespace(s string) string { 507 | esc := strings.Split(s, "\\\n") 508 | if len(esc) > 1 { 509 | for i := 1; i < len(esc); i++ { 510 | esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace) 511 | } 512 | } 513 | return strings.Join(esc, "") 514 | } 515 | 516 | func (p *parser) replaceEscapes(str string) string { 517 | var replaced []rune 518 | s := []byte(str) 519 | r := 0 520 | for r < len(s) { 521 | if s[r] != '\\' { 522 | c, size := utf8.DecodeRune(s[r:]) 523 | r += size 524 | replaced = append(replaced, c) 525 | continue 526 | } 527 | r += 1 528 | if r >= len(s) { 529 | p.bug("Escape sequence at end of string.") 530 | return "" 531 | } 532 | switch s[r] { 533 | default: 534 | p.bug("Expected valid escape code after \\, but got %q.", s[r]) 535 | return "" 536 | case 'b': 537 | replaced = append(replaced, rune(0x0008)) 538 | r += 1 539 | case 't': 540 | replaced = append(replaced, rune(0x0009)) 541 | r += 1 542 | case 'n': 543 | replaced = append(replaced, rune(0x000A)) 544 | r += 1 545 | case 'f': 546 | replaced = append(replaced, rune(0x000C)) 547 | r += 1 548 | case 'r': 549 | replaced = append(replaced, rune(0x000D)) 550 | r += 1 551 | case '"': 552 | replaced = append(replaced, rune(0x0022)) 553 | r += 1 554 | case '\\': 555 | replaced = append(replaced, rune(0x005C)) 556 | r += 1 557 | case 'u': 558 | // At this point, we know we have a Unicode escape of the form 559 | // `uXXXX` at [r, r+5). (Because the lexer guarantees this 560 | // for us.) 561 | escaped := p.asciiEscapeToUnicode(s[r+1 : r+5]) 562 | replaced = append(replaced, escaped) 563 | r += 5 564 | case 'U': 565 | // At this point, we know we have a Unicode escape of the form 566 | // `uXXXX` at [r, r+9). (Because the lexer guarantees this 567 | // for us.) 568 | escaped := p.asciiEscapeToUnicode(s[r+1 : r+9]) 569 | replaced = append(replaced, escaped) 570 | r += 9 571 | } 572 | } 573 | return string(replaced) 574 | } 575 | 576 | func (p *parser) asciiEscapeToUnicode(bs []byte) rune { 577 | s := string(bs) 578 | hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) 579 | if err != nil { 580 | p.bug("Could not parse '%s' as a hexadecimal number, but the "+ 581 | "lexer claims it's OK: %s", s, err) 582 | } 583 | if !utf8.ValidRune(rune(hex)) { 584 | p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s) 585 | } 586 | return rune(hex) 587 | } 588 | 589 | func isStringType(ty itemType) bool { 590 | return ty == itemString || ty == itemMultilineString || 591 | ty == itemRawString || ty == itemRawMultilineString 592 | } 593 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/session.vim: -------------------------------------------------------------------------------- 1 | au BufWritePost *.go silent!make tags > /dev/null 2>&1 2 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/type_check.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | // tomlType represents any Go type that corresponds to a TOML type. 4 | // While the first draft of the TOML spec has a simplistic type system that 5 | // probably doesn't need this level of sophistication, we seem to be militating 6 | // toward adding real composite types. 7 | type tomlType interface { 8 | typeString() string 9 | } 10 | 11 | // typeEqual accepts any two types and returns true if they are equal. 12 | func typeEqual(t1, t2 tomlType) bool { 13 | if t1 == nil || t2 == nil { 14 | return false 15 | } 16 | return t1.typeString() == t2.typeString() 17 | } 18 | 19 | func typeIsHash(t tomlType) bool { 20 | return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash) 21 | } 22 | 23 | type tomlBaseType string 24 | 25 | func (btype tomlBaseType) typeString() string { 26 | return string(btype) 27 | } 28 | 29 | func (btype tomlBaseType) String() string { 30 | return btype.typeString() 31 | } 32 | 33 | var ( 34 | tomlInteger tomlBaseType = "Integer" 35 | tomlFloat tomlBaseType = "Float" 36 | tomlDatetime tomlBaseType = "Datetime" 37 | tomlString tomlBaseType = "String" 38 | tomlBool tomlBaseType = "Bool" 39 | tomlArray tomlBaseType = "Array" 40 | tomlHash tomlBaseType = "Hash" 41 | tomlArrayHash tomlBaseType = "ArrayHash" 42 | ) 43 | 44 | // typeOfPrimitive returns a tomlType of any primitive value in TOML. 45 | // Primitive values are: Integer, Float, Datetime, String and Bool. 46 | // 47 | // Passing a lexer item other than the following will cause a BUG message 48 | // to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime. 49 | func (p *parser) typeOfPrimitive(lexItem item) tomlType { 50 | switch lexItem.typ { 51 | case itemInteger: 52 | return tomlInteger 53 | case itemFloat: 54 | return tomlFloat 55 | case itemDatetime: 56 | return tomlDatetime 57 | case itemString: 58 | return tomlString 59 | case itemMultilineString: 60 | return tomlString 61 | case itemRawString: 62 | return tomlString 63 | case itemRawMultilineString: 64 | return tomlString 65 | case itemBool: 66 | return tomlBool 67 | } 68 | p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) 69 | panic("unreachable") 70 | } 71 | 72 | // typeOfArray returns a tomlType for an array given a list of types of its 73 | // values. 74 | // 75 | // In the current spec, if an array is homogeneous, then its type is always 76 | // "Array". If the array is not homogeneous, an error is generated. 77 | func (p *parser) typeOfArray(types []tomlType) tomlType { 78 | // Empty arrays are cool. 79 | if len(types) == 0 { 80 | return tomlArray 81 | } 82 | 83 | theType := types[0] 84 | for _, t := range types[1:] { 85 | if !typeEqual(theType, t) { 86 | p.panicf("Array contains values of type '%s' and '%s', but "+ 87 | "arrays must be homogeneous.", theType, t) 88 | } 89 | } 90 | return tomlArray 91 | } 92 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/type_fields.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | // Struct field handling is adapted from code in encoding/json: 4 | // 5 | // Copyright 2010 The Go Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the Go distribution. 8 | 9 | import ( 10 | "reflect" 11 | "sort" 12 | "sync" 13 | ) 14 | 15 | // A field represents a single field found in a struct. 16 | type field struct { 17 | name string // the name of the field (`toml` tag included) 18 | tag bool // whether field has a `toml` tag 19 | index []int // represents the depth of an anonymous field 20 | typ reflect.Type // the type of the field 21 | } 22 | 23 | // byName sorts field by name, breaking ties with depth, 24 | // then breaking ties with "name came from toml tag", then 25 | // breaking ties with index sequence. 26 | type byName []field 27 | 28 | func (x byName) Len() int { return len(x) } 29 | 30 | func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 31 | 32 | func (x byName) Less(i, j int) bool { 33 | if x[i].name != x[j].name { 34 | return x[i].name < x[j].name 35 | } 36 | if len(x[i].index) != len(x[j].index) { 37 | return len(x[i].index) < len(x[j].index) 38 | } 39 | if x[i].tag != x[j].tag { 40 | return x[i].tag 41 | } 42 | return byIndex(x).Less(i, j) 43 | } 44 | 45 | // byIndex sorts field by index sequence. 46 | type byIndex []field 47 | 48 | func (x byIndex) Len() int { return len(x) } 49 | 50 | func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 51 | 52 | func (x byIndex) Less(i, j int) bool { 53 | for k, xik := range x[i].index { 54 | if k >= len(x[j].index) { 55 | return false 56 | } 57 | if xik != x[j].index[k] { 58 | return xik < x[j].index[k] 59 | } 60 | } 61 | return len(x[i].index) < len(x[j].index) 62 | } 63 | 64 | // typeFields returns a list of fields that TOML should recognize for the given 65 | // type. The algorithm is breadth-first search over the set of structs to 66 | // include - the top struct and then any reachable anonymous structs. 67 | func typeFields(t reflect.Type) []field { 68 | // Anonymous fields to explore at the current level and the next. 69 | current := []field{} 70 | next := []field{{typ: t}} 71 | 72 | // Count of queued names for current level and the next. 73 | count := map[reflect.Type]int{} 74 | nextCount := map[reflect.Type]int{} 75 | 76 | // Types already visited at an earlier level. 77 | visited := map[reflect.Type]bool{} 78 | 79 | // Fields found. 80 | var fields []field 81 | 82 | for len(next) > 0 { 83 | current, next = next, current[:0] 84 | count, nextCount = nextCount, map[reflect.Type]int{} 85 | 86 | for _, f := range current { 87 | if visited[f.typ] { 88 | continue 89 | } 90 | visited[f.typ] = true 91 | 92 | // Scan f.typ for fields to include. 93 | for i := 0; i < f.typ.NumField(); i++ { 94 | sf := f.typ.Field(i) 95 | if sf.PkgPath != "" && !sf.Anonymous { // unexported 96 | continue 97 | } 98 | opts := getOptions(sf.Tag) 99 | if opts.skip { 100 | continue 101 | } 102 | index := make([]int, len(f.index)+1) 103 | copy(index, f.index) 104 | index[len(f.index)] = i 105 | 106 | ft := sf.Type 107 | if ft.Name() == "" && ft.Kind() == reflect.Ptr { 108 | // Follow pointer. 109 | ft = ft.Elem() 110 | } 111 | 112 | // Record found field and index sequence. 113 | if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { 114 | tagged := opts.name != "" 115 | name := opts.name 116 | if name == "" { 117 | name = sf.Name 118 | } 119 | fields = append(fields, field{name, tagged, index, ft}) 120 | if count[f.typ] > 1 { 121 | // If there were multiple instances, add a second, 122 | // so that the annihilation code will see a duplicate. 123 | // It only cares about the distinction between 1 or 2, 124 | // so don't bother generating any more copies. 125 | fields = append(fields, fields[len(fields)-1]) 126 | } 127 | continue 128 | } 129 | 130 | // Record new anonymous struct to explore in next round. 131 | nextCount[ft]++ 132 | if nextCount[ft] == 1 { 133 | f := field{name: ft.Name(), index: index, typ: ft} 134 | next = append(next, f) 135 | } 136 | } 137 | } 138 | } 139 | 140 | sort.Sort(byName(fields)) 141 | 142 | // Delete all fields that are hidden by the Go rules for embedded fields, 143 | // except that fields with TOML tags are promoted. 144 | 145 | // The fields are sorted in primary order of name, secondary order 146 | // of field index length. Loop over names; for each name, delete 147 | // hidden fields by choosing the one dominant field that survives. 148 | out := fields[:0] 149 | for advance, i := 0, 0; i < len(fields); i += advance { 150 | // One iteration per name. 151 | // Find the sequence of fields with the name of this first field. 152 | fi := fields[i] 153 | name := fi.name 154 | for advance = 1; i+advance < len(fields); advance++ { 155 | fj := fields[i+advance] 156 | if fj.name != name { 157 | break 158 | } 159 | } 160 | if advance == 1 { // Only one field with this name 161 | out = append(out, fi) 162 | continue 163 | } 164 | dominant, ok := dominantField(fields[i : i+advance]) 165 | if ok { 166 | out = append(out, dominant) 167 | } 168 | } 169 | 170 | fields = out 171 | sort.Sort(byIndex(fields)) 172 | 173 | return fields 174 | } 175 | 176 | // dominantField looks through the fields, all of which are known to 177 | // have the same name, to find the single field that dominates the 178 | // others using Go's embedding rules, modified by the presence of 179 | // TOML tags. If there are multiple top-level fields, the boolean 180 | // will be false: This condition is an error in Go and we skip all 181 | // the fields. 182 | func dominantField(fields []field) (field, bool) { 183 | // The fields are sorted in increasing index-length order. The winner 184 | // must therefore be one with the shortest index length. Drop all 185 | // longer entries, which is easy: just truncate the slice. 186 | length := len(fields[0].index) 187 | tagged := -1 // Index of first tagged field. 188 | for i, f := range fields { 189 | if len(f.index) > length { 190 | fields = fields[:i] 191 | break 192 | } 193 | if f.tag { 194 | if tagged >= 0 { 195 | // Multiple tagged fields at the same level: conflict. 196 | // Return no field. 197 | return field{}, false 198 | } 199 | tagged = i 200 | } 201 | } 202 | if tagged >= 0 { 203 | return fields[tagged], true 204 | } 205 | // All remaining fields have the same length. If there's more than one, 206 | // we have a conflict (two fields named "X" at the same level) and we 207 | // return no field. 208 | if len(fields) > 1 { 209 | return field{}, false 210 | } 211 | return fields[0], true 212 | } 213 | 214 | var fieldCache struct { 215 | sync.RWMutex 216 | m map[reflect.Type][]field 217 | } 218 | 219 | // cachedTypeFields is like typeFields but uses a cache to avoid repeated work. 220 | func cachedTypeFields(t reflect.Type) []field { 221 | fieldCache.RLock() 222 | f := fieldCache.m[t] 223 | fieldCache.RUnlock() 224 | if f != nil { 225 | return f 226 | } 227 | 228 | // Compute fields without lock. 229 | // Might duplicate effort but won't hold other computations back. 230 | f = typeFields(t) 231 | if f == nil { 232 | f = []field{} 233 | } 234 | 235 | fieldCache.Lock() 236 | if fieldCache.m == nil { 237 | fieldCache.m = map[reflect.Type][]field{} 238 | } 239 | fieldCache.m[t] = f 240 | fieldCache.Unlock() 241 | return f 242 | } 243 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | *.swp 3 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | ![Status](https://github.com/elazarl/goproxy/workflows/Go/badge.svg) 6 | 7 | Package goproxy provides a customizable HTTP proxy library for Go (golang), 8 | 9 | It supports regular HTTP proxy, HTTPS through CONNECT, and "hijacking" HTTPS 10 | connection using "Man in the Middle" style attack. 11 | 12 | The intent of the proxy is to be usable with reasonable amount of traffic, 13 | yet customizable and programmable. 14 | 15 | The proxy itself is simply a `net/http` handler. 16 | 17 | In order to use goproxy, one should set their browser to use goproxy as an HTTP 18 | proxy. Here is how you do that [in Chrome](https://support.google.com/chrome/answer/96815?hl=en) 19 | and [in Firefox](http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox). 20 | 21 | For example, the URL you should use as proxy when running `./bin/basic` is 22 | `localhost:8080`, as this is the default binding for the basic proxy. 23 | 24 | ## Mailing List 25 | 26 | New features will be discussed on the [mailing list](https://groups.google.com/forum/#!forum/goproxy-dev) 27 | before their development. 28 | 29 | ## Latest Stable Release 30 | 31 | Get the latest goproxy from `gopkg.in/elazarl/goproxy.v1`. 32 | 33 | # Why not Fiddler2? 34 | 35 | Fiddler is an excellent software with similar intent. However, Fiddler is not 36 | as customizable as goproxy intends to be. The main difference is, Fiddler is not 37 | intended to be used as a real proxy. 38 | 39 | A possible use case that suits goproxy but 40 | not Fiddler, is gathering statistics on page load times for a certain website over a week. 41 | With goproxy you could ask all your users to set their proxy to a dedicated machine running a 42 | goproxy server. Fiddler is a GUI app not designed to be run like a server for multiple users. 43 | 44 | # A taste of goproxy 45 | 46 | To get a taste of `goproxy`, a basic HTTP/HTTPS transparent proxy 47 | 48 | ```go 49 | package main 50 | 51 | import ( 52 | "github.com/elazarl/goproxy" 53 | "log" 54 | "net/http" 55 | ) 56 | 57 | func main() { 58 | proxy := goproxy.NewProxyHttpServer() 59 | proxy.Verbose = true 60 | log.Fatal(http.ListenAndServe(":8080", proxy)) 61 | } 62 | ``` 63 | 64 | This line will add `X-GoProxy: yxorPoG-X` header to all requests sent through the proxy 65 | 66 | ```go 67 | proxy.OnRequest().DoFunc( 68 | func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { 69 | r.Header.Set("X-GoProxy","yxorPoG-X") 70 | return r,nil 71 | }) 72 | ``` 73 | 74 | `DoFunc` will process all incoming requests to the proxy. It will add a header to the request 75 | and return it. The proxy will send the modified request. 76 | 77 | Note that we returned nil value as the response. Had we returned a response, goproxy would 78 | have discarded the request and sent the new response to the client. 79 | 80 | In order to refuse connections to reddit at work time 81 | 82 | ```go 83 | proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc( 84 | func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { 85 | if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 { 86 | return r,goproxy.NewResponse(r, 87 | goproxy.ContentTypeText,http.StatusForbidden, 88 | "Don't waste your time!") 89 | } 90 | return r,nil 91 | }) 92 | ``` 93 | 94 | `DstHostIs` returns a `ReqCondition`, that is a function receiving a `Request` and returning a boolean. 95 | We will only process requests that match the condition. `DstHostIs("www.reddit.com")` will return 96 | a `ReqCondition` accepting only requests directed to "www.reddit.com". 97 | 98 | `DoFunc` will receive a function that will preprocess the request. We can change the request, or 99 | return a response. If the time is between 8:00am and 17:00pm, we will reject the request, and 100 | return a precanned text response saying "do not waste your time". 101 | 102 | See additional examples in the examples directory. 103 | 104 | 105 | # Type of handlers for manipulating connect/req/resp behavior 106 | 107 | There are 3 kinds of useful handlers to manipulate the behavior, as follows: 108 | 109 | ```go 110 | // handler called after receiving HTTP CONNECT from the client, and before proxy establish connection 111 | // with destination host 112 | httpsHandlers []HttpsHandler 113 | 114 | // handler called before proxy send HTTP request to destination host 115 | reqHandlers []ReqHandler 116 | 117 | // handler called after proxy receives HTTP Response from destination host, and before proxy forward 118 | // the Response to the client. 119 | respHandlers []RespHandler 120 | ``` 121 | 122 | Depending on what you want to manipulate, the ways to add handlers to each handler list are: 123 | 124 | ```go 125 | // Add handlers to httpsHandlers 126 | proxy.OnRequest(Some ReqConditions).HandleConnect(YourHandlerFunc()) 127 | 128 | // Add handlers to reqHandlers 129 | proxy.OnRequest(Some ReqConditions).Do(YourReqHandlerFunc()) 130 | 131 | // Add handlers to respHandlers 132 | proxy.OnResponse(Some RespConditions).Do(YourRespHandlerFunc()) 133 | ``` 134 | 135 | For example: 136 | 137 | ```go 138 | // This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase 139 | proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("reddit.*:443$"))).HandleConnect(goproxy.RejectConnect) 140 | 141 | // This will NOT reject the HTTPS request with URL ending with gif, due to the fact that proxy 142 | // only got the URL.Hostname and URL.Port during the HTTP CONNECT phase if the scheme is HTTPS, which is 143 | // quiet common these days. 144 | proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.RejectConnect) 145 | 146 | // The correct way to manipulate the HTTP request using URL.Path as condition is: 147 | proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc()) 148 | ``` 149 | 150 | # What's New 151 | 152 | 1. Ability to `Hijack` CONNECT requests. See 153 | [the eavesdropper example](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-eavesdropper/main.go#L27) 154 | 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) 155 | 156 | # License 157 | 158 | I put the software temporarily under the Go-compatible BSD license. 159 | If this prevents someone from using the software, do let me know and I'll consider changing it. 160 | 161 | At any rate, user feedback is very important for me, so I'll be delighted to know if you're using this package. 162 | 163 | # Beta Software 164 | 165 | I've received positive feedback from a few people who use goproxy in production settings. 166 | I believe it is good enough for usage. 167 | 168 | I'll try to keep reasonable backwards compatibility. In case of a major API change, 169 | I'll change the import path. 170 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | // StatusCodeIs returns a RespCondition, testing whether or not the HTTP status 165 | // code is one of the given ints 166 | func StatusCodeIs(codes ...int) RespCondition { 167 | codeSet := make(map[int]bool) 168 | for _, c := range codes { 169 | codeSet[c] = true 170 | } 171 | return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool { 172 | if resp == nil { 173 | return false 174 | } 175 | _, codeMatch := codeSet[resp.StatusCode] 176 | return codeMatch 177 | }) 178 | } 179 | 180 | // ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions. 181 | // You will use the ReqProxyConds struct to register a ReqHandler, that would filter 182 | // the request, only if all the given ReqCondition matched. 183 | // Typical usage: 184 | // proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...) 185 | func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds { 186 | return &ReqProxyConds{proxy, conds} 187 | } 188 | 189 | // ReqProxyConds aggregate ReqConditions for a ProxyHttpServer. Upon calling Do, it will register a ReqHandler that would 190 | // handle the request if all conditions on the HTTP request are met. 191 | type ReqProxyConds struct { 192 | proxy *ProxyHttpServer 193 | reqConds []ReqCondition 194 | } 195 | 196 | // DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f)) 197 | func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)) { 198 | pcond.Do(FuncReqHandler(f)) 199 | } 200 | 201 | // ReqProxyConds.Do will register the ReqHandler on the proxy, 202 | // the ReqHandler will handle the HTTP request if all the conditions 203 | // aggregated in the ReqProxyConds are met. Typical usage: 204 | // proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy 205 | // proxy.OnRequest(cond1,cond2).Do(handler) 206 | // // given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true 207 | // // if they are, will call handler.Handle(req,ctx) 208 | func (pcond *ReqProxyConds) Do(h ReqHandler) { 209 | pcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers, 210 | FuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { 211 | for _, cond := range pcond.reqConds { 212 | if !cond.HandleReq(r, ctx) { 213 | return r, nil 214 | } 215 | } 216 | return h.Handle(r, ctx) 217 | })) 218 | } 219 | 220 | // HandleConnect is used when proxy receives an HTTP CONNECT request, 221 | // it'll then use the HttpsHandler to determine what should it 222 | // do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction 223 | // struct returned will determine what to do with this request. ConnectAccept will simply accept the request 224 | // forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the 225 | // client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man 226 | // in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped 227 | // connection. 228 | // The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy 229 | // will use the default tls configuration. 230 | // proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests 231 | func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) { 232 | pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, 233 | FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 234 | for _, cond := range pcond.reqConds { 235 | if !cond.HandleReq(ctx.Req, ctx) { 236 | return nil, "" 237 | } 238 | } 239 | return h.HandleConnect(host, ctx) 240 | })) 241 | } 242 | 243 | // HandleConnectFunc is equivalent to HandleConnect, 244 | // for example, accepting CONNECT request if they contain a password in header 245 | // io.WriteString(h,password) 246 | // passHash := h.Sum(nil) 247 | // proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 248 | // c := sha1.New() 249 | // io.WriteString(c,ctx.Req.Header.Get("X-GoProxy-Auth")) 250 | // if c.Sum(nil) == passHash { 251 | // return OkConnect, host 252 | // } 253 | // return RejectConnect, host 254 | // }) 255 | func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string)) { 256 | pcond.HandleConnect(FuncHttpsHandler(f)) 257 | } 258 | 259 | func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) { 260 | pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, 261 | FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 262 | for _, cond := range pcond.reqConds { 263 | if !cond.HandleReq(ctx.Req, ctx) { 264 | return nil, "" 265 | } 266 | } 267 | return &ConnectAction{Action: ConnectHijack, Hijack: f}, host 268 | })) 269 | } 270 | 271 | // ProxyConds is used to aggregate RespConditions for a ProxyHttpServer. 272 | // Upon calling ProxyConds.Do, it will register a RespHandler that would 273 | // handle the HTTP response from remote server if all conditions on the HTTP response are met. 274 | type ProxyConds struct { 275 | proxy *ProxyHttpServer 276 | reqConds []ReqCondition 277 | respCond []RespCondition 278 | } 279 | 280 | // ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f)) 281 | func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response) { 282 | pcond.Do(FuncRespHandler(f)) 283 | } 284 | 285 | // ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every 286 | // request that matches the conditions aggregated in pcond. 287 | func (pcond *ProxyConds) Do(h RespHandler) { 288 | pcond.proxy.respHandlers = append(pcond.proxy.respHandlers, 289 | FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { 290 | for _, cond := range pcond.reqConds { 291 | if !cond.HandleReq(ctx.Req, ctx) { 292 | return resp 293 | } 294 | } 295 | for _, cond := range pcond.respCond { 296 | if !cond.HandleResp(resp, ctx) { 297 | return resp 298 | } 299 | } 300 | return h.Handle(resp, ctx) 301 | })) 302 | } 303 | 304 | // OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is 305 | // proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used 306 | // // if cond1.HandleResp(resp) && cond2.HandleResp(resp) 307 | func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds { 308 | return &ProxyConds{proxy, make([]ReqCondition, 0), conds} 309 | } 310 | 311 | // AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to 312 | // eavesdrop all https connections to www.google.com, we can use 313 | // proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm) 314 | var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 315 | return MitmConnect, host 316 | } 317 | 318 | // AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow 319 | // connections to hosts on any other port than 443 320 | // proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))). 321 | // HandleConnect(goproxy.AlwaysReject) 322 | var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { 323 | return RejectConnect, host 324 | } 325 | 326 | // HandleBytes will return a RespHandler that read the entire body of the request 327 | // to a byte array in memory, would run the user supplied f function on the byte arra, 328 | // and will replace the body of the original response with the resulting byte array. 329 | func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler { 330 | return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { 331 | b, err := ioutil.ReadAll(resp.Body) 332 | if err != nil { 333 | ctx.Warnf("Cannot read response %s", err) 334 | return resp 335 | } 336 | resp.Body.Close() 337 | 338 | resp.Body = ioutil.NopCloser(bytes.NewBuffer(f(b, ctx))) 339 | return resp 340 | }) 341 | } 342 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/elazarl/goproxy 2 | 3 | require github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 4 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | // ConnectAction enables the caller to override the standard connect flow. 40 | // When Action is ConnectHijack, it is up to the implementer to send the 41 | // HTTP 200, or any other valid http response back to the client from within the 42 | // Hijack func 43 | type ConnectAction struct { 44 | Action ConnectActionLiteral 45 | Hijack func(req *http.Request, client net.Conn, ctx *ProxyCtx) 46 | TLSConfig func(host string, ctx *ProxyCtx) (*tls.Config, error) 47 | } 48 | 49 | func stripPort(s string) string { 50 | ix := strings.IndexRune(s, ':') 51 | if ix == -1 { 52 | return s 53 | } 54 | return s[:ix] 55 | } 56 | 57 | func (proxy *ProxyHttpServer) dial(network, addr string) (c net.Conn, err error) { 58 | if proxy.Tr.Dial != nil { 59 | return proxy.Tr.Dial(network, addr) 60 | } 61 | return net.Dial(network, addr) 62 | } 63 | 64 | func (proxy *ProxyHttpServer) connectDial(network, addr string) (c net.Conn, err error) { 65 | if proxy.ConnectDial == nil { 66 | return proxy.dial(network, addr) 67 | } 68 | return proxy.ConnectDial(network, addr) 69 | } 70 | 71 | type halfClosable interface { 72 | net.Conn 73 | CloseWrite() error 74 | CloseRead() error 75 | } 76 | 77 | var _ halfClosable = (*net.TCPConn)(nil) 78 | 79 | func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) { 80 | ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, certStore: proxy.CertStore} 81 | 82 | hij, ok := w.(http.Hijacker) 83 | if !ok { 84 | panic("httpserver does not support hijacking") 85 | } 86 | 87 | proxyClient, _, e := hij.Hijack() 88 | if e != nil { 89 | panic("Cannot hijack connection " + e.Error()) 90 | } 91 | 92 | ctx.Logf("Running %d CONNECT handlers", len(proxy.httpsHandlers)) 93 | todo, host := OkConnect, r.URL.Host 94 | for i, h := range proxy.httpsHandlers { 95 | newtodo, newhost := h.HandleConnect(host, ctx) 96 | 97 | // If found a result, break the loop immediately 98 | if newtodo != nil { 99 | todo, host = newtodo, newhost 100 | ctx.Logf("on %dth handler: %v %s", i, todo, host) 101 | break 102 | } 103 | } 104 | switch todo.Action { 105 | case ConnectAccept: 106 | if !hasPort.MatchString(host) { 107 | host += ":80" 108 | } 109 | targetSiteCon, err := proxy.connectDial("tcp", host) 110 | if err != nil { 111 | httpError(proxyClient, ctx, err) 112 | return 113 | } 114 | ctx.Logf("Accepting CONNECT to %s", host) 115 | proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) 116 | 117 | targetTCP, targetOK := targetSiteCon.(halfClosable) 118 | proxyClientTCP, clientOK := proxyClient.(halfClosable) 119 | if targetOK && clientOK { 120 | go copyAndClose(ctx, targetTCP, proxyClientTCP) 121 | go copyAndClose(ctx, proxyClientTCP, targetTCP) 122 | } else { 123 | go func() { 124 | var wg sync.WaitGroup 125 | wg.Add(2) 126 | go copyOrWarn(ctx, targetSiteCon, proxyClient, &wg) 127 | go copyOrWarn(ctx, proxyClient, targetSiteCon, &wg) 128 | wg.Wait() 129 | proxyClient.Close() 130 | targetSiteCon.Close() 131 | 132 | }() 133 | } 134 | 135 | case ConnectHijack: 136 | todo.Hijack(r, proxyClient, ctx) 137 | case ConnectHTTPMitm: 138 | proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) 139 | ctx.Logf("Assuming CONNECT is plain HTTP tunneling, mitm proxying it") 140 | targetSiteCon, err := proxy.connectDial("tcp", host) 141 | if err != nil { 142 | ctx.Warnf("Error dialing to %s: %s", host, err.Error()) 143 | return 144 | } 145 | for { 146 | client := bufio.NewReader(proxyClient) 147 | remote := bufio.NewReader(targetSiteCon) 148 | req, err := http.ReadRequest(client) 149 | if err != nil && err != io.EOF { 150 | ctx.Warnf("cannot read request of MITM HTTP client: %+#v", err) 151 | } 152 | if err != nil { 153 | return 154 | } 155 | req, resp := proxy.filterRequest(req, ctx) 156 | if resp == nil { 157 | if err := req.Write(targetSiteCon); err != nil { 158 | httpError(proxyClient, ctx, err) 159 | return 160 | } 161 | resp, err = http.ReadResponse(remote, req) 162 | if err != nil { 163 | httpError(proxyClient, ctx, err) 164 | return 165 | } 166 | defer resp.Body.Close() 167 | } 168 | resp = proxy.filterResponse(resp, ctx) 169 | if err := resp.Write(proxyClient); err != nil { 170 | httpError(proxyClient, ctx, err) 171 | return 172 | } 173 | } 174 | case ConnectMitm: 175 | proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) 176 | ctx.Logf("Assuming CONNECT is TLS, mitm proxying it") 177 | // this goes in a separate goroutine, so that the net/http server won't think we're 178 | // still handling the request even after hijacking the connection. Those HTTP CONNECT 179 | // request can take forever, and the server will be stuck when "closed". 180 | // TODO: Allow Server.Close() mechanism to shut down this connection as nicely as possible 181 | tlsConfig := defaultTLSConfig 182 | if todo.TLSConfig != nil { 183 | var err error 184 | tlsConfig, err = todo.TLSConfig(host, ctx) 185 | if err != nil { 186 | httpError(proxyClient, ctx, err) 187 | return 188 | } 189 | } 190 | go func() { 191 | //TODO: cache connections to the remote website 192 | rawClientTls := tls.Server(proxyClient, tlsConfig) 193 | if err := rawClientTls.Handshake(); err != nil { 194 | ctx.Warnf("Cannot handshake client %v %v", r.Host, err) 195 | return 196 | } 197 | defer rawClientTls.Close() 198 | clientTlsReader := bufio.NewReader(rawClientTls) 199 | for !isEof(clientTlsReader) { 200 | req, err := http.ReadRequest(clientTlsReader) 201 | var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, UserData: ctx.UserData} 202 | if err != nil && err != io.EOF { 203 | return 204 | } 205 | if err != nil { 206 | ctx.Warnf("Cannot read TLS request from mitm'd client %v %v", r.Host, err) 207 | return 208 | } 209 | req.RemoteAddr = r.RemoteAddr // since we're converting the request, need to carry over the original connecting IP as well 210 | ctx.Logf("req %v", r.Host) 211 | 212 | if !httpsRegexp.MatchString(req.URL.String()) { 213 | req.URL, err = url.Parse("https://" + r.Host + req.URL.String()) 214 | } 215 | 216 | // Bug fix which goproxy fails to provide request 217 | // information URL in the context when does HTTPS MITM 218 | ctx.Req = req 219 | 220 | req, resp := proxy.filterRequest(req, ctx) 221 | if resp == nil { 222 | if isWebSocketRequest(req) { 223 | ctx.Logf("Request looks like websocket upgrade.") 224 | proxy.serveWebsocketTLS(ctx, w, req, tlsConfig, rawClientTls) 225 | return 226 | } 227 | if err != nil { 228 | ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path) 229 | return 230 | } 231 | removeProxyHeaders(ctx, req) 232 | resp, err = ctx.RoundTrip(req) 233 | if err != nil { 234 | ctx.Warnf("Cannot read TLS response from mitm'd server %v", err) 235 | return 236 | } 237 | ctx.Logf("resp %v", resp.Status) 238 | } 239 | resp = proxy.filterResponse(resp, ctx) 240 | defer resp.Body.Close() 241 | 242 | text := resp.Status 243 | statusCode := strconv.Itoa(resp.StatusCode) + " " 244 | if strings.HasPrefix(text, statusCode) { 245 | text = text[len(statusCode):] 246 | } 247 | // always use 1.1 to support chunked encoding 248 | if _, err := io.WriteString(rawClientTls, "HTTP/1.1"+" "+statusCode+text+"\r\n"); err != nil { 249 | ctx.Warnf("Cannot write TLS response HTTP status from mitm'd client: %v", err) 250 | return 251 | } 252 | // Since we don't know the length of resp, return chunked encoded response 253 | // TODO: use a more reasonable scheme 254 | resp.Header.Del("Content-Length") 255 | resp.Header.Set("Transfer-Encoding", "chunked") 256 | // Force connection close otherwise chrome will keep CONNECT tunnel open forever 257 | resp.Header.Set("Connection", "close") 258 | if err := resp.Header.Write(rawClientTls); err != nil { 259 | ctx.Warnf("Cannot write TLS response header from mitm'd client: %v", err) 260 | return 261 | } 262 | if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { 263 | ctx.Warnf("Cannot write TLS response header end from mitm'd client: %v", err) 264 | return 265 | } 266 | chunked := newChunkedWriter(rawClientTls) 267 | if _, err := io.Copy(chunked, resp.Body); err != nil { 268 | ctx.Warnf("Cannot write TLS response body from mitm'd client: %v", err) 269 | return 270 | } 271 | if err := chunked.Close(); err != nil { 272 | ctx.Warnf("Cannot write TLS chunked EOF from mitm'd client: %v", err) 273 | return 274 | } 275 | if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { 276 | ctx.Warnf("Cannot write TLS response chunked trailer from mitm'd client: %v", err) 277 | return 278 | } 279 | } 280 | ctx.Logf("Exiting on EOF") 281 | }() 282 | case ConnectProxyAuthHijack: 283 | proxyClient.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n")) 284 | todo.Hijack(r, proxyClient, ctx) 285 | case ConnectReject: 286 | if ctx.Resp != nil { 287 | if err := ctx.Resp.Write(proxyClient); err != nil { 288 | ctx.Warnf("Cannot write response that reject http CONNECT: %v", err) 289 | } 290 | } 291 | proxyClient.Close() 292 | } 293 | } 294 | 295 | func httpError(w io.WriteCloser, ctx *ProxyCtx, err error) { 296 | if _, err := io.WriteString(w, "HTTP/1.1 502 Bad Gateway\r\n\r\n"); err != nil { 297 | ctx.Warnf("Error responding to client: %s", err) 298 | } 299 | if err := w.Close(); err != nil { 300 | ctx.Warnf("Error closing client connection: %s", err) 301 | } 302 | } 303 | 304 | func copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader, wg *sync.WaitGroup) { 305 | if _, err := io.Copy(dst, src); err != nil { 306 | ctx.Warnf("Error copying to client: %s", err) 307 | } 308 | wg.Done() 309 | } 310 | 311 | func copyAndClose(ctx *ProxyCtx, dst, src halfClosable) { 312 | if _, err := io.Copy(dst, src); err != nil { 313 | ctx.Warnf("Error copying to client: %s", err) 314 | } 315 | 316 | dst.CloseWrite() 317 | src.CloseRead() 318 | } 319 | 320 | func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn, error) { 321 | https_proxy := os.Getenv("HTTPS_PROXY") 322 | if https_proxy == "" { 323 | https_proxy = os.Getenv("https_proxy") 324 | } 325 | if https_proxy == "" { 326 | return nil 327 | } 328 | return proxy.NewConnectDialToProxy(https_proxy) 329 | } 330 | 331 | func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(network, addr string) (net.Conn, error) { 332 | return proxy.NewConnectDialToProxyWithHandler(https_proxy, nil) 333 | } 334 | 335 | func (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(https_proxy string, connectReqHandler func(req *http.Request)) func(network, addr string) (net.Conn, error) { 336 | u, err := url.Parse(https_proxy) 337 | if err != nil { 338 | return nil 339 | } 340 | if u.Scheme == "" || u.Scheme == "http" { 341 | if strings.IndexRune(u.Host, ':') == -1 { 342 | u.Host += ":80" 343 | } 344 | return func(network, addr string) (net.Conn, error) { 345 | connectReq := &http.Request{ 346 | Method: "CONNECT", 347 | URL: &url.URL{Opaque: addr}, 348 | Host: addr, 349 | Header: make(http.Header), 350 | } 351 | if connectReqHandler != nil { 352 | connectReqHandler(connectReq) 353 | } 354 | c, err := proxy.dial(network, u.Host) 355 | if err != nil { 356 | return nil, err 357 | } 358 | connectReq.Write(c) 359 | // Read response. 360 | // Okay to use and discard buffered reader here, because 361 | // TLS server will not speak until spoken to. 362 | br := bufio.NewReader(c) 363 | resp, err := http.ReadResponse(br, connectReq) 364 | if err != nil { 365 | c.Close() 366 | return nil, err 367 | } 368 | defer resp.Body.Close() 369 | if resp.StatusCode != 200 { 370 | resp, err := ioutil.ReadAll(resp.Body) 371 | if err != nil { 372 | return nil, err 373 | } 374 | c.Close() 375 | return nil, errors.New("proxy refused connection" + string(resp)) 376 | } 377 | return c, nil 378 | } 379 | } 380 | if u.Scheme == "https" || u.Scheme == "wss" { 381 | if strings.IndexRune(u.Host, ':') == -1 { 382 | u.Host += ":443" 383 | } 384 | return func(network, addr string) (net.Conn, error) { 385 | c, err := proxy.dial(network, u.Host) 386 | if err != nil { 387 | return nil, err 388 | } 389 | c = tls.Client(c, proxy.Tr.TLSClientConfig) 390 | connectReq := &http.Request{ 391 | Method: "CONNECT", 392 | URL: &url.URL{Opaque: addr}, 393 | Host: addr, 394 | Header: make(http.Header), 395 | } 396 | if connectReqHandler != nil { 397 | connectReqHandler(connectReq) 398 | } 399 | connectReq.Write(c) 400 | // Read response. 401 | // Okay to use and discard buffered reader here, because 402 | // TLS server will not speak until spoken to. 403 | br := bufio.NewReader(c) 404 | resp, err := http.ReadResponse(br, connectReq) 405 | if err != nil { 406 | c.Close() 407 | return nil, err 408 | } 409 | defer resp.Body.Close() 410 | if resp.StatusCode != 200 { 411 | body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 500)) 412 | if err != nil { 413 | return nil, err 414 | } 415 | c.Close() 416 | return nil, errors.New("proxy refused connection" + string(body)) 417 | } 418 | return c, nil 419 | } 420 | } 421 | return nil 422 | } 423 | 424 | func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error) { 425 | return func(host string, ctx *ProxyCtx) (*tls.Config, error) { 426 | var err error 427 | var cert *tls.Certificate 428 | 429 | hostname := stripPort(host) 430 | config := defaultTLSConfig.Clone() 431 | ctx.Logf("signing for %s", stripPort(host)) 432 | 433 | genCert := func() (*tls.Certificate, error) { 434 | return signHost(*ca, []string{hostname}) 435 | } 436 | if ctx.certStore != nil { 437 | cert, err = ctx.certStore.Fetch(hostname, genCert) 438 | } else { 439 | cert, err = genCert() 440 | } 441 | 442 | if err != nil { 443 | ctx.Warnf("Cannot sign host certificate with provided CA: %s", err) 444 | return nil, err 445 | } 446 | 447 | config.Certificates = append(config.Certificates, *cert) 448 | return config, nil 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/logger.go: -------------------------------------------------------------------------------- 1 | package goproxy 2 | 3 | type Logger interface { 4 | Printf(format string, v ...interface{}) 5 | } 6 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | KeepHeader bool 34 | } 35 | 36 | var hasPort = regexp.MustCompile(`:\d+$`) 37 | 38 | func copyHeaders(dst, src http.Header, keepDestHeaders bool) { 39 | if !keepDestHeaders { 40 | for k := range dst { 41 | dst.Del(k) 42 | } 43 | } 44 | for k, vs := range src { 45 | for _, v := range vs { 46 | dst.Add(k, v) 47 | } 48 | } 49 | } 50 | 51 | func isEof(r *bufio.Reader) bool { 52 | _, err := r.Peek(1) 53 | if err == io.EOF { 54 | return true 55 | } 56 | return false 57 | } 58 | 59 | func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) { 60 | req = r 61 | for _, h := range proxy.reqHandlers { 62 | req, resp = h.Handle(r, ctx) 63 | // non-nil resp means the handler decided to skip sending the request 64 | // and return canned response instead. 65 | if resp != nil { 66 | break 67 | } 68 | } 69 | return 70 | } 71 | func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) { 72 | resp = respOrig 73 | for _, h := range proxy.respHandlers { 74 | ctx.Resp = resp 75 | resp = h.Handle(resp, ctx) 76 | } 77 | return 78 | } 79 | 80 | func removeProxyHeaders(ctx *ProxyCtx, r *http.Request) { 81 | r.RequestURI = "" // this must be reset when serving a request with the client 82 | ctx.Logf("Sending request %v %v", r.Method, r.URL.String()) 83 | // If no Accept-Encoding header exists, Transport will add the headers it can accept 84 | // and would wrap the response body with the relevant reader. 85 | r.Header.Del("Accept-Encoding") 86 | // curl can add that, see 87 | // https://jdebp.eu./FGA/web-proxy-connection-header.html 88 | r.Header.Del("Proxy-Connection") 89 | r.Header.Del("Proxy-Authenticate") 90 | r.Header.Del("Proxy-Authorization") 91 | // Connection, Authenticate and Authorization are single hop Header: 92 | // http://www.w3.org/Protocols/rfc2616/rfc2616.txt 93 | // 14.10 Connection 94 | // The Connection general-header field allows the sender to specify 95 | // options that are desired for that particular connection and MUST NOT 96 | // be communicated by proxies over further connections. 97 | 98 | // When server reads http request it sets req.Close to true if 99 | // "Connection" header contains "close". 100 | // https://github.com/golang/go/blob/master/src/net/http/request.go#L1080 101 | // Later, transfer.go adds "Connection: close" back when req.Close is true 102 | // https://github.com/golang/go/blob/master/src/net/http/transfer.go#L275 103 | // That's why tests that checks "Connection: close" removal fail 104 | if r.Header.Get("Connection") == "close" { 105 | r.Close = false 106 | } 107 | r.Header.Del("Connection") 108 | } 109 | 110 | // Standard net/http function. Shouldn't be used directly, http.Serve will use it. 111 | func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 112 | //r.Header["X-Forwarded-For"] = w.RemoteAddr() 113 | if r.Method == "CONNECT" { 114 | proxy.handleHttps(w, r) 115 | } else { 116 | ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy} 117 | 118 | var err error 119 | ctx.Logf("Got request %v %v %v %v", r.URL.Path, r.Host, r.Method, r.URL.String()) 120 | if !r.URL.IsAbs() { 121 | proxy.NonproxyHandler.ServeHTTP(w, r) 122 | return 123 | } 124 | r, resp := proxy.filterRequest(r, ctx) 125 | 126 | if resp == nil { 127 | if isWebSocketRequest(r) { 128 | ctx.Logf("Request looks like websocket upgrade.") 129 | proxy.serveWebsocket(ctx, w, r) 130 | } 131 | 132 | if !proxy.KeepHeader { 133 | removeProxyHeaders(ctx, r) 134 | } 135 | resp, err = ctx.RoundTrip(r) 136 | if err != nil { 137 | ctx.Error = err 138 | resp = proxy.filterResponse(nil, ctx) 139 | 140 | } 141 | if resp != nil { 142 | ctx.Logf("Received response %v", resp.Status) 143 | } 144 | } 145 | 146 | var origBody io.ReadCloser 147 | 148 | if resp != nil { 149 | origBody = resp.Body 150 | defer origBody.Close() 151 | } 152 | 153 | resp = proxy.filterResponse(resp, ctx) 154 | 155 | if resp == nil { 156 | var errorString string 157 | if ctx.Error != nil { 158 | errorString = "error read response " + r.URL.Host + " : " + ctx.Error.Error() 159 | ctx.Logf(errorString) 160 | http.Error(w, ctx.Error.Error(), 500) 161 | } else { 162 | errorString = "error read response " + r.URL.Host 163 | ctx.Logf(errorString) 164 | http.Error(w, errorString, 500) 165 | } 166 | return 167 | } 168 | ctx.Logf("Copying response to client %v [%d]", resp.Status, resp.StatusCode) 169 | // http.ResponseWriter will take care of filling the correct response length 170 | // Setting it now, might impose wrong value, contradicting the actual new 171 | // body the user returned. 172 | // We keep the original body to remove the header only if things changed. 173 | // This will prevent problems with HEAD requests where there's no body, yet, 174 | // the Content-Length header should be set. 175 | if origBody != resp.Body { 176 | resp.Header.Del("Content-Length") 177 | } 178 | copyHeaders(w.Header(), resp.Header, proxy.KeepDestinationHeaders) 179 | w.WriteHeader(resp.StatusCode) 180 | nr, err := io.Copy(w, resp.Body) 181 | if err := resp.Body.Close(); err != nil { 182 | ctx.Warnf("Can't close response body %v", err) 183 | } 184 | ctx.Logf("Copied %v bytes to client error=%v", nr, err) 185 | } 186 | } 187 | 188 | // NewProxyHttpServer creates and returns a proxy server, logging to stderr by default 189 | func NewProxyHttpServer() *ProxyHttpServer { 190 | proxy := ProxyHttpServer{ 191 | Logger: log.New(os.Stderr, "", log.LstdFlags), 192 | reqHandlers: []ReqHandler{}, 193 | respHandlers: []RespHandler{}, 194 | httpsHandlers: []HttpsHandler{}, 195 | NonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 196 | http.Error(w, "This is a proxy server. Does not respond to non-proxy requests.", 500) 197 | }), 198 | Tr: &http.Transport{TLSClientConfig: tlsClientSkipVerify, Proxy: http.ProxyFromEnvironment}, 199 | } 200 | 201 | proxy.ConnectDial = dialerFromEnv(&proxy) 202 | 203 | return &proxy 204 | } 205 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/goproxy/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 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/BurntSushi/toml v0.3.1 2 | ## explicit 3 | github.com/BurntSushi/toml 4 | # github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d 5 | ## explicit 6 | github.com/elazarl/goproxy 7 | --------------------------------------------------------------------------------