diff options
Diffstat (limited to 'libgo/go/net')
180 files changed, 2205 insertions, 596 deletions
diff --git a/libgo/go/net/addrselect.go b/libgo/go/net/addrselect.go index 6fb6baf..4603c55 100644 --- a/libgo/go/net/addrselect.go +++ b/libgo/go/net/addrselect.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris // Minimal RFC 6724 address selection. diff --git a/libgo/go/net/addrselect_test.go b/libgo/go/net/addrselect_test.go index a43ac5e..18784fe 100644 --- a/libgo/go/net/addrselect_test.go +++ b/libgo/go/net/addrselect_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/cgo_aix.go b/libgo/go/net/cgo_aix.go index 4f23d9b..577649f 100644 --- a/libgo/go/net/cgo_aix.go +++ b/libgo/go/net/cgo_aix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cgo && !netgo // +build cgo,!netgo package net diff --git a/libgo/go/net/cgo_android.go b/libgo/go/net/cgo_android.go index ab0368d..4b1a2e3 100644 --- a/libgo/go/net/cgo_android.go +++ b/libgo/go/net/cgo_android.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cgo && !netgo // +build cgo,!netgo package net diff --git a/libgo/go/net/cgo_bsd.go b/libgo/go/net/cgo_bsd.go index c1adf20..1268c89 100644 --- a/libgo/go/net/cgo_bsd.go +++ b/libgo/go/net/cgo_bsd.go @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build cgo,!netgo +//go:build cgo && !netgo && (darwin || dragonfly || freebsd) +// +build cgo +// +build !netgo // +build darwin dragonfly freebsd package net diff --git a/libgo/go/net/cgo_linux.go b/libgo/go/net/cgo_linux.go index 36bac34..4b45dad 100644 --- a/libgo/go/net/cgo_linux.go +++ b/libgo/go/net/cgo_linux.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !android && cgo && !netgo // +build !android,cgo,!netgo package net diff --git a/libgo/go/net/cgo_netbsd.go b/libgo/go/net/cgo_netbsd.go index 1ce0139..e23899d 100644 --- a/libgo/go/net/cgo_netbsd.go +++ b/libgo/go/net/cgo_netbsd.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cgo && !netgo // +build cgo,!netgo package net diff --git a/libgo/go/net/cgo_openbsd.go b/libgo/go/net/cgo_openbsd.go index 4610246..3714793 100644 --- a/libgo/go/net/cgo_openbsd.go +++ b/libgo/go/net/cgo_openbsd.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cgo && !netgo // +build cgo,!netgo package net diff --git a/libgo/go/net/cgo_resnew.go b/libgo/go/net/cgo_resnew.go index 8bc1c1c..6611fd7 100644 --- a/libgo/go/net/cgo_resnew.go +++ b/libgo/go/net/cgo_resnew.go @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build cgo,!netgo +//go:build cgo && !netgo && (aix || darwin || hurd || (linux && !android) || netbsd || solaris) +// +build cgo +// +build !netgo // +build aix darwin hurd linux,!android netbsd solaris package net diff --git a/libgo/go/net/cgo_resold.go b/libgo/go/net/cgo_resold.go index 8e13e41..33f664c 100644 --- a/libgo/go/net/cgo_resold.go +++ b/libgo/go/net/cgo_resold.go @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build cgo,!netgo +//go:build cgo && !netgo && (android || freebsd || dragonfly || openbsd) +// +build cgo +// +build !netgo // +build android freebsd dragonfly openbsd package net diff --git a/libgo/go/net/cgo_socknew.go b/libgo/go/net/cgo_socknew.go index 9dcd158..84b40c9 100644 --- a/libgo/go/net/cgo_socknew.go +++ b/libgo/go/net/cgo_socknew.go @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build cgo,!netgo +//go:build cgo && !netgo && (android || linux || solaris) +// +build cgo +// +build !netgo // +build android linux solaris package net diff --git a/libgo/go/net/cgo_sockold.go b/libgo/go/net/cgo_sockold.go index f514679..703b41b 100644 --- a/libgo/go/net/cgo_sockold.go +++ b/libgo/go/net/cgo_sockold.go @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build cgo,!netgo +//go:build cgo && !netgo && (aix || darwin || dragonfly || freebsd || hurd || netbsd || openbsd) +// +build cgo +// +build !netgo // +build aix darwin dragonfly freebsd hurd netbsd openbsd package net diff --git a/libgo/go/net/cgo_solaris.go b/libgo/go/net/cgo_solaris.go index bd1e8f3..95d5db5 100644 --- a/libgo/go/net/cgo_solaris.go +++ b/libgo/go/net/cgo_solaris.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cgo && !netgo // +build cgo,!netgo package net diff --git a/libgo/go/net/cgo_stub.go b/libgo/go/net/cgo_stub.go index 041f8af..039e4be 100644 --- a/libgo/go/net/cgo_stub.go +++ b/libgo/go/net/cgo_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !cgo || netgo // +build !cgo netgo package net diff --git a/libgo/go/net/cgo_unix.go b/libgo/go/net/cgo_unix.go index 0c9a488..462bf12 100644 --- a/libgo/go/net/cgo_unix.go +++ b/libgo/go/net/cgo_unix.go @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build cgo,!netgo +//go:build cgo && !netgo && (aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris) +// +build cgo +// +build !netgo // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/cgo_unix_test.go b/libgo/go/net/cgo_unix_test.go index e844ef5..98b3b4a 100644 --- a/libgo/go/net/cgo_unix_test.go +++ b/libgo/go/net/cgo_unix_test.go @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build cgo,!netgo +//go:build cgo && !netgo && (aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris) +// +build cgo +// +build !netgo // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/cgo_windows.go b/libgo/go/net/cgo_windows.go index 8968b75..1fd1f297 100644 --- a/libgo/go/net/cgo_windows.go +++ b/libgo/go/net/cgo_windows.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cgo && !netgo // +build cgo,!netgo package net diff --git a/libgo/go/net/conf.go b/libgo/go/net/conf.go index b0f1b79..fe7ebf1 100644 --- a/libgo/go/net/conf.go +++ b/libgo/go/net/conf.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/conf_netcgo.go b/libgo/go/net/conf_netcgo.go index abc33ce..c705152 100644 --- a/libgo/go/net/conf_netcgo.go +++ b/libgo/go/net/conf_netcgo.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build netcgo // +build netcgo package net diff --git a/libgo/go/net/conf_test.go b/libgo/go/net/conf_test.go index a8e1807..f5e4d86 100644 --- a/libgo/go/net/conf_test.go +++ b/libgo/go/net/conf_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/conn_test.go b/libgo/go/net/conn_test.go index 771cabc..45e271c 100644 --- a/libgo/go/net/conn_test.go +++ b/libgo/go/net/conn_test.go @@ -5,6 +5,7 @@ // This file implements API tests across platforms and will never have a build // tag. +//go:build !js // +build !js package net diff --git a/libgo/go/net/dial.go b/libgo/go/net/dial.go index 13a312a..486ced0 100644 --- a/libgo/go/net/dial.go +++ b/libgo/go/net/dial.go @@ -344,6 +344,9 @@ type sysDialer struct { // // See func Dial for a description of the network and address // parameters. +// +// Dial uses context.Background internally; to specify the context, use +// DialContext. func (d *Dialer) Dial(network, address string) (Conn, error) { return d.DialContext(context.Background(), network, address) } @@ -701,6 +704,9 @@ type sysListener struct { // // See func Dial for a description of the network and address // parameters. +// +// Listen uses context.Background internally; to specify the context, use +// ListenConfig.Listen. func Listen(network, address string) (Listener, error) { var lc ListenConfig return lc.Listen(context.Background(), network, address) @@ -728,6 +734,9 @@ func Listen(network, address string) (Listener, error) { // // See func Dial for a description of the network and address // parameters. +// +// ListenPacket uses context.Background internally; to specify the context, use +// ListenConfig.ListenPacket. func ListenPacket(network, address string) (PacketConn, error) { var lc ListenConfig return lc.ListenPacket(context.Background(), network, address) diff --git a/libgo/go/net/dial_test.go b/libgo/go/net/dial_test.go index 57cf555..723038c 100644 --- a/libgo/go/net/dial_test.go +++ b/libgo/go/net/dial_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net @@ -154,40 +155,27 @@ func slowDialTCP(ctx context.Context, network string, laddr, raddr *TCPAddr) (*T return c, err } -func dialClosedPort(t *testing.T) (actual, expected time.Duration) { - // Estimate the expected time for this platform. - // On Windows, dialing a closed port takes roughly 1 second, - // but other platforms should be instantaneous. - if runtime.GOOS == "windows" { - expected = 1500 * time.Millisecond - } else if runtime.GOOS == "darwin" || runtime.GOOS == "ios" { - expected = 150 * time.Millisecond - } else { - expected = 95 * time.Millisecond - } +func dialClosedPort(t *testing.T) (dialLatency time.Duration) { + // On most platforms, dialing a closed port should be nearly instantaneous — + // less than a few hundred milliseconds. However, on some platforms it may be + // much slower: on Windows and OpenBSD, it has been observed to take up to a + // few seconds. l, err := Listen("tcp", "127.0.0.1:0") if err != nil { - t.Logf("dialClosedPort: Listen failed: %v", err) - return 999 * time.Hour, expected + t.Fatalf("dialClosedPort: Listen failed: %v", err) } addr := l.Addr().String() l.Close() - // On OpenBSD, interference from TestTCPSelfConnect is mysteriously - // causing the first attempt to hang for a few seconds, so we throw - // away the first result and keep the second. - for i := 1; ; i++ { - startTime := time.Now() - c, err := Dial("tcp", addr) - if err == nil { - c.Close() - } - elapsed := time.Now().Sub(startTime) - if i == 2 { - t.Logf("dialClosedPort: measured delay %v", elapsed) - return elapsed, expected - } + + startTime := time.Now() + c, err := Dial("tcp", addr) + if err == nil { + c.Close() } + elapsed := time.Now().Sub(startTime) + t.Logf("dialClosedPort: measured delay %v", elapsed) + return elapsed } func TestDialParallel(t *testing.T) { @@ -197,10 +185,7 @@ func TestDialParallel(t *testing.T) { t.Skip("both IPv4 and IPv6 are required") } - closedPortDelay, expectClosedPortDelay := dialClosedPort(t) - if closedPortDelay > expectClosedPortDelay { - t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay) - } + closedPortDelay := dialClosedPort(t) const instant time.Duration = 0 const fallbackDelay = 200 * time.Millisecond @@ -655,15 +640,7 @@ func TestDialerLocalAddr(t *testing.T) { } c, err := d.Dial(tt.network, addr) if err == nil && tt.error != nil || err != nil && tt.error == nil { - // A suspected kernel bug in macOS 10.12 occasionally results in - // timeout errors when dialing address ::1. The errors have not - // been observed on newer versions of the OS, so we don't plan to work - // around them. See https://golang.org/issue/22019. - if tt.raddr == "::1" && os.Getenv("GO_BUILDER_NAME") == "darwin-amd64-10_12" && os.IsTimeout(err) { - t.Logf("ignoring timeout error on Darwin; see https://golang.org/issue/22019") - } else { - t.Errorf("%s %v->%s: got %v; want %v", tt.network, tt.laddr, tt.raddr, err, tt.error) - } + t.Errorf("%s %v->%s: got %v; want %v", tt.network, tt.laddr, tt.raddr, err, tt.error) } if err != nil { if perr := parseDialError(err); perr != nil { @@ -682,10 +659,7 @@ func TestDialerDualStack(t *testing.T) { t.Skip("both IPv4 and IPv6 are required") } - closedPortDelay, expectClosedPortDelay := dialClosedPort(t) - if closedPortDelay > expectClosedPortDelay { - t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay) - } + closedPortDelay := dialClosedPort(t) origTestHookLookupIP := testHookLookupIP defer func() { testHookLookupIP = origTestHookLookupIP }() diff --git a/libgo/go/net/dial_unix_test.go b/libgo/go/net/dial_unix_test.go index 1891d8c..4b9bc27 100644 --- a/libgo/go/net/dial_unix_test.go +++ b/libgo/go/net/dial_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/dnsclient.go b/libgo/go/net/dnsclient.go index e9c7384..1bbe396 100644 --- a/libgo/go/net/dnsclient.go +++ b/libgo/go/net/dnsclient.go @@ -5,6 +5,7 @@ package net import ( + "internal/itoa" "sort" "golang.org/x/net/dns/dnsmessage" @@ -33,7 +34,7 @@ func reverseaddr(addr string) (arpa string, err error) { return "", &DNSError{Err: "unrecognized address", Name: addr} } if ip.To4() != nil { - return uitoa(uint(ip[15])) + "." + uitoa(uint(ip[14])) + "." + uitoa(uint(ip[13])) + "." + uitoa(uint(ip[12])) + ".in-addr.arpa.", nil + return itoa.Uitoa(uint(ip[15])) + "." + itoa.Uitoa(uint(ip[14])) + "." + itoa.Uitoa(uint(ip[13])) + "." + itoa.Uitoa(uint(ip[12])) + ".in-addr.arpa.", nil } // Must be IPv6 buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) diff --git a/libgo/go/net/dnsclient_unix.go b/libgo/go/net/dnsclient_unix.go index c5bfab9..a326319 100644 --- a/libgo/go/net/dnsclient_unix.go +++ b/libgo/go/net/dnsclient_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris // DNS client: see RFC 1035. @@ -17,6 +18,7 @@ package net import ( "context" "errors" + "internal/itoa" "io" "os" "sync" @@ -509,7 +511,7 @@ func (o hostLookupOrder) String() string { if s, ok := lookupOrderName[o]; ok { return s } - return "hostLookupOrder=" + itoa(int(o)) + "??" + return "hostLookupOrder=" + itoa.Itoa(int(o)) + "??" } // goLookupHost is the native Go implementation of LookupHost. @@ -530,7 +532,7 @@ func (r *Resolver) goLookupHostOrder(ctx context.Context, name string, order hos return } } - ips, _, err := r.goLookupIPCNAMEOrder(ctx, name, order) + ips, _, err := r.goLookupIPCNAMEOrder(ctx, "ip", name, order) if err != nil { return } @@ -556,13 +558,13 @@ func goLookupIPFiles(name string) (addrs []IPAddr) { // goLookupIP is the native Go implementation of LookupIP. // The libc versions are in cgo_*.go. -func (r *Resolver) goLookupIP(ctx context.Context, host string) (addrs []IPAddr, err error) { +func (r *Resolver) goLookupIP(ctx context.Context, network, host string) (addrs []IPAddr, err error) { order := systemConf().hostLookupOrder(r, host) - addrs, _, err = r.goLookupIPCNAMEOrder(ctx, host, order) + addrs, _, err = r.goLookupIPCNAMEOrder(ctx, network, host, order) return } -func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order hostLookupOrder) (addrs []IPAddr, cname dnsmessage.Name, err error) { +func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name string, order hostLookupOrder) (addrs []IPAddr, cname dnsmessage.Name, err error) { if order == hostLookupFilesDNS || order == hostLookupFiles { addrs = goLookupIPFiles(name) if len(addrs) > 0 || order == hostLookupFiles { @@ -583,7 +585,13 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order error } lane := make(chan result, 1) - qtypes := [...]dnsmessage.Type{dnsmessage.TypeA, dnsmessage.TypeAAAA} + qtypes := []dnsmessage.Type{dnsmessage.TypeA, dnsmessage.TypeAAAA} + switch ipVersion(network) { + case '4': + qtypes = []dnsmessage.Type{dnsmessage.TypeA} + case '6': + qtypes = []dnsmessage.Type{dnsmessage.TypeAAAA} + } var queryFn func(fqdn string, qtype dnsmessage.Type) var responseFn func(fqdn string, qtype dnsmessage.Type) result if conf.singleRequest { @@ -728,7 +736,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order // goLookupCNAME is the native Go (non-cgo) implementation of LookupCNAME. func (r *Resolver) goLookupCNAME(ctx context.Context, host string) (string, error) { order := systemConf().hostLookupOrder(r, host) - _, cname, err := r.goLookupIPCNAMEOrder(ctx, host, order) + _, cname, err := r.goLookupIPCNAMEOrder(ctx, "ip", host, order) return cname.String(), err } diff --git a/libgo/go/net/dnsclient_unix_test.go b/libgo/go/net/dnsclient_unix_test.go index 3c9ada3..ce1a4f3 100644 --- a/libgo/go/net/dnsclient_unix_test.go +++ b/libgo/go/net/dnsclient_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net @@ -600,14 +601,14 @@ func TestGoLookupIPOrderFallbackToFile(t *testing.T) { name := fmt.Sprintf("order %v", order) // First ensure that we get an error when contacting a non-existent host. - _, _, err := r.goLookupIPCNAMEOrder(context.Background(), "notarealhost", order) + _, _, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", "notarealhost", order) if err == nil { t.Errorf("%s: expected error while looking up name not in hosts file", name) continue } // Now check that we get an address when the name appears in the hosts file. - addrs, _, err := r.goLookupIPCNAMEOrder(context.Background(), "thor", order) // entry is in "testdata/hosts" + addrs, _, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", "thor", order) // entry is in "testdata/hosts" if err != nil { t.Errorf("%s: expected to successfully lookup host entry", name) continue @@ -1845,6 +1846,17 @@ func TestCVE202133195(t *testing.T) { Target: dnsmessage.MustNewName("<html>.golang.org."), }, }, + dnsmessage.Resource{ + Header: dnsmessage.ResourceHeader{ + Name: n, + Type: dnsmessage.TypeSRV, + Class: dnsmessage.ClassINET, + Length: 4, + }, + Body: &dnsmessage.SRVResource{ + Target: dnsmessage.MustNewName("good.golang.org."), + }, + }, ) case dnsmessage.TypeMX: r.Answers = append(r.Answers, @@ -1859,6 +1871,17 @@ func TestCVE202133195(t *testing.T) { MX: dnsmessage.MustNewName("<html>.golang.org."), }, }, + dnsmessage.Resource{ + Header: dnsmessage.ResourceHeader{ + Name: dnsmessage.MustNewName("good.golang.org."), + Type: dnsmessage.TypeMX, + Class: dnsmessage.ClassINET, + Length: 4, + }, + Body: &dnsmessage.MXResource{ + MX: dnsmessage.MustNewName("good.golang.org."), + }, + }, ) case dnsmessage.TypeNS: r.Answers = append(r.Answers, @@ -1873,6 +1896,17 @@ func TestCVE202133195(t *testing.T) { NS: dnsmessage.MustNewName("<html>.golang.org."), }, }, + dnsmessage.Resource{ + Header: dnsmessage.ResourceHeader{ + Name: dnsmessage.MustNewName("good.golang.org."), + Type: dnsmessage.TypeNS, + Class: dnsmessage.ClassINET, + Length: 4, + }, + Body: &dnsmessage.NSResource{ + NS: dnsmessage.MustNewName("good.golang.org."), + }, + }, ) case dnsmessage.TypePTR: r.Answers = append(r.Answers, @@ -1887,6 +1921,17 @@ func TestCVE202133195(t *testing.T) { PTR: dnsmessage.MustNewName("<html>.golang.org."), }, }, + dnsmessage.Resource{ + Header: dnsmessage.ResourceHeader{ + Name: dnsmessage.MustNewName("good.golang.org."), + Type: dnsmessage.TypePTR, + Class: dnsmessage.ClassINET, + Length: 4, + }, + Body: &dnsmessage.PTRResource{ + PTR: dnsmessage.MustNewName("good.golang.org."), + }, + }, ) } return r, nil @@ -1902,57 +1947,177 @@ func TestCVE202133195(t *testing.T) { defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath) testHookHostsPath = "testdata/hosts" - _, err := r.LookupCNAME(context.Background(), "golang.org") - if expected := "lookup golang.org: CNAME target is invalid"; err == nil || err.Error() != expected { - t.Errorf("Resolver.LookupCNAME returned unexpected error, got %q, want %q", err, expected) - } - _, err = LookupCNAME("golang.org") - if expected := "lookup golang.org: CNAME target is invalid"; err == nil || err.Error() != expected { - t.Errorf("LookupCNAME returned unexpected error, got %q, want %q", err, expected) - } - - _, _, err = r.LookupSRV(context.Background(), "target", "tcp", "golang.org") - if expected := "lookup golang.org: SRV target is invalid"; err == nil || err.Error() != expected { - t.Errorf("Resolver.LookupSRV returned unexpected error, got %q, want %q", err, expected) - } - _, _, err = LookupSRV("target", "tcp", "golang.org") - if expected := "lookup golang.org: SRV target is invalid"; err == nil || err.Error() != expected { - t.Errorf("LookupSRV returned unexpected error, got %q, want %q", err, expected) + tests := []struct { + name string + f func(*testing.T) + }{ + { + name: "CNAME", + f: func(t *testing.T) { + expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"} + _, err := r.LookupCNAME(context.Background(), "golang.org") + if err.Error() != expectedErr.Error() { + t.Fatalf("unexpected error: %s", err) + } + _, err = LookupCNAME("golang.org") + if err.Error() != expectedErr.Error() { + t.Fatalf("unexpected error: %s", err) + } + }, + }, + { + name: "SRV (bad record)", + f: func(t *testing.T) { + expected := []*SRV{ + { + Target: "good.golang.org.", + }, + } + expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"} + _, records, err := r.LookupSRV(context.Background(), "target", "tcp", "golang.org") + if err.Error() != expectedErr.Error() { + t.Fatalf("unexpected error: %s", err) + } + if !reflect.DeepEqual(records, expected) { + t.Error("Unexpected record set") + } + _, records, err = LookupSRV("target", "tcp", "golang.org") + if err.Error() != expectedErr.Error() { + t.Errorf("unexpected error: %s", err) + } + if !reflect.DeepEqual(records, expected) { + t.Error("Unexpected record set") + } + }, + }, + { + name: "SRV (bad header)", + f: func(t *testing.T) { + _, _, err := r.LookupSRV(context.Background(), "hdr", "tcp", "golang.org.") + if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected { + t.Errorf("Resolver.LookupSRV returned unexpected error, got %q, want %q", err, expected) + } + _, _, err = LookupSRV("hdr", "tcp", "golang.org.") + if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected { + t.Errorf("LookupSRV returned unexpected error, got %q, want %q", err, expected) + } + }, + }, + { + name: "MX", + f: func(t *testing.T) { + expected := []*MX{ + { + Host: "good.golang.org.", + }, + } + expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"} + records, err := r.LookupMX(context.Background(), "golang.org") + if err.Error() != expectedErr.Error() { + t.Fatalf("unexpected error: %s", err) + } + if !reflect.DeepEqual(records, expected) { + t.Error("Unexpected record set") + } + records, err = LookupMX("golang.org") + if err.Error() != expectedErr.Error() { + t.Fatalf("unexpected error: %s", err) + } + if !reflect.DeepEqual(records, expected) { + t.Error("Unexpected record set") + } + }, + }, + { + name: "NS", + f: func(t *testing.T) { + expected := []*NS{ + { + Host: "good.golang.org.", + }, + } + expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"} + records, err := r.LookupNS(context.Background(), "golang.org") + if err.Error() != expectedErr.Error() { + t.Fatalf("unexpected error: %s", err) + } + if !reflect.DeepEqual(records, expected) { + t.Error("Unexpected record set") + } + records, err = LookupNS("golang.org") + if err.Error() != expectedErr.Error() { + t.Fatalf("unexpected error: %s", err) + } + if !reflect.DeepEqual(records, expected) { + t.Error("Unexpected record set") + } + }, + }, + { + name: "Addr", + f: func(t *testing.T) { + expected := []string{"good.golang.org."} + expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "192.0.2.42"} + records, err := r.LookupAddr(context.Background(), "192.0.2.42") + if err.Error() != expectedErr.Error() { + t.Fatalf("unexpected error: %s", err) + } + if !reflect.DeepEqual(records, expected) { + t.Error("Unexpected record set") + } + records, err = LookupAddr("192.0.2.42") + if err.Error() != expectedErr.Error() { + t.Fatalf("unexpected error: %s", err) + } + if !reflect.DeepEqual(records, expected) { + t.Error("Unexpected record set") + } + }, + }, } - _, _, err = r.LookupSRV(context.Background(), "hdr", "tcp", "golang.org") - if expected := "lookup golang.org: SRV header name is invalid"; err == nil || err.Error() != expected { - t.Errorf("Resolver.LookupSRV returned unexpected error, got %q, want %q", err, expected) - } - _, _, err = LookupSRV("hdr", "tcp", "golang.org") - if expected := "lookup golang.org: SRV header name is invalid"; err == nil || err.Error() != expected { - t.Errorf("LookupSRV returned unexpected error, got %q, want %q", err, expected) + for _, tc := range tests { + t.Run(tc.name, tc.f) } - _, err = r.LookupMX(context.Background(), "golang.org") - if expected := "lookup golang.org: MX target is invalid"; err == nil || err.Error() != expected { - t.Errorf("Resolver.LookupMX returned unexpected error, got %q, want %q", err, expected) - } - _, err = LookupMX("golang.org") - if expected := "lookup golang.org: MX target is invalid"; err == nil || err.Error() != expected { - t.Errorf("LookupMX returned unexpected error, got %q, want %q", err, expected) - } +} - _, err = r.LookupNS(context.Background(), "golang.org") - if expected := "lookup golang.org: NS target is invalid"; err == nil || err.Error() != expected { - t.Errorf("Resolver.LookupNS returned unexpected error, got %q, want %q", err, expected) - } - _, err = LookupNS("golang.org") - if expected := "lookup golang.org: NS target is invalid"; err == nil || err.Error() != expected { - t.Errorf("LookupNS returned unexpected error, got %q, want %q", err, expected) +func TestNullMX(t *testing.T) { + fake := fakeDNSServer{ + rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { + r := dnsmessage.Message{ + Header: dnsmessage.Header{ + ID: q.Header.ID, + Response: true, + RCode: dnsmessage.RCodeSuccess, + }, + Questions: q.Questions, + Answers: []dnsmessage.Resource{ + { + Header: dnsmessage.ResourceHeader{ + Name: q.Questions[0].Name, + Type: dnsmessage.TypeMX, + Class: dnsmessage.ClassINET, + }, + Body: &dnsmessage.MXResource{ + MX: dnsmessage.MustNewName("."), + }, + }, + }, + } + return r, nil + }, } - - _, err = r.LookupAddr(context.Background(), "192.0.2.42") - if expected := "lookup 192.0.2.42: PTR target is invalid"; err == nil || err.Error() != expected { - t.Errorf("Resolver.LookupAddr returned unexpected error, got %q, want %q", err, expected) + r := Resolver{PreferGo: true, Dial: fake.DialContext} + rrset, err := r.LookupMX(context.Background(), "golang.org") + if err != nil { + t.Fatalf("LookupMX: %v", err) } - _, err = LookupAddr("192.0.2.42") - if expected := "lookup 192.0.2.42: PTR target is invalid"; err == nil || err.Error() != expected { - t.Errorf("LookupAddr returned unexpected error, got %q, want %q", err, expected) + if want := []*MX{&MX{Host: "."}}; !reflect.DeepEqual(rrset, want) { + records := []string{} + for _, rr := range rrset { + records = append(records, fmt.Sprintf("%v", rr)) + } + t.Errorf("records = [%v]; want [%v]", strings.Join(records, " "), want[0]) } } diff --git a/libgo/go/net/dnsconfig_unix.go b/libgo/go/net/dnsconfig_unix.go index b76b32f..4b11602 100644 --- a/libgo/go/net/dnsconfig_unix.go +++ b/libgo/go/net/dnsconfig_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris // Read system DNS config from /etc/resolv.conf diff --git a/libgo/go/net/dnsconfig_unix_test.go b/libgo/go/net/dnsconfig_unix_test.go index f6edffc..59e21d6 100644 --- a/libgo/go/net/dnsconfig_unix_test.go +++ b/libgo/go/net/dnsconfig_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/dnsname_test.go b/libgo/go/net/dnsname_test.go index 2964982..d851bf7 100644 --- a/libgo/go/net/dnsname_test.go +++ b/libgo/go/net/dnsname_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/error_posix.go b/libgo/go/net/error_posix.go index c13d5bc..017f2cb 100644 --- a/libgo/go/net/error_posix.go +++ b/libgo/go/net/error_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris windows package net diff --git a/libgo/go/net/error_posix_test.go b/libgo/go/net/error_posix_test.go index b411a37..ea52a45 100644 --- a/libgo/go/net/error_posix_test.go +++ b/libgo/go/net/error_posix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !plan9 // +build !plan9 package net diff --git a/libgo/go/net/error_test.go b/libgo/go/net/error_test.go index 556eb8c..c304390 100644 --- a/libgo/go/net/error_test.go +++ b/libgo/go/net/error_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/error_unix.go b/libgo/go/net/error_unix.go index 415e928..3de4e76 100644 --- a/libgo/go/net/error_unix.go +++ b/libgo/go/net/error_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || js || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd js linux netbsd openbsd solaris package net diff --git a/libgo/go/net/error_unix_test.go b/libgo/go/net/error_unix_test.go index 9ce9e12..533a45e 100644 --- a/libgo/go/net/error_unix_test.go +++ b/libgo/go/net/error_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !plan9 && !windows // +build !plan9,!windows package net diff --git a/libgo/go/net/external_test.go b/libgo/go/net/external_test.go index f3c69c4..b8753cc 100644 --- a/libgo/go/net/external_test.go +++ b/libgo/go/net/external_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/fcntl_libc_test.go b/libgo/go/net/fcntl_libc_test.go new file mode 100644 index 0000000..02511c5 --- /dev/null +++ b/libgo/go/net/fcntl_libc_test.go @@ -0,0 +1,27 @@ +// Copyright 2021 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. + +//go:build aix || darwin || solaris +// +build aix darwin solaris + +package net + +import "syscall" + +// Use a helper function to call fcntl. This is defined in C in +// libgo/runtime. +//extern __go_fcntl_uintptr +func libc_fcntl(uintptr, uintptr, uintptr) (uintptr, uintptr) + +// Implemented in the syscall package. +//go:linkname fcntl syscall.fcntl +func fcntl(fd int, cmd int, arg int) (int, error) { + syscall.Entersyscall() + r, e := libc_fcntl(uintptr(fd), uintptr(cmd), uintptr(arg)) + syscall.Exitsyscall() + if e != 0 { + return int(r), syscall.Errno(e) + } + return int(r), nil +} diff --git a/libgo/go/net/fcntl_syscall_test.go b/libgo/go/net/fcntl_syscall_test.go new file mode 100644 index 0000000..59ba1a1 --- /dev/null +++ b/libgo/go/net/fcntl_syscall_test.go @@ -0,0 +1,27 @@ +// Copyright 2021 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. + +//go:build dragonfly || freebsd || linux || netbsd || openbsd +// +build dragonfly freebsd linux netbsd openbsd + +package net + +import ( + "syscall" +) + +// Use a helper function to call fcntl. This is defined in C in +// libgo/runtime. +//extern __go_fcntl_uintptr +func libc_fcntl(uintptr, uintptr, uintptr) (uintptr, uintptr) + +func fcntl(fd int, cmd int, arg int) (int, error) { + syscall.Entersyscall() + r, e := libc_fcntl(uintptr(fd), uintptr(cmd), uintptr(arg)) + syscall.Exitsyscall() + if e != 0 { + return int(r), syscall.Errno(e) + } + return int(r), nil +} diff --git a/libgo/go/net/fd_posix.go b/libgo/go/net/fd_posix.go index b2f99bc..a0f1f5a 100644 --- a/libgo/go/net/fd_posix.go +++ b/libgo/go/net/fd_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris windows package net @@ -63,10 +64,10 @@ func (fd *netFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) { return n, sa, wrapSyscallError(readFromSyscallName, err) } -func (fd *netFD) readMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.Sockaddr, err error) { - n, oobn, flags, sa, err = fd.pfd.ReadMsg(p, oob) +func (fd *netFD) readMsg(p []byte, oob []byte, flags int) (n, oobn, retflags int, sa syscall.Sockaddr, err error) { + n, oobn, retflags, sa, err = fd.pfd.ReadMsg(p, oob, flags) runtime.KeepAlive(fd) - return n, oobn, flags, sa, wrapSyscallError(readMsgSyscallName, err) + return n, oobn, retflags, sa, wrapSyscallError(readMsgSyscallName, err) } func (fd *netFD) Write(p []byte) (nn int, err error) { diff --git a/libgo/go/net/fd_unix.go b/libgo/go/net/fd_unix.go index ad79c06..e2db165 100644 --- a/libgo/go/net/fd_unix.go +++ b/libgo/go/net/fd_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/file_stub.go b/libgo/go/net/file_stub.go index bfb8100..9f988fe 100644 --- a/libgo/go/net/file_stub.go +++ b/libgo/go/net/file_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build js && wasm // +build js,wasm package net diff --git a/libgo/go/net/file_test.go b/libgo/go/net/file_test.go index 8c09c0d..a70ef1b 100644 --- a/libgo/go/net/file_test.go +++ b/libgo/go/net/file_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/file_unix.go b/libgo/go/net/file_unix.go index 25caafb..d36a881 100644 --- a/libgo/go/net/file_unix.go +++ b/libgo/go/net/file_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/hook_unix.go b/libgo/go/net/hook_unix.go index 780b23c..618c6c2 100644 --- a/libgo/go/net/hook_unix.go +++ b/libgo/go/net/hook_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris package net diff --git a/libgo/go/net/hosts_test.go b/libgo/go/net/hosts_test.go index f850e2f..19c4399 100644 --- a/libgo/go/net/hosts_test.go +++ b/libgo/go/net/hosts_test.go @@ -36,7 +36,7 @@ var lookupStaticHostTests = []struct { }, }, { - "testdata/ipv4-hosts", // see golang.org/issue/8996 + "testdata/ipv4-hosts", []staticHostEntry{ {"localhost", []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}}, {"localhost.localdomain", []string{"127.0.0.3"}}, @@ -102,7 +102,7 @@ var lookupStaticAddrTests = []struct { }, }, { - "testdata/ipv4-hosts", // see golang.org/issue/8996 + "testdata/ipv4-hosts", []staticHostEntry{ {"127.0.0.1", []string{"localhost"}}, {"127.0.0.2", []string{"localhost"}}, diff --git a/libgo/go/net/http/cgi/integration_test.go b/libgo/go/net/http/cgi/integration_test.go index 76cbca8..ef2eaf7 100644 --- a/libgo/go/net/http/cgi/integration_test.go +++ b/libgo/go/net/http/cgi/integration_test.go @@ -95,12 +95,6 @@ func (w *limitWriter) Write(p []byte) (n int, err error) { func TestKillChildAfterCopyError(t *testing.T) { testenv.MustHaveExec(t) - defer func() { testHookStartProcess = nil }() - proc := make(chan *os.Process, 1) - testHookStartProcess = func(p *os.Process) { - proc <- p - } - h := &Handler{ Path: os.Args[0], Root: "/test.go", @@ -112,26 +106,9 @@ func TestKillChildAfterCopyError(t *testing.T) { const writeLen = 50 << 10 rw := &customWriterRecorder{&limitWriter{&out, writeLen}, rec} - donec := make(chan bool, 1) - go func() { - h.ServeHTTP(rw, req) - donec <- true - }() - - select { - case <-donec: - if out.Len() != writeLen || out.Bytes()[0] != 'a' { - t.Errorf("unexpected output: %q", out.Bytes()) - } - case <-time.After(5 * time.Second): - t.Errorf("timeout. ServeHTTP hung and didn't kill the child process?") - select { - case p := <-proc: - p.Kill() - t.Logf("killed process") - default: - t.Logf("didn't kill process") - } + h.ServeHTTP(rw, req) + if out.Len() != writeLen || out.Bytes()[0] != 'a' { + t.Errorf("unexpected output: %q", out.Bytes()) } } diff --git a/libgo/go/net/http/cgi/posix_test.go b/libgo/go/net/http/cgi/posix_test.go index 9396ce0..bc58ea9 100644 --- a/libgo/go/net/http/cgi/posix_test.go +++ b/libgo/go/net/http/cgi/posix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !plan9 // +build !plan9 package cgi diff --git a/libgo/go/net/http/client.go b/libgo/go/net/http/client.go index 88e2028..4d380c6 100644 --- a/libgo/go/net/http/client.go +++ b/libgo/go/net/http/client.go @@ -17,6 +17,7 @@ import ( "fmt" "io" "log" + "net/http/internal/ascii" "net/url" "reflect" "sort" @@ -432,8 +433,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 -// value's Timeout method will report true if request timed out or was -// canceled. +// 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. @@ -442,6 +442,9 @@ func basicAuth(username, password string) string { // // To make a request with custom headers, use NewRequest and // DefaultClient.Do. +// +// 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) } @@ -466,6 +469,9 @@ func Get(url string) (resp *Response, err error) { // 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 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) if err != nil { @@ -541,7 +547,10 @@ func urlErrorOp(method string) string { if method == "" { return "Get" } - return method[:1] + strings.ToLower(method[1:]) + if lowerMethod, ok := ascii.ToLower(method); ok { + return method[:1] + lowerMethod[1:] + } + return method } // Do sends an HTTP request and returns an HTTP response, following @@ -579,8 +588,7 @@ func urlErrorOp(method string) string { // standard library body types. // // Any returned error will be of type *url.Error. The url.Error -// value's Timeout method will report true if request timed out or was -// canceled. +// value's Timeout method will report true if the request timed out. func (c *Client) Do(req *Request) (*Response, error) { return c.do(req) } @@ -719,7 +727,6 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) { reqBodyClosed = true if !deadline.IsZero() && didTimeout() { err = &httpError{ - // TODO: early in cycle: s/Client.Timeout exceeded/timeout or context cancellation/ err: err.Error() + " (Client.Timeout exceeded while awaiting headers)", timeout: true, } @@ -821,6 +828,9 @@ func defaultCheckRedirect(req *Request, via []*Request) error { // // See the Client.Do method documentation for details on how redirects // are handled. +// +// 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) } @@ -834,6 +844,9 @@ func Post(url, contentType string, body io.Reader) (resp *Response, err error) { // // To set custom headers, use NewRequest 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 // are handled. func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) { @@ -858,6 +871,9 @@ func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, // // See the Client.Do method documentation for details on how redirects // are handled. +// +// 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) } @@ -873,6 +889,9 @@ func PostForm(url string, data url.Values) (resp *Response, err error) { // // See the Client.Do method documentation for details on how redirects // are handled. +// +// 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())) } @@ -888,6 +907,9 @@ func (c *Client) PostForm(url string, data url.Values) (resp *Response, err erro // 308 (Permanent Redirect) // // Head is a wrapper around DefaultClient.Head +// +// 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) } @@ -901,6 +923,9 @@ func Head(url string) (resp *Response, err error) { // 303 (See Other) // 307 (Temporary Redirect) // 308 (Permanent Redirect) +// +// 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 { @@ -926,7 +951,7 @@ func (c *Client) CloseIdleConnections() { } // cancelTimerBody is an io.ReadCloser that wraps rc with two features: -// 1) on Read error or close, the stop func is called. +// 1) On Read error or close, the stop func is called. // 2) On Read failure, if reqDidTimeout is true, the error is wrapped and // marked as net.Error that hit its timeout. type cancelTimerBody struct { diff --git a/libgo/go/net/http/client_test.go b/libgo/go/net/http/client_test.go index d90b484..01d605c 100644 --- a/libgo/go/net/http/client_test.go +++ b/libgo/go/net/http/client_test.go @@ -257,12 +257,12 @@ func TestClientRedirects(t *testing.T) { t.Fatalf("Get error: %v", err) } res.Body.Close() - finalUrl := res.Request.URL.String() + finalURL := res.Request.URL.String() if e, g := "<nil>", fmt.Sprintf("%v", err); e != g { t.Errorf("with custom client, expected error %q, got %q", e, g) } - if !strings.HasSuffix(finalUrl, "/?n=15") { - t.Errorf("expected final url to end in /?n=15; got url %q", finalUrl) + if !strings.HasSuffix(finalURL, "/?n=15") { + t.Errorf("expected final url to end in /?n=15; got url %q", finalURL) } if e, g := 15, len(lastVia); e != g { t.Errorf("expected lastVia to have contained %d elements; got %d", e, g) @@ -1945,7 +1945,7 @@ func TestClientDoCanceledVsTimeout_h2(t *testing.T) { } // Issue 33545: lock-in the behavior promised by Client.Do's -// docs about request cancelation vs timing out. +// docs about request cancellation vs timing out. func testClientDoCanceledVsTimeout(t *testing.T, h2 bool) { defer afterTest(t) cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { diff --git a/libgo/go/net/http/cookie.go b/libgo/go/net/http/cookie.go index 141bc94..ca2c1c2 100644 --- a/libgo/go/net/http/cookie.go +++ b/libgo/go/net/http/cookie.go @@ -7,6 +7,7 @@ package http import ( "log" "net" + "net/http/internal/ascii" "net/textproto" "strconv" "strings" @@ -93,15 +94,23 @@ func readSetCookies(h Header) []*Cookie { if j := strings.Index(attr, "="); j >= 0 { attr, val = attr[:j], attr[j+1:] } - lowerAttr := strings.ToLower(attr) + 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 := strings.ToLower(val) + lowerVal, ascii := ascii.ToLower(val) + if !ascii { + c.SameSite = SameSiteDefaultMode + continue + } switch lowerVal { case "lax": c.SameSite = SameSiteLaxMode diff --git a/libgo/go/net/http/cookiejar/jar.go b/libgo/go/net/http/cookiejar/jar.go index 9f19917..e6583da 100644 --- a/libgo/go/net/http/cookiejar/jar.go +++ b/libgo/go/net/http/cookiejar/jar.go @@ -10,6 +10,7 @@ import ( "fmt" "net" "net/http" + "net/http/internal/ascii" "net/url" "sort" "strings" @@ -296,7 +297,6 @@ func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) { // host name. func canonicalHost(host string) (string, error) { var err error - host = strings.ToLower(host) if hasPort(host) { host, _, err = net.SplitHostPort(host) if err != nil { @@ -307,7 +307,13 @@ func canonicalHost(host string) (string, error) { // Strip trailing dot from fully qualified domain names. host = host[:len(host)-1] } - return toASCII(host) + encoded, err := toASCII(host) + if err != nil { + return "", err + } + // We know this is ascii, no need to check. + lower, _ := ascii.ToLower(encoded) + return lower, nil } // hasPort reports whether host contains a port number. host may be a host @@ -469,7 +475,12 @@ func (j *Jar) domainAndType(host, domain string) (string, bool, error) { // both are illegal. return "", false, errMalformedDomain } - domain = strings.ToLower(domain) + + domain, isASCII := ascii.ToLower(domain) + if !isASCII { + // Received non-ASCII domain, e.g. "perché.com" instead of "xn--perch-fsa.com" + return "", false, errMalformedDomain + } if domain[len(domain)-1] == '.' { // We received stuff like "Domain=www.example.com.". diff --git a/libgo/go/net/http/cookiejar/punycode.go b/libgo/go/net/http/cookiejar/punycode.go index a9cc666..c7f438d 100644 --- a/libgo/go/net/http/cookiejar/punycode.go +++ b/libgo/go/net/http/cookiejar/punycode.go @@ -8,6 +8,7 @@ package cookiejar import ( "fmt" + "net/http/internal/ascii" "strings" "unicode/utf8" ) @@ -133,12 +134,12 @@ const acePrefix = "xn--" // toASCII("bücher.example.com") is "xn--bcher-kva.example.com", and // toASCII("golang") is "golang". func toASCII(s string) (string, error) { - if ascii(s) { + if ascii.Is(s) { return s, nil } labels := strings.Split(s, ".") for i, label := range labels { - if !ascii(label) { + if !ascii.Is(label) { a, err := encode(acePrefix, label) if err != nil { return "", err @@ -148,12 +149,3 @@ func toASCII(s string) (string, error) { } return strings.Join(labels, "."), nil } - -func ascii(s string) bool { - for i := 0; i < len(s); i++ { - if s[i] >= utf8.RuneSelf { - return false - } - } - return true -} diff --git a/libgo/go/net/http/example_test.go b/libgo/go/net/http/example_test.go index c677d52..2f411d1 100644 --- a/libgo/go/net/http/example_test.go +++ b/libgo/go/net/http/example_test.go @@ -45,12 +45,15 @@ func ExampleGet() { if err != nil { log.Fatal(err) } - robots, err := io.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) res.Body.Close() + if res.StatusCode > 299 { + log.Fatalf("Response failed with status code: %d and\nbody: %s\n", res.StatusCode, body) + } if err != nil { log.Fatal(err) } - fmt.Printf("%s", robots) + fmt.Printf("%s", body) } func ExampleFileServer() { diff --git a/libgo/go/net/http/fcgi/child.go b/libgo/go/net/http/fcgi/child.go index 756722b..dc82bf7 100644 --- a/libgo/go/net/http/fcgi/child.go +++ b/libgo/go/net/http/fcgi/child.go @@ -16,7 +16,6 @@ import ( "net/http/cgi" "os" "strings" - "sync" "time" ) @@ -154,7 +153,6 @@ type child struct { conn *conn handler http.Handler - mu sync.Mutex // protects requests: requests map[uint16]*request // keyed by request ID } @@ -193,9 +191,7 @@ var ErrRequestAborted = errors.New("fcgi: request aborted by web server") var ErrConnClosed = errors.New("fcgi: connection to web server closed") func (c *child) handleRecord(rec *record) error { - c.mu.Lock() req, ok := c.requests[rec.h.Id] - c.mu.Unlock() if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues { // The spec says to ignore unknown request IDs. return nil @@ -218,9 +214,7 @@ func (c *child) handleRecord(rec *record) error { return nil } req = newRequest(rec.h.Id, br.flags) - c.mu.Lock() c.requests[rec.h.Id] = req - c.mu.Unlock() return nil case typeParams: // NOTE(eds): Technically a key-value pair can straddle the boundary @@ -248,8 +242,11 @@ func (c *child) handleRecord(rec *record) error { // TODO(eds): This blocks until the handler reads from the pipe. // If the handler takes a long time, it might be a problem. req.pw.Write(content) - } else if req.pw != nil { - req.pw.Close() + } else { + delete(c.requests, req.reqId) + if req.pw != nil { + req.pw.Close() + } } return nil case typeGetValues: @@ -260,9 +257,7 @@ func (c *child) handleRecord(rec *record) error { // If the filter role is implemented, read the data stream here. return nil case typeAbortRequest: - c.mu.Lock() delete(c.requests, rec.h.Id) - c.mu.Unlock() c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete) if req.pw != nil { req.pw.CloseWithError(ErrRequestAborted) @@ -309,9 +304,6 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) { // Make sure we serve something even if nothing was written to r r.Write(nil) r.Close() - c.mu.Lock() - delete(c.requests, req.reqId) - c.mu.Unlock() c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete) // Consume the entire body, so the host isn't still writing to @@ -330,8 +322,6 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) { } func (c *child) cleanUp() { - c.mu.Lock() - defer c.mu.Unlock() for _, req := range c.requests { if req.pw != nil { // race with call to Close in c.serveRequest doesn't matter because diff --git a/libgo/go/net/http/fcgi/fcgi_test.go b/libgo/go/net/http/fcgi/fcgi_test.go index b58111d..5888783 100644 --- a/libgo/go/net/http/fcgi/fcgi_test.go +++ b/libgo/go/net/http/fcgi/fcgi_test.go @@ -11,6 +11,7 @@ import ( "net/http" "strings" "testing" + "time" ) var sizeTests = []struct { @@ -399,3 +400,55 @@ func TestResponseWriterSniffsContentType(t *testing.T) { }) } } + +type signallingNopCloser struct { + io.Reader + closed chan bool +} + +func (*signallingNopCloser) Write(buf []byte) (int, error) { + return len(buf), nil +} + +func (rc *signallingNopCloser) Close() error { + close(rc.closed) + return nil +} + +// Test whether server properly closes connection when processing slow +// requests +func TestSlowRequest(t *testing.T) { + pr, pw := io.Pipe() + go func(w io.Writer) { + for _, buf := range [][]byte{ + streamBeginTypeStdin, + makeRecord(typeStdin, 1, nil), + } { + pw.Write(buf) + time.Sleep(100 * time.Millisecond) + } + }(pw) + + rc := &signallingNopCloser{pr, make(chan bool)} + handlerDone := make(chan bool) + + c := newChild(rc, http.HandlerFunc(func( + w http.ResponseWriter, + r *http.Request, + ) { + w.WriteHeader(200) + close(handlerDone) + })) + go c.serve() + defer c.cleanUp() + + timeout := time.After(2 * time.Second) + + <-handlerDone + select { + case <-rc.closed: + t.Log("FastCGI child closed connection") + case <-timeout: + t.Error("FastCGI child did not close socket after handling request") + } +} diff --git a/libgo/go/net/http/filetransport_test.go b/libgo/go/net/http/filetransport_test.go index b58888d..77fc8ee 100644 --- a/libgo/go/net/http/filetransport_test.go +++ b/libgo/go/net/http/filetransport_test.go @@ -23,12 +23,10 @@ func checker(t *testing.T) func(string, error) { func TestFileTransport(t *testing.T) { check := checker(t) - dname, err := os.MkdirTemp("", "") - check("TempDir", err) + dname := t.TempDir() fname := filepath.Join(dname, "foo.txt") - err = os.WriteFile(fname, []byte("Bar"), 0644) + err := os.WriteFile(fname, []byte("Bar"), 0644) check("WriteFile", err) - defer os.Remove(dname) defer os.Remove(fname) tr := &Transport{} diff --git a/libgo/go/net/http/fs.go b/libgo/go/net/http/fs.go index a28ae85..57e731e 100644 --- a/libgo/go/net/http/fs.go +++ b/libgo/go/net/http/fs.go @@ -46,7 +46,7 @@ type Dir string // to a possibly better non-nil error. In particular, it turns OS-specific errors // about opening files in non-directories into fs.ErrNotExist. See Issue 18984. func mapDirOpenError(originalErr error, name string) error { - if os.IsNotExist(originalErr) || os.IsPermission(originalErr) { + if errors.Is(originalErr, fs.ErrNotExist) || errors.Is(originalErr, fs.ErrPermission) { return originalErr } @@ -670,10 +670,10 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec // and historically Go's ServeContent always returned just "404 Not Found" for // all errors. We don't want to start leaking information in error messages. func toHTTPError(err error) (msg string, httpStatus int) { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return "404 page not found", StatusNotFound } - if os.IsPermission(err) { + if errors.Is(err, fs.ErrPermission) { return "403 Forbidden", StatusForbidden } // Default: diff --git a/libgo/go/net/http/fs_test.go b/libgo/go/net/http/fs_test.go index 2499051..b42ade1 100644 --- a/libgo/go/net/http/fs_test.go +++ b/libgo/go/net/http/fs_test.go @@ -11,7 +11,6 @@ import ( "fmt" "io" "io/fs" - "io/ioutil" "mime" "mime/multipart" "net" @@ -379,11 +378,7 @@ func mustRemoveAll(dir string) { func TestFileServerImplicitLeadingSlash(t *testing.T) { defer afterTest(t) - tempDir, err := os.MkdirTemp("", "") - if err != nil { - t.Fatalf("TempDir: %v", err) - } - defer mustRemoveAll(tempDir) + tempDir := t.TempDir() if err := os.WriteFile(filepath.Join(tempDir, "foo.txt"), []byte("Hello world"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } @@ -593,7 +588,7 @@ func TestServeIndexHtml(t *testing.T) { if err != nil { t.Fatal(err) } - b, err := ioutil.ReadAll(res.Body) + b, err := io.ReadAll(res.Body) if err != nil { t.Fatal("reading Body:", err) } @@ -1270,16 +1265,18 @@ func TestFileServerNotDirError(t *testing.T) { if err == nil { t.Fatal("err == nil; want != nil") } - if !os.IsNotExist(err) { - t.Errorf("err = %v; os.IsNotExist(err) = %v; want true", err, os.IsNotExist(err)) + if !errors.Is(err, fs.ErrNotExist) { + t.Errorf("err = %v; errors.Is(err, fs.ErrNotExist) = %v; want true", err, + errors.Is(err, fs.ErrNotExist)) } _, err = dir.Open("/index.html/not-a-dir/not-a-file") if err == nil { t.Fatal("err == nil; want != nil") } - if !os.IsNotExist(err) { - t.Errorf("err = %v; os.IsNotExist(err) = %v; want true", err, os.IsNotExist(err)) + if !errors.Is(err, fs.ErrNotExist) { + t.Errorf("err = %v; errors.Is(err, fs.ErrNotExist) = %v; want true", err, + errors.Is(err, fs.ErrNotExist)) } }) } diff --git a/libgo/go/net/http/h2_bundle.go b/libgo/go/net/http/h2_bundle.go index e13c661..0e5fbf8 100644 --- a/libgo/go/net/http/h2_bundle.go +++ b/libgo/go/net/http/h2_bundle.go @@ -1,3 +1,4 @@ +//go:build !nethttpomithttp2 // +build !nethttpomithttp2 // Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT. @@ -52,6 +53,48 @@ import ( "golang.org/x/net/idna" ) +// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t +// are equal, ASCII-case-insensitively. +func http2asciiEqualFold(s, t string) bool { + if len(s) != len(t) { + return false + } + for i := 0; i < len(s); i++ { + if http2lower(s[i]) != http2lower(t[i]) { + return false + } + } + return true +} + +// lower returns the ASCII lowercase version of b. +func http2lower(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b +} + +// isASCIIPrint returns whether s is ASCII and printable according to +// https://tools.ietf.org/html/rfc20#section-4.2. +func http2isASCIIPrint(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] < ' ' || s[i] > '~' { + return false + } + } + return true +} + +// asciiToLower returns the lowercase version of s if s is ASCII and printable, +// and whether or not it was. +func http2asciiToLower(s string) (lower string, ok bool) { + if !http2isASCIIPrint(s) { + return "", false + } + return strings.ToLower(s), true +} + // A list of the possible cipher suite ids. Taken from // https://www.iana.org/assignments/tls-parameters/tls-parameters.txt @@ -754,61 +797,69 @@ func (p *http2clientConnPool) getClientConn(req *Request, addr string, dialOnMis // It gets its own connection. http2traceGetConn(req, addr) const singleUse = true - cc, err := p.t.dialClientConn(addr, singleUse) + cc, err := p.t.dialClientConn(req.Context(), addr, singleUse) if err != nil { return nil, err } return cc, nil } - p.mu.Lock() - for _, cc := range p.conns[addr] { - if st := cc.idleState(); st.canTakeNewRequest { - if p.shouldTraceGetConn(st) { - http2traceGetConn(req, addr) + for { + p.mu.Lock() + for _, cc := range p.conns[addr] { + if st := cc.idleState(); st.canTakeNewRequest { + if p.shouldTraceGetConn(st) { + http2traceGetConn(req, addr) + } + p.mu.Unlock() + return cc, nil } + } + if !dialOnMiss { p.mu.Unlock() - return cc, nil + return nil, http2ErrNoCachedConn } - } - if !dialOnMiss { + http2traceGetConn(req, addr) + call := p.getStartDialLocked(req.Context(), addr) p.mu.Unlock() - return nil, http2ErrNoCachedConn + <-call.done + if http2shouldRetryDial(call, req) { + continue + } + return call.res, call.err } - http2traceGetConn(req, addr) - call := p.getStartDialLocked(addr) - p.mu.Unlock() - <-call.done - return call.res, call.err } // dialCall is an in-flight Transport dial call to a host. type http2dialCall struct { - _ http2incomparable - p *http2clientConnPool + _ http2incomparable + p *http2clientConnPool + // the context associated with the request + // that created this dialCall + ctx context.Context done chan struct{} // closed when done res *http2ClientConn // valid after done is closed err error // valid after done is closed } // requires p.mu is held. -func (p *http2clientConnPool) getStartDialLocked(addr string) *http2dialCall { +func (p *http2clientConnPool) getStartDialLocked(ctx context.Context, addr string) *http2dialCall { if call, ok := p.dialing[addr]; ok { // A dial is already in-flight. Don't start another. return call } - call := &http2dialCall{p: p, done: make(chan struct{})} + call := &http2dialCall{p: p, done: make(chan struct{}), ctx: ctx} if p.dialing == nil { p.dialing = make(map[string]*http2dialCall) } p.dialing[addr] = call - go call.dial(addr) + go call.dial(call.ctx, addr) return call } // run in its own goroutine. -func (c *http2dialCall) dial(addr string) { +func (c *http2dialCall) dial(ctx context.Context, addr string) { const singleUse = false // shared conn - c.res, c.err = c.p.t.dialClientConn(addr, singleUse) + c.res, c.err = c.p.t.dialClientConn(ctx, addr, singleUse) close(c.done) c.p.mu.Lock() @@ -953,6 +1004,31 @@ func (p http2noDialClientConnPool) GetClientConn(req *Request, addr string) (*ht return p.getClientConn(req, addr, http2noDialOnMiss) } +// shouldRetryDial reports whether the current request should +// retry dialing after the call finished unsuccessfully, for example +// if the dial was canceled because of a context cancellation or +// deadline expiry. +func http2shouldRetryDial(call *http2dialCall, req *Request) bool { + if call.err == nil { + // No error, no need to retry + return false + } + if call.ctx == req.Context() { + // If the call has the same context as the request, the dial + // should not be retried, since any cancellation will have come + // from this request. + return false + } + if !errors.Is(call.err, context.Canceled) && !errors.Is(call.err, context.DeadlineExceeded) { + // If the call error is not because of a context cancellation or a deadline expiry, + // the dial should not be retried. + return false + } + // Only retry if the error is a context cancellation error or deadline expiry + // and the context associated with the call was canceled or expired. + return call.ctx.Err() != nil +} + // Buffer chunks are allocated from a pool to reduce pressure on GC. // The maximum wasted space per dataBuffer is 2x the largest size class, // which happens when the dataBuffer has multiple chunks and there is @@ -2873,6 +2949,20 @@ func http2traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textpr return nil } +// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS +// connection. +func (t *http2Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) { + dialer := &tls.Dialer{ + Config: cfg, + } + cn, err := dialer.DialContext(ctx, network, addr) + if err != nil { + return nil, err + } + tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed + return tlsCn, nil +} + var http2DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1" type http2goroutineLock uint64 @@ -3094,12 +3184,12 @@ func http2buildCommonHeaderMaps() { } } -func http2lowerHeader(v string) string { +func http2lowerHeader(v string) (lower string, ascii bool) { http2buildCommonHeaderMapsOnce() if s, ok := http2commonLowerHeader[v]; ok { - return s + return s, true } - return strings.ToLower(v) + return http2asciiToLower(v) } var ( @@ -3797,13 +3887,12 @@ func http2ConfigureServer(s *Server, conf *http2Server) error { if s.TLSConfig == nil { s.TLSConfig = new(tls.Config) - } else if s.TLSConfig.CipherSuites != nil { - // If they already provided a CipherSuite list, return - // an error if it has a bad order or is missing - // ECDHE_RSA_WITH_AES_128_GCM_SHA256 or ECDHE_ECDSA_WITH_AES_128_GCM_SHA256. + } else if s.TLSConfig.CipherSuites != nil && s.TLSConfig.MinVersion < tls.VersionTLS13 { + // If they already provided a TLS 1.0–1.2 CipherSuite list, return an + // error if it is missing ECDHE_RSA_WITH_AES_128_GCM_SHA256 or + // ECDHE_ECDSA_WITH_AES_128_GCM_SHA256. haveRequired := false - sawBad := false - for i, cs := range s.TLSConfig.CipherSuites { + for _, cs := range s.TLSConfig.CipherSuites { switch cs { case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // Alternative MTI cipher to not discourage ECDSA-only servers. @@ -3811,14 +3900,9 @@ func http2ConfigureServer(s *Server, conf *http2Server) error { tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: haveRequired = true } - if http2isBadCipher(cs) { - sawBad = true - } else if sawBad { - return fmt.Errorf("http2: TLSConfig.CipherSuites index %d contains an HTTP/2-approved cipher suite (%#04x), but it comes after unapproved cipher suites. With this configuration, clients that don't support previous, approved cipher suites may be given an unapproved one and reject the connection.", i, cs) - } } if !haveRequired { - return fmt.Errorf("http2: TLSConfig.CipherSuites is missing an HTTP/2-required AES_128_GCM_SHA256 cipher (need at least one of TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 or TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256).") + return fmt.Errorf("http2: TLSConfig.CipherSuites is missing an HTTP/2-required AES_128_GCM_SHA256 cipher (need at least one of TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 or TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)") } } @@ -4864,7 +4948,9 @@ func (sc *http2serverConn) startGracefulShutdown() { sc.shutdownOnce.Do(func() { sc.sendServeMsg(http2gracefulShutdownMsg) }) } -// After sending GOAWAY, the connection will close after goAwayTimeout. +// After sending GOAWAY with an error code (non-graceful shutdown), the +// connection will close after goAwayTimeout. +// // If we close the connection immediately after sending GOAWAY, there may // be unsent data in our kernel receive buffer, which will cause the kernel // to send a TCP RST on close() instead of a FIN. This RST will abort the @@ -5200,23 +5286,37 @@ func (sc *http2serverConn) processSettingInitialWindowSize(val uint32) error { func (sc *http2serverConn) processData(f *http2DataFrame) error { sc.serveG.check() - if sc.inGoAway && sc.goAwayCode != http2ErrCodeNo { + id := f.Header().StreamID + if sc.inGoAway && (sc.goAwayCode != http2ErrCodeNo || id > sc.maxClientStreamID) { + // Discard all DATA frames if the GOAWAY is due to an + // error, or: + // + // Section 6.8: After sending a GOAWAY frame, the sender + // can discard frames for streams initiated by the + // receiver with identifiers higher than the identified + // last stream. return nil } - data := f.Data() - // "If a DATA frame is received whose stream is not in "open" - // or "half closed (local)" state, the recipient MUST respond - // with a stream error (Section 5.4.2) of type STREAM_CLOSED." - id := f.Header().StreamID + data := f.Data() state, st := sc.state(id) if id == 0 || state == http2stateIdle { + // Section 6.1: "DATA frames MUST be associated with a + // stream. If a DATA frame is received whose stream + // identifier field is 0x0, the recipient MUST respond + // with a connection error (Section 5.4.1) of type + // PROTOCOL_ERROR." + // // Section 5.1: "Receiving any frame other than HEADERS // or PRIORITY on a stream in this state MUST be // treated as a connection error (Section 5.4.1) of // type PROTOCOL_ERROR." return http2ConnectionError(http2ErrCodeProtocol) } + + // "If a DATA frame is received whose stream is not in "open" + // or "half closed (local)" state, the recipient MUST respond + // with a stream error (Section 5.4.2) of type STREAM_CLOSED." if st == nil || state != http2stateOpen || st.gotTrailerHeader || st.resetQueued { // This includes sending a RST_STREAM if the stream is // in stateHalfClosedLocal (which currently means that @@ -6344,8 +6444,12 @@ func (w *http2responseWriter) Push(target string, opts *PushOptions) error { // but PUSH_PROMISE requests cannot have a body. // http://tools.ietf.org/html/rfc7540#section-8.2 // Also disallow Host, since the promised URL must be absolute. - switch strings.ToLower(k) { - case "content-length", "content-encoding", "trailer", "te", "expect", "host": + if http2asciiEqualFold(k, "content-length") || + http2asciiEqualFold(k, "content-encoding") || + http2asciiEqualFold(k, "trailer") || + http2asciiEqualFold(k, "te") || + http2asciiEqualFold(k, "expect") || + http2asciiEqualFold(k, "host") { return fmt.Errorf("promised request headers cannot include %q", k) } } @@ -7067,12 +7171,12 @@ func http2canRetryError(err error) bool { return false } -func (t *http2Transport) dialClientConn(addr string, singleUse bool) (*http2ClientConn, error) { +func (t *http2Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*http2ClientConn, error) { host, _, err := net.SplitHostPort(addr) if err != nil { return nil, err } - tconn, err := t.dialTLS()("tcp", addr, t.newTLSConfig(host)) + tconn, err := t.dialTLS(ctx)("tcp", addr, t.newTLSConfig(host)) if err != nil { return nil, err } @@ -7093,34 +7197,24 @@ func (t *http2Transport) newTLSConfig(host string) *tls.Config { return cfg } -func (t *http2Transport) dialTLS() func(string, string, *tls.Config) (net.Conn, error) { +func (t *http2Transport) dialTLS(ctx context.Context) func(string, string, *tls.Config) (net.Conn, error) { if t.DialTLS != nil { return t.DialTLS } - return t.dialTLSDefault -} - -func (t *http2Transport) dialTLSDefault(network, addr string, cfg *tls.Config) (net.Conn, error) { - cn, err := tls.Dial(network, addr, cfg) - if err != nil { - return nil, err - } - if err := cn.Handshake(); err != nil { - return nil, err - } - if !cfg.InsecureSkipVerify { - if err := cn.VerifyHostname(cfg.ServerName); err != nil { + return func(network, addr string, cfg *tls.Config) (net.Conn, error) { + tlsCn, err := t.dialTLSWithContext(ctx, network, addr, cfg) + if err != nil { return nil, err } + state := tlsCn.ConnectionState() + if p := state.NegotiatedProtocol; p != http2NextProtoTLS { + return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", p, http2NextProtoTLS) + } + if !state.NegotiatedProtocolIsMutual { + return nil, errors.New("http2: could not negotiate protocol mutually") + } + return tlsCn, nil } - state := cn.ConnectionState() - if p := state.NegotiatedProtocol; p != http2NextProtoTLS { - return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", p, http2NextProtoTLS) - } - if !state.NegotiatedProtocolIsMutual { - return nil, errors.New("http2: could not negotiate protocol mutually") - } - return cn, nil } // disableKeepAlives reports whether connections should be closed as @@ -7508,7 +7602,7 @@ func http2checkConnHeaders(req *Request) error { if vv := req.Header["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") { return fmt.Errorf("http2: invalid Transfer-Encoding request header: %q", vv) } - if vv := req.Header["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !strings.EqualFold(vv[0], "close") && !strings.EqualFold(vv[0], "keep-alive")) { + if vv := req.Header["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !http2asciiEqualFold(vv[0], "close") && !http2asciiEqualFold(vv[0], "keep-alive")) { return fmt.Errorf("http2: invalid Connection request header: %q", vv) } return nil @@ -8049,19 +8143,21 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail var didUA bool for k, vv := range req.Header { - if strings.EqualFold(k, "host") || strings.EqualFold(k, "content-length") { + if http2asciiEqualFold(k, "host") || http2asciiEqualFold(k, "content-length") { // Host is :authority, already sent. // Content-Length is automatic, set below. continue - } else if strings.EqualFold(k, "connection") || strings.EqualFold(k, "proxy-connection") || - strings.EqualFold(k, "transfer-encoding") || strings.EqualFold(k, "upgrade") || - strings.EqualFold(k, "keep-alive") { + } else if http2asciiEqualFold(k, "connection") || + http2asciiEqualFold(k, "proxy-connection") || + http2asciiEqualFold(k, "transfer-encoding") || + http2asciiEqualFold(k, "upgrade") || + http2asciiEqualFold(k, "keep-alive") { // Per 8.1.2.2 Connection-Specific Header // Fields, don't send connection-specific // fields. We have already checked if any // are error-worthy so just ignore the rest. continue - } else if strings.EqualFold(k, "user-agent") { + } else if http2asciiEqualFold(k, "user-agent") { // Match Go's http1 behavior: at most one // User-Agent. If set to nil or empty string, // then omit it. Otherwise if not mentioned, @@ -8074,7 +8170,7 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail if vv[0] == "" { continue } - } else if strings.EqualFold(k, "cookie") { + } else if http2asciiEqualFold(k, "cookie") { // Per 8.1.2.5 To allow for better compression efficiency, the // Cookie header field MAY be split into separate header fields, // each with one or more cookie-pairs. @@ -8133,7 +8229,12 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail // Header list size is ok. Write the headers. enumerateHeaders(func(name, value string) { - name = strings.ToLower(name) + name, ascii := http2asciiToLower(name) + if !ascii { + // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header + // field names have to be ASCII characters (just as in HTTP/1.x). + return + } cc.writeHeader(name, value) if traceHeaders { http2traceWroteHeaderField(trace, name, value) @@ -8181,9 +8282,14 @@ func (cc *http2ClientConn) encodeTrailers(req *Request) ([]byte, error) { } for k, vv := range req.Trailer { + lowKey, ascii := http2asciiToLower(k) + if !ascii { + // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header + // field names have to be ASCII characters (just as in HTTP/1.x). + continue + } // Transfer-Encoding, etc.. have already been filtered at the // start of RoundTrip - lowKey := strings.ToLower(k) for _, v := range vv { cc.writeHeader(lowKey, v) } @@ -9606,7 +9712,12 @@ func http2encodeHeaders(enc *hpack.Encoder, h Header, keys []string) { } for _, k := range keys { vv := h[k] - k = http2lowerHeader(k) + k, ascii := http2lowerHeader(k) + if !ascii { + // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header + // field names have to be ASCII characters (just as in HTTP/1.x). + continue + } if !http2validWireHeaderFieldName(k) { // Skip it as backup paranoia. Per // golang.org/issue/14048, these should diff --git a/libgo/go/net/http/header.go b/libgo/go/net/http/header.go index b9b5391..4c72dcb 100644 --- a/libgo/go/net/http/header.go +++ b/libgo/go/net/http/header.go @@ -7,6 +7,7 @@ package http import ( "io" "net/http/httptrace" + "net/http/internal/ascii" "net/textproto" "sort" "strings" @@ -251,7 +252,7 @@ func hasToken(v, token string) bool { if endPos := sp + len(token); endPos != len(v) && !isTokenBoundary(v[endPos]) { continue } - if strings.EqualFold(v[sp:sp+len(token)], token) { + if ascii.EqualFold(v[sp:sp+len(token)], token) { return true } } diff --git a/libgo/go/net/http/http.go b/libgo/go/net/http/http.go index 4c5054b..101799f 100644 --- a/libgo/go/net/http/http.go +++ b/libgo/go/net/http/http.go @@ -62,15 +62,6 @@ func isNotToken(r rune) bool { return !httpguts.IsTokenRune(r) } -func isASCII(s string) bool { - for i := 0; i < len(s); i++ { - if s[i] >= utf8.RuneSelf { - return false - } - } - return true -} - // stringContainsCTLByte reports whether s contains any ASCII control character. func stringContainsCTLByte(s string) bool { for i := 0; i < len(s); i++ { diff --git a/libgo/go/net/http/http_test.go b/libgo/go/net/http/http_test.go index 3f1d7ce..0d92fe5 100644 --- a/libgo/go/net/http/http_test.go +++ b/libgo/go/net/http/http_test.go @@ -9,9 +9,13 @@ package http import ( "bytes" "internal/testenv" + "io/fs" "net/url" + "os" "os/exec" "reflect" + "regexp" + "strings" "testing" ) @@ -156,3 +160,61 @@ func BenchmarkCopyValues(b *testing.B) { b.Fatal("Benchmark wasn't run") } } + +var forbiddenStringsFunctions = map[string]bool{ + // Functions that use Unicode-aware case folding. + "EqualFold": true, + "Title": true, + "ToLower": true, + "ToLowerSpecial": true, + "ToTitle": true, + "ToTitleSpecial": true, + "ToUpper": true, + "ToUpperSpecial": true, + + // Functions that use Unicode-aware spaces. + "Fields": true, + "TrimSpace": true, +} + +// TestNoUnicodeStrings checks that nothing in net/http uses the Unicode-aware +// strings and bytes package functions. HTTP is mostly ASCII based, and doing +// Unicode-aware case folding or space stripping can introduce vulnerabilities. +func TestNoUnicodeStrings(t *testing.T) { + if !testenv.HasSrc() { + t.Skip("source code not available") + } + + re := regexp.MustCompile(`(strings|bytes).([A-Za-z]+)`) + if err := fs.WalkDir(os.DirFS("."), ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + t.Fatal(err) + } + + if path == "internal/ascii" { + return fs.SkipDir + } + if !strings.HasSuffix(path, ".go") || + strings.HasSuffix(path, "_test.go") || + path == "h2_bundle.go" || d.IsDir() { + return nil + } + + contents, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + for lineNum, line := range strings.Split(string(contents), "\n") { + for _, match := range re.FindAllStringSubmatch(line, -1) { + if !forbiddenStringsFunctions[match[2]] { + continue + } + t.Errorf("disallowed call to %s at %s:%d", match[0], path, lineNum+1) + } + } + + return nil + }); err != nil { + t.Fatal(err) + } +} diff --git a/libgo/go/net/http/httptest/recorder.go b/libgo/go/net/http/httptest/recorder.go index 2428482..1b712ef 100644 --- a/libgo/go/net/http/httptest/recorder.go +++ b/libgo/go/net/http/httptest/recorder.go @@ -122,11 +122,30 @@ func (rw *ResponseRecorder) WriteString(str string) (int, error) { return len(str), nil } +func checkWriteHeaderCode(code int) { + // Issue 22880: require valid WriteHeader status codes. + // For now we only enforce that it's three digits. + // In the future we might block things over 599 (600 and above aren't defined + // at https://httpwg.org/specs/rfc7231.html#status.codes) + // and we might block under 200 (once we have more mature 1xx support). + // But for now any three digits. + // + // We used to send "HTTP/1.1 000 0" on the wire in responses but there's + // no equivalent bogus thing we can realistically send in HTTP/2, + // so we'll consistently panic instead and help people find their bugs + // early. (We can't return an error from WriteHeader even if we wanted to.) + if code < 100 || code > 999 { + panic(fmt.Sprintf("invalid WriteHeader code %v", code)) + } +} + // WriteHeader implements http.ResponseWriter. func (rw *ResponseRecorder) WriteHeader(code int) { if rw.wroteHeader { return } + + checkWriteHeaderCode(code) rw.Code = code rw.wroteHeader = true if rw.HeaderMap == nil { diff --git a/libgo/go/net/http/httptest/recorder_test.go b/libgo/go/net/http/httptest/recorder_test.go index a865e87..8cb32dd 100644 --- a/libgo/go/net/http/httptest/recorder_test.go +++ b/libgo/go/net/http/httptest/recorder_test.go @@ -345,3 +345,28 @@ func TestParseContentLength(t *testing.T) { } } } + +// Ensure that httptest.Recorder panics when given a non-3 digit (XXX) +// status HTTP code. See https://golang.org/issues/45353 +func TestRecorderPanicsOnNonXXXStatusCode(t *testing.T) { + badCodes := []int{ + -100, 0, 99, 1000, 20000, + } + for _, badCode := range badCodes { + badCode := badCode + t.Run(fmt.Sprintf("Code=%d", badCode), func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("Expected a panic") + } + }() + + handler := func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(badCode) + } + r, _ := http.NewRequest("GET", "http://example.org/", nil) + rw := NewRecorder() + handler(rw, r) + }) + } +} diff --git a/libgo/go/net/http/httptest/server.go b/libgo/go/net/http/httptest/server.go index 8ebf681..a9faa9a 100644 --- a/libgo/go/net/http/httptest/server.go +++ b/libgo/go/net/http/httptest/server.go @@ -14,7 +14,7 @@ import ( "log" "net" "net/http" - "net/http/internal" + "net/http/internal/testcert" "os" "strings" "sync" @@ -144,7 +144,7 @@ func (s *Server) StartTLS() { if s.client == nil { s.client = &http.Client{Transport: &http.Transport{}} } - cert, err := tls.X509KeyPair(internal.LocalhostCert, internal.LocalhostKey) + cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey) if err != nil { panic(fmt.Sprintf("httptest: NewTLSServer: %v", err)) } @@ -316,6 +316,13 @@ func (s *Server) wrap() { s.Config.ConnState = func(c net.Conn, cs http.ConnState) { s.mu.Lock() defer s.mu.Unlock() + + // Keep Close from returning until the user's ConnState hook + // (if any) finishes. Without this, the call to forgetConn + // below might send the count to 0 before we run the hook. + s.wg.Add(1) + defer s.wg.Done() + switch cs { case http.StateNew: s.wg.Add(1) diff --git a/libgo/go/net/http/httptrace/trace.go b/libgo/go/net/http/httptrace/trace.go index 6a5cbac..5777c91 100644 --- a/libgo/go/net/http/httptrace/trace.go +++ b/libgo/go/net/http/httptrace/trace.go @@ -127,7 +127,7 @@ type ClientTrace struct { // ConnectDone is called when a new connection's Dial // completes. The provided err indicates whether the - // connection completedly successfully. + // connection completed successfully. // If net.Dialer.DualStack ("Happy Eyeballs") support is // enabled, this may be called multiple times. ConnectDone func(network, addr string, err error) diff --git a/libgo/go/net/http/httputil/dump_test.go b/libgo/go/net/http/httputil/dump_test.go index 8168b2e..366cc82 100644 --- a/libgo/go/net/http/httputil/dump_test.go +++ b/libgo/go/net/http/httputil/dump_test.go @@ -478,7 +478,7 @@ func TestDumpResponse(t *testing.T) { } } -// Issue 38352: Check for deadlock on cancelled requests. +// Issue 38352: Check for deadlock on canceled requests. func TestDumpRequestOutIssue38352(t *testing.T) { if testing.Short() { return diff --git a/libgo/go/net/http/httputil/reverseproxy.go b/libgo/go/net/http/httputil/reverseproxy.go index ccb8456..8b63368 100644 --- a/libgo/go/net/http/httputil/reverseproxy.go +++ b/libgo/go/net/http/httputil/reverseproxy.go @@ -13,6 +13,7 @@ import ( "log" "net" "net/http" + "net/http/internal/ascii" "net/textproto" "net/url" "strings" @@ -234,6 +235,15 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if req.ContentLength == 0 { outreq.Body = nil // Issue 16036: nil Body for http.Transport retries } + if outreq.Body != nil { + // 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 + // Issue 46866). Although calling Close doesn't guarantee there isn't + // any Read in flight after the handle returns, in practice it's safe to + // read after closing it. + defer outreq.Body.Close() + } if outreq.Header == nil { outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate } @@ -242,6 +252,10 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { outreq.Close = false reqUpType := upgradeType(outreq.Header) + if !ascii.IsPrint(reqUpType) { + p.getErrorHandler()(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType)) + return + } removeConnectionHeaders(outreq.Header) // Remove hop-by-hop headers to the backend. Especially @@ -534,13 +548,16 @@ func upgradeType(h http.Header) string { if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") { return "" } - return strings.ToLower(h.Get("Upgrade")) + return h.Get("Upgrade") } func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) { reqUpType := upgradeType(req.Header) resUpType := upgradeType(res.Header) - if reqUpType != resUpType { + 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)) + } + if !ascii.EqualFold(reqUpType, resUpType) { p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)) return } @@ -558,7 +575,7 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R backConnCloseCh := make(chan bool) go func() { - // Ensure that the cancelation of a request closes the backend. + // Ensure that the cancellation of a request closes the backend. // See issue https://golang.org/issue/35559. select { case <-req.Context().Done(): diff --git a/libgo/go/net/http/httputil/reverseproxy_test.go b/libgo/go/net/http/httputil/reverseproxy_test.go index 3211463..4b6ad77 100644 --- a/libgo/go/net/http/httputil/reverseproxy_test.go +++ b/libgo/go/net/http/httputil/reverseproxy_test.go @@ -16,6 +16,7 @@ import ( "log" "net/http" "net/http/httptest" + "net/http/internal/ascii" "net/url" "os" "reflect" @@ -1121,6 +1122,45 @@ func TestReverseProxy_PanicBodyError(t *testing.T) { rproxy.ServeHTTP(httptest.NewRecorder(), req) } +// Issue #46866: panic without closing incoming request body causes a panic +func TestReverseProxy_PanicClosesIncomingBody(t *testing.T) { + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + out := "this call was relayed by the reverse proxy" + // Coerce a wrong content length to induce io.ErrUnexpectedEOF + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(out)*2)) + fmt.Fprintln(w, out) + })) + defer backend.Close() + backendURL, err := url.Parse(backend.URL) + if err != nil { + t.Fatal(err) + } + proxyHandler := NewSingleHostReverseProxy(backendURL) + proxyHandler.ErrorLog = log.New(io.Discard, "", 0) // quiet for tests + frontend := httptest.NewServer(proxyHandler) + defer frontend.Close() + frontendClient := frontend.Client() + + var wg sync.WaitGroup + for i := 0; i < 2; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 10; j++ { + const reqLen = 6 * 1024 * 1024 + req, _ := http.NewRequest("POST", frontend.URL, &io.LimitedReader{R: neverEnding('x'), N: reqLen}) + req.ContentLength = reqLen + resp, _ := frontendClient.Transport.RoundTrip(req) + if resp != nil { + io.Copy(io.Discard, resp.Body) + resp.Body.Close() + } + } + }() + } + wg.Wait() +} + func TestSelectFlushInterval(t *testing.T) { tests := []struct { name string @@ -1242,7 +1282,7 @@ func TestReverseProxyWebSocket(t *testing.T) { t.Errorf("Header(XHeader) = %q; want %q", got, want) } - if upgradeType(res.Header) != "websocket" { + if !ascii.EqualFold(upgradeType(res.Header), "websocket") { t.Fatalf("not websocket upgrade; got %#v", res.Header) } rwc, ok := res.Body.(io.ReadWriteCloser) @@ -1267,7 +1307,7 @@ func TestReverseProxyWebSocket(t *testing.T) { } } -func TestReverseProxyWebSocketCancelation(t *testing.T) { +func TestReverseProxyWebSocketCancellation(t *testing.T) { n := 5 triggerCancelCh := make(chan bool, n) nthResponse := func(i int) string { @@ -1359,7 +1399,7 @@ func TestReverseProxyWebSocketCancelation(t *testing.T) { t.Errorf("X-Header mismatch\n\tgot: %q\n\twant: %q", g, w) } - if g, w := upgradeType(res.Header), "websocket"; g != w { + if g, w := upgradeType(res.Header), "websocket"; !ascii.EqualFold(g, w) { t.Fatalf("Upgrade header mismatch\n\tgot: %q\n\twant: %q", g, w) } diff --git a/libgo/go/net/http/internal/ascii/print.go b/libgo/go/net/http/internal/ascii/print.go new file mode 100644 index 0000000..585e5ba --- /dev/null +++ b/libgo/go/net/http/internal/ascii/print.go @@ -0,0 +1,61 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ascii + +import ( + "strings" + "unicode" +) + +// 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) { + return false + } + for i := 0; i < len(s); i++ { + if lower(s[i]) != lower(t[i]) { + return false + } + } + return true +} + +// lower returns the ASCII lowercase version of b. +func lower(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b +} + +// IsPrint returns whether s is ASCII and printable according to +// https://tools.ietf.org/html/rfc20#section-4.2. +func IsPrint(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] < ' ' || s[i] > '~' { + return false + } + } + return true +} + +// Is returns whether s is ASCII. +func Is(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] > unicode.MaxASCII { + return false + } + } + return true +} + +// ToLower returns the lowercase version of s if s is ASCII and printable. +func ToLower(s string) (lower string, ok bool) { + if !IsPrint(s) { + return "", false + } + return strings.ToLower(s), true +} diff --git a/libgo/go/net/http/internal/ascii/print_test.go b/libgo/go/net/http/internal/ascii/print_test.go new file mode 100644 index 0000000..0b7767ca --- /dev/null +++ b/libgo/go/net/http/internal/ascii/print_test.go @@ -0,0 +1,95 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ascii + +import "testing" + +func TestEqualFold(t *testing.T) { + var tests = []struct { + name string + a, b string + want bool + }{ + { + name: "empty", + want: true, + }, + { + name: "simple match", + a: "CHUNKED", + b: "chunked", + want: true, + }, + { + name: "same string", + a: "chunked", + b: "chunked", + want: true, + }, + { + name: "Unicode Kelvin symbol", + a: "chunKed", // This "K" is 'KELVIN SIGN' (\u212A) + b: "chunked", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := EqualFold(tt.a, tt.b); got != tt.want { + t.Errorf("AsciiEqualFold(%q,%q): got %v want %v", tt.a, tt.b, got, tt.want) + } + }) + } +} + +func TestIsPrint(t *testing.T) { + var tests = []struct { + name string + in string + want bool + }{ + { + name: "empty", + want: true, + }, + { + name: "ASCII low", + in: "This is a space: ' '", + want: true, + }, + { + name: "ASCII high", + in: "This is a tilde: '~'", + want: true, + }, + { + name: "ASCII low non-print", + in: "This is a unit separator: \x1F", + want: false, + }, + { + name: "Ascii high non-print", + in: "This is a Delete: \x7F", + want: false, + }, + { + name: "Unicode letter", + in: "Today it's 280K outside: it's freezing!", // This "K" is 'KELVIN SIGN' (\u212A) + want: false, + }, + { + name: "Unicode emoji", + in: "Gophers like 🧀", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsPrint(tt.in); got != tt.want { + t.Errorf("IsASCIIPrint(%q): got %v want %v", tt.in, got, tt.want) + } + }) + } +} diff --git a/libgo/go/net/http/internal/testcert.go b/libgo/go/net/http/internal/testcert/testcert.go index 2284a83..5f94704 100644 --- a/libgo/go/net/http/internal/testcert.go +++ b/libgo/go/net/http/internal/testcert/testcert.go @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package internal +// Package testcert contains a test-only localhost certificate. +package testcert import "strings" @@ -25,7 +26,7 @@ h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM fblo6RBxUQ== -----END CERTIFICATE-----`) -// LocalhostKey is the private key for localhostCert. +// LocalhostKey is the private key for LocalhostCert. var LocalhostKey = []byte(testingKey(`-----BEGIN RSA TESTING KEY----- MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB diff --git a/libgo/go/net/http/omithttp2.go b/libgo/go/net/http/omithttp2.go index 30c6e48..79599d0 100644 --- a/libgo/go/net/http/omithttp2.go +++ b/libgo/go/net/http/omithttp2.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build nethttpomithttp2 // +build nethttpomithttp2 package http diff --git a/libgo/go/net/http/pprof/pprof.go b/libgo/go/net/http/pprof/pprof.go index 5389a38..888ea35 100644 --- a/libgo/go/net/http/pprof/pprof.go +++ b/libgo/go/net/http/pprof/pprof.go @@ -287,7 +287,7 @@ func (name handler) serveDeltaProfile(w http.ResponseWriter, r *http.Request, p err := r.Context().Err() if err == context.DeadlineExceeded { serveError(w, http.StatusRequestTimeout, err.Error()) - } else { // TODO: what's a good status code for cancelled requests? 400? + } else { // TODO: what's a good status code for canceled requests? 400? serveError(w, http.StatusInternalServerError, err.Error()) } return @@ -431,7 +431,7 @@ Types of profiles available: b.WriteString(`</table> <a href="goroutine?debug=2">full goroutine stack dump</a> -<br/> +<br> <p> Profile Descriptions: <ul> diff --git a/libgo/go/net/http/request.go b/libgo/go/net/http/request.go index adba540..09cb0c7 100644 --- a/libgo/go/net/http/request.go +++ b/libgo/go/net/http/request.go @@ -19,6 +19,7 @@ import ( "mime/multipart" "net" "net/http/httptrace" + "net/http/internal/ascii" "net/textproto" "net/url" urlpkg "net/url" @@ -723,7 +724,7 @@ func idnaASCII(v string) (string, error) { // version does not. // Note that for correct ASCII IDNs ToASCII will only do considerably more // work, but it will not cause an allocation. - if isASCII(v) { + if ascii.Is(v) { return v, nil } return idna.Lookup.ToASCII(v) @@ -779,7 +780,8 @@ func removeZone(host string) string { } // ParseHTTPVersion parses an HTTP version string. -// "HTTP/1.0" returns (1, 0, true). +// "HTTP/1.0" returns (1, 0, true). Note that strings without +// a minor version, such as "HTTP/2", are not valid. func ParseHTTPVersion(vers string) (major, minor int, ok bool) { const Big = 1000000 // arbitrary upper bound switch vers { @@ -823,7 +825,7 @@ func validMethod(method string) bool { return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1 } -// NewRequest wraps NewRequestWithContext using the background context. +// NewRequest wraps NewRequestWithContext using context.Background. func NewRequest(method, url string, body io.Reader) (*Request, error) { return NewRequestWithContext(context.Background(), method, url, body) } @@ -947,7 +949,7 @@ func (r *Request) BasicAuth() (username, password string, ok bool) { func parseBasicAuth(auth string) (username, password string, ok bool) { const prefix = "Basic " // Case insensitive prefix match. See Issue 22736. - if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) { + if len(auth) < len(prefix) || !ascii.EqualFold(auth[:len(prefix)], prefix) { return } c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) @@ -1009,16 +1011,16 @@ func putTextprotoReader(r *textproto.Reader) { // 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) { - return readRequest(b, deleteHostHeader) -} + req, err := readRequest(b) + if err != nil { + return nil, err + } -// Constants for readRequest's deleteHostHeader parameter. -const ( - deleteHostHeader = true - keepHostHeader = false -) + delete(req.Header, "Host") + return req, err +} -func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) { +func readRequest(b *bufio.Reader) (req *Request, err error) { tp := newTextprotoReader(b) req = new(Request) @@ -1076,6 +1078,9 @@ func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err erro return nil, err } req.Header = Header(mimeHeader) + if len(req.Header["Host"]) > 1 { + return nil, fmt.Errorf("too many Host headers") + } // RFC 7230, section 5.3: Must treat // GET /index.html HTTP/1.1 @@ -1088,9 +1093,6 @@ func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err erro if req.Host == "" { req.Host = req.Header.get("Host") } - if deleteHostHeader { - delete(req.Header, "Host") - } fixPragmaCacheControl(req.Header) @@ -1123,6 +1125,9 @@ func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err erro // MaxBytesReader prevents clients from accidentally or maliciously // sending a large request and wasting server resources. func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser { + if n < 0 { // Treat negative limits as equivalent to 0. + n = 0 + } return &maxBytesReader{w: w, r: r, n: n} } @@ -1288,16 +1293,18 @@ func (r *Request) ParseForm() error { // its file parts are stored in memory, with the remainder stored on // disk in temporary files. // ParseMultipartForm calls 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. func (r *Request) ParseMultipartForm(maxMemory int64) error { if r.MultipartForm == multipartByReader { return errors.New("http: multipart handled by MultipartReader") } + var parseFormErr error if r.Form == nil { - err := r.ParseForm() - if err != nil { - return err - } + // Let errors in ParseForm fall through, and just + // return it at the end. + parseFormErr = r.ParseForm() } if r.MultipartForm != nil { return nil @@ -1324,7 +1331,7 @@ func (r *Request) ParseMultipartForm(maxMemory int64) error { r.MultipartForm = f - return nil + return parseFormErr } // FormValue returns the first value for the named component of the query. @@ -1452,5 +1459,5 @@ func requestMethodUsuallyLacksBody(method string) bool { // an HTTP/1 connection. func (r *Request) requiresHTTP1() bool { return hasToken(r.Header.Get("Connection"), "upgrade") && - strings.EqualFold(r.Header.Get("Upgrade"), "websocket") + ascii.EqualFold(r.Header.Get("Upgrade"), "websocket") } diff --git a/libgo/go/net/http/request_test.go b/libgo/go/net/http/request_test.go index 29297b0..4e0c4ba 100644 --- a/libgo/go/net/http/request_test.go +++ b/libgo/go/net/http/request_test.go @@ -32,9 +32,26 @@ func TestQuery(t *testing.T) { } } +// Issue #25192: Test that ParseForm fails but still parses the form when an URL +// containing a semicolon is provided. +func TestParseFormSemicolonSeparator(t *testing.T) { + for _, method := range []string{"POST", "PATCH", "PUT", "GET"} { + req, _ := NewRequest(method, "http://www.google.com/search?q=foo;q=bar&a=1", + strings.NewReader("q")) + err := req.ParseForm() + if err == nil { + t.Fatalf(`for method %s, ParseForm expected an error, got success`, method) + } + wantForm := url.Values{"a": []string{"1"}} + if !reflect.DeepEqual(req.Form, wantForm) { + t.Fatalf("for method %s, ParseForm expected req.Form = %v, want %v", method, req.Form, wantForm) + } + } +} + func TestParseFormQuery(t *testing.T) { req, _ := NewRequest("POST", "http://www.google.com/search?q=foo&q=bar&both=x&prio=1&orphan=nope&empty=not", - strings.NewReader("z=post&both=y&prio=2&=nokey&orphan;empty=&")) + strings.NewReader("z=post&both=y&prio=2&=nokey&orphan&empty=&")) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") if q := req.FormValue("q"); q != "foo" { @@ -245,6 +262,29 @@ func TestParseMultipartForm(t *testing.T) { } } +// Issue 45789: multipart form should not include directory path in filename +func TestParseMultipartFormFilename(t *testing.T) { + postData := + `--xxx +Content-Disposition: form-data; name="file"; filename="../usr/foobar.txt/" +Content-Type: text/plain + +--xxx-- +` + req := &Request{ + Method: "POST", + Header: Header{"Content-Type": {`multipart/form-data; boundary=xxx`}}, + Body: io.NopCloser(strings.NewReader(postData)), + } + _, hdr, err := req.FormFile("file") + if err != nil { + t.Fatal(err) + } + if hdr.Filename != "foobar.txt" { + t.Errorf("expected only the last element of the path, got %q", hdr.Filename) + } +} + // Issue #40430: Test that if maxMemory for ParseMultipartForm when combined with // the payload size and the internal leeway buffer size of 10MiB overflows, that we // correctly return an error. @@ -342,6 +382,18 @@ func TestMultipartRequest(t *testing.T) { validateTestMultipartContents(t, req, false) } +// Issue #25192: Test that ParseMultipartForm fails but still parses the +// multi-part form when an URL containing a semicolon is provided. +func TestParseMultipartFormSemicolonSeparator(t *testing.T) { + req := newTestMultipartRequest(t) + req.URL = &url.URL{RawQuery: "q=foo;q=bar"} + if err := req.ParseMultipartForm(25); err == nil { + t.Fatal("ParseMultipartForm expected error due to invalid semicolon, got nil") + } + defer req.MultipartForm.RemoveAll() + validateTestMultipartContents(t, req, false) +} + func TestMultipartRequestAuto(t *testing.T) { // Test that FormValue and FormFile automatically invoke // ParseMultipartForm and return the right values. @@ -469,6 +521,10 @@ var readRequestErrorTests = []struct { in: "HEAD / HTTP/1.1\r\nContent-Length:0\r\nContent-Length: 0\r\n\r\n", header: Header{"Content-Length": {"0"}}, }, + 11: { + in: "HEAD / HTTP/1.1\r\nHost: foo\r\nHost: bar\r\n\r\n\r\n\r\n", + err: "too many Host headers", + }, } func TestReadRequestErrors(t *testing.T) { @@ -850,6 +906,92 @@ func TestMaxBytesReaderStickyError(t *testing.T) { } } +// Issue 45101: maxBytesReader's Read panicked when n < -1. This test +// also ensures that Read treats negative limits as equivalent to 0. +func TestMaxBytesReaderDifferentLimits(t *testing.T) { + const testStr = "1234" + tests := [...]struct { + limit int64 + lenP int + wantN int + wantErr bool + }{ + 0: { + limit: -123, + lenP: 0, + wantN: 0, + wantErr: false, // Ensure we won't return an error when the limit is negative, but we don't need to read. + }, + 1: { + limit: -100, + lenP: 32 * 1024, + wantN: 0, + wantErr: true, + }, + 2: { + limit: -2, + lenP: 1, + wantN: 0, + wantErr: true, + }, + 3: { + limit: -1, + lenP: 2, + wantN: 0, + wantErr: true, + }, + 4: { + limit: 0, + lenP: 3, + wantN: 0, + wantErr: true, + }, + 5: { + limit: 1, + lenP: 4, + wantN: 1, + wantErr: true, + }, + 6: { + limit: 2, + lenP: 5, + wantN: 2, + wantErr: true, + }, + 7: { + limit: 3, + lenP: 2, + wantN: 2, + wantErr: false, + }, + 8: { + limit: int64(len(testStr)), + lenP: len(testStr), + wantN: len(testStr), + wantErr: false, + }, + 9: { + limit: 100, + lenP: 6, + wantN: len(testStr), + wantErr: false, + }, + } + for i, tt := range tests { + rc := MaxBytesReader(nil, io.NopCloser(strings.NewReader(testStr)), tt.limit) + + n, err := rc.Read(make([]byte, tt.lenP)) + + if n != tt.wantN { + t.Errorf("%d. n: %d, want n: %d", i, n, tt.wantN) + } + + if (err != nil) != tt.wantErr { + t.Errorf("%d. error: %v", i, err) + } + } +} + func TestWithContextDeepCopiesURL(t *testing.T) { req, err := NewRequest("POST", "https://golang.org/", nil) if err != nil { diff --git a/libgo/go/net/http/roundtrip.go b/libgo/go/net/http/roundtrip.go index 2ec736b..eef7c79 100644 --- a/libgo/go/net/http/roundtrip.go +++ b/libgo/go/net/http/roundtrip.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js || !wasm // +build !js !wasm package http diff --git a/libgo/go/net/http/roundtrip_js.go b/libgo/go/net/http/roundtrip_js.go index c6a221a..74c83a9 100644 --- a/libgo/go/net/http/roundtrip_js.go +++ b/libgo/go/net/http/roundtrip_js.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build js && wasm // +build js,wasm package http diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go index f868741..6394da3 100644 --- a/libgo/go/net/http/serve_test.go +++ b/libgo/go/net/http/serve_test.go @@ -25,6 +25,7 @@ import ( "net/http/httptest" "net/http/httputil" "net/http/internal" + "net/http/internal/testcert" "net/url" "os" "os/exec" @@ -1475,7 +1476,7 @@ func TestServeTLS(t *testing.T) { defer afterTest(t) defer SetTestHookServerServe(nil) - cert, err := tls.X509KeyPair(internal.LocalhostCert, internal.LocalhostKey) + cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey) if err != nil { t.Fatal(err) } @@ -1599,7 +1600,7 @@ func TestAutomaticHTTP2_Serve_WithTLSConfig(t *testing.T) { } func TestAutomaticHTTP2_ListenAndServe(t *testing.T) { - cert, err := tls.X509KeyPair(internal.LocalhostCert, internal.LocalhostKey) + cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey) if err != nil { t.Fatal(err) } @@ -1609,7 +1610,7 @@ func TestAutomaticHTTP2_ListenAndServe(t *testing.T) { } func TestAutomaticHTTP2_ListenAndServe_GetCertificate(t *testing.T) { - cert, err := tls.X509KeyPair(internal.LocalhostCert, internal.LocalhostKey) + cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey) if err != nil { t.Fatal(err) } @@ -6507,3 +6508,104 @@ func TestDisableKeepAliveUpgrade(t *testing.T) { t.Fatalf("unexpected value read from body:\ngot: %q\nwant: %q", b, "hello") } } + +func TestMuxRedirectRelative(t *testing.T) { + setParallel(t) + req, err := ReadRequest(bufio.NewReader(strings.NewReader("GET http://example.com HTTP/1.1\r\nHost: test\r\n\r\n"))) + if err != nil { + t.Errorf("%s", err) + } + mux := NewServeMux() + resp := httptest.NewRecorder() + mux.ServeHTTP(resp, req) + if got, want := resp.Header().Get("Location"), "/"; got != want { + t.Errorf("Location header expected %q; got %q", want, got) + } + if got, want := resp.Code, StatusMovedPermanently; got != want { + t.Errorf("Expected response code %d; got %d", want, got) + } +} + +// TestQuerySemicolon tests the behavior of semicolons in queries. See Issue 25192. +func TestQuerySemicolon(t *testing.T) { + t.Cleanup(func() { afterTest(t) }) + + tests := []struct { + query string + xNoSemicolons string + xWithSemicolons string + warning bool + }{ + {"?a=1;x=bad&x=good", "good", "bad", true}, + {"?a=1;b=bad&x=good", "good", "good", true}, + {"?a=1%3Bx=bad&x=good%3B", "good;", "good;", false}, + {"?a=1;x=good;x=bad", "", "good", true}, + } + + for _, tt := range tests { + t.Run(tt.query+"/allow=false", func(t *testing.T) { + allowSemicolons := false + testQuerySemicolon(t, tt.query, tt.xNoSemicolons, allowSemicolons, tt.warning) + }) + t.Run(tt.query+"/allow=true", func(t *testing.T) { + allowSemicolons, expectWarning := true, false + testQuerySemicolon(t, tt.query, tt.xWithSemicolons, allowSemicolons, expectWarning) + }) + } +} + +func testQuerySemicolon(t *testing.T, query string, wantX string, allowSemicolons, expectWarning bool) { + setParallel(t) + + writeBackX := func(w ResponseWriter, r *Request) { + x := r.URL.Query().Get("x") + if expectWarning { + if err := r.ParseForm(); err == nil || !strings.Contains(err.Error(), "semicolon") { + t.Errorf("expected error mentioning semicolons from ParseForm, got %v", err) + } + } else { + if err := r.ParseForm(); err != nil { + t.Errorf("expected no error from ParseForm, got %v", err) + } + } + if got := r.FormValue("x"); x != got { + t.Errorf("got %q from FormValue, want %q", got, x) + } + fmt.Fprintf(w, "%s", x) + } + + h := Handler(HandlerFunc(writeBackX)) + if allowSemicolons { + h = AllowQuerySemicolons(h) + } + + ts := httptest.NewUnstartedServer(h) + logBuf := &bytes.Buffer{} + ts.Config.ErrorLog = log.New(logBuf, "", 0) + ts.Start() + defer ts.Close() + + req, _ := NewRequest("GET", ts.URL+query, nil) + res, err := ts.Client().Do(req) + if err != nil { + t.Fatal(err) + } + slurp, _ := io.ReadAll(res.Body) + res.Body.Close() + if got, want := res.StatusCode, 200; got != want { + t.Errorf("Status = %d; want = %d", got, want) + } + if got, want := string(slurp), wantX; got != want { + t.Errorf("Body = %q; want = %q", got, want) + } + + if expectWarning { + if !strings.Contains(logBuf.String(), "semicolon") { + t.Errorf("got %q from ErrorLog, expected a mention of semicolons", logBuf.String()) + } + } else { + if strings.Contains(logBuf.String(), "semicolon") { + t.Errorf("got %q from ErrorLog, expected no mention of semicolons", logBuf.String()) + } + } +} diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go index 33aadd6..ce39933 100644 --- a/libgo/go/net/http/server.go +++ b/libgo/go/net/http/server.go @@ -333,7 +333,7 @@ func (c *conn) hijackLocked() (rwc net.Conn, buf *bufio.ReadWriter, err error) { const bufferBeforeChunkingSize = 2048 // chunkWriter writes to a response's conn buffer, and is the writer -// wrapped by the response.bufw buffered writer. +// wrapped by the response.w buffered writer. // // chunkWriter also is responsible for finalizing the Header, including // conditionally setting the Content-Type and setting a Content-Length @@ -577,37 +577,17 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) { return io.CopyBuffer(writerOnly{w}, src, buf) } - // sendfile path: - - // Do not start actually writing response until src is readable. - // If body length is <= sniffLen, sendfile/splice path will do - // little anyway. This small read also satisfies sniffing the - // body in case Content-Type is missing. - nr, er := src.Read(buf[:sniffLen]) - atEOF := errors.Is(er, io.EOF) - n += int64(nr) - - if nr > 0 { - // Write the small amount read normally. - nw, ew := w.Write(buf[:nr]) - if ew != nil { - err = ew - } else if nr != nw { - err = io.ErrShortWrite + // Copy the first sniffLen bytes before switching to ReadFrom. + // This ensures we don't start writing the response before the + // source is available (see golang.org/issue/5660) and provides + // enough bytes to perform Content-Type sniffing when required. + if !w.cw.wroteHeader { + n0, err := io.CopyBuffer(writerOnly{w}, io.LimitReader(src, sniffLen), buf) + n += n0 + if err != nil || n0 < sniffLen { + return n, err } } - if err == nil && er != nil && !atEOF { - err = er - } - - // Do not send StatusOK in the error case where nothing has been written. - if err == nil && !w.wroteHeader { - w.WriteHeader(StatusOK) // nr == 0, no error (or EOF) - } - - if err != nil || atEOF { - return n, err - } w.w.Flush() // get rid of any previous writes w.cw.flush() // make sure Header is written; flush data to rwc @@ -620,7 +600,7 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) { return n, err } - n0, err := io.Copy(writerOnly{w}, src) + n0, err := io.CopyBuffer(writerOnly{w}, src, buf) n += n0 return n, err } @@ -964,14 +944,14 @@ func (c *conn) readRequest(ctx context.Context) (w *response, err error) { hdrDeadline time.Time // or zero if none ) t0 := time.Now() - if d := c.server.readHeaderTimeout(); d != 0 { + if d := c.server.readHeaderTimeout(); d > 0 { hdrDeadline = t0.Add(d) } - if d := c.server.ReadTimeout; d != 0 { + if d := c.server.ReadTimeout; d > 0 { wholeReqDeadline = t0.Add(d) } c.rwc.SetReadDeadline(hdrDeadline) - if d := c.server.WriteTimeout; d != 0 { + if d := c.server.WriteTimeout; d > 0 { defer func() { c.rwc.SetWriteDeadline(time.Now().Add(d)) }() @@ -983,7 +963,7 @@ func (c *conn) readRequest(ctx context.Context) (w *response, err error) { peek, _ := c.bufr.Peek(4) // ReadRequest will get err below c.bufr.Discard(numLeadingCRorLF(peek)) } - req, err := readRequest(c.bufr, keepHostHeader) + req, err := readRequest(c.bufr) if err != nil { if c.r.hitReadLimit() { return nil, errTooLarge @@ -1003,9 +983,6 @@ func (c *conn) readRequest(ctx context.Context) (w *response, err error) { if req.ProtoAtLeast(1, 1) && (!haveHost || len(hosts) == 0) && !isH2Upgrade && req.Method != "CONNECT" { return nil, badRequestError("missing required Host header") } - if len(hosts) > 1 { - return nil, badRequestError("too many Host headers") - } if len(hosts) == 1 && !httpguts.ValidHostHeader(hosts[0]) { return nil, badRequestError("malformed Host header") } @@ -1557,12 +1534,12 @@ func (w *response) bodyAllowed() bool { // The Writers are wired together like: // // 1. *response (the ResponseWriter) -> -// 2. (*response).w, a *bufio.Writer of bufferBeforeChunkingSize bytes +// 2. (*response).w, a *bufio.Writer of bufferBeforeChunkingSize bytes -> // 3. chunkWriter.Writer (whose writeHeader finalizes Content-Length/Type) -// and which writes the chunk headers, if needed. -// 4. conn.buf, a bufio.Writer of default (4kB) bytes, writing to -> +// and which writes the chunk headers, if needed -> +// 4. conn.bufw, a *bufio.Writer of default (4kB) bytes, writing to -> // 5. checkConnErrorWriter{c}, which notes any non-nil error on Write -// and populates c.werr with it if so. but otherwise writes to: +// and populates c.werr with it if so, but otherwise writes to -> // 6. the rwc, the net.Conn. // // TODO(bradfitz): short-circuit some of the buffering when the @@ -1836,13 +1813,13 @@ func (c *conn) serve(ctx context.Context) { }() if tlsConn, ok := c.rwc.(*tls.Conn); ok { - if d := c.server.ReadTimeout; d != 0 { + if d := c.server.ReadTimeout; d > 0 { c.rwc.SetReadDeadline(time.Now().Add(d)) } - if d := c.server.WriteTimeout; d != 0 { + if d := c.server.WriteTimeout; d > 0 { c.rwc.SetWriteDeadline(time.Now().Add(d)) } - if err := tlsConn.Handshake(); err != nil { + if err := tlsConn.HandshakeContext(ctx); err != nil { // If the handshake failed due to the client not speaking // TLS, assume they're speaking plaintext HTTP and write a // 400 response on the TLS conn's underlying net.Conn. @@ -2412,9 +2389,8 @@ func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { if path != r.URL.Path { _, pattern = mux.handler(host, path) - url := *r.URL - url.Path = path - return RedirectHandler(url.String(), StatusMovedPermanently), pattern + u := &url.URL{Path: path, RawQuery: r.URL.RawQuery} + return RedirectHandler(u.String(), StatusMovedPermanently), pattern } return mux.handler(host, r.URL.Path) @@ -2572,7 +2548,8 @@ type Server struct { TLSConfig *tls.Config // ReadTimeout is the maximum duration for reading the entire - // request, including the body. + // request, including the body. A zero or negative value means + // there will be no timeout. // // Because ReadTimeout does not let Handlers make per-request // decisions on each request body's acceptable deadline or @@ -2592,6 +2569,7 @@ type Server struct { // writes of the response. It is reset whenever a new // request's header is read. Like ReadTimeout, it does not // let Handlers make decisions on a per-request basis. + // A zero or negative value means there will be no timeout. WriteTimeout time.Duration // IdleTimeout is the maximum amount of time to wait for the @@ -2643,7 +2621,7 @@ type Server struct { // value. ConnContext func(ctx context.Context, c net.Conn) context.Context - inShutdown atomicBool // true when when server is in shutdown + inShutdown atomicBool // true when server is in shutdown disableKeepAlives int32 // accessed atomically. nextProtoOnce sync.Once // guards setupHTTP2_* init @@ -2889,9 +2867,51 @@ func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } + + if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") { + var allowQuerySemicolonsInUse int32 + req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() { + atomic.StoreInt32(&allowQuerySemicolonsInUse, 1) + })) + defer func() { + if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 { + sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192") + } + }() + } + handler.ServeHTTP(rw, req) } +var silenceSemWarnContextKey = &contextKey{"silence-semicolons"} + +// AllowQuerySemicolons returns a handler that serves requests by converting any +// unescaped semicolons in the URL query to ampersands, and invoking the handler h. +// +// This restores the pre-Go 1.17 behavior of splitting query parameters on both +// semicolons and ampersands. (See golang.org/issue/25192). Note that this +// behavior doesn't match that of many proxies, and the mismatch can lead to +// security issues. +// +// AllowQuerySemicolons should be invoked before Request.ParseForm is called. +func AllowQuerySemicolons(h Handler) Handler { + return HandlerFunc(func(w ResponseWriter, r *Request) { + if silenceSemicolonsWarning, ok := r.Context().Value(silenceSemWarnContextKey).(func()); ok { + silenceSemicolonsWarning() + } + if strings.Contains(r.URL.RawQuery, ";") { + r2 := new(Request) + *r2 = *r + r2.URL = new(url.URL) + *r2.URL = *r.URL + r2.URL.RawQuery = strings.ReplaceAll(r.URL.RawQuery, ";", "&") + h.ServeHTTP(w, r2) + } else { + h.ServeHTTP(w, r) + } + }) +} + // ListenAndServe listens on the TCP network address srv.Addr and then // calls Serve to handle requests on incoming connections. // Accepted connections are configured to enable TCP keep-alives. diff --git a/libgo/go/net/http/sniff_test.go b/libgo/go/net/http/sniff_test.go index 8d53503..e913357 100644 --- a/libgo/go/net/http/sniff_test.go +++ b/libgo/go/net/http/sniff_test.go @@ -157,9 +157,25 @@ func testServerIssue5953(t *testing.T, h2 bool) { resp.Body.Close() } -func TestContentTypeWithCopy_h1(t *testing.T) { testContentTypeWithCopy(t, h1Mode) } -func TestContentTypeWithCopy_h2(t *testing.T) { testContentTypeWithCopy(t, h2Mode) } -func testContentTypeWithCopy(t *testing.T, h2 bool) { +type byteAtATimeReader struct { + buf []byte +} + +func (b *byteAtATimeReader) Read(p []byte) (n int, err error) { + if len(p) < 1 { + return 0, nil + } + if len(b.buf) == 0 { + return 0, io.EOF + } + p[0] = b.buf[0] + b.buf = b.buf[1:] + return 1, nil +} + +func TestContentTypeWithVariousSources_h1(t *testing.T) { testContentTypeWithVariousSources(t, h1Mode) } +func TestContentTypeWithVariousSources_h2(t *testing.T) { testContentTypeWithVariousSources(t, h2Mode) } +func testContentTypeWithVariousSources(t *testing.T, h2 bool) { defer afterTest(t) const ( @@ -167,30 +183,86 @@ func testContentTypeWithCopy(t *testing.T, h2 bool) { expected = "text/html; charset=utf-8" ) - cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { - // Use io.Copy from a bytes.Buffer to trigger ReadFrom. - buf := bytes.NewBuffer([]byte(input)) - n, err := io.Copy(w, buf) - if int(n) != len(input) || err != nil { - t.Errorf("io.Copy(w, %q) = %v, %v want %d, nil", input, n, err, len(input)) - } - })) - defer cst.close() + for _, test := range []struct { + name string + handler func(ResponseWriter, *Request) + }{{ + name: "write", + handler: func(w ResponseWriter, r *Request) { + // Write the whole input at once. + n, err := w.Write([]byte(input)) + if int(n) != len(input) || err != nil { + t.Errorf("w.Write(%q) = %v, %v want %d, nil", input, n, err, len(input)) + } + }, + }, { + name: "write one byte at a time", + handler: func(w ResponseWriter, r *Request) { + // Write the input one byte at a time. + buf := []byte(input) + for i := range buf { + n, err := w.Write(buf[i : i+1]) + if n != 1 || err != nil { + t.Errorf("w.Write(%q) = %v, %v want 1, nil", input, n, err) + } + } + }, + }, { + name: "copy from Reader", + handler: func(w ResponseWriter, r *Request) { + // Use io.Copy from a plain Reader. + type readerOnly struct{ io.Reader } + buf := bytes.NewBuffer([]byte(input)) + n, err := io.Copy(w, readerOnly{buf}) + if int(n) != len(input) || err != nil { + t.Errorf("io.Copy(w, %q) = %v, %v want %d, nil", input, n, err, len(input)) + } + }, + }, { + name: "copy from bytes.Buffer", + handler: func(w ResponseWriter, r *Request) { + // Use io.Copy from a bytes.Buffer to trigger ReadFrom. + buf := bytes.NewBuffer([]byte(input)) + n, err := io.Copy(w, buf) + if int(n) != len(input) || err != nil { + t.Errorf("io.Copy(w, %q) = %v, %v want %d, nil", input, n, err, len(input)) + } + }, + }, { + name: "copy one byte at a time", + handler: func(w ResponseWriter, r *Request) { + // Use io.Copy from a Reader that returns one byte at a time. + n, err := io.Copy(w, &byteAtATimeReader{[]byte(input)}) + if int(n) != len(input) || err != nil { + t.Errorf("io.Copy(w, %q) = %v, %v want %d, nil", input, n, err, len(input)) + } + }, + }} { + t.Run(test.name, func(t *testing.T) { + cst := newClientServerTest(t, h2, HandlerFunc(test.handler)) + defer cst.close() + + resp, err := cst.c.Get(cst.ts.URL) + if err != nil { + t.Fatalf("Get: %v", err) + } + if ct := resp.Header.Get("Content-Type"); ct != expected { + t.Errorf("Content-Type = %q, want %q", ct, expected) + } + if want, got := resp.Header.Get("Content-Length"), fmt.Sprint(len(input)); want != got { + t.Errorf("Content-Length = %q, want %q", want, got) + } + data, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("reading body: %v", err) + } else if !bytes.Equal(data, []byte(input)) { + t.Errorf("data is %q, want %q", data, input) + } + resp.Body.Close() + + }) - resp, err := cst.c.Get(cst.ts.URL) - if err != nil { - t.Fatalf("Get: %v", err) - } - if ct := resp.Header.Get("Content-Type"); ct != expected { - t.Errorf("Content-Type = %q, want %q", ct, expected) - } - data, err := io.ReadAll(resp.Body) - if err != nil { - t.Errorf("reading body: %v", err) - } else if !bytes.Equal(data, []byte(input)) { - t.Errorf("data is %q, want %q", data, input) } - resp.Body.Close() } func TestSniffWriteSize_h1(t *testing.T) { testSniffWriteSize(t, h1Mode) } diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go index fbb0c39..85c2e5a 100644 --- a/libgo/go/net/http/transfer.go +++ b/libgo/go/net/http/transfer.go @@ -12,6 +12,7 @@ import ( "io" "net/http/httptrace" "net/http/internal" + "net/http/internal/ascii" "net/textproto" "reflect" "sort" @@ -638,7 +639,7 @@ func (t *transferReader) parseTransferEncoding() error { if len(raw) != 1 { return &unsupportedTEError{fmt.Sprintf("too many transfer encodings: %q", raw)} } - if strings.ToLower(textproto.TrimString(raw[0])) != "chunked" { + if !ascii.EqualFold(textproto.TrimString(raw[0]), "chunked") { return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", raw[0])} } diff --git a/libgo/go/net/http/transport.go b/libgo/go/net/http/transport.go index 0aa4827..309194e 100644 --- a/libgo/go/net/http/transport.go +++ b/libgo/go/net/http/transport.go @@ -21,6 +21,7 @@ import ( "log" "net" "net/http/httptrace" + "net/http/internal/ascii" "net/textproto" "net/url" "os" @@ -426,6 +427,7 @@ func (t *Transport) onceSetNextProtoDefaults() { // // The environment values may be either a complete URL or a // "host[:port]", in which case the "http" scheme is assumed. +// The schemes "http", "https", and "socks5" are supported. // An error is returned if the value is a different form. // // A nil URL and nil error are returned if no proxy is defined in the @@ -786,10 +788,12 @@ func (t *Transport) CancelRequest(req *Request) { // Cancel an in-flight request, recording the error value. // Returns whether the request was canceled. func (t *Transport) cancelRequest(key cancelKey, err error) bool { + // This function must not return until the cancel func has completed. + // See: https://golang.org/issue/34658 t.reqMu.Lock() + defer t.reqMu.Unlock() cancel := t.reqCanceler[key] delete(t.reqCanceler, key) - t.reqMu.Unlock() if cancel != nil { cancel(err) } @@ -1185,7 +1189,7 @@ type wantConn struct { // hooks for testing to know when dials are done // beforeDial is called in the getConn goroutine when the dial is queued. - // afterDial is called when the dial is completed or cancelled. + // afterDial is called when the dial is completed or canceled. beforeDial func() afterDial func() @@ -1373,7 +1377,7 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persi trace.GotConn(httptrace.GotConnInfo{Conn: w.pc.conn, Reused: w.pc.isReused()}) } if w.err != nil { - // If the request has been cancelled, that's probably + // If the request has been canceled, that's probably // what caused w.err; if so, prefer to return the // cancellation error (see golang.org/issue/16049). select { @@ -1435,7 +1439,7 @@ func (t *Transport) queueForDial(w *wantConn) { // dialConnFor dials on behalf of w and delivers the result to w. // dialConnFor has received permission to dial w.cm and is counted in t.connCount[w.cm.key()]. -// If the dial is cancelled or unsuccessful, dialConnFor decrements t.connCount[w.cm.key()]. +// If the dial is canceled or unsuccessful, dialConnFor decrements t.connCount[w.cm.key()]. func (t *Transport) dialConnFor(w *wantConn) { defer w.afterDial() @@ -1505,7 +1509,7 @@ func (t *Transport) decConnsPerHost(key connectMethodKey) { // Add TLS to a persistent connection, i.e. negotiate a TLS session. If pconn is already a TLS // tunnel, this function establishes a nested TLS session inside the encrypted channel. // The remote endpoint's name may be overridden by TLSClientConfig.ServerName. -func (pconn *persistConn) addTLS(name string, trace *httptrace.ClientTrace) error { +func (pconn *persistConn) addTLS(ctx context.Context, name string, trace *httptrace.ClientTrace) error { // Initiate TLS and check remote host name against certificate. cfg := cloneTLSConfig(pconn.t.TLSClientConfig) if cfg.ServerName == "" { @@ -1527,7 +1531,7 @@ func (pconn *persistConn) addTLS(name string, trace *httptrace.ClientTrace) erro if trace != nil && trace.TLSHandshakeStart != nil { trace.TLSHandshakeStart() } - err := tlsConn.Handshake() + err := tlsConn.HandshakeContext(ctx) if timer != nil { timer.Stop() } @@ -1583,7 +1587,7 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers if trace != nil && trace.TLSHandshakeStart != nil { trace.TLSHandshakeStart() } - if err := tc.Handshake(); err != nil { + if err := tc.HandshakeContext(ctx); err != nil { go pconn.conn.Close() if trace != nil && trace.TLSHandshakeDone != nil { trace.TLSHandshakeDone(tls.ConnectionState{}, err) @@ -1607,7 +1611,7 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers if firstTLSHost, _, err = net.SplitHostPort(cm.addr()); err != nil { return nil, wrapErr(err) } - if err = pconn.addTLS(firstTLSHost, trace); err != nil { + if err = pconn.addTLS(ctx, firstTLSHost, trace); err != nil { return nil, wrapErr(err) } } @@ -1721,7 +1725,7 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers } if cm.proxyURL != nil && cm.targetScheme == "https" { - if err := pconn.addTLS(cm.tlsHost(), trace); err != nil { + if err := pconn.addTLS(ctx, cm.tlsHost(), trace); err != nil { return nil, err } } @@ -2183,7 +2187,7 @@ func (pc *persistConn) readLoop() { } resp.Body = body - if rc.addedGzip && strings.EqualFold(resp.Header.Get("Content-Encoding"), "gzip") { + if rc.addedGzip && ascii.EqualFold(resp.Header.Get("Content-Encoding"), "gzip") { resp.Body = &gzipReader{body: body} resp.Header.Del("Content-Encoding") resp.Header.Del("Content-Length") diff --git a/libgo/go/net/http/transport_internal_test.go b/libgo/go/net/http/transport_internal_test.go index 1097ffd..1cce272 100644 --- a/libgo/go/net/http/transport_internal_test.go +++ b/libgo/go/net/http/transport_internal_test.go @@ -12,7 +12,7 @@ import ( "errors" "io" "net" - "net/http/internal" + "net/http/internal/testcert" "strings" "testing" ) @@ -191,7 +191,7 @@ func (f roundTripFunc) RoundTrip(r *Request) (*Response, error) { // Issue 25009 func TestTransportBodyAltRewind(t *testing.T) { - cert, err := tls.X509KeyPair(internal.LocalhostCert, internal.LocalhostKey) + cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey) if err != nil { t.Fatal(err) } diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index d0202c0..cae5464 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -30,7 +30,7 @@ import ( "net/http/httptest" "net/http/httptrace" "net/http/httputil" - "net/http/internal" + "net/http/internal/testcert" "net/textproto" "net/url" "os" @@ -626,12 +626,15 @@ func TestTransportMaxConnsPerHost(t *testing.T) { t.Fatalf("ExportHttp2ConfigureTransport: %v", err) } - connCh := make(chan net.Conn, 1) + mu := sync.Mutex{} + var conns []net.Conn var dialCnt, gotConnCnt, tlsHandshakeCnt int32 tr.Dial = func(network, addr string) (net.Conn, error) { atomic.AddInt32(&dialCnt, 1) c, err := net.Dial(network, addr) - connCh <- c + mu.Lock() + defer mu.Unlock() + conns = append(conns, c) return c, err } @@ -685,7 +688,12 @@ func TestTransportMaxConnsPerHost(t *testing.T) { t.FailNow() } - (<-connCh).Close() + mu.Lock() + for _, c := range conns { + c.Close() + } + conns = nil + mu.Unlock() tr.CloseIdleConnections() doReq() @@ -3738,7 +3746,7 @@ func TestTransportDialTLSContext(t *testing.T) { if err != nil { return nil, err } - return c, c.Handshake() + return c, c.HandshakeContext(ctx) } req, err := NewRequest("GET", ts.URL, nil) @@ -4295,7 +4303,7 @@ func TestTransportReuseConnEmptyResponseBody(t *testing.T) { // Issue 13839 func TestNoCrashReturningTransportAltConn(t *testing.T) { - cert, err := tls.X509KeyPair(internal.LocalhostCert, internal.LocalhostKey) + cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey) if err != nil { t.Fatal(err) } diff --git a/libgo/go/net/http/triv.go b/libgo/go/net/http/triv.go index 23e65d5..4dc6240 100644 --- a/libgo/go/net/http/triv.go +++ b/libgo/go/net/http/triv.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore package main diff --git a/libgo/go/net/interface.go b/libgo/go/net/interface.go index 914aaa0..0e5d320 100644 --- a/libgo/go/net/interface.go +++ b/libgo/go/net/interface.go @@ -6,6 +6,7 @@ package net import ( "errors" + "internal/itoa" "sync" "time" ) @@ -230,7 +231,7 @@ func (zc *ipv6ZoneCache) name(index int) string { zoneCache.RUnlock() } if !ok { // last resort - name = uitoa(uint(index)) + name = itoa.Uitoa(uint(index)) } return name } diff --git a/libgo/go/net/interface_bsd.go b/libgo/go/net/interface_bsd.go index d791cb3..7578b1a 100644 --- a/libgo/go/net/interface_bsd.go +++ b/libgo/go/net/interface_bsd.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || netbsd || openbsd // +build darwin dragonfly freebsd netbsd openbsd package net diff --git a/libgo/go/net/interface_bsd_test.go b/libgo/go/net/interface_bsd_test.go index 947dde7..8d0d9c3 100644 --- a/libgo/go/net/interface_bsd_test.go +++ b/libgo/go/net/interface_bsd_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || netbsd || openbsd // +build darwin dragonfly freebsd netbsd openbsd package net diff --git a/libgo/go/net/interface_bsdvar.go b/libgo/go/net/interface_bsdvar.go index a809b5f..6230e0b 100644 --- a/libgo/go/net/interface_bsdvar.go +++ b/libgo/go/net/interface_bsdvar.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build dragonfly || netbsd || openbsd // +build dragonfly netbsd openbsd package net diff --git a/libgo/go/net/interface_freebsd.go b/libgo/go/net/interface_freebsd.go index 45badd6..2b51fcb 100644 --- a/libgo/go/net/interface_freebsd.go +++ b/libgo/go/net/interface_freebsd.go @@ -16,9 +16,9 @@ func interfaceMessages(ifindex int) ([]route.Message, error) { if err != nil { typ = route.RIBType(syscall.NET_RT_IFLIST) rib, err = route.FetchRIB(syscall.AF_UNSPEC, typ, ifindex) - } - if err != nil { - return nil, err + if err != nil { + return nil, err + } } return route.ParseRIB(typ, rib) } diff --git a/libgo/go/net/interface_plan9.go b/libgo/go/net/interface_plan9.go index 31bbaca..957975c 100644 --- a/libgo/go/net/interface_plan9.go +++ b/libgo/go/net/interface_plan9.go @@ -6,6 +6,7 @@ package net import ( "errors" + "internal/itoa" "os" ) @@ -38,8 +39,8 @@ func interfaceTable(ifindex int) ([]Interface, error) { func readInterface(i int) (*Interface, error) { ifc := &Interface{ - Index: i + 1, // Offset the index by one to suit the contract - Name: netdir + "/ipifc/" + itoa(i), // Name is the full path to the interface path in plan9 + Index: i + 1, // Offset the index by one to suit the contract + Name: netdir + "/ipifc/" + itoa.Itoa(i), // Name is the full path to the interface path in plan9 } ifcstat := ifc.Name + "/status" diff --git a/libgo/go/net/interface_stub.go b/libgo/go/net/interface_stub.go index 437b9eb..1075e36 100644 --- a/libgo/go/net/interface_stub.go +++ b/libgo/go/net/interface_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build hurd || (js && wasm) // +build hurd js,wasm package net diff --git a/libgo/go/net/interface_test.go b/libgo/go/net/interface_test.go index b2ef21e..754db36 100644 --- a/libgo/go/net/interface_test.go +++ b/libgo/go/net/interface_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/interface_unix_test.go b/libgo/go/net/interface_unix_test.go index bf41a0fb..0d69fa5 100644 --- a/libgo/go/net/interface_unix_test.go +++ b/libgo/go/net/interface_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd // +build darwin dragonfly freebsd linux netbsd openbsd package net diff --git a/libgo/go/net/internal/socktest/main_test.go b/libgo/go/net/internal/socktest/main_test.go index 3b0a48a..8af85d3 100644 --- a/libgo/go/net/internal/socktest/main_test.go +++ b/libgo/go/net/internal/socktest/main_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js && !plan9 // +build !js,!plan9 package socktest_test diff --git a/libgo/go/net/internal/socktest/main_unix_test.go b/libgo/go/net/internal/socktest/main_unix_test.go index 4d9d414..6aa8875 100644 --- a/libgo/go/net/internal/socktest/main_unix_test.go +++ b/libgo/go/net/internal/socktest/main_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js && !plan9 && !windows // +build !js,!plan9,!windows package socktest_test diff --git a/libgo/go/net/internal/socktest/switch_posix.go b/libgo/go/net/internal/socktest/switch_posix.go index 863edef..cda74e8 100644 --- a/libgo/go/net/internal/socktest/switch_posix.go +++ b/libgo/go/net/internal/socktest/switch_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !plan9 // +build !plan9 package socktest diff --git a/libgo/go/net/internal/socktest/switch_stub.go b/libgo/go/net/internal/socktest/switch_stub.go index 28ce72c..5aa2ece 100644 --- a/libgo/go/net/internal/socktest/switch_stub.go +++ b/libgo/go/net/internal/socktest/switch_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build plan9 // +build plan9 package socktest diff --git a/libgo/go/net/internal/socktest/switch_unix.go b/libgo/go/net/internal/socktest/switch_unix.go index 4c037ba..be9ef6d 100644 --- a/libgo/go/net/internal/socktest/switch_unix.go +++ b/libgo/go/net/internal/socktest/switch_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris package socktest diff --git a/libgo/go/net/internal/socktest/sys_cloexec.go b/libgo/go/net/internal/socktest/sys_cloexec.go index b13ba57..5e95896 100644 --- a/libgo/go/net/internal/socktest/sys_cloexec.go +++ b/libgo/go/net/internal/socktest/sys_cloexec.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build dragonfly || freebsd || hurd || illumos || linux || netbsd || openbsd // +build dragonfly freebsd hurd illumos linux netbsd openbsd package socktest diff --git a/libgo/go/net/internal/socktest/sys_unix.go b/libgo/go/net/internal/socktest/sys_unix.go index fbbffc6..39f3dbc 100644 --- a/libgo/go/net/internal/socktest/sys_unix.go +++ b/libgo/go/net/internal/socktest/sys_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris package socktest diff --git a/libgo/go/net/ip.go b/libgo/go/net/ip.go index c00fe8e..38e1aa2 100644 --- a/libgo/go/net/ip.go +++ b/libgo/go/net/ip.go @@ -12,7 +12,10 @@ package net -import "internal/bytealg" +import ( + "internal/bytealg" + "internal/itoa" +) // IP address lengths (bytes). const ( @@ -125,6 +128,25 @@ func (ip IP) IsLoopback() bool { return ip.Equal(IPv6loopback) } +// IsPrivate reports whether ip is a private address, according to +// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses). +func (ip IP) IsPrivate() bool { + if ip4 := ip.To4(); ip4 != nil { + // Following RFC 1918, Section 3. Private Address Space which says: + // The Internet Assigned Numbers Authority (IANA) has reserved the + // following three blocks of the IP address space for private internets: + // 10.0.0.0 - 10.255.255.255 (10/8 prefix) + // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) + // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) + return ip4[0] == 10 || + (ip4[0] == 172 && ip4[1]&0xf0 == 16) || + (ip4[0] == 192 && ip4[1] == 168) + } + // Following RFC 4193, Section 8. IANA Considerations which says: + // The IANA has assigned the FC00::/7 prefix to "Unique Local Unicast". + return len(ip) == IPv6len && ip[0]&0xfe == 0xfc +} + // IsMulticast reports whether ip is a multicast address. func (ip IP) IsMulticast() bool { if ip4 := ip.To4(); ip4 != nil { @@ -531,7 +553,7 @@ func (n *IPNet) String() string { if l == -1 { return nn.String() + "/" + m.String() } - return nn.String() + "/" + uitoa(uint(l)) + return nn.String() + "/" + itoa.Uitoa(uint(l)) } // Parse IPv4 address (d.d.d.d). @@ -552,6 +574,10 @@ func parseIPv4(s string) IP { if !ok || n > 0xFF { return nil } + if c > 1 && s[0] == '0' { + // Reject non-zero components with leading zeroes. + return nil + } s = s[c:] p[i] = byte(n) } diff --git a/libgo/go/net/ip_test.go b/libgo/go/net/ip_test.go index a5fc5e6..5bbda60 100644 --- a/libgo/go/net/ip_test.go +++ b/libgo/go/net/ip_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net @@ -20,9 +21,7 @@ var parseIPTests = []struct { }{ {"127.0.1.2", IPv4(127, 0, 1, 2)}, {"127.0.0.1", IPv4(127, 0, 0, 1)}, - {"127.001.002.003", IPv4(127, 1, 2, 3)}, {"::ffff:127.1.2.3", IPv4(127, 1, 2, 3)}, - {"::ffff:127.001.002.003", IPv4(127, 1, 2, 3)}, {"::ffff:7f01:0203", IPv4(127, 1, 2, 3)}, {"0:0:0:0:0000:ffff:127.1.2.3", IPv4(127, 1, 2, 3)}, {"0:0:0:0:000000:ffff:127.1.2.3", IPv4(127, 1, 2, 3)}, @@ -42,6 +41,11 @@ var parseIPTests = []struct { {"fe80::1%911", nil}, {"", nil}, {"a1:a2:a3:a4::b1:b2:b3:b4", nil}, // Issue 6628 + {"127.001.002.003", nil}, + {"::ffff:127.001.002.003", nil}, + {"123.000.000.000", nil}, + {"1.2..4", nil}, + {"0123.0.0.1", nil}, } func TestParseIP(t *testing.T) { @@ -357,6 +361,7 @@ var parseCIDRTests = []struct { {"0.0.-2.0/32", nil, nil, &ParseError{Type: "CIDR address", Text: "0.0.-2.0/32"}}, {"0.0.0.-3/32", nil, nil, &ParseError{Type: "CIDR address", Text: "0.0.0.-3/32"}}, {"0.0.0.0/-0", nil, nil, &ParseError{Type: "CIDR address", Text: "0.0.0.0/-0"}}, + {"127.000.000.001/32", nil, nil, &ParseError{Type: "CIDR address", Text: "127.000.000.001/32"}}, {"", nil, nil, &ParseError{Type: "CIDR address", Text: ""}}, } @@ -690,6 +695,28 @@ var ipAddrScopeTests = []struct { {IP.IsGlobalUnicast, IP{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, {IP.IsGlobalUnicast, IP{0xff, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, {IP.IsGlobalUnicast, nil, false}, + {IP.IsPrivate, nil, false}, + {IP.IsPrivate, IPv4(1, 1, 1, 1), false}, + {IP.IsPrivate, IPv4(9, 255, 255, 255), false}, + {IP.IsPrivate, IPv4(10, 0, 0, 0), true}, + {IP.IsPrivate, IPv4(10, 255, 255, 255), true}, + {IP.IsPrivate, IPv4(11, 0, 0, 0), false}, + {IP.IsPrivate, IPv4(172, 15, 255, 255), false}, + {IP.IsPrivate, IPv4(172, 16, 0, 0), true}, + {IP.IsPrivate, IPv4(172, 16, 255, 255), true}, + {IP.IsPrivate, IPv4(172, 23, 18, 255), true}, + {IP.IsPrivate, IPv4(172, 31, 255, 255), true}, + {IP.IsPrivate, IPv4(172, 31, 0, 0), true}, + {IP.IsPrivate, IPv4(172, 32, 0, 0), false}, + {IP.IsPrivate, IPv4(192, 167, 255, 255), false}, + {IP.IsPrivate, IPv4(192, 168, 0, 0), true}, + {IP.IsPrivate, IPv4(192, 168, 255, 255), true}, + {IP.IsPrivate, IPv4(192, 169, 0, 0), false}, + {IP.IsPrivate, IP{0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, false}, + {IP.IsPrivate, IP{0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, + {IP.IsPrivate, IP{0xfc, 0xff, 0x12, 0, 0, 0, 0, 0x44, 0, 0, 0, 0, 0, 0, 0, 0}, true}, + {IP.IsPrivate, IP{0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, true}, + {IP.IsPrivate, IP{0xfe, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, } func name(f interface{}) string { diff --git a/libgo/go/net/iprawsock_posix.go b/libgo/go/net/iprawsock_posix.go index fdb3913..ffc437c 100644 --- a/libgo/go/net/iprawsock_posix.go +++ b/libgo/go/net/iprawsock_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris windows package net @@ -74,7 +75,7 @@ func stripIPv4Header(n int, b []byte) int { func (c *IPConn) readMsg(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err error) { var sa syscall.Sockaddr - n, oobn, flags, sa, err = c.fd.readMsg(b, oob) + n, oobn, flags, sa, err = c.fd.readMsg(b, oob, 0) switch sa := sa.(type) { case *syscall.SockaddrInet4: addr = &IPAddr{IP: sa.Addr[0:]} diff --git a/libgo/go/net/iprawsock_test.go b/libgo/go/net/iprawsock_test.go index 8e3543d..a96448e 100644 --- a/libgo/go/net/iprawsock_test.go +++ b/libgo/go/net/iprawsock_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/ipsock.go b/libgo/go/net/ipsock.go index 7d0684d..0f5da25 100644 --- a/libgo/go/net/ipsock.go +++ b/libgo/go/net/ipsock.go @@ -7,6 +7,7 @@ package net import ( "context" "internal/bytealg" + "runtime" "sync" ) @@ -44,6 +45,13 @@ func supportsIPv6() bool { // IPv4 address inside an IPv6 address at transport layer // protocols. See RFC 4291, RFC 4038 and RFC 3493. func supportsIPv4map() bool { + // Some operating systems provide no support for mapping IPv4 + // addresses to IPv6, and a runtime check is unnecessary. + switch runtime.GOOS { + case "dragonfly", "openbsd": + return false + } + ipStackCaps.Once.Do(ipStackCaps.probe) return ipStackCaps.ipv4MappedIPv6Enabled } diff --git a/libgo/go/net/ipsock_plan9.go b/libgo/go/net/ipsock_plan9.go index 7a4b7a6..4328743 100644 --- a/libgo/go/net/ipsock_plan9.go +++ b/libgo/go/net/ipsock_plan9.go @@ -7,12 +7,13 @@ package net import ( "context" "internal/bytealg" + "internal/itoa" "io/fs" "os" "syscall" ) -// Probe probes IPv4, IPv6 and IPv4-mapped IPv6 communication +// probe probes IPv4, IPv6 and IPv4-mapped IPv6 communication // capabilities. // // Plan 9 uses IPv6 natively, see ip(3). @@ -336,9 +337,9 @@ func plan9LocalAddr(addr Addr) string { if port == 0 { return "" } - return itoa(port) + return itoa.Itoa(port) } - return ip.String() + "!" + itoa(port) + return ip.String() + "!" + itoa.Itoa(port) } func hangupCtlWrite(ctx context.Context, proto string, ctl *os.File, msg string) error { diff --git a/libgo/go/net/ipsock_posix.go b/libgo/go/net/ipsock_posix.go index 84e72d5..cdd191a 100644 --- a/libgo/go/net/ipsock_posix.go +++ b/libgo/go/net/ipsock_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris windows package net @@ -13,13 +14,13 @@ import ( "syscall" ) -// Probe probes IPv4, IPv6 and IPv4-mapped IPv6 communication +// probe probes IPv4, IPv6 and IPv4-mapped IPv6 communication // capabilities which are controlled by the IPV6_V6ONLY socket option // and kernel configuration. // // Should we try to use the IPv4 socket interface if we're only // dealing with IPv4 sockets? As long as the host system understands -// IPv4-mapped IPv6, it's okay to pass IPv4-mapeed IPv6 addresses to +// IPv4-mapped IPv6, it's okay to pass IPv4-mapped IPv6 addresses to // the IPv6 interface. That simplifies our code and is most // general. Unfortunately, we need to run on kernels built without // IPv6 support too. So probe the kernel to figure it out. diff --git a/libgo/go/net/listen_test.go b/libgo/go/net/listen_test.go index d8c7209..b1dce29 100644 --- a/libgo/go/net/listen_test.go +++ b/libgo/go/net/listen_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js && !plan9 // +build !js,!plan9 package net diff --git a/libgo/go/net/lookup.go b/libgo/go/net/lookup.go index 0660268..d350ef7 100644 --- a/libgo/go/net/lookup.go +++ b/libgo/go/net/lookup.go @@ -166,6 +166,9 @@ func (r *Resolver) getLookupGroup() *singleflight.Group { // LookupHost looks up the given host using the local resolver. // It returns a slice of that host's addresses. +// +// LookupHost uses context.Background internally; to specify the context, use +// Resolver.LookupHost. func LookupHost(host string) (addrs []string, err error) { return DefaultResolver.LookupHost(context.Background(), host) } @@ -353,6 +356,9 @@ func ipAddrsEface(addrs []IPAddr) []interface{} { } // LookupPort looks up the port for the given network and service. +// +// LookupPort uses context.Background internally; to specify the context, use +// Resolver.LookupPort. func LookupPort(network, service string) (port int, err error) { return DefaultResolver.LookupPort(context.Background(), network, service) } @@ -392,6 +398,9 @@ func (r *Resolver) LookupPort(ctx context.Context, network, service string) (por // // The returned canonical name is validated to be a properly // formatted presentation-format domain name. +// +// LookupCNAME uses context.Background internally; to specify the context, use +// Resolver.LookupCNAME. func LookupCNAME(host string) (cname string, err error) { return DefaultResolver.LookupCNAME(context.Background(), host) } @@ -415,7 +424,7 @@ func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, error) return "", err } if !isDomainName(cname) { - return "", &DNSError{Err: "CNAME target is invalid", Name: host} + return "", &DNSError{Err: errMalformedDNSRecordsDetail, Name: host} } return cname, nil } @@ -431,7 +440,9 @@ func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, error) // and proto are empty strings, LookupSRV looks up name directly. // // The returned service names are validated to be properly -// formatted presentation-format domain names. +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the the remaining results, if any. func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) { return DefaultResolver.LookupSRV(context.Background(), service, proto, name) } @@ -447,7 +458,9 @@ func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err // and proto are empty strings, LookupSRV looks up name directly. // // The returned service names are validated to be properly -// formatted presentation-format domain names. +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the the remaining results, if any. func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error) { cname, addrs, err := r.lookupSRV(ctx, service, proto, name) if err != nil { @@ -456,21 +469,31 @@ func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) ( if cname != "" && !isDomainName(cname) { return "", nil, &DNSError{Err: "SRV header name is invalid", Name: name} } + filteredAddrs := make([]*SRV, 0, len(addrs)) for _, addr := range addrs { if addr == nil { continue } if !isDomainName(addr.Target) { - return "", nil, &DNSError{Err: "SRV target is invalid", Name: name} + continue } + filteredAddrs = append(filteredAddrs, addr) } - return cname, addrs, nil + if len(addrs) != len(filteredAddrs) { + return cname, filteredAddrs, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} + } + return cname, filteredAddrs, nil } // LookupMX returns the DNS MX records for the given domain name sorted by preference. // // The returned mail server names are validated to be properly -// formatted presentation-format domain names. +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the the remaining results, if any. +// +// LookupMX uses context.Background internally; to specify the context, use +// Resolver.LookupMX. func LookupMX(name string) ([]*MX, error) { return DefaultResolver.LookupMX(context.Background(), name) } @@ -478,27 +501,41 @@ func LookupMX(name string) ([]*MX, error) { // LookupMX returns the DNS MX records for the given domain name sorted by preference. // // The returned mail server names are validated to be properly -// formatted presentation-format domain names. +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the the remaining results, if any. func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error) { records, err := r.lookupMX(ctx, name) if err != nil { return nil, err } + filteredMX := make([]*MX, 0, len(records)) for _, mx := range records { if mx == nil { continue } - if !isDomainName(mx.Host) { - return nil, &DNSError{Err: "MX target is invalid", Name: name} + // Bypass the hostname validity check for targets which contain only a dot, + // as this is used to represent a 'Null' MX record. + if mx.Host != "." && !isDomainName(mx.Host) { + continue } + filteredMX = append(filteredMX, mx) + } + if len(records) != len(filteredMX) { + return filteredMX, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} } - return records, nil + return filteredMX, nil } // LookupNS returns the DNS NS records for the given domain name. // // The returned name server names are validated to be properly -// formatted presentation-format domain names. +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the the remaining results, if any. +// +// LookupNS uses context.Background internally; to specify the context, use +// Resolver.LookupNS. func LookupNS(name string) ([]*NS, error) { return DefaultResolver.LookupNS(context.Background(), name) } @@ -506,24 +543,34 @@ func LookupNS(name string) ([]*NS, error) { // LookupNS returns the DNS NS records for the given domain name. // // The returned name server names are validated to be properly -// formatted presentation-format domain names. +// formatted presentation-format domain names. If the response contains +// invalid names, those records are filtered out and an error +// will be returned alongside the the remaining results, if any. func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error) { records, err := r.lookupNS(ctx, name) if err != nil { return nil, err } + filteredNS := make([]*NS, 0, len(records)) for _, ns := range records { if ns == nil { continue } if !isDomainName(ns.Host) { - return nil, &DNSError{Err: "NS target is invalid", Name: name} + continue } + filteredNS = append(filteredNS, ns) } - return records, nil + if len(records) != len(filteredNS) { + return filteredNS, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} + } + return filteredNS, nil } // LookupTXT returns the DNS TXT records for the given domain name. +// +// LookupTXT uses context.Background internally; to specify the context, use +// Resolver.LookupTXT. func LookupTXT(name string) ([]string, error) { return DefaultResolver.lookupTXT(context.Background(), name) } @@ -537,10 +584,14 @@ func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error) // of names mapping to that address. // // The returned names are validated to be properly formatted presentation-format -// domain names. +// domain names. If the response contains invalid names, those records are filtered +// out and an error will be returned alongside the the remaining results, if any. // // When using the host C library resolver, at most one result will be // returned. To bypass the host resolver, use a custom Resolver. +// +// LookupAddr uses context.Background internally; to specify the context, use +// Resolver.LookupAddr. func LookupAddr(addr string) (names []string, err error) { return DefaultResolver.LookupAddr(context.Background(), addr) } @@ -549,16 +600,26 @@ func LookupAddr(addr string) (names []string, err error) { // of names mapping to that address. // // The returned names are validated to be properly formatted presentation-format -// domain names. +// domain names. If the response contains invalid names, those records are filtered +// out and an error will be returned alongside the the remaining results, if any. func (r *Resolver) LookupAddr(ctx context.Context, addr string) ([]string, error) { names, err := r.lookupAddr(ctx, addr) if err != nil { return nil, err } + filteredNames := make([]string, 0, len(names)) for _, name := range names { - if !isDomainName(name) { - return nil, &DNSError{Err: "PTR target is invalid", Name: addr} + if isDomainName(name) { + filteredNames = append(filteredNames, name) } } - return names, nil + if len(names) != len(filteredNames) { + return filteredNames, &DNSError{Err: errMalformedDNSRecordsDetail, Name: addr} + } + return filteredNames, nil } + +// errMalformedDNSRecordsDetail is the DNSError detail which is returned when a Resolver.Lookup... +// method recieves DNS records which contain invalid DNS names. This may be returned alongside +// results which have had the malformed records filtered out. +var errMalformedDNSRecordsDetail = "DNS response contained records which contain invalid names" diff --git a/libgo/go/net/lookup_fake.go b/libgo/go/net/lookup_fake.go index 3b3c39b..f4fcaed 100644 --- a/libgo/go/net/lookup_fake.go +++ b/libgo/go/net/lookup_fake.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build js && wasm // +build js,wasm package net diff --git a/libgo/go/net/lookup_plan9.go b/libgo/go/net/lookup_plan9.go index 6a2d48e..75c18b3 100644 --- a/libgo/go/net/lookup_plan9.go +++ b/libgo/go/net/lookup_plan9.go @@ -8,6 +8,7 @@ import ( "context" "errors" "internal/bytealg" + "internal/itoa" "io" "os" ) @@ -84,7 +85,7 @@ func queryCS1(ctx context.Context, net string, ip IP, port int) (clone, dest str if len(ip) != 0 && !ip.IsUnspecified() { ips = ip.String() } - lines, err := queryCS(ctx, net, ips, itoa(port)) + lines, err := queryCS(ctx, net, ips, itoa.Itoa(port)) if err != nil { return } @@ -308,7 +309,7 @@ func (*Resolver) lookupTXT(ctx context.Context, name string) (txt []string, err } for _, line := range lines { if i := bytealg.IndexByteString(line, '\t'); i >= 0 { - txt = append(txt, absDomainName([]byte(line[i+1:]))) + txt = append(txt, line[i+1:]) } } return diff --git a/libgo/go/net/lookup_test.go b/libgo/go/net/lookup_test.go index 32a0d37..3faaf00 100644 --- a/libgo/go/net/lookup_test.go +++ b/libgo/go/net/lookup_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/lookup_unix.go b/libgo/go/net/lookup_unix.go index c27b1a0..05f49b0 100644 --- a/libgo/go/net/lookup_unix.go +++ b/libgo/go/net/lookup_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net @@ -89,7 +90,7 @@ func (r *Resolver) lookupHost(ctx context.Context, host string) (addrs []string, func (r *Resolver) lookupIP(ctx context.Context, network, host string) (addrs []IPAddr, err error) { if r.preferGo() { - return r.goLookupIP(ctx, host) + return r.goLookupIP(ctx, network, host) } order := systemConf().hostLookupOrder(r, host) if order == hostLookupCgo { @@ -99,7 +100,7 @@ func (r *Resolver) lookupIP(ctx context.Context, network, host string) (addrs [] // cgo not available (or netgo); fall back to Go's DNS resolver order = hostLookupFilesDNS } - ips, _, err := r.goLookupIPCNAMEOrder(ctx, host, order) + ips, _, err := r.goLookupIPCNAMEOrder(ctx, network, host, order) return ips, err } diff --git a/libgo/go/net/lookup_windows_test.go b/libgo/go/net/lookup_windows_test.go index 62b61ed..aa95501 100644 --- a/libgo/go/net/lookup_windows_test.go +++ b/libgo/go/net/lookup_windows_test.go @@ -299,7 +299,7 @@ func lookupPTR(name string) (ptr []string, err error) { ptr = make([]string, 0, 10) rx := regexp.MustCompile(`(?m)^Pinging\s+([a-zA-Z0-9.\-]+)\s+\[.*$`) for _, ans := range rx.FindAllStringSubmatch(r, -1) { - ptr = append(ptr, ans[1]+".") + ptr = append(ptr, absDomainName([]byte(ans[1]))) } return } diff --git a/libgo/go/net/main_cloexec_test.go b/libgo/go/net/main_cloexec_test.go index d436df1..03f7d63 100644 --- a/libgo/go/net/main_cloexec_test.go +++ b/libgo/go/net/main_cloexec_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build dragonfly || freebsd || hurd || illumos || linux || netbsd || openbsd // +build dragonfly freebsd hurd illumos linux netbsd openbsd package net diff --git a/libgo/go/net/main_conf_test.go b/libgo/go/net/main_conf_test.go index a92dff5..645b267 100644 --- a/libgo/go/net/main_conf_test.go +++ b/libgo/go/net/main_conf_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js && !plan9 && !windows // +build !js,!plan9,!windows package net diff --git a/libgo/go/net/main_noconf_test.go b/libgo/go/net/main_noconf_test.go index bac84aa..bcea630 100644 --- a/libgo/go/net/main_noconf_test.go +++ b/libgo/go/net/main_noconf_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build (js && wasm) || plan9 || windows // +build js,wasm plan9 windows package net diff --git a/libgo/go/net/main_posix_test.go b/libgo/go/net/main_posix_test.go index f2484f3..c9ab25a 100644 --- a/libgo/go/net/main_posix_test.go +++ b/libgo/go/net/main_posix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js && !plan9 // +build !js,!plan9 package net diff --git a/libgo/go/net/main_test.go b/libgo/go/net/main_test.go index 2d5be2e..dc17d3f 100644 --- a/libgo/go/net/main_test.go +++ b/libgo/go/net/main_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/main_unix_test.go b/libgo/go/net/main_unix_test.go index ef7e915..367cefc 100644 --- a/libgo/go/net/main_unix_test.go +++ b/libgo/go/net/main_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/mockserver_test.go b/libgo/go/net/mockserver_test.go index 867e31e..b50a1e5 100644 --- a/libgo/go/net/mockserver_test.go +++ b/libgo/go/net/mockserver_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/net.go b/libgo/go/net/net.go index 4b4ed12..a7c65ff 100644 --- a/libgo/go/net/net.go +++ b/libgo/go/net/net.go @@ -539,6 +539,9 @@ type ParseError struct { func (e *ParseError) Error() string { return "invalid " + e.Type + ": " + e.Text } +func (e *ParseError) Timeout() bool { return false } +func (e *ParseError) Temporary() bool { return false } + type AddrError struct { Err string Addr string @@ -642,7 +645,7 @@ var errClosed = poll.ErrNetClosing // another goroutine before the I/O is completed. This may be wrapped // in another error, and should normally be tested using // errors.Is(err, net.ErrClosed). -var ErrClosed = errClosed +var ErrClosed error = errClosed type writerOnly struct { io.Writer @@ -733,6 +736,7 @@ func (v *Buffers) consume(n int64) { return } n -= ln0 + (*v)[0] = nil *v = (*v)[1:] } } diff --git a/libgo/go/net/net_fake.go b/libgo/go/net/net_fake.go index 0c48dd5..74fc1da 100644 --- a/libgo/go/net/net_fake.go +++ b/libgo/go/net/net_fake.go @@ -4,6 +4,7 @@ // Fake networking for js/wasm. It is intended to allow tests of other package to pass. +//go:build js && wasm // +build js,wasm package net @@ -267,7 +268,7 @@ func (fd *netFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) { return 0, nil, syscall.ENOSYS } -func (fd *netFD) readMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.Sockaddr, err error) { +func (fd *netFD) readMsg(p []byte, oob []byte, flags int) (n, oobn, retflags int, sa syscall.Sockaddr, err error) { return 0, 0, 0, nil, syscall.ENOSYS } diff --git a/libgo/go/net/net_test.go b/libgo/go/net/net_test.go index 409e140..6e7be4d 100644 --- a/libgo/go/net/net_test.go +++ b/libgo/go/net/net_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net @@ -587,3 +588,23 @@ func TestNotTemporaryRead(t *testing.T) { } withTCPConnPair(t, client, server) } + +// The various errors should implement the Error interface. +func TestErrors(t *testing.T) { + var ( + _ Error = &OpError{} + _ Error = &ParseError{} + _ Error = &AddrError{} + _ Error = UnknownNetworkError("") + _ Error = InvalidAddrError("") + _ Error = &timeoutError{} + _ Error = &DNSConfigError{} + _ Error = &DNSError{} + ) + + // ErrClosed was introduced as type error, so we can't check + // it using a declaration. + if _, ok := ErrClosed.(Error); !ok { + t.Fatal("ErrClosed does not implement Error") + } +} diff --git a/libgo/go/net/nss.go b/libgo/go/net/nss.go index 98d740f..c12ee75 100644 --- a/libgo/go/net/nss.go +++ b/libgo/go/net/nss.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/nss_test.go b/libgo/go/net/nss_test.go index 53cbc4d..948b8d3 100644 --- a/libgo/go/net/nss_test.go +++ b/libgo/go/net/nss_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/packetconn_test.go b/libgo/go/net/packetconn_test.go index a377d33..aeb9845 100644 --- a/libgo/go/net/packetconn_test.go +++ b/libgo/go/net/packetconn_test.go @@ -5,6 +5,7 @@ // This file implements API tests across platforms and will never have a build // tag. +//go:build !js // +build !js package net diff --git a/libgo/go/net/parse.go b/libgo/go/net/parse.go index cdb35bb..6c230ab 100644 --- a/libgo/go/net/parse.go +++ b/libgo/go/net/parse.go @@ -172,32 +172,6 @@ func xtoi2(s string, e byte) (byte, bool) { return byte(n), ok && ei == 2 } -// Convert integer to decimal string. -func itoa(val int) string { - if val < 0 { - return "-" + uitoa(uint(-val)) - } - return uitoa(uint(val)) -} - -// Convert unsigned integer to decimal string. -func uitoa(val uint) string { - if val == 0 { // avoid string allocation - return "0" - } - var buf [20]byte // big enough for 64bit value base 10 - i := len(buf) - 1 - for val >= 10 { - q := val / 10 - buf[i] = byte('0' + val - q*10) - i-- - val = q - } - // val < 10 - buf[i] = byte('0' + val) - return string(buf[i:]) -} - // Convert i to a hexadecimal string. Leading zeros are not printed. func appendHex(dst []byte, i uint32) []byte { if i == 0 { diff --git a/libgo/go/net/port_unix.go b/libgo/go/net/port_unix.go index a3b402f..07b4cbb 100644 --- a/libgo/go/net/port_unix.go +++ b/libgo/go/net/port_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris // Read system port mappings from /etc/services diff --git a/libgo/go/net/protoconn_test.go b/libgo/go/net/protoconn_test.go index 6f83f52..fc9b386 100644 --- a/libgo/go/net/protoconn_test.go +++ b/libgo/go/net/protoconn_test.go @@ -5,6 +5,7 @@ // This file implements API tests across platforms and will never have a build // tag. +//go:build !js // +build !js package net diff --git a/libgo/go/net/rawconn_stub_test.go b/libgo/go/net/rawconn_stub_test.go index cec977f..975aa8d 100644 --- a/libgo/go/net/rawconn_stub_test.go +++ b/libgo/go/net/rawconn_stub_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build (js && wasm) || plan9 // +build js,wasm plan9 package net diff --git a/libgo/go/net/rawconn_test.go b/libgo/go/net/rawconn_test.go index a08ff89..3ef7af3 100644 --- a/libgo/go/net/rawconn_test.go +++ b/libgo/go/net/rawconn_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/rawconn_unix_test.go b/libgo/go/net/rawconn_unix_test.go index 21527c4..77df4f8 100644 --- a/libgo/go/net/rawconn_unix_test.go +++ b/libgo/go/net/rawconn_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/rpc/server.go b/libgo/go/net/rpc/server.go index 9cb9282..074c5b9 100644 --- a/libgo/go/net/rpc/server.go +++ b/libgo/go/net/rpc/server.go @@ -283,7 +283,7 @@ func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType { mtype := method.Type mname := method.Name // Method must be exported. - if method.PkgPath != "" { + if !method.IsExported() { continue } // Method needs three ins: receiver, *args, *reply. diff --git a/libgo/go/net/sendfile_stub.go b/libgo/go/net/sendfile_stub.go index 53bc53a..5753bc0 100644 --- a/libgo/go/net/sendfile_stub.go +++ b/libgo/go/net/sendfile_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || (js && wasm) || netbsd || openbsd // +build aix darwin js,wasm netbsd openbsd package net diff --git a/libgo/go/net/sendfile_test.go b/libgo/go/net/sendfile_test.go index d6057fd..54e51fa 100644 --- a/libgo/go/net/sendfile_test.go +++ b/libgo/go/net/sendfile_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net @@ -13,7 +14,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "runtime" "sync" @@ -366,7 +366,7 @@ func TestSendfileOnWriteTimeoutExceeded(t *testing.T) { } defer conn.Close() - n, err := io.Copy(ioutil.Discard, conn) + n, err := io.Copy(io.Discard, conn) if err != nil { t.Fatalf("expected nil error, but got %v", err) } diff --git a/libgo/go/net/sendfile_unix_alt.go b/libgo/go/net/sendfile_unix_alt.go index 8cededc..54667d6 100644 --- a/libgo/go/net/sendfile_unix_alt.go +++ b/libgo/go/net/sendfile_unix_alt.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build dragonfly || freebsd || solaris // +build dragonfly freebsd solaris package net diff --git a/libgo/go/net/server_test.go b/libgo/go/net/server_test.go index 4ac5443..7cbf152 100644 --- a/libgo/go/net/server_test.go +++ b/libgo/go/net/server_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net @@ -104,13 +105,6 @@ func TestTCPServer(t *testing.T) { if perr := parseDialError(err); perr != nil { t.Error(perr) } - if tt.taddr == "::1" && os.Getenv("GO_BUILDER_NAME") == "darwin-amd64-10_12" && os.IsTimeout(err) { - // A suspected kernel bug in macOS 10.12 occasionally results in - // "i/o timeout" errors when dialing address ::1. The errors have not - // been observed on newer versions of the OS, so we don't plan to work - // around them. See https://golang.org/issue/32919. - t.Skipf("skipping due to error on known-flaky macOS 10.12 builder: %v", err) - } t.Fatal(err) } defer c.Close() diff --git a/libgo/go/net/sock_bsd.go b/libgo/go/net/sock_bsd.go index 73fb6be..4c883ad 100644 --- a/libgo/go/net/sock_bsd.go +++ b/libgo/go/net/sock_bsd.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || netbsd || openbsd // +build darwin dragonfly freebsd netbsd openbsd package net diff --git a/libgo/go/net/sock_cloexec.go b/libgo/go/net/sock_cloexec.go index 43be6ff..cb57bb4 100644 --- a/libgo/go/net/sock_cloexec.go +++ b/libgo/go/net/sock_cloexec.go @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file implements sysSocket and accept for platforms that -// provide a fast path for setting SetNonblock and CloseOnExec. +// This file implements sysSocket for platforms that provide a fast path for +// setting SetNonblock and CloseOnExec. +//go:build dragonfly || freebsd || hurd || illumos || linux || netbsd || openbsd // +build dragonfly freebsd hurd illumos linux netbsd openbsd package net diff --git a/libgo/go/net/sock_posix.go b/libgo/go/net/sock_posix.go index 8fe9bc7..8c09b0b 100644 --- a/libgo/go/net/sock_posix.go +++ b/libgo/go/net/sock_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris windows package net diff --git a/libgo/go/net/sock_stub.go b/libgo/go/net/sock_stub.go index 6d44f43..1e5032e 100644 --- a/libgo/go/net/sock_stub.go +++ b/libgo/go/net/sock_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || hurd || (js && wasm) || solaris // +build aix hurd js,wasm solaris package net diff --git a/libgo/go/net/sockaddr_posix.go b/libgo/go/net/sockaddr_posix.go index 470d044..618d85f 100644 --- a/libgo/go/net/sockaddr_posix.go +++ b/libgo/go/net/sockaddr_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris windows package net diff --git a/libgo/go/net/sockopt_bsd.go b/libgo/go/net/sockopt_bsd.go index 7b8b8d9..e52fa88 100644 --- a/libgo/go/net/sockopt_bsd.go +++ b/libgo/go/net/sockopt_bsd.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || netbsd || openbsd // +build darwin dragonfly freebsd netbsd openbsd package net @@ -25,7 +26,7 @@ func setDefaultSockopts(s, family, sotype int, ipv6only bool) error { syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_PORTRANGE, syscall.IPV6_PORTRANGE_HIGH) } } - if supportsIPv4map() && family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW { + if family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW && supportsIPv4map() { // Allow both IP versions even if the OS default // is otherwise. Note that some operating systems // never admit this option. diff --git a/libgo/go/net/sockopt_posix.go b/libgo/go/net/sockopt_posix.go index d2cb30b..3478872 100644 --- a/libgo/go/net/sockopt_posix.go +++ b/libgo/go/net/sockopt_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris windows package net diff --git a/libgo/go/net/sockopt_stub.go b/libgo/go/net/sockopt_stub.go index 52624a3..99b5277 100644 --- a/libgo/go/net/sockopt_stub.go +++ b/libgo/go/net/sockopt_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build js && wasm // +build js,wasm package net diff --git a/libgo/go/net/sockoptip_bsdvar.go b/libgo/go/net/sockoptip_bsdvar.go index c93f592..8b0b5d2 100644 --- a/libgo/go/net/sockoptip_bsdvar.go +++ b/libgo/go/net/sockoptip_bsdvar.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd hurd netbsd openbsd solaris package net diff --git a/libgo/go/net/sockoptip_posix.go b/libgo/go/net/sockoptip_posix.go index 0ac50e3..a063e79 100644 --- a/libgo/go/net/sockoptip_posix.go +++ b/libgo/go/net/sockoptip_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris windows package net diff --git a/libgo/go/net/sockoptip_stub.go b/libgo/go/net/sockoptip_stub.go index 57cd289..4175922 100644 --- a/libgo/go/net/sockoptip_stub.go +++ b/libgo/go/net/sockoptip_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build js && wasm // +build js,wasm package net diff --git a/libgo/go/net/splice_stub.go b/libgo/go/net/splice_stub.go index 9106cb2..ce2e904 100644 --- a/libgo/go/net/splice_stub.go +++ b/libgo/go/net/splice_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !linux // +build !linux package net diff --git a/libgo/go/net/splice_test.go b/libgo/go/net/splice_test.go index cd4e01f..d5f6367 100644 --- a/libgo/go/net/splice_test.go +++ b/libgo/go/net/splice_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build linux // +build linux package net diff --git a/libgo/go/net/sys_cloexec.go b/libgo/go/net/sys_cloexec.go index 967b8be..a32483e 100644 --- a/libgo/go/net/sys_cloexec.go +++ b/libgo/go/net/sys_cloexec.go @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file implements sysSocket and accept for platforms that do not -// provide a fast path for setting SetNonblock and CloseOnExec. +// This file implements sysSocket for platforms that do not provide a fast path +// for setting SetNonblock and CloseOnExec. +//go:build aix || darwin || (solaris && !illumos) // +build aix darwin solaris,!illumos package net diff --git a/libgo/go/net/tcpsock.go b/libgo/go/net/tcpsock.go index 9a9b03a..19a90143 100644 --- a/libgo/go/net/tcpsock.go +++ b/libgo/go/net/tcpsock.go @@ -6,6 +6,7 @@ package net import ( "context" + "internal/itoa" "io" "os" "syscall" @@ -31,9 +32,9 @@ func (a *TCPAddr) String() string { } ip := ipEmptyString(a.IP) if a.Zone != "" { - return JoinHostPort(ip+"%"+a.Zone, itoa(a.Port)) + return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port)) } - return JoinHostPort(ip, itoa(a.Port)) + return JoinHostPort(ip, itoa.Itoa(a.Port)) } func (a *TCPAddr) isWildcard() bool { diff --git a/libgo/go/net/tcpsock_posix.go b/libgo/go/net/tcpsock_posix.go index 2c359da..9fd7822 100644 --- a/libgo/go/net/tcpsock_posix.go +++ b/libgo/go/net/tcpsock_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris windows package net diff --git a/libgo/go/net/tcpsock_test.go b/libgo/go/net/tcpsock_test.go index c220c0b..884c5cb 100644 --- a/libgo/go/net/tcpsock_test.go +++ b/libgo/go/net/tcpsock_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/tcpsock_unix_test.go b/libgo/go/net/tcpsock_unix_test.go index 2bd591b..41bd229 100644 --- a/libgo/go/net/tcpsock_unix_test.go +++ b/libgo/go/net/tcpsock_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js && !plan9 && !windows // +build !js,!plan9,!windows package net diff --git a/libgo/go/net/tcpsockopt_plan9.go b/libgo/go/net/tcpsockopt_plan9.go index fb56871..264359d 100644 --- a/libgo/go/net/tcpsockopt_plan9.go +++ b/libgo/go/net/tcpsockopt_plan9.go @@ -7,6 +7,7 @@ package net import ( + "internal/itoa" "syscall" "time" ) @@ -17,7 +18,7 @@ func setNoDelay(fd *netFD, noDelay bool) error { // Set keep alive period. func setKeepAlivePeriod(fd *netFD, d time.Duration) error { - cmd := "keepalive " + itoa(int(d/time.Millisecond)) + cmd := "keepalive " + itoa.Itoa(int(d/time.Millisecond)) _, e := fd.ctl.WriteAt([]byte(cmd), 0) return e } diff --git a/libgo/go/net/tcpsockopt_posix.go b/libgo/go/net/tcpsockopt_posix.go index 01bc421..4c99ab8 100644 --- a/libgo/go/net/tcpsockopt_posix.go +++ b/libgo/go/net/tcpsockopt_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris windows package net diff --git a/libgo/go/net/tcpsockopt_stub.go b/libgo/go/net/tcpsockopt_stub.go index d043da1..028d5fd 100644 --- a/libgo/go/net/tcpsockopt_stub.go +++ b/libgo/go/net/tcpsockopt_stub.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build js && wasm // +build js,wasm package net diff --git a/libgo/go/net/tcpsockopt_unix.go b/libgo/go/net/tcpsockopt_unix.go index e05cb73..cc0662a 100644 --- a/libgo/go/net/tcpsockopt_unix.go +++ b/libgo/go/net/tcpsockopt_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || freebsd || hurd || linux || netbsd // +build aix freebsd hurd linux netbsd package net diff --git a/libgo/go/net/testdata/ipv4-hosts b/libgo/go/net/testdata/ipv4-hosts index 5208bb4..6b99675 100644 --- a/libgo/go/net/testdata/ipv4-hosts +++ b/libgo/go/net/testdata/ipv4-hosts @@ -1,12 +1,8 @@ # See https://tools.ietf.org/html/rfc1123. -# -# The literal IPv4 address parser in the net package is a relaxed -# one. It may accept a literal IPv4 address in dotted-decimal notation -# with leading zeros such as "001.2.003.4". # internet address and host name 127.0.0.1 localhost # inline comment separated by tab -127.000.000.002 localhost # inline comment separated by space +127.0.0.2 localhost # inline comment separated by space # internet address, host name and aliases -127.000.000.003 localhost localhost.localdomain +127.0.0.3 localhost localhost.localdomain diff --git a/libgo/go/net/timeout_test.go b/libgo/go/net/timeout_test.go index 205aaa4..e1cf146 100644 --- a/libgo/go/net/timeout_test.go +++ b/libgo/go/net/timeout_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/udpsock.go b/libgo/go/net/udpsock.go index 571e099..70f2ce2 100644 --- a/libgo/go/net/udpsock.go +++ b/libgo/go/net/udpsock.go @@ -6,6 +6,7 @@ package net import ( "context" + "internal/itoa" "syscall" ) @@ -34,9 +35,9 @@ func (a *UDPAddr) String() string { } ip := ipEmptyString(a.IP) if a.Zone != "" { - return JoinHostPort(ip+"%"+a.Zone, itoa(a.Port)) + return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port)) } - return JoinHostPort(ip, itoa(a.Port)) + return JoinHostPort(ip, itoa.Itoa(a.Port)) } func (a *UDPAddr) isWildcard() bool { @@ -99,11 +100,20 @@ func (c *UDPConn) SyscallConn() (syscall.RawConn, error) { } // ReadFromUDP acts like ReadFrom but returns a UDPAddr. -func (c *UDPConn) ReadFromUDP(b []byte) (int, *UDPAddr, error) { +func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) { + // This function is designed to allow the caller to control the lifetime + // of the returned *UDPAddr and thereby prevent an allocation. + // See https://blog.filippo.io/efficient-go-apis-with-the-inliner/. + // The real work is done by readFromUDP, below. + return c.readFromUDP(b, &UDPAddr{}) +} + +// readFromUDP implements ReadFromUDP. +func (c *UDPConn) readFromUDP(b []byte, addr *UDPAddr) (int, *UDPAddr, error) { if !c.ok() { return 0, nil, syscall.EINVAL } - n, addr, err := c.readFrom(b) + n, addr, err := c.readFrom(b, addr) if err != nil { err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} } @@ -112,14 +122,9 @@ func (c *UDPConn) ReadFromUDP(b []byte) (int, *UDPAddr, error) { // ReadFrom implements the PacketConn ReadFrom method. func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error) { - if !c.ok() { - return 0, nil, syscall.EINVAL - } - n, addr, err := c.readFrom(b) - if err != nil { - err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} - } + n, addr, err := c.readFromUDP(b, &UDPAddr{}) if addr == nil { + // Return Addr(nil), not Addr(*UDPConn(nil)). return n, nil, err } return n, addr, err diff --git a/libgo/go/net/udpsock_plan9.go b/libgo/go/net/udpsock_plan9.go index 79986ce..1df293d 100644 --- a/libgo/go/net/udpsock_plan9.go +++ b/libgo/go/net/udpsock_plan9.go @@ -11,7 +11,7 @@ import ( "syscall" ) -func (c *UDPConn) readFrom(b []byte) (n int, addr *UDPAddr, err error) { +func (c *UDPConn) readFrom(b []byte, addr *UDPAddr) (int, *UDPAddr, error) { buf := make([]byte, udpHeaderSize+len(b)) m, err := c.fd.Read(buf) if err != nil { @@ -23,8 +23,9 @@ func (c *UDPConn) readFrom(b []byte) (n int, addr *UDPAddr, err error) { buf = buf[:m] h, buf := unmarshalUDPHeader(buf) - n = copy(b, buf) - return n, &UDPAddr{IP: h.raddr, Port: int(h.rport)}, nil + n := copy(b, buf) + *addr = UDPAddr{IP: h.raddr, Port: int(h.rport)} + return n, addr, nil } func (c *UDPConn) readMsg(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error) { diff --git a/libgo/go/net/udpsock_posix.go b/libgo/go/net/udpsock_posix.go index 858a3ef..a4c6da2 100644 --- a/libgo/go/net/udpsock_posix.go +++ b/libgo/go/net/udpsock_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris windows package net @@ -42,21 +43,23 @@ func (a *UDPAddr) toLocal(net string) sockaddr { return &UDPAddr{loopbackIP(net), a.Port, a.Zone} } -func (c *UDPConn) readFrom(b []byte) (int, *UDPAddr, error) { - var addr *UDPAddr +func (c *UDPConn) readFrom(b []byte, addr *UDPAddr) (int, *UDPAddr, error) { n, sa, err := c.fd.readFrom(b) switch sa := sa.(type) { case *syscall.SockaddrInet4: - addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port} + *addr = UDPAddr{IP: sa.Addr[0:], Port: sa.Port} case *syscall.SockaddrInet6: - addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: zoneCache.name(int(sa.ZoneId))} + *addr = UDPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: zoneCache.name(int(sa.ZoneId))} + default: + // No sockaddr, so don't return UDPAddr. + addr = nil } return n, addr, err } func (c *UDPConn) readMsg(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error) { var sa syscall.Sockaddr - n, oobn, flags, sa, err = c.fd.readMsg(b, oob) + n, oobn, flags, sa, err = c.fd.readMsg(b, oob, 0) switch sa := sa.(type) { case *syscall.SockaddrInet4: addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port} diff --git a/libgo/go/net/udpsock_test.go b/libgo/go/net/udpsock_test.go index 327eba6..0e8c351 100644 --- a/libgo/go/net/udpsock_test.go +++ b/libgo/go/net/udpsock_test.go @@ -2,12 +2,15 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net import ( + "errors" "internal/testenv" + "os" "reflect" "runtime" "testing" @@ -444,3 +447,51 @@ func TestUDPReadSizeError(t *testing.T) { } } } + +// TestUDPReadTimeout verifies that ReadFromUDP with timeout returns an error +// without data or an address. +func TestUDPReadTimeout(t *testing.T) { + la, err := ResolveUDPAddr("udp4", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + c, err := ListenUDP("udp4", la) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + c.SetDeadline(time.Now()) + b := make([]byte, 1) + n, addr, err := c.ReadFromUDP(b) + if !errors.Is(err, os.ErrDeadlineExceeded) { + t.Errorf("ReadFromUDP got err %v want os.ErrDeadlineExceeded", err) + } + if n != 0 { + t.Errorf("ReadFromUDP got n %d want 0", n) + } + if addr != nil { + t.Errorf("ReadFromUDP got addr %+#v want nil", addr) + } +} + +func BenchmarkWriteToReadFromUDP(b *testing.B) { + conn, err := ListenUDP("udp4", &UDPAddr{IP: IPv4(127, 0, 0, 1)}) + if err != nil { + b.Fatal(err) + } + addr := conn.LocalAddr() + buf := make([]byte, 8) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := conn.WriteTo(buf, addr) + if err != nil { + b.Fatal(err) + } + _, _, err = conn.ReadFromUDP(buf) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/libgo/go/net/unixsock_posix.go b/libgo/go/net/unixsock_posix.go index 3721485..af075af 100644 --- a/libgo/go/net/unixsock_posix.go +++ b/libgo/go/net/unixsock_posix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build aix || darwin || dragonfly || freebsd || hurd || (js && wasm) || linux || netbsd || openbsd || solaris || windows // +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris windows package net @@ -112,7 +113,11 @@ func (c *UnixConn) readFrom(b []byte) (int, *UnixAddr, error) { func (c *UnixConn) readMsg(b, oob []byte) (n, oobn, flags int, addr *UnixAddr, err error) { var sa syscall.Sockaddr - n, oobn, flags, sa, err = c.fd.readMsg(b, oob) + n, oobn, flags, sa, err = c.fd.readMsg(b, oob, readMsgFlags) + if readMsgFlags == 0 && err == nil && oobn > 0 { + setReadMsgCloseOnExec(oob[:oobn]) + } + switch sa := sa.(type) { case *syscall.SockaddrUnix: if sa.Name != "" { diff --git a/libgo/go/net/unixsock_readmsg_cloexec.go b/libgo/go/net/unixsock_readmsg_cloexec.go new file mode 100644 index 0000000..716484c --- /dev/null +++ b/libgo/go/net/unixsock_readmsg_cloexec.go @@ -0,0 +1,31 @@ +// Copyright 2021 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. + +//go:build aix || darwin || freebsd || solaris +// +build aix darwin freebsd solaris + +package net + +import "syscall" + +const readMsgFlags = 0 + +func setReadMsgCloseOnExec(oob []byte) { + scms, err := syscall.ParseSocketControlMessage(oob) + if err != nil { + return + } + + for _, scm := range scms { + if scm.Header.Level == syscall.SOL_SOCKET && scm.Header.Type == syscall.SCM_RIGHTS { + fds, err := syscall.ParseUnixRights(&scm) + if err != nil { + continue + } + for _, fd := range fds { + syscall.CloseOnExec(fd) + } + } + } +} diff --git a/libgo/go/net/unixsock_readmsg_cmsg_cloexec.go b/libgo/go/net/unixsock_readmsg_cmsg_cloexec.go new file mode 100644 index 0000000..bb851b8 --- /dev/null +++ b/libgo/go/net/unixsock_readmsg_cmsg_cloexec.go @@ -0,0 +1,14 @@ +// Copyright 2021 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. + +//go:build dragonfly || linux || netbsd || openbsd +// +build dragonfly linux netbsd openbsd + +package net + +import "syscall" + +const readMsgFlags = syscall.MSG_CMSG_CLOEXEC + +func setReadMsgCloseOnExec(oob []byte) {} diff --git a/libgo/go/net/unixsock_readmsg_other.go b/libgo/go/net/unixsock_readmsg_other.go new file mode 100644 index 0000000..3290761 --- /dev/null +++ b/libgo/go/net/unixsock_readmsg_other.go @@ -0,0 +1,12 @@ +// Copyright 2021 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. + +//go:build (js && wasm) || windows +// +build js,wasm windows + +package net + +const readMsgFlags = 0 + +func setReadMsgCloseOnExec(oob []byte) {} diff --git a/libgo/go/net/unixsock_readmsg_test.go b/libgo/go/net/unixsock_readmsg_test.go new file mode 100644 index 0000000..a4d2fca --- /dev/null +++ b/libgo/go/net/unixsock_readmsg_test.go @@ -0,0 +1,105 @@ +// Copyright 2021 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. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package net + +import ( + "os" + "syscall" + "testing" + "time" +) + +func TestUnixConnReadMsgUnixSCMRightsCloseOnExec(t *testing.T) { + if !testableNetwork("unix") { + t.Skip("not unix system") + } + + scmFile, err := os.Open(os.DevNull) + if err != nil { + t.Fatalf("file open: %v", err) + } + defer scmFile.Close() + + rights := syscall.UnixRights(int(scmFile.Fd())) + fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0) + if err != nil { + t.Fatalf("Socketpair: %v", err) + } + + writeFile := os.NewFile(uintptr(fds[0]), "write-socket") + defer writeFile.Close() + readFile := os.NewFile(uintptr(fds[1]), "read-socket") + defer readFile.Close() + + cw, err := FileConn(writeFile) + if err != nil { + t.Fatalf("FileConn: %v", err) + } + defer cw.Close() + cr, err := FileConn(readFile) + if err != nil { + t.Fatalf("FileConn: %v", err) + } + defer cr.Close() + + ucw, ok := cw.(*UnixConn) + if !ok { + t.Fatalf("got %T; want UnixConn", cw) + } + ucr, ok := cr.(*UnixConn) + if !ok { + t.Fatalf("got %T; want UnixConn", cr) + } + + oob := make([]byte, syscall.CmsgSpace(4)) + err = ucw.SetWriteDeadline(time.Now().Add(5 * time.Second)) + if err != nil { + t.Fatalf("Can't set unix connection timeout: %v", err) + } + _, _, err = ucw.WriteMsgUnix(nil, rights, nil) + if err != nil { + t.Fatalf("UnixConn readMsg: %v", err) + } + err = ucr.SetReadDeadline(time.Now().Add(5 * time.Second)) + if err != nil { + t.Fatalf("Can't set unix connection timeout: %v", err) + } + _, oobn, _, _, err := ucr.ReadMsgUnix(nil, oob) + if err != nil { + t.Fatalf("UnixConn readMsg: %v", err) + } + + scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + t.Fatalf("ParseSocketControlMessage: %v", err) + } + if len(scms) != 1 { + t.Fatalf("got scms = %#v; expected 1 SocketControlMessage", scms) + } + scm := scms[0] + gotFDs, err := syscall.ParseUnixRights(&scm) + if err != nil { + t.Fatalf("syscall.ParseUnixRights: %v", err) + } + if len(gotFDs) != 1 { + t.Fatalf("got FDs %#v: wanted only 1 fd", gotFDs) + } + defer func() { + if err := syscall.Close(gotFDs[0]); err != nil { + t.Fatalf("fail to close gotFDs: %v", err) + } + }() + + flags, err := fcntl(gotFDs[0], syscall.F_GETFD, 0) + if err != nil { + t.Fatalf("Can't get flags of fd:%#v, with err:%v", gotFDs[0], err) + } + if flags&syscall.FD_CLOEXEC == 0 { + t.Fatalf("got flags %#x, want %#x (FD_CLOEXEC) set", flags, syscall.FD_CLOEXEC) + } +} diff --git a/libgo/go/net/unixsock_test.go b/libgo/go/net/unixsock_test.go index 0b13bf6..71092e8 100644 --- a/libgo/go/net/unixsock_test.go +++ b/libgo/go/net/unixsock_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js && !plan9 && !windows // +build !js,!plan9,!windows package net diff --git a/libgo/go/net/unixsock_windows_test.go b/libgo/go/net/unixsock_windows_test.go index 5dccc14..29244f6 100644 --- a/libgo/go/net/unixsock_windows_test.go +++ b/libgo/go/net/unixsock_windows_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows package net diff --git a/libgo/go/net/url/url.go b/libgo/go/net/url/url.go index d90f5f0..20de0f6 100644 --- a/libgo/go/net/url/url.go +++ b/libgo/go/net/url/url.go @@ -425,31 +425,31 @@ func (u *Userinfo) String() string { return s } -// Maybe rawurl is of the form scheme:path. +// Maybe rawURL is of the form scheme:path. // (Scheme must be [a-zA-Z][a-zA-Z0-9+-.]*) -// If so, return scheme, path; else return "", rawurl. -func getscheme(rawurl string) (scheme, path string, err error) { - for i := 0; i < len(rawurl); i++ { - c := rawurl[i] +// If so, return scheme, path; else return "", rawURL. +func getScheme(rawURL string) (scheme, path string, err error) { + for i := 0; i < len(rawURL); i++ { + c := rawURL[i] switch { case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z': // do nothing case '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.': if i == 0 { - return "", rawurl, nil + return "", rawURL, nil } case c == ':': if i == 0 { return "", "", errors.New("missing protocol scheme") } - return rawurl[:i], rawurl[i+1:], nil + return rawURL[:i], rawURL[i+1:], nil default: // we have encountered an invalid character, // so there is no valid scheme - return "", rawurl, nil + return "", rawURL, nil } } - return "", rawurl, nil + return "", rawURL, nil } // split slices s into two substrings separated by the first occurrence of @@ -466,15 +466,15 @@ func split(s string, sep byte, cutc bool) (string, string) { return s[:i], s[i:] } -// Parse parses rawurl into a URL structure. +// Parse parses a raw url into a URL structure. // -// The rawurl may be relative (a path, without a host) or absolute +// The url may be relative (a path, without a host) or absolute // (starting with a scheme). Trying to parse a hostname and path // without a scheme is invalid but may not necessarily return an // error, due to parsing ambiguities. -func Parse(rawurl string) (*URL, error) { +func Parse(rawURL string) (*URL, error) { // Cut off #frag - u, frag := split(rawurl, '#', true) + u, frag := split(rawURL, '#', true) url, err := parse(u, false) if err != nil { return nil, &Error{"parse", u, err} @@ -483,20 +483,20 @@ func Parse(rawurl string) (*URL, error) { return url, nil } if err = url.setFragment(frag); err != nil { - return nil, &Error{"parse", rawurl, err} + return nil, &Error{"parse", rawURL, err} } return url, nil } -// ParseRequestURI parses rawurl into a URL structure. It assumes that -// rawurl was received in an HTTP request, so the rawurl is interpreted +// ParseRequestURI parses a raw url into a URL structure. It assumes that +// url was received in an HTTP request, so the url is interpreted // only as an absolute URI or an absolute path. -// The string rawurl is assumed not to have a #fragment suffix. +// The string url is assumed not to have a #fragment suffix. // (Web browsers strip #fragment before sending the URL to a web server.) -func ParseRequestURI(rawurl string) (*URL, error) { - url, err := parse(rawurl, true) +func ParseRequestURI(rawURL string) (*URL, error) { + url, err := parse(rawURL, true) if err != nil { - return nil, &Error{"parse", rawurl, err} + return nil, &Error{"parse", rawURL, err} } return url, nil } @@ -505,27 +505,27 @@ func ParseRequestURI(rawurl string) (*URL, error) { // viaRequest is true, the URL is assumed to have arrived via an HTTP request, // in which case only absolute URLs or path-absolute relative URLs are allowed. // If viaRequest is false, all forms of relative URLs are allowed. -func parse(rawurl string, viaRequest bool) (*URL, error) { +func parse(rawURL string, viaRequest bool) (*URL, error) { var rest string var err error - if stringContainsCTLByte(rawurl) { + if stringContainsCTLByte(rawURL) { return nil, errors.New("net/url: invalid control character in URL") } - if rawurl == "" && viaRequest { + if rawURL == "" && viaRequest { return nil, errors.New("empty url") } url := new(URL) - if rawurl == "*" { + if rawURL == "*" { url.Path = "*" return url, nil } // Split off possible leading "http:", "mailto:", etc. // Cannot contain escaped characters. - if url.Scheme, rest, err = getscheme(rawurl); err != nil { + if url.Scheme, rest, err = getScheme(rawURL); err != nil { return nil, err } url.Scheme = strings.ToLower(url.Scheme) @@ -909,15 +909,22 @@ func (v Values) Del(key string) { delete(v, key) } +// Has checks whether a given key is set. +func (v Values) Has(key string) bool { + _, ok := v[key] + return ok +} + // ParseQuery parses the URL-encoded query string and returns // a map listing the values specified for each key. // ParseQuery always returns a non-nil map containing all the // valid query parameters found; err describes the first decoding error // encountered, if any. // -// Query is expected to be a list of key=value settings separated by -// ampersands or semicolons. A setting without an equals sign is -// interpreted as a key set to an empty value. +// Query is expected to be a list of key=value settings separated by ampersands. +// A setting without an equals sign is interpreted as a key set to an empty +// value. +// Settings containing a non-URL-encoded semicolon are considered invalid. func ParseQuery(query string) (Values, error) { m := make(Values) err := parseQuery(m, query) @@ -927,11 +934,15 @@ func ParseQuery(query string) (Values, error) { func parseQuery(m Values, query string) (err error) { for query != "" { key := query - if i := strings.IndexAny(key, "&;"); i >= 0 { + if i := strings.IndexAny(key, "&"); i >= 0 { key, query = key[:i], key[i+1:] } else { query = "" } + if strings.Contains(key, ";") { + err = fmt.Errorf("invalid semicolon separator in query") + continue + } if key == "" { continue } @@ -1009,6 +1020,8 @@ func resolvePath(base, ref string) string { ) first := true remaining := full + // We want to return a leading '/', so write it now. + dst.WriteByte('/') for i >= 0 { i = strings.IndexByte(remaining, '/') if i < 0 { @@ -1023,10 +1036,12 @@ func resolvePath(base, ref string) string { } if elem == ".." { - str := dst.String() + // Ignore the leading '/' we already wrote. + str := dst.String()[1:] index := strings.LastIndexByte(str, '/') dst.Reset() + dst.WriteByte('/') if index == -1 { first = true } else { @@ -1045,7 +1060,12 @@ func resolvePath(base, ref string) string { dst.WriteByte('/') } - return "/" + strings.TrimPrefix(dst.String(), "/") + // We wrote an initial '/', but we don't want two. + r := dst.String() + if len(r) > 1 && r[1] == '/' { + r = r[1:] + } + return r } // IsAbs reports whether the URL is absolute. @@ -1058,11 +1078,11 @@ func (u *URL) IsAbs() bool { // may be relative or absolute. Parse returns nil, err on parse // failure, otherwise its return value is the same as ResolveReference. func (u *URL) Parse(ref string) (*URL, error) { - refurl, err := Parse(ref) + refURL, err := Parse(ref) if err != nil { return nil, err } - return u.ResolveReference(refurl), nil + return u.ResolveReference(refURL), nil } // ResolveReference resolves a URI reference to an absolute URI from @@ -1151,8 +1171,8 @@ func (u *URL) Port() string { // splitHostPort separates host and port. If the port is not valid, it returns // the entire input as host, and it doesn't check the validity of the host. // Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric. -func splitHostPort(hostport string) (host, port string) { - host = hostport +func splitHostPort(hostPort string) (host, port string) { + host = hostPort colon := strings.LastIndexByte(host, ':') if colon != -1 && validOptionalPort(host[colon:]) { diff --git a/libgo/go/net/url/url_test.go b/libgo/go/net/url/url_test.go index f02e4650..63c8e69 100644 --- a/libgo/go/net/url/url_test.go +++ b/libgo/go/net/url/url_test.go @@ -1295,10 +1295,10 @@ func TestResolveReference(t *testing.T) { } func TestQueryValues(t *testing.T) { - u, _ := Parse("http://x.com?foo=bar&bar=1&bar=2") + u, _ := Parse("http://x.com?foo=bar&bar=1&bar=2&baz") v := u.Query() - if len(v) != 2 { - t.Errorf("got %d keys in Query values, want 2", len(v)) + if len(v) != 3 { + t.Errorf("got %d keys in Query values, want 3", len(v)) } if g, e := v.Get("foo"), "bar"; g != e { t.Errorf("Get(foo) = %q, want %q", g, e) @@ -1313,6 +1313,18 @@ func TestQueryValues(t *testing.T) { if g, e := v.Get("baz"), ""; g != e { t.Errorf("Get(baz) = %q, want %q", g, e) } + if h, e := v.Has("foo"), true; h != e { + t.Errorf("Has(foo) = %t, want %t", h, e) + } + if h, e := v.Has("bar"), true; h != e { + t.Errorf("Has(bar) = %t, want %t", h, e) + } + if h, e := v.Has("baz"), true; h != e { + t.Errorf("Has(baz) = %t, want %t", h, e) + } + if h, e := v.Has("noexist"), false; h != e { + t.Errorf("Has(noexist) = %t, want %t", h, e) + } v.Del("bar") if g, e := v.Get("bar"), ""; g != e { t.Errorf("second Get(bar) = %q, want %q", g, e) @@ -1322,57 +1334,125 @@ func TestQueryValues(t *testing.T) { type parseTest struct { query string out Values + ok bool } var parseTests = []parseTest{ { + query: "a=1", + out: Values{"a": []string{"1"}}, + ok: true, + }, + { query: "a=1&b=2", out: Values{"a": []string{"1"}, "b": []string{"2"}}, + ok: true, }, { query: "a=1&a=2&a=banana", out: Values{"a": []string{"1", "2", "banana"}}, + ok: true, }, { query: "ascii=%3Ckey%3A+0x90%3E", out: Values{"ascii": []string{"<key: 0x90>"}}, + ok: true, + }, { + query: "a=1;b=2", + out: Values{}, + ok: false, + }, { + query: "a;b=1", + out: Values{}, + ok: false, + }, { + query: "a=%3B", // hex encoding for semicolon + out: Values{"a": []string{";"}}, + ok: true, }, { - query: "a=1;b=2", - out: Values{"a": []string{"1"}, "b": []string{"2"}}, + query: "a%3Bb=1", + out: Values{"a;b": []string{"1"}}, + ok: true, }, { query: "a=1&a=2;a=banana", - out: Values{"a": []string{"1", "2", "banana"}}, + out: Values{"a": []string{"1"}}, + ok: false, + }, + { + query: "a;b&c=1", + out: Values{"c": []string{"1"}}, + ok: false, + }, + { + query: "a=1&b=2;a=3&c=4", + out: Values{"a": []string{"1"}, "c": []string{"4"}}, + ok: false, + }, + { + query: "a=1&b=2;c=3", + out: Values{"a": []string{"1"}}, + ok: false, + }, + { + query: ";", + out: Values{}, + ok: false, + }, + { + query: "a=1;", + out: Values{}, + ok: false, + }, + { + query: "a=1&;", + out: Values{"a": []string{"1"}}, + ok: false, + }, + { + query: ";a=1&b=2", + out: Values{"b": []string{"2"}}, + ok: false, + }, + { + query: "a=1&b=2;", + out: Values{"a": []string{"1"}}, + ok: false, }, } func TestParseQuery(t *testing.T) { - for i, test := range parseTests { - form, err := ParseQuery(test.query) - if err != nil { - t.Errorf("test %d: Unexpected error: %v", i, err) - continue - } - if len(form) != len(test.out) { - t.Errorf("test %d: len(form) = %d, want %d", i, len(form), len(test.out)) - } - for k, evs := range test.out { - vs, ok := form[k] - if !ok { - t.Errorf("test %d: Missing key %q", i, k) - continue + for _, test := range parseTests { + t.Run(test.query, func(t *testing.T) { + form, err := ParseQuery(test.query) + if test.ok != (err == nil) { + want := "<error>" + if test.ok { + want = "<nil>" + } + t.Errorf("Unexpected error: %v, want %v", err, want) } - if len(vs) != len(evs) { - t.Errorf("test %d: len(form[%q]) = %d, want %d", i, k, len(vs), len(evs)) - continue + if len(form) != len(test.out) { + t.Errorf("len(form) = %d, want %d", len(form), len(test.out)) } - for j, ev := range evs { - if v := vs[j]; v != ev { - t.Errorf("test %d: form[%q][%d] = %q, want %q", i, k, j, v, ev) + for k, evs := range test.out { + vs, ok := form[k] + if !ok { + t.Errorf("Missing key %q", k) + continue + } + if len(vs) != len(evs) { + t.Errorf("len(form[%q]) = %d, want %d", k, len(vs), len(evs)) + continue + } + for j, ev := range evs { + if v := vs[j]; v != ev { + t.Errorf("form[%q][%d] = %q, want %q", k, j, v, ev) + } } } - } + }) } } diff --git a/libgo/go/net/write_unix_test.go b/libgo/go/net/write_unix_test.go index 6d8cb6a..f79f2d0 100644 --- a/libgo/go/net/write_unix_test.go +++ b/libgo/go/net/write_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/writev_test.go b/libgo/go/net/writev_test.go index d603b7f..bf40ca2 100644 --- a/libgo/go/net/writev_test.go +++ b/libgo/go/net/writev_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !js // +build !js package net diff --git a/libgo/go/net/writev_unix.go b/libgo/go/net/writev_unix.go index 8b20f42..a0fedc2 100644 --- a/libgo/go/net/writev_unix.go +++ b/libgo/go/net/writev_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd // +build darwin dragonfly freebsd illumos linux netbsd openbsd package net |