web.guard
web 守卫面:CSRF 双提交中间件 + 令牌桶限流中间件(Plan 2c Task 2; http/frm/csrf + http/frm/limit 薄收编,调用不复制;纪律 §14-1;§8-A5 时钟注入)
use 面:web.core(Resp/Req/取头)+ web.router(测试装配链)+ std.str(index_of/trim/ starts_with/contains)+ http.frm.csrf + http.frm.limit(两独立叶,零共享; std.crypto 不直 use —— csrf 单叶口径,mw.ct auth 同款,消费方再入即同叶双径 E5020)。
== csrf_mw:无状态双提交(HMAC 签名硬化,frm/csrf 全语义面) == 安全方法(GET/HEAD/OPTIONS 及一切非写法)→ next 直通;写方法(POST/PUT/DELETE/ PATCH)→ cookie 端(csrf_cookie_get 读 Cookie 头)vs 提交端(csrf_form_get 读 form-urlencoded 体,字段名 csrf)→ csrf_verify(双提交等值 + nonce 域 + hex 形 + HMAC 重算)→ 不合 403(体 "csrf rejected",frm csrf_resp_403 同串;wire 串形态 不入 Resp 世界,以 status() 值构造 —— 语义随 frm,形态随框架)。通过面(安全+ 已验写)响应侧恒发新 csrf cookie(csrf_issue 签发 + csrf_set_cookie 行剥壳取值, SameSite=Lax 随 frm;HttpOnly 不设 —— 双提交 JS 可读,frm 头注权衡)。 nonce 熵源 = 闭包持原子序号十进制补零至 16(frm 头注「uuid_v4/random 序号/会话 指纹」取「序号」支;抗伪造在 HMAC 签名面,不依赖 nonce 保密;序号确定性 → 测试 面可断言;真熵装配归 composition,志向)。表单解析双面登记:csrf_form_get 零解码 (frm 口径)—— 签发域 [A-Za-z0-9.] 无 %/+ 出现,与 web.core form() 的 pdec 在 令牌域恒等,两侧取值等价。
== rate_limit_mw:令牌桶 per-key + max-in-flight 闸(frm/limit 全语义面) == 判定序随 frm:in-flight 门先于令牌桶(shed 优先 —— 满载不再耗桶令牌)。桶补充/ 消费/Retry-After 全走 lim_take(cap 封顶、整步前进、ceil 算 retry);装配期 lim_cfg 校验,参数非正 panic(失败在启动,不在半夜 —— timeout_ms 先例)。 超限 → 429 + Retry-After(lt_retry;绑定口径);shed → 503 + Retry-After 1 (frm lim_resp_503 常量礼让值;wire 串同不入 Resp 世界,以 status() 值构造)。
—— v1 结构性边界(如实登记,非临时态)——
① per-key 口径:Req 无 remote 字段(v1)→ 键 = X-Forwarded-For 首段(trim), 缺省 "local"。XFF 客户端可伪造 → 伪造键各得新桶,per-key 限流可被绕过且键表 增长攻击面由此而生 —— 真键面挂 Req.remote/serve 取 peer 字段落位(§13 候选/ P8),本件头注即闸。 ② 键表容量:maxk()=1024,线性扫描(v1)。满表 miss 并入 "local" 共享桶(降级 为全局限流);表满且 "local" 未入表(全 XFF 流量)→ 溢出键不记放行(fail-open, bkt_plan 纯面测试钉死)。原位压缩/淘汰归志向。 ③ 键表载体 = Mutex[List[BktEnt]](键唯一不变式:命中原位 Index 替换,miss 才 push)—— 绑定速记 Mutex[Map[Str,LimBkt]] 的证据化调整:std/map put 函数式 (值语义)+ with_mut 闭包内整体重绑不写回 Mutex(web_todo README 在册实证), 函数式 Map 无写回位;List 原位 Index 替换写回经探针实证(整元素替换写回 9/ I64 位写回 5;复合 l[i].f 写回被拒「assign target:Member」—— 故整元素替换形)。 ④ max-in-flight:load→acq→store 三步非原子(v1 阻塞串行档 §8.2 无并发窗口; 并发波落地需 CAS/fetch_add 形信号量,志向);释放点 = next 返回即 rel(链形 fn 无 post-return 钩子,frm/limit 头注同款登记 —— 较「响应写出后」略早,诚实口径)。 ⑤ csrf 令牌不绑会话(frm v1 无会话绑定)+ SameSite=Lax 为跨站主防;子域 cookie 注入面 = 双提交经典弱点,frm 头注已在册,随。
接线说明:本件纯判定面(无二进制 body/载体问题),可正常挂链上线 —— compress_mw 的「勿裸上线闸」(2c Task 1 载体边界)不适用本件。 E3070 口径:两中间件闭包体全 let/return 形(无 var 声明、无 Ident 赋值);状态 变更全经原子方法(store/fetch_add)、Mutex.with_mut 闭包内 Index 替换/push 方法位;可变逻辑全提顶层 fn(E3070 全文件 var 名交集纪律,mw.ct 先例同款)。
pub struct
struct Pair
| Field | Type |
|---|---|
k |
Str |
v |
Str |
struct Resp
| Field | Type |
|---|---|
status |
I32 |
headers |
List[Pair] |
body |
Str |
struct JObj
| Field | Type |
|---|---|
parts |
List[Str] |
struct Req[S]
| Field | Type |
|---|---|
method |
Str |
path |
Str |
query |
Str |
body |
Str |
headers |
List[Pair] |
cookies |
List[Pair] |
params |
List[Pair] |
session |
List[Pair] |
state |
S |
struct RouteSeg
| Field | Type |
|---|---|
kind |
Str |
text |
Str |
struct Route[S]
| Field | Type |
|---|---|
m |
Str |
segs |
List[RouteSeg] |
chain |
List[fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp] |
h |
fn(&Req[S]) -> Resp |
struct Router[S]
| Field | Type |
|---|---|
state |
S |
routes |
List[Route[S]] |
mws |
List[fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp] |
not_found_h |
List[fn(&Req[S]) -> Resp] |
not_allowed_h |
List[fn(&Req[S]) -> Resp] |
struct Match[S]
| Field | Type |
|---|---|
kind |
I32 |
route |
Route[S] |
params |
List[Pair] |
splat |
Str |
allow |
Str |
struct CsrfTok
| Field | Type |
|---|---|
ok |
I64 |
why |
I64 |
tok |
Str |
struct CsrfVer
| Field | Type |
|---|---|
rc |
I64 |
why |
I64 |
struct LimCfg
| Field | Type |
|---|---|
ok |
I64 |
err |
I64 |
cap |
I64 |
refil |
I64 |
ivl |
I64 |
maxif |
I64 |
struct LimBkt
| Field | Type |
|---|---|
tks |
I64 |
last |
I64 |
struct LimTake
| Field | Type |
|---|---|
ok |
I64 |
retry |
I64 |
tks |
I64 |
last |
I64 |
struct LimIf
| Field | Type |
|---|---|
ok |
I64 |
n |
I64 |
pub fn
| Signature | Returns | Description |
|---|---|---|
csrf_mw[S](key: Str) |
fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp |
csrf_mw —— CSRF 双提交中间件(§4.5 形态 ③ 带状态工厂:原子序号随闭包) |
rate_limit_mw[S](cap: I64, refil: I64, ivl: I64, maxif: I64, now: fn() -> I64) |
fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp |
rate_limit_mw —— 令牌桶限流中间件(§4.5 形态 ③:键表 + in-flight 计数随闭包; now 注入 §8-A5,真钟归装配 \| \| now_ms() / 1000 形,测试面 fake 前进) |
html(body: Str) |
Resp |
— |
json(body: Str) |
Resp |
— |
text(body: Str) |
Resp |
— |
redirect(loc: Str) |
Resp |
— |
status(n: I32, body: Str) |
Resp |
— |
xml(body: Str) |
Resp |
— |
yaml(body: Str) |
Resp |
— |
err_json(n: I32, msg: Str) |
Resp |
— |
attachment(r: Resp, filename: Str) |
Resp |
— |
with(r: Resp, k: Str, v: Str) |
Resp |
— |
with_status(r: Resp, n: I32) |
Resp |
— |
resp_header(r: Resp, k: Str) |
Str |
— |
resp_body(r: Resp) |
Str |
— |
resp_status(r: Resp) |
I32 |
— |
json_obj() |
JObj |
— |
str(b: JObj, k: Str, v: Str) |
JObj |
— |
i64(b: JObj, k: Str, v: I64) |
JObj |
— |
bool(b: JObj, k: Str, v: Bool) |
JObj |
— |
strs(b: JObj, k: Str, xs: List[Str]) |
JObj |
— |
end(b: JObj) |
Str |
— |
end_status(b: JObj, n: I32) |
Resp |
— |
csv_rows(headers: List[Str], rows: List[List[Str]]) |
Str |
— |
req_of[S](method: Str, path: Str, state: S) |
Req[S] |
— |
req_full[S](method: Str, path: Str, body: Str, hdrs: List[Pair], state: S) |
Req[S] |
— |
req_set_params[S](r: &Req[S], params: List[Pair]) |
Req[S] |
— |
req_set_session[S](r: &Req[S], sess: List[Pair]) |
Req[S] |
— |
pdec(s: Str) |
Str |
— |
param[S](r: &Req[S], name: Str) |
Str |
— |
pair_get(ps: List[Pair], k: Str) |
Str |
— |
param_i64[S](r: &Req[S], name: Str) |
Option[I64] |
— |
param_f64[S](r: &Req[S], name: Str) |
Option[F64] |
— |
query[S](r: &Req[S], name: Str) |
Str |
— |
query_all[S](r: &Req[S], name: Str) |
List[Str] |
— |
form[S](r: &Req[S], name: Str) |
Str |
— |
form_all[S](r: &Req[S], name: Str) |
List[Str] |
— |
header[S](r: &Req[S], name: Str) |
Str |
— |
cookie[S](r: &Req[S], name: Str) |
Str |
— |
session[S](r: &Req[S], name: Str) |
Str |
— |
router[S](state: S) |
Router[S] |
— |
get[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
post[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
put[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
middleware[S](r: Router[S], mw: fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp) |
Router[S] |
— |
mount_at[S](r: Router[S], prefix: Str, sub: Router[S]) |
Router[S] |
— |
has_conflict[S](r: Router[S]) |
Bool |
— |
install[S](r: Router[S], p: fn(Router[S]) -> Router[S]) |
Router[S] |
— |
not_found[S](r: Router[S], h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
method_not_allowed[S](r: Router[S], h: fn(&Req[S]) -> Resp) |
Router[S] |
— |
test_call[S](r: Router[S], method: Str, path: Str, body: Str) |
Resp |
— |
route_match[S](r: Router[S], m: Str, path: Str) |
Match[S] |
— |
dispatch_h[S](r: Router[S], method: Str, path: Str, body: Str, hdrs: List[Pair]) |
Resp |
— |
dispatch[S](r: Router[S], method: Str, path: Str, body: Str) |
Resp |
— |
ct_rc(t: CsrfTok) |
I64 |
— |
ct_why(t: CsrfTok) |
I64 |
— |
ct_tok(t: CsrfTok) |
Str |
— |
csrf_issue(key: Str, nonce: Str) |
CsrfTok |
— |
cv_rc(v: CsrfVer) |
I64 |
— |
cv_why(v: CsrfVer) |
I64 |
— |
csrf_verify(key: Str, cookie_tok: Str, submit_tok: Str) |
CsrfVer |
— |
csrf_cookie_get(cookie_hdr: Str, name: Str) |
Str |
— |
csrf_form_get(body: Str, name: Str) |
Str |
— |
csrf_set_cookie(tok: Str) |
Str |
— |
lim_err_ok() |
I64 |
— |
lim_err_param() |
I64 |
— |
lcfg_ok(c: LimCfg) |
I64 |
— |
lcfg_err(c: LimCfg) |
I64 |
— |
lcfg_cap(c: LimCfg) |
I64 |
— |
lim_cfg(cap: I64, refil: I64, ivl: I64, maxif: I64) |
LimCfg |
— |
lim_bkt(tks: I64, last: I64) |
LimBkt |
— |
lim_bkt_new(cap: I64, now: I64) |
LimBkt |
— |
lt_ok(t: LimTake) |
I64 |
— |
lt_retry(t: LimTake) |
I64 |
— |
lt_tks(t: LimTake) |
I64 |
— |
lt_last(t: LimTake) |
I64 |
— |
lim_take(b: LimBkt, c: LimCfg, now: I64, cost: I64) |
LimTake |
— |
lif_ok(x: LimIf) |
I64 |
— |
lif_n(x: LimIf) |
I64 |
— |
lim_acq(n: I64, maxif: I64) |
LimIf |
— |
lim_rel(n: I64) |
I64 |
— |
lim_ip_key(ip: Str) |
I64 |
— |
lim_resp_503(retry: I64) |
Str |
— |