gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

http.go (9987B)


      1 // Copyright 2018 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package acme
      6 
      7 import (
      8 	"bytes"
      9 	"context"
     10 	"crypto"
     11 	"crypto/rand"
     12 	"encoding/json"
     13 	"errors"
     14 	"fmt"
     15 	"io"
     16 	"math/big"
     17 	"net/http"
     18 	"strconv"
     19 	"strings"
     20 	"time"
     21 )
     22 
     23 // retryTimer encapsulates common logic for retrying unsuccessful requests.
     24 // It is not safe for concurrent use.
     25 type retryTimer struct {
     26 	// backoffFn provides backoff delay sequence for retries.
     27 	// See Client.RetryBackoff doc comment.
     28 	backoffFn func(n int, r *http.Request, res *http.Response) time.Duration
     29 	// n is the current retry attempt.
     30 	n int
     31 }
     32 
     33 func (t *retryTimer) inc() {
     34 	t.n++
     35 }
     36 
     37 // backoff pauses the current goroutine as described in Client.RetryBackoff.
     38 func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error {
     39 	d := t.backoffFn(t.n, r, res)
     40 	if d <= 0 {
     41 		return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n)
     42 	}
     43 	wakeup := time.NewTimer(d)
     44 	defer wakeup.Stop()
     45 	select {
     46 	case <-ctx.Done():
     47 		return ctx.Err()
     48 	case <-wakeup.C:
     49 		return nil
     50 	}
     51 }
     52 
     53 func (c *Client) retryTimer() *retryTimer {
     54 	f := c.RetryBackoff
     55 	if f == nil {
     56 		f = defaultBackoff
     57 	}
     58 	return &retryTimer{backoffFn: f}
     59 }
     60 
     61 // defaultBackoff provides default Client.RetryBackoff implementation
     62 // using a truncated exponential backoff algorithm,
     63 // as described in Client.RetryBackoff.
     64 //
     65 // The n argument is always bounded between 1 and 30.
     66 // The returned value is always greater than 0.
     67 func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
     68 	const max = 10 * time.Second
     69 	var jitter time.Duration
     70 	if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
     71 		// Set the minimum to 1ms to avoid a case where
     72 		// an invalid Retry-After value is parsed into 0 below,
     73 		// resulting in the 0 returned value which would unintentionally
     74 		// stop the retries.
     75 		jitter = (1 + time.Duration(x.Int64())) * time.Millisecond
     76 	}
     77 	if v, ok := res.Header["Retry-After"]; ok {
     78 		return retryAfter(v[0]) + jitter
     79 	}
     80 
     81 	if n < 1 {
     82 		n = 1
     83 	}
     84 	if n > 30 {
     85 		n = 30
     86 	}
     87 	d := time.Duration(1<<uint(n-1))*time.Second + jitter
     88 	if d > max {
     89 		return max
     90 	}
     91 	return d
     92 }
     93 
     94 // retryAfter parses a Retry-After HTTP header value,
     95 // trying to convert v into an int (seconds) or use http.ParseTime otherwise.
     96 // It returns zero value if v cannot be parsed.
     97 func retryAfter(v string) time.Duration {
     98 	if i, err := strconv.Atoi(v); err == nil {
     99 		return time.Duration(i) * time.Second
    100 	}
    101 	t, err := http.ParseTime(v)
    102 	if err != nil {
    103 		return 0
    104 	}
    105 	return t.Sub(timeNow())
    106 }
    107 
    108 // resOkay is a function that reports whether the provided response is okay.
    109 // It is expected to keep the response body unread.
    110 type resOkay func(*http.Response) bool
    111 
    112 // wantStatus returns a function which reports whether the code
    113 // matches the status code of a response.
    114 func wantStatus(codes ...int) resOkay {
    115 	return func(res *http.Response) bool {
    116 		for _, code := range codes {
    117 			if code == res.StatusCode {
    118 				return true
    119 			}
    120 		}
    121 		return false
    122 	}
    123 }
    124 
    125 // get issues an unsigned GET request to the specified URL.
    126 // It returns a non-error value only when ok reports true.
    127 //
    128 // get retries unsuccessful attempts according to c.RetryBackoff
    129 // until the context is done or a non-retriable error is received.
    130 func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
    131 	retry := c.retryTimer()
    132 	for {
    133 		req, err := http.NewRequest("GET", url, nil)
    134 		if err != nil {
    135 			return nil, err
    136 		}
    137 		res, err := c.doNoRetry(ctx, req)
    138 		switch {
    139 		case err != nil:
    140 			return nil, err
    141 		case ok(res):
    142 			return res, nil
    143 		case isRetriable(res.StatusCode):
    144 			retry.inc()
    145 			resErr := responseError(res)
    146 			res.Body.Close()
    147 			// Ignore the error value from retry.backoff
    148 			// and return the one from last retry, as received from the CA.
    149 			if retry.backoff(ctx, req, res) != nil {
    150 				return nil, resErr
    151 			}
    152 		default:
    153 			defer res.Body.Close()
    154 			return nil, responseError(res)
    155 		}
    156 	}
    157 }
    158 
    159 // postAsGet is POST-as-GET, a replacement for GET in RFC 8555
    160 // as described in https://tools.ietf.org/html/rfc8555#section-6.3.
    161 // It makes a POST request in KID form with zero JWS payload.
    162 // See nopayload doc comments in jws.go.
    163 func (c *Client) postAsGet(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
    164 	return c.post(ctx, nil, url, noPayload, ok)
    165 }
    166 
    167 // post issues a signed POST request in JWS format using the provided key
    168 // to the specified URL. If key is nil, c.Key is used instead.
    169 // It returns a non-error value only when ok reports true.
    170 //
    171 // post retries unsuccessful attempts according to c.RetryBackoff
    172 // until the context is done or a non-retriable error is received.
    173 // It uses postNoRetry to make individual requests.
    174 func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body interface{}, ok resOkay) (*http.Response, error) {
    175 	retry := c.retryTimer()
    176 	for {
    177 		res, req, err := c.postNoRetry(ctx, key, url, body)
    178 		if err != nil {
    179 			return nil, err
    180 		}
    181 		if ok(res) {
    182 			return res, nil
    183 		}
    184 		resErr := responseError(res)
    185 		res.Body.Close()
    186 		switch {
    187 		// Check for bad nonce before isRetriable because it may have been returned
    188 		// with an unretriable response code such as 400 Bad Request.
    189 		case isBadNonce(resErr):
    190 			// Consider any previously stored nonce values to be invalid.
    191 			c.clearNonces()
    192 		case !isRetriable(res.StatusCode):
    193 			return nil, resErr
    194 		}
    195 		retry.inc()
    196 		// Ignore the error value from retry.backoff
    197 		// and return the one from last retry, as received from the CA.
    198 		if err := retry.backoff(ctx, req, res); err != nil {
    199 			return nil, resErr
    200 		}
    201 	}
    202 }
    203 
    204 // postNoRetry signs the body with the given key and POSTs it to the provided url.
    205 // It is used by c.post to retry unsuccessful attempts.
    206 // The body argument must be JSON-serializable.
    207 //
    208 // If key argument is nil, c.Key is used to sign the request.
    209 // If key argument is nil and c.accountKID returns a non-zero keyID,
    210 // the request is sent in KID form. Otherwise, JWK form is used.
    211 //
    212 // In practice, when interfacing with RFC-compliant CAs most requests are sent in KID form
    213 // and JWK is used only when KID is unavailable: new account endpoint and certificate
    214 // revocation requests authenticated by a cert key.
    215 // See jwsEncodeJSON for other details.
    216 func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, *http.Request, error) {
    217 	kid := noKeyID
    218 	if key == nil {
    219 		if c.Key == nil {
    220 			return nil, nil, errors.New("acme: Client.Key must be populated to make POST requests")
    221 		}
    222 		key = c.Key
    223 		kid = c.accountKID(ctx)
    224 	}
    225 	nonce, err := c.popNonce(ctx, url)
    226 	if err != nil {
    227 		return nil, nil, err
    228 	}
    229 	b, err := jwsEncodeJSON(body, key, kid, nonce, url)
    230 	if err != nil {
    231 		return nil, nil, err
    232 	}
    233 	req, err := http.NewRequest("POST", url, bytes.NewReader(b))
    234 	if err != nil {
    235 		return nil, nil, err
    236 	}
    237 	req.Header.Set("Content-Type", "application/jose+json")
    238 	res, err := c.doNoRetry(ctx, req)
    239 	if err != nil {
    240 		return nil, nil, err
    241 	}
    242 	c.addNonce(res.Header)
    243 	return res, req, nil
    244 }
    245 
    246 // doNoRetry issues a request req, replacing its context (if any) with ctx.
    247 func (c *Client) doNoRetry(ctx context.Context, req *http.Request) (*http.Response, error) {
    248 	req.Header.Set("User-Agent", c.userAgent())
    249 	res, err := c.httpClient().Do(req.WithContext(ctx))
    250 	if err != nil {
    251 		select {
    252 		case <-ctx.Done():
    253 			// Prefer the unadorned context error.
    254 			// (The acme package had tests assuming this, previously from ctxhttp's
    255 			// behavior, predating net/http supporting contexts natively)
    256 			// TODO(bradfitz): reconsider this in the future. But for now this
    257 			// requires no test updates.
    258 			return nil, ctx.Err()
    259 		default:
    260 			return nil, err
    261 		}
    262 	}
    263 	return res, nil
    264 }
    265 
    266 func (c *Client) httpClient() *http.Client {
    267 	if c.HTTPClient != nil {
    268 		return c.HTTPClient
    269 	}
    270 	return http.DefaultClient
    271 }
    272 
    273 // packageVersion is the version of the module that contains this package, for
    274 // sending as part of the User-Agent header. It's set in version_go112.go.
    275 var packageVersion string
    276 
    277 // userAgent returns the User-Agent header value. It includes the package name,
    278 // the module version (if available), and the c.UserAgent value (if set).
    279 func (c *Client) userAgent() string {
    280 	ua := "golang.org/x/crypto/acme"
    281 	if packageVersion != "" {
    282 		ua += "@" + packageVersion
    283 	}
    284 	if c.UserAgent != "" {
    285 		ua = c.UserAgent + " " + ua
    286 	}
    287 	return ua
    288 }
    289 
    290 // isBadNonce reports whether err is an ACME "badnonce" error.
    291 func isBadNonce(err error) bool {
    292 	// According to the spec badNonce is urn:ietf:params:acme:error:badNonce.
    293 	// However, ACME servers in the wild return their versions of the error.
    294 	// See https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-5.4
    295 	// and https://github.com/letsencrypt/boulder/blob/0e07eacb/docs/acme-divergences.md#section-66.
    296 	ae, ok := err.(*Error)
    297 	return ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce")
    298 }
    299 
    300 // isRetriable reports whether a request can be retried
    301 // based on the response status code.
    302 //
    303 // Note that a "bad nonce" error is returned with a non-retriable 400 Bad Request code.
    304 // Callers should parse the response and check with isBadNonce.
    305 func isRetriable(code int) bool {
    306 	return code <= 399 || code >= 500 || code == http.StatusTooManyRequests
    307 }
    308 
    309 // responseError creates an error of Error type from resp.
    310 func responseError(resp *http.Response) error {
    311 	// don't care if ReadAll returns an error:
    312 	// json.Unmarshal will fail in that case anyway
    313 	b, _ := io.ReadAll(resp.Body)
    314 	e := &wireError{Status: resp.StatusCode}
    315 	if err := json.Unmarshal(b, e); err != nil {
    316 		// this is not a regular error response:
    317 		// populate detail with anything we received,
    318 		// e.Status will already contain HTTP response code value
    319 		e.Detail = string(b)
    320 		if e.Detail == "" {
    321 			e.Detail = resp.Status
    322 		}
    323 	}
    324 	return e.error(resp.Header)
    325 }