feat(06-03): add path-scoped CORS and per-route body limits
- CORS matches Laravel path globs (api/* includes nested segments); unlisted paths get no headers - Non-raw routes wrap http.MaxBytesReader from http.body_limits.default_bytes; body.limit:N overrides innermost - Raw routes stay uncapped at this layer
This commit is contained in:
127
surf/bodylimit_test.go
Normal file
127
surf/bodylimit_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package surf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/summercms/backpack"
|
||||
"git.golem15.com/golem15/summercms/pact"
|
||||
"git.golem15.com/golem15/summercms/party"
|
||||
)
|
||||
|
||||
func TestBodyLimitDefaultRejectsOversizedBody(t *testing.T) {
|
||||
cfg := writeHTTPConfig(t, `
|
||||
body_limits:
|
||||
default_bytes: 32
|
||||
upload_bytes: 64
|
||||
`)
|
||||
p := bodyEchoPlugin{id: "golem15.demo"}
|
||||
h, err := Assemble(backpack.New(cfg), []party.Plugin{p})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/echo", strings.NewReader(strings.Repeat("a", 64)))
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusRequestEntityTooLarge {
|
||||
t.Fatalf("status = %d body=%q (want 413 from MaxBytesReader)", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodPost, "/echo", strings.NewReader("ok"))
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("small body status = %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBodyLimitRawExempt(t *testing.T) {
|
||||
cfg := writeHTTPConfig(t, `
|
||||
body_limits:
|
||||
default_bytes: 8
|
||||
upload_bytes: 8
|
||||
`)
|
||||
p := bodyEchoPlugin{id: "golem15.demo", raw: true}
|
||||
h, err := Assemble(backpack.New(cfg), []party.Plugin{p})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/echo", strings.NewReader(strings.Repeat("a", 64)))
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("raw route should not apply default body limit, status = %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBodyLimitOverride(t *testing.T) {
|
||||
cfg := writeHTTPConfig(t, `
|
||||
body_limits:
|
||||
default_bytes: 8
|
||||
upload_bytes: 64
|
||||
`)
|
||||
p := bodyEchoPlugin{id: "golem15.demo", extra: []string{"body.limit:64"}}
|
||||
h, err := Assemble(backpack.New(cfg), []party.Plugin{p})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/echo", strings.NewReader(strings.Repeat("a", 32)))
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("override should allow 32 bytes, status = %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBodyLimitLoadsConfigValues(t *testing.T) {
|
||||
cfg := writeHTTPConfig(t, `
|
||||
body_limits:
|
||||
default_bytes: 8388608
|
||||
upload_bytes: 2097152
|
||||
`)
|
||||
r, err := BuildRouter(backpack.New(cfg), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.defaultBytes != 8388608 || r.uploadBytes != 2097152 {
|
||||
t.Fatalf("limits = %d / %d", r.defaultBytes, r.uploadBytes)
|
||||
}
|
||||
}
|
||||
|
||||
type bodyEchoPlugin struct {
|
||||
id string
|
||||
raw bool
|
||||
extra []string
|
||||
}
|
||||
|
||||
func (p bodyEchoPlugin) ID() string { return p.id }
|
||||
func (p bodyEchoPlugin) Requires() []string { return nil }
|
||||
func (p bodyEchoPlugin) Register(*backpack.App) error { return nil }
|
||||
func (p bodyEchoPlugin) Boot(*backpack.App) error { return nil }
|
||||
func (p bodyEchoPlugin) Routes(r pact.Router) error {
|
||||
h := func(w http.ResponseWriter, req *http.Request) {
|
||||
_, err := io.Copy(io.Discard, req.Body)
|
||||
if err != nil {
|
||||
var maxErr *http.MaxBytesError
|
||||
if errors.As(err, &maxErr) {
|
||||
w.WriteHeader(http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
open := r.Group
|
||||
if p.raw {
|
||||
open = r.GroupRaw
|
||||
}
|
||||
open("/", Use(p.extra...), func(g pact.Router) {
|
||||
g.Post("/echo", h)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user