Skip to content

Reject full responses to resumed download requests - #11102

Open
abo3losh1 wants to merge 3 commits into
python-poetry:mainfrom
abo3losh1:fix/download-reject-ignored-range
Open

abo3losh1 wants to merge 3 commits into
python-poetry:mainfrom
abo3losh1:fix/download-reject-ignored-range

Conversation

@abo3losh1

Copy link
Copy Markdown

Summary

Poetry resumes a partial download when the first response advertises Accept-Ranges: bytes. Some servers still ignore the later Range request and return 200 with the full file. Poetry then appends that full body to the partial body and reports a successful download.

For example, if the first request yields abcd from an eight byte file, the retry asks for bytes=4-. If the server returns the full abcdefgh with status 200, the old code writes abcdabcdefgh.

The downloader now requires status 206 for a resumed request. It closes a response with any other status and raises ChunkedEncodingError. The existing atomic_open context leaves the destination unpublished on failure.

Tests

The new test models a server that advertises ranges on the first response and ignores the range on the retry. It failed on main because no error was raised. The two existing retry stubs now expose the HTTP status for their initial and resumed responses.

  • pytest tests/utils/test_download.py tests/installation/test_executor.py -q -o addopts='': 106 passed, 1 skipped.
  • Ruff 0.16.6 check and format: clean on both changed files.
  • Mypy: no issues in either changed file.

Checklist

  • Added tests for changed code.
  • Updated documentation for changed code. No public API or documented option changed.

A server can advertise byte ranges, then return the full file with status 200 to a resumed request. The downloader previously appended that body to the partial file and reported success. The regression test checks the response is rejected and the destination is not published.
A resumed request must return HTTP 206. If a server ignores Range and returns 200, close the response and raise ChunkedEncodingError so the partial data cannot be combined with a full file.
Copilot AI lite review requested due to automatic review settings September 27, 2026 01:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/poetry/utils/download.py" line_range="102-106" />
<code_context>
         )
         try:
             response.raise_for_status()
+            if start > 0 and response.status_code != 206:
+                raise ChunkedEncodingError(
+                    f"Server ignored the Range request while resuming {self._url}."
</code_context>
<issue_to_address>
**issue (bug_risk):** A resumed response with an HTTP error status such as 416 or 500 raises requests' HTTPError before the new status check runs, so it does not raise the promised ChunkedEncodingError for every non-206 resumed response.

**Triggers:** When the server responds to the resume request with a non-206 error status.

**Suggested fix:** Check `response.status_code != 206` before `raise_for_status()` (or convert the resulting HTTPError to ChunkedEncodingError) while still closing the response.

```suggestion
            if start > 0 and response.status_code != 206:
                raise ChunkedEncodingError(
                    f"Server ignored the Range request while resuming {self._url}."
                )
            response.raise_for_status()
```
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: src/poetry/utils/download.py:106


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines 102 to +106
response.raise_for_status()
if start > 0 and response.status_code != 206:
raise ChunkedEncodingError(
f"Server ignored the Range request while resuming {self._url}."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): A resumed response with an HTTP error status such as 416 or 500 raises requests' HTTPError before the new status check runs, so it does not raise the promised ChunkedEncodingError for every non-206 resumed response.

Triggers: When the server responds to the resume request with a non-206 error status.

Suggested fix: Check response.status_code != 206 before raise_for_status() (or convert the resulting HTTPError to ChunkedEncodingError) while still closing the response.

Suggested change
response.raise_for_status()
if start > 0 and response.status_code != 206:
raise ChunkedEncodingError(
f"Server ignored the Range request while resuming {self._url}."
)
if start > 0 and response.status_code != 206:
raise ChunkedEncodingError(
f"Server ignored the Range request while resuming {self._url}."
)
response.raise_for_status()

@dimbleby

dimbleby commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

What servers advertise support for ranges and do not support ranges? Did you raise a bug against them?

@abo3losh1

Copy link
Copy Markdown
Author

Good question. No, I don't have a specific server that did this, and I haven't filed a bug against one.

I added the check because RFC 9110 (section 14.2) lets a server ignore a Range header even when it advertises Accept-Ranges: bytes, so it's allowed behaviour rather than a server bug. It can also happen behind a CDN, where the first request and the retry are served by different machines.

To be fair about the impact: poetry checks the archive hash after downloading, so a file stitched together like this would still fail the install. It would fail with a hash mismatch though, which points at the wrong problem. This change makes it fail at the download step, with an error that says what actually happened.

If you don't think that's worth the extra code, I'm happy to close it.

This branch has not been deployed

No deployments
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.

3 participants