github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/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 "log" 10 "net" 11 "net/http" 12 "net/http/cookiejar" 13 "net/http/httputil" 14 "os" 15 "sync" 16 "time" 17 18 "github.com/rclone/rclone/fs" 19 "github.com/rclone/rclone/fs/accounting" 20 "github.com/rclone/rclone/lib/structs" 21 "golang.org/x/net/publicsuffix" 22 ) 23 24 const ( 25 separatorReq = ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" 26 separatorResp = "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" 27 ) 28 29 var ( 30 transport http.RoundTripper 31 noTransport = new(sync.Once) 32 cookieJar, _ = cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) 33 logMutex sync.Mutex 34 ) 35 36 // ResetTransport resets the existing transport, allowing it to take new settings. 37 // Should only be used for testing. 38 func ResetTransport() { 39 noTransport = new(sync.Once) 40 } 41 42 // NewTransportCustom returns an http.RoundTripper with the correct timeouts. 43 // The customize function is called if set to give the caller an opportunity to 44 // customize any defaults in the Transport. 45 func NewTransportCustom(ctx context.Context, customize func(*http.Transport)) http.RoundTripper { 46 ci := fs.GetConfig(ctx) 47 // Start with a sensible set of defaults then override. 48 // This also means we get new stuff when it gets added to go 49 t := new(http.Transport) 50 structs.SetDefaults(t, http.DefaultTransport.(*http.Transport)) 51 t.Proxy = http.ProxyFromEnvironment 52 t.MaxIdleConnsPerHost = 2 * (ci.Checkers + ci.Transfers + 1) 53 t.MaxIdleConns = 2 * t.MaxIdleConnsPerHost 54 t.TLSHandshakeTimeout = ci.ConnectTimeout 55 t.ResponseHeaderTimeout = ci.Timeout 56 t.DisableKeepAlives = ci.DisableHTTPKeepAlives 57 58 // TLS Config 59 t.TLSClientConfig = &tls.Config{ 60 InsecureSkipVerify: ci.InsecureSkipVerify, 61 } 62 63 // Load client certs 64 if ci.ClientCert != "" || ci.ClientKey != "" { 65 if ci.ClientCert == "" || ci.ClientKey == "" { 66 log.Fatalf("Both --client-cert and --client-key must be set") 67 } 68 cert, err := tls.LoadX509KeyPair(ci.ClientCert, ci.ClientKey) 69 if err != nil { 70 log.Fatalf("Failed to load --client-cert/--client-key pair: %v", err) 71 } 72 t.TLSClientConfig.Certificates = []tls.Certificate{cert} 73 } 74 75 // Load CA certs 76 if len(ci.CaCert) != 0 { 77 78 caCertPool := x509.NewCertPool() 79 80 for _, cert := range ci.CaCert { 81 caCert, err := os.ReadFile(cert) 82 if err != nil { 83 log.Fatalf("Failed to read --ca-cert file %q : %v", cert, err) 84 } 85 ok := caCertPool.AppendCertsFromPEM(caCert) 86 if !ok { 87 log.Fatalf("Failed to add certificates from --ca-cert file %q", cert) 88 } 89 } 90 t.TLSClientConfig.RootCAs = caCertPool 91 } 92 93 t.DisableCompression = ci.NoGzip 94 t.DialContext = func(reqCtx context.Context, network, addr string) (net.Conn, error) { 95 return NewDialer(ctx).DialContext(reqCtx, network, addr) 96 } 97 t.IdleConnTimeout = 60 * time.Second 98 t.ExpectContinueTimeout = ci.ExpectContinueTimeout 99 100 if ci.Dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpAuth|fs.DumpRequests|fs.DumpResponses) != 0 { 101 fs.Debugf(nil, "You have specified to dump information. Please be noted that the "+ 102 "Accept-Encoding as shown may not be correct in the request and the response may not show "+ 103 "Content-Encoding if the go standard libraries auto gzip encoding was in effect. In this case"+ 104 " the body of the request will be gunzipped before showing it.") 105 } 106 107 if ci.DisableHTTP2 { 108 t.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{} 109 } 110 111 // customize the transport if required 112 if customize != nil { 113 customize(t) 114 } 115 116 // Wrap that http.Transport in our own transport 117 return newTransport(ci, t) 118 } 119 120 // NewTransport returns an http.RoundTripper with the correct timeouts 121 func NewTransport(ctx context.Context) http.RoundTripper { 122 (*noTransport).Do(func() { 123 transport = NewTransportCustom(ctx, nil) 124 }) 125 return transport 126 } 127 128 // NewClient returns an http.Client with the correct timeouts 129 func NewClient(ctx context.Context) *http.Client { 130 ci := fs.GetConfig(ctx) 131 client := &http.Client{ 132 Transport: NewTransport(ctx), 133 } 134 if ci.Cookie { 135 client.Jar = cookieJar 136 } 137 return client 138 } 139 140 // Transport is our http Transport which wraps an http.Transport 141 // * Sets the User Agent 142 // * Does logging 143 // * Updates metrics 144 type Transport struct { 145 *http.Transport 146 dump fs.DumpFlags 147 filterRequest func(req *http.Request) 148 userAgent string 149 headers []*fs.HTTPOption 150 metrics *Metrics 151 } 152 153 // newTransport wraps the http.Transport passed in and logs all 154 // roundtrips including the body if logBody is set. 155 func newTransport(ci *fs.ConfigInfo, transport *http.Transport) *Transport { 156 return &Transport{ 157 Transport: transport, 158 dump: ci.Dump, 159 userAgent: ci.UserAgent, 160 headers: ci.Headers, 161 metrics: DefaultMetrics, 162 } 163 } 164 165 // SetRequestFilter sets a filter to be used on each request 166 func (t *Transport) SetRequestFilter(f func(req *http.Request)) { 167 t.filterRequest = f 168 } 169 170 // A mutex to protect this map 171 var checkedHostMu sync.RWMutex 172 173 // A map of servers we have checked for time 174 var checkedHost = make(map[string]struct{}, 1) 175 176 // Check the server time is the same as ours, once for each server 177 func checkServerTime(req *http.Request, resp *http.Response) { 178 host := req.URL.Host 179 if req.Host != "" { 180 host = req.Host 181 } 182 checkedHostMu.RLock() 183 _, ok := checkedHost[host] 184 checkedHostMu.RUnlock() 185 if ok { 186 return 187 } 188 dateString := resp.Header.Get("Date") 189 if dateString == "" { 190 return 191 } 192 date, err := http.ParseTime(dateString) 193 if err != nil { 194 fs.Debugf(nil, "Couldn't parse Date: from server %s: %q: %v", host, dateString, err) 195 return 196 } 197 dt := time.Since(date) 198 const window = 5 * 60 * time.Second 199 if dt > window || dt < -window { 200 fs.Logf(nil, "Time may be set wrong - time from %q is %v different from this computer", host, dt) 201 } 202 checkedHostMu.Lock() 203 checkedHost[host] = struct{}{} 204 checkedHostMu.Unlock() 205 } 206 207 // cleanAuth gets rid of one authBuf header within the first 4k 208 func cleanAuth(buf, authBuf []byte) []byte { 209 // Find how much buffer to check 210 n := 4096 211 if len(buf) < n { 212 n = len(buf) 213 } 214 // See if there is an Authorization: header 215 i := bytes.Index(buf[:n], authBuf) 216 if i < 0 { 217 return buf 218 } 219 i += len(authBuf) 220 // Overwrite the next 4 chars with 'X' 221 for j := 0; i < len(buf) && j < 4; j++ { 222 if buf[i] == '\n' { 223 break 224 } 225 buf[i] = 'X' 226 i++ 227 } 228 // Snip out to the next '\n' 229 j := bytes.IndexByte(buf[i:], '\n') 230 if j < 0 { 231 return buf[:i] 232 } 233 n = copy(buf[i:], buf[i+j:]) 234 return buf[:i+n] 235 } 236 237 var authBufs = [][]byte{ 238 []byte("Authorization: "), 239 []byte("X-Auth-Token: "), 240 } 241 242 // cleanAuths gets rid of all the possible Auth headers 243 func cleanAuths(buf []byte) []byte { 244 for _, authBuf := range authBufs { 245 buf = cleanAuth(buf, authBuf) 246 } 247 return buf 248 } 249 250 // RoundTrip implements the RoundTripper interface. 251 func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 252 // Limit transactions per second if required 253 accounting.LimitTPS(req.Context()) 254 // Force user agent 255 req.Header.Set("User-Agent", t.userAgent) 256 // Set user defined headers 257 for _, option := range t.headers { 258 req.Header.Set(option.Key, option.Value) 259 } 260 // Filter the request if required 261 if t.filterRequest != nil { 262 t.filterRequest(req) 263 } 264 // Logf request 265 if t.dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpAuth|fs.DumpRequests|fs.DumpResponses) != 0 { 266 buf, _ := httputil.DumpRequestOut(req, t.dump&(fs.DumpBodies|fs.DumpRequests) != 0) 267 if t.dump&fs.DumpAuth == 0 { 268 buf = cleanAuths(buf) 269 } 270 logMutex.Lock() 271 fs.Debugf(nil, "%s", separatorReq) 272 fs.Debugf(nil, "%s (req %p)", "HTTP REQUEST", req) 273 fs.Debugf(nil, "%s", string(buf)) 274 fs.Debugf(nil, "%s", separatorReq) 275 logMutex.Unlock() 276 } 277 // Do round trip 278 resp, err = t.Transport.RoundTrip(req) 279 // Logf response 280 if t.dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpAuth|fs.DumpRequests|fs.DumpResponses) != 0 { 281 logMutex.Lock() 282 fs.Debugf(nil, "%s", separatorResp) 283 fs.Debugf(nil, "%s (req %p)", "HTTP RESPONSE", req) 284 if err != nil { 285 fs.Debugf(nil, "Error: %v", err) 286 } else { 287 buf, _ := httputil.DumpResponse(resp, t.dump&(fs.DumpBodies|fs.DumpResponses) != 0) 288 fs.Debugf(nil, "%s", string(buf)) 289 } 290 fs.Debugf(nil, "%s", separatorResp) 291 logMutex.Unlock() 292 } 293 // Update metrics 294 t.metrics.onResponse(req, resp) 295 296 if err == nil { 297 checkServerTime(req, resp) 298 } 299 return resp, err 300 }