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

This commit is contained in:
Patricio Whittingslow
2026-07-29 16:26:20 -03:00
parent 546f11e375
commit 6b54f74dd5
9 changed files with 394 additions and 53 deletions
+4 -1
View File
@@ -22,6 +22,9 @@ func (f *Form) Reset(buf []byte, capKV int) {
// 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
@@ -54,7 +57,7 @@ func (f *Form) Decode() error {
return err
} else if len(v) == 0 {
if nk != len(k) {
f.kv.setAt(i, k, v)
f.kv.setAt(i, k[:nk], v) // k[:nk]: the decoded key is shorter.
}
continue
}
+23
View File
@@ -66,6 +66,29 @@ func TestFormDecode(t *testing.T) {
}
}
// Decoding a valueless key that shrinks must rewrite the key without inventing
// a value: the pair has no '=' before Decode and must have none after.
func TestFormDecodeValuelessKeyShrinks(t *testing.T) {
var f Form
const body = "a=1&o%6Bay"
if err := f.ParseBytes([]byte(body)); err != nil {
t.Fatal(err)
}
if err := f.Decode(); err != nil {
t.Fatal(err)
}
const want = "a=1|okay"
if got := render(&f); got != want {
t.Errorf("want %q, got %q", want, got)
}
if _, value := f.Pair(1); value != nil {
t.Errorf("want valueless pair to stay valueless, got value %q", value)
}
if !f.Has("okay") {
t.Error("want decoded valueless key present")
}
}
// A malformed escape must be reported, never silently passed through.
func TestFormDecodeMalformed(t *testing.T) {
for _, body := range []string{"q=%zz", "%zz=v", "q=%4"} {
-38
View File
@@ -241,49 +241,11 @@ func (h *Header) SetInt(key string, value int64, base int) {
// Calling Set mangles the buffer.
func (h *Header) Set(key, value string) (enoughSpace bool) {
return h.hbuf.kv.Set(key, value)
// useKv := h.takeReusableSlot(key)
// if useKv == nil {
// h.hbuf.kv.appendPair(key, value)
// } else {
// useKv.value = h.hbuf.kv.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
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.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 {
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
+4 -2
View File
@@ -225,9 +225,11 @@ func (mb *KVBuffer) At(i int) (key, value []byte) {
}
func (mb *KVBuffer) setAt(i int, k, v []byte) {
mb.flags |= flagMangledBuffer
// Route through slice, not bytes2tok: a nil v is a pair with no '=' and must
// stay absent rather than trip the alias check on a nil pointer.
mb.kvs[i] = argsKV{
key: bytes2tok(mb.buf, k),
value: bytes2tok(mb.buf, v),
key: mb.slice(k),
value: mb.slice(v),
}
}
+4 -4
View File
@@ -12,10 +12,10 @@ var (
errNoProto = errors.New("missing protocol, HTTP/0.9 unsupported")
// ErrNeedMoreData signals a parser was handed an incomplete buffer: append
// more data to it and call again.
ErrNeedMoreData = errors.New("need more data: cannot find trailing lf/delimiter")
errNoBoundary = errors.New("httpraw: multipart boundary not set")
errUnparsed = errors.New("need to finish parsing")
errInvalidName = errors.New("invalid header name")
ErrNeedMoreData = errors.New("need more data: cannot find trailing lf/delimiter")
errNoBoundary = errors.New("httpraw: multipart boundary not set")
errUnparsed = errors.New("need to finish parsing")
errInvalidName = errors.New("invalid header name")
// ErrBufferExhausted signals a buffer with no room left for the data being
// written and no permission to grow, see [KVBuffer.EnableBufferGrowth].
// Enlarging the buffer handed to Reset is the only fix; a server answers it