mirror of
https://github.com/soypat/lneto.git
synced 2026-08-16 12:53:26 +00:00
5c54030f19
* 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
158 lines
4.2 KiB
Go
158 lines
4.2 KiB
Go
package httpraw
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// render joins a form's pairs as "key=value", a valueless key as "key".
|
|
func render(f *Form) string {
|
|
var sb strings.Builder
|
|
for i := range f.Len() {
|
|
key, value := f.Pair(i)
|
|
if i > 0 {
|
|
sb.WriteByte('|')
|
|
}
|
|
sb.Write(key)
|
|
if value != nil {
|
|
sb.WriteByte('=')
|
|
sb.Write(value)
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
func TestFormParse(t *testing.T) {
|
|
for _, test := range []struct {
|
|
body string
|
|
want string
|
|
}{
|
|
{body: "", want: ""},
|
|
{body: "q=go", want: "q=go"},
|
|
{body: "name=Jos%C3%A9+P%C3%A9rez&msg=hi+there&ok=on", want: "name=Jos%C3%A9+P%C3%A9rez|msg=hi+there|ok=on"},
|
|
{body: "ok", want: "ok"}, // Flag: no '=' at all.
|
|
{body: "ok=", want: "ok="}, // Present but empty.
|
|
{body: "&&q=go&", want: "q=go"}, // Empty sequences skipped.
|
|
{body: "tag=a&tag=b", want: "tag=a|tag=b"}, // Duplicates kept in order.
|
|
{body: "=v", want: "=v"}, // Empty name kept.
|
|
{body: "a=b=c", want: "a=b=c"}, // Only the first '=' splits.
|
|
} {
|
|
var f Form
|
|
if err := f.ParseBytes([]byte(test.body)); err != nil {
|
|
t.Fatalf("%q: %s", test.body, err)
|
|
}
|
|
if got := render(&f); got != test.want {
|
|
t.Errorf("%q: want %q, got %q", test.body, test.want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Decode rewrites keys and values in place: percent escapes and '+' as space.
|
|
func TestFormDecode(t *testing.T) {
|
|
var f Form
|
|
const body = "name=Jos%C3%A9+P%C3%A9rez&a%20b=c%2Bd&ok"
|
|
if err := f.ParseBytes([]byte(body)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := f.Decode(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const want = "name=José Pérez|a b=c+d|ok"
|
|
if got := render(&f); got != want {
|
|
t.Errorf("want %q, got %q", want, got)
|
|
}
|
|
if got := string(f.Get("a b")); got != "c+d" {
|
|
t.Errorf("want decoded key lookup %q, got %q", "c+d", got)
|
|
}
|
|
}
|
|
|
|
// 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"} {
|
|
var f Form
|
|
if err := f.ParseBytes([]byte(body)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := f.Decode(); err == nil {
|
|
t.Errorf("%q: want decode error, got nil", body)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormGetHas(t *testing.T) {
|
|
var f Form
|
|
if err := f.ParseBytes([]byte("tag=a&tag=b&ok&empty=")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := string(f.Get("tag")); got != "a" {
|
|
t.Errorf("want first value %q, got %q", "a", got)
|
|
}
|
|
if got := f.Get("ok"); got != nil {
|
|
t.Errorf("want nil value for valueless key, got %q", got)
|
|
}
|
|
if got := f.Get("nope"); got != nil {
|
|
t.Errorf("want nil for absent key, got %q", got)
|
|
}
|
|
if v := f.Get("empty"); v == nil || len(v) != 0 {
|
|
t.Errorf("want present empty value, got %v", v)
|
|
}
|
|
for _, key := range []string{"tag", "ok", "empty"} {
|
|
if !f.Has(key) {
|
|
t.Errorf("want Has(%q) true", key)
|
|
}
|
|
}
|
|
if f.Has("nope") {
|
|
t.Error("want Has(nope) false")
|
|
}
|
|
}
|
|
|
|
func TestFormAppendKeyValues(t *testing.T) {
|
|
const body = "name=go&ok&empty=&tag=a&tag=b"
|
|
var f Form
|
|
if err := f.ParseBytes([]byte(body)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := string(f.AppendKeyValues(nil)); got != body {
|
|
t.Errorf("want round trip %q, got %q", body, got)
|
|
}
|
|
}
|
|
|
|
// Parsing into a reused Form must not allocate: the pair storage is reused.
|
|
func TestFormParseReuseNoAlloc(t *testing.T) {
|
|
body := []byte("name=go&tag=a&tag=b&ok")
|
|
var f Form
|
|
if err := f.ParseBytes(body); err != nil { // Warm up the pair storage.
|
|
t.Fatal(err)
|
|
}
|
|
allocs := testing.AllocsPerRun(100, func() {
|
|
f.Reset(body, 0)
|
|
f.Parse()
|
|
})
|
|
if allocs != 0 {
|
|
t.Errorf("reused Form allocated %v times, want 0", allocs)
|
|
}
|
|
}
|