Skip to content

Commit

Permalink
Add RequestHeader.RawHeaders()
Browse files Browse the repository at this point in the history
  • Loading branch information
erikdubbelboer committed Aug 29, 2018
1 parent 2cc8e6b commit e29392a
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 7 deletions.
39 changes: 32 additions & 7 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type RequestHeader struct {
cookies []argsKV

rawHeaders []byte

// stores an immutable copy of headers as they were recieved from the wire.
rawHeadersCopy []byte
}

// SetContentRange sets 'Content-Range: bytes startPos-endPos/contentLength'
Expand Down Expand Up @@ -1524,6 +1527,20 @@ func (h *RequestHeader) Header() []byte {
return h.bufKV.value
}

// RawHeaders returns raw header key/value bytes.
//
// Depending on server configuration, header keys may be normalized to
// capital-case in place.
//
// This copy is set aside during parsing, so empty slice is returned for all
// cases where parsing did not happen. Similarly, request line is not stored
// during parsing and can not be returned.
//
// The slice is not safe to use after the handler returns.
func (h *RequestHeader) RawHeaders() []byte {
return h.rawHeadersCopy
}

// String returns request header representation.
func (h *RequestHeader) String() string {
return string(h.Header())
Expand Down Expand Up @@ -1621,18 +1638,20 @@ func (h *RequestHeader) parse(buf []byte) (int, error) {
}

var n int
var rawHeaders []byte
rawHeaders, n, err = readRawHeaders(h.rawHeaders[:0], buf[m:])
if err != nil {
return 0, err
}
h.rawHeadersCopy = append(h.rawHeadersCopy[:0], rawHeaders...)
if !h.ignoreBody() || h.noHTTP11 {
n, err = h.parseHeaders(buf[m:])
if err != nil {
return 0, err
}
h.rawHeaders = append(h.rawHeaders[:0], buf[m:m+n]...)
h.rawHeadersParsed = true
} else {
var rawHeaders []byte
rawHeaders, n, err = readRawHeaders(h.rawHeaders[:0], buf[m:])
if err != nil {
return 0, err
}
h.rawHeaders = rawHeaders
}
return m + n, nil
Expand Down Expand Up @@ -1876,8 +1895,7 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
v := peekArgBytes(h.h, strConnection)
h.connectionClose = !hasHeaderValue(v, strKeepAlive) && !hasHeaderValue(v, strKeepAliveCamelCase)
}

return len(buf) - len(s.b), nil
return s.hLen, nil
}

func (h *RequestHeader) parseRawHeaders() {
Expand Down Expand Up @@ -1928,17 +1946,22 @@ type headerScanner struct {
value []byte
err error

// hLen stores header subslice len
hLen int

disableNormalizing bool
}

func (s *headerScanner) next() bool {
bLen := len(s.b)
if bLen >= 2 && s.b[0] == '\r' && s.b[1] == '\n' {
s.b = s.b[2:]
s.hLen += 2
return false
}
if bLen >= 1 && s.b[0] == '\n' {
s.b = s.b[1:]
s.hLen++
return false
}
n := bytes.IndexByte(s.b, ':')
Expand All @@ -1952,13 +1975,15 @@ func (s *headerScanner) next() bool {
for len(s.b) > n && s.b[n] == ' ' {
n++
}
s.hLen += n
s.b = s.b[n:]
n = bytes.IndexByte(s.b, '\n')
if n < 0 {
s.err = errNeedMore
return false
}
s.value = s.b[:n]
s.hLen += n + 1
s.b = s.b[n+1:]

if n > 0 && s.value[n-1] == '\r' {
Expand Down
111 changes: 111 additions & 0 deletions header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,117 @@ func TestRequestHeaderEmptyValueFromString(t *testing.T) {
}
}

func TestRequestRawHeaders(t *testing.T) {
kvs := "host: foobar\r\n" +
"value: b\r\n" +
"\r\n"
t.Run("normalized", func(t *testing.T) {
s := "GET / HTTP/1.1\r\n" + kvs
exp := kvs
var h RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if string(h.Host()) != "foobar" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
}
v2 := h.Peek("Value")
if !bytes.Equal(v2, []byte{'b'}) {
t.Fatalf("expecting non empty value. Got %q", v2)
}
if raw := h.RawHeaders(); string(raw) != exp {
t.Fatalf("expected header %q, got %q", exp, raw)
}
})
for _, n := range []int{0, 1, 4, 8} {
t.Run(fmt.Sprintf("post-%dk", n), func(t *testing.T) {
l := 1024 * n
body := make([]byte, l)
for i := range body {
body[i] = 'a'
}
cl := fmt.Sprintf("Content-Length: %d\r\n", l)
s := "POST / HTTP/1.1\r\n" + cl + kvs + string(body)
exp := cl + kvs
var h RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if string(h.Host()) != "foobar" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
}
v2 := h.Peek("Value")
if !bytes.Equal(v2, []byte{'b'}) {
t.Fatalf("expecting non empty value. Got %q", v2)
}
if raw := h.RawHeaders(); string(raw) != exp {
t.Fatalf("expected header %q, got %q", exp, raw)
}
})
}
t.Run("http10", func(t *testing.T) {
s := "GET / HTTP/1.0\r\n" + kvs
exp := kvs
var h RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if string(h.Host()) != "foobar" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
}
v2 := h.Peek("Value")
if !bytes.Equal(v2, []byte{'b'}) {
t.Fatalf("expecting non empty value. Got %q", v2)
}
if raw := h.RawHeaders(); string(raw) != exp {
t.Fatalf("expected header %q, got %q", exp, raw)
}
})
t.Run("non-normalized", func(t *testing.T) {
s := "GET / HTTP/1.1\r\n" + kvs
exp := kvs
var h RequestHeader
h.DisableNormalizing()
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if string(h.Host()) != "" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "")
}
v2 := h.Peek("value")
if !bytes.Equal(v2, []byte{'b'}) {
t.Fatalf("expecting non empty value. Got %q", v2)
}
if raw := h.RawHeaders(); string(raw) != exp {
t.Fatalf("expected header %q, got %q", exp, raw)
}
})
t.Run("no-kvs", func(t *testing.T) {
s := "GET / HTTP/1.1\r\n\r\n"
exp := ""
var h RequestHeader
h.DisableNormalizing()
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if string(h.Host()) != "" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "")
}
v1 := h.Peek("NoKey")
if len(v1) > 0 {
t.Fatalf("expecting empty value. Got %q", v1)
}
if raw := h.RawHeaders(); string(raw) != exp {
t.Fatalf("expected header %q, got %q", exp, raw)
}
})
}

func TestRequestHeaderSetCookieWithSpecialChars(t *testing.T) {
var h RequestHeader
h.Set("Cookie", "ID&14")
Expand Down

0 comments on commit e29392a

Please sign in to comment.