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))
def test_esc_double_quote(self): with self.subTest( "Ensure that the double quote character is handled correctly." ): attributes = Attributes( header_content_split(r'text/html; charset="UTF-\"8"', ";")) self.assertEqual(attributes["charset"], 'UTF-"8') self.assertEqual(str(attributes), r'text/html; charset="UTF-\"8"')
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
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))
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)