Skip to content

Commit da9d472

Browse files
committed
parse plain int in a few more inputs
1 parent c012b33 commit da9d472

6 files changed

Lines changed: 41 additions & 13 deletions

File tree

src/werkzeug/_internal.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,19 +193,20 @@ def __repr__(self) -> str:
193193
return f"<{type(self).__name__} {self.name}>"
194194

195195

196-
_plain_int_re = re.compile(r"-?\d+", re.ASCII)
196+
_plain_int_re = re.compile(r"-?[a-z0-9]+", re.ASCII | re.IGNORECASE)
197197

198198

199-
def _plain_int(value: str) -> int:
200-
"""Parse an int only if it is only ASCII digits and ``-``.
199+
def _plain_int(value: str, base: int = 10) -> int:
200+
"""Parse an int only if it is ASCII digits and ``-``.
201201
202-
This disallows ``+``, ``_``, and non-ASCII digits, which are accepted by ``int`` but
203-
are not allowed in HTTP header values.
202+
This disallows ``+``, ``_``, and non-ASCII digits, which are accepted by
203+
``int`` but are not allowed in HTTP header values.
204204
205-
Any leading or trailing whitespace is stripped
205+
Any surrounding whitespace is stripped.
206206
"""
207207
value = value.strip()
208+
208209
if _plain_int_re.fullmatch(value) is None:
209210
raise ValueError
210211

211-
return int(value)
212+
return int(value, base)

src/werkzeug/debug/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from zlib import adler32
2222

2323
from .._internal import _log
24+
from .._internal import _plain_int
2425
from ..exceptions import NotFound
2526
from ..exceptions import SecurityError
2627
from ..http import parse_cookie
@@ -450,7 +451,7 @@ def check_pin_trust(self, environ: WSGIEnvironment) -> bool | None:
450451
ts_str, pin_hash = val.split("|", 1)
451452

452453
try:
453-
ts = int(ts_str)
454+
ts = _plain_int(ts_str)
454455
except ValueError:
455456
return False
456457

src/werkzeug/http.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from urllib.parse import unquote
1919

2020
from ._internal import _dt_as_utc
21+
from ._internal import _plain_int
2122

2223
if t.TYPE_CHECKING:
2324
from _typeshed.wsgi import WSGIEnvironment
@@ -1147,7 +1148,7 @@ def parse_age(value: str | None = None) -> timedelta | None:
11471148
if not value:
11481149
return None
11491150
try:
1150-
seconds = int(value)
1151+
seconds = _plain_int(value)
11511152
except ValueError:
11521153
return None
11531154
if seconds < 0:
@@ -1167,10 +1168,9 @@ def dump_age(age: timedelta | int | None = None) -> str | None:
11671168
"""
11681169
if age is None:
11691170
return None
1171+
11701172
if isinstance(age, timedelta):
11711173
age = int(age.total_seconds())
1172-
else:
1173-
age = int(age)
11741174

11751175
if age < 0:
11761176
raise ValueError("age cannot be negative")

src/werkzeug/middleware/shared_data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def _opener(self, filename: str) -> _TOpener:
151151
return lambda: (
152152
open(filename, "rb"),
153153
datetime.fromtimestamp(os.path.getmtime(filename), tz=timezone.utc),
154-
int(os.path.getsize(filename)),
154+
os.path.getsize(filename),
155155
)
156156

157157
def get_file_loader(self, filename: str) -> _TLoader:

src/werkzeug/serving.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from urllib.parse import urlsplit
3232

3333
from ._internal import _log
34+
from ._internal import _plain_int
3435
from ._internal import _wsgi_encoding_dance
3536
from .datastructures import HeaderSet
3637
from .exceptions import InternalServerError
@@ -108,7 +109,7 @@ def readable(self) -> bool:
108109
def read_chunk_len(self) -> int:
109110
try:
110111
line = self._rfile.readline().decode("latin1")
111-
_len = int(line.strip(), 16)
112+
_len = _plain_int(line, 16)
112113
except ValueError as e:
113114
raise OSError("Invalid chunk header") from e
114115
if _len < 0:

tests/test_internal.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import pytest
2+
3+
from werkzeug._internal import _plain_int
14
from werkzeug.test import create_environ
25
from werkzeug.wrappers import Request
36
from werkzeug.wrappers import Response
@@ -28,3 +31,25 @@ def test_wrapper_internals():
2831
response = Response(["Hällo Wörld".encode()])
2932
headers = response.get_wsgi_headers(create_environ())
3033
assert "Content-Length" in headers
34+
35+
36+
@pytest.mark.parametrize(
37+
("value", "base", "expect"),
38+
[
39+
("123", 10, 123),
40+
("-123", 10, -123),
41+
("1_23", 10, None),
42+
("+123", 10, None),
43+
("𝟙𝟚𝟛", 10, None),
44+
("7B", 10, None),
45+
("7B", 16, 123),
46+
("-7B", 16, -123),
47+
("7b", 16, 123),
48+
],
49+
)
50+
def test_plain_int(value: str, base: int, expect: int | None) -> None:
51+
if expect is None:
52+
with pytest.raises(ValueError):
53+
_plain_int(value, base)
54+
else:
55+
assert _plain_int(value, base) == expect

0 commit comments

Comments
 (0)