aboutsummaryrefslogtreecommitdiff
path: root/src/net/http
diff options
context:
space:
mode:
authorNicholas S. Husin <nsh@golang.org>2026-01-22 22:48:46 -0500
committerNicholas Husin <nsh@golang.org>2026-01-23 11:09:53 -0800
commitca5ffe0092363f21df2c57b50144ba056f260049 (patch)
treee181aa7d9e80504a0173fc8dfc70241a08713db1 /src/net/http
parent4af8ad24ee3b55ccb644680d95e2502e5551ea0b (diff)
downloadgo-ca5ffe0092363f21df2c57b50144ba056f260049.tar.xz
all: update vendored dependencies
This CL does the following: 1. Bundles up golang.org/x/net/internal/httpsfv since h2_bundle.go now relies on it. 2. Modifies h2_bundle.go import mapping to account for httpsfv package. 3. Updates all vendored dependencies using golang.org/x/build/cmd/updatestd. For #75500 Change-Id: Ia2f41ad606092fe20b62f946266190502b146977 Reviewed-on: https://go-review.googlesource.com/c/go/+/738621 Reviewed-by: Nicholas Husin <husin@google.com> Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/net/http')
-rw-r--r--src/net/http/h2_bundle.go297
-rw-r--r--src/net/http/http.go2
-rw-r--r--src/net/http/internal/httpsfv/httpsfv.go666
3 files changed, 921 insertions, 44 deletions
diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go
index 5a7420bc7e..7793d1cd5e 100644
--- a/src/net/http/h2_bundle.go
+++ b/src/net/http/h2_bundle.go
@@ -1,7 +1,7 @@
//go:build !nethttpomithttp2
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
-// $ bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 -import=golang.org/x/net/internal/httpcommon=net/http/internal/httpcommon golang.org/x/net/http2
+// $ bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 -import=golang.org/x/net/internal/httpcommon=net/http/internal/httpcommon -import=golang.org/x/net/internal/httpsfv=net/http/internal/httpsfv golang.org/x/net/http2
// Package http2 implements the HTTP/2 protocol.
//
@@ -36,11 +36,13 @@ import (
"net"
"net/http/httptrace"
"net/http/internal/httpcommon"
+ "net/http/internal/httpsfv"
"net/textproto"
"net/url"
"os"
"reflect"
"runtime"
+ "slices"
"sort"
"strconv"
"strings"
@@ -1031,6 +1033,10 @@ func http2shouldRetryDial(call *http2dialCall, req *Request) bool {
return call.ctx.Err() != nil
}
+func http2clientPriorityDisabled(s *Server) bool {
+ return s.DisableClientPriority
+}
+
// http2Config is a package-internal version of net/http.HTTP2Config.
//
// http.HTTP2Config was added in Go 1.24.
@@ -1586,33 +1592,36 @@ const http2frameHeaderLen = 9
var http2padZeros = make([]byte, 255) // zeros for padding
// A FrameType is a registered frame type as defined in
-// https://httpwg.org/specs/rfc7540.html#rfc.section.11.2
+// https://httpwg.org/specs/rfc7540.html#rfc.section.11.2 and other future
+// RFCs.
type http2FrameType uint8
const (
- http2FrameData http2FrameType = 0x0
- http2FrameHeaders http2FrameType = 0x1
- http2FramePriority http2FrameType = 0x2
- http2FrameRSTStream http2FrameType = 0x3
- http2FrameSettings http2FrameType = 0x4
- http2FramePushPromise http2FrameType = 0x5
- http2FramePing http2FrameType = 0x6
- http2FrameGoAway http2FrameType = 0x7
- http2FrameWindowUpdate http2FrameType = 0x8
- http2FrameContinuation http2FrameType = 0x9
+ http2FrameData http2FrameType = 0x0
+ http2FrameHeaders http2FrameType = 0x1
+ http2FramePriority http2FrameType = 0x2
+ http2FrameRSTStream http2FrameType = 0x3
+ http2FrameSettings http2FrameType = 0x4
+ http2FramePushPromise http2FrameType = 0x5
+ http2FramePing http2FrameType = 0x6
+ http2FrameGoAway http2FrameType = 0x7
+ http2FrameWindowUpdate http2FrameType = 0x8
+ http2FrameContinuation http2FrameType = 0x9
+ http2FramePriorityUpdate http2FrameType = 0x10
)
var http2frameNames = [...]string{
- http2FrameData: "DATA",
- http2FrameHeaders: "HEADERS",
- http2FramePriority: "PRIORITY",
- http2FrameRSTStream: "RST_STREAM",
- http2FrameSettings: "SETTINGS",
- http2FramePushPromise: "PUSH_PROMISE",
- http2FramePing: "PING",
- http2FrameGoAway: "GOAWAY",
- http2FrameWindowUpdate: "WINDOW_UPDATE",
- http2FrameContinuation: "CONTINUATION",
+ http2FrameData: "DATA",
+ http2FrameHeaders: "HEADERS",
+ http2FramePriority: "PRIORITY",
+ http2FrameRSTStream: "RST_STREAM",
+ http2FrameSettings: "SETTINGS",
+ http2FramePushPromise: "PUSH_PROMISE",
+ http2FramePing: "PING",
+ http2FrameGoAway: "GOAWAY",
+ http2FrameWindowUpdate: "WINDOW_UPDATE",
+ http2FrameContinuation: "CONTINUATION",
+ http2FramePriorityUpdate: "PRIORITY_UPDATE",
}
func (t http2FrameType) String() string {
@@ -1688,16 +1697,17 @@ var http2flagName = map[http2FrameType]map[http2Flags]string{
type http2frameParser func(fc *http2frameCache, fh http2FrameHeader, countError func(string), payload []byte) (http2Frame, error)
var http2frameParsers = [...]http2frameParser{
- http2FrameData: http2parseDataFrame,
- http2FrameHeaders: http2parseHeadersFrame,
- http2FramePriority: http2parsePriorityFrame,
- http2FrameRSTStream: http2parseRSTStreamFrame,
- http2FrameSettings: http2parseSettingsFrame,
- http2FramePushPromise: http2parsePushPromise,
- http2FramePing: http2parsePingFrame,
- http2FrameGoAway: http2parseGoAwayFrame,
- http2FrameWindowUpdate: http2parseWindowUpdateFrame,
- http2FrameContinuation: http2parseContinuationFrame,
+ http2FrameData: http2parseDataFrame,
+ http2FrameHeaders: http2parseHeadersFrame,
+ http2FramePriority: http2parsePriorityFrame,
+ http2FrameRSTStream: http2parseRSTStreamFrame,
+ http2FrameSettings: http2parseSettingsFrame,
+ http2FramePushPromise: http2parsePushPromise,
+ http2FramePing: http2parsePingFrame,
+ http2FrameGoAway: http2parseGoAwayFrame,
+ http2FrameWindowUpdate: http2parseWindowUpdateFrame,
+ http2FrameContinuation: http2parseContinuationFrame,
+ http2FramePriorityUpdate: http2parsePriorityUpdateFrame,
}
func http2typeFrameParser(t http2FrameType) http2frameParser {
@@ -2746,9 +2756,34 @@ type http2PriorityFrame struct {
http2PriorityParam
}
-var http2defaultRFC9218Priority = http2PriorityParam{
- incremental: 0,
- urgency: 3,
+// defaultRFC9218Priority determines what priority we should use as the default
+// value.
+//
+// According to RFC 9218, by default, streams should be given an urgency of 3
+// and should be non-incremental. However, making streams non-incremental by
+// default would be a huge change to our historical behavior where we would
+// round-robin writes across streams. When streams are non-incremental, we
+// would process streams of the same urgency one-by-one to completion instead.
+//
+// To avoid such a sudden change which might break some HTTP/2 users, this
+// function allows the caller to specify whether they can actually use the
+// default value as specified in RFC 9218. If not, this function will return a
+// priority value where streams are incremental by default instead: effectively
+// a round-robin between stream of the same urgency.
+//
+// As an example, a server might not be able to use the RFC 9218 default value
+// when it's not sure that the client it is serving is aware of RFC 9218.
+func http2defaultRFC9218Priority(canUseDefault bool) http2PriorityParam {
+ if canUseDefault {
+ return http2PriorityParam{
+ urgency: 3,
+ incremental: 0,
+ }
+ }
+ return http2PriorityParam{
+ urgency: 3,
+ incremental: 1,
+ }
}
// Note that HTTP/2 has had two different prioritization schemes, and
@@ -2832,6 +2867,74 @@ func (f *http2Framer) WritePriority(streamID uint32, p http2PriorityParam) error
return f.endWrite()
}
+// PriorityUpdateFrame is a PRIORITY_UPDATE frame as described in
+// https://www.rfc-editor.org/rfc/rfc9218.html#name-the-priority_update-frame.
+type http2PriorityUpdateFrame struct {
+ http2FrameHeader
+ Priority string
+ PrioritizedStreamID uint32
+}
+
+func http2parseRFC9218Priority(s string, canUseDefault bool) (p http2PriorityParam, ok bool) {
+ p = http2defaultRFC9218Priority(canUseDefault)
+ ok = httpsfv.ParseDictionary(s, func(key, val, _ string) {
+ switch key {
+ case "u":
+ if u, ok := httpsfv.ParseInteger(val); ok && u >= 0 && u <= 7 {
+ p.urgency = uint8(u)
+ }
+ case "i":
+ if i, ok := httpsfv.ParseBoolean(val); ok {
+ if i {
+ p.incremental = 1
+ } else {
+ p.incremental = 0
+ }
+ }
+ }
+ })
+ if !ok {
+ return http2defaultRFC9218Priority(canUseDefault), ok
+ }
+ return p, true
+}
+
+func http2parsePriorityUpdateFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), payload []byte) (http2Frame, error) {
+ if fh.StreamID != 0 {
+ countError("frame_priority_update_non_zero_stream")
+ return nil, http2connError{http2ErrCodeProtocol, "PRIORITY_UPDATE frame with non-zero stream ID"}
+ }
+ if len(payload) < 4 {
+ countError("frame_priority_update_bad_length")
+ return nil, http2connError{http2ErrCodeFrameSize, fmt.Sprintf("PRIORITY_UPDATE frame payload size was %d; want at least 4", len(payload))}
+ }
+ v := binary.BigEndian.Uint32(payload[:4])
+ streamID := v & 0x7fffffff // mask off high bit
+ if streamID == 0 {
+ countError("frame_priority_update_prioritizing_zero_stream")
+ return nil, http2connError{http2ErrCodeProtocol, "PRIORITY_UPDATE frame with prioritized stream ID of zero"}
+ }
+ return &http2PriorityUpdateFrame{
+ http2FrameHeader: fh,
+ PrioritizedStreamID: streamID,
+ Priority: string(payload[4:]),
+ }, nil
+}
+
+// WritePriorityUpdate writes a PRIORITY_UPDATE frame.
+//
+// It will perform exactly one Write to the underlying Writer.
+// It is the caller's responsibility to not call other Write methods concurrently.
+func (f *http2Framer) WritePriorityUpdate(streamID uint32, priority string) error {
+ if !http2validStreamID(streamID) && !f.AllowIllegalWrites {
+ return http2errStreamID
+ }
+ f.startWrite(http2FramePriorityUpdate, 0, 0)
+ f.writeUint32(streamID)
+ f.writeBytes([]byte(priority))
+ return f.endWrite()
+}
+
// A RSTStreamFrame allows for abnormal termination of a stream.
// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.4
type http2RSTStreamFrame struct {
@@ -3113,6 +3216,23 @@ func (mh *http2MetaHeadersFrame) PseudoFields() []hpack.HeaderField {
return mh.Fields
}
+func (mh *http2MetaHeadersFrame) rfc9218Priority(priorityAware bool) (p http2PriorityParam, priorityAwareAfter, hasIntermediary bool) {
+ var s string
+ for _, field := range mh.Fields {
+ if field.Name == "priority" {
+ s = field.Value
+ priorityAware = true
+ }
+ if slices.Contains([]string{"via", "forwarded", "x-forwarded-for"}, field.Name) {
+ hasIntermediary = true
+ }
+ }
+ // No need to check for ok. parseRFC9218Priority will return a default
+ // value if there is no priority field or if the field cannot be parsed.
+ p, _ = http2parseRFC9218Priority(s, priorityAware && !hasIntermediary)
+ return p, priorityAware, hasIntermediary
+}
+
func (mh *http2MetaHeadersFrame) checkPseudos() error {
var isRequest, isResponse bool
pf := mh.PseudoFields()
@@ -3619,6 +3739,7 @@ const (
http2SettingMaxFrameSize http2SettingID = 0x5
http2SettingMaxHeaderListSize http2SettingID = 0x6
http2SettingEnableConnectProtocol http2SettingID = 0x8
+ http2SettingNoRFC7540Priorities http2SettingID = 0x9
)
var http2settingName = map[http2SettingID]string{
@@ -3629,6 +3750,7 @@ var http2settingName = map[http2SettingID]string{
http2SettingMaxFrameSize: "MAX_FRAME_SIZE",
http2SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
http2SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL",
+ http2SettingNoRFC7540Priorities: "NO_RFC7540_PRIORITIES",
}
func (s http2SettingID) String() string {
@@ -4454,10 +4576,13 @@ func (s *http2Server) serveConn(c net.Conn, opts *http2ServeConnOpts, newf func(
sc.conn.SetWriteDeadline(time.Time{})
}
- if s.NewWriteScheduler != nil {
+ switch {
+ case s.NewWriteScheduler != nil:
sc.writeSched = s.NewWriteScheduler()
- } else {
+ case http2clientPriorityDisabled(http1srv):
sc.writeSched = http2newRoundRobinWriteScheduler()
+ default:
+ sc.writeSched = http2newPriorityWriteSchedulerRFC9218()
}
// These start at the RFC-specified defaults. If there is a higher
@@ -4630,6 +4755,23 @@ type http2serverConn struct {
// Used by startGracefulShutdown.
shutdownOnce sync.Once
+
+ // Used for RFC 9218 prioritization.
+ hasIntermediary bool // connection is done via an intermediary / proxy
+ priorityAware bool // the client has sent priority signal, meaning that it is aware of it.
+}
+
+func (sc *http2serverConn) writeSchedIgnoresRFC7540() bool {
+ switch sc.writeSched.(type) {
+ case *http2priorityWriteSchedulerRFC9218:
+ return true
+ case *http2randomWriteScheduler:
+ return true
+ case *http2roundRobinWriteScheduler:
+ return true
+ default:
+ return false
+ }
}
func (sc *http2serverConn) maxHeaderListSize() uint32 {
@@ -4923,6 +5065,9 @@ func (sc *http2serverConn) serve(conf http2http2Config) {
if !http2disableExtendedConnectProtocol {
settings = append(settings, http2Setting{http2SettingEnableConnectProtocol, 1})
}
+ if sc.writeSchedIgnoresRFC7540() {
+ settings = append(settings, http2Setting{http2SettingNoRFC7540Priorities, 1})
+ }
sc.writeFrame(http2FrameWriteRequest{
write: settings,
})
@@ -5611,6 +5756,8 @@ func (sc *http2serverConn) processFrame(f http2Frame) error {
// A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE
// frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
return sc.countError("push_promise", http2ConnectionError(http2ErrCodeProtocol))
+ case *http2PriorityUpdateFrame:
+ return sc.processPriorityUpdate(f)
default:
sc.vlogf("http2: server ignoring frame: %v", f.Header())
return nil
@@ -5791,6 +5938,10 @@ func (sc *http2serverConn) processSetting(s http2Setting) error {
case http2SettingEnableConnectProtocol:
// Receipt of this parameter by a server does not
// have any impact
+ case http2SettingNoRFC7540Priorities:
+ if s.Val > 1 {
+ return http2ConnectionError(http2ErrCodeProtocol)
+ }
default:
// Unknown setting: "An endpoint that receives a SETTINGS
// frame with any unknown or unsupported identifier MUST
@@ -6061,13 +6212,33 @@ func (sc *http2serverConn) processHeaders(f *http2MetaHeadersFrame) error {
if f.StreamEnded() {
initialState = http2stateHalfClosedRemote
}
- st := sc.newStream(id, 0, initialState)
+
+ // We are handling two special cases here:
+ // 1. When a request is sent via an intermediary, we force priority to be
+ // u=3,i. This is essentially a round-robin behavior, and is done to ensure
+ // fairness between, for example, multiple clients using the same proxy.
+ // 2. Until a client has shown that it is aware of RFC 9218, we make its
+ // streams non-incremental by default. This is done to preserve the
+ // historical behavior of handling streams in a round-robin manner, rather
+ // than one-by-one to completion.
+ initialPriority := http2defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary)
+ if _, ok := sc.writeSched.(*http2priorityWriteSchedulerRFC9218); ok && !sc.hasIntermediary {
+ headerPriority, priorityAware, hasIntermediary := f.rfc9218Priority(sc.priorityAware)
+ initialPriority = headerPriority
+ sc.hasIntermediary = hasIntermediary
+ if priorityAware {
+ sc.priorityAware = true
+ }
+ }
+ st := sc.newStream(id, 0, initialState, initialPriority)
if f.HasPriority() {
if err := sc.checkPriority(f.StreamID, f.Priority); err != nil {
return err
}
- sc.writeSched.AdjustStream(st.id, f.Priority)
+ if !sc.writeSchedIgnoresRFC7540() {
+ sc.writeSched.AdjustStream(st.id, f.Priority)
+ }
}
rw, req, err := sc.newWriterAndRequest(st, f)
@@ -6108,7 +6279,7 @@ func (sc *http2serverConn) upgradeRequest(req *Request) {
sc.serveG.check()
id := uint32(1)
sc.maxClientStreamID = id
- st := sc.newStream(id, 0, http2stateHalfClosedRemote)
+ st := sc.newStream(id, 0, http2stateHalfClosedRemote, http2defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary))
st.reqTrailer = req.Trailer
if st.reqTrailer != nil {
st.trailer = make(Header)
@@ -6173,11 +6344,32 @@ func (sc *http2serverConn) processPriority(f *http2PriorityFrame) error {
if err := sc.checkPriority(f.StreamID, f.http2PriorityParam); err != nil {
return err
}
+ // We need to avoid calling AdjustStream when using the RFC 9218 write
+ // scheduler. Otherwise, incremental's zero value in PriorityParam will
+ // unexpectedly make all streams non-incremental. This causes us to process
+ // streams one-by-one to completion rather than doing it in a round-robin
+ // manner (the historical behavior), which might be unexpected to users.
+ if sc.writeSchedIgnoresRFC7540() {
+ return nil
+ }
sc.writeSched.AdjustStream(f.StreamID, f.http2PriorityParam)
return nil
}
-func (sc *http2serverConn) newStream(id, pusherID uint32, state http2streamState) *http2stream {
+func (sc *http2serverConn) processPriorityUpdate(f *http2PriorityUpdateFrame) error {
+ sc.priorityAware = true
+ if _, ok := sc.writeSched.(*http2priorityWriteSchedulerRFC9218); !ok {
+ return nil
+ }
+ p, ok := http2parseRFC9218Priority(f.Priority, sc.priorityAware)
+ if !ok {
+ return sc.countError("unparsable_priority_update", http2streamError(f.PrioritizedStreamID, http2ErrCodeProtocol))
+ }
+ sc.writeSched.AdjustStream(f.PrioritizedStreamID, p)
+ return nil
+}
+
+func (sc *http2serverConn) newStream(id, pusherID uint32, state http2streamState, priority http2PriorityParam) *http2stream {
sc.serveG.check()
if id == 0 {
panic("internal error: cannot create stream with id 0")
@@ -6200,7 +6392,7 @@ func (sc *http2serverConn) newStream(id, pusherID uint32, state http2streamState
}
sc.streams[id] = st
- sc.writeSched.OpenStream(st.id, http2OpenStreamOptions{PusherID: pusherID})
+ sc.writeSched.OpenStream(st.id, http2OpenStreamOptions{PusherID: pusherID, priority: priority})
if st.isPushed() {
sc.curPushedStreams++
} else {
@@ -7206,7 +7398,7 @@ func (sc *http2serverConn) startPush(msg *http2startPushRequest) {
// transition to "half closed (remote)" after sending the initial HEADERS, but
// we start in "half closed (remote)" for simplicity.
// See further comments at the definition of stateHalfClosedRemote.
- promised := sc.newStream(promisedID, msg.parent.id, http2stateHalfClosedRemote)
+ promised := sc.newStream(promisedID, msg.parent.id, http2stateHalfClosedRemote, http2defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary))
rw, req, err := sc.newWriterAndRequestNoBody(promised, httpcommon.ServerRequestParam{
Method: msg.method,
Scheme: msg.url.Scheme,
@@ -11452,6 +11644,10 @@ type http2PriorityWriteSchedulerConfig struct {
// frames by following HTTP/2 priorities as described in RFC 7540 Section 5.3.
// If cfg is nil, default options are used.
func http2NewPriorityWriteScheduler(cfg *http2PriorityWriteSchedulerConfig) http2WriteScheduler {
+ return http2newPriorityWriteSchedulerRFC7540(cfg)
+}
+
+func http2newPriorityWriteSchedulerRFC7540(cfg *http2PriorityWriteSchedulerConfig) http2WriteScheduler {
if cfg == nil {
// For justification of these defaults, see:
// https://docs.google.com/document/d/1oLhNg1skaWD4_DtaoCxdSRN5erEXrH-KnLrMwEpOtFY
@@ -11875,6 +12071,15 @@ type http2priorityWriteSchedulerRFC9218 struct {
// incremental streams or not, when urgency is the same in a given Pop()
// call.
prioritizeIncremental bool
+
+ // priorityUpdateBuf is used to buffer the most recent PRIORITY_UPDATE we
+ // receive per https://www.rfc-editor.org/rfc/rfc9218.html#name-the-priority_update-frame.
+ priorityUpdateBuf struct {
+ // streamID being 0 means that the buffer is empty. This is a safe
+ // assumption as PRIORITY_UPDATE for stream 0 is a PROTOCOL_ERROR.
+ streamID uint32
+ priority http2PriorityParam
+ }
}
func http2newPriorityWriteSchedulerRFC9218() http2WriteScheduler {
@@ -11888,6 +12093,10 @@ func (ws *http2priorityWriteSchedulerRFC9218) OpenStream(streamID uint32, opt ht
if ws.streams[streamID].location != nil {
panic(fmt.Errorf("stream %d already opened", streamID))
}
+ if streamID == ws.priorityUpdateBuf.streamID {
+ ws.priorityUpdateBuf.streamID = 0
+ opt.priority = ws.priorityUpdateBuf.priority
+ }
q := ws.queuePool.get()
ws.streams[streamID] = http2streamMetadata{
location: q,
@@ -11933,6 +12142,8 @@ func (ws *http2priorityWriteSchedulerRFC9218) AdjustStream(streamID uint32, prio
metadata := ws.streams[streamID]
q, u, i := metadata.location, metadata.priority.urgency, metadata.priority.incremental
if q == nil {
+ ws.priorityUpdateBuf.streamID = streamID
+ ws.priorityUpdateBuf.priority = priority
return
}
diff --git a/src/net/http/http.go b/src/net/http/http.go
index d346e60646..d3e9a2787a 100644
--- a/src/net/http/http.go
+++ b/src/net/http/http.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:generate bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 -import=golang.org/x/net/internal/httpcommon=net/http/internal/httpcommon golang.org/x/net/http2
+//go:generate bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 -import=golang.org/x/net/internal/httpcommon=net/http/internal/httpcommon -import=golang.org/x/net/internal/httpsfv=net/http/internal/httpsfv golang.org/x/net/http2
package http
diff --git a/src/net/http/internal/httpsfv/httpsfv.go b/src/net/http/internal/httpsfv/httpsfv.go
new file mode 100644
index 0000000000..07f07ebf1c
--- /dev/null
+++ b/src/net/http/internal/httpsfv/httpsfv.go
@@ -0,0 +1,666 @@
+// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
+//go:generate bundle -o httpsfv.go -prefix= golang.org/x/net/internal/httpsfv
+
+// Package httpsfv provides functionality for dealing with HTTP Structured
+// Field Values.
+//
+
+package httpsfv
+
+import (
+ "slices"
+ "strconv"
+ "strings"
+ "time"
+ "unicode/utf8"
+)
+
+func isLCAlpha(b byte) bool {
+ return (b >= 'a' && b <= 'z')
+}
+
+func isAlpha(b byte) bool {
+ return isLCAlpha(b) || (b >= 'A' && b <= 'Z')
+}
+
+func isDigit(b byte) bool {
+ return b >= '0' && b <= '9'
+}
+
+func isVChar(b byte) bool {
+ return b >= 0x21 && b <= 0x7e
+}
+
+func isSP(b byte) bool {
+ return b == 0x20
+}
+
+func isTChar(b byte) bool {
+ if isAlpha(b) || isDigit(b) {
+ return true
+ }
+ return slices.Contains([]byte{'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'}, b)
+}
+
+func countLeftWhitespace(s string) int {
+ i := 0
+ for _, ch := range []byte(s) {
+ if ch != ' ' && ch != '\t' {
+ break
+ }
+ i++
+ }
+ return i
+}
+
+// https://www.rfc-editor.org/rfc/rfc4648#section-8.
+func decOctetHex(ch1, ch2 byte) (ch byte, ok bool) {
+ decBase16 := func(in byte) (out byte, ok bool) {
+ if !isDigit(in) && !(in >= 'a' && in <= 'f') {
+ return 0, false
+ }
+ if isDigit(in) {
+ return in - '0', true
+ }
+ return in - 'a' + 10, true
+ }
+
+ if ch1, ok = decBase16(ch1); !ok {
+ return 0, ok
+ }
+ if ch2, ok = decBase16(ch2); !ok {
+ return 0, ok
+ }
+ return ch1<<4 | ch2, true
+}
+
+// ParseList parses a list from a given HTTP Structured Field Values.
+//
+// Given an HTTP SFV string that represents a list, it will call the given
+// function using each of the members and parameters contained in the list.
+// This allows the caller to extract information out of the list.
+//
+// This function will return once it encounters the end of the string, or
+// something that is not a list. If it cannot consume the entire given
+// string, the ok value returned will be false.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-list.
+func ParseList(s string, f func(member, param string)) (ok bool) {
+ for len(s) != 0 {
+ var member, param string
+ if len(s) != 0 && s[0] == '(' {
+ if member, s, ok = consumeBareInnerList(s, nil); !ok {
+ return ok
+ }
+ } else {
+ if member, s, ok = consumeBareItem(s); !ok {
+ return ok
+ }
+ }
+ if param, s, ok = consumeParameter(s, nil); !ok {
+ return ok
+ }
+ if f != nil {
+ f(member, param)
+ }
+
+ s = s[countLeftWhitespace(s):]
+ if len(s) == 0 {
+ break
+ }
+ if s[0] != ',' {
+ return false
+ }
+ s = s[1:]
+ s = s[countLeftWhitespace(s):]
+ if len(s) == 0 {
+ return false
+ }
+ }
+ return true
+}
+
+// consumeBareInnerList consumes an inner list
+// (https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-inner-list),
+// except for the inner list's top-most parameter.
+// For example, given `(a;b c;d);e`, it will consume only `(a;b c;d)`.
+func consumeBareInnerList(s string, f func(bareItem, param string)) (consumed, rest string, ok bool) {
+ if len(s) == 0 || s[0] != '(' {
+ return "", s, false
+ }
+ rest = s[1:]
+ for len(rest) != 0 {
+ var bareItem, param string
+ rest = rest[countLeftWhitespace(rest):]
+ if len(rest) != 0 && rest[0] == ')' {
+ rest = rest[1:]
+ break
+ }
+ if bareItem, rest, ok = consumeBareItem(rest); !ok {
+ return "", s, ok
+ }
+ if param, rest, ok = consumeParameter(rest, nil); !ok {
+ return "", s, ok
+ }
+ if len(rest) == 0 || (rest[0] != ')' && !isSP(rest[0])) {
+ return "", s, false
+ }
+ if f != nil {
+ f(bareItem, param)
+ }
+ }
+ return s[:len(s)-len(rest)], rest, true
+}
+
+// ParseBareInnerList parses a bare inner list from a given HTTP Structured
+// Field Values.
+//
+// We define a bare inner list as an inner list
+// (https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-inner-list),
+// without the top-most parameter of the inner list. For example, given the
+// inner list `(a;b c;d);e`, the bare inner list would be `(a;b c;d)`.
+//
+// Given an HTTP SFV string that represents a bare inner list, it will call the
+// given function using each of the bare item and parameter within the bare
+// inner list. This allows the caller to extract information out of the bare
+// inner list.
+//
+// This function will return once it encounters the end of the bare inner list,
+// or something that is not a bare inner list. If it cannot consume the entire
+// given string, the ok value returned will be false.
+func ParseBareInnerList(s string, f func(bareItem, param string)) (ok bool) {
+ _, rest, ok := consumeBareInnerList(s, f)
+ return rest == "" && ok
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item.
+func consumeItem(s string, f func(bareItem, param string)) (consumed, rest string, ok bool) {
+ var bareItem, param string
+ if bareItem, rest, ok = consumeBareItem(s); !ok {
+ return "", s, ok
+ }
+ if param, rest, ok = consumeParameter(rest, nil); !ok {
+ return "", s, ok
+ }
+ if f != nil {
+ f(bareItem, param)
+ }
+ return s[:len(s)-len(rest)], rest, true
+}
+
+// ParseItem parses an item from a given HTTP Structured Field Values.
+//
+// Given an HTTP SFV string that represents an item, it will call the given
+// function once, with the bare item and the parameter of the item. This allows
+// the caller to extract information out of the item.
+//
+// This function will return once it encounters the end of the string, or
+// something that is not an item. If it cannot consume the entire given
+// string, the ok value returned will be false.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item.
+func ParseItem(s string, f func(bareItem, param string)) (ok bool) {
+ _, rest, ok := consumeItem(s, f)
+ return rest == "" && ok
+}
+
+// ParseDictionary parses a dictionary from a given HTTP Structured Field
+// Values.
+//
+// Given an HTTP SFV string that represents a dictionary, it will call the
+// given function using each of the keys, values, and parameters contained in
+// the dictionary. This allows the caller to extract information out of the
+// dictionary.
+//
+// This function will return once it encounters the end of the string, or
+// something that is not a dictionary. If it cannot consume the entire given
+// string, the ok value returned will be false.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-dictionary.
+func ParseDictionary(s string, f func(key, val, param string)) (ok bool) {
+ for len(s) != 0 {
+ var key, val, param string
+ val = "?1" // Default value for empty val is boolean true.
+ if key, s, ok = consumeKey(s); !ok {
+ return ok
+ }
+ if len(s) != 0 && s[0] == '=' {
+ s = s[1:]
+ if len(s) != 0 && s[0] == '(' {
+ if val, s, ok = consumeBareInnerList(s, nil); !ok {
+ return ok
+ }
+ } else {
+ if val, s, ok = consumeBareItem(s); !ok {
+ return ok
+ }
+ }
+ }
+ if param, s, ok = consumeParameter(s, nil); !ok {
+ return ok
+ }
+ if f != nil {
+ f(key, val, param)
+ }
+ s = s[countLeftWhitespace(s):]
+ if len(s) == 0 {
+ break
+ }
+ if s[0] == ',' {
+ s = s[1:]
+ }
+ s = s[countLeftWhitespace(s):]
+ if len(s) == 0 {
+ return false
+ }
+ }
+ return true
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#parse-param.
+func consumeParameter(s string, f func(key, val string)) (consumed, rest string, ok bool) {
+ rest = s
+ for len(rest) != 0 {
+ var key, val string
+ val = "?1" // Default value for empty val is boolean true.
+ if rest[0] != ';' {
+ break
+ }
+ rest = rest[1:]
+ rest = rest[countLeftWhitespace(rest):]
+ key, rest, ok = consumeKey(rest)
+ if !ok {
+ return "", s, ok
+ }
+ if len(rest) != 0 && rest[0] == '=' {
+ rest = rest[1:]
+ val, rest, ok = consumeBareItem(rest)
+ if !ok {
+ return "", s, ok
+ }
+ }
+ if f != nil {
+ f(key, val)
+ }
+ }
+ return s[:len(s)-len(rest)], rest, true
+}
+
+// ParseParameter parses a parameter from a given HTTP Structured Field Values.
+//
+// Given an HTTP SFV string that represents a parameter, it will call the given
+// function using each of the keys and values contained in the parameter. This
+// allows the caller to extract information out of the parameter.
+//
+// This function will return once it encounters the end of the string, or
+// something that is not a parameter. If it cannot consume the entire given
+// string, the ok value returned will be false.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#parse-param.
+func ParseParameter(s string, f func(key, val string)) (ok bool) {
+ _, rest, ok := consumeParameter(s, f)
+ return rest == "" && ok
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-key.
+func consumeKey(s string) (consumed, rest string, ok bool) {
+ if len(s) == 0 || (!isLCAlpha(s[0]) && s[0] != '*') {
+ return "", s, false
+ }
+ i := 0
+ for _, ch := range []byte(s) {
+ if !isLCAlpha(ch) && !isDigit(ch) && !slices.Contains([]byte("_-.*"), ch) {
+ break
+ }
+ i++
+ }
+ return s[:i], s[i:], true
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim.
+func consumeIntegerOrDecimal(s string) (consumed, rest string, ok bool) {
+ var i, signOffset, periodIndex int
+ var isDecimal bool
+ if i < len(s) && s[i] == '-' {
+ i++
+ signOffset++
+ }
+ if i >= len(s) {
+ return "", s, false
+ }
+ if !isDigit(s[i]) {
+ return "", s, false
+ }
+ for i < len(s) {
+ ch := s[i]
+ if isDigit(ch) {
+ i++
+ continue
+ }
+ if !isDecimal && ch == '.' {
+ if i-signOffset > 12 {
+ return "", s, false
+ }
+ periodIndex = i
+ isDecimal = true
+ i++
+ continue
+ }
+ break
+ }
+ if !isDecimal && i-signOffset > 15 {
+ return "", s, false
+ }
+ if isDecimal {
+ if i-signOffset > 16 {
+ return "", s, false
+ }
+ if s[i-1] == '.' {
+ return "", s, false
+ }
+ if i-periodIndex-1 > 3 {
+ return "", s, false
+ }
+ }
+ return s[:i], s[i:], true
+}
+
+// ParseInteger parses an integer from a given HTTP Structured Field Values.
+//
+// The entire HTTP SFV string must consist of a valid integer. It returns the
+// parsed integer and an ok boolean value, indicating success or not.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim.
+func ParseInteger(s string) (parsed int64, ok bool) {
+ if _, rest, ok := consumeIntegerOrDecimal(s); !ok || rest != "" {
+ return 0, false
+ }
+ if n, err := strconv.ParseInt(s, 10, 64); err == nil {
+ return n, true
+ }
+ return 0, false
+}
+
+// ParseDecimal parses a decimal from a given HTTP Structured Field Values.
+//
+// The entire HTTP SFV string must consist of a valid decimal. It returns the
+// parsed decimal and an ok boolean value, indicating success or not.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim.
+func ParseDecimal(s string) (parsed float64, ok bool) {
+ if _, rest, ok := consumeIntegerOrDecimal(s); !ok || rest != "" {
+ return 0, false
+ }
+ if !strings.Contains(s, ".") {
+ return 0, false
+ }
+ if n, err := strconv.ParseFloat(s, 64); err == nil {
+ return n, true
+ }
+ return 0, false
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string.
+func consumeString(s string) (consumed, rest string, ok bool) {
+ if len(s) == 0 || s[0] != '"' {
+ return "", s, false
+ }
+ for i := 1; i < len(s); i++ {
+ switch ch := s[i]; ch {
+ case '\\':
+ if i+1 >= len(s) {
+ return "", s, false
+ }
+ i++
+ if ch = s[i]; ch != '"' && ch != '\\' {
+ return "", s, false
+ }
+ case '"':
+ return s[:i+1], s[i+1:], true
+ default:
+ if !isVChar(ch) && !isSP(ch) {
+ return "", s, false
+ }
+ }
+ }
+ return "", s, false
+}
+
+// ParseString parses a Go string from a given HTTP Structured Field Values.
+//
+// The entire HTTP SFV string must consist of a valid string. It returns the
+// parsed string and an ok boolean value, indicating success or not.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string.
+func ParseString(s string) (parsed string, ok bool) {
+ if _, rest, ok := consumeString(s); !ok || rest != "" {
+ return "", false
+ }
+ return s[1 : len(s)-1], true
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-token
+func consumeToken(s string) (consumed, rest string, ok bool) {
+ if len(s) == 0 || (!isAlpha(s[0]) && s[0] != '*') {
+ return "", s, false
+ }
+ i := 0
+ for _, ch := range []byte(s) {
+ if !isTChar(ch) && !slices.Contains([]byte(":/"), ch) {
+ break
+ }
+ i++
+ }
+ return s[:i], s[i:], true
+}
+
+// ParseToken parses a token from a given HTTP Structured Field Values.
+//
+// The entire HTTP SFV string must consist of a valid token. It returns the
+// parsed token and an ok boolean value, indicating success or not.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-token
+func ParseToken(s string) (parsed string, ok bool) {
+ if _, rest, ok := consumeToken(s); !ok || rest != "" {
+ return "", false
+ }
+ return s, true
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-byte-sequence.
+func consumeByteSequence(s string) (consumed, rest string, ok bool) {
+ if len(s) == 0 || s[0] != ':' {
+ return "", s, false
+ }
+ for i := 1; i < len(s); i++ {
+ if ch := s[i]; ch == ':' {
+ return s[:i+1], s[i+1:], true
+ }
+ if ch := s[i]; !isAlpha(ch) && !isDigit(ch) && !slices.Contains([]byte("+/="), ch) {
+ return "", s, false
+ }
+ }
+ return "", s, false
+}
+
+// ParseByteSequence parses a byte sequence from a given HTTP Structured Field
+// Values.
+//
+// The entire HTTP SFV string must consist of a valid byte sequence. It returns
+// the parsed byte sequence and an ok boolean value, indicating success or not.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-byte-sequence.
+func ParseByteSequence(s string) (parsed []byte, ok bool) {
+ if _, rest, ok := consumeByteSequence(s); !ok || rest != "" {
+ return nil, false
+ }
+ return []byte(s[1 : len(s)-1]), true
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean.
+func consumeBoolean(s string) (consumed, rest string, ok bool) {
+ if len(s) >= 2 && (s[:2] == "?0" || s[:2] == "?1") {
+ return s[:2], s[2:], true
+ }
+ return "", s, false
+}
+
+// ParseBoolean parses a boolean from a given HTTP Structured Field Values.
+//
+// The entire HTTP SFV string must consist of a valid boolean. It returns the
+// parsed boolean and an ok boolean value, indicating success or not.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean.
+func ParseBoolean(s string) (parsed bool, ok bool) {
+ if _, rest, ok := consumeBoolean(s); !ok || rest != "" {
+ return false, false
+ }
+ return s == "?1", true
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date.
+func consumeDate(s string) (consumed, rest string, ok bool) {
+ if len(s) == 0 || s[0] != '@' {
+ return "", s, false
+ }
+ if _, rest, ok = consumeIntegerOrDecimal(s[1:]); !ok {
+ return "", s, ok
+ }
+ consumed = s[:len(s)-len(rest)]
+ if slices.Contains([]byte(consumed), '.') {
+ return "", s, false
+ }
+ return consumed, rest, ok
+}
+
+// ParseDate parses a date from a given HTTP Structured Field Values.
+//
+// The entire HTTP SFV string must consist of a valid date. It returns the
+// parsed date and an ok boolean value, indicating success or not.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date.
+func ParseDate(s string) (parsed time.Time, ok bool) {
+ if _, rest, ok := consumeDate(s); !ok || rest != "" {
+ return time.Time{}, false
+ }
+ if n, ok := ParseInteger(s[1:]); !ok {
+ return time.Time{}, false
+ } else {
+ return time.Unix(n, 0), true
+ }
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-display-string.
+func consumeDisplayString(s string) (consumed, rest string, ok bool) {
+ // To prevent excessive allocation, especially when input is large, we
+ // maintain a buffer of 4 bytes to keep track of the last rune we
+ // encounter. This way, we can validate that the display string conforms to
+ // UTF-8 without actually building the whole string.
+ var lastRune [4]byte
+ var runeLen int
+ isPartOfValidRune := func(ch byte) bool {
+ lastRune[runeLen] = ch
+ runeLen++
+ if utf8.FullRune(lastRune[:runeLen]) {
+ r, s := utf8.DecodeRune(lastRune[:runeLen])
+ if r == utf8.RuneError {
+ return false
+ }
+ copy(lastRune[:], lastRune[s:runeLen])
+ runeLen -= s
+ return true
+ }
+ return runeLen <= 4
+ }
+
+ if len(s) <= 1 || s[:2] != `%"` {
+ return "", s, false
+ }
+ i := 2
+ for i < len(s) {
+ ch := s[i]
+ if !isVChar(ch) && !isSP(ch) {
+ return "", s, false
+ }
+ switch ch {
+ case '"':
+ if runeLen > 0 {
+ return "", s, false
+ }
+ return s[:i+1], s[i+1:], true
+ case '%':
+ if i+2 >= len(s) {
+ return "", s, false
+ }
+ if ch, ok = decOctetHex(s[i+1], s[i+2]); !ok {
+ return "", s, ok
+ }
+ if ok = isPartOfValidRune(ch); !ok {
+ return "", s, ok
+ }
+ i += 3
+ default:
+ if ok = isPartOfValidRune(ch); !ok {
+ return "", s, ok
+ }
+ i++
+ }
+ }
+ return "", s, false
+}
+
+// ParseDisplayString parses a display string from a given HTTP Structured
+// Field Values.
+//
+// The entire HTTP SFV string must consist of a valid display string. It
+// returns the parsed display string and an ok boolean value, indicating
+// success or not.
+//
+// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-display-string.
+func ParseDisplayString(s string) (parsed string, ok bool) {
+ if _, rest, ok := consumeDisplayString(s); !ok || rest != "" {
+ return "", false
+ }
+ // consumeDisplayString() already validates that we have a valid display
+ // string. Therefore, we can just construct the display string, without
+ // validating it again.
+ s = s[2 : len(s)-1]
+ var b strings.Builder
+ for i := 0; i < len(s); {
+ if s[i] == '%' {
+ decoded, _ := decOctetHex(s[i+1], s[i+2])
+ b.WriteByte(decoded)
+ i += 3
+ continue
+ }
+ b.WriteByte(s[i])
+ i++
+ }
+ return b.String(), true
+}
+
+// https://www.rfc-editor.org/rfc/rfc9651.html#parse-bare-item.
+func consumeBareItem(s string) (consumed, rest string, ok bool) {
+ if len(s) == 0 {
+ return "", s, false
+ }
+ ch := s[0]
+ switch {
+ case ch == '-' || isDigit(ch):
+ return consumeIntegerOrDecimal(s)
+ case ch == '"':
+ return consumeString(s)
+ case ch == '*' || isAlpha(ch):
+ return consumeToken(s)
+ case ch == ':':
+ return consumeByteSequence(s)
+ case ch == '?':
+ return consumeBoolean(s)
+ case ch == '@':
+ return consumeDate(s)
+ case ch == '%':
+ return consumeDisplayString(s)
+ default:
+ return "", s, false
+ }
+}