request.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. // Copyright 2025 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. package httpcommon
  5. import (
  6. "context"
  7. "errors"
  8. "fmt"
  9. "net/http/httptrace"
  10. "net/textproto"
  11. "net/url"
  12. "sort"
  13. "strconv"
  14. "strings"
  15. "golang.org/x/net/http/httpguts"
  16. "golang.org/x/net/http2/hpack"
  17. )
  18. var (
  19. ErrRequestHeaderListSize = errors.New("request header list larger than peer's advertised limit")
  20. )
  21. // Request is a subset of http.Request.
  22. // It'd be simpler to pass an *http.Request, of course, but we can't depend on net/http
  23. // without creating a dependency cycle.
  24. type Request struct {
  25. URL *url.URL
  26. Method string
  27. Host string
  28. Header map[string][]string
  29. Trailer map[string][]string
  30. ActualContentLength int64 // 0 means 0, -1 means unknown
  31. }
  32. // EncodeHeadersParam is parameters to EncodeHeaders.
  33. type EncodeHeadersParam struct {
  34. Request Request
  35. // AddGzipHeader indicates that an "accept-encoding: gzip" header should be
  36. // added to the request.
  37. AddGzipHeader bool
  38. // PeerMaxHeaderListSize, when non-zero, is the peer's MAX_HEADER_LIST_SIZE setting.
  39. PeerMaxHeaderListSize uint64
  40. // DefaultUserAgent is the User-Agent header to send when the request
  41. // neither contains a User-Agent nor disables it.
  42. DefaultUserAgent string
  43. }
  44. // EncodeHeadersParam is the result of EncodeHeaders.
  45. type EncodeHeadersResult struct {
  46. HasBody bool
  47. HasTrailers bool
  48. }
  49. // EncodeHeaders constructs request headers common to HTTP/2 and HTTP/3.
  50. // It validates a request and calls headerf with each pseudo-header and header
  51. // for the request.
  52. // The headerf function is called with the validated, canonicalized header name.
  53. func EncodeHeaders(ctx context.Context, param EncodeHeadersParam, headerf func(name, value string)) (res EncodeHeadersResult, _ error) {
  54. req := param.Request
  55. // Check for invalid connection-level headers.
  56. if err := checkConnHeaders(req.Header); err != nil {
  57. return res, err
  58. }
  59. if req.URL == nil {
  60. return res, errors.New("Request.URL is nil")
  61. }
  62. host := req.Host
  63. if host == "" {
  64. host = req.URL.Host
  65. }
  66. host, err := httpguts.PunycodeHostPort(host)
  67. if err != nil {
  68. return res, err
  69. }
  70. if !httpguts.ValidHostHeader(host) {
  71. return res, errors.New("invalid Host header")
  72. }
  73. // isNormalConnect is true if this is a non-extended CONNECT request.
  74. isNormalConnect := false
  75. var protocol string
  76. if vv := req.Header[":protocol"]; len(vv) > 0 {
  77. protocol = vv[0]
  78. }
  79. if req.Method == "CONNECT" && protocol == "" {
  80. isNormalConnect = true
  81. } else if protocol != "" && req.Method != "CONNECT" {
  82. return res, errors.New("invalid :protocol header in non-CONNECT request")
  83. }
  84. // Validate the path, except for non-extended CONNECT requests which have no path.
  85. var path string
  86. if !isNormalConnect {
  87. path = req.URL.RequestURI()
  88. if !validPseudoPath(path) {
  89. orig := path
  90. path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
  91. if !validPseudoPath(path) {
  92. if req.URL.Opaque != "" {
  93. return res, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
  94. } else {
  95. return res, fmt.Errorf("invalid request :path %q", orig)
  96. }
  97. }
  98. }
  99. }
  100. // Check for any invalid headers+trailers and return an error before we
  101. // potentially pollute our hpack state. (We want to be able to
  102. // continue to reuse the hpack encoder for future requests)
  103. if err := validateHeaders(req.Header); err != "" {
  104. return res, fmt.Errorf("invalid HTTP header %s", err)
  105. }
  106. if err := validateHeaders(req.Trailer); err != "" {
  107. return res, fmt.Errorf("invalid HTTP trailer %s", err)
  108. }
  109. trailers, err := commaSeparatedTrailers(req.Trailer)
  110. if err != nil {
  111. return res, err
  112. }
  113. enumerateHeaders := func(f func(name, value string)) {
  114. // 8.1.2.3 Request Pseudo-Header Fields
  115. // The :path pseudo-header field includes the path and query parts of the
  116. // target URI (the path-absolute production and optionally a '?' character
  117. // followed by the query production, see Sections 3.3 and 3.4 of
  118. // [RFC3986]).
  119. f(":authority", host)
  120. m := req.Method
  121. if m == "" {
  122. m = "GET"
  123. }
  124. f(":method", m)
  125. if !isNormalConnect {
  126. f(":path", path)
  127. f(":scheme", req.URL.Scheme)
  128. }
  129. if protocol != "" {
  130. f(":protocol", protocol)
  131. }
  132. if trailers != "" {
  133. f("trailer", trailers)
  134. }
  135. var didUA bool
  136. for k, vv := range req.Header {
  137. if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") {
  138. // Host is :authority, already sent.
  139. // Content-Length is automatic, set below.
  140. continue
  141. } else if asciiEqualFold(k, "connection") ||
  142. asciiEqualFold(k, "proxy-connection") ||
  143. asciiEqualFold(k, "transfer-encoding") ||
  144. asciiEqualFold(k, "upgrade") ||
  145. asciiEqualFold(k, "keep-alive") {
  146. // Per 8.1.2.2 Connection-Specific Header
  147. // Fields, don't send connection-specific
  148. // fields. We have already checked if any
  149. // are error-worthy so just ignore the rest.
  150. continue
  151. } else if asciiEqualFold(k, "user-agent") {
  152. // Match Go's http1 behavior: at most one
  153. // User-Agent. If set to nil or empty string,
  154. // then omit it. Otherwise if not mentioned,
  155. // include the default (below).
  156. didUA = true
  157. if len(vv) < 1 {
  158. continue
  159. }
  160. vv = vv[:1]
  161. if vv[0] == "" {
  162. continue
  163. }
  164. } else if asciiEqualFold(k, "cookie") {
  165. // Per 8.1.2.5 To allow for better compression efficiency, the
  166. // Cookie header field MAY be split into separate header fields,
  167. // each with one or more cookie-pairs.
  168. for _, v := range vv {
  169. for {
  170. p := strings.IndexByte(v, ';')
  171. if p < 0 {
  172. break
  173. }
  174. f("cookie", v[:p])
  175. p++
  176. // strip space after semicolon if any.
  177. for p+1 <= len(v) && v[p] == ' ' {
  178. p++
  179. }
  180. v = v[p:]
  181. }
  182. if len(v) > 0 {
  183. f("cookie", v)
  184. }
  185. }
  186. continue
  187. } else if k == ":protocol" {
  188. // :protocol pseudo-header was already sent above.
  189. continue
  190. }
  191. for _, v := range vv {
  192. f(k, v)
  193. }
  194. }
  195. if shouldSendReqContentLength(req.Method, req.ActualContentLength) {
  196. f("content-length", strconv.FormatInt(req.ActualContentLength, 10))
  197. }
  198. if param.AddGzipHeader {
  199. f("accept-encoding", "gzip")
  200. }
  201. if !didUA {
  202. f("user-agent", param.DefaultUserAgent)
  203. }
  204. }
  205. // Do a first pass over the headers counting bytes to ensure
  206. // we don't exceed cc.peerMaxHeaderListSize. This is done as a
  207. // separate pass before encoding the headers to prevent
  208. // modifying the hpack state.
  209. if param.PeerMaxHeaderListSize > 0 {
  210. hlSize := uint64(0)
  211. enumerateHeaders(func(name, value string) {
  212. hf := hpack.HeaderField{Name: name, Value: value}
  213. hlSize += uint64(hf.Size())
  214. })
  215. if hlSize > param.PeerMaxHeaderListSize {
  216. return res, ErrRequestHeaderListSize
  217. }
  218. }
  219. trace := httptrace.ContextClientTrace(ctx)
  220. // Header list size is ok. Write the headers.
  221. enumerateHeaders(func(name, value string) {
  222. name, ascii := LowerHeader(name)
  223. if !ascii {
  224. // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
  225. // field names have to be ASCII characters (just as in HTTP/1.x).
  226. return
  227. }
  228. headerf(name, value)
  229. if trace != nil && trace.WroteHeaderField != nil {
  230. trace.WroteHeaderField(name, []string{value})
  231. }
  232. })
  233. res.HasBody = req.ActualContentLength != 0
  234. res.HasTrailers = trailers != ""
  235. return res, nil
  236. }
  237. // IsRequestGzip reports whether we should add an Accept-Encoding: gzip header
  238. // for a request.
  239. func IsRequestGzip(method string, header map[string][]string, disableCompression bool) bool {
  240. // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
  241. if !disableCompression &&
  242. len(header["Accept-Encoding"]) == 0 &&
  243. len(header["Range"]) == 0 &&
  244. method != "HEAD" {
  245. // Request gzip only, not deflate. Deflate is ambiguous and
  246. // not as universally supported anyway.
  247. // See: https://zlib.net/zlib_faq.html#faq39
  248. //
  249. // Note that we don't request this for HEAD requests,
  250. // due to a bug in nginx:
  251. // http://trac.nginx.org/nginx/ticket/358
  252. // https://golang.org/issue/5522
  253. //
  254. // We don't request gzip if the request is for a range, since
  255. // auto-decoding a portion of a gzipped document will just fail
  256. // anyway. See https://golang.org/issue/8923
  257. return true
  258. }
  259. return false
  260. }
  261. // checkConnHeaders checks whether req has any invalid connection-level headers.
  262. //
  263. // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2-3
  264. // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.2-1
  265. //
  266. // Certain headers are special-cased as okay but not transmitted later.
  267. // For example, we allow "Transfer-Encoding: chunked", but drop the header when encoding.
  268. func checkConnHeaders(h map[string][]string) error {
  269. if vv := h["Upgrade"]; len(vv) > 0 && (vv[0] != "" && vv[0] != "chunked") {
  270. return fmt.Errorf("invalid Upgrade request header: %q", vv)
  271. }
  272. if vv := h["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
  273. return fmt.Errorf("invalid Transfer-Encoding request header: %q", vv)
  274. }
  275. if vv := h["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) {
  276. return fmt.Errorf("invalid Connection request header: %q", vv)
  277. }
  278. return nil
  279. }
  280. func commaSeparatedTrailers(trailer map[string][]string) (string, error) {
  281. keys := make([]string, 0, len(trailer))
  282. for k := range trailer {
  283. k = CanonicalHeader(k)
  284. switch k {
  285. case "Transfer-Encoding", "Trailer", "Content-Length":
  286. return "", fmt.Errorf("invalid Trailer key %q", k)
  287. }
  288. keys = append(keys, k)
  289. }
  290. if len(keys) > 0 {
  291. sort.Strings(keys)
  292. return strings.Join(keys, ","), nil
  293. }
  294. return "", nil
  295. }
  296. // validPseudoPath reports whether v is a valid :path pseudo-header
  297. // value. It must be either:
  298. //
  299. // - a non-empty string starting with '/'
  300. // - the string '*', for OPTIONS requests.
  301. //
  302. // For now this is only used a quick check for deciding when to clean
  303. // up Opaque URLs before sending requests from the Transport.
  304. // See golang.org/issue/16847
  305. //
  306. // We used to enforce that the path also didn't start with "//", but
  307. // Google's GFE accepts such paths and Chrome sends them, so ignore
  308. // that part of the spec. See golang.org/issue/19103.
  309. func validPseudoPath(v string) bool {
  310. return (len(v) > 0 && v[0] == '/') || v == "*"
  311. }
  312. func validateHeaders(hdrs map[string][]string) string {
  313. for k, vv := range hdrs {
  314. if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" {
  315. return fmt.Sprintf("name %q", k)
  316. }
  317. for _, v := range vv {
  318. if !httpguts.ValidHeaderFieldValue(v) {
  319. // Don't include the value in the error,
  320. // because it may be sensitive.
  321. return fmt.Sprintf("value for header %q", k)
  322. }
  323. }
  324. }
  325. return ""
  326. }
  327. // shouldSendReqContentLength reports whether we should send
  328. // a "content-length" request header. This logic is basically a copy of the net/http
  329. // transferWriter.shouldSendContentLength.
  330. // The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
  331. // -1 means unknown.
  332. func shouldSendReqContentLength(method string, contentLength int64) bool {
  333. if contentLength > 0 {
  334. return true
  335. }
  336. if contentLength < 0 {
  337. return false
  338. }
  339. // For zero bodies, whether we send a content-length depends on the method.
  340. // It also kinda doesn't matter for http2 either way, with END_STREAM.
  341. switch method {
  342. case "POST", "PUT", "PATCH":
  343. return true
  344. default:
  345. return false
  346. }
  347. }
  348. // ServerRequestParam is parameters to NewServerRequest.
  349. type ServerRequestParam struct {
  350. Method string
  351. Scheme, Authority, Path string
  352. Protocol string
  353. Header map[string][]string
  354. }
  355. // ServerRequestResult is the result of NewServerRequest.
  356. type ServerRequestResult struct {
  357. // Various http.Request fields.
  358. URL *url.URL
  359. RequestURI string
  360. Trailer map[string][]string
  361. NeedsContinue bool // client provided an "Expect: 100-continue" header
  362. // If the request should be rejected, this is a short string suitable for passing
  363. // to the http2 package's CountError function.
  364. // It might be a bit odd to return errors this way rather than returing an error,
  365. // but this ensures we don't forget to include a CountError reason.
  366. InvalidReason string
  367. }
  368. func NewServerRequest(rp ServerRequestParam) ServerRequestResult {
  369. needsContinue := httpguts.HeaderValuesContainsToken(rp.Header["Expect"], "100-continue")
  370. if needsContinue {
  371. delete(rp.Header, "Expect")
  372. }
  373. // Merge Cookie headers into one "; "-delimited value.
  374. if cookies := rp.Header["Cookie"]; len(cookies) > 1 {
  375. rp.Header["Cookie"] = []string{strings.Join(cookies, "; ")}
  376. }
  377. // Setup Trailers
  378. var trailer map[string][]string
  379. for _, v := range rp.Header["Trailer"] {
  380. for _, key := range strings.Split(v, ",") {
  381. key = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(key))
  382. switch key {
  383. case "Transfer-Encoding", "Trailer", "Content-Length":
  384. // Bogus. (copy of http1 rules)
  385. // Ignore.
  386. default:
  387. if trailer == nil {
  388. trailer = make(map[string][]string)
  389. }
  390. trailer[key] = nil
  391. }
  392. }
  393. }
  394. delete(rp.Header, "Trailer")
  395. // "':authority' MUST NOT include the deprecated userinfo subcomponent
  396. // for "http" or "https" schemed URIs."
  397. // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8
  398. if strings.IndexByte(rp.Authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") {
  399. return ServerRequestResult{
  400. InvalidReason: "userinfo_in_authority",
  401. }
  402. }
  403. var url_ *url.URL
  404. var requestURI string
  405. if rp.Method == "CONNECT" && rp.Protocol == "" {
  406. url_ = &url.URL{Host: rp.Authority}
  407. requestURI = rp.Authority // mimic HTTP/1 server behavior
  408. } else {
  409. var err error
  410. url_, err = url.ParseRequestURI(rp.Path)
  411. if err != nil {
  412. return ServerRequestResult{
  413. InvalidReason: "bad_path",
  414. }
  415. }
  416. requestURI = rp.Path
  417. }
  418. return ServerRequestResult{
  419. URL: url_,
  420. NeedsContinue: needsContinue,
  421. RequestURI: requestURI,
  422. Trailer: trailer,
  423. }
  424. }