Files
lneto/http/httpraw/cookie.go
T
Pat Whittingslow 5c54030f19 add http/httphi (#171)
* add http/httphi

* begin adding httphi tests

* claude found neat bugs

* add low level Handle function and more tests

* more tests, run go generate

* add Hijacker-like functionality

* improve locking and acquisition of Exchanges in reconfiguring

* several bugfixes, add internal.IntLen, round up http-linux example with new router API

* small nit

* add benchmarks

* add query handling

* remove ForEach pattern, allocates in TinyGo

* massive documentation push and code reordering in files

* Router.Handle returns error after being torn down

* run go fix

* rework Mux interface to receive a string request path

* add MethodFrom

* minor doc nit

* fail on incomplete staging

* add raw buffer access

* add streaming API distinct from Exchange

* begin adding multipart form logic

* finish rounding up multipart form parsing

* remove status type

* first Multipart approach

* begin adding readMultiPart

* add Exchange.ReadMultiparts reimagining of clanker slop

* ai insists with backoffs

* simplify clanker slop

* apply go fix

* add a pattern argument to Mux

* explicit header key/value alloc and add ExchangeConfig

* fix tests after excplicit header alloc change

* fix examples

* run go fix

* expose rawsock as experimental package (will use for external benchmarks)

* remove backoff from form parsing

* @MDr164 suggestions get potential fixes

* apply go fix

* add examples

* add README.md

* fix rawsock tinygo implementation

* apply @MDr164 various fixes

* update documentation on ContentLength methods and fix bug in Form reset on empty body

* fix tests

* add fuzz tests

* run go fix

* io.ErrNoProgress on parsing form spin

* run go fix

* remove backoff assumption from Router

* httphi.Handle rejects unsupported protocols

* go format router.go

* add kvbuffer

* rewrite Cookie with KVBuffer

* mid refactor of KVBuffer into Header

* work on KVBuffer exhausted semantics

* add Go's ServeMux Request.PathValue access semantics to Exchange, Mux and MuxSlice

* add PathValue example

* document all the things; improve req Query semantics; add Form.EnableBufferGrowth

* unexport kvBuffer

* add Exchange.PathValueAppend

* use stdlib in example instead of rawsock

* remove rawsock from http example

* add darwin arch rawsock

* fix example

* rename Router.TeardownGoroutines to Shutdown matching http.Server.Shutdown

* rename types and identifiers

* @MDr164 Content-Type and Transfer-Encoding bug catches
2026-07-29 20:20:21 -03:00

159 lines
4.5 KiB
Go

package httpraw
import (
"bytes"
)
// Cookie implements cookie key-value parsing. Methods function similarly to eponymous [Header] methods.
// Cookie represents a single-line Cookie header value in a HTTP header, much like the standard library Cookie.
type Cookie struct {
kv kvBuffer
}
// EnableBufferGrowth allows the cookie's buffer to grow past what [Cookie.Reset] was
// handed. See [kvBuffer.EnableBufferGrowth].
func (c *Cookie) EnableBufferGrowth(enableBufferGrowth bool) {
c.kv.EnableBufferGrowth(enableBufferGrowth)
}
// Reset functions very similarly to [Header.Reset]. Can be used for in-place cookie parsing.
func (c *Cookie) Reset(buf []byte, capKV int) { c.kv.Reset(buf, capKV) }
func (c *Cookie) valid() bool {
return len(c.kv.kvs) > 0 && c.kv.kvs[0].key.len > 0
}
// Name returns the first cookie key which is commonly referred to as the cookie's name. Returns nil if not found.
func (c *Cookie) Name() []byte {
if !c.valid() {
return nil
}
return c.kv.AtKey(0)
}
// Value returns the first cookie value associated with the name. Returns nil if not found.
func (c *Cookie) Value() []byte {
if !c.valid() {
return nil
}
return c.kv.AtValue(0)
}
// ParseBytes copies the argument bytes to the Cookie's underlying buffer and parses the cookie.
func (c *Cookie) ParseBytes(cookie []byte) error {
c.Reset(nil, 0)
c.kv.buf = append(c.kv.buf[:0], cookie...)
return c.Parse()
}
// CopyFrom makes a copy of the argument cookie to the receiver dst argument. No memory is shared between cookies.
func (dst *Cookie) CopyFrom(c Cookie) { dst.kv.CopyFrom(&c.kv) }
// Parse parses the cookie's buffer in place.
func (c *Cookie) Parse() error {
if c.kv.Len() > 0 {
return errCookiesParsed
}
off := 0
for {
k, v, n := parseCookie(c.kv.buf[off:])
if n == 0 {
break
}
if !c.kv.setInternal(k, v) {
return ErrBufferExhausted
}
off += n
}
if c.kv.Len() == 0 {
return errNoCookies
}
return nil
}
// ForEach iterates over the cookie's key-value pairs, stopping on the first
// error returned by cb and returning it.
func (c *Cookie) ForEach(cb func(key, value []byte) bool) {
c.kv.ForEach(cb)
}
// Get gets a cookie's value from its key. Use HasValueOrKey to check if a key or single-valued cookie is present in the cookie.
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 {
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..
//
// Cookie: <cookie>\r\n
func parseCookie(cookie []byte) (key, value []byte, cookieEnd int) {
if len(cookie) == 0 {
return nil, nil, 0
}
valueEnd := bytes.IndexByte(cookie, ';')
if valueEnd < 0 { // Ouch this `if` looks like it kills CPU pipepline.
valueEnd = len(cookie)
cookieEnd = len(cookie)
} else {
cookieEnd = valueEnd + 1
}
eqIdx := bytes.IndexByte(cookie[:valueEnd], '=')
key = cookie[:0]
if eqIdx > 0 {
key = trimCookie(cookie[:eqIdx], false)
}
value = trimCookie(cookie[eqIdx+1:valueEnd], true)
return key, value, cookieEnd
}
func trimCookie(src []byte, trimQuotes bool) []byte {
for len(src) > 0 && src[0] == ' ' {
src = src[1:] // skip leading whitespace.
}
for len(src) > 0 && src[len(src)-1] == ' ' {
src = src[:len(src)-1] // skip trailing whitespace
}
if trimQuotes {
if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
src = src[1 : len(src)-1] // Trim leading+trailing quotes.
}
}
return src
}
// String returns the string representation of the cookie value, much like the standard library http.Cookie.String method.
func (c *Cookie) String() string {
buf := c.AppendKeyValues(nil)
return b2s(buf)
}
// AppendKeyValues appends the HTTP header value of the cookie expected after the "Cookie:" string. Does not include trailing \r\n's.
func (c *Cookie) AppendKeyValues(dst []byte) []byte {
nc := c.kv.Len()
for i := range nc {
k, v := c.kv.At(i)
if len(k) != 0 {
dst = append(dst, k...)
dst = append(dst, '=')
}
dst = append(dst, v...)
if i+1 < nc {
dst = append(dst, ';', ' ')
}
}
return dst
}