diff options
Diffstat (limited to 'libgo/go/net')
100 files changed, 2499 insertions, 1015 deletions
diff --git a/libgo/go/net/cgo_android.go b/libgo/go/net/cgo_android.go new file mode 100644 index 00000000000..3819ce56a4f --- /dev/null +++ b/libgo/go/net/cgo_android.go @@ -0,0 +1,14 @@ +// Copyright 2014 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. + +// +build cgo,!netgo + +package net + +//#include <netdb.h> +import "C" + +func cgoAddrInfoFlags() C.int { + return C.AI_CANONNAME +} diff --git a/libgo/go/net/cgo_linux.go b/libgo/go/net/cgo_linux.go index 77522f9141b..0e332261acc 100644 --- a/libgo/go/net/cgo_linux.go +++ b/libgo/go/net/cgo_linux.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build cgo,!netgo +// +build !android,cgo,!netgo package net diff --git a/libgo/go/net/conn_test.go b/libgo/go/net/conn_test.go index 37bb4e2c071..9c9d1a8057d 100644 --- a/libgo/go/net/conn_test.go +++ b/libgo/go/net/conn_test.go @@ -38,7 +38,7 @@ func TestConnAndListener(t *testing.T) { } case "unixpacket": switch runtime.GOOS { - case "darwin", "nacl", "openbsd", "plan9", "windows": + case "android", "darwin", "nacl", "openbsd", "plan9", "windows": continue case "freebsd": // FreeBSD 8 doesn't support unixpacket continue diff --git a/libgo/go/net/dial.go b/libgo/go/net/dial.go index 93569c253cd..e6f0436cdd3 100644 --- a/libgo/go/net/dial.go +++ b/libgo/go/net/dial.go @@ -118,9 +118,8 @@ func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) { // "unixpacket". // // For TCP and UDP networks, addresses have the form host:port. -// If host is a literal IPv6 address or host name, it must be enclosed -// in square brackets as in "[::1]:80", "[ipv6-host]:http" or -// "[ipv6-host%zone]:80". +// If host is a literal IPv6 address it must be enclosed +// in square brackets as in "[::1]:80" or "[ipv6-host%zone]:80". // The functions JoinHostPort and SplitHostPort manipulate addresses // in this form. // @@ -214,14 +213,12 @@ func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Con nracers := len(ras) for nracers > 0 { sig <- true - select { - case racer := <-lane: - if racer.error == nil { - return racer.Conn, nil - } - lastErr = racer.error - nracers-- + racer := <-lane + if racer.error == nil { + return racer.Conn, nil } + lastErr = racer.error + nracers-- } return nil, lastErr } diff --git a/libgo/go/net/dial_test.go b/libgo/go/net/dial_test.go index f9260fd281b..42898d669f7 100644 --- a/libgo/go/net/dial_test.go +++ b/libgo/go/net/dial_test.go @@ -119,6 +119,7 @@ func TestSelfConnect(t *testing.T) { // TODO(brainman): do not know why it hangs. t.Skip("skipping known-broken test on windows") } + // Test that Dial does not honor self-connects. // See the comment in DialTCP. @@ -149,8 +150,12 @@ func TestSelfConnect(t *testing.T) { for i := 0; i < n; i++ { c, err := DialTimeout("tcp", addr, time.Millisecond) if err == nil { + if c.LocalAddr().String() == addr { + t.Errorf("#%d: Dial %q self-connect", i, addr) + } else { + t.Logf("#%d: Dial %q succeeded - possibly racing with other listener", i, addr) + } c.Close() - t.Errorf("#%d: Dial %q succeeded", i, addr) } } } @@ -334,6 +339,8 @@ func numTCP() (ntcp, nopen, nclose int, err error) { } func TestDialMultiFDLeak(t *testing.T) { + t.Skip("flaky test - golang.org/issue/8764") + if !supportsIPv4 || !supportsIPv6 { t.Skip("neither ipv4 nor ipv6 is supported") } @@ -460,6 +467,11 @@ func TestDialer(t *testing.T) { } func TestDialDualStackLocalhost(t *testing.T) { + switch runtime.GOOS { + case "nacl": + t.Skipf("skipping test on %q", runtime.GOOS) + } + if ips, err := LookupIP("localhost"); err != nil { t.Fatalf("LookupIP failed: %v", err) } else if len(ips) < 2 || !supportsIPv4 || !supportsIPv6 { @@ -488,7 +500,7 @@ func TestDialDualStackLocalhost(t *testing.T) { } d := &Dialer{DualStack: true} - for _ = range dss.lns { + for range dss.lns { if c, err := d.Dial("tcp", "localhost:"+dss.port); err != nil { t.Errorf("Dial failed: %v", err) } else { diff --git a/libgo/go/net/dnsclient.go b/libgo/go/net/dnsclient.go index 9bffa11f916..e8014e4ffc9 100644 --- a/libgo/go/net/dnsclient.go +++ b/libgo/go/net/dnsclient.go @@ -196,9 +196,7 @@ func (addrs byPriorityWeight) shuffleByWeight() { s += int(addrs[i].Weight) if s > n { if i > 0 { - t := addrs[i] - copy(addrs[1:i+1], addrs[0:i]) - addrs[0] = t + addrs[0], addrs[i] = addrs[i], addrs[0] } break } diff --git a/libgo/go/net/dnsclient_unix.go b/libgo/go/net/dnsclient_unix.go index 3713efd0e3c..7511083f795 100644 --- a/libgo/go/net/dnsclient_unix.go +++ b/libgo/go/net/dnsclient_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris // DNS client: see RFC 1035. // Has to be linked into package net for Dial. @@ -16,6 +16,7 @@ package net import ( + "errors" "io" "math/rand" "os" @@ -23,118 +24,174 @@ import ( "time" ) -// Send a request on the connection and hope for a reply. -// Up to cfg.attempts attempts. -func exchange(cfg *dnsConfig, c Conn, name string, qtype uint16) (*dnsMsg, error) { - _, useTCP := c.(*TCPConn) - if len(name) >= 256 { - return nil, &DNSError{Err: "name too long", Name: name} +// A dnsConn represents a DNS transport endpoint. +type dnsConn interface { + Conn + + // readDNSResponse reads a DNS response message from the DNS + // transport endpoint and returns the received DNS response + // message. + readDNSResponse() (*dnsMsg, error) + + // writeDNSQuery writes a DNS query message to the DNS + // connection endpoint. + writeDNSQuery(*dnsMsg) error +} + +func (c *UDPConn) readDNSResponse() (*dnsMsg, error) { + b := make([]byte, 512) // see RFC 1035 + n, err := c.Read(b) + if err != nil { + return nil, err } - out := new(dnsMsg) - out.id = uint16(rand.Int()) ^ uint16(time.Now().UnixNano()) - out.question = []dnsQuestion{ - {name, qtype, dnsClassINET}, + msg := &dnsMsg{} + if !msg.Unpack(b[:n]) { + return nil, errors.New("cannot unmarshal DNS message") } - out.recursion_desired = true - msg, ok := out.Pack() + return msg, nil +} + +func (c *UDPConn) writeDNSQuery(msg *dnsMsg) error { + b, ok := msg.Pack() if !ok { - return nil, &DNSError{Err: "internal error - cannot pack message", Name: name} + return errors.New("cannot marshal DNS message") } - if useTCP { - mlen := uint16(len(msg)) - msg = append([]byte{byte(mlen >> 8), byte(mlen)}, msg...) + if _, err := c.Write(b); err != nil { + return err } - for attempt := 0; attempt < cfg.attempts; attempt++ { - n, err := c.Write(msg) + return nil +} + +func (c *TCPConn) readDNSResponse() (*dnsMsg, error) { + b := make([]byte, 1280) // 1280 is a reasonable initial size for IP over Ethernet, see RFC 4035 + if _, err := io.ReadFull(c, b[:2]); err != nil { + return nil, err + } + l := int(b[0])<<8 | int(b[1]) + if l > len(b) { + b = make([]byte, l) + } + n, err := io.ReadFull(c, b[:l]) + if err != nil { + return nil, err + } + msg := &dnsMsg{} + if !msg.Unpack(b[:n]) { + return nil, errors.New("cannot unmarshal DNS message") + } + return msg, nil +} + +func (c *TCPConn) writeDNSQuery(msg *dnsMsg) error { + b, ok := msg.Pack() + if !ok { + return errors.New("cannot marshal DNS message") + } + l := uint16(len(b)) + b = append([]byte{byte(l >> 8), byte(l)}, b...) + if _, err := c.Write(b); err != nil { + return err + } + return nil +} + +func (d *Dialer) dialDNS(network, server string) (dnsConn, error) { + switch network { + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": + default: + return nil, UnknownNetworkError(network) + } + // Calling Dial here is scary -- we have to be sure not to + // dial a name that will require a DNS lookup, or Dial will + // call back here to translate it. The DNS config parser has + // already checked that all the cfg.servers[i] are IP + // addresses, which Dial will use without a DNS lookup. + c, err := d.Dial(network, server) + if err != nil { + return nil, err + } + switch network { + case "tcp", "tcp4", "tcp6": + return c.(*TCPConn), nil + case "udp", "udp4", "udp6": + return c.(*UDPConn), nil + } + panic("unreachable") +} + +// exchange sends a query on the connection and hopes for a response. +func exchange(server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) { + d := Dialer{Timeout: timeout} + out := dnsMsg{ + dnsMsgHdr: dnsMsgHdr{ + recursion_desired: true, + }, + question: []dnsQuestion{ + {name, qtype, dnsClassINET}, + }, + } + for _, network := range []string{"udp", "tcp"} { + c, err := d.dialDNS(network, server) if err != nil { return nil, err } - - if cfg.timeout == 0 { - c.SetReadDeadline(noDeadline) - } else { - c.SetReadDeadline(time.Now().Add(time.Duration(cfg.timeout) * time.Second)) + defer c.Close() + if timeout > 0 { + c.SetDeadline(time.Now().Add(timeout)) } - buf := make([]byte, 2000) - if useTCP { - n, err = io.ReadFull(c, buf[:2]) - if err != nil { - if e, ok := err.(Error); ok && e.Timeout() { - continue - } - } - mlen := int(buf[0])<<8 | int(buf[1]) - if mlen > len(buf) { - buf = make([]byte, mlen) - } - n, err = io.ReadFull(c, buf[:mlen]) - } else { - n, err = c.Read(buf) + out.id = uint16(rand.Int()) ^ uint16(time.Now().UnixNano()) + if err := c.writeDNSQuery(&out); err != nil { + return nil, err } + in, err := c.readDNSResponse() if err != nil { - if e, ok := err.(Error); ok && e.Timeout() { - continue - } return nil, err } - buf = buf[:n] - in := new(dnsMsg) - if !in.Unpack(buf) || in.id != out.id { + if in.id != out.id { + return nil, errors.New("DNS message ID mismatch") + } + if in.truncated { // see RFC 5966 continue } return in, nil } - var server string - if a := c.RemoteAddr(); a != nil { - server = a.String() - } - return nil, &DNSError{Err: "no answer from server", Name: name, Server: server, IsTimeout: true} + return nil, errors.New("no answer from DNS server") } // Do a lookup for a single name, which must be rooted // (otherwise answer will not find the answers). -func tryOneName(cfg *dnsConfig, name string, qtype uint16) (cname string, addrs []dnsRR, err error) { +func tryOneName(cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, error) { if len(cfg.servers) == 0 { return "", nil, &DNSError{Err: "no DNS servers", Name: name} } - for i := 0; i < len(cfg.servers); i++ { - // Calling Dial here is scary -- we have to be sure - // not to dial a name that will require a DNS lookup, - // or Dial will call back here to translate it. - // The DNS config parser has already checked that - // all the cfg.servers[i] are IP addresses, which - // Dial will use without a DNS lookup. - server := cfg.servers[i] + ":53" - c, cerr := Dial("udp", server) - if cerr != nil { - err = cerr - continue - } - msg, merr := exchange(cfg, c, name, qtype) - c.Close() - if merr != nil { - err = merr - continue - } - if msg.truncated { // see RFC 5966 - c, cerr = Dial("tcp", server) - if cerr != nil { - err = cerr + if len(name) >= 256 { + return "", nil, &DNSError{Err: "DNS name too long", Name: name} + } + timeout := time.Duration(cfg.timeout) * time.Second + var lastErr error + for i := 0; i < cfg.attempts; i++ { + for _, server := range cfg.servers { + server = JoinHostPort(server, "53") + msg, err := exchange(server, name, qtype, timeout) + if err != nil { + lastErr = &DNSError{ + Err: err.Error(), + Name: name, + Server: server, + } + if nerr, ok := err.(Error); ok && nerr.Timeout() { + lastErr.(*DNSError).IsTimeout = true + } continue } - msg, merr = exchange(cfg, c, name, qtype) - c.Close() - if merr != nil { - err = merr - continue + cname, addrs, err := answer(name, server, msg, qtype) + if err == nil || err.(*DNSError).Err == noSuchHost { + return cname, addrs, err } - } - cname, addrs, err = answer(name, server, msg, qtype) - if err == nil || err.(*DNSError).Err == noSuchHost { - break + lastErr = err } } - return + return "", nil, lastErr } func convertRR_A(records []dnsRR) []IP { @@ -240,13 +297,10 @@ func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) } // Can try as ordinary name. cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype) - if err == nil { + if rooted || err == nil { return } } - if rooted { - return - } // Otherwise, try suffixes. for i := 0; i < len(cfg.dnsConfig.search); i++ { @@ -260,15 +314,15 @@ func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) } } - // Last ditch effort: try unsuffixed. - rname := name - if !rooted { - rname += "." - } - cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype) - if err == nil { - return + // Last ditch effort: try unsuffixed only if we haven't already, + // that is, name is not rooted and has less than ndots dots. + if count(name, '.') < cfg.dnsConfig.ndots { + cname, addrs, err = tryOneName(cfg.dnsConfig, name+".", qtype) + if err == nil { + return + } } + if e, ok := err.(*DNSError); ok { // Show original name passed to lookup, not suffixed one. // In general we might have tried many suffixes; showing @@ -320,31 +374,36 @@ func goLookupIP(name string) (addrs []IP, err error) { return } } - var records []dnsRR - var cname string - var err4, err6 error - cname, records, err4 = lookup(name, dnsTypeA) - addrs = convertRR_A(records) - if cname != "" { - name = cname - } - _, records, err6 = lookup(name, dnsTypeAAAA) - if err4 != nil && err6 == nil { - // Ignore A error because AAAA lookup succeeded. - err4 = nil + type racer struct { + qtype uint16 + rrs []dnsRR + error } - if err6 != nil && len(addrs) > 0 { - // Ignore AAAA error because A lookup succeeded. - err6 = nil + lane := make(chan racer, 1) + qtypes := [...]uint16{dnsTypeA, dnsTypeAAAA} + for _, qtype := range qtypes { + go func(qtype uint16) { + _, rrs, err := lookup(name, qtype) + lane <- racer{qtype, rrs, err} + }(qtype) } - if err4 != nil { - return nil, err4 + var lastErr error + for range qtypes { + racer := <-lane + if racer.error != nil { + lastErr = racer.error + continue + } + switch racer.qtype { + case dnsTypeA: + addrs = append(addrs, convertRR_A(racer.rrs)...) + case dnsTypeAAAA: + addrs = append(addrs, convertRR_AAAA(racer.rrs)...) + } } - if err6 != nil { - return nil, err6 + if len(addrs) == 0 && lastErr != nil { + return nil, lastErr } - - addrs = append(addrs, convertRR_AAAA(records)...) return addrs, nil } diff --git a/libgo/go/net/dnsclient_unix_test.go b/libgo/go/net/dnsclient_unix_test.go index 2350142d610..1167c26b39d 100644 --- a/libgo/go/net/dnsclient_unix_test.go +++ b/libgo/go/net/dnsclient_unix_test.go @@ -16,19 +16,79 @@ import ( "time" ) -func TestTCPLookup(t *testing.T) { +var dnsTransportFallbackTests = []struct { + server string + name string + qtype uint16 + timeout int + rcode int +}{ + // Querying "com." with qtype=255 usually makes an answer + // which requires more than 512 bytes. + {"8.8.8.8:53", "com.", dnsTypeALL, 2, dnsRcodeSuccess}, + {"8.8.4.4:53", "com.", dnsTypeALL, 4, dnsRcodeSuccess}, +} + +func TestDNSTransportFallback(t *testing.T) { if testing.Short() || !*testExternal { t.Skip("skipping test to avoid external network") } - c, err := Dial("tcp", "8.8.8.8:53") - if err != nil { - t.Fatalf("Dial failed: %v", err) + + for _, tt := range dnsTransportFallbackTests { + timeout := time.Duration(tt.timeout) * time.Second + msg, err := exchange(tt.server, tt.name, tt.qtype, timeout) + if err != nil { + t.Error(err) + continue + } + switch msg.rcode { + case tt.rcode, dnsRcodeServerFailure: + default: + t.Errorf("got %v from %v; want %v", msg.rcode, tt.server, tt.rcode) + continue + } } - defer c.Close() - cfg := &dnsConfig{timeout: 10, attempts: 3} - _, err = exchange(cfg, c, "com.", dnsTypeALL) - if err != nil { - t.Fatalf("exchange failed: %v", err) +} + +// See RFC 6761 for further information about the reserved, pseudo +// domain names. +var specialDomainNameTests = []struct { + name string + qtype uint16 + rcode int +}{ + // Name resoltion APIs and libraries should not recongnize the + // followings as special. + {"1.0.168.192.in-addr.arpa.", dnsTypePTR, dnsRcodeNameError}, + {"test.", dnsTypeALL, dnsRcodeNameError}, + {"example.com.", dnsTypeALL, dnsRcodeSuccess}, + + // Name resoltion APIs and libraries should recongnize the + // followings as special and should not send any queries. + // Though, we test those names here for verifying nagative + // answers at DNS query-response interaction level. + {"localhost.", dnsTypeALL, dnsRcodeNameError}, + {"invalid.", dnsTypeALL, dnsRcodeNameError}, +} + +func TestSpecialDomainName(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("skipping test to avoid external network") + } + + server := "8.8.8.8:53" + for _, tt := range specialDomainNameTests { + msg, err := exchange(server, tt.name, tt.qtype, 0) + if err != nil { + t.Error(err) + continue + } + switch msg.rcode { + case tt.rcode, dnsRcodeServerFailure: + default: + t.Errorf("got %v from %v; want %v", msg.rcode, server, tt.rcode) + continue + } } } @@ -144,7 +204,7 @@ func TestReloadResolvConfChange(t *testing.T) { if _, err := goLookupIP("golang.org"); err != nil { t.Fatalf("goLookupIP(good) failed: %v", err) } - r.WantServers([]string{"[8.8.8.8]"}) + r.WantServers([]string{"8.8.8.8"}) // Using a bad resolv.conf when we had a good one // before should not update the config @@ -155,5 +215,32 @@ func TestReloadResolvConfChange(t *testing.T) { // A new good config should get picked up r.SetConf("nameserver 8.8.4.4") - r.WantServers([]string{"[8.8.4.4]"}) + r.WantServers([]string{"8.8.4.4"}) +} + +func BenchmarkGoLookupIP(b *testing.B) { + for i := 0; i < b.N; i++ { + goLookupIP("www.example.com") + } +} + +func BenchmarkGoLookupIPNoSuchHost(b *testing.B) { + for i := 0; i < b.N; i++ { + goLookupIP("some.nonexistent") + } +} + +func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) { + onceLoadConfig.Do(loadDefaultConfig) + if cfg.dnserr != nil || cfg.dnsConfig == nil { + b.Fatalf("loadConfig failed: %v", cfg.dnserr) + } + // This looks ugly but it's safe as long as benchmarks are run + // sequentially in package testing. + orig := cfg.dnsConfig + cfg.dnsConfig.servers = append([]string{"203.0.113.254"}, cfg.dnsConfig.servers...) // use TEST-NET-3 block, see RFC 5737 + for i := 0; i < b.N; i++ { + goLookupIP("www.example.com") + } + cfg.dnsConfig = orig } diff --git a/libgo/go/net/dnsconfig_unix.go b/libgo/go/net/dnsconfig_unix.go index db45716f124..66ab7c4dd30 100644 --- a/libgo/go/net/dnsconfig_unix.go +++ b/libgo/go/net/dnsconfig_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris // Read system DNS config from /etc/resolv.conf @@ -25,13 +25,12 @@ func dnsReadConfig(filename string) (*dnsConfig, error) { if err != nil { return nil, &DNSConfigError{err} } - conf := new(dnsConfig) - conf.servers = make([]string, 0, 3) // small, but the standard limit - conf.search = make([]string, 0) - conf.ndots = 1 - conf.timeout = 5 - conf.attempts = 2 - conf.rotate = false + defer file.close() + conf := &dnsConfig{ + ndots: 1, + timeout: 5, + attempts: 2, + } for line, ok := file.readLine(); ok; line, ok = file.readLine() { f := getFields(line) if len(f) < 1 { @@ -39,30 +38,20 @@ func dnsReadConfig(filename string) (*dnsConfig, error) { } switch f[0] { case "nameserver": // add one name server - a := conf.servers - n := len(a) - if len(f) > 1 && n < cap(a) { + if len(f) > 1 && len(conf.servers) < 3 { // small, but the standard limit // One more check: make sure server name is // just an IP address. Otherwise we need DNS // to look it up. - name := f[1] - switch len(ParseIP(name)) { - case 16: - name = "[" + name + "]" - fallthrough - case 4: - a = a[0 : n+1] - a[n] = name - conf.servers = a + if parseIPv4(f[1]) != nil { + conf.servers = append(conf.servers, f[1]) + } else if ip, _ := parseIPv6(f[1], true); ip != nil { + conf.servers = append(conf.servers, f[1]) } } case "domain": // set search path to just this domain if len(f) > 1 { - conf.search = make([]string, 1) - conf.search[0] = f[1] - } else { - conf.search = make([]string, 0) + conf.search = []string{f[1]} } case "search": // set search path to given servers @@ -99,8 +88,6 @@ func dnsReadConfig(filename string) (*dnsConfig, error) { } } } - file.close() - return conf, nil } diff --git a/libgo/go/net/dnsconfig_unix_test.go b/libgo/go/net/dnsconfig_unix_test.go index 37ed4931dbe..94fb0c32e24 100644 --- a/libgo/go/net/dnsconfig_unix_test.go +++ b/libgo/go/net/dnsconfig_unix_test.go @@ -6,41 +6,64 @@ package net -import "testing" +import ( + "reflect" + "testing" +) -func TestDNSReadConfig(t *testing.T) { - dnsConfig, err := dnsReadConfig("testdata/resolv.conf") - if err != nil { - t.Fatal(err) - } - - if len(dnsConfig.servers) != 1 { - t.Errorf("len(dnsConfig.servers) = %d; want %d", len(dnsConfig.servers), 1) - } - if dnsConfig.servers[0] != "[192.168.1.1]" { - t.Errorf("dnsConfig.servers[0] = %s; want %s", dnsConfig.servers[0], "[192.168.1.1]") - } - - if len(dnsConfig.search) != 1 { - t.Errorf("len(dnsConfig.search) = %d; want %d", len(dnsConfig.search), 1) - } - if dnsConfig.search[0] != "Home" { - t.Errorf("dnsConfig.search[0] = %s; want %s", dnsConfig.search[0], "Home") - } - - if dnsConfig.ndots != 5 { - t.Errorf("dnsConfig.ndots = %d; want %d", dnsConfig.ndots, 5) - } - - if dnsConfig.timeout != 10 { - t.Errorf("dnsConfig.timeout = %d; want %d", dnsConfig.timeout, 10) - } - - if dnsConfig.attempts != 3 { - t.Errorf("dnsConfig.attempts = %d; want %d", dnsConfig.attempts, 3) - } +var dnsReadConfigTests = []struct { + name string + conf dnsConfig +}{ + { + name: "testdata/resolv.conf", + conf: dnsConfig{ + servers: []string{"8.8.8.8", "2001:4860:4860::8888", "fe80::1%lo0"}, + search: []string{"localdomain"}, + ndots: 5, + timeout: 10, + attempts: 3, + rotate: true, + }, + }, + { + name: "testdata/domain-resolv.conf", + conf: dnsConfig{ + servers: []string{"8.8.8.8"}, + search: []string{"localdomain"}, + ndots: 1, + timeout: 5, + attempts: 2, + }, + }, + { + name: "testdata/search-resolv.conf", + conf: dnsConfig{ + servers: []string{"8.8.8.8"}, + search: []string{"test", "invalid"}, + ndots: 1, + timeout: 5, + attempts: 2, + }, + }, + { + name: "testdata/empty-resolv.conf", + conf: dnsConfig{ + ndots: 1, + timeout: 5, + attempts: 2, + }, + }, +} - if dnsConfig.rotate != true { - t.Errorf("dnsConfig.rotate = %t; want %t", dnsConfig.rotate, true) +func TestDNSReadConfig(t *testing.T) { + for _, tt := range dnsReadConfigTests { + conf, err := dnsReadConfig(tt.name) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(conf, &tt.conf) { + t.Errorf("got %v; want %v", conf, &tt.conf) + } } } diff --git a/libgo/go/net/fd_unix.go b/libgo/go/net/fd_unix.go index 7c73ddca777..7a97faeba34 100644 --- a/libgo/go/net/fd_unix.go +++ b/libgo/go/net/fd_unix.go @@ -401,7 +401,7 @@ func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oob return } -func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (netfd *netFD, err error) { +func (fd *netFD) accept() (netfd *netFD, err error) { if err := fd.readLock(); err != nil { return nil, err } @@ -438,7 +438,7 @@ func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (netfd *netFD, err e return nil, err } lsa, _ := syscall.Getsockname(netfd.sysfd) - netfd.setAddr(toAddr(lsa), toAddr(rsa)) + netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa)) return netfd, nil } diff --git a/libgo/go/net/fd_windows.go b/libgo/go/net/fd_windows.go index d1129dccc47..f3a534a1de0 100644 --- a/libgo/go/net/fd_windows.go +++ b/libgo/go/net/fd_windows.go @@ -294,6 +294,18 @@ func (fd *netFD) init() error { fd.skipSyncNotif = true } } + // Disable SIO_UDP_CONNRESET behavior. + // http://support.microsoft.com/kb/263823 + switch fd.net { + case "udp", "udp4", "udp6": + ret := uint32(0) + flag := uint32(0) + size := uint32(unsafe.Sizeof(flag)) + err := syscall.WSAIoctl(fd.sysfd, syscall.SIO_UDP_CONNRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0) + if err != nil { + return os.NewSyscallError("WSAIoctl", err) + } + } fd.rop.mode = 'r' fd.wop.mode = 'w' fd.rop.fd = fd @@ -520,7 +532,7 @@ func (fd *netFD) writeTo(buf []byte, sa syscall.Sockaddr) (int, error) { }) } -func (fd *netFD) acceptOne(toAddr func(syscall.Sockaddr) Addr, rawsa []syscall.RawSockaddrAny, o *operation) (*netFD, error) { +func (fd *netFD) acceptOne(rawsa []syscall.RawSockaddrAny, o *operation) (*netFD, error) { // Get new socket. s, err := sysSocket(fd.family, fd.sotype, 0) if err != nil { @@ -559,7 +571,7 @@ func (fd *netFD) acceptOne(toAddr func(syscall.Sockaddr) Addr, rawsa []syscall.R return netfd, nil } -func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) { +func (fd *netFD) accept() (*netFD, error) { if err := fd.readLock(); err != nil { return nil, err } @@ -570,7 +582,7 @@ func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) { var err error var rawsa [2]syscall.RawSockaddrAny for { - netfd, err = fd.acceptOne(toAddr, rawsa[:], o) + netfd, err = fd.acceptOne(rawsa[:], o) if err == nil { break } @@ -603,7 +615,7 @@ func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) { lsa, _ := lrsa.Sockaddr() rsa, _ := rrsa.Sockaddr() - netfd.setAddr(toAddr(lsa), toAddr(rsa)) + netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa)) return netfd, nil } diff --git a/libgo/go/net/file_stub.go b/libgo/go/net/file_stub.go new file mode 100644 index 00000000000..4281072ef93 --- /dev/null +++ b/libgo/go/net/file_stub.go @@ -0,0 +1,38 @@ +// Copyright 2011 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. + +// +build nacl + +package net + +import ( + "os" + "syscall" +) + +// FileConn returns a copy of the network connection corresponding to +// the open file f. It is the caller's responsibility to close f when +// finished. Closing c does not affect f, and closing f does not +// affect c. +func FileConn(f *os.File) (c Conn, err error) { + return nil, syscall.ENOPROTOOPT + +} + +// FileListener returns a copy of the network listener corresponding +// to the open file f. It is the caller's responsibility to close l +// when finished. Closing l does not affect f, and closing f does not +// affect l. +func FileListener(f *os.File) (l Listener, err error) { + return nil, syscall.ENOPROTOOPT + +} + +// FilePacketConn returns a copy of the packet network connection +// corresponding to the open file f. It is the caller's +// responsibility to close f when finished. Closing c does not affect +// f, and closing f does not affect c. +func FilePacketConn(f *os.File) (c PacketConn, err error) { + return nil, syscall.ENOPROTOOPT +} diff --git a/libgo/go/net/file_test.go b/libgo/go/net/file_test.go index d81bca78249..6fab06a9c6e 100644 --- a/libgo/go/net/file_test.go +++ b/libgo/go/net/file_test.go @@ -89,7 +89,7 @@ var fileListenerTests = []struct { func TestFileListener(t *testing.T) { switch runtime.GOOS { - case "windows": + case "nacl", "windows": t.Skipf("skipping test on %q", runtime.GOOS) } diff --git a/libgo/go/net/file_unix.go b/libgo/go/net/file_unix.go index 07b3ecf6263..214a4196c8e 100644 --- a/libgo/go/net/file_unix.go +++ b/libgo/go/net/file_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/hosts.go b/libgo/go/net/hosts.go index e6674ba3415..9400503e41e 100644 --- a/libgo/go/net/hosts.go +++ b/libgo/go/net/hosts.go @@ -51,7 +51,7 @@ func readHosts() { } } // Update the data cache. - hosts.expire = time.Now().Add(cacheMaxAge) + hosts.expire = now.Add(cacheMaxAge) hosts.path = hp hosts.byName = hs hosts.byAddr = is diff --git a/libgo/go/net/http/client.go b/libgo/go/net/http/client.go index a5a3abe6138..ce884d1f07b 100644 --- a/libgo/go/net/http/client.go +++ b/libgo/go/net/http/client.go @@ -101,6 +101,30 @@ type RoundTripper interface { // return true if the string includes a port. func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } +// refererForURL returns a referer without any authentication info or +// an empty string if lastReq scheme is https and newReq scheme is http. +func refererForURL(lastReq, newReq *url.URL) string { + // https://tools.ietf.org/html/rfc7231#section-5.5.2 + // "Clients SHOULD NOT include a Referer header field in a + // (non-secure) HTTP request if the referring page was + // transferred with a secure protocol." + if lastReq.Scheme == "https" && newReq.Scheme == "http" { + return "" + } + referer := lastReq.String() + if lastReq.User != nil { + // This is not very efficient, but is the best we can + // do without: + // - introducing a new method on URL + // - creating a race condition + // - copying the URL struct manually, which would cause + // maintenance problems down the line + auth := lastReq.User.String() + "@" + referer = strings.Replace(referer, auth, "", 1) + } + return referer +} + // Used in Send to implement io.ReadCloser by bundling together the // bufio.Reader through which we read the response, and the underlying // network connection. @@ -324,8 +348,8 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo if len(via) > 0 { // Add the Referer header. lastReq := via[len(via)-1] - if lastReq.URL.Scheme != "https" { - nreq.Header.Set("Referer", lastReq.URL.String()) + if ref := refererForURL(lastReq.URL, nreq.URL); ref != "" { + nreq.Header.Set("Referer", ref) } err = redirectChecker(nreq, via) diff --git a/libgo/go/net/http/client_test.go b/libgo/go/net/http/client_test.go index 6392c1baf39..56b6563c486 100644 --- a/libgo/go/net/http/client_test.go +++ b/libgo/go/net/http/client_test.go @@ -1036,3 +1036,40 @@ func TestClientTrailers(t *testing.T) { t.Errorf("Response trailers = %#v; want %#v", res.Trailer, want) } } + +func TestReferer(t *testing.T) { + tests := []struct { + lastReq, newReq string // from -> to URLs + want string + }{ + // don't send user: + {"http://gopher@test.com", "http://link.com", "http://test.com"}, + {"https://gopher@test.com", "https://link.com", "https://test.com"}, + + // don't send a user and password: + {"http://gopher:go@test.com", "http://link.com", "http://test.com"}, + {"https://gopher:go@test.com", "https://link.com", "https://test.com"}, + + // nothing to do: + {"http://test.com", "http://link.com", "http://test.com"}, + {"https://test.com", "https://link.com", "https://test.com"}, + + // https to http doesn't send a referer: + {"https://test.com", "http://link.com", ""}, + {"https://gopher:go@test.com", "http://link.com", ""}, + } + for _, tt := range tests { + l, err := url.Parse(tt.lastReq) + if err != nil { + t.Fatal(err) + } + n, err := url.Parse(tt.newReq) + if err != nil { + t.Fatal(err) + } + r := ExportRefererForURL(l, n) + if r != tt.want { + t.Errorf("refererForURL(%q, %q) = %q; want %q", tt.lastReq, tt.newReq, r, tt.want) + } + } +} diff --git a/libgo/go/net/http/cookie.go b/libgo/go/net/http/cookie.go index dc60ba87f5f..a0d0fdbbd07 100644 --- a/libgo/go/net/http/cookie.go +++ b/libgo/go/net/http/cookie.go @@ -56,7 +56,7 @@ func readSetCookies(h Header) []*Cookie { if !isCookieNameValid(name) { continue } - value, success := parseCookieValue(value) + value, success := parseCookieValue(value, true) if !success { continue } @@ -76,7 +76,7 @@ func readSetCookies(h Header) []*Cookie { attr, val = attr[:j], attr[j+1:] } lowerAttr := strings.ToLower(attr) - val, success = parseCookieValue(val) + val, success = parseCookieValue(val, false) if !success { c.Unparsed = append(c.Unparsed, parts[i]) continue @@ -205,7 +205,7 @@ func readCookies(h Header, filter string) []*Cookie { if filter != "" && filter != name { continue } - val, success := parseCookieValue(val) + val, success := parseCookieValue(val, true) if !success { continue } @@ -345,9 +345,9 @@ func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string { return string(buf) } -func parseCookieValue(raw string) (string, bool) { +func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) { // Strip the quotes, if present. - if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { + if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { raw = raw[1 : len(raw)-1] } for i := 0; i < len(raw); i++ { diff --git a/libgo/go/net/http/cookie_test.go b/libgo/go/net/http/cookie_test.go index f78f37299f4..98dc2fade0d 100644 --- a/libgo/go/net/http/cookie_test.go +++ b/libgo/go/net/http/cookie_test.go @@ -313,6 +313,14 @@ var readCookiesTests = []struct { {Name: "c2", Value: "v2"}, }, }, + { + Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}}, + "", + []*Cookie{ + {Name: "Cookie-1", Value: "v$1"}, + {Name: "c2", Value: "v2"}, + }, + }, } func TestReadCookies(t *testing.T) { @@ -327,6 +335,30 @@ func TestReadCookies(t *testing.T) { } } +func TestSetCookieDoubleQuotes(t *testing.T) { + res := &Response{Header: Header{}} + res.Header.Add("Set-Cookie", `quoted0=none; max-age=30`) + res.Header.Add("Set-Cookie", `quoted1="cookieValue"; max-age=31`) + res.Header.Add("Set-Cookie", `quoted2=cookieAV; max-age="32"`) + res.Header.Add("Set-Cookie", `quoted3="both"; max-age="33"`) + got := res.Cookies() + want := []*Cookie{ + {Name: "quoted0", Value: "none", MaxAge: 30}, + {Name: "quoted1", Value: "cookieValue", MaxAge: 31}, + {Name: "quoted2", Value: "cookieAV"}, + {Name: "quoted3", Value: "both"}, + } + if len(got) != len(want) { + t.Fatal("got %d cookies, want %d", len(got), len(want)) + } + for i, w := range want { + g := got[i] + if g.Name != w.Name || g.Value != w.Value || g.MaxAge != w.MaxAge { + t.Errorf("cookie #%d:\ngot %v\nwant %v", i, g, w) + } + } +} + func TestCookieSanitizeValue(t *testing.T) { defer log.SetOutput(os.Stderr) var logbuf bytes.Buffer diff --git a/libgo/go/net/http/cookiejar/jar.go b/libgo/go/net/http/cookiejar/jar.go index 389ab58e418..0e0fac9286e 100644 --- a/libgo/go/net/http/cookiejar/jar.go +++ b/libgo/go/net/http/cookiejar/jar.go @@ -30,7 +30,7 @@ import ( // set a cookie for bar.com. // // A public suffix list implementation is in the package -// code.google.com/p/go.net/publicsuffix. +// golang.org/x/net/publicsuffix. type PublicSuffixList interface { // PublicSuffix returns the public suffix of domain. // diff --git a/libgo/go/net/http/export_test.go b/libgo/go/net/http/export_test.go index 960563b2409..87b6c0773aa 100644 --- a/libgo/go/net/http/export_test.go +++ b/libgo/go/net/http/export_test.go @@ -9,6 +9,7 @@ package http import ( "net" + "net/url" "time" ) @@ -57,6 +58,26 @@ func (t *Transport) IdleConnChMapSizeForTesting() int { return len(t.idleConnCh) } +func (t *Transport) IsIdleForTesting() bool { + t.idleMu.Lock() + defer t.idleMu.Unlock() + return t.wantIdle +} + +func (t *Transport) RequestIdleConnChForTesting() { + t.getIdleConnCh(connectMethod{nil, "http", "example.com"}) +} + +func (t *Transport) PutIdleTestConn() bool { + c, _ := net.Pipe() + return t.putIdleConn(&persistConn{ + t: t, + conn: c, // dummy + closech: make(chan struct{}), // so it can be closed + cacheKey: connectMethodKey{"", "http", "example.com"}, + }) +} + func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler { f := func() <-chan time.Time { return ch @@ -66,7 +87,22 @@ func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler { func ResetCachedEnvironment() { httpProxyEnv.reset() + httpsProxyEnv.reset() noProxyEnv.reset() } var DefaultUserAgent = defaultUserAgent + +func ExportRefererForURL(lastReq, newReq *url.URL) string { + return refererForURL(lastReq, newReq) +} + +// SetPendingDialHooks sets the hooks that run before and after handling +// pending dials. +func SetPendingDialHooks(before, after func()) { + prePendingDial, postPendingDial = before, after +} + +var ExportServerNewConn = (*Server).newConn + +var ExportCloseWriteAndWait = (*conn).closeWriteAndWait diff --git a/libgo/go/net/http/fs.go b/libgo/go/net/http/fs.go index 8576cf844a3..e322f710a5d 100644 --- a/libgo/go/net/http/fs.go +++ b/libgo/go/net/http/fs.go @@ -22,8 +22,12 @@ import ( "time" ) -// A Dir implements http.FileSystem using the native file -// system restricted to a specific directory tree. +// A Dir implements FileSystem using the native file system restricted to a +// specific directory tree. +// +// While the FileSystem.Open method takes '/'-separated paths, a Dir's string +// value is a filename on the native file system, not a URL, so it is separated +// by filepath.Separator, which isn't necessarily '/'. // // An empty Dir is treated as ".". type Dir string @@ -139,7 +143,7 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, if checkLastModified(w, r, modtime) { return } - rangeReq, done := checkETag(w, r) + rangeReq, done := checkETag(w, r, modtime) if done { return } @@ -212,12 +216,6 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, code = StatusPartialContent w.Header().Set("Content-Range", ra.contentRange(size)) case len(ranges) > 1: - for _, ra := range ranges { - if ra.start > size { - Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) - return - } - } sendSize = rangesMIMESize(ranges, ctype, size) code = StatusPartialContent @@ -281,11 +279,14 @@ func checkLastModified(w ResponseWriter, r *Request, modtime time.Time) bool { } // checkETag implements If-None-Match and If-Range checks. -// The ETag must have been previously set in the ResponseWriter's headers. +// +// The ETag or modtime must have been previously set in the +// ResponseWriter's headers. The modtime is only compared at second +// granularity and may be the zero value to mean unknown. // // The return value is the effective request "Range" header to use and // whether this request is now considered done. -func checkETag(w ResponseWriter, r *Request) (rangeReq string, done bool) { +func checkETag(w ResponseWriter, r *Request, modtime time.Time) (rangeReq string, done bool) { etag := w.Header().get("Etag") rangeReq = r.Header.get("Range") @@ -296,11 +297,17 @@ func checkETag(w ResponseWriter, r *Request) (rangeReq string, done bool) { // We only support ETag versions. // The caller must have set the ETag on the response already. if ir := r.Header.get("If-Range"); ir != "" && ir != etag { - // TODO(bradfitz): handle If-Range requests with Last-Modified - // times instead of ETags? I'd rather not, at least for - // now. That seems like a bug/compromise in the RFC 2616, and - // I've never heard of anybody caring about that (yet). - rangeReq = "" + // The If-Range value is typically the ETag value, but it may also be + // the modtime date. See golang.org/issue/8367. + timeMatches := false + if !modtime.IsZero() { + if t, err := ParseTime(ir); err == nil && t.Unix() == modtime.Unix() { + timeMatches = true + } + } + if !timeMatches { + rangeReq = "" + } } if inm := r.Header.get("If-None-Match"); inm != "" { @@ -378,7 +385,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec // use contents of index.html for directory, if present if d.IsDir() { - index := name + indexPage + index := strings.TrimSuffix(name, "/") + indexPage ff, err := fs.Open(index) if err == nil { defer ff.Close() @@ -400,7 +407,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec return } - // serverContent will check modification time + // serveContent will check modification time sizeFunc := func() (int64, error) { return d.Size(), nil } serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f) } diff --git a/libgo/go/net/http/fs_test.go b/libgo/go/net/http/fs_test.go index c9a77c9f6aa..2ddd4ca5fe9 100644 --- a/libgo/go/net/http/fs_test.go +++ b/libgo/go/net/http/fs_test.go @@ -721,6 +721,28 @@ func TestServeContent(t *testing.T) { wantStatus: 200, wantContentType: "text/css; charset=utf-8", }, + "range_with_modtime": { + file: "testdata/style.css", + modtime: time.Date(2014, 6, 25, 17, 12, 18, 0 /* nanos */, time.UTC), + reqHeader: map[string]string{ + "Range": "bytes=0-4", + "If-Range": "Wed, 25 Jun 2014 17:12:18 GMT", + }, + wantStatus: StatusPartialContent, + wantContentType: "text/css; charset=utf-8", + wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT", + }, + "range_with_modtime_nanos": { + file: "testdata/style.css", + modtime: time.Date(2014, 6, 25, 17, 12, 18, 123 /* nanos */, time.UTC), + reqHeader: map[string]string{ + "Range": "bytes=0-4", + "If-Range": "Wed, 25 Jun 2014 17:12:18 GMT", + }, + wantStatus: StatusPartialContent, + wantContentType: "text/css; charset=utf-8", + wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT", + }, } for testName, tt := range tests { var content io.ReadSeeker @@ -860,4 +882,41 @@ func TestLinuxSendfileChild(*testing.T) { } } +func TestFileServerCleanPath(t *testing.T) { + tests := []struct { + path string + wantCode int + wantOpen []string + }{ + {"/", 200, []string{"/", "/index.html"}}, + {"/dir", 301, []string{"/dir"}}, + {"/dir/", 200, []string{"/dir", "/dir/index.html"}}, + } + for _, tt := range tests { + var log []string + rr := httptest.NewRecorder() + req, _ := NewRequest("GET", "http://foo.localhost"+tt.path, nil) + FileServer(fileServerCleanPathDir{&log}).ServeHTTP(rr, req) + if !reflect.DeepEqual(log, tt.wantOpen) { + t.Logf("For %s: Opens = %q; want %q", tt.path, log, tt.wantOpen) + } + if rr.Code != tt.wantCode { + t.Logf("For %s: Response code = %d; want %d", tt.path, rr.Code, tt.wantCode) + } + } +} + +type fileServerCleanPathDir struct { + log *[]string +} + +func (d fileServerCleanPathDir) Open(path string) (File, error) { + *(d.log) = append(*(d.log), path) + if path == "/" || path == "/dir" || path == "/dir/" { + // Just return back something that's a directory. + return Dir(".").Open(".") + } + return nil, os.ErrNotExist +} + type panicOnSeek struct{ io.ReadSeeker } diff --git a/libgo/go/net/http/httptest/server.go b/libgo/go/net/http/httptest/server.go index 7f265552f52..789e7bf41e6 100644 --- a/libgo/go/net/http/httptest/server.go +++ b/libgo/go/net/http/httptest/server.go @@ -203,7 +203,7 @@ func (h *waitGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // localhostCert is a PEM-encoded TLS cert with SAN IPs // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end // of ASN.1 time). -// generated from src/pkg/crypto/tls: +// generated from src/crypto/tls: // go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h var localhostCert = []byte(`-----BEGIN CERTIFICATE----- MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD diff --git a/libgo/go/net/http/httptest/server_test.go b/libgo/go/net/http/httptest/server_test.go index 4fc4c702082..500a9f0b800 100644 --- a/libgo/go/net/http/httptest/server_test.go +++ b/libgo/go/net/http/httptest/server_test.go @@ -8,7 +8,6 @@ import ( "io/ioutil" "net/http" "testing" - "time" ) func TestServer(t *testing.T) { @@ -28,26 +27,3 @@ func TestServer(t *testing.T) { t.Errorf("got %q, want hello", string(got)) } } - -func TestIssue7264(t *testing.T) { - t.Skip("broken test - removed at tip") - for i := 0; i < 1000; i++ { - func() { - inHandler := make(chan bool, 1) - ts := NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - inHandler <- true - })) - defer ts.Close() - tr := &http.Transport{ - ResponseHeaderTimeout: time.Nanosecond, - } - defer tr.CloseIdleConnections() - c := &http.Client{Transport: tr} - res, err := c.Get(ts.URL) - <-inHandler - if err == nil { - res.Body.Close() - } - }() - } -} diff --git a/libgo/go/net/http/httputil/chunked.go b/libgo/go/net/http/httputil/chunked.go deleted file mode 100644 index 9632bfd19d5..00000000000 --- a/libgo/go/net/http/httputil/chunked.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2009 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. - -// The wire protocol for HTTP's "chunked" Transfer-Encoding. - -// This code is duplicated in net/http and net/http/httputil. -// Please make any changes in both files. - -package httputil - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" -) - -const maxLineLength = 4096 // assumed <= bufio.defaultBufSize - -var ErrLineTooLong = errors.New("header line too long") - -// newChunkedReader returns a new chunkedReader that translates the data read from r -// out of HTTP "chunked" format before returning it. -// The chunkedReader returns io.EOF when the final 0-length chunk is read. -// -// newChunkedReader is not needed by normal applications. The http package -// automatically decodes chunking when reading response bodies. -func newChunkedReader(r io.Reader) io.Reader { - br, ok := r.(*bufio.Reader) - if !ok { - br = bufio.NewReader(r) - } - return &chunkedReader{r: br} -} - -type chunkedReader struct { - r *bufio.Reader - n uint64 // unread bytes in chunk - err error - buf [2]byte -} - -func (cr *chunkedReader) beginChunk() { - // chunk-size CRLF - var line []byte - line, cr.err = readLine(cr.r) - if cr.err != nil { - return - } - cr.n, cr.err = parseHexUint(line) - if cr.err != nil { - return - } - if cr.n == 0 { - cr.err = io.EOF - } -} - -func (cr *chunkedReader) chunkHeaderAvailable() bool { - n := cr.r.Buffered() - if n > 0 { - peek, _ := cr.r.Peek(n) - return bytes.IndexByte(peek, '\n') >= 0 - } - return false -} - -func (cr *chunkedReader) Read(b []uint8) (n int, err error) { - for cr.err == nil { - if cr.n == 0 { - if n > 0 && !cr.chunkHeaderAvailable() { - // We've read enough. Don't potentially block - // reading a new chunk header. - break - } - cr.beginChunk() - continue - } - if len(b) == 0 { - break - } - rbuf := b - if uint64(len(rbuf)) > cr.n { - rbuf = rbuf[:cr.n] - } - var n0 int - n0, cr.err = cr.r.Read(rbuf) - n += n0 - b = b[n0:] - cr.n -= uint64(n0) - // If we're at the end of a chunk, read the next two - // bytes to verify they are "\r\n". - if cr.n == 0 && cr.err == nil { - if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err == nil { - if cr.buf[0] != '\r' || cr.buf[1] != '\n' { - cr.err = errors.New("malformed chunked encoding") - } - } - } - } - return n, cr.err -} - -// Read a line of bytes (up to \n) from b. -// Give up if the line exceeds maxLineLength. -// The returned bytes are a pointer into storage in -// the bufio, so they are only valid until the next bufio read. -func readLine(b *bufio.Reader) (p []byte, err error) { - if p, err = b.ReadSlice('\n'); err != nil { - // We always know when EOF is coming. - // If the caller asked for a line, there should be a line. - if err == io.EOF { - err = io.ErrUnexpectedEOF - } else if err == bufio.ErrBufferFull { - err = ErrLineTooLong - } - return nil, err - } - if len(p) >= maxLineLength { - return nil, ErrLineTooLong - } - return trimTrailingWhitespace(p), nil -} - -func trimTrailingWhitespace(b []byte) []byte { - for len(b) > 0 && isASCIISpace(b[len(b)-1]) { - b = b[:len(b)-1] - } - return b -} - -func isASCIISpace(b byte) bool { - return b == ' ' || b == '\t' || b == '\n' || b == '\r' -} - -// newChunkedWriter returns a new chunkedWriter that translates writes into HTTP -// "chunked" format before writing them to w. Closing the returned chunkedWriter -// sends the final 0-length chunk that marks the end of the stream. -// -// newChunkedWriter is not needed by normal applications. The http -// package adds chunking automatically if handlers don't set a -// Content-Length header. Using newChunkedWriter inside a handler -// would result in double chunking or chunking with a Content-Length -// length, both of which are wrong. -func newChunkedWriter(w io.Writer) io.WriteCloser { - return &chunkedWriter{w} -} - -// Writing to chunkedWriter translates to writing in HTTP chunked Transfer -// Encoding wire format to the underlying Wire chunkedWriter. -type chunkedWriter struct { - Wire io.Writer -} - -// Write the contents of data as one chunk to Wire. -// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has -// a bug since it does not check for success of io.WriteString -func (cw *chunkedWriter) Write(data []byte) (n int, err error) { - - // Don't send 0-length data. It looks like EOF for chunked encoding. - if len(data) == 0 { - return 0, nil - } - - if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil { - return 0, err - } - if n, err = cw.Wire.Write(data); err != nil { - return - } - if n != len(data) { - err = io.ErrShortWrite - return - } - _, err = io.WriteString(cw.Wire, "\r\n") - - return -} - -func (cw *chunkedWriter) Close() error { - _, err := io.WriteString(cw.Wire, "0\r\n") - return err -} - -func parseHexUint(v []byte) (n uint64, err error) { - for _, b := range v { - n <<= 4 - switch { - case '0' <= b && b <= '9': - b = b - '0' - case 'a' <= b && b <= 'f': - b = b - 'a' + 10 - case 'A' <= b && b <= 'F': - b = b - 'A' + 10 - default: - return 0, errors.New("invalid byte in chunk length") - } - n |= uint64(b) - } - return -} diff --git a/libgo/go/net/http/httputil/chunked_test.go b/libgo/go/net/http/httputil/chunked_test.go deleted file mode 100644 index a7a57746885..00000000000 --- a/libgo/go/net/http/httputil/chunked_test.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2011 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. - -// This code is duplicated in net/http and net/http/httputil. -// Please make any changes in both files. - -package httputil - -import ( - "bufio" - "bytes" - "fmt" - "io" - "io/ioutil" - "strings" - "testing" -) - -func TestChunk(t *testing.T) { - var b bytes.Buffer - - w := newChunkedWriter(&b) - const chunk1 = "hello, " - const chunk2 = "world! 0123456789abcdef" - w.Write([]byte(chunk1)) - w.Write([]byte(chunk2)) - w.Close() - - if g, e := b.String(), "7\r\nhello, \r\n17\r\nworld! 0123456789abcdef\r\n0\r\n"; g != e { - t.Fatalf("chunk writer wrote %q; want %q", g, e) - } - - r := newChunkedReader(&b) - data, err := ioutil.ReadAll(r) - if err != nil { - t.Logf(`data: "%s"`, data) - t.Fatalf("ReadAll from reader: %v", err) - } - if g, e := string(data), chunk1+chunk2; g != e { - t.Errorf("chunk reader read %q; want %q", g, e) - } -} - -func TestChunkReadMultiple(t *testing.T) { - // Bunch of small chunks, all read together. - { - var b bytes.Buffer - w := newChunkedWriter(&b) - w.Write([]byte("foo")) - w.Write([]byte("bar")) - w.Close() - - r := newChunkedReader(&b) - buf := make([]byte, 10) - n, err := r.Read(buf) - if n != 6 || err != io.EOF { - t.Errorf("Read = %d, %v; want 6, EOF", n, err) - } - buf = buf[:n] - if string(buf) != "foobar" { - t.Errorf("Read = %q; want %q", buf, "foobar") - } - } - - // One big chunk followed by a little chunk, but the small bufio.Reader size - // should prevent the second chunk header from being read. - { - var b bytes.Buffer - w := newChunkedWriter(&b) - // fillBufChunk is 11 bytes + 3 bytes header + 2 bytes footer = 16 bytes, - // the same as the bufio ReaderSize below (the minimum), so even - // though we're going to try to Read with a buffer larger enough to also - // receive "foo", the second chunk header won't be read yet. - const fillBufChunk = "0123456789a" - const shortChunk = "foo" - w.Write([]byte(fillBufChunk)) - w.Write([]byte(shortChunk)) - w.Close() - - r := newChunkedReader(bufio.NewReaderSize(&b, 16)) - buf := make([]byte, len(fillBufChunk)+len(shortChunk)) - n, err := r.Read(buf) - if n != len(fillBufChunk) || err != nil { - t.Errorf("Read = %d, %v; want %d, nil", n, err, len(fillBufChunk)) - } - buf = buf[:n] - if string(buf) != fillBufChunk { - t.Errorf("Read = %q; want %q", buf, fillBufChunk) - } - - n, err = r.Read(buf) - if n != len(shortChunk) || err != io.EOF { - t.Errorf("Read = %d, %v; want %d, EOF", n, err, len(shortChunk)) - } - } - - // And test that we see an EOF chunk, even though our buffer is already full: - { - r := newChunkedReader(bufio.NewReader(strings.NewReader("3\r\nfoo\r\n0\r\n"))) - buf := make([]byte, 3) - n, err := r.Read(buf) - if n != 3 || err != io.EOF { - t.Errorf("Read = %d, %v; want 3, EOF", n, err) - } - if string(buf) != "foo" { - t.Errorf("buf = %q; want foo", buf) - } - } -} - -func TestChunkReaderAllocs(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - var buf bytes.Buffer - w := newChunkedWriter(&buf) - a, b, c := []byte("aaaaaa"), []byte("bbbbbbbbbbbb"), []byte("cccccccccccccccccccccccc") - w.Write(a) - w.Write(b) - w.Write(c) - w.Close() - - readBuf := make([]byte, len(a)+len(b)+len(c)+1) - byter := bytes.NewReader(buf.Bytes()) - bufr := bufio.NewReader(byter) - mallocs := testing.AllocsPerRun(100, func() { - byter.Seek(0, 0) - bufr.Reset(byter) - r := newChunkedReader(bufr) - n, err := io.ReadFull(r, readBuf) - if n != len(readBuf)-1 { - t.Fatalf("read %d bytes; want %d", n, len(readBuf)-1) - } - if err != io.ErrUnexpectedEOF { - t.Fatalf("read error = %v; want ErrUnexpectedEOF", err) - } - }) - if mallocs > 1.5 { - t.Errorf("mallocs = %v; want 1", mallocs) - } -} - -func TestParseHexUint(t *testing.T) { - for i := uint64(0); i <= 1234; i++ { - line := []byte(fmt.Sprintf("%x", i)) - got, err := parseHexUint(line) - if err != nil { - t.Fatalf("on %d: %v", i, err) - } - if got != i { - t.Errorf("for input %q = %d; want %d", line, got, i) - } - } - _, err := parseHexUint([]byte("bogus")) - if err == nil { - t.Error("expected error on bogus input") - } -} diff --git a/libgo/go/net/http/httputil/dump.go b/libgo/go/net/http/httputil/dump.go index 2a7a413d01a..ac8f103f9b9 100644 --- a/libgo/go/net/http/httputil/dump.go +++ b/libgo/go/net/http/httputil/dump.go @@ -95,19 +95,27 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { // with a dummy response. var buf bytes.Buffer // records the output pr, pw := io.Pipe() + defer pr.Close() + defer pw.Close() dr := &delegateReader{c: make(chan io.Reader)} // Wait for the request before replying with a dummy response: go func() { - http.ReadRequest(bufio.NewReader(pr)) + req, err := http.ReadRequest(bufio.NewReader(pr)) + if err == nil { + // Ensure all the body is read; otherwise + // we'll get a partial dump. + io.Copy(ioutil.Discard, req.Body) + req.Body.Close() + } dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n") }() t := &http.Transport{ + DisableKeepAlives: true, Dial: func(net, addr string) (net.Conn, error) { return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil }, } - defer t.CloseIdleConnections() _, err := t.RoundTrip(reqSend) diff --git a/libgo/go/net/http/httputil/dump_test.go b/libgo/go/net/http/httputil/dump_test.go index e1ffb3935ac..024ee5a86f4 100644 --- a/libgo/go/net/http/httputil/dump_test.go +++ b/libgo/go/net/http/httputil/dump_test.go @@ -111,6 +111,30 @@ var dumpTests = []dumpTest{ NoBody: true, }, + + // Request with Body > 8196 (default buffer size) + { + Req: http.Request{ + Method: "POST", + URL: &url.URL{ + Scheme: "http", + Host: "post.tld", + Path: "/", + }, + ContentLength: 8193, + ProtoMajor: 1, + ProtoMinor: 1, + }, + + Body: bytes.Repeat([]byte("a"), 8193), + + WantDumpOut: "POST / HTTP/1.1\r\n" + + "Host: post.tld\r\n" + + "User-Agent: Go 1.1 package http\r\n" + + "Content-Length: 8193\r\n" + + "Accept-Encoding: gzip\r\n\r\n" + + strings.Repeat("a", 8193), + }, } func TestDumpRequest(t *testing.T) { @@ -125,6 +149,8 @@ func TestDumpRequest(t *testing.T) { tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b)) case func() io.ReadCloser: tt.Req.Body = b() + default: + t.Fatalf("Test %d: unsupported Body of %T", i, tt.Body) } } setBody() @@ -159,7 +185,9 @@ func TestDumpRequest(t *testing.T) { } } if dg := runtime.NumGoroutine() - numg0; dg > 4 { - t.Errorf("Unexpectedly large number of new goroutines: %d new", dg) + buf := make([]byte, 4096) + buf = buf[:runtime.Stack(buf, true)] + t.Errorf("Unexpectedly large number of new goroutines: %d new: %s", dg, buf) } } diff --git a/libgo/go/net/http/httputil/httputil.go b/libgo/go/net/http/httputil/httputil.go index 74fb6c6556f..2e523e9e269 100644 --- a/libgo/go/net/http/httputil/httputil.go +++ b/libgo/go/net/http/httputil/httputil.go @@ -6,7 +6,10 @@ // more common ones in the net/http package. package httputil -import "io" +import ( + "io" + "net/http/internal" +) // NewChunkedReader returns a new chunkedReader that translates the data read from r // out of HTTP "chunked" format before returning it. @@ -15,7 +18,7 @@ import "io" // NewChunkedReader is not needed by normal applications. The http package // automatically decodes chunking when reading response bodies. func NewChunkedReader(r io.Reader) io.Reader { - return newChunkedReader(r) + return internal.NewChunkedReader(r) } // NewChunkedWriter returns a new chunkedWriter that translates writes into HTTP @@ -28,5 +31,9 @@ func NewChunkedReader(r io.Reader) io.Reader { // would result in double chunking or chunking with a Content-Length // length, both of which are wrong. func NewChunkedWriter(w io.Writer) io.WriteCloser { - return newChunkedWriter(w) + return internal.NewChunkedWriter(w) } + +// ErrLineTooLong is returned when reading malformed chunked data +// with lines that are too long. +var ErrLineTooLong = internal.ErrLineTooLong diff --git a/libgo/go/net/http/httputil/reverseproxy.go b/libgo/go/net/http/httputil/reverseproxy.go index 48ada5f5fdb..ab463701803 100644 --- a/libgo/go/net/http/httputil/reverseproxy.go +++ b/libgo/go/net/http/httputil/reverseproxy.go @@ -40,6 +40,12 @@ type ReverseProxy struct { // response body. // If zero, no periodic flushing is done. FlushInterval time.Duration + + // ErrorLog specifies an optional logger for errors + // that occur when attempting to proxy the request. + // If nil, logging goes to os.Stderr via the log package's + // standard logger. + ErrorLog *log.Logger } func singleJoiningSlash(a, b string) string { @@ -138,7 +144,7 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { res, err := transport.RoundTrip(outreq) if err != nil { - log.Printf("http: proxy error: %v", err) + p.logf("http: proxy error: %v", err) rw.WriteHeader(http.StatusInternalServerError) return } @@ -171,6 +177,14 @@ func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) { io.Copy(dst, src) } +func (p *ReverseProxy) logf(format string, args ...interface{}) { + if p.ErrorLog != nil { + p.ErrorLog.Printf(format, args...) + } else { + log.Printf(format, args...) + } +} + type writeFlusher interface { io.Writer http.Flusher diff --git a/libgo/go/net/http/chunked.go b/libgo/go/net/http/internal/chunked.go index 749f29d3269..9294deb3e5e 100644 --- a/libgo/go/net/http/chunked.go +++ b/libgo/go/net/http/internal/chunked.go @@ -4,10 +4,9 @@ // The wire protocol for HTTP's "chunked" Transfer-Encoding. -// This code is duplicated in net/http and net/http/httputil. -// Please make any changes in both files. - -package http +// Package internal contains HTTP internals shared by net/http and +// net/http/httputil. +package internal import ( "bufio" @@ -21,13 +20,13 @@ const maxLineLength = 4096 // assumed <= bufio.defaultBufSize var ErrLineTooLong = errors.New("header line too long") -// newChunkedReader returns a new chunkedReader that translates the data read from r +// NewChunkedReader returns a new chunkedReader that translates the data read from r // out of HTTP "chunked" format before returning it. // The chunkedReader returns io.EOF when the final 0-length chunk is read. // -// newChunkedReader is not needed by normal applications. The http package +// NewChunkedReader is not needed by normal applications. The http package // automatically decodes chunking when reading response bodies. -func newChunkedReader(r io.Reader) io.Reader { +func NewChunkedReader(r io.Reader) io.Reader { br, ok := r.(*bufio.Reader) if !ok { br = bufio.NewReader(r) @@ -135,16 +134,16 @@ func isASCIISpace(b byte) bool { return b == ' ' || b == '\t' || b == '\n' || b == '\r' } -// newChunkedWriter returns a new chunkedWriter that translates writes into HTTP +// NewChunkedWriter returns a new chunkedWriter that translates writes into HTTP // "chunked" format before writing them to w. Closing the returned chunkedWriter // sends the final 0-length chunk that marks the end of the stream. // -// newChunkedWriter is not needed by normal applications. The http +// NewChunkedWriter is not needed by normal applications. The http // package adds chunking automatically if handlers don't set a // Content-Length header. Using newChunkedWriter inside a handler // would result in double chunking or chunking with a Content-Length // length, both of which are wrong. -func newChunkedWriter(w io.Writer) io.WriteCloser { +func NewChunkedWriter(w io.Writer) io.WriteCloser { return &chunkedWriter{w} } diff --git a/libgo/go/net/http/chunked_test.go b/libgo/go/net/http/internal/chunked_test.go index 34544790aff..ebc626ea9d0 100644 --- a/libgo/go/net/http/chunked_test.go +++ b/libgo/go/net/http/internal/chunked_test.go @@ -2,10 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This code is duplicated in net/http and net/http/httputil. -// Please make any changes in both files. - -package http +package internal import ( "bufio" @@ -20,7 +17,7 @@ import ( func TestChunk(t *testing.T) { var b bytes.Buffer - w := newChunkedWriter(&b) + w := NewChunkedWriter(&b) const chunk1 = "hello, " const chunk2 = "world! 0123456789abcdef" w.Write([]byte(chunk1)) @@ -31,7 +28,7 @@ func TestChunk(t *testing.T) { t.Fatalf("chunk writer wrote %q; want %q", g, e) } - r := newChunkedReader(&b) + r := NewChunkedReader(&b) data, err := ioutil.ReadAll(r) if err != nil { t.Logf(`data: "%s"`, data) @@ -46,12 +43,12 @@ func TestChunkReadMultiple(t *testing.T) { // Bunch of small chunks, all read together. { var b bytes.Buffer - w := newChunkedWriter(&b) + w := NewChunkedWriter(&b) w.Write([]byte("foo")) w.Write([]byte("bar")) w.Close() - r := newChunkedReader(&b) + r := NewChunkedReader(&b) buf := make([]byte, 10) n, err := r.Read(buf) if n != 6 || err != io.EOF { @@ -67,7 +64,7 @@ func TestChunkReadMultiple(t *testing.T) { // should prevent the second chunk header from being read. { var b bytes.Buffer - w := newChunkedWriter(&b) + w := NewChunkedWriter(&b) // fillBufChunk is 11 bytes + 3 bytes header + 2 bytes footer = 16 bytes, // the same as the bufio ReaderSize below (the minimum), so even // though we're going to try to Read with a buffer larger enough to also @@ -78,7 +75,7 @@ func TestChunkReadMultiple(t *testing.T) { w.Write([]byte(shortChunk)) w.Close() - r := newChunkedReader(bufio.NewReaderSize(&b, 16)) + r := NewChunkedReader(bufio.NewReaderSize(&b, 16)) buf := make([]byte, len(fillBufChunk)+len(shortChunk)) n, err := r.Read(buf) if n != len(fillBufChunk) || err != nil { @@ -97,7 +94,7 @@ func TestChunkReadMultiple(t *testing.T) { // And test that we see an EOF chunk, even though our buffer is already full: { - r := newChunkedReader(bufio.NewReader(strings.NewReader("3\r\nfoo\r\n0\r\n"))) + r := NewChunkedReader(bufio.NewReader(strings.NewReader("3\r\nfoo\r\n0\r\n"))) buf := make([]byte, 3) n, err := r.Read(buf) if n != 3 || err != io.EOF { @@ -114,7 +111,7 @@ func TestChunkReaderAllocs(t *testing.T) { t.Skip("skipping in short mode") } var buf bytes.Buffer - w := newChunkedWriter(&buf) + w := NewChunkedWriter(&buf) a, b, c := []byte("aaaaaa"), []byte("bbbbbbbbbbbb"), []byte("cccccccccccccccccccccccc") w.Write(a) w.Write(b) @@ -127,7 +124,7 @@ func TestChunkReaderAllocs(t *testing.T) { mallocs := testing.AllocsPerRun(100, func() { byter.Seek(0, 0) bufr.Reset(byter) - r := newChunkedReader(bufr) + r := NewChunkedReader(bufr) n, err := io.ReadFull(r, readBuf) if n != len(readBuf)-1 { t.Fatalf("read %d bytes; want %d", n, len(readBuf)-1) diff --git a/libgo/go/net/http/z_last_test.go b/libgo/go/net/http/main_test.go index 5a0cc119849..b8c71fd19fd 100644 --- a/libgo/go/net/http/z_last_test.go +++ b/libgo/go/net/http/main_test.go @@ -5,7 +5,9 @@ package http_test import ( + "fmt" "net/http" + "os" "runtime" "sort" "strings" @@ -13,6 +15,14 @@ import ( "time" ) +func TestMain(m *testing.M) { + v := m.Run() + if v == 0 && goroutineLeaked() { + os.Exit(1) + } + os.Exit(v) +} + func interestingGoroutines() (gs []string) { buf := make([]byte, 2<<20) buf = buf[:runtime.Stack(buf, true)] @@ -30,6 +40,7 @@ func interestingGoroutines() (gs []string) { // These only show up with GOTRACEBACK=2; Issue 5005 (comment 28) strings.Contains(stack, "runtime.goexit") || strings.Contains(stack, "created by runtime.gc") || + strings.Contains(stack, "net/http_test.interestingGoroutines") || strings.Contains(stack, "runtime.MHeap_Scavenger") { continue } @@ -40,10 +51,10 @@ func interestingGoroutines() (gs []string) { } // Verify the other tests didn't leave any goroutines running. -// This is in a file named z_last_test.go so it sorts at the end. -func TestGoroutinesRunning(t *testing.T) { +func goroutineLeaked() bool { if testing.Short() { - t.Skip("not counting goroutines for leakage in -short mode") + // not counting goroutines for leakage in -short mode + return false } gs := interestingGoroutines() @@ -54,13 +65,14 @@ func TestGoroutinesRunning(t *testing.T) { n++ } - t.Logf("num goroutines = %d", n) - if n > 0 { - t.Error("Too many goroutines.") - for stack, count := range stackCount { - t.Logf("%d instances of:\n%s", count, stack) - } + if n == 0 { + return false + } + fmt.Fprintf(os.Stderr, "Too many goroutines running after net/http test(s).\n") + for stack, count := range stackCount { + fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack) } + return true } func afterTest(t *testing.T) { diff --git a/libgo/go/net/http/pprof/pprof.go b/libgo/go/net/http/pprof/pprof.go index 0c7548e3ef3..a23f1bc4bc6 100644 --- a/libgo/go/net/http/pprof/pprof.go +++ b/libgo/go/net/http/pprof/pprof.go @@ -162,6 +162,10 @@ func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Unknown profile: %s\n", name) return } + gc, _ := strconv.Atoi(r.FormValue("gc")) + if name == "heap" && gc > 0 { + runtime.GC() + } p.WriteTo(w, debug) return } diff --git a/libgo/go/net/http/readrequest_test.go b/libgo/go/net/http/readrequest_test.go index ffdd6a892da..e930d99af62 100644 --- a/libgo/go/net/http/readrequest_test.go +++ b/libgo/go/net/http/readrequest_test.go @@ -11,6 +11,7 @@ import ( "io" "net/url" "reflect" + "strings" "testing" ) @@ -295,14 +296,39 @@ var reqTests = []reqTest{ noTrailer, noError, }, + + // Connection: close. golang.org/issue/8261 + { + "GET / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\n\r\n", + &Request{ + Method: "GET", + URL: &url.URL{ + Path: "/", + }, + Header: Header{ + // This wasn't removed from Go 1.0 to + // Go 1.3, so locking it in that we + // keep this: + "Connection": []string{"close"}, + }, + Host: "issue8261.com", + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Close: true, + RequestURI: "/", + }, + + noBody, + noTrailer, + noError, + }, } func TestReadRequest(t *testing.T) { for i := range reqTests { tt := &reqTests[i] - var braw bytes.Buffer - braw.WriteString(tt.Raw) - req, err := ReadRequest(bufio.NewReader(&braw)) + req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.Raw))) if err != nil { if err.Error() != tt.Error { t.Errorf("#%d: error %q, want error %q", i, err.Error(), tt.Error) @@ -311,21 +337,22 @@ func TestReadRequest(t *testing.T) { } rbody := req.Body req.Body = nil - diff(t, fmt.Sprintf("#%d Request", i), req, tt.Req) + testName := fmt.Sprintf("Test %d (%q)", i, tt.Raw) + diff(t, testName, req, tt.Req) var bout bytes.Buffer if rbody != nil { _, err := io.Copy(&bout, rbody) if err != nil { - t.Fatalf("#%d. copying body: %v", i, err) + t.Fatalf("%s: copying body: %v", testName, err) } rbody.Close() } body := bout.String() if body != tt.Body { - t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) + t.Errorf("%s: Body = %q want %q", testName, body, tt.Body) } if !reflect.DeepEqual(tt.Trailer, req.Trailer) { - t.Errorf("#%d. Trailers differ.\n got: %v\nwant: %v", i, req.Trailer, tt.Trailer) + t.Errorf("%s: Trailers differ.\n got: %v\nwant: %v", testName, req.Trailer, tt.Trailer) } } } diff --git a/libgo/go/net/http/request.go b/libgo/go/net/http/request.go index a67092066ad..487eebcb841 100644 --- a/libgo/go/net/http/request.go +++ b/libgo/go/net/http/request.go @@ -10,6 +10,7 @@ import ( "bufio" "bytes" "crypto/tls" + "encoding/base64" "errors" "fmt" "io" @@ -390,10 +391,16 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) err w = bw } - fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri) + _, err := fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri) + if err != nil { + return err + } // Header lines - fmt.Fprintf(w, "Host: %s\r\n", host) + _, err = fmt.Fprintf(w, "Host: %s\r\n", host) + if err != nil { + return err + } // Use the defaultUserAgent unless the Header contains one, which // may be blank to not send the header. @@ -404,7 +411,10 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) err } } if userAgent != "" { - fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent) + _, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent) + if err != nil { + return err + } } // Process Body,ContentLength,Close,Trailer @@ -429,7 +439,10 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) err } } - io.WriteString(w, "\r\n") + _, err = io.WriteString(w, "\r\n") + if err != nil { + return err + } // Write body and trailer err = tw.WriteBody(w) @@ -509,6 +522,35 @@ func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { return req, nil } +// BasicAuth returns the username and password provided in the request's +// Authorization header, if the request uses HTTP Basic Authentication. +// See RFC 2617, Section 2. +func (r *Request) BasicAuth() (username, password string, ok bool) { + auth := r.Header.Get("Authorization") + if auth == "" { + return + } + return parseBasicAuth(auth) +} + +// parseBasicAuth parses an HTTP Basic Authentication string. +// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). +func parseBasicAuth(auth string) (username, password string, ok bool) { + if !strings.HasPrefix(auth, "Basic ") { + return + } + c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic ")) + if err != nil { + return + } + cs := string(c) + s := strings.IndexByte(cs, ':') + if s < 0 { + return + } + return cs[:s], cs[s+1:], true +} + // SetBasicAuth sets the request's Authorization header to use HTTP // Basic Authentication with the provided username and password. // @@ -623,6 +665,7 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) { return nil, err } + req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false) return req, nil } @@ -807,8 +850,10 @@ func (r *Request) ParseMultipartForm(maxMemory int64) error { // FormValue returns the first value for the named component of the query. // POST and PUT body parameters take precedence over URL query string values. -// FormValue calls ParseMultipartForm and ParseForm if necessary. -// To access multiple values of the same key use ParseForm. +// FormValue calls ParseMultipartForm and ParseForm if necessary and ignores +// any errors returned by these functions. +// To access multiple values of the same key, call ParseForm and +// then inspect Request.Form directly. func (r *Request) FormValue(key string) string { if r.Form == nil { r.ParseMultipartForm(defaultMaxMemory) @@ -821,7 +866,8 @@ func (r *Request) FormValue(key string) string { // PostFormValue returns the first value for the named component of the POST // or PUT request body. URL query parameters are ignored. -// PostFormValue calls ParseMultipartForm and ParseForm if necessary. +// PostFormValue calls ParseMultipartForm and ParseForm if necessary and ignores +// any errors returned by these functions. func (r *Request) PostFormValue(key string) string { if r.PostForm == nil { r.ParseMultipartForm(defaultMaxMemory) diff --git a/libgo/go/net/http/request_test.go b/libgo/go/net/http/request_test.go index b9fa3c2bfc4..759ea4e8b5d 100644 --- a/libgo/go/net/http/request_test.go +++ b/libgo/go/net/http/request_test.go @@ -7,6 +7,7 @@ package http_test import ( "bufio" "bytes" + "encoding/base64" "fmt" "io" "io/ioutil" @@ -396,6 +397,75 @@ func TestParseHTTPVersion(t *testing.T) { } } +type getBasicAuthTest struct { + username, password string + ok bool +} + +type parseBasicAuthTest getBasicAuthTest + +type basicAuthCredentialsTest struct { + username, password string +} + +var getBasicAuthTests = []struct { + username, password string + ok bool +}{ + {"Aladdin", "open sesame", true}, + {"Aladdin", "open:sesame", true}, + {"", "", true}, +} + +func TestGetBasicAuth(t *testing.T) { + for _, tt := range getBasicAuthTests { + r, _ := NewRequest("GET", "http://example.com/", nil) + r.SetBasicAuth(tt.username, tt.password) + username, password, ok := r.BasicAuth() + if ok != tt.ok || username != tt.username || password != tt.password { + t.Errorf("BasicAuth() = %#v, want %#v", getBasicAuthTest{username, password, ok}, + getBasicAuthTest{tt.username, tt.password, tt.ok}) + } + } + // Unauthenticated request. + r, _ := NewRequest("GET", "http://example.com/", nil) + username, password, ok := r.BasicAuth() + if ok { + t.Errorf("expected false from BasicAuth when the request is unauthenticated") + } + want := basicAuthCredentialsTest{"", ""} + if username != want.username || password != want.password { + t.Errorf("expected credentials: %#v when the request is unauthenticated, got %#v", + want, basicAuthCredentialsTest{username, password}) + } +} + +var parseBasicAuthTests = []struct { + header, username, password string + ok bool +}{ + {"Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "Aladdin", "open sesame", true}, + {"Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open:sesame")), "Aladdin", "open:sesame", true}, + {"Basic " + base64.StdEncoding.EncodeToString([]byte(":")), "", "", true}, + {"Basic" + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "", "", false}, + {base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "", "", false}, + {"Basic ", "", "", false}, + {"Basic Aladdin:open sesame", "", "", false}, + {`Digest username="Aladdin"`, "", "", false}, +} + +func TestParseBasicAuth(t *testing.T) { + for _, tt := range parseBasicAuthTests { + r, _ := NewRequest("GET", "http://example.com/", nil) + r.Header.Set("Authorization", tt.header) + username, password, ok := r.BasicAuth() + if ok != tt.ok || username != tt.username || password != tt.password { + t.Errorf("BasicAuth() = %#v, want %#v", getBasicAuthTest{username, password, ok}, + getBasicAuthTest{tt.username, tt.password, tt.ok}) + } + } +} + type logWrites struct { t *testing.T dst *[]string diff --git a/libgo/go/net/http/requestwrite_test.go b/libgo/go/net/http/requestwrite_test.go index dc0e204cac9..7a6bd587863 100644 --- a/libgo/go/net/http/requestwrite_test.go +++ b/libgo/go/net/http/requestwrite_test.go @@ -280,7 +280,7 @@ var reqWriteTests = []reqWriteTest{ ContentLength: 10, // but we're going to send only 5 bytes }, Body: []byte("12345"), - WantError: errors.New("http: Request.ContentLength=10 with Body length 5"), + WantError: errors.New("http: ContentLength=10 with Body length 5"), }, // Request with a ContentLength of 4 but an 8 byte body. @@ -294,7 +294,7 @@ var reqWriteTests = []reqWriteTest{ ContentLength: 4, // but we're going to try to send 8 bytes }, Body: []byte("12345678"), - WantError: errors.New("http: Request.ContentLength=4 with Body length 8"), + WantError: errors.New("http: ContentLength=4 with Body length 8"), }, // Request with a 5 ContentLength and nil body. @@ -563,3 +563,61 @@ func mustParseURL(s string) *url.URL { } return u } + +type writerFunc func([]byte) (int, error) + +func (f writerFunc) Write(p []byte) (int, error) { return f(p) } + +// TestRequestWriteError tests the Write err != nil checks in (*Request).write. +func TestRequestWriteError(t *testing.T) { + failAfter, writeCount := 0, 0 + errFail := errors.New("fake write failure") + + // w is the buffered io.Writer to write the request to. It + // fails exactly once on its Nth Write call, as controlled by + // failAfter. It also tracks the number of calls in + // writeCount. + w := struct { + io.ByteWriter // to avoid being wrapped by a bufio.Writer + io.Writer + }{ + nil, + writerFunc(func(p []byte) (n int, err error) { + writeCount++ + if failAfter == 0 { + err = errFail + } + failAfter-- + return len(p), err + }), + } + + req, _ := NewRequest("GET", "http://example.com/", nil) + const writeCalls = 4 // number of Write calls in current implementation + sawGood := false + for n := 0; n <= writeCalls+2; n++ { + failAfter = n + writeCount = 0 + err := req.Write(w) + var wantErr error + if n < writeCalls { + wantErr = errFail + } + if err != wantErr { + t.Errorf("for fail-after %d Writes, err = %v; want %v", n, err, wantErr) + continue + } + if err == nil { + sawGood = true + if writeCount != writeCalls { + t.Fatalf("writeCalls constant is outdated in test") + } + } + if writeCount > writeCalls || writeCount > n+1 { + t.Errorf("for fail-after %d, saw unexpectedly high (%d) write calls", n, writeCount) + } + } + if !sawGood { + t.Fatalf("writeCalls constant is outdated in test") + } +} diff --git a/libgo/go/net/http/response_test.go b/libgo/go/net/http/response_test.go index 4b8946f7ae4..06e940d9aba 100644 --- a/libgo/go/net/http/response_test.go +++ b/libgo/go/net/http/response_test.go @@ -12,6 +12,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http/internal" "net/url" "reflect" "regexp" @@ -376,6 +377,34 @@ some body`, "Body here\n", }, + + // 206 Partial Content. golang.org/issue/8923 + { + "HTTP/1.1 206 Partial Content\r\n" + + "Content-Type: text/plain; charset=utf-8\r\n" + + "Accept-Ranges: bytes\r\n" + + "Content-Range: bytes 0-5/1862\r\n" + + "Content-Length: 6\r\n\r\n" + + "foobar", + + Response{ + Status: "206 Partial Content", + StatusCode: 206, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq("GET"), + Header: Header{ + "Accept-Ranges": []string{"bytes"}, + "Content-Length": []string{"6"}, + "Content-Type": []string{"text/plain; charset=utf-8"}, + "Content-Range": []string{"bytes 0-5/1862"}, + }, + ContentLength: 6, + }, + + "foobar", + }, } func TestReadResponse(t *testing.T) { @@ -451,7 +480,7 @@ func TestReadResponseCloseInMiddle(t *testing.T) { } var wr io.Writer = &buf if test.chunked { - wr = newChunkedWriter(wr) + wr = internal.NewChunkedWriter(wr) } if test.compressed { buf.WriteString("Content-Encoding: gzip\r\n") diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go index 8371dd82f58..6bd168d3de3 100644 --- a/libgo/go/net/http/serve_test.go +++ b/libgo/go/net/http/serve_test.go @@ -15,6 +15,7 @@ import ( "io" "io/ioutil" "log" + "math/rand" "net" . "net/http" "net/http/httptest" @@ -777,6 +778,35 @@ func TestChunkedResponseHeaders(t *testing.T) { } } +func TestIdentityResponseHeaders(t *testing.T) { + defer afterTest(t) + log.SetOutput(ioutil.Discard) // is noisy otherwise + defer log.SetOutput(os.Stderr) + + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header().Set("Transfer-Encoding", "identity") + w.(Flusher).Flush() + fmt.Fprintf(w, "I am an identity response.") + })) + defer ts.Close() + + res, err := Get(ts.URL) + if err != nil { + t.Fatalf("Get error: %v", err) + } + defer res.Body.Close() + + if g, e := res.TransferEncoding, []string(nil); !reflect.DeepEqual(g, e) { + t.Errorf("expected TransferEncoding of %v; got %v", e, g) + } + if _, haveCL := res.Header["Content-Length"]; haveCL { + t.Errorf("Unexpected Content-Length") + } + if !res.Close { + t.Errorf("expected Connection: close; got %v", res.Close) + } +} + // Test304Responses verifies that 304s don't declare that they're // chunking in their response headers and aren't allowed to produce // output. @@ -1188,6 +1218,82 @@ func TestTimeoutHandler(t *testing.T) { } } +// See issues 8209 and 8414. +func TestTimeoutHandlerRace(t *testing.T) { + defer afterTest(t) + + delayHi := HandlerFunc(func(w ResponseWriter, r *Request) { + ms, _ := strconv.Atoi(r.URL.Path[1:]) + if ms == 0 { + ms = 1 + } + for i := 0; i < ms; i++ { + w.Write([]byte("hi")) + time.Sleep(time.Millisecond) + } + }) + + ts := httptest.NewServer(TimeoutHandler(delayHi, 20*time.Millisecond, "")) + defer ts.Close() + + var wg sync.WaitGroup + gate := make(chan bool, 10) + n := 50 + if testing.Short() { + n = 10 + gate = make(chan bool, 3) + } + for i := 0; i < n; i++ { + gate <- true + wg.Add(1) + go func() { + defer wg.Done() + defer func() { <-gate }() + res, err := Get(fmt.Sprintf("%s/%d", ts.URL, rand.Intn(50))) + if err == nil { + io.Copy(ioutil.Discard, res.Body) + res.Body.Close() + } + }() + } + wg.Wait() +} + +// See issues 8209 and 8414. +func TestTimeoutHandlerRaceHeader(t *testing.T) { + defer afterTest(t) + + delay204 := HandlerFunc(func(w ResponseWriter, r *Request) { + w.WriteHeader(204) + }) + + ts := httptest.NewServer(TimeoutHandler(delay204, time.Nanosecond, "")) + defer ts.Close() + + var wg sync.WaitGroup + gate := make(chan bool, 50) + n := 500 + if testing.Short() { + n = 10 + } + for i := 0; i < n; i++ { + gate <- true + wg.Add(1) + go func() { + defer wg.Done() + defer func() { <-gate }() + res, err := Get(ts.URL) + if err != nil { + t.Error(err) + return + } + defer res.Body.Close() + io.Copy(ioutil.Discard, res.Body) + }() + } + wg.Wait() +} + // Verifies we don't path.Clean() on the wrong parts in redirects. func TestRedirectMunging(t *testing.T) { req, _ := NewRequest("GET", "http://example.com/", nil) @@ -2405,13 +2511,13 @@ func TestServerConnState(t *testing.T) { } want := map[int][]ConnState{ - 1: []ConnState{StateNew, StateActive, StateIdle, StateActive, StateClosed}, - 2: []ConnState{StateNew, StateActive, StateIdle, StateActive, StateClosed}, - 3: []ConnState{StateNew, StateActive, StateHijacked}, - 4: []ConnState{StateNew, StateActive, StateHijacked}, - 5: []ConnState{StateNew, StateClosed}, - 6: []ConnState{StateNew, StateActive, StateClosed}, - 7: []ConnState{StateNew, StateActive, StateIdle, StateClosed}, + 1: {StateNew, StateActive, StateIdle, StateActive, StateClosed}, + 2: {StateNew, StateActive, StateIdle, StateActive, StateClosed}, + 3: {StateNew, StateActive, StateHijacked}, + 4: {StateNew, StateActive, StateHijacked}, + 5: {StateNew, StateClosed}, + 6: {StateNew, StateActive, StateClosed}, + 7: {StateNew, StateActive, StateIdle, StateClosed}, } logString := func(m map[int][]ConnState) string { var b bytes.Buffer @@ -2531,6 +2637,126 @@ func TestServerConnStateNew(t *testing.T) { } } +type closeWriteTestConn struct { + rwTestConn + didCloseWrite bool +} + +func (c *closeWriteTestConn) CloseWrite() error { + c.didCloseWrite = true + return nil +} + +func TestCloseWrite(t *testing.T) { + var srv Server + var testConn closeWriteTestConn + c, err := ExportServerNewConn(&srv, &testConn) + if err != nil { + t.Fatal(err) + } + ExportCloseWriteAndWait(c) + if !testConn.didCloseWrite { + t.Error("didn't see CloseWrite call") + } +} + +// This verifies that a handler can Flush and then Hijack. +// +// An similar test crashed once during development, but it was only +// testing this tangentially and temporarily until another TODO was +// fixed. +// +// So add an explicit test for this. +func TestServerFlushAndHijack(t *testing.T) { + defer afterTest(t) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + io.WriteString(w, "Hello, ") + w.(Flusher).Flush() + conn, buf, _ := w.(Hijacker).Hijack() + buf.WriteString("6\r\nworld!\r\n0\r\n\r\n") + if err := buf.Flush(); err != nil { + t.Error(err) + } + if err := conn.Close(); err != nil { + t.Error(err) + } + })) + defer ts.Close() + res, err := Get(ts.URL) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + all, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + if want := "Hello, world!"; string(all) != want { + t.Errorf("Got %q; want %q", all, want) + } +} + +// golang.org/issue/8534 -- the Server shouldn't reuse a connection +// for keep-alive after it's seen any Write error (e.g. a timeout) on +// that net.Conn. +// +// To test, verify we don't timeout or see fewer unique client +// addresses (== unique connections) than requests. +func TestServerKeepAliveAfterWriteError(t *testing.T) { + if testing.Short() { + t.Skip("skipping in -short mode") + } + defer afterTest(t) + const numReq = 3 + addrc := make(chan string, numReq) + ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) { + addrc <- r.RemoteAddr + time.Sleep(500 * time.Millisecond) + w.(Flusher).Flush() + })) + ts.Config.WriteTimeout = 250 * time.Millisecond + ts.Start() + defer ts.Close() + + errc := make(chan error, numReq) + go func() { + defer close(errc) + for i := 0; i < numReq; i++ { + res, err := Get(ts.URL) + if res != nil { + res.Body.Close() + } + errc <- err + } + }() + + timeout := time.NewTimer(numReq * 2 * time.Second) // 4x overkill + defer timeout.Stop() + addrSeen := map[string]bool{} + numOkay := 0 + for { + select { + case v := <-addrc: + addrSeen[v] = true + case err, ok := <-errc: + if !ok { + if len(addrSeen) != numReq { + t.Errorf("saw %d unique client addresses; want %d", len(addrSeen), numReq) + } + if numOkay != 0 { + t.Errorf("got %d successful client requests; want 0", numOkay) + } + return + } + if err == nil { + numOkay++ + } + case <-timeout.C: + t.Fatal("timeout waiting for requests to complete") + } + } +} + func BenchmarkClientServer(b *testing.B) { b.ReportAllocs() b.StopTimer() @@ -2560,24 +2786,44 @@ func BenchmarkClientServer(b *testing.B) { } func BenchmarkClientServerParallel4(b *testing.B) { - benchmarkClientServerParallel(b, 4) + benchmarkClientServerParallel(b, 4, false) } func BenchmarkClientServerParallel64(b *testing.B) { - benchmarkClientServerParallel(b, 64) + benchmarkClientServerParallel(b, 64, false) } -func benchmarkClientServerParallel(b *testing.B, parallelism int) { +func BenchmarkClientServerParallelTLS4(b *testing.B) { + benchmarkClientServerParallel(b, 4, true) +} + +func BenchmarkClientServerParallelTLS64(b *testing.B) { + benchmarkClientServerParallel(b, 64, true) +} + +func benchmarkClientServerParallel(b *testing.B, parallelism int, useTLS bool) { b.ReportAllocs() - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { + ts := httptest.NewUnstartedServer(HandlerFunc(func(rw ResponseWriter, r *Request) { fmt.Fprintf(rw, "Hello world.\n") })) + if useTLS { + ts.StartTLS() + } else { + ts.Start() + } defer ts.Close() b.ResetTimer() b.SetParallelism(parallelism) b.RunParallel(func(pb *testing.PB) { + noVerifyTransport := &Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + defer noVerifyTransport.CloseIdleConnections() + client := &Client{Transport: noVerifyTransport} for pb.Next() { - res, err := Get(ts.URL) + res, err := client.Get(ts.URL) if err != nil { b.Logf("Get: %v", err) continue diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go index eae097eb8e9..008d5aa7a74 100644 --- a/libgo/go/net/http/server.go +++ b/libgo/go/net/http/server.go @@ -42,6 +42,12 @@ var ( // and then return. Returning signals that the request is finished // and that the HTTP server can move on to the next request on // the connection. +// +// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes +// that the effect of the panic was isolated to the active request. +// It recovers the panic, logs a stack trace to the server error log, +// and hangs up the connection. +// type Handler interface { ServeHTTP(ResponseWriter, *Request) } @@ -108,6 +114,8 @@ type conn struct { remoteAddr string // network address of remote side server *Server // the Server on which the connection arrived rwc net.Conn // i/o connection + w io.Writer // checkConnErrorWriter's copy of wrc, not zeroed on Hijack + werr error // any errors writing to w sr liveSwitchReader // where the LimitReader reads from; usually the rwc lr *io.LimitedReader // io.LimitReader(sr) buf *bufio.ReadWriter // buffered(lr,rwc), reading from bufio->limitReader->sr->rwc @@ -426,13 +434,14 @@ func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) { c.remoteAddr = rwc.RemoteAddr().String() c.server = srv c.rwc = rwc + c.w = rwc if debugServerConnections { c.rwc = newLoggingConn("server", c.rwc) } c.sr = liveSwitchReader{r: c.rwc} c.lr = io.LimitReader(&c.sr, noLimit).(*io.LimitedReader) br := newBufioReader(c.lr) - bw := newBufioWriterSize(c.rwc, 4<<10) + bw := newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) c.buf = bufio.NewReadWriter(br, bw) return c, nil } @@ -833,13 +842,20 @@ func (cw *chunkWriter) writeHeader(p []byte) { } else if hasCL { delHeader("Transfer-Encoding") } else if w.req.ProtoAtLeast(1, 1) { - // HTTP/1.1 or greater: use chunked transfer encoding - // to avoid closing the connection at EOF. - // TODO: this blows away any custom or stacked Transfer-Encoding they - // might have set. Deal with that as need arises once we have a valid - // use case. - cw.chunking = true - setHeader.transferEncoding = "chunked" + // HTTP/1.1 or greater: Transfer-Encoding has been set to identity, and no + // content-length has been provided. The connection must be closed after the + // reply is written, and no chunking is to be done. This is the setup + // recommended in the Server-Sent Events candidate recommendation 11, + // section 8. + if hasTE && te == "identity" { + cw.chunking = false + w.closeAfterReply = true + } else { + // HTTP/1.1 or greater: use chunked transfer encoding + // to avoid closing the connection at EOF. + cw.chunking = true + setHeader.transferEncoding = "chunked" + } } else { // HTTP version < 1.1: cannot do chunked transfer // encoding and we don't know the Content-Length so @@ -943,8 +959,10 @@ func (w *response) bodyAllowed() bool { // 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 -// 5. the rwc, the net.Conn. +// 4. conn.buf, 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: +// 6. the rwc, the net.Conn. // // TODO(bradfitz): short-circuit some of the buffering when the // initial header contains both a Content-Type and Content-Length. @@ -1014,6 +1032,12 @@ func (w *response) finishRequest() { // Did not write enough. Avoid getting out of sync. w.closeAfterReply = true } + + // There was some error writing to the underlying connection + // during the request, so don't re-use this conn. + if w.conn.werr != nil { + w.closeAfterReply = true + } } func (w *response) Flush() { @@ -1058,15 +1082,21 @@ func (c *conn) close() { // This timeout is somewhat arbitrary (~latency around the planet). const rstAvoidanceDelay = 500 * time.Millisecond +type closeWriter interface { + CloseWrite() error +} + +var _ closeWriter = (*net.TCPConn)(nil) + // closeWrite flushes any outstanding data and sends a FIN packet (if // client is connected via TCP), signalling that we're done. We then -// pause for a bit, hoping the client processes it before `any +// pause for a bit, hoping the client processes it before any // subsequent RST. // // See http://golang.org/issue/3595 func (c *conn) closeWriteAndWait() { c.finalFlush() - if tcp, ok := c.rwc.(*net.TCPConn); ok { + if tcp, ok := c.rwc.(closeWriter); ok { tcp.CloseWrite() } time.Sleep(rstAvoidanceDelay) @@ -1916,9 +1946,9 @@ func (tw *timeoutWriter) Header() Header { func (tw *timeoutWriter) Write(p []byte) (int, error) { tw.mu.Lock() - timedOut := tw.timedOut - tw.mu.Unlock() - if timedOut { + defer tw.mu.Unlock() + tw.wroteHeader = true // implicitly at least + if tw.timedOut { return 0, ErrHandlerTimeout } return tw.w.Write(p) @@ -1926,12 +1956,11 @@ func (tw *timeoutWriter) Write(p []byte) (int, error) { func (tw *timeoutWriter) WriteHeader(code int) { tw.mu.Lock() + defer tw.mu.Unlock() if tw.timedOut || tw.wroteHeader { - tw.mu.Unlock() return } tw.wroteHeader = true - tw.mu.Unlock() tw.w.WriteHeader(code) } @@ -2050,3 +2079,18 @@ func (c *loggingConn) Close() (err error) { log.Printf("%s.Close() = %v", c.name, err) return } + +// checkConnErrorWriter writes to c.rwc and records any write errors to c.werr. +// It only contains one field (and a pointer field at that), so it +// fits in an interface value without an extra allocation. +type checkConnErrorWriter struct { + c *conn +} + +func (w checkConnErrorWriter) Write(p []byte) (n int, err error) { + n, err = w.c.w.Write(p) // c.w == c.rwc, except after a hijack, when rwc is nil. + if err != nil && w.c.werr == nil { + w.c.werr = err + } + return +} diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go index 7f63686528a..520500330bc 100644 --- a/libgo/go/net/http/transfer.go +++ b/libgo/go/net/http/transfer.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http/internal" "net/textproto" "sort" "strconv" @@ -18,6 +19,10 @@ import ( "sync" ) +// ErrLineTooLong is returned when reading request or response bodies +// with malformed chunked encoding. +var ErrLineTooLong = internal.ErrLineTooLong + type errorReader struct { err error } @@ -198,7 +203,7 @@ func (t *transferWriter) WriteBody(w io.Writer) error { // Write body if t.Body != nil { if chunked(t.TransferEncoding) { - cw := newChunkedWriter(w) + cw := internal.NewChunkedWriter(w) _, err = io.Copy(cw, t.Body) if err == nil { err = cw.Close() @@ -223,7 +228,7 @@ func (t *transferWriter) WriteBody(w io.Writer) error { } if !t.ResponseToHEAD && t.ContentLength != -1 && t.ContentLength != ncopy { - return fmt.Errorf("http: Request.ContentLength=%d with Body length %d", + return fmt.Errorf("http: ContentLength=%d with Body length %d", t.ContentLength, ncopy) } @@ -298,7 +303,7 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { t.StatusCode = rr.StatusCode t.ProtoMajor = rr.ProtoMajor t.ProtoMinor = rr.ProtoMinor - t.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header) + t.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header, true) isResponse = true if rr.Request != nil { t.RequestMethod = rr.Request.Method @@ -365,7 +370,7 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { if noBodyExpected(t.RequestMethod) { t.Body = eofReader } else { - t.Body = &body{src: newChunkedReader(r), hdr: msg, r: r, closing: t.Close} + t.Body = &body{src: internal.NewChunkedReader(r), hdr: msg, r: r, closing: t.Close} } case realLength == 0: t.Body = eofReader @@ -497,7 +502,7 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header, // Determine whether to hang up after sending a request and body, or // receiving a response and body // 'header' is the request headers -func shouldClose(major, minor int, header Header) bool { +func shouldClose(major, minor int, header Header, removeCloseHeader bool) bool { if major < 1 { return true } else if major == 1 && minor == 0 { @@ -509,7 +514,9 @@ func shouldClose(major, minor int, header Header) bool { // TODO: Should split on commas, toss surrounding white space, // and check each field. if strings.ToLower(header.get("Connection")) == "close" { - header.Del("Connection") + if removeCloseHeader { + header.Del("Connection") + } return true } } diff --git a/libgo/go/net/http/transport.go b/libgo/go/net/http/transport.go index b1cc632a782..782f7cd395b 100644 --- a/libgo/go/net/http/transport.go +++ b/libgo/go/net/http/transport.go @@ -43,17 +43,20 @@ var DefaultTransport RoundTripper = &Transport{ // MaxIdleConnsPerHost. const DefaultMaxIdleConnsPerHost = 2 -// Transport is an implementation of RoundTripper that supports http, -// https, and http proxies (for either http or https with CONNECT). +// Transport is an implementation of RoundTripper that supports HTTP, +// HTTPS, and HTTP proxies (for either HTTP or HTTPS with CONNECT). // Transport can also cache connections for future re-use. type Transport struct { - idleMu sync.Mutex - idleConn map[connectMethodKey][]*persistConn - idleConnCh map[connectMethodKey]chan *persistConn + idleMu sync.Mutex + wantIdle bool // user has requested to close all idle conns + idleConn map[connectMethodKey][]*persistConn + idleConnCh map[connectMethodKey]chan *persistConn + reqMu sync.Mutex reqCanceler map[*Request]func() - altMu sync.RWMutex - altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper + + altMu sync.RWMutex + altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper // Proxy specifies a function to return a proxy for a given // Request. If the function returns a non-nil error, the @@ -61,11 +64,22 @@ type Transport struct { // If Proxy is nil or returns a nil *URL, no proxy is used. Proxy func(*Request) (*url.URL, error) - // Dial specifies the dial function for creating TCP - // connections. + // Dial specifies the dial function for creating unencrypted + // TCP connections. // If Dial is nil, net.Dial is used. Dial func(network, addr string) (net.Conn, error) + // DialTLS specifies an optional dial function for creating + // TLS connections for non-proxied HTTPS requests. + // + // If DialTLS is nil, Dial and TLSClientConfig are used. + // + // If DialTLS is set, the Dial hook is not used for HTTPS + // requests and the TLSClientConfig and TLSHandshakeTimeout + // are ignored. The returned net.Conn is assumed to already be + // past the TLS handshake. + DialTLS func(network, addr string) (net.Conn, error) + // TLSClientConfig specifies the TLS configuration to use with // tls.Client. If nil, the default configuration is used. TLSClientConfig *tls.Config @@ -105,15 +119,28 @@ type Transport struct { // ProxyFromEnvironment returns the URL of the proxy to use for a // given request, as indicated by the environment variables -// $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy). -// An error is returned if the proxy environment is invalid. +// HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions +// thereof). HTTPS_PROXY takes precedence over HTTP_PROXY for https +// requests. +// +// The environment values may be either a complete URL or a +// "host[:port]", in which case the "http" scheme is assumed. +// 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 -// environment, or a proxy should not be used for the given request. +// environment, or a proxy should not be used for the given request, +// as defined by NO_PROXY. // // As a special case, if req.URL.Host is "localhost" (with or without // a port number), then a nil URL and nil error will be returned. func ProxyFromEnvironment(req *Request) (*url.URL, error) { - proxy := httpProxyEnv.Get() + var proxy string + if req.URL.Scheme == "https" { + proxy = httpsProxyEnv.Get() + } + if proxy == "" { + proxy = httpProxyEnv.Get() + } if proxy == "" { return nil, nil } @@ -238,6 +265,7 @@ func (t *Transport) CloseIdleConnections() { m := t.idleConn t.idleConn = nil t.idleConnCh = nil + t.wantIdle = true t.idleMu.Unlock() for _, conns := range m { for _, pconn := range conns { @@ -265,6 +293,9 @@ var ( httpProxyEnv = &envOnce{ names: []string{"HTTP_PROXY", "http_proxy"}, } + httpsProxyEnv = &envOnce{ + names: []string{"HTTPS_PROXY", "https_proxy"}, + } noProxyEnv = &envOnce{ names: []string{"NO_PROXY", "no_proxy"}, } @@ -305,7 +336,7 @@ func (t *Transport) connectMethodForRequest(treq *transportRequest) (cm connectM if t.Proxy != nil { cm.proxyURL, err = t.Proxy(treq.Request) } - return cm, nil + return cm, err } // proxyAuth returns the Proxy-Authorization header to set @@ -358,6 +389,11 @@ func (t *Transport) putIdleConn(pconn *persistConn) bool { delete(t.idleConnCh, key) } } + if t.wantIdle { + t.idleMu.Unlock() + pconn.close() + return false + } if t.idleConn == nil { t.idleConn = make(map[connectMethodKey][]*persistConn) } @@ -386,6 +422,7 @@ func (t *Transport) getIdleConnCh(cm connectMethod) chan *persistConn { key := cm.key() t.idleMu.Lock() defer t.idleMu.Unlock() + t.wantIdle = false if t.idleConnCh == nil { t.idleConnCh = make(map[connectMethodKey]chan *persistConn) } @@ -444,6 +481,9 @@ func (t *Transport) dial(network, addr string) (c net.Conn, err error) { return net.Dial(network, addr) } +// Testing hooks: +var prePendingDial, postPendingDial func() + // getConn dials and creates a new persistConn to the target as // specified in the connectMethod. This includes doing a proxy CONNECT // and/or setting up TLS. If this doesn't return an error, the persistConn @@ -460,9 +500,17 @@ func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error dialc := make(chan dialRes) handlePendingDial := func() { - if v := <-dialc; v.err == nil { - t.putIdleConn(v.pc) + if prePendingDial != nil { + prePendingDial() } + go func() { + if v := <-dialc; v.err == nil { + t.putIdleConn(v.pc) + } + if postPendingDial != nil { + postPendingDial() + } + }() } cancelc := make(chan struct{}) @@ -484,53 +532,65 @@ func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error // else's dial that they didn't use. // But our dial is still going, so give it away // when it finishes: - go handlePendingDial() + handlePendingDial() return pc, nil case <-cancelc: - go handlePendingDial() + handlePendingDial() return nil, errors.New("net/http: request canceled while waiting for connection") } } func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { - conn, err := t.dial("tcp", cm.addr()) - if err != nil { - if cm.proxyURL != nil { - err = fmt.Errorf("http: error connecting to proxy %s: %v", cm.proxyURL, err) - } - return nil, err - } - - pa := cm.proxyAuth() - pconn := &persistConn{ t: t, cacheKey: cm.key(), - conn: conn, reqch: make(chan requestAndChan, 1), writech: make(chan writeRequest, 1), closech: make(chan struct{}), writeErrCh: make(chan error, 1), } + tlsDial := t.DialTLS != nil && cm.targetScheme == "https" && cm.proxyURL == nil + if tlsDial { + var err error + pconn.conn, err = t.DialTLS("tcp", cm.addr()) + if err != nil { + return nil, err + } + if tc, ok := pconn.conn.(*tls.Conn); ok { + cs := tc.ConnectionState() + pconn.tlsState = &cs + } + } else { + conn, err := t.dial("tcp", cm.addr()) + if err != nil { + if cm.proxyURL != nil { + err = fmt.Errorf("http: error connecting to proxy %s: %v", cm.proxyURL, err) + } + return nil, err + } + pconn.conn = conn + } + // Proxy setup. switch { case cm.proxyURL == nil: - // Do nothing. + // Do nothing. Not using a proxy. case cm.targetScheme == "http": pconn.isProxy = true - if pa != "" { + if pa := cm.proxyAuth(); pa != "" { pconn.mutateHeaderFunc = func(h Header) { h.Set("Proxy-Authorization", pa) } } case cm.targetScheme == "https": + conn := pconn.conn connectReq := &Request{ Method: "CONNECT", URL: &url.URL{Opaque: cm.targetAddr}, Host: cm.targetAddr, Header: make(Header), } - if pa != "" { + if pa := cm.proxyAuth(); pa != "" { connectReq.Header.Set("Proxy-Authorization", pa) } connectReq.Write(conn) @@ -551,7 +611,7 @@ func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { } } - if cm.targetScheme == "https" { + if cm.targetScheme == "https" && !tlsDial { // Initiate TLS and check remote host name against certificate. cfg := t.TLSClientConfig if cfg == nil || cfg.ServerName == "" { @@ -564,7 +624,7 @@ func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { cfg = &clone } } - plainConn := conn + plainConn := pconn.conn tlsConn := tls.Client(plainConn, cfg) errc := make(chan error, 2) var timer *time.Timer // for canceling TLS handshake @@ -980,11 +1040,14 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err } // Ask for a compressed version if the caller didn't set their - // own value for Accept-Encoding. We only attempted to + // own value for Accept-Encoding. We only attempt to // uncompress the gzip stream if we were the layer that // requested it. requestedGzip := false - if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" && req.Method != "HEAD" { + if !pc.t.DisableCompression && + req.Header.Get("Accept-Encoding") == "" && + req.Header.Get("Range") == "" && + req.Method != "HEAD" { // Request gzip only, not deflate. Deflate is ambiguous and // not as universally supported anyway. // See: http://www.gzip.org/zlib/zlib_faq.html#faq38 @@ -993,6 +1056,10 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err // due to a bug in nginx: // http://trac.nginx.org/nginx/ticket/358 // http://golang.org/issue/5522 + // + // We don't request gzip if the request is for a range, since + // auto-decoding a portion of a gzipped document will just fail + // anyway. See http://golang.org/issue/8923 requestedGzip = true req.extraHeaders().Set("Accept-Encoding", "gzip") } diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index 964ca0fca54..defa6337082 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -1063,20 +1063,18 @@ func TestTransportConcurrency(t *testing.T) { var wg sync.WaitGroup wg.Add(numReqs) - tr := &Transport{ - Dial: func(netw, addr string) (c net.Conn, err error) { - // Due to the Transport's "socket late - // binding" (see idleConnCh in transport.go), - // the numReqs HTTP requests below can finish - // with a dial still outstanding. So count - // our dials as work too so the leak checker - // doesn't complain at us. - wg.Add(1) - defer wg.Done() - return net.Dial(netw, addr) - }, - } + // Due to the Transport's "socket late binding" (see + // idleConnCh in transport.go), the numReqs HTTP requests + // below can finish with a dial still outstanding. To keep + // the leak checker happy, keep track of pending dials and + // wait for them to finish (and be closed or returned to the + // idle pool) before we close idle connections. + SetPendingDialHooks(func() { wg.Add(1) }, wg.Done) + defer SetPendingDialHooks(nil, nil) + + tr := &Transport{} defer tr.CloseIdleConnections() + c := &Client{Transport: tr} reqs := make(chan string) defer close(reqs) @@ -1703,26 +1701,40 @@ Content-Length: %d } type proxyFromEnvTest struct { - req string // URL to fetch; blank means "http://example.com" - env string - noenv string + req string // URL to fetch; blank means "http://example.com" + + env string // HTTP_PROXY + httpsenv string // HTTPS_PROXY + noenv string // NO_RPXY + want string wanterr error } func (t proxyFromEnvTest) String() string { var buf bytes.Buffer + space := func() { + if buf.Len() > 0 { + buf.WriteByte(' ') + } + } if t.env != "" { fmt.Fprintf(&buf, "http_proxy=%q", t.env) } + if t.httpsenv != "" { + space() + fmt.Fprintf(&buf, "https_proxy=%q", t.httpsenv) + } if t.noenv != "" { - fmt.Fprintf(&buf, " no_proxy=%q", t.noenv) + space() + fmt.Fprintf(&buf, "no_proxy=%q", t.noenv) } req := "http://example.com" if t.req != "" { req = t.req } - fmt.Fprintf(&buf, " req=%q", req) + space() + fmt.Fprintf(&buf, "req=%q", req) return strings.TrimSpace(buf.String()) } @@ -1733,7 +1745,15 @@ var proxyFromEnvTests = []proxyFromEnvTest{ {env: "https://cache.corp.example.com", want: "https://cache.corp.example.com"}, {env: "http://127.0.0.1:8080", want: "http://127.0.0.1:8080"}, {env: "https://127.0.0.1:8080", want: "https://127.0.0.1:8080"}, + + // Don't use secure for http + {req: "http://insecure.tld/", env: "http.proxy.tld", httpsenv: "secure.proxy.tld", want: "http://http.proxy.tld"}, + // Use secure for https. + {req: "https://secure.tld/", env: "http.proxy.tld", httpsenv: "secure.proxy.tld", want: "http://secure.proxy.tld"}, + {req: "https://secure.tld/", env: "http.proxy.tld", httpsenv: "https://secure.proxy.tld", want: "https://secure.proxy.tld"}, + {want: "<nil>"}, + {noenv: "example.com", req: "http://example.com/", env: "proxy", want: "<nil>"}, {noenv: ".example.com", req: "http://example.com/", env: "proxy", want: "<nil>"}, {noenv: "ample.com", req: "http://example.com/", env: "proxy", want: "http://proxy"}, @@ -1745,6 +1765,7 @@ func TestProxyFromEnvironment(t *testing.T) { ResetProxyEnv() for _, tt := range proxyFromEnvTests { os.Setenv("HTTP_PROXY", tt.env) + os.Setenv("HTTPS_PROXY", tt.httpsenv) os.Setenv("NO_PROXY", tt.noenv) ResetCachedEnvironment() reqURL := tt.req @@ -2098,6 +2119,136 @@ func TestTransportClosesBodyOnError(t *testing.T) { } } +func TestTransportDialTLS(t *testing.T) { + var mu sync.Mutex // guards following + var gotReq, didDial bool + + ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { + mu.Lock() + gotReq = true + mu.Unlock() + })) + defer ts.Close() + tr := &Transport{ + DialTLS: func(netw, addr string) (net.Conn, error) { + mu.Lock() + didDial = true + mu.Unlock() + c, err := tls.Dial(netw, addr, &tls.Config{ + InsecureSkipVerify: true, + }) + if err != nil { + return nil, err + } + return c, c.Handshake() + }, + } + defer tr.CloseIdleConnections() + client := &Client{Transport: tr} + res, err := client.Get(ts.URL) + if err != nil { + t.Fatal(err) + } + res.Body.Close() + mu.Lock() + if !gotReq { + t.Error("didn't get request") + } + if !didDial { + t.Error("didn't use dial hook") + } +} + +// Test for issue 8755 +// Ensure that if a proxy returns an error, it is exposed by RoundTrip +func TestRoundTripReturnsProxyError(t *testing.T) { + badProxy := func(*http.Request) (*url.URL, error) { + return nil, errors.New("errorMessage") + } + + tr := &Transport{Proxy: badProxy} + + req, _ := http.NewRequest("GET", "http://example.com", nil) + + _, err := tr.RoundTrip(req) + + if err == nil { + t.Error("Expected proxy error to be returned by RoundTrip") + } +} + +// tests that putting an idle conn after a call to CloseIdleConns does return it +func TestTransportCloseIdleConnsThenReturn(t *testing.T) { + tr := &Transport{} + wantIdle := func(when string, n int) bool { + got := tr.IdleConnCountForTesting("|http|example.com") // key used by PutIdleTestConn + if got == n { + return true + } + t.Errorf("%s: idle conns = %d; want %d", when, got, n) + return false + } + wantIdle("start", 0) + if !tr.PutIdleTestConn() { + t.Fatal("put failed") + } + if !tr.PutIdleTestConn() { + t.Fatal("second put failed") + } + wantIdle("after put", 2) + tr.CloseIdleConnections() + if !tr.IsIdleForTesting() { + t.Error("should be idle after CloseIdleConnections") + } + wantIdle("after close idle", 0) + if tr.PutIdleTestConn() { + t.Fatal("put didn't fail") + } + wantIdle("after second put", 0) + + tr.RequestIdleConnChForTesting() // should toggle the transport out of idle mode + if tr.IsIdleForTesting() { + t.Error("shouldn't be idle after RequestIdleConnChForTesting") + } + if !tr.PutIdleTestConn() { + t.Fatal("after re-activation") + } + wantIdle("after final put", 1) +} + +// This tests that an client requesting a content range won't also +// implicitly ask for gzip support. If they want that, they need to do it +// on their own. +// golang.org/issue/8923 +func TestTransportRangeAndGzip(t *testing.T) { + defer afterTest(t) + reqc := make(chan *Request, 1) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + reqc <- r + })) + defer ts.Close() + + req, _ := NewRequest("GET", ts.URL, nil) + req.Header.Set("Range", "bytes=7-11") + res, err := DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + + select { + case r := <-reqc: + if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + t.Error("Transport advertised gzip support in the Accept header") + } + if r.Header.Get("Range") == "" { + t.Error("no Range in request") + } + case <-time.After(10 * time.Second): + t.Fatal("timeout") + } + res.Body.Close() +} + func wantBody(res *http.Response, err error, want string) error { if err != nil { return err diff --git a/libgo/go/net/ip.go b/libgo/go/net/ip.go index 0582009b8bd..4a93e97b39d 100644 --- a/libgo/go/net/ip.go +++ b/libgo/go/net/ip.go @@ -287,6 +287,7 @@ func (ip IP) String() string { if j > i && j-i > e1-e0 { e0 = i e1 = j + i = j } } // The symbol "::" MUST NOT be used to shorten just one 16 bit 0 field. @@ -295,21 +296,23 @@ func (ip IP) String() string { e1 = -1 } + const maxLen = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + b := make([]byte, 0, maxLen) + // Print with possible :: in place of run of zeros - var s string for i := 0; i < IPv6len; i += 2 { if i == e0 { - s += "::" + b = append(b, ':', ':') i = e1 if i >= IPv6len { break } } else if i > 0 { - s += ":" + b = append(b, ':') } - s += itox((uint(p[i])<<8)|uint(p[i+1]), 1) + b = appendHex(b, (uint32(p[i])<<8)|uint32(p[i+1])) } - return s + return string(b) } // ipEmptyString is like ip.String except that it returns @@ -419,14 +422,14 @@ func (m IPMask) Size() (ones, bits int) { // String returns the hexadecimal form of m, with no punctuation. func (m IPMask) String() string { - s := "" - for _, b := range m { - s += itox(uint(b), 2) - } - if len(s) == 0 { + if len(m) == 0 { return "<nil>" } - return s + buf := make([]byte, len(m)*2) + for i, b := range m { + buf[i*2], buf[i*2+1] = hexDigit[b>>4], hexDigit[b&0xf] + } + return string(buf) } func networkNumberAndMask(n *IPNet) (ip IP, m IPMask) { @@ -646,11 +649,16 @@ func (e *ParseError) Error() string { // If s is not a valid textual representation of an IP address, // ParseIP returns nil. func ParseIP(s string) IP { - if ip := parseIPv4(s); ip != nil { - return ip + for i := 0; i < len(s); i++ { + switch s[i] { + case '.': + return parseIPv4(s) + case ':': + ip, _ := parseIPv6(s, false) + return ip + } } - ip, _ := parseIPv6(s, false) - return ip + return nil } // ParseCIDR parses s as a CIDR notation IP address and mask, diff --git a/libgo/go/net/ip_test.go b/libgo/go/net/ip_test.go index ffeb9d315e7..485ff51153b 100644 --- a/libgo/go/net/ip_test.go +++ b/libgo/go/net/ip_test.go @@ -44,6 +44,14 @@ func TestParseIP(t *testing.T) { } } +func BenchmarkParseIP(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, tt := range parseIPTests { + ParseIP(tt.in) + } + } +} + // Issue 6339 func TestMarshalEmptyIP(t *testing.T) { for _, in := range [][]byte{nil, []byte("")} { @@ -91,6 +99,16 @@ func TestIPString(t *testing.T) { } } +func BenchmarkIPString(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, tt := range ipStringTests { + if tt.in != nil { + tt.in.String() + } + } + } +} + var ipMaskTests = []struct { in IP mask IPMask @@ -131,6 +149,14 @@ func TestIPMaskString(t *testing.T) { } } +func BenchmarkIPMaskString(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, tt := range ipMaskStringTests { + tt.in.String() + } + } +} + var parseCIDRTests = []struct { in string ip IP diff --git a/libgo/go/net/ipraw_test.go b/libgo/go/net/ipraw_test.go index 0632dafc65e..92dc8dc5694 100644 --- a/libgo/go/net/ipraw_test.go +++ b/libgo/go/net/ipraw_test.go @@ -68,6 +68,11 @@ func skipRawSocketTest(t *testing.T) (skip bool, skipmsg string) { } func TestResolveIPAddr(t *testing.T) { + switch runtime.GOOS { + case "nacl": + t.Skipf("skipping test on %q", runtime.GOOS) + } + for _, tt := range resolveIPAddrTests { addr, err := ResolveIPAddr(tt.net, tt.litAddrOrName) if err != tt.err { diff --git a/libgo/go/net/iprawsock_posix.go b/libgo/go/net/iprawsock_posix.go index bbb3f3ed66c..99b081ba8c8 100644 --- a/libgo/go/net/iprawsock_posix.go +++ b/libgo/go/net/iprawsock_posix.go @@ -198,7 +198,7 @@ func dialIP(netProto string, laddr, raddr *IPAddr, deadline time.Time) (*IPConn, if raddr == nil { return nil, &OpError{Op: "dial", Net: netProto, Addr: nil, Err: errMissingAddress} } - fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_RAW, proto, "dial", sockaddrToIP) + fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_RAW, proto, "dial") if err != nil { return nil, &OpError{Op: "dial", Net: netProto, Addr: raddr, Err: err} } @@ -219,7 +219,7 @@ func ListenIP(netProto string, laddr *IPAddr) (*IPConn, error) { default: return nil, &OpError{Op: "listen", Net: netProto, Addr: laddr, Err: UnknownNetworkError(netProto)} } - fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_RAW, proto, "listen", sockaddrToIP) + fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_RAW, proto, "listen") if err != nil { return nil, &OpError{Op: "listen", Net: netProto, Addr: laddr, Err: err} } diff --git a/libgo/go/net/ipsock_posix.go b/libgo/go/net/ipsock_posix.go index 2ba4c8efd53..f9ebe40a21e 100644 --- a/libgo/go/net/ipsock_posix.go +++ b/libgo/go/net/ipsock_posix.go @@ -132,9 +132,9 @@ func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) (family // Internet sockets (TCP, UDP, IP) -func internetSocket(net string, laddr, raddr sockaddr, deadline time.Time, sotype, proto int, mode string, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) { +func internetSocket(net string, laddr, raddr sockaddr, deadline time.Time, sotype, proto int, mode string) (fd *netFD, err error) { family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode) - return socket(net, family, sotype, proto, ipv6only, laddr, raddr, deadline, toAddr) + return socket(net, family, sotype, proto, ipv6only, laddr, raddr, deadline) } func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, error) { diff --git a/libgo/go/net/lookup.go b/libgo/go/net/lookup.go index 20f20578cde..aeffe6c9b72 100644 --- a/libgo/go/net/lookup.go +++ b/libgo/go/net/lookup.go @@ -40,10 +40,16 @@ func lookupIPMerge(host string) (addrs []IP, err error) { addrsi, err, shared := lookupGroup.Do(host, func() (interface{}, error) { return lookupIP(host) }) + return lookupIPReturn(addrsi, err, shared) +} + +// lookupIPReturn turns the return values from singleflight.Do into +// the return values from LookupIP. +func lookupIPReturn(addrsi interface{}, err error, shared bool) ([]IP, error) { if err != nil { return nil, err } - addrs = addrsi.([]IP) + addrs := addrsi.([]IP) if shared { clone := make([]IP, len(addrs)) copy(clone, addrs) @@ -52,41 +58,40 @@ func lookupIPMerge(host string) (addrs []IP, err error) { return addrs, nil } +// lookupIPDeadline looks up a hostname with a deadline. func lookupIPDeadline(host string, deadline time.Time) (addrs []IP, err error) { if deadline.IsZero() { return lookupIPMerge(host) } - // TODO(bradfitz): consider pushing the deadline down into the - // name resolution functions. But that involves fixing it for - // the native Go resolver, cgo, Windows, etc. - // - // In the meantime, just use a goroutine. Most users affected - // by http://golang.org/issue/2631 are due to TCP connections - // to unresponsive hosts, not DNS. + // We could push the deadline down into the name resolution + // functions. However, the most commonly used implementation + // calls getaddrinfo, which has no timeout. + timeout := deadline.Sub(time.Now()) if timeout <= 0 { - err = errTimeout - return + return nil, errTimeout } t := time.NewTimer(timeout) defer t.Stop() - type res struct { - addrs []IP - err error - } - resc := make(chan res, 1) - go func() { - a, err := lookupIPMerge(host) - resc <- res{a, err} - }() + + ch := lookupGroup.DoChan(host, func() (interface{}, error) { + return lookupIP(host) + }) + select { case <-t.C: - err = errTimeout - case r := <-resc: - addrs, err = r.addrs, r.err + // The DNS lookup timed out for some reason. Force + // future requests to start the DNS lookup again + // rather than waiting for the current lookup to + // complete. See issue 8602. + lookupGroup.Forget(host) + + return nil, errTimeout + + case r := <-ch: + return lookupIPReturn(r.v, r.err, r.shared) } - return } // LookupPort looks up the port for the given network and service. diff --git a/libgo/go/net/lookup_stub.go b/libgo/go/net/lookup_stub.go new file mode 100644 index 00000000000..502aafb2702 --- /dev/null +++ b/libgo/go/net/lookup_stub.go @@ -0,0 +1,49 @@ +// Copyright 2011 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. + +// +build nacl + +package net + +import "syscall" + +func lookupProtocol(name string) (proto int, err error) { + return 0, syscall.ENOPROTOOPT +} + +func lookupHost(host string) (addrs []string, err error) { + return nil, syscall.ENOPROTOOPT +} + +func lookupIP(host string) (ips []IP, err error) { + return nil, syscall.ENOPROTOOPT +} + +func lookupPort(network, service string) (port int, err error) { + return 0, syscall.ENOPROTOOPT +} + +func lookupCNAME(name string) (cname string, err error) { + return "", syscall.ENOPROTOOPT +} + +func lookupSRV(service, proto, name string) (cname string, srvs []*SRV, err error) { + return "", nil, syscall.ENOPROTOOPT +} + +func lookupMX(name string) (mxs []*MX, err error) { + return nil, syscall.ENOPROTOOPT +} + +func lookupNS(name string) (nss []*NS, err error) { + return nil, syscall.ENOPROTOOPT +} + +func lookupTXT(name string) (txts []string, err error) { + return nil, syscall.ENOPROTOOPT +} + +func lookupAddr(addr string) (ptrs []string, err error) { + return nil, syscall.ENOPROTOOPT +} diff --git a/libgo/go/net/lookup_test.go b/libgo/go/net/lookup_test.go index 3355e469489..057e1322b99 100644 --- a/libgo/go/net/lookup_test.go +++ b/libgo/go/net/lookup_test.go @@ -15,87 +15,181 @@ import ( var testExternal = flag.Bool("external", true, "allow use of external networks during long test") -func TestGoogleSRV(t *testing.T) { +var lookupGoogleSRVTests = []struct { + service, proto, name string + cname, target string +}{ + { + "xmpp-server", "tcp", "google.com", + ".google.com", ".google.com", + }, + { + "", "", "_xmpp-server._tcp.google.com", // non-standard back door + ".google.com", ".google.com", + }, +} + +func TestLookupGoogleSRV(t *testing.T) { if testing.Short() || !*testExternal { t.Skip("skipping test to avoid external network") } - _, addrs, err := LookupSRV("xmpp-server", "tcp", "google.com") - if err != nil { - t.Errorf("failed: %s", err) + + for _, tt := range lookupGoogleSRVTests { + cname, srvs, err := LookupSRV(tt.service, tt.proto, tt.name) + if err != nil { + t.Fatal(err) + } + if len(srvs) == 0 { + t.Error("got no record") + } + if !strings.Contains(cname, tt.cname) { + t.Errorf("got %q; want %q", cname, tt.cname) + } + for _, srv := range srvs { + if !strings.Contains(srv.Target, tt.target) { + t.Errorf("got %v; want a record containing %q", srv, tt.target) + } + } } - if len(addrs) == 0 { - t.Errorf("no results") +} + +func TestLookupGmailMX(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("skipping test to avoid external network") } - // Non-standard back door. - _, addrs, err = LookupSRV("", "", "_xmpp-server._tcp.google.com") + mxs, err := LookupMX("gmail.com") if err != nil { - t.Errorf("back door failed: %s", err) + t.Fatal(err) } - if len(addrs) == 0 { - t.Errorf("back door no results") + if len(mxs) == 0 { + t.Error("got no record") + } + for _, mx := range mxs { + if !strings.Contains(mx.Host, ".google.com") { + t.Errorf("got %v; want a record containing .google.com.", mx) + } } } -func TestGmailMX(t *testing.T) { +func TestLookupGmailNS(t *testing.T) { if testing.Short() || !*testExternal { t.Skip("skipping test to avoid external network") } - mx, err := LookupMX("gmail.com") + + nss, err := LookupNS("gmail.com") if err != nil { - t.Errorf("failed: %s", err) + t.Fatal(err) + } + if len(nss) == 0 { + t.Error("got no record") } - if len(mx) == 0 { - t.Errorf("no results") + for _, ns := range nss { + if !strings.Contains(ns.Host, ".google.com") { + t.Errorf("got %v; want a record containing .google.com.", ns) + } } } -func TestGmailNS(t *testing.T) { +func TestLookupGmailTXT(t *testing.T) { if testing.Short() || !*testExternal { t.Skip("skipping test to avoid external network") } - ns, err := LookupNS("gmail.com") + + txts, err := LookupTXT("gmail.com") if err != nil { - t.Errorf("failed: %s", err) + t.Fatal(err) + } + if len(txts) == 0 { + t.Error("got no record") + } + for _, txt := range txts { + if !strings.Contains(txt, "spf") { + t.Errorf("got %q; want a spf record", txt) + } + } +} + +var lookupGooglePublicDNSAddrs = []struct { + addr string + name string +}{ + {"8.8.8.8", ".google.com."}, + {"8.8.4.4", ".google.com."}, + {"2001:4860:4860::8888", ".google.com."}, + {"2001:4860:4860::8844", ".google.com."}, +} + +func TestLookupGooglePublicDNSAddr(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("skipping test to avoid external network") } - if len(ns) == 0 { - t.Errorf("no results") + + for _, tt := range lookupGooglePublicDNSAddrs { + names, err := LookupAddr(tt.addr) + if err != nil { + t.Fatal(err) + } + if len(names) == 0 { + t.Error("got no record") + } + for _, name := range names { + if !strings.HasSuffix(name, tt.name) { + t.Errorf("got %q; want a record containing %q", name, tt.name) + } + } } } -func TestGmailTXT(t *testing.T) { +func TestLookupIANACNAME(t *testing.T) { if testing.Short() || !*testExternal { t.Skip("skipping test to avoid external network") } - txt, err := LookupTXT("gmail.com") + + cname, err := LookupCNAME("www.iana.org") if err != nil { - t.Errorf("failed: %s", err) + t.Fatal(err) } - if len(txt) == 0 || len(txt[0]) == 0 { - t.Errorf("no results") + if !strings.HasSuffix(cname, ".icann.org.") { + t.Errorf("got %q; want a record containing .icann.org.", cname) } } -func TestGoogleDNSAddr(t *testing.T) { +func TestLookupGoogleHost(t *testing.T) { if testing.Short() || !*testExternal { t.Skip("skipping test to avoid external network") } - names, err := LookupAddr("8.8.8.8") + + addrs, err := LookupHost("google.com") if err != nil { - t.Errorf("failed: %s", err) + t.Fatal(err) + } + if len(addrs) == 0 { + t.Error("got no record") } - if len(names) == 0 { - t.Errorf("no results") + for _, addr := range addrs { + if ParseIP(addr) == nil { + t.Errorf("got %q; want a literal ip address", addr) + } } } -func TestLookupIANACNAME(t *testing.T) { +func TestLookupGoogleIP(t *testing.T) { if testing.Short() || !*testExternal { t.Skip("skipping test to avoid external network") } - cname, err := LookupCNAME("www.iana.org") - if !strings.HasSuffix(cname, ".icann.org.") || err != nil { - t.Errorf(`LookupCNAME("www.iana.org.") = %q, %v, want "*.icann.org.", nil`, cname, err) + + ips, err := LookupIP("google.com") + if err != nil { + t.Fatal(err) + } + if len(ips) == 0 { + t.Error("got no record") + } + for _, ip := range ips { + if ip.To4() == nil && ip.To16() == nil { + t.Errorf("got %v; want an ip address", ip) + } } } diff --git a/libgo/go/net/lookup_unix.go b/libgo/go/net/lookup_unix.go index b1d2f8f31a9..a54578456d7 100644 --- a/libgo/go/net/lookup_unix.go +++ b/libgo/go/net/lookup_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/lookup_windows.go b/libgo/go/net/lookup_windows.go index 130364231d4..6a925b0a7ad 100644 --- a/libgo/go/net/lookup_windows.go +++ b/libgo/go/net/lookup_windows.go @@ -210,14 +210,21 @@ func lookupCNAME(name string) (cname string, err error) { defer releaseThread() var r *syscall.DNSRecord e := syscall.DnsQuery(name, syscall.DNS_TYPE_CNAME, 0, nil, &r, nil) + // windows returns DNS_INFO_NO_RECORDS if there are no CNAME-s + if errno, ok := e.(syscall.Errno); ok && errno == syscall.DNS_INFO_NO_RECORDS { + // if there are no aliases, the canonical name is the input name + if name == "" || name[len(name)-1] != '.' { + return name + ".", nil + } + return name, nil + } if e != nil { return "", os.NewSyscallError("LookupCNAME", e) } defer syscall.DnsRecordListFree(r, 1) - if r != nil && r.Type == syscall.DNS_TYPE_CNAME { - v := (*syscall.DNSPTRData)(unsafe.Pointer(&r.Data[0])) - cname = syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:]) + "." - } + + resolved := resolveCNAME(syscall.StringToUTF16Ptr(name), r) + cname = syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(resolved))[:]) + "." return } @@ -236,8 +243,9 @@ func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err return "", nil, os.NewSyscallError("LookupSRV", e) } defer syscall.DnsRecordListFree(r, 1) + addrs = make([]*SRV, 0, 10) - for p := r; p != nil && p.Type == syscall.DNS_TYPE_SRV; p = p.Next { + for _, p := range validRecs(r, syscall.DNS_TYPE_SRV, target) { v := (*syscall.DNSSRVData)(unsafe.Pointer(&p.Data[0])) addrs = append(addrs, &SRV{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Target))[:]), v.Port, v.Priority, v.Weight}) } @@ -254,8 +262,9 @@ func lookupMX(name string) (mx []*MX, err error) { return nil, os.NewSyscallError("LookupMX", e) } defer syscall.DnsRecordListFree(r, 1) + mx = make([]*MX, 0, 10) - for p := r; p != nil && p.Type == syscall.DNS_TYPE_MX; p = p.Next { + for _, p := range validRecs(r, syscall.DNS_TYPE_MX, name) { v := (*syscall.DNSMXData)(unsafe.Pointer(&p.Data[0])) mx = append(mx, &MX{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.NameExchange))[:]) + ".", v.Preference}) } @@ -272,8 +281,9 @@ func lookupNS(name string) (ns []*NS, err error) { return nil, os.NewSyscallError("LookupNS", e) } defer syscall.DnsRecordListFree(r, 1) + ns = make([]*NS, 0, 10) - for p := r; p != nil && p.Type == syscall.DNS_TYPE_NS; p = p.Next { + for _, p := range validRecs(r, syscall.DNS_TYPE_NS, name) { v := (*syscall.DNSPTRData)(unsafe.Pointer(&p.Data[0])) ns = append(ns, &NS{syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:]) + "."}) } @@ -289,9 +299,10 @@ func lookupTXT(name string) (txt []string, err error) { return nil, os.NewSyscallError("LookupTXT", e) } defer syscall.DnsRecordListFree(r, 1) + txt = make([]string, 0, 10) - if r != nil && r.Type == syscall.DNS_TYPE_TEXT { - d := (*syscall.DNSTXTData)(unsafe.Pointer(&r.Data[0])) + for _, p := range validRecs(r, syscall.DNS_TYPE_TEXT, name) { + d := (*syscall.DNSTXTData)(unsafe.Pointer(&p.Data[0])) for _, v := range (*[1 << 10]*uint16)(unsafe.Pointer(&(d.StringArray[0])))[:d.StringCount] { s := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(v))[:]) txt = append(txt, s) @@ -313,10 +324,58 @@ func lookupAddr(addr string) (name []string, err error) { return nil, os.NewSyscallError("LookupAddr", e) } defer syscall.DnsRecordListFree(r, 1) + name = make([]string, 0, 10) - for p := r; p != nil && p.Type == syscall.DNS_TYPE_PTR; p = p.Next { + for _, p := range validRecs(r, syscall.DNS_TYPE_PTR, arpa) { v := (*syscall.DNSPTRData)(unsafe.Pointer(&p.Data[0])) name = append(name, syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:])) } return name, nil } + +const dnsSectionMask = 0x0003 + +// returns only results applicable to name and resolves CNAME entries +func validRecs(r *syscall.DNSRecord, dnstype uint16, name string) []*syscall.DNSRecord { + cname := syscall.StringToUTF16Ptr(name) + if dnstype != syscall.DNS_TYPE_CNAME { + cname = resolveCNAME(cname, r) + } + rec := make([]*syscall.DNSRecord, 0, 10) + for p := r; p != nil; p = p.Next { + if p.Dw&dnsSectionMask != syscall.DnsSectionAnswer { + continue + } + if p.Type != dnstype { + continue + } + if !syscall.DnsNameCompare(cname, p.Name) { + continue + } + rec = append(rec, p) + } + return rec +} + +// returns the last CNAME in chain +func resolveCNAME(name *uint16, r *syscall.DNSRecord) *uint16 { + // limit cname resolving to 10 in case of a infinite CNAME loop +Cname: + for cnameloop := 0; cnameloop < 10; cnameloop++ { + for p := r; p != nil; p = p.Next { + if p.Dw&dnsSectionMask != syscall.DnsSectionAnswer { + continue + } + if p.Type != syscall.DNS_TYPE_CNAME { + continue + } + if !syscall.DnsNameCompare(name, p.Name) { + continue + } + name = (*syscall.DNSPTRData)(unsafe.Pointer(&r.Data[0])).Host + continue Cname + } + break + } + return name +} diff --git a/libgo/go/net/mail/message.go b/libgo/go/net/mail/message.go index ba0778caa73..19aa888d872 100644 --- a/libgo/go/net/mail/message.go +++ b/libgo/go/net/mail/message.go @@ -28,6 +28,7 @@ import ( "strconv" "strings" "time" + "unicode" ) var debug = debugT(false) @@ -445,7 +446,7 @@ func decodeRFC2047Word(s string) (string, error) { return "", errors.New("address not RFC 2047 encoded") } charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2]) - if charset != "iso-8859-1" && charset != "utf-8" { + if charset != "us-ascii" && charset != "iso-8859-1" && charset != "utf-8" { return "", fmt.Errorf("charset not supported: %q", charset) } @@ -466,6 +467,16 @@ func decodeRFC2047Word(s string) (string, error) { } switch charset { + case "us-ascii": + b := new(bytes.Buffer) + for _, c := range dec { + if c >= 0x80 { + b.WriteRune(unicode.ReplacementChar) + } else { + b.WriteRune(rune(c)) + } + } + return b.String(), nil case "iso-8859-1": b := new(bytes.Buffer) for _, c := range dec { diff --git a/libgo/go/net/mail/message_test.go b/libgo/go/net/mail/message_test.go index eb9c8cbdc9b..6ba48be04fa 100644 --- a/libgo/go/net/mail/message_test.go +++ b/libgo/go/net/mail/message_test.go @@ -194,6 +194,16 @@ func TestAddressParsing(t *testing.T) { }, }, }, + // RFC 2047 "Q"-encoded US-ASCII address. Dumb but legal. + { + `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`, + []*Address{ + { + Name: `Jorg Doe`, + Address: "joerg@example.com", + }, + }, + }, // RFC 2047 "Q"-encoded UTF-8 address. { `=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>`, diff --git a/libgo/go/net/multicast_test.go b/libgo/go/net/multicast_test.go index 63dbce88e9a..5f253f44a45 100644 --- a/libgo/go/net/multicast_test.go +++ b/libgo/go/net/multicast_test.go @@ -25,7 +25,7 @@ var ipv4MulticastListenerTests = []struct { // port. func TestIPv4MulticastListener(t *testing.T) { switch runtime.GOOS { - case "nacl", "plan9": + case "android", "nacl", "plan9": t.Skipf("skipping test on %q", runtime.GOOS) case "solaris": t.Skipf("skipping test on solaris, see issue 7399") diff --git a/libgo/go/net/net.go b/libgo/go/net/net.go index ca56af54fc6..cb31af5e347 100644 --- a/libgo/go/net/net.go +++ b/libgo/go/net/net.go @@ -32,7 +32,6 @@ The Listen function creates servers: conn, err := ln.Accept() if err != nil { // handle error - continue } go handleConnection(conn) } diff --git a/libgo/go/net/parse.go b/libgo/go/net/parse.go index ee6e7e99522..e1d0130c9ac 100644 --- a/libgo/go/net/parse.go +++ b/libgo/go/net/parse.go @@ -210,18 +210,18 @@ func itod(i uint) string { return string(b[bp:]) } -// Convert i to hexadecimal string. -func itox(i uint, min int) string { - // Assemble hexadecimal in reverse order. - var b [32]byte - bp := len(b) - for ; i > 0 || min > 0; i /= 16 { - bp-- - b[bp] = "0123456789abcdef"[byte(i%16)] - min-- +// Convert i to a hexadecimal string. Leading zeros are not printed. +func appendHex(dst []byte, i uint32) []byte { + if i == 0 { + return append(dst, '0') } - - return string(b[bp:]) + for j := 7; j >= 0; j-- { + v := i >> uint(j*4) + if v > 0 { + dst = append(dst, hexDigit[v&0xf]) + } + } + return dst } // Number of occurrences of b in s. diff --git a/libgo/go/net/parse_test.go b/libgo/go/net/parse_test.go index b86bc32884b..7b213b75bde 100644 --- a/libgo/go/net/parse_test.go +++ b/libgo/go/net/parse_test.go @@ -12,9 +12,9 @@ import ( ) func TestReadLine(t *testing.T) { - // /etc/services file does not exist on windows and Plan 9. + // /etc/services file does not exist on android, plan9, windows. switch runtime.GOOS { - case "plan9", "windows": + case "android", "plan9", "windows": t.Skipf("skipping test on %q", runtime.GOOS) } filename := "/etc/services" // a nice big file diff --git a/libgo/go/net/port_test.go b/libgo/go/net/port_test.go index 9e8968f359c..4811ade69e0 100644 --- a/libgo/go/net/port_test.go +++ b/libgo/go/net/port_test.go @@ -5,6 +5,7 @@ package net import ( + "runtime" "testing" ) @@ -43,6 +44,11 @@ var porttests = []portTest{ } func TestLookupPort(t *testing.T) { + switch runtime.GOOS { + case "nacl": + t.Skipf("skipping test on %q", runtime.GOOS) + } + for i := 0; i < len(porttests); i++ { tt := porttests[i] if port, err := LookupPort(tt.netw, tt.name); port != tt.port || (err == nil) != tt.ok { diff --git a/libgo/go/net/port_unix.go b/libgo/go/net/port_unix.go index 89558c1f029..348c771c351 100644 --- a/libgo/go/net/port_unix.go +++ b/libgo/go/net/port_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris // Read system port mappings from /etc/services diff --git a/libgo/go/net/rpc/client.go b/libgo/go/net/rpc/client.go index 21f79b06844..d0c4a69214e 100644 --- a/libgo/go/net/rpc/client.go +++ b/libgo/go/net/rpc/client.go @@ -41,10 +41,10 @@ type Call struct { type Client struct { codec ClientCodec - sending sync.Mutex + reqMutex sync.Mutex // protects following + request Request mutex sync.Mutex // protects following - request Request seq uint64 pending map[uint64]*Call closing bool // user has called Close @@ -69,8 +69,8 @@ type ClientCodec interface { } func (client *Client) send(call *Call) { - client.sending.Lock() - defer client.sending.Unlock() + client.reqMutex.Lock() + defer client.reqMutex.Unlock() // Register this call. client.mutex.Lock() @@ -146,7 +146,7 @@ func (client *Client) input() { } } // Terminate pending calls. - client.sending.Lock() + client.reqMutex.Lock() client.mutex.Lock() client.shutdown = true closing := client.closing @@ -162,7 +162,7 @@ func (client *Client) input() { call.done() } client.mutex.Unlock() - client.sending.Unlock() + client.reqMutex.Unlock() if debugLog && err != io.EOF && !closing { log.Println("rpc: client protocol error:", err) } diff --git a/libgo/go/net/rpc/client_test.go b/libgo/go/net/rpc/client_test.go index bbfc1ec3a3e..5dd111b299f 100644 --- a/libgo/go/net/rpc/client_test.go +++ b/libgo/go/net/rpc/client_test.go @@ -6,6 +6,10 @@ package rpc import ( "errors" + "fmt" + "net" + "runtime" + "strings" "testing" ) @@ -34,3 +38,54 @@ func TestCloseCodec(t *testing.T) { t.Error("client.Close did not close codec") } } + +// Test that errors in gob shut down the connection. Issue 7689. + +type R struct { + msg []byte // Not exported, so R does not work with gob. +} + +type S struct{} + +func (s *S) Recv(nul *struct{}, reply *R) error { + *reply = R{[]byte("foo")} + return nil +} + +func TestGobError(t *testing.T) { + if runtime.GOOS == "plan9" { + t.Skip("skipping test; see http://golang.org/issue/8908") + } + defer func() { + err := recover() + if err == nil { + t.Fatal("no error") + } + if !strings.Contains("reading body EOF", err.(error).Error()) { + t.Fatal("expected `reading body EOF', got", err) + } + }() + Register(new(S)) + + listen, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + panic(err) + } + go Accept(listen) + + client, err := Dial("tcp", listen.Addr().String()) + if err != nil { + panic(err) + } + + var reply Reply + err = client.Call("S.Recv", &struct{}{}, &reply) + if err != nil { + panic(err) + } + + fmt.Printf("%#v\n", reply) + client.Close() + + listen.Close() +} diff --git a/libgo/go/net/rpc/debug.go b/libgo/go/net/rpc/debug.go index 926466d6255..98b2c1c6c4a 100644 --- a/libgo/go/net/rpc/debug.go +++ b/libgo/go/net/rpc/debug.go @@ -11,9 +11,9 @@ package rpc import ( "fmt" + "html/template" "net/http" "sort" - "text/template" ) const debugText = `<html> diff --git a/libgo/go/net/rpc/server.go b/libgo/go/net/rpc/server.go index 6b264b46b8e..83728d55a18 100644 --- a/libgo/go/net/rpc/server.go +++ b/libgo/go/net/rpc/server.go @@ -395,6 +395,7 @@ type gobServerCodec struct { dec *gob.Decoder enc *gob.Encoder encBuf *bufio.Writer + closed bool } func (c *gobServerCodec) ReadRequestHeader(r *Request) error { @@ -407,15 +408,32 @@ func (c *gobServerCodec) ReadRequestBody(body interface{}) error { func (c *gobServerCodec) WriteResponse(r *Response, body interface{}) (err error) { if err = c.enc.Encode(r); err != nil { + if c.encBuf.Flush() == nil { + // Gob couldn't encode the header. Should not happen, so if it does, + // shut down the connection to signal that the connection is broken. + log.Println("rpc: gob error encoding response:", err) + c.Close() + } return } if err = c.enc.Encode(body); err != nil { + if c.encBuf.Flush() == nil { + // Was a gob problem encoding the body but the header has been written. + // Shut down the connection to signal that the connection is broken. + log.Println("rpc: gob error encoding body:", err) + c.Close() + } return } return c.encBuf.Flush() } func (c *gobServerCodec) Close() error { + if c.closed { + // Only call c.rwc.Close once; otherwise the semantics are undefined. + return nil + } + c.closed = true return c.rwc.Close() } @@ -426,7 +444,12 @@ func (c *gobServerCodec) Close() error { // connection. To use an alternate codec, use ServeCodec. func (server *Server) ServeConn(conn io.ReadWriteCloser) { buf := bufio.NewWriter(conn) - srv := &gobServerCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(buf), buf} + srv := &gobServerCodec{ + rwc: conn, + dec: gob.NewDecoder(conn), + enc: gob.NewEncoder(buf), + encBuf: buf, + } server.ServeCodec(srv) } diff --git a/libgo/go/net/singleflight.go b/libgo/go/net/singleflight.go index dc58affdaac..bf599f0cc94 100644 --- a/libgo/go/net/singleflight.go +++ b/libgo/go/net/singleflight.go @@ -8,10 +8,18 @@ import "sync" // call is an in-flight or completed singleflight.Do call type call struct { - wg sync.WaitGroup - val interface{} - err error - dups int + wg sync.WaitGroup + + // These fields are written once before the WaitGroup is done + // and are only read after the WaitGroup is done. + val interface{} + err error + + // These fields are read and written with the singleflight + // mutex held before the WaitGroup is done, and are read but + // not written after the WaitGroup is done. + dups int + chans []chan<- singleflightResult } // singleflight represents a class of work and forms a namespace in @@ -21,6 +29,14 @@ type singleflight struct { m map[string]*call // lazily initialized } +// singleflightResult holds the results of Do, so they can be passed +// on a channel. +type singleflightResult struct { + v interface{} + err error + shared bool +} + // Do executes and returns the results of the given function, making // sure that only one execution is in-flight for a given key at a // time. If a duplicate comes in, the duplicate caller waits for the @@ -42,12 +58,52 @@ func (g *singleflight) Do(key string, fn func() (interface{}, error)) (v interfa g.m[key] = c g.mu.Unlock() + g.doCall(c, key, fn) + return c.val, c.err, c.dups > 0 +} + +// DoChan is like Do but returns a channel that will receive the +// results when they are ready. +func (g *singleflight) DoChan(key string, fn func() (interface{}, error)) <-chan singleflightResult { + ch := make(chan singleflightResult, 1) + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + c.chans = append(c.chans, ch) + g.mu.Unlock() + return ch + } + c := &call{chans: []chan<- singleflightResult{ch}} + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + go g.doCall(c, key, fn) + + return ch +} + +// doCall handles the single call for a key. +func (g *singleflight) doCall(c *call, key string, fn func() (interface{}, error)) { c.val, c.err = fn() c.wg.Done() g.mu.Lock() delete(g.m, key) + for _, ch := range c.chans { + ch <- singleflightResult{c.val, c.err, c.dups > 0} + } g.mu.Unlock() +} - return c.val, c.err, c.dups > 0 +// Forget tells the singleflight to forget about a key. Future calls +// to Do for this key will call the function rather than waiting for +// an earlier call to complete. +func (g *singleflight) Forget(key string) { + g.mu.Lock() + delete(g.m, key) + g.mu.Unlock() } diff --git a/libgo/go/net/smtp/smtp_test.go b/libgo/go/net/smtp/smtp_test.go index 3fba1ea5ae3..5c659e8a095 100644 --- a/libgo/go/net/smtp/smtp_test.go +++ b/libgo/go/net/smtp/smtp_test.go @@ -669,7 +669,7 @@ func sendMail(hostPort string) error { // localhostCert is a PEM-encoded TLS cert with SAN IPs // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end // of ASN.1 time). -// generated from src/pkg/crypto/tls: +// generated from src/crypto/tls: // go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h var localhostCert = []byte(`-----BEGIN CERTIFICATE----- MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD diff --git a/libgo/go/net/sock_bsd.go b/libgo/go/net/sock_bsd.go index 48fb7852757..6c37109f5e4 100644 --- a/libgo/go/net/sock_bsd.go +++ b/libgo/go/net/sock_bsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd nacl netbsd openbsd +// +build darwin dragonfly freebsd netbsd openbsd package net diff --git a/libgo/go/net/sock_posix.go b/libgo/go/net/sock_posix.go index c80c7d6a2f1..3f956df65a6 100644 --- a/libgo/go/net/sock_posix.go +++ b/libgo/go/net/sock_posix.go @@ -36,7 +36,7 @@ type sockaddr interface { // socket returns a network file descriptor that is ready for // asynchronous I/O using the network poller. -func socket(net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) { +func socket(net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, deadline time.Time) (fd *netFD, err error) { s, err := sysSocket(family, sotype, proto) if err != nil { return nil, err @@ -75,27 +75,51 @@ func socket(net string, family, sotype, proto int, ipv6only bool, laddr, raddr s if laddr != nil && raddr == nil { switch sotype { case syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET: - if err := fd.listenStream(laddr, listenerBacklog, toAddr); err != nil { + if err := fd.listenStream(laddr, listenerBacklog); err != nil { fd.Close() return nil, err } return fd, nil case syscall.SOCK_DGRAM: - if err := fd.listenDatagram(laddr, toAddr); err != nil { + if err := fd.listenDatagram(laddr); err != nil { fd.Close() return nil, err } return fd, nil } } - if err := fd.dial(laddr, raddr, deadline, toAddr); err != nil { + if err := fd.dial(laddr, raddr, deadline); err != nil { fd.Close() return nil, err } return fd, nil } -func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) error { +func (fd *netFD) addrFunc() func(syscall.Sockaddr) Addr { + switch fd.family { + case syscall.AF_INET, syscall.AF_INET6: + switch fd.sotype { + case syscall.SOCK_STREAM: + return sockaddrToTCP + case syscall.SOCK_DGRAM: + return sockaddrToUDP + case syscall.SOCK_RAW: + return sockaddrToIP + } + case syscall.AF_UNIX: + switch fd.sotype { + case syscall.SOCK_STREAM: + return sockaddrToUnix + case syscall.SOCK_DGRAM: + return sockaddrToUnixgram + case syscall.SOCK_SEQPACKET: + return sockaddrToUnixpacket + } + } + return func(syscall.Sockaddr) Addr { return nil } +} + +func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time) error { var err error var lsa syscall.Sockaddr if laddr != nil { @@ -123,14 +147,14 @@ func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time, toAddr func(sys } lsa, _ = syscall.Getsockname(fd.sysfd) if rsa, _ = syscall.Getpeername(fd.sysfd); rsa != nil { - fd.setAddr(toAddr(lsa), toAddr(rsa)) + fd.setAddr(fd.addrFunc()(lsa), fd.addrFunc()(rsa)) } else { - fd.setAddr(toAddr(lsa), raddr) + fd.setAddr(fd.addrFunc()(lsa), raddr) } return nil } -func (fd *netFD) listenStream(laddr sockaddr, backlog int, toAddr func(syscall.Sockaddr) Addr) error { +func (fd *netFD) listenStream(laddr sockaddr, backlog int) error { if err := setDefaultListenerSockopts(fd.sysfd); err != nil { return err } @@ -148,11 +172,11 @@ func (fd *netFD) listenStream(laddr sockaddr, backlog int, toAddr func(syscall.S return err } lsa, _ := syscall.Getsockname(fd.sysfd) - fd.setAddr(toAddr(lsa), nil) + fd.setAddr(fd.addrFunc()(lsa), nil) return nil } -func (fd *netFD) listenDatagram(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error { +func (fd *netFD) listenDatagram(laddr sockaddr) error { switch addr := laddr.(type) { case *UDPAddr: // We provide a socket that listens to a wildcard @@ -187,6 +211,6 @@ func (fd *netFD) listenDatagram(laddr sockaddr, toAddr func(syscall.Sockaddr) Ad return err } lsa, _ := syscall.Getsockname(fd.sysfd) - fd.setAddr(toAddr(lsa), nil) + fd.setAddr(fd.addrFunc()(lsa), nil) return nil } diff --git a/libgo/go/net/sock_solaris.go b/libgo/go/net/sock_stub.go index 90fe9de894c..ed6b0894893 100644 --- a/libgo/go/net/sock_solaris.go +++ b/libgo/go/net/sock_stub.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build nacl solaris + package net import "syscall" diff --git a/libgo/go/net/sockopt_bsd.go b/libgo/go/net/sockopt_bsd.go index 2fa3b6f1d36..d5b3621c526 100644 --- a/libgo/go/net/sockopt_bsd.go +++ b/libgo/go/net/sockopt_bsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd nacl netbsd openbsd +// +build darwin dragonfly freebsd netbsd openbsd package net @@ -17,7 +17,7 @@ func setDefaultSockopts(s, family, sotype int, ipv6only bool) error { // On DragonFly BSD, we adjust the ephemeral port // range because unlike other BSD systems its default // port range doesn't conform to IANA recommendation - // as described in RFC 6355 and is pretty narrow. + // as described in RFC 6056 and is pretty narrow. switch family { case syscall.AF_INET: syscall.SetsockoptInt(s, syscall.IPPROTO_IP, syscall.IP_PORTRANGE, syscall.IP_PORTRANGE_HIGH) diff --git a/libgo/go/net/sockopt_posix.go b/libgo/go/net/sockopt_posix.go index 921918c37f5..1654d1b85e4 100644 --- a/libgo/go/net/sockopt_posix.go +++ b/libgo/go/net/sockopt_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows +// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows package net diff --git a/libgo/go/net/sockopt_stub.go b/libgo/go/net/sockopt_stub.go new file mode 100644 index 00000000000..de5ee0bb63c --- /dev/null +++ b/libgo/go/net/sockopt_stub.go @@ -0,0 +1,37 @@ +// Copyright 2011 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. + +// +build nacl + +package net + +import "syscall" + +func setDefaultSockopts(s, family, sotype int, ipv6only bool) error { + return nil +} + +func setDefaultListenerSockopts(s int) error { + return nil +} + +func setDefaultMulticastSockopts(s int) error { + return nil +} + +func setReadBuffer(fd *netFD, bytes int) error { + return syscall.ENOPROTOOPT +} + +func setWriteBuffer(fd *netFD, bytes int) error { + return syscall.ENOPROTOOPT +} + +func setKeepAlive(fd *netFD, keepalive bool) error { + return syscall.ENOPROTOOPT +} + +func setLinger(fd *netFD, sec int) error { + return syscall.ENOPROTOOPT +} diff --git a/libgo/go/net/sockoptip_bsd.go b/libgo/go/net/sockoptip_bsd.go index 87132f0f461..2199e480d42 100644 --- a/libgo/go/net/sockoptip_bsd.go +++ b/libgo/go/net/sockoptip_bsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd nacl netbsd openbsd +// +build darwin dragonfly freebsd netbsd openbsd package net diff --git a/libgo/go/net/sockoptip_posix.go b/libgo/go/net/sockoptip_posix.go index b5c80e44909..c2579be9114 100644 --- a/libgo/go/net/sockoptip_posix.go +++ b/libgo/go/net/sockoptip_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux nacl netbsd openbsd windows +// +build darwin dragonfly freebsd linux netbsd openbsd windows package net diff --git a/libgo/go/net/sockoptip_stub.go b/libgo/go/net/sockoptip_stub.go index dcd3a22b57d..32ec5ddb859 100644 --- a/libgo/go/net/sockoptip_stub.go +++ b/libgo/go/net/sockoptip_stub.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build solaris +// +build nacl solaris package net @@ -10,30 +10,30 @@ import "syscall" func setIPv4MulticastInterface(fd *netFD, ifi *Interface) error { // See golang.org/issue/7399. - return syscall.EINVAL + return syscall.ENOPROTOOPT } func setIPv4MulticastLoopback(fd *netFD, v bool) error { // See golang.org/issue/7399. - return syscall.EINVAL + return syscall.ENOPROTOOPT } func joinIPv4Group(fd *netFD, ifi *Interface, ip IP) error { // See golang.org/issue/7399. - return syscall.EINVAL + return syscall.ENOPROTOOPT } func setIPv6MulticastInterface(fd *netFD, ifi *Interface) error { // See golang.org/issue/7399. - return syscall.EINVAL + return syscall.ENOPROTOOPT } func setIPv6MulticastLoopback(fd *netFD, v bool) error { // See golang.org/issue/7399. - return syscall.EINVAL + return syscall.ENOPROTOOPT } func joinIPv6Group(fd *netFD, ifi *Interface, ip IP) error { // See golang.org/issue/7399. - return syscall.EINVAL + return syscall.ENOPROTOOPT } diff --git a/libgo/go/net/tcpsock_posix.go b/libgo/go/net/tcpsock_posix.go index b79b115ca5b..dd78aefa773 100644 --- a/libgo/go/net/tcpsock_posix.go +++ b/libgo/go/net/tcpsock_posix.go @@ -153,7 +153,7 @@ func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) { } func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) { - fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) + fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial") // TCP has a rarely used mechanism called a 'simultaneous connection' in // which Dial("tcp", addr1, addr2) run on the machine at addr1 can @@ -183,7 +183,7 @@ func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, e if err == nil { fd.Close() } - fd, err = internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) + fd, err = internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial") } if err != nil { @@ -231,7 +231,7 @@ func (l *TCPListener) AcceptTCP() (*TCPConn, error) { if l == nil || l.fd == nil { return nil, syscall.EINVAL } - fd, err := l.fd.accept(sockaddrToTCP) + fd, err := l.fd.accept() if err != nil { return nil, err } @@ -291,7 +291,7 @@ func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) { if laddr == nil { laddr = &TCPAddr{} } - fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_STREAM, 0, "listen", sockaddrToTCP) + fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_STREAM, 0, "listen") if err != nil { return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: err} } diff --git a/libgo/go/net/tcpsockopt_darwin.go b/libgo/go/net/tcpsockopt_darwin.go index 33140849c95..1f1609088ba 100644 --- a/libgo/go/net/tcpsockopt_darwin.go +++ b/libgo/go/net/tcpsockopt_darwin.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// TCP socket options for darwin - package net import ( @@ -12,16 +10,20 @@ import ( "time" ) -// Set keep alive period. +const sysTCP_KEEPINTVL = 0x101 + func setKeepAlivePeriod(fd *netFD, d time.Duration) error { if err := fd.incref(); err != nil { return err } defer fd.decref() - // The kernel expects seconds so round to next highest second. d += (time.Second - time.Nanosecond) secs := int(d.Seconds()) - + switch err := syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, sysTCP_KEEPINTVL, secs); err { + case nil, syscall.ENOPROTOOPT: // OS X 10.7 and earlier don't support this option + default: + return os.NewSyscallError("setsockopt", err) + } return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs)) } diff --git a/libgo/go/net/tcpsockopt_dragonfly.go b/libgo/go/net/tcpsockopt_dragonfly.go index d10a77773d8..0aa213239d1 100644 --- a/libgo/go/net/tcpsockopt_dragonfly.go +++ b/libgo/go/net/tcpsockopt_dragonfly.go @@ -10,20 +10,17 @@ import ( "time" ) -// Set keep alive period. func setKeepAlivePeriod(fd *netFD, d time.Duration) error { if err := fd.incref(); err != nil { return err } defer fd.decref() - - // The kernel expects milliseconds so round to next highest millisecond. + // The kernel expects milliseconds so round to next highest + // millisecond. d += (time.Millisecond - time.Nanosecond) - msecs := int(time.Duration(d.Nanoseconds()) / time.Millisecond) - - err := os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, msecs)) - if err != nil { - return err + msecs := int(d / time.Millisecond) + if err := syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, msecs); err != nil { + return os.NewSyscallError("setsockopt", err) } return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, msecs)) } diff --git a/libgo/go/net/tcpsockopt_openbsd.go b/libgo/go/net/tcpsockopt_openbsd.go index 3480f932c80..041e1786a92 100644 --- a/libgo/go/net/tcpsockopt_openbsd.go +++ b/libgo/go/net/tcpsockopt_openbsd.go @@ -2,26 +2,15 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// TCP socket options for openbsd - package net import ( - "os" "syscall" "time" ) -// Set keep alive period. func setKeepAlivePeriod(fd *netFD, d time.Duration) error { - if err := fd.incref(); err != nil { - return err - } - defer fd.decref() - - // The kernel expects seconds so round to next highest second. - d += (time.Second - time.Nanosecond) - secs := int(d.Seconds()) - - return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.SO_KEEPALIVE, secs)) + // OpenBSD has no user-settable per-socket TCP keepalive + // options. + return syscall.ENOPROTOOPT } diff --git a/libgo/go/net/tcpsockopt_posix.go b/libgo/go/net/tcpsockopt_posix.go index 6484bad4b45..0abf3f97f6b 100644 --- a/libgo/go/net/tcpsockopt_posix.go +++ b/libgo/go/net/tcpsockopt_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows +// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows package net diff --git a/libgo/go/net/tcpsockopt_solaris.go b/libgo/go/net/tcpsockopt_solaris.go deleted file mode 100644 index eaab6b6787b..00000000000 --- a/libgo/go/net/tcpsockopt_solaris.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2013 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. - -// TCP socket options for solaris - -package net - -import ( - "os" - "syscall" - "time" -) - -// Set keep alive period. -func setKeepAlivePeriod(fd *netFD, d time.Duration) error { - if err := fd.incref(); err != nil { - return err - } - defer fd.decref() - - // The kernel expects seconds so round to next highest second. - d += (time.Second - time.Nanosecond) - secs := int(d.Seconds()) - - return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.SO_KEEPALIVE, secs)) -} diff --git a/libgo/go/net/tcpsockopt_stub.go b/libgo/go/net/tcpsockopt_stub.go new file mode 100644 index 00000000000..b413a764d82 --- /dev/null +++ b/libgo/go/net/tcpsockopt_stub.go @@ -0,0 +1,20 @@ +// Copyright 2009 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. + +// +build nacl + +package net + +import ( + "syscall" + "time" +) + +func setNoDelay(fd *netFD, noDelay bool) error { + return syscall.ENOPROTOOPT +} + +func setKeepAlivePeriod(fd *netFD, d time.Duration) error { + return syscall.ENOPROTOOPT +} diff --git a/libgo/go/net/tcpsockopt_unix.go b/libgo/go/net/tcpsockopt_unix.go index 2693a541d20..c9f604cad7b 100644 --- a/libgo/go/net/tcpsockopt_unix.go +++ b/libgo/go/net/tcpsockopt_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build freebsd linux nacl netbsd +// +build freebsd linux netbsd solaris package net @@ -12,20 +12,16 @@ import ( "time" ) -// Set keep alive period. func setKeepAlivePeriod(fd *netFD, d time.Duration) error { if err := fd.incref(); err != nil { return err } defer fd.decref() - // The kernel expects seconds so round to next highest second. d += (time.Second - time.Nanosecond) secs := int(d.Seconds()) - - err := os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs)) - if err != nil { - return err + if err := syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs); err != nil { + return os.NewSyscallError("setsockopt", err) } return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs)) } diff --git a/libgo/go/net/tcpsockopt_windows.go b/libgo/go/net/tcpsockopt_windows.go index 8ef1407977f..091f5233f20 100644 --- a/libgo/go/net/tcpsockopt_windows.go +++ b/libgo/go/net/tcpsockopt_windows.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// TCP socket options for windows - package net import ( @@ -18,14 +16,14 @@ func setKeepAlivePeriod(fd *netFD, d time.Duration) error { return err } defer fd.decref() - - // Windows expects milliseconds so round to next highest millisecond. + // The kernel expects milliseconds so round to next highest + // millisecond. d += (time.Millisecond - time.Nanosecond) - millis := uint32(d / time.Millisecond) + msecs := uint32(d / time.Millisecond) ka := syscall.TCPKeepalive{ OnOff: 1, - Time: millis, - Interval: millis, + Time: msecs, + Interval: msecs, } ret := uint32(0) size := uint32(unsafe.Sizeof(ka)) diff --git a/libgo/go/net/testdata/domain-resolv.conf b/libgo/go/net/testdata/domain-resolv.conf new file mode 100644 index 00000000000..ff269180f43 --- /dev/null +++ b/libgo/go/net/testdata/domain-resolv.conf @@ -0,0 +1,5 @@ +# /etc/resolv.conf + +search test invalid +domain localdomain +nameserver 8.8.8.8 diff --git a/libgo/go/net/testdata/empty-resolv.conf b/libgo/go/net/testdata/empty-resolv.conf new file mode 100644 index 00000000000..c4b2b576549 --- /dev/null +++ b/libgo/go/net/testdata/empty-resolv.conf @@ -0,0 +1 @@ +# /etc/resolv.conf diff --git a/libgo/go/net/testdata/resolv.conf b/libgo/go/net/testdata/resolv.conf index 3841bbf9044..04e87eed03f 100644 --- a/libgo/go/net/testdata/resolv.conf +++ b/libgo/go/net/testdata/resolv.conf @@ -1,6 +1,8 @@ # /etc/resolv.conf -domain Home -nameserver 192.168.1.1 +domain localdomain +nameserver 8.8.8.8 +nameserver 2001:4860:4860::8888 +nameserver fe80::1%lo0 options ndots:5 timeout:10 attempts:3 rotate options attempts 3 diff --git a/libgo/go/net/testdata/search-resolv.conf b/libgo/go/net/testdata/search-resolv.conf new file mode 100644 index 00000000000..1c846bfaffc --- /dev/null +++ b/libgo/go/net/testdata/search-resolv.conf @@ -0,0 +1,5 @@ +# /etc/resolv.conf + +domain localdomain +search test invalid +nameserver 8.8.8.8 diff --git a/libgo/go/net/udp_test.go b/libgo/go/net/udp_test.go index e1778779cf5..125bbca6c40 100644 --- a/libgo/go/net/udp_test.go +++ b/libgo/go/net/udp_test.go @@ -9,6 +9,7 @@ import ( "runtime" "strings" "testing" + "time" ) func TestResolveUDPAddr(t *testing.T) { @@ -34,6 +35,46 @@ func TestResolveUDPAddr(t *testing.T) { } } +func TestReadFromUDP(t *testing.T) { + switch runtime.GOOS { + case "nacl", "plan9": + t.Skipf("skipping test on %q, see issue 8916", runtime.GOOS) + } + + ra, err := ResolveUDPAddr("udp", "127.0.0.1:7") + if err != nil { + t.Fatal(err) + } + + la, err := ResolveUDPAddr("udp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + + c, err := ListenUDP("udp", la) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + _, err = c.WriteToUDP([]byte("a"), ra) + if err != nil { + t.Fatal(err) + } + + err = c.SetDeadline(time.Now().Add(100 * time.Millisecond)) + if err != nil { + t.Fatal(err) + } + b := make([]byte, 1) + _, _, err = c.ReadFromUDP(b) + if err == nil { + t.Fatal("ReadFromUDP should fail") + } else if !isTimeout(err) { + t.Fatal(err) + } +} + func TestWriteToUDP(t *testing.T) { switch runtime.GOOS { case "plan9": diff --git a/libgo/go/net/udpsock_posix.go b/libgo/go/net/udpsock_posix.go index 5dfba94e9a6..a0533366a42 100644 --- a/libgo/go/net/udpsock_posix.go +++ b/libgo/go/net/udpsock_posix.go @@ -175,7 +175,7 @@ func DialUDP(net string, laddr, raddr *UDPAddr) (*UDPConn, error) { } func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, error) { - fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_DGRAM, 0, "dial", sockaddrToUDP) + fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_DGRAM, 0, "dial") if err != nil { return nil, &OpError{Op: "dial", Net: net, Addr: raddr, Err: err} } @@ -198,7 +198,7 @@ func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error) { if laddr == nil { laddr = &UDPAddr{} } - fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen", sockaddrToUDP) + fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen") if err != nil { return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: err} } @@ -218,7 +218,7 @@ func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, e if gaddr == nil || gaddr.IP == nil { return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: errMissingAddress} } - fd, err := internetSocket(net, gaddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen", sockaddrToUDP) + fd, err := internetSocket(net, gaddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen") if err != nil { return nil, &OpError{Op: "listen", Net: net, Addr: gaddr, Err: err} } diff --git a/libgo/go/net/unicast_posix_test.go b/libgo/go/net/unicast_posix_test.go index 452ac925428..ab7ef40a758 100644 --- a/libgo/go/net/unicast_posix_test.go +++ b/libgo/go/net/unicast_posix_test.go @@ -204,6 +204,9 @@ func TestDualStackTCPListener(t *testing.T) { // to a test listener with various address families, differnet // listening address and same port. func TestDualStackUDPListener(t *testing.T) { + if testing.Short() { + t.Skip("skipping in -short mode, see issue 5001") + } switch runtime.GOOS { case "plan9": t.Skipf("skipping test on %q", runtime.GOOS) diff --git a/libgo/go/net/unix_test.go b/libgo/go/net/unix_test.go index 05643ddf9ae..1cdff3908c1 100644 --- a/libgo/go/net/unix_test.go +++ b/libgo/go/net/unix_test.go @@ -256,8 +256,11 @@ func TestUnixConnLocalAndRemoteNames(t *testing.T) { t.Fatalf("UnixConn.Write failed: %v", err) } - if runtime.GOOS == "linux" && laddr == "" { - laddr = "@" // autobind feature + switch runtime.GOOS { + case "android", "linux": + if laddr == "" { + laddr = "@" // autobind feature + } } var connAddrs = [3]struct{ got, want Addr }{ {ln.Addr(), ta}, @@ -308,9 +311,13 @@ func TestUnixgramConnLocalAndRemoteNames(t *testing.T) { } }() - if runtime.GOOS == "linux" && laddr == "" { - laddr = "@" // autobind feature + switch runtime.GOOS { + case "android", "linux": + if laddr == "" { + laddr = "@" // autobind feature + } } + var connAddrs = [4]struct{ got, want Addr }{ {c1.LocalAddr(), ta}, {c1.RemoteAddr(), nil}, diff --git a/libgo/go/net/unixsock_posix.go b/libgo/go/net/unixsock_posix.go index 2610779bfd2..3c2e78bdca3 100644 --- a/libgo/go/net/unixsock_posix.go +++ b/libgo/go/net/unixsock_posix.go @@ -42,14 +42,7 @@ func unixSocket(net string, laddr, raddr sockaddr, mode string, deadline time.Ti return nil, errors.New("unknown mode: " + mode) } - f := sockaddrToUnix - if sotype == syscall.SOCK_DGRAM { - f = sockaddrToUnixgram - } else if sotype == syscall.SOCK_SEQPACKET { - f = sockaddrToUnixpacket - } - - fd, err := socket(net, syscall.AF_UNIX, sotype, 0, false, laddr, raddr, deadline, f) + fd, err := socket(net, syscall.AF_UNIX, sotype, 0, false, laddr, raddr, deadline) if err != nil { return nil, err } @@ -286,11 +279,7 @@ func (l *UnixListener) AcceptUnix() (*UnixConn, error) { if l == nil || l.fd == nil { return nil, syscall.EINVAL } - toAddr := sockaddrToUnix - if l.fd.sotype == syscall.SOCK_SEQPACKET { - toAddr = sockaddrToUnixpacket - } - fd, err := l.fd.accept(toAddr) + fd, err := l.fd.accept() if err != nil { return nil, err } diff --git a/libgo/go/net/url/url.go b/libgo/go/net/url/url.go index 75f650a2756..f167408faba 100644 --- a/libgo/go/net/url/url.go +++ b/libgo/go/net/url/url.go @@ -64,7 +64,6 @@ func (e EscapeError) Error() string { // Return true if the specified character should be escaped when // appearing in a URL string, according to RFC 3986. -// When 'all' is true the full range of reserved characters are matched. func shouldEscape(c byte, mode encoding) bool { // §2.3 Unreserved characters (alphanum) if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' { @@ -86,10 +85,12 @@ func shouldEscape(c byte, mode encoding) bool { // last two as well. That leaves only ? to escape. return c == '?' - case encodeUserPassword: // §3.2.2 - // The RFC allows ; : & = + $ , in userinfo, so we must escape only @ and /. - // The parsing of userinfo treats : as special so we must escape that too. - return c == '@' || c == '/' || c == ':' + case encodeUserPassword: // §3.2.1 + // The RFC allows ';', ':', '&', '=', '+', '$', and ',' in + // userinfo, so we must escape only '@', '/', and '?'. + // The parsing of userinfo treats ':' as special so we must escape + // that too. + return c == '@' || c == '/' || c == '?' || c == ':' case encodeQueryComponent: // §3.4 // The RFC reserves (so we must escape) everything. @@ -440,6 +441,24 @@ func parseAuthority(authority string) (user *Userinfo, host string, err error) { } // String reassembles the URL into a valid URL string. +// The general form of the result is one of: +// +// scheme:opaque +// scheme://userinfo@host/path?query#fragment +// +// If u.Opaque is non-empty, String uses the first form; +// otherwise it uses the second form. +// +// In the second form, the following rules apply: +// - if u.Scheme is empty, scheme: is omitted. +// - if u.User is nil, userinfo@ is omitted. +// - if u.Host is empty, host/ is omitted. +// - if u.Scheme and u.Host are empty and u.User is nil, +// the entire scheme://userinfo@host/ is omitted. +// - if u.Host is non-empty and u.Path begins with a /, +// the form host/path does not add its own /. +// - if u.RawQuery is empty, ?query is omitted. +// - if u.Fragment is empty, #fragment is omitted. func (u *URL) String() string { var buf bytes.Buffer if u.Scheme != "" { diff --git a/libgo/go/net/url/url_test.go b/libgo/go/net/url/url_test.go index cad758f2385..d8b19d805d0 100644 --- a/libgo/go/net/url/url_test.go +++ b/libgo/go/net/url/url_test.go @@ -279,6 +279,16 @@ var urltests = []URLTest{ }, "a/b/c", }, + // escaped '?' in username and password + { + "http://%3Fam:pa%3Fsword@google.com", + &URL{ + Scheme: "http", + User: UserPassword("?am", "pa?sword"), + Host: "google.com", + }, + "", + }, } // more useful string for debugging than fmt's struct printer @@ -903,3 +913,49 @@ func TestParseFailure(t *testing.T) { t.Errorf(`ParseQuery(%q) returned error %q, want something containing %q"`, url, errStr, "%gh") } } + +type shouldEscapeTest struct { + in byte + mode encoding + escape bool +} + +var shouldEscapeTests = []shouldEscapeTest{ + // Unreserved characters (§2.3) + {'a', encodePath, false}, + {'a', encodeUserPassword, false}, + {'a', encodeQueryComponent, false}, + {'a', encodeFragment, false}, + {'z', encodePath, false}, + {'A', encodePath, false}, + {'Z', encodePath, false}, + {'0', encodePath, false}, + {'9', encodePath, false}, + {'-', encodePath, false}, + {'-', encodeUserPassword, false}, + {'-', encodeQueryComponent, false}, + {'-', encodeFragment, false}, + {'.', encodePath, false}, + {'_', encodePath, false}, + {'~', encodePath, false}, + + // User information (§3.2.1) + {':', encodeUserPassword, true}, + {'/', encodeUserPassword, true}, + {'?', encodeUserPassword, true}, + {'@', encodeUserPassword, true}, + {'$', encodeUserPassword, false}, + {'&', encodeUserPassword, false}, + {'+', encodeUserPassword, false}, + {',', encodeUserPassword, false}, + {';', encodeUserPassword, false}, + {'=', encodeUserPassword, false}, +} + +func TestShouldEscape(t *testing.T) { + for _, tt := range shouldEscapeTests { + if shouldEscape(tt.in, tt.mode) != tt.escape { + t.Errorf("shouldEscape(%q, %v) returned %v; expected %v", tt.in, tt.mode, !tt.escape, tt.escape) + } + } +} diff --git a/libgo/go/net/z_last_test.go b/libgo/go/net/z_last_test.go index 4f6a54a560a..716c103db26 100644 --- a/libgo/go/net/z_last_test.go +++ b/libgo/go/net/z_last_test.go @@ -8,6 +8,7 @@ import ( "flag" "fmt" "testing" + "time" ) var testDNSFlood = flag.Bool("dnsflood", false, "whether to test dns query flooding") @@ -35,3 +36,64 @@ func TestDNSThreadLimit(t *testing.T) { // If we're still here, it worked. } + +func TestLookupIPDeadline(t *testing.T) { + if !*testDNSFlood { + t.Skip("test disabled; use -dnsflood to enable") + } + + const N = 5000 + const timeout = 3 * time.Second + c := make(chan error, 2*N) + for i := 0; i < N; i++ { + name := fmt.Sprintf("%d.net-test.golang.org", i) + go func() { + _, err := lookupIPDeadline(name, time.Now().Add(timeout/2)) + c <- err + }() + go func() { + _, err := lookupIPDeadline(name, time.Now().Add(timeout)) + c <- err + }() + } + qstats := struct { + succeeded, failed int + timeout, temporary, other int + unknown int + }{} + deadline := time.After(timeout + time.Second) + for i := 0; i < 2*N; i++ { + select { + case <-deadline: + t.Fatal("deadline exceeded") + case err := <-c: + switch err := err.(type) { + case nil: + qstats.succeeded++ + case Error: + qstats.failed++ + if err.Timeout() { + qstats.timeout++ + } + if err.Temporary() { + qstats.temporary++ + } + if !err.Timeout() && !err.Temporary() { + qstats.other++ + } + default: + qstats.failed++ + qstats.unknown++ + } + } + } + + // A high volume of DNS queries for sub-domain of golang.org + // would be coordinated by authoritative or recursive server, + // or stub resolver which implements query-response rate + // limitation, so we can expect some query successes and more + // failures including timeout, temporary and other here. + // As a rule, unknown must not be shown but it might possibly + // happen due to issue 4856 for now. + t.Logf("%v succeeded, %v failed (%v timeout, %v temporary, %v other, %v unknown)", qstats.succeeded, qstats.failed, qstats.timeout, qstats.temporary, qstats.other, qstats.unknown) +} |