diff --git a/http/httpraw/header.go b/http/httpraw/header.go index abbc21f..e766ae9 100644 --- a/http/httpraw/header.go +++ b/http/httpraw/header.go @@ -412,10 +412,12 @@ func (*noCopy) Unlock() {} // - content-length -> Content-Length // - cOnTeNt-LenGtH -> Content-Length func NormalizeHeaderKey(b []byte) (modified bool) { + if len(b) == 0 { + return false + } const asciiCapDiff = 'a' - 'A' for i := -1; i < len(b); i++ { - ch := b[i] - nextToUpper := i == -1 || (ch == '-' && i < len(b)-1) + nextToUpper := i == -1 || (b[i] == '-' && i < len(b)-1) if nextToUpper { i++ isLower := b[i] >= 'a' && b[i] <= 'z' @@ -442,7 +444,6 @@ func CopyNormalizedHeaderValue(dst []byte, value []byte) (n int, modified bool) if len(dst) < len(value) { panic("httpraw.CopyNormalizedHeaderValue: dst buffer shorter than length") } - lineStart := false write := 0 read := 0 for { @@ -453,34 +454,18 @@ func CopyNormalizedHeaderValue(dst []byte, value []byte) (n int, modified bool) } omit := 1 rmStart += read - if rmStart > 0 && value[rmStart] == '\r' { - rmStart-- - omit++ - } - if rmStart+1 < len(value) && value[rmStart+1] == '\t' { omit++ } - n := copy(dst[write:], value[:rmStart]) - read += omit + n - write += n - } - return write, modified - for read := 0; read < len(value); read++ { - c := value[read] - switch { - case c == '\r' || c == '\n': - lineStart = c == '\n' - continue - case lineStart && c == '\t': - c = ' ' - modified = true - default: - lineStart = false + if rmStart > 0 && value[rmStart-1] == '\r' { + rmStart-- + omit++ } - dst[write] = c - write++ + modified = true + n := copy(dst[write:], value[read:rmStart]) + dst[write+n] = ' ' + read += omit + n + write += n + 1 } - modified = modified || n != len(value) return write, modified } diff --git a/http/httpraw/header_test.go b/http/httpraw/header_test.go index 568bc3c..9a27216 100644 --- a/http/httpraw/header_test.go +++ b/http/httpraw/header_test.go @@ -144,3 +144,61 @@ func strSameSite(mode http.SameSite) string { panic("invalid same site") } } + +func TestHeaderNormalizeKey(t *testing.T) { + var tests = []struct { + key string + wantnorm string + }{ + {key: "", wantnorm: ""}, + {key: "a-a-a", wantnorm: "A-A-A"}, + {key: "a-a-a-", wantnorm: "A-A-A-"}, + {key: "-", wantnorm: "-"}, + {key: "CONTENT", wantnorm: "Content"}, + {key: "cONTENT", wantnorm: "Content"}, + {key: "Content-Length", wantnorm: "Content-Length"}, + {key: "Content-length", wantnorm: "Content-Length"}, + {key: "content-length", wantnorm: "Content-Length"}, + {key: "conTent-lENgth", wantnorm: "Content-Length"}, + {key: "conTent-lENgth-", wantnorm: "Content-Length-"}, + } + for _, test := range tests { + gotKey := []byte(test.key) + modified := NormalizeHeaderKey(gotKey) + if string(gotKey) != test.wantnorm { + t.Errorf("mismatch want %q got %q", test.wantnorm, gotKey) + } + wantMod := string(gotKey) != test.key + if wantMod != modified { + t.Errorf("mismatch want mod=%v, got mod=%v", wantMod, modified) + } + } +} + +func TestCopyNormalizedHeaderValue(t *testing.T) { + var tests = []struct { + value string + wantnorm string + }{ + {value: "abc\r\n\tdef\r\n\tghi", wantnorm: "abc def ghi"}, + {value: "abc\r\n\tabc", wantnorm: "abc abc"}, + {value: "abc\n\tdef\n\tghi", wantnorm: "abc def ghi"}, + {value: "abc\n\tdef\n\tghi\n\t", wantnorm: "abc def ghi "}, + {value: "abc\n\tdef\n\tghi\r\n\t", wantnorm: "abc def ghi "}, + {value: "", wantnorm: ""}, + {value: "abc", wantnorm: "abc"}, + } + dst := make([]byte, 256) + for _, test := range tests { + value := []byte(test.value) + n, modified := CopyNormalizedHeaderValue(dst[:len(value)], value) + got := dst[:n] + if string(got) != test.wantnorm { + t.Errorf("mismatch want %q got %q", test.wantnorm, got) + } + wantMod := string(got) != test.value + if wantMod != modified { + t.Errorf("mismatch want mod=%v, got mod=%v", wantMod, modified) + } + } +}