Skip to content

Fix static.php RFC 7233 range handling and built-in server compatibility#10144

Open
JohnRDOrazio wants to merge 5 commits intoroundcube:masterfrom
JohnRDOrazio:fix/static-php-robustness
Open

Fix static.php RFC 7233 range handling and built-in server compatibility#10144
JohnRDOrazio wants to merge 5 commits intoroundcube:masterfrom
JohnRDOrazio:fix/static-php-robustness

Conversation

@JohnRDOrazio
Copy link
Copy Markdown
Contributor

Summary

Fixes several bugs in static.php's range request handling and file serving logic:

  • Fix suffix-range requests (bytes=-500): was serving bytes 0-500 (first 501 bytes) instead of the last 500 bytes per RFC 7233 §2.1
  • Fix suffix-range larger than file (bytes=-9999): was returning 416 instead of clamping to the full file per RFC 7233
  • Fix header/fopen ordering: response headers were sent before fopen(), making it impossible to return 500 on failure
  • Add flush() to range request loop for PHP built-in server consistency
  • Clean output buffers before serving to prevent Content-Length mismatch
  • Use chunked fread()+flush() on cli-server SAPI for reliable CSS/JS delivery

Closes #10143

Split out from #10085 per maintainer request.

Test plan

  • New test fixture (tests/fixtures/static_original_router.php) runs the original unfixed code on a second PHP built-in server
  • testOriginalSuffixRangeReturnsWrongBytes — proves old code returns wrong bytes, new code returns correct bytes
  • testOriginalSuffixRangeLargerThanFileReturns416 — proves old code returns 416, new code returns 206
  • testOriginalOutputBufferCorruptsResponse — proves output buffering corrupts responses without ob_end_clean()
  • testCssFileIntegrity / testJsFileIntegrity — byte-for-byte verification of CSS/JS serving on built-in server
  • testContentLengthAccuracy — parameterized across file sizes (54B to 387KB)
  • testRangeHeaderSuffix, testRangeHeaderOpenEnded, testRangeHeaderFullFileViaRange — RFC 7233 compliance
  • Existing tests preserved: testRangeHeaderInvalid, testRangeHeaderStandard, testModifiedSinceHeader, testExistingResources, testForbiddenResources

🤖 Generated with Claude Code

JohnRDOrazio and others added 5 commits April 9, 2026 21:08
…mpatibility

- Fix suffix-range requests (e.g., "bytes=-500" for last 500 bytes)
- Fix open-ended range requests (e.g., "bytes=10-" for byte 10 to end)
- Clean output buffers before serving to prevent Content-Length mismatch
- Use chunked reading with flush for PHP built-in server compatibility
- Use readfile() for efficient full-file serving on non-cli-server SAPIs
- Add tests for suffix-range, open-ended range, and full-file range requests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split the monolithic testRangeHeader into focused test methods with
RFC 7233 references documenting what was broken and why:

- testRangeHeaderSuffix: Proves suffix-range "bytes=-500" returns the
  LAST 500 bytes. The old code set start=0 for empty prefix, serving
  bytes 0-500 (first 501 bytes) instead — completely wrong content.

- testRangeHeaderSuffixLargerThanFile: Proves "bytes=-9999" on a
  1058-byte file returns the whole file (206). The old code interpreted
  this as "bytes=0-9999", failed bounds check, returned 416 error.

- testRangeHeaderOpenEnded: Verifies "bytes=10-" returns byte 10 to EOF.

- testRangeHeaderFullFileViaRange: Verifies "bytes=0-" returns full file.

- testContentLengthAccuracy: Verifies Content-Length matches actual body
  size, catching output buffer corruption on PHP built-in server.

- testRangeHeaderInvalid/Standard: Preserved from original test suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The core motivation for the static.php changes is that the PHP built-in
server (cli-server SAPI) could not correctly serve static CSS and JS
files. Output buffering could inject extra bytes before file content,
causing Content-Length mismatch and truncated downloads. Large files
like app.js (~387KB) also required chunked fread()+flush() to deliver
completely.

New tests:
- testCssFileIntegrity: Verifies .less files are served byte-for-byte
  identically to disk, with matching Content-Length
- testJsFileIntegrity: Same for JS files, including the large app.js
  that triggers chunked delivery issues
- testContentLengthAccuracy: Parameterized across file types/sizes
  (54B gif to 387KB js) to catch Content-Length vs body mismatches

All tests run against the built-in server via ServerTestCase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a test fixture (tests/fixtures/static_original_router.php) that
runs the ORIGINAL unfixed serveStaticFile logic on a second PHP
built-in server, allowing direct comparison against the fixed version.

Three new tests demonstrate the actual bugs:

- testOriginalSuffixRangeReturnsWrongBytes: Proves "bytes=-500" on the
  original server returns bytes 0-500 (first 501 bytes) while the fixed
  server correctly returns bytes 558-1057 (last 500 bytes). The two
  responses contain completely different data.

- testOriginalSuffixRangeLargerThanFileReturns416: Proves "bytes=-9999"
  on a 1058-byte file returns 416 on the original server (because it
  interprets it as "bytes=0-9999" which fails bounds check), while the
  fixed server correctly clamps and returns 206 with the full file.

- testOriginalOutputBufferCorruptsResponse: Proves that when output
  buffering is active, the original server's response begins with
  buffer junk and the file's last bytes are truncated (because
  Content-Length only accounts for the file size, not the buffer).
  The fixed server's ob_end_clean() prevents this corruption.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address critical issues found by CodeRabbit review:

- Move fopen() before http_response_code(206) and header() calls in
  the range request path, so a 500 error can be sent if fopen fails
  (previously headers were already sent, making 500 impossible)
- Same fix for the cli-server full-file path: open file before headers
- Add flush() after each chunk in the range request loop for cli-server
  SAPI, matching the full-file path behavior for consistency
- Fix parse_url() false return handling in test fixture (parse_url can
  return false, not just null, on malformed URLs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

static.php: RFC 7233 suffix-range bug and built-in server compatibility issues

1 participant