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