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

View as plain text