Skip to content

Commit

Permalink
Follow RFCs 7230 and 9112 for HTTP versions (#1710)
Browse files Browse the repository at this point in the history
Require that HTTP versions match the following pattern: HTTP/[0-9]\.[0-9]
  • Loading branch information
erikdubbelboer committed Feb 11, 2024
1 parent a8cb5d5 commit 3327266
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 32 deletions.
36 changes: 26 additions & 10 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]...)

Expand Down
14 changes: 5 additions & 9 deletions header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
15 changes: 3 additions & 12 deletions http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package fasthttp
import (
"bufio"
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
Expand All @@ -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) {
Expand Down
1 change: 0 additions & 1 deletion strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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("://")
Expand Down

0 comments on commit 3327266

Please sign in to comment.