Skip to content

Commit ed6a27a

Browse files
authored
Fix header parser (#1808) (#1810)
* Fix RequestHeader parser (#1808) When FastHTTP receives a header value suffixed or prefixed with tabs, they should be stripped. * Remove redundant code * Add test for header parser including tabs (#1808)
1 parent 0324e8d commit ed6a27a

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed

header.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3296,7 +3296,7 @@ func (s *headerScanner) next() bool {
32963296
s.key = s.b[:n]
32973297
normalizeHeaderKey(s.key, s.disableNormalizing)
32983298
n++
3299-
for len(s.b) > n && s.b[n] == ' ' {
3299+
for len(s.b) > n && (s.b[n] == ' ' || s.b[n] == '\t') {
33003300
n++
33013301
// the newline index is a relative index, and lines below trimmed `s.b` by `n`,
33023302
// so the relative newline index also shifted forward. it's safe to decrease
@@ -3350,13 +3350,14 @@ func (s *headerScanner) next() bool {
33503350
if n > 0 && s.value[n-1] == rChar {
33513351
n--
33523352
}
3353-
for n > 0 && s.value[n-1] == ' ' {
3353+
for n > 0 && (s.value[n-1] == ' ' || s.value[n-1] == '\t') {
33543354
n--
33553355
}
33563356
s.value = s.value[:n]
33573357
if isMultiLineValue {
33583358
s.value, s.b, s.hLen = normalizeHeaderValue(s.value, oldB, s.hLen)
33593359
}
3360+
33603361
return true
33613362
}
33623363

@@ -3435,6 +3436,7 @@ func normalizeHeaderValue(ov, ob []byte, headerLength int) (nv, nb []byte, nhl i
34353436
}
34363437
write := 0
34373438
shrunk := 0
3439+
once := false
34383440
lineStart := false
34393441
for read := 0; read < length; read++ {
34403442
c := ov[read]
@@ -3443,10 +3445,17 @@ func normalizeHeaderValue(ov, ob []byte, headerLength int) (nv, nb []byte, nhl i
34433445
shrunk++
34443446
if c == nChar {
34453447
lineStart = true
3448+
once = false
34463449
}
34473450
continue
3448-
case lineStart && c == '\t':
3449-
c = ' '
3451+
case lineStart && (c == '\t' || c == ' '):
3452+
if !once {
3453+
c = ' '
3454+
once = true
3455+
} else {
3456+
shrunk++
3457+
continue
3458+
}
34503459
default:
34513460
lineStart = false
34523461
}

header_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,53 @@ func TestResponseHeaderMultiLineValue(t *testing.T) {
113113
}
114114
}
115115

116+
func TestIssue1808(t *testing.T) {
117+
t.Parallel()
118+
119+
s := "HTTP/1.1 200\r\n" +
120+
"WithTabs: \t v1 \t\r\n" + // "v1"
121+
"WithTabs-Start: \t \t v1 \r\n" + // "v1"
122+
"WithTabs-End: v1 \t \t\t\t\r\n" + // "v1"
123+
"WithTabs-Multi-Line: \t v1 \t;\r\n \t v2 \t;\r\n\t v3\r\n" + // "v1 \t; v2 \t; v3"
124+
"\r\n"
125+
126+
resHeader := new(ResponseHeader)
127+
if _, err := resHeader.parse([]byte(s)); err != nil {
128+
t.Fatalf("parse headers with tabs values failed, %v", err)
129+
}
130+
131+
groundTruth := map[string]string{
132+
"WithTabs": "v1",
133+
"WithTabs-Start": "v1",
134+
"WithTabs-End": "v1",
135+
"WithTabs-Multi-Line": "v1 \t; v2 \t; v3",
136+
}
137+
138+
for name, want := range groundTruth {
139+
if got := b2s(resHeader.Peek(name)); got != want {
140+
t.Errorf("ResponseHeader.parser() unexpected %q got: %q want: %q", name, got, want)
141+
}
142+
}
143+
144+
s = "GET / HTTP/1.1\r\n" +
145+
"WithTabs: \t v1 \t\r\n" + // "v1"
146+
"WithTabs-Start: \t \t v1 \r\n" + // "v1"
147+
"WithTabs-End: v1 \t \t\t\t\r\n" + // "v1"
148+
"WithTabs-Multi-Line: \t v1 \t;\r\n \t v2 \t;\r\n\t v3\r\n" + // "v1 \t; v2 \t; v3"
149+
"\r\n"
150+
151+
reqHeader := new(RequestHeader)
152+
if _, err := reqHeader.parse([]byte(s)); err != nil {
153+
t.Fatalf("parse headers with tabs values failed, %v", err)
154+
}
155+
156+
for name, want := range groundTruth {
157+
if got := b2s(reqHeader.Peek(name)); got != want {
158+
t.Errorf("RequestHeader.parser() unexpected %q got: %q want: %q", name, got, want)
159+
}
160+
}
161+
}
162+
116163
func TestResponseHeaderMultiLineName(t *testing.T) {
117164
t.Parallel()
118165

0 commit comments

Comments
 (0)