fix(ios): review adjustments for #419#441
Merged
dcalhoun merged 5 commits intofeat/improve-media-uploads-and-errorsfrom Apr 9, 2026
Merged
fix(ios): review adjustments for #419#441dcalhoun merged 5 commits intofeat/improve-media-uploads-and-errorsfrom
dcalhoun merged 5 commits intofeat/improve-media-uploads-and-errorsfrom
Conversation
Change `bytesWritten` from `Int` to `Int64` for consistency with `expectedContentLength` and `maxBodySize`, which are already `Int64`.
Expose maxRequestBodySize on MediaUploadServer.start() and add an integration test that sends an oversized request and verifies the response includes both a 413 status and CORS headers.
The multipart overhead (~191 bytes) plus auth headers meant the previous limit of 100 bytes caused the connection to reset before the drain could complete. Use 1024 bytes with a 2048-byte payload so the parser can drain the body and deliver the 413 response.
URLSession treats a server response during upload as a connection error (NSURLErrorNetworkConnectionLost). Use a raw NWConnection to send the request and read the response directly, which correctly receives the 413 with CORS headers.
When the entire HTTP request (headers + body) arrives in a single read, the parser enters DRAINING but never completes because the body bytes were already counted in bytesWritten. Subsequent reads find no more data, causing a timeout. Check the drain condition immediately when entering the draining state, transitioning to complete if all body bytes have already been received. Fixes both iOS and Android parsers.
1d31c05 to
8d0e3af
Compare
dcalhoun
approved these changes
Apr 9, 2026
Member
dcalhoun
left a comment
There was a problem hiding this comment.
Changes tested well for me—both successful uploads and 413 error messages. Thank you!
691bb27
into
feat/improve-media-uploads-and-errors
11 checks passed
dcalhoun
added a commit
that referenced
this pull request
Apr 9, 2026
* fix: Avoid "native" term in user-facing errors messages The term "native" is likely unfamiliar to users, provides no tangible value, and may cause confusion. * fix: drain oversized request body before sending 413 response When Content-Length exceeds maxBodySize, the server now reads and discards the full request body before responding with 413. This ensures the client (WebView fetch) receives the error response cleanly instead of a connection reset (RFC 9110 §15.5.14). Adds a `.draining` parser state that tracks consumed bytes without buffering them, keeping memory and disk usage at zero for rejected uploads. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: include CORS headers on server-generated error responses Add an errorResponseHeaders parameter to HTTPServer (iOS) and HttpServer (Android) so that callers can specify headers to include on all server-generated error responses (413, 407, 408, etc.). MediaUploadServer passes its CORS headers through this parameter so the browser does not block error responses due to missing Access-Control-Allow-Origin. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert "fix: include CORS headers on server-generated error responses" This reverts commit 0b6c67b. * fix: route 413 response through handler for CORS headers Instead of the HTTP server library building 413 responses directly (which lacked CORS headers), payloadTooLarge is now treated as a non-fatal parse error. parseRequest() returns the parsed headers as a partial request and exposes the error via a new parseError property. The server passes it to the handler via a serverError field on the request, letting MediaUploadServer build the response with CORS headers — consistent with how OPTIONS preflight is handled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf(ios): stream multipart upload body from disk instead of memory Replace Data(contentsOf:) + httpBody with a streaming InputStream via httpBodyStream in DefaultMediaUploader. The multipart body (preamble, file content, epilogue) is written through a bound stream pair on a background thread, keeping peak memory at ~65 KB regardless of file size — down from ~2x file size previously. Android already streams via OkHttp's file.asRequestBody() and needs no changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf: passthrough upload when delegate does not modify the file When the delegate's processFile returns the original file unchanged (e.g., GIFs, non-images, files already within size limits), the original request body is forwarded directly to WordPress — skipping multipart re-encoding and the extra file read. Detection: after processFile, compare the returned URL/File to the input. If unchanged and uploadFile returns nil, signal passthrough back to handleUpload which streams the original body via passthroughUpload(). Also extracts shared response parsing into performUpload() on both platforms to avoid duplication between upload() and passthroughUpload(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(android): fix Detekt lint violations Extract readUntil() helper from HttpServer.handleRequest() to reduce nesting depth and throw count. Extract performPassthroughUpload() from MediaUploadServer.processAndRespond() to consolidate throw statements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ios): prevent integer overflow in drain mode byte tracking Cast `bytesWritten` and `offset` to Int64 individually before subtracting, avoiding a potential Int overflow when the difference is computed before the widening cast. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(ios): replace deprecated URL.path with path(percentEncoded:) URL.path is deprecated on iOS 16+. Use path(percentEncoded: false) to get the file system path without percent-encoding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(ios): localize "file too large" error via EditorLocalization Add a `fileTooLarge` case to `EditorLocalizableString` so host apps can provide translations for the 413 error message. The hardcoded string in MediaUploadServer now reads from the localization system. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert "refactor(ios): localize "file too large" error via EditorLocalization" This reverts commit 71440d9. * fix(ios): review adjustments for #419 (#441) * fix(ios): use Int64 for HTTPRequestParser.bytesWritten Change `bytesWritten` from `Int` to `Int64` for consistency with `expectedContentLength` and `maxBodySize`, which are already `Int64`. * test(ios): add end-to-end test for 413 response with CORS headers Expose maxRequestBodySize on MediaUploadServer.start() and add an integration test that sends an oversized request and verifies the response includes both a 413 status and CORS headers. * fix(test): increase maxRequestBodySize for 413 test The multipart overhead (~191 bytes) plus auth headers meant the previous limit of 100 bytes caused the connection to reset before the drain could complete. Use 1024 bytes with a 2048-byte payload so the parser can drain the body and deliver the 413 response. * fix(test): use raw TCP for 413 test to avoid URLSession connection reset URLSession treats a server response during upload as a connection error (NSURLErrorNetworkConnectionLost). Use a raw NWConnection to send the request and read the response directly, which correctly receives the 413 with CORS headers. * fix: complete drain immediately when body arrives with headers When the entire HTTP request (headers + body) arrives in a single read, the parser enters DRAINING but never completes because the body bytes were already counted in bytesWritten. Subsequent reads find no more data, causing a timeout. Check the drain condition immediately when entering the draining state, transitioning to complete if all body bytes have already been received. Fixes both iOS and Android parsers. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Jeremy Massel <1123407+jkmassel@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suggested changes from reviewing #419.
Changes
1. Use
Int64forHTTPRequestParser.bytesWrittenbytesWrittenwasIntwhileexpectedContentLengthandmaxBodySizeare alreadyInt64. This is consistent on all Apple platforms today (whereIntis 64-bit), but usingInt64explicitly matches the convention used throughout the parser and avoids unnecessaryInt64(...)casts at comparison sites.2. Add end-to-end test for 413 + CORS headers
The core motivation for the drain+handler routing in #419 is that 413 responses include CORS headers so the WebView's
fetch()can read them. This adds an integration test that sends an oversized request toMediaUploadServerand verifies both the 413 status code and theAccess-Control-Allow-Originheader.To support this,
MediaUploadServer.start()now exposes amaxRequestBodySizeparameter (defaulting to the existing 4 GB), so the test can use a small limit without sending gigabytes of data.Related issues
OutputStream.writereturn value 0 treated as fatal in bound stream writersMediaUploadServertests silently skip when server can't bind