Skip to content

fix(web): accept empty chunked API POST requests - #725

Merged
Ark0N merged 1 commit into
Ark0N:masterfrom
kdxcxs:fix/empty-api-post-body
Oct 28, 2026
Merged

Ark0N merged 1 commit into
Ark0N:masterfrom
kdxcxs:fix/empty-api-post-body

Conversation

@kdxcxs

@kdxcxs kdxcxs commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Bodyless POST requests sent through Cloudflare Tunnel can reach Codeman as HTTP/1.1 chunked requests with no Content-Type and zero payload bytes. Fastify rejects these with 415 FST_ERR_CTP_INVALID_MEDIA_TYPE before the route handler runs, breaking actions such as opening saved web previews.

Register a shared fallback parser that accepts an untyped API POST only after confirming its stream is empty. It preserves request.body === undefined and the original headers. Nonempty untyped streams are rejected on the first byte without buffering the remaining body.

Existing JSON, text and multipart parsers retain precedence. Required-body validation, authentication, unknown-route handling and the webview proxy’s encapsulated stream parser remain in effect.

Validation:

  • Added 27 regression tests covering parser boundaries, raw HTTP/1.1 framing, client disconnects, consecutive requests and real WebServer routing, including API aliases and base paths.
  • All six static checks passed; the separate xterm package suite passed 261 tests.
  • Reproduced the triggering request framing through a real Cloudflare Access + Tunnel hostname. Verified the final implementation locally with live API requests and desktop/mobile-viewport Playwright preview checks.

Handle untyped zero-byte POST streams at the shared API parser boundary.
Keep request.body undefined and preserve existing typed parsers, 404
fallbacks, authentication and webview proxy stream handling.

Reject nonempty untyped streams on the first byte, and cover parser
boundaries, raw HTTP framing and real WebServer routing with regressions.
@Ark0N

Ark0N commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Thank you @kdxcxs, and welcome! This fixes bodyless POSTs that arrive through Cloudflare Tunnel as empty chunked requests with no Content-Type: they now reach their route with no body, exactly as a Content-Length: 0 request does, instead of being refused 415 by Fastify before the handler runs.

The scoping is right. Go's HTTP client (which cloudflared uses) frames an empty body of unknown length as chunked for POST, PUT and PATCH and drops it for DELETE, and every PUT/PATCH in the frontend carries a JSON body, so POST under /api/ is the whole failure. The tests are thorough: the real-server one fails on master with the exact 415 and passes here, and a live run against a booted server showed the same.

One thing I will touch at merge time: src/web/request-body-parser.ts:1 gets an @fileoverview block (the repo uses that tag on every module for discovery) noting why it exists, why it is POST + /api/ only, and that it owns the root '*' content-type slot. Nothing for you to change; this will go in with that small addition.

@Ark0N
Ark0N merged commit b0487cc into Ark0N:master Oct 28, 2026
Ark0N pushed a commit that referenced this pull request Oct 28, 2026
Add the @fileoverview the repo carries on every module: why the parser
exists, why it is POST + /api/ only, and that it owns the root '*'
content-type slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Ark0N

Ark0N commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Merged in 1.47.0 (https://github.com/Ark0N/Codeman/releases/tag/codeman%401.47.0). Thank you for tracing this through a real Access + Tunnel hostname, and for the thorough tests. At merge I added the @fileoverview block to src/web/request-body-parser.ts (ad2f06c): why it exists, why it is POST + /api/ only, and that it owns the root '*' slot (Fastify refuses a second one at boot). The release review went through the CSRF, hook, multipart, web-tab proxy, /api/v1 and base-path cases and found nothing to change, and on a built server an empty chunked POST with no Content-Type now reaches its route while a non-empty one is still a 415.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants