mirror of
https://github.com/soypat/lneto.git
synced 2026-08-16 04:43:29 +00:00
155 lines
4.8 KiB
Go
155 lines
4.8 KiB
Go
package httpraw
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
)
|
|
|
|
// Form holds "application/x-www-form-urlencoded" key-value pairs, the encoding
|
|
// HTML forms use for POST bodies and query strings alike. Methods function
|
|
// similarly to eponymous [Cookie] methods.
|
|
//
|
|
// Pairs are stored as they appear on the wire, percent-encoded and with '+'
|
|
// 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 {
|
|
kv kvBuffer
|
|
}
|
|
|
|
// EnableBufferGrowth allows the form's buffer to grow past what [Form.Reset] was
|
|
// handed. See [kvBuffer.EnableBufferGrowth].
|
|
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, capKV int) {
|
|
f.kv.Reset(buf, capKV)
|
|
}
|
|
|
|
// ReadFromBytes appends buf to the underlying buffer, accumulating data to parse. Returns ErrBufferExhausted when buf does not fit and growth is disabled.
|
|
func (f *Form) ReadFromBytes(b []byte) error { return f.kv.ReadFromBytes(b) }
|
|
|
|
// ReadLimited appends at most limit bytes read from r to the underlying buffer. A read returning data alongside io.EOF reports a nil error, later ones io.EOF.
|
|
func (f *Form) ReadLimited(r io.Reader, limit int) (int, error) { return f.kv.ReadLimited(r, limit) }
|
|
|
|
// ParseBytes copies the argument bytes to the Form's underlying buffer and parses them.
|
|
func (f *Form) ParseBytes(b []byte) error {
|
|
f.Reset(nil, 0)
|
|
if len(b) == 0 {
|
|
return nil // An empty body is an empty form, not a failure to read one.
|
|
}
|
|
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.kv.discardKVs()
|
|
key, value, rest := NextQueryPair(f.kv.buf)
|
|
for key != nil {
|
|
if !f.kv.setInternal(key, value) {
|
|
return ErrBufferExhausted
|
|
}
|
|
key, value, rest = NextQueryPair(rest)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Decode rewrites every key and value in place, replacing percent escapes and
|
|
// '+' 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.
|
|
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
|
|
} else if len(v) == 0 {
|
|
if nk != len(k) {
|
|
f.kv.setAt(i, k[:nk], v) // k[:nk]: the decoded key is shorter.
|
|
}
|
|
continue
|
|
}
|
|
nv, err := CopyDecodedPercentURL(v, v, plusAsSpace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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 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) {
|
|
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 { return f.kv.Get(key) }
|
|
|
|
// Has returns true if key is present, with or without a value.
|
|
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 {
|
|
nkv := f.kv.Len()
|
|
for i := range nkv {
|
|
key, value := f.Pair(i)
|
|
if i > 0 {
|
|
dst = append(dst, '&')
|
|
}
|
|
dst = append(dst, key...)
|
|
if value != nil {
|
|
dst = append(dst, '=')
|
|
dst = append(dst, value...)
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// NextQueryPair splits the leading key-value pair off a query string and returns
|
|
// what remains of it. Loop until rawkey is nil:
|
|
//
|
|
// rawkey, rawval, rest := httpraw.NextQueryPair(h.RequestQuery())
|
|
// for rawkey != nil {
|
|
// // use rawkey, rawval.
|
|
// rawkey, rawval, rest = httpraw.NextQueryPair(rest)
|
|
// }
|
|
//
|
|
// A pair with no '=' yields a nil rawval, i.e: "debug" in "?debug&q=go", which
|
|
// distinguishes it from "?debug=" where the value is present and empty. Empty
|
|
// sequences are skipped, so "?&&q=go&" yields a single pair. Only '&' separates
|
|
// pairs and only the first '=' splits a pair.
|
|
func NextQueryPair(query []byte) (rawkey, rawval, rest []byte) {
|
|
for len(query) > 0 {
|
|
pair := query
|
|
amp := bytes.IndexByte(query, '&')
|
|
if amp >= 0 {
|
|
pair, query = query[:amp], query[amp+1:]
|
|
} else {
|
|
query = nil
|
|
}
|
|
if len(pair) == 0 {
|
|
continue // Empty sequence, see WHATWG URL urlencoded parsing.
|
|
}
|
|
if before, after, ok := bytes.Cut(pair, []byte{'='}); ok {
|
|
return before, after, query
|
|
}
|
|
return pair, nil, query
|
|
}
|
|
return nil, nil, nil
|
|
}
|