Skip to content

Commit

Permalink
Merge pull request #910 from ReneWerner87/issue_875
Browse files Browse the repository at this point in the history
issue 875
  • Loading branch information
kirillDanshin authored Nov 8, 2020
2 parents 61f1899 + 49ca9f8 commit f80bf2d
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 23 deletions.
74 changes: 51 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,28 @@ func normalizeHeaderKey(b []byte, disableNormalizing bool) {
}
}

// removeNewLines will replace `\r` and `\n` with an empty space
func removeNewLines(raw []byte, rawStr string) []byte {
// check if a `\r` is present and save the position.
// if no `\r` is found, check if a `\n` is present.
// note: as of 1.15.4 strings.IndexByte is faster than bytes.IndexByte.
if found := strings.IndexByte(rawStr, rChar); found == -1 {
if found = strings.IndexByte(rawStr, nChar); found == -1 {
return raw
}
}
// 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)
}
})
}
}

0 comments on commit f80bf2d

Please sign in to comment.