diff options
| author | Nicholas S. Husin <nsh@golang.org> | 2026-03-30 19:17:03 -0400 |
|---|---|---|
| committer | Nicholas Husin <nsh@golang.org> | 2026-04-10 08:24:28 -0700 |
| commit | 2f3c778b232dd53c41e1b623d25cd9f4ab28aaa5 (patch) | |
| tree | 3959d70ffde2c7c385b0cc5c62cb3ac56dbe1516 /src/vendor/golang.org/x/net/internal | |
| parent | ce4459cf0ee339b3bcf0ed10427079a234aade36 (diff) | |
| download | go-2f3c778b232dd53c41e1b623d25cd9f4ab28aaa5.tar.xz | |
net/http: add support for running HTTP tests against HTTP/3
Add support within clientserver_test.go to bring up a test HTTP/3 server
and client when http3Mode testMode option is passed.
To be able to reuse net/http/httptest, net/http/httptest.Server.StartTLS
(and Start) have been modified so they can be called with a nil
Listener. In such cases, both methods will behave identically as usual,
but will not actually make its server serve or set its transport dialer,
both of which requires having a listener. This should be a no-op for
regular users of the package, whose entrypoint via functions such as
NewServer will automatically set a local listener.
Actually enabling HTTP/3 for our tests will be done in a separate CL.
For #70914
Change-Id: Ibc5fc83287b6a04b46e668a54924761a92b620a4
Reviewed-on: https://go-review.googlesource.com/c/go/+/740122
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/vendor/golang.org/x/net/internal')
20 files changed, 3834 insertions, 0 deletions
diff --git a/src/vendor/golang.org/x/net/internal/http3/body.go b/src/vendor/golang.org/x/net/internal/http3/body.go new file mode 100644 index 0000000000..6db183beb8 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/body.go @@ -0,0 +1,230 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "errors" + "fmt" + "io" + "net" + "net/http" + "net/textproto" + "strings" + "sync" + + "golang.org/x/net/http/httpguts" +) + +// extractTrailerFromHeader extracts the "Trailer" header values from a header +// map, and populates a trailer map with those values as keys. The extracted +// header values will be canonicalized. +func extractTrailerFromHeader(header, trailer http.Header) { + for _, names := range header["Trailer"] { + names = textproto.TrimString(names) + for name := range strings.SplitSeq(names, ",") { + name = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(name)) + if !httpguts.ValidTrailerHeader(name) { + continue + } + trailer[name] = nil + } + } +} + +// A bodyWriter writes a request or response body to a stream +// as a series of DATA frames. +type bodyWriter struct { + st *stream + remain int64 // -1 when content-length is not known + flush bool // flush the stream after every write + name string // "request" or "response" + trailer http.Header // trailer headers that will be written once bodyWriter is closed. + enc *qpackEncoder // QPACK encoder used by the connection. +} + +func (w *bodyWriter) write(ps ...[]byte) (n int, err error) { + var size int64 + for _, p := range ps { + size += int64(len(p)) + } + // If write is called with empty byte slices, just return instead of + // sending out a DATA frame containing nothing. + if size == 0 { + return 0, nil + } + if w.remain >= 0 && size > w.remain { + return 0, &streamError{ + code: errH3InternalError, + message: w.name + " body longer than specified content length", + } + } + w.st.writeVarint(int64(frameTypeData)) + w.st.writeVarint(size) + for _, p := range ps { + var n2 int + n2, err = w.st.Write(p) + n += n2 + if w.remain >= 0 { + w.remain -= int64(n) + } + if err != nil { + break + } + } + if w.flush && err == nil { + err = w.st.Flush() + } + if err != nil { + err = fmt.Errorf("writing %v body: %w", w.name, err) + } + return n, err +} + +func (w *bodyWriter) Write(p []byte) (n int, err error) { + return w.write(p) +} + +func (w *bodyWriter) Close() error { + if w.remain > 0 { + return errors.New(w.name + " body shorter than specified content length") + } + if len(w.trailer) > 0 { + encTrailer := w.enc.encode(func(f func(itype indexType, name, value string)) { + for name, values := range w.trailer { + if !httpguts.ValidHeaderFieldName(name) { + continue + } + for _, val := range values { + if !httpguts.ValidHeaderFieldValue(val) { + continue + } + f(mayIndex, name, val) + } + } + }) + w.st.writeVarint(int64(frameTypeHeaders)) + w.st.writeVarint(int64(len(encTrailer))) + w.st.Write(encTrailer) + } + if w.st != nil && w.st.stream != nil { + w.st.stream.CloseWrite() + } + return nil +} + +// A bodyReader reads a request or response body from a stream. +type bodyReader struct { + st *stream + + mu sync.Mutex + remain int64 + err error + // If not nil, the body contains an "Expect: 100-continue" header, and + // send100Continue should be called when Read is invoked for the first + // time. + send100Continue func() + // A map where the key represents the trailer header names we expect. If + // there is a HEADERS frame after reading DATA frames to EOF, the value of + // the headers will be written here, provided that the name of the header + // exists in the map already. + trailer http.Header +} + +func (r *bodyReader) Read(p []byte) (n int, err error) { + // The HTTP/1 and HTTP/2 implementations both permit concurrent reads from a body, + // in the sense that the race detector won't complain. + // Use a mutex here to provide the same behavior. + r.mu.Lock() + defer r.mu.Unlock() + if r.send100Continue != nil { + r.send100Continue() + r.send100Continue = nil + } + if r.err != nil { + return 0, r.err + } + defer func() { + if err != nil { + r.err = err + } + }() + if r.st.lim == 0 { + // We've finished reading the previous DATA frame, so end it. + if err := r.st.endFrame(); err != nil { + return 0, err + } + } + // Read the next DATA frame header, + // if we aren't already in the middle of one. + for r.st.lim < 0 { + ftype, err := r.st.readFrameHeader() + if err == io.EOF && r.remain > 0 { + return 0, &streamError{ + code: errH3MessageError, + message: "body shorter than content-length", + } + } + if err != nil { + return 0, err + } + switch ftype { + case frameTypeData: + if r.remain >= 0 && r.st.lim > r.remain { + return 0, &streamError{ + code: errH3MessageError, + message: "body longer than content-length", + } + } + // Fall out of the loop and process the frame body below. + case frameTypeHeaders: + // This HEADERS frame contains the message trailers. + if r.remain > 0 { + return 0, &streamError{ + code: errH3MessageError, + message: "body shorter than content-length", + } + } + var dec qpackDecoder + if err := dec.decode(r.st, func(_ indexType, name, value string) error { + if _, ok := r.trailer[name]; ok { + r.trailer.Add(name, value) + } + return nil + }); err != nil { + return 0, err + } + if err := r.st.discardFrame(); err != nil { + return 0, err + } + return 0, io.EOF + default: + if err := r.st.discardUnknownFrame(ftype); err != nil { + return 0, err + } + } + } + // We are now reading the content of a DATA frame. + // Fill the read buffer or read to the end of the frame, + // whichever comes first. + if int64(len(p)) > r.st.lim { + p = p[:r.st.lim] + } + n, err = r.st.Read(p) + if r.remain > 0 { + r.remain -= int64(n) + } + return n, err +} + +func (r *bodyReader) Close() error { + // Unlike the HTTP/1 and HTTP/2 body readers (at the time of this comment being written), + // calling Close concurrently with Read will interrupt the read. + r.st.stream.CloseRead() + // Make sure that any data that has already been written to bodyReader + // cannot be read after it has been closed. + r.err = net.ErrClosed + r.remain = 0 + return nil +} diff --git a/src/vendor/golang.org/x/net/internal/http3/conn.go b/src/vendor/golang.org/x/net/internal/http3/conn.go new file mode 100644 index 0000000000..6a3c962b41 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/conn.go @@ -0,0 +1,131 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "context" + "io" + "sync" + + "golang.org/x/net/quic" +) + +type streamHandler interface { + handleControlStream(*stream) error + handlePushStream(*stream) error + handleEncoderStream(*stream) error + handleDecoderStream(*stream) error + handleRequestStream(*stream) error + abort(error) +} + +type genericConn struct { + mu sync.Mutex + + // The peer may create exactly one control, encoder, and decoder stream. + // streamsCreated is a bitset of streams created so far. + // Bits are 1 << streamType. + streamsCreated uint8 +} + +func (c *genericConn) acceptStreams(qconn *quic.Conn, h streamHandler) { + for { + // Use context.Background: This blocks until a stream is accepted + // or the connection closes. + st, err := qconn.AcceptStream(context.Background()) + if err != nil { + return // connection closed + } + if st.IsReadOnly() { + go c.handleUnidirectionalStream(newStream(st), h) + } else { + go c.handleRequestStream(newStream(st), h) + } + } +} + +func (c *genericConn) handleUnidirectionalStream(st *stream, h streamHandler) { + // Unidirectional stream header: One varint with the stream type. + v, err := st.readVarint() + if err != nil { + h.abort(&connectionError{ + code: errH3StreamCreationError, + message: "error reading unidirectional stream header", + }) + return + } + stype := streamType(v) + if err := c.checkStreamCreation(stype); err != nil { + h.abort(err) + return + } + switch stype { + case streamTypeControl: + err = h.handleControlStream(st) + case streamTypePush: + err = h.handlePushStream(st) + case streamTypeEncoder: + err = h.handleEncoderStream(st) + case streamTypeDecoder: + err = h.handleDecoderStream(st) + default: + // "Recipients of unknown stream types MUST either abort reading + // of the stream or discard incoming data without further processing." + // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2-7 + // + // We should send the H3_STREAM_CREATION_ERROR error code, + // but the quic package currently doesn't allow setting error codes + // for STOP_SENDING frames. + // TODO: Should CloseRead take an error code? + err = nil + } + if err == io.EOF { + err = &connectionError{ + code: errH3ClosedCriticalStream, + message: streamType(stype).String() + " stream closed", + } + } + c.handleStreamError(st, h, err) +} + +func (c *genericConn) handleRequestStream(st *stream, h streamHandler) { + c.handleStreamError(st, h, h.handleRequestStream(st)) +} + +func (c *genericConn) handleStreamError(st *stream, h streamHandler, err error) { + switch err := err.(type) { + case *connectionError: + h.abort(err) + case nil: + st.stream.CloseRead() + st.stream.CloseWrite() + case *streamError: + st.stream.CloseRead() + st.stream.Reset(uint64(err.code)) + default: + st.stream.CloseRead() + st.stream.Reset(uint64(errH3InternalError)) + } +} + +func (c *genericConn) checkStreamCreation(stype streamType) error { + switch stype { + case streamTypeControl, streamTypeEncoder, streamTypeDecoder: + // The peer may create exactly one control, encoder, and decoder stream. + default: + return nil + } + c.mu.Lock() + defer c.mu.Unlock() + bit := uint8(1) << stype + if c.streamsCreated&bit != 0 { + return &connectionError{ + code: errH3StreamCreationError, + message: "multiple " + stype.String() + " streams created", + } + } + c.streamsCreated |= bit + return nil +} diff --git a/src/vendor/golang.org/x/net/internal/http3/doc.go b/src/vendor/golang.org/x/net/internal/http3/doc.go new file mode 100644 index 0000000000..5530113f69 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/doc.go @@ -0,0 +1,10 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package http3 implements the HTTP/3 protocol. +// +// This package is a work in progress. +// It is not ready for production usage. +// Its API is subject to change without notice. +package http3 diff --git a/src/vendor/golang.org/x/net/internal/http3/errors.go b/src/vendor/golang.org/x/net/internal/http3/errors.go new file mode 100644 index 0000000000..273ad014a6 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/errors.go @@ -0,0 +1,102 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import "fmt" + +// http3Error is an HTTP/3 error code. +type http3Error int + +const ( + // https://www.rfc-editor.org/rfc/rfc9114.html#section-8.1 + errH3NoError = http3Error(0x0100) + errH3GeneralProtocolError = http3Error(0x0101) + errH3InternalError = http3Error(0x0102) + errH3StreamCreationError = http3Error(0x0103) + errH3ClosedCriticalStream = http3Error(0x0104) + errH3FrameUnexpected = http3Error(0x0105) + errH3FrameError = http3Error(0x0106) + errH3ExcessiveLoad = http3Error(0x0107) + errH3IDError = http3Error(0x0108) + errH3SettingsError = http3Error(0x0109) + errH3MissingSettings = http3Error(0x010a) + errH3RequestRejected = http3Error(0x010b) + errH3RequestCancelled = http3Error(0x010c) + errH3RequestIncomplete = http3Error(0x010d) + errH3MessageError = http3Error(0x010e) + errH3ConnectError = http3Error(0x010f) + errH3VersionFallback = http3Error(0x0110) + + // https://www.rfc-editor.org/rfc/rfc9204.html#section-8.3 + errQPACKDecompressionFailed = http3Error(0x0200) + errQPACKEncoderStreamError = http3Error(0x0201) + errQPACKDecoderStreamError = http3Error(0x0202) +) + +func (e http3Error) Error() string { + switch e { + case errH3NoError: + return "H3_NO_ERROR" + case errH3GeneralProtocolError: + return "H3_GENERAL_PROTOCOL_ERROR" + case errH3InternalError: + return "H3_INTERNAL_ERROR" + case errH3StreamCreationError: + return "H3_STREAM_CREATION_ERROR" + case errH3ClosedCriticalStream: + return "H3_CLOSED_CRITICAL_STREAM" + case errH3FrameUnexpected: + return "H3_FRAME_UNEXPECTED" + case errH3FrameError: + return "H3_FRAME_ERROR" + case errH3ExcessiveLoad: + return "H3_EXCESSIVE_LOAD" + case errH3IDError: + return "H3_ID_ERROR" + case errH3SettingsError: + return "H3_SETTINGS_ERROR" + case errH3MissingSettings: + return "H3_MISSING_SETTINGS" + case errH3RequestRejected: + return "H3_REQUEST_REJECTED" + case errH3RequestCancelled: + return "H3_REQUEST_CANCELLED" + case errH3RequestIncomplete: + return "H3_REQUEST_INCOMPLETE" + case errH3MessageError: + return "H3_MESSAGE_ERROR" + case errH3ConnectError: + return "H3_CONNECT_ERROR" + case errH3VersionFallback: + return "H3_VERSION_FALLBACK" + case errQPACKDecompressionFailed: + return "QPACK_DECOMPRESSION_FAILED" + case errQPACKEncoderStreamError: + return "QPACK_ENCODER_STREAM_ERROR" + case errQPACKDecoderStreamError: + return "QPACK_DECODER_STREAM_ERROR" + } + return fmt.Sprintf("H3_ERROR_%v", int(e)) +} + +// A streamError is an error which terminates a stream, but not the connection. +// https://www.rfc-editor.org/rfc/rfc9114.html#section-8-1 +type streamError struct { + code http3Error + message string +} + +func (e *streamError) Error() string { return e.message } +func (e *streamError) Unwrap() error { return e.code } + +// A connectionError is an error which results in the entire connection closing. +// https://www.rfc-editor.org/rfc/rfc9114.html#section-8-2 +type connectionError struct { + code http3Error + message string +} + +func (e *connectionError) Error() string { return e.message } +func (e *connectionError) Unwrap() error { return e.code } diff --git a/src/vendor/golang.org/x/net/internal/http3/http3.go b/src/vendor/golang.org/x/net/internal/http3/http3.go new file mode 100644 index 0000000000..189e3e749b --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/http3.go @@ -0,0 +1,95 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "context" + "fmt" +) + +// Stream types. +// +// For unidirectional streams, the value is the stream type sent over the wire. +// +// For bidirectional streams (which are always request streams), +// the value is arbitrary and never sent on the wire. +type streamType int64 + +const ( + // Bidirectional request stream. + // All bidirectional streams are request streams. + // This stream type is never sent over the wire. + // + // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1 + streamTypeRequest = streamType(-1) + + // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2 + streamTypeControl = streamType(0x00) + streamTypePush = streamType(0x01) + + // https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2 + streamTypeEncoder = streamType(0x02) + streamTypeDecoder = streamType(0x03) +) + +// canceledCtx is a canceled Context. +// Used for performing non-blocking QUIC operations. +var canceledCtx = func() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + return ctx +}() + +func (stype streamType) String() string { + switch stype { + case streamTypeRequest: + return "request" + case streamTypeControl: + return "control" + case streamTypePush: + return "push" + case streamTypeEncoder: + return "encoder" + case streamTypeDecoder: + return "decoder" + default: + return "unknown" + } +} + +// Frame types. +type frameType int64 + +const ( + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2 + frameTypeData = frameType(0x00) + frameTypeHeaders = frameType(0x01) + frameTypeCancelPush = frameType(0x03) + frameTypeSettings = frameType(0x04) + frameTypePushPromise = frameType(0x05) + frameTypeGoaway = frameType(0x07) + frameTypeMaxPushID = frameType(0x0d) +) + +func (ftype frameType) String() string { + switch ftype { + case frameTypeData: + return "DATA" + case frameTypeHeaders: + return "HEADERS" + case frameTypeCancelPush: + return "CANCEL_PUSH" + case frameTypeSettings: + return "SETTINGS" + case frameTypePushPromise: + return "PUSH_PROMISE" + case frameTypeGoaway: + return "GOAWAY" + case frameTypeMaxPushID: + return "MAX_PUSH_ID" + default: + return fmt.Sprintf("UNKNOWN_%d", int64(ftype)) + } +} diff --git a/src/vendor/golang.org/x/net/internal/http3/qpack.go b/src/vendor/golang.org/x/net/internal/http3/qpack.go new file mode 100644 index 0000000000..64ce99aaa0 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/qpack.go @@ -0,0 +1,332 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "errors" + "io" + + "golang.org/x/net/http2/hpack" +) + +// QPACK (RFC 9204) header compression wire encoding. +// https://www.rfc-editor.org/rfc/rfc9204.html + +// tableType is the static or dynamic table. +// +// The T bit in QPACK instructions indicates whether a table index refers to +// the dynamic (T=0) or static (T=1) table. tableTypeForTBit and tableType.tbit +// convert a T bit from the wire encoding to/from a tableType. +type tableType byte + +const ( + dynamicTable = 0x00 // T=0, dynamic table + staticTable = 0xff // T=1, static table +) + +// tableTypeForTbit returns the table type corresponding to a T bit value. +// The input parameter contains a byte masked to contain only the T bit. +func tableTypeForTbit(bit byte) tableType { + if bit == 0 { + return dynamicTable + } + return staticTable +} + +// tbit produces the T bit corresponding to the table type. +// The input parameter contains a byte with the T bit set to 1, +// and the return is either the input or 0 depending on the table type. +func (t tableType) tbit(bit byte) byte { + return bit & byte(t) +} + +// indexType indicates a literal's indexing status. +// +// The N bit in QPACK instructions indicates whether a literal is "never-indexed". +// A never-indexed literal (N=1) must not be encoded as an indexed literal if it +// forwarded on another connection. +// +// (See https://www.rfc-editor.org/rfc/rfc9204.html#section-7.1 for details on the +// security reasons for never-indexed literals.) +type indexType byte + +const ( + mayIndex = 0x00 // N=0, not a never-indexed literal + neverIndex = 0xff // N=1, never-indexed literal +) + +// indexTypeForNBit returns the index type corresponding to a N bit value. +// The input parameter contains a byte masked to contain only the N bit. +func indexTypeForNBit(bit byte) indexType { + if bit == 0 { + return mayIndex + } + return neverIndex +} + +// nbit produces the N bit corresponding to the table type. +// The input parameter contains a byte with the N bit set to 1, +// and the return is either the input or 0 depending on the table type. +func (t indexType) nbit(bit byte) byte { + return bit & byte(t) +} + +// Indexed Field Line: +// +// 0 1 2 3 4 5 6 7 +// +---+---+---+---+---+---+---+---+ +// | 1 | T | Index (6+) | +// +---+---+-----------------------+ +// +// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.5.2 + +func appendIndexedFieldLine(b []byte, ttype tableType, index int) []byte { + const tbit = 0b_01000000 + return appendPrefixedInt(b, 0b_1000_0000|ttype.tbit(tbit), 6, int64(index)) +} + +func (st *stream) decodeIndexedFieldLine(b byte) (itype indexType, name, value string, err error) { + index, err := st.readPrefixedIntWithByte(b, 6) + if err != nil { + return 0, "", "", err + } + const tbit = 0b_0100_0000 + if tableTypeForTbit(b&tbit) == staticTable { + ent, err := staticTableEntry(index) + if err != nil { + return 0, "", "", err + } + return mayIndex, ent.name, ent.value, nil + } else { + return 0, "", "", errors.New("dynamic table is not supported yet") + } +} + +// Literal Field Line With Name Reference: +// +// 0 1 2 3 4 5 6 7 +// +---+---+---+---+---+---+---+---+ +// | 0 | 1 | N | T |Name Index (4+)| +// +---+---+---+---+---------------+ +// | H | Value Length (7+) | +// +---+---------------------------+ +// | Value String (Length bytes) | +// +-------------------------------+ +// +// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.5.4 + +func appendLiteralFieldLineWithNameReference(b []byte, ttype tableType, itype indexType, nameIndex int, value string) []byte { + const tbit = 0b_0001_0000 + const nbit = 0b_0010_0000 + b = appendPrefixedInt(b, 0b_0100_0000|itype.nbit(nbit)|ttype.tbit(tbit), 4, int64(nameIndex)) + b = appendPrefixedString(b, 0, 7, value) + return b +} + +func (st *stream) decodeLiteralFieldLineWithNameReference(b byte) (itype indexType, name, value string, err error) { + nameIndex, err := st.readPrefixedIntWithByte(b, 4) + if err != nil { + return 0, "", "", err + } + + const tbit = 0b_0001_0000 + if tableTypeForTbit(b&tbit) == staticTable { + ent, err := staticTableEntry(nameIndex) + if err != nil { + return 0, "", "", err + } + name = ent.name + } else { + return 0, "", "", errors.New("dynamic table is not supported yet") + } + + _, value, err = st.readPrefixedString(7) + if err != nil { + return 0, "", "", err + } + + const nbit = 0b_0010_0000 + itype = indexTypeForNBit(b & nbit) + + return itype, name, value, nil +} + +// Literal Field Line with Literal Name: +// +// 0 1 2 3 4 5 6 7 +// +---+---+---+---+---+---+---+---+ +// | 0 | 0 | 1 | N | H |NameLen(3+)| +// +---+---+---+---+---+-----------+ +// | Name String (Length bytes) | +// +---+---------------------------+ +// | H | Value Length (7+) | +// +---+---------------------------+ +// | Value String (Length bytes) | +// +-------------------------------+ +// +// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.5.6 + +func appendLiteralFieldLineWithLiteralName(b []byte, itype indexType, name, value string) []byte { + const nbit = 0b_0001_0000 + b = appendPrefixedString(b, 0b_0010_0000|itype.nbit(nbit), 3, name) + b = appendPrefixedString(b, 0, 7, value) + return b +} + +func (st *stream) decodeLiteralFieldLineWithLiteralName(b byte) (itype indexType, name, value string, err error) { + name, err = st.readPrefixedStringWithByte(b, 3) + if err != nil { + return 0, "", "", err + } + _, value, err = st.readPrefixedString(7) + if err != nil { + return 0, "", "", err + } + const nbit = 0b_0001_0000 + itype = indexTypeForNBit(b & nbit) + return itype, name, value, nil +} + +// Prefixed-integer encoding from RFC 7541, section 5.1 +// +// Prefixed integers consist of some number of bits of data, +// N bits of encoded integer, and 0 or more additional bytes of +// encoded integer. +// +// The RFCs represent this as, for example: +// +// 0 1 2 3 4 5 6 7 +// +---+---+---+---+---+---+---+---+ +// | 0 | 0 | 1 | Capacity (5+) | +// +---+---+---+-------------------+ +// +// "Capacity" is an integer with a 5-bit prefix. +// +// In the following functions, a "prefixLen" parameter is the number +// of integer bits in the first byte (5 in the above example), and +// a "firstByte" parameter is a byte containing the first byte of +// the encoded value (0x001x_xxxx in the above example). +// +// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.1.1 +// https://www.rfc-editor.org/rfc/rfc7541#section-5.1 + +// readPrefixedInt reads an RFC 7541 prefixed integer from st. +func (st *stream) readPrefixedInt(prefixLen uint8) (firstByte byte, v int64, err error) { + firstByte, err = st.ReadByte() + if err != nil { + return 0, 0, errQPACKDecompressionFailed + } + v, err = st.readPrefixedIntWithByte(firstByte, prefixLen) + return firstByte, v, err +} + +// readPrefixedIntWithByte reads an RFC 7541 prefixed integer from st. +// The first byte has already been read from the stream. +func (st *stream) readPrefixedIntWithByte(firstByte byte, prefixLen uint8) (v int64, err error) { + prefixMask := (byte(1) << prefixLen) - 1 + v = int64(firstByte & prefixMask) + if v != int64(prefixMask) { + return v, nil + } + m := 0 + for { + b, err := st.ReadByte() + if err != nil { + return 0, errQPACKDecompressionFailed + } + v += int64(b&127) << m + m += 7 + if b&128 == 0 { + break + } + } + return v, err +} + +// appendPrefixedInt appends an RFC 7541 prefixed integer to b. +// +// The firstByte parameter includes the non-integer bits of the first byte. +// The other bits must be zero. +func appendPrefixedInt(b []byte, firstByte byte, prefixLen uint8, i int64) []byte { + u := uint64(i) + prefixMask := (uint64(1) << prefixLen) - 1 + if u < prefixMask { + return append(b, firstByte|byte(u)) + } + b = append(b, firstByte|byte(prefixMask)) + u -= prefixMask + for u >= 128 { + b = append(b, 0x80|byte(u&0x7f)) + u >>= 7 + } + return append(b, byte(u)) +} + +// String literal encoding from RFC 7541, section 5.2 +// +// String literals consist of a single bit flag indicating +// whether the string is Huffman-encoded, a prefixed integer (see above), +// and the string. +// +// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.1.2 +// https://www.rfc-editor.org/rfc/rfc7541#section-5.2 + +// readPrefixedString reads an RFC 7541 string from st. +func (st *stream) readPrefixedString(prefixLen uint8) (firstByte byte, s string, err error) { + firstByte, err = st.ReadByte() + if err != nil { + return 0, "", errQPACKDecompressionFailed + } + s, err = st.readPrefixedStringWithByte(firstByte, prefixLen) + return firstByte, s, err +} + +// readPrefixedStringWithByte reads an RFC 7541 string from st. +// The first byte has already been read from the stream. +func (st *stream) readPrefixedStringWithByte(firstByte byte, prefixLen uint8) (s string, err error) { + size, err := st.readPrefixedIntWithByte(firstByte, prefixLen) + if err != nil { + return "", errQPACKDecompressionFailed + } + + hbit := byte(1) << prefixLen + isHuffman := firstByte&hbit != 0 + + // TODO: Avoid allocating here. + data := make([]byte, size) + if _, err := io.ReadFull(st, data); err != nil { + return "", errQPACKDecompressionFailed + } + if isHuffman { + // TODO: Move Huffman functions into a new package that hpack (HTTP/2) + // and this package can both import. Most of the hpack package isn't + // relevant to HTTP/3. + s, err := hpack.HuffmanDecodeToString(data) + if err != nil { + return "", errQPACKDecompressionFailed + } + return s, nil + } + return string(data), nil +} + +// appendPrefixedString appends an RFC 7541 string to st, +// applying Huffman encoding and setting the H bit (indicating Huffman encoding) +// when appropriate. +// +// The firstByte parameter includes the non-integer bits of the first byte. +// The other bits must be zero. +func appendPrefixedString(b []byte, firstByte byte, prefixLen uint8, s string) []byte { + huffmanLen := hpack.HuffmanEncodeLength(s) + if huffmanLen < uint64(len(s)) { + hbit := byte(1) << prefixLen + b = appendPrefixedInt(b, firstByte|hbit, prefixLen, int64(huffmanLen)) + b = hpack.AppendHuffmanString(b, s) + } else { + b = appendPrefixedInt(b, firstByte, prefixLen, int64(len(s))) + b = append(b, s...) + } + return b +} diff --git a/src/vendor/golang.org/x/net/internal/http3/qpack_decode.go b/src/vendor/golang.org/x/net/internal/http3/qpack_decode.go new file mode 100644 index 0000000000..7348ae76f0 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/qpack_decode.go @@ -0,0 +1,81 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "errors" + "math/bits" +) + +type qpackDecoder struct { + // The decoder has no state for now, + // but that'll change once we add dynamic table support. + // + // TODO: dynamic table support. +} + +func (qd *qpackDecoder) decode(st *stream, f func(itype indexType, name, value string) error) error { + // Encoded Field Section prefix. + + // We set SETTINGS_QPACK_MAX_TABLE_CAPACITY to 0, + // so the Required Insert Count must be 0. + _, requiredInsertCount, err := st.readPrefixedInt(8) + if err != nil { + return err + } + if requiredInsertCount != 0 { + return errQPACKDecompressionFailed + } + + // Delta Base. We don't use the dynamic table yet, so this may be ignored. + _, _, err = st.readPrefixedInt(7) + if err != nil { + return err + } + + sawNonPseudo := false + for st.lim > 0 { + firstByte, err := st.ReadByte() + if err != nil { + return err + } + var name, value string + var itype indexType + switch bits.LeadingZeros8(firstByte) { + case 0: + // Indexed Field Line + itype, name, value, err = st.decodeIndexedFieldLine(firstByte) + case 1: + // Literal Field Line With Name Reference + itype, name, value, err = st.decodeLiteralFieldLineWithNameReference(firstByte) + case 2: + // Literal Field Line with Literal Name + itype, name, value, err = st.decodeLiteralFieldLineWithLiteralName(firstByte) + case 3: + // Indexed Field Line With Post-Base Index + err = errors.New("dynamic table is not supported yet") + case 4: + // Indexed Field Line With Post-Base Name Reference + err = errors.New("dynamic table is not supported yet") + } + if err != nil { + return err + } + if len(name) == 0 { + return errH3MessageError + } + if name[0] == ':' { + if sawNonPseudo { + return errH3MessageError + } + } else { + sawNonPseudo = true + } + if err := f(itype, name, value); err != nil { + return err + } + } + return nil +} diff --git a/src/vendor/golang.org/x/net/internal/http3/qpack_encode.go b/src/vendor/golang.org/x/net/internal/http3/qpack_encode.go new file mode 100644 index 0000000000..193f7f93be --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/qpack_encode.go @@ -0,0 +1,45 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +type qpackEncoder struct { + // The encoder has no state for now, + // but that'll change once we add dynamic table support. + // + // TODO: dynamic table support. +} + +func (qe *qpackEncoder) init() { + staticTableOnce.Do(initStaticTableMaps) +} + +// encode encodes a list of headers into a QPACK encoded field section. +// +// The headers func must produce the same headers on repeated calls, +// although the order may vary. +func (qe *qpackEncoder) encode(headers func(func(itype indexType, name, value string))) []byte { + // Encoded Field Section prefix. + // + // We don't yet use the dynamic table, so both values here are zero. + var b []byte + b = appendPrefixedInt(b, 0, 8, 0) // Required Insert Count + b = appendPrefixedInt(b, 0, 7, 0) // Delta Base + + headers(func(itype indexType, name, value string) { + if itype == mayIndex { + if i, ok := staticTableByNameValue[tableEntry{name, value}]; ok { + b = appendIndexedFieldLine(b, staticTable, i) + return + } + } + if i, ok := staticTableByName[name]; ok { + b = appendLiteralFieldLineWithNameReference(b, staticTable, itype, i, value) + } else { + b = appendLiteralFieldLineWithLiteralName(b, itype, name, value) + } + }) + + return b +} diff --git a/src/vendor/golang.org/x/net/internal/http3/qpack_static.go b/src/vendor/golang.org/x/net/internal/http3/qpack_static.go new file mode 100644 index 0000000000..6c0b51c5e6 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/qpack_static.go @@ -0,0 +1,142 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import "sync" + +type tableEntry struct { + name string + value string +} + +// staticTableEntry returns the static table entry with the given index. +func staticTableEntry(index int64) (tableEntry, error) { + if index >= int64(len(staticTableEntries)) { + return tableEntry{}, errQPACKDecompressionFailed + } + return staticTableEntries[index], nil +} + +func initStaticTableMaps() { + staticTableByName = make(map[string]int) + staticTableByNameValue = make(map[tableEntry]int) + for i, ent := range staticTableEntries { + if _, ok := staticTableByName[ent.name]; !ok { + staticTableByName[ent.name] = i + } + staticTableByNameValue[ent] = i + } +} + +var ( + staticTableOnce sync.Once + staticTableByName map[string]int + staticTableByNameValue map[tableEntry]int +) + +// https://www.rfc-editor.org/rfc/rfc9204.html#appendix-A +// +// Note that this is different from the HTTP/2 static table. +var staticTableEntries = [...]tableEntry{ + 0: {":authority", ""}, + 1: {":path", "/"}, + 2: {"age", "0"}, + 3: {"content-disposition", ""}, + 4: {"content-length", "0"}, + 5: {"cookie", ""}, + 6: {"date", ""}, + 7: {"etag", ""}, + 8: {"if-modified-since", ""}, + 9: {"if-none-match", ""}, + 10: {"last-modified", ""}, + 11: {"link", ""}, + 12: {"location", ""}, + 13: {"referer", ""}, + 14: {"set-cookie", ""}, + 15: {":method", "CONNECT"}, + 16: {":method", "DELETE"}, + 17: {":method", "GET"}, + 18: {":method", "HEAD"}, + 19: {":method", "OPTIONS"}, + 20: {":method", "POST"}, + 21: {":method", "PUT"}, + 22: {":scheme", "http"}, + 23: {":scheme", "https"}, + 24: {":status", "103"}, + 25: {":status", "200"}, + 26: {":status", "304"}, + 27: {":status", "404"}, + 28: {":status", "503"}, + 29: {"accept", "*/*"}, + 30: {"accept", "application/dns-message"}, + 31: {"accept-encoding", "gzip, deflate, br"}, + 32: {"accept-ranges", "bytes"}, + 33: {"access-control-allow-headers", "cache-control"}, + 34: {"access-control-allow-headers", "content-type"}, + 35: {"access-control-allow-origin", "*"}, + 36: {"cache-control", "max-age=0"}, + 37: {"cache-control", "max-age=2592000"}, + 38: {"cache-control", "max-age=604800"}, + 39: {"cache-control", "no-cache"}, + 40: {"cache-control", "no-store"}, + 41: {"cache-control", "public, max-age=31536000"}, + 42: {"content-encoding", "br"}, + 43: {"content-encoding", "gzip"}, + 44: {"content-type", "application/dns-message"}, + 45: {"content-type", "application/javascript"}, + 46: {"content-type", "application/json"}, + 47: {"content-type", "application/x-www-form-urlencoded"}, + 48: {"content-type", "image/gif"}, + 49: {"content-type", "image/jpeg"}, + 50: {"content-type", "image/png"}, + 51: {"content-type", "text/css"}, + 52: {"content-type", "text/html; charset=utf-8"}, + 53: {"content-type", "text/plain"}, + 54: {"content-type", "text/plain;charset=utf-8"}, + 55: {"range", "bytes=0-"}, + 56: {"strict-transport-security", "max-age=31536000"}, + 57: {"strict-transport-security", "max-age=31536000; includesubdomains"}, + 58: {"strict-transport-security", "max-age=31536000; includesubdomains; preload"}, + 59: {"vary", "accept-encoding"}, + 60: {"vary", "origin"}, + 61: {"x-content-type-options", "nosniff"}, + 62: {"x-xss-protection", "1; mode=block"}, + 63: {":status", "100"}, + 64: {":status", "204"}, + 65: {":status", "206"}, + 66: {":status", "302"}, + 67: {":status", "400"}, + 68: {":status", "403"}, + 69: {":status", "421"}, + 70: {":status", "425"}, + 71: {":status", "500"}, + 72: {"accept-language", ""}, + 73: {"access-control-allow-credentials", "FALSE"}, + 74: {"access-control-allow-credentials", "TRUE"}, + 75: {"access-control-allow-headers", "*"}, + 76: {"access-control-allow-methods", "get"}, + 77: {"access-control-allow-methods", "get, post, options"}, + 78: {"access-control-allow-methods", "options"}, + 79: {"access-control-expose-headers", "content-length"}, + 80: {"access-control-request-headers", "content-type"}, + 81: {"access-control-request-method", "get"}, + 82: {"access-control-request-method", "post"}, + 83: {"alt-svc", "clear"}, + 84: {"authorization", ""}, + 85: {"content-security-policy", "script-src 'none'; object-src 'none'; base-uri 'none'"}, + 86: {"early-data", "1"}, + 87: {"expect-ct", ""}, + 88: {"forwarded", ""}, + 89: {"if-range", ""}, + 90: {"origin", ""}, + 91: {"purpose", "prefetch"}, + 92: {"server", ""}, + 93: {"timing-allow-origin", "*"}, + 94: {"upgrade-insecure-requests", "1"}, + 95: {"user-agent", ""}, + 96: {"x-forwarded-for", ""}, + 97: {"x-frame-options", "deny"}, + 98: {"x-frame-options", "sameorigin"}, +} diff --git a/src/vendor/golang.org/x/net/internal/http3/quic.go b/src/vendor/golang.org/x/net/internal/http3/quic.go new file mode 100644 index 0000000000..4f1cca179e --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/quic.go @@ -0,0 +1,40 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "crypto/tls" + + "golang.org/x/net/quic" +) + +func initConfig(config *quic.Config) *quic.Config { + if config == nil { + config = &quic.Config{} + } + + // maybeCloneTLSConfig clones the user-provided tls.Config (but only once) + // prior to us modifying it. + needCloneTLSConfig := true + maybeCloneTLSConfig := func() *tls.Config { + if needCloneTLSConfig { + config.TLSConfig = config.TLSConfig.Clone() + needCloneTLSConfig = false + } + return config.TLSConfig + } + + if config.TLSConfig == nil { + config.TLSConfig = &tls.Config{} + needCloneTLSConfig = false + } + if config.TLSConfig.MinVersion == 0 { + maybeCloneTLSConfig().MinVersion = tls.VersionTLS13 + } + if config.TLSConfig.NextProtos == nil { + maybeCloneTLSConfig().NextProtos = []string{"h3"} + } + return config +} diff --git a/src/vendor/golang.org/x/net/internal/http3/roundtrip.go b/src/vendor/golang.org/x/net/internal/http3/roundtrip.go new file mode 100644 index 0000000000..2ea584b773 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/roundtrip.go @@ -0,0 +1,417 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "errors" + "io" + "net/http" + "net/http/httptrace" + "net/textproto" + "strconv" + "sync" + + "golang.org/x/net/http/httpguts" + "golang.org/x/net/internal/httpcommon" +) + +type roundTripState struct { + cc *clientConn + st *stream + + // Request body, provided by the caller. + onceCloseReqBody sync.Once + reqBody io.ReadCloser + + reqBodyWriter bodyWriter + + // Response.Body, provided to the caller. + respBody io.ReadCloser + + trace *httptrace.ClientTrace + + errOnce sync.Once + err error +} + +// abort terminates the RoundTrip. +// It returns the first fatal error encountered by the RoundTrip call. +func (rt *roundTripState) abort(err error) error { + rt.errOnce.Do(func() { + rt.err = err + switch e := err.(type) { + case *connectionError: + rt.cc.abort(e) + case *streamError: + rt.st.stream.CloseRead() + rt.st.stream.Reset(uint64(e.code)) + default: + rt.st.stream.CloseRead() + rt.st.stream.Reset(uint64(errH3NoError)) + } + }) + return rt.err +} + +// closeReqBody closes the Request.Body, at most once. +func (rt *roundTripState) closeReqBody() { + if rt.reqBody != nil { + rt.onceCloseReqBody.Do(func() { + rt.reqBody.Close() + }) + } +} + +// TODO: Set up the rest of the hooks that might be in rt.trace. +func (rt *roundTripState) maybeCallGot1xxResponse(status int, h http.Header) error { + if rt.trace == nil || rt.trace.Got1xxResponse == nil { + return nil + } + return rt.trace.Got1xxResponse(status, textproto.MIMEHeader(h)) +} + +func (rt *roundTripState) maybeCallGot100Continue() { + if rt.trace == nil || rt.trace.Got100Continue == nil { + return + } + rt.trace.Got100Continue() +} + +func (rt *roundTripState) maybeCallWait100Continue() { + if rt.trace == nil || rt.trace.Wait100Continue == nil { + return + } + rt.trace.Wait100Continue() +} + +// RoundTrip sends a request on the connection. +func (cc *clientConn) RoundTrip(req *http.Request) (_ *http.Response, err error) { + // Each request gets its own QUIC stream. + st, err := newConnStream(req.Context(), cc.qconn, streamTypeRequest) + if err != nil { + return nil, err + } + rt := &roundTripState{ + cc: cc, + st: st, + trace: httptrace.ContextClientTrace(req.Context()), + } + defer func() { + if err != nil { + err = rt.abort(err) + } + }() + + // Cancel reads/writes on the stream when the request expires. + st.stream.SetReadContext(req.Context()) + st.stream.SetWriteContext(req.Context()) + + headers := cc.enc.encode(func(yield func(itype indexType, name, value string)) { + _, err = httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{ + Request: httpcommon.Request{ + URL: req.URL, + Method: req.Method, + Host: req.Host, + Header: req.Header, + Trailer: req.Trailer, + ActualContentLength: actualContentLength(req), + }, + AddGzipHeader: false, // TODO: add when appropriate + PeerMaxHeaderListSize: 0, + DefaultUserAgent: "Go-http-client/3", + }, func(name, value string) { + // Issue #71374: Consider supporting never-indexed fields. + yield(mayIndex, name, value) + }) + }) + if err != nil { + return nil, err + } + + // Write the HEADERS frame. + st.writeVarint(int64(frameTypeHeaders)) + st.writeVarint(int64(len(headers))) + st.Write(headers) + if err := st.Flush(); err != nil { + return nil, err + } + + var bodyAndTrailerWritten bool + is100ContinueReq := httpguts.HeaderValuesContainsToken(req.Header["Expect"], "100-continue") + if is100ContinueReq { + rt.maybeCallWait100Continue() + } else { + bodyAndTrailerWritten = true + go cc.writeBodyAndTrailer(rt, req) + } + + // Read the response headers. + for { + ftype, err := st.readFrameHeader() + if err != nil { + return nil, err + } + switch ftype { + case frameTypeHeaders: + statusCode, h, err := cc.handleHeaders(st) + if err != nil { + return nil, err + } + + // TODO: Handle 1xx responses. + if isInfoStatus(statusCode) { + if err := rt.maybeCallGot1xxResponse(statusCode, h); err != nil { + return nil, err + } + switch statusCode { + case 100: + rt.maybeCallGot100Continue() + if is100ContinueReq && !bodyAndTrailerWritten { + bodyAndTrailerWritten = true + go cc.writeBodyAndTrailer(rt, req) + continue + } + // If we did not send "Expect: 100-continue" request but + // received status 100 anyways, just continue per usual and + // let the caller decide what to do with the response. + default: + continue + } + } + + // We have the response headers. + // Set up the response and return it to the caller. + contentLength, err := parseResponseContentLength(req.Method, statusCode, h) + if err != nil { + return nil, err + } + + trailer := make(http.Header) + extractTrailerFromHeader(h, trailer) + delete(h, "Trailer") + + if (contentLength != 0 && req.Method != http.MethodHead) || len(trailer) > 0 { + rt.respBody = &bodyReader{ + st: st, + remain: contentLength, + trailer: trailer, + } + } else { + rt.respBody = http.NoBody + } + resp := &http.Response{ + Proto: "HTTP/3.0", + ProtoMajor: 3, + Header: h, + StatusCode: statusCode, + Status: strconv.Itoa(statusCode) + " " + http.StatusText(statusCode), + ContentLength: contentLength, + Trailer: trailer, + Body: (*transportResponseBody)(rt), + } + // TODO: Automatic Content-Type: gzip decoding. + return resp, nil + case frameTypePushPromise: + if err := cc.handlePushPromise(st); err != nil { + return nil, err + } + default: + if err := st.discardUnknownFrame(ftype); err != nil { + return nil, err + } + } + } +} + +// actualContentLength returns a sanitized version of req.ContentLength, +// where 0 actually means zero (not unknown) and -1 means unknown. +func actualContentLength(req *http.Request) int64 { + if req.Body == nil || req.Body == http.NoBody { + return 0 + } + if req.ContentLength != 0 { + return req.ContentLength + } + return -1 +} + +// writeBodyAndTrailer handles writing the body and trailer for a given +// request, if any. This function will close the write direction of the stream. +func (cc *clientConn) writeBodyAndTrailer(rt *roundTripState, req *http.Request) { + defer rt.closeReqBody() + + declaredTrailer := req.Trailer.Clone() + + rt.reqBody = req.Body + rt.reqBodyWriter.st = rt.st + rt.reqBodyWriter.remain = actualContentLength(req) + rt.reqBodyWriter.flush = true + rt.reqBodyWriter.name = "request" + rt.reqBodyWriter.trailer = req.Trailer + rt.reqBodyWriter.enc = &cc.enc + if req.Body == nil { + rt.reqBody = http.NoBody + } + + if _, err := io.Copy(&rt.reqBodyWriter, rt.reqBody); err != nil { + rt.abort(err) + } + // Get rid of any trailer that was not declared beforehand, before we + // close the request body which will cause the trailer headers to be + // written. + for name := range req.Trailer { + if _, ok := declaredTrailer[name]; !ok { + delete(req.Trailer, name) + } + } + if err := rt.reqBodyWriter.Close(); err != nil { + rt.abort(err) + } +} + +// transportResponseBody is the Response.Body returned by RoundTrip. +type transportResponseBody roundTripState + +// Read is Response.Body.Read. +func (b *transportResponseBody) Read(p []byte) (n int, err error) { + return b.respBody.Read(p) +} + +var errRespBodyClosed = errors.New("response body closed") + +// Close is Response.Body.Close. +// Closing the response body is how the caller signals that they're done with a request. +func (b *transportResponseBody) Close() error { + rt := (*roundTripState)(b) + // Close the request body, which should wake up copyRequestBody if it's + // currently blocked reading the body. + rt.closeReqBody() + // Close the request stream, since we're done with the request. + // Reset closes the sending half of the stream. + rt.st.stream.Reset(uint64(errH3NoError)) + // respBody.Close is responsible for closing the receiving half. + err := rt.respBody.Close() + if err == nil { + err = errRespBodyClosed + } + err = rt.abort(err) + if err == errRespBodyClosed { + // No other errors occurred before closing Response.Body, + // so consider this a successful request. + return nil + } + return err +} + +func parseResponseContentLength(method string, statusCode int, h http.Header) (int64, error) { + clens := h["Content-Length"] + if len(clens) == 0 { + return -1, nil + } + + // We allow duplicate Content-Length headers, + // but only if they all have the same value. + for _, v := range clens[1:] { + if clens[0] != v { + return -1, &streamError{errH3MessageError, "mismatching Content-Length headers"} + } + } + + // "A server MUST NOT send a Content-Length header field in any response + // with a status code of 1xx (Informational) or 204 (No Content). + // A server MUST NOT send a Content-Length header field in any 2xx (Successful) + // response to a CONNECT request [...]" + // https://www.rfc-editor.org/rfc/rfc9110#section-8.6-8 + if (statusCode >= 100 && statusCode < 200) || + statusCode == 204 || + (method == "CONNECT" && statusCode >= 200 && statusCode < 300) { + // This is a protocol violation, but a fairly harmless one. + // Just ignore the header. + return -1, nil + } + + contentLen, err := strconv.ParseUint(clens[0], 10, 63) + if err != nil { + return -1, &streamError{errH3MessageError, "invalid Content-Length header"} + } + return int64(contentLen), nil +} + +func (cc *clientConn) handleHeaders(st *stream) (statusCode int, h http.Header, err error) { + haveStatus := false + cookie := "" + // Issue #71374: Consider tracking the never-indexed status of headers + // with the N bit set in their QPACK encoding. + err = cc.dec.decode(st, func(_ indexType, name, value string) error { + switch { + case name == ":status": + if haveStatus { + return &streamError{errH3MessageError, "duplicate :status"} + } + haveStatus = true + statusCode, err = strconv.Atoi(value) + if err != nil { + return &streamError{errH3MessageError, "invalid :status"} + } + case name[0] == ':': + // "Endpoints MUST treat a request or response + // that contains undefined or invalid + // pseudo-header fields as malformed." + // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.3-3 + return &streamError{errH3MessageError, "undefined pseudo-header"} + case name == "cookie": + // "If a decompressed field section contains multiple cookie field lines, + // these MUST be concatenated into a single byte string [...]" + // using the two-byte delimiter of "; "'' + // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2.1-2 + if cookie == "" { + cookie = value + } else { + cookie += "; " + value + } + default: + if h == nil { + h = make(http.Header) + } + // TODO: Use a per-connection canonicalization cache as we do in HTTP/2. + // Maybe we could put this in the QPACK decoder and have it deliver + // pre-canonicalized headers to us here? + cname := httpcommon.CanonicalHeader(name) + // TODO: Consider using a single []string slice for all headers, + // as we do in the HTTP/1 and HTTP/2 cases. + // This is a bit tricky, since we don't know the number of headers + // at the start of decoding. Perhaps it's worth doing a two-pass decode, + // or perhaps we should just allocate header value slices in + // reasonably-sized chunks. + h[cname] = append(h[cname], value) + } + return nil + }) + if !haveStatus { + // "[The :status] pseudo-header field MUST be included in all responses [...]" + // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.3.2-1 + err = errH3MessageError + } + if cookie != "" { + if h == nil { + h = make(http.Header) + } + h["Cookie"] = []string{cookie} + } + if err := st.endFrame(); err != nil { + return 0, nil, err + } + return statusCode, h, err +} + +func (cc *clientConn) handlePushPromise(st *stream) error { + // "A client MUST treat receipt of a PUSH_PROMISE frame that contains a + // larger push ID than the client has advertised as a connection error of H3_ID_ERROR." + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.5-5 + return &connectionError{ + code: errH3IDError, + message: "PUSH_PROMISE received when no MAX_PUSH_ID has been sent", + } +} diff --git a/src/vendor/golang.org/x/net/internal/http3/server.go b/src/vendor/golang.org/x/net/internal/http3/server.go new file mode 100644 index 0000000000..28c8cda849 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/server.go @@ -0,0 +1,791 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "maps" + "net/http" + "slices" + "strconv" + "strings" + "sync" + "time" + + "golang.org/x/net/http/httpguts" + "golang.org/x/net/internal/httpcommon" + "golang.org/x/net/quic" +) + +// A server is an HTTP/3 server. +// The zero value for server is a valid server. +type server struct { + // handler to invoke for requests, http.DefaultServeMux if nil. + handler http.Handler + + config *quic.Config + + listenQUIC func(addr string, config *quic.Config) (*quic.Endpoint, error) + + initOnce sync.Once + + serveCtx context.Context + serveCtxCancel context.CancelFunc + + // connClosed is used to signal that a connection has been unregistered + // from activeConns. That way, when shutting down gracefully, the server + // can avoid busy-waiting for activeConns to be empty. + connClosed chan any + mu sync.Mutex // Guards fields below. + activeConns map[*serverConn]struct{} +} + +// netHTTPHandler is an interface that is implemented by +// net/http.http3ServerHandler in std. +// +// It provides a way for information to be passed between x/net and net/http +// that would otherwise be inaccessible, such as the TLS configs that users +// have supplied to net/http servers. +// +// This allows us to integrate our HTTP/3 server implementation with the +// net/http server when RegisterServer is called. +type netHTTPHandler interface { + http.Handler + TLSConfig() *tls.Config + BaseContext() context.Context + Addr() string + ListenErrHook(err error) + ShutdownContext() context.Context +} + +type ServerOpts struct { + // ListenQUIC determines how the server will open a QUIC endpoint. + // By default, quic.Listen("udp", addr, config) is used. + ListenQUIC func(addr string, config *quic.Config) (*quic.Endpoint, error) + + // QUICConfig is the QUIC configuration used by the server. + // QUICConfig may be nil and should not be modified after calling + // RegisterServer. + // If QUICConfig.TLSConfig is nil, the TLSConfig of the net/http Server + // given to RegisterServer will be used. + QUICConfig *quic.Config +} + +// RegisterServer adds HTTP/3 support to a net/http Server. +// +// RegisterServer must be called before s begins serving, and only affects +// s.ListenAndServeTLS. +func RegisterServer(s *http.Server, opts ServerOpts) { + if s.TLSNextProto == nil { + s.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) + } + s.TLSNextProto["http/3"] = func(s *http.Server, c *tls.Conn, h http.Handler) { + stdHandler, ok := h.(netHTTPHandler) + if !ok { + panic("RegisterServer was given a server that does not implement netHTTPHandler") + } + if opts.QUICConfig == nil { + opts.QUICConfig = &quic.Config{} + } + if opts.QUICConfig.TLSConfig == nil { + opts.QUICConfig.TLSConfig = stdHandler.TLSConfig() + } + s3 := &server{ + config: opts.QUICConfig, + listenQUIC: opts.ListenQUIC, + handler: stdHandler, + serveCtx: stdHandler.BaseContext(), + } + s3.init() + s.RegisterOnShutdown(func() { + s3.shutdown(stdHandler.ShutdownContext()) + }) + stdHandler.ListenErrHook(s3.listenAndServe(stdHandler.Addr())) + } +} + +func (s *server) init() { + s.initOnce.Do(func() { + s.config = initConfig(s.config) + if s.handler == nil { + s.handler = http.DefaultServeMux + } + if s.serveCtx == nil { + s.serveCtx = context.Background() + } + if s.listenQUIC == nil { + s.listenQUIC = func(addr string, config *quic.Config) (*quic.Endpoint, error) { + return quic.Listen("udp", addr, config) + } + } + s.serveCtx, s.serveCtxCancel = context.WithCancel(s.serveCtx) + s.activeConns = make(map[*serverConn]struct{}) + s.connClosed = make(chan any, 1) + }) +} + +// listenAndServe listens on the UDP network address addr +// and then calls Serve to handle requests on incoming connections. +func (s *server) listenAndServe(addr string) error { + s.init() + e, err := s.listenQUIC(addr, s.config) + if err != nil { + return err + } + go s.serve(e) + return nil +} + +// serve accepts incoming connections on the QUIC endpoint e, +// and handles requests from those connections. +func (s *server) serve(e *quic.Endpoint) error { + s.init() + defer e.Close(canceledCtx) + for { + qconn, err := e.Accept(s.serveCtx) + if err != nil { + return err + } + go s.newServerConn(qconn, s.handler) + } +} + +// shutdown attempts a graceful shutdown for the server. +func (s *server) shutdown(ctx context.Context) { + // Set a reasonable default in case ctx is nil. + if ctx == nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(context.Background(), time.Second) + defer cancel() + } + + // Send GOAWAY frames to all active connections to give a chance for them + // to gracefully terminate. + s.mu.Lock() + for sc := range s.activeConns { + // TODO: Modify x/net/quic stream API so that write errors from context + // deadline are sticky. + go sc.sendGoaway() + } + s.mu.Unlock() + + // Complete shutdown as soon as there are no more active connections or ctx + // is done, whichever comes first. + defer func() { + s.mu.Lock() + defer s.mu.Unlock() + s.serveCtxCancel() + for sc := range s.activeConns { + sc.abort(&connectionError{ + code: errH3NoError, + message: "server is shutting down", + }) + } + }() + noMoreConns := func() bool { + s.mu.Lock() + defer s.mu.Unlock() + return len(s.activeConns) == 0 + } + for { + if noMoreConns() { + return + } + select { + case <-ctx.Done(): + return + case <-s.connClosed: + } + } +} + +func (s *server) registerConn(sc *serverConn) { + s.mu.Lock() + defer s.mu.Unlock() + s.activeConns[sc] = struct{}{} +} + +func (s *server) unregisterConn(sc *serverConn) { + s.mu.Lock() + delete(s.activeConns, sc) + s.mu.Unlock() + select { + case s.connClosed <- struct{}{}: + default: + // Channel already full. No need to send more values since we are just + // using this channel as a simpler sync.Cond. + } +} + +type serverConn struct { + qconn *quic.Conn + + genericConn // for handleUnidirectionalStream + enc qpackEncoder + dec qpackDecoder + handler http.Handler + + // For handling shutdown. + controlStream *stream + mu sync.Mutex // Guards everything below. + maxRequestStreamID int64 + goawaySent bool +} + +func (s *server) newServerConn(qconn *quic.Conn, handler http.Handler) { + sc := &serverConn{ + qconn: qconn, + handler: handler, + } + s.registerConn(sc) + defer s.unregisterConn(sc) + sc.enc.init() + + // Create control stream and send SETTINGS frame. + // TODO: Time out on creating stream. + var err error + sc.controlStream, err = newConnStream(context.Background(), sc.qconn, streamTypeControl) + if err != nil { + return + } + sc.controlStream.writeSettings() + sc.controlStream.Flush() + + sc.acceptStreams(sc.qconn, sc) +} + +func (sc *serverConn) handleControlStream(st *stream) error { + // "A SETTINGS frame MUST be sent as the first frame of each control stream [...]" + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4-2 + if err := st.readSettings(func(settingsType, settingsValue int64) error { + switch settingsType { + case settingsMaxFieldSectionSize: + _ = settingsValue // TODO + case settingsQPACKMaxTableCapacity: + _ = settingsValue // TODO + case settingsQPACKBlockedStreams: + _ = settingsValue // TODO + default: + // Unknown settings types are ignored. + } + return nil + }); err != nil { + return err + } + + for { + ftype, err := st.readFrameHeader() + if err != nil { + return err + } + switch ftype { + case frameTypeCancelPush: + // "If a server receives a CANCEL_PUSH frame for a push ID + // that has not yet been mentioned by a PUSH_PROMISE frame, + // this MUST be treated as a connection error of type H3_ID_ERROR." + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.3-8 + return &connectionError{ + code: errH3IDError, + message: "CANCEL_PUSH for unsent push ID", + } + case frameTypeGoaway: + return errH3NoError + default: + // Unknown frames are ignored. + if err := st.discardUnknownFrame(ftype); err != nil { + return err + } + } + } +} + +func (sc *serverConn) handleEncoderStream(*stream) error { + // TODO + return nil +} + +func (sc *serverConn) handleDecoderStream(*stream) error { + // TODO + return nil +} + +func (sc *serverConn) handlePushStream(*stream) error { + // "[...] if a server receives a client-initiated push stream, + // this MUST be treated as a connection error of type H3_STREAM_CREATION_ERROR." + // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2.2-3 + return &connectionError{ + code: errH3StreamCreationError, + message: "client created push stream", + } +} + +type pseudoHeader struct { + method string + scheme string + path string + authority string +} + +func (sc *serverConn) parseHeader(st *stream) (http.Header, pseudoHeader, error) { + ftype, err := st.readFrameHeader() + if err != nil { + return nil, pseudoHeader{}, err + } + if ftype != frameTypeHeaders { + return nil, pseudoHeader{}, err + } + header := make(http.Header) + var pHeader pseudoHeader + var dec qpackDecoder + if err := dec.decode(st, func(_ indexType, name, value string) error { + switch name { + case ":method": + pHeader.method = value + case ":scheme": + pHeader.scheme = value + case ":path": + pHeader.path = value + case ":authority": + pHeader.authority = value + default: + header.Add(name, value) + } + return nil + }); err != nil { + return nil, pseudoHeader{}, err + } + if err := st.endFrame(); err != nil { + return nil, pseudoHeader{}, err + } + return header, pHeader, nil +} + +func (sc *serverConn) sendGoaway() { + sc.mu.Lock() + if sc.goawaySent || sc.controlStream == nil { + sc.mu.Unlock() + return + } + sc.goawaySent = true + sc.mu.Unlock() + + // No lock in this section in case writing to stream blocks. This is safe + // since sc.maxRequestStreamID is only updated when sc.goawaySent is false. + sc.controlStream.writeVarint(int64(frameTypeGoaway)) + sc.controlStream.writeVarint(int64(sizeVarint(uint64(sc.maxRequestStreamID)))) + sc.controlStream.writeVarint(sc.maxRequestStreamID) + sc.controlStream.Flush() +} + +// requestShouldGoAway returns true if st has a stream ID that is equal or +// greater than the ID we have sent in a GOAWAY frame, if any. +func (sc *serverConn) requestShouldGoaway(st *stream) bool { + sc.mu.Lock() + defer sc.mu.Unlock() + if sc.goawaySent { + return st.stream.ID() >= sc.maxRequestStreamID + } else { + sc.maxRequestStreamID = max(sc.maxRequestStreamID, st.stream.ID()) + return false + } +} + +func (sc *serverConn) handleRequestStream(st *stream) error { + if sc.requestShouldGoaway(st) { + return &streamError{ + code: errH3RequestRejected, + message: "GOAWAY request with equal or lower ID than the stream has been sent", + } + } + header, pHeader, err := sc.parseHeader(st) + if err != nil { + return err + } + + reqInfo := httpcommon.NewServerRequest(httpcommon.ServerRequestParam{ + Method: pHeader.method, + Scheme: pHeader.scheme, + Authority: pHeader.authority, + Path: pHeader.path, + Header: header, + }) + if reqInfo.InvalidReason != "" { + return &streamError{ + code: errH3MessageError, + message: reqInfo.InvalidReason, + } + } + + var body io.ReadCloser + contentLength := int64(-1) + if n, err := strconv.Atoi(header.Get("Content-Length")); err == nil { + contentLength = int64(n) + } + if contentLength != 0 || len(reqInfo.Trailer) != 0 { + body = &bodyReader{ + st: st, + remain: contentLength, + trailer: reqInfo.Trailer, + } + } else { + body = http.NoBody + } + + req := &http.Request{ + Proto: "HTTP/3.0", + Method: pHeader.method, + Host: pHeader.authority, + URL: reqInfo.URL, + RequestURI: reqInfo.RequestURI, + Trailer: reqInfo.Trailer, + ProtoMajor: 3, + RemoteAddr: sc.qconn.RemoteAddr().String(), + Body: body, + Header: header, + ContentLength: contentLength, + } + defer req.Body.Close() + + rw := &responseWriter{ + st: st, + headers: make(http.Header), + trailer: make(http.Header), + bb: make(bodyBuffer, 0, defaultBodyBufferCap), + cannotHaveBody: req.Method == "HEAD", + bw: &bodyWriter{ + st: st, + remain: -1, + flush: false, + name: "response", + enc: &sc.enc, + }, + } + defer rw.close() + if reqInfo.NeedsContinue { + req.Body.(*bodyReader).send100Continue = func() { + rw.WriteHeader(100) + } + } + + // TODO: handle panic coming from the HTTP handler. + sc.handler.ServeHTTP(rw, req) + return nil +} + +// abort closes the connection with an error. +func (sc *serverConn) abort(err error) { + if e, ok := err.(*connectionError); ok { + sc.qconn.Abort(&quic.ApplicationError{ + Code: uint64(e.code), + Reason: e.message, + }) + } else { + sc.qconn.Abort(err) + } +} + +// responseCanHaveBody reports whether a given response status code permits a +// body. See RFC 7230, section 3.3. +func responseCanHaveBody(status int) bool { + switch { + case status >= 100 && status <= 199: + return false + case status == 204: + return false + case status == 304: + return false + } + return true +} + +type responseWriter struct { + st *stream + bw *bodyWriter + mu sync.Mutex + headers http.Header + trailer http.Header + bb bodyBuffer + wroteHeader bool // Non-1xx header has been (logically) written. + statusCode int // Status of the response that will be sent in HEADERS frame. + statusCodeSet bool // Status of the response has been set via a call to WriteHeader. + cannotHaveBody bool // Response should not have a body (e.g. response to a HEAD request). + bodyLenLeft int // How much of the content body is left to be sent, set via "Content-Length" header. -1 if unknown. +} + +func (rw *responseWriter) Header() http.Header { + return rw.headers +} + +// prepareTrailerForWriteLocked populates any pre-declared trailer header with +// its value, and passes it to bodyWriter so it can be written after body EOF. +// Caller must hold rw.mu. +func (rw *responseWriter) prepareTrailerForWriteLocked() { + for name := range rw.trailer { + if val, ok := rw.headers[name]; ok { + rw.trailer[name] = val + } else { + delete(rw.trailer, name) + } + } + if len(rw.trailer) > 0 { + rw.bw.trailer = rw.trailer + } +} + +// writeHeaderLockedOnce writes the final response header. If rw.wroteHeader is +// true, calling this method is a no-op. Sending informational status headers +// should be done using writeInfoHeaderLocked, rather than this method. +// Caller must hold rw.mu. +func (rw *responseWriter) writeHeaderLockedOnce() { + if rw.wroteHeader { + return + } + if !responseCanHaveBody(rw.statusCode) { + rw.cannotHaveBody = true + } + // If there is any Trailer declared in headers, save them so we know which + // trailers have been pre-declared. Also, write back the extracted value, + // which is canonicalized, to rw.Header for consistency. + if _, ok := rw.headers["Trailer"]; ok { + extractTrailerFromHeader(rw.headers, rw.trailer) + rw.headers.Set("Trailer", strings.Join(slices.Sorted(maps.Keys(rw.trailer)), ", ")) + } + + rw.bb.inferHeader(rw.headers, rw.statusCode) + encHeaders := rw.bw.enc.encode(func(f func(itype indexType, name, value string)) { + f(mayIndex, ":status", strconv.Itoa(rw.statusCode)) + for name, values := range rw.headers { + if !httpguts.ValidHeaderFieldName(name) { + continue + } + for _, val := range values { + if !httpguts.ValidHeaderFieldValue(val) { + continue + } + // Issue #71374: Consider supporting never-indexed fields. + f(mayIndex, name, val) + } + } + }) + + rw.st.writeVarint(int64(frameTypeHeaders)) + rw.st.writeVarint(int64(len(encHeaders))) + rw.st.Write(encHeaders) + rw.wroteHeader = true +} + +// writeHeaderLocked writes informational status headers (i.e. status 1XX). +// If a non-informational status header has been written via +// writeHeaderLockedOnce, this method is a no-op. +// Caller must hold rw.mu. +func (rw *responseWriter) writeHeaderLocked(statusCode int) { + if rw.wroteHeader { + return + } + encHeaders := rw.bw.enc.encode(func(f func(itype indexType, name, value string)) { + f(mayIndex, ":status", strconv.Itoa(statusCode)) + for name, values := range rw.headers { + if name == "Content-Length" || name == "Transfer-Encoding" { + continue + } + if !httpguts.ValidHeaderFieldName(name) { + continue + } + for _, val := range values { + if !httpguts.ValidHeaderFieldValue(val) { + continue + } + // Issue #71374: Consider supporting never-indexed fields. + f(mayIndex, name, val) + } + } + }) + rw.st.writeVarint(int64(frameTypeHeaders)) + rw.st.writeVarint(int64(len(encHeaders))) + rw.st.Write(encHeaders) +} + +func isInfoStatus(status int) bool { + return status >= 100 && status < 200 +} + +// checkWriteHeaderCode is a copy of net/http's checkWriteHeaderCode. +func checkWriteHeaderCode(code int) { + // Issue 22880: require valid WriteHeader status codes. + // For now we only enforce that it's three digits. + // In the future we might block things over 599 (600 and above aren't defined + // at http://httpwg.org/specs/rfc7231.html#status.codes). + // But for now any three digits. + // + // We used to send "HTTP/1.1 000 0" on the wire in responses but there's + // no equivalent bogus thing we can realistically send in HTTP/3, + // so we'll consistently panic instead and help people find their bugs + // early. (We can't return an error from WriteHeader even if we wanted to.) + if code < 100 || code > 999 { + panic(fmt.Sprintf("invalid WriteHeader code %v", code)) + } +} + +func (rw *responseWriter) WriteHeader(statusCode int) { + // TODO: handle sending informational status headers (e.g. 103). + rw.mu.Lock() + defer rw.mu.Unlock() + if rw.statusCodeSet { + return + } + checkWriteHeaderCode(statusCode) + + // Informational headers can be sent multiple times, and should be flushed + // immediately. + if isInfoStatus(statusCode) { + rw.writeHeaderLocked(statusCode) + rw.st.Flush() + return + } + + // Non-informational headers should only be set once, and should be + // buffered. + rw.statusCodeSet = true + rw.statusCode = statusCode + if n, err := strconv.Atoi(rw.Header().Get("Content-Length")); err == nil { + rw.bodyLenLeft = n + } else { + rw.bodyLenLeft = -1 // Unknown. + } +} + +// trimWriteLocked trims a byte slice, b, such that the length of b will not +// exceed rw.bodyLenLeft. This method will update rw.bodyLenLeft when trimming +// b, and will also return whether b was trimmed or not. +// Caller must hold rw.mu. +func (rw *responseWriter) trimWriteLocked(b []byte) ([]byte, bool) { + if rw.bodyLenLeft < 0 { + return b, false + } + n := min(len(b), rw.bodyLenLeft) + rw.bodyLenLeft -= n + return b[:n], n != len(b) +} + +func (rw *responseWriter) Write(b []byte) (n int, err error) { + // Calling Write implicitly calls WriteHeader(200) if WriteHeader has not + // been called before. + rw.WriteHeader(http.StatusOK) + rw.mu.Lock() + defer rw.mu.Unlock() + + if rw.statusCode == http.StatusNotModified { + return 0, http.ErrBodyNotAllowed + } + + b, trimmed := rw.trimWriteLocked(b) + if trimmed { + defer func() { + err = http.ErrContentLength + }() + } + + // If b fits entirely in our body buffer, save it to the buffer and return + // early so we can coalesce small writes. + // As a special case, we always want to save b to the buffer even when b is + // big if we had yet to write our header, so we can infer headers like + // "Content-Type" with as much information as possible. + initialBLen := len(b) + initialBufLen := len(rw.bb) + if !rw.wroteHeader || len(b) <= cap(rw.bb)-len(rw.bb) { + b = rw.bb.write(b) + if len(b) == 0 { + return initialBLen, nil + } + } + + // Reaching this point means that our buffer has been sufficiently filled. + // Therefore, we now want to: + // 1. Infer and write response headers based on our body buffer, if not + // done yet. + // 2. Write our body buffer and the rest of b (if any). + // 3. Reset the current body buffer so it can be used again. + rw.writeHeaderLockedOnce() + if rw.cannotHaveBody { + return initialBLen, nil + } + if n, err := rw.bw.write(rw.bb, b); err != nil { + return max(0, n-initialBufLen), err + } + rw.bb.discard() + return initialBLen, nil +} + +func (rw *responseWriter) Flush() { + // Calling Flush implicitly calls WriteHeader(200) if WriteHeader has not + // been called before. + rw.WriteHeader(http.StatusOK) + rw.mu.Lock() + defer rw.mu.Unlock() + rw.writeHeaderLockedOnce() + if !rw.cannotHaveBody { + rw.bw.Write(rw.bb) + rw.bb.discard() + } + rw.st.Flush() +} + +func (rw *responseWriter) close() error { + rw.Flush() + rw.mu.Lock() + defer rw.mu.Unlock() + rw.prepareTrailerForWriteLocked() + if err := rw.bw.Close(); err != nil { + return err + } + return rw.st.stream.Close() +} + +// defaultBodyBufferCap is the default number of bytes of body that we are +// willing to save in a buffer for the sake of inferring headers and coalescing +// small writes. 512 was chosen to be consistent with how much +// http.DetectContentType is willing to read. +const defaultBodyBufferCap = 512 + +// bodyBuffer is a buffer used to store body content of a response. +type bodyBuffer []byte + +// write writes b to the buffer. It returns a new slice of b, which contains +// any remaining data that could not be written to the buffer, if any. +func (bb *bodyBuffer) write(b []byte) []byte { + n := min(len(b), cap(*bb)-len(*bb)) + *bb = append(*bb, b[:n]...) + return b[n:] +} + +// discard resets the buffer so it can be used again. +func (bb *bodyBuffer) discard() { + *bb = (*bb)[:0] +} + +// inferHeader populates h with the header values that we can infer from our +// current buffer content, if not already explicitly set. This method should be +// called only once with as much body content as possible in the buffer, before +// a HEADERS frame is sent, and before discard has been called. Doing so +// properly is the responsibility of the caller. +func (bb *bodyBuffer) inferHeader(h http.Header, status int) { + if _, ok := h["Date"]; !ok { + h.Set("Date", time.Now().UTC().Format(http.TimeFormat)) + } + // If the Content-Encoding is non-blank, we shouldn't + // sniff the body. See Issue golang.org/issue/31753. + _, hasCE := h["Content-Encoding"] + _, hasCT := h["Content-Type"] + if !hasCE && !hasCT && responseCanHaveBody(status) && len(*bb) > 0 { + h.Set("Content-Type", http.DetectContentType(*bb)) + } + // We can technically infer Content-Length too here, as long as the entire + // response body fits within hi.buf and does not require flushing. However, + // we have chosen not to do so for now as Content-Length is not very + // important for HTTP/3, and such inconsistent behavior might be confusing. +} diff --git a/src/vendor/golang.org/x/net/internal/http3/settings.go b/src/vendor/golang.org/x/net/internal/http3/settings.go new file mode 100644 index 0000000000..2d2ca0e705 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/settings.go @@ -0,0 +1,66 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +const ( + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4.1 + settingsMaxFieldSectionSize = 0x06 + + // https://www.rfc-editor.org/rfc/rfc9204.html#section-5 + settingsQPACKMaxTableCapacity = 0x01 + settingsQPACKBlockedStreams = 0x07 +) + +// writeSettings writes a complete SETTINGS frame. +// Its parameter is a list of alternating setting types and values. +func (st *stream) writeSettings(settings ...int64) { + var size int64 + for _, s := range settings { + // Settings values that don't fit in a QUIC varint ([0,2^62)) will panic here. + size += int64(sizeVarint(uint64(s))) + } + st.writeVarint(int64(frameTypeSettings)) + st.writeVarint(size) + for _, s := range settings { + st.writeVarint(s) + } +} + +// readSettings reads a complete SETTINGS frame, including the frame header. +func (st *stream) readSettings(f func(settingType, value int64) error) error { + frameType, err := st.readFrameHeader() + if err != nil || frameType != frameTypeSettings { + return &connectionError{ + code: errH3MissingSettings, + message: "settings not sent on control stream", + } + } + for st.lim > 0 { + settingsType, err := st.readVarint() + if err != nil { + return err + } + settingsValue, err := st.readVarint() + if err != nil { + return err + } + + // Use of HTTP/2 settings where there is no corresponding HTTP/3 setting + // is an error. + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4.1-5 + switch settingsType { + case 0x02, 0x03, 0x04, 0x05: + return &connectionError{ + code: errH3SettingsError, + message: "use of reserved setting", + } + } + + if err := f(settingsType, settingsValue); err != nil { + return err + } + } + return st.endFrame() +} diff --git a/src/vendor/golang.org/x/net/internal/http3/stream.go b/src/vendor/golang.org/x/net/internal/http3/stream.go new file mode 100644 index 0000000000..93294d43de --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/stream.go @@ -0,0 +1,260 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "context" + "io" + + "golang.org/x/net/quic" +) + +// A stream wraps a QUIC stream, providing methods to read/write various values. +type stream struct { + stream *quic.Stream + + // lim is the current read limit. + // Reading a frame header sets the limit to the end of the frame. + // Reading past the limit or reading less than the limit and ending the frame + // results in an error. + // -1 indicates no limit. + lim int64 +} + +// newConnStream creates a new stream on a connection. +// It writes the stream header for unidirectional streams. +// +// The stream returned by newStream is not flushed, +// and will not be sent to the peer until the caller calls +// Flush or writes enough data to the stream. +func newConnStream(ctx context.Context, qconn *quic.Conn, stype streamType) (*stream, error) { + var qs *quic.Stream + var err error + if stype == streamTypeRequest { + // Request streams are bidirectional. + qs, err = qconn.NewStream(ctx) + } else { + // All other streams are unidirectional. + qs, err = qconn.NewSendOnlyStream(ctx) + } + if err != nil { + return nil, err + } + st := &stream{ + stream: qs, + lim: -1, // no limit + } + if stype != streamTypeRequest { + // Unidirectional stream header. + st.writeVarint(int64(stype)) + } + return st, err +} + +func newStream(qs *quic.Stream) *stream { + return &stream{ + stream: qs, + lim: -1, // no limit + } +} + +// readFrameHeader reads the type and length fields of an HTTP/3 frame. +// It sets the read limit to the end of the frame. +// +// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.1 +func (st *stream) readFrameHeader() (ftype frameType, err error) { + if st.lim >= 0 { + // We shouldn't call readFrameHeader before ending the previous frame. + return 0, errH3FrameError + } + ftype, err = readVarint[frameType](st) + if err != nil { + return 0, err + } + size, err := st.readVarint() + if err != nil { + return 0, err + } + st.lim = size + return ftype, nil +} + +// endFrame is called after reading a frame to reset the read limit. +// It returns an error if the entire contents of a frame have not been read. +func (st *stream) endFrame() error { + if st.lim != 0 { + return &connectionError{ + code: errH3FrameError, + message: "invalid HTTP/3 frame", + } + } + st.lim = -1 + return nil +} + +// readFrameData returns the remaining data in the current frame. +func (st *stream) readFrameData() ([]byte, error) { + if st.lim < 0 { + return nil, errH3FrameError + } + // TODO: Pool buffers to avoid allocation here. + b := make([]byte, st.lim) + _, err := io.ReadFull(st, b) + if err != nil { + return nil, err + } + return b, nil +} + +// ReadByte reads one byte from the stream. +func (st *stream) ReadByte() (b byte, err error) { + if err := st.recordBytesRead(1); err != nil { + return 0, err + } + b, err = st.stream.ReadByte() + if err != nil { + if err == io.EOF && st.lim < 0 { + return 0, io.EOF + } + return 0, errH3FrameError + } + return b, nil +} + +// Read reads from the stream. +func (st *stream) Read(b []byte) (int, error) { + n, err := st.stream.Read(b) + if e2 := st.recordBytesRead(n); e2 != nil { + return 0, e2 + } + if err == io.EOF { + if st.lim == 0 { + // EOF at end of frame, ignore. + return n, nil + } else if st.lim > 0 { + // EOF inside frame, error. + return 0, errH3FrameError + } else { + // EOF outside of frame, surface to caller. + return n, io.EOF + } + } + if err != nil { + return 0, errH3FrameError + } + return n, nil +} + +// discardUnknownFrame discards an unknown frame. +// +// HTTP/3 requires that unknown frames be ignored on all streams. +// However, a known frame appearing in an unexpected place is a fatal error, +// so this returns an error if the frame is one we know. +func (st *stream) discardUnknownFrame(ftype frameType) error { + switch ftype { + case frameTypeData, + frameTypeHeaders, + frameTypeCancelPush, + frameTypeSettings, + frameTypePushPromise, + frameTypeGoaway, + frameTypeMaxPushID: + return &connectionError{ + code: errH3FrameUnexpected, + message: "unexpected " + ftype.String() + " frame", + } + } + return st.discardFrame() +} + +// discardFrame discards any remaining data in the current frame and resets the read limit. +func (st *stream) discardFrame() error { + // TODO: Consider adding a *quic.Stream method to discard some amount of data. + for range st.lim { + _, err := st.stream.ReadByte() + if err != nil { + return &streamError{errH3FrameError, err.Error()} + } + } + st.lim = -1 + return nil +} + +// Write writes to the stream. +func (st *stream) Write(b []byte) (int, error) { return st.stream.Write(b) } + +// Flush commits data written to the stream. +func (st *stream) Flush() error { return st.stream.Flush() } + +// readVarint reads a QUIC variable-length integer from the stream. +func (st *stream) readVarint() (v int64, err error) { + b, err := st.stream.ReadByte() + if err != nil { + return 0, err + } + v = int64(b & 0x3f) + n := 1 << (b >> 6) + for i := 1; i < n; i++ { + b, err := st.stream.ReadByte() + if err != nil { + return 0, errH3FrameError + } + v = (v << 8) | int64(b) + } + if err := st.recordBytesRead(n); err != nil { + return 0, err + } + return v, nil +} + +// readVarint reads a varint of a particular type. +func readVarint[T ~int64 | ~uint64](st *stream) (T, error) { + v, err := st.readVarint() + return T(v), err +} + +// writeVarint writes a QUIC variable-length integer to the stream. +func (st *stream) writeVarint(v int64) { + switch { + case v <= (1<<6)-1: + st.stream.WriteByte(byte(v)) + case v <= (1<<14)-1: + st.stream.WriteByte((1 << 6) | byte(v>>8)) + st.stream.WriteByte(byte(v)) + case v <= (1<<30)-1: + st.stream.WriteByte((2 << 6) | byte(v>>24)) + st.stream.WriteByte(byte(v >> 16)) + st.stream.WriteByte(byte(v >> 8)) + st.stream.WriteByte(byte(v)) + case v <= (1<<62)-1: + st.stream.WriteByte((3 << 6) | byte(v>>56)) + st.stream.WriteByte(byte(v >> 48)) + st.stream.WriteByte(byte(v >> 40)) + st.stream.WriteByte(byte(v >> 32)) + st.stream.WriteByte(byte(v >> 24)) + st.stream.WriteByte(byte(v >> 16)) + st.stream.WriteByte(byte(v >> 8)) + st.stream.WriteByte(byte(v)) + default: + panic("varint too large") + } +} + +// recordBytesRead records that n bytes have been read. +// It returns an error if the read passes the current limit. +func (st *stream) recordBytesRead(n int) error { + if st.lim < 0 { + return nil + } + st.lim -= int64(n) + if st.lim < 0 { + st.stream = nil // panic if we try to read again + return &connectionError{ + code: errH3FrameError, + message: "invalid HTTP/3 frame", + } + } + return nil +} diff --git a/src/vendor/golang.org/x/net/internal/http3/transport.go b/src/vendor/golang.org/x/net/internal/http3/transport.go new file mode 100644 index 0000000000..a99824c9f4 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/transport.go @@ -0,0 +1,284 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +import ( + "context" + "fmt" + "net/http" + "net/url" + "sync" + + "golang.org/x/net/quic" +) + +// A transport is an HTTP/3 transport. +// +// It does not manage a pool of connections, +// and therefore does not implement net/http.RoundTripper. +// +// TODO: Provide a way to register an HTTP/3 transport with a net/http.transport's +// connection pool. +type transport struct { + // config is the QUIC configuration used for client connections. + config *quic.Config + + mu sync.Mutex // Guards fields below. + // endpoint is the QUIC endpoint used by connections created by the + // transport. If CloseIdleConnections is called when activeConns is empty, + // endpoint will be unset. If unset, endpoint will be initialized by any + // call to dial. + endpoint *quic.Endpoint + activeConns map[*clientConn]struct{} + inFlightDials int +} + +// netHTTPTransport implements the net/http.dialClientConner interface, +// allowing our HTTP/3 transport to integrate with net/http. +type netHTTPTransport struct { + *transport +} + +// RoundTrip is defined since Transport.RegisterProtocol takes in a +// RoundTripper. However, this method will never be used as net/http's +// dialClientConner interface does not have a RoundTrip method and will only +// use DialClientConn to create a new RoundTripper. +func (t netHTTPTransport) RoundTrip(*http.Request) (*http.Response, error) { + panic("netHTTPTransport.RoundTrip should never be called") +} + +func (t netHTTPTransport) DialClientConn(ctx context.Context, addr string, _ *url.URL, _ func()) (http.RoundTripper, error) { + return t.transport.dial(ctx, addr) +} + +// RegisterTransport configures a net/http HTTP/1 Transport to use HTTP/3. +// +// TODO: most likely, add another arg for transport configuration. +func RegisterTransport(tr *http.Transport) { + tr3 := &transport{ + // initConfig will clone the tr.TLSClientConfig. + config: initConfig(&quic.Config{ + TLSConfig: tr.TLSClientConfig, + }), + activeConns: make(map[*clientConn]struct{}), + } + tr.RegisterProtocol("http/3", netHTTPTransport{tr3}) +} + +func (tr *transport) incInFlightDials() { + tr.mu.Lock() + defer tr.mu.Unlock() + tr.inFlightDials++ +} + +func (tr *transport) decInFlightDials() { + tr.mu.Lock() + defer tr.mu.Unlock() + tr.inFlightDials-- +} + +func (tr *transport) initEndpoint() (err error) { + tr.mu.Lock() + defer tr.mu.Unlock() + if tr.endpoint == nil { + tr.endpoint, err = quic.Listen("udp", ":0", nil) + } + return err +} + +// dial creates a new HTTP/3 client connection. +func (tr *transport) dial(ctx context.Context, target string) (*clientConn, error) { + tr.incInFlightDials() + defer tr.decInFlightDials() + + if err := tr.initEndpoint(); err != nil { + return nil, err + } + qconn, err := tr.endpoint.Dial(ctx, "udp", target, tr.config) + if err != nil { + return nil, err + } + return tr.newClientConn(ctx, qconn) +} + +// CloseIdleConnections is called by net/http.Transport.CloseIdleConnections +// after all existing idle connections are closed using http3.clientConn.Close. +// +// When the transport has no active connections anymore, calling this method +// will make the transport clean up any shared resources that are no longer +// required, such as its QUIC endpoint. +func (tr *transport) CloseIdleConnections() { + tr.mu.Lock() + defer tr.mu.Unlock() + if tr.endpoint == nil || len(tr.activeConns) > 0 || tr.inFlightDials > 0 { + return + } + tr.endpoint.Close(canceledCtx) + tr.endpoint = nil +} + +// A clientConn is a client HTTP/3 connection. +// +// Multiple goroutines may invoke methods on a clientConn simultaneously. +type clientConn struct { + qconn *quic.Conn + genericConn + + enc qpackEncoder + dec qpackDecoder +} + +func (tr *transport) registerConn(cc *clientConn) { + tr.mu.Lock() + defer tr.mu.Unlock() + tr.activeConns[cc] = struct{}{} +} + +func (tr *transport) unregisterConn(cc *clientConn) { + tr.mu.Lock() + defer tr.mu.Unlock() + delete(tr.activeConns, cc) +} + +func (tr *transport) newClientConn(ctx context.Context, qconn *quic.Conn) (*clientConn, error) { + cc := &clientConn{ + qconn: qconn, + } + tr.registerConn(cc) + cc.enc.init() + + // Create control stream and send SETTINGS frame. + controlStream, err := newConnStream(ctx, cc.qconn, streamTypeControl) + if err != nil { + tr.unregisterConn(cc) + return nil, fmt.Errorf("http3: cannot create control stream: %v", err) + } + controlStream.writeSettings() + controlStream.Flush() + + go func() { + cc.acceptStreams(qconn, cc) + tr.unregisterConn(cc) + }() + return cc, nil +} + +// TODO: implement the rest of net/http.ClientConn methods beyond Close. +func (cc *clientConn) Close() error { + // We need to use Close rather than Abort on the QUIC connection. + // Otherwise, when a net/http.Transport.CloseIdleConnections is called, it + // might call the http3.transport.CloseIdleConnections prior to all idle + // connections being fully closed; this would make it unable to close its + // QUIC endpoint, making http3.transport.CloseIdleConnections a no-op + // unintentionally. + return cc.qconn.Close() +} + +func (cc *clientConn) Err() error { + return nil +} + +func (cc *clientConn) Reserve() error { + return nil +} + +func (cc *clientConn) Release() { +} + +func (cc *clientConn) Available() int { + return 0 +} + +func (cc *clientConn) InFlight() int { + return 0 +} + +func (cc *clientConn) handleControlStream(st *stream) error { + // "A SETTINGS frame MUST be sent as the first frame of each control stream [...]" + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4-2 + if err := st.readSettings(func(settingsType, settingsValue int64) error { + switch settingsType { + case settingsMaxFieldSectionSize: + _ = settingsValue // TODO + case settingsQPACKMaxTableCapacity: + _ = settingsValue // TODO + case settingsQPACKBlockedStreams: + _ = settingsValue // TODO + default: + // Unknown settings types are ignored. + } + return nil + }); err != nil { + return err + } + + for { + ftype, err := st.readFrameHeader() + if err != nil { + return err + } + switch ftype { + case frameTypeCancelPush: + // "If a CANCEL_PUSH frame is received that references a push ID + // greater than currently allowed on the connection, + // this MUST be treated as a connection error of type H3_ID_ERROR." + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.3-7 + return &connectionError{ + code: errH3IDError, + message: "CANCEL_PUSH received when no MAX_PUSH_ID has been sent", + } + case frameTypeGoaway: + // TODO: Wait for requests to complete before closing connection. + return errH3NoError + default: + // Unknown frames are ignored. + if err := st.discardUnknownFrame(ftype); err != nil { + return err + } + } + } +} + +func (cc *clientConn) handleEncoderStream(*stream) error { + // TODO + return nil +} + +func (cc *clientConn) handleDecoderStream(*stream) error { + // TODO + return nil +} + +func (cc *clientConn) handlePushStream(*stream) error { + // "A client MUST treat receipt of a push stream as a connection error + // of type H3_ID_ERROR when no MAX_PUSH_ID frame has been sent [...]" + // https://www.rfc-editor.org/rfc/rfc9114.html#section-4.6-3 + return &connectionError{ + code: errH3IDError, + message: "push stream created when no MAX_PUSH_ID has been sent", + } +} + +func (cc *clientConn) handleRequestStream(st *stream) error { + // "Clients MUST treat receipt of a server-initiated bidirectional + // stream as a connection error of type H3_STREAM_CREATION_ERROR [...]" + // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1-3 + return &connectionError{ + code: errH3StreamCreationError, + message: "server created bidirectional stream", + } +} + +// abort closes the connection with an error. +func (cc *clientConn) abort(err error) { + if e, ok := err.(*connectionError); ok { + cc.qconn.Abort(&quic.ApplicationError{ + Code: uint64(e.code), + Reason: e.message, + }) + } else { + cc.qconn.Abort(err) + } +} diff --git a/src/vendor/golang.org/x/net/internal/http3/varint.go b/src/vendor/golang.org/x/net/internal/http3/varint.go new file mode 100644 index 0000000000..bee3d71fb3 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/http3/varint.go @@ -0,0 +1,23 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http3 + +// sizeVarint returns the size of the variable-length integer encoding of f. +// Copied from internal/quic/quicwire to break dependency that makes bundling +// into std more complicated. +func sizeVarint(v uint64) int { + switch { + case v <= 63: + return 1 + case v <= 16383: + return 2 + case v <= 1073741823: + return 4 + case v <= 4611686018427387903: + return 8 + default: + panic("varint too large") + } +} diff --git a/src/vendor/golang.org/x/net/internal/httpcommon/ascii.go b/src/vendor/golang.org/x/net/internal/httpcommon/ascii.go new file mode 100644 index 0000000000..ed14da5afc --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/httpcommon/ascii.go @@ -0,0 +1,53 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package httpcommon + +import "strings" + +// The HTTP protocols are defined in terms of ASCII, not Unicode. This file +// contains helper functions which may use Unicode-aware functions which would +// otherwise be unsafe and could introduce vulnerabilities if used improperly. + +// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t +// are equal, ASCII-case-insensitively. +func asciiEqualFold(s, t string) bool { + if len(s) != len(t) { + return false + } + for i := 0; i < len(s); i++ { + if lower(s[i]) != lower(t[i]) { + return false + } + } + return true +} + +// lower returns the ASCII lowercase version of b. +func lower(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b +} + +// isASCIIPrint returns whether s is ASCII and printable according to +// https://tools.ietf.org/html/rfc20#section-4.2. +func isASCIIPrint(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] < ' ' || s[i] > '~' { + return false + } + } + return true +} + +// asciiToLower returns the lowercase version of s if s is ASCII and printable, +// and whether or not it was. +func asciiToLower(s string) (lower string, ok bool) { + if !isASCIIPrint(s) { + return "", false + } + return strings.ToLower(s), true +} diff --git a/src/vendor/golang.org/x/net/internal/httpcommon/headermap.go b/src/vendor/golang.org/x/net/internal/httpcommon/headermap.go new file mode 100644 index 0000000000..92483d8e41 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/httpcommon/headermap.go @@ -0,0 +1,115 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package httpcommon + +import ( + "net/textproto" + "sync" +) + +var ( + commonBuildOnce sync.Once + commonLowerHeader map[string]string // Go-Canonical-Case -> lower-case + commonCanonHeader map[string]string // lower-case -> Go-Canonical-Case +) + +func buildCommonHeaderMapsOnce() { + commonBuildOnce.Do(buildCommonHeaderMaps) +} + +func buildCommonHeaderMaps() { + common := []string{ + "accept", + "accept-charset", + "accept-encoding", + "accept-language", + "accept-ranges", + "age", + "access-control-allow-credentials", + "access-control-allow-headers", + "access-control-allow-methods", + "access-control-allow-origin", + "access-control-expose-headers", + "access-control-max-age", + "access-control-request-headers", + "access-control-request-method", + "allow", + "authorization", + "cache-control", + "content-disposition", + "content-encoding", + "content-language", + "content-length", + "content-location", + "content-range", + "content-type", + "cookie", + "date", + "etag", + "expect", + "expires", + "from", + "host", + "if-match", + "if-modified-since", + "if-none-match", + "if-unmodified-since", + "last-modified", + "link", + "location", + "max-forwards", + "origin", + "proxy-authenticate", + "proxy-authorization", + "range", + "referer", + "refresh", + "retry-after", + "server", + "set-cookie", + "strict-transport-security", + "trailer", + "transfer-encoding", + "user-agent", + "vary", + "via", + "www-authenticate", + "x-forwarded-for", + "x-forwarded-proto", + } + commonLowerHeader = make(map[string]string, len(common)) + commonCanonHeader = make(map[string]string, len(common)) + for _, v := range common { + chk := textproto.CanonicalMIMEHeaderKey(v) + commonLowerHeader[chk] = v + commonCanonHeader[v] = chk + } +} + +// LowerHeader returns the lowercase form of a header name, +// used on the wire for HTTP/2 and HTTP/3 requests. +func LowerHeader(v string) (lower string, ascii bool) { + buildCommonHeaderMapsOnce() + if s, ok := commonLowerHeader[v]; ok { + return s, true + } + return asciiToLower(v) +} + +// CanonicalHeader canonicalizes a header name. (For example, "host" becomes "Host".) +func CanonicalHeader(v string) string { + buildCommonHeaderMapsOnce() + if s, ok := commonCanonHeader[v]; ok { + return s + } + return textproto.CanonicalMIMEHeaderKey(v) +} + +// CachedCanonicalHeader returns the canonical form of a well-known header name. +func CachedCanonicalHeader(v string) (string, bool) { + buildCommonHeaderMapsOnce() + s, ok := commonCanonHeader[v] + return s, ok +} diff --git a/src/vendor/golang.org/x/net/internal/httpcommon/request.go b/src/vendor/golang.org/x/net/internal/httpcommon/request.go new file mode 100644 index 0000000000..1e10f89ebf --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/httpcommon/request.go @@ -0,0 +1,467 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package httpcommon + +import ( + "context" + "errors" + "fmt" + "net/http/httptrace" + "net/textproto" + "net/url" + "sort" + "strconv" + "strings" + + "golang.org/x/net/http/httpguts" + "golang.org/x/net/http2/hpack" +) + +var ( + ErrRequestHeaderListSize = errors.New("request header list larger than peer's advertised limit") +) + +// Request is a subset of http.Request. +// It'd be simpler to pass an *http.Request, of course, but we can't depend on net/http +// without creating a dependency cycle. +type Request struct { + URL *url.URL + Method string + Host string + Header map[string][]string + Trailer map[string][]string + ActualContentLength int64 // 0 means 0, -1 means unknown +} + +// EncodeHeadersParam is parameters to EncodeHeaders. +type EncodeHeadersParam struct { + Request Request + + // AddGzipHeader indicates that an "accept-encoding: gzip" header should be + // added to the request. + AddGzipHeader bool + + // PeerMaxHeaderListSize, when non-zero, is the peer's MAX_HEADER_LIST_SIZE setting. + PeerMaxHeaderListSize uint64 + + // DefaultUserAgent is the User-Agent header to send when the request + // neither contains a User-Agent nor disables it. + DefaultUserAgent string +} + +// EncodeHeadersResult is the result of EncodeHeaders. +type EncodeHeadersResult struct { + HasBody bool + HasTrailers bool +} + +// EncodeHeaders constructs request headers common to HTTP/2 and HTTP/3. +// It validates a request and calls headerf with each pseudo-header and header +// for the request. +// The headerf function is called with the validated, canonicalized header name. +func EncodeHeaders(ctx context.Context, param EncodeHeadersParam, headerf func(name, value string)) (res EncodeHeadersResult, _ error) { + req := param.Request + + // Check for invalid connection-level headers. + if err := checkConnHeaders(req.Header); err != nil { + return res, err + } + + if req.URL == nil { + return res, errors.New("Request.URL is nil") + } + + host := req.Host + if host == "" { + host = req.URL.Host + } + host, err := httpguts.PunycodeHostPort(host) + if err != nil { + return res, err + } + if !httpguts.ValidHostHeader(host) { + return res, errors.New("invalid Host header") + } + + // isNormalConnect is true if this is a non-extended CONNECT request. + isNormalConnect := false + var protocol string + if vv := req.Header[":protocol"]; len(vv) > 0 { + protocol = vv[0] + } + if req.Method == "CONNECT" && protocol == "" { + isNormalConnect = true + } else if protocol != "" && req.Method != "CONNECT" { + return res, errors.New("invalid :protocol header in non-CONNECT request") + } + + // Validate the path, except for non-extended CONNECT requests which have no path. + var path string + if !isNormalConnect { + path = req.URL.RequestURI() + if !validPseudoPath(path) { + orig := path + path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host) + if !validPseudoPath(path) { + if req.URL.Opaque != "" { + return res, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque) + } else { + return res, fmt.Errorf("invalid request :path %q", orig) + } + } + } + } + + // Check for any invalid headers+trailers and return an error before we + // potentially pollute our hpack state. (We want to be able to + // continue to reuse the hpack encoder for future requests) + if err := validateHeaders(req.Header); err != "" { + return res, fmt.Errorf("invalid HTTP header %s", err) + } + if err := validateHeaders(req.Trailer); err != "" { + return res, fmt.Errorf("invalid HTTP trailer %s", err) + } + + trailers, err := commaSeparatedTrailers(req.Trailer) + if err != nil { + return res, err + } + + enumerateHeaders := func(f func(name, value string)) { + // 8.1.2.3 Request Pseudo-Header Fields + // The :path pseudo-header field includes the path and query parts of the + // target URI (the path-absolute production and optionally a '?' character + // followed by the query production, see Sections 3.3 and 3.4 of + // [RFC3986]). + f(":authority", host) + m := req.Method + if m == "" { + m = "GET" + } + f(":method", m) + if !isNormalConnect { + f(":path", path) + f(":scheme", req.URL.Scheme) + } + if protocol != "" { + f(":protocol", protocol) + } + if trailers != "" { + f("trailer", trailers) + } + + var didUA bool + for k, vv := range req.Header { + if asciiEqualFold(k, "host") || asciiEqualFold(k, "content-length") { + // Host is :authority, already sent. + // Content-Length is automatic, set below. + continue + } else if asciiEqualFold(k, "connection") || + asciiEqualFold(k, "proxy-connection") || + asciiEqualFold(k, "transfer-encoding") || + asciiEqualFold(k, "upgrade") || + asciiEqualFold(k, "keep-alive") { + // Per 8.1.2.2 Connection-Specific Header + // Fields, don't send connection-specific + // fields. We have already checked if any + // are error-worthy so just ignore the rest. + continue + } else if asciiEqualFold(k, "user-agent") { + // Match Go's http1 behavior: at most one + // User-Agent. If set to nil or empty string, + // then omit it. Otherwise if not mentioned, + // include the default (below). + didUA = true + if len(vv) < 1 { + continue + } + vv = vv[:1] + if vv[0] == "" { + continue + } + } else if asciiEqualFold(k, "cookie") { + // Per 8.1.2.5 To allow for better compression efficiency, the + // Cookie header field MAY be split into separate header fields, + // each with one or more cookie-pairs. + for _, v := range vv { + for { + p := strings.IndexByte(v, ';') + if p < 0 { + break + } + f("cookie", v[:p]) + p++ + // strip space after semicolon if any. + for p+1 <= len(v) && v[p] == ' ' { + p++ + } + v = v[p:] + } + if len(v) > 0 { + f("cookie", v) + } + } + continue + } else if k == ":protocol" { + // :protocol pseudo-header was already sent above. + continue + } + + for _, v := range vv { + f(k, v) + } + } + if shouldSendReqContentLength(req.Method, req.ActualContentLength) { + f("content-length", strconv.FormatInt(req.ActualContentLength, 10)) + } + if param.AddGzipHeader { + f("accept-encoding", "gzip") + } + if !didUA { + f("user-agent", param.DefaultUserAgent) + } + } + + // Do a first pass over the headers counting bytes to ensure + // we don't exceed cc.peerMaxHeaderListSize. This is done as a + // separate pass before encoding the headers to prevent + // modifying the hpack state. + if param.PeerMaxHeaderListSize > 0 { + hlSize := uint64(0) + enumerateHeaders(func(name, value string) { + hf := hpack.HeaderField{Name: name, Value: value} + hlSize += uint64(hf.Size()) + }) + + if hlSize > param.PeerMaxHeaderListSize { + return res, ErrRequestHeaderListSize + } + } + + trace := httptrace.ContextClientTrace(ctx) + + // Header list size is ok. Write the headers. + enumerateHeaders(func(name, value string) { + name, ascii := LowerHeader(name) + if !ascii { + // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header + // field names have to be ASCII characters (just as in HTTP/1.x). + return + } + + headerf(name, value) + + if trace != nil && trace.WroteHeaderField != nil { + trace.WroteHeaderField(name, []string{value}) + } + }) + + res.HasBody = req.ActualContentLength != 0 + res.HasTrailers = trailers != "" + return res, nil +} + +// IsRequestGzip reports whether we should add an Accept-Encoding: gzip header +// for a request. +func IsRequestGzip(method string, header map[string][]string, disableCompression bool) bool { + // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere? + if !disableCompression && + len(header["Accept-Encoding"]) == 0 && + len(header["Range"]) == 0 && + method != "HEAD" { + // Request gzip only, not deflate. Deflate is ambiguous and + // not as universally supported anyway. + // See: https://zlib.net/zlib_faq.html#faq39 + // + // Note that we don't request this for HEAD requests, + // due to a bug in nginx: + // http://trac.nginx.org/nginx/ticket/358 + // https://golang.org/issue/5522 + // + // We don't request gzip if the request is for a range, since + // auto-decoding a portion of a gzipped document will just fail + // anyway. See https://golang.org/issue/8923 + return true + } + return false +} + +// checkConnHeaders checks whether req has any invalid connection-level headers. +// +// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2-3 +// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.2-1 +// +// Certain headers are special-cased as okay but not transmitted later. +// For example, we allow "Transfer-Encoding: chunked", but drop the header when encoding. +func checkConnHeaders(h map[string][]string) error { + if vv := h["Upgrade"]; len(vv) > 0 && (vv[0] != "" && vv[0] != "chunked") { + return fmt.Errorf("invalid Upgrade request header: %q", vv) + } + if vv := h["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") { + return fmt.Errorf("invalid Transfer-Encoding request header: %q", vv) + } + if vv := h["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) { + return fmt.Errorf("invalid Connection request header: %q", vv) + } + return nil +} + +func commaSeparatedTrailers(trailer map[string][]string) (string, error) { + keys := make([]string, 0, len(trailer)) + for k := range trailer { + k = CanonicalHeader(k) + switch k { + case "Transfer-Encoding", "Trailer", "Content-Length": + return "", fmt.Errorf("invalid Trailer key %q", k) + } + keys = append(keys, k) + } + if len(keys) > 0 { + sort.Strings(keys) + return strings.Join(keys, ","), nil + } + return "", nil +} + +// validPseudoPath reports whether v is a valid :path pseudo-header +// value. It must be either: +// +// - a non-empty string starting with '/' +// - the string '*', for OPTIONS requests. +// +// For now this is only used a quick check for deciding when to clean +// up Opaque URLs before sending requests from the Transport. +// See golang.org/issue/16847 +// +// We used to enforce that the path also didn't start with "//", but +// Google's GFE accepts such paths and Chrome sends them, so ignore +// that part of the spec. See golang.org/issue/19103. +func validPseudoPath(v string) bool { + return (len(v) > 0 && v[0] == '/') || v == "*" +} + +func validateHeaders(hdrs map[string][]string) string { + for k, vv := range hdrs { + if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" { + return fmt.Sprintf("name %q", k) + } + for _, v := range vv { + if !httpguts.ValidHeaderFieldValue(v) { + // Don't include the value in the error, + // because it may be sensitive. + return fmt.Sprintf("value for header %q", k) + } + } + } + return "" +} + +// shouldSendReqContentLength reports whether we should send +// a "content-length" request header. This logic is basically a copy of the net/http +// transferWriter.shouldSendContentLength. +// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown). +// -1 means unknown. +func shouldSendReqContentLength(method string, contentLength int64) bool { + if contentLength > 0 { + return true + } + if contentLength < 0 { + return false + } + // For zero bodies, whether we send a content-length depends on the method. + // It also kinda doesn't matter for http2 either way, with END_STREAM. + switch method { + case "POST", "PUT", "PATCH": + return true + default: + return false + } +} + +// ServerRequestParam is parameters to NewServerRequest. +type ServerRequestParam struct { + Method string + Scheme, Authority, Path string + Protocol string + Header map[string][]string +} + +// ServerRequestResult is the result of NewServerRequest. +type ServerRequestResult struct { + // Various http.Request fields. + URL *url.URL + RequestURI string + Trailer map[string][]string + + NeedsContinue bool // client provided an "Expect: 100-continue" header + + // If the request should be rejected, this is a short string suitable for passing + // to the http2 package's CountError function. + // It might be a bit odd to return errors this way rather than returning an error, + // but this ensures we don't forget to include a CountError reason. + InvalidReason string +} + +func NewServerRequest(rp ServerRequestParam) ServerRequestResult { + needsContinue := httpguts.HeaderValuesContainsToken(rp.Header["Expect"], "100-continue") + if needsContinue { + delete(rp.Header, "Expect") + } + // Merge Cookie headers into one "; "-delimited value. + if cookies := rp.Header["Cookie"]; len(cookies) > 1 { + rp.Header["Cookie"] = []string{strings.Join(cookies, "; ")} + } + + // Setup Trailers + var trailer map[string][]string + for _, v := range rp.Header["Trailer"] { + for _, key := range strings.Split(v, ",") { + key = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(key)) + switch key { + case "Transfer-Encoding", "Trailer", "Content-Length": + // Bogus. (copy of http1 rules) + // Ignore. + default: + if trailer == nil { + trailer = make(map[string][]string) + } + trailer[key] = nil + } + } + } + delete(rp.Header, "Trailer") + + // "':authority' MUST NOT include the deprecated userinfo subcomponent + // for "http" or "https" schemed URIs." + // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8 + if strings.IndexByte(rp.Authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") { + return ServerRequestResult{ + InvalidReason: "userinfo_in_authority", + } + } + + var url_ *url.URL + var requestURI string + if rp.Method == "CONNECT" && rp.Protocol == "" { + url_ = &url.URL{Host: rp.Authority} + requestURI = rp.Authority // mimic HTTP/1 server behavior + } else { + var err error + url_, err = url.ParseRequestURI(rp.Path) + if err != nil { + return ServerRequestResult{ + InvalidReason: "bad_path", + } + } + requestURI = rp.Path + } + + return ServerRequestResult{ + URL: url_, + NeedsContinue: needsContinue, + RequestURI: requestURI, + Trailer: trailer, + } +} diff --git a/src/vendor/golang.org/x/net/internal/quic/quicwire/wire.go b/src/vendor/golang.org/x/net/internal/quic/quicwire/wire.go new file mode 100644 index 0000000000..1a06a22519 --- /dev/null +++ b/src/vendor/golang.org/x/net/internal/quic/quicwire/wire.go @@ -0,0 +1,150 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package quicwire encodes and decode QUIC/HTTP3 wire encoding types, +// particularly variable-length integers. +package quicwire + +import "encoding/binary" + +const ( + MaxVarintSize = 8 // encoded size in bytes + MaxVarint = (1 << 62) - 1 +) + +// ConsumeVarint parses a variable-length integer, reporting its length. +// It returns a negative length upon an error. +// +// https://www.rfc-editor.org/rfc/rfc9000.html#section-16 +func ConsumeVarint(b []byte) (v uint64, n int) { + if len(b) < 1 { + return 0, -1 + } + b0 := b[0] & 0x3f + switch b[0] >> 6 { + case 0: + return uint64(b0), 1 + case 1: + if len(b) < 2 { + return 0, -1 + } + return uint64(b0)<<8 | uint64(b[1]), 2 + case 2: + if len(b) < 4 { + return 0, -1 + } + return uint64(b0)<<24 | uint64(b[1])<<16 | uint64(b[2])<<8 | uint64(b[3]), 4 + case 3: + if len(b) < 8 { + return 0, -1 + } + return uint64(b0)<<56 | uint64(b[1])<<48 | uint64(b[2])<<40 | uint64(b[3])<<32 | uint64(b[4])<<24 | uint64(b[5])<<16 | uint64(b[6])<<8 | uint64(b[7]), 8 + } + return 0, -1 +} + +// ConsumeVarintInt64 parses a variable-length integer as an int64. +func ConsumeVarintInt64(b []byte) (v int64, n int) { + u, n := ConsumeVarint(b) + // QUIC varints are 62-bits large, so this conversion can never overflow. + return int64(u), n +} + +// AppendVarint appends a variable-length integer to b. +// +// https://www.rfc-editor.org/rfc/rfc9000.html#section-16 +func AppendVarint(b []byte, v uint64) []byte { + switch { + case v <= 63: + return append(b, byte(v)) + case v <= 16383: + return append(b, (1<<6)|byte(v>>8), byte(v)) + case v <= 1073741823: + return append(b, (2<<6)|byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) + case v <= 4611686018427387903: + return append(b, (3<<6)|byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) + default: + panic("varint too large") + } +} + +// SizeVarint returns the size of the variable-length integer encoding of f. +func SizeVarint(v uint64) int { + switch { + case v <= 63: + return 1 + case v <= 16383: + return 2 + case v <= 1073741823: + return 4 + case v <= 4611686018427387903: + return 8 + default: + panic("varint too large") + } +} + +// ConsumeUint32 parses a 32-bit fixed-length, big-endian integer, reporting its length. +// It returns a negative length upon an error. +func ConsumeUint32(b []byte) (uint32, int) { + if len(b) < 4 { + return 0, -1 + } + return binary.BigEndian.Uint32(b), 4 +} + +// ConsumeUint64 parses a 64-bit fixed-length, big-endian integer, reporting its length. +// It returns a negative length upon an error. +func ConsumeUint64(b []byte) (uint64, int) { + if len(b) < 8 { + return 0, -1 + } + return binary.BigEndian.Uint64(b), 8 +} + +// ConsumeUint8Bytes parses a sequence of bytes prefixed with an 8-bit length, +// reporting the total number of bytes consumed. +// It returns a negative length upon an error. +func ConsumeUint8Bytes(b []byte) ([]byte, int) { + if len(b) < 1 { + return nil, -1 + } + size := int(b[0]) + const n = 1 + if size > len(b[n:]) { + return nil, -1 + } + return b[n:][:size], size + n +} + +// AppendUint8Bytes appends a sequence of bytes prefixed by an 8-bit length. +func AppendUint8Bytes(b, v []byte) []byte { + if len(v) > 0xff { + panic("uint8-prefixed bytes too large") + } + b = append(b, uint8(len(v))) + b = append(b, v...) + return b +} + +// ConsumeVarintBytes parses a sequence of bytes preceded by a variable-length integer length, +// reporting the total number of bytes consumed. +// It returns a negative length upon an error. +func ConsumeVarintBytes(b []byte) ([]byte, int) { + size, n := ConsumeVarint(b) + if n < 0 { + return nil, -1 + } + if size > uint64(len(b[n:])) { + return nil, -1 + } + return b[n:][:size], int(size) + n +} + +// AppendVarintBytes appends a sequence of bytes prefixed by a variable-length integer length. +func AppendVarintBytes(b, v []byte) []byte { + b = AppendVarint(b, uint64(len(v))) + b = append(b, v...) + return b +} |
