mirror of
https://github.com/tinygo-org/net.git
synced 2026-07-26 08:18:39 +00:00
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:
@@ -19,7 +19,7 @@ packages in a TinyGo application.
|
||||
|
||||
## "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
|
||||
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.
|
||||
|
||||
@@ -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 Fast Fallback support
|
||||
@@ -30,11 +30,14 @@ const (
|
||||
// mptcpStatus is a tristate for Multipath TCP, see go.dev/issue/56539
|
||||
type mptcpStatus uint8
|
||||
|
||||
// TINYGO: mptcpStatusListen stub for ListenConfig compatibility
|
||||
type mptcpStatusListen uint8
|
||||
|
||||
// A Dialer contains options for connecting to an address.
|
||||
//
|
||||
// The zero value for each field is equivalent to dialing
|
||||
// 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.
|
||||
type Dialer struct {
|
||||
@@ -66,12 +69,24 @@ type Dialer struct {
|
||||
|
||||
// KeepAlive specifies the interval between keep-alive
|
||||
// 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
|
||||
// (currently 15 seconds), if supported by the protocol and operating
|
||||
// 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.
|
||||
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.
|
||||
@@ -86,7 +101,7 @@ func Dial(network, address string) (Conn, error) {
|
||||
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.
|
||||
// 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
|
||||
// parameters.
|
||||
//
|
||||
// Dial uses context.Background internally; to specify the context, use
|
||||
// DialContext.
|
||||
// Dial uses [context.Background] internally; to specify the context, use
|
||||
// [Dialer.DialContext].
|
||||
func (d *Dialer) Dial(network, address string) (Conn, error) {
|
||||
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
|
||||
// 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.
|
||||
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
|
||||
// connection but before binding it to the operating system.
|
||||
//
|
||||
// Network and address parameters passed to Control method are not
|
||||
// necessarily the ones passed to Listen. For example, passing "tcp" to
|
||||
// Listen will cause the Control function to be called with "tcp4" or "tcp6".
|
||||
// Network and address parameters passed to Control function are not
|
||||
// necessarily the ones passed to Listen. Calling Listen with TCP networks
|
||||
// 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
|
||||
|
||||
// KeepAlive specifies the keep-alive period for network
|
||||
// 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
|
||||
// that do not support keep-alives ignore this field.
|
||||
// If negative, keep-alives are disabled.
|
||||
// that do not support keep-alive ignore this field.
|
||||
// If negative, keep-alive are disabled.
|
||||
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
|
||||
// used, any call to Listen with "tcp(4|6)" as network will use MPTCP if
|
||||
// supported by the operating system.
|
||||
mptcpStatus mptcpStatus
|
||||
mptcpStatus mptcpStatusListen
|
||||
}
|
||||
|
||||
// Listen announces on the local network address.
|
||||
//
|
||||
// See func Listen for a description of the network and address
|
||||
// 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) {
|
||||
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
|
||||
// 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) {
|
||||
return nil, errors.New("dial:ListenConfig:ListenPacket not implemented")
|
||||
}
|
||||
|
||||
+66
-60
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -27,34 +27,33 @@ import (
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// A Client is an HTTP client. Its zero value (DefaultClient) is a
|
||||
// usable client that uses DefaultTransport.
|
||||
// A Client is an HTTP client. Its zero value ([DefaultClient]) is a
|
||||
// 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
|
||||
// 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
|
||||
// redirects.
|
||||
//
|
||||
// When following redirects, the Client will forward all headers set on the
|
||||
// initial Request except:
|
||||
// initial [Request] except:
|
||||
//
|
||||
// • when forwarding sensitive headers like "Authorization",
|
||||
// "WWW-Authenticate", and "Cookie" to untrusted targets.
|
||||
// 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.
|
||||
// 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.
|
||||
//
|
||||
// • when forwarding the "Cookie" header with a non-nil 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.
|
||||
// When forwarding the "Cookie" header, any mutated cookies will be omitted,
|
||||
// with the expectation that the Jar will insert those mutated cookies
|
||||
// with the updated values (assuming the origin matches).
|
||||
// If Jar is nil, the initial cookies are forwarded without change.
|
||||
// - when forwarding sensitive headers like "Authorization",
|
||||
// "WWW-Authenticate", and "Cookie" to untrusted targets.
|
||||
// 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.
|
||||
// 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.
|
||||
// - when forwarding the "Cookie" header with a non-nil 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.
|
||||
// When forwarding the "Cookie" header, any mutated cookies will be omitted,
|
||||
// with the expectation that the Jar will insert those mutated cookies
|
||||
// with the updated values (assuming the origin matches).
|
||||
// If Jar is nil, the initial cookies are forwarded without change.
|
||||
type Client struct {
|
||||
// Transport specifies the mechanism by which individual
|
||||
// HTTP requests are made.
|
||||
@@ -106,11 +105,11 @@ type Client struct {
|
||||
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{}
|
||||
|
||||
// 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
|
||||
// goroutines.
|
||||
@@ -144,8 +143,13 @@ type RoundTripper interface {
|
||||
|
||||
// didTimeout is non-nil only if err != nil.
|
||||
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 {
|
||||
for _, cookie := range c.Jar.Cookies(req.URL) {
|
||||
for _, cookie := range c.Jar.Cookies(cookieURL) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
@@ -155,7 +159,7 @@ func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTime
|
||||
}
|
||||
if c.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
c.Jar.SetCookies(cookieURL, rc)
|
||||
}
|
||||
}
|
||||
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
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// To make a request with custom headers, use NewRequest and
|
||||
// To make a request with custom headers, use [NewRequest] and
|
||||
// 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.
|
||||
func Get(url string) (resp *Response, err error) {
|
||||
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
|
||||
// following redirect codes, Get follows the redirect after calling the
|
||||
// Client's CheckRedirect function:
|
||||
// [Client.CheckRedirect] function:
|
||||
//
|
||||
// 301 (Moved Permanently)
|
||||
// 302 (Found)
|
||||
@@ -354,18 +358,18 @@ func Get(url string) (resp *Response, err error) {
|
||||
// 307 (Temporary 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
|
||||
// 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
|
||||
// timed out.
|
||||
//
|
||||
// When err is nil, resp always contains a non-nil resp.Body.
|
||||
// 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.
|
||||
func (c *Client) Get(url string) (resp *Response, err error) {
|
||||
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
|
||||
// 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
|
||||
// read to EOF and closed, the Client's underlying RoundTripper
|
||||
// (typically Transport) may not be able to re-use a persistent TCP
|
||||
// read to EOF and closed, the [Client]'s underlying [RoundTripper]
|
||||
// (typically [Transport]) may not be able to re-use a persistent TCP
|
||||
// connection to the server for a subsequent "keep-alive" request.
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// (or HEAD if the original request was HEAD), with no body.
|
||||
// A 307 or 308 redirect preserves the original HTTP method and body,
|
||||
// provided that the Request.GetBody function is defined.
|
||||
// The NewRequest function automatically sets GetBody for common
|
||||
// provided that the [Request.GetBody] function is defined.
|
||||
// The [NewRequest] function automatically sets GetBody for common
|
||||
// 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.
|
||||
func (c *Client) Do(req *Request) (*Response, error) {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
_ = *c // panic early if c is nil; see go.dev/issue/53521
|
||||
|
||||
var err error
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// and Client.Do.
|
||||
// To make a request with a specified context.Context, use [NewRequestWithContext]
|
||||
// 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.
|
||||
func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) {
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
// Caller should close resp.Body when done reading from it.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
func PostForm(url string, data url.Values) (resp *Response, err error) {
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
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()))
|
||||
@@ -554,7 +560,7 @@ func (c *Client) PostForm(url string, data url.Values) (resp *Response, err erro
|
||||
//
|
||||
// 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.
|
||||
func Head(url string) (resp *Response, err error) {
|
||||
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
|
||||
// following redirect codes, Head follows the redirect after calling the
|
||||
// Client's CheckRedirect function:
|
||||
// [Client.CheckRedirect] function:
|
||||
//
|
||||
// 301 (Moved Permanently)
|
||||
// 302 (Found)
|
||||
@@ -570,8 +576,8 @@ func Head(url string) (resp *Response, err error) {
|
||||
// 307 (Temporary Redirect)
|
||||
// 308 (Permanent Redirect)
|
||||
//
|
||||
// To make a request with a specified context.Context, use NewRequestWithContext
|
||||
// and Client.Do.
|
||||
// To make a request with a specified [context.Context], use [NewRequestWithContext]
|
||||
// and [Client.Do].
|
||||
func (c *Client) Head(url string) (resp *Response, err error) {
|
||||
req, err := NewRequest("HEAD", url, nil)
|
||||
if err != nil {
|
||||
|
||||
+49
-4
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -10,8 +8,18 @@ import (
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"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 {
|
||||
if v == nil {
|
||||
return nil
|
||||
@@ -21,6 +29,15 @@ func cloneURLValues(v url.Values) url.Values {
|
||||
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 {
|
||||
if u == nil {
|
||||
return nil
|
||||
@@ -34,6 +51,15 @@ func cloneURL(u *url.URL) *url.URL {
|
||||
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 {
|
||||
if f == nil {
|
||||
return nil
|
||||
@@ -42,7 +68,7 @@ func cloneMultipartForm(f *multipart.Form) *multipart.Form {
|
||||
Value: (map[string][]string)(Header(f.Value).Clone()),
|
||||
}
|
||||
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 {
|
||||
vv2 := make([]*multipart.FileHeader, len(vv))
|
||||
for i, v := range vv {
|
||||
@@ -55,6 +81,15 @@ func cloneMultipartForm(f *multipart.Form) *multipart.Form {
|
||||
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 {
|
||||
if fh == nil {
|
||||
return nil
|
||||
@@ -67,6 +102,16 @@ func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader {
|
||||
|
||||
// cloneOrMakeHeader invokes Header.Clone but if the
|
||||
// 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 {
|
||||
clone := hdr.Clone()
|
||||
if clone == nil {
|
||||
|
||||
+237
-132
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied and modified from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied and modified from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -9,6 +7,7 @@ package http
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/godebug"
|
||||
"log"
|
||||
"net"
|
||||
"net/http/internal/ascii"
|
||||
@@ -18,13 +17,16 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var httpcookiemaxnum = godebug.New("httpcookiemaxnum")
|
||||
|
||||
// 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.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc6265 for details.
|
||||
type Cookie struct {
|
||||
Name string
|
||||
Value string
|
||||
Name string
|
||||
Value string
|
||||
Quoted bool // indicates whether the Value was originally quoted
|
||||
|
||||
Path string // optional
|
||||
Domain string // optional
|
||||
@@ -34,12 +36,13 @@ type Cookie struct {
|
||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||
// MaxAge>0 means Max-Age attribute present and given in seconds
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
SameSite SameSite
|
||||
Raw string
|
||||
Unparsed []string // Raw text of unparsed attribute-value pairs
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
SameSite SameSite
|
||||
Partitioned bool
|
||||
Raw string
|
||||
Unparsed []string // Raw text of unparsed attribute-value pairs
|
||||
}
|
||||
|
||||
// SameSite allows a server to define a cookie attribute making it impossible for
|
||||
@@ -57,115 +60,192 @@ const (
|
||||
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
|
||||
// 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 {
|
||||
cookieCount := len(h["Set-Cookie"])
|
||||
if cookieCount == 0 {
|
||||
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)
|
||||
for _, line := range h["Set-Cookie"] {
|
||||
parts := strings.Split(textproto.TrimString(line), ";")
|
||||
if len(parts) == 1 && parts[0] == "" {
|
||||
continue
|
||||
if cookie, err := ParseSetCookie(line); err == nil {
|
||||
cookies = append(cookies, cookie)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// silently dropped.
|
||||
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 other fields are set).
|
||||
// If c is nil or c.Name is invalid, the empty string is returned.
|
||||
func (c *Cookie) String() string {
|
||||
if c == nil || !isCookieNameValid(c.Name) {
|
||||
if c == nil || !isToken(c.Name) {
|
||||
return ""
|
||||
}
|
||||
// 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.WriteString(c.Name)
|
||||
b.WriteRune('=')
|
||||
b.WriteString(sanitizeCookieValue(c.Value))
|
||||
b.WriteString(sanitizeCookieValue(c.Value, c.Quoted))
|
||||
|
||||
if len(c.Path) > 0 {
|
||||
b.WriteString("; Path=")
|
||||
@@ -238,6 +318,9 @@ func (c *Cookie) String() string {
|
||||
case SameSiteStrictMode:
|
||||
b.WriteString("; SameSite=Strict")
|
||||
}
|
||||
if c.Partitioned {
|
||||
b.WriteString("; Partitioned")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -246,7 +329,7 @@ func (c *Cookie) Valid() error {
|
||||
if c == nil {
|
||||
return errors.New("http: nil Cookie")
|
||||
}
|
||||
if !isCookieNameValid(c.Name) {
|
||||
if !isToken(c.Name) {
|
||||
return errors.New("http: invalid Cookie.Name")
|
||||
}
|
||||
if !c.Expires.IsZero() && !validCookieExpires(c.Expires) {
|
||||
@@ -269,19 +352,39 @@ func (c *Cookie) Valid() error {
|
||||
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
|
||||
}
|
||||
|
||||
// readCookies parses all "Cookie" values from the header h and
|
||||
// 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 {
|
||||
lines := h["Cookie"]
|
||||
if len(lines) == 0 {
|
||||
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], ";"))
|
||||
for _, line := range lines {
|
||||
line = textproto.TrimString(line)
|
||||
@@ -295,17 +398,17 @@ func readCookies(h Header, filter string) []*Cookie {
|
||||
}
|
||||
name, val, _ := strings.Cut(part, "=")
|
||||
name = textproto.TrimString(name)
|
||||
if !isCookieNameValid(name) {
|
||||
if !isToken(name) {
|
||||
continue
|
||||
}
|
||||
if filter != "" && filter != name {
|
||||
continue
|
||||
}
|
||||
val, ok := parseCookieValue(val, true)
|
||||
val, quoted, ok := parseCookieValue(val, true)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
cookies = append(cookies, &Cookie{Name: name, Value: val})
|
||||
cookies = append(cookies, &Cookie{Name: name, Value: val, Quoted: quoted})
|
||||
}
|
||||
}
|
||||
return cookies
|
||||
@@ -340,7 +443,8 @@ func isCookieDomainName(s string) bool {
|
||||
}
|
||||
|
||||
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:]
|
||||
}
|
||||
last := byte('.')
|
||||
@@ -390,6 +494,8 @@ func sanitizeCookieName(n string) string {
|
||||
}
|
||||
|
||||
// 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
|
||||
//
|
||||
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
|
||||
@@ -399,15 +505,11 @@ func sanitizeCookieName(n string) string {
|
||||
// ; and backslash
|
||||
//
|
||||
// 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
|
||||
// commas or spaces.
|
||||
// thus we produce a quoted cookie-value if v contains commas or spaces.
|
||||
// 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)
|
||||
if len(v) == 0 {
|
||||
return v
|
||||
}
|
||||
if strings.ContainsAny(v, " ,") {
|
||||
if strings.ContainsAny(v, " ,") || quoted {
|
||||
return `"` + v + `"`
|
||||
}
|
||||
return v
|
||||
@@ -449,22 +551,25 @@ func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string {
|
||||
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.
|
||||
if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
|
||||
raw = raw[1 : len(raw)-1]
|
||||
quoted = true
|
||||
}
|
||||
for i := 0; i < len(raw); i++ {
|
||||
if !validCookieValueByte(raw[i]) {
|
||||
return "", false
|
||||
return "", quoted, false
|
||||
}
|
||||
}
|
||||
return raw, true
|
||||
}
|
||||
|
||||
func isCookieNameValid(raw string) bool {
|
||||
if raw == "" {
|
||||
return false
|
||||
}
|
||||
return strings.IndexFunc(raw, isNotToken) < 0
|
||||
return raw, quoted, true
|
||||
}
|
||||
|
||||
+175
-51
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -11,6 +9,7 @@ package http
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/godebug"
|
||||
"io"
|
||||
"io/fs"
|
||||
"mime"
|
||||
@@ -26,12 +25,12 @@ import (
|
||||
"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.
|
||||
//
|
||||
// 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
|
||||
// by filepath.Separator, which isn't necessarily '/'.
|
||||
// While the [FileSystem.Open] method takes '/'-separated paths, a Dir's string
|
||||
// value is a directory path on the native file system, not a URL, so it is separated
|
||||
// by [filepath.Separator], which isn't necessarily '/'.
|
||||
//
|
||||
// Note that Dir could expose sensitive files and directories. Dir will follow
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (d Dir) Open(name string) (File, error) {
|
||||
// TINYGO: internal/safefilepath isn't avail until 1.20, so keep pre 1.20
|
||||
// TINYGO: code here until TinyGo min Go version is >= 1.20.
|
||||
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
|
||||
return nil, errors.New("http: invalid character in file path")
|
||||
path := path.Clean("/" + name)[1:]
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
path, err := filepath.Localize(path)
|
||||
if err != nil {
|
||||
return nil, errInvalidUnsafePath
|
||||
}
|
||||
dir := string(d)
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
|
||||
fullName := filepath.Join(dir, path)
|
||||
f, err := os.Open(fullName)
|
||||
if err != nil {
|
||||
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.
|
||||
// The elements in a file path are separated by slash ('/', U+002F)
|
||||
// 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:
|
||||
// the FS adapter function converts an fs.FS to a FileSystem.
|
||||
// This interface predates the [fs.FS] interface, which can be used instead:
|
||||
// the [FS] adapter function converts an fs.FS to a FileSystem.
|
||||
type FileSystem interface {
|
||||
Open(name string) (File, error)
|
||||
}
|
||||
|
||||
// A File is returned by a FileSystem's Open method and can be
|
||||
// served by the FileServer implementation.
|
||||
// A File is returned by a [FileSystem]'s Open method and can be
|
||||
// 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 {
|
||||
io.Closer
|
||||
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) })
|
||||
|
||||
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")
|
||||
for i, n := 0, dirs.len(); i < n; i++ {
|
||||
name := dirs.name(i)
|
||||
@@ -168,8 +177,42 @@ func dirList(w ResponseWriter, r *Request, f File) {
|
||||
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
|
||||
// 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
|
||||
// handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since,
|
||||
// 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
|
||||
// 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
|
||||
// and passing it to DetectContentType.
|
||||
// and passing it to [DetectContentType].
|
||||
// The name is otherwise unused; in particular it can be empty and is
|
||||
// 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
|
||||
// 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,
|
||||
// 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) {
|
||||
sizeFunc := func() (int64, error) {
|
||||
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])
|
||||
_, err := content.Seek(0, io.SeekStart) // rewind to output whole file
|
||||
if err != nil {
|
||||
Error(w, "seeker can't seek", StatusInternalServerError)
|
||||
serveError(w, "seeker can't seek", StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -255,12 +304,12 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
|
||||
|
||||
size, err := sizeFunc()
|
||||
if err != nil {
|
||||
Error(w, err.Error(), StatusInternalServerError)
|
||||
serveError(w, err.Error(), StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if size < 0 {
|
||||
// Should never happen but just to be sure
|
||||
Error(w, "negative content size computed", StatusInternalServerError)
|
||||
serveError(w, "negative content size computed", StatusInternalServerError)
|
||||
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))
|
||||
fallthrough
|
||||
default:
|
||||
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
|
||||
serveError(w, err.Error(), StatusRequestedRangeNotSatisfiable)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -308,7 +357,7 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
|
||||
// multipart responses."
|
||||
ra := ranges[0]
|
||||
if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
|
||||
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
|
||||
serveError(w, err.Error(), StatusRequestedRangeNotSatisfiable)
|
||||
return
|
||||
}
|
||||
sendSize = ra.length
|
||||
@@ -345,10 +394,35 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
|
||||
}
|
||||
|
||||
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.WriteHeader(code)
|
||||
|
||||
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 modtime needs to be truncated too.
|
||||
modtime = modtime.Truncate(time.Second)
|
||||
// TINYGO: time.Compare not until Go 1.20
|
||||
if modtime.Before(t) || modtime.Equal(t) {
|
||||
if ret := modtime.Compare(t); ret <= 0 {
|
||||
return condTrue
|
||||
}
|
||||
return condFalse
|
||||
@@ -501,8 +574,7 @@ func checkIfModifiedSince(r *Request, modtime time.Time) condResult {
|
||||
// The Last-Modified header truncates sub-second precision so
|
||||
// the modtime needs to be truncated too.
|
||||
modtime = modtime.Truncate(time.Second)
|
||||
// TINYGO: time.Compare not until Go 1.20
|
||||
if modtime.Before(t) || modtime.Equal(t) {
|
||||
if ret := modtime.Compare(t); ret <= 0 {
|
||||
return condFalse
|
||||
}
|
||||
return condTrue
|
||||
@@ -618,7 +690,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
|
||||
f, err := fs.Open(name)
|
||||
if err != nil {
|
||||
msg, code := toHTTPError(err)
|
||||
Error(w, msg, code)
|
||||
serveError(w, msg, code)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
@@ -626,7 +698,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
|
||||
d, err := f.Stat()
|
||||
if err != nil {
|
||||
msg, code := toHTTPError(err)
|
||||
Error(w, msg, code)
|
||||
serveError(w, msg, code)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -639,11 +711,16 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
|
||||
localRedirect(w, r, path.Base(url)+"/")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if url[len(url)-1] == '/' {
|
||||
localRedirect(w, r, "../"+path.Base(url))
|
||||
} else if url[len(url)-1] == '/' {
|
||||
base := 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
|
||||
}
|
||||
localRedirect(w, r, "../"+base)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,6 +773,9 @@ func toHTTPError(err error) (msg string, httpStatus int) {
|
||||
if errors.Is(err, fs.ErrPermission) {
|
||||
return "403 Forbidden", StatusForbidden
|
||||
}
|
||||
if errors.Is(err, errInvalidUnsafePath) {
|
||||
return "404 page not found", StatusNotFound
|
||||
}
|
||||
// Default:
|
||||
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
|
||||
// interpreted relative to the current directory and may ascend to
|
||||
// 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
|
||||
// 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.
|
||||
//
|
||||
// As another special case, ServeFile 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.
|
||||
// use [ServeContent].
|
||||
//
|
||||
// Outside of those two special cases, ServeFile does not use
|
||||
// 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.
|
||||
// Note that name might not contain "..", for example if code (still
|
||||
// incorrectly) used filepath.Join(myDir, r.URL.Path).
|
||||
Error(w, "invalid URL path", StatusBadRequest)
|
||||
serveError(w, "invalid URL path", StatusBadRequest)
|
||||
return
|
||||
}
|
||||
dir, file := filepath.Split(name)
|
||||
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 {
|
||||
if !strings.Contains(v, "..") {
|
||||
return false
|
||||
}
|
||||
for _, ent := range strings.FieldsFunc(v, isSlashRune) {
|
||||
for ent := range strings.FieldsFuncSeq(v, isSlashRune) {
|
||||
if ent == ".." {
|
||||
return true
|
||||
}
|
||||
@@ -835,9 +948,9 @@ func (f ioFile) Readdir(count int) ([]fs.FileInfo, error) {
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// FS converts fsys to a FileSystem implementation,
|
||||
// for use with FileServer and NewFileTransport.
|
||||
// The files provided by fsys must implement io.Seeker.
|
||||
// FS converts fsys to a [FileSystem] implementation,
|
||||
// for use with [FileServer] and [NewFileTransport].
|
||||
// The files provided by fsys must implement [io.Seeker].
|
||||
func FS(fsys fs.FS) FileSystem {
|
||||
return ioFS{fsys}
|
||||
}
|
||||
@@ -850,17 +963,28 @@ func FS(fsys fs.FS) FileSystem {
|
||||
// "index.html".
|
||||
//
|
||||
// To use the operating system's file system implementation,
|
||||
// use http.Dir:
|
||||
// use [http.Dir]:
|
||||
//
|
||||
// http.Handle("/", http.FileServer(http.Dir("/tmp")))
|
||||
//
|
||||
// To use an fs.FS implementation, use http.FS to convert it:
|
||||
//
|
||||
// http.Handle("/", http.FileServer(http.FS(fsys)))
|
||||
// To use an [fs.FS] implementation, use [http.FileServerFS] instead.
|
||||
func FileServer(root FileSystem) Handler {
|
||||
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) {
|
||||
upath := r.URL.Path
|
||||
if !strings.HasPrefix(upath, "/") {
|
||||
@@ -898,7 +1022,7 @@ func parseRange(s string, size int64) ([]httpRange, error) {
|
||||
}
|
||||
var ranges []httpRange
|
||||
noOverlap := false
|
||||
for _, ra := range strings.Split(s[len(b):], ",") {
|
||||
for ra := range strings.SplitSeq(s[len(b):], ",") {
|
||||
ra = textproto.TrimString(ra)
|
||||
if ra == "" {
|
||||
continue
|
||||
|
||||
+11
-17
@@ -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
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"io"
|
||||
"net/http/internal/ascii"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -23,13 +23,13 @@ import (
|
||||
// A Header represents the key-value pairs in an HTTP header.
|
||||
//
|
||||
// The keys should be in canonical form, as returned by
|
||||
// CanonicalHeaderKey.
|
||||
// [CanonicalHeaderKey].
|
||||
type Header map[string][]string
|
||||
|
||||
// Add adds the key, value pair to the header.
|
||||
// It appends to any existing values associated with key.
|
||||
// The key is case insensitive; it is canonicalized by
|
||||
// CanonicalHeaderKey.
|
||||
// [CanonicalHeaderKey].
|
||||
func (h Header) Add(key, value string) {
|
||||
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
|
||||
// single element value. It replaces any existing values
|
||||
// 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.
|
||||
func (h Header) Set(key, value string) {
|
||||
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
|
||||
// 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
|
||||
// keys are stored in canonical form. To use non-canonical keys,
|
||||
// access the map directly.
|
||||
@@ -54,7 +54,7 @@ func (h Header) Get(key string) string {
|
||||
}
|
||||
|
||||
// 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
|
||||
// keys, access the map directly.
|
||||
// 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.
|
||||
// The key is case insensitive; it is canonicalized by
|
||||
// CanonicalHeaderKey.
|
||||
// [CanonicalHeaderKey].
|
||||
func (h Header) Del(key string) {
|
||||
textproto.MIMEHeader(h).Del(key)
|
||||
}
|
||||
@@ -128,7 +128,7 @@ var timeFormats = []string{
|
||||
|
||||
// ParseTime parses a time header (such as the Date: header),
|
||||
// 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) {
|
||||
for _, layout := range timeFormats {
|
||||
t, err = time.Parse(layout, text)
|
||||
@@ -155,17 +155,11 @@ type keyValues struct {
|
||||
values []string
|
||||
}
|
||||
|
||||
// A headerSorter implements sort.Interface by sorting a []keyValues
|
||||
// by key. It's used as a pointer, so it can fit in a sort.Interface
|
||||
// interface value without allocation.
|
||||
// headerSorter contains a slice of keyValues sorted by keyValues.key.
|
||||
type headerSorter struct {
|
||||
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{
|
||||
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
|
||||
sort.Sort(hs)
|
||||
slices.SortFunc(hs.kvs, func(a, b keyValues) int { return strings.Compare(a.key, b.key) })
|
||||
return kvs, hs
|
||||
}
|
||||
|
||||
|
||||
+148
-9
@@ -1,10 +1,8 @@
|
||||
// TINYGO: The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 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
|
||||
|
||||
@@ -18,6 +16,67 @@ import (
|
||||
"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
|
||||
// makes that struct also non-comparable, and generally doesn't add
|
||||
// any size (as long as it's first).
|
||||
@@ -60,8 +119,10 @@ func removeEmptyPort(host string) string {
|
||||
return host
|
||||
}
|
||||
|
||||
func isNotToken(r rune) bool {
|
||||
return !httpguts.IsTokenRune(r)
|
||||
// isToken reports whether v is a valid token (https://www.rfc-editor.org/rfc/rfc2616#section-2.2).
|
||||
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.
|
||||
@@ -105,10 +166,10 @@ func hexEscapeNonASCII(s string) string {
|
||||
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
|
||||
// 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{}
|
||||
|
||||
type noBody struct{}
|
||||
@@ -123,7 +184,7 @@ var (
|
||||
_ io.ReadCloser = NoBody
|
||||
)
|
||||
|
||||
// PushOptions describes options for Pusher.Push.
|
||||
// PushOptions describes options for [Pusher.Push].
|
||||
type PushOptions struct {
|
||||
// Method specifies the HTTP method for the promised request.
|
||||
// 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.
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -8,13 +8,19 @@ package httptest
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewRequest returns a new incoming server Request, suitable
|
||||
// for passing to an http.Handler for testing.
|
||||
// NewRequest wraps NewRequestWithContext using context.Background.
|
||||
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
|
||||
// path or an absolute URL. If target is an absolute URL, the host name
|
||||
@@ -27,16 +33,16 @@ import (
|
||||
//
|
||||
// An empty method means "GET".
|
||||
//
|
||||
// The provided body may be nil. If the body is of type *bytes.Reader,
|
||||
// *strings.Reader, or *bytes.Buffer, the Request.ContentLength is
|
||||
// set.
|
||||
// The provided body may be nil. If the body is of type [bytes.Reader],
|
||||
// [strings.Reader], [bytes.Buffer], or the value [http.NoBody],
|
||||
// the Request.ContentLength is set.
|
||||
//
|
||||
// NewRequest panics on error for ease of use in testing, where a
|
||||
// panic is acceptable.
|
||||
//
|
||||
// To generate a client HTTP request instead of a server request, see
|
||||
// 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 == "" {
|
||||
method = "GET"
|
||||
}
|
||||
@@ -44,6 +50,7 @@ func NewRequest(method, target string, body io.Reader) *http.Request {
|
||||
if err != nil {
|
||||
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.
|
||||
req.Proto = "HTTP/1.1"
|
||||
@@ -61,6 +68,9 @@ func NewRequest(method, target string, body io.Reader) *http.Request {
|
||||
default:
|
||||
req.ContentLength = -1
|
||||
}
|
||||
if body == http.NoBody {
|
||||
req.ContentLength = 0
|
||||
}
|
||||
if rc, ok := body.(io.ReadCloser); ok {
|
||||
req.Body = rc
|
||||
} else {
|
||||
|
||||
+35
-47
@@ -12,9 +12,11 @@ import (
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"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.
|
||||
type ResponseRecorder struct {
|
||||
// Code is the HTTP response code set by WriteHeader.
|
||||
@@ -45,7 +47,7 @@ type ResponseRecorder struct {
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
// NewRecorder returns an initialized [ResponseRecorder].
|
||||
func NewRecorder() *ResponseRecorder {
|
||||
return &ResponseRecorder{
|
||||
HeaderMap: make(http.Header),
|
||||
@@ -55,12 +57,12 @@ func NewRecorder() *ResponseRecorder {
|
||||
}
|
||||
|
||||
// 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"
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
func (rw *ResponseRecorder) Header() http.Header {
|
||||
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
|
||||
// rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||
// Record the write, even if we're going to return an error.
|
||||
rw.writeHeader(buf, "")
|
||||
if rw.Body != nil {
|
||||
rw.Body.Write(buf)
|
||||
}
|
||||
if !bodyAllowedForStatus(rw.Code) {
|
||||
return 0, http.ErrBodyNotAllowed
|
||||
}
|
||||
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.
|
||||
func (rw *ResponseRecorder) WriteString(str string) (int, error) {
|
||||
// Record the write, even if we're going to return an error.
|
||||
rw.writeHeader(nil, str)
|
||||
if rw.Body != nil {
|
||||
rw.Body.WriteString(str)
|
||||
}
|
||||
if !bodyAllowedForStatus(rw.Code) {
|
||||
return 0, http.ErrBodyNotAllowed
|
||||
}
|
||||
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) {
|
||||
// Issue 22880: require valid WriteHeader status codes.
|
||||
// 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) {
|
||||
if rw.wroteHeader {
|
||||
return
|
||||
@@ -152,7 +176,7 @@ func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||
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.
|
||||
func (rw *ResponseRecorder) Flush() {
|
||||
if !rw.wroteHeader {
|
||||
@@ -173,7 +197,7 @@ func (rw *ResponseRecorder) Flush() {
|
||||
// did a write.
|
||||
//
|
||||
// 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.
|
||||
func (rw *ResponseRecorder) Result() *http.Response {
|
||||
@@ -205,9 +229,9 @@ func (rw *ResponseRecorder) Result() *http.Response {
|
||||
if trailers, ok := rw.snapHeader["Trailer"]; ok {
|
||||
res.Trailer = make(http.Header, len(trailers))
|
||||
for _, k := range trailers {
|
||||
for _, k := range strings.Split(k, ",") {
|
||||
for k := range strings.SplitSeq(k, ",") {
|
||||
k = http.CanonicalHeaderKey(textproto.TrimString(k))
|
||||
if !validTrailerHeader(k) {
|
||||
if !httpguts.ValidTrailerHeader(k) {
|
||||
// Ignore since forbidden by RFC 7230, section 4.1.2.
|
||||
continue
|
||||
}
|
||||
@@ -251,39 +275,3 @@ func parseContentLength(cl string) int64 {
|
||||
}
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func newLocalListener() net.Listener {
|
||||
// When debugging a particular http server-based test,
|
||||
// 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.
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewServer(handler http.Handler) *Server {
|
||||
ts := NewUnstartedServer(handler)
|
||||
@@ -102,7 +102,7 @@ func NewServer(handler http.Handler) *Server {
|
||||
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
|
||||
// StartTLS.
|
||||
@@ -120,6 +120,7 @@ func (s *Server) Start() {
|
||||
if s.URL != "" {
|
||||
panic("Server already started")
|
||||
}
|
||||
|
||||
if s.client == nil {
|
||||
// TINYGO: Removed transport
|
||||
s.client = &http.Client{}
|
||||
@@ -216,7 +217,10 @@ func (s *Server) CloseClientConnections() {
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
return s.client
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
// 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
|
||||
// of redirected requests.
|
||||
//
|
||||
// See https://blog.golang.org/http-tracing for more.
|
||||
// See https://go.dev/blog/http-tracing for more.
|
||||
type ClientTrace struct {
|
||||
// GetConn is called before a connection is created or
|
||||
// retrieved from an idle pool. The hostPort is the
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.5 official implementation.
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -73,8 +71,8 @@ func outgoingLength(req *http.Request) int64 {
|
||||
return -1
|
||||
}
|
||||
|
||||
// DumpRequestOut is like DumpRequest but for outgoing client requests. It
|
||||
// includes any headers that the standard http.Transport adds, such as
|
||||
// DumpRequestOut is like [DumpRequest] but for outgoing client requests. It
|
||||
// includes any headers that the standard [http.Transport] adds, such as
|
||||
// User-Agent.
|
||||
func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
|
||||
save := req.Body
|
||||
@@ -149,7 +147,6 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
|
||||
|
||||
req.Body = save
|
||||
if err != nil {
|
||||
pw.Close()
|
||||
dr.err = err
|
||||
close(quitReadCh)
|
||||
return nil, err
|
||||
@@ -205,17 +202,17 @@ var reqWriteExcludeHeaderDump = map[string]bool{
|
||||
// representation. It should only be used by servers to debug client
|
||||
// requests. The returned representation is an approximation only;
|
||||
// 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
|
||||
// intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their
|
||||
// original binary representations.
|
||||
//
|
||||
// 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,
|
||||
// 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.
|
||||
func DumpRequest(req *http.Request, body bool) ([]byte, error) {
|
||||
var err error
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.5 official implementation.
|
||||
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// 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
|
||||
// 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
|
||||
// automatically decodes chunking when reading response bodies.
|
||||
|
||||
+17
-19
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.5 official implementation.
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// 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.
|
||||
// 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 {
|
||||
mu sync.Mutex // read-write protects the following fields
|
||||
c net.Conn
|
||||
@@ -52,7 +50,7 @@ type ServerConn struct {
|
||||
// It is low-level, old, and unused by Go's current HTTP stack.
|
||||
// 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 {
|
||||
if r == nil {
|
||||
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)}
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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) {
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
@@ -74,7 +72,7 @@ func (sc *ServerConn) Hijack() (net.Conn, *bufio.Reader) {
|
||||
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 {
|
||||
c, _ := sc.Hijack()
|
||||
if c != nil {
|
||||
@@ -83,7 +81,7 @@ func (sc *ServerConn) Close() error {
|
||||
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
|
||||
// first request on an HTTP/1.0 connection, or after a Connection:close on a
|
||||
// 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
|
||||
// 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 {
|
||||
|
||||
// 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.
|
||||
// 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 {
|
||||
mu sync.Mutex // read-write protects the following fields
|
||||
c net.Conn
|
||||
@@ -246,7 +244,7 @@ type ClientConn struct {
|
||||
// It is low-level, old, and unused by Go's current HTTP stack.
|
||||
// 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 {
|
||||
if r == nil {
|
||||
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.
|
||||
// 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 {
|
||||
cc := NewClientConn(c, r)
|
||||
cc.writeReq = (*http.Request).WriteProxy
|
||||
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
|
||||
// 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) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
@@ -284,7 +282,7 @@ func (cc *ClientConn) Hijack() (c net.Conn, r *bufio.Reader) {
|
||||
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 {
|
||||
c, _ := cc.Hijack()
|
||||
if c != nil {
|
||||
@@ -293,7 +291,7 @@ func (cc *ClientConn) Close() error {
|
||||
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
|
||||
// keep-alive connection is logically closed after this request and the opposing
|
||||
// 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
|
||||
// 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
|
||||
// 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) {
|
||||
// Retrieve the pipeline ID of this request/response pair
|
||||
cc.mu.Lock()
|
||||
|
||||
+185
-48
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.5 official implementation.
|
||||
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -23,12 +21,13 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
// In is the request received by the proxy.
|
||||
// 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
|
||||
// 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".
|
||||
// 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.
|
||||
// To preserve the inbound request's Host header (the default behavior
|
||||
// of NewSingleHostReverseProxy):
|
||||
// of [NewSingleHostReverseProxy]):
|
||||
//
|
||||
// rewriteFunc := func(r *httputil.ProxyRequest) {
|
||||
// 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,
|
||||
// SetXForwarded appends the client IP address to it. To append to the
|
||||
// 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:
|
||||
//
|
||||
// rewriteFunc := func(r *httputil.ProxyRequest) {
|
||||
@@ -102,6 +103,13 @@ func (r *ProxyRequest) SetXForwarded() {
|
||||
//
|
||||
// 1xx responses are forwarded to the client if the underlying
|
||||
// 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 {
|
||||
// Rewrite must be a function which modifies
|
||||
// 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.
|
||||
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.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
Transport http.RoundTripper
|
||||
@@ -188,6 +166,10 @@ type ReverseProxy struct {
|
||||
// If the backend is unreachable, the optional ErrorHandler is
|
||||
// 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
|
||||
// with its error value. If ErrorHandler is nil, its default
|
||||
// implementation is used.
|
||||
@@ -199,10 +181,92 @@ type ReverseProxy struct {
|
||||
// If nil, the default is to log the provided error and return
|
||||
// a 502 Status Bad Gateway response.
|
||||
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
|
||||
// byte slices for use by io.CopyBuffer.
|
||||
// byte slices for use by [io.CopyBuffer].
|
||||
type BufferPool interface {
|
||||
Get() []byte
|
||||
Put([]byte)
|
||||
@@ -241,13 +305,17 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
|
||||
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
|
||||
// target's path is "/base" and the incoming request was for "/dir",
|
||||
// the target request will be for /base/dir.
|
||||
//
|
||||
// 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
|
||||
// NewSingleHostReverseProxy provides, use ReverseProxy directly
|
||||
// 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
|
||||
}
|
||||
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
|
||||
// allowed, and the RoundTrip goroutine that reads the Body can outlive
|
||||
// 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", "")
|
||||
}
|
||||
|
||||
var (
|
||||
roundTripMutex sync.Mutex
|
||||
roundTripDone bool
|
||||
)
|
||||
trace := &httptrace.ClientTrace{
|
||||
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()
|
||||
copyHeader(h, http.Header(header))
|
||||
rw.WriteHeader(code)
|
||||
|
||||
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
|
||||
for k := range h {
|
||||
delete(h, k)
|
||||
}
|
||||
|
||||
clear(h)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace))
|
||||
|
||||
res, err := transport.RoundTrip(outreq)
|
||||
roundTripMutex.Lock()
|
||||
roundTripDone = true
|
||||
roundTripMutex.Unlock()
|
||||
if err != nil {
|
||||
p.getErrorHandler()(rw, outreq, err)
|
||||
return
|
||||
@@ -568,7 +659,7 @@ func shouldPanicOnCopyError(req *http.Request) bool {
|
||||
func removeHopByHopHeaders(h http.Header) {
|
||||
// RFC 7230, section 6.1: Remove headers listed in the "Connection" header.
|
||||
for _, f := range h["Connection"] {
|
||||
for _, sf := range strings.Split(f, ",") {
|
||||
for sf := range strings.SplitSeq(f, ",") {
|
||||
if sf = textproto.TrimString(sf); sf != "" {
|
||||
h.Del(sf)
|
||||
}
|
||||
@@ -730,6 +821,7 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
|
||||
resUpType := upgradeType(res.Header)
|
||||
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))
|
||||
return
|
||||
}
|
||||
if !ascii.EqualFold(reqUpType, resUpType) {
|
||||
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}
|
||||
go spc.copyToBackend(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
|
||||
// forth have nice names in stacks.
|
||||
type switchProtocolCopier struct {
|
||||
@@ -793,13 +893,33 @@ type switchProtocolCopier struct {
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
|
||||
_, err := io.Copy(c.user, c.backend)
|
||||
errc <- err
|
||||
if _, err := io.Copy(c.user, c.backend); err != nil {
|
||||
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) {
|
||||
_, err := io.Copy(c.backend, c.user)
|
||||
errc <- err
|
||||
if _, err := io.Copy(c.backend, c.user); err != nil {
|
||||
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 {
|
||||
@@ -834,3 +954,20 @@ func ishex(c byte) bool {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -11,7 +9,7 @@ import (
|
||||
"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.
|
||||
func EqualFold(s, t string) bool {
|
||||
if len(s) != len(t) {
|
||||
|
||||
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
+52
-16
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// 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
|
||||
// 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
|
||||
// automatically decodes chunking when reading response bodies.
|
||||
@@ -41,7 +39,8 @@ type chunkedReader struct {
|
||||
n uint64 // unread bytes in chunk
|
||||
err error
|
||||
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() {
|
||||
@@ -51,10 +50,36 @@ func (cr *chunkedReader) beginChunk() {
|
||||
if cr.err != nil {
|
||||
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)
|
||||
if cr.err != nil {
|
||||
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 {
|
||||
cr.err = io.EOF
|
||||
}
|
||||
@@ -139,26 +164,34 @@ func readChunkLine(b *bufio.Reader) ([]byte, error) {
|
||||
}
|
||||
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 {
|
||||
return nil, ErrLineTooLong
|
||||
}
|
||||
p = trimTrailingWhitespace(p)
|
||||
p, err = removeChunkExtension(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func isASCIISpace(b byte) bool {
|
||||
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
||||
func isOWS(b byte) bool {
|
||||
return b == ' ' || b == '\t'
|
||||
}
|
||||
|
||||
var semi = []byte(";")
|
||||
@@ -201,7 +234,7 @@ type chunkedWriter struct {
|
||||
|
||||
// Write the contents of data as one chunk to Wire.
|
||||
// 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) {
|
||||
|
||||
// Don't send 0-length data. It looks like EOF for chunked encoding.
|
||||
@@ -233,9 +266,9 @@ func (cw *chunkedWriter) Close() error {
|
||||
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
|
||||
// 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
|
||||
// request bodies which may be generated slowly. See Issue 6574.
|
||||
type FlushAfterChunkWriter struct {
|
||||
@@ -243,6 +276,9 @@ type FlushAfterChunkWriter struct {
|
||||
}
|
||||
|
||||
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 {
|
||||
switch {
|
||||
case '0' <= b && b <= '9':
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// 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
|
||||
{"10000000000000000", 0, "http chunk length too large"},
|
||||
{"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++ {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
+1
-1
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
+1
-1
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
+16
-10
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.5 official implementation.
|
||||
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -69,7 +67,7 @@
|
||||
// in your browser.
|
||||
//
|
||||
// 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
|
||||
|
||||
import (
|
||||
@@ -79,6 +77,7 @@ import (
|
||||
"fmt"
|
||||
"html"
|
||||
"internal/godebug"
|
||||
"internal/goexperiment"
|
||||
"internal/profile"
|
||||
"io"
|
||||
"log"
|
||||
@@ -353,12 +352,13 @@ func collectProfile(p *pprof.Profile) (*profile.Profile, error) {
|
||||
}
|
||||
|
||||
var profileSupportsDelta = map[handler]bool{
|
||||
"allocs": true,
|
||||
"block": true,
|
||||
"goroutine": true,
|
||||
"heap": true,
|
||||
"mutex": true,
|
||||
"threadcreate": true,
|
||||
"allocs": true,
|
||||
"block": true,
|
||||
"goroutineleak": true,
|
||||
"goroutine": true,
|
||||
"heap": true,
|
||||
"mutex": true,
|
||||
"threadcreate": true,
|
||||
}
|
||||
|
||||
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.",
|
||||
}
|
||||
|
||||
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 {
|
||||
Name string
|
||||
Href string
|
||||
|
||||
+137
-106
@@ -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 trace stuff
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http/internal/ascii"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
_ "unsafe" // for linkname
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
@@ -110,14 +112,10 @@ var reqWriteExcludeHeader = map[string]bool{
|
||||
//
|
||||
// The field semantics differ slightly between client and server
|
||||
// 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 {
|
||||
// Method specifies the HTTP method (GET, POST, PUT, etc.).
|
||||
// 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
|
||||
|
||||
// URL specifies either the URI being requested (for server
|
||||
@@ -327,6 +325,10 @@ type Request struct {
|
||||
// redirects.
|
||||
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
|
||||
// be modified via copying the whole Request using Clone or WithContext.
|
||||
// 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
|
||||
// Clone or WithContext.
|
||||
// [Request.Clone] or [Request.WithContext].
|
||||
//
|
||||
// The returned context is always non-nil; it defaults to the
|
||||
// background context.
|
||||
@@ -368,8 +370,8 @@ func (r *Request) Context() context.Context {
|
||||
// lifetime of a request and its response: obtaining a connection,
|
||||
// sending the request, and reading the response headers and body.
|
||||
//
|
||||
// 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 create a new request with a context, use [NewRequestWithContext].
|
||||
// To make a deep copy of a request with a new context, use [Request.Clone].
|
||||
func (r *Request) WithContext(ctx context.Context) *Request {
|
||||
if ctx == nil {
|
||||
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.
|
||||
// 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
|
||||
// lifetime of a request and its response: obtaining a connection,
|
||||
// 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.ctx = ctx
|
||||
r2.URL = cloneURL(r.URL)
|
||||
if r.Header != nil {
|
||||
r2.Header = r.Header.Clone()
|
||||
}
|
||||
if r.Trailer != nil {
|
||||
r2.Trailer = r.Trailer.Clone()
|
||||
}
|
||||
r2.Header = r.Header.Clone()
|
||||
r2.Trailer = r.Trailer.Clone()
|
||||
if s := r.TransferEncoding; s != nil {
|
||||
s2 := make([]string, len(s))
|
||||
copy(s2, s)
|
||||
@@ -415,13 +415,7 @@ func (r *Request) Clone(ctx context.Context) *Request {
|
||||
copy(s2, s)
|
||||
r2.matches = s2
|
||||
}
|
||||
if s := r.otherValues; s != nil {
|
||||
s2 := make(map[string]string, len(s))
|
||||
for k, v := range s {
|
||||
s2[k] = v
|
||||
}
|
||||
r2.otherValues = s2
|
||||
}
|
||||
r2.otherValues = maps.Clone(r.otherValues)
|
||||
return r2
|
||||
}
|
||||
|
||||
@@ -442,11 +436,20 @@ func (r *Request) Cookies() []*Cookie {
|
||||
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.
|
||||
var ErrNoCookie = errors.New("http: named cookie not present")
|
||||
|
||||
// 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
|
||||
// be returned.
|
||||
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 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,
|
||||
// separated by semicolon.
|
||||
// AddCookie only sanitizes c's name and value, and does not sanitize
|
||||
// a Cookie header already present in the request.
|
||||
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 != "" {
|
||||
r.Header.Set("Cookie", c+"; "+s)
|
||||
} else {
|
||||
@@ -478,7 +481,7 @@ func (r *Request) AddCookie(c *Cookie) {
|
||||
//
|
||||
// Referer is misspelled as in the request itself, a mistake 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
|
||||
// alternate (correct English) spelling req.Referrer() but cannot
|
||||
// diagnose programs that use Header["Referrer"].
|
||||
@@ -496,7 +499,7 @@ var multipartByReader = &multipart.Form{
|
||||
|
||||
// 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.
|
||||
// Use this function instead of ParseMultipartForm to
|
||||
// Use this function instead of [Request.ParseMultipartForm] to
|
||||
// process the request body as a stream.
|
||||
func (r *Request) MultipartReader() (*multipart.Reader, error) {
|
||||
if r.MultipartForm == multipartByReader {
|
||||
@@ -559,15 +562,15 @@ const defaultUserAgent = "Go-http-client/1.1"
|
||||
// TransferEncoding
|
||||
// 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:
|
||||
// chunked" to the header. Body is closed after it is sent.
|
||||
func (r *Request) Write(w io.Writer) error {
|
||||
return r.write(w, false, nil, nil)
|
||||
}
|
||||
|
||||
// WriteProxy is like Write but writes the request in the form
|
||||
// expected by an HTTP proxy. In particular, WriteProxy writes the
|
||||
// WriteProxy is like [Request.Write] but writes the request in the form
|
||||
// expected by an HTTP proxy. In particular, [Request.WriteProxy] writes the
|
||||
// initial Request-URI line of the request with an absolute URI, per
|
||||
// section 5.3 of RFC 7230, including the scheme and host.
|
||||
// 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")
|
||||
}
|
||||
if userAgent != "" {
|
||||
userAgent = headerNewlineToSpace.Replace(userAgent)
|
||||
userAgent = textproto.TrimString(userAgent)
|
||||
_, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -820,35 +825,36 @@ func validMethod(method string) bool {
|
||||
extension-method = token
|
||||
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) {
|
||||
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.
|
||||
//
|
||||
// If the provided body is also an io.Closer, the returned
|
||||
// Request.Body is set to body and will be closed by the Client
|
||||
// methods Do, Post, and PostForm, and Transport.RoundTrip.
|
||||
// If the provided body is also an [io.Closer], the returned
|
||||
// [Request.Body] is set to body and will be closed (possibly
|
||||
// asynchronously) by the Client methods Do, Post, and PostForm,
|
||||
// and [Transport.RoundTrip].
|
||||
//
|
||||
// NewRequestWithContext returns a Request suitable 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
|
||||
// net/http/httptest package, use ReadRequest, or manually update the
|
||||
// Request fields. For an outgoing client request, the context
|
||||
// [Client.Do] or [Transport.RoundTrip]. To create a request for use with
|
||||
// testing a Server Handler, either use the [net/http/httptest.NewRequest] function,
|
||||
// use [ReadRequest], or manually update the Request fields.
|
||||
// For an outgoing client request, the context
|
||||
// controls the entire lifetime of a request and its response:
|
||||
// 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.
|
||||
//
|
||||
// If body is of type *bytes.Buffer, *bytes.Reader, or
|
||||
// *strings.Reader, the returned request's ContentLength is set to its
|
||||
// If body is of type [*bytes.Buffer], [*bytes.Reader], or
|
||||
// [*strings.Reader], the returned request's ContentLength is set to its
|
||||
// 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.
|
||||
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
|
||||
if method == "" {
|
||||
@@ -944,6 +950,16 @@ func (r *Request) BasicAuth() (username, password string, ok bool) {
|
||||
|
||||
// parseBasicAuth parses an HTTP Basic Authentication string.
|
||||
// "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) {
|
||||
const prefix = "Basic "
|
||||
// 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
|
||||
// additional requirements on pre-escaping the username and
|
||||
// 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) {
|
||||
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 is a low-level function and should only be used for
|
||||
// specialized applications; most code should use the Server to read
|
||||
// requests and handle them via the Handler interface. ReadRequest
|
||||
// specialized applications; most code should use the [Server] to read
|
||||
// requests and handle them via the [Handler] interface. ReadRequest
|
||||
// only supports HTTP/1.x requests. For HTTP/2, use golang.org/x/net/http2.
|
||||
func ReadRequest(b *bufio.Reader) (*Request, error) {
|
||||
req, err := readRequest(b)
|
||||
@@ -1016,9 +1032,20 @@ func ReadRequest(b *bufio.Reader) (*Request, error) {
|
||||
}
|
||||
|
||||
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) {
|
||||
tp := newTextprotoReader(b)
|
||||
defer putTextprotoReader(tp)
|
||||
@@ -1116,15 +1143,15 @@ func readRequest(b *bufio.Reader) (req *Request, err error) {
|
||||
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
|
||||
// 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.
|
||||
//
|
||||
// MaxBytesReader prevents clients from accidentally or maliciously
|
||||
// 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.
|
||||
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
|
||||
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}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
// 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.
|
||||
//
|
||||
// For other HTTP methods, or when the Content-Type is not
|
||||
// application/x-www-form-urlencoded, the request Body is not read, and
|
||||
// r.PostForm is initialized to a non-nil, empty value.
|
||||
//
|
||||
// ParseMultipartForm calls ParseForm automatically.
|
||||
// [Request.ParseMultipartForm] calls ParseForm automatically.
|
||||
// ParseForm is idempotent.
|
||||
func (r *Request) ParseForm() 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
|
||||
// its file parts are stored in memory, with the remainder stored on
|
||||
// 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
|
||||
// continues parsing the request body.
|
||||
// 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.
|
||||
// POST and PUT body parameters take precedence over URL query string values.
|
||||
// FormValue calls ParseMultipartForm and ParseForm if necessary and ignores
|
||||
// any errors returned by these functions.
|
||||
// The precedence order:
|
||||
// 1. application/x-www-form-urlencoded form body (POST, PUT, PATCH only)
|
||||
// 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.
|
||||
// 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 {
|
||||
if r.Form == nil {
|
||||
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,
|
||||
// PATCH, or PUT request body. URL query parameters are ignored.
|
||||
// PostFormValue calls ParseMultipartForm and ParseForm if necessary and ignores
|
||||
// PUT, or PATCH request body. URL query parameters are ignored.
|
||||
// PostFormValue calls [Request.ParseMultipartForm] and [Request.ParseForm] if necessary and ignores
|
||||
// any errors returned by these functions.
|
||||
// If key is not present, PostFormValue returns the empty 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 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) {
|
||||
if r.MultipartForm == multipartByReader {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return hasToken(r.Header.get("Expect"), "100-continue")
|
||||
}
|
||||
@@ -1475,47 +1550,3 @@ func (r *Request) requiresHTTP1() bool {
|
||||
return hasToken(r.Header.Get("Connection"), "upgrade") &&
|
||||
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
@@ -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: 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.
|
||||
//
|
||||
// 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
|
||||
// is streamed on demand as the Body field is read.
|
||||
type Response struct {
|
||||
@@ -124,13 +124,13 @@ func (r *Response) Cookies() []*Cookie {
|
||||
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.
|
||||
var ErrNoLocation = errors.New("http: no Location header in response")
|
||||
|
||||
// Location returns the URL of the response's "Location" header,
|
||||
// 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.
|
||||
func (r *Response) Location() (*url.URL, error) {
|
||||
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.
|
||||
// The req parameter optionally specifies the Request that corresponds
|
||||
// to this Response. If nil, a GET request is assumed.
|
||||
// The req parameter optionally specifies the [Request] that corresponds
|
||||
// to this [Response]. If nil, a GET request is assumed.
|
||||
// Clients must call resp.Body.Close when finished reading resp.Body.
|
||||
// After that call, clients can inspect resp.Trailer to find key/value
|
||||
// pairs included in the response trailer.
|
||||
|
||||
+38
-16
@@ -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
|
||||
// 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.SetDeadline(time.Time{})
|
||||
|
||||
buf = bufio.NewReadWriter(c.bufr, bufio.NewWriter(rwc))
|
||||
if c.r.hasByte {
|
||||
if _, err := c.bufr.Peek(c.bufr.Buffered() + 1); err != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
@@ -592,9 +594,8 @@ type writerOnly struct {
|
||||
// to a *net.TCPConn with sendfile, or from a supported src type such
|
||||
// as a *net.TCPConn on Linux with splice.
|
||||
func (w *response) ReadFrom(src io.Reader) (n int64, err error) {
|
||||
bufp := copyBufPool.Get().(*[]byte)
|
||||
buf := *bufp
|
||||
defer copyBufPool.Put(bufp)
|
||||
buf := getCopyBuf()
|
||||
defer putCopyBuf(buf)
|
||||
|
||||
// Our underlying w.conn.rwc is usually a *TCPConn (with its
|
||||
// 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
|
||||
|
||||
// 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)
|
||||
n += n0
|
||||
w.written += n0
|
||||
@@ -824,11 +825,19 @@ var (
|
||||
bufioWriter4kPool sync.Pool
|
||||
)
|
||||
|
||||
var copyBufPool = sync.Pool{
|
||||
New: func() any {
|
||||
b := make([]byte, 32*1024)
|
||||
return &b
|
||||
},
|
||||
const copyBufPoolSize = 32 * 1024
|
||||
|
||||
var copyBufPool = sync.Pool{New: func() any { return new([copyBufPoolSize]byte) }}
|
||||
|
||||
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 {
|
||||
@@ -1770,7 +1779,7 @@ func (c *conn) close() {
|
||||
// from closing a connection with known unread data.
|
||||
// This RST seems to occur mostly on BSD systems. (And Windows?)
|
||||
// This timeout is somewhat arbitrary (~latency around the planet).
|
||||
const rstAvoidanceDelay = 500 * time.Millisecond
|
||||
var rstAvoidanceDelay = 500 * time.Millisecond
|
||||
|
||||
type closeWriter interface {
|
||||
CloseWrite() error
|
||||
@@ -2123,8 +2132,21 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
// writes are done to w.
|
||||
// The error message should be plain text.
|
||||
func Error(w ResponseWriter, error string, code int) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
h := w.Header()
|
||||
|
||||
// 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)
|
||||
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.
|
||||
// See RFC 7231, section 7.1.2
|
||||
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
|
||||
oldpath = "/"
|
||||
}
|
||||
@@ -3151,7 +3173,7 @@ func (s *Server) trackConn(c *conn, add bool) {
|
||||
}
|
||||
|
||||
func (s *Server) idleTimeout() time.Duration {
|
||||
if s.IdleTimeout != 0 {
|
||||
if s.IdleTimeout > 0 {
|
||||
return s.IdleTimeout
|
||||
}
|
||||
return s.ReadTimeout
|
||||
|
||||
+1
-1
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
+1
-1
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
+58
-42
@@ -1,4 +1,4 @@
|
||||
// TINYGO: The following is copied and modified from Go 1.21.4 official implementation.
|
||||
// TINYGO: The following is copied and modified from Go 1.26.2 official implementation.
|
||||
|
||||
// TINYGO: Removed trace stuff
|
||||
// TINYGO: Hook readTransfer is onEOF callback to get notified when request of
|
||||
@@ -15,12 +15,14 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/godebug"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http/internal"
|
||||
"net/http/internal/ascii"
|
||||
"net/textproto"
|
||||
"reflect"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -313,7 +315,7 @@ func (t *transferWriter) writeHeader(w io.Writer) error {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
sort.Strings(keys)
|
||||
slices.Sort(keys)
|
||||
// TODO: could do better allocation-wise here, but trailers are rare,
|
||||
// so being lazy for now.
|
||||
if _, err := io.WriteString(w, "Trailer: "+strings.Join(keys, ",")+"\r\n"); err != nil {
|
||||
@@ -342,7 +344,7 @@ func (t *transferWriter) writeBody(w io.Writer) (err error) {
|
||||
// nopCloser or readTrackingBody. This is to ensure that we can take advantage of
|
||||
// OS-level optimizations in the event that the body is an
|
||||
// *os.File.
|
||||
if t.Body != nil {
|
||||
if !t.ResponseToHEAD && t.Body != nil {
|
||||
var body = t.unwrapBody()
|
||||
if chunked(t.TransferEncoding) {
|
||||
if bw, ok := w.(*bufio.Writer); ok && !t.IsResponse {
|
||||
@@ -384,7 +386,7 @@ func (t *transferWriter) writeBody(w io.Writer) (err error) {
|
||||
t.ContentLength, ncopy)
|
||||
}
|
||||
|
||||
if chunked(t.TransferEncoding) {
|
||||
if !t.ResponseToHEAD && chunked(t.TransferEncoding) {
|
||||
// Write Trailer header
|
||||
if t.Trailer != nil {
|
||||
if err := t.Trailer.Write(w); err != nil {
|
||||
@@ -402,7 +404,10 @@ func (t *transferWriter) writeBody(w io.Writer) (err error) {
|
||||
//
|
||||
// This function is only intended for use in writeBody.
|
||||
func (t *transferWriter) doBodyCopy(dst io.Writer, src io.Reader) (n int64, err error) {
|
||||
n, err = io.Copy(dst, src)
|
||||
buf := getCopyBuf()
|
||||
defer putCopyBuf(buf)
|
||||
|
||||
n, err = io.CopyBuffer(dst, src, buf)
|
||||
if err != nil && err != io.EOF {
|
||||
t.bodyReadError = err
|
||||
}
|
||||
@@ -523,7 +528,7 @@ func readTransfer(msg any, r *bufio.Reader, onEOF func()) (err error) {
|
||||
return err
|
||||
}
|
||||
if isResponse && t.RequestMethod == "HEAD" {
|
||||
if n, err := parseContentLength(t.Header.get("Content-Length")); err != nil {
|
||||
if n, err := parseContentLength(t.Header["Content-Length"]); err != nil {
|
||||
return err
|
||||
} else {
|
||||
t.ContentLength = n
|
||||
@@ -642,19 +647,6 @@ func (t *transferReader) parseTransferEncoding() error {
|
||||
return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", raw[0])}
|
||||
}
|
||||
|
||||
// RFC 7230 3.3.2 says "A sender MUST NOT send a Content-Length header field
|
||||
// in any message that contains a Transfer-Encoding header field."
|
||||
//
|
||||
// but also: "If a message is received with both a Transfer-Encoding and a
|
||||
// Content-Length header field, the Transfer-Encoding overrides the
|
||||
// Content-Length. Such a message might indicate an attempt to perform
|
||||
// request smuggling (Section 9.5) or response splitting (Section 9.4) and
|
||||
// ought to be handled as an error. A sender MUST remove the received
|
||||
// Content-Length field prior to forwarding such a message downstream."
|
||||
//
|
||||
// Reportedly, these appear in the wild.
|
||||
delete(t.Header, "Content-Length")
|
||||
|
||||
t.Chunked = true
|
||||
return nil
|
||||
}
|
||||
@@ -662,7 +654,7 @@ func (t *transferReader) parseTransferEncoding() error {
|
||||
// Determine the expected body length, using RFC 7230 Section 3.3. This
|
||||
// function is not a method, because ultimately it should be shared by
|
||||
// ReadResponse and ReadRequest.
|
||||
func fixLength(isResponse bool, status int, requestMethod string, header Header, chunked bool) (int64, error) {
|
||||
func fixLength(isResponse bool, status int, requestMethod string, header Header, chunked bool) (n int64, err error) {
|
||||
isRequest := !isResponse
|
||||
contentLens := header["Content-Length"]
|
||||
|
||||
@@ -686,6 +678,14 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
|
||||
contentLens = header["Content-Length"]
|
||||
}
|
||||
|
||||
// Reject requests with invalid Content-Length headers.
|
||||
if len(contentLens) > 0 {
|
||||
n, err = parseContentLength(contentLens)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
}
|
||||
|
||||
// Logic based on response type or status
|
||||
if isResponse && noResponseBodyExpected(requestMethod) {
|
||||
return 0, nil
|
||||
@@ -698,23 +698,29 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// According to RFC 9112, "If a message is received with both a
|
||||
// Transfer-Encoding and a Content-Length header field, the Transfer-Encoding
|
||||
// overrides the Content-Length. Such a message might indicate an attempt to
|
||||
// perform request smuggling (Section 11.2) or response splitting (Section 11.1)
|
||||
// and ought to be handled as an error. An intermediary that chooses to forward
|
||||
// the message MUST first remove the received Content-Length field and process
|
||||
// the Transfer-Encoding (as described below) prior to forwarding the message downstream."
|
||||
//
|
||||
// Chunked-encoding requests with either valid Content-Length
|
||||
// headers or no Content-Length headers are accepted after removing
|
||||
// the Content-Length field from header.
|
||||
//
|
||||
// Logic based on Transfer-Encoding
|
||||
if chunked {
|
||||
header.Del("Content-Length")
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// Logic based on Content-Length
|
||||
var cl string
|
||||
if len(contentLens) == 1 {
|
||||
cl = textproto.TrimString(contentLens[0])
|
||||
}
|
||||
if cl != "" {
|
||||
n, err := parseContentLength(cl)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if len(contentLens) > 0 {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
header.Del("Content-Length")
|
||||
|
||||
if isRequest {
|
||||
@@ -812,10 +818,10 @@ type body struct {
|
||||
onHitEOF func() // if non-nil, func to call when EOF is Read
|
||||
}
|
||||
|
||||
// ErrBodyReadAfterClose is returned when reading a Request or Response
|
||||
// ErrBodyReadAfterClose is returned when reading a [Request] or [Response]
|
||||
// Body after the body has been closed. This typically happens when the body is
|
||||
// read after an HTTP Handler calls WriteHeader or Write on its
|
||||
// ResponseWriter.
|
||||
// read after an HTTP [Handler] calls WriteHeader or Write on its
|
||||
// [ResponseWriter].
|
||||
var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body")
|
||||
|
||||
func (b *body) Read(p []byte) (n int, err error) {
|
||||
@@ -945,9 +951,7 @@ func mergeSetHeader(dst *Header, src Header) {
|
||||
*dst = src
|
||||
return
|
||||
}
|
||||
for k, vv := range src {
|
||||
(*dst)[k] = vv
|
||||
}
|
||||
maps.Copy(*dst, src)
|
||||
}
|
||||
|
||||
// unreadDataSizeLocked returns the number of bytes of unread input.
|
||||
@@ -1034,19 +1038,31 @@ func (bl bodyLocked) Read(p []byte) (n int, err error) {
|
||||
return bl.b.readLocked(p)
|
||||
}
|
||||
|
||||
// parseContentLength trims whitespace from s and returns -1 if no value
|
||||
// is set, or the value if it's >= 0.
|
||||
func parseContentLength(cl string) (int64, error) {
|
||||
cl = textproto.TrimString(cl)
|
||||
if cl == "" {
|
||||
var httplaxcontentlength = godebug.New("httplaxcontentlength")
|
||||
|
||||
// parseContentLength checks that the header is valid and then trims
|
||||
// whitespace. It returns -1 if no value is set otherwise the value
|
||||
// if it's >= 0.
|
||||
func parseContentLength(clHeaders []string) (int64, error) {
|
||||
if len(clHeaders) == 0 {
|
||||
return -1, nil
|
||||
}
|
||||
cl := textproto.TrimString(clHeaders[0])
|
||||
|
||||
// The Content-Length must be a valid numeric value.
|
||||
// See: https://datatracker.ietf.org/doc/html/rfc2616/#section-14.13
|
||||
if cl == "" {
|
||||
if httplaxcontentlength.Value() == "1" {
|
||||
httplaxcontentlength.IncNonDefault()
|
||||
return -1, nil
|
||||
}
|
||||
return 0, badStringError("invalid empty Content-Length", cl)
|
||||
}
|
||||
n, err := strconv.ParseUint(cl, 10, 63)
|
||||
if err != nil {
|
||||
return 0, badStringError("bad Content-Length", cl)
|
||||
}
|
||||
return int64(n), nil
|
||||
|
||||
}
|
||||
|
||||
// finishAsyncByteRead finishes reading the 1-byte sniff
|
||||
|
||||
+4
-3
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -13,12 +13,13 @@ package http
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type readTrackingBody struct {
|
||||
io.ReadCloser
|
||||
didRead bool
|
||||
didClose bool
|
||||
didRead bool // not atomic.Bool because only one goroutine (the user's) should be accessing
|
||||
didClose atomic.Bool
|
||||
}
|
||||
|
||||
type Transport struct{}
|
||||
|
||||
+4
-4
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -30,7 +30,7 @@ var (
|
||||
type Interface struct {
|
||||
Index int // positive integer that starts at one, zero is never used
|
||||
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
|
||||
Flags Flags // e.g., FlagUp, FlagLoopback, FlagMulticast
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func Interfaces() ([]Interface, error) {
|
||||
// addresses.
|
||||
//
|
||||
// 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) {
|
||||
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
|
||||
// sharing the logical data link; for more precision use
|
||||
// InterfaceByName.
|
||||
// [InterfaceByName].
|
||||
func InterfaceByIndex(index int) (*Interface, error) {
|
||||
return nil, errors.New("InterfaceByIndex not implemented")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -16,7 +14,8 @@ package net
|
||||
|
||||
import (
|
||||
"internal/bytealg"
|
||||
"internal/itoa"
|
||||
"internal/strconv"
|
||||
"internal/stringslite"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
@@ -40,7 +39,7 @@ type IP []byte
|
||||
// An IPMask is a bitmask that can be used to manipulate
|
||||
// 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
|
||||
|
||||
// An IPNet represents an IP network.
|
||||
@@ -74,9 +73,9 @@ func IPv4Mask(a, b, c, d byte) IPMask {
|
||||
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.
|
||||
// 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 {
|
||||
if bits != 8*IPv4len && bits != 8*IPv6len {
|
||||
return nil
|
||||
@@ -302,11 +301,18 @@ func (ip IP) String() string {
|
||||
if len(ip) != IPv4len && len(ip) != IPv6len {
|
||||
return "?" + hexString(ip)
|
||||
}
|
||||
// If IPv4, use dotted notation.
|
||||
if p4 := ip.To4(); len(p4) == IPv4len {
|
||||
return netip.AddrFrom4([4]byte(p4)).String()
|
||||
|
||||
var buf []byte
|
||||
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 {
|
||||
@@ -326,21 +332,45 @@ func ipEmptyString(ip IP) string {
|
||||
return ip.String()
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
// The encoding is the same as returned by String, with one exception:
|
||||
// When len(ip) is zero, it returns an empty slice.
|
||||
func (ip IP) MarshalText() ([]byte, error) {
|
||||
if len(ip) == 0 {
|
||||
return []byte(""), nil
|
||||
// appendTo appends the string representation of ip to b and returns the expanded b
|
||||
// If len(ip) != IPv4len or IPv6len, it appends nothing.
|
||||
func (ip IP) appendTo(b []byte) []byte {
|
||||
// If IPv4, use dotted notation.
|
||||
if p4 := ip.To4(); len(p4) == IPv4len {
|
||||
ip = p4
|
||||
}
|
||||
if len(ip) != IPv4len && len(ip) != IPv6len {
|
||||
return nil, &AddrError{Err: "invalid IP address", Addr: hexString(ip)}
|
||||
}
|
||||
return []byte(ip.String()), nil
|
||||
addr, _ := netip.AddrFromSlice(ip)
|
||||
return addr.AppendTo(b)
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
// The IP address is expected in a form accepted by ParseIP.
|
||||
// AppendText implements the [encoding.TextAppender] interface.
|
||||
// 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 {
|
||||
if len(text) == 0 {
|
||||
*ip = nil
|
||||
@@ -485,14 +515,15 @@ func (n *IPNet) String() string {
|
||||
if l == -1 {
|
||||
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.
|
||||
// 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.
|
||||
// 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 {
|
||||
if addr, valid := parseIP(s); valid {
|
||||
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
|
||||
// 192.0.2.1 and the network 192.0.2.0/24.
|
||||
func ParseCIDR(s string) (IP, *IPNet, error) {
|
||||
i := bytealg.IndexByteString(s, '/')
|
||||
if i < 0 {
|
||||
addr, mask, found := stringslite.Cut(s, "/")
|
||||
if !found {
|
||||
return nil, nil, &ParseError{Type: "CIDR address", Text: s}
|
||||
}
|
||||
addr, mask := s[:i], s[i+1:]
|
||||
|
||||
ipAddr, err := netip.ParseAddr(addr)
|
||||
if err != nil || ipAddr.Zone() != "" {
|
||||
|
||||
+17
-6
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -8,6 +8,7 @@ package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@@ -26,8 +27,18 @@ import (
|
||||
// BUG(mikio): On JS and Plan 9, methods and functions related
|
||||
// to IPConn are not implemented.
|
||||
|
||||
// BUG(mikio): On Windows, the File method of IPConn is not
|
||||
// implemented.
|
||||
// BUG: On Windows, raw IP sockets are restricted by the operating system.
|
||||
// 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.
|
||||
type IPAddr struct {
|
||||
@@ -80,14 +91,14 @@ func ResolveIPAddr(network, address string) (*IPAddr, error) {
|
||||
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.
|
||||
type IPConn struct {
|
||||
conn
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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")
|
||||
}
|
||||
|
||||
// WriteTo implements the PacketConn WriteTo method.
|
||||
// WriteTo implements the [PacketConn] WriteTo method.
|
||||
func (c *IPConn) WriteTo(b []byte, addr Addr) (int, error) {
|
||||
return 0, errors.New("WriteTo not implemented")
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
// 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
|
||||
|
||||
// The port starts after the last colon.
|
||||
i := last(hostport, ':')
|
||||
i := bytealg.LastIndexByteString(hostport, ':')
|
||||
if i < 0 {
|
||||
return addrErr(hostport, missingPort)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func SplitHostPort(hostport string) (host, port string, err error) {
|
||||
func splitHostZone(s string) (host, zone string) {
|
||||
// The IPv6 scoped addressing zone identifier starts after the
|
||||
// last percent sign.
|
||||
if i := last(s, '%'); i > 0 {
|
||||
if i := bytealg.LastIndexByteString(s, '%'); i > 0 {
|
||||
host, zone = s[:i], s[i+1:]
|
||||
} else {
|
||||
host = s
|
||||
|
||||
@@ -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.
|
||||
// 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 uses context.Background internally; to specify the context, use
|
||||
// Resolver.LookupPort.
|
||||
// LookupPort uses [context.Background] internally; to specify the context, use
|
||||
// [Resolver.LookupPort].
|
||||
func LookupPort(network, service string) (port int, err error) {
|
||||
return 0, errors.New("net:LookupPort not implemented")
|
||||
}
|
||||
|
||||
+1
-1
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
+1
-1
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -38,8 +38,9 @@ func (a HardwareAddr) String() string {
|
||||
// 0000.5e00.5301
|
||||
// 0200.5e10.0000.0001
|
||||
// 0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001
|
||||
// 00005e005301
|
||||
func ParseMAC(s string) (hw HardwareAddr, err error) {
|
||||
if len(s) < 14 {
|
||||
if len(s) < 12 {
|
||||
goto error
|
||||
}
|
||||
|
||||
@@ -79,7 +80,23 @@ func ParseMAC(s string) (hw HardwareAddr, err error) {
|
||||
x += 5
|
||||
}
|
||||
} 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
|
||||
|
||||
|
||||
+13
-3
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// 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}, ""},
|
||||
{"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.
|
||||
{"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}, ""},
|
||||
{"02005e1000000001", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""},
|
||||
|
||||
// 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: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"},
|
||||
{"0123:4567:89AF", nil, "invalid MAC address"},
|
||||
{"0123-4567-89AF", nil, "invalid MAC address"},
|
||||
{"0123456789AF0", nil, "invalid MAC address"},
|
||||
}
|
||||
|
||||
func TestParseMAC(t *testing.T) {
|
||||
|
||||
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
|
||||
// Addr represents a network end point address.
|
||||
//
|
||||
// The two methods Network and String conventionally return strings
|
||||
// that can be passed as the arguments to Dial, but the exact form
|
||||
// The two methods [Addr.Network] and [Addr.String] conventionally return strings
|
||||
// that can be passed as the arguments to [Dial], but the exact form
|
||||
// and meaning of the strings is up to the implementation.
|
||||
type Addr interface {
|
||||
Network() string // name of the network (for example, "tcp", "udp")
|
||||
@@ -38,6 +38,8 @@ type Conn interface {
|
||||
|
||||
// Close closes the connection.
|
||||
// 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
|
||||
|
||||
// LocalAddr returns the local network address, if known.
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// TINYGO: The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// TINYGO: The following is copied from Go 1.26.2 official implementation.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@@ -66,6 +64,14 @@ func (f *file) readLine() (s string, ok bool) {
|
||||
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) {
|
||||
fd, err := os.Open(name)
|
||||
if err != nil {
|
||||
@@ -174,42 +180,6 @@ func xtoi2(s string, e byte) (byte, bool) {
|
||||
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.
|
||||
func hasUpperCase(s string) bool {
|
||||
for i := range s {
|
||||
@@ -281,23 +251,12 @@ func foreachField(x string, fn func(field string) error) error {
|
||||
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,
|
||||
// ASCII-case-insensitively.
|
||||
func stringsHasSuffixFold(s, suffix string) bool {
|
||||
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
|
||||
// are equal, ASCII-case-insensitively.
|
||||
func stringsEqualFold(s, t string) bool {
|
||||
@@ -311,17 +270,3 @@ func stringsEqualFold(s, t string) bool {
|
||||
}
|
||||
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,5 +1,3 @@
|
||||
// The following is copied from Go 1.21.4 official implementation.
|
||||
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
@@ -108,7 +106,7 @@ type pipe struct {
|
||||
}
|
||||
|
||||
// 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,
|
||||
// copying data directly between the two; there is no internal
|
||||
// buffering.
|
||||
|
||||
+24
-10
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -9,7 +9,6 @@ package net
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/itoa"
|
||||
"io"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
@@ -17,6 +16,17 @@ import (
|
||||
"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.
|
||||
type TCPAddr struct {
|
||||
IP IP
|
||||
@@ -24,7 +34,7 @@ type TCPAddr struct {
|
||||
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.
|
||||
//
|
||||
@@ -47,9 +57,9 @@ func (a *TCPAddr) String() string {
|
||||
}
|
||||
ip := ipEmptyString(a.IP)
|
||||
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 {
|
||||
@@ -79,7 +89,7 @@ func (a *TCPAddr) opAddr() Addr {
|
||||
// recommended, because it will return at most one of the host name's
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// address family-agnostic unspecified address.
|
||||
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.
|
||||
type TCPConn struct {
|
||||
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.
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Units are 1/2 seconds
|
||||
return netdev.SetSockOpt(c.fd, _SOL_TCP, _TCP_KEEPINTVL, 2*d.Seconds())
|
||||
|
||||
+6
-7
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -9,7 +9,6 @@ package net
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/itoa"
|
||||
"io"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
@@ -24,7 +23,7 @@ type UDPAddr struct {
|
||||
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.
|
||||
//
|
||||
@@ -47,9 +46,9 @@ func (a *UDPAddr) String() string {
|
||||
}
|
||||
ip := ipEmptyString(a.IP)
|
||||
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 {
|
||||
@@ -79,7 +78,7 @@ func (a *UDPAddr) opAddr() Addr {
|
||||
// recommended, because it will return at most one of the host name's
|
||||
// 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.
|
||||
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
|
||||
// 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.
|
||||
func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error) {
|
||||
return 0, 0, errors.New("WriteMsgUDP not implemented")
|
||||
|
||||
+1
-1
@@ -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.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
|
||||
Reference in New Issue
Block a user