Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue 875 #910

Merged
merged 5 commits into from
Nov 8, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
75 changes: 52 additions & 23 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import (
"time"
)

const (
rChar = byte('\r')
nChar = byte('\n')
)

// ResponseHeader represents HTTP response header.
//
// It is forbidden copying ResponseHeader instances.
Expand Down Expand Up @@ -1420,7 +1425,7 @@ func bufferSnippet(b []byte) string {

func isOnlyCRLF(b []byte) bool {
for _, ch := range b {
if ch != '\r' && ch != '\n' {
if ch != rChar && ch != nChar {
return false
}
}
Expand Down Expand Up @@ -1732,7 +1737,7 @@ func peekRawHeader(buf, key []byte) []byte {
if n < 0 {
return nil
}
if n > 0 && buf[n-1] != '\n' {
if n > 0 && buf[n-1] != nChar {
return nil
}
n += len(key)
Expand All @@ -1748,22 +1753,22 @@ func peekRawHeader(buf, key []byte) []byte {
}
n++
buf = buf[n:]
n = bytes.IndexByte(buf, '\n')
n = bytes.IndexByte(buf, nChar)
if n < 0 {
return nil
}
if n > 0 && buf[n-1] == '\r' {
if n > 0 && buf[n-1] == rChar {
n--
}
return buf[:n]
}

func readRawHeaders(dst, buf []byte) ([]byte, int, error) {
n := bytes.IndexByte(buf, '\n')
n := bytes.IndexByte(buf, nChar)
if n < 0 {
return dst[:0], 0, errNeedMore
}
if (n == 1 && buf[0] == '\r') || n == 0 {
if (n == 1 && buf[0] == rChar) || n == 0 {
// empty headers
return dst, n + 1, nil
}
Expand All @@ -1773,13 +1778,13 @@ func readRawHeaders(dst, buf []byte) ([]byte, int, error) {
m := n
for {
b = b[m:]
m = bytes.IndexByte(b, '\n')
m = bytes.IndexByte(b, nChar)
if m < 0 {
return dst, 0, errNeedMore
}
m++
n += m
if (m == 2 && b[0] == '\r') || m == 1 {
if (m == 2 && b[0] == rChar) || m == 1 {
dst = append(dst, buf[:n]...)
return dst, n, nil
}
Expand Down Expand Up @@ -2012,12 +2017,12 @@ func (s *headerScanner) next() bool {
s.initialized = true
}
bLen := len(s.b)
if bLen >= 2 && s.b[0] == '\r' && s.b[1] == '\n' {
if bLen >= 2 && s.b[0] == rChar && s.b[1] == nChar {
s.b = s.b[2:]
s.hLen += 2
return false
}
if bLen >= 1 && s.b[0] == '\n' {
if bLen >= 1 && s.b[0] == nChar {
s.b = s.b[1:]
s.hLen++
return false
Expand All @@ -2030,7 +2035,7 @@ func (s *headerScanner) next() bool {
n = bytes.IndexByte(s.b, ':')

// There can't be a \n inside the header name, check for this.
x := bytes.IndexByte(s.b, '\n')
x := bytes.IndexByte(s.b, nChar)
if x < 0 {
// A header name should always at some point be followed by a \n
// even if it's the one that terminates the header block.
Expand Down Expand Up @@ -2063,7 +2068,7 @@ func (s *headerScanner) next() bool {
n = s.nextNewLine
s.nextNewLine = -1
} else {
n = bytes.IndexByte(s.b, '\n')
n = bytes.IndexByte(s.b, nChar)
}
if n < 0 {
s.err = errNeedMore
Expand All @@ -2077,10 +2082,10 @@ func (s *headerScanner) next() bool {
if s.b[n+1] != ' ' && s.b[n+1] != '\t' {
break
}
d := bytes.IndexByte(s.b[n+1:], '\n')
d := bytes.IndexByte(s.b[n+1:], nChar)
if d <= 0 {
break
} else if d == 1 && s.b[n+1] == '\r' {
} else if d == 1 && s.b[n+1] == rChar {
break
}
e := n + d + 1
Expand All @@ -2101,7 +2106,7 @@ func (s *headerScanner) next() bool {
s.hLen += n + 1
s.b = s.b[n+1:]

if n > 0 && s.value[n-1] == '\r' {
if n > 0 && s.value[n-1] == rChar {
n--
}
for n > 0 && s.value[n-1] == ' ' {
Expand Down Expand Up @@ -2157,12 +2162,12 @@ func hasHeaderValue(s, value []byte) bool {
}

func nextLine(b []byte) ([]byte, []byte, error) {
nNext := bytes.IndexByte(b, '\n')
nNext := bytes.IndexByte(b, nChar)
if nNext < 0 {
return nil, nil, errNeedMore
}
n := nNext
if n > 0 && b[n-1] == '\r' {
if n > 0 && b[n-1] == rChar {
n--
}
return b[:n], b[nNext+1:], nil
Expand All @@ -2175,7 +2180,8 @@ var (
func initHeaderKV(kv *argsKV, key, value string, disableNormalizing bool) {
kv.key = getHeaderKeyBytes(kv, key, disableNormalizing)
// https://tools.ietf.org/html/rfc7230#section-3.2.4
kv.value = append(kv.value[:0], foldReplacer.Replace(value)...)
kv.value = append(kv.value[:0], value...)
kv.value = removeNewLines(kv.value, value)
}

func getHeaderKeyBytes(kv *argsKV, key string, disableNormalizing bool) []byte {
Expand All @@ -2195,9 +2201,9 @@ func normalizeHeaderValue(ov, ob []byte, headerLength int) (nv, nb []byte, nhl i
lineStart := false
for read := 0; read < length; read++ {
c := ov[read]
if c == '\r' || c == '\n' {
if c == rChar || c == nChar {
shrunk++
if c == '\n' {
if c == nChar {
lineStart = true
}
continue
Expand All @@ -2215,13 +2221,13 @@ func normalizeHeaderValue(ov, ob []byte, headerLength int) (nv, nb []byte, nhl i

// Check if we need to skip \r\n or just \n
skip := 0
if ob[write] == '\r' {
if ob[write+1] == '\n' {
if ob[write] == rChar {
if ob[write+1] == nChar {
skip += 2
} else {
skip++
}
} else if ob[write] == '\n' {
} else if ob[write] == nChar {
skip++
}

Expand Down Expand Up @@ -2254,6 +2260,29 @@ func normalizeHeaderKey(b []byte, disableNormalizing bool) {
}
}

// removeNewLines will replace `\r` and `\n` with an empty space
func removeNewLines(raw []byte, rawStr string) []byte {
// strings.IndexByte is faster than bytes.IndexByte
// check if a `\r` is present and save the position.
// if no `\r` is found, check if a `\n` is present,
if found := strings.IndexByte(rawStr, rChar); found == -1 {
// check if a `\n` is present if no `\r` is found
if found = strings.IndexByte(rawStr, nChar); found == -1 {
return raw
}
}
ReneWerner87 marked this conversation as resolved.
Show resolved Hide resolved
// loop from found position to replace `\r` or `\n` with empty space
for i := 0; i < len(raw); i++ {
switch raw[i] {
case rChar, nChar:
raw[i] = ' '
default:
continue
}
}
return raw
}

// AppendNormalizedHeaderKey appends normalized header key (name) to dst
// and returns the resulting dst.
//
Expand Down
32 changes: 32 additions & 0 deletions header_timing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"io"
"strconv"
"testing"

"github.com/valyala/bytebufferpool"
Expand Down Expand Up @@ -146,3 +147,34 @@ func benchmarkNormalizeHeaderKey(b *testing.B, src []byte) {
}
})
}

func BenchmarkRemoveNewLines(b *testing.B) {
type testcase struct {
value string
expectedValue string
}

var testcases = []testcase{
{value: "MaliciousValue", expectedValue: "MaliciousValue"},
{value: "MaliciousValue\r\n", expectedValue: "MaliciousValue "},
{value: "Malicious\nValue", expectedValue: "Malicious Value"},
{value: "Malicious\rValue", expectedValue: "Malicious Value"},
}

for i, tcase := range testcases {
caseName := strconv.FormatInt(int64(i), 10)
b.Run(caseName, func(subB *testing.B) {
subB.ReportAllocs()
var h RequestHeader
for i := 0; i < subB.N; i++ {
h.Set("Test", tcase.value)
}
subB.StopTimer()
actualValue := string(h.Peek("Test"))

if actualValue != tcase.expectedValue {
subB.Errorf("unexpected value, got: %+v", actualValue)
}
})
}
}