Skip to content

fix(download): treat a short clean EOF as resumable, not complete - #503

Open
A831ARD0 wants to merge 1 commit into
spotiflacapp:mainfrom
A831ARD0:fix/verify-download-content-length
Open

fix(download): treat a short clean EOF as resumable, not complete#503
A831ARD0 wants to merge 1 commit into
spotiflacapp:mainfrom
A831ARD0:fix/verify-download-content-length

Conversation

@A831ARD0

@A831ARD0 A831ARD0 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #491, which was fixed in 4.8.5 by making mid-download resume opt-in (safe default: fail and delete the staged partial file, since switching networks can route a stable URL to a different CDN object even with an unchanged validator).

The remaining gap

That fix only changes behavior for a real read error. fileDownload's copyBody loop still breaks out and promotes the file on any clean io.EOF, without checking whether the bytes written actually reached the response's Content-Length. Some transports (this app's uTLS-based client, used for TLS-fingerprint spoofing, is exactly this kind of custom transport) can surface a mid-transfer connection drop as a plain io.EOF instead of io.ErrUnexpectedEOF — so a network change mid-download could still get silently promoted as a "successful" truncated file, bypassing the new opt-in resume logic entirely (it never even reaches the canResume check).

The fix

A clean EOF short of a known Content-Length is now routed through the same resume-or-fail path as a real read error, so it respects the same resume option introduced in 4.8.5:

  • Fails and cleans up the staged file by default (matching the safe-default policy).
  • Resumes via Range/If-Range when the caller passed resume: true and the server provided a validator.

Tests

extension_runtime_file_download_integrity_test.go adds three tests, verified to fail on the pre-fix code and pass with it:

  • TestFileDownloadShortCleanEOFFailsByDefaultEvenWithValidator — fails and cleans up by default, even with a validator present.
  • TestFileDownloadResumesAfterShortCleanEOFWhenEnabled — resumes and completes correctly with resume: true.
  • TestFileDownloadShortCleanEOFWithoutValidatorFails — fails and leaves no file behind when there's no validator at all.

Known follow-up (not in this PR)

fileDownloadChunked's unknown-total-size path (used for YouTube's CDN, where the total length isn't known upfront) has an analogous but harder-to-fix ambiguity: without a known length, there's no reliable way to distinguish a legitimately short final chunk from a truncated one.

Verification

  • go build ./..., go vet ./..., gofmt -l — clean
  • go test ./... — all green, including the 3 new tests
  • Rebased onto current main after the 4.8.5 resume changes landed

Follow-up to spotiflacapp#491, which was fixed in 4.8.5 by making mid-download
resume opt-in (safe default: fail and delete the staged file, since
switching networks can route a stable URL to a different CDN object).

That fix only changes what happens on a *real* read error. It doesn't
help if a transport surfaces a mid-transfer connection drop as a plain
io.EOF instead of io.ErrUnexpectedEOF - fileDownload's copyBody loop
still breaks out and promotes the file on any clean EOF, regardless of
whether written bytes actually reached Content-Length. This app's
uTLS-based client (used for TLS-fingerprint spoofing) is exactly the
kind of custom transport where that guarantee isn't necessarily upheld.

Now a clean EOF short of a known Content-Length is routed through the
same resume-or-fail path as a real read error, so it respects the same
opt-in `resume` option: fails and cleans up the staged file by default,
or resumes via Range/If-Range when the caller explicitly requested it
and the server provided a validator.

extension_runtime_file_download_integrity_test.go adds three tests:
- fails by default even when a validator is present (matches the
  opt-in policy from 4.8.5)
- resumes correctly when `resume: true` is passed
- fails and cleans up when there's no validator at all

All three fail on the pre-fix code and pass with it.

Note: fileDownloadChunked's unknown-total-size path (used for
YouTube's CDN) has an analogous but harder-to-fix ambiguity - without
a known length there's no way to distinguish a legitimately short
final chunk from a truncated one - left as a follow-up.
@A831ARD0
A831ARD0 force-pushed the fix/verify-download-content-length branch from c29cb75 to 610266c Compare August 1, 2026 20:38
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.

1 participant