Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/wsgi.py: 26%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import io
4import typing as t
5import wsgiref.types as wt
6from functools import partial
7from functools import update_wrapper
9from ._internal import _wsgi_decoding_dance
10from .exceptions import ClientDisconnected
11from .exceptions import RequestEntityTooLarge
12from .sansio import utils as _sansio_utils
15def _responder(f: t.Callable[..., wt.WSGIApplication]) -> wt.WSGIApplication:
16 """Marks a function as responder. Decorate a function with it and it
17 will automatically call the return value as WSGI application.
19 Example::
21 @responder
22 def application(environ, start_response):
23 return Response('Hello World!')
25 .. deprecated:: 3.2
26 Will be removed in Werkzeug 4.0. Use ``Request.application`` instead.
27 """
28 return update_wrapper(lambda *a: f(*a)(*a[-2:]), f)
31def _get_current_url(
32 environ: wt.WSGIEnvironment,
33 root_only: bool = False,
34 strip_querystring: bool = False,
35 host_only: bool = False,
36 trusted_hosts: t.Collection[str] | None = None,
37) -> str:
38 """Recreate the URL for a request from the parts in a WSGI
39 environment.
41 The URL is an IRI, not a URI, so it may contain Unicode characters.
42 Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII.
44 :param environ: The WSGI environment to get the URL parts from.
45 :param root_only: Only build the root path, don't include the
46 remaining path or query string.
47 :param strip_querystring: Don't include the query string.
48 :param host_only: Only build the scheme and host.
49 :param trusted_hosts: A list of trusted host names to validate the
50 host against.
52 .. deprecated:: 3.2
53 Will be removed in Werkzeug 4.0. Use ``request.url``, ``base_url``,
54 ``root_url``, or ``host_url`` instead.
55 """
56 parts = {
57 "scheme": environ["wsgi.url_scheme"],
58 "host": _get_host(environ, trusted_hosts),
59 }
61 if not host_only:
62 parts["root_path"] = environ.get("SCRIPT_NAME", "")
64 if not root_only:
65 parts["path"] = environ.get("PATH_INFO", "")
67 if not strip_querystring:
68 parts["query_string"] = environ.get("QUERY_STRING", "").encode("latin1")
70 return _sansio_utils.get_current_url(**parts)
73def _get_server(
74 environ: wt.WSGIEnvironment,
75) -> tuple[str, int | None] | None:
76 name = environ.get("SERVER_NAME")
78 if name is None:
79 return None
81 try:
82 port: int | None = int(environ.get("SERVER_PORT", None)) # type: ignore[arg-type]
83 except (TypeError, ValueError):
84 # unix socket
85 port = None
87 return name, port
90def _get_host(
91 environ: wt.WSGIEnvironment, trusted_hosts: t.Collection[str] | None = None
92) -> str:
93 """Get and validate a request's ``host:port`` based on the values in the
94 given WSGI environ.
96 The ``Host`` header sent by the client is preferred. Otherwise, the server's
97 configured address is used. If the server address is a Unix socket, it is
98 ignored. The port is omitted if it matches the standard HTTP or HTTPS ports.
100 The value is passed through :func:`host_is_trusted`. The host must be made
101 up of valid characters, but this does not check validity beyond that. If a
102 list of trusted domains is given, the domain must match one.
104 :param environ: The WSGI environ.
105 :param trusted_hosts: A list of trusted domains to match. These should
106 already be IDNA encoded, but will be encoded if needed. The port is
107 ignored for this check. If a name starts with a dot it will match as a
108 suffix, accepting all subdomains. If empty or ``None``, all domains are
109 allowed.
111 :return: Host, with port if necessary.
112 :raise .SecurityError: If the host is not trusted.
114 .. deprecated:: 3.2
115 Will be removed in Werkzeug 4.0. Use ``Request.trusted_hosts`` and
116 ``Request.host`` instead.
118 .. versionchanged:: 3.2
119 When using the server address, Unix sockets are ignored.
121 .. versionchanged:: 3.1.8
122 The empty string is again returned if no host header value is available,
123 or if the characters are invalid.
125 .. versionchanged:: 3.1.7
126 The characters of the host value are validated. The empty string is no
127 longer allowed if no header value is available.
129 .. versionchanged:: 3.1.3
130 If ``SERVER_NAME`` is IPv6, it is wrapped in ``[]``.
131 """
132 return _sansio_utils.get_host(
133 environ["wsgi.url_scheme"],
134 environ.get("HTTP_HOST"),
135 _get_server(environ),
136 trusted_hosts,
137 )
140def _get_content_length(environ: wt.WSGIEnvironment) -> int | None:
141 """Return the ``Content-Length`` header value as an int. If the header is not given
142 or the ``Transfer-Encoding`` header is ``chunked``, ``None`` is returned to indicate
143 a streaming request. If the value is not an integer, or negative, 0 is returned.
145 :param environ: The WSGI environ to get the content length from.
147 .. deprecated:: 3.2
148 Will be removed in Werkzeug 4.0. Use ``Request.content_length`` instead.
150 .. versionadded:: 0.9
151 """
152 return _sansio_utils._get_content_length(
153 http_content_length=environ.get("CONTENT_LENGTH"),
154 http_transfer_encoding=environ.get("HTTP_TRANSFER_ENCODING"),
155 )
158def _get_input_stream(
159 environ: wt.WSGIEnvironment,
160 safe_fallback: bool = True,
161 max_content_length: int | None = None,
162) -> t.IO[bytes]:
163 """Return the WSGI input stream, wrapped so that it may be read safely
164 without going past the ``Content-Length`` header value or
165 ``max_content_length``. This must be used to pass a safe stream to other
166 functions, which are not responsible for enforcing this limit.
168 If ``Content-Length`` exceeds ``max_content_length``, a
169 :exc:`RequestEntityTooLarge`` ``413 Content Too Large`` error is raised.
171 If the WSGI server sets ``environ["wsgi.input_terminated"]``, it indicates that the
172 server handles terminating the stream, so it is safe to read directly. For example,
173 a server that knows how to handle chunked requests safely would set this.
175 If ``max_content_length`` is set, it can be enforced on streams if
176 ``wsgi.input_terminated`` is set. Otherwise, an empty stream is returned unless the
177 user explicitly disables this safe fallback.
179 If the limit is reached before the underlying stream is exhausted (such as a file
180 that is too large, or an infinite stream), the remaining contents of the stream
181 cannot be read safely. Depending on how the server handles this, clients may show a
182 "connection reset" failure instead of seeing the 413 response.
184 :param environ: The WSGI environ containing the stream.
185 :param safe_fallback: Return an empty stream when ``Content-Length`` is not set.
186 Disabling this allows infinite streams, which can be a denial-of-service risk.
187 :param max_content_length: The maximum length that content-length or streaming
188 requests may not exceed.
190 .. deprecated:: 3.2
191 Will be removed in Werkzeug 4.0. Use ``request.stream`` instead.
193 .. versionchanged:: 2.3.2
194 ``max_content_length`` is only applied to streaming requests if the server sets
195 ``wsgi.input_terminated``.
197 .. versionchanged:: 2.3
198 Check ``max_content_length`` and raise an error if it is exceeded.
200 .. versionadded:: 0.9
201 """
202 from .wrappers.request import Request
204 request = Request(environ)
205 request.max_content_length = max_content_length
206 stream = request.stream
208 if (
209 not safe_fallback
210 and isinstance(stream, io.BytesIO)
211 and stream.getbuffer().nbytes == 0
212 ):
213 return environ["wsgi.input_stream"] # type: ignore[no-any-return]
215 return stream
218def _get_path_info(environ: wt.WSGIEnvironment) -> str:
219 """Return ``PATH_INFO`` from the WSGI environment.
221 :param environ: WSGI environment to get the path from.
223 .. deprecated:: 3.2
224 Will be removed in Werkzeug 4.0. Use ``request.path`` instead.
226 .. versionchanged:: 3.0
227 The ``charset`` and ``errors`` parameters were removed.
229 .. versionadded:: 0.9
230 """
231 return _wsgi_decoding_dance(environ.get("PATH_INFO", ""))
234class ClosingIterator:
235 """Wrap an iterable that may or may not have a ``close`` method, adding that
236 and additional functions to its own ``close`` method.
238 Rather than using this directly, build a :class:`.Response` and use its
239 :meth:`~.Response.call_on_close` method to add cleanup functions. It will
240 handle creating the closing iterator.
242 If a WSGI application returns an iterable with a ``close`` method, it
243 will be called by the server at the end of the response. This class can be
244 used to add additional cleanup when receiving an iterable from some other
245 code.
247 This does not handle the case where an exception interrupts the application
248 before it returns the iterable. A higher level wrapper to handle safe
249 execution and resource cleanup is needed.
250 """
252 def __init__(
253 self,
254 iterable: t.Iterable[bytes],
255 callbacks: None
256 | (t.Callable[[], None] | t.Iterable[t.Callable[[], None]]) = None,
257 ) -> None:
258 iterator = iter(iterable)
259 self._next = t.cast(t.Callable[[], bytes], partial(next, iterator))
260 if callbacks is None:
261 callbacks = []
262 elif callable(callbacks):
263 callbacks = [callbacks]
264 else:
265 callbacks = list(callbacks)
266 iterable_close = getattr(iterable, "close", None)
267 if iterable_close:
268 callbacks.insert(0, iterable_close)
269 self._callbacks = callbacks
271 def __iter__(self) -> ClosingIterator:
272 return self
274 def __next__(self) -> bytes:
275 return self._next()
277 def close(self) -> None:
278 for callback in self._callbacks:
279 callback()
282def _wrap_file(
283 environ: wt.WSGIEnvironment, file: t.IO[bytes], buffer_size: int = 128 * 1024
284) -> t.Iterable[bytes]:
285 """Wrap a file with the ``wsgi.file_wrapper`` provided by the WSGI server.
286 The WSGI server provides this if it has a way to send file data more
287 efficiently. If it's not provided, uses a basic wrapper for consistent
288 iteration.
290 You'll also want to set :attr:`.Response.direct_passthrough` to ``True`` to
291 tell the response not to do its own iteration.
293 Use :attr:`send_file` instead of generating a file response manually. It
294 will handle caching, range requests, wrapping, and more.
296 :param file: A file-like object in ``rb`` mode.
297 :param buffer_size: Number of bytes to read for each iteration.
299 .. deprecated:: 3.2
300 Will be removed in Werkzeug 4.0. Use ``Request.wrap_file`` instead.
302 .. versionchanged:: 3.2
303 Increase default ``buffer_size`` from 8 KiB to 128 KiB.
305 .. versionadded:: 0.5
306 """
307 cls: wt.FileWrapper = environ.get("wsgi.file_wrapper", _FileWrapper)
308 return cls(file, buffer_size)
311class _FileWrapper:
312 """Iterate over a binary file in predictable chunks instead of arbitrary
313 lines. Used as the default by :func:`wrap_file` if the WSGI server does not
314 provide its own wrapper.
316 :param file: A file in ``rb`` mode.
317 :param buffer_size: Number of bytes to read for each iteration.
319 .. deprecated:: 3.2
320 Will be removed in Werkzeug 4.0. Use ``wrap_file`` instead.
322 .. versionchanged:: 3.2
323 Increase default ``buffer_size`` from 8 KiB to 128 KiB.
325 .. versionadded:: 0.5
326 """
328 def __init__(self, file: t.IO[bytes], buffer_size: int = 128 * 1024) -> None:
329 self.file = file
330 self.buffer_size = buffer_size
332 def close(self) -> None:
333 try:
334 self.file.close()
335 except AttributeError:
336 pass
338 def __iter__(self) -> t.Iterator[bytes]:
339 return self
341 def __next__(self) -> bytes:
342 if data := self.file.read(self.buffer_size):
343 return data
345 raise StopIteration()
348class _RangeWrapper:
349 """This class can be used to convert an iterable object into
350 an iterable that will only yield a piece of the underlying content.
351 It yields blocks until the underlying stream range is fully read.
352 The yielded blocks will have a size that can't exceed the original
353 iterator defined block size, but that can be smaller.
355 If you're using this object together with a :class:`Response` you have
356 to use the `direct_passthrough` mode.
358 :param iterable: an iterable object with a :meth:`__next__` method.
359 :param start_byte: byte from which read will start.
360 :param byte_range: how many bytes to read.
361 """
363 def __init__(
364 self,
365 iterable: t.Iterable[bytes] | t.IO[bytes],
366 start_byte: int = 0,
367 byte_range: int | None = None,
368 ):
369 self.iterable = iter(iterable)
370 self.byte_range = byte_range
371 self.start_byte = start_byte
372 self.end_byte = None
374 if byte_range is not None:
375 self.end_byte = start_byte + byte_range
377 self.read_length = 0
378 self.seekable = hasattr(iterable, "seekable") and iterable.seekable()
379 self.end_reached = False
381 def __iter__(self) -> _RangeWrapper:
382 return self
384 def _next_chunk(self) -> bytes:
385 try:
386 chunk = next(self.iterable)
387 self.read_length += len(chunk)
388 return chunk
389 except StopIteration:
390 self.end_reached = True
391 raise
393 def _first_iteration(self) -> tuple[bytes | None, int]:
394 chunk = None
395 if self.seekable:
396 self.iterable.seek(self.start_byte) # type: ignore[attr-defined]
397 self.read_length = self.iterable.tell() # type: ignore[attr-defined]
398 contextual_read_length = self.read_length
399 else:
400 while self.read_length <= self.start_byte:
401 chunk = self._next_chunk()
402 if chunk is not None:
403 chunk = chunk[self.start_byte - self.read_length :]
404 contextual_read_length = self.start_byte
405 return chunk, contextual_read_length
407 def _next(self) -> bytes:
408 if self.end_reached:
409 raise StopIteration()
410 chunk = None
411 contextual_read_length = self.read_length
412 if self.read_length == 0:
413 chunk, contextual_read_length = self._first_iteration()
414 if chunk is None:
415 chunk = self._next_chunk()
416 if self.end_byte is not None and self.read_length >= self.end_byte:
417 self.end_reached = True
418 return chunk[: self.end_byte - contextual_read_length]
419 return chunk
421 def __next__(self) -> bytes:
422 chunk = self._next()
423 if chunk:
424 return chunk
425 self.end_reached = True
426 raise StopIteration()
428 def close(self) -> None:
429 if hasattr(self.iterable, "close"):
430 self.iterable.close()
433class _LimitedStream(io.RawIOBase):
434 """Wrap a stream so that it doesn't read more than a given limit. This is
435 used to limit ``wsgi.input`` to the ``Content-Length`` header value or
436 :attr:`.Request.max_content_length`.
438 If the limit is reached before the underlying stream is exhausted (such as a
439 file that is too large, or an infinite stream), the remaining contents of
440 the stream cannot be read safely. Depending on how the server handles this,
441 clients may show a "connection reset" failure instead of seeing the 413
442 response.
444 :param stream: The stream to read from. Must be a readable binary IO object.
445 :param limit: The limit in bytes to not read past. Should be either the
446 ``Content-Length`` header value or ``request.max_content_length``.
447 :param is_max: Whether the given ``limit`` is ``request.max_content_length``
448 instead of the ``Content-Length`` header value. This changes how
449 exhausted and disconnect events are handled.
450 :raises .RequestEntityTooLarge: Attempted to read after a max limit.
451 :raises .ClientDisconnected: Reading returned zero bytes or raised an error,
452 when the limit is not a max.
454 .. deprecated:: 3.2
455 Will be private in Werkzeug 4.0. Use ``Request.stream`` instead.
457 .. versionchanged:: 2.3
458 Handle ``max_content_length`` differently than ``Content-Length``.
460 .. versionchanged:: 2.3
461 Implements ``io.RawIOBase`` rather than ``io.IOBase``.
462 """
464 def __init__(self, stream: t.IO[bytes], limit: int, is_max: bool = False) -> None:
465 self._stream = stream
466 self._pos = 0
467 self.limit = limit
468 self._limit_is_max = is_max
470 @property
471 def is_exhausted(self) -> bool:
472 """Whether the current stream position has reached the limit."""
473 return self._pos >= self.limit
475 def on_exhausted(self) -> None:
476 """Called when attempting to read after the limit has been reached. If
477 the limit is a maximum, raises :exc:`.RequestEntityTooLarge`.
479 .. versionchanged:: 2.3
480 Raises ``RequestEntityTooLarge`` if the limit is a maximum.
482 .. versionchanged:: 2.3
483 Any return value is ignored.
484 """
485 if self._limit_is_max:
486 raise RequestEntityTooLarge()
488 def on_disconnect(self, error: Exception | None = None) -> None:
489 """Called when an attempted read receives zero bytes before the limit
490 was reached. This indicates that the client disconnected before sending
491 the full request body. Raises :exc:`.ClientDisconnected`, unless the
492 limit is a maximum and no error was raised.
494 .. versionchanged:: 2.3
495 Added the ``error`` parameter. Do nothing if the limit is a maximum and no
496 error was raised.
498 .. versionchanged:: 2.3
499 Any return value is ignored.
500 """
501 if not self._limit_is_max or error is not None:
502 raise ClientDisconnected()
504 # If the limit is a maximum, then we may have read zero bytes because the
505 # streaming body is complete. There's no way to distinguish that from the
506 # client disconnecting early.
508 def exhaust(self) -> bytes:
509 """Exhaust the stream by reading until the limit is reached or the client
510 disconnects, returning the remaining data.
512 .. deprecated:: 3.2
513 Will be removed in Werkzeug 4.0.
515 .. versionchanged:: 2.3
516 Return the remaining data.
518 .. versionchanged:: 2.2.3
519 Handle case where wrapped stream returns fewer bytes than requested.
520 """
521 if not self.is_exhausted:
522 return self.readall()
524 return b""
526 def readinto(self, b: bytearray) -> int | None: # type: ignore[override]
527 size = len(b)
528 remaining = self.limit - self._pos
530 if remaining <= 0:
531 self.on_exhausted()
532 return 0
534 if hasattr(self._stream, "readinto"):
535 # Use stream.readinto if it's available.
536 if size <= remaining:
537 # The size fits in the remaining limit, use the buffer directly.
538 try:
539 out_size: int | None = self._stream.readinto(b)
540 except (OSError, ValueError) as e:
541 self.on_disconnect(error=e)
542 return 0
543 else:
544 # Use a temp buffer with the remaining limit as the size.
545 temp_b = bytearray(remaining)
547 try:
548 out_size = self._stream.readinto(temp_b)
549 except (OSError, ValueError) as e:
550 self.on_disconnect(error=e)
551 return 0
553 if out_size:
554 # Need to slice both sides. Math here is complicated.
555 # size=10, remaining=5, out_size=3, remaining > out_size
556 # Without slicing temp_b (b[:s] = tb), would try to
557 # put 5 bytes into 3 bytes, causing b to resize to 12.
558 b[:out_size] = temp_b[:out_size]
559 else:
560 # WSGI requires that stream.read is available.
561 try:
562 data = self._stream.read(min(size, remaining))
563 except (OSError, ValueError) as e:
564 self.on_disconnect(error=e)
565 return 0
567 out_size = len(data)
568 b[:out_size] = data
570 if not out_size:
571 # Read zero bytes from the stream.
572 self.on_disconnect()
573 return 0
575 self._pos += out_size
576 return out_size
578 def readall(self) -> bytes:
579 if self.is_exhausted:
580 self.on_exhausted()
581 return b""
583 out = bytearray()
585 # The parent implementation uses "while True", which results in an extra read.
586 while not self.is_exhausted:
587 data = self.read(1024 * 64)
589 # Stream may return empty before a max limit is reached.
590 if not data:
591 break
593 out.extend(data)
595 return bytes(out)
597 def tell(self) -> int:
598 """Return the current stream position.
600 .. versionadded:: 0.9
601 """
602 return self._pos
604 def readable(self) -> bool:
605 return True
608if not t.TYPE_CHECKING:
610 def __getattr__(name: str) -> t.Any:
611 import warnings
613 if name == "responder":
614 warnings.warn(
615 "'responder' is deprecated and will be removed in Werkzeug 4.0. Use"
616 " 'Request.application' instead.",
617 DeprecationWarning,
618 stacklevel=2,
619 )
620 return _responder
622 if name == "FileWrapper":
623 warnings.warn(
624 "'FileWrapper' is deprecated and will be removed in Werkzeug 4.0. Use"
625 " 'wrap_file' instead.",
626 DeprecationWarning,
627 stacklevel=2,
628 )
629 return _FileWrapper
631 if name == "get_input_stream":
632 warnings.warn(
633 "The 'get_input_stream' function is deprecated and will be removed in"
634 " Werkzeug 4.0. Use 'request.stream' instead.",
635 DeprecationWarning,
636 stacklevel=2,
637 )
638 return _get_input_stream
640 if name == "LimitedStream":
641 warnings.warn(
642 "The 'LimitedStream' class is deprecated and will be private in"
643 " Werkzeug 4.0. Use 'request.stream' instead.",
644 DeprecationWarning,
645 stacklevel=2,
646 )
647 return _LimitedStream
649 if name == "get_content_length":
650 warnings.warn(
651 "The 'get_content_length' function is deprecated and will be removed in"
652 " Werkzeug 4.0. Use 'Request.content_length' instead.",
653 DeprecationWarning,
654 stacklevel=2,
655 )
656 return _get_content_length
658 if name == "get_path_info":
659 warnings.warn(
660 "The 'get_path_info' function is deprecated and will be removed in"
661 " Werkzeug 4.0. Use 'Request.path' instead.",
662 DeprecationWarning,
663 stacklevel=2,
664 )
665 return _get_path_info
667 if name == "get_current_url":
668 warnings.warn(
669 "The 'get_current_url' function is deprecated and will be"
670 " removed in Werkzeug 4.0. Use 'Request.url', 'base_url',"
671 " 'root_url', or 'host_url' instead.",
672 DeprecationWarning,
673 stacklevel=2,
674 )
675 return _get_current_url
677 if name == "get_host":
678 warnings.warn(
679 "The 'get_host' function is deprecated and will be removed in Werkzeug"
680 " 4.0. Use 'Request.host' instead.",
681 DeprecationWarning,
682 stacklevel=2,
683 )
684 return _get_host
686 if name == "host_is_trusted":
687 from .sansio.utils import host_is_trusted
689 warnings.warn(
690 "The 'host_is_trusted' function is deprecated and will be"
691 " removed in Werkzeug 4.0. Use 'Request.trusted_hosts' and"
692 " 'Request.host' instead.",
693 DeprecationWarning,
694 stacklevel=2,
695 )
696 return host_is_trusted
698 if name == "wrap_file":
699 warnings.warn(
700 "The 'wrap_file' function is deprecated and will be removed in"
701 " Werkzeug 4.0. Use 'Request.wrap_file' instead.",
702 DeprecationWarning,
703 stacklevel=2,
704 )
705 return _wrap_file
707 raise AttributeError(name)