764 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			764 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
from __future__ import annotations
 | 
						|
 | 
						|
import typing as t
 | 
						|
from datetime import datetime
 | 
						|
from datetime import timedelta
 | 
						|
from datetime import timezone
 | 
						|
from http import HTTPStatus
 | 
						|
 | 
						|
from ..datastructures import CallbackDict
 | 
						|
from ..datastructures import ContentRange
 | 
						|
from ..datastructures import ContentSecurityPolicy
 | 
						|
from ..datastructures import Headers
 | 
						|
from ..datastructures import HeaderSet
 | 
						|
from ..datastructures import ResponseCacheControl
 | 
						|
from ..datastructures import WWWAuthenticate
 | 
						|
from ..http import COEP
 | 
						|
from ..http import COOP
 | 
						|
from ..http import dump_age
 | 
						|
from ..http import dump_cookie
 | 
						|
from ..http import dump_header
 | 
						|
from ..http import dump_options_header
 | 
						|
from ..http import http_date
 | 
						|
from ..http import HTTP_STATUS_CODES
 | 
						|
from ..http import parse_age
 | 
						|
from ..http import parse_cache_control_header
 | 
						|
from ..http import parse_content_range_header
 | 
						|
from ..http import parse_csp_header
 | 
						|
from ..http import parse_date
 | 
						|
from ..http import parse_options_header
 | 
						|
from ..http import parse_set_header
 | 
						|
from ..http import quote_etag
 | 
						|
from ..http import unquote_etag
 | 
						|
from ..utils import get_content_type
 | 
						|
from ..utils import header_property
 | 
						|
 | 
						|
if t.TYPE_CHECKING:
 | 
						|
    from ..datastructures.cache_control import _CacheControl
 | 
						|
 | 
						|
 | 
						|
def _set_property(name: str, doc: str | None = None) -> property:
 | 
						|
    def fget(self: Response) -> HeaderSet:
 | 
						|
        def on_update(header_set: HeaderSet) -> None:
 | 
						|
            if not header_set and name in self.headers:
 | 
						|
                del self.headers[name]
 | 
						|
            elif header_set:
 | 
						|
                self.headers[name] = header_set.to_header()
 | 
						|
 | 
						|
        return parse_set_header(self.headers.get(name), on_update)
 | 
						|
 | 
						|
    def fset(
 | 
						|
        self: Response,
 | 
						|
        value: None | (str | dict[str, str | int] | t.Iterable[str]),
 | 
						|
    ) -> None:
 | 
						|
        if not value:
 | 
						|
            del self.headers[name]
 | 
						|
        elif isinstance(value, str):
 | 
						|
            self.headers[name] = value
 | 
						|
        else:
 | 
						|
            self.headers[name] = dump_header(value)
 | 
						|
 | 
						|
    return property(fget, fset, doc=doc)
 | 
						|
 | 
						|
 | 
						|
class Response:
 | 
						|
    """Represents the non-IO parts of an HTTP response, specifically the
 | 
						|
    status and headers but not the body.
 | 
						|
 | 
						|
    This class is not meant for general use. It should only be used when
 | 
						|
    implementing WSGI, ASGI, or another HTTP application spec. Werkzeug
 | 
						|
    provides a WSGI implementation at :cls:`werkzeug.wrappers.Response`.
 | 
						|
 | 
						|
    :param status: The status code for the response. Either an int, in
 | 
						|
        which case the default status message is added, or a string in
 | 
						|
        the form ``{code} {message}``, like ``404 Not Found``. Defaults
 | 
						|
        to 200.
 | 
						|
    :param headers: A :class:`~werkzeug.datastructures.Headers` object,
 | 
						|
        or a list of ``(key, value)`` tuples that will be converted to a
 | 
						|
        ``Headers`` object.
 | 
						|
    :param mimetype: The mime type (content type without charset or
 | 
						|
        other parameters) of the response. If the value starts with
 | 
						|
        ``text/`` (or matches some other special cases), the charset
 | 
						|
        will be added to create the ``content_type``.
 | 
						|
    :param content_type: The full content type of the response.
 | 
						|
        Overrides building the value from ``mimetype``.
 | 
						|
 | 
						|
    .. versionchanged:: 3.0
 | 
						|
        The ``charset`` attribute was removed.
 | 
						|
 | 
						|
    .. versionadded:: 2.0
 | 
						|
    """
 | 
						|
 | 
						|
    #: the default status if none is provided.
 | 
						|
    default_status = 200
 | 
						|
 | 
						|
    #: the default mimetype if none is provided.
 | 
						|
    default_mimetype: str | None = "text/plain"
 | 
						|
 | 
						|
    #: Warn if a cookie header exceeds this size. The default, 4093, should be
 | 
						|
    #: safely `supported by most browsers <cookie_>`_. A cookie larger than
 | 
						|
    #: this size will still be sent, but it may be ignored or handled
 | 
						|
    #: incorrectly by some browsers. Set to 0 to disable this check.
 | 
						|
    #:
 | 
						|
    #: .. versionadded:: 0.13
 | 
						|
    #:
 | 
						|
    #: .. _`cookie`: http://browsercookielimits.squawky.net/
 | 
						|
    max_cookie_size = 4093
 | 
						|
 | 
						|
    # A :class:`Headers` object representing the response headers.
 | 
						|
    headers: Headers
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        status: int | str | HTTPStatus | None = None,
 | 
						|
        headers: t.Mapping[str, str | t.Iterable[str]]
 | 
						|
        | t.Iterable[tuple[str, str]]
 | 
						|
        | None = None,
 | 
						|
        mimetype: str | None = None,
 | 
						|
        content_type: str | None = None,
 | 
						|
    ) -> None:
 | 
						|
        if isinstance(headers, Headers):
 | 
						|
            self.headers = headers
 | 
						|
        elif not headers:
 | 
						|
            self.headers = Headers()
 | 
						|
        else:
 | 
						|
            self.headers = Headers(headers)
 | 
						|
 | 
						|
        if content_type is None:
 | 
						|
            if mimetype is None and "content-type" not in self.headers:
 | 
						|
                mimetype = self.default_mimetype
 | 
						|
            if mimetype is not None:
 | 
						|
                mimetype = get_content_type(mimetype, "utf-8")
 | 
						|
            content_type = mimetype
 | 
						|
        if content_type is not None:
 | 
						|
            self.headers["Content-Type"] = content_type
 | 
						|
        if status is None:
 | 
						|
            status = self.default_status
 | 
						|
        self.status = status  # type: ignore
 | 
						|
 | 
						|
    def __repr__(self) -> str:
 | 
						|
        return f"<{type(self).__name__} [{self.status}]>"
 | 
						|
 | 
						|
    @property
 | 
						|
    def status_code(self) -> int:
 | 
						|
        """The HTTP status code as a number."""
 | 
						|
        return self._status_code
 | 
						|
 | 
						|
    @status_code.setter
 | 
						|
    def status_code(self, code: int) -> None:
 | 
						|
        self.status = code  # type: ignore
 | 
						|
 | 
						|
    @property
 | 
						|
    def status(self) -> str:
 | 
						|
        """The HTTP status code as a string."""
 | 
						|
        return self._status
 | 
						|
 | 
						|
    @status.setter
 | 
						|
    def status(self, value: str | int | HTTPStatus) -> None:
 | 
						|
        self._status, self._status_code = self._clean_status(value)
 | 
						|
 | 
						|
    def _clean_status(self, value: str | int | HTTPStatus) -> tuple[str, int]:
 | 
						|
        if isinstance(value, (int, HTTPStatus)):
 | 
						|
            status_code = int(value)
 | 
						|
        else:
 | 
						|
            value = value.strip()
 | 
						|
 | 
						|
            if not value:
 | 
						|
                raise ValueError("Empty status argument")
 | 
						|
 | 
						|
            code_str, sep, _ = value.partition(" ")
 | 
						|
 | 
						|
            try:
 | 
						|
                status_code = int(code_str)
 | 
						|
            except ValueError:
 | 
						|
                # only message
 | 
						|
                return f"0 {value}", 0
 | 
						|
 | 
						|
            if sep:
 | 
						|
                # code and message
 | 
						|
                return value, status_code
 | 
						|
 | 
						|
        # only code, look up message
 | 
						|
        try:
 | 
						|
            status = f"{status_code} {HTTP_STATUS_CODES[status_code].upper()}"
 | 
						|
        except KeyError:
 | 
						|
            status = f"{status_code} UNKNOWN"
 | 
						|
 | 
						|
        return status, status_code
 | 
						|
 | 
						|
    def set_cookie(
 | 
						|
        self,
 | 
						|
        key: str,
 | 
						|
        value: str = "",
 | 
						|
        max_age: timedelta | int | None = None,
 | 
						|
        expires: str | datetime | int | float | None = None,
 | 
						|
        path: str | None = "/",
 | 
						|
        domain: str | None = None,
 | 
						|
        secure: bool = False,
 | 
						|
        httponly: bool = False,
 | 
						|
        samesite: str | None = None,
 | 
						|
        partitioned: bool = False,
 | 
						|
    ) -> None:
 | 
						|
        """Sets a cookie.
 | 
						|
 | 
						|
        A warning is raised if the size of the cookie header exceeds
 | 
						|
        :attr:`max_cookie_size`, but the header will still be set.
 | 
						|
 | 
						|
        :param key: the key (name) of the cookie to be set.
 | 
						|
        :param value: the value of the cookie.
 | 
						|
        :param max_age: should be a number of seconds, or `None` (default) if
 | 
						|
                        the cookie should last only as long as the client's
 | 
						|
                        browser session.
 | 
						|
        :param expires: should be a `datetime` object or UNIX timestamp.
 | 
						|
        :param path: limits the cookie to a given path, per default it will
 | 
						|
                     span the whole domain.
 | 
						|
        :param domain: if you want to set a cross-domain cookie.  For example,
 | 
						|
                       ``domain="example.com"`` will set a cookie that is
 | 
						|
                       readable by the domain ``www.example.com``,
 | 
						|
                       ``foo.example.com`` etc.  Otherwise, a cookie will only
 | 
						|
                       be readable by the domain that set it.
 | 
						|
        :param secure: If ``True``, the cookie will only be available
 | 
						|
            via HTTPS.
 | 
						|
        :param httponly: Disallow JavaScript access to the cookie.
 | 
						|
        :param samesite: Limit the scope of the cookie to only be
 | 
						|
            attached to requests that are "same-site".
 | 
						|
        :param partitioned: If ``True``, the cookie will be partitioned.
 | 
						|
 | 
						|
        .. versionchanged:: 3.1
 | 
						|
            The ``partitioned`` parameter was added.
 | 
						|
        """
 | 
						|
        self.headers.add(
 | 
						|
            "Set-Cookie",
 | 
						|
            dump_cookie(
 | 
						|
                key,
 | 
						|
                value=value,
 | 
						|
                max_age=max_age,
 | 
						|
                expires=expires,
 | 
						|
                path=path,
 | 
						|
                domain=domain,
 | 
						|
                secure=secure,
 | 
						|
                httponly=httponly,
 | 
						|
                max_size=self.max_cookie_size,
 | 
						|
                samesite=samesite,
 | 
						|
                partitioned=partitioned,
 | 
						|
            ),
 | 
						|
        )
 | 
						|
 | 
						|
    def delete_cookie(
 | 
						|
        self,
 | 
						|
        key: str,
 | 
						|
        path: str | None = "/",
 | 
						|
        domain: str | None = None,
 | 
						|
        secure: bool = False,
 | 
						|
        httponly: bool = False,
 | 
						|
        samesite: str | None = None,
 | 
						|
        partitioned: bool = False,
 | 
						|
    ) -> None:
 | 
						|
        """Delete a cookie.  Fails silently if key doesn't exist.
 | 
						|
 | 
						|
        :param key: the key (name) of the cookie to be deleted.
 | 
						|
        :param path: if the cookie that should be deleted was limited to a
 | 
						|
                     path, the path has to be defined here.
 | 
						|
        :param domain: if the cookie that should be deleted was limited to a
 | 
						|
                       domain, that domain has to be defined here.
 | 
						|
        :param secure: If ``True``, the cookie will only be available
 | 
						|
            via HTTPS.
 | 
						|
        :param httponly: Disallow JavaScript access to the cookie.
 | 
						|
        :param samesite: Limit the scope of the cookie to only be
 | 
						|
            attached to requests that are "same-site".
 | 
						|
        :param partitioned: If ``True``, the cookie will be partitioned.
 | 
						|
        """
 | 
						|
        self.set_cookie(
 | 
						|
            key,
 | 
						|
            expires=0,
 | 
						|
            max_age=0,
 | 
						|
            path=path,
 | 
						|
            domain=domain,
 | 
						|
            secure=secure,
 | 
						|
            httponly=httponly,
 | 
						|
            samesite=samesite,
 | 
						|
            partitioned=partitioned,
 | 
						|
        )
 | 
						|
 | 
						|
    @property
 | 
						|
    def is_json(self) -> bool:
 | 
						|
        """Check if the mimetype indicates JSON data, either
 | 
						|
        :mimetype:`application/json` or :mimetype:`application/*+json`.
 | 
						|
        """
 | 
						|
        mt = self.mimetype
 | 
						|
        return mt is not None and (
 | 
						|
            mt == "application/json"
 | 
						|
            or mt.startswith("application/")
 | 
						|
            and mt.endswith("+json")
 | 
						|
        )
 | 
						|
 | 
						|
    # Common Descriptors
 | 
						|
 | 
						|
    @property
 | 
						|
    def mimetype(self) -> str | None:
 | 
						|
        """The mimetype (content type without charset etc.)"""
 | 
						|
        ct = self.headers.get("content-type")
 | 
						|
 | 
						|
        if ct:
 | 
						|
            return ct.split(";")[0].strip()
 | 
						|
        else:
 | 
						|
            return None
 | 
						|
 | 
						|
    @mimetype.setter
 | 
						|
    def mimetype(self, value: str) -> None:
 | 
						|
        self.headers["Content-Type"] = get_content_type(value, "utf-8")
 | 
						|
 | 
						|
    @property
 | 
						|
    def mimetype_params(self) -> dict[str, str]:
 | 
						|
        """The mimetype parameters as dict. For example if the
 | 
						|
        content type is ``text/html; charset=utf-8`` the params would be
 | 
						|
        ``{'charset': 'utf-8'}``.
 | 
						|
 | 
						|
        .. versionadded:: 0.5
 | 
						|
        """
 | 
						|
 | 
						|
        def on_update(d: CallbackDict[str, str]) -> None:
 | 
						|
            self.headers["Content-Type"] = dump_options_header(self.mimetype, d)
 | 
						|
 | 
						|
        d = parse_options_header(self.headers.get("content-type", ""))[1]
 | 
						|
        return CallbackDict(d, on_update)
 | 
						|
 | 
						|
    location = header_property[str](
 | 
						|
        "Location",
 | 
						|
        doc="""The Location response-header field is used to redirect
 | 
						|
        the recipient to a location other than the Request-URI for
 | 
						|
        completion of the request or identification of a new
 | 
						|
        resource.""",
 | 
						|
    )
 | 
						|
    age = header_property(
 | 
						|
        "Age",
 | 
						|
        None,
 | 
						|
        parse_age,
 | 
						|
        dump_age,  # type: ignore
 | 
						|
        doc="""The Age response-header field conveys the sender's
 | 
						|
        estimate of the amount of time since the response (or its
 | 
						|
        revalidation) was generated at the origin server.
 | 
						|
 | 
						|
        Age values are non-negative decimal integers, representing time
 | 
						|
        in seconds.""",
 | 
						|
    )
 | 
						|
    content_type = header_property[str](
 | 
						|
        "Content-Type",
 | 
						|
        doc="""The Content-Type entity-header field indicates the media
 | 
						|
        type of the entity-body sent to the recipient or, in the case of
 | 
						|
        the HEAD method, the media type that would have been sent had
 | 
						|
        the request been a GET.""",
 | 
						|
    )
 | 
						|
    content_length = header_property(
 | 
						|
        "Content-Length",
 | 
						|
        None,
 | 
						|
        int,
 | 
						|
        str,
 | 
						|
        doc="""The Content-Length entity-header field indicates the size
 | 
						|
        of the entity-body, in decimal number of OCTETs, sent to the
 | 
						|
        recipient or, in the case of the HEAD method, the size of the
 | 
						|
        entity-body that would have been sent had the request been a
 | 
						|
        GET.""",
 | 
						|
    )
 | 
						|
    content_location = header_property[str](
 | 
						|
        "Content-Location",
 | 
						|
        doc="""The Content-Location entity-header field MAY be used to
 | 
						|
        supply the resource location for the entity enclosed in the
 | 
						|
        message when that entity is accessible from a location separate
 | 
						|
        from the requested resource's URI.""",
 | 
						|
    )
 | 
						|
    content_encoding = header_property[str](
 | 
						|
        "Content-Encoding",
 | 
						|
        doc="""The Content-Encoding entity-header field is used as a
 | 
						|
        modifier to the media-type. When present, its value indicates
 | 
						|
        what additional content codings have been applied to the
 | 
						|
        entity-body, and thus what decoding mechanisms must be applied
 | 
						|
        in order to obtain the media-type referenced by the Content-Type
 | 
						|
        header field.""",
 | 
						|
    )
 | 
						|
    content_md5 = header_property[str](
 | 
						|
        "Content-MD5",
 | 
						|
        doc="""The Content-MD5 entity-header field, as defined in
 | 
						|
        RFC 1864, is an MD5 digest of the entity-body for the purpose of
 | 
						|
        providing an end-to-end message integrity check (MIC) of the
 | 
						|
        entity-body. (Note: a MIC is good for detecting accidental
 | 
						|
        modification of the entity-body in transit, but is not proof
 | 
						|
        against malicious attacks.)""",
 | 
						|
    )
 | 
						|
    date = header_property(
 | 
						|
        "Date",
 | 
						|
        None,
 | 
						|
        parse_date,
 | 
						|
        http_date,
 | 
						|
        doc="""The Date general-header field represents the date and
 | 
						|
        time at which the message was originated, having the same
 | 
						|
        semantics as orig-date in RFC 822.
 | 
						|
 | 
						|
        .. versionchanged:: 2.0
 | 
						|
            The datetime object is timezone-aware.
 | 
						|
        """,
 | 
						|
    )
 | 
						|
    expires = header_property(
 | 
						|
        "Expires",
 | 
						|
        None,
 | 
						|
        parse_date,
 | 
						|
        http_date,
 | 
						|
        doc="""The Expires entity-header field gives the date/time after
 | 
						|
        which the response is considered stale. A stale cache entry may
 | 
						|
        not normally be returned by a cache.
 | 
						|
 | 
						|
        .. versionchanged:: 2.0
 | 
						|
            The datetime object is timezone-aware.
 | 
						|
        """,
 | 
						|
    )
 | 
						|
    last_modified = header_property(
 | 
						|
        "Last-Modified",
 | 
						|
        None,
 | 
						|
        parse_date,
 | 
						|
        http_date,
 | 
						|
        doc="""The Last-Modified entity-header field indicates the date
 | 
						|
        and time at which the origin server believes the variant was
 | 
						|
        last modified.
 | 
						|
 | 
						|
        .. versionchanged:: 2.0
 | 
						|
            The datetime object is timezone-aware.
 | 
						|
        """,
 | 
						|
    )
 | 
						|
 | 
						|
    @property
 | 
						|
    def retry_after(self) -> datetime | None:
 | 
						|
        """The Retry-After response-header field can be used with a
 | 
						|
        503 (Service Unavailable) response to indicate how long the
 | 
						|
        service is expected to be unavailable to the requesting client.
 | 
						|
 | 
						|
        Time in seconds until expiration or date.
 | 
						|
 | 
						|
        .. versionchanged:: 2.0
 | 
						|
            The datetime object is timezone-aware.
 | 
						|
        """
 | 
						|
        value = self.headers.get("retry-after")
 | 
						|
        if value is None:
 | 
						|
            return None
 | 
						|
 | 
						|
        try:
 | 
						|
            seconds = int(value)
 | 
						|
        except ValueError:
 | 
						|
            return parse_date(value)
 | 
						|
 | 
						|
        return datetime.now(timezone.utc) + timedelta(seconds=seconds)
 | 
						|
 | 
						|
    @retry_after.setter
 | 
						|
    def retry_after(self, value: datetime | int | str | None) -> None:
 | 
						|
        if value is None:
 | 
						|
            if "retry-after" in self.headers:
 | 
						|
                del self.headers["retry-after"]
 | 
						|
            return
 | 
						|
        elif isinstance(value, datetime):
 | 
						|
            value = http_date(value)
 | 
						|
        else:
 | 
						|
            value = str(value)
 | 
						|
        self.headers["Retry-After"] = value
 | 
						|
 | 
						|
    vary = _set_property(
 | 
						|
        "Vary",
 | 
						|
        doc="""The Vary field value indicates the set of request-header
 | 
						|
        fields that fully determines, while the response is fresh,
 | 
						|
        whether a cache is permitted to use the response to reply to a
 | 
						|
        subsequent request without revalidation.""",
 | 
						|
    )
 | 
						|
    content_language = _set_property(
 | 
						|
        "Content-Language",
 | 
						|
        doc="""The Content-Language entity-header field describes the
 | 
						|
        natural language(s) of the intended audience for the enclosed
 | 
						|
        entity. Note that this might not be equivalent to all the
 | 
						|
        languages used within the entity-body.""",
 | 
						|
    )
 | 
						|
    allow = _set_property(
 | 
						|
        "Allow",
 | 
						|
        doc="""The Allow entity-header field lists the set of methods
 | 
						|
        supported by the resource identified by the Request-URI. The
 | 
						|
        purpose of this field is strictly to inform the recipient of
 | 
						|
        valid methods associated with the resource. An Allow header
 | 
						|
        field MUST be present in a 405 (Method Not Allowed)
 | 
						|
        response.""",
 | 
						|
    )
 | 
						|
 | 
						|
    # ETag
 | 
						|
 | 
						|
    @property
 | 
						|
    def cache_control(self) -> ResponseCacheControl:
 | 
						|
        """The Cache-Control general-header field is used to specify
 | 
						|
        directives that MUST be obeyed by all caching mechanisms along the
 | 
						|
        request/response chain.
 | 
						|
        """
 | 
						|
 | 
						|
        def on_update(cache_control: _CacheControl) -> None:
 | 
						|
            if not cache_control and "cache-control" in self.headers:
 | 
						|
                del self.headers["cache-control"]
 | 
						|
            elif cache_control:
 | 
						|
                self.headers["Cache-Control"] = cache_control.to_header()
 | 
						|
 | 
						|
        return parse_cache_control_header(
 | 
						|
            self.headers.get("cache-control"), on_update, ResponseCacheControl
 | 
						|
        )
 | 
						|
 | 
						|
    def set_etag(self, etag: str, weak: bool = False) -> None:
 | 
						|
        """Set the etag, and override the old one if there was one."""
 | 
						|
        self.headers["ETag"] = quote_etag(etag, weak)
 | 
						|
 | 
						|
    def get_etag(self) -> tuple[str, bool] | tuple[None, None]:
 | 
						|
        """Return a tuple in the form ``(etag, is_weak)``.  If there is no
 | 
						|
        ETag the return value is ``(None, None)``.
 | 
						|
        """
 | 
						|
        return unquote_etag(self.headers.get("ETag"))
 | 
						|
 | 
						|
    accept_ranges = header_property[str](
 | 
						|
        "Accept-Ranges",
 | 
						|
        doc="""The `Accept-Ranges` header. Even though the name would
 | 
						|
        indicate that multiple values are supported, it must be one
 | 
						|
        string token only.
 | 
						|
 | 
						|
        The values ``'bytes'`` and ``'none'`` are common.
 | 
						|
 | 
						|
        .. versionadded:: 0.7""",
 | 
						|
    )
 | 
						|
 | 
						|
    @property
 | 
						|
    def content_range(self) -> ContentRange:
 | 
						|
        """The ``Content-Range`` header as a
 | 
						|
        :class:`~werkzeug.datastructures.ContentRange` object. Available
 | 
						|
        even if the header is not set.
 | 
						|
 | 
						|
        .. versionadded:: 0.7
 | 
						|
        """
 | 
						|
 | 
						|
        def on_update(rng: ContentRange) -> None:
 | 
						|
            if not rng:
 | 
						|
                del self.headers["content-range"]
 | 
						|
            else:
 | 
						|
                self.headers["Content-Range"] = rng.to_header()
 | 
						|
 | 
						|
        rv = parse_content_range_header(self.headers.get("content-range"), on_update)
 | 
						|
        # always provide a content range object to make the descriptor
 | 
						|
        # more user friendly.  It provides an unset() method that can be
 | 
						|
        # used to remove the header quickly.
 | 
						|
        if rv is None:
 | 
						|
            rv = ContentRange(None, None, None, on_update=on_update)
 | 
						|
        return rv
 | 
						|
 | 
						|
    @content_range.setter
 | 
						|
    def content_range(self, value: ContentRange | str | None) -> None:
 | 
						|
        if not value:
 | 
						|
            del self.headers["content-range"]
 | 
						|
        elif isinstance(value, str):
 | 
						|
            self.headers["Content-Range"] = value
 | 
						|
        else:
 | 
						|
            self.headers["Content-Range"] = value.to_header()
 | 
						|
 | 
						|
    # Authorization
 | 
						|
 | 
						|
    @property
 | 
						|
    def www_authenticate(self) -> WWWAuthenticate:
 | 
						|
        """The ``WWW-Authenticate`` header parsed into a :class:`.WWWAuthenticate`
 | 
						|
        object. Modifying the object will modify the header value.
 | 
						|
 | 
						|
        This header is not set by default. To set this header, assign an instance of
 | 
						|
        :class:`.WWWAuthenticate` to this attribute.
 | 
						|
 | 
						|
        .. code-block:: python
 | 
						|
 | 
						|
            response.www_authenticate = WWWAuthenticate(
 | 
						|
                "basic", {"realm": "Authentication Required"}
 | 
						|
            )
 | 
						|
 | 
						|
        Multiple values for this header can be sent to give the client multiple options.
 | 
						|
        Assign a list to set multiple headers. However, modifying the items in the list
 | 
						|
        will not automatically update the header values, and accessing this attribute
 | 
						|
        will only ever return the first value.
 | 
						|
 | 
						|
        To unset this header, assign ``None`` or use ``del``.
 | 
						|
 | 
						|
        .. versionchanged:: 2.3
 | 
						|
            This attribute can be assigned to to set the header. A list can be assigned
 | 
						|
            to set multiple header values. Use ``del`` to unset the header.
 | 
						|
 | 
						|
        .. versionchanged:: 2.3
 | 
						|
            :class:`WWWAuthenticate` is no longer a ``dict``. The ``token`` attribute
 | 
						|
            was added for auth challenges that use a token instead of parameters.
 | 
						|
        """
 | 
						|
        value = WWWAuthenticate.from_header(self.headers.get("WWW-Authenticate"))
 | 
						|
 | 
						|
        if value is None:
 | 
						|
            value = WWWAuthenticate("basic")
 | 
						|
 | 
						|
        def on_update(value: WWWAuthenticate) -> None:
 | 
						|
            self.www_authenticate = value
 | 
						|
 | 
						|
        value._on_update = on_update
 | 
						|
        return value
 | 
						|
 | 
						|
    @www_authenticate.setter
 | 
						|
    def www_authenticate(
 | 
						|
        self, value: WWWAuthenticate | list[WWWAuthenticate] | None
 | 
						|
    ) -> None:
 | 
						|
        if not value:  # None or empty list
 | 
						|
            del self.www_authenticate
 | 
						|
        elif isinstance(value, list):
 | 
						|
            # Clear any existing header by setting the first item.
 | 
						|
            self.headers.set("WWW-Authenticate", value[0].to_header())
 | 
						|
 | 
						|
            for item in value[1:]:
 | 
						|
                # Add additional header lines for additional items.
 | 
						|
                self.headers.add("WWW-Authenticate", item.to_header())
 | 
						|
        else:
 | 
						|
            self.headers.set("WWW-Authenticate", value.to_header())
 | 
						|
 | 
						|
            def on_update(value: WWWAuthenticate) -> None:
 | 
						|
                self.www_authenticate = value
 | 
						|
 | 
						|
            # When setting a single value, allow updating it directly.
 | 
						|
            value._on_update = on_update
 | 
						|
 | 
						|
    @www_authenticate.deleter
 | 
						|
    def www_authenticate(self) -> None:
 | 
						|
        if "WWW-Authenticate" in self.headers:
 | 
						|
            del self.headers["WWW-Authenticate"]
 | 
						|
 | 
						|
    # CSP
 | 
						|
 | 
						|
    @property
 | 
						|
    def content_security_policy(self) -> ContentSecurityPolicy:
 | 
						|
        """The ``Content-Security-Policy`` header as a
 | 
						|
        :class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available
 | 
						|
        even if the header is not set.
 | 
						|
 | 
						|
        The Content-Security-Policy header adds an additional layer of
 | 
						|
        security to help detect and mitigate certain types of attacks.
 | 
						|
        """
 | 
						|
 | 
						|
        def on_update(csp: ContentSecurityPolicy) -> None:
 | 
						|
            if not csp:
 | 
						|
                del self.headers["content-security-policy"]
 | 
						|
            else:
 | 
						|
                self.headers["Content-Security-Policy"] = csp.to_header()
 | 
						|
 | 
						|
        rv = parse_csp_header(self.headers.get("content-security-policy"), on_update)
 | 
						|
        if rv is None:
 | 
						|
            rv = ContentSecurityPolicy(None, on_update=on_update)
 | 
						|
        return rv
 | 
						|
 | 
						|
    @content_security_policy.setter
 | 
						|
    def content_security_policy(
 | 
						|
        self, value: ContentSecurityPolicy | str | None
 | 
						|
    ) -> None:
 | 
						|
        if not value:
 | 
						|
            del self.headers["content-security-policy"]
 | 
						|
        elif isinstance(value, str):
 | 
						|
            self.headers["Content-Security-Policy"] = value
 | 
						|
        else:
 | 
						|
            self.headers["Content-Security-Policy"] = value.to_header()
 | 
						|
 | 
						|
    @property
 | 
						|
    def content_security_policy_report_only(self) -> ContentSecurityPolicy:
 | 
						|
        """The ``Content-Security-policy-report-only`` header as a
 | 
						|
        :class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available
 | 
						|
        even if the header is not set.
 | 
						|
 | 
						|
        The Content-Security-Policy-Report-Only header adds a csp policy
 | 
						|
        that is not enforced but is reported thereby helping detect
 | 
						|
        certain types of attacks.
 | 
						|
        """
 | 
						|
 | 
						|
        def on_update(csp: ContentSecurityPolicy) -> None:
 | 
						|
            if not csp:
 | 
						|
                del self.headers["content-security-policy-report-only"]
 | 
						|
            else:
 | 
						|
                self.headers["Content-Security-policy-report-only"] = csp.to_header()
 | 
						|
 | 
						|
        rv = parse_csp_header(
 | 
						|
            self.headers.get("content-security-policy-report-only"), on_update
 | 
						|
        )
 | 
						|
        if rv is None:
 | 
						|
            rv = ContentSecurityPolicy(None, on_update=on_update)
 | 
						|
        return rv
 | 
						|
 | 
						|
    @content_security_policy_report_only.setter
 | 
						|
    def content_security_policy_report_only(
 | 
						|
        self, value: ContentSecurityPolicy | str | None
 | 
						|
    ) -> None:
 | 
						|
        if not value:
 | 
						|
            del self.headers["content-security-policy-report-only"]
 | 
						|
        elif isinstance(value, str):
 | 
						|
            self.headers["Content-Security-policy-report-only"] = value
 | 
						|
        else:
 | 
						|
            self.headers["Content-Security-policy-report-only"] = value.to_header()
 | 
						|
 | 
						|
    # CORS
 | 
						|
 | 
						|
    @property
 | 
						|
    def access_control_allow_credentials(self) -> bool:
 | 
						|
        """Whether credentials can be shared by the browser to
 | 
						|
        JavaScript code. As part of the preflight request it indicates
 | 
						|
        whether credentials can be used on the cross origin request.
 | 
						|
        """
 | 
						|
        return "Access-Control-Allow-Credentials" in self.headers
 | 
						|
 | 
						|
    @access_control_allow_credentials.setter
 | 
						|
    def access_control_allow_credentials(self, value: bool | None) -> None:
 | 
						|
        if value is True:
 | 
						|
            self.headers["Access-Control-Allow-Credentials"] = "true"
 | 
						|
        else:
 | 
						|
            self.headers.pop("Access-Control-Allow-Credentials", None)
 | 
						|
 | 
						|
    access_control_allow_headers = header_property(
 | 
						|
        "Access-Control-Allow-Headers",
 | 
						|
        load_func=parse_set_header,
 | 
						|
        dump_func=dump_header,
 | 
						|
        doc="Which headers can be sent with the cross origin request.",
 | 
						|
    )
 | 
						|
 | 
						|
    access_control_allow_methods = header_property(
 | 
						|
        "Access-Control-Allow-Methods",
 | 
						|
        load_func=parse_set_header,
 | 
						|
        dump_func=dump_header,
 | 
						|
        doc="Which methods can be used for the cross origin request.",
 | 
						|
    )
 | 
						|
 | 
						|
    access_control_allow_origin = header_property[str](
 | 
						|
        "Access-Control-Allow-Origin",
 | 
						|
        doc="The origin or '*' for any origin that may make cross origin requests.",
 | 
						|
    )
 | 
						|
 | 
						|
    access_control_expose_headers = header_property(
 | 
						|
        "Access-Control-Expose-Headers",
 | 
						|
        load_func=parse_set_header,
 | 
						|
        dump_func=dump_header,
 | 
						|
        doc="Which headers can be shared by the browser to JavaScript code.",
 | 
						|
    )
 | 
						|
 | 
						|
    access_control_max_age = header_property(
 | 
						|
        "Access-Control-Max-Age",
 | 
						|
        load_func=int,
 | 
						|
        dump_func=str,
 | 
						|
        doc="The maximum age in seconds the access control settings can be cached for.",
 | 
						|
    )
 | 
						|
 | 
						|
    cross_origin_opener_policy = header_property[COOP](
 | 
						|
        "Cross-Origin-Opener-Policy",
 | 
						|
        load_func=lambda value: COOP(value),
 | 
						|
        dump_func=lambda value: value.value,
 | 
						|
        default=COOP.UNSAFE_NONE,
 | 
						|
        doc="""Allows control over sharing of browsing context group with cross-origin
 | 
						|
        documents. Values must be a member of the :class:`werkzeug.http.COOP` enum.""",
 | 
						|
    )
 | 
						|
 | 
						|
    cross_origin_embedder_policy = header_property[COEP](
 | 
						|
        "Cross-Origin-Embedder-Policy",
 | 
						|
        load_func=lambda value: COEP(value),
 | 
						|
        dump_func=lambda value: value.value,
 | 
						|
        default=COEP.UNSAFE_NONE,
 | 
						|
        doc="""Prevents a document from loading any cross-origin resources that do not
 | 
						|
        explicitly grant the document permission. Values must be a member of the
 | 
						|
        :class:`werkzeug.http.COEP` enum.""",
 | 
						|
    )
 |