Upgrade net package from Go 1.21.4 to Go 1.26.2

Backport upstream Go standard library changes to TinyGo's net package.

Unmodified files (18) replaced directly from Go 1.26.2 source.
Modified files (22) merged via 3-way diff, preserving all TinyGo
netdev adaptations, TINYGO markers, and embedded-device constraints.

Notable upstream changes included:
- net/http: cookie handling improvements, fs enhancements,
  reverse proxy updates, chunked encoding fixes
- net/http: ServeMux routing and pattern matching updates
- net: IP parsing and MAC address handling improvements
- Various doc link syntax modernization (e.g. [Dial], [Buffers])

TinyGo-only files (netdev.go, tlssock.go) unchanged.
All TINYGO comment markers preserved.
This commit is contained in:
deadprogram
2026-04-13 17:36:35 +02:00
committed by Ron Evans
parent a3417d034f
commit 35e809e0e5
46 changed files with 1580 additions and 779 deletions
+58 -42
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied and modified from Go 1.21.4 official implementation.
// TINYGO: The following is copied and modified from Go 1.26.2 official implementation.
// TINYGO: Removed trace stuff
// TINYGO: Hook readTransfer is onEOF callback to get notified when request of
@@ -15,12 +15,14 @@ import (
"bytes"
"errors"
"fmt"
"internal/godebug"
"io"
"maps"
"net/http/internal"
"net/http/internal/ascii"
"net/textproto"
"reflect"
"sort"
"slices"
"strconv"
"strings"
"sync"
@@ -313,7 +315,7 @@ func (t *transferWriter) writeHeader(w io.Writer) error {
keys = append(keys, k)
}
if len(keys) > 0 {
sort.Strings(keys)
slices.Sort(keys)
// TODO: could do better allocation-wise here, but trailers are rare,
// so being lazy for now.
if _, err := io.WriteString(w, "Trailer: "+strings.Join(keys, ",")+"\r\n"); err != nil {
@@ -342,7 +344,7 @@ func (t *transferWriter) writeBody(w io.Writer) (err error) {
// nopCloser or readTrackingBody. This is to ensure that we can take advantage of
// OS-level optimizations in the event that the body is an
// *os.File.
if t.Body != nil {
if !t.ResponseToHEAD && t.Body != nil {
var body = t.unwrapBody()
if chunked(t.TransferEncoding) {
if bw, ok := w.(*bufio.Writer); ok && !t.IsResponse {
@@ -384,7 +386,7 @@ func (t *transferWriter) writeBody(w io.Writer) (err error) {
t.ContentLength, ncopy)
}
if chunked(t.TransferEncoding) {
if !t.ResponseToHEAD && chunked(t.TransferEncoding) {
// Write Trailer header
if t.Trailer != nil {
if err := t.Trailer.Write(w); err != nil {
@@ -402,7 +404,10 @@ func (t *transferWriter) writeBody(w io.Writer) (err error) {
//
// This function is only intended for use in writeBody.
func (t *transferWriter) doBodyCopy(dst io.Writer, src io.Reader) (n int64, err error) {
n, err = io.Copy(dst, src)
buf := getCopyBuf()
defer putCopyBuf(buf)
n, err = io.CopyBuffer(dst, src, buf)
if err != nil && err != io.EOF {
t.bodyReadError = err
}
@@ -523,7 +528,7 @@ func readTransfer(msg any, r *bufio.Reader, onEOF func()) (err error) {
return err
}
if isResponse && t.RequestMethod == "HEAD" {
if n, err := parseContentLength(t.Header.get("Content-Length")); err != nil {
if n, err := parseContentLength(t.Header["Content-Length"]); err != nil {
return err
} else {
t.ContentLength = n
@@ -642,19 +647,6 @@ func (t *transferReader) parseTransferEncoding() error {
return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", raw[0])}
}
// RFC 7230 3.3.2 says "A sender MUST NOT send a Content-Length header field
// in any message that contains a Transfer-Encoding header field."
//
// but also: "If a message is received with both a Transfer-Encoding and a
// Content-Length header field, the Transfer-Encoding overrides the
// Content-Length. Such a message might indicate an attempt to perform
// request smuggling (Section 9.5) or response splitting (Section 9.4) and
// ought to be handled as an error. A sender MUST remove the received
// Content-Length field prior to forwarding such a message downstream."
//
// Reportedly, these appear in the wild.
delete(t.Header, "Content-Length")
t.Chunked = true
return nil
}
@@ -662,7 +654,7 @@ func (t *transferReader) parseTransferEncoding() error {
// Determine the expected body length, using RFC 7230 Section 3.3. This
// function is not a method, because ultimately it should be shared by
// ReadResponse and ReadRequest.
func fixLength(isResponse bool, status int, requestMethod string, header Header, chunked bool) (int64, error) {
func fixLength(isResponse bool, status int, requestMethod string, header Header, chunked bool) (n int64, err error) {
isRequest := !isResponse
contentLens := header["Content-Length"]
@@ -686,6 +678,14 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
contentLens = header["Content-Length"]
}
// Reject requests with invalid Content-Length headers.
if len(contentLens) > 0 {
n, err = parseContentLength(contentLens)
if err != nil {
return -1, err
}
}
// Logic based on response type or status
if isResponse && noResponseBodyExpected(requestMethod) {
return 0, nil
@@ -698,23 +698,29 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
return 0, nil
}
// According to RFC 9112, "If a message is received with both a
// Transfer-Encoding and a Content-Length header field, the Transfer-Encoding
// overrides the Content-Length. Such a message might indicate an attempt to
// perform request smuggling (Section 11.2) or response splitting (Section 11.1)
// and ought to be handled as an error. An intermediary that chooses to forward
// the message MUST first remove the received Content-Length field and process
// the Transfer-Encoding (as described below) prior to forwarding the message downstream."
//
// Chunked-encoding requests with either valid Content-Length
// headers or no Content-Length headers are accepted after removing
// the Content-Length field from header.
//
// Logic based on Transfer-Encoding
if chunked {
header.Del("Content-Length")
return -1, nil
}
// Logic based on Content-Length
var cl string
if len(contentLens) == 1 {
cl = textproto.TrimString(contentLens[0])
}
if cl != "" {
n, err := parseContentLength(cl)
if err != nil {
return -1, err
}
if len(contentLens) > 0 {
return n, nil
}
header.Del("Content-Length")
if isRequest {
@@ -812,10 +818,10 @@ type body struct {
onHitEOF func() // if non-nil, func to call when EOF is Read
}
// ErrBodyReadAfterClose is returned when reading a Request or Response
// ErrBodyReadAfterClose is returned when reading a [Request] or [Response]
// Body after the body has been closed. This typically happens when the body is
// read after an HTTP Handler calls WriteHeader or Write on its
// ResponseWriter.
// read after an HTTP [Handler] calls WriteHeader or Write on its
// [ResponseWriter].
var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body")
func (b *body) Read(p []byte) (n int, err error) {
@@ -945,9 +951,7 @@ func mergeSetHeader(dst *Header, src Header) {
*dst = src
return
}
for k, vv := range src {
(*dst)[k] = vv
}
maps.Copy(*dst, src)
}
// unreadDataSizeLocked returns the number of bytes of unread input.
@@ -1034,19 +1038,31 @@ func (bl bodyLocked) Read(p []byte) (n int, err error) {
return bl.b.readLocked(p)
}
// parseContentLength trims whitespace from s and returns -1 if no value
// is set, or the value if it's >= 0.
func parseContentLength(cl string) (int64, error) {
cl = textproto.TrimString(cl)
if cl == "" {
var httplaxcontentlength = godebug.New("httplaxcontentlength")
// parseContentLength checks that the header is valid and then trims
// whitespace. It returns -1 if no value is set otherwise the value
// if it's >= 0.
func parseContentLength(clHeaders []string) (int64, error) {
if len(clHeaders) == 0 {
return -1, nil
}
cl := textproto.TrimString(clHeaders[0])
// The Content-Length must be a valid numeric value.
// See: https://datatracker.ietf.org/doc/html/rfc2616/#section-14.13
if cl == "" {
if httplaxcontentlength.Value() == "1" {
httplaxcontentlength.IncNonDefault()
return -1, nil
}
return 0, badStringError("invalid empty Content-Length", cl)
}
n, err := strconv.ParseUint(cl, 10, 63)
if err != nil {
return 0, badStringError("bad Content-Length", cl)
}
return int64(n), nil
}
// finishAsyncByteRead finishes reading the 1-byte sniff