github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/fshttp/http.go (about)

     1  // Package fshttp contains the common http parts of the config, Transport and Client
     2  package fshttp
     3  
     4  import (
     5  	"bytes"
     6  	"context"
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"io/ioutil"
    10  	"log"
    11  	"net"
    12  	"net/http"
    13  	"net/http/cookiejar"
    14  	"net/http/httputil"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/rclone/rclone/fs"
    19  	"github.com/rclone/rclone/lib/structs"
    20  	"golang.org/x/net/publicsuffix"
    21  	"golang.org/x/time/rate"
    22  )
    23  
    24  const (
    25  	separatorReq  = ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
    26  	separatorResp = "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
    27  )
    28  
    29  var (
    30  	transport    http.RoundTripper
    31  	noTransport  = new(sync.Once)
    32  	tpsBucket    *rate.Limiter // for limiting number of http transactions per second
    33  	cookieJar, _ = cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
    34  )
    35  
    36  // StartHTTPTokenBucket starts the token bucket if necessary
    37  func StartHTTPTokenBucket() {
    38  	if fs.Config.TPSLimit > 0 {
    39  		tpsBurst := fs.Config.TPSLimitBurst
    40  		if tpsBurst < 1 {
    41  			tpsBurst = 1
    42  		}
    43  		tpsBucket = rate.NewLimiter(rate.Limit(fs.Config.TPSLimit), tpsBurst)
    44  		fs.Infof(nil, "Starting HTTP transaction limiter: max %g transactions/s with burst %d", fs.Config.TPSLimit, tpsBurst)
    45  	}
    46  }
    47  
    48  // A net.Conn that sets a deadline for every Read or Write operation
    49  type timeoutConn struct {
    50  	net.Conn
    51  	timeout time.Duration
    52  }
    53  
    54  // create a timeoutConn using the timeout
    55  func newTimeoutConn(conn net.Conn, timeout time.Duration) (c *timeoutConn, err error) {
    56  	c = &timeoutConn{
    57  		Conn:    conn,
    58  		timeout: timeout,
    59  	}
    60  	err = c.nudgeDeadline()
    61  	return
    62  }
    63  
    64  // Nudge the deadline for an idle timeout on by c.timeout if non-zero
    65  func (c *timeoutConn) nudgeDeadline() (err error) {
    66  	if c.timeout == 0 {
    67  		return nil
    68  	}
    69  	when := time.Now().Add(c.timeout)
    70  	return c.Conn.SetDeadline(when)
    71  }
    72  
    73  // readOrWrite bytes doing idle timeouts
    74  func (c *timeoutConn) readOrWrite(f func([]byte) (int, error), b []byte) (n int, err error) {
    75  	n, err = f(b)
    76  	// Don't nudge if no bytes or an error
    77  	if n == 0 || err != nil {
    78  		return
    79  	}
    80  	// Nudge the deadline on successful Read or Write
    81  	err = c.nudgeDeadline()
    82  	return
    83  }
    84  
    85  // Read bytes doing idle timeouts
    86  func (c *timeoutConn) Read(b []byte) (n int, err error) {
    87  	return c.readOrWrite(c.Conn.Read, b)
    88  }
    89  
    90  // Write bytes doing idle timeouts
    91  func (c *timeoutConn) Write(b []byte) (n int, err error) {
    92  	return c.readOrWrite(c.Conn.Write, b)
    93  }
    94  
    95  // dial with context and timeouts
    96  func dialContextTimeout(ctx context.Context, network, address string, ci *fs.ConfigInfo) (net.Conn, error) {
    97  	dialer := NewDialer(ci)
    98  	c, err := dialer.DialContext(ctx, network, address)
    99  	if err != nil {
   100  		return c, err
   101  	}
   102  	return newTimeoutConn(c, ci.Timeout)
   103  }
   104  
   105  // ResetTransport resets the existing transport, allowing it to take new settings.
   106  // Should only be used for testing.
   107  func ResetTransport() {
   108  	noTransport = new(sync.Once)
   109  }
   110  
   111  // NewTransportCustom returns an http.RoundTripper with the correct timeouts.
   112  // The customize function is called if set to give the caller an opportunity to
   113  // customize any defaults in the Transport.
   114  func NewTransportCustom(ci *fs.ConfigInfo, customize func(*http.Transport)) http.RoundTripper {
   115  	// Start with a sensible set of defaults then override.
   116  	// This also means we get new stuff when it gets added to go
   117  	t := new(http.Transport)
   118  	structs.SetDefaults(t, http.DefaultTransport.(*http.Transport))
   119  	t.Proxy = http.ProxyFromEnvironment
   120  	t.MaxIdleConnsPerHost = 2 * (ci.Checkers + ci.Transfers + 1)
   121  	t.MaxIdleConns = 2 * t.MaxIdleConnsPerHost
   122  	t.TLSHandshakeTimeout = ci.ConnectTimeout
   123  	t.ResponseHeaderTimeout = ci.Timeout
   124  
   125  	// TLS Config
   126  	t.TLSClientConfig = &tls.Config{
   127  		InsecureSkipVerify: ci.InsecureSkipVerify,
   128  	}
   129  
   130  	// Load client certs
   131  	if ci.ClientCert != "" || ci.ClientKey != "" {
   132  		if ci.ClientCert == "" || ci.ClientKey == "" {
   133  			log.Fatalf("Both --client-cert and --client-key must be set")
   134  		}
   135  		cert, err := tls.LoadX509KeyPair(ci.ClientCert, ci.ClientKey)
   136  		if err != nil {
   137  			log.Fatalf("Failed to load --client-cert/--client-key pair: %v", err)
   138  		}
   139  		t.TLSClientConfig.Certificates = []tls.Certificate{cert}
   140  		t.TLSClientConfig.BuildNameToCertificate()
   141  	}
   142  
   143  	// Load CA cert
   144  	if ci.CaCert != "" {
   145  		caCert, err := ioutil.ReadFile(ci.CaCert)
   146  		if err != nil {
   147  			log.Fatalf("Failed to read --ca-cert: %v", err)
   148  		}
   149  		caCertPool := x509.NewCertPool()
   150  		ok := caCertPool.AppendCertsFromPEM(caCert)
   151  		if !ok {
   152  			log.Fatalf("Failed to add certificates from --ca-cert")
   153  		}
   154  		t.TLSClientConfig.RootCAs = caCertPool
   155  	}
   156  
   157  	t.DisableCompression = ci.NoGzip
   158  	t.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
   159  		return dialContextTimeout(ctx, network, addr, ci)
   160  	}
   161  	t.IdleConnTimeout = 60 * time.Second
   162  	t.ExpectContinueTimeout = ci.ExpectContinueTimeout
   163  
   164  	if ci.Dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpAuth|fs.DumpRequests|fs.DumpResponses) != 0 {
   165  		fs.Debugf(nil, "You have specified to dump information. Please be noted that the "+
   166  			"Accept-Encoding as shown may not be correct in the request and the response may not show "+
   167  			"Content-Encoding if the go standard libraries auto gzip encoding was in effect. In this case"+
   168  			" the body of the request will be gunzipped before showing it.")
   169  	}
   170  
   171  	// customize the transport if required
   172  	if customize != nil {
   173  		customize(t)
   174  	}
   175  
   176  	// Wrap that http.Transport in our own transport
   177  	return newTransport(ci, t)
   178  }
   179  
   180  // NewTransport returns an http.RoundTripper with the correct timeouts
   181  func NewTransport(ci *fs.ConfigInfo) http.RoundTripper {
   182  	(*noTransport).Do(func() {
   183  		transport = NewTransportCustom(ci, nil)
   184  	})
   185  	return transport
   186  }
   187  
   188  // NewClient returns an http.Client with the correct timeouts
   189  func NewClient(ci *fs.ConfigInfo) *http.Client {
   190  	client := &http.Client{
   191  		Transport: NewTransport(ci),
   192  	}
   193  	if ci.Cookie {
   194  		client.Jar = cookieJar
   195  	}
   196  	return client
   197  }
   198  
   199  // Transport is our http Transport which wraps an http.Transport
   200  // * Sets the User Agent
   201  // * Does logging
   202  type Transport struct {
   203  	*http.Transport
   204  	dump          fs.DumpFlags
   205  	filterRequest func(req *http.Request)
   206  	userAgent     string
   207  	headers       []*fs.HTTPOption
   208  }
   209  
   210  // newTransport wraps the http.Transport passed in and logs all
   211  // roundtrips including the body if logBody is set.
   212  func newTransport(ci *fs.ConfigInfo, transport *http.Transport) *Transport {
   213  	return &Transport{
   214  		Transport: transport,
   215  		dump:      ci.Dump,
   216  		userAgent: ci.UserAgent,
   217  		headers:   ci.Headers,
   218  	}
   219  }
   220  
   221  // SetRequestFilter sets a filter to be used on each request
   222  func (t *Transport) SetRequestFilter(f func(req *http.Request)) {
   223  	t.filterRequest = f
   224  }
   225  
   226  // A mutex to protect this map
   227  var checkedHostMu sync.RWMutex
   228  
   229  // A map of servers we have checked for time
   230  var checkedHost = make(map[string]struct{}, 1)
   231  
   232  // Check the server time is the same as ours, once for each server
   233  func checkServerTime(req *http.Request, resp *http.Response) {
   234  	host := req.URL.Host
   235  	if req.Host != "" {
   236  		host = req.Host
   237  	}
   238  	checkedHostMu.RLock()
   239  	_, ok := checkedHost[host]
   240  	checkedHostMu.RUnlock()
   241  	if ok {
   242  		return
   243  	}
   244  	dateString := resp.Header.Get("Date")
   245  	if dateString == "" {
   246  		return
   247  	}
   248  	date, err := http.ParseTime(dateString)
   249  	if err != nil {
   250  		fs.Debugf(nil, "Couldn't parse Date: from server %s: %q: %v", host, dateString, err)
   251  		return
   252  	}
   253  	dt := time.Since(date)
   254  	const window = 5 * 60 * time.Second
   255  	if dt > window || dt < -window {
   256  		fs.Logf(nil, "Time may be set wrong - time from %q is %v different from this computer", host, dt)
   257  	}
   258  	checkedHostMu.Lock()
   259  	checkedHost[host] = struct{}{}
   260  	checkedHostMu.Unlock()
   261  }
   262  
   263  // cleanAuth gets rid of one authBuf header within the first 4k
   264  func cleanAuth(buf, authBuf []byte) []byte {
   265  	// Find how much buffer to check
   266  	n := 4096
   267  	if len(buf) < n {
   268  		n = len(buf)
   269  	}
   270  	// See if there is an Authorization: header
   271  	i := bytes.Index(buf[:n], authBuf)
   272  	if i < 0 {
   273  		return buf
   274  	}
   275  	i += len(authBuf)
   276  	// Overwrite the next 4 chars with 'X'
   277  	for j := 0; i < len(buf) && j < 4; j++ {
   278  		if buf[i] == '\n' {
   279  			break
   280  		}
   281  		buf[i] = 'X'
   282  		i++
   283  	}
   284  	// Snip out to the next '\n'
   285  	j := bytes.IndexByte(buf[i:], '\n')
   286  	if j < 0 {
   287  		return buf[:i]
   288  	}
   289  	n = copy(buf[i:], buf[i+j:])
   290  	return buf[:i+n]
   291  }
   292  
   293  var authBufs = [][]byte{
   294  	[]byte("Authorization: "),
   295  	[]byte("X-Auth-Token: "),
   296  }
   297  
   298  // cleanAuths gets rid of all the possible Auth headers
   299  func cleanAuths(buf []byte) []byte {
   300  	for _, authBuf := range authBufs {
   301  		buf = cleanAuth(buf, authBuf)
   302  	}
   303  	return buf
   304  }
   305  
   306  // RoundTrip implements the RoundTripper interface.
   307  func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
   308  	// Get transactions per second token first if limiting
   309  	if tpsBucket != nil {
   310  		tbErr := tpsBucket.Wait(req.Context())
   311  		if tbErr != nil && tbErr != context.Canceled {
   312  			fs.Errorf(nil, "HTTP token bucket error: %v", tbErr)
   313  		}
   314  	}
   315  	// Force user agent
   316  	req.Header.Set("User-Agent", t.userAgent)
   317  	// Set user defined headers
   318  	for _, option := range t.headers {
   319  		req.Header.Set(option.Key, option.Value)
   320  	}
   321  	// Filter the request if required
   322  	if t.filterRequest != nil {
   323  		t.filterRequest(req)
   324  	}
   325  	// Logf request
   326  	if t.dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpAuth|fs.DumpRequests|fs.DumpResponses) != 0 {
   327  		buf, _ := httputil.DumpRequestOut(req, t.dump&(fs.DumpBodies|fs.DumpRequests) != 0)
   328  		if t.dump&fs.DumpAuth == 0 {
   329  			buf = cleanAuths(buf)
   330  		}
   331  		fs.Debugf(nil, "%s", separatorReq)
   332  		fs.Debugf(nil, "%s (req %p)", "HTTP REQUEST", req)
   333  		fs.Debugf(nil, "%s", string(buf))
   334  		fs.Debugf(nil, "%s", separatorReq)
   335  	}
   336  	// Do round trip
   337  	resp, err = t.Transport.RoundTrip(req)
   338  	// Logf response
   339  	if t.dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpAuth|fs.DumpRequests|fs.DumpResponses) != 0 {
   340  		fs.Debugf(nil, "%s", separatorResp)
   341  		fs.Debugf(nil, "%s (req %p)", "HTTP RESPONSE", req)
   342  		if err != nil {
   343  			fs.Debugf(nil, "Error: %v", err)
   344  		} else {
   345  			buf, _ := httputil.DumpResponse(resp, t.dump&(fs.DumpBodies|fs.DumpResponses) != 0)
   346  			fs.Debugf(nil, "%s", string(buf))
   347  		}
   348  		fs.Debugf(nil, "%s", separatorResp)
   349  	}
   350  	if err == nil {
   351  		checkServerTime(req, resp)
   352  	}
   353  	return resp, err
   354  }
   355  
   356  // NewDialer creates a net.Dialer structure with Timeout, Keepalive
   357  // and LocalAddr set from rclone flags.
   358  func NewDialer(ci *fs.ConfigInfo) *net.Dialer {
   359  	dialer := &net.Dialer{
   360  		Timeout:   ci.ConnectTimeout,
   361  		KeepAlive: 30 * time.Second,
   362  	}
   363  	if ci.BindAddr != nil {
   364  		dialer.LocalAddr = &net.TCPAddr{IP: ci.BindAddr}
   365  	}
   366  	return dialer
   367  }