Skip to content

accept case-insensitive range units in http_range - #13584

Open
LALITH0110 wants to merge 2 commits into
aio-libs:masterfrom
LALITH0110:fix/http-range-case-insensitive
Open

accept case-insensitive range units in http_range#13584
LALITH0110 wants to merge 2 commits into
aio-libs:masterfrom
LALITH0110:fix/http-range-case-insensitive

Conversation

@LALITH0110

Copy link
Copy Markdown
Contributor

What do these changes do?

BaseRequest.http_range matched the Range header with a case-sensitive pattern,
so a range unit written with any uppercase letters was rejected. RFC 9110
sec. 14.1.1 states that range unit names are case-insensitive.

re.IGNORECASE is added alongside the existing re.ASCII rather than replacing
it. That distinction matters: with re.IGNORECASE alone, Unicode case folding also
accepts byteſ=0-3 (U+017F LATIN SMALL LETTER LONG S, which folds to s), which
is not a valid spelling of the bytes unit. Keeping re.ASCII confines folding to
ASCII, so the only newly accepted spellings are ASCII case variants of bytes.

A regression test asserts the long-s spelling is still rejected, alongside the
existing test_range_non_ascii.

Are there changes in behavior for the user?

Yes, and only in the permissive direction. A Range header whose unit is spelled
with uppercase letters is now honoured instead of rejected. Previously
:class:~aiohttp.web.FileResponse turned the resulting ValueError into
416 Range Not Satisfiable for a range that was in fact satisfiable:

Range: bytes=0-3   -> 206 Partial Content   (before and after)
Range: Bytes=0-3   -> 416 before  ->  206 after
Range: BYTES=0-3   -> 416 before  ->  206 after
Range: bYtEs=0-3   -> 416 before  ->  206 after
Range: byteſ=0-3   -> 416 before  ->  416 after   (still rejected)

Nothing that was previously accepted changes meaning, and no header that should be
invalid becomes valid.

Is it a substantial burden for the maintainers to support this?

No. It is a one-flag change to an existing regex in a single property, with no new
API, no new dependency, and no configuration. The added tests pin both directions —
the ASCII case variants that must be accepted, and the Unicode look-alike that must
not be — so the ASCII-only constraint cannot be dropped later without a test failing.

Related issue number

Fixes #13580

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes
    • docs/web_reference.rst documents http_range without specifying unit case, so
      it already describes the corrected behaviour; no docs change was needed. N/A
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt
  • Add a new news fragment into the CHANGES/ folder
    • CHANGES/13580.bugfix.rst
Agent run output — test logs

Pure-Python mode, macOS arm64, Python 3.12.

Targeted tests, with the fix applied:

$ AIOHTTP_NO_EXTENSIONS=1 pytest tests/test_web_request.py -k range
14 passed, 133 deselected in 0.05s

Reverting only aiohttp/web_request.py (tests untouched), to confirm the new
tests actually exercise the fix:

FAILED tests/test_web_request.py::test_range_to_slice_unit_case_insensitive[Bytes]
FAILED tests/test_web_request.py::test_range_to_slice_unit_case_insensitive[BYTES]
FAILED tests/test_web_request.py::test_range_to_slice_unit_case_insensitive[bYtEs]
3 failed, 11 passed, 133 deselected in 0.12s

Wider sweep:

$ AIOHTTP_NO_EXTENSIONS=1 pytest tests/ -k web
1202 passed, 120 skipped, 3296 deselected in 14.40s

$ AIOHTTP_NO_EXTENSIONS=1 pytest tests/test_web_sendfile.py tests/test_web_sendfile_functional.py
103 passed, 30 skipped in 1.64s

End-to-end against a live web.FileResponse server over TCP, which is where the
416 is user-visible (output quoted in the behaviour section above).

Lint, compared against the unmodified origin/master copies of the same two files
so that pre-existing findings are not attributed to this change:

$ flake8 aiohttp/web_request.py tests/test_web_request.py     # clean
$ black --check aiohttp/web_request.py tests/test_web_request.py
2 files would be left unchanged.

Not tested with Cython extensions: this change is in web_request.py and does not
touch the llhttp parser or websocket code.

Drafted with Claude Opus 5 (Claude Code); reviewed by @LALITH0110.

RFC 9110 sec. 14.1.1 states that range unit names are case-insensitive,
but BaseRequest.http_range matched the Range header with a case-sensitive
pattern. A request sending "Range: Bytes=0-3" raised ValueError, which
FileResponse turns into 416 Range Not Satisfiable even though the range
is satisfiable.

re.IGNORECASE is added alongside the existing re.ASCII rather than
replacing it. That matters: with IGNORECASE alone, Unicode case folding
would also accept "byteſ=0-3" (LATIN SMALL LETTER LONG S), since
U+017F folds to "s". Keeping re.ASCII confines folding to ASCII, so the
only newly accepted spellings are the ASCII case variants of "bytes".

Tests cover the accepted variants and add a regression test asserting the
long-s spelling is still rejected.
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Aug 29, 2026
@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.02%. Comparing base (20acdf4) to head (d658c62).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #13584   +/-   ##
=======================================
  Coverage   99.02%   99.02%           
=======================================
  Files         135      135           
  Lines       50500    50509    +9     
  Branches     2652     2652           
=======================================
+ Hits        50007    50016    +9     
  Misses        370      370           
  Partials      123      123           
Flag Coverage Δ
Autobahn 22.03% <30.00%> (+<0.01%) ⬆️
CI-GHA 98.91% <100.00%> (+<0.01%) ⬆️
OS-Linux 98.68% <100.00%> (+<0.01%) ⬆️
OS-Windows 97.09% <100.00%> (+<0.01%) ⬆️
OS-macOS 97.97% <100.00%> (+<0.01%) ⬆️
Py-3.10 98.11% <100.00%> (+<0.01%) ⬆️
Py-3.11 98.34% <100.00%> (+<0.01%) ⬆️
Py-3.12 98.43% <100.00%> (+<0.01%) ⬆️
Py-3.13 98.41% <100.00%> (-0.01%) ⬇️
Py-3.14 98.45% <100.00%> (+<0.01%) ⬆️
Py-3.14t 97.61% <100.00%> (-0.01%) ⬇️
Py-pypy-3.11 97.39% <100.00%> (-0.01%) ⬇️
VM-macos 97.97% <100.00%> (+<0.01%) ⬆️
VM-ubuntu 98.68% <100.00%> (+<0.01%) ⬆️
VM-windows 97.09% <100.00%> (+<0.01%) ⬆️
cython-coverage 83.07% <0.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@codspeed-hq

codspeed-hq Bot commented Aug 29, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 96 untouched benchmarks
⏩ 83 skipped benchmarks1


Comparing LALITH0110:fix/http-range-case-insensitive (d658c62) with master (20acdf4)

Open in CodSpeed

Footnotes

  1. 83 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@LALITH0110
LALITH0110 marked this pull request as ready for review August 29, 2026 17:34
@greptile-apps

greptile-apps Bot commented Aug 29, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge with no actionable correctness, compatibility, or security issues identified.

The regex now accepts precisely the intended ASCII case variants, preserves existing range parsing semantics and non-ASCII rejection, and is covered by focused regression tests.

Reviews (1): Last reviewed commit: "add PR-numbered changelog symlink" | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

http_range rejects valid case-insensitive range unit names (Range: Bytes=… → 416)

1 participant