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  }