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  }