Skip to content

fix(ios): review adjustments for #419#441

Merged
dcalhoun merged 5 commits intofeat/improve-media-uploads-and-errorsfrom
jkmassel/review-pr-419-v2
Apr 9, 2026
Merged

fix(ios): review adjustments for #419#441
dcalhoun merged 5 commits intofeat/improve-media-uploads-and-errorsfrom
jkmassel/review-pr-419-v2

Conversation

@jkmassel
Copy link
Copy Markdown
Contributor

@jkmassel jkmassel commented Apr 8, 2026

Suggested changes from reviewing #419.

Changes

1. Use Int64 for HTTPRequestParser.bytesWritten

bytesWritten was Int while expectedContentLength and maxBodySize are already Int64. This is consistent on all Apple platforms today (where Int is 64-bit), but using Int64 explicitly matches the convention used throughout the parser and avoids unnecessary Int64(...) 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 to MediaUploadServer and verifies both the 413 status code and the Access-Control-Allow-Origin header.

To support this, MediaUploadServer.start() now exposes a maxRequestBodySize parameter (defaulting to the existing 4 GB), so the test can use a small limit without sending gigabytes of data.

Related issues

jkmassel added 2 commits April 8, 2026 13:01
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.
@github-actions github-actions bot added the [Type] Bug An existing feature does not function as intended label Apr 8, 2026
jkmassel added 3 commits April 8, 2026 13:16
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.
@jkmassel jkmassel force-pushed the jkmassel/review-pr-419-v2 branch from 1d31c05 to 8d0e3af Compare April 8, 2026 20:35
@jkmassel jkmassel requested review from dcalhoun April 8, 2026 21:22
@jkmassel jkmassel marked this pull request as ready for review April 8, 2026 21:22
Copy link
Copy Markdown
Member

@dcalhoun dcalhoun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes tested well for me—both successful uploads and 413 error messages. Thank you!

@dcalhoun dcalhoun merged commit 691bb27 into feat/improve-media-uploads-and-errors Apr 9, 2026
11 checks passed
@dcalhoun dcalhoun deleted the jkmassel/review-pr-419-v2 branch April 9, 2026 19:16
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants