Your branch is up to date with 'origin/master'. Already up to date. Only in .: .git Only in .: .github Only in .: .gitignore Only in .: DIFF.txt Only in .: LICENSE Only in .: PATENTS Only in .: README.md Only in .: UPSTREAM diff -ur ./upstreamrepo/src/net/http/alpn_test.go ./alpn_test.go --- ./upstreamrepo/src/net/http/alpn_test.go 2024-11-24 23:16:38 +++ ./alpn_test.go 2024-11-22 23:02:43 @@ -11,10 +11,11 @@ "crypto/x509" "fmt" "io" - . "net/http" - "net/http/httptest" "strings" "testing" + + . "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" ) func TestNextProtoUpgrade(t *testing.T) { @@ -35,7 +36,7 @@ ts.TLS = &tls.Config{ NextProtos: []string{"unhandled-proto", "tls-0.9"}, } - ts.Config.TLSNextProto = map[string]func(*Server, *tls.Conn, Handler){ + ts.Config.TLSNextProto = map[string]func(*Server, TLSConn, Handler){ "tls-0.9": handleTLSProtocol09, } ts.StartTLS() @@ -104,7 +105,7 @@ // handleTLSProtocol09 implements the HTTP/0.9 protocol over TLS, for the // TestNextProtoUpgrade test. -func handleTLSProtocol09(srv *Server, conn *tls.Conn, h Handler) { +func handleTLSProtocol09(srv *Server, conn TLSConn, h Handler) { br := bufio.NewReader(conn) line, err := br.ReadString('\n') if err != nil { diff -ur ./upstreamrepo/src/net/http/cgi/cgi_main.go ./cgi/cgi_main.go --- ./upstreamrepo/src/net/http/cgi/cgi_main.go 2024-11-24 23:18:10 +++ ./cgi/cgi_main.go 2024-11-23 11:52:18 @@ -7,12 +7,13 @@ import ( "fmt" "io" - "net/http" "os" "path" "sort" "strings" "time" + + http "github.com/ooni/oohttp" ) func cgiMain() { diff -ur ./upstreamrepo/src/net/http/cgi/child.go ./cgi/child.go --- ./upstreamrepo/src/net/http/cgi/child.go 2024-11-24 23:16:38 +++ ./cgi/child.go 2024-11-22 23:02:44 @@ -14,11 +14,12 @@ "fmt" "io" "net" - "net/http" "net/url" "os" "strconv" "strings" + + http "github.com/ooni/oohttp" ) // Request returns the HTTP request as represented in the current diff -ur ./upstreamrepo/src/net/http/cgi/child_test.go ./cgi/child_test.go --- ./upstreamrepo/src/net/http/cgi/child_test.go 2024-11-24 23:16:38 +++ ./cgi/child_test.go 2024-11-22 23:02:43 @@ -9,10 +9,11 @@ import ( "bufio" "bytes" - "net/http" - "net/http/httptest" "strings" "testing" + + http "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" ) func TestRequest(t *testing.T) { diff -ur ./upstreamrepo/src/net/http/cgi/host.go ./cgi/host.go --- ./upstreamrepo/src/net/http/cgi/host.go 2024-11-24 23:18:10 +++ ./cgi/host.go 2024-11-22 23:02:44 @@ -20,7 +20,6 @@ "io" "log" "net" - "net/http" "net/textproto" "os" "os/exec" @@ -30,6 +29,7 @@ "strconv" "strings" + http "github.com/ooni/oohttp" "golang.org/x/net/http/httpguts" ) diff -ur ./upstreamrepo/src/net/http/cgi/host_test.go ./cgi/host_test.go --- ./upstreamrepo/src/net/http/cgi/host_test.go 2024-11-24 23:18:10 +++ ./cgi/host_test.go 2024-11-23 11:52:36 @@ -9,11 +9,8 @@ import ( "bufio" "fmt" - "internal/testenv" "io" "net" - "net/http" - "net/http/httptest" "os" "path/filepath" "reflect" @@ -22,6 +19,10 @@ "strings" "testing" "time" + + http "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" + testenv "github.com/ooni/oohttp/internal/testenv" ) // TestMain executes the test binary as the cgi server if diff -ur ./upstreamrepo/src/net/http/cgi/integration_test.go ./cgi/integration_test.go --- ./upstreamrepo/src/net/http/cgi/integration_test.go 2024-11-24 23:16:38 +++ ./cgi/integration_test.go 2024-11-23 00:41:57 @@ -12,14 +12,15 @@ "bytes" "errors" "fmt" - "internal/testenv" "io" - "net/http" - "net/http/httptest" "net/url" "os" "strings" "testing" + + http "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" + testenv "github.com/ooni/oohttp/internal/testenv" ) // This test is a CGI host (testing host.go) that runs its own binary diff -ur ./upstreamrepo/src/net/http/client.go ./client.go --- ./upstreamrepo/src/net/http/client.go 2024-11-24 23:18:10 +++ ./client.go 2024-11-22 23:02:44 @@ -17,7 +17,6 @@ "fmt" "io" "log" - "net/http/internal/ascii" "net/url" "reflect" "sort" @@ -25,6 +24,8 @@ "sync" "sync/atomic" "time" + + "github.com/ooni/oohttp/internal/ascii" ) // A Client is an HTTP client. Its zero value ([DefaultClient]) is a diff -ur ./upstreamrepo/src/net/http/client_test.go ./client_test.go --- ./upstreamrepo/src/net/http/client_test.go 2024-11-24 23:18:10 +++ ./client_test.go 2024-11-22 23:02:44 @@ -13,13 +13,9 @@ "encoding/base64" "errors" "fmt" - "internal/testenv" "io" "log" "net" - . "net/http" - "net/http/cookiejar" - "net/http/httptest" "net/url" "reflect" "runtime" @@ -29,6 +25,11 @@ "sync/atomic" "testing" "time" + + . "github.com/ooni/oohttp" + cookiejar "github.com/ooni/oohttp/cookiejar" + httptest "github.com/ooni/oohttp/httptest" + testenv "github.com/ooni/oohttp/internal/testenv" ) var robotsTxtHandler = HandlerFunc(func(w ResponseWriter, r *Request) { diff -ur ./upstreamrepo/src/net/http/clientserver_test.go ./clientserver_test.go --- ./upstreamrepo/src/net/http/clientserver_test.go 2024-11-24 23:18:10 +++ ./clientserver_test.go 2024-11-22 23:02:44 @@ -18,10 +18,6 @@ "io" "log" "net" - . "net/http" - "net/http/httptest" - "net/http/httptrace" - "net/http/httputil" "net/textproto" "net/url" "os" @@ -33,6 +29,11 @@ "sync/atomic" "testing" "time" + + . "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" + httptrace "github.com/ooni/oohttp/httptrace" + httputil "github.com/ooni/oohttp/httputil" ) type testMode string @@ -1076,7 +1077,7 @@ } atomic.AddInt32(&numOpen, 1) c := noteCloseConn{rc, func() { atomic.AddInt32(&numClose, 1) }} - return tls.Client(c, tlsConfig), nil + return TLSClientFactory(c, tlsConfig), nil }, } if err := ExportHttp2ConfigureTransport(tr); err != nil { @@ -1548,9 +1549,9 @@ return } gotLog := strings.TrimSpace(errorLog.String()) - wantLog := "http: superfluous response.WriteHeader call from net/http_test.testWriteHeaderAfterWrite.func1 (clientserver_test.go:" + wantLog := "http: superfluous response.WriteHeader call from github.com/ooni/oohttp.relevantCaller (server.go:" if hijack { - wantLog = "http: response.WriteHeader on hijacked connection from net/http_test.testWriteHeaderAfterWrite.func1 (clientserver_test.go:" + wantLog = "http: response.WriteHeader on hijacked connection from github.com/ooni/oohttp.relevantCaller (server.go:" } if !strings.HasPrefix(gotLog, wantLog) { t.Errorf("stderr output = %q; want %q", gotLog, wantLog) diff -ur ./upstreamrepo/src/net/http/cookie.go ./cookie.go --- ./upstreamrepo/src/net/http/cookie.go 2024-11-24 23:18:10 +++ ./cookie.go 2024-11-22 23:02:44 @@ -9,11 +9,12 @@ "fmt" "log" "net" - "net/http/internal/ascii" "net/textproto" "strconv" "strings" "time" + + ascii "github.com/ooni/oohttp/internal/ascii" ) // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an diff -ur ./upstreamrepo/src/net/http/cookiejar/example_test.go ./cookiejar/example_test.go --- ./upstreamrepo/src/net/http/cookiejar/example_test.go 2024-11-24 23:16:38 +++ ./cookiejar/example_test.go 2024-11-22 23:02:43 @@ -7,10 +7,11 @@ import ( "fmt" "log" - "net/http" - "net/http/cookiejar" - "net/http/httptest" "net/url" + + http "github.com/ooni/oohttp" + cookiejar "github.com/ooni/oohttp/cookiejar" + httptest "github.com/ooni/oohttp/httptest" ) func ExampleNew() { diff -ur ./upstreamrepo/src/net/http/cookiejar/jar.go ./cookiejar/jar.go --- ./upstreamrepo/src/net/http/cookiejar/jar.go 2024-11-24 23:18:10 +++ ./cookiejar/jar.go 2024-11-22 23:02:44 @@ -9,13 +9,14 @@ "errors" "fmt" "net" - "net/http" - "net/http/internal/ascii" "net/url" "sort" "strings" "sync" "time" + + http "github.com/ooni/oohttp" + ascii "github.com/ooni/oohttp/internal/ascii" ) // PublicSuffixList provides the public suffix of a domain. For example: diff -ur ./upstreamrepo/src/net/http/cookiejar/jar_test.go ./cookiejar/jar_test.go --- ./upstreamrepo/src/net/http/cookiejar/jar_test.go 2024-11-24 23:18:10 +++ ./cookiejar/jar_test.go 2024-11-22 23:02:43 @@ -6,12 +6,13 @@ import ( "fmt" - "net/http" "net/url" "sort" "strings" "testing" "time" + + http "github.com/ooni/oohttp" ) // tNow is the synthetic current time used as now during testing. diff -ur ./upstreamrepo/src/net/http/cookiejar/punycode.go ./cookiejar/punycode.go --- ./upstreamrepo/src/net/http/cookiejar/punycode.go 2024-11-24 23:16:38 +++ ./cookiejar/punycode.go 2024-11-22 23:02:43 @@ -8,9 +8,10 @@ import ( "fmt" - "net/http/internal/ascii" "strings" "unicode/utf8" + + ascii "github.com/ooni/oohttp/internal/ascii" ) // These parameter values are specified in section 5. diff -ur ./upstreamrepo/src/net/http/doc.go ./doc.go --- ./upstreamrepo/src/net/http/doc.go 2024-11-24 23:16:38 +++ ./doc.go 2024-11-22 23:02:44 @@ -3,7 +3,12 @@ // license that can be found in the LICENSE file. /* -Package http provides HTTP client and server implementations. +Package http is a fork of stdlib's net/http that provides HTTP client +and server implementations that are compatible with alternative TLS +libraries such as github.com/refraction-networking/utls. + +See https://github.com/ooni/oohttp for more information on how to use +this package and which are its limitations. [Get], [Head], [Post], and [PostForm] make HTTP (or HTTPS) requests: Only in .: example diff -ur ./upstreamrepo/src/net/http/example_filesystem_test.go ./example_filesystem_test.go --- ./upstreamrepo/src/net/http/example_filesystem_test.go 2024-11-24 23:18:10 +++ ./example_filesystem_test.go 2024-11-22 23:02:43 @@ -7,8 +7,9 @@ import ( "io/fs" "log" - "net/http" "strings" + + http "github.com/ooni/oohttp" ) // containsDotFile reports whether name contains a path element starting with a period. diff -ur ./upstreamrepo/src/net/http/example_handle_test.go ./example_handle_test.go --- ./upstreamrepo/src/net/http/example_handle_test.go 2024-11-24 23:16:38 +++ ./example_handle_test.go 2024-11-22 23:02:43 @@ -7,8 +7,9 @@ import ( "fmt" "log" - "net/http" "sync" + + http "github.com/ooni/oohttp" ) type countHandler struct { diff -ur ./upstreamrepo/src/net/http/example_test.go ./example_test.go --- ./upstreamrepo/src/net/http/example_test.go 2024-11-24 23:18:10 +++ ./example_test.go 2024-11-22 23:02:43 @@ -9,9 +9,10 @@ "fmt" "io" "log" - "net/http" "os" "os/signal" + + http "github.com/ooni/oohttp" ) func ExampleHijacker() { diff -ur ./upstreamrepo/src/net/http/fcgi/child.go ./fcgi/child.go --- ./upstreamrepo/src/net/http/fcgi/child.go 2024-11-24 23:16:38 +++ ./fcgi/child.go 2024-11-22 23:02:44 @@ -12,11 +12,12 @@ "fmt" "io" "net" - "net/http" - "net/http/cgi" "os" "strings" "time" + + http "github.com/ooni/oohttp" + cgi "github.com/ooni/oohttp/cgi" ) // request holds the state for an in-progress request. As soon as it's complete, diff -ur ./upstreamrepo/src/net/http/fcgi/fcgi_test.go ./fcgi/fcgi_test.go --- ./upstreamrepo/src/net/http/fcgi/fcgi_test.go 2024-11-24 23:16:38 +++ ./fcgi/fcgi_test.go 2024-11-22 23:02:43 @@ -8,10 +8,11 @@ "bytes" "errors" "io" - "net/http" "strings" "testing" "time" + + http "github.com/ooni/oohttp" ) var sizeTests = []struct { diff -ur ./upstreamrepo/src/net/http/fs.go ./fs.go --- ./upstreamrepo/src/net/http/fs.go 2024-11-24 23:18:10 +++ ./fs.go 2024-11-22 23:02:44 @@ -9,7 +9,6 @@ import ( "errors" "fmt" - "internal/safefilepath" "io" "io/fs" "mime" @@ -23,6 +22,8 @@ "strconv" "strings" "time" + + safefilepath "github.com/ooni/oohttp/internal/safefilepath" ) // A Dir implements [FileSystem] using the native file system restricted to a diff -ur ./upstreamrepo/src/net/http/fs_test.go ./fs_test.go --- ./upstreamrepo/src/net/http/fs_test.go 2024-11-24 23:18:10 +++ ./fs_test.go 2024-11-23 01:23:39 @@ -10,15 +10,11 @@ "compress/gzip" "errors" "fmt" - "internal/testenv" "io" "io/fs" "mime" "mime/multipart" "net" - "net/http" - . "net/http" - "net/http/httptest" "net/url" "os" "os/exec" @@ -31,6 +27,10 @@ "testing" "testing/fstest" "time" + + . "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" + testenv "github.com/ooni/oohttp/internal/testenv" ) const ( @@ -1664,7 +1664,7 @@ func (grw gzipResponseWriter) Flush() { grw.w.Flush() - if fw, ok := grw.ResponseWriter.(http.Flusher); ok { + if fw, ok := grw.ResponseWriter.(Flusher); ok { fw.Flush() } } Only in .: go.mod Only in .: go.sum diff -ur ./upstreamrepo/src/net/http/h2_bundle.go ./h2_bundle.go --- ./upstreamrepo/src/net/http/h2_bundle.go 2024-11-24 23:18:10 +++ ./h2_bundle.go 2024-11-23 01:05:37 @@ -35,7 +35,6 @@ "math/bits" mathrand "math/rand" "net" - "net/http/httptrace" "net/textproto" "net/url" "os" @@ -48,6 +47,7 @@ "sync/atomic" "time" + httptrace "github.com/ooni/oohttp/httptrace" "golang.org/x/net/http/httpguts" "golang.org/x/net/http2/hpack" "golang.org/x/net/idna" @@ -879,7 +879,7 @@ // This code decides which ones live or die. // The return value used is whether c was used. // c is never closed. -func (p *http2clientConnPool) addConnIfNeeded(key string, t *http2Transport, c *tls.Conn) (used bool, err error) { +func (p *http2clientConnPool) addConnIfNeeded(key string, t *http2Transport, c TLSConn) (used bool, err error) { p.mu.Lock() for _, cc := range p.conns[key] { if cc.CanTakeNewRequest() { @@ -915,7 +915,7 @@ err error } -func (c *http2addConnCall) run(t *http2Transport, key string, tc *tls.Conn) { +func (c *http2addConnCall) run(t *http2Transport, key string, tc TLSConn) { cc, err := t.NewClientConn(tc) p := c.p @@ -1894,6 +1894,9 @@ // returned error is ErrFrameTooLarge. Other errors may be of type // ConnectionError, StreamError, or anything else from the underlying // reader. +// +// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID +// indicates the stream responsible for the error. func (fr *http2Framer) ReadFrame() (http2Frame, error) { fr.errDetail = nil if fr.lastFrame != nil { @@ -2926,7 +2929,7 @@ // readMetaFrame returns 0 or more CONTINUATION frames from fr and // merge them into the provided hf and returns a MetaHeadersFrame // with the decoded hpack values. -func (fr *http2Framer) readMetaFrame(hf *http2HeadersFrame) (*http2MetaHeadersFrame, error) { +func (fr *http2Framer) readMetaFrame(hf *http2HeadersFrame) (http2Frame, error) { if fr.AllowIllegalReads { return nil, errors.New("illegal use of AllowIllegalReads with ReadMetaHeaders") } @@ -3013,7 +3016,7 @@ } if _, err := hdec.Write(frag); err != nil { - return nil, http2ConnectionError(http2ErrCodeCompression) + return mh, http2ConnectionError(http2ErrCodeCompression) } if hc.HeadersEnded() { @@ -3030,7 +3033,7 @@ mh.http2HeadersFrame.invalidate() if err := hdec.Close(); err != nil { - return nil, http2ConnectionError(http2ErrCodeCompression) + return mh, http2ConnectionError(http2ErrCodeCompression) } if invalid != nil { fr.errDetail = invalid @@ -3092,6 +3095,41 @@ return buf.String() } +func http2traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool { + return trace != nil && trace.WroteHeaderField != nil +} + +func http2traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) { + if trace != nil && trace.WroteHeaderField != nil { + trace.WroteHeaderField(k, []string{v}) + } +} + +func http2traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error { + if trace != nil { + return trace.Got1xxResponse + } + return nil +} + +// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS +// connection. +func (t *http2Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (TLSConn, error) { + dialer := &tls.Dialer{ + Config: cfg, + } + cn, err := dialer.DialContext(ctx, network, addr) + if err != nil { + return nil, err + } + tlsCn := cn.(TLSConn) // DialContext comment promises this will always succeed + return tlsCn, nil +} + +func http2tlsUnderlyingConn(tc TLSConn) net.Conn { + return tc.NetConn() +} + var http2DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1" type http2goroutineLock uint64 @@ -4111,9 +4149,9 @@ } if s.TLSNextProto == nil { - s.TLSNextProto = map[string]func(*Server, *tls.Conn, Handler){} + s.TLSNextProto = map[string]func(*Server, TLSConn, Handler){} } - protoHandler := func(hs *Server, c *tls.Conn, h Handler) { + protoHandler := func(hs *Server, c TLSConn, h Handler) { if http2testHookOnConn != nil { http2testHookOnConn() } @@ -5297,6 +5335,11 @@ sc.goAway(http2ErrCodeFlowControl) return true case http2ConnectionError: + if res.f != nil { + if id := res.f.Header().StreamID; id > sc.maxClientStreamID { + sc.maxClientStreamID = id + } + } sc.logf("http2: server connection error from %v: %v", sc.conn.RemoteAddr(), ev) sc.goAway(http2ErrCode(ev)) return true // goAway will handle shutdown @@ -7142,7 +7185,7 @@ DialTLS func(network, addr string, cfg *tls.Config) (net.Conn, error) // TLSClientConfig specifies the TLS configuration to use with - // tls.Client. If nil, the default configuration is used. + // TLSClientFactory. If nil, the default configuration is used. TLSClientConfig *tls.Config // ConnPool optionally specifies an alternate connection pool to use. @@ -7307,7 +7350,7 @@ if !http2strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") { t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1") } - upgradeFn := func(authority string, c *tls.Conn) RoundTripper { + upgradeFn := func(authority string, c TLSConn) RoundTripper { addr := http2authorityAddr("https", authority) if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil { go c.Close() @@ -7322,7 +7365,7 @@ return t2 } if m := t1.TLSNextProto; len(m) == 0 { - t1.TLSNextProto = map[string]func(string, *tls.Conn) RoundTripper{ + t1.TLSNextProto = map[string]func(string, TLSConn) RoundTripper{ "h2": upgradeFn, } } else { @@ -8072,7 +8115,7 @@ // A tls.Conn.Close can hang for a long time if the peer is unresponsive. // Try to shut it down more aggressively. func (cc *http2ClientConn) forceCloseConn() { - tc, ok := cc.tconn.(*tls.Conn) + tc, ok := cc.tconn.(TLSConn) if !ok { return } @@ -10262,37 +10305,6 @@ if trace != nil && trace.GotFirstResponseByte != nil { trace.GotFirstResponseByte() } -} - -func http2traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool { - return trace != nil && trace.WroteHeaderField != nil -} - -func http2traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) { - if trace != nil && trace.WroteHeaderField != nil { - trace.WroteHeaderField(k, []string{v}) - } -} - -func http2traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error { - if trace != nil { - return trace.Got1xxResponse - } - return nil -} - -// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS -// connection. -func (t *http2Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) { - dialer := &tls.Dialer{ - Config: cfg, - } - cn, err := dialer.DialContext(ctx, network, addr) - if err != nil { - return nil, err - } - tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed - return tlsCn, nil } // writeFramer is implemented by any type that is used to write frames. diff -ur ./upstreamrepo/src/net/http/header.go ./header.go --- ./upstreamrepo/src/net/http/header.go 2024-11-24 23:18:10 +++ ./header.go 2024-11-22 23:02:44 @@ -6,14 +6,14 @@ import ( "io" - "net/http/httptrace" - "net/http/internal/ascii" "net/textproto" "sort" "strings" "sync" "time" + httptrace "github.com/ooni/oohttp/httptrace" + ascii "github.com/ooni/oohttp/internal/ascii" "golang.org/x/net/http/httpguts" ) diff -ur ./upstreamrepo/src/net/http/header_test.go ./header_test.go --- ./upstreamrepo/src/net/http/header_test.go 2024-11-24 23:16:38 +++ ./header_test.go 2024-11-22 23:02:43 @@ -6,12 +6,13 @@ import ( "bytes" - "internal/race" "reflect" "runtime" "strings" "testing" "time" + + fakerace "github.com/ooni/oohttp/internal/fakerace" ) var headerWriteTests = []struct { @@ -217,10 +218,11 @@ } func TestHeaderWriteSubsetAllocs(t *testing.T) { + t.Skip("test disabled in the github.com/ooni/oohttp fork") if testing.Short() { t.Skip("skipping alloc test in short mode") } - if race.Enabled { + if fakerace.Enabled { t.Skip("skipping test under race detector") } if runtime.GOMAXPROCS(0) > 1 { diff -ur ./upstreamrepo/src/net/http/http_test.go ./http_test.go --- ./upstreamrepo/src/net/http/http_test.go 2024-11-24 23:18:10 +++ ./http_test.go 2024-11-22 23:02:44 @@ -8,7 +8,6 @@ import ( "bytes" - "internal/testenv" "io/fs" "net/url" "os" @@ -16,6 +15,8 @@ "regexp" "strings" "testing" + + testenv "github.com/ooni/oohttp/internal/testenv" ) func TestForeachHeaderElement(t *testing.T) { @@ -52,6 +53,7 @@ // This catches accidental dependencies between the HTTP transport and // server code. func TestCmdGoNoHTTPServer(t *testing.T) { + t.Skip("test disabled in the github.com/ooni/oohttp fork") t.Parallel() goBin := testenv.GoToolPath(t) out, err := testenv.Command(t, goBin, "tool", "nm", goBin).CombinedOutput() @@ -83,6 +85,7 @@ // Tests that the nethttpomithttp2 build tag doesn't rot too much, // even if there's not a regular builder on it. func TestOmitHTTP2(t *testing.T) { + t.Skip("test disabled in the github.com/ooni/oohttp fork") if testing.Short() { t.Skip("skipping in short mode") } @@ -98,6 +101,7 @@ // in short mode. // The TestOmitHTTP2 test above actually runs tests (in long mode). func TestOmitHTTP2Vet(t *testing.T) { + t.Skip("test disabled in the github.com/ooni/oohttp fork") t.Parallel() goTool := testenv.GoToolPath(t) out, err := testenv.Command(t, goTool, "vet", "-tags=nethttpomithttp2", "net/http").CombinedOutput() diff -ur ./upstreamrepo/src/net/http/httptest/example_test.go ./httptest/example_test.go --- ./upstreamrepo/src/net/http/httptest/example_test.go 2024-11-24 23:16:38 +++ ./httptest/example_test.go 2024-11-22 23:02:43 @@ -8,8 +8,9 @@ "fmt" "io" "log" - "net/http" - "net/http/httptest" + + http "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" ) func ExampleResponseRecorder() { diff -ur ./upstreamrepo/src/net/http/httptest/httptest.go ./httptest/httptest.go --- ./upstreamrepo/src/net/http/httptest/httptest.go 2024-11-24 23:18:10 +++ ./httptest/httptest.go 2024-11-22 23:02:44 @@ -10,8 +10,9 @@ "bytes" "crypto/tls" "io" - "net/http" "strings" + + http "github.com/ooni/oohttp" ) // NewRequest returns a new incoming server Request, suitable diff -ur ./upstreamrepo/src/net/http/httptest/httptest_test.go ./httptest/httptest_test.go --- ./upstreamrepo/src/net/http/httptest/httptest_test.go 2024-11-24 23:18:10 +++ ./httptest/httptest_test.go 2024-11-22 23:02:43 @@ -7,11 +7,12 @@ import ( "crypto/tls" "io" - "net/http" "net/url" "reflect" "strings" "testing" + + http "github.com/ooni/oohttp" ) func TestNewRequest(t *testing.T) { diff -ur ./upstreamrepo/src/net/http/httptest/recorder.go ./httptest/recorder.go --- ./upstreamrepo/src/net/http/httptest/recorder.go 2024-11-24 23:16:38 +++ ./httptest/recorder.go 2024-11-22 23:02:44 @@ -8,11 +8,11 @@ "bytes" "fmt" "io" - "net/http" "net/textproto" "strconv" "strings" + http "github.com/ooni/oohttp" "golang.org/x/net/http/httpguts" ) diff -ur ./upstreamrepo/src/net/http/httptest/recorder_test.go ./httptest/recorder_test.go --- ./upstreamrepo/src/net/http/httptest/recorder_test.go 2024-11-24 23:16:38 +++ ./httptest/recorder_test.go 2024-11-22 23:02:43 @@ -7,8 +7,9 @@ import ( "fmt" "io" - "net/http" "testing" + + http "github.com/ooni/oohttp" ) func TestRecorder(t *testing.T) { diff -ur ./upstreamrepo/src/net/http/httptest/server.go ./httptest/server.go --- ./upstreamrepo/src/net/http/httptest/server.go 2024-11-24 23:18:10 +++ ./httptest/server.go 2024-11-22 23:02:44 @@ -13,12 +13,13 @@ "fmt" "log" "net" - "net/http" - "net/http/internal/testcert" "os" "strings" "sync" "time" + + http "github.com/ooni/oohttp" + testcert "github.com/ooni/oohttp/internal/testcert" ) // A Server is an HTTP server listening on a system-chosen port on the diff -ur ./upstreamrepo/src/net/http/httptest/server_test.go ./httptest/server_test.go --- ./upstreamrepo/src/net/http/httptest/server_test.go 2024-11-24 23:18:10 +++ ./httptest/server_test.go 2024-11-22 23:02:43 @@ -8,9 +8,10 @@ "bufio" "io" "net" - "net/http" "sync" "testing" + + http "github.com/ooni/oohttp" ) type newServerFunc func(http.Handler) *Server diff -ur ./upstreamrepo/src/net/http/httptrace/example_test.go ./httptrace/example_test.go --- ./upstreamrepo/src/net/http/httptrace/example_test.go 2024-11-24 23:16:38 +++ ./httptrace/example_test.go 2024-11-22 23:02:43 @@ -7,8 +7,9 @@ import ( "fmt" "log" - "net/http" - "net/http/httptrace" + + http "github.com/ooni/oohttp" + httptrace "github.com/ooni/oohttp/httptrace" ) func Example() { diff -ur ./upstreamrepo/src/net/http/httptrace/trace.go ./httptrace/trace.go --- ./upstreamrepo/src/net/http/httptrace/trace.go 2024-11-24 23:16:38 +++ ./httptrace/trace.go 2024-11-22 23:02:44 @@ -9,7 +9,6 @@ import ( "context" "crypto/tls" - "internal/nettrace" "net" "net/textproto" "reflect" @@ -39,31 +38,6 @@ trace.compose(old) ctx = context.WithValue(ctx, clientEventContextKey{}, trace) - if trace.hasNetHooks() { - nt := &nettrace.Trace{ - ConnectStart: trace.ConnectStart, - ConnectDone: trace.ConnectDone, - } - if trace.DNSStart != nil { - nt.DNSStart = func(name string) { - trace.DNSStart(DNSStartInfo{Host: name}) - } - } - if trace.DNSDone != nil { - nt.DNSDone = func(netIPs []any, coalesced bool, err error) { - addrs := make([]net.IPAddr, len(netIPs)) - for i, ip := range netIPs { - addrs[i] = ip.(net.IPAddr) - } - trace.DNSDone(DNSDoneInfo{ - Addrs: addrs, - Coalesced: coalesced, - Err: err, - }) - } - } - ctx = context.WithValue(ctx, nettrace.TraceKey{}, nt) - } return ctx } diff -ur ./upstreamrepo/src/net/http/httputil/dump.go ./httputil/dump.go --- ./upstreamrepo/src/net/http/httputil/dump.go 2024-11-24 23:16:38 +++ ./httputil/dump.go 2024-11-22 23:02:44 @@ -11,10 +11,11 @@ "fmt" "io" "net" - "net/http" "net/url" "strings" "time" + + http "github.com/ooni/oohttp" ) // drainBody reads all of b to memory and then returns two equivalent diff -ur ./upstreamrepo/src/net/http/httputil/dump_test.go ./httputil/dump_test.go --- ./upstreamrepo/src/net/http/httputil/dump_test.go 2024-11-24 23:16:38 +++ ./httputil/dump_test.go 2024-11-22 23:02:43 @@ -11,13 +11,14 @@ "fmt" "io" "math/rand" - "net/http" "net/url" "runtime" "runtime/pprof" "strings" "testing" "time" + + http "github.com/ooni/oohttp" ) type eofReader struct{} diff -ur ./upstreamrepo/src/net/http/httputil/example_test.go ./httputil/example_test.go --- ./upstreamrepo/src/net/http/httputil/example_test.go 2024-11-24 23:16:38 +++ ./httputil/example_test.go 2024-11-22 23:02:43 @@ -8,11 +8,12 @@ "fmt" "io" "log" - "net/http" - "net/http/httptest" - "net/http/httputil" "net/url" "strings" + + http "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" + httputil "github.com/ooni/oohttp/httputil" ) func ExampleDumpRequest() { diff -ur ./upstreamrepo/src/net/http/httputil/httputil.go ./httputil/httputil.go --- ./upstreamrepo/src/net/http/httputil/httputil.go 2024-11-24 23:16:38 +++ ./httputil/httputil.go 2024-11-22 23:02:44 @@ -8,7 +8,8 @@ import ( "io" - "net/http/internal" + + internal "github.com/ooni/oohttp/internal" ) // NewChunkedReader returns a new chunkedReader that translates the data read from r diff -ur ./upstreamrepo/src/net/http/httputil/persist.go ./httputil/persist.go --- ./upstreamrepo/src/net/http/httputil/persist.go 2024-11-24 23:16:38 +++ ./httputil/persist.go 2024-11-22 23:02:44 @@ -9,9 +9,10 @@ "errors" "io" "net" - "net/http" "net/textproto" "sync" + + http "github.com/ooni/oohttp" ) var ( diff -ur ./upstreamrepo/src/net/http/httputil/reverseproxy.go ./httputil/reverseproxy.go --- ./upstreamrepo/src/net/http/httputil/reverseproxy.go 2024-11-24 23:18:10 +++ ./httputil/reverseproxy.go 2024-11-22 23:02:44 @@ -14,15 +14,15 @@ "log" "mime" "net" - "net/http" - "net/http/httptrace" - "net/http/internal/ascii" "net/textproto" "net/url" "strings" "sync" "time" + http "github.com/ooni/oohttp" + httptrace "github.com/ooni/oohttp/httptrace" + ascii "github.com/ooni/oohttp/internal/ascii" "golang.org/x/net/http/httpguts" ) diff -ur ./upstreamrepo/src/net/http/httputil/reverseproxy_test.go ./httputil/reverseproxy_test.go --- ./upstreamrepo/src/net/http/httputil/reverseproxy_test.go 2024-11-24 23:18:10 +++ ./httputil/reverseproxy_test.go 2024-11-22 23:02:43 @@ -14,10 +14,6 @@ "fmt" "io" "log" - "net/http" - "net/http/httptest" - "net/http/httptrace" - "net/http/internal/ascii" "net/textproto" "net/url" "os" @@ -28,6 +24,11 @@ "sync" "testing" "time" + + http "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" + httptrace "github.com/ooni/oohttp/httptrace" + ascii "github.com/ooni/oohttp/internal/ascii" ) const fakeHopHeader = "X-Fake-Hop-Header-For-Test" Only in ./internal: bisect Only in ./internal: fakerace Only in ./internal: godebug Only in ./internal: godebugs Only in ./internal: safefilepath Only in ./internal: testenv diff -ur ./upstreamrepo/src/net/http/main_test.go ./main_test.go --- ./upstreamrepo/src/net/http/main_test.go 2024-11-24 23:18:10 +++ ./main_test.go 2024-11-22 23:02:43 @@ -8,13 +8,14 @@ "fmt" "io" "log" - "net/http" "os" "runtime" "sort" "strings" "testing" "time" + + http "github.com/ooni/oohttp" ) var quietLog = log.New(io.Discard, "", 0) @@ -44,7 +45,7 @@ // 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, "interestingGoroutines") || + strings.Contains(stack, "oohttp_test.interestingGoroutines") || strings.Contains(stack, "runtime.MHeap_Scavenger") { continue } Only in .: oohttp.png Only in ./upstreamrepo/src/net/http/pprof: pprof.go Only in ./upstreamrepo/src/net/http/pprof: pprof_test.go diff -ur ./upstreamrepo/src/net/http/request.go ./request.go --- ./upstreamrepo/src/net/http/request.go 2024-11-24 23:18:10 +++ ./request.go 2024-11-22 23:02:44 @@ -17,8 +17,6 @@ "io" "mime" "mime/multipart" - "net/http/httptrace" - "net/http/internal/ascii" "net/textproto" "net/url" urlpkg "net/url" @@ -26,6 +24,8 @@ "strings" "sync" + httptrace "github.com/ooni/oohttp/httptrace" + ascii "github.com/ooni/oohttp/internal/ascii" "golang.org/x/net/http/httpguts" "golang.org/x/net/idna" ) diff -ur ./upstreamrepo/src/net/http/request_test.go ./request_test.go --- ./upstreamrepo/src/net/http/request_test.go 2024-11-24 23:18:10 +++ ./request_test.go 2024-11-23 01:59:43 @@ -15,15 +15,15 @@ "io" "math" "mime/multipart" - "net/http" - . "net/http" - "net/http/httptest" "net/url" "os" "reflect" "regexp" "strings" "testing" + + . "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" ) func TestQuery(t *testing.T) { @@ -1056,7 +1056,7 @@ // Ensure that Request.Clone works correctly with PathValue. // See issue 64911. func TestRequestClonePathValue(t *testing.T) { - req, _ := http.NewRequest("GET", "https://example.org/", nil) + req, _ := NewRequest("GET", "https://example.org/", nil) req.SetPathValue("p1", "orig") clonedReq := req.Clone(context.Background()) @@ -1536,7 +1536,7 @@ func TestStatus(t *testing.T) { // The main purpose of this test is to check 405 responses and the Allow header. - h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + h := HandlerFunc(func(w ResponseWriter, r *Request) {}) mux := NewServeMux() mux.Handle("GET /g", h) mux.Handle("POST /p", h) @@ -1564,11 +1564,11 @@ {"PUT", "/r/", 405, "GET, HEAD"}, {"PUT", "/r", 200, ""}, } { - req, err := http.NewRequest(test.method, server.URL+test.path, nil) + req, err := NewRequest(test.method, server.URL+test.path, nil) if err != nil { t.Fatal(err) } - res, err := http.DefaultClient.Do(req) + res, err := DefaultClient.Do(req) if err != nil { t.Fatal(err) } diff -ur ./upstreamrepo/src/net/http/response_test.go ./response_test.go --- ./upstreamrepo/src/net/http/response_test.go 2024-11-24 23:18:10 +++ ./response_test.go 2024-11-22 23:02:44 @@ -12,12 +12,13 @@ "fmt" "go/token" "io" - "net/http/internal" "net/url" "reflect" "regexp" "strings" "testing" + + "github.com/ooni/oohttp/internal" ) type respTest struct { diff -ur ./upstreamrepo/src/net/http/responsecontroller_test.go ./responsecontroller_test.go --- ./upstreamrepo/src/net/http/responsecontroller_test.go 2024-11-24 23:18:10 +++ ./responsecontroller_test.go 2024-11-22 23:02:44 @@ -8,11 +8,12 @@ "errors" "fmt" "io" - . "net/http" "os" "sync" "testing" "time" + + . "github.com/ooni/oohttp" ) func TestResponseControllerFlush(t *testing.T) { run(t, testResponseControllerFlush) } diff -ur ./upstreamrepo/src/net/http/serve_test.go ./serve_test.go --- ./upstreamrepo/src/net/http/serve_test.go 2024-11-24 23:18:10 +++ ./serve_test.go 2024-11-23 00:44:01 @@ -16,21 +16,13 @@ "encoding/json" "errors" "fmt" - "internal/testenv" "io" "log" "math/rand" "mime/multipart" "net" - . "net/http" - "net/http/httptest" - "net/http/httptrace" - "net/http/httputil" - "net/http/internal" - "net/http/internal/testcert" "net/url" "os" - "path/filepath" "reflect" "regexp" "runtime" @@ -41,6 +33,14 @@ "syscall" "testing" "time" + + . "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" + httptrace "github.com/ooni/oohttp/httptrace" + httputil "github.com/ooni/oohttp/httputil" + internal "github.com/ooni/oohttp/internal" + testcert "github.com/ooni/oohttp/internal/testcert" + testenv "github.com/ooni/oohttp/internal/testenv" ) type dummyAddr string @@ -1868,6 +1868,7 @@ // should ignore client request bodies that a handler didn't read // and close the connection. func TestServerUnreadRequestBodyLarge(t *testing.T) { + t.Skip("test disabled in the github.com/ooni/oohttp fork") setParallel(t) if testing.Short() && testenv.Builder() == "" { t.Log("skipping in short mode") @@ -2002,6 +2003,7 @@ } func TestHandlerBodyClose(t *testing.T) { + t.Skip("test disabled in the github.com/ooni/oohttp fork") setParallel(t) if testing.Short() && testenv.Builder() == "" { t.Skip("skipping in -short mode") @@ -6341,10 +6343,6 @@ t.Skip("skipping in short mode") } - pc, curFile, _, _ := runtime.Caller(0) - curFileBaseName := filepath.Base(curFile) - testFuncName := runtime.FuncForPC(pc).Name() - timeoutMsg := "timed out here!" tests := []struct { @@ -6421,14 +6419,11 @@ t.Fatalf("Server logs count mismatch\ngot %d, want %d\n\nGot\n%s\n", g, w, blob) } - lastSpuriousLine := <-lastLine - firstSpuriousLine := lastSpuriousLine - 3 + _ = <-lastLine // Now ensure that the regexes match exactly. // "http: superfluous response.WriteHeader call from .func\d.\d (:lastSpuriousLine-[1, 3]" - for i, logEntry := range logEntries { - wantLine := firstSpuriousLine + i - pat := fmt.Sprintf("^http: superfluous response.WriteHeader call from %s.func\\d+.\\d+ \\(%s:%d\\)$", - testFuncName, curFileBaseName, wantLine) + for _, logEntry := range logEntries { + pat := `^http: superfluous response.WriteHeader call from github.com/ooni/oohttp.relevantCaller \(server.go:` re := regexp.MustCompile(pat) if !re.MatchString(logEntry) { t.Errorf("Log entry mismatch\n\t%s\ndoes not match\n\t%s", logEntry, pat) diff -ur ./upstreamrepo/src/net/http/servemux121.go ./servemux121.go --- ./upstreamrepo/src/net/http/servemux121.go 2024-11-24 23:18:10 +++ ./servemux121.go 2024-11-23 11:49:48 @@ -11,11 +11,12 @@ // they mostly involve renaming functions, usually by unexporting them. import ( - "internal/godebug" "net/url" "sort" "strings" "sync" + + "github.com/ooni/oohttp/internal/godebug" ) var httpmuxgo121 = godebug.New("httpmuxgo121") diff -ur ./upstreamrepo/src/net/http/server.go ./server.go --- ./upstreamrepo/src/net/http/server.go 2024-11-24 23:18:10 +++ ./server.go 2024-11-23 00:38:12 @@ -13,7 +13,6 @@ "crypto/tls" "errors" "fmt" - "internal/godebug" "io" "log" "math/rand" @@ -260,7 +259,7 @@ // rwc is the underlying network connection. // This is never wrapped by other types and is the value given out // to CloseNotifier callers. It is usually of type *net.TCPConn or - // *tls.Conn. + // any type that implements TLSConn. rwc net.Conn // remoteAddr is rwc.RemoteAddr().String(). It is not populated synchronously @@ -1911,7 +1910,7 @@ } }() - if tlsConn, ok := c.rwc.(*tls.Conn); ok { + if tlsConn, ok := c.rwc.(TLSConn); ok { tlsTO := c.server.tlsHandshakeTimeout() if tlsTO > 0 { dl := time.Now().Add(tlsTO) @@ -2784,7 +2783,7 @@ // // The handler is typically nil, in which case [DefaultServeMux] is used. // -// HTTP/2 support is only enabled if the Listener returns [*tls.Conn] +// HTTP/2 support is only enabled if the Listener returns TLSConn // connections and they were configured with "h2" in the TLS // Config.NextProtos. // @@ -2882,7 +2881,7 @@ // automatically closed when the function returns. // If TLSNextProto is not nil, HTTP/2 support is not enabled // automatically. - TLSNextProto map[string]func(*Server, *tls.Conn, Handler) + TLSNextProto map[string]func(*Server, TLSConn, Handler) // ConnState specifies an optional callback function that is // called when a client connection changes state. See the @@ -3195,7 +3194,7 @@ // didn't set it on the http.Server, but did pass it to // tls.NewListener and passed that listener to Serve. // So we should configure HTTP/2 (to set up srv.TLSNextProto) - // in case the listener returns an "h2" *tls.Conn. + // in case the listener returns an "h2" TLSConn. return true } // The user specified a TLSConfig on their http.Server. @@ -3216,7 +3215,7 @@ // new service goroutine for each. The service goroutines read requests and // then call srv.Handler to reply to them. // -// HTTP/2 support is only enabled if the Listener returns [*tls.Conn] +// HTTP/2 support is only enabled if the Listener returns TLSConn // connections and they were configured with "h2" in the TLS // Config.NextProtos. // @@ -3509,8 +3508,6 @@ } } -var http2server = godebug.New("http2server") - // onceSetNextProtoDefaults configures HTTP/2, if the user hasn't // configured otherwise. (by setting srv.TLSNextProto non-nil) // It must only be called via srv.nextProtoOnce (use srv.setupHTTP2_*). @@ -3518,10 +3515,6 @@ if omitBundledHTTP2 { return } - if http2server.Value() == "0" { - http2server.IncNonDefault() - return - } // Enable HTTP/2 by default if the user hasn't otherwise // configured their TLSNextProto map. if srv.TLSNextProto == nil { @@ -3721,7 +3714,7 @@ // Requests come from ALPN protocol handlers. type initALPNRequest struct { ctx context.Context - c *tls.Conn + c TLSConn h serverHandler } diff -ur ./upstreamrepo/src/net/http/sniff_test.go ./sniff_test.go --- ./upstreamrepo/src/net/http/sniff_test.go 2024-11-24 23:18:10 +++ ./sniff_test.go 2024-11-22 23:02:43 @@ -9,11 +9,12 @@ "fmt" "io" "log" - . "net/http" "reflect" "strconv" "strings" "testing" + + . "github.com/ooni/oohttp" ) var sniffTests = []struct { Only in .: stdlibwrapper.go Only in .: stdlibwrapper_test.go Only in .: tlsconn.go Only in .: tools diff -ur ./upstreamrepo/src/net/http/transfer.go ./transfer.go --- ./upstreamrepo/src/net/http/transfer.go 2024-11-24 23:18:10 +++ ./transfer.go 2024-11-24 11:23:18 @@ -9,11 +9,7 @@ "bytes" "errors" "fmt" - "internal/godebug" "io" - "net/http/httptrace" - "net/http/internal" - "net/http/internal/ascii" "net/textproto" "reflect" "sort" @@ -22,6 +18,10 @@ "sync" "time" + httptrace "github.com/ooni/oohttp/httptrace" + internal "github.com/ooni/oohttp/internal" + ascii "github.com/ooni/oohttp/internal/ascii" + "github.com/ooni/oohttp/internal/godebug" "golang.org/x/net/http/httpguts" ) diff -ur ./upstreamrepo/src/net/http/transport.go ./transport.go --- ./upstreamrepo/src/net/http/transport.go 2024-11-24 23:18:10 +++ ./transport.go 2024-11-22 23:02:44 @@ -17,12 +17,9 @@ "crypto/tls" "errors" "fmt" - "internal/godebug" "io" "log" "net" - "net/http/httptrace" - "net/http/internal/ascii" "net/textproto" "net/url" "reflect" @@ -31,6 +28,8 @@ "sync/atomic" "time" + httptrace "github.com/ooni/oohttp/httptrace" + ascii "github.com/ooni/oohttp/internal/ascii" "golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpproxy" ) @@ -172,7 +171,7 @@ DialTLS func(network, addr string) (net.Conn, error) // TLSClientConfig specifies the TLS configuration to use with - // tls.Client. + // TLSClientFactory. // If nil, the default configuration is used. // If non-nil, HTTP/2 support may not be enabled by default. TLSClientConfig *tls.Config @@ -245,7 +244,7 @@ // must return a RoundTripper that then handles the request. // If TLSNextProto is not nil, HTTP/2 support is not enabled // automatically. - TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper + TLSNextProto map[string]func(authority string, c TLSConn) RoundTripper // ProxyConnectHeader optionally specifies headers to send to // proxies during CONNECT requests. @@ -290,6 +289,13 @@ // To use a custom dialer or TLS config and still attempt HTTP/2 // upgrades, set this to true. ForceAttemptHTTP2 bool + + // TLSClientFactory is an ooni/oohttp extension. If this field is not + // nil, we'll use it. Otherwise we'll default to using the + // oohttp.TLSClientFactory global factory. (But, if you set the + // DialTLSContext function, you'll completely bypass this + // per-Transport-or-global TLSClientFactory mechanism.) + TLSClientFactory func(conn net.Conn, config *tls.Config) TLSConn } // A cancelKey is the key of the reqCanceler map. @@ -338,12 +344,13 @@ ForceAttemptHTTP2: t.ForceAttemptHTTP2, WriteBufferSize: t.WriteBufferSize, ReadBufferSize: t.ReadBufferSize, + TLSClientFactory: t.TLSClientFactory, } if t.TLSClientConfig != nil { t2.TLSClientConfig = t.TLSClientConfig.Clone() } if !t.tlsNextProtoWasNil { - npm := map[string]func(authority string, c *tls.Conn) RoundTripper{} + npm := map[string]func(authority string, c TLSConn) RoundTripper{} for k, v := range t.TLSNextProto { npm[k] = v } @@ -366,16 +373,10 @@ return t.DialTLS != nil || t.DialTLSContext != nil } -var http2client = godebug.New("http2client") - // onceSetNextProtoDefaults initializes TLSNextProto. // It must be called via t.nextProtoOnce.Do. func (t *Transport) onceSetNextProtoDefaults() { t.tlsNextProtoWasNil = (t.TLSNextProto == nil) - if http2client.Value() == "0" { - http2client.IncNonDefault() - return - } // If they've already configured http2 with // golang.org/x/net/http2 instead of the bundled copy, try to @@ -1558,7 +1559,7 @@ cfg.NextProtos = nil } plainConn := pconn.conn - tlsConn := tls.Client(plainConn, cfg) + tlsConn := pconn.t.tlsClientFactory(plainConn, cfg) // oohttp ext to allow utls errc := make(chan error, 2) var timer *time.Timer // for canceling TLS handshake if d := pconn.t.TLSHandshakeTimeout; d != 0 { @@ -1625,7 +1626,7 @@ if err != nil { return nil, wrapErr(err) } - if tc, ok := pconn.conn.(*tls.Conn); ok { + if tc, ok := pconn.conn.(TLSConn); ok { // Handshake here, in case DialTLS didn't. TLSNextProto below // depends on it for knowing the connection state. if trace != nil && trace.TLSHandshakeStart != nil { @@ -1784,7 +1785,7 @@ if s := pconn.tlsState; s != nil && s.NegotiatedProtocolIsMutual && s.NegotiatedProtocol != "" { if next, ok := t.TLSNextProto[s.NegotiatedProtocol]; ok { - alt := next(cm.targetAddr, pconn.conn.(*tls.Conn)) + alt := next(cm.targetAddr, pconn.conn.(TLSConn)) if e, ok := alt.(erringRoundTripper); ok { // pconn.conn was closed by next (http2configureTransports.upgradeFn). return nil, e.RoundTripErr() diff -ur ./upstreamrepo/src/net/http/transport_internal_test.go ./transport_internal_test.go --- ./upstreamrepo/src/net/http/transport_internal_test.go 2024-11-24 23:18:10 +++ ./transport_internal_test.go 2024-11-22 23:02:43 @@ -8,13 +8,15 @@ import ( "bytes" + "context" "crypto/tls" "errors" "io" "net" - "net/http/internal/testcert" "strings" "testing" + + testcert "github.com/ooni/oohttp/internal/testcert" ) // Issue 15446: incorrect wrapping of errors when server closes an idle connection. @@ -214,7 +216,8 @@ t.Error(err) return } - if err := sc.(*tls.Conn).Handshake(); err != nil { + ctx := context.Background() + if err := sc.(TLSConn).HandshakeContext(ctx); err != nil { t.Error(err) return } @@ -227,8 +230,8 @@ roundTripped := false tr := &Transport{ DisableKeepAlives: true, - TLSNextProto: map[string]func(string, *tls.Conn) RoundTripper{ - "foo": func(authority string, c *tls.Conn) RoundTripper { + TLSNextProto: map[string]func(string, TLSConn) RoundTripper{ + "foo": func(authority string, c TLSConn) RoundTripper { return roundTripFunc(func(r *Request) (*Response, error) { n, _ := io.Copy(io.Discard, r.Body) if n == 0 { diff -ur ./upstreamrepo/src/net/http/transport_test.go ./transport_test.go --- ./upstreamrepo/src/net/http/transport_test.go 2024-11-24 23:18:10 +++ ./transport_test.go 2024-11-22 23:02:44 @@ -21,16 +21,10 @@ "errors" "fmt" "go/token" - "internal/nettrace" "io" "log" mrand "math/rand" "net" - . "net/http" - "net/http/httptest" - "net/http/httptrace" - "net/http/httputil" - "net/http/internal/testcert" "net/textproto" "net/url" "os" @@ -44,6 +38,11 @@ "testing/iotest" "time" + . "github.com/ooni/oohttp" + httptest "github.com/ooni/oohttp/httptest" + httptrace "github.com/ooni/oohttp/httptrace" + httputil "github.com/ooni/oohttp/httputil" + testcert "github.com/ooni/oohttp/internal/testcert" "golang.org/x/net/http/httpguts" ) @@ -4503,7 +4502,7 @@ func TestTransportAutomaticHTTP2_TLSNextProto(t *testing.T) { testTransportAutoHTTP(t, &Transport{ - TLSNextProto: make(map[string]func(string, *tls.Conn) RoundTripper), + TLSNextProto: make(map[string]func(string, TLSConn) RoundTripper), }, false) } @@ -4612,7 +4611,8 @@ t.Error(err) return } - if err := sc.(*tls.Conn).Handshake(); err != nil { + ctx := context.Background() + if err := sc.(TLSConn).HandshakeContext(ctx); err != nil { t.Error(err) return } @@ -4631,8 +4631,8 @@ tr := &Transport{ DisableKeepAlives: true, - TLSNextProto: map[string]func(string, *tls.Conn) RoundTripper{ - "foo": func(authority string, c *tls.Conn) RoundTripper { + TLSNextProto: map[string]func(string, TLSConn) RoundTripper{ + "foo": func(authority string, c TLSConn) RoundTripper { madeRoundTripper <- true return funcRoundTripper(func() { t.Error("foo RoundTripper should not be called") @@ -4760,201 +4760,6 @@ } } -func TestTransportEventTrace(t *testing.T) { - run(t, func(t *testing.T, mode testMode) { - testTransportEventTrace(t, mode, false) - }, testNotParallel) -} - -// test a non-nil httptrace.ClientTrace but with all hooks set to zero. -func TestTransportEventTrace_NoHooks(t *testing.T) { - run(t, func(t *testing.T, mode testMode) { - testTransportEventTrace(t, mode, true) - }, testNotParallel) -} - -func testTransportEventTrace(t *testing.T, mode testMode, noHooks bool) { - const resBody = "some body" - gotWroteReqEvent := make(chan struct{}, 500) - cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { - if r.Method == "GET" { - // Do nothing for the second request. - return - } - if _, err := io.ReadAll(r.Body); err != nil { - t.Error(err) - } - if !noHooks { - <-gotWroteReqEvent - } - io.WriteString(w, resBody) - }), func(tr *Transport) { - if tr.TLSClientConfig != nil { - tr.TLSClientConfig.InsecureSkipVerify = true - } - }) - defer cst.close() - - cst.tr.ExpectContinueTimeout = 1 * time.Second - - var mu sync.Mutex // guards buf - var buf strings.Builder - logf := func(format string, args ...any) { - mu.Lock() - defer mu.Unlock() - fmt.Fprintf(&buf, format, args...) - buf.WriteByte('\n') - } - - addrStr := cst.ts.Listener.Addr().String() - ip, port, err := net.SplitHostPort(addrStr) - if err != nil { - t.Fatal(err) - } - - // Install a fake DNS server. - ctx := context.WithValue(context.Background(), nettrace.LookupIPAltResolverKey{}, func(ctx context.Context, network, host string) ([]net.IPAddr, error) { - if host != "dns-is-faked.golang" { - t.Errorf("unexpected DNS host lookup for %q/%q", network, host) - return nil, nil - } - return []net.IPAddr{{IP: net.ParseIP(ip)}}, nil - }) - - body := "some body" - req, _ := NewRequest("POST", cst.scheme()+"://dns-is-faked.golang:"+port, strings.NewReader(body)) - req.Header["X-Foo-Multiple-Vals"] = []string{"bar", "baz"} - trace := &httptrace.ClientTrace{ - GetConn: func(hostPort string) { logf("Getting conn for %v ...", hostPort) }, - GotConn: func(ci httptrace.GotConnInfo) { logf("got conn: %+v", ci) }, - GotFirstResponseByte: func() { logf("first response byte") }, - PutIdleConn: func(err error) { logf("PutIdleConn = %v", err) }, - DNSStart: func(e httptrace.DNSStartInfo) { logf("DNS start: %+v", e) }, - DNSDone: func(e httptrace.DNSDoneInfo) { logf("DNS done: %+v", e) }, - ConnectStart: func(network, addr string) { logf("ConnectStart: Connecting to %s %s ...", network, addr) }, - ConnectDone: func(network, addr string, err error) { - if err != nil { - t.Errorf("ConnectDone: %v", err) - } - logf("ConnectDone: connected to %s %s = %v", network, addr, err) - }, - WroteHeaderField: func(key string, value []string) { - logf("WroteHeaderField: %s: %v", key, value) - }, - WroteHeaders: func() { - logf("WroteHeaders") - }, - Wait100Continue: func() { logf("Wait100Continue") }, - Got100Continue: func() { logf("Got100Continue") }, - WroteRequest: func(e httptrace.WroteRequestInfo) { - logf("WroteRequest: %+v", e) - gotWroteReqEvent <- struct{}{} - }, - } - if mode == http2Mode { - trace.TLSHandshakeStart = func() { logf("tls handshake start") } - trace.TLSHandshakeDone = func(s tls.ConnectionState, err error) { - logf("tls handshake done. ConnectionState = %v \n err = %v", s, err) - } - } - if noHooks { - // zero out all func pointers, trying to get some path to crash - *trace = httptrace.ClientTrace{} - } - req = req.WithContext(httptrace.WithClientTrace(ctx, trace)) - - req.Header.Set("Expect", "100-continue") - res, err := cst.c.Do(req) - if err != nil { - t.Fatal(err) - } - logf("got roundtrip.response") - slurp, err := io.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - logf("consumed body") - if string(slurp) != resBody || res.StatusCode != 200 { - t.Fatalf("Got %q, %v; want %q, 200 OK", slurp, res.Status, resBody) - } - res.Body.Close() - - if noHooks { - // Done at this point. Just testing a full HTTP - // requests can happen with a trace pointing to a zero - // ClientTrace, full of nil func pointers. - return - } - - mu.Lock() - got := buf.String() - mu.Unlock() - - wantOnce := func(sub string) { - if strings.Count(got, sub) != 1 { - t.Errorf("expected substring %q exactly once in output.", sub) - } - } - wantOnceOrMore := func(sub string) { - if strings.Count(got, sub) == 0 { - t.Errorf("expected substring %q at least once in output.", sub) - } - } - wantOnce("Getting conn for dns-is-faked.golang:" + port) - wantOnce("DNS start: {Host:dns-is-faked.golang}") - wantOnce("DNS done: {Addrs:[{IP:" + ip + " Zone:}] Err: Coalesced:false}") - wantOnce("got conn: {") - wantOnceOrMore("Connecting to tcp " + addrStr) - wantOnceOrMore("connected to tcp " + addrStr + " = ") - wantOnce("Reused:false WasIdle:false IdleTime:0s") - wantOnce("first response byte") - if mode == http2Mode { - wantOnce("tls handshake start") - wantOnce("tls handshake done") - } else { - wantOnce("PutIdleConn = ") - wantOnce("WroteHeaderField: User-Agent: [Go-http-client/1.1]") - // TODO(meirf): issue 19761. Make these agnostic to h1/h2. (These are not h1 specific, but the - // WroteHeaderField hook is not yet implemented in h2.) - wantOnce(fmt.Sprintf("WroteHeaderField: Host: [dns-is-faked.golang:%s]", port)) - wantOnce(fmt.Sprintf("WroteHeaderField: Content-Length: [%d]", len(body))) - wantOnce("WroteHeaderField: X-Foo-Multiple-Vals: [bar baz]") - wantOnce("WroteHeaderField: Accept-Encoding: [gzip]") - } - wantOnce("WroteHeaders") - wantOnce("Wait100Continue") - wantOnce("Got100Continue") - wantOnce("WroteRequest: {Err:}") - if strings.Contains(got, " to udp ") { - t.Errorf("should not see UDP (DNS) connections") - } - if t.Failed() { - t.Errorf("Output:\n%s", got) - } - - // And do a second request: - req, _ = NewRequest("GET", cst.scheme()+"://dns-is-faked.golang:"+port, nil) - req = req.WithContext(httptrace.WithClientTrace(ctx, trace)) - res, err = cst.c.Do(req) - if err != nil { - t.Fatal(err) - } - if res.StatusCode != 200 { - t.Fatal(res.Status) - } - res.Body.Close() - - mu.Lock() - got = buf.String() - mu.Unlock() - - sub := "Getting conn for dns-is-faked.golang:" - if gotn, want := strings.Count(got, sub), 2; gotn != want { - t.Errorf("substring %q appeared %d times; want %d. Log:\n%s", sub, gotn, want, got) - } - -} - func TestTransportEventTraceTLSVerify(t *testing.T) { run(t, testTransportEventTraceTLSVerify, []testMode{https1Mode, http2Mode}) } @@ -5038,56 +4843,6 @@ } } -func TestTransportEventTraceRealDNS(t *testing.T) { - skipIfDNSHijacked(t) - defer afterTest(t) - tr := &Transport{} - defer tr.CloseIdleConnections() - c := &Client{Transport: tr} - - var mu sync.Mutex // guards buf - var buf strings.Builder - logf := func(format string, args ...any) { - mu.Lock() - defer mu.Unlock() - fmt.Fprintf(&buf, format, args...) - buf.WriteByte('\n') - } - - req, _ := NewRequest("GET", "http://dns-should-not-resolve.golang:80", nil) - trace := &httptrace.ClientTrace{ - DNSStart: func(e httptrace.DNSStartInfo) { logf("DNSStart: %+v", e) }, - DNSDone: func(e httptrace.DNSDoneInfo) { logf("DNSDone: %+v", e) }, - ConnectStart: func(network, addr string) { logf("ConnectStart: %s %s", network, addr) }, - ConnectDone: func(network, addr string, err error) { logf("ConnectDone: %s %s %v", network, addr, err) }, - } - req = req.WithContext(httptrace.WithClientTrace(context.Background(), trace)) - - resp, err := c.Do(req) - if err == nil { - resp.Body.Close() - t.Fatal("expected error during DNS lookup") - } - - mu.Lock() - got := buf.String() - mu.Unlock() - - wantSub := func(sub string) { - if !strings.Contains(got, sub) { - t.Errorf("expected substring %q in output.", sub) - } - } - wantSub("DNSStart: {Host:dns-should-not-resolve.golang}") - wantSub("DNSDone: {Addrs:[] Err:") - if strings.Contains(got, "ConnectStart") || strings.Contains(got, "ConnectDone") { - t.Errorf("should not see Connect events") - } - if t.Failed() { - t.Errorf("Output:\n%s", got) - } -} - // Issue 14353: port can only contain digits. func TestTransportRejectsAlphaPort(t *testing.T) { res, err := Get("http://dummy.tld:123foo/bar") @@ -5154,60 +4909,6 @@ } } -func TestTransportMaxIdleConns(t *testing.T) { - run(t, testTransportMaxIdleConns, []testMode{http1Mode}) -} -func testTransportMaxIdleConns(t *testing.T, mode testMode) { - ts := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { - // No body for convenience. - })).ts - c := ts.Client() - tr := c.Transport.(*Transport) - tr.MaxIdleConns = 4 - - ip, port, err := net.SplitHostPort(ts.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - ctx := context.WithValue(context.Background(), nettrace.LookupIPAltResolverKey{}, func(ctx context.Context, _, host string) ([]net.IPAddr, error) { - return []net.IPAddr{{IP: net.ParseIP(ip)}}, nil - }) - - hitHost := func(n int) { - req, _ := NewRequest("GET", fmt.Sprintf("http://host-%d.dns-is-faked.golang:"+port, n), nil) - req = req.WithContext(ctx) - res, err := c.Do(req) - if err != nil { - t.Fatal(err) - } - res.Body.Close() - } - for i := 0; i < 4; i++ { - hitHost(i) - } - want := []string{ - "|http|host-0.dns-is-faked.golang:" + port, - "|http|host-1.dns-is-faked.golang:" + port, - "|http|host-2.dns-is-faked.golang:" + port, - "|http|host-3.dns-is-faked.golang:" + port, - } - if got := tr.IdleConnKeysForTesting(); !reflect.DeepEqual(got, want) { - t.Fatalf("idle conn keys mismatch.\n got: %q\nwant: %q\n", got, want) - } - - // Now hitting the 5th host should kick out the first host: - hitHost(4) - want = []string{ - "|http|host-1.dns-is-faked.golang:" + port, - "|http|host-2.dns-is-faked.golang:" + port, - "|http|host-3.dns-is-faked.golang:" + port, - "|http|host-4.dns-is-faked.golang:" + port, - } - if got := tr.IdleConnKeysForTesting(); !reflect.DeepEqual(got, want) { - t.Fatalf("idle conn keys mismatch after 5th host.\n got: %q\nwant: %q\n", got, want) - } -} - func TestTransportIdleConnTimeout(t *testing.T) { run(t, testTransportIdleConnTimeout) } func testTransportIdleConnTimeout(t *testing.T, mode testMode) { if testing.Short() { @@ -5392,76 +5093,6 @@ } } -// Issue 13835: international domain names should work -func TestTransportIDNA(t *testing.T) { run(t, testTransportIDNA) } -func testTransportIDNA(t *testing.T, mode testMode) { - const uniDomain = "гофер.го" - const punyDomain = "xn--c1ae0ajs.xn--c1aw" - - var port string - cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { - want := punyDomain + ":" + port - if r.Host != want { - t.Errorf("Host header = %q; want %q", r.Host, want) - } - if mode == http2Mode { - if r.TLS == nil { - t.Errorf("r.TLS == nil") - } else if r.TLS.ServerName != punyDomain { - t.Errorf("TLS.ServerName = %q; want %q", r.TLS.ServerName, punyDomain) - } - } - w.Header().Set("Hit-Handler", "1") - }), func(tr *Transport) { - if tr.TLSClientConfig != nil { - tr.TLSClientConfig.InsecureSkipVerify = true - } - }) - - ip, port, err := net.SplitHostPort(cst.ts.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - - // Install a fake DNS server. - ctx := context.WithValue(context.Background(), nettrace.LookupIPAltResolverKey{}, func(ctx context.Context, network, host string) ([]net.IPAddr, error) { - if host != punyDomain { - t.Errorf("got DNS host lookup for %q/%q; want %q", network, host, punyDomain) - return nil, nil - } - return []net.IPAddr{{IP: net.ParseIP(ip)}}, nil - }) - - req, _ := NewRequest("GET", cst.scheme()+"://"+uniDomain+":"+port, nil) - trace := &httptrace.ClientTrace{ - GetConn: func(hostPort string) { - want := net.JoinHostPort(punyDomain, port) - if hostPort != want { - t.Errorf("getting conn for %q; want %q", hostPort, want) - } - }, - DNSStart: func(e httptrace.DNSStartInfo) { - if e.Host != punyDomain { - t.Errorf("DNSStart Host = %q; want %q", e.Host, punyDomain) - } - }, - } - req = req.WithContext(httptrace.WithClientTrace(ctx, trace)) - - res, err := cst.tr.RoundTrip(req) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - if res.Header.Get("Hit-Handler") != "1" { - out, err := httputil.DumpResponse(res, true) - if err != nil { - t.Fatal(err) - } - t.Errorf("Response body wasn't from Handler. Got:\n%s\n", out) - } -} - // Issue 13290: send User-Agent in proxy CONNECT func TestTransportProxyConnectHeader(t *testing.T) { run(t, testTransportProxyConnectHeader, []testMode{http1Mode}) @@ -6234,11 +5865,12 @@ GetProxyConnectHeader: func(context.Context, *url.URL, string) (Header, error) { return nil, nil }, MaxResponseHeaderBytes: 1, ForceAttemptHTTP2: true, - TLSNextProto: map[string]func(authority string, c *tls.Conn) RoundTripper{ - "foo": func(authority string, c *tls.Conn) RoundTripper { panic("") }, + TLSNextProto: map[string]func(authority string, c TLSConn) RoundTripper{ + "foo": func(authority string, c TLSConn) RoundTripper { panic("") }, }, - ReadBufferSize: 1, - WriteBufferSize: 1, + ReadBufferSize: 1, + WriteBufferSize: 1, + TLSClientFactory: TLSClientFactory, // set to the global one } tr2 := tr.Clone() rv := reflect.ValueOf(tr2).Elem() diff -ur ./upstreamrepo/src/net/http/triv.go ./triv.go --- ./upstreamrepo/src/net/http/triv.go 2024-11-24 23:16:38 +++ ./triv.go 2024-11-22 23:02:44 @@ -12,12 +12,13 @@ "fmt" "io" "log" - "net/http" "os" "os/exec" "strconv" "strings" "sync" + + http "github.com/ooni/oohttp" ) // hello world, the web server Only in .: upstreamrepo diff -ur ./upstreamrepo/src/internal/safefilepath/path_test.go ./internal/safefilepath/path_test.go --- ./upstreamrepo/src/internal/safefilepath/path_test.go 2024-11-24 23:18:10 +++ ./internal/safefilepath/path_test.go 2024-11-22 23:02:43 @@ -5,11 +5,12 @@ package safefilepath_test import ( - "internal/safefilepath" "os" "path/filepath" "runtime" "testing" + + "github.com/ooni/oohttp/internal/safefilepath" ) type PathTest struct {