diff --git a/http/httphi/exchange.go b/http/httphi/exchange.go index 3043cb4..447d424 100644 --- a/http/httphi/exchange.go +++ b/http/httphi/exchange.go @@ -434,7 +434,7 @@ func (exch *Exchange) RequestParseForm(dst *httpraw.Form, buf []byte) error { length, present, err := exch.RequestContentLength() if !present { - dst.Reset(nil) + dst.Reset(nil, 0) return nil // No length is no body, RFC 9112 6.3. } else if err != nil { return err @@ -454,7 +454,7 @@ func (exch *Exchange) RequestParseForm(dst *httpraw.Form, buf []byte) error { return err } } - dst.Reset(buf) + dst.Reset(buf, 0) return dst.Parse() } diff --git a/http/httpraw/cookie.go b/http/httpraw/cookie.go index cc03786..ce25dff 100644 --- a/http/httpraw/cookie.go +++ b/http/httpraw/cookie.go @@ -77,8 +77,17 @@ func (c *Cookie) Get(key string) []byte { return c.kv.Get(key) } // HasKeyOrSingleValue returns true if the cookie contains a pair with the given // key or a valueless attribute with the given text, i.e: "Secure" or "HttpOnly". +// It cannot defer to [KVBuffer.Present]: parseCookie stores a valueless +// attribute with an empty key and the text as the value, so a key-only lookup +// would never match one. func (c *Cookie) HasKeyOrSingleValue(keyOrSingleValue string) bool { - return c.kv.Present(keyOrSingleValue) + for i, nc := 0, c.kv.Len(); i < nc; i++ { + k, v := c.kv.At(i) + if (len(k) == 0 && b2s(v) == keyOrSingleValue) || b2s(k) == keyOrSingleValue { + return true + } + } + return false } // parseCookie parses a cookie inside cookie buffer and adds it to cookie buffer.. diff --git a/http/httpraw/form.go b/http/httpraw/form.go index 66bc8eb..6304721 100644 --- a/http/httpraw/form.go +++ b/http/httpraw/form.go @@ -8,39 +8,35 @@ package httpraw // undecoded, until [Form.Decode] rewrites them in place. The caller bounds the // data: Form parses the buffer it is handed and reads nothing more. type Form struct { - buf []byte - kvs []argsKV + kv KVBuffer } +func (f *Form) EnableBufferGrowth(enableGrowth bool) { f.kv.EnableBufferGrowth(enableGrowth) } + // Reset discards parsed pairs and sets the buffer to parse in place. // If buf is nil the current buffer is reused. -func (f *Form) Reset(buf []byte) { - if buf == nil { - buf = f.buf[:0] - } - *f = Form{ - buf: buf, - kvs: f.kvs[:0], - } +func (f *Form) Reset(buf []byte, capKV int) { + f.kv.Reset(buf, capKV) } // ParseBytes copies the argument bytes to the Form's underlying buffer and parses them. func (f *Form) ParseBytes(b []byte) error { - f.Reset(nil) - f.buf = append(f.buf[:0], b...) + f.Reset(nil, 0) + err := f.kv.ReadFromBytes(b) + if err != nil { + return err + } return f.Parse() } // Parse parses the form's buffer in place. func (f *Form) Parse() error { - f.kvs = f.kvs[:0] - key, value, rest := NextQueryPair(f.buf) + f.kv.discardKVs() + key, value, rest := NextQueryPair(f.kv.buf) for key != nil { - kv := argsKV{key: bytes2tok(f.buf, key)} - if value != nil { - kv.value = bytes2tok(f.buf, value) + if !f.kv.setInternal(key, value) { + return errOOM } - f.kvs = append(f.kvs, kv) key, value, rest = NextQueryPair(rest) } return nil @@ -50,66 +46,51 @@ func (f *Form) Parse() error { // '+' with the bytes they encode. Decoding only shrinks, so no memory is added. func (f *Form) Decode() error { const plusAsSpace = true // Form encoded data, unlike a path. - for i := range f.kvs { - kv := &f.kvs[i] - n, err := CopyDecodedPercentURL(tok2bytes(f.buf, kv.key), tok2bytes(f.buf, kv.key), plusAsSpace) + nkvs := f.kv.Len() + for i := range nkvs { + k, v := f.kv.At(i) + nk, err := CopyDecodedPercentURL(k, k, plusAsSpace) if err != nil { return err - } - kv.key.len = tokint(n) - if !kv.HasValue() { + } else if len(v) == 0 { + if nk != len(k) { + f.kv.setAt(i, k, v) + } continue } - n, err = CopyDecodedPercentURL(tok2bytes(f.buf, kv.value), tok2bytes(f.buf, kv.value), plusAsSpace) + nv, err := CopyDecodedPercentURL(v, v, plusAsSpace) if err != nil { return err } - kv.value.len = tokint(n) + if nk != len(k) || nv != len(v) { + f.kv.setAt(i, k[:nk], v[:nv]) + } } return nil } // Len returns the amount of key-value pairs parsed. -func (f *Form) Len() int { return len(f.kvs) } +func (f *Form) Len() int { return f.kv.Len() } // Pair returns the i'th key-value pair in wire order. The value is nil for a // pair with no '=', i.e: "ok" in "ok&q=go", which distinguishes it from "ok=" // where the value is present and empty. func (f *Form) Pair(i int) (key, value []byte) { - kv := f.kvs[i] - key = tok2bytes(f.buf, kv.key) - if kv.HasValue() { - value = tok2bytes(f.buf, kv.value) - } - return key, value + return f.kv.At(i) } // Get returns the value of the first pair matching key, nil if absent or if the // pair has no value. Bytes are compared as stored, so call [Form.Decode] first // when keys may be encoded. -func (f *Form) Get(key string) []byte { - for i := range f.kvs { - gotKey, value := f.Pair(i) - if b2s(gotKey) == key { - return value - } - } - return nil -} +func (f *Form) Get(key string) []byte { return f.kv.Get(key) } // Has returns true if key is present, with or without a value. -func (f *Form) Has(key string) bool { - for i := range f.kvs { - if b2s(tok2bytes(f.buf, f.kvs[i].key)) == key { - return true - } - } - return false -} +func (f *Form) Has(key string) bool { return f.kv.Present(key) } // AppendKeyValues appends the form's wire representation to dst and returns it. func (f *Form) AppendKeyValues(dst []byte) []byte { - for i := range f.kvs { + nkv := f.kv.Len() + for i := range nkv { key, value := f.Pair(i) if i > 0 { dst = append(dst, '&') diff --git a/http/httpraw/form_test.go b/http/httpraw/form_test.go index 8024d4e..1ba0434 100644 --- a/http/httpraw/form_test.go +++ b/http/httpraw/form_test.go @@ -125,7 +125,7 @@ func TestFormParseReuseNoAlloc(t *testing.T) { t.Fatal(err) } allocs := testing.AllocsPerRun(100, func() { - f.Reset(body) + f.Reset(body, 0) f.Parse() }) if allocs != 0 { diff --git a/http/httpraw/header.go b/http/httpraw/header.go index ba474ee..66182ae 100644 --- a/http/httpraw/header.go +++ b/http/httpraw/header.go @@ -3,7 +3,6 @@ package httpraw import ( "bytes" "io" - "slices" "strconv" ) @@ -29,6 +28,7 @@ const ( flagConnClose flagNoHTTP11 flagMangledBuffer // set when header fields appended to buffer via Add,Set calls + flagKVAppended // set after KV appended to buffer outside Read methods. flagReaderEOF // set if [Header.SetStatus] or [Header.SetStatusInt] has been called. FlagStatusSet @@ -57,30 +57,27 @@ type Header struct { // Response fields. statusCode headerSlice statusText headerSlice - - flags Flags - _ noCopy + _ noCopy } // Flags returns [Flags] to signal status code has been set, Connection:Close or other useful signals provided by flags. -func (h *Header) Flags() Flags { return h.flags } +func (h *Header) Flags() Flags { return h.hbuf.kv.flags } // ConfigBufferGrowth configures the memory the header may use. Setting // outlives [Header.Reset]. Call before parsing/reading. // // enableBufferGrowth enables growing both the header buffer and the header key/value pair slice. func (h *Header) ConfigBufferGrowth(enableBufferGrowth bool) { - if !enableBufferGrowth { - h.flags |= flagNoBufferGrow - } else { - h.flags &^= flagNoBufferGrow - } + h.hbuf.kv.EnableBufferGrowth(enableBufferGrowth) } // ParseBytes copies the bytes into buffer and parses the HTTP header. It fails if HTTP header data is incomplete. func (h *Header) ParseBytes(asResponse bool, b []byte) error { h.Reset(nil, 0) - h.hbuf.readFromBytes(b) + err := h.hbuf.kv.ReadFromBytes(b) + if err != nil { + return err + } return h.parse(asResponse) } @@ -88,7 +85,7 @@ func (h *Header) ParseBytes(asResponse bool, b []byte) error { // It fails if HTTP data is incomplete. func (h *Header) Parse(asResponse bool) error { debuglog("http:parse:reset") - h.Reset(h.hbuf.buf, 0) + h.Reset(h.hbuf.kv.buf, 0) debuglog("http:parse:start") return h.parse(asResponse) } @@ -110,9 +107,10 @@ func (h *Header) Parse(asResponse bool) error { // return err // } func (h *Header) TryParse(asResponse bool) (needMoreData bool, err error) { - if h.flags.HasAny(flagDoneParsingHeader) { + flags := h.Flags() + if flags.HasAny(flagDoneParsingHeader) { return false, errAlreadyParsed - } else if h.flags.HasAny(flagMangledBuffer) { + } else if flags.HasAny(flagMangledBuffer) { return false, errMangledBuffer } if asResponse && h.statusCode.len == 0 || !asResponse && h.requestTarget.start == 0 { @@ -121,80 +119,42 @@ func (h *Header) TryParse(asResponse bool) (needMoreData bool, err error) { return err == ErrNeedMoreData, err } } - err = h.parseNextHeaders(h.flags) + err = h.parseNextHeaders(flags) return err == ErrNeedMoreData, err } // ParsingSuccess returns true if TryParse was successful, that is to say it returned needMoreData==false and err==nil. func (h *Header) ParsingSuccess() bool { - return h.flags.HasAny(flagDoneParsingHeader) + return h.Flags().HasAny(flagDoneParsingHeader) } // ReadFromLimited reads at most maxBytesToRead from reader and appends them to underlying buffer. // Used to accumulate HTTP header for later parsing with [Header.TryParse]. // If read is successful (read length>0) and reader returns [io.EOF] then ReadFromLimited will return a nil error. func (h *Header) ReadFromLimited(r io.Reader, maxBytesToRead int) (int, error) { - if maxBytesToRead <= 0 { - return 0, ErrSmallHeaderBuffer - } else if h.flags.HasAny(flagMangledBuffer) { - return 0, errMangledBuffer - } else if h.flags.HasAny(flagReaderEOF) { - return 0, io.EOF // Now we do return EOF. - } - free := h.BufferFree() - if free < maxBytesToRead { - if h.flags.HasAny(flagNoBufferGrow) { - return 0, ErrSmallHeaderBuffer - } - h.hbuf.buf = slices.Grow(h.hbuf.buf, maxBytesToRead) - } - blen := len(h.hbuf.buf) - b := h.hbuf.buf[blen:min(blen+maxBytesToRead, cap(h.hbuf.buf))] - if len(b) == 0 { - return 0, ErrSmallHeaderBuffer - } - n, err := r.Read(b) - if err != nil && err == io.EOF { - h.flags |= flagReaderEOF - if n > 0 { - err = nil // Nil-out error if read was succesful so as to not spook readers. - } - } - h.hbuf.buf = h.hbuf.buf[:blen+n] - return n, err + return h.hbuf.kv.ReadLimited(r, maxBytesToRead) } // ReadFromBytes appends argument buffer to underlying buffer. // Used to accumulate HTTP header for later parsing with [Header.TryParse]. -func (h *Header) ReadFromBytes(b []byte) (int, error) { - if len(b) == 0 { - return 0, ErrSmallHeaderBuffer - } - free := h.BufferFree() - if free < len(b) { - if h.flags.HasAny(flagNoBufferGrow) { - return 0, ErrSmallHeaderBuffer - } - h.hbuf.buf = slices.Grow(h.hbuf.buf, len(b)) - } - h.hbuf.readFromBytes(b) - return len(b), nil +func (h *Header) ReadFromBytes(b []byte) error { + return h.hbuf.kv.ReadFromBytes(b) } // BufferReceived returns the amoung of bytes read during calls to Read* methods. // Returns 0 if buffer is invalid/mangled. func (h *Header) BufferReceived() int { - if h.flags.HasAny(flagMangledBuffer | flagOOMReached) { + if h.Flags().HasAny(flagMangledBuffer | flagOOMReached) { return 0 } - return len(h.hbuf.buf) + return len(h.hbuf.kv.BufferRaw()) } // BufferParsed returns the amount of bytes parsed during a call to Parse* methods. // If the Parse* method completed without error then BufferParsed returns the header's length including the final "\r\n\r\n" text. // BufferParsed returns 0 if the buffer is invalid/mangled or if no header data has been parsed succesfully. func (h *Header) BufferParsed() int { - if h.flags.HasAny(flagMangledBuffer | flagOOMReached) { + if h.Flags().HasAny(flagMangledBuffer | flagOOMReached) { return 0 } return h.hbuf.off @@ -202,13 +162,13 @@ func (h *Header) BufferParsed() int { // BufferRaw returns the undeerlying buffer as stored currently in memory. // The length of the returned buffer is the used portion. Capacity of returned slice is [Header.BufferCapacity]. -func (h *Header) BufferRaw() []byte { return h.hbuf.buf } +func (h *Header) BufferRaw() []byte { return h.hbuf.kv.BufferRaw() } // BufferUsed returns the raw memory used. // // BufferUsed + BufferFree == BufferCapacity func (h *Header) BufferUsed() int { - return len(h.hbuf.buf) + return len(h.hbuf.kv.BufferRaw()) } // BufferFree returns amount of bytes free in underlying buffer. @@ -222,29 +182,12 @@ func (h *Header) BufferFree() int { // // BufferUsed + BufferFree == BufferCapacity func (h *Header) BufferCapacity() int { - return cap(h.hbuf.buf) + return cap(h.hbuf.kv.BufferRaw()) } // ForEach iterates over header key-value field tuples. -func (h *Header) ForEach(cb func(key, value []byte) error) error { - return h.hbuf.forEach(cb) -} - -func (hb *headerBuf) forEach(cb func(key, value []byte) error) error { - nh := len(hb.headers) - for i := range nh { - kv := hb.headers[i] - if !kv.isValid() { - continue - } - key := hb.musttoken(kv.key) - value := hb.musttoken(kv.value) - err := cb(key, value) - if err != nil { - return err - } - } - return nil +func (h *Header) ForEach(cb func(key, value []byte) bool) { + h.hbuf.kv.ForEach(cb) } // Reset discards all parsed data and sets the buffer data to buf. This method @@ -259,23 +202,21 @@ func (h *Header) Reset(buf []byte, numHeaderCapacity int) { const persistentFlags = flagNoBufferGrow debuglog("http:reset:hbuf") h.hbuf.reset(buf, numHeaderCapacity) - if h.flags.HasAny(flagNoBufferGrow) && cap(h.hbuf.buf) < 32 { + if h.Flags().HasAny(flagNoBufferGrow) && h.BufferCapacity() < 32 { panic("small buffer and flagNoBufferGrow set") } - *h = Header{ - hbuf: h.hbuf, - flags: h.flags & persistentFlags, - } + *h = Header{hbuf: h.hbuf} debuglog("http:reset:done") } // Body returns the surplus data following headers. It is only valid as long as Parse* or Reset methods are not called. func (h *Header) Body() ([]byte, error) { debuglog("http:body") - if h.flags.HasAny(flagMangledBuffer) { + flags := h.Flags() + if flags.HasAny(flagMangledBuffer) { return nil, errMangledBuffer - } else if h.flags.HasAny(flagDoneParsingHeader) { - return h.hbuf.buf[h.hbuf.off:], nil + } else if flags.HasAny(flagDoneParsingHeader) { + return h.BufferRaw()[h.hbuf.off:], nil } return nil, errUnparsed } @@ -303,48 +244,51 @@ func (h *Header) SetInt(key string, value int64, base int) { // Set sets a key-value pair in the HTTP header. // Calling Set mangles the buffer. -func (h *Header) Set(key, value string) { - useKv := h.takeReusableSlot(key) - if useKv == nil { - h.appendHeader(key, value) - } else { - useKv.value = h.reuseOrAppend(useKv.value, value) - } +func (h *Header) Set(key, value string) (enoughSpace bool) { + return h.hbuf.kv.Set(key, value) + + // useKv := h.takeReusableSlot(key) + // if useKv == nil { + // h.appendHeader(key, value) + // } else { + // useKv.value = h.reuseOrAppend(useKv.value, value) + // } } // takeReusableSlot returns the valid key-value entry for key with the largest // value buffer (best candidate for in-place reuse) and invalidates any other // entries sharing the key. Returns nil if the key is not present. func (h *Header) takeReusableSlot(key string) *argsKV { - hb := &h.hbuf + // hb := &h.hbuf var useKv *argsKV - for i := 0; i < len(hb.headers); i++ { - // Search for key-value with largest buffer for value to store value reusing buffer. - gotkv := &hb.headers[i] - if gotkv.isValid() && b2s(hb.musttoken(gotkv.key)) == key { - if useKv == nil { - useKv = gotkv - } else if gotkv.value.len > useKv.value.len { - useKv.invalidate() - useKv = gotkv - } else { - gotkv.invalidate() - } - } - } + // for i := 0; i < len(hb.headers); i++ { + // // Search for key-value with largest buffer for value to store value reusing buffer. + // gotkv := &hb.headers[i] + // if gotkv.isValidHeader() && b2s(hb.musttoken(gotkv.key)) == key { + // if useKv == nil { + // useKv = gotkv + // } else if gotkv.value.len > useKv.value.len { + // useKv.invalidate() + // useKv = gotkv + // } else { + // gotkv.invalidate() + // } + // } + // } return useKv } // Get gets the first exact-match value of a key found in the headers. Use [Header.ForEach] to find multiple values corresponding to same key. func (h *Header) Get(key string) []byte { - debuglog("http:get:start") - kv := h.peekHeader(key) - if kv.isValid() { - debuglog("http:get:found") - return h.hbuf.musttoken(kv.value) - } - debuglog("http:get:notfound") - return nil + return h.hbuf.kv.Get(key) + // debuglog("http:get:start") + // kv := h.peekHeader(key) + // if kv.isValidHeader() { + // debuglog("http:get:found") + // return h.hbuf.musttoken(kv.value) + // } + // debuglog("http:get:notfound") + // return nil } // GetFold gets the first value whose key matches key under ASCII case-insensitive @@ -352,11 +296,10 @@ func (h *Header) Get(key string) []byte { // Use [Header.Get] for exact match and [Header.ForEach] to find multiple values // corresponding to same key. func (h *Header) GetFold(key string) []byte { - hb := &h.hbuf - for i := 0; i < len(hb.headers); i++ { - kv := hb.headers[i] - if kv.isValid() && asciiEqualFold(b2s(hb.musttoken(kv.key)), key) { - return hb.musttoken(kv.value) + nh := h.hbuf.kv.Len() + for i := range nh { + if asciiEqualFold(key, b2s(h.hbuf.kv.AtKey(i))) { + return h.hbuf.kv.AtValue(i) } } return nil @@ -387,9 +330,9 @@ func asciiEqualFold(a, b string) bool { // NormalizeKeys normalizes all header keys. i.e: CONTENT-type -> Content-Type func (h *Header) NormalizeKeys() { - for _, kv := range h.hbuf.headers { - if kv.isValid() { - NormalizeHeaderKey(h.hbuf.musttoken(kv.key)) + for i, kv := range h.hbuf.kv.kvs { + if kv.isValidHeader() { + NormalizeHeaderKey(h.hbuf.kv.AtKey(i)) } } } @@ -507,19 +450,19 @@ func (h *Header) Status() (code, statusText []byte) { if h.statusCode.len == 0 { return nil, nil } - return h.hbuf.musttoken(h.statusCode), h.hbuf.musttoken(h.statusText) + return h.hbuf.kv.musttoken(h.statusCode), h.hbuf.kv.musttoken(h.statusText) } // SetStatus sets the response header's status code and status text. i.e: "200" "OK". func (h *Header) SetStatus(code, statusText string) { - h.flags |= FlagStatusSet + h.hbuf.kv.flags |= FlagStatusSet h.statusCode = h.reuseOrAppend(h.statusCode, code) h.statusText = h.reuseOrAppend(h.statusText, statusText) } // SetStatusInt is identical to [Header.SetStatus] but performs integer to text conversion for status code. func (h *Header) SetStatusInt(code int64, statusText string) { - h.flags |= FlagStatusSet + h.hbuf.kv.flags |= FlagStatusSet h.statusCode = h.reuseOrAppendInt(h.statusCode, code, 10) h.statusText = h.reuseOrAppend(h.statusText, statusText) } @@ -528,13 +471,13 @@ func (h *Header) getNonEmptyValue(s headerSlice) []byte { if s.len == 0 { return nil // If empty then value is invalid, return nil. } - return h.hbuf.musttoken(s) + return h.hbuf.kv.musttoken(s) } // AppendRequest appends the request header representation to the buffer and returns the result. func (h *Header) AppendRequest(dst []byte) ([]byte, error) { proto := h.Protocol() - if h.flags.HasAny(flagOOMReached) { + if h.hbuf.kv.flags.HasAny(flagOOMReached) { return dst, errOOM } else if h.requestTarget.len == 0 || h.method.len == 0 { return dst, errNeedMethodURI @@ -574,7 +517,7 @@ func (h *Header) AppendResponse(dst []byte) ([]byte, error) { // AppendResponseNoHeaders appends the first line of the response containing protocol and status code/text: i.e: "HTTP/1.1 200 OK\r\n" func (h *Header) AppendResponseNoHeaders(dst []byte) ([]byte, error) { proto := h.Protocol() - if h.flags.HasAny(flagOOMReached) { + if h.hbuf.kv.flags.HasAny(flagOOMReached) { return dst, errOOM } else if h.statusCode.len == 0 || h.statusText.len == 0 { return dst, errBadStatusCodeTxt @@ -595,12 +538,10 @@ func (h *Header) AppendResponseNoHeaders(dst []byte) ([]byte, error) { // AppendHeaders appends headers to buffer. Use AppendRequest and AppendResponse over this. // Does not append extra \r\n to end. Appends nothing if contains no headers. func (h *Header) AppendHeaders(dst []byte) []byte { - for i, n := 0, len(h.hbuf.headers); i < n; i++ { - kv := &h.hbuf.headers[i] - if kv.isValid() { - key := h.hbuf.musttoken(kv.key) - value := h.hbuf.musttoken(kv.value) - dst = appendHeaderLine(dst, b2s(key), b2s(value)) + for i, kv := range h.hbuf.kv.kvs { + if kv.isValidHeader() { + k, v := h.hbuf.kv.At(i) + dst = appendHeaderLine(dst, b2s(k), b2s(v)) } } return dst diff --git a/http/httpraw/kvbuffer.go b/http/httpraw/kvbuffer.go index 2751aa5..7450a8b 100644 --- a/http/httpraw/kvbuffer.go +++ b/http/httpraw/kvbuffer.go @@ -1,7 +1,9 @@ package httpraw import ( + "io" "slices" + "strconv" "github.com/soypat/lneto/internal" ) @@ -26,6 +28,52 @@ func (mb *KVBuffer) EnableBufferGrowth(enableGrowth bool) { } } +func (mb *KVBuffer) discardKVs() { mb.kvs = mb.kvs[:0] } + +func (mb *KVBuffer) BufferGrowthEnabled() bool { return !mb.flags.HasAny(flagNoBufferGrow) } + +func (mb *KVBuffer) ReadFromBytes(buf []byte) error { + if mb.flags.HasAny(flagMangledBuffer) { + return errMangledBuffer + } else if len(buf)+cap(mb.buf) > maxBufLen { + return errOOM + } + free := mb.free() + if len(buf) > free && !mb.BufferGrowthEnabled() { + return errOOM + } + mb.buf = append(mb.buf, buf...) + return nil +} + +func (mb *KVBuffer) ReadLimited(r io.Reader, limit int) (int, error) { + if mb.flags.HasAny(flagMangledBuffer) { + return 0, errMangledBuffer + } else if mb.flags.HasAny(flagReaderEOF) { + return 0, io.EOF + } else if limit <= 0 { + return 0, io.ErrNoProgress + } else if len(mb.buf) >= maxBufLen { + return 0, errOOM + } + free := mb.free() + if limit > free { + if !mb.BufferGrowthEnabled() { + return 0, errOOM + } + mb.buf = slices.Grow(mb.buf, limit) + } + n, err := r.Read(mb.buf[len(mb.buf):min(len(mb.buf)+limit, maxBufLen)]) + mb.buf = mb.buf[:len(mb.buf)+n] + if err != nil { + if n > 0 && err == io.EOF { + mb.flags |= flagReaderEOF + err = nil // Nil out EOF to not scare off readers. + } + } + return n, err +} + func (mb *KVBuffer) Reset(buf []byte, kvCap int) { if buf == nil { mb.buf = mb.buf[:0] @@ -41,15 +89,19 @@ func (mb *KVBuffer) CopyFrom(src *KVBuffer) { mb.kvs = append(mb.kvs[:0], src.kvs...) } +// Get returns the value of the first pair matching key. +// Bytes are compared as stored, so if using a Form call [Form.Decode] first when keys may be encoded. +// Returns nil for an absent key and for a valueless pair alike, so use +// [KVBuffer.Present] to tell the two apart. func (mb *KVBuffer) Get(key string) []byte { - v := mb.getIdx(key) - if v < 0 { + i := mb.getIdx(key) + if i < 0 { return nil } - return mb.musttoken(mb.kvs[v].value) + return mb.AtValue(i) } -// ForEach iterates over the cookie's key-value pairs until cb returns false. +// ForEach iterates over the cookie's key-value pairs as stored until cb returns false. func (c *KVBuffer) ForEach(cb func(key, value []byte) bool) { nc := len(c.kvs) for i := range nc { @@ -61,18 +113,76 @@ func (c *KVBuffer) ForEach(cb func(key, value []byte) bool) { } } -func (mb *KVBuffer) Present(key string) bool { +// Has returns true if key is present, with or without a value. +func (mb *KVBuffer) Present(key string) bool { // TODO: rename to Has. return mb.getIdx(key) >= 0 } -func (mb *KVBuffer) Add(key, value string) bool { + +// Has returns true if key is present, with or without a value. +func (mb *KVBuffer) HasKeyValue(key, value string) bool { + idx := mb.getIdx(key) + if idx >= 0 { + return b2s(mb.musttoken(mb.kvs[idx].value)) == value + } + return false +} +func (mb *KVBuffer) Add(key, value string) (enoughSpace bool) { mb.appendPair(key, value) return mb.getIdx(key) >= 0 } -func (mb *KVBuffer) setInternal(key, value []byte) bool { +// Set replaces key's value and invalidates every other pair sharing the key, so +// a following [KVBuffer.Get] sees exactly one value. +// +// It rewrites in place when it can: of the pairs it would invalidate it keeps +// the smallest whose key and value regions both still hold the new pair, +// leaving the roomier regions for a later Set. When none fits the pair is +// appended with [KVBuffer.Add] and the invalidated regions are stranded, since +// nothing here compacts the buffer. +func (mb *KVBuffer) Set(key, value string) (enoughSpace bool) { + reuse := -1 + for i := range mb.kvs { + kv := &mb.kvs[i] + if !kv.isValid() || b2s(mb.musttoken(kv.key)) != key { + continue + } + // A valueless pair holds no value region, so reusing one would write the + // value over byte 0. Let it fall through to Add, which gives the pair a + // real region and keeps "ok" distinct from "ok=". + fits := kv.HasValue() && int(kv.key.len) >= len(key) && int(kv.value.len) >= len(value) + if fits && (reuse < 0 || kv.size() < mb.kvs[reuse].size()) { + if reuse >= 0 { + mb.kvs[reuse].invalidate() // Superseded by a tighter fit. + } + reuse = i + continue + } + kv.invalidate() + } + if reuse < 0 { + return mb.Add(key, value) + } + mb.overwriteAt(reuse, key, value) + return true +} + +// overwriteAt writes key and value over the regions pair i already owns. The +// caller must have checked both fit; the bytes freed by a shorter pair are +// stranded, not reclaimed. +func (mb *KVBuffer) overwriteAt(i int, key, value string) { + mb.flags |= flagMangledBuffer + kv := &mb.kvs[i] + copy(mb.buf[kv.key.start:], key) + kv.key.len = tokint(len(key)) + copy(mb.buf[kv.value.start:], value) + kv.value.len = tokint(len(value)) +} + +func (mb *KVBuffer) setInternal(key, value []byte) (enoughSpace bool) { if !mb.canAddOneKV() { return false } + mb.flags |= flagKVAppended mb.kvs = append(mb.kvs, argsKV{ key: mb.slice(key), value: mb.slice(value), @@ -83,10 +193,26 @@ func (mb *KVBuffer) setInternal(key, value []byte) bool { func (mb *KVBuffer) Len() int { return len(mb.kvs) } func (mb *KVBuffer) At(i int) (key, value []byte) { kv := mb.kvs[i] + if !kv.HasValue() { + return mb.musttoken(kv.key), nil + } return mb.musttoken(kv.key), mb.musttoken(kv.value) } -func (mb *KVBuffer) AtKey(i int) (key []byte) { return mb.musttoken(mb.kvs[i].key) } -func (mb *KVBuffer) AtValue(i int) (key []byte) { return mb.musttoken(mb.kvs[i].value) } +func (mb *KVBuffer) setAt(i int, k, v []byte) { + mb.flags |= flagMangledBuffer + mb.kvs[i] = argsKV{ + key: bytes2tok(mb.buf, k), + value: bytes2tok(mb.buf, v), + } +} + +func (mb *KVBuffer) AtKey(i int) (key []byte) { return mb.musttoken(mb.kvs[i].key) } +func (mb *KVBuffer) AtValue(i int) (key []byte) { + if !mb.kvs[i].HasValue() { + return nil + } + return mb.musttoken(mb.kvs[i].value) +} func (mb *KVBuffer) getIdx(key string) int { for i, kv := range mb.kvs { @@ -106,12 +232,21 @@ func (mb *KVBuffer) getInvalidIdx() int { return -1 } +func (mb *KVBuffer) getInvalidOrKeyIdx(key string) int { + for i, kv := range mb.kvs { + if !kv.isValid() || key == b2s(mb.musttoken(kv.key)) { + return i + } + } + return -1 +} + // reserve ensures need free bytes are available in the buffer, growing it when // permitted. It accounts for the byte-0 reservation on an empty buffer (see // mustAppendSlice). It returns false and sets flagOOMReached when the space // cannot be guaranteed: a tokint offset overflow, or a full buffer with // flagNoBufferGrow set. -func (mb *KVBuffer) reserve(need int) bool { +func (mb *KVBuffer) reserve(need int) (enoughSpace bool) { if len(mb.buf) == 0 { need++ // mustAppend* reserves byte 0 on an empty buffer. } @@ -130,24 +265,31 @@ func (mb *KVBuffer) reserve(need int) bool { } func (mb *KVBuffer) appendPair(key, value string) bool { - // reserve accounts for the byte-0 reservation mustAppendSlice makes on an - // empty buffer, and drops (flagging OOM) rather than panicking when growth - // is disabled and space runs out. - if !mb.canAddOneKV() || !mb.reserve(len(key)+len(value)) { return false } - k := mb.mustAppendSlice(key) - v := mb.mustAppendSlice(value) - debuglog("http:appendhdr:grow-hdrs") + mb.flags |= flagKVAppended mb.kvs = append(mb.kvs, argsKV{ - key: k, - value: v, + key: mb.mustAppendSlice(key), + value: mb.mustAppendSlice(value), }) return true } -func (mb *KVBuffer) canAddOneKV() bool { +func (mb *KVBuffer) appendPairInt(key string, value int64, base int) bool { + vlen := internal.IntLen(value, base) + if !mb.canAddOneKV() || !mb.reserve(len(key)+vlen) { + return false + } + mb.flags |= flagKVAppended + mb.kvs = append(mb.kvs, argsKV{ + key: mb.mustAppendSlice(key), + value: mb.mustAppendInt(value, base), + }) + return true +} + +func (mb *KVBuffer) canAddOneKV() (enoughSpace bool) { return len(mb.kvs) < cap(mb.kvs) || mb.flags&flagNoBufferGrow == 0 } @@ -161,7 +303,20 @@ func (mb *KVBuffer) mustAppendSlice(value string) headerSlice { return mb.slice(mb.buf[L : L+len(value)]) } +func (hb *KVBuffer) mustAppendInt(value int64, base int) headerSlice { + L := len(hb.buf) + if L == 0 { + L++ // Valid key-values start after byte 0. + } + v := strconv.AppendInt(hb.buf[L:L], value, base) + hb.buf = hb.buf[:L+len(v)] + return hb.slice(hb.buf[L : L+len(v)]) +} + func (mb *KVBuffer) slice(value []byte) headerSlice { + if value == nil { + return headerSlice{} + } return bytes2tok(mb.buf, value) } @@ -169,3 +324,32 @@ func (mb KVBuffer) musttoken(slice headerSlice) []byte { return tok2bytes(mb.buf, slice) } func (mb *KVBuffer) noKV() argsKV { return argsKV{} } + +type tokint = uint16 + +type headerSlice struct { + start tokint + len tokint +} + +type argsKV struct { + key headerSlice + value headerSlice // value start >0 means value is present. +} + +// isValid is for stores parsed in place, where offset 0 is the first key so +// only length can signal presence. Empty keys are valid: see valueless cookies. +func (kv argsKV) isValid() bool { + return kv.key.len > 0 || kv.value.len > 0 +} + +// isValidHeader is for the append-built [Header] store, where mustAppendSlice +// burns byte 0 so a zero offset means absent. Drops offset-0 pairs otherwise. +func (kv argsKV) isValidHeader() bool { return kv.key.start > 0 } + +func (kv *argsKV) invalidate() { + *kv = argsKV{} +} + +// size is the buffer a pair occupies, used to pick the tightest slot to reuse. +func (kv argsKV) size() int { return int(kv.key.len) + int(kv.value.len) } diff --git a/http/httpraw/parse.go b/http/httpraw/parse.go index 5752c59..aef42e0 100644 --- a/http/httpraw/parse.go +++ b/http/httpraw/parse.go @@ -49,42 +49,21 @@ var ( const maxBufLen = 0xffff type headerBuf struct { + kv KVBuffer // buf[:len] holds entire HTTP header data, which may be normalized by [flags]. buf[off:len] holds data not yet processed during parsing. - buf []byte + // buf []byte // offset into buf for parsing. off int // args contains key-value store. - headers []argsKV + // headers []argsKV } // reset sets the buffer data and discards all parsed data. The field table is // grown to match the new buffer's capacity and never shrinks, so a header // reused across requests settles on its largest buffer and stops allocating. func (h *headerBuf) reset(buf []byte, numHeaderCapacity int) { - if buf == nil { - buf = h.buf[:0] // Reuse buffer but discard raw data on nil input. - } - if numHeaderCapacity != 0 { - internal.SliceReuse(&h.headers, numHeaderCapacity) - } else { - h.headers = h.headers[:0] - } - *h = headerBuf{ - buf: buf, - headers: h.headers, - } -} - -type tokint = uint16 - -type headerSlice struct { - start tokint - len tokint -} - -type argsKV struct { - key headerSlice - value headerSlice // value start >0 means value is present. + h.kv.Reset(buf, numHeaderCapacity) + h.off = 0 } type scannerState struct { @@ -109,20 +88,22 @@ func (h *Header) parse(asResponse bool) (err error) { return err } debuglog("http:firstline:done") - err = h.parseNextHeaders(h.flags) + err = h.parseNextHeaders(h.Flags()) debuglog("http:headers:done") return err } func (h *Header) parseFirstLine(asResponse bool) (err error) { - if len(h.hbuf.buf) > maxBufLen { + if len(h.hbuf.kv.buf) > maxBufLen { return errBufferTooLarge // Offsets would overflow uint16 tokint. } + flags := h.Flags() if asResponse { - h.statusCode, h.statusText, h.flags, err = h.hbuf.parseFirstLineResponse(h.flags) + h.statusCode, h.statusText, flags, err = h.hbuf.parseFirstLineResponse(flags) } else { - h.method, h.requestTarget, h.proto, h.flags, err = h.hbuf.parseFirstLineRequest(h.flags) + h.method, h.requestTarget, h.proto, flags, err = h.hbuf.parseFirstLineRequest(flags) } + h.hbuf.kv.flags = flags return err } @@ -130,39 +111,33 @@ func (h *Header) parseNextHeaders(flags Flags) error { var ss scannerState h.hbuf.parseNextHeaders(&ss, flags) if ss.err != nil { - h.flags |= flagConnClose + h.hbuf.kv.flags |= flagConnClose return ss.err } - h.flags |= flagDoneParsingHeader + h.hbuf.kv.flags |= flagDoneParsingHeader return nil } -func (hb *headerBuf) readFromBytes(b []byte) { - hb.buf = append(hb.buf, b...) -} - -func (hb *headerBuf) free() int { return cap(hb.buf) - len(hb.buf) } +func (hb *headerBuf) free() int { return hb.kv.free() } func (hb *headerBuf) parseNextHeaders(ss *scannerState, flags Flags) { debuglog("http:nexthdr:loop") - for kv := hb.next(ss); kv.isValid(); kv = hb.next(ss) { - if len(hb.headers) == cap(hb.headers) && flags.HasAny(flagNoBufferGrow) { - // Refuse to grow the headers slice: the caller granted this much - // memory and no more, see [Header.Reset]. + for kv := hb.next(ss); kv.isValidHeader(); kv = hb.next(ss) { + if !hb.kv.canAddOneKV() { ss.err = ErrHeaderTooMany return } - hb.headers = append(hb.headers, kv) + hb.kv.kvs = append(hb.kv.kvs, kv) } debuglog("http:nexthdr:done") } func (hb *headerBuf) offBuf() []byte { - return hb.buf[hb.off:] + return hb.kv.buf[hb.off:] } func (hb *headerBuf) skipLeadingCRLF() { - for hb.off < len(hb.buf) && (hb.buf[hb.off] == '\n' || hb.buf[hb.off] == '\r') { + for hb.off < len(hb.kv.buf) && (hb.kv.buf[hb.off] == '\n' || hb.kv.buf[hb.off] == '\r') { hb.off++ } } @@ -172,7 +147,7 @@ func (hb *headerBuf) scanLine() []byte { if len(buf) > 0 && buf[len(buf)-1] == '\r' { buf = buf[:len(buf)-1] // exclude carriage return. } - if hb.off < len(hb.buf) { + if hb.off < len(hb.kv.buf) { hb.off++ // consume newline. } return buf @@ -206,8 +181,8 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags Flags) (method, uri, proto reqURIEnd := bytes.IndexByte(b[methodEnd+1:], ' ') if reqURIEnd > 0 { reqURIEnd += methodEnd + 1 - uri = hb.slice(b[methodEnd+1 : reqURIEnd]) - proto = hb.slice(b[reqURIEnd+1:]) // Skip space before protocol. + uri = hb.kv.slice(b[methodEnd+1 : reqURIEnd]) + proto = hb.kv.slice(b[reqURIEnd+1:]) // Skip space before protocol. if b2s(b[reqURIEnd+1:]) != strHTTP11 { flags |= flagNoHTTP11 } @@ -216,9 +191,9 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags Flags) (method, uri, proto } else { // No version provided. flags |= flagNoHTTP11 - uri = hb.slice(b[methodEnd+1:]) + uri = hb.kv.slice(b[methodEnd+1:]) } - method = hb.slice(b[:methodEnd]) + method = hb.kv.slice(b[:methodEnd]) return method, uri, proto, flags, nil } @@ -260,66 +235,19 @@ func (hb *headerBuf) parseFirstLineResponse(initFlags Flags) (statusCode, status return statusCode, statusText, flags, errBadStatusCode } } - statusCode = hb.slice(code) + statusCode = hb.kv.slice(code) if codeEnd < len(b) { - statusText = hb.slice(b[codeEnd+1:]) // Skip space before text. + statusText = hb.kv.slice(b[codeEnd+1:]) // Skip space before text. } debuglog("http:resp:done") return statusCode, statusText, flags, nil } -func (kv argsKV) isValid() bool { - return kv.key.start > 0 -} - -func (kv *argsKV) invalidate() { - *kv = argsKV{} -} - -func (tb headerBuf) musttoken(slice headerSlice) []byte { - return tok2bytes(tb.buf, slice) - -} - -func (tb headerBuf) slice(b []byte) headerSlice { - return bytes2tok(tb.buf, b) -} - func (kv argsKV) HasValue() bool { return kv.value.start > 0 } -func (h *Header) hasHeaderValue(key, value string) bool { - kv := h.peekHeader(key) - return kv.isValid() && b2s(h.hbuf.musttoken(kv.value)) == value -} - -// peekHeader returns header key-value for the given key. -// -// The returned value is valid until the request is released, -// either though ReleaseRequest or your request handler returning. -// Do not store references to returned value. Make copies instead. -func (h *Header) peekHeader(key string) argsKV { - hb := &h.hbuf - for i := 0; i < len(h.hbuf.headers); i++ { - if b2s(hb.musttoken(h.hbuf.headers[i].key)) == key { - return h.hbuf.headers[i] - } - } - return hb.noKV() -} - -func (hb *headerBuf) mustAppendSlice(value string) headerSlice { - L := len(hb.buf) - if L == 0 { - L++ // Valid key-values start after 0. - } - copy(hb.buf[L:L+len(value)], value) - hb.buf = hb.buf[:L+len(value)] - return hb.slice(hb.buf[L : L+len(value)]) -} - func (h *Header) reuseOrAppend(tok headerSlice, value string) headerSlice { if tok.len > tokint(len(value)) { - copy(h.hbuf.musttoken(tok), value) + copy(h.hbuf.kv.musttoken(tok), value) tok.len = tokint(len(value)) return tok } @@ -332,7 +260,7 @@ func (h *Header) appendSlice(value string) headerSlice { return headerSlice{} } h.flags |= flagMangledBuffer - return h.hbuf.mustAppendSlice(value) + return h.hbuf.kv.mustAppendSlice(value) } func (h *Header) appendHeader(key, value string) { @@ -437,7 +365,7 @@ func (hb *headerBuf) next(ss *scannerState) argsKV { ss.nextColon = -1 ss.nextNewLine = -1 } - buf := hb.buf[hb.off:] + buf := hb.kv.buf[hb.off:] blen := len(buf) if blen >= 2 && buf[0] == '\r' && buf[1] == '\n' { hb.off += 2 @@ -484,7 +412,7 @@ func (hb *headerBuf) next(ss *scannerState) argsKV { // Ready to store key.. var resultKV argsKV - resultKV.key = hb.slice(buf[:n]) + resultKV.key = hb.kv.slice(buf[:n]) n++ // consume colon. for len(buf) > n && buf[n] == ' ' { n++ // Trim leading spaces. @@ -511,18 +439,19 @@ func (hb *headerBuf) next(ss *scannerState) argsKV { if valueEnd > valueStart && buf[valueEnd-1] == '\r' { valueEnd-- // Trim \r character if present before value. } - resultKV.value = hb.slice(buf[valueStart:valueEnd]) + resultKV.value = hb.kv.slice(buf[valueStart:valueEnd]) hb.off += n return resultKV } // ConnectionClose returns true if 'Connection: close' header is set or if a invalid header was found. func (h *Header) ConnectionClose() bool { - closed := h.flags.HasAny(flagConnClose) || - h.hasHeaderValue(headerConnection, strClose) || - (h.flags.HasAny(flagNoHTTP11) && !h.hasHeaderValue(headerConnection, "keep-alive")) + flags := h.Flags() + closed := flags.HasAny(flagConnClose) || + h.hbuf.kv.HasKeyValue(headerConnection, strClose) || + (flags.HasAny(flagNoHTTP11) && !h.hbuf.kv.HasKeyValue(headerConnection, "keep-alive")) if closed { - h.flags |= flagConnClose + h.hbuf.kv.flags |= flagConnClose } return closed } diff --git a/http/httpraw/parse_test.go b/http/httpraw/parse_test.go index 5e70fae..6450c65 100644 --- a/http/httpraw/parse_test.go +++ b/http/httpraw/parse_test.go @@ -331,6 +331,14 @@ func TestCookie_ParseBytes(t *testing.T) { if string(c.Get("Path")) != "/" { t.Errorf("Path = %q; want /", c.Get("Path")) } + // The first pair sits at buffer offset 0, which a presence check keyed on + // the offset rather than the length reads as absent. + if string(c.Get("session")) != "abc123" { + t.Errorf("Get(session) = %q; want abc123", c.Get("session")) + } + if !c.HasKeyOrSingleValue("session") { + t.Error("expected first pair to be present by key") + } if !c.HasKeyOrSingleValue("Secure") { t.Error("expected Secure flag") }