Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/datastructures/headers.py: 54%
230 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 07:17 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 07:17 +0000
1from __future__ import annotations
3import re
4import typing as t
6from .._internal import _missing
7from ..exceptions import BadRequestKeyError
8from .mixins import ImmutableHeadersMixin
9from .structures import iter_multi_items
10from .structures import MultiDict
13class Headers:
14 """An object that stores some headers. It has a dict-like interface,
15 but is ordered, can store the same key multiple times, and iterating
16 yields ``(key, value)`` pairs instead of only keys.
18 This data structure is useful if you want a nicer way to handle WSGI
19 headers which are stored as tuples in a list.
21 From Werkzeug 0.3 onwards, the :exc:`KeyError` raised by this class is
22 also a subclass of the :class:`~exceptions.BadRequest` HTTP exception
23 and will render a page for a ``400 BAD REQUEST`` if caught in a
24 catch-all for HTTP exceptions.
26 Headers is mostly compatible with the Python :class:`wsgiref.headers.Headers`
27 class, with the exception of `__getitem__`. :mod:`wsgiref` will return
28 `None` for ``headers['missing']``, whereas :class:`Headers` will raise
29 a :class:`KeyError`.
31 To create a new ``Headers`` object, pass it a list, dict, or
32 other ``Headers`` object with default values. These values are
33 validated the same way values added later are.
35 :param defaults: The list of default values for the :class:`Headers`.
37 .. versionchanged:: 2.1.0
38 Default values are validated the same as values added later.
40 .. versionchanged:: 0.9
41 This data structure now stores unicode values similar to how the
42 multi dicts do it. The main difference is that bytes can be set as
43 well which will automatically be latin1 decoded.
45 .. versionchanged:: 0.9
46 The :meth:`linked` function was removed without replacement as it
47 was an API that does not support the changes to the encoding model.
48 """
50 def __init__(self, defaults=None):
51 self._list = []
52 if defaults is not None:
53 self.extend(defaults)
55 def __getitem__(self, key, _get_mode=False):
56 if not _get_mode:
57 if isinstance(key, int):
58 return self._list[key]
59 elif isinstance(key, slice):
60 return self.__class__(self._list[key])
61 if not isinstance(key, str):
62 raise BadRequestKeyError(key)
63 ikey = key.lower()
64 for k, v in self._list:
65 if k.lower() == ikey:
66 return v
67 # micro optimization: if we are in get mode we will catch that
68 # exception one stack level down so we can raise a standard
69 # key error instead of our special one.
70 if _get_mode:
71 raise KeyError()
72 raise BadRequestKeyError(key)
74 def __eq__(self, other):
75 def lowered(item):
76 return (item[0].lower(),) + item[1:]
78 return other.__class__ is self.__class__ and set(
79 map(lowered, other._list)
80 ) == set(map(lowered, self._list))
82 __hash__ = None
84 def get(self, key, default=None, type=None):
85 """Return the default value if the requested data doesn't exist.
86 If `type` is provided and is a callable it should convert the value,
87 return it or raise a :exc:`ValueError` if that is not possible. In
88 this case the function will return the default as if the value was not
89 found:
91 >>> d = Headers([('Content-Length', '42')])
92 >>> d.get('Content-Length', type=int)
93 42
95 :param key: The key to be looked up.
96 :param default: The default value to be returned if the key can't
97 be looked up. If not further specified `None` is
98 returned.
99 :param type: A callable that is used to cast the value in the
100 :class:`Headers`. If a :exc:`ValueError` is raised
101 by this callable the default value is returned.
103 .. versionchanged:: 3.0
104 The ``as_bytes`` parameter was removed.
106 .. versionchanged:: 0.9
107 The ``as_bytes`` parameter was added.
108 """
109 try:
110 rv = self.__getitem__(key, _get_mode=True)
111 except KeyError:
112 return default
113 if type is None:
114 return rv
115 try:
116 return type(rv)
117 except ValueError:
118 return default
120 def getlist(self, key, type=None):
121 """Return the list of items for a given key. If that key is not in the
122 :class:`Headers`, the return value will be an empty list. Just like
123 :meth:`get`, :meth:`getlist` accepts a `type` parameter. All items will
124 be converted with the callable defined there.
126 :param key: The key to be looked up.
127 :param type: A callable that is used to cast the value in the
128 :class:`Headers`. If a :exc:`ValueError` is raised
129 by this callable the value will be removed from the list.
130 :return: a :class:`list` of all the values for the key.
132 .. versionchanged:: 3.0
133 The ``as_bytes`` parameter was removed.
135 .. versionchanged:: 0.9
136 The ``as_bytes`` parameter was added.
137 """
138 ikey = key.lower()
139 result = []
140 for k, v in self:
141 if k.lower() == ikey:
142 if type is not None:
143 try:
144 v = type(v)
145 except ValueError:
146 continue
147 result.append(v)
148 return result
150 def get_all(self, name):
151 """Return a list of all the values for the named field.
153 This method is compatible with the :mod:`wsgiref`
154 :meth:`~wsgiref.headers.Headers.get_all` method.
155 """
156 return self.getlist(name)
158 def items(self, lower=False):
159 for key, value in self:
160 if lower:
161 key = key.lower()
162 yield key, value
164 def keys(self, lower=False):
165 for key, _ in self.items(lower):
166 yield key
168 def values(self):
169 for _, value in self.items():
170 yield value
172 def extend(self, *args, **kwargs):
173 """Extend headers in this object with items from another object
174 containing header items as well as keyword arguments.
176 To replace existing keys instead of extending, use
177 :meth:`update` instead.
179 If provided, the first argument can be another :class:`Headers`
180 object, a :class:`MultiDict`, :class:`dict`, or iterable of
181 pairs.
183 .. versionchanged:: 1.0
184 Support :class:`MultiDict`. Allow passing ``kwargs``.
185 """
186 if len(args) > 1:
187 raise TypeError(f"update expected at most 1 arguments, got {len(args)}")
189 if args:
190 for key, value in iter_multi_items(args[0]):
191 self.add(key, value)
193 for key, value in iter_multi_items(kwargs):
194 self.add(key, value)
196 def __delitem__(self, key, _index_operation=True):
197 if _index_operation and isinstance(key, (int, slice)):
198 del self._list[key]
199 return
200 key = key.lower()
201 new = []
202 for k, v in self._list:
203 if k.lower() != key:
204 new.append((k, v))
205 self._list[:] = new
207 def remove(self, key):
208 """Remove a key.
210 :param key: The key to be removed.
211 """
212 return self.__delitem__(key, _index_operation=False)
214 def pop(self, key=None, default=_missing):
215 """Removes and returns a key or index.
217 :param key: The key to be popped. If this is an integer the item at
218 that position is removed, if it's a string the value for
219 that key is. If the key is omitted or `None` the last
220 item is removed.
221 :return: an item.
222 """
223 if key is None:
224 return self._list.pop()
225 if isinstance(key, int):
226 return self._list.pop(key)
227 try:
228 rv = self[key]
229 self.remove(key)
230 except KeyError:
231 if default is not _missing:
232 return default
233 raise
234 return rv
236 def popitem(self):
237 """Removes a key or index and returns a (key, value) item."""
238 return self.pop()
240 def __contains__(self, key):
241 """Check if a key is present."""
242 try:
243 self.__getitem__(key, _get_mode=True)
244 except KeyError:
245 return False
246 return True
248 def __iter__(self):
249 """Yield ``(key, value)`` tuples."""
250 return iter(self._list)
252 def __len__(self):
253 return len(self._list)
255 def add(self, _key, _value, **kw):
256 """Add a new header tuple to the list.
258 Keyword arguments can specify additional parameters for the header
259 value, with underscores converted to dashes::
261 >>> d = Headers()
262 >>> d.add('Content-Type', 'text/plain')
263 >>> d.add('Content-Disposition', 'attachment', filename='foo.png')
265 The keyword argument dumping uses :func:`dump_options_header`
266 behind the scenes.
268 .. versionadded:: 0.4.1
269 keyword arguments were added for :mod:`wsgiref` compatibility.
270 """
271 if kw:
272 _value = _options_header_vkw(_value, kw)
273 _value = _str_header_value(_value)
274 self._list.append((_key, _value))
276 def add_header(self, _key, _value, **_kw):
277 """Add a new header tuple to the list.
279 An alias for :meth:`add` for compatibility with the :mod:`wsgiref`
280 :meth:`~wsgiref.headers.Headers.add_header` method.
281 """
282 self.add(_key, _value, **_kw)
284 def clear(self):
285 """Clears all headers."""
286 del self._list[:]
288 def set(self, _key, _value, **kw):
289 """Remove all header tuples for `key` and add a new one. The newly
290 added key either appears at the end of the list if there was no
291 entry or replaces the first one.
293 Keyword arguments can specify additional parameters for the header
294 value, with underscores converted to dashes. See :meth:`add` for
295 more information.
297 .. versionchanged:: 0.6.1
298 :meth:`set` now accepts the same arguments as :meth:`add`.
300 :param key: The key to be inserted.
301 :param value: The value to be inserted.
302 """
303 if kw:
304 _value = _options_header_vkw(_value, kw)
305 _value = _str_header_value(_value)
306 if not self._list:
307 self._list.append((_key, _value))
308 return
309 listiter = iter(self._list)
310 ikey = _key.lower()
311 for idx, (old_key, _old_value) in enumerate(listiter):
312 if old_key.lower() == ikey:
313 # replace first occurrence
314 self._list[idx] = (_key, _value)
315 break
316 else:
317 self._list.append((_key, _value))
318 return
319 self._list[idx + 1 :] = [t for t in listiter if t[0].lower() != ikey]
321 def setlist(self, key, values):
322 """Remove any existing values for a header and add new ones.
324 :param key: The header key to set.
325 :param values: An iterable of values to set for the key.
327 .. versionadded:: 1.0
328 """
329 if values:
330 values_iter = iter(values)
331 self.set(key, next(values_iter))
333 for value in values_iter:
334 self.add(key, value)
335 else:
336 self.remove(key)
338 def setdefault(self, key, default):
339 """Return the first value for the key if it is in the headers,
340 otherwise set the header to the value given by ``default`` and
341 return that.
343 :param key: The header key to get.
344 :param default: The value to set for the key if it is not in the
345 headers.
346 """
347 if key in self:
348 return self[key]
350 self.set(key, default)
351 return default
353 def setlistdefault(self, key, default):
354 """Return the list of values for the key if it is in the
355 headers, otherwise set the header to the list of values given
356 by ``default`` and return that.
358 Unlike :meth:`MultiDict.setlistdefault`, modifying the returned
359 list will not affect the headers.
361 :param key: The header key to get.
362 :param default: An iterable of values to set for the key if it
363 is not in the headers.
365 .. versionadded:: 1.0
366 """
367 if key not in self:
368 self.setlist(key, default)
370 return self.getlist(key)
372 def __setitem__(self, key, value):
373 """Like :meth:`set` but also supports index/slice based setting."""
374 if isinstance(key, (slice, int)):
375 if isinstance(key, int):
376 value = [value]
377 value = [(k, _str_header_value(v)) for (k, v) in value]
378 if isinstance(key, int):
379 self._list[key] = value[0]
380 else:
381 self._list[key] = value
382 else:
383 self.set(key, value)
385 def update(self, *args, **kwargs):
386 """Replace headers in this object with items from another
387 headers object and keyword arguments.
389 To extend existing keys instead of replacing, use :meth:`extend`
390 instead.
392 If provided, the first argument can be another :class:`Headers`
393 object, a :class:`MultiDict`, :class:`dict`, or iterable of
394 pairs.
396 .. versionadded:: 1.0
397 """
398 if len(args) > 1:
399 raise TypeError(f"update expected at most 1 arguments, got {len(args)}")
401 if args:
402 mapping = args[0]
404 if isinstance(mapping, (Headers, MultiDict)):
405 for key in mapping.keys():
406 self.setlist(key, mapping.getlist(key))
407 elif isinstance(mapping, dict):
408 for key, value in mapping.items():
409 if isinstance(value, (list, tuple)):
410 self.setlist(key, value)
411 else:
412 self.set(key, value)
413 else:
414 for key, value in mapping:
415 self.set(key, value)
417 for key, value in kwargs.items():
418 if isinstance(value, (list, tuple)):
419 self.setlist(key, value)
420 else:
421 self.set(key, value)
423 def to_wsgi_list(self):
424 """Convert the headers into a list suitable for WSGI.
426 :return: list
427 """
428 return list(self)
430 def copy(self):
431 return self.__class__(self._list)
433 def __copy__(self):
434 return self.copy()
436 def __str__(self):
437 """Returns formatted headers suitable for HTTP transmission."""
438 strs = []
439 for key, value in self.to_wsgi_list():
440 strs.append(f"{key}: {value}")
441 strs.append("\r\n")
442 return "\r\n".join(strs)
444 def __repr__(self):
445 return f"{type(self).__name__}({list(self)!r})"
448def _options_header_vkw(value: str, kw: dict[str, t.Any]):
449 return http.dump_options_header(
450 value, {k.replace("_", "-"): v for k, v in kw.items()}
451 )
454_newline_re = re.compile(r"[\r\n]")
457def _str_header_value(value: t.Any) -> str:
458 if not isinstance(value, str):
459 value = str(value)
461 if _newline_re.search(value) is not None:
462 raise ValueError("Header values must not contain newline characters.")
464 return value
467class EnvironHeaders(ImmutableHeadersMixin, Headers):
468 """Read only version of the headers from a WSGI environment. This
469 provides the same interface as `Headers` and is constructed from
470 a WSGI environment.
471 From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
472 subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will
473 render a page for a ``400 BAD REQUEST`` if caught in a catch-all for
474 HTTP exceptions.
475 """
477 def __init__(self, environ):
478 self.environ = environ
480 def __eq__(self, other):
481 return self.environ is other.environ
483 __hash__ = None
485 def __getitem__(self, key, _get_mode=False):
486 # _get_mode is a no-op for this class as there is no index but
487 # used because get() calls it.
488 if not isinstance(key, str):
489 raise KeyError(key)
490 key = key.upper().replace("-", "_")
491 if key in {"CONTENT_TYPE", "CONTENT_LENGTH"}:
492 return self.environ[key]
493 return self.environ[f"HTTP_{key}"]
495 def __len__(self):
496 # the iter is necessary because otherwise list calls our
497 # len which would call list again and so forth.
498 return len(list(iter(self)))
500 def __iter__(self):
501 for key, value in self.environ.items():
502 if key.startswith("HTTP_") and key not in {
503 "HTTP_CONTENT_TYPE",
504 "HTTP_CONTENT_LENGTH",
505 }:
506 yield key[5:].replace("_", "-").title(), value
507 elif key in {"CONTENT_TYPE", "CONTENT_LENGTH"} and value:
508 yield key.replace("_", "-").title(), value
510 def copy(self):
511 raise TypeError(f"cannot create {type(self).__name__!r} copies")
514# circular dependencies
515from .. import http