Skip to content

Commit ac9974c

Browse files
authored
Fix qvalue parsing (#2753)
2 parents dd1f137 + 88f4ed6 commit ac9974c

File tree

4 files changed

+14
-22
lines changed

4 files changed

+14
-22
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Unreleased
1212
enforcement. :issue:`2734`
1313
- Fix empty file streaming when testing. :issue:`2740`
1414
- Clearer error message when URL rule does not start with slash. :pr:`2750`
15+
- ``Accept`` ``q`` value can be a float without a decimal part. :issue:`2751`
1516

1617

1718
Version 2.3.6

src/werkzeug/_internal.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,6 @@ def _decode_idna(domain: str) -> str:
313313

314314

315315
_plain_int_re = re.compile(r"-?\d+", re.ASCII)
316-
_plain_float_re = re.compile(r"-?\d+\.\d+", re.ASCII)
317316

318317

319318
def _plain_int(value: str) -> int:
@@ -329,19 +328,3 @@ def _plain_int(value: str) -> int:
329328
raise ValueError
330329

331330
return int(value)
332-
333-
334-
def _plain_float(value: str) -> float:
335-
"""Parse a float only if it is only ASCII digits and ``-``, and contains digits
336-
before and after the ``.``.
337-
338-
This disallows ``+``, ``_``, non-ASCII digits, and ``.123``, which are accepted by
339-
``float`` but are not allowed in HTTP header values.
340-
341-
Any leading or trailing whitespace is stripped
342-
"""
343-
value = value.strip()
344-
if _plain_float_re.fullmatch(value) is None:
345-
raise ValueError
346-
347-
return float(value)

src/werkzeug/http.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from urllib.request import parse_http_list as _parse_list_header
1919

2020
from ._internal import _dt_as_utc
21-
from ._internal import _plain_float
2221
from ._internal import _plain_int
2322

2423
if t.TYPE_CHECKING:
@@ -614,6 +613,7 @@ def parse_options_header(value: str | None) -> tuple[str, dict[str, str]]:
614613
return value, options
615614

616615

616+
_q_value_re = re.compile(r"-?\d+(\.\d+)?", re.ASCII)
617617
_TAnyAccept = t.TypeVar("_TAnyAccept", bound="ds.Accept")
618618

619619

@@ -656,13 +656,15 @@ def parse_accept_header(
656656
item, options = parse_options_header(item)
657657

658658
if "q" in options:
659-
try:
660-
# pop q, remaining options are reconstructed
661-
q = _plain_float(options.pop("q"))
662-
except ValueError:
659+
# pop q, remaining options are reconstructed
660+
q_str = options.pop("q").strip()
661+
662+
if _q_value_re.fullmatch(q_str) is None:
663663
# ignore an invalid q
664664
continue
665665

666+
q = float(q_str)
667+
666668
if q < 0 or q > 1:
667669
# ignore an invalid q
668670
continue

tests/test_http.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,12 @@ def test_accept_invalid_float(value):
776776
assert list(a.values()) == ["en"]
777777

778778

779+
def test_accept_valid_int_one_zero():
780+
assert http.parse_accept_header("en;q=1") == http.parse_accept_header("en;q=1.0")
781+
assert http.parse_accept_header("en;q=0") == http.parse_accept_header("en;q=0.0")
782+
assert http.parse_accept_header("en;q=5") == http.parse_accept_header("en;q=5.0")
783+
784+
779785
@pytest.mark.parametrize("value", ["🯱🯲🯳", "+1-", "1-1_23"])
780786
def test_range_invalid_int(value):
781787
assert http.parse_range_header(value) is None

0 commit comments

Comments
 (0)