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
+1 -1
View File
@@ -19,7 +19,7 @@ packages in a TinyGo application.
## "net" Package ## "net" Package
The "net" package is ported from Go 1.21.4. The tree listings below shows the The "net" package is ported from Go 1.26.2. The tree listings below shows the
files copied. If the file is marked with an '\*', it is copied _and_ modified files copied. If the file is marked with an '\*', it is copied _and_ modified
to work with netdev. If the file is marked with an '+', the file is new. If to work with netdev. If the file is marked with an '+', the file is new. If
there is no mark, it is a straight copy. there is no mark, it is a straight copy.
+49 -14
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: Omit DualStack support // TINYGO: Omit DualStack support
// TINYGO: Omit Fast Fallback support // TINYGO: Omit Fast Fallback support
@@ -30,11 +30,14 @@ const (
// mptcpStatus is a tristate for Multipath TCP, see go.dev/issue/56539 // mptcpStatus is a tristate for Multipath TCP, see go.dev/issue/56539
type mptcpStatus uint8 type mptcpStatus uint8
// TINYGO: mptcpStatusListen stub for ListenConfig compatibility
type mptcpStatusListen uint8
// A Dialer contains options for connecting to an address. // A Dialer contains options for connecting to an address.
// //
// The zero value for each field is equivalent to dialing // The zero value for each field is equivalent to dialing
// without that option. Dialing with the zero value of Dialer // without that option. Dialing with the zero value of Dialer
// is therefore equivalent to just calling the Dial function. // is therefore equivalent to just calling the [Dial] function.
// //
// It is safe to call Dialer's methods concurrently. // It is safe to call Dialer's methods concurrently.
type Dialer struct { type Dialer struct {
@@ -66,12 +69,24 @@ type Dialer struct {
// KeepAlive specifies the interval between keep-alive // KeepAlive specifies the interval between keep-alive
// probes for an active network connection. // probes for an active network connection.
//
// KeepAlive is ignored if KeepAliveConfig.Enable is true.
//
// If zero, keep-alive probes are sent with a default value // If zero, keep-alive probes are sent with a default value
// (currently 15 seconds), if supported by the protocol and operating // (currently 15 seconds), if supported by the protocol and operating
// system. Network protocols or operating systems that do // system. Network protocols or operating systems that do
// not support keep-alives ignore this field. // not support keep-alive ignore this field.
// If negative, keep-alive probes are disabled. // If negative, keep-alive probes are disabled.
KeepAlive time.Duration KeepAlive time.Duration
// KeepAliveConfig specifies the keep-alive probe configuration
// for an active network connection, when supported by the
// protocol and operating system.
//
// If KeepAliveConfig.Enable is true, keep-alive probes are enabled.
// If KeepAliveConfig.Enable is false and KeepAlive is negative,
// keep-alive probes are disabled.
KeepAliveConfig KeepAliveConfig
} }
// Dial connects to the address on the named network. // Dial connects to the address on the named network.
@@ -86,7 +101,7 @@ func Dial(network, address string) (Conn, error) {
return d.Dial(network, address) return d.Dial(network, address)
} }
// DialTimeout acts like Dial but takes a timeout. // DialTimeout acts like [Dial] but takes a timeout.
// //
// The timeout includes name resolution, if required. // The timeout includes name resolution, if required.
// When using TCP, and the host in the address parameter resolves to // When using TCP, and the host in the address parameter resolves to
@@ -106,8 +121,8 @@ func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
// See func Dial for a description of the network and address // See func Dial for a description of the network and address
// parameters. // parameters.
// //
// Dial uses context.Background internally; to specify the context, use // Dial uses [context.Background] internally; to specify the context, use
// DialContext. // [Dialer.DialContext].
func (d *Dialer) Dial(network, address string) (Conn, error) { func (d *Dialer) Dial(network, address string) (Conn, error) {
return d.DialContext(context.Background(), network, address) return d.DialContext(context.Background(), network, address)
} }
@@ -128,7 +143,7 @@ func (d *Dialer) Dial(network, address string) (Conn, error) {
// the connect to each single address will be given 15 seconds to complete // the connect to each single address will be given 15 seconds to complete
// before trying the next one. // before trying the next one.
// //
// See func Dial for a description of the network and address // See func [Dial] for a description of the network and address
// parameters. // parameters.
func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) { func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {
@@ -157,29 +172,46 @@ type ListenConfig struct {
// If Control is not nil, it is called after creating the network // If Control is not nil, it is called after creating the network
// connection but before binding it to the operating system. // connection but before binding it to the operating system.
// //
// Network and address parameters passed to Control method are not // Network and address parameters passed to Control function are not
// necessarily the ones passed to Listen. For example, passing "tcp" to // necessarily the ones passed to Listen. Calling Listen with TCP networks
// Listen will cause the Control function to be called with "tcp4" or "tcp6". // will cause the Control function to be called with "tcp4" or "tcp6",
// UDP networks become "udp4" or "udp6", IP networks become "ip4" or "ip6",
// and other known networks are passed as-is.
Control func(network, address string, c syscall.RawConn) error Control func(network, address string, c syscall.RawConn) error
// KeepAlive specifies the keep-alive period for network // KeepAlive specifies the keep-alive period for network
// connections accepted by this listener. // connections accepted by this listener.
// If zero, keep-alives are enabled if supported by the protocol //
// KeepAlive is ignored if KeepAliveConfig.Enable is true.
//
// If zero, keep-alive are enabled if supported by the protocol
// and operating system. Network protocols or operating systems // and operating system. Network protocols or operating systems
// that do not support keep-alives ignore this field. // that do not support keep-alive ignore this field.
// If negative, keep-alives are disabled. // If negative, keep-alive are disabled.
KeepAlive time.Duration KeepAlive time.Duration
// KeepAliveConfig specifies the keep-alive probe configuration
// for an active network connection, when supported by the
// protocol and operating system.
//
// If KeepAliveConfig.Enable is true, keep-alive probes are enabled.
// If KeepAliveConfig.Enable is false and KeepAlive is negative,
// keep-alive probes are disabled.
KeepAliveConfig KeepAliveConfig
// If mptcpStatus is set to a value allowing Multipath TCP (MPTCP) to be // If mptcpStatus is set to a value allowing Multipath TCP (MPTCP) to be
// used, any call to Listen with "tcp(4|6)" as network will use MPTCP if // used, any call to Listen with "tcp(4|6)" as network will use MPTCP if
// supported by the operating system. // supported by the operating system.
mptcpStatus mptcpStatus mptcpStatus mptcpStatusListen
} }
// Listen announces on the local network address. // Listen announces on the local network address.
// //
// See func Listen for a description of the network and address // See func Listen for a description of the network and address
// parameters. // parameters.
//
// The ctx argument is used while resolving the address on which to listen;
// it does not affect the returned Listener.
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) { func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
return nil, errors.New("dial:ListenConfig:Listen not implemented") return nil, errors.New("dial:ListenConfig:Listen not implemented")
} }
@@ -188,6 +220,9 @@ func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Li
// //
// See func ListenPacket for a description of the network and address // See func ListenPacket for a description of the network and address
// parameters. // parameters.
//
// The ctx argument is used while resolving the address on which to listen;
// it does not affect the returned PacketConn.
func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (PacketConn, error) { func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (PacketConn, error) {
return nil, errors.New("dial:ListenConfig:ListenPacket not implemented") return nil, errors.New("dial:ListenConfig:ListenPacket not implemented")
} }
+66 -60
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.
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -27,34 +27,33 @@ import (
"golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpguts"
) )
// A Client is an HTTP client. Its zero value (DefaultClient) is a // A Client is an HTTP client. Its zero value ([DefaultClient]) is a
// usable client that uses DefaultTransport. // usable client that uses [DefaultTransport].
// //
// The Client's Transport typically has internal state (cached TCP // The [Client.Transport] typically has internal state (cached TCP
// connections), so Clients should be reused instead of created as // connections), so Clients should be reused instead of created as
// needed. Clients are safe for concurrent use by multiple goroutines. // needed. Clients are safe for concurrent use by multiple goroutines.
// //
// A Client is higher-level than a RoundTripper (such as Transport) // A Client is higher-level than a [RoundTripper] (such as [Transport])
// and additionally handles HTTP details such as cookies and // and additionally handles HTTP details such as cookies and
// redirects. // redirects.
// //
// When following redirects, the Client will forward all headers set on the // When following redirects, the Client will forward all headers set on the
// initial Request except: // initial [Request] except:
// //
// when forwarding sensitive headers like "Authorization", // - when forwarding sensitive headers like "Authorization",
// "WWW-Authenticate", and "Cookie" to untrusted targets. // "WWW-Authenticate", and "Cookie" to untrusted targets.
// These headers will be ignored when following a redirect to a domain // These headers will be ignored when following a redirect to a domain
// that is not a subdomain match or exact match of the initial domain. // that is not a subdomain match or exact match of the initial domain.
// For example, a redirect from "foo.com" to either "foo.com" or "sub.foo.com" // For example, a redirect from "foo.com" to either "foo.com" or "sub.foo.com"
// will forward the sensitive headers, but a redirect to "bar.com" will not. // will forward the sensitive headers, but a redirect to "bar.com" will not.
// // - when forwarding the "Cookie" header with a non-nil cookie Jar.
// • when forwarding the "Cookie" header with a non-nil cookie Jar. // Since each redirect may mutate the state of the cookie jar,
// Since each redirect may mutate the state of the cookie jar, // a redirect may possibly alter a cookie set in the initial request.
// a redirect may possibly alter a cookie set in the initial request. // When forwarding the "Cookie" header, any mutated cookies will be omitted,
// When forwarding the "Cookie" header, any mutated cookies will be omitted, // with the expectation that the Jar will insert those mutated cookies
// with the expectation that the Jar will insert those mutated cookies // with the updated values (assuming the origin matches).
// with the updated values (assuming the origin matches). // If Jar is nil, the initial cookies are forwarded without change.
// If Jar is nil, the initial cookies are forwarded without change.
type Client struct { type Client struct {
// Transport specifies the mechanism by which individual // Transport specifies the mechanism by which individual
// HTTP requests are made. // HTTP requests are made.
@@ -106,11 +105,11 @@ type Client struct {
Timeout time.Duration Timeout time.Duration
} }
// DefaultClient is the default Client and is used by Get, Head, and Post. // DefaultClient is the default [Client] and is used by [Get], [Head], and [Post].
var DefaultClient = &Client{} var DefaultClient = &Client{}
// RoundTripper is an interface representing the ability to execute a // RoundTripper is an interface representing the ability to execute a
// single HTTP transaction, obtaining the Response for a given Request. // single HTTP transaction, obtaining the [Response] for a given [Request].
// //
// A RoundTripper must be safe for concurrent use by multiple // A RoundTripper must be safe for concurrent use by multiple
// goroutines. // goroutines.
@@ -144,8 +143,13 @@ type RoundTripper interface {
// didTimeout is non-nil only if err != nil. // didTimeout is non-nil only if err != nil.
func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) { func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
cookieURL := req.URL
if req.Host != "" {
cookieURL = cloneURL(cookieURL)
cookieURL.Host = req.Host
}
if c.Jar != nil { if c.Jar != nil {
for _, cookie := range c.Jar.Cookies(req.URL) { for _, cookie := range c.Jar.Cookies(cookieURL) {
req.AddCookie(cookie) req.AddCookie(cookie)
} }
} }
@@ -155,7 +159,7 @@ func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTime
} }
if c.Jar != nil { if c.Jar != nil {
if rc := resp.Cookies(); len(rc) > 0 { if rc := resp.Cookies(); len(rc) > 0 {
c.Jar.SetCookies(req.URL, rc) c.Jar.SetCookies(cookieURL, rc)
} }
} }
return resp, nil, nil return resp, nil, nil
@@ -327,7 +331,7 @@ func basicAuth(username, password string) string {
// //
// An error is returned if there were too many redirects or if there // An error is returned if there were too many redirects or if there
// was an HTTP protocol error. A non-2xx response doesn't cause an // was an HTTP protocol error. A non-2xx response doesn't cause an
// error. Any returned error will be of type *url.Error. The url.Error // error. Any returned error will be of type [*url.Error]. The url.Error
// value's Timeout method will report true if the request timed out. // value's Timeout method will report true if the request timed out.
// //
// When err is nil, resp always contains a non-nil resp.Body. // When err is nil, resp always contains a non-nil resp.Body.
@@ -335,10 +339,10 @@ func basicAuth(username, password string) string {
// //
// Get is a wrapper around DefaultClient.Get. // Get is a wrapper around DefaultClient.Get.
// //
// To make a request with custom headers, use NewRequest and // To make a request with custom headers, use [NewRequest] and
// DefaultClient.Do. // DefaultClient.Do.
// //
// To make a request with a specified context.Context, use NewRequestWithContext // To make a request with a specified context.Context, use [NewRequestWithContext]
// and DefaultClient.Do. // and DefaultClient.Do.
func Get(url string) (resp *Response, err error) { func Get(url string) (resp *Response, err error) {
return DefaultClient.Get(url) return DefaultClient.Get(url)
@@ -346,7 +350,7 @@ func Get(url string) (resp *Response, err error) {
// Get issues a GET to the specified URL. If the response is one of the // Get issues a GET to the specified URL. If the response is one of the
// following redirect codes, Get follows the redirect after calling the // following redirect codes, Get follows the redirect after calling the
// Client's CheckRedirect function: // [Client.CheckRedirect] function:
// //
// 301 (Moved Permanently) // 301 (Moved Permanently)
// 302 (Found) // 302 (Found)
@@ -354,18 +358,18 @@ func Get(url string) (resp *Response, err error) {
// 307 (Temporary Redirect) // 307 (Temporary Redirect)
// 308 (Permanent Redirect) // 308 (Permanent Redirect)
// //
// An error is returned if the Client's CheckRedirect function fails // An error is returned if the [Client.CheckRedirect] function fails
// or if there was an HTTP protocol error. A non-2xx response doesn't // or if there was an HTTP protocol error. A non-2xx response doesn't
// cause an error. Any returned error will be of type *url.Error. The // cause an error. Any returned error will be of type [*url.Error]. The
// url.Error value's Timeout method will report true if the request // url.Error value's Timeout method will report true if the request
// timed out. // timed out.
// //
// When err is nil, resp always contains a non-nil resp.Body. // When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it. // Caller should close resp.Body when done reading from it.
// //
// To make a request with custom headers, use NewRequest and Client.Do. // To make a request with custom headers, use [NewRequest] and [Client.Do].
// //
// To make a request with a specified context.Context, use NewRequestWithContext // To make a request with a specified context.Context, use [NewRequestWithContext]
// and Client.Do. // and Client.Do.
func (c *Client) Get(url string) (resp *Response, err error) { func (c *Client) Get(url string) (resp *Response, err error) {
req, err := NewRequest("GET", url, nil) req, err := NewRequest("GET", url, nil)
@@ -398,20 +402,21 @@ func urlErrorOp(method string) string {
// connectivity problem). A non-2xx status code doesn't cause an // connectivity problem). A non-2xx status code doesn't cause an
// error. // error.
// //
// If the returned error is nil, the Response will contain a non-nil // If the returned error is nil, the [Response] will contain a non-nil
// Body which the user is expected to close. If the Body is not both // Body which the user is expected to close. If the Body is not both
// read to EOF and closed, the Client's underlying RoundTripper // read to EOF and closed, the [Client]'s underlying [RoundTripper]
// (typically Transport) may not be able to re-use a persistent TCP // (typically [Transport]) may not be able to re-use a persistent TCP
// connection to the server for a subsequent "keep-alive" request. // connection to the server for a subsequent "keep-alive" request.
// //
// The request Body, if non-nil, will be closed by the underlying // The request Body, if non-nil, will be closed by the underlying
// Transport, even on errors. // Transport, even on errors. The Body may be closed asynchronously after
// Do returns.
// //
// On error, any Response can be ignored. A non-nil Response with a // On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when CheckRedirect fails, and even then // non-nil error only occurs when CheckRedirect fails, and even then
// the returned Response.Body is already closed. // the returned [Response.Body] is already closed.
// //
// Generally Get, Post, or PostForm will be used instead of Do. // Generally [Get], [Post], or [PostForm] will be used instead of Do.
// //
// If the server replies with a redirect, the Client first uses the // If the server replies with a redirect, the Client first uses the
// CheckRedirect function to determine whether the redirect should be // CheckRedirect function to determine whether the redirect should be
@@ -419,11 +424,11 @@ func urlErrorOp(method string) string {
// subsequent requests to use HTTP method GET // subsequent requests to use HTTP method GET
// (or HEAD if the original request was HEAD), with no body. // (or HEAD if the original request was HEAD), with no body.
// A 307 or 308 redirect preserves the original HTTP method and body, // A 307 or 308 redirect preserves the original HTTP method and body,
// provided that the Request.GetBody function is defined. // provided that the [Request.GetBody] function is defined.
// The NewRequest function automatically sets GetBody for common // The [NewRequest] function automatically sets GetBody for common
// standard library body types. // standard library body types.
// //
// Any returned error will be of type *url.Error. The url.Error // Any returned error will be of type [*url.Error]. The url.Error
// value's Timeout method will report true if the request timed out. // value's Timeout method will report true if the request timed out.
func (c *Client) Do(req *Request) (*Response, error) { func (c *Client) Do(req *Request) (*Response, error) {
if c.Transport != nil { if c.Transport != nil {
@@ -441,6 +446,7 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
Err: errors.New("http: nil Request.URL"), Err: errors.New("http: nil Request.URL"),
} }
} }
_ = *c // panic early if c is nil; see go.dev/issue/53521
var err error var err error
var didTimeout func() bool var didTimeout func() bool
@@ -465,17 +471,17 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
// //
// Caller should close resp.Body when done reading from it. // Caller should close resp.Body when done reading from it.
// //
// If the provided body is an io.Closer, it is closed after the // If the provided body is an [io.Closer], it is closed after the
// request. // request.
// //
// Post is a wrapper around DefaultClient.Post. // Post is a wrapper around DefaultClient.Post.
// //
// To set custom headers, use NewRequest and DefaultClient.Do. // To set custom headers, use [NewRequest] and DefaultClient.Do.
// //
// See the Client.Do method documentation for details on how redirects // See the [Client.Do] method documentation for details on how redirects
// are handled. // are handled.
// //
// To make a request with a specified context.Context, use NewRequestWithContext // To make a request with a specified context.Context, use [NewRequestWithContext]
// and DefaultClient.Do. // and DefaultClient.Do.
func Post(url, contentType string, body io.Reader) (resp *Response, err error) { func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
return DefaultClient.Post(url, contentType, body) return DefaultClient.Post(url, contentType, body)
@@ -485,15 +491,15 @@ func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
// //
// Caller should close resp.Body when done reading from it. // Caller should close resp.Body when done reading from it.
// //
// If the provided body is an io.Closer, it is closed after the // If the provided body is an [io.Closer], it is closed after the
// request. // request.
// //
// To set custom headers, use NewRequest and Client.Do. // To set custom headers, use [NewRequest] and [Client.Do].
// //
// To make a request with a specified context.Context, use NewRequestWithContext // To make a request with a specified context.Context, use [NewRequestWithContext]
// and Client.Do. // and [Client.Do].
// //
// See the Client.Do method documentation for details on how redirects // See the [Client.Do] method documentation for details on how redirects
// are handled. // are handled.
func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) { func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) {
req, err := NewRequest("POST", url, body) req, err := NewRequest("POST", url, body)
@@ -508,17 +514,17 @@ func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response,
// values URL-encoded as the request body. // values URL-encoded as the request body.
// //
// The Content-Type header is set to application/x-www-form-urlencoded. // The Content-Type header is set to application/x-www-form-urlencoded.
// To set other headers, use NewRequest and DefaultClient.Do. // To set other headers, use [NewRequest] and DefaultClient.Do.
// //
// When err is nil, resp always contains a non-nil resp.Body. // When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it. // Caller should close resp.Body when done reading from it.
// //
// PostForm is a wrapper around DefaultClient.PostForm. // PostForm is a wrapper around DefaultClient.PostForm.
// //
// See the Client.Do method documentation for details on how redirects // See the [Client.Do] method documentation for details on how redirects
// are handled. // are handled.
// //
// To make a request with a specified context.Context, use NewRequestWithContext // To make a request with a specified [context.Context], use [NewRequestWithContext]
// and DefaultClient.Do. // and DefaultClient.Do.
func PostForm(url string, data url.Values) (resp *Response, err error) { func PostForm(url string, data url.Values) (resp *Response, err error) {
return DefaultClient.PostForm(url, data) return DefaultClient.PostForm(url, data)
@@ -528,15 +534,15 @@ func PostForm(url string, data url.Values) (resp *Response, err error) {
// with data's keys and values URL-encoded as the request body. // with data's keys and values URL-encoded as the request body.
// //
// The Content-Type header is set to application/x-www-form-urlencoded. // The Content-Type header is set to application/x-www-form-urlencoded.
// To set other headers, use NewRequest and Client.Do. // To set other headers, use [NewRequest] and [Client.Do].
// //
// When err is nil, resp always contains a non-nil resp.Body. // When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it. // Caller should close resp.Body when done reading from it.
// //
// See the Client.Do method documentation for details on how redirects // See the [Client.Do] method documentation for details on how redirects
// are handled. // are handled.
// //
// To make a request with a specified context.Context, use NewRequestWithContext // To make a request with a specified context.Context, use [NewRequestWithContext]
// and Client.Do. // and Client.Do.
func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) { func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) {
return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
@@ -554,7 +560,7 @@ func (c *Client) PostForm(url string, data url.Values) (resp *Response, err erro
// //
// Head is a wrapper around DefaultClient.Head. // Head is a wrapper around DefaultClient.Head.
// //
// To make a request with a specified context.Context, use NewRequestWithContext // To make a request with a specified [context.Context], use [NewRequestWithContext]
// and DefaultClient.Do. // and DefaultClient.Do.
func Head(url string) (resp *Response, err error) { func Head(url string) (resp *Response, err error) {
return DefaultClient.Head(url) return DefaultClient.Head(url)
@@ -562,7 +568,7 @@ func Head(url string) (resp *Response, err error) {
// Head issues a HEAD to the specified URL. If the response is one of the // Head issues a HEAD to the specified URL. If the response is one of the
// following redirect codes, Head follows the redirect after calling the // following redirect codes, Head follows the redirect after calling the
// Client's CheckRedirect function: // [Client.CheckRedirect] function:
// //
// 301 (Moved Permanently) // 301 (Moved Permanently)
// 302 (Found) // 302 (Found)
@@ -570,8 +576,8 @@ func Head(url string) (resp *Response, err error) {
// 307 (Temporary Redirect) // 307 (Temporary Redirect)
// 308 (Permanent Redirect) // 308 (Permanent Redirect)
// //
// To make a request with a specified context.Context, use NewRequestWithContext // To make a request with a specified [context.Context], use [NewRequestWithContext]
// and Client.Do. // and [Client.Do].
func (c *Client) Head(url string) (resp *Response, err error) { func (c *Client) Head(url string) (resp *Response, err error) {
req, err := NewRequest("HEAD", url, nil) req, err := NewRequest("HEAD", url, nil)
if err != nil { if err != nil {
+49 -4
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -10,8 +8,18 @@ import (
"mime/multipart" "mime/multipart"
"net/textproto" "net/textproto"
"net/url" "net/url"
_ "unsafe" // for linkname
) )
// cloneURLValues should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/searKing/golang
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname cloneURLValues
func cloneURLValues(v url.Values) url.Values { func cloneURLValues(v url.Values) url.Values {
if v == nil { if v == nil {
return nil return nil
@@ -21,6 +29,15 @@ func cloneURLValues(v url.Values) url.Values {
return url.Values(Header(v).Clone()) return url.Values(Header(v).Clone())
} }
// cloneURL should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/searKing/golang
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname cloneURL
func cloneURL(u *url.URL) *url.URL { func cloneURL(u *url.URL) *url.URL {
if u == nil { if u == nil {
return nil return nil
@@ -34,6 +51,15 @@ func cloneURL(u *url.URL) *url.URL {
return u2 return u2
} }
// cloneMultipartForm should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/searKing/golang
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname cloneMultipartForm
func cloneMultipartForm(f *multipart.Form) *multipart.Form { func cloneMultipartForm(f *multipart.Form) *multipart.Form {
if f == nil { if f == nil {
return nil return nil
@@ -42,7 +68,7 @@ func cloneMultipartForm(f *multipart.Form) *multipart.Form {
Value: (map[string][]string)(Header(f.Value).Clone()), Value: (map[string][]string)(Header(f.Value).Clone()),
} }
if f.File != nil { if f.File != nil {
m := make(map[string][]*multipart.FileHeader) m := make(map[string][]*multipart.FileHeader, len(f.File))
for k, vv := range f.File { for k, vv := range f.File {
vv2 := make([]*multipart.FileHeader, len(vv)) vv2 := make([]*multipart.FileHeader, len(vv))
for i, v := range vv { for i, v := range vv {
@@ -55,6 +81,15 @@ func cloneMultipartForm(f *multipart.Form) *multipart.Form {
return f2 return f2
} }
// cloneMultipartFileHeader should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/searKing/golang
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname cloneMultipartFileHeader
func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader { func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader {
if fh == nil { if fh == nil {
return nil return nil
@@ -67,6 +102,16 @@ func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader {
// cloneOrMakeHeader invokes Header.Clone but if the // cloneOrMakeHeader invokes Header.Clone but if the
// result is nil, it'll instead make and return a non-nil Header. // result is nil, it'll instead make and return a non-nil Header.
//
// cloneOrMakeHeader should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/searKing/golang
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname cloneOrMakeHeader
func cloneOrMakeHeader(hdr Header) Header { func cloneOrMakeHeader(hdr Header) Header {
clone := hdr.Clone() clone := hdr.Clone()
if clone == nil { if clone == nil {
+237 -132
View File
@@ -1,6 +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.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -9,6 +7,7 @@ package http
import ( import (
"errors" "errors"
"fmt" "fmt"
"internal/godebug"
"log" "log"
"net" "net"
"net/http/internal/ascii" "net/http/internal/ascii"
@@ -18,13 +17,16 @@ import (
"time" "time"
) )
var httpcookiemaxnum = godebug.New("httpcookiemaxnum")
// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request. // HTTP response or the Cookie header of an HTTP request.
// //
// See https://tools.ietf.org/html/rfc6265 for details. // See https://tools.ietf.org/html/rfc6265 for details.
type Cookie struct { type Cookie struct {
Name string Name string
Value string Value string
Quoted bool // indicates whether the Value was originally quoted
Path string // optional Path string // optional
Domain string // optional Domain string // optional
@@ -34,12 +36,13 @@ type Cookie struct {
// MaxAge=0 means no 'Max-Age' attribute specified. // MaxAge=0 means no 'Max-Age' attribute specified.
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
// MaxAge>0 means Max-Age attribute present and given in seconds // MaxAge>0 means Max-Age attribute present and given in seconds
MaxAge int MaxAge int
Secure bool Secure bool
HttpOnly bool HttpOnly bool
SameSite SameSite SameSite SameSite
Raw string Partitioned bool
Unparsed []string // Raw text of unparsed attribute-value pairs Raw string
Unparsed []string // Raw text of unparsed attribute-value pairs
} }
// SameSite allows a server to define a cookie attribute making it impossible for // SameSite allows a server to define a cookie attribute making it impossible for
@@ -57,115 +60,192 @@ const (
SameSiteNoneMode SameSiteNoneMode
) )
var (
errBlankCookie = errors.New("http: blank cookie")
errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
errInvalidCookieName = errors.New("http: invalid cookie name")
errInvalidCookieValue = errors.New("http: invalid cookie value")
errCookieNumLimitExceeded = errors.New("http: number of cookies exceeded limit")
)
const defaultCookieMaxNum = 3000
func cookieNumWithinMax(cookieNum int) bool {
withinDefaultMax := cookieNum <= defaultCookieMaxNum
if httpcookiemaxnum.Value() == "" {
return withinDefaultMax
}
if customMax, err := strconv.Atoi(httpcookiemaxnum.Value()); err == nil {
withinCustomMax := customMax == 0 || cookieNum <= customMax
if withinDefaultMax != withinCustomMax {
httpcookiemaxnum.IncNonDefault()
}
return withinCustomMax
}
return withinDefaultMax
}
// ParseCookie parses a Cookie header value and returns all the cookies
// which were set in it. Since the same cookie name can appear multiple times
// the returned Values can contain more than one value for a given key.
func ParseCookie(line string) ([]*Cookie, error) {
if !cookieNumWithinMax(strings.Count(line, ";") + 1) {
return nil, errCookieNumLimitExceeded
}
parts := strings.Split(textproto.TrimString(line), ";")
if len(parts) == 1 && parts[0] == "" {
return nil, errBlankCookie
}
cookies := make([]*Cookie, 0, len(parts))
for _, s := range parts {
s = textproto.TrimString(s)
name, value, found := strings.Cut(s, "=")
if !found {
return nil, errEqualNotFoundInCookie
}
if !isToken(name) {
return nil, errInvalidCookieName
}
value, quoted, found := parseCookieValue(value, true)
if !found {
return nil, errInvalidCookieValue
}
cookies = append(cookies, &Cookie{Name: name, Value: value, Quoted: quoted})
}
return cookies, nil
}
// ParseSetCookie parses a Set-Cookie header value and returns a cookie.
// It returns an error on syntax error.
func ParseSetCookie(line string) (*Cookie, error) {
parts := strings.Split(textproto.TrimString(line), ";")
if len(parts) == 1 && parts[0] == "" {
return nil, errBlankCookie
}
parts[0] = textproto.TrimString(parts[0])
name, value, ok := strings.Cut(parts[0], "=")
if !ok {
return nil, errEqualNotFoundInCookie
}
name = textproto.TrimString(name)
if !isToken(name) {
return nil, errInvalidCookieName
}
value, quoted, ok := parseCookieValue(value, true)
if !ok {
return nil, errInvalidCookieValue
}
c := &Cookie{
Name: name,
Value: value,
Quoted: quoted,
Raw: line,
}
for i := 1; i < len(parts); i++ {
parts[i] = textproto.TrimString(parts[i])
if len(parts[i]) == 0 {
continue
}
attr, val, _ := strings.Cut(parts[i], "=")
lowerAttr, isASCII := ascii.ToLower(attr)
if !isASCII {
continue
}
val, _, ok = parseCookieValue(val, false)
if !ok {
c.Unparsed = append(c.Unparsed, parts[i])
continue
}
switch lowerAttr {
case "samesite":
lowerVal, ascii := ascii.ToLower(val)
if !ascii {
c.SameSite = SameSiteDefaultMode
continue
}
switch lowerVal {
case "lax":
c.SameSite = SameSiteLaxMode
case "strict":
c.SameSite = SameSiteStrictMode
case "none":
c.SameSite = SameSiteNoneMode
default:
c.SameSite = SameSiteDefaultMode
}
continue
case "secure":
c.Secure = true
continue
case "httponly":
c.HttpOnly = true
continue
case "domain":
c.Domain = val
continue
case "max-age":
secs, err := strconv.Atoi(val)
if err != nil || secs != 0 && val[0] == '0' {
break
}
if secs <= 0 {
secs = -1
}
c.MaxAge = secs
continue
case "expires":
c.RawExpires = val
exptime, err := time.Parse(time.RFC1123, val)
if err != nil {
exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
if err != nil {
c.Expires = time.Time{}
break
}
}
c.Expires = exptime.UTC()
continue
case "path":
c.Path = val
continue
case "partitioned":
c.Partitioned = true
continue
}
c.Unparsed = append(c.Unparsed, parts[i])
}
return c, nil
}
// readSetCookies parses all "Set-Cookie" values from // readSetCookies parses all "Set-Cookie" values from
// the header h and returns the successfully parsed Cookies. // the header h and returns the successfully parsed Cookies.
//
// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
// GODEBUG option is not explicitly turned off, this function will silently
// fail and return an empty slice.
func readSetCookies(h Header) []*Cookie { func readSetCookies(h Header) []*Cookie {
cookieCount := len(h["Set-Cookie"]) cookieCount := len(h["Set-Cookie"])
if cookieCount == 0 { if cookieCount == 0 {
return []*Cookie{} return []*Cookie{}
} }
// Cookie limit was unfortunately introduced at a later point in time.
// As such, we can only fail by returning an empty slice rather than
// explicit error.
if !cookieNumWithinMax(cookieCount) {
return []*Cookie{}
}
cookies := make([]*Cookie, 0, cookieCount) cookies := make([]*Cookie, 0, cookieCount)
for _, line := range h["Set-Cookie"] { for _, line := range h["Set-Cookie"] {
parts := strings.Split(textproto.TrimString(line), ";") if cookie, err := ParseSetCookie(line); err == nil {
if len(parts) == 1 && parts[0] == "" { cookies = append(cookies, cookie)
continue
} }
parts[0] = textproto.TrimString(parts[0])
name, value, ok := strings.Cut(parts[0], "=")
if !ok {
continue
}
name = textproto.TrimString(name)
if !isCookieNameValid(name) {
continue
}
value, ok = parseCookieValue(value, true)
if !ok {
continue
}
c := &Cookie{
Name: name,
Value: value,
Raw: line,
}
for i := 1; i < len(parts); i++ {
parts[i] = textproto.TrimString(parts[i])
if len(parts[i]) == 0 {
continue
}
attr, val, _ := strings.Cut(parts[i], "=")
lowerAttr, isASCII := ascii.ToLower(attr)
if !isASCII {
continue
}
val, ok = parseCookieValue(val, false)
if !ok {
c.Unparsed = append(c.Unparsed, parts[i])
continue
}
switch lowerAttr {
case "samesite":
lowerVal, ascii := ascii.ToLower(val)
if !ascii {
c.SameSite = SameSiteDefaultMode
continue
}
switch lowerVal {
case "lax":
c.SameSite = SameSiteLaxMode
case "strict":
c.SameSite = SameSiteStrictMode
case "none":
c.SameSite = SameSiteNoneMode
default:
c.SameSite = SameSiteDefaultMode
}
continue
case "secure":
c.Secure = true
continue
case "httponly":
c.HttpOnly = true
continue
case "domain":
c.Domain = val
continue
case "max-age":
secs, err := strconv.Atoi(val)
if err != nil || secs != 0 && val[0] == '0' {
break
}
if secs <= 0 {
secs = -1
}
c.MaxAge = secs
continue
case "expires":
c.RawExpires = val
exptime, err := time.Parse(time.RFC1123, val)
if err != nil {
exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
if err != nil {
c.Expires = time.Time{}
break
}
}
c.Expires = exptime.UTC()
continue
case "path":
c.Path = val
continue
}
c.Unparsed = append(c.Unparsed, parts[i])
}
cookies = append(cookies, c)
} }
return cookies return cookies
} }
// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. // SetCookie adds a Set-Cookie header to the provided [ResponseWriter]'s headers.
// The provided cookie must have a valid Name. Invalid cookies may be // The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped. // silently dropped.
func SetCookie(w ResponseWriter, cookie *Cookie) { func SetCookie(w ResponseWriter, cookie *Cookie) {
@@ -174,12 +254,12 @@ func SetCookie(w ResponseWriter, cookie *Cookie) {
} }
} }
// String returns the serialization of the cookie for use in a Cookie // String returns the serialization of the cookie for use in a [Cookie]
// header (if only Name and Value are set) or a Set-Cookie response // header (if only Name and Value are set) or a Set-Cookie response
// header (if other fields are set). // header (if other fields are set).
// If c is nil or c.Name is invalid, the empty string is returned. // If c is nil or c.Name is invalid, the empty string is returned.
func (c *Cookie) String() string { func (c *Cookie) String() string {
if c == nil || !isCookieNameValid(c.Name) { if c == nil || !isToken(c.Name) {
return "" return ""
} }
// extraCookieLength derived from typical length of cookie attributes // extraCookieLength derived from typical length of cookie attributes
@@ -189,7 +269,7 @@ func (c *Cookie) String() string {
b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength) b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength)
b.WriteString(c.Name) b.WriteString(c.Name)
b.WriteRune('=') b.WriteRune('=')
b.WriteString(sanitizeCookieValue(c.Value)) b.WriteString(sanitizeCookieValue(c.Value, c.Quoted))
if len(c.Path) > 0 { if len(c.Path) > 0 {
b.WriteString("; Path=") b.WriteString("; Path=")
@@ -238,6 +318,9 @@ func (c *Cookie) String() string {
case SameSiteStrictMode: case SameSiteStrictMode:
b.WriteString("; SameSite=Strict") b.WriteString("; SameSite=Strict")
} }
if c.Partitioned {
b.WriteString("; Partitioned")
}
return b.String() return b.String()
} }
@@ -246,7 +329,7 @@ func (c *Cookie) Valid() error {
if c == nil { if c == nil {
return errors.New("http: nil Cookie") return errors.New("http: nil Cookie")
} }
if !isCookieNameValid(c.Name) { if !isToken(c.Name) {
return errors.New("http: invalid Cookie.Name") return errors.New("http: invalid Cookie.Name")
} }
if !c.Expires.IsZero() && !validCookieExpires(c.Expires) { if !c.Expires.IsZero() && !validCookieExpires(c.Expires) {
@@ -269,19 +352,39 @@ func (c *Cookie) Valid() error {
return errors.New("http: invalid Cookie.Domain") return errors.New("http: invalid Cookie.Domain")
} }
} }
if c.Partitioned {
if !c.Secure {
return errors.New("http: partitioned cookies must be set with Secure")
}
}
return nil return nil
} }
// readCookies parses all "Cookie" values from the header h and // readCookies parses all "Cookie" values from the header h and
// returns the successfully parsed Cookies. // returns the successfully parsed Cookies.
// //
// if filter isn't empty, only cookies of that name are returned. // If filter isn't empty, only cookies of that name are returned.
//
// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
// GODEBUG option is not explicitly turned off, this function will silently
// fail and return an empty slice.
func readCookies(h Header, filter string) []*Cookie { func readCookies(h Header, filter string) []*Cookie {
lines := h["Cookie"] lines := h["Cookie"]
if len(lines) == 0 { if len(lines) == 0 {
return []*Cookie{} return []*Cookie{}
} }
// Cookie limit was unfortunately introduced at a later point in time.
// As such, we can only fail by returning an empty slice rather than
// explicit error.
cookieCount := 0
for _, line := range lines {
cookieCount += strings.Count(line, ";") + 1
}
if !cookieNumWithinMax(cookieCount) {
return []*Cookie{}
}
cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";")) cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";"))
for _, line := range lines { for _, line := range lines {
line = textproto.TrimString(line) line = textproto.TrimString(line)
@@ -295,17 +398,17 @@ func readCookies(h Header, filter string) []*Cookie {
} }
name, val, _ := strings.Cut(part, "=") name, val, _ := strings.Cut(part, "=")
name = textproto.TrimString(name) name = textproto.TrimString(name)
if !isCookieNameValid(name) { if !isToken(name) {
continue continue
} }
if filter != "" && filter != name { if filter != "" && filter != name {
continue continue
} }
val, ok := parseCookieValue(val, true) val, quoted, ok := parseCookieValue(val, true)
if !ok { if !ok {
continue continue
} }
cookies = append(cookies, &Cookie{Name: name, Value: val}) cookies = append(cookies, &Cookie{Name: name, Value: val, Quoted: quoted})
} }
} }
return cookies return cookies
@@ -340,7 +443,8 @@ func isCookieDomainName(s string) bool {
} }
if s[0] == '.' { if s[0] == '.' {
// A cookie a domain attribute may start with a leading dot. // A cookie domain attribute may start with a leading dot.
// Per RFC 6265 section 5.2.3, a leading dot is ignored.
s = s[1:] s = s[1:]
} }
last := byte('.') last := byte('.')
@@ -390,6 +494,8 @@ func sanitizeCookieName(n string) string {
} }
// sanitizeCookieValue produces a suitable cookie-value from v. // sanitizeCookieValue produces a suitable cookie-value from v.
// It receives a quoted bool indicating whether the value was originally
// quoted.
// https://tools.ietf.org/html/rfc6265#section-4.1.1 // https://tools.ietf.org/html/rfc6265#section-4.1.1
// //
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
@@ -399,15 +505,11 @@ func sanitizeCookieName(n string) string {
// ; and backslash // ; and backslash
// //
// We loosen this as spaces and commas are common in cookie values // We loosen this as spaces and commas are common in cookie values
// but we produce a quoted cookie-value if and only if v contains // thus we produce a quoted cookie-value if v contains commas or spaces.
// commas or spaces.
// See https://golang.org/issue/7243 for the discussion. // See https://golang.org/issue/7243 for the discussion.
func sanitizeCookieValue(v string) string { func sanitizeCookieValue(v string, quoted bool) string {
v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
if len(v) == 0 { if strings.ContainsAny(v, " ,") || quoted {
return v
}
if strings.ContainsAny(v, " ,") {
return `"` + v + `"` return `"` + v + `"`
} }
return v return v
@@ -449,22 +551,25 @@ func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string {
return string(buf) return string(buf)
} }
func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) { // parseCookieValue parses a cookie value according to RFC 6265.
// If allowDoubleQuote is true, parseCookieValue will consider that it
// is parsing the cookie-value;
// otherwise, it will consider that it is parsing a cookie-av value
// (cookie attribute-value).
//
// It returns the parsed cookie value, a boolean indicating whether the
// parsing was successful, and a boolean indicating whether the parsed
// value was enclosed in double quotes.
func parseCookieValue(raw string, allowDoubleQuote bool) (value string, quoted, ok bool) {
// Strip the quotes, if present. // Strip the quotes, if present.
if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
raw = raw[1 : len(raw)-1] raw = raw[1 : len(raw)-1]
quoted = true
} }
for i := 0; i < len(raw); i++ { for i := 0; i < len(raw); i++ {
if !validCookieValueByte(raw[i]) { if !validCookieValueByte(raw[i]) {
return "", false return "", quoted, false
} }
} }
return raw, true return raw, quoted, true
}
func isCookieNameValid(raw string) bool {
if raw == "" {
return false
}
return strings.IndexFunc(raw, isNotToken) < 0
} }
+175 -51
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -11,6 +9,7 @@ package http
import ( import (
"errors" "errors"
"fmt" "fmt"
"internal/godebug"
"io" "io"
"io/fs" "io/fs"
"mime" "mime"
@@ -26,12 +25,12 @@ import (
"time" "time"
) )
// A Dir implements FileSystem using the native file system restricted to a // A Dir implements [FileSystem] using the native file system restricted to a
// specific directory tree. // specific directory tree.
// //
// While the FileSystem.Open method takes '/'-separated paths, a Dir's string // While the [FileSystem.Open] method takes '/'-separated paths, a Dir's string
// value is a filename on the native file system, not a URL, so it is separated // value is a directory path on the native file system, not a URL, so it is separated
// by filepath.Separator, which isn't necessarily '/'. // by [filepath.Separator], which isn't necessarily '/'.
// //
// Note that Dir could expose sensitive files and directories. Dir will follow // Note that Dir could expose sensitive files and directories. Dir will follow
// symlinks pointing out of the directory tree, which can be especially dangerous // symlinks pointing out of the directory tree, which can be especially dangerous
@@ -68,19 +67,27 @@ func mapOpenError(originalErr error, name string, sep rune, stat func(string) (f
return originalErr return originalErr
} }
// Open implements FileSystem using os.Open, opening files for reading rooted // errInvalidUnsafePath is returned by Dir.Open when the call to
// filepath.Localize fails. filepath.Localize returns an error if the path
// cannot be represented by the operating system.
var errInvalidUnsafePath = errors.New("http: invalid or unsafe file path")
// Open implements [FileSystem] using [os.Open], opening files for reading rooted
// and relative to the directory d. // and relative to the directory d.
func (d Dir) Open(name string) (File, error) { func (d Dir) Open(name string) (File, error) {
// TINYGO: internal/safefilepath isn't avail until 1.20, so keep pre 1.20 path := path.Clean("/" + name)[1:]
// TINYGO: code here until TinyGo min Go version is >= 1.20. if path == "" {
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { path = "."
return nil, errors.New("http: invalid character in file path") }
path, err := filepath.Localize(path)
if err != nil {
return nil, errInvalidUnsafePath
} }
dir := string(d) dir := string(d)
if dir == "" { if dir == "" {
dir = "." dir = "."
} }
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))) fullName := filepath.Join(dir, path)
f, err := os.Open(fullName) f, err := os.Open(fullName)
if err != nil { if err != nil {
return nil, mapOpenError(err, fullName, filepath.Separator, os.Stat) return nil, mapOpenError(err, fullName, filepath.Separator, os.Stat)
@@ -91,18 +98,18 @@ func (d Dir) Open(name string) (File, error) {
// A FileSystem implements access to a collection of named files. // A FileSystem implements access to a collection of named files.
// The elements in a file path are separated by slash ('/', U+002F) // The elements in a file path are separated by slash ('/', U+002F)
// characters, regardless of host operating system convention. // characters, regardless of host operating system convention.
// See the FileServer function to convert a FileSystem to a Handler. // See the [FileServer] function to convert a FileSystem to a [Handler].
// //
// This interface predates the fs.FS interface, which can be used instead: // This interface predates the [fs.FS] interface, which can be used instead:
// the FS adapter function converts an fs.FS to a FileSystem. // the [FS] adapter function converts an fs.FS to a FileSystem.
type FileSystem interface { type FileSystem interface {
Open(name string) (File, error) Open(name string) (File, error)
} }
// A File is returned by a FileSystem's Open method and can be // A File is returned by a [FileSystem]'s Open method and can be
// served by the FileServer implementation. // served by the [FileServer] implementation.
// //
// The methods should behave the same as those on an *os.File. // The methods should behave the same as those on an [*os.File].
type File interface { type File interface {
io.Closer io.Closer
io.Reader io.Reader
@@ -153,6 +160,8 @@ func dirList(w ResponseWriter, r *Request, f File) {
sort.Slice(dirs, func(i, j int) bool { return dirs.name(i) < dirs.name(j) }) sort.Slice(dirs, func(i, j int) bool { return dirs.name(i) < dirs.name(j) })
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<!doctype html>\n")
fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width\">\n")
fmt.Fprintf(w, "<pre>\n") fmt.Fprintf(w, "<pre>\n")
for i, n := 0, dirs.len(); i < n; i++ { for i, n := 0, dirs.len(); i < n; i++ {
name := dirs.name(i) name := dirs.name(i)
@@ -168,8 +177,42 @@ func dirList(w ResponseWriter, r *Request, f File) {
fmt.Fprintf(w, "</pre>\n") fmt.Fprintf(w, "</pre>\n")
} }
// GODEBUG=httpservecontentkeepheaders=1 restores the pre-1.23 behavior of not deleting
// Cache-Control, Content-Encoding, Etag, or Last-Modified headers on ServeContent errors.
var httpservecontentkeepheaders = godebug.New("httpservecontentkeepheaders")
// serveError serves an error from ServeFile, ServeFileFS, and ServeContent.
// Because those can all be configured by the caller by setting headers like
// Etag, Last-Modified, and Cache-Control to send on a successful response,
// the error path needs to clear them, since they may not be meant for errors.
func serveError(w ResponseWriter, text string, code int) {
h := w.Header()
nonDefault := false
for _, k := range []string{
"Cache-Control",
"Content-Encoding",
"Etag",
"Last-Modified",
} {
if !h.has(k) {
continue
}
if httpservecontentkeepheaders.Value() == "1" {
nonDefault = true
} else {
h.Del(k)
}
}
if nonDefault {
httpservecontentkeepheaders.IncNonDefault()
}
Error(w, text, code)
}
// ServeContent replies to the request using the content in the // ServeContent replies to the request using the content in the
// provided ReadSeeker. The main benefit of ServeContent over io.Copy // provided ReadSeeker. The main benefit of ServeContent over [io.Copy]
// is that it handles Range requests properly, sets the MIME type, and // is that it handles Range requests properly, sets the MIME type, and
// handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since, // handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since,
// and If-Range requests. // and If-Range requests.
@@ -177,7 +220,7 @@ func dirList(w ResponseWriter, r *Request, f File) {
// If the response's Content-Type header is not set, ServeContent // If the response's Content-Type header is not set, ServeContent
// first tries to deduce the type from name's file extension and, // first tries to deduce the type from name's file extension and,
// if that fails, falls back to reading the first block of the content // if that fails, falls back to reading the first block of the content
// and passing it to DetectContentType. // and passing it to [DetectContentType].
// The name is otherwise unused; in particular it can be empty and is // The name is otherwise unused; in particular it can be empty and is
// never sent in the response. // never sent in the response.
// //
@@ -188,11 +231,17 @@ func dirList(w ResponseWriter, r *Request, f File) {
// //
// The content's Seek method must work: ServeContent uses // The content's Seek method must work: ServeContent uses
// a seek to the end of the content to determine its size. // a seek to the end of the content to determine its size.
// Note that [*os.File] implements the [io.ReadSeeker] interface.
// //
// If the caller has set w's ETag header formatted per RFC 7232, section 2.3, // If the caller has set w's ETag header formatted per RFC 7232, section 2.3,
// ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range. // ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range.
// //
// Note that *os.File implements the io.ReadSeeker interface. // If an error occurs when serving the request (for example, when
// handling an invalid range request), ServeContent responds with an
// error message. By default, ServeContent strips the Cache-Control,
// Content-Encoding, ETag, and Last-Modified headers from error responses.
// The GODEBUG setting httpservecontentkeepheaders=1 causes ServeContent
// to preserve these headers.
func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) { func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
sizeFunc := func() (int64, error) { sizeFunc := func() (int64, error) {
size, err := content.Seek(0, io.SeekEnd) size, err := content.Seek(0, io.SeekEnd)
@@ -244,7 +293,7 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
ctype = DetectContentType(buf[:n]) ctype = DetectContentType(buf[:n])
_, err := content.Seek(0, io.SeekStart) // rewind to output whole file _, err := content.Seek(0, io.SeekStart) // rewind to output whole file
if err != nil { if err != nil {
Error(w, "seeker can't seek", StatusInternalServerError) serveError(w, "seeker can't seek", StatusInternalServerError)
return return
} }
} }
@@ -255,12 +304,12 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
size, err := sizeFunc() size, err := sizeFunc()
if err != nil { if err != nil {
Error(w, err.Error(), StatusInternalServerError) serveError(w, err.Error(), StatusInternalServerError)
return return
} }
if size < 0 { if size < 0 {
// Should never happen but just to be sure // Should never happen but just to be sure
Error(w, "negative content size computed", StatusInternalServerError) serveError(w, "negative content size computed", StatusInternalServerError)
return return
} }
@@ -282,7 +331,7 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size)) w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
fallthrough fallthrough
default: default:
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) serveError(w, err.Error(), StatusRequestedRangeNotSatisfiable)
return return
} }
@@ -308,7 +357,7 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
// multipart responses." // multipart responses."
ra := ranges[0] ra := ranges[0]
if _, err := content.Seek(ra.start, io.SeekStart); err != nil { if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) serveError(w, err.Error(), StatusRequestedRangeNotSatisfiable)
return return
} }
sendSize = ra.length sendSize = ra.length
@@ -345,10 +394,35 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
} }
w.Header().Set("Accept-Ranges", "bytes") w.Header().Set("Accept-Ranges", "bytes")
if w.Header().Get("Content-Encoding") == "" {
// We should be able to unconditionally set the Content-Length here.
//
// However, there is a pattern observed in the wild that this breaks:
// The user wraps the ResponseWriter in one which gzips data written to it,
// and sets "Content-Encoding: gzip".
//
// The user shouldn't be doing this; the serveContent path here depends
// on serving seekable data with a known length. If you want to compress
// on the fly, then you shouldn't be using ServeFile/ServeContent, or
// you should compress the entire file up-front and provide a seekable
// view of the compressed data.
//
// However, since we've observed this pattern in the wild, and since
// setting Content-Length here breaks code that mostly-works today,
// skip setting Content-Length if the user set Content-Encoding.
//
// If this is a range request, always set Content-Length.
// If the user isn't changing the bytes sent in the ResponseWrite,
// the Content-Length will be correct.
// If the user is changing the bytes sent, then the range request wasn't
// going to work properly anyway and we aren't worse off.
//
// A possible future improvement on this might be to look at the type
// of the ResponseWriter, and always set Content-Length if it's one
// that we recognize.
if len(ranges) > 0 || w.Header().Get("Content-Encoding") == "" {
w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10)) w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
} }
w.WriteHeader(code) w.WriteHeader(code)
if r.Method != "HEAD" { if r.Method != "HEAD" {
@@ -449,8 +523,7 @@ func checkIfUnmodifiedSince(r *Request, modtime time.Time) condResult {
// The Last-Modified header truncates sub-second precision so // The Last-Modified header truncates sub-second precision so
// the modtime needs to be truncated too. // the modtime needs to be truncated too.
modtime = modtime.Truncate(time.Second) modtime = modtime.Truncate(time.Second)
// TINYGO: time.Compare not until Go 1.20 if ret := modtime.Compare(t); ret <= 0 {
if modtime.Before(t) || modtime.Equal(t) {
return condTrue return condTrue
} }
return condFalse return condFalse
@@ -501,8 +574,7 @@ func checkIfModifiedSince(r *Request, modtime time.Time) condResult {
// The Last-Modified header truncates sub-second precision so // The Last-Modified header truncates sub-second precision so
// the modtime needs to be truncated too. // the modtime needs to be truncated too.
modtime = modtime.Truncate(time.Second) modtime = modtime.Truncate(time.Second)
// TINYGO: time.Compare not until Go 1.20 if ret := modtime.Compare(t); ret <= 0 {
if modtime.Before(t) || modtime.Equal(t) {
return condFalse return condFalse
} }
return condTrue return condTrue
@@ -618,7 +690,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
f, err := fs.Open(name) f, err := fs.Open(name)
if err != nil { if err != nil {
msg, code := toHTTPError(err) msg, code := toHTTPError(err)
Error(w, msg, code) serveError(w, msg, code)
return return
} }
defer f.Close() defer f.Close()
@@ -626,7 +698,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
d, err := f.Stat() d, err := f.Stat()
if err != nil { if err != nil {
msg, code := toHTTPError(err) msg, code := toHTTPError(err)
Error(w, msg, code) serveError(w, msg, code)
return return
} }
@@ -639,11 +711,16 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
localRedirect(w, r, path.Base(url)+"/") localRedirect(w, r, path.Base(url)+"/")
return return
} }
} else { } else if url[len(url)-1] == '/' {
if url[len(url)-1] == '/' { base := path.Base(url)
localRedirect(w, r, "../"+path.Base(url)) if base == "/" || base == "." {
// The FileSystem maps a path like "/" or "/./" to a file instead of a directory.
msg := "http: attempting to traverse a non-directory"
serveError(w, msg, StatusInternalServerError)
return return
} }
localRedirect(w, r, "../"+base)
return
} }
} }
@@ -696,6 +773,9 @@ func toHTTPError(err error) (msg string, httpStatus int) {
if errors.Is(err, fs.ErrPermission) { if errors.Is(err, fs.ErrPermission) {
return "403 Forbidden", StatusForbidden return "403 Forbidden", StatusForbidden
} }
if errors.Is(err, errInvalidUnsafePath) {
return "404 page not found", StatusNotFound
}
// Default: // Default:
return "500 Internal Server Error", StatusInternalServerError return "500 Internal Server Error", StatusInternalServerError
} }
@@ -716,17 +796,17 @@ func localRedirect(w ResponseWriter, r *Request, newPath string) {
// If the provided file or directory name is a relative path, it is // If the provided file or directory name is a relative path, it is
// interpreted relative to the current directory and may ascend to // interpreted relative to the current directory and may ascend to
// parent directories. If the provided name is constructed from user // parent directories. If the provided name is constructed from user
// input, it should be sanitized before calling ServeFile. // input, it should be sanitized before calling [ServeFile].
// //
// As a precaution, ServeFile will reject requests where r.URL.Path // As a precaution, ServeFile will reject requests where r.URL.Path
// contains a ".." path element; this protects against callers who // contains a ".." path element; this protects against callers who
// might unsafely use filepath.Join on r.URL.Path without sanitizing // might unsafely use [filepath.Join] on r.URL.Path without sanitizing
// it and then use that filepath.Join result as the name argument. // it and then use that filepath.Join result as the name argument.
// //
// As another special case, ServeFile redirects any request where r.URL.Path // As another special case, ServeFile redirects any request where r.URL.Path
// ends in "/index.html" to the same path, without the final // ends in "/index.html" to the same path, without the final
// "index.html". To avoid such redirects either modify the path or // "index.html". To avoid such redirects either modify the path or
// use ServeContent. // use [ServeContent].
// //
// Outside of those two special cases, ServeFile does not use // Outside of those two special cases, ServeFile does not use
// r.URL.Path for selecting the file or directory to serve; only the // r.URL.Path for selecting the file or directory to serve; only the
@@ -738,18 +818,51 @@ func ServeFile(w ResponseWriter, r *Request, name string) {
// here and ".." may not be wanted. // here and ".." may not be wanted.
// Note that name might not contain "..", for example if code (still // Note that name might not contain "..", for example if code (still
// incorrectly) used filepath.Join(myDir, r.URL.Path). // incorrectly) used filepath.Join(myDir, r.URL.Path).
Error(w, "invalid URL path", StatusBadRequest) serveError(w, "invalid URL path", StatusBadRequest)
return return
} }
dir, file := filepath.Split(name) dir, file := filepath.Split(name)
serveFile(w, r, Dir(dir), file, false) serveFile(w, r, Dir(dir), file, false)
} }
// ServeFileFS replies to the request with the contents
// of the named file or directory from the file system fsys.
// The files provided by fsys must implement [io.Seeker].
//
// If the provided name is constructed from user input, it should be
// sanitized before calling [ServeFileFS].
//
// As a precaution, ServeFileFS will reject requests where r.URL.Path
// contains a ".." path element; this protects against callers who
// might unsafely use [filepath.Join] on r.URL.Path without sanitizing
// it and then use that filepath.Join result as the name argument.
//
// As another special case, ServeFileFS redirects any request where r.URL.Path
// ends in "/index.html" to the same path, without the final
// "index.html". To avoid such redirects either modify the path or
// use [ServeContent].
//
// Outside of those two special cases, ServeFileFS does not use
// r.URL.Path for selecting the file or directory to serve; only the
// file or directory provided in the name argument is used.
func ServeFileFS(w ResponseWriter, r *Request, fsys fs.FS, name string) {
if containsDotDot(r.URL.Path) {
// Too many programs use r.URL.Path to construct the argument to
// serveFile. Reject the request under the assumption that happened
// here and ".." may not be wanted.
// Note that name might not contain "..", for example if code (still
// incorrectly) used filepath.Join(myDir, r.URL.Path).
serveError(w, "invalid URL path", StatusBadRequest)
return
}
serveFile(w, r, FS(fsys), name, false)
}
func containsDotDot(v string) bool { func containsDotDot(v string) bool {
if !strings.Contains(v, "..") { if !strings.Contains(v, "..") {
return false return false
} }
for _, ent := range strings.FieldsFunc(v, isSlashRune) { for ent := range strings.FieldsFuncSeq(v, isSlashRune) {
if ent == ".." { if ent == ".." {
return true return true
} }
@@ -835,9 +948,9 @@ func (f ioFile) Readdir(count int) ([]fs.FileInfo, error) {
return list, nil return list, nil
} }
// FS converts fsys to a FileSystem implementation, // FS converts fsys to a [FileSystem] implementation,
// for use with FileServer and NewFileTransport. // for use with [FileServer] and [NewFileTransport].
// The files provided by fsys must implement io.Seeker. // The files provided by fsys must implement [io.Seeker].
func FS(fsys fs.FS) FileSystem { func FS(fsys fs.FS) FileSystem {
return ioFS{fsys} return ioFS{fsys}
} }
@@ -850,17 +963,28 @@ func FS(fsys fs.FS) FileSystem {
// "index.html". // "index.html".
// //
// To use the operating system's file system implementation, // To use the operating system's file system implementation,
// use http.Dir: // use [http.Dir]:
// //
// http.Handle("/", http.FileServer(http.Dir("/tmp"))) // http.Handle("/", http.FileServer(http.Dir("/tmp")))
// //
// To use an fs.FS implementation, use http.FS to convert it: // To use an [fs.FS] implementation, use [http.FileServerFS] instead.
//
// http.Handle("/", http.FileServer(http.FS(fsys)))
func FileServer(root FileSystem) Handler { func FileServer(root FileSystem) Handler {
return &fileHandler{root} return &fileHandler{root}
} }
// FileServerFS returns a handler that serves HTTP requests
// with the contents of the file system fsys.
// The files provided by fsys must implement [io.Seeker].
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
// "index.html".
//
// http.Handle("/", http.FileServerFS(fsys))
func FileServerFS(root fs.FS) Handler {
return FileServer(FS(root))
}
func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) { func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
upath := r.URL.Path upath := r.URL.Path
if !strings.HasPrefix(upath, "/") { if !strings.HasPrefix(upath, "/") {
@@ -898,7 +1022,7 @@ func parseRange(s string, size int64) ([]httpRange, error) {
} }
var ranges []httpRange var ranges []httpRange
noOverlap := false noOverlap := false
for _, ra := range strings.Split(s[len(b):], ",") { for ra := range strings.SplitSeq(s[len(b):], ",") {
ra = textproto.TrimString(ra) ra = textproto.TrimString(ra)
if ra == "" { if ra == "" {
continue continue
+11 -17
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: Removed trace stuff
@@ -12,7 +12,7 @@ import (
"io" "io"
"net/http/internal/ascii" "net/http/internal/ascii"
"net/textproto" "net/textproto"
"sort" "slices"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -23,13 +23,13 @@ import (
// A Header represents the key-value pairs in an HTTP header. // A Header represents the key-value pairs in an HTTP header.
// //
// The keys should be in canonical form, as returned by // The keys should be in canonical form, as returned by
// CanonicalHeaderKey. // [CanonicalHeaderKey].
type Header map[string][]string type Header map[string][]string
// Add adds the key, value pair to the header. // Add adds the key, value pair to the header.
// It appends to any existing values associated with key. // It appends to any existing values associated with key.
// The key is case insensitive; it is canonicalized by // The key is case insensitive; it is canonicalized by
// CanonicalHeaderKey. // [CanonicalHeaderKey].
func (h Header) Add(key, value string) { func (h Header) Add(key, value string) {
textproto.MIMEHeader(h).Add(key, value) textproto.MIMEHeader(h).Add(key, value)
} }
@@ -37,7 +37,7 @@ func (h Header) Add(key, value string) {
// Set sets the header entries associated with key to the // Set sets the header entries associated with key to the
// single element value. It replaces any existing values // single element value. It replaces any existing values
// associated with key. The key is case insensitive; it is // associated with key. The key is case insensitive; it is
// canonicalized by textproto.CanonicalMIMEHeaderKey. // canonicalized by [textproto.CanonicalMIMEHeaderKey].
// To use non-canonical keys, assign to the map directly. // To use non-canonical keys, assign to the map directly.
func (h Header) Set(key, value string) { func (h Header) Set(key, value string) {
textproto.MIMEHeader(h).Set(key, value) textproto.MIMEHeader(h).Set(key, value)
@@ -45,7 +45,7 @@ func (h Header) Set(key, value string) {
// Get gets the first value associated with the given key. If // Get gets the first value associated with the given key. If
// there are no values associated with the key, Get returns "". // there are no values associated with the key, Get returns "".
// It is case insensitive; textproto.CanonicalMIMEHeaderKey is // It is case insensitive; [textproto.CanonicalMIMEHeaderKey] is
// used to canonicalize the provided key. Get assumes that all // used to canonicalize the provided key. Get assumes that all
// keys are stored in canonical form. To use non-canonical keys, // keys are stored in canonical form. To use non-canonical keys,
// access the map directly. // access the map directly.
@@ -54,7 +54,7 @@ func (h Header) Get(key string) string {
} }
// Values returns all values associated with the given key. // Values returns all values associated with the given key.
// It is case insensitive; textproto.CanonicalMIMEHeaderKey is // It is case insensitive; [textproto.CanonicalMIMEHeaderKey] is
// used to canonicalize the provided key. To use non-canonical // used to canonicalize the provided key. To use non-canonical
// keys, access the map directly. // keys, access the map directly.
// The returned slice is not a copy. // The returned slice is not a copy.
@@ -79,7 +79,7 @@ func (h Header) has(key string) bool {
// Del deletes the values associated with key. // Del deletes the values associated with key.
// The key is case insensitive; it is canonicalized by // The key is case insensitive; it is canonicalized by
// CanonicalHeaderKey. // [CanonicalHeaderKey].
func (h Header) Del(key string) { func (h Header) Del(key string) {
textproto.MIMEHeader(h).Del(key) textproto.MIMEHeader(h).Del(key)
} }
@@ -128,7 +128,7 @@ var timeFormats = []string{
// ParseTime parses a time header (such as the Date: header), // ParseTime parses a time header (such as the Date: header),
// trying each of the three formats allowed by HTTP/1.1: // trying each of the three formats allowed by HTTP/1.1:
// TimeFormat, time.RFC850, and time.ANSIC. // [TimeFormat], [time.RFC850], and [time.ANSIC].
func ParseTime(text string) (t time.Time, err error) { func ParseTime(text string) (t time.Time, err error) {
for _, layout := range timeFormats { for _, layout := range timeFormats {
t, err = time.Parse(layout, text) t, err = time.Parse(layout, text)
@@ -155,17 +155,11 @@ type keyValues struct {
values []string values []string
} }
// A headerSorter implements sort.Interface by sorting a []keyValues // headerSorter contains a slice of keyValues sorted by keyValues.key.
// by key. It's used as a pointer, so it can fit in a sort.Interface
// interface value without allocation.
type headerSorter struct { type headerSorter struct {
kvs []keyValues kvs []keyValues
} }
func (s *headerSorter) Len() int { return len(s.kvs) }
func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] }
func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key }
var headerSorterPool = sync.Pool{ var headerSorterPool = sync.Pool{
New: func() any { return new(headerSorter) }, New: func() any { return new(headerSorter) },
} }
@@ -185,7 +179,7 @@ func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *h
} }
} }
hs.kvs = kvs hs.kvs = kvs
sort.Sort(hs) slices.SortFunc(hs.kvs, func(a, b keyValues) int { return strings.Compare(a.key, b.key) })
return kvs, hs return kvs, hs
} }
+148 -9
View File
@@ -1,10 +1,8 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:generate bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 golang.org/x/net/http2 //go:generate bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 -import=golang.org/x/net/internal/httpcommon=net/http/internal/httpcommon golang.org/x/net/http2
package http package http
@@ -18,6 +16,67 @@ import (
"golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpguts"
) )
// Protocols is a set of HTTP protocols.
// The zero value is an empty set of protocols.
//
// The supported protocols are:
//
// - HTTP1 is the HTTP/1.0 and HTTP/1.1 protocols.
// HTTP1 is supported on both unsecured TCP and secured TLS connections.
//
// - HTTP2 is the HTTP/2 protcol over a TLS connection.
//
// - UnencryptedHTTP2 is the HTTP/2 protocol over an unsecured TCP connection.
type Protocols struct {
bits uint8
}
const (
protoHTTP1 = 1 << iota
protoHTTP2
protoUnencryptedHTTP2
)
// HTTP1 reports whether p includes HTTP/1.
func (p Protocols) HTTP1() bool { return p.bits&protoHTTP1 != 0 }
// SetHTTP1 adds or removes HTTP/1 from p.
func (p *Protocols) SetHTTP1(ok bool) { p.setBit(protoHTTP1, ok) }
// HTTP2 reports whether p includes HTTP/2.
func (p Protocols) HTTP2() bool { return p.bits&protoHTTP2 != 0 }
// SetHTTP2 adds or removes HTTP/2 from p.
func (p *Protocols) SetHTTP2(ok bool) { p.setBit(protoHTTP2, ok) }
// UnencryptedHTTP2 reports whether p includes unencrypted HTTP/2.
func (p Protocols) UnencryptedHTTP2() bool { return p.bits&protoUnencryptedHTTP2 != 0 }
// SetUnencryptedHTTP2 adds or removes unencrypted HTTP/2 from p.
func (p *Protocols) SetUnencryptedHTTP2(ok bool) { p.setBit(protoUnencryptedHTTP2, ok) }
func (p *Protocols) setBit(bit uint8, ok bool) {
if ok {
p.bits |= bit
} else {
p.bits &^= bit
}
}
func (p Protocols) String() string {
var s []string
if p.HTTP1() {
s = append(s, "HTTP1")
}
if p.HTTP2() {
s = append(s, "HTTP2")
}
if p.UnencryptedHTTP2() {
s = append(s, "UnencryptedHTTP2")
}
return "{" + strings.Join(s, ",") + "}"
}
// incomparable is a zero-width, non-comparable type. Adding it to a struct // incomparable is a zero-width, non-comparable type. Adding it to a struct
// makes that struct also non-comparable, and generally doesn't add // makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first). // any size (as long as it's first).
@@ -60,8 +119,10 @@ func removeEmptyPort(host string) string {
return host return host
} }
func isNotToken(r rune) bool { // isToken reports whether v is a valid token (https://www.rfc-editor.org/rfc/rfc2616#section-2.2).
return !httpguts.IsTokenRune(r) func isToken(v string) bool {
// For historical reasons, this function is called ValidHeaderFieldName (see issue #67031).
return httpguts.ValidHeaderFieldName(v)
} }
// stringContainsCTLByte reports whether s contains any ASCII control character. // stringContainsCTLByte reports whether s contains any ASCII control character.
@@ -105,10 +166,10 @@ func hexEscapeNonASCII(s string) string {
return string(b) return string(b)
} }
// NoBody is an io.ReadCloser with no bytes. Read always returns EOF // NoBody is an [io.ReadCloser] with no bytes. Read always returns EOF
// and Close always returns nil. It can be used in an outgoing client // and Close always returns nil. It can be used in an outgoing client
// request to explicitly signal that a request has zero bytes. // request to explicitly signal that a request has zero bytes.
// An alternative, however, is to simply set Request.Body to nil. // An alternative, however, is to simply set [Request.Body] to nil.
var NoBody = noBody{} var NoBody = noBody{}
type noBody struct{} type noBody struct{}
@@ -123,7 +184,7 @@ var (
_ io.ReadCloser = NoBody _ io.ReadCloser = NoBody
) )
// PushOptions describes options for Pusher.Push. // PushOptions describes options for [Pusher.Push].
type PushOptions struct { type PushOptions struct {
// Method specifies the HTTP method for the promised request. // Method specifies the HTTP method for the promised request.
// If set, it must be "GET" or "HEAD". Empty means "GET". // If set, it must be "GET" or "HEAD". Empty means "GET".
@@ -165,3 +226,81 @@ type Pusher interface {
// is not supported on the underlying connection. // is not supported on the underlying connection.
Push(target string, opts *PushOptions) error Push(target string, opts *PushOptions) error
} }
// HTTP2Config defines HTTP/2 configuration parameters common to
// both [Transport] and [Server].
type HTTP2Config struct {
// MaxConcurrentStreams optionally specifies the number of
// concurrent streams that a client may have open at a time.
// If zero, MaxConcurrentStreams defaults to at least 100.
//
// This parameter only applies to Servers.
MaxConcurrentStreams int
// StrictMaxConcurrentRequests controls whether an HTTP/2 server's
// concurrency limit should be respected across all connections
// to that server.
// If true, new requests sent when a connection's concurrency limit
// has been exceeded will block until an existing request completes.
// If false, an additional connection will be opened if all
// existing connections are at their limit.
//
// This parameter only applies to Transports.
StrictMaxConcurrentRequests bool
// MaxDecoderHeaderTableSize optionally specifies an upper limit for the
// size of the header compression table used for decoding headers sent
// by the peer.
// A valid value is less than 4MiB.
// If zero or invalid, a default value is used.
MaxDecoderHeaderTableSize int
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
// header compression table used for sending headers to the peer.
// A valid value is less than 4MiB.
// If zero or invalid, a default value is used.
MaxEncoderHeaderTableSize int
// MaxReadFrameSize optionally specifies the largest frame
// this endpoint is willing to read.
// A valid value is between 16KiB and 16MiB, inclusive.
// If zero or invalid, a default value is used.
MaxReadFrameSize int
// MaxReceiveBufferPerConnection is the maximum size of the
// flow control window for data received on a connection.
// A valid value is at least 64KiB and less than 4MiB.
// If invalid, a default value is used.
MaxReceiveBufferPerConnection int
// MaxReceiveBufferPerStream is the maximum size of
// the flow control window for data received on a stream (request).
// A valid value is less than 4MiB.
// If zero or invalid, a default value is used.
MaxReceiveBufferPerStream int
// SendPingTimeout is the timeout after which a health check using a ping
// frame will be carried out if no frame is received on a connection.
// If zero, no health check is performed.
SendPingTimeout time.Duration
// PingTimeout is the timeout after which a connection will be closed
// if a response to a ping is not received.
// If zero, a default of 15 seconds is used.
PingTimeout time.Duration
// WriteByteTimeout is the timeout after which a connection will be
// closed if no data can be written to it. The timeout begins when data is
// available to write, and is extended whenever any bytes are written.
WriteByteTimeout time.Duration
// PermitProhibitedCipherSuites, if true, permits the use of
// cipher suites prohibited by the HTTP/2 spec.
PermitProhibitedCipherSuites bool
// CountError, if non-nil, is called on HTTP/2 errors.
// It is intended to increment a metric for monitoring.
// The errType contains only lowercase letters, digits, and underscores
// (a-z, 0-9, _).
CountError func(errType string)
}
+16 -6
View File
@@ -8,13 +8,19 @@ package httptest
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"io" "io"
"net/http" "net/http"
"strings" "strings"
) )
// NewRequest returns a new incoming server Request, suitable // NewRequest wraps NewRequestWithContext using context.Background.
// for passing to an http.Handler for testing. func NewRequest(method, target string, body io.Reader) *http.Request {
return NewRequestWithContext(context.Background(), method, target, body)
}
// NewRequestWithContext returns a new incoming server Request, suitable
// for passing to an [http.Handler] for testing.
// //
// The target is the RFC 7230 "request-target": it may be either a // The target is the RFC 7230 "request-target": it may be either a
// path or an absolute URL. If target is an absolute URL, the host name // path or an absolute URL. If target is an absolute URL, the host name
@@ -27,16 +33,16 @@ import (
// //
// An empty method means "GET". // An empty method means "GET".
// //
// The provided body may be nil. If the body is of type *bytes.Reader, // The provided body may be nil. If the body is of type [bytes.Reader],
// *strings.Reader, or *bytes.Buffer, the Request.ContentLength is // [strings.Reader], [bytes.Buffer], or the value [http.NoBody],
// set. // the Request.ContentLength is set.
// //
// NewRequest panics on error for ease of use in testing, where a // NewRequest panics on error for ease of use in testing, where a
// panic is acceptable. // panic is acceptable.
// //
// To generate a client HTTP request instead of a server request, see // To generate a client HTTP request instead of a server request, see
// the NewRequest function in the net/http package. // the NewRequest function in the net/http package.
func NewRequest(method, target string, body io.Reader) *http.Request { func NewRequestWithContext(ctx context.Context, method, target string, body io.Reader) *http.Request {
if method == "" { if method == "" {
method = "GET" method = "GET"
} }
@@ -44,6 +50,7 @@ func NewRequest(method, target string, body io.Reader) *http.Request {
if err != nil { if err != nil {
panic("invalid NewRequest arguments; " + err.Error()) panic("invalid NewRequest arguments; " + err.Error())
} }
req = req.WithContext(ctx)
// HTTP/1.0 was used above to avoid needing a Host field. Change it to 1.1 here. // HTTP/1.0 was used above to avoid needing a Host field. Change it to 1.1 here.
req.Proto = "HTTP/1.1" req.Proto = "HTTP/1.1"
@@ -61,6 +68,9 @@ func NewRequest(method, target string, body io.Reader) *http.Request {
default: default:
req.ContentLength = -1 req.ContentLength = -1
} }
if body == http.NoBody {
req.ContentLength = 0
}
if rc, ok := body.(io.ReadCloser); ok { if rc, ok := body.(io.ReadCloser); ok {
req.Body = rc req.Body = rc
} else { } else {
+35 -47
View File
@@ -12,9 +12,11 @@ import (
"net/textproto" "net/textproto"
"strconv" "strconv"
"strings" "strings"
"golang.org/x/net/http/httpguts"
) )
// ResponseRecorder is an implementation of http.ResponseWriter that // ResponseRecorder is an implementation of [http.ResponseWriter] that
// records its mutations for later inspection in tests. // records its mutations for later inspection in tests.
type ResponseRecorder struct { type ResponseRecorder struct {
// Code is the HTTP response code set by WriteHeader. // Code is the HTTP response code set by WriteHeader.
@@ -45,7 +47,7 @@ type ResponseRecorder struct {
wroteHeader bool wroteHeader bool
} }
// NewRecorder returns an initialized ResponseRecorder. // NewRecorder returns an initialized [ResponseRecorder].
func NewRecorder() *ResponseRecorder { func NewRecorder() *ResponseRecorder {
return &ResponseRecorder{ return &ResponseRecorder{
HeaderMap: make(http.Header), HeaderMap: make(http.Header),
@@ -55,12 +57,12 @@ func NewRecorder() *ResponseRecorder {
} }
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if // DefaultRemoteAddr is the default remote address to return in RemoteAddr if
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder. // an explicit DefaultRemoteAddr isn't set on [ResponseRecorder].
const DefaultRemoteAddr = "1.2.3.4" const DefaultRemoteAddr = "1.2.3.4"
// Header implements http.ResponseWriter. It returns the response // Header implements [http.ResponseWriter]. It returns the response
// headers to mutate within a handler. To test the headers that were // headers to mutate within a handler. To test the headers that were
// written after a handler completes, use the Result method and see // written after a handler completes, use the [ResponseRecorder.Result] method and see
// the returned Response value's Header. // the returned Response value's Header.
func (rw *ResponseRecorder) Header() http.Header { func (rw *ResponseRecorder) Header() http.Header {
m := rw.HeaderMap m := rw.HeaderMap
@@ -103,23 +105,45 @@ func (rw *ResponseRecorder) writeHeader(b []byte, str string) {
// Write implements http.ResponseWriter. The data in buf is written to // Write implements http.ResponseWriter. The data in buf is written to
// rw.Body, if not nil. // rw.Body, if not nil.
func (rw *ResponseRecorder) Write(buf []byte) (int, error) { func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
// Record the write, even if we're going to return an error.
rw.writeHeader(buf, "") rw.writeHeader(buf, "")
if rw.Body != nil { if rw.Body != nil {
rw.Body.Write(buf) rw.Body.Write(buf)
} }
if !bodyAllowedForStatus(rw.Code) {
return 0, http.ErrBodyNotAllowed
}
return len(buf), nil return len(buf), nil
} }
// WriteString implements io.StringWriter. The data in str is written // WriteString implements [io.StringWriter]. The data in str is written
// to rw.Body, if not nil. // to rw.Body, if not nil.
func (rw *ResponseRecorder) WriteString(str string) (int, error) { func (rw *ResponseRecorder) WriteString(str string) (int, error) {
// Record the write, even if we're going to return an error.
rw.writeHeader(nil, str) rw.writeHeader(nil, str)
if rw.Body != nil { if rw.Body != nil {
rw.Body.WriteString(str) rw.Body.WriteString(str)
} }
if !bodyAllowedForStatus(rw.Code) {
return 0, http.ErrBodyNotAllowed
}
return len(str), nil return len(str), nil
} }
// bodyAllowedForStatus reports whether a given response status code
// permits a body. See RFC 7230, section 3.3.
func bodyAllowedForStatus(status int) bool {
switch {
case status >= 100 && status <= 199:
return false
case status == 204:
return false
case status == 304:
return false
}
return true
}
func checkWriteHeaderCode(code int) { func checkWriteHeaderCode(code int) {
// Issue 22880: require valid WriteHeader status codes. // Issue 22880: require valid WriteHeader status codes.
// For now we only enforce that it's three digits. // For now we only enforce that it's three digits.
@@ -137,7 +161,7 @@ func checkWriteHeaderCode(code int) {
} }
} }
// WriteHeader implements http.ResponseWriter. // WriteHeader implements [http.ResponseWriter].
func (rw *ResponseRecorder) WriteHeader(code int) { func (rw *ResponseRecorder) WriteHeader(code int) {
if rw.wroteHeader { if rw.wroteHeader {
return return
@@ -152,7 +176,7 @@ func (rw *ResponseRecorder) WriteHeader(code int) {
rw.snapHeader = rw.HeaderMap.Clone() rw.snapHeader = rw.HeaderMap.Clone()
} }
// Flush implements http.Flusher. To test whether Flush was // Flush implements [http.Flusher]. To test whether Flush was
// called, see rw.Flushed. // called, see rw.Flushed.
func (rw *ResponseRecorder) Flush() { func (rw *ResponseRecorder) Flush() {
if !rw.wroteHeader { if !rw.wroteHeader {
@@ -173,7 +197,7 @@ func (rw *ResponseRecorder) Flush() {
// did a write. // did a write.
// //
// The Response.Body is guaranteed to be non-nil and Body.Read call is // The Response.Body is guaranteed to be non-nil and Body.Read call is
// guaranteed to not return any error other than io.EOF. // guaranteed to not return any error other than [io.EOF].
// //
// Result must only be called after the handler has finished running. // Result must only be called after the handler has finished running.
func (rw *ResponseRecorder) Result() *http.Response { func (rw *ResponseRecorder) Result() *http.Response {
@@ -205,9 +229,9 @@ func (rw *ResponseRecorder) Result() *http.Response {
if trailers, ok := rw.snapHeader["Trailer"]; ok { if trailers, ok := rw.snapHeader["Trailer"]; ok {
res.Trailer = make(http.Header, len(trailers)) res.Trailer = make(http.Header, len(trailers))
for _, k := range trailers { for _, k := range trailers {
for _, k := range strings.Split(k, ",") { for k := range strings.SplitSeq(k, ",") {
k = http.CanonicalHeaderKey(textproto.TrimString(k)) k = http.CanonicalHeaderKey(textproto.TrimString(k))
if !validTrailerHeader(k) { if !httpguts.ValidTrailerHeader(k) {
// Ignore since forbidden by RFC 7230, section 4.1.2. // Ignore since forbidden by RFC 7230, section 4.1.2.
continue continue
} }
@@ -251,39 +275,3 @@ func parseContentLength(cl string) int64 {
} }
return int64(n) return int64(n)
} }
// ValidTrailerHeader reports whether name is a valid header field name to appear
// in trailers.
// See RFC 7230, Section 4.1.2
// Copied from golang.org/x/net/http/httpguts
func validTrailerHeader(name string) bool {
name = textproto.CanonicalMIMEHeaderKey(name)
if strings.HasPrefix(name, "If-") || badTrailer[name] {
return false
}
return true
}
var badTrailer = map[string]bool{
"Authorization": true,
"Cache-Control": true,
"Connection": true,
"Content-Encoding": true,
"Content-Length": true,
"Content-Range": true,
"Content-Type": true,
"Expect": true,
"Host": true,
"Keep-Alive": true,
"Max-Forwards": true,
"Pragma": true,
"Proxy-Authenticate": true,
"Proxy-Authorization": true,
"Proxy-Connection": true,
"Range": true,
"Realm": true,
"Te": true,
"Trailer": true,
"Transfer-Encoding": true,
"Www-Authenticate": true,
}
+8 -4
View File
@@ -71,7 +71,7 @@ func newLocalListener() net.Listener {
// When debugging a particular http server-based test, // When debugging a particular http server-based test,
// this flag lets you run // this flag lets you run
// //
// go test -run=BrokenTest -httptest.serve=127.0.0.1:8000 // go test -run='^BrokenTest$' -httptest.serve=127.0.0.1:8000
// //
// to start the broken server so you can interact with it manually. // to start the broken server so you can interact with it manually.
// We only register this flag if it looks like the caller knows about it // We only register this flag if it looks like the caller knows about it
@@ -94,7 +94,7 @@ func strSliceContainsPrefix(v []string, pre string) bool {
return false return false
} }
// NewServer starts and returns a new Server. // NewServer starts and returns a new [Server].
// The caller should call Close when finished, to shut it down. // The caller should call Close when finished, to shut it down.
func NewServer(handler http.Handler) *Server { func NewServer(handler http.Handler) *Server {
ts := NewUnstartedServer(handler) ts := NewUnstartedServer(handler)
@@ -102,7 +102,7 @@ func NewServer(handler http.Handler) *Server {
return ts return ts
} }
// NewUnstartedServer returns a new Server but doesn't start it. // NewUnstartedServer returns a new [Server] but doesn't start it.
// //
// After changing its configuration, the caller should call Start or // After changing its configuration, the caller should call Start or
// StartTLS. // StartTLS.
@@ -120,6 +120,7 @@ func (s *Server) Start() {
if s.URL != "" { if s.URL != "" {
panic("Server already started") panic("Server already started")
} }
if s.client == nil { if s.client == nil {
// TINYGO: Removed transport // TINYGO: Removed transport
s.client = &http.Client{} s.client = &http.Client{}
@@ -216,7 +217,10 @@ func (s *Server) CloseClientConnections() {
// Client returns an HTTP client configured for making requests to the server. // Client returns an HTTP client configured for making requests to the server.
// It is configured to trust the server's TLS test certificate and will // It is configured to trust the server's TLS test certificate and will
// close its idle connections on Server.Close. // close its idle connections on [Server.Close].
// Use Server.URL as the base URL to send requests to the server.
// The returned client will also redirect any requests to "example.com"
// or its subdomains to the server.
func (s *Server) Client() *http.Client { func (s *Server) Client() *http.Client {
return s.client return s.client
} }
+2 -2
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied and modified from Go 1.21.5 official implementation. // TINYGO: The following is copied and modified from Go 1.26.2 official implementation.
// Copyright 2016 The Go Authors. All rights reserved. // Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -54,7 +54,7 @@ func WithClientTrace(ctx context.Context, trace *ClientTrace) context.Context {
// during a single round trip and has no hooks that span a series // during a single round trip and has no hooks that span a series
// of redirected requests. // of redirected requests.
// //
// See https://blog.golang.org/http-tracing for more. // See https://go.dev/blog/http-tracing for more.
type ClientTrace struct { type ClientTrace struct {
// GetConn is called before a connection is created or // GetConn is called before a connection is created or
// retrieved from an idle pool. The hostPort is the // retrieved from an idle pool. The hostPort is the
+6 -9
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.5 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -73,8 +71,8 @@ func outgoingLength(req *http.Request) int64 {
return -1 return -1
} }
// DumpRequestOut is like DumpRequest but for outgoing client requests. It // DumpRequestOut is like [DumpRequest] but for outgoing client requests. It
// includes any headers that the standard http.Transport adds, such as // includes any headers that the standard [http.Transport] adds, such as
// User-Agent. // User-Agent.
func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
save := req.Body save := req.Body
@@ -149,7 +147,6 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
req.Body = save req.Body = save
if err != nil { if err != nil {
pw.Close()
dr.err = err dr.err = err
close(quitReadCh) close(quitReadCh)
return nil, err return nil, err
@@ -205,17 +202,17 @@ var reqWriteExcludeHeaderDump = map[string]bool{
// representation. It should only be used by servers to debug client // representation. It should only be used by servers to debug client
// requests. The returned representation is an approximation only; // requests. The returned representation is an approximation only;
// some details of the initial request are lost while parsing it into // some details of the initial request are lost while parsing it into
// an http.Request. In particular, the order and case of header field // an [http.Request]. In particular, the order and case of header field
// names are lost. The order of values in multi-valued headers is kept // names are lost. The order of values in multi-valued headers is kept
// intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their // intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their
// original binary representations. // original binary representations.
// //
// If body is true, DumpRequest also returns the body. To do so, it // If body is true, DumpRequest also returns the body. To do so, it
// consumes req.Body and then replaces it with a new io.ReadCloser // consumes req.Body and then replaces it with a new [io.ReadCloser]
// that yields the same bytes. If DumpRequest returns an error, // that yields the same bytes. If DumpRequest returns an error,
// the state of req is undefined. // the state of req is undefined.
// //
// The documentation for http.Request.Write details which fields // The documentation for [http.Request.Write] details which fields
// of req are included in the dump. // of req are included in the dump.
func DumpRequest(req *http.Request, body bool) ([]byte, error) { func DumpRequest(req *http.Request, body bool) ([]byte, error) {
var err error var err error
+2 -4
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.5 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -15,7 +13,7 @@ import (
// NewChunkedReader returns a new chunkedReader that translates the data read from r // NewChunkedReader returns a new chunkedReader that translates the data read from r
// out of HTTP "chunked" format before returning it. // out of HTTP "chunked" format before returning it.
// The chunkedReader returns io.EOF when the final 0-length chunk is read. // The chunkedReader returns [io.EOF] when the final 0-length chunk is read.
// //
// NewChunkedReader is not needed by normal applications. The http package // NewChunkedReader is not needed by normal applications. The http package
// automatically decodes chunking when reading response bodies. // automatically decodes chunking when reading response bodies.
+17 -19
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.5 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -35,7 +33,7 @@ var errClosed = errors.New("i/o operation on closed connection")
// It is low-level, old, and unused by Go's current HTTP stack. // It is low-level, old, and unused by Go's current HTTP stack.
// We should have deleted it before Go 1. // We should have deleted it before Go 1.
// //
// Deprecated: Use the Server in package net/http instead. // Deprecated: Use the Server in package [net/http] instead.
type ServerConn struct { type ServerConn struct {
mu sync.Mutex // read-write protects the following fields mu sync.Mutex // read-write protects the following fields
c net.Conn c net.Conn
@@ -52,7 +50,7 @@ type ServerConn struct {
// It is low-level, old, and unused by Go's current HTTP stack. // It is low-level, old, and unused by Go's current HTTP stack.
// We should have deleted it before Go 1. // We should have deleted it before Go 1.
// //
// Deprecated: Use the Server in package net/http instead. // Deprecated: Use the Server in package [net/http] instead.
func NewServerConn(c net.Conn, r *bufio.Reader) *ServerConn { func NewServerConn(c net.Conn, r *bufio.Reader) *ServerConn {
if r == nil { if r == nil {
r = bufio.NewReader(c) r = bufio.NewReader(c)
@@ -60,10 +58,10 @@ func NewServerConn(c net.Conn, r *bufio.Reader) *ServerConn {
return &ServerConn{c: c, r: r, pipereq: make(map[*http.Request]uint)} return &ServerConn{c: c, r: r, pipereq: make(map[*http.Request]uint)}
} }
// Hijack detaches the ServerConn and returns the underlying connection as well // Hijack detaches the [ServerConn] and returns the underlying connection as well
// as the read-side bufio which may have some left over data. Hijack may be // as the read-side bufio which may have some left over data. Hijack may be
// called before Read has signaled the end of the keep-alive logic. The user // called before Read has signaled the end of the keep-alive logic. The user
// should not call Hijack while Read or Write is in progress. // should not call Hijack while [ServerConn.Read] or [ServerConn.Write] is in progress.
func (sc *ServerConn) Hijack() (net.Conn, *bufio.Reader) { func (sc *ServerConn) Hijack() (net.Conn, *bufio.Reader) {
sc.mu.Lock() sc.mu.Lock()
defer sc.mu.Unlock() defer sc.mu.Unlock()
@@ -74,7 +72,7 @@ func (sc *ServerConn) Hijack() (net.Conn, *bufio.Reader) {
return c, r return c, r
} }
// Close calls Hijack and then also closes the underlying connection. // Close calls [ServerConn.Hijack] and then also closes the underlying connection.
func (sc *ServerConn) Close() error { func (sc *ServerConn) Close() error {
c, _ := sc.Hijack() c, _ := sc.Hijack()
if c != nil { if c != nil {
@@ -83,7 +81,7 @@ func (sc *ServerConn) Close() error {
return nil return nil
} }
// Read returns the next request on the wire. An ErrPersistEOF is returned if // Read returns the next request on the wire. An [ErrPersistEOF] is returned if
// it is gracefully determined that there are no more requests (e.g. after the // it is gracefully determined that there are no more requests (e.g. after the
// first request on an HTTP/1.0 connection, or after a Connection:close on a // first request on an HTTP/1.0 connection, or after a Connection:close on a
// HTTP/1.1 connection). // HTTP/1.1 connection).
@@ -173,7 +171,7 @@ func (sc *ServerConn) Pending() int {
// Write writes resp in response to req. To close the connection gracefully, set the // Write writes resp in response to req. To close the connection gracefully, set the
// Response.Close field to true. Write should be considered operational until // Response.Close field to true. Write should be considered operational until
// it returns an error, regardless of any errors returned on the Read side. // it returns an error, regardless of any errors returned on the [ServerConn.Read] side.
func (sc *ServerConn) Write(req *http.Request, resp *http.Response) error { func (sc *ServerConn) Write(req *http.Request, resp *http.Response) error {
// Retrieve the pipeline ID of this request/response pair // Retrieve the pipeline ID of this request/response pair
@@ -228,7 +226,7 @@ func (sc *ServerConn) Write(req *http.Request, resp *http.Response) error {
// It is low-level, old, and unused by Go's current HTTP stack. // It is low-level, old, and unused by Go's current HTTP stack.
// We should have deleted it before Go 1. // We should have deleted it before Go 1.
// //
// Deprecated: Use Client or Transport in package net/http instead. // Deprecated: Use Client or Transport in package [net/http] instead.
type ClientConn struct { type ClientConn struct {
mu sync.Mutex // read-write protects the following fields mu sync.Mutex // read-write protects the following fields
c net.Conn c net.Conn
@@ -246,7 +244,7 @@ type ClientConn struct {
// It is low-level, old, and unused by Go's current HTTP stack. // It is low-level, old, and unused by Go's current HTTP stack.
// We should have deleted it before Go 1. // We should have deleted it before Go 1.
// //
// Deprecated: Use the Client or Transport in package net/http instead. // Deprecated: Use the Client or Transport in package [net/http] instead.
func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn { func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn {
if r == nil { if r == nil {
r = bufio.NewReader(c) r = bufio.NewReader(c)
@@ -263,17 +261,17 @@ func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn {
// It is low-level, old, and unused by Go's current HTTP stack. // It is low-level, old, and unused by Go's current HTTP stack.
// We should have deleted it before Go 1. // We should have deleted it before Go 1.
// //
// Deprecated: Use the Client or Transport in package net/http instead. // Deprecated: Use the Client or Transport in package [net/http] instead.
func NewProxyClientConn(c net.Conn, r *bufio.Reader) *ClientConn { func NewProxyClientConn(c net.Conn, r *bufio.Reader) *ClientConn {
cc := NewClientConn(c, r) cc := NewClientConn(c, r)
cc.writeReq = (*http.Request).WriteProxy cc.writeReq = (*http.Request).WriteProxy
return cc return cc
} }
// Hijack detaches the ClientConn and returns the underlying connection as well // Hijack detaches the [ClientConn] and returns the underlying connection as well
// as the read-side bufio which may have some left over data. Hijack may be // as the read-side bufio which may have some left over data. Hijack may be
// called before the user or Read have signaled the end of the keep-alive // called before the user or Read have signaled the end of the keep-alive
// logic. The user should not call Hijack while Read or Write is in progress. // logic. The user should not call Hijack while [ClientConn.Read] or ClientConn.Write is in progress.
func (cc *ClientConn) Hijack() (c net.Conn, r *bufio.Reader) { func (cc *ClientConn) Hijack() (c net.Conn, r *bufio.Reader) {
cc.mu.Lock() cc.mu.Lock()
defer cc.mu.Unlock() defer cc.mu.Unlock()
@@ -284,7 +282,7 @@ func (cc *ClientConn) Hijack() (c net.Conn, r *bufio.Reader) {
return return
} }
// Close calls Hijack and then also closes the underlying connection. // Close calls [ClientConn.Hijack] and then also closes the underlying connection.
func (cc *ClientConn) Close() error { func (cc *ClientConn) Close() error {
c, _ := cc.Hijack() c, _ := cc.Hijack()
if c != nil { if c != nil {
@@ -293,7 +291,7 @@ func (cc *ClientConn) Close() error {
return nil return nil
} }
// Write writes a request. An ErrPersistEOF error is returned if the connection // Write writes a request. An [ErrPersistEOF] error is returned if the connection
// has been closed in an HTTP keep-alive sense. If req.Close equals true, the // has been closed in an HTTP keep-alive sense. If req.Close equals true, the
// keep-alive connection is logically closed after this request and the opposing // keep-alive connection is logically closed after this request and the opposing
// server is informed. An ErrUnexpectedEOF indicates the remote closed the // server is informed. An ErrUnexpectedEOF indicates the remote closed the
@@ -359,9 +357,9 @@ func (cc *ClientConn) Pending() int {
} }
// Read reads the next response from the wire. A valid response might be // Read reads the next response from the wire. A valid response might be
// returned together with an ErrPersistEOF, which means that the remote // returned together with an [ErrPersistEOF], which means that the remote
// requested that this be the last request serviced. Read can be called // requested that this be the last request serviced. Read can be called
// concurrently with Write, but not with another Read. // concurrently with [ClientConn.Write], but not with another Read.
func (cc *ClientConn) Read(req *http.Request) (resp *http.Response, err error) { func (cc *ClientConn) Read(req *http.Request) (resp *http.Response, err error) {
// Retrieve the pipeline ID of this request/response pair // Retrieve the pipeline ID of this request/response pair
cc.mu.Lock() cc.mu.Lock()
+185 -48
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.5 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -23,12 +21,13 @@ import (
"net/url" "net/url"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpguts"
) )
// A ProxyRequest contains a request to be rewritten by a ReverseProxy. // A ProxyRequest contains a request to be rewritten by a [ReverseProxy].
type ProxyRequest struct { type ProxyRequest struct {
// In is the request received by the proxy. // In is the request received by the proxy.
// The Rewrite function must not modify In. // The Rewrite function must not modify In.
@@ -44,10 +43,12 @@ type ProxyRequest struct {
// SetURL routes the outbound request to the scheme, host, and base path // SetURL routes the outbound request to the scheme, host, and base path
// provided in target. If the target's path is "/base" and the incoming // provided in target. If the target's path is "/base" and the incoming
// request was for "/dir", the target request will be for "/base/dir". // request was for "/dir", the target request will be for "/base/dir".
// To route requests without joining the incoming path,
// set r.Out.URL directly.
// //
// SetURL rewrites the outbound Host header to match the target's host. // SetURL rewrites the outbound Host header to match the target's host.
// To preserve the inbound request's Host header (the default behavior // To preserve the inbound request's Host header (the default behavior
// of NewSingleHostReverseProxy): // of [NewSingleHostReverseProxy]):
// //
// rewriteFunc := func(r *httputil.ProxyRequest) { // rewriteFunc := func(r *httputil.ProxyRequest) {
// r.SetURL(url) // r.SetURL(url)
@@ -70,7 +71,7 @@ func (r *ProxyRequest) SetURL(target *url.URL) {
// If the outbound request contains an existing X-Forwarded-For header, // If the outbound request contains an existing X-Forwarded-For header,
// SetXForwarded appends the client IP address to it. To append to the // SetXForwarded appends the client IP address to it. To append to the
// inbound request's X-Forwarded-For header (the default behavior of // inbound request's X-Forwarded-For header (the default behavior of
// ReverseProxy when using a Director function), copy the header // [ReverseProxy] when using a Director function), copy the header
// from the inbound request before calling SetXForwarded: // from the inbound request before calling SetXForwarded:
// //
// rewriteFunc := func(r *httputil.ProxyRequest) { // rewriteFunc := func(r *httputil.ProxyRequest) {
@@ -102,6 +103,13 @@ func (r *ProxyRequest) SetXForwarded() {
// //
// 1xx responses are forwarded to the client if the underlying // 1xx responses are forwarded to the client if the underlying
// transport supports ClientTrace.Got1xxResponse. // transport supports ClientTrace.Got1xxResponse.
//
// Hop-by-hop headers (see RFC 9110, section 7.6.1), including
// Connection, Proxy-Connection, Keep-Alive, Proxy-Authenticate,
// Proxy-Authorization, TE, Trailer, Transfer-Encoding, and Upgrade,
// are removed from client requests and backend responses.
// The Rewrite function may be used to add hop-by-hop headers to the request,
// and the ModifyResponse function may be used to remove them from the response.
type ReverseProxy struct { type ReverseProxy struct {
// Rewrite must be a function which modifies // Rewrite must be a function which modifies
// the request into a new request to be sent // the request into a new request to be sent
@@ -126,36 +134,6 @@ type ReverseProxy struct {
// At most one of Rewrite or Director may be set. // At most one of Rewrite or Director may be set.
Rewrite func(*ProxyRequest) Rewrite func(*ProxyRequest)
// Director is a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Director must not access the provided Request
// after returning.
//
// By default, the X-Forwarded-For header is set to the
// value of the client IP address. If an X-Forwarded-For
// header already exists, the client IP is appended to the
// existing values. As a special case, if the header
// exists in the Request.Header map but has a nil value
// (such as when set by the Director func), the X-Forwarded-For
// header is not modified.
//
// To prevent IP spoofing, be sure to delete any pre-existing
// X-Forwarded-For header coming from the client or
// an untrusted proxy.
//
// Hop-by-hop headers are removed from the request after
// Director returns, which can remove headers added by
// Director. Use a Rewrite function instead to ensure
// modifications to the request are preserved.
//
// Unparsable query parameters are removed from the outbound
// request if Request.Form is set after Director returns.
//
// At most one of Rewrite or Director may be set.
Director func(*http.Request)
// The transport used to perform proxy requests. // The transport used to perform proxy requests.
// If nil, http.DefaultTransport is used. // If nil, http.DefaultTransport is used.
Transport http.RoundTripper Transport http.RoundTripper
@@ -188,6 +166,10 @@ type ReverseProxy struct {
// If the backend is unreachable, the optional ErrorHandler is // If the backend is unreachable, the optional ErrorHandler is
// called without any call to ModifyResponse. // called without any call to ModifyResponse.
// //
// Hop-by-hop headers are removed from the response before
// calling ModifyResponse. ModifyResponse may need to remove
// additional headers to fit its deployment model, such as Alt-Svc.
//
// If ModifyResponse returns an error, ErrorHandler is called // If ModifyResponse returns an error, ErrorHandler is called
// with its error value. If ErrorHandler is nil, its default // with its error value. If ErrorHandler is nil, its default
// implementation is used. // implementation is used.
@@ -199,10 +181,92 @@ type ReverseProxy struct {
// If nil, the default is to log the provided error and return // If nil, the default is to log the provided error and return
// a 502 Status Bad Gateway response. // a 502 Status Bad Gateway response.
ErrorHandler func(http.ResponseWriter, *http.Request, error) ErrorHandler func(http.ResponseWriter, *http.Request, error)
// Director is deprecated. Use Rewrite instead.
//
// This function is insecure:
//
// - Hop-by-hop headers are removed from the request after Director
// returns, which can remove headers added by Director.
// A client can designate headers as hop-by-hop by listing them
// in the Connection header, so this permits a malicious client
// to remove any headers that may be added by Director.
//
// - X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto
// headers in inbound requests are preserved by default,
// which can permit IP spoofing if the Director function is
// not careful to remove these headers.
//
// Rewrite addresses these issues.
//
// As an example of converting a Director function to Rewrite:
//
// // ReverseProxy with a Director function.
// proxy := &httputil.ReverseProxy{
// Director: func(req *http.Request) {
// req.URL.Scheme = "https"
// req.URL.Host = proxyHost
//
// // A malicious client can remove this header.
// req.Header.Set("Some-Header", "some-header-value")
//
// // X-Forwarded-* headers sent by the client are preserved,
// // since Director did not remove them.
// },
// }
//
// // ReverseProxy with a Rewrite function.
// proxy := &httputil.ReverseProxy{
// Rewrite: func(preq *httputil.ProxyRequest) {
// // See also ProxyRequest.SetURL.
// preq.Out.URL.Scheme = "https"
// preq.Out.URL.Host = proxyHost
//
// // This header cannot be affected by a malicious client.
// preq.Out.Header.Set("Some-Header", "some-header-value")
//
// // X-Forwarded- headers sent by the client have been
// // removed from preq.Out.
// // ProxyRequest.SetXForwarded optionally adds new ones.
// preq.SetXForwarded()
// },
// }
//
// Director is a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Director must not access the provided Request
// after returning.
//
// By default, the X-Forwarded-For header is set to the
// value of the client IP address. If an X-Forwarded-For
// header already exists, the client IP is appended to the
// existing values. As a special case, if the header
// exists in the Request.Header map but has a nil value
// (such as when set by the Director func), the X-Forwarded-For
// header is not modified.
//
// To prevent IP spoofing, be sure to delete any pre-existing
// X-Forwarded-For header coming from the client or
// an untrusted proxy.
//
// Hop-by-hop headers are removed from the request after
// Director returns, which can remove headers added by
// Director. Use a Rewrite function instead to ensure
// modifications to the request are preserved.
//
// Unparsable query parameters are removed from the outbound
// request if Request.Form is set after Director returns.
//
// At most one of Rewrite or Director may be set.
//
// Deprecated: Use Rewrite instead.
Director func(*http.Request)
} }
// A BufferPool is an interface for getting and returning temporary // A BufferPool is an interface for getting and returning temporary
// byte slices for use by io.CopyBuffer. // byte slices for use by [io.CopyBuffer].
type BufferPool interface { type BufferPool interface {
Get() []byte Get() []byte
Put([]byte) Put([]byte)
@@ -241,13 +305,17 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
return a.Path + b.Path, apath + bpath return a.Path + b.Path, apath + bpath
} }
// NewSingleHostReverseProxy returns a new ReverseProxy that routes // NewSingleHostReverseProxy returns a new [ReverseProxy] that routes
// URLs to the scheme, host, and base path provided in target. If the // URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir", // target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir. // the target request will be for /base/dir.
// //
// NewSingleHostReverseProxy does not rewrite the Host header. // NewSingleHostReverseProxy does not rewrite the Host header.
// //
// For backwards compatibility reasons, NewSingleHostReverseProxy
// returns a ReverseProxy using the deprecated Director function.
// This proxy preserves X-Forwarded-* headers sent by the client.
//
// To customize the ReverseProxy behavior beyond what // To customize the ReverseProxy behavior beyond what
// NewSingleHostReverseProxy provides, use ReverseProxy directly // NewSingleHostReverseProxy provides, use ReverseProxy directly
// with a Rewrite function. The ProxyRequest SetURL method // with a Rewrite function. The ProxyRequest SetURL method
@@ -368,6 +436,18 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
} }
if outreq.Body != nil { if outreq.Body != nil {
// Wrap the body in a reader where Close does nothing. This is done
// because p.Transport.RoundTrip would close the reverse proxy's
// outbound request body if it fails to connect to upstream. If we do
// not wrap the body, when we close the reverse proxy's outbound
// request, it will also close the reverse proxy's inbound request body
// (i.e. the client's outbound request body). This is because
// http.(*Request).Clone creates a shallow copy of the body. This can
// cause an infinite hang in cases where the body is not yet received
// from the client (e.g. 100-continue requests): Close, which
// internally tries to consume the body content, would be called too
// early and would hang.
outreq.Body = &noopCloseReader{readCloser: outreq.Body}
// Reading from the request body after returning from a handler is not // Reading from the request body after returning from a handler is not
// allowed, and the RoundTrip goroutine that reads the Body can outlive // allowed, and the RoundTrip goroutine that reads the Body can outlive
// this handler. This can lead to a crash if the handler panics (see // this handler. This can lead to a crash if the handler panics (see
@@ -456,23 +536,34 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
outreq.Header.Set("User-Agent", "") outreq.Header.Set("User-Agent", "")
} }
var (
roundTripMutex sync.Mutex
roundTripDone bool
)
trace := &httptrace.ClientTrace{ trace := &httptrace.ClientTrace{
Got1xxResponse: func(code int, header textproto.MIMEHeader) error { Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
roundTripMutex.Lock()
defer roundTripMutex.Unlock()
if roundTripDone {
// If RoundTrip has returned, don't try to further modify
// the ResponseWriter's header map.
return nil
}
h := rw.Header() h := rw.Header()
copyHeader(h, http.Header(header)) copyHeader(h, http.Header(header))
rw.WriteHeader(code) rw.WriteHeader(code)
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses // Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
for k := range h { clear(h)
delete(h, k)
}
return nil return nil
}, },
} }
outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace)) outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace))
res, err := transport.RoundTrip(outreq) res, err := transport.RoundTrip(outreq)
roundTripMutex.Lock()
roundTripDone = true
roundTripMutex.Unlock()
if err != nil { if err != nil {
p.getErrorHandler()(rw, outreq, err) p.getErrorHandler()(rw, outreq, err)
return return
@@ -568,7 +659,7 @@ func shouldPanicOnCopyError(req *http.Request) bool {
func removeHopByHopHeaders(h http.Header) { func removeHopByHopHeaders(h http.Header) {
// RFC 7230, section 6.1: Remove headers listed in the "Connection" header. // RFC 7230, section 6.1: Remove headers listed in the "Connection" header.
for _, f := range h["Connection"] { for _, f := range h["Connection"] {
for _, sf := range strings.Split(f, ",") { for sf := range strings.SplitSeq(f, ",") {
if sf = textproto.TrimString(sf); sf != "" { if sf = textproto.TrimString(sf); sf != "" {
h.Del(sf) h.Del(sf)
} }
@@ -730,6 +821,7 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
resUpType := upgradeType(res.Header) resUpType := upgradeType(res.Header)
if !ascii.IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller. if !ascii.IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller.
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType)) p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType))
return
} }
if !ascii.EqualFold(reqUpType, resUpType) { if !ascii.EqualFold(reqUpType, resUpType) {
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)) p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
@@ -783,9 +875,17 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
spc := switchProtocolCopier{user: conn, backend: backConn} spc := switchProtocolCopier{user: conn, backend: backConn}
go spc.copyToBackend(errc) go spc.copyToBackend(errc)
go spc.copyFromBackend(errc) go spc.copyFromBackend(errc)
<-errc
// Wait until both copy functions have sent on the error channel,
// or until one fails.
err := <-errc
if err == nil {
err = <-errc
}
} }
var errCopyDone = errors.New("hijacked connection copy complete")
// switchProtocolCopier exists so goroutines proxying data back and // switchProtocolCopier exists so goroutines proxying data back and
// forth have nice names in stacks. // forth have nice names in stacks.
type switchProtocolCopier struct { type switchProtocolCopier struct {
@@ -793,13 +893,33 @@ type switchProtocolCopier struct {
} }
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) { func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
_, err := io.Copy(c.user, c.backend) if _, err := io.Copy(c.user, c.backend); err != nil {
errc <- err errc <- err
return
}
// backend conn has reached EOF so propogate close write to user conn
if wc, ok := c.user.(interface{ CloseWrite() error }); ok {
errc <- wc.CloseWrite()
return
}
errc <- errCopyDone
} }
func (c switchProtocolCopier) copyToBackend(errc chan<- error) { func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
_, err := io.Copy(c.backend, c.user) if _, err := io.Copy(c.backend, c.user); err != nil {
errc <- err errc <- err
return
}
// user conn has reached EOF so propogate close write to backend conn
if wc, ok := c.backend.(interface{ CloseWrite() error }); ok {
errc <- wc.CloseWrite()
return
}
errc <- errCopyDone
} }
func cleanQueryParams(s string) string { func cleanQueryParams(s string) string {
@@ -834,3 +954,20 @@ func ishex(c byte) bool {
} }
return false return false
} }
type noopCloseReader struct {
readCloser io.ReadCloser
closed atomic.Bool
}
func (ncr *noopCloseReader) Close() error {
ncr.closed.Store(true)
return nil
}
func (ncr *noopCloseReader) Read(p []byte) (int, error) {
if ncr.closed.Load() {
return 0, errors.New("ReverseProxy does an invalid Read on closed Body")
}
return ncr.readCloser.Read(p)
}
+2 -4
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -11,7 +9,7 @@ import (
"unicode" "unicode"
) )
// EqualFold is strings.EqualFold, ASCII only. It reports whether s and t // EqualFold is [strings.EqualFold], ASCII only. It reports whether s and t
// are equal, ASCII-case-insensitively. // are equal, ASCII-case-insensitively.
func EqualFold(s, t string) bool { func EqualFold(s, t string) bool {
if len(s) != len(t) { if len(s) != len(t) {
+1 -1
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2021 The Go Authors. All rights reserved. // Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
+52 -16
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -24,7 +22,7 @@ var ErrLineTooLong = errors.New("header line too long")
// NewChunkedReader returns a new chunkedReader that translates the data read from r // NewChunkedReader returns a new chunkedReader that translates the data read from r
// out of HTTP "chunked" format before returning it. // out of HTTP "chunked" format before returning it.
// The chunkedReader returns io.EOF when the final 0-length chunk is read. // The chunkedReader returns [io.EOF] when the final 0-length chunk is read.
// //
// NewChunkedReader is not needed by normal applications. The http package // NewChunkedReader is not needed by normal applications. The http package
// automatically decodes chunking when reading response bodies. // automatically decodes chunking when reading response bodies.
@@ -41,7 +39,8 @@ type chunkedReader struct {
n uint64 // unread bytes in chunk n uint64 // unread bytes in chunk
err error err error
buf [2]byte buf [2]byte
checkEnd bool // whether need to check for \r\n chunk footer checkEnd bool // whether need to check for \r\n chunk footer
excess int64 // "excessive" chunk overhead, for malicious sender detection
} }
func (cr *chunkedReader) beginChunk() { func (cr *chunkedReader) beginChunk() {
@@ -51,10 +50,36 @@ func (cr *chunkedReader) beginChunk() {
if cr.err != nil { if cr.err != nil {
return return
} }
cr.excess += int64(len(line)) + 2 // header, plus \r\n after the chunk data
line = trimTrailingWhitespace(line)
line, cr.err = removeChunkExtension(line)
if cr.err != nil {
return
}
cr.n, cr.err = parseHexUint(line) cr.n, cr.err = parseHexUint(line)
if cr.err != nil { if cr.err != nil {
return return
} }
// A sender who sends one byte per chunk will send 5 bytes of overhead
// for every byte of data. ("1\r\nX\r\n" to send "X".)
// We want to allow this, since streaming a byte at a time can be legitimate.
//
// A sender can use chunk extensions to add arbitrary amounts of additional
// data per byte read. ("1;very long extension\r\nX\r\n" to send "X".)
// We don't want to disallow extensions (although we discard them),
// but we also don't want to allow a sender to reduce the signal/noise ratio
// arbitrarily.
//
// We track the amount of excess overhead read,
// and produce an error if it grows too large.
//
// Currently, we say that we're willing to accept 16 bytes of overhead per chunk,
// plus twice the amount of real data in the chunk.
cr.excess -= 16 + (2 * int64(cr.n))
cr.excess = max(cr.excess, 0)
if cr.excess > 16*1024 {
cr.err = errors.New("chunked encoding contains too much non-data")
}
if cr.n == 0 { if cr.n == 0 {
cr.err = io.EOF cr.err = io.EOF
} }
@@ -139,26 +164,34 @@ func readChunkLine(b *bufio.Reader) ([]byte, error) {
} }
return nil, err return nil, err
} }
// RFC 9112 permits parsers to accept a bare \n as a line ending in headers,
// but not in chunked encoding lines. See https://www.rfc-editor.org/errata/eid7633,
// which explicitly rejects a clarification permitting \n as a chunk terminator.
//
// Verify that the line ends in a CRLF, and that no CRs appear before the end.
if idx := bytes.IndexByte(p, '\r'); idx == -1 {
return nil, errors.New("chunked line ends with bare LF")
} else if idx != len(p)-2 {
return nil, errors.New("invalid CR in chunked line")
}
p = p[:len(p)-2] // trim CRLF
if len(p) >= maxLineLength { if len(p) >= maxLineLength {
return nil, ErrLineTooLong return nil, ErrLineTooLong
} }
p = trimTrailingWhitespace(p)
p, err = removeChunkExtension(p)
if err != nil {
return nil, err
}
return p, nil return p, nil
} }
func trimTrailingWhitespace(b []byte) []byte { func trimTrailingWhitespace(b []byte) []byte {
for len(b) > 0 && isASCIISpace(b[len(b)-1]) { for len(b) > 0 && isOWS(b[len(b)-1]) {
b = b[:len(b)-1] b = b[:len(b)-1]
} }
return b return b
} }
func isASCIISpace(b byte) bool { func isOWS(b byte) bool {
return b == ' ' || b == '\t' || b == '\n' || b == '\r' return b == ' ' || b == '\t'
} }
var semi = []byte(";") var semi = []byte(";")
@@ -201,7 +234,7 @@ type chunkedWriter struct {
// Write the contents of data as one chunk to Wire. // Write the contents of data as one chunk to Wire.
// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has // NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has
// a bug since it does not check for success of io.WriteString // a bug since it does not check for success of [io.WriteString]
func (cw *chunkedWriter) Write(data []byte) (n int, err error) { func (cw *chunkedWriter) Write(data []byte) (n int, err error) {
// Don't send 0-length data. It looks like EOF for chunked encoding. // Don't send 0-length data. It looks like EOF for chunked encoding.
@@ -233,9 +266,9 @@ func (cw *chunkedWriter) Close() error {
return err return err
} }
// FlushAfterChunkWriter signals from the caller of NewChunkedWriter // FlushAfterChunkWriter signals from the caller of [NewChunkedWriter]
// that each chunk should be followed by a flush. It is used by the // that each chunk should be followed by a flush. It is used by the
// http.Transport code to keep the buffering behavior for headers and // [net/http.Transport] code to keep the buffering behavior for headers and
// trailers, but flush out chunks aggressively in the middle for // trailers, but flush out chunks aggressively in the middle for
// request bodies which may be generated slowly. See Issue 6574. // request bodies which may be generated slowly. See Issue 6574.
type FlushAfterChunkWriter struct { type FlushAfterChunkWriter struct {
@@ -243,6 +276,9 @@ type FlushAfterChunkWriter struct {
} }
func parseHexUint(v []byte) (n uint64, err error) { func parseHexUint(v []byte) (n uint64, err error) {
if len(v) == 0 {
return 0, errors.New("empty hex number for chunk length")
}
for i, b := range v { for i, b := range v {
switch { switch {
case '0' <= b && b <= '9': case '0' <= b && b <= '9':
+88 -3
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -155,6 +153,7 @@ func TestParseHexUint(t *testing.T) {
{"00000000000000000", 0, "http chunk length too large"}, // could accept if we wanted {"00000000000000000", 0, "http chunk length too large"}, // could accept if we wanted
{"10000000000000000", 0, "http chunk length too large"}, {"10000000000000000", 0, "http chunk length too large"},
{"00000000000000001", 0, "http chunk length too large"}, // could accept if we wanted {"00000000000000001", 0, "http chunk length too large"}, // could accept if we wanted
{"", 0, "empty hex number for chunk length"},
} }
for i := uint64(0); i <= 1234; i++ { for i := uint64(0); i <= 1234; i++ {
tests = append(tests, testCase{in: fmt.Sprintf("%x", i), want: i}) tests = append(tests, testCase{in: fmt.Sprintf("%x", i), want: i})
@@ -241,3 +240,89 @@ func TestChunkEndReadError(t *testing.T) {
t.Errorf("expected %v, got %v", readErr, err) t.Errorf("expected %v, got %v", readErr, err)
} }
} }
func TestChunkReaderTooMuchOverhead(t *testing.T) {
// If the sender is sending 100x as many chunk header bytes as chunk data,
// we should reject the stream at some point.
chunk := []byte("1;")
for i := 0; i < 100; i++ {
chunk = append(chunk, 'a') // chunk extension
}
chunk = append(chunk, "\r\nX\r\n"...)
const bodylen = 1 << 20
r := NewChunkedReader(&funcReader{f: func(i int) ([]byte, error) {
if i < bodylen {
return chunk, nil
}
return []byte("0\r\n"), nil
}})
_, err := io.ReadAll(r)
if err == nil {
t.Fatalf("successfully read body with excessive overhead; want error")
}
}
func TestChunkReaderByteAtATime(t *testing.T) {
// Sending one byte per chunk should not trip the excess-overhead detection.
const bodylen = 1 << 20
r := NewChunkedReader(&funcReader{f: func(i int) ([]byte, error) {
if i < bodylen {
return []byte("1\r\nX\r\n"), nil
}
return []byte("0\r\n"), nil
}})
got, err := io.ReadAll(r)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(got) != bodylen {
t.Errorf("read %v bytes, want %v", len(got), bodylen)
}
}
func TestChunkInvalidInputs(t *testing.T) {
for _, test := range []struct {
name string
b string
}{{
name: "bare LF in chunk size",
b: "1\na\r\n0\r\n",
}, {
name: "extra LF in chunk size",
b: "1\r\r\na\r\n0\r\n",
}, {
name: "bare LF in chunk data",
b: "1\r\na\n0\r\n",
}, {
name: "bare LF in chunk extension",
b: "1;\na\r\n0\r\n",
}} {
t.Run(test.name, func(t *testing.T) {
r := NewChunkedReader(strings.NewReader(test.b))
got, err := io.ReadAll(r)
if err == nil {
t.Fatalf("unexpectedly parsed invalid chunked data:\n%q", got)
}
})
}
}
type funcReader struct {
f func(iteration int) ([]byte, error)
i int
b []byte
err error
}
func (r *funcReader) Read(p []byte) (n int, err error) {
if len(r.b) == 0 && r.err == nil {
r.b, r.err = r.f(r.i)
r.i++
}
n = copy(p, r.b)
r.b = r.b[n:]
if len(r.b) > 0 {
return n, nil
}
return n, r.err
}
+1 -1
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.
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
+1 -1
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2015 The Go Authors. All rights reserved. // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
+1 -1
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied and modified from Go 1.23.7 official implementation. // TINYGO: The following is copied and modified from Go 1.26.2 official implementation.
// Copyright 2023 The Go Authors. All rights reserved. // Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
+16 -10
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.5 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -69,7 +67,7 @@
// in your browser. // in your browser.
// //
// For a study of the facility in action, visit // For a study of the facility in action, visit
// https://blog.golang.org/2011/06/profiling-go-programs.html. // https://go.dev/blog/pprof.
package pprof package pprof
import ( import (
@@ -79,6 +77,7 @@ import (
"fmt" "fmt"
"html" "html"
"internal/godebug" "internal/godebug"
"internal/goexperiment"
"internal/profile" "internal/profile"
"io" "io"
"log" "log"
@@ -353,12 +352,13 @@ func collectProfile(p *pprof.Profile) (*profile.Profile, error) {
} }
var profileSupportsDelta = map[handler]bool{ var profileSupportsDelta = map[handler]bool{
"allocs": true, "allocs": true,
"block": true, "block": true,
"goroutine": true, "goroutineleak": true,
"heap": true, "goroutine": true,
"mutex": true, "heap": true,
"threadcreate": true, "mutex": true,
"threadcreate": true,
} }
var profileDescriptions = map[string]string{ var profileDescriptions = map[string]string{
@@ -374,6 +374,12 @@ var profileDescriptions = map[string]string{
"trace": "A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.", "trace": "A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.",
} }
func init() {
if goexperiment.GoroutineLeakProfile {
profileDescriptions["goroutineleak"] = "Stack traces of all leaked goroutines. Use debug=2 as a query parameter to export in the same format as an unrecovered panic."
}
}
type profileEntry struct { type profileEntry struct {
Name string Name string
Href string Href string
+137 -106
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 multipart stuff // TINYGO: Removed multipart stuff
// TINYGO: Removed trace stuff // TINYGO: Removed trace stuff
@@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"maps"
"mime" "mime"
"mime/multipart" "mime/multipart"
"net/http/internal/ascii" "net/http/internal/ascii"
@@ -29,6 +30,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
_ "unsafe" // for linkname
"golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpguts"
) )
@@ -110,14 +112,10 @@ var reqWriteExcludeHeader = map[string]bool{
// //
// The field semantics differ slightly between client and server // The field semantics differ slightly between client and server
// usage. In addition to the notes on the fields below, see the // usage. In addition to the notes on the fields below, see the
// documentation for Request.Write and RoundTripper. // documentation for [Request.Write] and [RoundTripper].
type Request struct { type Request struct {
// Method specifies the HTTP method (GET, POST, PUT, etc.). // Method specifies the HTTP method (GET, POST, PUT, etc.).
// For client requests, an empty string means GET. // For client requests, an empty string means GET.
//
// Go's HTTP client does not support sending a request with
// the CONNECT method. See the documentation on Transport for
// details.
Method string Method string
// URL specifies either the URI being requested (for server // URL specifies either the URI being requested (for server
@@ -327,6 +325,10 @@ type Request struct {
// redirects. // redirects.
Response *Response Response *Response
// Pattern is the [ServeMux] pattern that matched the request.
// It is empty if the request was not matched against a pattern.
Pattern string
// ctx is either the client or server context. It should only // ctx is either the client or server context. It should only
// be modified via copying the whole Request using Clone or WithContext. // be modified via copying the whole Request using Clone or WithContext.
// It is unexported to prevent people from using Context wrong // It is unexported to prevent people from using Context wrong
@@ -344,7 +346,7 @@ type Request struct {
} }
// Context returns the request's context. To change the context, use // Context returns the request's context. To change the context, use
// Clone or WithContext. // [Request.Clone] or [Request.WithContext].
// //
// The returned context is always non-nil; it defaults to the // The returned context is always non-nil; it defaults to the
// background context. // background context.
@@ -368,8 +370,8 @@ func (r *Request) Context() context.Context {
// lifetime of a request and its response: obtaining a connection, // lifetime of a request and its response: obtaining a connection,
// sending the request, and reading the response headers and body. // sending the request, and reading the response headers and body.
// //
// To create a new request with a context, use NewRequestWithContext. // To create a new request with a context, use [NewRequestWithContext].
// To make a deep copy of a request with a new context, use Request.Clone. // To make a deep copy of a request with a new context, use [Request.Clone].
func (r *Request) WithContext(ctx context.Context) *Request { func (r *Request) WithContext(ctx context.Context) *Request {
if ctx == nil { if ctx == nil {
panic("nil context") panic("nil context")
@@ -383,6 +385,8 @@ func (r *Request) WithContext(ctx context.Context) *Request {
// Clone returns a deep copy of r with its context changed to ctx. // Clone returns a deep copy of r with its context changed to ctx.
// The provided ctx must be non-nil. // The provided ctx must be non-nil.
// //
// Clone only makes a shallow copy of the Body field.
//
// For an outgoing client request, the context controls the entire // For an outgoing client request, the context controls the entire
// lifetime of a request and its response: obtaining a connection, // lifetime of a request and its response: obtaining a connection,
// sending the request, and reading the response headers and body. // sending the request, and reading the response headers and body.
@@ -394,12 +398,8 @@ func (r *Request) Clone(ctx context.Context) *Request {
*r2 = *r *r2 = *r
r2.ctx = ctx r2.ctx = ctx
r2.URL = cloneURL(r.URL) r2.URL = cloneURL(r.URL)
if r.Header != nil { r2.Header = r.Header.Clone()
r2.Header = r.Header.Clone() r2.Trailer = r.Trailer.Clone()
}
if r.Trailer != nil {
r2.Trailer = r.Trailer.Clone()
}
if s := r.TransferEncoding; s != nil { if s := r.TransferEncoding; s != nil {
s2 := make([]string, len(s)) s2 := make([]string, len(s))
copy(s2, s) copy(s2, s)
@@ -415,13 +415,7 @@ func (r *Request) Clone(ctx context.Context) *Request {
copy(s2, s) copy(s2, s)
r2.matches = s2 r2.matches = s2
} }
if s := r.otherValues; s != nil { r2.otherValues = maps.Clone(r.otherValues)
s2 := make(map[string]string, len(s))
for k, v := range s {
s2[k] = v
}
r2.otherValues = s2
}
return r2 return r2
} }
@@ -442,11 +436,20 @@ func (r *Request) Cookies() []*Cookie {
return readCookies(r.Header, "") return readCookies(r.Header, "")
} }
// CookiesNamed parses and returns the named HTTP cookies sent with the request
// or an empty slice if none matched.
func (r *Request) CookiesNamed(name string) []*Cookie {
if name == "" {
return []*Cookie{}
}
return readCookies(r.Header, name)
}
// ErrNoCookie is returned by Request's Cookie method when a cookie is not found. // ErrNoCookie is returned by Request's Cookie method when a cookie is not found.
var ErrNoCookie = errors.New("http: named cookie not present") var ErrNoCookie = errors.New("http: named cookie not present")
// Cookie returns the named cookie provided in the request or // Cookie returns the named cookie provided in the request or
// ErrNoCookie if not found. // [ErrNoCookie] if not found.
// If multiple cookies match the given name, only one cookie will // If multiple cookies match the given name, only one cookie will
// be returned. // be returned.
func (r *Request) Cookie(name string) (*Cookie, error) { func (r *Request) Cookie(name string) (*Cookie, error) {
@@ -460,13 +463,13 @@ func (r *Request) Cookie(name string) (*Cookie, error) {
} }
// AddCookie adds a cookie to the request. Per RFC 6265 section 5.4, // AddCookie adds a cookie to the request. Per RFC 6265 section 5.4,
// AddCookie does not attach more than one Cookie header field. That // AddCookie does not attach more than one [Cookie] header field. That
// means all cookies, if any, are written into the same line, // means all cookies, if any, are written into the same line,
// separated by semicolon. // separated by semicolon.
// AddCookie only sanitizes c's name and value, and does not sanitize // AddCookie only sanitizes c's name and value, and does not sanitize
// a Cookie header already present in the request. // a Cookie header already present in the request.
func (r *Request) AddCookie(c *Cookie) { func (r *Request) AddCookie(c *Cookie) {
s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value, c.Quoted))
if c := r.Header.Get("Cookie"); c != "" { if c := r.Header.Get("Cookie"); c != "" {
r.Header.Set("Cookie", c+"; "+s) r.Header.Set("Cookie", c+"; "+s)
} else { } else {
@@ -478,7 +481,7 @@ func (r *Request) AddCookie(c *Cookie) {
// //
// Referer is misspelled as in the request itself, a mistake from the // Referer is misspelled as in the request itself, a mistake from the
// earliest days of HTTP. This value can also be fetched from the // earliest days of HTTP. This value can also be fetched from the
// Header map as Header["Referer"]; the benefit of making it available // [Header] map as Header["Referer"]; the benefit of making it available
// as a method is that the compiler can diagnose programs that use the // as a method is that the compiler can diagnose programs that use the
// alternate (correct English) spelling req.Referrer() but cannot // alternate (correct English) spelling req.Referrer() but cannot
// diagnose programs that use Header["Referrer"]. // diagnose programs that use Header["Referrer"].
@@ -496,7 +499,7 @@ var multipartByReader = &multipart.Form{
// MultipartReader returns a MIME multipart reader if this is a // MultipartReader returns a MIME multipart reader if this is a
// multipart/form-data or a multipart/mixed POST request, else returns nil and an error. // multipart/form-data or a multipart/mixed POST request, else returns nil and an error.
// Use this function instead of ParseMultipartForm to // Use this function instead of [Request.ParseMultipartForm] to
// process the request body as a stream. // process the request body as a stream.
func (r *Request) MultipartReader() (*multipart.Reader, error) { func (r *Request) MultipartReader() (*multipart.Reader, error) {
if r.MultipartForm == multipartByReader { if r.MultipartForm == multipartByReader {
@@ -559,15 +562,15 @@ const defaultUserAgent = "Go-http-client/1.1"
// TransferEncoding // TransferEncoding
// Body // Body
// //
// If Body is present, Content-Length is <= 0 and TransferEncoding // If Body is present, Content-Length is <= 0 and [Request.TransferEncoding]
// hasn't been set to "identity", Write adds "Transfer-Encoding: // hasn't been set to "identity", Write adds "Transfer-Encoding:
// chunked" to the header. Body is closed after it is sent. // chunked" to the header. Body is closed after it is sent.
func (r *Request) Write(w io.Writer) error { func (r *Request) Write(w io.Writer) error {
return r.write(w, false, nil, nil) return r.write(w, false, nil, nil)
} }
// WriteProxy is like Write but writes the request in the form // WriteProxy is like [Request.Write] but writes the request in the form
// expected by an HTTP proxy. In particular, WriteProxy writes the // expected by an HTTP proxy. In particular, [Request.WriteProxy] writes the
// initial Request-URI line of the request with an absolute URI, per // initial Request-URI line of the request with an absolute URI, per
// section 5.3 of RFC 7230, including the scheme and host. // section 5.3 of RFC 7230, including the scheme and host.
// In either case, WriteProxy also writes a Host header, using // In either case, WriteProxy also writes a Host header, using
@@ -684,6 +687,8 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF
userAgent = r.Header.Get("User-Agent") userAgent = r.Header.Get("User-Agent")
} }
if userAgent != "" { if userAgent != "" {
userAgent = headerNewlineToSpace.Replace(userAgent)
userAgent = textproto.TrimString(userAgent)
_, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent) _, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent)
if err != nil { if err != nil {
return err return err
@@ -820,35 +825,36 @@ func validMethod(method string) bool {
extension-method = token extension-method = token
token = 1*<any CHAR except CTLs or separators> token = 1*<any CHAR except CTLs or separators>
*/ */
return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1 return isToken(method)
} }
// NewRequest wraps NewRequestWithContext using context.Background. // NewRequest wraps [NewRequestWithContext] using [context.Background].
func NewRequest(method, url string, body io.Reader) (*Request, error) { func NewRequest(method, url string, body io.Reader) (*Request, error) {
return NewRequestWithContext(context.Background(), method, url, body) return NewRequestWithContext(context.Background(), method, url, body)
} }
// NewRequestWithContext returns a new Request given a method, URL, and // NewRequestWithContext returns a new [Request] given a method, URL, and
// optional body. // optional body.
// //
// If the provided body is also an io.Closer, the returned // If the provided body is also an [io.Closer], the returned
// Request.Body is set to body and will be closed by the Client // [Request.Body] is set to body and will be closed (possibly
// methods Do, Post, and PostForm, and Transport.RoundTrip. // asynchronously) by the Client methods Do, Post, and PostForm,
// and [Transport.RoundTrip].
// //
// NewRequestWithContext returns a Request suitable for use with // NewRequestWithContext returns a Request suitable for use with
// Client.Do or Transport.RoundTrip. To create a request for use with // [Client.Do] or [Transport.RoundTrip]. To create a request for use with
// testing a Server Handler, either use the NewRequest function in the // testing a Server Handler, either use the [net/http/httptest.NewRequest] function,
// net/http/httptest package, use ReadRequest, or manually update the // use [ReadRequest], or manually update the Request fields.
// Request fields. For an outgoing client request, the context // For an outgoing client request, the context
// controls the entire lifetime of a request and its response: // controls the entire lifetime of a request and its response:
// obtaining a connection, sending the request, and reading the // obtaining a connection, sending the request, and reading the
// response headers and body. See the Request type's documentation for // response headers and body. See the [Request] type's documentation for
// the difference between inbound and outbound request fields. // the difference between inbound and outbound request fields.
// //
// If body is of type *bytes.Buffer, *bytes.Reader, or // If body is of type [*bytes.Buffer], [*bytes.Reader], or
// *strings.Reader, the returned request's ContentLength is set to its // [*strings.Reader], the returned request's ContentLength is set to its
// exact value (instead of -1), GetBody is populated (so 307 and 308 // exact value (instead of -1), GetBody is populated (so 307 and 308
// redirects can replay the body), and Body is set to NoBody if the // redirects can replay the body), and Body is set to [NoBody] if the
// ContentLength is 0. // ContentLength is 0.
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) { func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
if method == "" { if method == "" {
@@ -944,6 +950,16 @@ func (r *Request) BasicAuth() (username, password string, ok bool) {
// parseBasicAuth parses an HTTP Basic Authentication string. // parseBasicAuth parses an HTTP Basic Authentication string.
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). // "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
//
// parseBasicAuth should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/sagernet/sing
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname parseBasicAuth
func parseBasicAuth(auth string) (username, password string, ok bool) { func parseBasicAuth(auth string) (username, password string, ok bool) {
const prefix = "Basic " const prefix = "Basic "
// Case insensitive prefix match. See Issue 22736. // Case insensitive prefix match. See Issue 22736.
@@ -972,7 +988,7 @@ func parseBasicAuth(auth string) (username, password string, ok bool) {
// The username may not contain a colon. Some protocols may impose // The username may not contain a colon. Some protocols may impose
// additional requirements on pre-escaping the username and // additional requirements on pre-escaping the username and
// password. For instance, when used with OAuth2, both arguments must // password. For instance, when used with OAuth2, both arguments must
// be URL encoded first with url.QueryEscape. // be URL encoded first with [url.QueryEscape].
func (r *Request) SetBasicAuth(username, password string) { func (r *Request) SetBasicAuth(username, password string) {
r.Header.Set("Authorization", "Basic "+basicAuth(username, password)) r.Header.Set("Authorization", "Basic "+basicAuth(username, password))
} }
@@ -1006,8 +1022,8 @@ func putTextprotoReader(r *textproto.Reader) {
// ReadRequest reads and parses an incoming request from b. // ReadRequest reads and parses an incoming request from b.
// //
// ReadRequest is a low-level function and should only be used for // ReadRequest is a low-level function and should only be used for
// specialized applications; most code should use the Server to read // specialized applications; most code should use the [Server] to read
// requests and handle them via the Handler interface. ReadRequest // requests and handle them via the [Handler] interface. ReadRequest
// only supports HTTP/1.x requests. For HTTP/2, use golang.org/x/net/http2. // only supports HTTP/1.x requests. For HTTP/2, use golang.org/x/net/http2.
func ReadRequest(b *bufio.Reader) (*Request, error) { func ReadRequest(b *bufio.Reader) (*Request, error) {
req, err := readRequest(b) req, err := readRequest(b)
@@ -1016,9 +1032,20 @@ func ReadRequest(b *bufio.Reader) (*Request, error) {
} }
delete(req.Header, "Host") delete(req.Header, "Host")
return req, err return req, nil
} }
// readRequest should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/sagernet/sing
// - github.com/v2fly/v2ray-core/v4
// - github.com/v2fly/v2ray-core/v5
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname readRequest
func readRequest(b *bufio.Reader) (req *Request, err error) { func readRequest(b *bufio.Reader) (req *Request, err error) {
tp := newTextprotoReader(b) tp := newTextprotoReader(b)
defer putTextprotoReader(tp) defer putTextprotoReader(tp)
@@ -1116,15 +1143,15 @@ func readRequest(b *bufio.Reader) (req *Request, err error) {
return req, nil return req, nil
} }
// MaxBytesReader is similar to io.LimitReader but is intended for // MaxBytesReader is similar to [io.LimitReader] but is intended for
// limiting the size of incoming request bodies. In contrast to // limiting the size of incoming request bodies. In contrast to
// io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a // io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a
// non-nil error of type *MaxBytesError for a Read beyond the limit, // non-nil error of type [*MaxBytesError] for a Read beyond the limit,
// and closes the underlying reader when its Close method is called. // and closes the underlying reader when its Close method is called.
// //
// MaxBytesReader prevents clients from accidentally or maliciously // MaxBytesReader prevents clients from accidentally or maliciously
// sending a large request and wasting server resources. If possible, // sending a large request and wasting server resources. If possible,
// it tells the ResponseWriter to close the connection after the limit // it tells the [ResponseWriter] to close the connection after the limit
// has been reached. // has been reached.
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser { func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
if n < 0 { // Treat negative limits as equivalent to 0. if n < 0 { // Treat negative limits as equivalent to 0.
@@ -1133,7 +1160,7 @@ func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
return &maxBytesReader{w: w, r: r, i: n, n: n} return &maxBytesReader{w: w, r: r, i: n, n: n}
} }
// MaxBytesError is returned by MaxBytesReader when its read limit is exceeded. // MaxBytesError is returned by [MaxBytesReader] when its read limit is exceeded.
type MaxBytesError struct { type MaxBytesError struct {
Limit int64 Limit int64
} }
@@ -1258,14 +1285,14 @@ func parsePostForm(r *Request) (vs url.Values, err error) {
// as a form and puts the results into both r.PostForm and r.Form. Request body // as a form and puts the results into both r.PostForm and r.Form. Request body
// parameters take precedence over URL query string values in r.Form. // parameters take precedence over URL query string values in r.Form.
// //
// If the request Body's size has not already been limited by MaxBytesReader, // If the request Body's size has not already been limited by [MaxBytesReader],
// the size is capped at 10MB. // the size is capped at 10MB.
// //
// For other HTTP methods, or when the Content-Type is not // For other HTTP methods, or when the Content-Type is not
// application/x-www-form-urlencoded, the request Body is not read, and // application/x-www-form-urlencoded, the request Body is not read, and
// r.PostForm is initialized to a non-nil, empty value. // r.PostForm is initialized to a non-nil, empty value.
// //
// ParseMultipartForm calls ParseForm automatically. // [Request.ParseMultipartForm] calls ParseForm automatically.
// ParseForm is idempotent. // ParseForm is idempotent.
func (r *Request) ParseForm() error { func (r *Request) ParseForm() error {
var err error var err error
@@ -1306,7 +1333,7 @@ func (r *Request) ParseForm() error {
// The whole request body is parsed and up to a total of maxMemory bytes of // The whole request body is parsed and up to a total of maxMemory bytes of
// its file parts are stored in memory, with the remainder stored on // its file parts are stored in memory, with the remainder stored on
// disk in temporary files. // disk in temporary files.
// ParseMultipartForm calls ParseForm if necessary. // ParseMultipartForm calls [Request.ParseForm] if necessary.
// If ParseForm returns an error, ParseMultipartForm returns it but also // If ParseForm returns an error, ParseMultipartForm returns it but also
// continues parsing the request body. // continues parsing the request body.
// After one call to ParseMultipartForm, subsequent calls have no effect. // After one call to ParseMultipartForm, subsequent calls have no effect.
@@ -1349,12 +1376,16 @@ func (r *Request) ParseMultipartForm(maxMemory int64) error {
} }
// FormValue returns the first value for the named component of the query. // FormValue returns the first value for the named component of the query.
// POST and PUT body parameters take precedence over URL query string values. // The precedence order:
// FormValue calls ParseMultipartForm and ParseForm if necessary and ignores // 1. application/x-www-form-urlencoded form body (POST, PUT, PATCH only)
// any errors returned by these functions. // 2. query parameters (always)
// 3. multipart/form-data form body (always)
//
// FormValue calls [Request.ParseMultipartForm] and [Request.ParseForm]
// if necessary and ignores any errors returned by these functions.
// If key is not present, FormValue returns the empty string. // If key is not present, FormValue returns the empty string.
// To access multiple values of the same key, call ParseForm and // To access multiple values of the same key, call ParseForm and
// then inspect Request.Form directly. // then inspect [Request.Form] directly.
func (r *Request) FormValue(key string) string { func (r *Request) FormValue(key string) string {
if r.Form == nil { if r.Form == nil {
r.ParseMultipartForm(defaultMaxMemory) r.ParseMultipartForm(defaultMaxMemory)
@@ -1366,8 +1397,8 @@ func (r *Request) FormValue(key string) string {
} }
// PostFormValue returns the first value for the named component of the POST, // PostFormValue returns the first value for the named component of the POST,
// PATCH, or PUT request body. URL query parameters are ignored. // PUT, or PATCH request body. URL query parameters are ignored.
// PostFormValue calls ParseMultipartForm and ParseForm if necessary and ignores // PostFormValue calls [Request.ParseMultipartForm] and [Request.ParseForm] if necessary and ignores
// any errors returned by these functions. // any errors returned by these functions.
// If key is not present, PostFormValue returns the empty string. // If key is not present, PostFormValue returns the empty string.
func (r *Request) PostFormValue(key string) string { func (r *Request) PostFormValue(key string) string {
@@ -1381,7 +1412,7 @@ func (r *Request) PostFormValue(key string) string {
} }
// FormFile returns the first file for the provided form key. // FormFile returns the first file for the provided form key.
// FormFile calls ParseMultipartForm and ParseForm if necessary. // FormFile calls [Request.ParseMultipartForm] and [Request.ParseForm] if necessary.
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) { func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
if r.MultipartForm == multipartByReader { if r.MultipartForm == multipartByReader {
return nil, nil, errors.New("http: multipart handled by MultipartReader") return nil, nil, errors.New("http: multipart handled by MultipartReader")
@@ -1401,6 +1432,50 @@ func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, e
return nil, nil, ErrMissingFile return nil, nil, ErrMissingFile
} }
// PathValue returns the value for the named path wildcard in the [ServeMux] pattern
// that matched the request.
// It returns the empty string if the request was not matched against a pattern
// or there is no such wildcard in the pattern.
func (r *Request) PathValue(name string) string {
if i := r.patIndex(name); i >= 0 {
return r.matches[i]
}
return r.otherValues[name]
}
// SetPathValue sets name to value, so that subsequent calls to r.PathValue(name)
// return value.
func (r *Request) SetPathValue(name, value string) {
if i := r.patIndex(name); i >= 0 {
r.matches[i] = value
} else {
if r.otherValues == nil {
r.otherValues = map[string]string{}
}
r.otherValues[name] = value
}
}
// patIndex returns the index of name in the list of named wildcards of the
// request's pattern, or -1 if there is no such name.
func (r *Request) patIndex(name string) int {
// The linear search seems expensive compared to a map, but just creating the map
// takes a lot of time, and most patterns will just have a couple of wildcards.
if r.pat == nil {
return -1
}
i := 0
for _, seg := range r.pat.segments {
if seg.wild && seg.s != "" {
if name == seg.s {
return i
}
i++
}
}
return -1
}
func (r *Request) expectsContinue() bool { func (r *Request) expectsContinue() bool {
return hasToken(r.Header.get("Expect"), "100-continue") return hasToken(r.Header.get("Expect"), "100-continue")
} }
@@ -1475,47 +1550,3 @@ func (r *Request) requiresHTTP1() bool {
return hasToken(r.Header.Get("Connection"), "upgrade") && return hasToken(r.Header.Get("Connection"), "upgrade") &&
ascii.EqualFold(r.Header.Get("Upgrade"), "websocket") ascii.EqualFold(r.Header.Get("Upgrade"), "websocket")
} }
// PathValue returns the value for the named path wildcard in the [ServeMux] pattern
// that matched the request.
// It returns the empty string if the request was not matched against a pattern
// or there is no such wildcard in the pattern.
func (r *Request) PathValue(name string) string {
if i := r.patIndex(name); i >= 0 {
return r.matches[i]
}
return r.otherValues[name]
}
// SetPathValue sets name to value, so that subsequent calls to r.PathValue(name)
// return value.
func (r *Request) SetPathValue(name, value string) {
if i := r.patIndex(name); i >= 0 {
r.matches[i] = value
} else {
if r.otherValues == nil {
r.otherValues = map[string]string{}
}
r.otherValues[name] = value
}
}
// patIndex returns the index of name in the list of named wildcards of the
// request's pattern, or -1 if there is no such name.
func (r *Request) patIndex(name string) int {
// The linear search seems expensive compared to a map, but just creating the map
// takes a lot of time, and most patterns will just have a couple of wildcards.
if r.pat == nil {
return -1
}
i := 0
for _, seg := range r.pat.segments {
if seg.wild && seg.s != "" {
if name == seg.s {
return i
}
i++
}
}
return -1
}
+6 -6
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 TLS connection state // TINYGO: Removed TLS connection state
// TINYGO: Added onEOF hook to get callback when response has been read // TINYGO: Added onEOF hook to get callback when response has been read
@@ -33,7 +33,7 @@ var respExcludeHeader = map[string]bool{
// Response represents the response from an HTTP request. // Response represents the response from an HTTP request.
// //
// The Client and Transport return Responses from servers once // The [Client] and [Transport] return Responses from servers once
// the response headers have been received. The response body // the response headers have been received. The response body
// is streamed on demand as the Body field is read. // is streamed on demand as the Body field is read.
type Response struct { type Response struct {
@@ -124,13 +124,13 @@ func (r *Response) Cookies() []*Cookie {
return readSetCookies(r.Header) return readSetCookies(r.Header)
} }
// ErrNoLocation is returned by Response's Location method // ErrNoLocation is returned by the [Response.Location] method
// when no Location header is present. // when no Location header is present.
var ErrNoLocation = errors.New("http: no Location header in response") var ErrNoLocation = errors.New("http: no Location header in response")
// Location returns the URL of the response's "Location" header, // Location returns the URL of the response's "Location" header,
// if present. Relative redirects are resolved relative to // if present. Relative redirects are resolved relative to
// the Response's Request. ErrNoLocation is returned if no // [Response.Request]. [ErrNoLocation] is returned if no
// Location header is present. // Location header is present.
func (r *Response) Location() (*url.URL, error) { func (r *Response) Location() (*url.URL, error) {
lv := r.Header.Get("Location") lv := r.Header.Get("Location")
@@ -144,8 +144,8 @@ func (r *Response) Location() (*url.URL, error) {
} }
// ReadResponse reads and returns an HTTP response from r. // ReadResponse reads and returns an HTTP response from r.
// The req parameter optionally specifies the Request that corresponds // The req parameter optionally specifies the [Request] that corresponds
// to this Response. If nil, a GET request is assumed. // to this [Response]. If nil, a GET request is assumed.
// Clients must call resp.Body.Close when finished reading resp.Body. // Clients must call resp.Body.Close when finished reading resp.Body.
// After that call, clients can inspect resp.Trailer to find key/value // After that call, clients can inspect resp.Trailer to find key/value
// pairs included in the response trailer. // pairs included in the response trailer.
+38 -16
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: atomic.Pointer and atomic.Uint64 are added in Go 1.19, so keep // TINYGO: atomic.Pointer and atomic.Uint64 are added in Go 1.19, so keep
// pre-1.19 code to cover min TinyGo. If TinyGo min moves to 1.19 or higher, // pre-1.19 code to cover min TinyGo. If TinyGo min moves to 1.19 or higher,
@@ -332,12 +332,14 @@ func (c *conn) hijackLocked() (rwc net.Conn, buf *bufio.ReadWriter, err error) {
rwc = c.rwc rwc = c.rwc
rwc.SetDeadline(time.Time{}) rwc.SetDeadline(time.Time{})
buf = bufio.NewReadWriter(c.bufr, bufio.NewWriter(rwc))
if c.r.hasByte { if c.r.hasByte {
if _, err := c.bufr.Peek(c.bufr.Buffered() + 1); err != nil { if _, err := c.bufr.Peek(c.bufr.Buffered() + 1); err != nil {
return nil, nil, fmt.Errorf("unexpected Peek failure reading buffered byte: %v", err) return nil, nil, fmt.Errorf("unexpected Peek failure reading buffered byte: %v", err)
} }
} }
c.bufw.Reset(rwc)
buf = bufio.NewReadWriter(c.bufr, c.bufw)
c.setState(rwc, StateHijacked, runHooks) c.setState(rwc, StateHijacked, runHooks)
return return
} }
@@ -592,9 +594,8 @@ type writerOnly struct {
// to a *net.TCPConn with sendfile, or from a supported src type such // to a *net.TCPConn with sendfile, or from a supported src type such
// as a *net.TCPConn on Linux with splice. // as a *net.TCPConn on Linux with splice.
func (w *response) ReadFrom(src io.Reader) (n int64, err error) { func (w *response) ReadFrom(src io.Reader) (n int64, err error) {
bufp := copyBufPool.Get().(*[]byte) buf := getCopyBuf()
buf := *bufp defer putCopyBuf(buf)
defer copyBufPool.Put(bufp)
// Our underlying w.conn.rwc is usually a *TCPConn (with its // Our underlying w.conn.rwc is usually a *TCPConn (with its
// own ReadFrom method). If not, just fall back to the normal // own ReadFrom method). If not, just fall back to the normal
@@ -620,7 +621,7 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) {
w.cw.flush() // make sure Header is written; flush data to rwc w.cw.flush() // make sure Header is written; flush data to rwc
// Now that cw has been flushed, its chunking field is guaranteed initialized. // Now that cw has been flushed, its chunking field is guaranteed initialized.
if !w.cw.chunking && w.bodyAllowed() { if !w.cw.chunking && w.bodyAllowed() && w.req.Method != "HEAD" {
n0, err := rf.ReadFrom(src) n0, err := rf.ReadFrom(src)
n += n0 n += n0
w.written += n0 w.written += n0
@@ -824,11 +825,19 @@ var (
bufioWriter4kPool sync.Pool bufioWriter4kPool sync.Pool
) )
var copyBufPool = sync.Pool{ const copyBufPoolSize = 32 * 1024
New: func() any {
b := make([]byte, 32*1024) var copyBufPool = sync.Pool{New: func() any { return new([copyBufPoolSize]byte) }}
return &b
}, func getCopyBuf() []byte {
return copyBufPool.Get().(*[copyBufPoolSize]byte)[:]
}
func putCopyBuf(b []byte) {
if len(b) != copyBufPoolSize {
panic("trying to put back buffer of the wrong size in the copyBufPool")
}
copyBufPool.Put((*[copyBufPoolSize]byte)(b))
} }
func bufioWriterPool(size int) *sync.Pool { func bufioWriterPool(size int) *sync.Pool {
@@ -1770,7 +1779,7 @@ func (c *conn) close() {
// from closing a connection with known unread data. // from closing a connection with known unread data.
// This RST seems to occur mostly on BSD systems. (And Windows?) // This RST seems to occur mostly on BSD systems. (And Windows?)
// This timeout is somewhat arbitrary (~latency around the planet). // This timeout is somewhat arbitrary (~latency around the planet).
const rstAvoidanceDelay = 500 * time.Millisecond var rstAvoidanceDelay = 500 * time.Millisecond
type closeWriter interface { type closeWriter interface {
CloseWrite() error CloseWrite() error
@@ -2123,8 +2132,21 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
// writes are done to w. // writes are done to w.
// The error message should be plain text. // The error message should be plain text.
func Error(w ResponseWriter, error string, code int) { func Error(w ResponseWriter, error string, code int) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8") h := w.Header()
w.Header().Set("X-Content-Type-Options", "nosniff")
// Delete the Content-Length header, which might be for some other content.
// Assuming the error string fits in the writer's buffer, we'll figure
// out the correct Content-Length for it later.
//
// We don't delete Content-Encoding, because some middleware sets
// Content-Encoding: gzip and wraps the ResponseWriter to compress on-the-fly.
// See https://go.dev/issue/66343.
h.Del("Content-Length")
// There might be content type already set, but we reset it to
// text/plain for the error message.
h.Set("Content-Type", "text/plain; charset=utf-8")
h.Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code) w.WriteHeader(code)
fmt.Fprintln(w, error) fmt.Fprintln(w, error)
} }
@@ -2181,7 +2203,7 @@ func Redirect(w ResponseWriter, r *Request, url string, code int) {
// but doing it ourselves is more reliable. // but doing it ourselves is more reliable.
// See RFC 7231, section 7.1.2 // See RFC 7231, section 7.1.2
if u.Scheme == "" && u.Host == "" { if u.Scheme == "" && u.Host == "" {
oldpath := r.URL.Path oldpath := r.URL.EscapedPath()
if oldpath == "" { // should not happen, but avoid a crash if it does if oldpath == "" { // should not happen, but avoid a crash if it does
oldpath = "/" oldpath = "/"
} }
@@ -3151,7 +3173,7 @@ func (s *Server) trackConn(c *conn, add bool) {
} }
func (s *Server) idleTimeout() time.Duration { func (s *Server) idleTimeout() time.Duration {
if s.IdleTimeout != 0 { if s.IdleTimeout > 0 {
return s.IdleTimeout return s.IdleTimeout
} }
return s.ReadTimeout return s.ReadTimeout
+1 -1
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
+1 -1
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
+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: Removed trace stuff
// TINYGO: Hook readTransfer is onEOF callback to get notified when request of // TINYGO: Hook readTransfer is onEOF callback to get notified when request of
@@ -15,12 +15,14 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"internal/godebug"
"io" "io"
"maps"
"net/http/internal" "net/http/internal"
"net/http/internal/ascii" "net/http/internal/ascii"
"net/textproto" "net/textproto"
"reflect" "reflect"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -313,7 +315,7 @@ func (t *transferWriter) writeHeader(w io.Writer) error {
keys = append(keys, k) keys = append(keys, k)
} }
if len(keys) > 0 { if len(keys) > 0 {
sort.Strings(keys) slices.Sort(keys)
// TODO: could do better allocation-wise here, but trailers are rare, // TODO: could do better allocation-wise here, but trailers are rare,
// so being lazy for now. // so being lazy for now.
if _, err := io.WriteString(w, "Trailer: "+strings.Join(keys, ",")+"\r\n"); err != nil { 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 // 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-level optimizations in the event that the body is an
// *os.File. // *os.File.
if t.Body != nil { if !t.ResponseToHEAD && t.Body != nil {
var body = t.unwrapBody() var body = t.unwrapBody()
if chunked(t.TransferEncoding) { if chunked(t.TransferEncoding) {
if bw, ok := w.(*bufio.Writer); ok && !t.IsResponse { 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) t.ContentLength, ncopy)
} }
if chunked(t.TransferEncoding) { if !t.ResponseToHEAD && chunked(t.TransferEncoding) {
// Write Trailer header // Write Trailer header
if t.Trailer != nil { if t.Trailer != nil {
if err := t.Trailer.Write(w); err != 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. // This function is only intended for use in writeBody.
func (t *transferWriter) doBodyCopy(dst io.Writer, src io.Reader) (n int64, err error) { 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 { if err != nil && err != io.EOF {
t.bodyReadError = err t.bodyReadError = err
} }
@@ -523,7 +528,7 @@ func readTransfer(msg any, r *bufio.Reader, onEOF func()) (err error) {
return err return err
} }
if isResponse && t.RequestMethod == "HEAD" { 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 return err
} else { } else {
t.ContentLength = n t.ContentLength = n
@@ -642,19 +647,6 @@ func (t *transferReader) parseTransferEncoding() error {
return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", raw[0])} 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 t.Chunked = true
return nil return nil
} }
@@ -662,7 +654,7 @@ func (t *transferReader) parseTransferEncoding() error {
// Determine the expected body length, using RFC 7230 Section 3.3. This // Determine the expected body length, using RFC 7230 Section 3.3. This
// function is not a method, because ultimately it should be shared by // function is not a method, because ultimately it should be shared by
// ReadResponse and ReadRequest. // 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 isRequest := !isResponse
contentLens := header["Content-Length"] contentLens := header["Content-Length"]
@@ -686,6 +678,14 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
contentLens = header["Content-Length"] 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 // Logic based on response type or status
if isResponse && noResponseBodyExpected(requestMethod) { if isResponse && noResponseBodyExpected(requestMethod) {
return 0, nil return 0, nil
@@ -698,23 +698,29 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
return 0, nil 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 // Logic based on Transfer-Encoding
if chunked { if chunked {
header.Del("Content-Length")
return -1, nil return -1, nil
} }
// Logic based on Content-Length // Logic based on Content-Length
var cl string if len(contentLens) > 0 {
if len(contentLens) == 1 {
cl = textproto.TrimString(contentLens[0])
}
if cl != "" {
n, err := parseContentLength(cl)
if err != nil {
return -1, err
}
return n, nil return n, nil
} }
header.Del("Content-Length") header.Del("Content-Length")
if isRequest { if isRequest {
@@ -812,10 +818,10 @@ type body struct {
onHitEOF func() // if non-nil, func to call when EOF is Read 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 // 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 // read after an HTTP [Handler] calls WriteHeader or Write on its
// ResponseWriter. // [ResponseWriter].
var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body") var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body")
func (b *body) Read(p []byte) (n int, err error) { func (b *body) Read(p []byte) (n int, err error) {
@@ -945,9 +951,7 @@ func mergeSetHeader(dst *Header, src Header) {
*dst = src *dst = src
return return
} }
for k, vv := range src { maps.Copy(*dst, src)
(*dst)[k] = vv
}
} }
// unreadDataSizeLocked returns the number of bytes of unread input. // 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) return bl.b.readLocked(p)
} }
// parseContentLength trims whitespace from s and returns -1 if no value var httplaxcontentlength = godebug.New("httplaxcontentlength")
// is set, or the value if it's >= 0.
func parseContentLength(cl string) (int64, error) { // parseContentLength checks that the header is valid and then trims
cl = textproto.TrimString(cl) // whitespace. It returns -1 if no value is set otherwise the value
if cl == "" { // if it's >= 0.
func parseContentLength(clHeaders []string) (int64, error) {
if len(clHeaders) == 0 {
return -1, nil 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) n, err := strconv.ParseUint(cl, 10, 63)
if err != nil { if err != nil {
return 0, badStringError("bad Content-Length", cl) return 0, badStringError("bad Content-Length", cl)
} }
return int64(n), nil return int64(n), nil
} }
// finishAsyncByteRead finishes reading the 1-byte sniff // finishAsyncByteRead finishes reading the 1-byte sniff
+4 -3
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.
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -13,12 +13,13 @@ package http
import ( import (
"io" "io"
"sync/atomic"
) )
type readTrackingBody struct { type readTrackingBody struct {
io.ReadCloser io.ReadCloser
didRead bool didRead bool // not atomic.Bool because only one goroutine (the user's) should be accessing
didClose bool didClose atomic.Bool
} }
type Transport struct{} type Transport struct{}
+4 -4
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.
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -30,7 +30,7 @@ var (
type Interface struct { type Interface struct {
Index int // positive integer that starts at one, zero is never used Index int // positive integer that starts at one, zero is never used
MTU int // maximum transmission unit MTU int // maximum transmission unit
Name string // e.g., "en0", "lo0", "eth0.100" Name string // e.g., "en0", "lo0", "eth0.100"; may be the empty string
HardwareAddr HardwareAddr // IEEE MAC-48, EUI-48 and EUI-64 form HardwareAddr HardwareAddr // IEEE MAC-48, EUI-48 and EUI-64 form
Flags Flags // e.g., FlagUp, FlagLoopback, FlagMulticast Flags Flags // e.g., FlagUp, FlagLoopback, FlagMulticast
} }
@@ -80,7 +80,7 @@ func Interfaces() ([]Interface, error) {
// addresses. // addresses.
// //
// The returned list does not identify the associated interface; use // The returned list does not identify the associated interface; use
// Interfaces and Interface.Addrs for more detail. // Interfaces and [Interface.Addrs] for more detail.
func InterfaceAddrs() ([]Addr, error) { func InterfaceAddrs() ([]Addr, error) {
return nil, errors.New("InterfaceAddrs not implemented") return nil, errors.New("InterfaceAddrs not implemented")
} }
@@ -89,7 +89,7 @@ func InterfaceAddrs() ([]Addr, error) {
// //
// On Solaris, it returns one of the logical network interfaces // On Solaris, it returns one of the logical network interfaces
// sharing the logical data link; for more precision use // sharing the logical data link; for more precision use
// InterfaceByName. // [InterfaceByName].
func InterfaceByIndex(index int) (*Interface, error) { func InterfaceByIndex(index int) (*Interface, error) {
return nil, errors.New("InterfaceByIndex not implemented") return nil, errors.New("InterfaceByIndex not implemented")
} }
+58 -28
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -16,7 +14,8 @@ package net
import ( import (
"internal/bytealg" "internal/bytealg"
"internal/itoa" "internal/strconv"
"internal/stringslite"
"net/netip" "net/netip"
) )
@@ -40,7 +39,7 @@ type IP []byte
// An IPMask is a bitmask that can be used to manipulate // An IPMask is a bitmask that can be used to manipulate
// IP addresses for IP addressing and routing. // IP addresses for IP addressing and routing.
// //
// See type IPNet and func ParseCIDR for details. // See type [IPNet] and func [ParseCIDR] for details.
type IPMask []byte type IPMask []byte
// An IPNet represents an IP network. // An IPNet represents an IP network.
@@ -74,9 +73,9 @@ func IPv4Mask(a, b, c, d byte) IPMask {
return p return p
} }
// CIDRMask returns an IPMask consisting of 'ones' 1 bits // CIDRMask returns an [IPMask] consisting of 'ones' 1 bits
// followed by 0s up to a total length of 'bits' bits. // followed by 0s up to a total length of 'bits' bits.
// For a mask of this form, CIDRMask is the inverse of IPMask.Size. // For a mask of this form, CIDRMask is the inverse of [IPMask.Size].
func CIDRMask(ones, bits int) IPMask { func CIDRMask(ones, bits int) IPMask {
if bits != 8*IPv4len && bits != 8*IPv6len { if bits != 8*IPv4len && bits != 8*IPv6len {
return nil return nil
@@ -302,11 +301,18 @@ func (ip IP) String() string {
if len(ip) != IPv4len && len(ip) != IPv6len { if len(ip) != IPv4len && len(ip) != IPv6len {
return "?" + hexString(ip) return "?" + hexString(ip)
} }
// If IPv4, use dotted notation.
if p4 := ip.To4(); len(p4) == IPv4len { var buf []byte
return netip.AddrFrom4([4]byte(p4)).String() switch len(ip) {
case IPv4len:
const maxCap = len("255.255.255.255")
buf = make([]byte, 0, maxCap)
case IPv6len:
const maxCap = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
buf = make([]byte, 0, maxCap)
} }
return netip.AddrFrom16([16]byte(ip)).String() buf = ip.appendTo(buf)
return string(buf)
} }
func hexString(b []byte) string { func hexString(b []byte) string {
@@ -326,21 +332,45 @@ func ipEmptyString(ip IP) string {
return ip.String() return ip.String()
} }
// MarshalText implements the encoding.TextMarshaler interface. // appendTo appends the string representation of ip to b and returns the expanded b
// The encoding is the same as returned by String, with one exception: // If len(ip) != IPv4len or IPv6len, it appends nothing.
// When len(ip) is zero, it returns an empty slice. func (ip IP) appendTo(b []byte) []byte {
func (ip IP) MarshalText() ([]byte, error) { // If IPv4, use dotted notation.
if len(ip) == 0 { if p4 := ip.To4(); len(p4) == IPv4len {
return []byte(""), nil ip = p4
} }
if len(ip) != IPv4len && len(ip) != IPv6len { addr, _ := netip.AddrFromSlice(ip)
return nil, &AddrError{Err: "invalid IP address", Addr: hexString(ip)} return addr.AppendTo(b)
}
return []byte(ip.String()), nil
} }
// UnmarshalText implements the encoding.TextUnmarshaler interface. // AppendText implements the [encoding.TextAppender] interface.
// The IP address is expected in a form accepted by ParseIP. // The encoding is the same as returned by [IP.String], with one exception:
// When len(ip) is zero, it appends nothing.
func (ip IP) AppendText(b []byte) ([]byte, error) {
if len(ip) == 0 {
return b, nil
}
if len(ip) != IPv4len && len(ip) != IPv6len {
return b, &AddrError{Err: "invalid IP address", Addr: hexString(ip)}
}
return ip.appendTo(b), nil
}
// MarshalText implements the [encoding.TextMarshaler] interface.
// The encoding is the same as returned by [IP.String], with one exception:
// When len(ip) is zero, it returns an empty slice.
func (ip IP) MarshalText() ([]byte, error) {
// 24 is satisfied with all IPv4 addresses and short IPv6 addresses
b, err := ip.AppendText(make([]byte, 0, 24))
if err != nil {
return nil, err
}
return b, nil
}
// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
// The IP address is expected in a form accepted by [ParseIP].
func (ip *IP) UnmarshalText(text []byte) error { func (ip *IP) UnmarshalText(text []byte) error {
if len(text) == 0 { if len(text) == 0 {
*ip = nil *ip = nil
@@ -485,14 +515,15 @@ func (n *IPNet) String() string {
if l == -1 { if l == -1 {
return nn.String() + "/" + m.String() return nn.String() + "/" + m.String()
} }
return nn.String() + "/" + itoa.Uitoa(uint(l)) return nn.String() + "/" + strconv.Itoa(l)
} }
// ParseIP parses s as an IP address, returning the result. // ParseIP parses s as an IP address, returning the result.
// The string s can be in IPv4 dotted decimal ("192.0.2.1"), IPv6 // The string s can be in IPv4 dotted decimal ("192.0.2.1"), IPv6
// ("2001:db8::68"), or IPv4-mapped IPv6 ("::ffff:192.0.2.1") form. // ("2001:db8::68"), or IPv4-mapped IPv6 ("::ffff:192.0.2.1") form.
// If s is not a valid textual representation of an IP address, // If s is not a valid textual representation of an IP address,
// ParseIP returns nil. // ParseIP returns nil. The returned address is always 16 bytes,
// IPv4 addresses are returned in IPv4-mapped IPv6 form.
func ParseIP(s string) IP { func ParseIP(s string) IP {
if addr, valid := parseIP(s); valid { if addr, valid := parseIP(s); valid {
return IP(addr[:]) return IP(addr[:])
@@ -517,11 +548,10 @@ func parseIP(s string) ([16]byte, bool) {
// For example, ParseCIDR("192.0.2.1/24") returns the IP address // For example, ParseCIDR("192.0.2.1/24") returns the IP address
// 192.0.2.1 and the network 192.0.2.0/24. // 192.0.2.1 and the network 192.0.2.0/24.
func ParseCIDR(s string) (IP, *IPNet, error) { func ParseCIDR(s string) (IP, *IPNet, error) {
i := bytealg.IndexByteString(s, '/') addr, mask, found := stringslite.Cut(s, "/")
if i < 0 { if !found {
return nil, nil, &ParseError{Type: "CIDR address", Text: s} return nil, nil, &ParseError{Type: "CIDR address", Text: s}
} }
addr, mask := s[:i], s[i+1:]
ipAddr, err := netip.ParseAddr(addr) ipAddr, err := netip.ParseAddr(addr)
if err != nil || ipAddr.Zone() != "" { if err != nil || ipAddr.Zone() != "" {
+17 -6
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.
// Copyright 2010 The Go Authors. All rights reserved. // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -8,6 +8,7 @@ package net
import ( import (
"errors" "errors"
"net/netip"
"syscall" "syscall"
) )
@@ -26,8 +27,18 @@ import (
// BUG(mikio): On JS and Plan 9, methods and functions related // BUG(mikio): On JS and Plan 9, methods and functions related
// to IPConn are not implemented. // to IPConn are not implemented.
// BUG(mikio): On Windows, the File method of IPConn is not // BUG: On Windows, raw IP sockets are restricted by the operating system.
// implemented. // Sending TCP data, sending UDP data with invalid source addresses,
// and calling bind with TCP protocol don't work.
//
// See Winsock reference for details.
func ipAddrFromAddr(addr netip.Addr) *IPAddr {
return &IPAddr{
IP: addr.AsSlice(),
Zone: addr.Zone(),
}
}
// IPAddr represents the address of an IP end point. // IPAddr represents the address of an IP end point.
type IPAddr struct { type IPAddr struct {
@@ -80,14 +91,14 @@ func ResolveIPAddr(network, address string) (*IPAddr, error) {
return nil, errors.New("ResolveIPAddr not implemented") return nil, errors.New("ResolveIPAddr not implemented")
} }
// IPConn is the implementation of the Conn and PacketConn interfaces // IPConn is the implementation of the [Conn] and [PacketConn] interfaces
// for IP network connections. // for IP network connections.
type IPConn struct { type IPConn struct {
conn conn
} }
// SyscallConn returns a raw network connection. // SyscallConn returns a raw network connection.
// This implements the syscall.Conn interface. // This implements the [syscall.Conn] interface.
func (c *IPConn) SyscallConn() (syscall.RawConn, error) { func (c *IPConn) SyscallConn() (syscall.RawConn, error) {
return nil, errors.New("SyscallConn not implemented") return nil, errors.New("SyscallConn not implemented")
} }
@@ -114,7 +125,7 @@ func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) {
return 0, errors.New("WriteToIP not implemented") return 0, errors.New("WriteToIP not implemented")
} }
// WriteTo implements the PacketConn WriteTo method. // WriteTo implements the [PacketConn] WriteTo method.
func (c *IPConn) WriteTo(b []byte, addr Addr) (int, error) { func (c *IPConn) WriteTo(b []byte, addr Addr) (int, error) {
return 0, errors.New("WriteTo not implemented") return 0, errors.New("WriteTo not implemented")
} }
+3 -3
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.
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -30,7 +30,7 @@ func SplitHostPort(hostport string) (host, port string, err error) {
j, k := 0, 0 j, k := 0, 0
// The port starts after the last colon. // The port starts after the last colon.
i := last(hostport, ':') i := bytealg.LastIndexByteString(hostport, ':')
if i < 0 { if i < 0 {
return addrErr(hostport, missingPort) return addrErr(hostport, missingPort)
} }
@@ -77,7 +77,7 @@ func SplitHostPort(hostport string) (host, port string, err error) {
func splitHostZone(s string) (host, zone string) { func splitHostZone(s string) (host, zone string) {
// The IPv6 scoped addressing zone identifier starts after the // The IPv6 scoped addressing zone identifier starts after the
// last percent sign. // last percent sign.
if i := last(s, '%'); i > 0 { if i := bytealg.LastIndexByteString(s, '%'); i > 0 {
host, zone = s[:i], s[i+1:] host, zone = s[:i], s[i+1:]
} else { } else {
host = s host = s
+3 -3
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.
// Copyright 2012 The Go Authors. All rights reserved. // Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -12,8 +12,8 @@ import (
// LookupPort looks up the port for the given network and service. // LookupPort looks up the port for the given network and service.
// //
// LookupPort uses context.Background internally; to specify the context, use // LookupPort uses [context.Background] internally; to specify the context, use
// Resolver.LookupPort. // [Resolver.LookupPort].
func LookupPort(network, service string) (port int, err error) { func LookupPort(network, service string) (port int, err error) {
return 0, errors.New("net:LookupPort not implemented") return 0, errors.New("net:LookupPort not implemented")
} }
+1 -1
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied and modified from Go 1.22.0 official implementation. // TINYGO: The following is copied and modified from Go 1.26.2 official implementation.
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
+1 -1
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied and modified from Go 1.22.0 official implementation. // TINYGO: The following is copied and modified from Go 1.26.2 official implementation.
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
+20 -3
View File
@@ -1,4 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -38,8 +38,9 @@ func (a HardwareAddr) String() string {
// 0000.5e00.5301 // 0000.5e00.5301
// 0200.5e10.0000.0001 // 0200.5e10.0000.0001
// 0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001 // 0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001
// 00005e005301
func ParseMAC(s string) (hw HardwareAddr, err error) { func ParseMAC(s string) (hw HardwareAddr, err error) {
if len(s) < 14 { if len(s) < 12 {
goto error goto error
} }
@@ -79,7 +80,23 @@ func ParseMAC(s string) (hw HardwareAddr, err error) {
x += 5 x += 5
} }
} else { } else {
goto error if len(s)%2 != 0 {
goto error
}
n := len(s) / 2
if n != 6 && n != 8 && n != 20 {
goto error
}
hw = make(HardwareAddr, len(s)/2)
for x, i := 0, 0; i < n; i++ {
var ok bool
if hw[i], ok = xtoi2(s[x:x+2], 0); !ok {
goto error
}
x += 2
}
} }
return hw, nil return hw, nil
+13 -3
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -21,11 +19,13 @@ var parseMACTests = []struct {
{"00:00:5e:00:53:01", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""}, {"00:00:5e:00:53:01", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""},
{"00-00-5e-00-53-01", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""}, {"00-00-5e-00-53-01", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""},
{"0000.5e00.5301", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""}, {"0000.5e00.5301", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""},
{"00005e005301", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""},
// See RFC 7042, Section 2.2.2. // See RFC 7042, Section 2.2.2.
{"02:00:5e:10:00:00:00:01", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""}, {"02:00:5e:10:00:00:00:01", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""},
{"02-00-5e-10-00-00-00-01", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""}, {"02-00-5e-10-00-00-00-01", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""},
{"0200.5e10.0000.0001", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""}, {"0200.5e10.0000.0001", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""},
{"02005e1000000001", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""},
// See RFC 4391, Section 9.1.1. // See RFC 4391, Section 9.1.1.
{ {
@@ -55,6 +55,15 @@ var parseMACTests = []struct {
}, },
"", "",
}, },
{
"00000000fe8000000000000002005e1000000001",
HardwareAddr{
0x00, 0x00, 0x00, 0x00,
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01,
},
"",
},
{"ab:cd:ef:AB:CD:EF", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}, ""}, {"ab:cd:ef:AB:CD:EF", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}, ""},
{"ab:cd:ef:AB:CD:EF:ab:cd", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd}, ""}, {"ab:cd:ef:AB:CD:EF:ab:cd", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd}, ""},
@@ -80,6 +89,7 @@ var parseMACTests = []struct {
{"01:02-03-04-05-06", nil, "invalid MAC address"}, {"01:02-03-04-05-06", nil, "invalid MAC address"},
{"0123:4567:89AF", nil, "invalid MAC address"}, {"0123:4567:89AF", nil, "invalid MAC address"},
{"0123-4567-89AF", nil, "invalid MAC address"}, {"0123-4567-89AF", nil, "invalid MAC address"},
{"0123456789AF0", nil, "invalid MAC address"},
} }
func TestParseMAC(t *testing.T) { func TestParseMAC(t *testing.T) {
+5 -3
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.
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -14,8 +14,8 @@ import (
// Addr represents a network end point address. // Addr represents a network end point address.
// //
// The two methods Network and String conventionally return strings // The two methods [Addr.Network] and [Addr.String] conventionally return strings
// that can be passed as the arguments to Dial, but the exact form // that can be passed as the arguments to [Dial], but the exact form
// and meaning of the strings is up to the implementation. // and meaning of the strings is up to the implementation.
type Addr interface { type Addr interface {
Network() string // name of the network (for example, "tcp", "udp") Network() string // name of the network (for example, "tcp", "udp")
@@ -38,6 +38,8 @@ type Conn interface {
// Close closes the connection. // Close closes the connection.
// Any blocked Read or Write operations will be unblocked and return errors. // Any blocked Read or Write operations will be unblocked and return errors.
// Close may or may not block until any buffered data is sent;
// for TCP connections see [*TCPConn.SetLinger].
Close() error Close() error
// LocalAddr returns the local network address, if known. // LocalAddr returns the local network address, if known.
+9 -64
View File
@@ -1,6 +1,4 @@
// TINYGO: The following is copied from Go 1.21.4 official implementation. // TINYGO: The following is copied from Go 1.26.2 official implementation.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -66,6 +64,14 @@ func (f *file) readLine() (s string, ok bool) {
return return
} }
func (f *file) stat() (mtime time.Time, size int64, err error) {
st, err := f.file.Stat()
if err != nil {
return time.Time{}, 0, err
}
return st.ModTime(), st.Size(), nil
}
func open(name string) (*file, error) { func open(name string) (*file, error) {
fd, err := os.Open(name) fd, err := os.Open(name)
if err != nil { if err != nil {
@@ -174,42 +180,6 @@ func xtoi2(s string, e byte) (byte, bool) {
return byte(n), ok && ei == 2 return byte(n), ok && ei == 2
} }
// Convert i to a hexadecimal string. Leading zeros are not printed.
func appendHex(dst []byte, i uint32) []byte {
if i == 0 {
return append(dst, '0')
}
for j := 7; j >= 0; j-- {
v := i >> uint(j*4)
if v > 0 {
dst = append(dst, hexDigit[v&0xf])
}
}
return dst
}
// Number of occurrences of b in s.
func count(s string, b byte) int {
n := 0
for i := 0; i < len(s); i++ {
if s[i] == b {
n++
}
}
return n
}
// Index of rightmost occurrence of b in s.
func last(s string, b byte) int {
i := len(s)
for i--; i >= 0; i-- {
if s[i] == b {
break
}
}
return i
}
// hasUpperCase tells whether the given string contains at least one upper-case. // hasUpperCase tells whether the given string contains at least one upper-case.
func hasUpperCase(s string) bool { func hasUpperCase(s string) bool {
for i := range s { for i := range s {
@@ -281,23 +251,12 @@ func foreachField(x string, fn func(field string) error) error {
return nil return nil
} }
// stringsHasSuffix is strings.HasSuffix. It reports whether s ends in
// suffix.
func stringsHasSuffix(s, suffix string) bool {
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}
// stringsHasSuffixFold reports whether s ends in suffix, // stringsHasSuffixFold reports whether s ends in suffix,
// ASCII-case-insensitively. // ASCII-case-insensitively.
func stringsHasSuffixFold(s, suffix string) bool { func stringsHasSuffixFold(s, suffix string) bool {
return len(s) >= len(suffix) && stringsEqualFold(s[len(s)-len(suffix):], suffix) return len(s) >= len(suffix) && stringsEqualFold(s[len(s)-len(suffix):], suffix)
} }
// stringsHasPrefix is strings.HasPrefix. It reports whether s begins with prefix.
func stringsHasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
// stringsEqualFold is strings.EqualFold, ASCII only. It reports whether s and t // stringsEqualFold is strings.EqualFold, ASCII only. It reports whether s and t
// are equal, ASCII-case-insensitively. // are equal, ASCII-case-insensitively.
func stringsEqualFold(s, t string) bool { func stringsEqualFold(s, t string) bool {
@@ -311,17 +270,3 @@ func stringsEqualFold(s, t string) bool {
} }
return true return true
} }
func readFull(r io.Reader) (all []byte, err error) {
buf := make([]byte, 1024)
for {
n, err := r.Read(buf)
all = append(all, buf[:n]...)
if err == io.EOF {
return all, nil
}
if err != nil {
return nil, err
}
}
}
+1 -3
View File
@@ -1,5 +1,3 @@
// The following is copied from Go 1.21.4 official implementation.
// Copyright 2010 The Go Authors. All rights reserved. // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -108,7 +106,7 @@ type pipe struct {
} }
// Pipe creates a synchronous, in-memory, full duplex // Pipe creates a synchronous, in-memory, full duplex
// network connection; both ends implement the Conn interface. // network connection; both ends implement the [Conn] interface.
// Reads on one end are matched with writes on the other, // Reads on one end are matched with writes on the other,
// copying data directly between the two; there is no internal // copying data directly between the two; there is no internal
// buffering. // buffering.
+24 -10
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.
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -9,7 +9,6 @@ package net
import ( import (
"errors" "errors"
"fmt" "fmt"
"internal/itoa"
"io" "io"
"net/netip" "net/netip"
"strconv" "strconv"
@@ -17,6 +16,17 @@ import (
"time" "time"
) )
// KeepAliveConfig specifies the keep-alive probe configuration
// for an active network connection, when supported by the
// protocol and operating system.
// TINYGO: Stub definition for compatibility; not used on embedded devices.
type KeepAliveConfig struct {
Enable bool
Idle time.Duration
Interval time.Duration
Count int
}
// TCPAddr represents the address of a TCP end point. // TCPAddr represents the address of a TCP end point.
type TCPAddr struct { type TCPAddr struct {
IP IP IP IP
@@ -24,7 +34,7 @@ type TCPAddr struct {
Zone string // IPv6 scoped addressing zone Zone string // IPv6 scoped addressing zone
} }
// AddrPort returns the TCPAddr a as a netip.AddrPort. // AddrPort returns the [TCPAddr] a as a [netip.AddrPort].
// //
// If a.Port does not fit in a uint16, it's silently truncated. // If a.Port does not fit in a uint16, it's silently truncated.
// //
@@ -47,9 +57,9 @@ func (a *TCPAddr) String() string {
} }
ip := ipEmptyString(a.IP) ip := ipEmptyString(a.IP)
if a.Zone != "" { if a.Zone != "" {
return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port)) return JoinHostPort(ip+"%"+a.Zone, strconv.Itoa(a.Port))
} }
return JoinHostPort(ip, itoa.Itoa(a.Port)) return JoinHostPort(ip, strconv.Itoa(a.Port))
} }
func (a *TCPAddr) isWildcard() bool { func (a *TCPAddr) isWildcard() bool {
@@ -79,7 +89,7 @@ func (a *TCPAddr) opAddr() Addr {
// recommended, because it will return at most one of the host name's // recommended, because it will return at most one of the host name's
// IP addresses. // IP addresses.
// //
// See func Dial for a description of the network and address // See func [Dial] for a description of the network and address
// parameters. // parameters.
func ResolveTCPAddr(network, address string) (*TCPAddr, error) { func ResolveTCPAddr(network, address string) (*TCPAddr, error) {
@@ -119,7 +129,7 @@ func ResolveTCPAddr(network, address string) (*TCPAddr, error) {
return &TCPAddr{IP: ip.AsSlice(), Port: port}, nil return &TCPAddr{IP: ip.AsSlice(), Port: port}, nil
} }
// TCPAddrFromAddrPort returns addr as a TCPAddr. If addr.IsValid() is false, // TCPAddrFromAddrPort returns addr as a [TCPAddr]. If addr.IsValid() is false,
// then the returned TCPAddr will contain a nil IP field, indicating an // then the returned TCPAddr will contain a nil IP field, indicating an
// address family-agnostic unspecified address. // address family-agnostic unspecified address.
func TCPAddrFromAddrPort(addr netip.AddrPort) *TCPAddr { func TCPAddrFromAddrPort(addr netip.AddrPort) *TCPAddr {
@@ -130,7 +140,7 @@ func TCPAddrFromAddrPort(addr netip.AddrPort) *TCPAddr {
} }
} }
// TCPConn is an implementation of the Conn interface for TCP network // TCPConn is an implementation of the [Conn] interface for TCP network
// connections. // connections.
type TCPConn struct { type TCPConn struct {
fd int fd int
@@ -191,7 +201,7 @@ func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error) {
// TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc. // TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc.
// SyscallConn returns a raw network connection. // SyscallConn returns a raw network connection.
// This implements the syscall.Conn interface. // This implements the [syscall.Conn] interface.
func (c *TCPConn) SyscallConn() (syscall.RawConn, error) { func (c *TCPConn) SyscallConn() (syscall.RawConn, error) {
return nil, errors.New("SyscallConn not implemented") return nil, errors.New("SyscallConn not implemented")
} }
@@ -262,7 +272,11 @@ func (c *TCPConn) SetKeepAlive(keepalive bool) error {
return netdev.SetSockOpt(c.fd, _SOL_SOCKET, _SO_KEEPALIVE, keepalive) return netdev.SetSockOpt(c.fd, _SOL_SOCKET, _SO_KEEPALIVE, keepalive)
} }
// SetKeepAlivePeriod sets period between keep-alives. // SetKeepAlivePeriod sets the duration the connection needs to
// remain idle before TCP starts sending keepalive probes.
//
// Note that calling this method on Windows prior to Windows 10 version 1709
// will reset the KeepAliveInterval to the default system value, which is normally 1 second.
func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error { func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error {
// Units are 1/2 seconds // Units are 1/2 seconds
return netdev.SetSockOpt(c.fd, _SOL_TCP, _TCP_KEEPINTVL, 2*d.Seconds()) return netdev.SetSockOpt(c.fd, _SOL_TCP, _TCP_KEEPINTVL, 2*d.Seconds())
+6 -7
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.
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@@ -9,7 +9,6 @@ package net
import ( import (
"errors" "errors"
"fmt" "fmt"
"internal/itoa"
"io" "io"
"net/netip" "net/netip"
"strconv" "strconv"
@@ -24,7 +23,7 @@ type UDPAddr struct {
Zone string // IPv6 scoped addressing zone Zone string // IPv6 scoped addressing zone
} }
// AddrPort returns the UDPAddr a as a netip.AddrPort. // AddrPort returns the [UDPAddr] a as a [netip.AddrPort].
// //
// If a.Port does not fit in a uint16, it's silently truncated. // If a.Port does not fit in a uint16, it's silently truncated.
// //
@@ -47,9 +46,9 @@ func (a *UDPAddr) String() string {
} }
ip := ipEmptyString(a.IP) ip := ipEmptyString(a.IP)
if a.Zone != "" { if a.Zone != "" {
return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port)) return JoinHostPort(ip+"%"+a.Zone, strconv.Itoa(a.Port))
} }
return JoinHostPort(ip, itoa.Itoa(a.Port)) return JoinHostPort(ip, strconv.Itoa(a.Port))
} }
func (a *UDPAddr) isWildcard() bool { func (a *UDPAddr) isWildcard() bool {
@@ -79,7 +78,7 @@ func (a *UDPAddr) opAddr() Addr {
// recommended, because it will return at most one of the host name's // recommended, because it will return at most one of the host name's
// IP addresses. // IP addresses.
// //
// See func Dial for a description of the network and address // See func [Dial] for a description of the network and address
// parameters. // parameters.
func ResolveUDPAddr(network, address string) (*UDPAddr, error) { func ResolveUDPAddr(network, address string) (*UDPAddr, error) {
@@ -260,7 +259,7 @@ func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error) {
// data is copied from oob. It returns the number of payload and // data is copied from oob. It returns the number of payload and
// out-of-band bytes written. // out-of-band bytes written.
// //
// The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be // The packages [golang.org/x/net/ipv4] and [golang.org/x/net/ipv6] can be
// used to manipulate IP-level socket options in oob. // used to manipulate IP-level socket options in oob.
func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error) { func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error) {
return 0, 0, errors.New("WriteMsgUDP not implemented") return 0, 0, errors.New("WriteMsgUDP not implemented")
+1 -1
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.
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style