Source file src/net/http/http1_server_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package http_test
     6  
     7  import (
     8  	"bufio"
     9  	"errors"
    10  	"internal/nettest"
    11  	"io"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"slices"
    15  	"strings"
    16  	"sync"
    17  	"testing"
    18  	"testing/synctest"
    19  )
    20  
    21  func TestHTTP1ServerInvalidTrailers(t *testing.T) {
    22  	for _, test := range []struct {
    23  		name    string
    24  		request string
    25  	}{{
    26  		name: "invalid trailer",
    27  		request: joinCRLF(
    28  			"POST / HTTP/1.1",
    29  			"Host: example.tld",
    30  			"Trailer: Park",
    31  			"Transfer-Encoding: chunked",
    32  			"",
    33  			"3",
    34  			"xxx",
    35  			"0",
    36  			"I'm not a valid trailer",
    37  			"GET /smuggled HTTP/1.1",
    38  			"Host: example.tld",
    39  			"Content-Length: 0",
    40  			"",
    41  		),
    42  	}, {
    43  		name: "trailer section ends with bare LF",
    44  		request: joinCRLF(
    45  			"POST / HTTP/1.1",
    46  			"Host: example.tld",
    47  			"Transfer-Encoding: chunked",
    48  			"",
    49  			"3",
    50  			"xxx",
    51  			"0",
    52  			"\nGET /smuggled HTTP/1.1",
    53  			"Host: example.tld",
    54  			"Content-Length: 0",
    55  			"",
    56  		),
    57  	}, {
    58  		name: "trailer line ends with bare LF",
    59  		request: joinCRLF(
    60  			"POST / HTTP/1.1",
    61  			"Host: example.tld",
    62  			"Transfer-Encoding: chunked",
    63  			"",
    64  			"3",
    65  			"xxx",
    66  			"0",
    67  			"A: 1\nB: 2",
    68  			"",
    69  		),
    70  	}, {
    71  		name: "bare CR before end of trailers",
    72  		request: joinCRLF(
    73  			"POST / HTTP/1.1",
    74  			"Host: example.tld",
    75  			"Transfer-Encoding: chunked",
    76  			"",
    77  			"3",
    78  			"xxx",
    79  			"0",
    80  			"Foo: bar\r\r\n\r\n",
    81  		),
    82  	}} {
    83  		synctest.Subtest(t, test.name, func(t *testing.T) {
    84  			handler := newTestHandler(t)
    85  			st := newHTTP1ServerTest(t, handler.ServeHTTP)
    86  			defer handler.Close()
    87  
    88  			conn := st.dial()
    89  			conn.writeMessage(test.request)
    90  
    91  			call := handler.nextCall()
    92  			http.NewResponseController(call.w).EnableFullDuplex()
    93  			n, err := io.Copy(io.Discard, call.req.Body)
    94  			if err == nil {
    95  				t.Errorf("read %v request data bytes without error; want error", n)
    96  			}
    97  			call.exit()
    98  
    99  			// We should close the connection after sending the response.
   100  			conn.wantResponse("HTTP/1.1 200 OK", nil)
   101  			conn.wantClosed()
   102  		})
   103  	}
   104  }
   105  
   106  // An http1ServerTest tests an HTTP/1 server using a fake network.
   107  // It must be used in a synctest bubble.
   108  type http1ServerTest struct {
   109  	t  *testing.T
   110  	ts *httptest.Server
   111  }
   112  
   113  func newHTTP1ServerTest(t *testing.T, h http.HandlerFunc) *http1ServerTest {
   114  	if h == nil {
   115  		h = func(w http.ResponseWriter, req *http.Request) {}
   116  	}
   117  	st := &http1ServerTest{
   118  		t:  t,
   119  		ts: httptest.NewTestServer(t, h),
   120  	}
   121  	return st
   122  }
   123  
   124  // client returns a Client that sends requests to the server.
   125  func (st *http1ServerTest) client() *http.Client {
   126  	return st.ts.Client()
   127  }
   128  
   129  // transport returns a Transport that sends requests to the server.
   130  func (st *http1ServerTest) transport() *http.Transport {
   131  	return st.ts.Client().Transport.(*http.Transport)
   132  }
   133  
   134  // dial returns a connection to the server.
   135  func (st *http1ServerTest) dial() *http1TestConn {
   136  	t := st.t
   137  	t.Helper()
   138  	nc, err := st.transport().DialContext(st.t.Context(), "tcp", "example.tld")
   139  	if err != nil {
   140  		t.Fatal(err)
   141  	}
   142  	t.Cleanup(func() {
   143  		nc.Close()
   144  	})
   145  	conn := nc.(*nettest.Conn)
   146  	conn.SetReadError(errWouldBlock) // effectively make reads non-blocking
   147  	return &http1TestConn{
   148  		t:    st.t,
   149  		conn: conn,
   150  		bufr: bufio.NewReader(conn),
   151  	}
   152  }
   153  
   154  var errWouldBlock = errors.New("would block")
   155  
   156  type http1TestConn struct {
   157  	t    *testing.T
   158  	conn *nettest.Conn
   159  	bufr *bufio.Reader
   160  }
   161  
   162  // writeMessage writes a number of CRLF-terminated lines to the connection.
   163  func (tc *http1TestConn) writeMessage(lines ...string) {
   164  	t := tc.t
   165  	t.Helper()
   166  	if _, err := tc.conn.Write([]byte(strings.Join(lines, "\r\n") + "\r\n")); err != nil {
   167  		t.Fatalf("conn write: %v", err)
   168  	}
   169  }
   170  
   171  // readResponse reads a response from the connection (not including the response body).
   172  func (tc *http1TestConn) readResponse() *http.Response {
   173  	t := tc.t
   174  	t.Helper()
   175  	synctest.Wait()
   176  	resp, err := http.ReadResponse(tc.bufr, nil)
   177  	if err != nil {
   178  		t.Fatalf("ReadResponse: %v", err)
   179  	}
   180  	return resp
   181  }
   182  
   183  func (tc *http1TestConn) wantResponse(wantStart string, wantHeaders http.Header) {
   184  	t := tc.t
   185  	t.Helper()
   186  	synctest.Wait()
   187  	gotStart, err := tc.bufr.ReadString('\n')
   188  	if err != nil {
   189  		t.Fatalf("read from conn: %q, %v; want start line %q", gotStart, err, wantStart)
   190  	}
   191  	if got, want := gotStart, wantStart+"\r\n"; got != want {
   192  		t.Fatalf("read start line:\n%q\nwant:\n%q", got, want)
   193  	}
   194  	gotHeaders := make(http.Header)
   195  	for {
   196  		line, err := tc.bufr.ReadString('\n')
   197  		if err != nil {
   198  			t.Fatalf("read from conn: %v (want header)", err)
   199  		}
   200  		line, ok := strings.CutSuffix(line, "\r\n")
   201  		if !ok {
   202  			t.Fatalf("header line has no CRLF suffix: %q", line)
   203  		}
   204  		if line == "" {
   205  			break
   206  		}
   207  		k, v, ok := strings.Cut(line, ": ")
   208  		if !ok {
   209  			t.Fatalf("invalid header line: %q", line)
   210  		}
   211  		gotHeaders[k] = append(gotHeaders[k], v)
   212  	}
   213  	for k, wantv := range wantHeaders {
   214  		gotv := gotHeaders[k]
   215  		if !slices.Equal(gotv, wantv) {
   216  			t.Errorf("header %v = %q, want %q", k, gotv, wantv)
   217  		}
   218  	}
   219  	if t.Failed() {
   220  		t.FailNow()
   221  	}
   222  }
   223  
   224  // wantIdle asserts that the connection is not closed and has no pending data to read.
   225  func (tc *http1TestConn) wantIdle() {
   226  	t := tc.t
   227  	t.Helper()
   228  	synctest.Wait()
   229  	if got, err := tc.bufr.Peek(32); len(got) != 0 || !errors.Is(err, errWouldBlock) {
   230  		t.Fatalf("read from conn: %q, %v; expect conn to be idle", got, err)
   231  	}
   232  }
   233  
   234  // wantClosed asserts that the connection is read-closed and has no pending data to read.
   235  func (tc *http1TestConn) wantClosed() {
   236  	t := tc.t
   237  	t.Helper()
   238  	synctest.Wait()
   239  	if got, err := tc.bufr.Peek(32); len(got) != 0 || err != io.EOF {
   240  		t.Fatalf("read from conn: %q; expect conn to be closed", got)
   241  	}
   242  }
   243  
   244  type testHandler struct {
   245  	t      *testing.T
   246  	mu     sync.Mutex
   247  	calls  []*testHandlerCall
   248  	closed bool
   249  }
   250  
   251  func newTestHandler(t *testing.T) *testHandler {
   252  	h := &testHandler{t: t}
   253  	t.Cleanup(func() {
   254  		// testHandler.Close should be called before the server shuts down.
   255  		// Catch the case where we forgot to do this.
   256  		if !h.closed {
   257  			t.Errorf("testHandler.Close not called")
   258  		}
   259  	})
   260  	return h
   261  }
   262  
   263  func (h *testHandler) Close() {
   264  	h.t.Helper()
   265  	synctest.Wait()
   266  	h.mu.Lock()
   267  	defer h.mu.Unlock()
   268  	if len(h.calls) > 0 {
   269  		h.t.Errorf("test finished with %v handler calls unhandled", len(h.calls))
   270  	}
   271  	for _, call := range h.calls {
   272  		call.exit()
   273  	}
   274  	h.calls = nil
   275  	h.closed = true
   276  }
   277  
   278  func (h *testHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   279  	call := &testHandlerCall{
   280  		w:   w,
   281  		req: req,
   282  		ch:  make(chan func()),
   283  	}
   284  	h.mu.Lock()
   285  	if h.closed {
   286  		h.t.Errorf("test handler called after close")
   287  	}
   288  	h.calls = append(h.calls, call)
   289  	h.mu.Unlock()
   290  	for f := range call.ch {
   291  		f()
   292  	}
   293  }
   294  
   295  func (h *testHandler) nextCall() *testHandlerCall {
   296  	h.t.Helper()
   297  	synctest.Wait()
   298  	h.mu.Lock()
   299  	defer h.mu.Unlock()
   300  	if len(h.calls) == 0 {
   301  		h.t.Fatal("expected server handler call, got none")
   302  	}
   303  	call := h.calls[0]
   304  	h.calls = h.calls[1:]
   305  	h.t.Cleanup(call.exit)
   306  	return call
   307  }
   308  
   309  // testHandlerCall is a call to the server handler's ServeHTTP method.
   310  type testHandlerCall struct {
   311  	w         http.ResponseWriter
   312  	req       *http.Request
   313  	closeOnce sync.Once
   314  	ch        chan func()
   315  }
   316  
   317  // do executes f in the handler's goroutine.
   318  func (call *testHandlerCall) do(f func(http.ResponseWriter, *http.Request)) {
   319  	donec := make(chan struct{})
   320  	call.ch <- func() {
   321  		defer close(donec)
   322  		f(call.w, call.req)
   323  	}
   324  	<-donec
   325  }
   326  
   327  // exit causes the handler to return.
   328  func (call *testHandlerCall) exit() {
   329  	call.closeOnce.Do(func() {
   330  		close(call.ch)
   331  	})
   332  }
   333  
   334  func joinCRLF(s ...string) string {
   335  	return strings.Join(s, "\r\n")
   336  }
   337  

View as plain text