From 2a82a57b9d87fe932b68b06afd9027671058de4e Mon Sep 17 00:00:00 2001 From: RW Date: Wed, 12 Nov 2025 02:31:36 +0100 Subject: [PATCH] Expose header parsing error variables (#2096) --- header.go | 72 +++++++++++++++++++++++++++--------------------- headerscanner.go | 2 +- server.go | 2 +- server_test.go | 2 +- 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/header.go b/header.go index bc4e725..c27d6c9 100644 --- a/header.go +++ b/header.go @@ -463,7 +463,22 @@ func (h *header) AddTrailer(trailer string) error { return h.AddTrailerBytes(s2b(trailer)) } -var ErrBadTrailer = errors.New("contain forbidden trailer") +var ( + ErrBadTrailer = errors.New("contain forbidden trailer") + ErrReadingResponseHeaders = errors.New("error when reading response headers") + ErrReadingResponseTrailer = errors.New("error when reading response trailer") + ErrResponseFirstLineMissingSpace = errors.New("cannot find whitespace in the first line of response") + ErrUnexpectedStatusCodeChar = errors.New("unexpected char at the end of status code") + ErrMissingRequestMethod = errors.New("cannot find http request method") + ErrUnsupportedRequestMethod = errors.New("unsupported http request method") + ErrExtraWhitespaceInRequestLine = errors.New("extra whitespace in request line") + ErrEmptyRequestURI = errors.New("requestURI cannot be empty") + ErrDuplicateContentLength = errors.New("duplicate Content-Length header") + ErrUnsupportedTransferEncoding = errors.New("unsupported Transfer-Encoding") + ErrNonNumericChars = errors.New("non-numeric chars found") + ErrNeedMore = errors.New("need more data: cannot find trailing lf") + ErrSmallReadBuffer = errors.New("small read buffer. Increase ReadBufferSize") +) // AddTrailerBytes add Trailer header value for chunked response // to indicate which headers will be sent after the body. @@ -2088,7 +2103,7 @@ func (h *ResponseHeader) Read(r *bufio.Reader) error { if err == nil { return nil } - if err != errNeedMore { + if err != ErrNeedMore { h.resetSkipNormalize() return err } @@ -2113,11 +2128,11 @@ func (h *ResponseHeader) tryRead(r *bufio.Reader, n int) error { if err == bufio.ErrBufferFull { if h.secureErrorLogMessage { return &ErrSmallBuffer{ - error: errors.New("error when reading response headers"), + error: ErrReadingResponseHeaders, } } return &ErrSmallBuffer{ - error: fmt.Errorf("error when reading response headers: %w", errSmallBuffer), + error: fmt.Errorf("error when reading response headers: %w", ErrSmallReadBuffer), } } @@ -2142,7 +2157,7 @@ func (h *header) ReadTrailer(r *bufio.Reader) error { if err == nil { return nil } - if err != errNeedMore { + if err != ErrNeedMore { return err } n = r.Buffered() + 1 @@ -2165,11 +2180,11 @@ func (h *header) tryReadTrailer(r *bufio.Reader, n int) error { if err == bufio.ErrBufferFull { if h.secureErrorLogMessage { return &ErrSmallBuffer{ - error: errors.New("error when reading response trailer"), + error: ErrReadingResponseTrailer, } } return &ErrSmallBuffer{ - error: fmt.Errorf("error when reading response trailer: %w", errSmallBuffer), + error: fmt.Errorf("error when reading response trailer: %w", ErrSmallReadBuffer), } } @@ -2189,11 +2204,11 @@ func (h *header) tryReadTrailer(r *bufio.Reader, n int) error { } func headerError(typ string, err, errParse error, b []byte, secureErrorLogMessage bool) error { - if errParse != errNeedMore { + if errParse != ErrNeedMore { return headerErrorMsg(typ, errParse, b, secureErrorLogMessage) } if err == nil { - return errNeedMore + return ErrNeedMore } // Buggy servers may leave trailing CRLFs after http body. @@ -2206,7 +2221,7 @@ func headerError(typ string, err, errParse error, b []byte, secureErrorLogMessag return headerErrorMsg(typ, err, b, secureErrorLogMessage) } return &ErrSmallBuffer{ - error: headerErrorMsg(typ, errSmallBuffer, b, secureErrorLogMessage), + error: headerErrorMsg(typ, ErrSmallReadBuffer, b, secureErrorLogMessage), } } @@ -2234,7 +2249,7 @@ func (h *RequestHeader) readLoop(r *bufio.Reader, waitForMore bool) error { if err == nil { return nil } - if !waitForMore || err != errNeedMore { + if !waitForMore || err != ErrNeedMore { h.resetSkipNormalize() return err } @@ -2257,7 +2272,7 @@ func (h *RequestHeader) tryRead(r *bufio.Reader, n int) error { // This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 . if err == bufio.ErrBufferFull { return &ErrSmallBuffer{ - error: fmt.Errorf("error when reading request headers: %w (n=%d, r.Buffered()=%d)", errSmallBuffer, n, r.Buffered()), + error: fmt.Errorf("error when reading request headers: %w (n=%d, r.Buffered()=%d)", ErrSmallReadBuffer, n, r.Buffered()), } } @@ -2744,7 +2759,7 @@ func (h *ResponseHeader) parseFirstLine(buf []byte) (int, error) { n := bytes.IndexByte(b, ' ') if n < 0 { if h.secureErrorLogMessage { - return 0, errors.New("cannot find whitespace in the first line of response") + return 0, ErrResponseFirstLineMissingSpace } return 0, fmt.Errorf("cannot find whitespace in the first line of response %q", buf) } @@ -2761,7 +2776,7 @@ func (h *ResponseHeader) parseFirstLine(buf []byte) (int, error) { } if len(b) > n && b[n] != ' ' { if h.secureErrorLogMessage { - return 0, errors.New("unexpected char at the end of status code") + return 0, ErrUnexpectedStatusCodeChar } return 0, fmt.Errorf("unexpected char at the end of status code. Response %q", buf) } @@ -2795,7 +2810,7 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { n := bytes.IndexByte(b, ' ') if n <= 0 { if h.secureErrorLogMessage { - return 0, errors.New("cannot find http request method") + return 0, ErrMissingRequestMethod } return 0, fmt.Errorf("cannot find http request method in %q", buf) } @@ -2803,7 +2818,7 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { if !isValidMethod(h.method) { if h.secureErrorLogMessage { - return 0, errors.New("unsupported http request method") + return 0, ErrUnsupportedRequestMethod } return 0, fmt.Errorf("unsupported http request method %q in %q", h.method, buf) } @@ -2813,7 +2828,7 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { // Check for extra whitespace after method - only one space should separate method from URI if len(b) > 0 && b[0] == ' ' { if h.secureErrorLogMessage { - return 0, errors.New("extra whitespace in request line") + return 0, ErrExtraWhitespaceInRequestLine } return 0, fmt.Errorf("extra whitespace in request line %q", buf) } @@ -2824,7 +2839,7 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { return 0, fmt.Errorf("cannot find whitespace in the first line of request %q", buf) } else if n == 0 { if h.secureErrorLogMessage { - return 0, errors.New("requestURI cannot be empty") + return 0, ErrEmptyRequestURI } return 0, fmt.Errorf("requestURI cannot be empty in %q", buf) } @@ -2832,7 +2847,7 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { // Check for extra whitespace - only one space should separate URI from HTTP version if n+1 < len(b) && b[n+1] == ' ' { if h.secureErrorLogMessage { - return 0, errors.New("extra whitespace in request line") + return 0, ErrExtraWhitespaceInRequestLine } return 0, fmt.Errorf("extra whitespace in request line %q", buf) } @@ -2869,7 +2884,7 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { func readRawHeaders(dst, buf []byte) ([]byte, int, error) { n := bytes.IndexByte(buf, nChar) if n < 0 { - return dst[:0], 0, errNeedMore + return dst[:0], 0, ErrNeedMore } if (n == 1 && buf[0] == rChar) || n == 0 { // empty headers @@ -2883,7 +2898,7 @@ func readRawHeaders(dst, buf []byte) ([]byte, int, error) { b = b[m:] m = bytes.IndexByte(b, nChar) if m < 0 { - return dst, 0, errNeedMore + return dst, 0, ErrNeedMore } m++ n += m @@ -3077,7 +3092,7 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) { if caseInsensitiveCompare(s.key, strContentLength) { if contentLengthSeen { h.connectionClose = true - return 0, errors.New("duplicate Content-Length header") + return 0, ErrDuplicateContentLength } contentLengthSeen = true @@ -3110,7 +3125,7 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) { if !isIdentity && !isChunked { h.connectionClose = true if h.secureErrorLogMessage { - return 0, errors.New("unsupported Transfer-Encoding") + return 0, ErrUnsupportedTransferEncoding } return 0, fmt.Errorf("unsupported Transfer-Encoding: %q", s.value) } @@ -3169,15 +3184,13 @@ func (h *RequestHeader) collectCookies() { h.cookiesCollected = true } -var errNonNumericChars = errors.New("non-numeric chars found") - func parseContentLength(b []byte) (int, error) { v, n, err := parseUintBuf(b) if err != nil { return -1, fmt.Errorf("cannot parse Content-Length: %w", err) } if n != len(b) { - return -1, fmt.Errorf("cannot parse Content-Length: %w", errNonNumericChars) + return -1, fmt.Errorf("cannot parse Content-Length: %w", ErrNonNumericChars) } return v, nil } @@ -3227,7 +3240,7 @@ func hasHeaderValue(s, value []byte) bool { func nextLine(b []byte) ([]byte, []byte, error) { nNext := bytes.IndexByte(b, nChar) if nNext < 0 { - return nil, nil, errNeedMore + return nil, nil, ErrNeedMore } n := nNext if n > 0 && b[n-1] == rChar { @@ -3370,11 +3383,6 @@ func copyTrailer(dst, src [][]byte) [][]byte { return dst } -var ( - errNeedMore = errors.New("need more data: cannot find trailing lf") - errSmallBuffer = errors.New("small read buffer. Increase ReadBufferSize") -) - // ErrNothingRead is returned when a keep-alive connection is closed, // either because the remote closed it or because of a read timeout. type ErrNothingRead struct { diff --git a/headerscanner.go b/headerscanner.go index 109504f..a4c9123 100644 --- a/headerscanner.go +++ b/headerscanner.go @@ -27,7 +27,7 @@ func (s *headerScanner) next() bool { i := bytes.Index(s.b, strCRLFCRLF) if i < 0 { - s.err = errNeedMore + s.err = ErrNeedMore return false } i += 4 diff --git a/server.go b/server.go index 0598e82..522dff6 100644 --- a/server.go +++ b/server.go @@ -2314,7 +2314,7 @@ func (s *Server) serveConn(c net.Conn) error { // outgoing buffer first so it doesn't have to wait. if bw != nil && bw.Buffered() > 0 { err = ctx.Request.Header.readLoop(br, false) - if err == errNeedMore { + if err == ErrNeedMore { err = bw.Flush() if err != nil { break diff --git a/server_test.go b/server_test.go index 254e7f8..6aeae43 100644 --- a/server_test.go +++ b/server_test.go @@ -500,7 +500,7 @@ func TestServerErrSmallBuffer(t *testing.T) { t.Fatal("missing 'Connection: close' response header") } - expectedErr := errSmallBuffer.Error() + expectedErr := ErrSmallReadBuffer.Error() if !strings.Contains(serverErr.Error(), expectedErr) { t.Fatalf("unexpected log output: %v. Expecting %q", serverErr, expectedErr) }