Source file src/net/http/internal/httpcommon/httpcommon.go

     1  // Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
     2  //go:generate bundle -prefix= -o=httpcommon.go golang.org/x/net/internal/httpcommon
     3  
     4  package httpcommon
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"net/http/httptrace"
    11  	"net/textproto"
    12  	"net/url"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  
    18  	"golang.org/x/net/http/httpguts"
    19  	"golang.org/x/net/http2/hpack"
    20  )
    21  
    22  // The HTTP protocols are defined in terms of ASCII, not Unicode. This file
    23  // contains helper functions which may use Unicode-aware functions which would
    24  // otherwise be unsafe and could introduce vulnerabilities if used improperly.
    25  
    26  // asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t
    27  // are equal, ASCII-case-insensitively.
    28  func asciiEqualFold(s, t string) bool {
    29  	if len(s) != len(t) {
    30  		return false
    31  	}
    32  	for i := 0; i < len(s); i++ {
    33  		if lower(s[i]) != lower(t[i]) {
    34  			return false
    35  		}
    36  	}
    37  	return true
    38  }
    39  
    40  // lower returns the ASCII lowercase version of b.
    41  func lower(b byte) byte {
    42  	if 'A' <= b && b <= 'Z' {
    43  		return b + ('a' - 'A')
    44  	}
    45  	return b
    46  }
    47  
    48  // isASCIIPrint returns whether s is ASCII and printable according to
    49  // https://tools.ietf.org/html/rfc20#section-4.2.
    50  func isASCIIPrint(s string) bool {
    51  	for i := 0; i < len(s); i++ {
    52  		if s[i] < ' ' || s[i] > '~' {
    53  			return false
    54  		}
    55  	}
    56  	return true
    57  }
    58  
    59  // asciiToLower returns the lowercase version of s if s is ASCII and printable,
    60  // and whether or not it was.
    61  func asciiToLower(s string) (lower string, ok bool) {
    62  	if !isASCIIPrint(s) {
    63  		return "", false
    64  	}
    65  	return strings.ToLower(s), true
    66  }
    67  
    68  var (
    69  	commonBuildOnce   sync.Once
    70  	commonLowerHeader map[string]string // Go-Canonical-Case -> lower-case
    71  	commonCanonHeader map[string]string // lower-case -> Go-Canonical-Case
    72  )
    73  
    74  func buildCommonHeaderMapsOnce() {
    75  	commonBuildOnce.Do(buildCommonHeaderMaps)
    76  }
    77  
    78  func buildCommonHeaderMaps() {
    79  	common := []string{
    80  		"accept",
    81  		"accept-charset",
    82  		"accept-encoding",
    83  		"accept-language",
    84  		"accept-ranges",
    85  		"age",
    86  		"access-control-allow-credentials",
    87  		"access-control-allow-headers",
    88  		"access-control-allow-methods",
    89  		"access-control-allow-origin",
    90  		"access-control-expose-headers",
    91  		"access-control-max-age",
    92  		"access-control-request-headers",
    93  		"access-control-request-method",
    94  		"allow",
    95  		"authorization",
    96  		"cache-control",
    97  		"content-disposition",
    98  		"content-encoding",
    99  		"content-language",
   100  		"content-length",
   101  		"content-location",
   102  		"content-range",
   103  		"content-type",
   104  		"cookie",
   105  		"date",
   106  		"etag",
   107  		"expect",
   108  		"expires",
   109  		"from",
   110  		"host",
   111  		"if-match",
   112  		"if-modified-since",
   113  		"if-none-match",
   114  		"if-unmodified-since",
   115  		"last-modified",
   116  		"link",
   117  		"location",
   118  		"max-forwards",
   119  		"origin",
   120  		"proxy-authenticate",
   121  		"proxy-authorization",
   122  		"range",
   123  		"referer",
   124  		"refresh",
   125  		"retry-after",
   126  		"server",
   127  		"set-cookie",
   128  		"strict-transport-security",
   129  		"trailer",
   130  		"transfer-encoding",
   131  		"user-agent",
   132  		"vary",
   133  		"via",
   134  		"www-authenticate",
   135  		"x-forwarded-for",
   136  		"x-forwarded-proto",
   137  	}
   138  	commonLowerHeader = make(map[string]string, len(common))
   139  	commonCanonHeader = make(map[string]string, len(common))
   140  	for _, v := range common {
   141  		chk := textproto.CanonicalMIMEHeaderKey(v)
   142  		commonLowerHeader[chk] = v
   143  		commonCanonHeader[v] = chk
   144  	}
   145  }
   146  
   147  // LowerHeader returns the lowercase form of a header name,
   148  // used on the wire for HTTP/2 and HTTP/3 requests.
   149  func LowerHeader(v string) (lower string, ascii bool) {
   150  	buildCommonHeaderMapsOnce()
   151  	if s, ok := commonLowerHeader[v]; ok {
   152  		return s, true
   153  	}
   154  	return asciiToLower(v)
   155  }
   156  
   157  // CanonicalHeader canonicalizes a header name. (For example, "host" becomes "Host".)
   158  func CanonicalHeader(v string) string {
   159  	buildCommonHeaderMapsOnce()
   160  	if s, ok := commonCanonHeader[v]; ok {
   161  		return s
   162  	}
   163  	return textproto.CanonicalMIMEHeaderKey(v)
   164  }
   165  
   166  // CachedCanonicalHeader returns the canonical form of a well-known header name.
   167  func CachedCanonicalHeader(v string) (string, bool) {
   168  	buildCommonHeaderMapsOnce()
   169  	s, ok := commonCanonHeader[v]
   170  	return s, ok
   171  }
   172  
   173  var (
   174  	ErrRequestHeaderListSize = errors.New("request header list larger than peer's advertised limit")
   175  )
   176  
   177  // Request is a subset of http.Request.
   178  // It'd be simpler to pass an *http.Request, of course, but we can't depend on net/http
   179  // without creating a dependency cycle.
   180  type Request struct {
   181  	URL                 *url.URL
   182  	Method              string
   183  	Host                string
   184  	Header              map[string][]string
   185  	Trailer             map[string][]string
   186  	ActualContentLength int64 // 0 means 0, -1 means unknown
   187  }
   188  
   189  // EncodeHeadersParam is parameters to EncodeHeaders.
   190  type EncodeHeadersParam struct {
   191  	Request Request
   192  
   193  	// AddGzipHeader indicates that an "accept-encoding: gzip" header should be
   194  	// added to the request.
   195  	AddGzipHeader bool
   196  
   197  	// PeerMaxHeaderListSize, when non-zero, is the peer's MAX_HEADER_LIST_SIZE setting.
   198  	PeerMaxHeaderListSize uint64
   199  
   200  	// DefaultUserAgent is the User-Agent header to send when the request
   201  	// neither contains a User-Agent nor disables it.
   202  	DefaultUserAgent string
   203  }
   204  
   205  // EncodeHeadersParam is the result of EncodeHeaders.
   206  type EncodeHeadersResult struct {
   207  	HasBody     bool
   208  	HasTrailers bool
   209  }
   210  
   211  // EncodeHeaders constructs request headers common to HTTP/2 and HTTP/3.
   212  // It validates a request and calls headerf with each pseudo-header and header
   213  // for the request.
   214  // The headerf function is called with the validated, canonicalized header name.
   215  func EncodeHeaders(ctx context.Context, param EncodeHeadersParam, headerf func(name, value string)) (res EncodeHeadersResult, _ error) {
   216  	req := param.Request
   217  
   218  	// Check for invalid connection-level headers.
   219  	if err := checkConnHeaders(req.Header); err != nil {
   220  		return res, err
   221  	}
   222  
   223  	if req.URL == nil {
   224  		return res, errors.New("Request.URL is nil")
   225  	}
   226  
   227  	host := req.Host
   228  	if host == "" {
   229  		host = req.URL.Host
   230  	}
   231  	host, err := httpguts.PunycodeHostPort(host)
   232  	if err != nil {
   233  		return res, err
   234  	}
   235  	if !httpguts.ValidHostHeader(host) {
   236  		return res, errors.New("invalid Host header")
   237  	}
   238  
   239  	// isNormalConnect is true if this is a non-extended CONNECT request.
   240  	isNormalConnect := false
   241  	var protocol string
   242  	if vv := req.Header[":protocol"]; len(vv) > 0 {
   243  		protocol = vv[0]
   244  	}
   245  	if req.Method == "CONNECT" && protocol == "" {
   246  		isNormalConnect = true
   247  	} else if protocol != "" && req.Method != "CONNECT" {
   248  		return res, errors.New("invalid :protocol header in non-CONNECT request")
   249  	}
   250  
   251  	// Validate the path, except for non-extended CONNECT requests which have no path.
   252  	var path string
   253  	if !isNormalConnect {
   254  		path = req.URL.RequestURI()
   255  		if !validPseudoPath(path) {
   256  			orig := path
   257  			path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
   258  			if !validPseudoPath(path) {
   259  				if req.URL.Opaque != "" {
   260  					return res, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
   261  				} else {
   262  					return res, fmt.Errorf("invalid request :path %q", orig)
   263  				}
   264  			}
   265  		}
   266  	}
   267  
   268  	// Check for any invalid headers+trailers and return an error before we
   269  	// potentially pollute our hpack state. (We want to be able to
   270  	// continue to reuse the hpack encoder for future requests)
   271  	if err := validateHeaders(req.Header); err != "" {
   272  		return res, fmt.Errorf("invalid HTTP header %s", err)
   273  	}
   274  	if err := validateHeaders(req.Trailer); err != "" {
   275  		return res, fmt.Errorf("invalid HTTP trailer %s", err)
   276  	}
   277  
   278  	trailers, err := commaSeparatedTrailers(req.Trailer)
   279  	if err != nil {
   280  		return res, err
   281  	}
   282  
   283  	enumerateHeaders := func(f func(name, value string)) {
   284  		// 8.1.2.3 Request Pseudo-Header Fields
   285  		// The :path pseudo-header field includes the path and query parts of the
   286  		// target URI (the path-absolute production and optionally a '?' character
   287  		// followed by the query production, see Sections 3.3 and 3.4 of
   288  		// [RFC3986]).
   289  		f(":authority", host)
   290  		m := req.Method
   291  		if m == "" {
   292  			m = "GET"
   293  		}
   294  		f(":method", m)
   295  		if !isNormalConnect {
   296  			f(":path", path)
   297  			f(":scheme", req.URL.Scheme)
   298  		}
   299  		if protocol != "" {
   300  			f(":protocol", protocol)
   301  		}
   302  		if trailers != "" {
   303  			f("trailer", trailers)
   304  		}
   305  
   306  		var didUA bool
   307  		for k, vv := range req.Header {
   308  			if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") {
   309  				// Host is :authority, already sent.
   310  				// Content-Length is automatic, set below.
   311  				continue
   312  			} else if asciiEqualFold(k, "connection") ||
   313  				asciiEqualFold(k, "proxy-connection") ||
   314  				asciiEqualFold(k, "transfer-encoding") ||
   315  				asciiEqualFold(k, "upgrade") ||
   316  				asciiEqualFold(k, "keep-alive") {
   317  				// Per 8.1.2.2 Connection-Specific Header
   318  				// Fields, don't send connection-specific
   319  				// fields. We have already checked if any
   320  				// are error-worthy so just ignore the rest.
   321  				continue
   322  			} else if asciiEqualFold(k, "user-agent") {
   323  				// Match Go's http1 behavior: at most one
   324  				// User-Agent. If set to nil or empty string,
   325  				// then omit it. Otherwise if not mentioned,
   326  				// include the default (below).
   327  				didUA = true
   328  				if len(vv) < 1 {
   329  					continue
   330  				}
   331  				vv = vv[:1]
   332  				if vv[0] == "" {
   333  					continue
   334  				}
   335  			} else if asciiEqualFold(k, "cookie") {
   336  				// Per 8.1.2.5 To allow for better compression efficiency, the
   337  				// Cookie header field MAY be split into separate header fields,
   338  				// each with one or more cookie-pairs.
   339  				for _, v := range vv {
   340  					for {
   341  						p := strings.IndexByte(v, ';')
   342  						if p < 0 {
   343  							break
   344  						}
   345  						f("cookie", v[:p])
   346  						p++
   347  						// strip space after semicolon if any.
   348  						for p+1 <= len(v) && v[p] == ' ' {
   349  							p++
   350  						}
   351  						v = v[p:]
   352  					}
   353  					if len(v) > 0 {
   354  						f("cookie", v)
   355  					}
   356  				}
   357  				continue
   358  			} else if k == ":protocol" {
   359  				// :protocol pseudo-header was already sent above.
   360  				continue
   361  			}
   362  
   363  			for _, v := range vv {
   364  				f(k, v)
   365  			}
   366  		}
   367  		if shouldSendReqContentLength(req.Method, req.ActualContentLength) {
   368  			f("content-length", strconv.FormatInt(req.ActualContentLength, 10))
   369  		}
   370  		if param.AddGzipHeader {
   371  			f("accept-encoding", "gzip")
   372  		}
   373  		if !didUA {
   374  			f("user-agent", param.DefaultUserAgent)
   375  		}
   376  	}
   377  
   378  	// Do a first pass over the headers counting bytes to ensure
   379  	// we don't exceed cc.peerMaxHeaderListSize. This is done as a
   380  	// separate pass before encoding the headers to prevent
   381  	// modifying the hpack state.
   382  	if param.PeerMaxHeaderListSize > 0 {
   383  		hlSize := uint64(0)
   384  		enumerateHeaders(func(name, value string) {
   385  			hf := hpack.HeaderField{Name: name, Value: value}
   386  			hlSize += uint64(hf.Size())
   387  		})
   388  
   389  		if hlSize > param.PeerMaxHeaderListSize {
   390  			return res, ErrRequestHeaderListSize
   391  		}
   392  	}
   393  
   394  	trace := httptrace.ContextClientTrace(ctx)
   395  
   396  	// Header list size is ok. Write the headers.
   397  	enumerateHeaders(func(name, value string) {
   398  		name, ascii := LowerHeader(name)
   399  		if !ascii {
   400  			// Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
   401  			// field names have to be ASCII characters (just as in HTTP/1.x).
   402  			return
   403  		}
   404  
   405  		headerf(name, value)
   406  
   407  		if trace != nil && trace.WroteHeaderField != nil {
   408  			trace.WroteHeaderField(name, []string{value})
   409  		}
   410  	})
   411  
   412  	res.HasBody = req.ActualContentLength != 0
   413  	res.HasTrailers = trailers != ""
   414  	return res, nil
   415  }
   416  
   417  // IsRequestGzip reports whether we should add an Accept-Encoding: gzip header
   418  // for a request.
   419  func IsRequestGzip(method string, header map[string][]string, disableCompression bool) bool {
   420  	// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
   421  	if !disableCompression &&
   422  		len(header["Accept-Encoding"]) == 0 &&
   423  		len(header["Range"]) == 0 &&
   424  		method != "HEAD" {
   425  		// Request gzip only, not deflate. Deflate is ambiguous and
   426  		// not as universally supported anyway.
   427  		// See: https://zlib.net/zlib_faq.html#faq39
   428  		//
   429  		// Note that we don't request this for HEAD requests,
   430  		// due to a bug in nginx:
   431  		//   http://trac.nginx.org/nginx/ticket/358
   432  		//   https://golang.org/issue/5522
   433  		//
   434  		// We don't request gzip if the request is for a range, since
   435  		// auto-decoding a portion of a gzipped document will just fail
   436  		// anyway. See https://golang.org/issue/8923
   437  		return true
   438  	}
   439  	return false
   440  }
   441  
   442  // checkConnHeaders checks whether req has any invalid connection-level headers.
   443  //
   444  // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2-3
   445  // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.2-1
   446  //
   447  // Certain headers are special-cased as okay but not transmitted later.
   448  // For example, we allow "Transfer-Encoding: chunked", but drop the header when encoding.
   449  func checkConnHeaders(h map[string][]string) error {
   450  	if vv := h["Upgrade"]; len(vv) > 0 && (vv[0] != "" && vv[0] != "chunked") {
   451  		return fmt.Errorf("invalid Upgrade request header: %q", vv)
   452  	}
   453  	if vv := h["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
   454  		return fmt.Errorf("invalid Transfer-Encoding request header: %q", vv)
   455  	}
   456  	if vv := h["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) {
   457  		return fmt.Errorf("invalid Connection request header: %q", vv)
   458  	}
   459  	return nil
   460  }
   461  
   462  func commaSeparatedTrailers(trailer map[string][]string) (string, error) {
   463  	keys := make([]string, 0, len(trailer))
   464  	for k := range trailer {
   465  		k = CanonicalHeader(k)
   466  		switch k {
   467  		case "Transfer-Encoding", "Trailer", "Content-Length":
   468  			return "", fmt.Errorf("invalid Trailer key %q", k)
   469  		}
   470  		keys = append(keys, k)
   471  	}
   472  	if len(keys) > 0 {
   473  		sort.Strings(keys)
   474  		return strings.Join(keys, ","), nil
   475  	}
   476  	return "", nil
   477  }
   478  
   479  // validPseudoPath reports whether v is a valid :path pseudo-header
   480  // value. It must be either:
   481  //
   482  //   - a non-empty string starting with '/'
   483  //   - the string '*', for OPTIONS requests.
   484  //
   485  // For now this is only used a quick check for deciding when to clean
   486  // up Opaque URLs before sending requests from the Transport.
   487  // See golang.org/issue/16847
   488  //
   489  // We used to enforce that the path also didn't start with "//", but
   490  // Google's GFE accepts such paths and Chrome sends them, so ignore
   491  // that part of the spec. See golang.org/issue/19103.
   492  func validPseudoPath(v string) bool {
   493  	return (len(v) > 0 && v[0] == '/') || v == "*"
   494  }
   495  
   496  func validateHeaders(hdrs map[string][]string) string {
   497  	for k, vv := range hdrs {
   498  		if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" {
   499  			return fmt.Sprintf("name %q", k)
   500  		}
   501  		for _, v := range vv {
   502  			if !httpguts.ValidHeaderFieldValue(v) {
   503  				// Don't include the value in the error,
   504  				// because it may be sensitive.
   505  				return fmt.Sprintf("value for header %q", k)
   506  			}
   507  		}
   508  	}
   509  	return ""
   510  }
   511  
   512  // shouldSendReqContentLength reports whether we should send
   513  // a "content-length" request header. This logic is basically a copy of the net/http
   514  // transferWriter.shouldSendContentLength.
   515  // The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
   516  // -1 means unknown.
   517  func shouldSendReqContentLength(method string, contentLength int64) bool {
   518  	if contentLength > 0 {
   519  		return true
   520  	}
   521  	if contentLength < 0 {
   522  		return false
   523  	}
   524  	// For zero bodies, whether we send a content-length depends on the method.
   525  	// It also kinda doesn't matter for http2 either way, with END_STREAM.
   526  	switch method {
   527  	case "POST", "PUT", "PATCH":
   528  		return true
   529  	default:
   530  		return false
   531  	}
   532  }
   533  

View as plain text