Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/datastructures/headers.py: 58%
251 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-09 06:08 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-09 06:08 +0000
1from __future__ import annotations
3import re
4import typing as t
5import warnings
7from .._internal import _missing
8from ..exceptions import BadRequestKeyError
9from .mixins import ImmutableHeadersMixin
10from .structures import iter_multi_items
11from .structures import MultiDict
14class Headers:
15 """An object that stores some headers. It has a dict-like interface,
16 but is ordered, can store the same key multiple times, and iterating
17 yields ``(key, value)`` pairs instead of only keys.
19 This data structure is useful if you want a nicer way to handle WSGI
20 headers which are stored as tuples in a list.
22 From Werkzeug 0.3 onwards, the :exc:`KeyError` raised by this class is
23 also a subclass of the :class:`~exceptions.BadRequest` HTTP exception
24 and will render a page for a ``400 BAD REQUEST`` if caught in a
25 catch-all for HTTP exceptions.
27 Headers is mostly compatible with the Python :class:`wsgiref.headers.Headers`
28 class, with the exception of `__getitem__`. :mod:`wsgiref` will return
29 `None` for ``headers['missing']``, whereas :class:`Headers` will raise
30 a :class:`KeyError`.
32 To create a new ``Headers`` object, pass it a list, dict, or
33 other ``Headers`` object with default values. These values are
34 validated the same way values added later are.
36 :param defaults: The list of default values for the :class:`Headers`.
38 .. versionchanged:: 2.1.0
39 Default values are validated the same as values added later.
41 .. versionchanged:: 0.9
42 This data structure now stores unicode values similar to how the
43 multi dicts do it. The main difference is that bytes can be set as
44 well which will automatically be latin1 decoded.
46 .. versionchanged:: 0.9
47 The :meth:`linked` function was removed without replacement as it
48 was an API that does not support the changes to the encoding model.
49 """
51 def __init__(self, defaults=None):
52 self._list = []
53 if defaults is not None:
54 self.extend(defaults)
56 def __getitem__(self, key, _get_mode=False):
57 if not _get_mode:
58 if isinstance(key, int):
59 return self._list[key]
60 elif isinstance(key, slice):
61 return self.__class__(self._list[key])
62 if not isinstance(key, str):
63 raise BadRequestKeyError(key)
64 ikey = key.lower()
65 for k, v in self._list:
66 if k.lower() == ikey:
67 return v
68 # micro optimization: if we are in get mode we will catch that
69 # exception one stack level down so we can raise a standard
70 # key error instead of our special one.
71 if _get_mode:
72 raise KeyError()
73 raise BadRequestKeyError(key)
75 def __eq__(self, other):
76 def lowered(item):
77 return (item[0].lower(),) + item[1:]
79 return other.__class__ is self.__class__ and set(
80 map(lowered, other._list)
81 ) == set(map(lowered, self._list))
83 __hash__ = None
85 def get(self, key, default=None, type=None, as_bytes=None):
86 """Return the default value if the requested data doesn't exist.
87 If `type` is provided and is a callable it should convert the value,
88 return it or raise a :exc:`ValueError` if that is not possible. In
89 this case the function will return the default as if the value was not
90 found:
92 >>> d = Headers([('Content-Length', '42')])
93 >>> d.get('Content-Length', type=int)
94 42
96 :param key: The key to be looked up.
97 :param default: The default value to be returned if the key can't
98 be looked up. If not further specified `None` is
99 returned.
100 :param type: A callable that is used to cast the value in the
101 :class:`Headers`. If a :exc:`ValueError` is raised
102 by this callable the default value is returned.
104 .. versionchanged:: 2.3
105 The ``as_bytes`` parameter is deprecated and will be removed
106 in Werkzeug 3.0.
108 .. versionchanged:: 0.9
109 The ``as_bytes`` parameter was added.
110 """
111 if as_bytes is not None:
112 warnings.warn(
113 "The 'as_bytes' parameter is deprecated and will be"
114 " removed in Werkzeug 3.0.",
115 DeprecationWarning,
116 stacklevel=2,
117 )
119 try:
120 rv = self.__getitem__(key, _get_mode=True)
121 except KeyError:
122 return default
123 if as_bytes:
124 rv = rv.encode("latin1")
125 if type is None:
126 return rv
127 try:
128 return type(rv)
129 except ValueError:
130 return default
132 def getlist(self, key, type=None, as_bytes=None):
133 """Return the list of items for a given key. If that key is not in the
134 :class:`Headers`, the return value will be an empty list. Just like
135 :meth:`get`, :meth:`getlist` accepts a `type` parameter. All items will
136 be converted with the callable defined there.
138 :param key: The key to be looked up.
139 :param type: A callable that is used to cast the value in the
140 :class:`Headers`. If a :exc:`ValueError` is raised
141 by this callable the value will be removed from the list.
142 :return: a :class:`list` of all the values for the key.
144 .. versionchanged:: 2.3
145 The ``as_bytes`` parameter is deprecated and will be removed
146 in Werkzeug 3.0.
148 .. versionchanged:: 0.9
149 The ``as_bytes`` parameter was added.
150 """
151 if as_bytes is not None:
152 warnings.warn(
153 "The 'as_bytes' parameter is deprecated and will be"
154 " removed in Werkzeug 3.0.",
155 DeprecationWarning,
156 stacklevel=2,
157 )
159 ikey = key.lower()
160 result = []
161 for k, v in self:
162 if k.lower() == ikey:
163 if as_bytes:
164 v = v.encode("latin1")
165 if type is not None:
166 try:
167 v = type(v)
168 except ValueError:
169 continue
170 result.append(v)
171 return result
173 def get_all(self, name):
174 """Return a list of all the values for the named field.
176 This method is compatible with the :mod:`wsgiref`
177 :meth:`~wsgiref.headers.Headers.get_all` method.
178 """
179 return self.getlist(name)
181 def items(self, lower=False):
182 for key, value in self:
183 if lower:
184 key = key.lower()
185 yield key, value
187 def keys(self, lower=False):
188 for key, _ in self.items(lower):
189 yield key
191 def values(self):
192 for _, value in self.items():
193 yield value
195 def extend(self, *args, **kwargs):
196 """Extend headers in this object with items from another object
197 containing header items as well as keyword arguments.
199 To replace existing keys instead of extending, use
200 :meth:`update` instead.
202 If provided, the first argument can be another :class:`Headers`
203 object, a :class:`MultiDict`, :class:`dict`, or iterable of
204 pairs.
206 .. versionchanged:: 1.0
207 Support :class:`MultiDict`. Allow passing ``kwargs``.
208 """
209 if len(args) > 1:
210 raise TypeError(f"update expected at most 1 arguments, got {len(args)}")
212 if args:
213 for key, value in iter_multi_items(args[0]):
214 self.add(key, value)
216 for key, value in iter_multi_items(kwargs):
217 self.add(key, value)
219 def __delitem__(self, key, _index_operation=True):
220 if _index_operation and isinstance(key, (int, slice)):
221 del self._list[key]
222 return
223 key = key.lower()
224 new = []
225 for k, v in self._list:
226 if k.lower() != key:
227 new.append((k, v))
228 self._list[:] = new
230 def remove(self, key):
231 """Remove a key.
233 :param key: The key to be removed.
234 """
235 return self.__delitem__(key, _index_operation=False)
237 def pop(self, key=None, default=_missing):
238 """Removes and returns a key or index.
240 :param key: The key to be popped. If this is an integer the item at
241 that position is removed, if it's a string the value for
242 that key is. If the key is omitted or `None` the last
243 item is removed.
244 :return: an item.
245 """
246 if key is None:
247 return self._list.pop()
248 if isinstance(key, int):
249 return self._list.pop(key)
250 try:
251 rv = self[key]
252 self.remove(key)
253 except KeyError:
254 if default is not _missing:
255 return default
256 raise
257 return rv
259 def popitem(self):
260 """Removes a key or index and returns a (key, value) item."""
261 return self.pop()
263 def __contains__(self, key):
264 """Check if a key is present."""
265 try:
266 self.__getitem__(key, _get_mode=True)
267 except KeyError:
268 return False
269 return True
271 def __iter__(self):
272 """Yield ``(key, value)`` tuples."""
273 return iter(self._list)
275 def __len__(self):
276 return len(self._list)
278 def add(self, _key, _value, **kw):
279 """Add a new header tuple to the list.
281 Keyword arguments can specify additional parameters for the header
282 value, with underscores converted to dashes::
284 >>> d = Headers()
285 >>> d.add('Content-Type', 'text/plain')
286 >>> d.add('Content-Disposition', 'attachment', filename='foo.png')
288 The keyword argument dumping uses :func:`dump_options_header`
289 behind the scenes.
291 .. versionadded:: 0.4.1
292 keyword arguments were added for :mod:`wsgiref` compatibility.
293 """
294 if kw:
295 _value = _options_header_vkw(_value, kw)
296 _key = _str_header_key(_key)
297 _value = _str_header_value(_value)
298 self._list.append((_key, _value))
300 def add_header(self, _key, _value, **_kw):
301 """Add a new header tuple to the list.
303 An alias for :meth:`add` for compatibility with the :mod:`wsgiref`
304 :meth:`~wsgiref.headers.Headers.add_header` method.
305 """
306 self.add(_key, _value, **_kw)
308 def clear(self):
309 """Clears all headers."""
310 del self._list[:]
312 def set(self, _key, _value, **kw):
313 """Remove all header tuples for `key` and add a new one. The newly
314 added key either appears at the end of the list if there was no
315 entry or replaces the first one.
317 Keyword arguments can specify additional parameters for the header
318 value, with underscores converted to dashes. See :meth:`add` for
319 more information.
321 .. versionchanged:: 0.6.1
322 :meth:`set` now accepts the same arguments as :meth:`add`.
324 :param key: The key to be inserted.
325 :param value: The value to be inserted.
326 """
327 if kw:
328 _value = _options_header_vkw(_value, kw)
329 _key = _str_header_key(_key)
330 _value = _str_header_value(_value)
331 if not self._list:
332 self._list.append((_key, _value))
333 return
334 listiter = iter(self._list)
335 ikey = _key.lower()
336 for idx, (old_key, _old_value) in enumerate(listiter):
337 if old_key.lower() == ikey:
338 # replace first occurrence
339 self._list[idx] = (_key, _value)
340 break
341 else:
342 self._list.append((_key, _value))
343 return
344 self._list[idx + 1 :] = [t for t in listiter if t[0].lower() != ikey]
346 def setlist(self, key, values):
347 """Remove any existing values for a header and add new ones.
349 :param key: The header key to set.
350 :param values: An iterable of values to set for the key.
352 .. versionadded:: 1.0
353 """
354 if values:
355 values_iter = iter(values)
356 self.set(key, next(values_iter))
358 for value in values_iter:
359 self.add(key, value)
360 else:
361 self.remove(key)
363 def setdefault(self, key, default):
364 """Return the first value for the key if it is in the headers,
365 otherwise set the header to the value given by ``default`` and
366 return that.
368 :param key: The header key to get.
369 :param default: The value to set for the key if it is not in the
370 headers.
371 """
372 if key in self:
373 return self[key]
375 self.set(key, default)
376 return default
378 def setlistdefault(self, key, default):
379 """Return the list of values for the key if it is in the
380 headers, otherwise set the header to the list of values given
381 by ``default`` and return that.
383 Unlike :meth:`MultiDict.setlistdefault`, modifying the returned
384 list will not affect the headers.
386 :param key: The header key to get.
387 :param default: An iterable of values to set for the key if it
388 is not in the headers.
390 .. versionadded:: 1.0
391 """
392 if key not in self:
393 self.setlist(key, default)
395 return self.getlist(key)
397 def __setitem__(self, key, value):
398 """Like :meth:`set` but also supports index/slice based setting."""
399 if isinstance(key, (slice, int)):
400 if isinstance(key, int):
401 value = [value]
402 value = [(_str_header_key(k), _str_header_value(v)) for (k, v) in value]
403 if isinstance(key, int):
404 self._list[key] = value[0]
405 else:
406 self._list[key] = value
407 else:
408 self.set(key, value)
410 def update(self, *args, **kwargs):
411 """Replace headers in this object with items from another
412 headers object and keyword arguments.
414 To extend existing keys instead of replacing, use :meth:`extend`
415 instead.
417 If provided, the first argument can be another :class:`Headers`
418 object, a :class:`MultiDict`, :class:`dict`, or iterable of
419 pairs.
421 .. versionadded:: 1.0
422 """
423 if len(args) > 1:
424 raise TypeError(f"update expected at most 1 arguments, got {len(args)}")
426 if args:
427 mapping = args[0]
429 if isinstance(mapping, (Headers, MultiDict)):
430 for key in mapping.keys():
431 self.setlist(key, mapping.getlist(key))
432 elif isinstance(mapping, dict):
433 for key, value in mapping.items():
434 if isinstance(value, (list, tuple)):
435 self.setlist(key, value)
436 else:
437 self.set(key, value)
438 else:
439 for key, value in mapping:
440 self.set(key, value)
442 for key, value in kwargs.items():
443 if isinstance(value, (list, tuple)):
444 self.setlist(key, value)
445 else:
446 self.set(key, value)
448 def to_wsgi_list(self):
449 """Convert the headers into a list suitable for WSGI.
451 :return: list
452 """
453 return list(self)
455 def copy(self):
456 return self.__class__(self._list)
458 def __copy__(self):
459 return self.copy()
461 def __str__(self):
462 """Returns formatted headers suitable for HTTP transmission."""
463 strs = []
464 for key, value in self.to_wsgi_list():
465 strs.append(f"{key}: {value}")
466 strs.append("\r\n")
467 return "\r\n".join(strs)
469 def __repr__(self):
470 return f"{type(self).__name__}({list(self)!r})"
473def _options_header_vkw(value: str, kw: dict[str, t.Any]):
474 return http.dump_options_header(
475 value, {k.replace("_", "-"): v for k, v in kw.items()}
476 )
479def _str_header_key(key: t.Any) -> str:
480 if not isinstance(key, str):
481 warnings.warn(
482 "Header keys must be strings. Passing other types is deprecated and will"
483 " not be supported in Werkzeug 3.0.",
484 DeprecationWarning,
485 stacklevel=2,
486 )
488 if isinstance(key, bytes):
489 key = key.decode("latin-1")
490 else:
491 key = str(key)
493 return key
496_newline_re = re.compile(r"[\r\n]")
499def _str_header_value(value: t.Any) -> str:
500 if isinstance(value, bytes):
501 warnings.warn(
502 "Passing bytes as a header value is deprecated and will not be supported in"
503 " Werkzeug 3.0.",
504 DeprecationWarning,
505 stacklevel=2,
506 )
507 value = value.decode("latin-1")
509 if not isinstance(value, str):
510 value = str(value)
512 if _newline_re.search(value) is not None:
513 raise ValueError("Header values must not contain newline characters.")
515 return value
518class EnvironHeaders(ImmutableHeadersMixin, Headers):
519 """Read only version of the headers from a WSGI environment. This
520 provides the same interface as `Headers` and is constructed from
521 a WSGI environment.
522 From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
523 subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will
524 render a page for a ``400 BAD REQUEST`` if caught in a catch-all for
525 HTTP exceptions.
526 """
528 def __init__(self, environ):
529 self.environ = environ
531 def __eq__(self, other):
532 return self.environ is other.environ
534 __hash__ = None
536 def __getitem__(self, key, _get_mode=False):
537 # _get_mode is a no-op for this class as there is no index but
538 # used because get() calls it.
539 if not isinstance(key, str):
540 raise KeyError(key)
541 key = key.upper().replace("-", "_")
542 if key in {"CONTENT_TYPE", "CONTENT_LENGTH"}:
543 return self.environ[key]
544 return self.environ[f"HTTP_{key}"]
546 def __len__(self):
547 # the iter is necessary because otherwise list calls our
548 # len which would call list again and so forth.
549 return len(list(iter(self)))
551 def __iter__(self):
552 for key, value in self.environ.items():
553 if key.startswith("HTTP_") and key not in {
554 "HTTP_CONTENT_TYPE",
555 "HTTP_CONTENT_LENGTH",
556 }:
557 yield key[5:].replace("_", "-").title(), value
558 elif key in {"CONTENT_TYPE", "CONTENT_LENGTH"} and value:
559 yield key.replace("_", "-").title(), value
561 def copy(self):
562 raise TypeError(f"cannot create {type(self).__name__!r} copies")
565# circular dependencies
566from .. import http