fix(server): defeat sendfile to avoid corrupted GET responses on Darwin#382
Open
spencer-scw wants to merge 1 commit into
Open
fix(server): defeat sendfile to avoid corrupted GET responses on Darwin#382spencer-scw wants to merge 1 commit into
spencer-scw wants to merge 1 commit into
Conversation
http.ServeFile on Darwin currently corrupts every send/ GET response on qrcp's default code path. With qrcp bound to a LAN interface (the normal case), the wire bytes come back in the order [file[512:], HTTP/1.1 headers, file[:512]], which curl rejects as "Received HTTP/0.9 when not allowed" and browsers render as garbled binary. The root cause is in the Go stdlib's interaction with macOS sendfile(2) on non-loopback interfaces, reproducible in a bare 8-line http.HandleFunc + http.ServeFile program. Files <=512 bytes and loopback bindings are unaffected. Hiding ReadFrom from the accepted *net.TCPConn forces net/http's response writer to fall back to a plain Write() loop, which serializes headers and body in the correct order. The small per-byte overhead vs. sendfile is negligible for the LAN file-transfer use case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Owner
|
Good catch! |
Author
|
It looks like the CI failures are pre-existing pending #381 |
claudiodangelis
approved these changes
Jun 13, 2026
Owner
|
LGTM, I will publish this in the next update. |
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.
AI usage disclaimer: I used Claude Code to diagnose and patch a bug I was having on macOS sending an image. The patch seems reasonable to me, but I don't fully understand the specifics of the upstream bug in Golang it identified. The patch is simple though- it just short-ciruits the faulty upstream code path with a simpler working one. AI also left a fairly verbose comment in the code explaining how it works, so just let me know if you want it removed. It does provide good documentation into the decision being made, so it might be worth leaving until a golang fix is merged upstream.
Summary
qrcp sendcurrently produces a corrupted response body for every GET when bound to a non-loopback interface (the default). With a 29 KB JPEG, the response bytes come back in the order[file[512:], HTTP/1.1 headers, file[:512]].curlrejects withReceived HTTP/0.9 when not allowedand browsers render the bytes as garbled binary. Affects every file > 512 bytes.Root cause is a Go stdlib bug in the interaction between
http.ServeFileand macOSsendfile(2)on non-loopback interfaces. It's reproducible in an 8-line program with no third-party code. I plan to file it upstream againstgolang/goseparately.This PR works around it in qrcp by hiding
ReadFromfrom the accepted*net.TCPConn, which causesnet/http's response writer to fall back from sendfile to a plainWrite()loop. That path serializes headers and body in the correct order. Small per-byte overhead vs. sendfile, irrelevant for LAN file transfers.Reproduction (before the patch)
With qrcp v0.11.6 / current
main, on macOS, sending any file larger than 512 bytes:Raw socket inspection confirms the body+headers reordering:
After the patch
Test plan
qrcp send file.jpgon macOS, file > 512 bytes, downloads cleanly via curlcmp)qrcp send -z file1 file2zip path still worksqrcp receiveupload path still works (touches the same listener)splice, notsendfile; should not be impacted by this change either way)🤖 Generated with Claude Code