carlos / todomvc-bakeoff Public
fix(rust): read the request body before answering
A 403 or unknown-route 404 sent before the POST body was read raced Carloku's edge and came back as a 502 about a third of the time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+20
−6
apps/rust/src/app.rs+20 −6
diff --git a/apps/rust/src/app.rs b/apps/rust/src/app.rs| index ef3b4f8..f4a4d66 100644 |
| --- a/apps/rust/src/app.rs |
| +++ b/apps/rust/src/app.rs |
| @@ -58,18 +58,32 @@ pub fn router(state: AppState) -> Router { |
| .with_state(state) |
| } |
| -/// Refuses a POST whose `Origin` is present and isn't ours. No `Origin` |
| -/// header is allowed: the contract has no CSRF token, and SameSite=Lax |
| -/// already keeps the cookie off cross-site POSTs. |
| +/// The largest request body we read: a 10,000-character title is ~90 KB |
| +/// form-encoded at worst. |
| +const BODY_LIMIT: usize = 1 << 20; |
| + |
| +/// Reads the whole request body before anything answers, then refuses a |
| +/// POST whose `Origin` is present and isn't ours. |
| +/// |
| +/// Reading first matters behind Carloku's edge: a response sent while the |
| +/// body is still unread (a 403 here, a 404 for an unknown route) makes |
| +/// hyper drop the connection, and the edge reports that as a 502. |
| +/// |
| +/// No `Origin` header is allowed: the contract has no CSRF token, and |
| +/// SameSite=Lax already keeps the cookie off cross-site POSTs. |
| async fn same_origin(State(s): State<AppState>, req: Request, next: Next) -> Response { |
| - if req.method() == Method::POST { |
| - if let Some(origin) = req.headers().get(header::ORIGIN) { |
| + let (parts, body) = req.into_parts(); |
| + let Ok(bytes) = axum::body::to_bytes(body, BODY_LIMIT).await else { |
| + return StatusCode::PAYLOAD_TOO_LARGE.into_response(); |
| + }; |
| + if parts.method == Method::POST { |
| + if let Some(origin) = parts.headers.get(header::ORIGIN) { |
| if origin.as_bytes() != s.origin.as_bytes() { |
| return StatusCode::FORBIDDEN.into_response(); |
| } |
| } |
| } |
| - next.run(req).await |
| + next.run(Request::from_parts(parts, axum::body::Body::from(bytes))).await |
| } |
| // ---- the visitor's list ---------------------------------------------- |