From 332726634240b82456ce8563cd7aa4027612ce36 Mon Sep 17 00:00:00 2001 From: Erik Dubbelboer Date: Sun, 11 Feb 2024 14:55:31 +0800 Subject: Follow RFCs 7230 and 9112 for HTTP versions (#1710) Require that HTTP versions match the following pattern: HTTP/[0-9]\.[0-9] --- header.go | 36 ++++++++++++++++++++++++++---------- header_test.go | 14 +++++--------- http_test.go | 15 +++------------ strings.go | 1 - 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/header.go b/header.go index ac279d7..bdee768 100644 --- a/header.go +++ b/header.go @@ -2870,24 +2870,40 @@ func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) { h.method = append(h.method[:0], b[:n]...) b = b[n+1:] - protoStr := strHTTP11 // parse requestURI n = bytes.LastIndexByte(b, ' ') - switch { - case n < 0: - h.noHTTP11 = true - n = len(b) - protoStr = strHTTP10 - case n == 0: + if n < 0 { + return 0, fmt.Errorf("cannot find whitespace in the first line of request %q", buf) + } else if n == 0 { if h.secureErrorLogMessage { return 0, fmt.Errorf("requestURI cannot be empty") } return 0, fmt.Errorf("requestURI cannot be empty in %q", buf) - case !bytes.Equal(b[n+1:], strHTTP11): - h.noHTTP11 = true - protoStr = b[n+1:] } + protoStr := b[n+1:] + + // Follow RFCs 7230 and 9112 and require that HTTP versions match the following pattern: HTTP/[0-9]\.[0-9] + if len(protoStr) != len(strHTTP11) { + if h.secureErrorLogMessage { + return 0, fmt.Errorf("unsupported HTTP version %q", protoStr) + } + return 0, fmt.Errorf("unsupported HTTP version %q in %q", protoStr, buf) + } + if !bytes.HasPrefix(protoStr, strHTTP11[:5]) { + if h.secureErrorLogMessage { + return 0, fmt.Errorf("unsupported HTTP version %q", protoStr) + } + return 0, fmt.Errorf("unsupported HTTP version %q in %q", protoStr, buf) + } + if protoStr[5] < '0' || protoStr[5] > '9' || protoStr[7] < '0' || protoStr[7] > '9' { + if h.secureErrorLogMessage { + return 0, fmt.Errorf("unsupported HTTP version %q", protoStr) + } + return 0, fmt.Errorf("unsupported HTTP version %q in %q", protoStr, buf) + } + + h.noHTTP11 = !bytes.Equal(protoStr, strHTTP11) h.proto = append(h.proto[:0], protoStr...) h.requestURI = append(h.requestURI[:0], b[:n]...) diff --git a/header_test.go b/header_test.go index 163fdd7..c0f98dc 100644 --- a/header_test.go +++ b/header_test.go @@ -1341,11 +1341,6 @@ func TestRequestHeaderHTTPVer(t *testing.T) { // non-http/1.1 testRequestHeaderHTTPVer(t, "GET / HTTP/1.0\r\nHost: aa.com\r\n\r\n", true) testRequestHeaderHTTPVer(t, "GET / HTTP/0.9\r\nHost: aa.com\r\n\r\n", true) - testRequestHeaderHTTPVer(t, "GET / foobar\r\nHost: aa.com\r\n\r\n", true) - - // empty http version - testRequestHeaderHTTPVer(t, "GET /\r\nHost: aaa.com\r\n\r\n", true) - testRequestHeaderHTTPVer(t, "GET / \r\nHost: aaa.com\r\n\r\n", true) // http/1.1 testRequestHeaderHTTPVer(t, "GET / HTTP/1.1\r\nHost: a.com\r\n\r\n", false) @@ -1365,6 +1360,8 @@ func testResponseHeaderHTTPVer(t *testing.T, s string, connectionClose bool) { } func testRequestHeaderHTTPVer(t *testing.T, s string, connectionClose bool) { + t.Helper() + var h RequestHeader r := bytes.NewBufferString(s) @@ -2641,10 +2638,6 @@ func TestRequestHeaderReadSuccess(t *testing.T) { testRequestHeaderReadSuccess(t, h, "GET http://gooGle.com/foO/%20bar?xxx#aaa HTTP/1.1\r\nHost: aa.cOM\r\n\r\ntrail", -2, "http://gooGle.com/foO/%20bar?xxx#aaa", "aa.cOM", "", "", nil) - // no protocol in the first line - testRequestHeaderReadSuccess(t, h, "GET /foo/bar\r\nHost: google.com\r\n\r\nisdD", - -2, "/foo/bar", "google.com", "", "", nil) - // blank lines before the first line testRequestHeaderReadSuccess(t, h, "\r\n\n\r\nGET /aaa HTTP/1.1\r\nHost: aaa.com\r\n\r\nsss", -2, "/aaa", "aaa.com", "", "", nil) @@ -2713,6 +2706,9 @@ func TestResponseHeaderReadError(t *testing.T) { // forbidden trailer testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: -1\r\nTrailer: Foo, Content-Length\r\n\r\n") + + // no protocol in the first line + testResponseHeaderReadError(t, h, "GET /foo/bar\r\nHost: google.com\r\n\r\nisdD") } func TestResponseHeaderReadErrorSecureLog(t *testing.T) { diff --git a/http_test.go b/http_test.go index 098990a..a82f23e 100644 --- a/http_test.go +++ b/http_test.go @@ -3,7 +3,6 @@ package fasthttp import ( "bufio" "bytes" - "encoding/base64" "errors" "fmt" "io" @@ -22,23 +21,15 @@ import ( func TestInvalidTrailers(t *testing.T) { t.Parallel() - if err := (&Response{}).Read(bufio.NewReader(bytes.NewReader([]byte{0x20, 0x30, 0x0a, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0xff, 0x0a, 0x0a, 0x30, 0x0d, 0x0a, 0x30}))); !errors.Is(err, io.EOF) { + if err := (&Response{}).Read(bufio.NewReader(strings.NewReader(" 0\nTransfer-Encoding:\xff\n\n0\r\n0"))); !errors.Is(err, io.EOF) { t.Fatalf("%#v", err) } - if err := (&Response{}).Read(bufio.NewReader(bytes.NewReader([]byte{0xff, 0x20, 0x0a, 0x54, 0x52, 0x61, 0x49, 0x4c, 0x65, 0x52, 0x3a, 0x2c, 0x0a, 0x0a}))); !errors.Is(err, errEmptyInt) { + if err := (&Response{}).Read(bufio.NewReader(strings.NewReader("\xff \nTRaILeR:,\n\n"))); !errors.Is(err, errEmptyInt) { t.Fatal(err) } - if err := (&Response{}).Read(bufio.NewReader(bytes.NewReader([]byte{0x54, 0x52, 0x61, 0x49, 0x4c, 0x65, 0x52, 0x3a, 0x2c, 0x0a, 0x0a}))); !strings.Contains(err.Error(), "cannot find whitespace in the first line of response") { + if err := (&Response{}).Read(bufio.NewReader(strings.NewReader("TRaILeR:,\n\n"))); !strings.Contains(err.Error(), "cannot find whitespace in the first line of response") { t.Fatal(err) } - if err := (&Request{}).Read(bufio.NewReader(bytes.NewReader([]byte{0xff, 0x20, 0x0a, 0x54, 0x52, 0x61, 0x49, 0x4c, 0x65, 0x52, 0x3a, 0x2c, 0x0a, 0x0a}))); !strings.Contains(err.Error(), "contain forbidden trailer") { - t.Fatal(err) - } - - b, _ := base64.StdEncoding.DecodeString("tCAKIDoKCToKICAKCToKICAKCToKIAogOgoJOgogIAoJOgovIC8vOi4KOh0KVFJhSUxlUjo9HT09HQpUUmFJTGVSOicQAApUUmFJTGVSOj0gHSAKCT09HQoKOgoKCgo=") - if err := (&Request{}).Read(bufio.NewReader(bytes.NewReader(b))); !strings.Contains(err.Error(), "error when reading request headers: invalid header key") { - t.Fatalf("%#v", err) - } } func TestResponseEmptyTransferEncoding(t *testing.T) { diff --git a/strings.go b/strings.go index 3cec8ed..3374678 100644 --- a/strings.go +++ b/strings.go @@ -19,7 +19,6 @@ var ( strCRLF = []byte("\r\n") strHTTP = []byte("http") strHTTPS = []byte("https") - strHTTP10 = []byte("HTTP/1.0") strHTTP11 = []byte("HTTP/1.1") strColon = []byte(":") strColonSlashSlash = []byte("://") -- cgit v1.2.3