"""Representing and manipulating email headers via custom objects.This module provides an implementation of the HeaderRegistry API.The implementation is designed to flexibly follow RFC5322 rules.Eventually HeaderRegistry will be a public API, but it isn't yet,and will probably change some before that happens."""from types import MappingProxyTypefrom email import utilsfrom email import errorsfrom email import _header_value_parser as parserclass Address:def __init__(self, display_name='', username='', domain='', addr_spec=None):"""Create an object representing a full email address.An address can have a 'display_name', a 'username', and a 'domain'. Inaddition to specifying the username and domain separately, they may bespecified together by using the addr_spec keyword *instead of* theusername and domain keywords. If an addr_spec string is specified itmust be properly quoted according to RFC 5322 rules; an error will beraised if it is not.An Address object has display_name, username, domain, and addr_specattributes, all of which are read-only. The addr_spec and the stringvalue of the object are both quoted according to RFC5322 rules, butwithout any Content Transfer Encoding."""# This clause with its potential 'raise' may only happen when an# application program creates an Address object using an addr_spec# keyword. The email library code itself must always supply username# and domain.if addr_spec is not None:if username or domain:raise TypeError("addrspec specified when username and/or ""domain also specified")a_s, rest = parser.get_addr_spec(addr_spec)if rest:raise ValueError("Invalid addr_spec; only '{}' ""could be parsed from '{}'".format(a_s, addr_spec))if a_s.all_defects:raise a_s.all_defects[0]username = a_s.local_partdomain = a_s.domainself._display_name = display_nameself._username = usernameself._domain = domain@propertydef display_name(self):return self._display_name@propertydef username(self):return self._username@propertydef domain(self):return self._domain@propertydef addr_spec(self):"""The addr_spec (username@domain) portion of the address, quotedaccording to RFC 5322 rules, but with no Content Transfer Encoding."""nameset = set(self.username)if len(nameset) > len(nameset-parser.DOT_ATOM_ENDS):lp = parser.quote_string(self.username)else:lp = self.usernameif self.domain:return lp + '@' + self.domainif not lp:return '<>'return lpdef __repr__(self):return "{}(display_name={!r}, username={!r}, domain={!r})".format(self.__class__.__name__,self.display_name, self.username, self.domain)def __str__(self):nameset = set(self.display_name)if len(nameset) > len(nameset-parser.SPECIALS):disp = parser.quote_string(self.display_name)else:disp = self.display_nameif disp:addr_spec = '' if self.addr_spec=='<>' else self.addr_specreturn "{} <{}>".format(disp, addr_spec)return self.addr_specdef __eq__(self, other):if type(other) != type(self):return Falsereturn (self.display_name == other.display_name andself.username == other.username andself.domain == other.domain)class Group:def __init__(self, display_name=None, addresses=None):"""Create an object representing an address group.An address group consists of a display_name followed by colon and alist of addresses (see Address) terminated by a semi-colon. The Groupis created by specifying a display_name and a possibly empty list ofAddress objects. A Group can also be used to represent a singleaddress that is not in a group, which is convenient when manipulatinglists that are a combination of Groups and individual Addresses. Inthis case the display_name should be set to None. In particular, thestring representation of a Group whose display_name is None is the sameas the Address object, if there is one and only one Address object inthe addresses list."""self._display_name = display_nameself._addresses = tuple(addresses) if addresses else tuple()@propertydef display_name(self):return self._display_name@propertydef addresses(self):return self._addressesdef __repr__(self):return "{}(display_name={!r}, addresses={!r}".format(self.__class__.__name__,self.display_name, self.addresses)def __str__(self):if self.display_name is None and len(self.addresses)==1:return str(self.addresses[0])disp = self.display_nameif disp is not None:nameset = set(disp)if len(nameset) > len(nameset-parser.SPECIALS):disp = parser.quote_string(disp)adrstr = ", ".join(str(x) for x in self.addresses)adrstr = ' ' + adrstr if adrstr else adrstrreturn "{}:{};".format(disp, adrstr)def __eq__(self, other):if type(other) != type(self):return Falsereturn (self.display_name == other.display_name andself.addresses == other.addresses)# Header Classes #class BaseHeader(str):"""Base class for message headers.Implements generic behavior and provides tools for subclasses.A subclass must define a classmethod named 'parse' that takes an unfoldedvalue string and a dictionary as its arguments. The dictionary willcontain one key, 'defects', initialized to an empty list. After the callthe dictionary must contain two additional keys: parse_tree, set to theparse tree obtained from parsing the header, and 'decoded', set to thestring value of the idealized representation of the data from the value.(That is, encoded words are decoded, and values that have canonicalrepresentations are so represented.)The defects key is intended to collect parsing defects, which the messageparser will subsequently dispose of as appropriate. The parser should not,insofar as practical, raise any errors. Defects should be added to thelist instead. The standard header parsers register defects for RFCcompliance issues, for obsolete RFC syntax, and for unrecoverable parsingerrors.The parse method may add additional keys to the dictionary. In this casethe subclass must define an 'init' method, which will be passed thedictionary as its keyword arguments. The method should use (usually bysetting them as the value of similarly named attributes) and remove all theextra keys added by its parse method, and then use super to call its parentclass with the remaining arguments and keywords.The subclass should also make sure that a 'max_count' attribute is definedthat is either None or 1. XXX: need to better define this API."""def __new__(cls, name, value):kwds = {'defects': []}cls.parse(value, kwds)if utils._has_surrogates(kwds['decoded']):kwds['decoded'] = utils._sanitize(kwds['decoded'])self = str.__new__(cls, kwds['decoded'])del kwds['decoded']self.init(name, **kwds)return selfdef init(self, name, *, parse_tree, defects):self._name = nameself._parse_tree = parse_treeself._defects = defects@propertydef name(self):return self._name@propertydef defects(self):return tuple(self._defects)def __reduce__(self):return (_reconstruct_header,(self.__class__.__name__,self.__class__.__bases__,str(self),),self.__dict__)@classmethoddef _reconstruct(cls, value):return str.__new__(cls, value)def fold(self, *, policy):"""Fold header according to policy.The parsed representation of the header is folded according toRFC5322 rules, as modified by the policy. If the parse treecontains surrogateescaped bytes, the bytes are CTE encoded usingthe charset 'unknown-8bit".Any non-ASCII characters in the parse tree are CTE encoded usingcharset utf-8. XXX: make this a policy setting.The returned value is an ASCII-only string possibly containing linesepcharacters, and ending with a linesep character. The string includesthe header name and the ': ' separator."""# At some point we need to put fws here if it was in the source.header = parser.Header([parser.HeaderLabel([parser.ValueTerminal(self.name, 'header-name'),parser.ValueTerminal(':', 'header-sep')]),])if self._parse_tree:header.append(parser.CFWSList([parser.WhiteSpaceTerminal(' ', 'fws')]))header.append(self._parse_tree)return header.fold(policy=policy)def _reconstruct_header(cls_name, bases, value):return type(cls_name, bases, {})._reconstruct(value)class UnstructuredHeader:max_count = Nonevalue_parser = staticmethod(parser.get_unstructured)@classmethoddef parse(cls, value, kwds):kwds['parse_tree'] = cls.value_parser(value)kwds['decoded'] = str(kwds['parse_tree'])class UniqueUnstructuredHeader(UnstructuredHeader):max_count = 1class DateHeader:"""Header whose value consists of a single timestamp.Provides an additional attribute, datetime, which is either an awaredatetime using a timezone, or a naive datetime if the timezonein the input string is -0000. Also accepts a datetime as input.The 'value' attribute is the normalized form of the timestamp,which means it is the output of format_datetime on the datetime."""max_count = None# This is used only for folding, not for creating 'decoded'.value_parser = staticmethod(parser.get_unstructured)@classmethoddef parse(cls, value, kwds):if not value:kwds['defects'].append(errors.HeaderMissingRequiredValue())kwds['datetime'] = Nonekwds['decoded'] = ''kwds['parse_tree'] = parser.TokenList()returnif isinstance(value, str):value = utils.parsedate_to_datetime(value)kwds['datetime'] = valuekwds['decoded'] = utils.format_datetime(kwds['datetime'])kwds['parse_tree'] = cls.value_parser(kwds['decoded'])def init(self, *args, **kw):self._datetime = kw.pop('datetime')super().init(*args, **kw)@propertydef datetime(self):return self._datetimeclass UniqueDateHeader(DateHeader):max_count = 1class AddressHeader:max_count = None@staticmethoddef value_parser(value):address_list, value = parser.get_address_list(value)assert not value, 'this should not happen'return address_list@classmethoddef parse(cls, value, kwds):if isinstance(value, str):# We are translating here from the RFC language (address/mailbox)# to our API language (group/address).kwds['parse_tree'] = address_list = cls.value_parser(value)groups = []for addr in address_list.addresses:groups.append(Group(addr.display_name,[Address(mb.display_name or '',mb.local_part or '',mb.domain or '')for mb in addr.all_mailboxes]))defects = list(address_list.all_defects)else:# Assume it is Address/Group stuffif not hasattr(value, '__iter__'):value = [value]groups = [Group(None, [item]) if not hasattr(item, 'addresses')else itemfor item in value]defects = []kwds['groups'] = groupskwds['defects'] = defectskwds['decoded'] = ', '.join([str(item) for item in groups])if 'parse_tree' not in kwds:kwds['parse_tree'] = cls.value_parser(kwds['decoded'])def init(self, *args, **kw):self._groups = tuple(kw.pop('groups'))self._addresses = Nonesuper().init(*args, **kw)@propertydef groups(self):return self._groups@propertydef addresses(self):if self._addresses is None:self._addresses = tuple(address for group in self._groupsfor address in group.addresses)return self._addressesclass UniqueAddressHeader(AddressHeader):max_count = 1class SingleAddressHeader(AddressHeader):@propertydef address(self):if len(self.addresses)!=1:raise ValueError(("value of single address header {} is not ""a single address").format(self.name))return self.addresses[0]class UniqueSingleAddressHeader(SingleAddressHeader):max_count = 1class MIMEVersionHeader:max_count = 1value_parser = staticmethod(parser.parse_mime_version)@classmethoddef parse(cls, value, kwds):kwds['parse_tree'] = parse_tree = cls.value_parser(value)kwds['decoded'] = str(parse_tree)kwds['defects'].extend(parse_tree.all_defects)kwds['major'] = None if parse_tree.minor is None else parse_tree.majorkwds['minor'] = parse_tree.minorif parse_tree.minor is not None:kwds['version'] = '{}.{}'.format(kwds['major'], kwds['minor'])else:kwds['version'] = Nonedef init(self, *args, **kw):self._version = kw.pop('version')self._major = kw.pop('major')self._minor = kw.pop('minor')super().init(*args, **kw)@propertydef major(self):return self._major@propertydef minor(self):return self._minor@propertydef version(self):return self._versionclass ParameterizedMIMEHeader:# Mixin that handles the params dict. Must be subclassed and# a property value_parser for the specific header provided.max_count = 1@classmethoddef parse(cls, value, kwds):kwds['parse_tree'] = parse_tree = cls.value_parser(value)kwds['decoded'] = str(parse_tree)kwds['defects'].extend(parse_tree.all_defects)if parse_tree.params is None:kwds['params'] = {}else:# The MIME RFCs specify that parameter ordering is arbitrary.kwds['params'] = {utils._sanitize(name).lower():utils._sanitize(value)for name, value in parse_tree.params}def init(self, *args, **kw):self._params = kw.pop('params')super().init(*args, **kw)@propertydef params(self):return MappingProxyType(self._params)class ContentTypeHeader(ParameterizedMIMEHeader):value_parser = staticmethod(parser.parse_content_type_header)def init(self, *args, **kw):super().init(*args, **kw)self._maintype = utils._sanitize(self._parse_tree.maintype)self._subtype = utils._sanitize(self._parse_tree.subtype)@propertydef maintype(self):return self._maintype@propertydef subtype(self):return self._subtype@propertydef content_type(self):return self.maintype + '/' + self.subtypeclass ContentDispositionHeader(ParameterizedMIMEHeader):value_parser = staticmethod(parser.parse_content_disposition_header)def init(self, *args, **kw):super().init(*args, **kw)cd = self._parse_tree.content_dispositionself._content_disposition = cd if cd is None else utils._sanitize(cd)@propertydef content_disposition(self):return self._content_dispositionclass ContentTransferEncodingHeader:max_count = 1value_parser = staticmethod(parser.parse_content_transfer_encoding_header)@classmethoddef parse(cls, value, kwds):kwds['parse_tree'] = parse_tree = cls.value_parser(value)kwds['decoded'] = str(parse_tree)kwds['defects'].extend(parse_tree.all_defects)def init(self, *args, **kw):super().init(*args, **kw)self._cte = utils._sanitize(self._parse_tree.cte)@propertydef cte(self):return self._cteclass MessageIDHeader:max_count = 1value_parser = staticmethod(parser.parse_message_id)@classmethoddef parse(cls, value, kwds):kwds['parse_tree'] = parse_tree = cls.value_parser(value)kwds['decoded'] = str(parse_tree)kwds['defects'].extend(parse_tree.all_defects)# The header factory #_default_header_map = {'subject': UniqueUnstructuredHeader,'date': UniqueDateHeader,'resent-date': DateHeader,'orig-date': UniqueDateHeader,'sender': UniqueSingleAddressHeader,'resent-sender': SingleAddressHeader,'to': UniqueAddressHeader,'resent-to': AddressHeader,'cc': UniqueAddressHeader,'resent-cc': AddressHeader,'bcc': UniqueAddressHeader,'resent-bcc': AddressHeader,'from': UniqueAddressHeader,'resent-from': AddressHeader,'reply-to': UniqueAddressHeader,'mime-version': MIMEVersionHeader,'content-type': ContentTypeHeader,'content-disposition': ContentDispositionHeader,'content-transfer-encoding': ContentTransferEncodingHeader,'message-id': MessageIDHeader,}class HeaderRegistry:"""A header_factory and header registry."""def __init__(self, base_class=BaseHeader, default_class=UnstructuredHeader,use_default_map=True):"""Create a header_factory that works with the Policy API.base_class is the class that will be the last class in the createdheader class's __bases__ list. default_class is the class that will beused if "name" (see __call__) does not appear in the registry.use_default_map controls whether or not the default mapping of names tospecialized classes is copied in to the registry when the factory iscreated. The default is True."""self.registry = {}self.base_class = base_classself.default_class = default_classif use_default_map:self.registry.update(_default_header_map)def map_to_type(self, name, cls):"""Register cls as the specialized class for handling "name" headers."""self.registry[name.lower()] = clsdef __getitem__(self, name):cls = self.registry.get(name.lower(), self.default_class)return type('_'+cls.__name__, (cls, self.base_class), {})def __call__(self, name, value):"""Create a header instance for header 'name' from 'value'.Creates a header instance by creating a specialized class for parsingand representing the specified header by combining the factorybase_class with a specialized class from the registry or thedefault_class, and passing the name and value to the constructedclass's constructor."""return self[name](name, value)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。