feat: add cross-platform HTTP/1.1 parser and local proxy server for iOS and Android#367
feat: add cross-platform HTTP/1.1 parser and local proxy server for iOS and Android#367
Conversation
cce3ff6 to
57d1b7c
Compare
29f9c27 to
0a09c12
Compare
Add a hardened, RFC-conformant HTTP/1.1 request parser and local proxy server for iOS. The parser is exposed to JavaScript running in the WebView, so it enforces strict validation per RFC 7230/9110/9112 to prevent request smuggling, header injection, and resource exhaustion. Includes: - HTTPRequestParser: Incremental parser with disk-backed buffering - HTTPRequestSerializer: Stateless header parsing with full RFC validation - HeaderValue: RFC 2045 parameter extraction with quoted string handling - MultipartPart: RFC 7578 multipart/form-data with lazy file-slice refs - RequestBody: In-memory and file-backed storage with InputStream access - HTTPServer: Local server on Network.framework with async handler API, connection limits, read timeouts (408 per RFC 9110 §15.5.9), and constant-time bearer token auth - HTTPResponse: Response serialization with header sanitization, Content-Length always derived from actual body size All size-related types use Int64 to match Kotlin's Long and prevent ambiguity on future platforms. 820+ tests covering RFC 7230, 7578, 8941, 9110, 9112, and 9651. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tures Add a pure-Kotlin implementation of the HTTP/1.1 request parser that mirrors the Swift GutenbergKitHTTP library, enabling HTTP parsing on Android without native dependencies. Includes HeaderValue, HTTPRequestSerializer, HTTPRequestParser (with disk-backed buffering), ParsedHTTPRequest, MultipartPart, RequestBody, and HttpServer with bearer token authentication, constant-time token comparison, status code clamping, and Content-Length always derived from actual body size — matching the Swift server's security model. Both platforms are validated against 101 shared JSON test fixtures in test-fixtures/http/ covering header value extraction, request parsing (basic, error, incremental), multipart parsing (field-based, raw-body, error), and 8 dedicated UTF-8 edge cases (overlong encodings, lone surrogates, truncated sequences). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add demo app screens that start the local HTTP server and display its address for manual testing with curl or a browser. Enables on-device validation of the parser against adversarial inputs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run the shared JSON test fixtures on an actual Android device via connectedDebugAndroidTest, validating the pure-Kotlin HTTP parser under ART in addition to the JVM unit tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0a09c12 to
e8cda3c
Compare
dcalhoun
left a comment
There was a problem hiding this comment.
@jkmassel the testing instructions succeeded for me.
I also had Claude swiftly rebase #357 atop this work and update it to use the HTTP server library in this work. Good news is that it seems to work. The result of the work is in the feat/leverage-host-media-processing-stacked branch (here is the current diff).
I did encounter a couple of issues that I noted in inline comments below. I worked around them in the feat/leverage-host-media-processing-stacked branch, but we might address them in the library instead. WDYT?
CORS preflight requests (OPTIONS) never include credentials per Fetch spec §3.3.5, so the server now skips authentication for OPTIONS requests. This allows browser-based clients to complete the preflight handshake without a bearer token. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Browser fetch() silently strips Proxy-* headers (Fetch spec §2.2.2), making Proxy-Authorization unusable from web contexts. Add Relay-Authorization as a non-forbidden alternative that carries the same bearer token. Proxy-Authorization remains supported and takes precedence when both headers are present. Relay-Authorization is treated as hop-by-hop and stripped before forwarding to upstream servers, keeping Authorization free for upstream credentials. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@jkmassel the latest changes worked for the media upload implementation. 🎉 Noting two things: First, it appears your two commits were pushed to a new Second, I'll share a few additional thoughts on the new The This creates a hidden obligation. Any caller serving a browser client has to independently discover that they need to handle CORS policy (allowed origins, methods, headers) is application-level and not something the library can own without configuration that arguably falls outside its scope. Instead, should we clearly document this obligation in |
The OPTIONS auth exemption is only half the CORS story — handlers must also return appropriate Access-Control-Allow-* headers. When proxying to a remote server, pass the upstream response through unaltered. When serving local content, the handler must generate the CORS headers itself. Document this in the class-level docs to prevent silent preflight failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
a184a2c adds some documentation about the "handle your own CORS" requirement. I thought about adding a helper that makes it a one-liner for non-relay purposes, but I think that would be better in some future PR if it's really needed. |
What?
Adds a hardened, RFC-conformant HTTP/1.1 request parser and local proxy server for both iOS (Swift) and Android (Kotlin), with shared cross-platform test fixtures to guarantee behavioral parity.
Why?
GutenbergKit's native integration embeds a web editor that communicates with native networking through an in-process HTTP server. Because the server is exposed to JavaScript running in the WebView, the parser must be hardened against malformed and adversarial input per RFC 7230/9110/9112 — a lenient parser could enable request smuggling, header injection, or denial-of-service via resource exhaustion.
How?
Swift (iOS)
GutenbergKitHTTPmodule — Incremental, stateful parser (HTTPRequestParser) that buffers to a temporary file on disk so memory stays flat regardless of body size. Strict RFC conformance: rejects obs-fold, whitespace before colon, conflicting Content-Length, Transfer-Encoding, invalid UTF-8 (round-trip validated), lone surrogates, overlong encodings.HTTPServer— Local HTTP/1.1 server on Network.framework with async handler API, connection limits, read timeouts, and constant-time bearer token authentication.multipart/form-datasupport with lazy body references (file slices, not copies).RequestBody— Abstracts over in-memory and file-backed storage withInputStreamand asyncdataaccess.Kotlin (Android)
org.wordpress.gutenberg.http) — Feature-identical port of the Swift parser with no native dependencies. IncludesHeaderValue,HTTPRequestSerializer,HTTPRequestParser(with disk-backed buffering viaBuffer/TempFileOwner),ParsedHTTPRequest,MultipartPart, andRequestBody.HttpServer— Local HTTP/1.1 server with connection limits, read timeouts, pure-Kotlin response serialization, and proper 400 responses on premature connection close.Shared cross-platform test fixtures
test-fixtures/http/covering header value extraction (20 cases), request parsing (58 basic + 42 error + 4 incremental), and multipart parsing (32 field-based + 7 error).whitespaceBeforeColonerror (request smuggling vector per RFC 7230 §3.2.4).Key design decisions
Content-Lengthon both platforms (SwiftInt64, KotlinLong) with a 4 GB default max body size.inMemoryBodyThreshold(512 KB default) — bodies below threshold stay in memory, larger ones reference the temp file directly as a slice.Testing Instructions
Automated (CI runs these on every push)
swift test— runs 820+ tests including 163 fixture-based cross-platform testscd android && ./gradlew :Gutenberg:test— runs all Android unit tests including fixture testsOn-device (manual)
ios/Demo-iOS/Gutenberg.xcodeprojin Xcode, run on a device, navigate to the Media Proxy Server screen — it prints the server URLcd android && ./gradlew :Gutenberg:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=org.wordpress.gutenberg.http.InstrumentedFixtureTestsruns all 163 fixture cases on a connected device🤖 Generated with Claude Code