httpcommon.mx raw

   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  	"bytes"
  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 bytes.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 bytes.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 := [][]byte{
  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 = map[string]string{}
 139  	commonCanonHeader = map[string]string{}
 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][][]byte
 185  	Trailer             map[string][][]byte
 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 = bytes.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 := bytes.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, [][]byte{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][][]byte, 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][][]byte) 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][][]byte) (string, error) {
 463  	keys := [][]byte{: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 string(bytes.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][][]byte) 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  
 534  // ServerRequestParam is parameters to NewServerRequest.
 535  type ServerRequestParam struct {
 536  	Method                  string
 537  	Scheme, Authority, Path string
 538  	Protocol                string
 539  	Header                  map[string][][]byte
 540  }
 541  
 542  // ServerRequestResult is the result of NewServerRequest.
 543  type ServerRequestResult struct {
 544  	// Various http.Request fields.
 545  	URL        *url.URL
 546  	RequestURI string
 547  	Trailer    map[string][][]byte
 548  
 549  	NeedsContinue bool // client provided an "Expect: 100-continue" header
 550  
 551  	// If the request should be rejected, this is a short string suitable for passing
 552  	// to the http2 package's CountError function.
 553  	// It might be a bit odd to return errors this way rather than returing an error,
 554  	// but this ensures we don't forget to include a CountError reason.
 555  	InvalidReason string
 556  }
 557  
 558  func NewServerRequest(rp ServerRequestParam) ServerRequestResult {
 559  	needsContinue := httpguts.HeaderValuesContainsToken(rp.Header["Expect"], "100-continue")
 560  	if needsContinue {
 561  		delete(rp.Header, "Expect")
 562  	}
 563  	// Merge Cookie headers into one "; "-delimited value.
 564  	if cookies := rp.Header["Cookie"]; len(cookies) > 1 {
 565  		rp.Header["Cookie"] = [][]byte{bytes.Join(cookies, "; ")}
 566  	}
 567  
 568  	// Setup Trailers
 569  	var trailer map[string][][]byte
 570  	for _, v := range rp.Header["Trailer"] {
 571  		for _, key := range bytes.Split(v, ",") {
 572  			key = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(key))
 573  			switch key {
 574  			case "Transfer-Encoding", "Trailer", "Content-Length":
 575  				// Bogus. (copy of http1 rules)
 576  				// Ignore.
 577  			default:
 578  				if trailer == nil {
 579  					trailer = map[string][][]byte{}
 580  				}
 581  				trailer[key] = nil
 582  			}
 583  		}
 584  	}
 585  	delete(rp.Header, "Trailer")
 586  
 587  	// "':authority' MUST NOT include the deprecated userinfo subcomponent
 588  	// for "http" or "https" schemed URIs."
 589  	// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8
 590  	if bytes.IndexByte(rp.Authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") {
 591  		return ServerRequestResult{
 592  			InvalidReason: "userinfo_in_authority",
 593  		}
 594  	}
 595  
 596  	var url_ *url.URL
 597  	var requestURI string
 598  	if rp.Method == "CONNECT" && rp.Protocol == "" {
 599  		url_ = &url.URL{Host: rp.Authority}
 600  		requestURI = rp.Authority // mimic HTTP/1 server behavior
 601  	} else {
 602  		var err error
 603  		url_, err = url.ParseRequestURI(rp.Path)
 604  		if err != nil {
 605  			return ServerRequestResult{
 606  				InvalidReason: "bad_path",
 607  			}
 608  		}
 609  		requestURI = rp.Path
 610  	}
 611  
 612  	return ServerRequestResult{
 613  		URL:           url_,
 614  		NeedsContinue: needsContinue,
 615  		RequestURI:    requestURI,
 616  		Trailer:       trailer,
 617  	}
 618  }
 619