mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-15 16:07:51 +03:00
header: reject pre-colon whitespace in request headers (#2187)
Reject request header field names with whitespace immediately before the colon instead of trimming them before special-header handling. This prevents parser differentials for malformed framing and routing headers such as Content-Length, Transfer-Encoding, and Host when a frontend forwards raw invalid request headers. Keep the existing response and trailer compatibility behavior unchanged, and add regression coverage for both header-only parsing and full request body reads.
This commit is contained in:
@@ -3119,9 +3119,12 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
|
||||
s.b = buf
|
||||
|
||||
for s.next() {
|
||||
// Trim trailing whitespace before the colon to normalize headers
|
||||
// like "Content-Length :" to "Content-Length:".
|
||||
key := s.key
|
||||
s.key = trimTrailingSpace(s.key)
|
||||
if len(s.key) != len(key) {
|
||||
h.connectionClose = true
|
||||
return 0, fmt.Errorf("invalid header key %q", key)
|
||||
}
|
||||
|
||||
if len(s.key) == 0 {
|
||||
h.connectionClose = true
|
||||
|
||||
@@ -3372,6 +3372,12 @@ func TestRequestHeaderReadError(t *testing.T) {
|
||||
// Space before header name
|
||||
testRequestHeaderReadError(t, h, "G(ET /foo/bar HTTP/1.1\r\n foo: bar\r\n\r\n")
|
||||
|
||||
// Whitespace before the colon in request header fields
|
||||
testRequestHeaderReadError(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaa.com\r\nFoo : bar\r\n\r\n")
|
||||
testRequestHeaderReadError(t, h, "GET /foo/bar HTTP/1.1\r\nHost : aaa.com\r\n\r\n")
|
||||
testRequestHeaderReadError(t, h, "POST /foo/bar HTTP/1.1\r\nHost: aaa.com\r\nContent-Length : 4\r\n\r\ntest")
|
||||
testRequestHeaderReadError(t, h, "POST /foo/bar HTTP/1.1\r\nHost: aaa.com\r\nTransfer-Encoding : chunked\r\n\r\n4\r\ntest\r\n0\r\n\r\n")
|
||||
|
||||
// Duplicate host header
|
||||
testRequestHeaderReadError(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaa.com\r\nhost: bbb.com\r\n\r\n")
|
||||
|
||||
|
||||
+13
-15
@@ -1777,25 +1777,23 @@ func TestRequestReadLimitBody(t *testing.T) {
|
||||
testRequestReadLimitBodySuccess(t, "GET /foo HTTP/1.0\r\n\r\n", 0)
|
||||
}
|
||||
|
||||
func TestRequestReadLimitBodyWhitespaceBeforeColonFramingHeaders(t *testing.T) {
|
||||
func TestRequestReadLimitBodyRejectWhitespaceBeforeColonFramingHeaders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var req Request
|
||||
r := bytes.NewBufferString("POST /foo HTTP/1.1\r\nHost: a.com\r\nContent-Length : 4\r\n\r\ntestNEXT")
|
||||
br := bufio.NewReader(r)
|
||||
if err := req.ReadLimitBody(br, 10); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := string(req.Body()); got != "test" {
|
||||
t.Fatalf("unexpected body %q", got)
|
||||
tests := []string{
|
||||
"POST /foo HTTP/1.1\r\nHost: a.com\r\nContent-Length : 4\r\n\r\ntestNEXT",
|
||||
"POST /foo HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding : chunked\r\n\r\n4\r\ntest\r\n0\r\n\r\n",
|
||||
}
|
||||
|
||||
rest, err := io.ReadAll(br)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected read error: %v", err)
|
||||
}
|
||||
if got := string(rest); got != "NEXT" {
|
||||
t.Fatalf("unexpected buffered bytes %q", got)
|
||||
for _, s := range tests {
|
||||
var req Request
|
||||
br := bufio.NewReader(bytes.NewBufferString(s))
|
||||
if err := req.ReadLimitBody(br, 10); err == nil {
|
||||
t.Fatalf("expecting error for %q", s)
|
||||
}
|
||||
if body := req.Body(); len(body) != 0 {
|
||||
t.Fatalf("unexpected body %q for %q", body, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user