Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions pkg/protocol/http1/ext/headerscanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ import (
"github.com/cloudwego/hertz/pkg/common/utils"
)

var errInvalidName = errs.NewPublic("invalid header name")
var (
errInvalidName = errs.NewPublic("invalid header name")
errHeaderTooLong = errs.NewPublic("header block exceeds maximum size")
)

type HeaderScanner struct {
B []byte
Expand All @@ -61,14 +64,12 @@ type HeaderScanner struct {

DisableNormalizing bool

// by checking whether the Next line contains a colon or not to tell
// it's a header entry or a multi line value of current header entry.
// the side effect of this operation is that we know the index of the
// Next colon and new line, so this can be used during Next iteration,
// instead of find them again.
// MaxHeaderSize limits total header bytes parsed. 0 means no limit.
// When exceeded, Err is set to errHeaderTooLong.
MaxHeaderSize int

nextColon int
nextNewLine int

initialized bool
}

Expand All @@ -83,6 +84,10 @@ func (s *HeaderScanner) Next() bool {
s.nextNewLine = -1
s.initialized = true
}
if s.MaxHeaderSize > 0 && s.HLen > s.MaxHeaderSize {
s.Err = errHeaderTooLong
return false
}
bLen := len(s.B)
if bLen >= 2 && s.B[0] == '\r' && s.B[1] == '\n' {
s.B = s.B[2:]
Expand Down
37 changes: 37 additions & 0 deletions pkg/protocol/http1/ext/headerscanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,40 @@ func testTestHeaderScannerError(t *testing.T, rawHeaders string, expectError err
assert.NotNil(t, hs.Err)
assert.True(t, errors.Is(hs.Err, expectError))
}

func TestHeaderScannerMaxHeaderSize(t *testing.T) {
// normal case: headers within limit
rawHeaders := "Host: example.com\r\nContent-Type: text/html\r\n\r\n"
hs := &HeaderScanner{}
hs.B = []byte(rawHeaders)
hs.MaxHeaderSize = 4096
count := 0
for hs.Next() {
count++
}
assert.Nil(t, hs.Err)
assert.DeepEqual(t, 2, count)

// headers exceed limit
hs2 := &HeaderScanner{}
hs2.B = []byte(rawHeaders)
hs2.MaxHeaderSize = 10 // very small limit
for hs2.Next() {
}
assert.NotNil(t, hs2.Err)
assert.True(t, errors.Is(hs2.Err, errHeaderTooLong))
}

func TestHeaderScannerMaxHeaderSizeDisabled(t *testing.T) {
// MaxHeaderSize=0 means no limit (default, backward compatible)
rawHeaders := "Host: example.com\r\nContent-Type: text/html\r\n\r\n"
hs := &HeaderScanner{}
hs.B = []byte(rawHeaders)
// MaxHeaderSize defaults to 0
count := 0
for hs.Next() {
count++
}
assert.Nil(t, hs.Err)
assert.DeepEqual(t, 2, count)
}