diff --git a/header.go b/header.go index 3cfe82593a..0917cc89af 100644 --- a/header.go +++ b/header.go @@ -3296,7 +3296,7 @@ func (s *headerScanner) next() bool { s.key = s.b[:n] normalizeHeaderKey(s.key, s.disableNormalizing) n++ - for len(s.b) > n && s.b[n] == ' ' { + for len(s.b) > n && (s.b[n] == ' ' || s.b[n] == '\t') { n++ // the newline index is a relative index, and lines below trimmed `s.b` by `n`, // so the relative newline index also shifted forward. it's safe to decrease @@ -3350,13 +3350,14 @@ func (s *headerScanner) next() bool { if n > 0 && s.value[n-1] == rChar { n-- } - for n > 0 && s.value[n-1] == ' ' { + for n > 0 && (s.value[n-1] == ' ' || s.value[n-1] == '\t') { n-- } s.value = s.value[:n] if isMultiLineValue { s.value, s.b, s.hLen = normalizeHeaderValue(s.value, oldB, s.hLen) } + return true } @@ -3435,6 +3436,7 @@ func normalizeHeaderValue(ov, ob []byte, headerLength int) (nv, nb []byte, nhl i } write := 0 shrunk := 0 + once := false lineStart := false for read := 0; read < length; read++ { c := ov[read] @@ -3443,10 +3445,17 @@ func normalizeHeaderValue(ov, ob []byte, headerLength int) (nv, nb []byte, nhl i shrunk++ if c == nChar { lineStart = true + once = false } continue - case lineStart && c == '\t': - c = ' ' + case lineStart && (c == '\t' || c == ' '): + if !once { + c = ' ' + once = true + } else { + shrunk++ + continue + } default: lineStart = false } diff --git a/header_test.go b/header_test.go index 89eea8346a..6eae1cb0e1 100644 --- a/header_test.go +++ b/header_test.go @@ -113,6 +113,53 @@ func TestResponseHeaderMultiLineValue(t *testing.T) { } } +func TestIssue1808(t *testing.T) { + t.Parallel() + + s := "HTTP/1.1 200\r\n" + + "WithTabs: \t v1 \t\r\n" + // "v1" + "WithTabs-Start: \t \t v1 \r\n" + // "v1" + "WithTabs-End: v1 \t \t\t\t\r\n" + // "v1" + "WithTabs-Multi-Line: \t v1 \t;\r\n \t v2 \t;\r\n\t v3\r\n" + // "v1 \t; v2 \t; v3" + "\r\n" + + resHeader := new(ResponseHeader) + if _, err := resHeader.parse([]byte(s)); err != nil { + t.Fatalf("parse headers with tabs values failed, %v", err) + } + + groundTruth := map[string]string{ + "WithTabs": "v1", + "WithTabs-Start": "v1", + "WithTabs-End": "v1", + "WithTabs-Multi-Line": "v1 \t; v2 \t; v3", + } + + for name, want := range groundTruth { + if got := b2s(resHeader.Peek(name)); got != want { + t.Errorf("ResponseHeader.parser() unexpected %q got: %q want: %q", name, got, want) + } + } + + s = "GET / HTTP/1.1\r\n" + + "WithTabs: \t v1 \t\r\n" + // "v1" + "WithTabs-Start: \t \t v1 \r\n" + // "v1" + "WithTabs-End: v1 \t \t\t\t\r\n" + // "v1" + "WithTabs-Multi-Line: \t v1 \t;\r\n \t v2 \t;\r\n\t v3\r\n" + // "v1 \t; v2 \t; v3" + "\r\n" + + reqHeader := new(RequestHeader) + if _, err := reqHeader.parse([]byte(s)); err != nil { + t.Fatalf("parse headers with tabs values failed, %v", err) + } + + for name, want := range groundTruth { + if got := b2s(reqHeader.Peek(name)); got != want { + t.Errorf("RequestHeader.parser() unexpected %q got: %q want: %q", name, got, want) + } + } +} + func TestResponseHeaderMultiLineName(t *testing.T) { t.Parallel()