Skip to content

Drop lone surrogates that split a percent escape in the Cython quoter#1752

Open
rodrigobnogueira wants to merge 1 commit into
aio-libs:masterfrom
rodrigobnogueira:fix/quoter-surrogate-percent-recombine
Open

Drop lone surrogates that split a percent escape in the Cython quoter#1752
rodrigobnogueira wants to merge 1 commit into
aio-libs:masterfrom
rodrigobnogueira:fix/quoter-surrogate-percent-recombine

Conversation

@rodrigobnogueira

@rodrigobnogueira rodrigobnogueira commented Jun 9, 2026

Copy link
Copy Markdown
Member

What do these changes do?

The Cython quoter scans a string code point by code point, so a lone
surrogate that lands inside a %XX escape broke it: the % was
treated as a literal and re-encoded to %25 while the hex digits
were left in place (for example %2, a lone surrogate, then 0
quoted to %2520). The pure-Python quoter drops lone surrogates with
errors="ignore" before it scans, so it read the same input as
%20. The C quoter now skips lone surrogates while reading the two
hex digits, so both backends recombine the escape identically.

This is the second of two lone-surrogate parity gaps between the
backends; #1751 handles the case where a lone surrogate is the only
character that changes.

Are there changes in behavior for the user?

Yes, on the compiled extension only: requoting a string where a lone
surrogate falls within a %XX escape now drops the surrogate and
keeps the escape (matching the pure-Python build) instead of
percent-encoding the %.

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

No. It adds a small helper that skips lone surrogates in the requote
lookahead of yarl/_quoting_c.pyx; the pure-Python quoter already
behaves this way, so there is no public API change.

Related issue number

No public issue; related to #1751.

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes (N/A; internal behaviour, no public API change)
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt (N/A; file not present in repo)
  • Add a new news fragment into the CHANGES/ folder
Test details (optional, for reviewers)

Tests (both backends): python -m pytest tests/test_quoting.py tests/test_url_parsing.py, and the same with YARL_NO_EXTENSIONS=1; all pass. The new test_quoting.py case runs under both the py_quoter and c_quoter fixtures and fails on the unpatched C extension (%2520 vs %20), confirming it catches the bug. A differential fuzz of the C and pure-Python quoters over surrogate-laden inputs across the built-in quoter configurations shows no remaining divergence in this class. Pre-commit (ruff, codespell, cython-lint) and a Cython line-tracing coverage build of the changed lines pass locally.

@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Jun 9, 2026
@codspeed-hq

codspeed-hq Bot commented Jun 9, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 20.37%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
✅ 95 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
test_long_query_with_pct 3.4 ms 2.7 ms +28.3%
test_quoter_quote_utf8 129.7 µs 114.9 µs +12.94%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing rodrigobnogueira:fix/quoter-surrogate-percent-recombine (8f244e1) with master (a57cbee)

Open in CodSpeed

The Cython quoter scans a string code point by code point, so a lone
surrogate landing inside a "%XX" escape broke it: the "%" was treated
as a literal and re-encoded to "%25" while the hex digits were left in
place (for example "%2", a lone surrogate, then "0" quoted to "%2520").
The pure-Python quoter drops lone surrogates with errors="ignore"
before scanning, so it read the same input as "%20". Skip lone
surrogates while reading the two hex digits so the C quoter recombines
the escape the same way, restoring parity between the two backends.
@rodrigobnogueira rodrigobnogueira force-pushed the fix/quoter-surrogate-percent-recombine branch from dd4fcff to 8f244e1 Compare June 9, 2026 12:15
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.81%. Comparing base (a57cbee) to head (8f244e1).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1752      +/-   ##
==========================================
- Coverage   99.86%   99.81%   -0.05%     
==========================================
  Files          21       21              
  Lines        4310     4319       +9     
  Branches      252      252              
==========================================
+ Hits         4304     4311       +7     
- Misses          4        5       +1     
- Partials        2        3       +1     
Flag Coverage Δ
CI-GHA 99.74% <100.00%> (-0.05%) ⬇️
OS-Linux 99.74% <100.00%> (-0.05%) ⬇️
OS-Windows 98.54% <100.00%> (+<0.01%) ⬆️
OS-macOS 98.68% <100.00%> (-0.05%) ⬇️
Py-3.10 99.69% <100.00%> (+<0.01%) ⬆️
Py-3.11 99.69% <100.00%> (+<0.01%) ⬆️
Py-3.12 99.69% <100.00%> (+<0.01%) ⬆️
Py-3.13 99.69% <100.00%> (-0.05%) ⬇️
Py-3.14 99.69% <100.00%> (-0.05%) ⬇️
Py-3.14t 99.69% <100.00%> (-0.05%) ⬇️
Py-pypy-3.11 99.37% <100.00%> (+<0.01%) ⬆️
VM-macos-latest 98.68% <100.00%> (-0.05%) ⬇️
VM-ubuntu-latest 99.74% <100.00%> (-0.05%) ⬇️
VM-windows-latest 98.54% <100.00%> (+<0.01%) ⬆️
pytest 99.74% <100.00%> (-0.05%) ⬇️

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.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rodrigobnogueira rodrigobnogueira marked this pull request as ready for review June 10, 2026 02:43
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.

1 participant