// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package http3

import (
	"bytes"
	"compress/gzip"
	"errors"
	"io"
	"net/http"
	"net/http/httptrace"
	"net/textproto"
	"reflect"
	"slices"
	"strconv"
	"strings"
	"testing"
	"testing/synctest"

	"golang.org/x/net/quic"
)

func TestRoundTripSimple(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		req, _ := http.NewRequest("GET", "https://example.tld/", nil)
		req.Header["User-Agent"] = nil
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)
		st.wantSomeHeaders(http.Header{
			":authority": []string{"example.tld"},
			":method":    []string{"GET"},
			":path":      []string{"/"},
			":scheme":    []string{"https"},
		})
		st.writeHeaders(http.Header{
			":status":       []string{"200"},
			"x-some-header": []string{"value"},
		})
		rt.wantStatus(200)
		rt.wantHeaders(http.Header{
			"X-Some-Header": []string{"value"},
		})
	})
}

func TestRoundTripWithBadHeaders(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		req, _ := http.NewRequest("GET", "https://example.tld/", nil)
		req.Header["Invalid\nHeader"] = []string{"x"}
		rt := tc.roundTrip(req)
		rt.wantError("RoundTrip fails when request contains invalid headers")
	})
}

func TestRoundTripWithUnknownFrame(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		req, _ := http.NewRequest("GET", "https://example.tld/", nil)
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)
		st.wantHeaders(nil)

		// Write an unknown frame type before the response HEADERS.
		data := "frame content"
		st.writeVarint(0x1f + 0x21)      // reserved frame type
		st.writeVarint(int64(len(data))) // size
		st.Write([]byte(data))

		st.writeHeaders(http.Header{
			":status": []string{"200"},
		})
		rt.wantStatus(200)
	})
}

func TestRoundTripWithInvalidPushPromise(t *testing.T) {
	// "A client MUST treat receipt of a PUSH_PROMISE frame that contains
	// a larger push ID than the client has advertised as a connection error of H3_ID_ERROR."
	// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.5-5
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		req, _ := http.NewRequest("GET", "https://example.tld/", nil)
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)
		st.wantHeaders(nil)

		// Write a PUSH_PROMISE frame.
		// Since the client hasn't indicated willingness to accept pushes,
		// this is a connection error.
		st.writePushPromise(0, http.Header{
			":path": []string{"/foo"},
		})
		rt.wantError("RoundTrip fails after receiving invalid PUSH_PROMISE")
		tc.wantClosed(
			"push ID exceeds client's MAX_PUSH_ID",
			errH3IDError,
		)
	})
}

func TestRoundTripResponseContentLength(t *testing.T) {
	for _, test := range []struct {
		name              string
		respHeader        http.Header
		wantContentLength int64
		wantError         bool
	}{{
		name: "valid",
		respHeader: http.Header{
			":status":        []string{"200"},
			"content-length": []string{"100"},
		},
		wantContentLength: 100,
	}, {
		name: "absent",
		respHeader: http.Header{
			":status": []string{"200"},
		},
		wantContentLength: -1,
	}, {
		name: "unparsable",
		respHeader: http.Header{
			":status":        []string{"200"},
			"content-length": []string{"1 1"},
		},
		wantError: true,
	}, {
		name: "duplicated",
		respHeader: http.Header{
			":status":        []string{"200"},
			"content-length": []string{"500", "500", "500"},
		},
		wantContentLength: 500,
	}, {
		name: "inconsistent",
		respHeader: http.Header{
			":status":        []string{"200"},
			"content-length": []string{"1", "2"},
		},
		wantError: true,
	}, {
		// 204 responses aren't allowed to contain a Content-Length header.
		// We just ignore it.
		name: "204",
		respHeader: http.Header{
			":status":        []string{"204"},
			"content-length": []string{"100"},
		},
		wantContentLength: -1,
	}} {
		synctestSubtest(t, test.name, func(t *testing.T) {
			tc := newTestClientConn(t)
			tc.greet()

			req, _ := http.NewRequest("GET", "https://example.tld/", nil)
			rt := tc.roundTrip(req)
			st := tc.wantStream(streamTypeRequest)
			st.wantHeaders(nil)
			st.writeHeaders(test.respHeader)
			if test.wantError {
				rt.wantError("invalid content-length in response")
				return
			}
			if got, want := rt.response().ContentLength, test.wantContentLength; got != want {
				t.Errorf("Response.ContentLength = %v, want %v", got, want)
			}
		})
	}
}

func TestRoundTripMalformedResponses(t *testing.T) {
	for _, test := range []struct {
		name       string
		respHeader http.Header
	}{{
		name: "duplicate :status",
		respHeader: http.Header{
			":status": {"200", "204"},
		},
	}, {
		name: "unparsable :status",
		respHeader: http.Header{
			":status": {"frogpants"},
		},
	}, {
		name: "undefined pseudo-header",
		respHeader: http.Header{
			":status":  {"200"},
			":unknown": {"x"},
		},
	}, {
		name:       "no :status",
		respHeader: http.Header{},
	}, {
		name: "header name with control character",
		respHeader: http.Header{
			":status":             {"200"},
			"name\nevilinjection": {"Value"},
		},
	}, {
		name: "header name with uppercase character",
		respHeader: http.Header{
			":status": {"200"},
			"nAme":    {"Value"},
		},
	}, {
		name:       "pseudo-header name with control character",
		respHeader: http.Header{":status\nevilinjection": {"200"}},
	}, {
		name:       "pseudo-header name with uppercase character",
		respHeader: http.Header{":stAtus": {"200"}},
	}, {
		name: "header value with control character",
		respHeader: http.Header{
			":status": {"200"},
			"name":    {"Value\nEvilInjection"},
		},
	}, {
		name:       "pseudo-header value with control character",
		respHeader: http.Header{":status": {"200\nEvilInjection"}},
	}} {
		synctestSubtest(t, test.name, func(t *testing.T) {
			tc := newTestClientConn(t)
			tc.greet()

			req, _ := http.NewRequest("GET", "https://example.tld/", nil)
			rt := tc.roundTrip(req)
			st := tc.wantStream(streamTypeRequest)
			st.wantHeaders(nil)
			st.writeHeadersRaw(test.respHeader)
			rt.wantError("malformed response")
		})
	}
}

func TestRoundTripCrumbledCookiesInResponse(t *testing.T) {
	// "If a decompressed field section contains multiple cookie field lines,
	// these MUST be concatenated into a single byte string [...]"
	// using the two-byte delimiter of "; "''
	// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2.1-2
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		req, _ := http.NewRequest("GET", "https://example.tld/", nil)
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)
		st.wantHeaders(nil)
		st.writeHeaders(http.Header{
			":status": []string{"200"},
			"cookie":  []string{"a=1", "b=2; c=3", "d=4"},
		})
		rt.wantStatus(200)
		rt.wantHeaders(http.Header{
			"Cookie": []string{"a=1; b=2; c=3; d=4"},
		})
	})
}

func TestRoundTripRequestBodySent(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		bodyr, bodyw := io.Pipe()

		req, _ := http.NewRequest("GET", "https://example.tld/", bodyr)
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)
		st.wantHeaders(nil)

		bodyw.Write([]byte{0, 1, 2, 3, 4})
		st.wantData([]byte{0, 1, 2, 3, 4})

		bodyw.Write([]byte{5, 6, 7})
		st.wantData([]byte{5, 6, 7})

		bodyw.Close()
		st.wantClosed("request body sent")

		st.writeHeaders(http.Header{
			":status": []string{"200"},
		})
		rt.wantStatus(200)
		rt.response().Body.Close()
	})
}

func TestRoundTripRequestBodyErrors(t *testing.T) {
	for _, test := range []struct {
		name          string
		body          io.Reader
		contentLength int64
	}{{
		name:          "too short",
		contentLength: 10,
		body:          bytes.NewReader([]byte{0, 1, 2, 3, 4}),
	}, {
		name:          "too long",
		contentLength: 5,
		body:          bytes.NewReader([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}),
	}, {
		name: "read error",
		body: io.MultiReader(
			bytes.NewReader([]byte{0, 1, 2, 3, 4}),
			&testReader{
				readFunc: func([]byte) (int, error) {
					return 0, errors.New("read error")
				},
			},
		),
	}} {
		synctestSubtest(t, test.name, func(t *testing.T) {
			tc := newTestClientConn(t)
			tc.greet()

			req, _ := http.NewRequest("GET", "https://example.tld/", test.body)
			req.ContentLength = test.contentLength
			rt := tc.roundTrip(req)
			st := tc.wantStream(streamTypeRequest)

			// The Transport should send some number of frames before detecting an
			// error in the request body and aborting the request.
			synctest.Wait()
			for {
				_, err := st.readFrameHeader()
				if err != nil {
					var code quic.StreamErrorCode
					if !errors.As(err, &code) {
						t.Fatalf("request stream closed with error %v: want QUIC stream error", err)
					}
					break
				}
				if err := st.discardFrame(); err != nil {
					t.Fatalf("discardFrame: %v", err)
				}
			}

			// RoundTrip returns with an error.
			rt.wantError("request fails due to body error")
		})
	}
}

func TestRoundTripRequestBodyErrorAfterHeaders(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		bodyr, bodyw := io.Pipe()
		req, _ := http.NewRequest("GET", "https://example.tld/", bodyr)
		req.ContentLength = 10
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)

		// Server sends response headers, and RoundTrip returns.
		// The request body hasn't been sent yet.
		st.wantHeaders(nil)
		st.writeHeaders(http.Header{
			":status": []string{"200"},
		})
		rt.wantStatus(200)

		// Write too many bytes to the request body, triggering a request error.
		bodyw.Write(make([]byte, req.ContentLength+1))

		//io.Copy(io.Discard, st)
		st.wantError(quic.StreamErrorCode(errH3InternalError))

		if err := rt.response().Body.Close(); err == nil {
			t.Fatalf("Response.Body.Close() = %v, want error", err)
		}
	})
}

func TestRoundTripExpect100Continue(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		var callCount1xx, callCount100, callCount100Wait int
		trace := &httptrace.ClientTrace{
			Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
				callCount1xx++
				return nil
			},
			Got100Continue: func() {
				callCount100++
			},
			Wait100Continue: func() {
				callCount100Wait++
			},
		}

		tc := newTestClientConn(t)
		tc.greet()
		clientBody := []byte("client's body that will be sent later")
		serverBody := []byte("server's body")

		// Client sends an Expect: 100-continue request.
		req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), "GET", "https://example.tld/", bytes.NewBuffer(clientBody))
		req.Header = http.Header{"Expect": {"100-continue"}}
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)

		// Server reads the header.
		st.wantHeaders(nil)
		st.wantIdle("client has yet to send its body")

		// Server responds with HTTP status 100.
		st.writeHeaders(http.Header{
			":status": []string{"100"},
		})

		// Client sends its body after receiving HTTP status 100 response.
		st.wantData(clientBody)

		// The server sends its response after getting the client's body.
		st.writeHeaders(http.Header{
			":status": []string{"200"},
		})
		st.writeData(serverBody)
		st.CloseWrite()

		// Client receives the response from server.
		rt.wantStatus(200)
		rt.wantBody(serverBody)

		gotCount := []int{callCount1xx, callCount100, callCount100Wait}
		if !slices.Equal(gotCount, []int{1, 1, 1}) {
			t.Errorf("Got1xxResponse, Got100Continue, and Wait100Continue was called %v times respectively, want [1 1 1]", gotCount)
		}
	})
}

func TestRoundTripExpect100ContinueRejected(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		var callCount1xx, callCount100, callCount100Wait int
		trace := &httptrace.ClientTrace{
			Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
				callCount1xx++
				return nil
			},
			Got100Continue: func() {
				callCount100++
			},
			Wait100Continue: func() {
				callCount100Wait++
			},
		}

		tc := newTestClientConn(t)
		tc.greet()

		// Client sends an Expect: 100-continue request.
		req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), "GET", "https://example.tld/", bytes.NewBufferString("client's body"))
		req.Header = http.Header{"Expect": {"100-continue"}}
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)

		// Server reads the header.
		st.wantHeaders(nil)
		st.wantIdle("client has yet to send its body")

		// Server rejects it.
		st.writeHeaders(http.Header{
			":status": []string{"200"},
		})
		st.wantIdle("client does not send its body without getting status 100")
		serverBody := []byte("server's body")
		st.writeData(serverBody)
		st.CloseWrite()

		rt.wantStatus(200)
		rt.wantBody(serverBody)

		gotCount := []int{callCount1xx, callCount100, callCount100Wait}
		if !slices.Equal(gotCount, []int{0, 0, 1}) {
			t.Errorf("Got1xxResponse, Got100Continue, and Wait100Continue was called %v times respectively, want [0 0 1]", gotCount)
		}
	})
}

func TestRoundTripNoBodyClosesStream(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		req, _ := http.NewRequest("PUT", "https://example.tld/", nil)
		tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)

		st.wantHeaders(nil)
		st.wantClosed("no DATA frames to send")
	})
}

func TestRoundTripReadRespWithNoBody(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		// Case 1: we know response body is empty because the server closes the
		// write direction of the stream.
		req, _ := http.NewRequest("GET", "https://example.tld/", nil)
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)
		st.wantHeaders(nil)
		st.writeHeaders(http.Header{
			":status": {"200"},
		})
		st.CloseWrite()
		rt.wantStatus(200)
		st.wantClosed("request is complete")

		// Case 2: we know response body is empty because the server indicates
		// a Content-Length of 0.
		req, _ = http.NewRequest("GET", "https://example.tld/", nil)
		rt = tc.roundTrip(req)
		st = tc.wantStream(streamTypeRequest)
		st.wantHeaders(nil)
		st.writeHeaders(http.Header{
			":status":        {"200"},
			"content-length": {"0"},
		})
		rt.wantStatus(200)
		st.wantClosed("request is complete")

		// Case 3: we know response body is empty because we sent a HEAD
		// request.
		req, _ = http.NewRequest("HEAD", "https://example.tld/", nil)
		rt = tc.roundTrip(req)
		st = tc.wantStream(streamTypeRequest)
		st.wantHeaders(nil)
		st.writeHeaders(http.Header{
			":status":        {"200"},
			"content-length": {"1000"},
		})
		rt.wantStatus(200)
		st.wantClosed("request is complete")
	})
}

func TestRoundTripWriteTrailer(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		var req *http.Request
		req, _ = http.NewRequest("POST", "https://example.tld/", io.MultiReader(
			testReader{readFunc: func(_ []byte) (int, error) {
				req.Trailer["Client-Trailer-A"] = []string{"valuea"}
				// Transport should not send undeclared trailer.
				req.Trailer["Undeclared-Trailer"] = []string{"undeclared"}
				return 0, io.EOF
			}},
			strings.NewReader("a body"),
			testReader{readFunc: func(_ []byte) (int, error) {
				req.Trailer["Client-Trailer-B"] = []string{"valueb"}
				// Transport should not send undeclared trailer.
				req.Trailer["Undeclared-Trailer"] = []string{"undeclared"}
				return 0, io.EOF
			}},
		))
		req.Trailer = http.Header{
			"Client-Trailer-A": nil,
			"Client-Trailer-B": nil,
		}
		tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)
		st.wantHeaders(nil)
		st.wantData([]byte("a body"))
		st.wantHeaders(http.Header{
			"Client-Trailer-A": {"valuea"},
			"Client-Trailer-B": {"valueb"},
		})
		st.wantClosed("request is complete")
	})
}

func TestRoundTripWriteTrailerNoBody(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		var req *http.Request
		req, _ = http.NewRequest("POST", "https://example.tld/", io.MultiReader(
			testReader{readFunc: func(_ []byte) (int, error) {
				req.Trailer["Client-Trailer-A"] = []string{"valuea"}
				// Transport should not send undeclared trailer.
				req.Trailer["Undeclared-Trailer"] = []string{"undeclared"}
				return 0, io.EOF
			}},
			testReader{readFunc: func(_ []byte) (int, error) {
				req.Trailer["Client-Trailer-B"] = []string{"valueb"}
				// Transport should not send undeclared trailer.
				req.Trailer["Undeclared-Trailer"] = []string{"undeclared"}
				return 0, io.EOF
			}},
		))
		req.Trailer = http.Header{
			"Client-Trailer-A": nil,
			"Client-Trailer-B": nil,
		}
		tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)
		st.wantHeaders(nil)
		st.wantHeaders(http.Header{
			"Client-Trailer-A": {"valuea"},
			"Client-Trailer-B": {"valueb"},
		})
		st.wantClosed("request is complete")
	})
}

func TestRoundTripReadTrailer(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		var req *http.Request
		req, _ = http.NewRequest("GET", "https://example.tld/", nil)
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)

		st.wantHeaders(nil)
		st.writeHeaders(http.Header{
			":status": {"200"},
			"trailer": {"Server-Trailer-A, Server-Trailer-B", "server-trailer-c"}, // Should be canonicalized.
		})
		body := []byte("body from server")
		st.writeData(body)
		st.writeHeaders(http.Header{
			"server-trailer-a": {"valuea"},
			// Note that Server-Trailer-B is skipped.
			"server-trailer-c":   {"valuec"},
			"undeclared-trailer": {"undeclared"},
		})

		rt.wantStatus(200)
		// Trailer is stripped off from http.Response.Header and given in http.Response.Trailer.
		rt.wantHeaders(http.Header{})
		rt.wantTrailers(http.Header{
			"Server-Trailer-A": nil,
			"Server-Trailer-B": nil,
			"Server-Trailer-C": nil,
		})

		// Trailer updated after reading the body to EOF.
		rt.wantBody(body)
		rt.wantTrailers(http.Header{
			"Server-Trailer-A": {"valuea"},
			"Server-Trailer-B": nil,
			"Server-Trailer-C": {"valuec"},
			// Transport should accept undeclared trailers.
			"Undeclared-Trailer": {"undeclared"},
		})
		st.wantClosed("request is complete")
	})
}

func TestRoundTripReadTrailerNoBody(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		var req *http.Request
		req, _ = http.NewRequest("GET", "https://example.tld/", nil)
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)

		st.wantHeaders(nil)
		st.writeHeaders(http.Header{
			":status":        {"200"},
			"content-length": {"0"},
			"trailer":        {"Server-Trailer-A, Server-Trailer-B", "server-trailer-c"}, // Should be canonicalized.
		})
		st.writeHeaders(http.Header{
			"server-trailer-a": {"valuea"},
			// Note that Server-Trailer-B is skipped.
			"server-trailer-c":   {"valuec"},
			"undeclared-trailer": {"undeclared"},
		})

		rt.wantStatus(200)
		// Trailer is stripped off from http.Response.Header and given in http.Response.Trailer.
		rt.wantHeaders(http.Header{"Content-Length": {"0"}})
		rt.wantTrailers(http.Header{
			"Server-Trailer-A": nil,
			"Server-Trailer-B": nil,
			"Server-Trailer-C": nil,
		})

		// Trailer updated after reading the empty body to EOF.
		rt.wantBody(make([]byte, 0))
		rt.wantTrailers(http.Header{
			"Server-Trailer-A": {"valuea"},
			"Server-Trailer-B": nil,
			"Server-Trailer-C": {"valuec"},
			// Transport should accept undeclared trailers.
			"Undeclared-Trailer": {"undeclared"},
		})
		st.wantClosed("request is complete")
	})
}

func TestRoundTrip103EarlyHints(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		firstHeader := http.Header{
			":status": {"103"},
			"Link":    {"</style.css>; rel=preload; as=style"},
		}
		secondHeader := http.Header{
			":status": {"103"},
			"Link":    {"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"},
		}

		var respCounter int
		trace := &httptrace.ClientTrace{
			Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
				var wantHeader textproto.MIMEHeader
				switch respCounter {
				case 0:
					wantHeader = textproto.MIMEHeader(firstHeader)
				case 1:
					wantHeader = textproto.MIMEHeader(secondHeader)
				default:
					t.Error("Unexpected 1xx response")
				}
				wantHeader.Del(":status")
				if !reflect.DeepEqual(header, wantHeader) {
					t.Errorf("got %v early hints header, want %v", header, wantHeader)
				}
				respCounter++
				return nil
			},
		}
		req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), "GET", "https://example.tld/", nil)

		tc := newTestClientConn(t)
		tc.greet()
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)

		st.wantHeaders(nil)
		st.writeHeaders(firstHeader)
		st.writeHeaders(secondHeader)

		st.writeHeaders(http.Header{
			":status": {"200"},
		})
		body := []byte("some body")
		st.writeData(body)
		st.CloseWrite()

		rt.wantStatus(200)
		rt.wantBody(body)
		st.wantClosed("request is complete")
	})
}

func TestRoundTripGzipEnabled(t *testing.T) {
	tests := []struct {
		name     string
		explicit bool
	}{
		{
			name:     "transparent",
			explicit: false,
		},
		{
			name:     "explicit",
			explicit: true,
		},
	}
	for _, tt := range tests {
		synctestSubtest(t, tt.name, func(t *testing.T) {
			tc := newTestClientConn(t)
			tc.greet()

			req, _ := http.NewRequest("GET", "https://example.tld/", nil)
			if tt.explicit {
				req.Header.Set("Accept-Encoding", "gzip")
			}
			rt := tc.roundTrip(req)
			st := tc.wantStream(streamTypeRequest)

			// Verify that client sends Accept-Encoding: gzip.
			st.wantSomeHeaders(http.Header{
				"Accept-Encoding": []string{"gzip"},
			})

			// Server responds with gzip.
			var buf bytes.Buffer
			gw := gzip.NewWriter(&buf)
			gw.Write([]byte("hello world"))
			gw.Close()
			st.writeHeaders(http.Header{
				":status":          []string{"200"},
				"content-encoding": []string{"gzip"},
				"content-length":   []string{strconv.Itoa(buf.Len())},
			})
			st.writeData(buf.Bytes())
			st.CloseWrite()

			rt.wantStatus(200)

			if tt.explicit {
				// When user explicitly sets gzip, the server response should
				// be given as is.
				rt.wantBody(buf.Bytes())
				resp, err := rt.result()
				if err != nil {
					t.Fatal(err)
				}
				if resp.Header.Get("Content-Encoding") != "gzip" {
					t.Errorf("Content-Encoding = %q, want gzip", resp.Header.Get("Content-Encoding"))
				}
				if resp.Header.Get("Content-Length") != strconv.Itoa(buf.Len()) {
					t.Errorf("Content-Length = %q, want %d", resp.Header.Get("Content-Length"), buf.Len())
				}
				if resp.ContentLength != int64(buf.Len()) {
					t.Errorf("ContentLength = %d, want %d", resp.ContentLength, buf.Len())
				}
				if resp.Uncompressed {
					t.Errorf("Uncompressed = true, want false")
				}
			} else {
				// When gzip is transparently set, we automatically decode the
				// response body, and make sure stale information about the
				// gzip content length and encoding are updated.
				rt.wantBody([]byte("hello world"))
				resp, err := rt.result()
				if err != nil {
					t.Fatal(err)
				}
				if resp.Header.Get("Content-Encoding") != "" {
					t.Errorf("Content-Encoding = %q, want empty", resp.Header.Get("Content-Encoding"))
				}
				if resp.Header.Get("Content-Length") != "" {
					t.Errorf("Content-Length = %q, want empty", resp.Header.Get("Content-Length"))
				}
				if resp.ContentLength != -1 {
					t.Errorf("ContentLength = %d, want -1", resp.ContentLength)
				}
				if !resp.Uncompressed {
					t.Errorf("Uncompressed = false, want true")
				}
			}
		})
	}
}

func TestRoundTripGzipDisabled(t *testing.T) {
	tests := []struct {
		name  string
		setup func(tc *testClientConn, req *http.Request, wantHeaders http.Header)
	}{
		{
			name: "explicitly disabled",
			setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
				tc.tr.tr1.DisableCompression = true
			},
		},
		{
			name: "HEAD request",
			setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
				req.Method = "HEAD"
				wantHeaders.Set(":method", "HEAD")
			},
		},
		{
			name: "contains Range header",
			setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
				req.Header.Set("Range", "bytes=0-10")
				wantHeaders.Set("Range", "bytes=0-10")
			},
		},
		{
			name: "contains Accept-Encoding-identity header",
			setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
				req.Header.Set("Accept-Encoding", "identity")
				wantHeaders.Set("Accept-Encoding", "identity")
			},
		},
	}
	for _, tt := range tests {
		synctestSubtest(t, tt.name, func(t *testing.T) {
			tc := newTestClientConn(t)
			req, _ := http.NewRequest("GET", "https://example.tld/", nil)
			wantHeaders := http.Header{
				":authority": []string{"example.tld"},
				":method":    []string{"GET"},
				":path":      []string{"/"},
				":scheme":    []string{"https"},
				"User-Agent": []string{"Go-http-client/3"},
			}
			tt.setup(tc, req, wantHeaders)
			tc.greet()

			rt := tc.roundTrip(req)
			st := tc.wantStream(streamTypeRequest)

			// Verify that client does not send Accept-Encoding: gzip.
			st.wantHeaders(wantHeaders)

			st.writeHeaders(http.Header{
				":status": []string{"200"},
			})
			rt.wantStatus(200)
		})
	}
}

func TestRoundTripGzipWithTrailers(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		req, _ := http.NewRequest("GET", "https://example.tld/", nil)
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)

		// Verify that client sends Accept-Encoding: gzip.
		st.wantSomeHeaders(http.Header{
			"Accept-Encoding": []string{"gzip"},
		})

		// Server responds with gzip and trailer declaration.
		var buf bytes.Buffer
		gw := gzip.NewWriter(&buf)
		gw.Write([]byte("hello world"))
		gw.Close()
		st.writeHeaders(http.Header{
			":status":          []string{"200"},
			"content-encoding": []string{"gzip"},
			"trailer":          []string{"Server-Trailer-A"},
		})
		st.writeData(buf.Bytes())
		st.writeHeaders(http.Header{
			"server-trailer-a": {"valuea"},
		})
		st.CloseWrite()

		rt.wantStatus(200)
		rt.wantTrailers(http.Header{
			"Server-Trailer-A": nil,
		})
		rt.wantBody([]byte("hello world"))
		rt.wantTrailers(http.Header{
			"Server-Trailer-A": {"valuea"},
		})
		st.wantClosed("request is complete")
	})
}

func TestRoundTripGzipConcurrentCloseAndRead(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		tc := newTestClientConn(t)
		tc.greet()

		req, _ := http.NewRequest("GET", "https://example.tld/", nil)
		rt := tc.roundTrip(req)
		st := tc.wantStream(streamTypeRequest)

		// Verify that client sends Accept-Encoding: gzip.
		st.wantSomeHeaders(http.Header{
			"Accept-Encoding": []string{"gzip"},
		})

		// Server responds with gzip.
		st.writeHeaders(http.Header{
			":status":          []string{"200"},
			"content-encoding": []string{"gzip"},
		})
		rt.wantStatus(200)

		resp, err := rt.result()
		if err != nil {
			t.Fatal(err)
		}

		// Read from the response body in a goroutine while it is empty.
		// This will block indefinitely.
		readErrCh := make(chan error, 1)
		go func() {
			var p [10]byte
			_, err := resp.Body.Read(p[:])
			readErrCh <- err
		}()
		synctest.Wait()

		if err := resp.Body.Close(); err != nil {
			t.Fatalf("Body.Close() = %v", err)
		}
		synctest.Wait()

		select {
		case err := <-readErrCh:
			if err == nil {
				t.Error("Read returned nil error, want error")
			}
		default:
			t.Error("Read did not unblock on Close")
		}
	})
}
