Skip to content

Commit 7868bef

Browse files
committed
Merge branch 'stable'
2 parents 9a69323 + 6b56ed5 commit 7868bef

File tree

6 files changed

+71
-28
lines changed

6 files changed

+71
-28
lines changed

CHANGES.rst

+14
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ Version 3.2.0
66
Unreleased
77

88

9+
Version 3.1.3
10+
-------------
11+
12+
Released 2024-11-08
13+
14+
- Initial data passed to ``MultiDict`` and similar interfaces only accepts
15+
``list``, ``tuple``, or ``set`` when passing multiple values. It had been
16+
changed to accept any ``Collection``, but this matched types that should be
17+
treated as single values, such as ``bytes``. :issue:`2994`
18+
- When the ``Host`` header is not set and ``Request.host`` falls back to the
19+
WSGI ``SERVER_NAME`` value, if that value is an IPv6 address it is wrapped
20+
in ``[]`` to match the ``Host`` header. :issue:`2993`
21+
22+
923
Version 3.1.2
1024
-------------
1125

src/werkzeug/datastructures/headers.py

+14-13
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def __init__(
6262
defaults: (
6363
Headers
6464
| MultiDict[str, t.Any]
65-
| cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
65+
| cabc.Mapping[str, t.Any | list[t.Any] | tuple[t.Any, ...] | set[t.Any]]
6666
| cabc.Iterable[tuple[str, t.Any]]
6767
| None
6868
) = None,
@@ -227,7 +227,7 @@ def extend(
227227
arg: (
228228
Headers
229229
| MultiDict[str, t.Any]
230-
| cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
230+
| cabc.Mapping[str, t.Any | list[t.Any] | tuple[t.Any, ...] | set[t.Any]]
231231
| cabc.Iterable[tuple[str, t.Any]]
232232
| None
233233
) = None,
@@ -491,12 +491,14 @@ def update(
491491
arg: (
492492
Headers
493493
| MultiDict[str, t.Any]
494-
| cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
494+
| cabc.Mapping[
495+
str, t.Any | list[t.Any] | tuple[t.Any, ...] | cabc.Set[t.Any]
496+
]
495497
| cabc.Iterable[tuple[str, t.Any]]
496498
| None
497499
) = None,
498500
/,
499-
**kwargs: t.Any | cabc.Collection[t.Any],
501+
**kwargs: t.Any | list[t.Any] | tuple[t.Any, ...] | cabc.Set[t.Any],
500502
) -> None:
501503
"""Replace headers in this object with items from another
502504
headers object and keyword arguments.
@@ -516,9 +518,7 @@ def update(
516518
self.setlist(key, arg.getlist(key))
517519
elif isinstance(arg, cabc.Mapping):
518520
for key, value in arg.items():
519-
if isinstance(value, cabc.Collection) and not isinstance(
520-
value, str
521-
):
521+
if isinstance(value, (list, tuple, set)):
522522
self.setlist(key, value)
523523
else:
524524
self.set(key, value)
@@ -527,13 +527,16 @@ def update(
527527
self.set(key, value)
528528

529529
for key, value in kwargs.items():
530-
if isinstance(value, cabc.Collection) and not isinstance(value, str):
530+
if isinstance(value, (list, tuple, set)):
531531
self.setlist(key, value)
532532
else:
533533
self.set(key, value)
534534

535535
def __or__(
536-
self, other: cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
536+
self,
537+
other: cabc.Mapping[
538+
str, t.Any | list[t.Any] | tuple[t.Any, ...] | cabc.Set[t.Any]
539+
],
537540
) -> te.Self:
538541
if not isinstance(other, cabc.Mapping):
539542
return NotImplemented
@@ -545,13 +548,11 @@ def __or__(
545548
def __ior__(
546549
self,
547550
other: (
548-
cabc.Mapping[str, t.Any | cabc.Collection[t.Any]]
551+
cabc.Mapping[str, t.Any | list[t.Any] | tuple[t.Any, ...] | cabc.Set[t.Any]]
549552
| cabc.Iterable[tuple[str, t.Any]]
550553
),
551554
) -> te.Self:
552-
if not isinstance(other, (cabc.Mapping, cabc.Iterable)) or isinstance(
553-
other, str
554-
):
555+
if not isinstance(other, (cabc.Mapping, cabc.Iterable)):
555556
return NotImplemented
556557

557558
self.update(other)

src/werkzeug/datastructures/structures.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
def iter_multi_items(
2323
mapping: (
2424
MultiDict[K, V]
25-
| cabc.Mapping[K, V | cabc.Collection[V]]
25+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
2626
| cabc.Iterable[tuple[K, V]]
2727
),
2828
) -> cabc.Iterator[tuple[K, V]]:
@@ -33,11 +33,11 @@ def iter_multi_items(
3333
yield from mapping.items(multi=True)
3434
elif isinstance(mapping, cabc.Mapping):
3535
for key, value in mapping.items():
36-
if isinstance(value, cabc.Collection) and not isinstance(value, str):
36+
if isinstance(value, (list, tuple, set)):
3737
for v in value:
3838
yield key, v
3939
else:
40-
yield key, value # type: ignore[misc]
40+
yield key, value
4141
else:
4242
yield from mapping
4343

@@ -182,7 +182,7 @@ def __init__(
182182
self,
183183
mapping: (
184184
MultiDict[K, V]
185-
| cabc.Mapping[K, V | cabc.Collection[V]]
185+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
186186
| cabc.Iterable[tuple[K, V]]
187187
| None
188188
) = None,
@@ -194,7 +194,7 @@ def __init__(
194194
elif isinstance(mapping, cabc.Mapping):
195195
tmp = {}
196196
for key, value in mapping.items():
197-
if isinstance(value, cabc.Collection) and not isinstance(value, str):
197+
if isinstance(value, (list, tuple, set)):
198198
value = list(value)
199199

200200
if not value:
@@ -419,7 +419,7 @@ def update( # type: ignore[override]
419419
self,
420420
mapping: (
421421
MultiDict[K, V]
422-
| cabc.Mapping[K, V | cabc.Collection[V]]
422+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
423423
| cabc.Iterable[tuple[K, V]]
424424
),
425425
) -> None:
@@ -444,7 +444,7 @@ def update( # type: ignore[override]
444444
self.add(key, value)
445445

446446
def __or__( # type: ignore[override]
447-
self, other: cabc.Mapping[K, V | cabc.Collection[V]]
447+
self, other: cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
448448
) -> MultiDict[K, V]:
449449
if not isinstance(other, cabc.Mapping):
450450
return NotImplemented
@@ -455,11 +455,12 @@ def __or__( # type: ignore[override]
455455

456456
def __ior__( # type: ignore[override]
457457
self,
458-
other: cabc.Mapping[K, V | cabc.Collection[V]] | cabc.Iterable[tuple[K, V]],
458+
other: (
459+
cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
460+
| cabc.Iterable[tuple[K, V]]
461+
),
459462
) -> te.Self:
460-
if not isinstance(other, (cabc.Mapping, cabc.Iterable)) or isinstance(
461-
other, str
462-
):
463+
if not isinstance(other, (cabc.Mapping, cabc.Iterable)):
463464
return NotImplemented
464465

465466
self.update(other)
@@ -600,7 +601,7 @@ def __init__(
600601
self,
601602
mapping: (
602603
MultiDict[K, V]
603-
| cabc.Mapping[K, V | cabc.Collection[V]]
604+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
604605
| cabc.Iterable[tuple[K, V]]
605606
| None
606607
) = None,
@@ -744,7 +745,7 @@ def update( # type: ignore[override]
744745
self,
745746
mapping: (
746747
MultiDict[K, V]
747-
| cabc.Mapping[K, V | cabc.Collection[V]]
748+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
748749
| cabc.Iterable[tuple[K, V]]
749750
),
750751
) -> None:
@@ -1009,7 +1010,7 @@ def __init__(
10091010
self,
10101011
mapping: (
10111012
MultiDict[K, V]
1012-
| cabc.Mapping[K, V | cabc.Collection[V]]
1013+
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
10131014
| cabc.Iterable[tuple[K, V]]
10141015
| None
10151016
) = None,

src/werkzeug/sansio/utils.py

+8
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ def get_host(
7171
:return: Host, with port if necessary.
7272
:raise ~werkzeug.exceptions.SecurityError: If the host is not
7373
trusted.
74+
75+
.. versionchanged:: 3.1.3
76+
If ``SERVER_NAME`` is IPv6, it is wrapped in ``[]``.
7477
"""
7578
host = ""
7679

@@ -79,6 +82,11 @@ def get_host(
7982
elif server is not None:
8083
host = server[0]
8184

85+
# If SERVER_NAME is IPv6, wrap it in [] to match Host header.
86+
# Check for : because domain or IPv4 can't have that.
87+
if ":" in host and host[0] != "[":
88+
host = f"[{host}]"
89+
8290
if server[1] is not None:
8391
host = f"{host}:{server[1]}"
8492

tests/sansio/test_utils.py

+4
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@
1414
("https", "spam", None, "spam"),
1515
("https", "spam:443", None, "spam"),
1616
("http", "spam:8080", None, "spam:8080"),
17+
("http", "127.0.0.1:8080", None, "127.0.0.1:8080"),
18+
("http", "[::1]:8080", None, "[::1]:8080"),
1719
("ws", "spam", None, "spam"),
1820
("ws", "spam:80", None, "spam"),
1921
("wss", "spam", None, "spam"),
2022
("wss", "spam:443", None, "spam"),
2123
("http", None, ("spam", 80), "spam"),
2224
("http", None, ("spam", 8080), "spam:8080"),
25+
("http", None, ("127.0.0.1", 8080), "127.0.0.1:8080"),
26+
("http", None, ("::1", 8080), "[::1]:8080"),
2327
("http", None, ("unix/socket", None), "unix/socket"),
2428
("http", "spam", ("eggs", 80), "spam"),
2529
],

tests/test_datastructures.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
from __future__ import annotations
2+
13
import io
24
import pickle
35
import tempfile
6+
import typing as t
47
from contextlib import contextmanager
58
from copy import copy
69
from copy import deepcopy
@@ -43,7 +46,7 @@ def items(self, multi=1):
4346

4447

4548
class _MutableMultiDictTests:
46-
storage_class: type["ds.MultiDict"]
49+
storage_class: type[ds.MultiDict]
4750

4851
def test_pickle(self):
4952
cls = self.storage_class
@@ -1280,3 +1283,15 @@ def test_range_to_header(ranges):
12801283
def test_range_validates_ranges(ranges):
12811284
with pytest.raises(ValueError):
12821285
ds.Range("bytes", ranges)
1286+
1287+
1288+
@pytest.mark.parametrize(
1289+
("value", "expect"),
1290+
[
1291+
({"a": "ab"}, [("a", "ab")]),
1292+
({"a": ["a", "b"]}, [("a", "a"), ("a", "b")]),
1293+
({"a": b"ab"}, [("a", b"ab")]),
1294+
],
1295+
)
1296+
def test_iter_multi_data(value: t.Any, expect: list[tuple[t.Any, t.Any]]) -> None:
1297+
assert list(ds.iter_multi_items(value)) == expect

0 commit comments

Comments
 (0)