Beispiel #1
0
 def __contains__(self, item: str) -> bool:
     """
     Verify if a string match a member or an attribute name of an Header.
     """
     if item in self.attrs:
         return True
     item = normalize_str(item)
     for attr in self.attrs:
         target = normalize_str(attr)
         if item == target or item in header_content_split(target, " "):
             return True
     return False
Beispiel #2
0
    def __isub__(self, other: Union[Header, str]) -> "Headers":
        """
        Inline subtract, using the operator '-'. If str is subtracted to it,
        would be looking for header named like provided str.
        Would remove any entries named 'Set-Cookies'. eg :
        >>> headers = Header("Set-Cookies", "HELLO=WORLD") + Header("Allow", "POST")
        >>> headers.has("Set-Cookies")
        True
        >>> headers -= 'Set-Cookies'
        >>> headers.has("Set-Cookies")
        False
        """
        if isinstance(other, str):
            other_normalized = normalize_str(other)
            to_be_removed = list()

            for header in self:
                if other_normalized == header.normalized_name:
                    to_be_removed.append(header)

            for header in to_be_removed:
                self._headers.remove(header)

            return self

        if isinstance(other, Header):
            if other in self:
                self._headers.remove(other)
                return self

        raise TypeError('Cannot subtract type "{type_}" to Headers.'.format(
            type_=str(type(other))))
Beispiel #3
0
    def __setitem__(self, key: str, value: str) -> None:
        """
        Set header using the bracket syntax. This operation would remove any existing header named after the key.
        Warning, if your value contain comma separated entries, it will split it into multiple Header instance.
        >>> headers = Headers()
        >>> headers.content_type = "application/json"
        >>> len(headers)
        1
        >>> headers.accept = "text/html, application/json;q=1.0"
        >>> len(headers)
        3
        """
        if not isinstance(value, str):
            raise TypeError(
                "Cannot assign header '{key}' using type {type_} to headers.".
                format(key=key, type_=type(value)))
        if key in self:
            del self[key]

        # Permit to detect multiple entries.
        if normalize_str(key) != "subject":

            entries: List[str] = header_content_split(value, ",")

            if len(entries) > 1:

                for entry in entries:
                    self._headers.append(Header(key, entry))

                return

        self._headers.append(Header(key, value))
Beispiel #4
0
    def __isub__(self, other: str) -> "Header":
        """
        This method should allow you to remove attribute or member from header.
        """
        if not isinstance(other, str):
            raise TypeError("You cannot subtract {type_} to an Header.".format(
                type_=str(type(other))))

        if other not in self:
            raise ValueError(
                "You cannot subtract '{element}' from '{header_name}' Header because its not there."
                .format(element=other, header_name=self.pretty_name))

        other = normalize_str(other)

        if other in self._valued_attrs:
            del self[other]

        if other in self._not_valued_attrs:
            self._not_valued_attrs.remove(other)
            while True:
                try:
                    self._not_valued_attrs.remove(other)
                except ValueError:
                    break
            for elem in findall(
                    r"{member_name}(?=[;\n])".format(
                        member_name=escape(other)),
                    self._content + "\n",
                    IGNORECASE,
            ):
                self._content = header_strip(self._content, elem)

        return self
Beispiel #5
0
 def __dir__(self) -> Iterable[str]:
     """
     Provide a better auto-completion when using Python interpreter. We are feeding __dir__ so Python can be aware
     of what properties are callable. In other words, more precise auto-completion when not using IDE.
     """
     return list(super().__dir__()) + [
         normalize_str(key) for key in self._valued_attrs.keys()
     ]
Beispiel #6
0
    def __contains__(self, item: Union[Header, str]) -> bool:
        """
        This method will allow you to test if a header, based on its string name, is present or not in headers.
        You could also use a Header object to verify it's presence.
        """
        item = normalize_str(item) if isinstance(item, str) else item

        for header in self:
            if isinstance(item, str) and header.normalized_name == item:
                return True
            if isinstance(item, Header) and header == item:
                return True

        return False
Beispiel #7
0
    def __init__(self, name: str, content: str):
        """
        :param name: The name of the header, should contain only ASCII characters with no spaces in it.
        :param content: Initial content associated with the header.
        """
        if not is_legal_header_name(name):
            raise ValueError(
                f"'{name}' is not a valid header name. Cannot proceed with it."
            )

        self._name: str = name
        self._normalized_name: str = normalize_str(self._name)
        self._pretty_name: str = prettify_header_name(self._name)
        self._content: str = content

        self._members: List[str] = header_content_split(self._content, ";")

        self._not_valued_attrs: List[str] = list()
        self._valued_attrs: MutableMapping[str, Union[
            str, List[str]]] = CaseInsensitiveDict()

        for member in self._members:
            if member == "":
                continue

            if "=" in member:
                key, value = tuple(member.split("=", maxsplit=1))

                # avoid confusing base64 look alike single value for (key, value)
                if value.count("=") == len(value) or len(
                        value) == 0 or " " in key:
                    self._not_valued_attrs.append(unquote(member))
                    continue

                if key not in self._valued_attrs:
                    self._valued_attrs[key] = value
                else:
                    if isinstance(self._valued_attrs[key], str):
                        self._valued_attrs[key] = [
                            self._valued_attrs[key], value
                        ]  # type: ignore
                    else:
                        self._valued_attrs[key].append(value)  # type: ignore

                continue

            self._not_valued_attrs.append(unquote(member))
Beispiel #8
0
    def __delattr__(self, item: str) -> None:
        """
        Remove any attribute named after the key in header using the property notation.
        >>> headers = Header("Content-Type", "text/html; charset=UTF-8") + Header("Vary", "Content-Type")
        >>> repr(headers.content_type)
        'Content-Type: text/html; charset=UTF-8'
        >>> del headers.content_type.charset
        >>> repr(headers.content_type)
        'Content-Type: text/html'
        """
        item = normalize_str(item)

        if item not in self._valued_attrs:
            raise AttributeError(
                "'{item}' attribute is not defined within '{header}' header.".
                format(item=item, header=self.name))

        del self[item]
Beispiel #9
0
    def index(self,
              __value: Union[Header, str],
              __start: int = 0,
              __stop: int = -1) -> int:
        """
        Search for the first appearance of an header based on its name or instance in Headers.
        Same method signature as list().index().
        Raises IndexError if not found.
        >>> headers = Header("A", "hello") + Header("B", "world") + Header("C", "funny; riddle")
        >>> headers.index("A")
        0
        >>> headers.index("A", 1)
        Traceback (most recent call last):
        ...
        IndexError: Value 'A' is not present within Headers.
        >>> headers.index("A", 0, 1)
        0
        >>> headers.index("C")
        2
        >>> headers.index(headers[0])
        0
        >>> headers.index(headers[1])
        1
        """

        value_is_header: bool = isinstance(__value, Header)
        normalized_value: Optional[str] = normalize_str(
            __value  # type: ignore
        ) if not value_is_header else None
        headers_len: int = len(self)

        # Convert indices to positive indices
        __start = __start % headers_len if __start < 0 else __start
        __stop = __stop % headers_len if __stop < 0 else __stop

        for header, index in zip(self._headers[__start:__stop + 1],
                                 range(__start, __stop + 1)):
            if value_is_header and __value == header:
                return index
            elif normalized_value == header.normalized_name:
                return index

        raise IndexError(f"Value '{__value}' is not present within Headers.")
Beispiel #10
0
    def __getitem__(self, item: Union[str, int]) -> Union[Header, List[Header]]:
        """
        Extract header using the bracket syntax, dict-like. The result is either a single Header or a list of Header.
        """
        if isinstance(item, int):
            return self._headers[item]

        item = normalize_str(item)

        if item not in self:
            raise KeyError(
                "'{item}' header is not defined in headers.".format(item=item)
            )

        headers: List[Header] = list()

        for header in self._headers:
            if header.normalized_name == item:
                headers.append(header)

        return headers if len(headers) > 1 or OUTPUT_LOCK_TYPE else headers.pop()
Beispiel #11
0
    def __delitem__(self, key: str) -> None:
        """
        Remove all matching header named after called key.
        >>> headers = Header("Content-Type", "text/html") + Header("Allow", "POST")
        >>> headers.has("Content-Type")
        True
        >>> del headers['content-type']
        >>> headers.has("Content-Type")
        False
        """
        key = normalize_str(key)
        to_be_removed = []

        if key not in self:
            raise KeyError(
                "'{item}' header is not defined in headers.".format(item=key))

        for header in self:
            if header.normalized_name == key:
                to_be_removed.append(header)

        for header in to_be_removed:
            self._headers.remove(header)
Beispiel #12
0
 def __delitem__(self, key: str) -> None:
     del self._store[normalize_str(key)]
Beispiel #13
0
 def __getitem__(self, key: str) -> Any:
     return self._store[normalize_str(key)][1]
Beispiel #14
0
 def __setitem__(self, key: str, value: Any) -> None:
     # Use the lowercased key for lookups, but store the actual
     # key alongside the value.
     self._store[normalize_str(key)] = (key, value)
Beispiel #15
0
def parse_it(raw_headers: Any) -> Headers:
    """
    Just decode anything that could contain headers. That simple PERIOD.
    :param raw_headers: Accept bytes, str, fp, dict, email.Message, requests.Response, urllib3.HTTPResponse and httpx.Response.
    :raises:
        TypeError: If passed argument cannot be parsed to extract headers from it.
    """

    headers: Optional[Iterable[Tuple[str, Any]]] = None

    if isinstance(raw_headers, str):
        headers = HeaderParser().parsestr(raw_headers,
                                          headersonly=True).items()
    elif isinstance(raw_headers, bytes) or isinstance(raw_headers, RawIOBase):
        decoded, not_decoded = extract_encoded_headers(
            raw_headers if isinstance(raw_headers, bytes
                                      ) else raw_headers.read() or b"")
        return parse_it(decoded)
    elif isinstance(raw_headers, Mapping) or isinstance(raw_headers, Message):
        headers = raw_headers.items()
    else:
        r = extract_class_name(type(raw_headers))

        if r:
            if r == "requests.models.Response":
                headers = []
                for header_name in raw_headers.raw.headers:
                    for header_content in raw_headers.raw.headers.getlist(
                            header_name):
                        headers.append((header_name, header_content))
            elif r in [
                    "httpx._models.Response", "urllib3.response.HTTPResponse"
            ]:
                headers = raw_headers.headers.items()

    if headers is None:
        raise TypeError(
            "Cannot parse type {type_} as it is not supported by kiss-header.".
            format(type_=type(raw_headers)))

    revised_headers: List[Tuple[str, str]] = decode_partials(headers)

    # Sometime raw content does not begin with headers. If that is the case, search for the next line.
    if (len(revised_headers) == 0 and len(raw_headers) > 0 and
        (isinstance(raw_headers, bytes) or isinstance(raw_headers, str))):
        next_iter = raw_headers.split(
            b"\n" if isinstance(raw_headers, bytes) else "\n",
            maxsplit=1  # type: ignore
        )

        if len(next_iter) >= 2:
            return parse_it(next_iter[-1])

    # Prepare Header objects
    list_of_headers: List[Header] = []

    for head, content in revised_headers:

        # We should ignore when a illegal name is considered as an header. We avoid ValueError (in __init__ of Header)
        if is_legal_header_name(head) is False:
            continue

        entries: List[str] = header_content_split(content, ",")

        # Multiple entries are detected in one content at the only exception that its not IMAP header "Subject".
        if len(entries) > 1 and normalize_str(head) != "subject":
            for entry in entries:
                list_of_headers.append(Header(head, entry))
        else:
            list_of_headers.append(Header(head, content))

    return Headers(*list_of_headers)