github.com/echohead/hub@v2.2.1+incompatible/github/http.go (about) 1 package github 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net" 9 "net/http" 10 "net/url" 11 "os" 12 "regexp" 13 "strings" 14 "time" 15 16 "github.com/github/hub/utils" 17 ) 18 19 type verboseTransport struct { 20 Transport *http.Transport 21 Verbose bool 22 OverrideURL *url.URL 23 Out io.Writer 24 Colorized bool 25 } 26 27 func (t *verboseTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 28 if t.Verbose { 29 t.dumpRequest(req) 30 } 31 32 if t.OverrideURL != nil { 33 port := "80" 34 if s := strings.Split(req.URL.Host, ":"); len(s) > 1 { 35 port = s[1] 36 } 37 38 req = cloneRequest(req) 39 req.Header.Set("X-Original-Scheme", req.URL.Scheme) 40 req.Header.Set("X-Original-Port", port) 41 req.URL.Scheme = t.OverrideURL.Scheme 42 req.URL.Host = t.OverrideURL.Host 43 } 44 45 resp, err = t.Transport.RoundTrip(req) 46 47 if err == nil && t.Verbose { 48 t.dumpResponse(resp) 49 } 50 51 return 52 } 53 54 func (t *verboseTransport) dumpRequest(req *http.Request) { 55 info := fmt.Sprintf("> %s %s://%s%s", req.Method, req.URL.Scheme, req.Host, req.URL.Path) 56 t.verbosePrintln(info) 57 t.dumpHeaders(req.Header, ">") 58 body := t.dumpBody(req.Body) 59 if body != nil { 60 // reset body since it's been read 61 req.Body = body 62 } 63 } 64 65 func (t *verboseTransport) dumpResponse(resp *http.Response) { 66 info := fmt.Sprintf("< HTTP %d", resp.StatusCode) 67 location, err := resp.Location() 68 if err == nil { 69 info = fmt.Sprintf("%s\n< Location: %s", info, location.String()) 70 } 71 t.verbosePrintln(info) 72 t.dumpHeaders(resp.Header, "<") 73 body := t.dumpBody(resp.Body) 74 if body != nil { 75 // reset body since it's been read 76 resp.Body = body 77 } 78 } 79 80 func (t *verboseTransport) dumpHeaders(header http.Header, indent string) { 81 dumpHeaders := []string{"Authorization", "X-GitHub-OTP", "Localtion"} 82 for _, h := range dumpHeaders { 83 v := header.Get(h) 84 if v != "" { 85 r := regexp.MustCompile("(?i)^(basic|token) (.+)") 86 if r.MatchString(v) { 87 v = r.ReplaceAllString(v, "$1 [REDACTED]") 88 } 89 90 info := fmt.Sprintf("%s %s: %s", indent, h, v) 91 t.verbosePrintln(info) 92 } 93 } 94 } 95 96 func (t *verboseTransport) dumpBody(body io.ReadCloser) io.ReadCloser { 97 if body == nil { 98 return nil 99 } 100 101 defer body.Close() 102 buf := new(bytes.Buffer) 103 _, err := io.Copy(buf, body) 104 utils.Check(err) 105 106 if buf.Len() > 0 { 107 t.verbosePrintln(buf.String()) 108 } 109 110 return ioutil.NopCloser(buf) 111 } 112 113 func (t *verboseTransport) verbosePrintln(msg string) { 114 if t.Colorized { 115 msg = fmt.Sprintf("\033[36m%s\033[0m", msg) 116 } 117 118 fmt.Fprintln(t.Out, msg) 119 } 120 121 func newHttpClient(testHost string, verbose bool) *http.Client { 122 var testURL *url.URL 123 if testHost != "" { 124 testURL, _ = url.Parse(testHost) 125 } 126 tr := &verboseTransport{ 127 Transport: &http.Transport{ 128 Proxy: proxyFromEnvironment, 129 Dial: (&net.Dialer{ 130 Timeout: 30 * time.Second, 131 KeepAlive: 30 * time.Second, 132 }).Dial, 133 TLSHandshakeTimeout: 10 * time.Second, 134 }, 135 Verbose: verbose, 136 OverrideURL: testURL, 137 Out: os.Stderr, 138 Colorized: isTerminal(os.Stderr.Fd()), 139 } 140 return &http.Client{Transport: tr} 141 } 142 143 func cloneRequest(req *http.Request) *http.Request { 144 dup := new(http.Request) 145 *dup = *req 146 dup.URL, _ = url.Parse(req.URL.String()) 147 dup.Header = make(http.Header) 148 for k, s := range req.Header { 149 dup.Header[k] = s 150 } 151 return dup 152 } 153 154 // An implementation of http.ProxyFromEnvironment that isn't broken 155 func proxyFromEnvironment(req *http.Request) (*url.URL, error) { 156 proxy := os.Getenv("http_proxy") 157 if proxy == "" { 158 proxy = os.Getenv("HTTP_PROXY") 159 } 160 if proxy == "" { 161 return nil, nil 162 } 163 164 proxyURL, err := url.Parse(proxy) 165 if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") { 166 if proxyURL, err := url.Parse("http://" + proxy); err == nil { 167 return proxyURL, nil 168 } 169 } 170 171 if err != nil { 172 return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err) 173 } 174 175 return proxyURL, nil 176 }