tests passing babyyyyy, header is 112 bytes of pure ecstasy

This commit is contained in:
soypat
2025-05-25 02:01:53 -03:00
parent 095c4e1b57
commit b63ce5a964
3 changed files with 87 additions and 86 deletions
+46 -84
View File
@@ -88,6 +88,9 @@ func (hb *headerBuf) parseFirstLine(initFlags flags) (method, uri, proto headerS
methodEnd := max(0, bytes.IndexByte(b, ' ')) methodEnd := max(0, bytes.IndexByte(b, ' '))
reqURIEnd := bytes.IndexByte(b[methodEnd+1:], ' ') reqURIEnd := bytes.IndexByte(b[methodEnd+1:], ' ')
if reqURIEnd >= 0 {
reqURIEnd += methodEnd + 1
}
switch { switch {
case reqURIEnd < 0: case reqURIEnd < 0:
flags |= noHTTP11 flags |= noHTTP11
@@ -106,11 +109,7 @@ func (hb *headerBuf) parseFirstLine(initFlags flags) (method, uri, proto headerS
} }
type scannerState struct { type scannerState struct {
err error err error
// hLen stores header subslice len
hLen int
disableNormalizing bool disableNormalizing bool
// by checking whether the next line contains a colon or not to tell // by checking whether the next line contains a colon or not to tell
@@ -128,7 +127,7 @@ func (h *header) parseHeaders(ss *scannerState) (err error) {
hb := &h.hbuf hb := &h.hbuf
h.contentLength = -2 h.contentLength = -2
for kv := hb.nextKV(ss); kv.isValid(); kv = hb.nextKV(ss) { for kv := hb.nextKV2(ss); kv.isValid(); kv = hb.nextKV2(ss) {
if h.flags.hasAny(disableSpecialHeader) { if h.flags.hasAny(disableSpecialHeader) {
h.hbuf.headers = append(h.hbuf.headers, kv) h.hbuf.headers = append(h.hbuf.headers, kv)
continue continue
@@ -271,49 +270,46 @@ func readRawHeaders(dst []byte, buf string) ([]byte, int, error) {
} }
func (hb *headerBuf) noKV() argsKV { return argsKV{} } func (hb *headerBuf) noKV() argsKV { return argsKV{} }
func (hb *headerBuf) nextKV(ss *scannerState) argsKV { func (hb *headerBuf) nextKV2(ss *scannerState) argsKV {
if !ss.initialized { if !ss.initialized {
ss.nextColon = -1 ss.nextColon = -1
ss.nextNewLine = -1 ss.nextNewLine = -1
ss.initialized = true
} }
buf := hb.buf[hb.off:] buf := hb.buf[hb.off:]
bLen := len(buf) blen := len(buf)
if bLen >= 2 && buf[0] == rChar && buf[1] == nChar { if blen >= 2 && buf[0] == '\r' && buf[1] == '\n' {
hb.off += 2 hb.off += 2
return hb.noKV() // \r\n\r\n Ends header. return hb.noKV() // \r\n\r\n Ends header.
} } else if blen >= 1 && buf[0] == '\n' {
if bLen >= 1 && buf[0] == nChar { hb.off += 1
hb.off++ return hb.noKV() // \n\n Ends header.
return hb.noKV() // \n\n: Ends header.
} }
var n int // n is parsing offset. Will start by storing colon index.
n := 0
if ss.nextColon >= 0 { if ss.nextColon >= 0 {
// Retake from last colon found.
n = ss.nextColon n = ss.nextColon
ss.nextColon = -1 ss.nextColon = -1
} else { } else {
n = bytes.IndexByte(buf, ':') n = bytes.IndexByte(buf, ':')
x := bytes.IndexByte(buf, '\n')
// There can't be a \n inside the header name, check for this.
x := bytes.IndexByte(buf, nChar)
if x < 0 { if x < 0 {
// A header name should always at some point be followed by a \n // A header name should always at some point be followed by a \n
// even if it's the one that terminates the header block. // even if it's the one that terminates the header block.
ss.err = errNeedMore ss.err = errNeedMore
return hb.noKV() return hb.noKV()
} } else if x < n {
if x < n { // There was a \n before the colon! This is invalid.
// There was a \n before the :
ss.err = errInvalidName ss.err = errInvalidName
return hb.noKV() return hb.noKV()
} else if n < 0 {
// No colon found, probably missing data.
ss.err = errNeedMore
return hb.noKV()
} }
} }
if n < 0 { // n stores colon position by now.
ss.err = errNeedMore
return hb.noKV()
}
if bytes.IndexByte(buf[:n], ' ') >= 0 || bytes.IndexByte(buf[:n], '\t') >= 0 { if bytes.IndexByte(buf[:n], ' ') >= 0 || bytes.IndexByte(buf[:n], '\t') >= 0 {
// Spaces between the header key and colon are not allowed. // Spaces between the header key and colon are not allowed.
// See RFC 7230, Section 3.2.4. // See RFC 7230, Section 3.2.4.
@@ -321,72 +317,38 @@ func (hb *headerBuf) nextKV(ss *scannerState) argsKV {
return hb.noKV() return hb.noKV()
} }
// Ready to store key..
var resultKV argsKV var resultKV argsKV
resultKV.key = hb.slice(buf[:n]) resultKV.key = hb.slice(buf[:n])
normalizeHeaderKey(buf[:n], ss.disableNormalizing) normalizeHeaderKey(buf[:n], ss.disableNormalizing)
n++ n++ // consume colon.
for len(buf) > n && buf[n] == ' ' { for len(buf) > n && buf[n] == ' ' {
n++ n++ // Trim leading spaces.
// 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
// to a minus value, it means it's invalid, and will find the newline again.
ss.nextNewLine--
} }
ss.hLen += n // n now points to start of value.
buf = buf[n:] valueStart := n
if ss.nextNewLine >= 0 {
n = ss.nextNewLine
ss.nextNewLine = -1
} else {
n = bytes.IndexByte(buf, nChar)
}
if n < 0 {
ss.err = errNeedMore
return hb.noKV()
}
isMultiLineValue := false
for {
if n+1 >= len(buf) {
break
}
if buf[n+1] != ' ' && buf[n+1] != '\t' {
break
}
d := bytes.IndexByte(buf[n+1:], nChar)
if d <= 0 {
break
} else if d == 1 && buf[n+1] == rChar {
break
}
e := n + d + 1
if c := bytes.IndexByte(buf[n+1:e], ':'); c >= 0 {
ss.nextColon = c
ss.nextNewLine = d - c - 1
break
}
isMultiLineValue = true
n = e
}
if n >= len(buf) {
ss.err = errNeedMore
return hb.noKV()
}
oldB := buf
value := buf[:n]
ss.hLen += n + 1
buf = buf[n+1:]
if n > 0 && value[n-1] == rChar { // Find end of value. Values may be multiline, in which case we must treat newlines followed by whitespace as part of the value.
n-- for {
nl := bytes.IndexByte(buf[n:], '\n')
if nl < 0 || nl+n+1 == len(buf) {
// No newline or newline is last character and can't know if is multiline.
ss.err = errNeedMore
return hb.noKV()
}
n += nl + 1 // Index of the newly found newline.
nextChar := buf[n]
if nextChar != ' ' && nextChar != '\t' {
break // End of value found.
}
} }
for n > 0 && value[n-1] == ' ' {
n-- valueEnd := n - 1 // Trim newline.
if valueEnd > valueStart && buf[valueEnd-1] == '\r' {
valueEnd-- // Trim \r character if present before value.
} }
value = value[:n] resultKV.value = hb.slice(buf[valueStart:valueEnd])
if isMultiLineValue { hb.off += n
value, buf, ss.hLen = normalizeHeaderValue(value, oldB, ss.hLen)
}
resultKV.value = hb.slice(value)
return resultKV return resultKV
} }
+33
View File
@@ -0,0 +1,33 @@
package httpx
import (
"bytes"
"net/http"
"strings"
"testing"
)
func TestHeaderParseRequest(t *testing.T) {
const (
wantMethod = "GET"
wantURI = "/"
wantMessage = "hello world!"
)
req, err := http.NewRequest(wantMethod, wantURI, strings.NewReader(wantMessage))
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
req.Write(&buf)
var hdr header
err = hdr.ParseBytes(buf.Bytes())
if err != nil {
t.Fatal(err)
}
if !hdr.MethodIs(wantMethod) {
t.Errorf("want method %s, got %q", wantMethod, hdr.Method())
}
if !bytes.Equal(hdr.RequestURI(), []byte(wantURI)) {
t.Errorf("want request URI %q, got %q", wantURI, hdr.RequestURI())
}
}
+8 -2
View File
@@ -45,8 +45,8 @@ func (tb headerBuf) musttoken(slice headerSlice) []byte {
} }
func (tb headerBuf) slice(b []byte) headerSlice { func (tb headerBuf) slice(b []byte) headerSlice {
base := uintptr(unsafe.Pointer(&tb.buf[0])) base := uintptr(unsafe.Pointer(unsafe.SliceData(tb.buf)))
off := uintptr(unsafe.Pointer(&b[0])) off := uintptr(unsafe.Pointer(unsafe.SliceData(b)))
if off < base || off > base+uintptr(len(tb.buf)) { if off < base || off > base+uintptr(len(tb.buf)) {
panic("httpx: argument buffer does not alias header buffer") panic("httpx: argument buffer does not alias header buffer")
} }
@@ -87,6 +87,12 @@ type header struct {
flags flags flags flags
} }
func (h *header) ParseBytes(b []byte) error {
h.resetSkipNormalize()
h.hbuf.readFromBytes(b)
return h.parse()
}
func (h *header) Set(key, value string) { func (h *header) Set(key, value string) {
h.SetCanonical(key, value) //TODO: implement non-canonical. h.SetCanonical(key, value) //TODO: implement non-canonical.
} }