17db96d56Sopenharmony_ci"""Representing and manipulating email headers via custom objects.
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciThis module provides an implementation of the HeaderRegistry API.
47db96d56Sopenharmony_ciThe implementation is designed to flexibly follow RFC5322 rules.
57db96d56Sopenharmony_ci"""
67db96d56Sopenharmony_cifrom types import MappingProxyType
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_cifrom email import utils
97db96d56Sopenharmony_cifrom email import errors
107db96d56Sopenharmony_cifrom email import _header_value_parser as parser
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_ciclass Address:
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_ci    def __init__(self, display_name='', username='', domain='', addr_spec=None):
157db96d56Sopenharmony_ci        """Create an object representing a full email address.
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ci        An address can have a 'display_name', a 'username', and a 'domain'.  In
187db96d56Sopenharmony_ci        addition to specifying the username and domain separately, they may be
197db96d56Sopenharmony_ci        specified together by using the addr_spec keyword *instead of* the
207db96d56Sopenharmony_ci        username and domain keywords.  If an addr_spec string is specified it
217db96d56Sopenharmony_ci        must be properly quoted according to RFC 5322 rules; an error will be
227db96d56Sopenharmony_ci        raised if it is not.
237db96d56Sopenharmony_ci
247db96d56Sopenharmony_ci        An Address object has display_name, username, domain, and addr_spec
257db96d56Sopenharmony_ci        attributes, all of which are read-only.  The addr_spec and the string
267db96d56Sopenharmony_ci        value of the object are both quoted according to RFC5322 rules, but
277db96d56Sopenharmony_ci        without any Content Transfer Encoding.
287db96d56Sopenharmony_ci
297db96d56Sopenharmony_ci        """
307db96d56Sopenharmony_ci
317db96d56Sopenharmony_ci        inputs = ''.join(filter(None, (display_name, username, domain, addr_spec)))
327db96d56Sopenharmony_ci        if '\r' in inputs or '\n' in inputs:
337db96d56Sopenharmony_ci            raise ValueError("invalid arguments; address parts cannot contain CR or LF")
347db96d56Sopenharmony_ci
357db96d56Sopenharmony_ci        # This clause with its potential 'raise' may only happen when an
367db96d56Sopenharmony_ci        # application program creates an Address object using an addr_spec
377db96d56Sopenharmony_ci        # keyword.  The email library code itself must always supply username
387db96d56Sopenharmony_ci        # and domain.
397db96d56Sopenharmony_ci        if addr_spec is not None:
407db96d56Sopenharmony_ci            if username or domain:
417db96d56Sopenharmony_ci                raise TypeError("addrspec specified when username and/or "
427db96d56Sopenharmony_ci                                "domain also specified")
437db96d56Sopenharmony_ci            a_s, rest = parser.get_addr_spec(addr_spec)
447db96d56Sopenharmony_ci            if rest:
457db96d56Sopenharmony_ci                raise ValueError("Invalid addr_spec; only '{}' "
467db96d56Sopenharmony_ci                                 "could be parsed from '{}'".format(
477db96d56Sopenharmony_ci                                    a_s, addr_spec))
487db96d56Sopenharmony_ci            if a_s.all_defects:
497db96d56Sopenharmony_ci                raise a_s.all_defects[0]
507db96d56Sopenharmony_ci            username = a_s.local_part
517db96d56Sopenharmony_ci            domain = a_s.domain
527db96d56Sopenharmony_ci        self._display_name = display_name
537db96d56Sopenharmony_ci        self._username = username
547db96d56Sopenharmony_ci        self._domain = domain
557db96d56Sopenharmony_ci
567db96d56Sopenharmony_ci    @property
577db96d56Sopenharmony_ci    def display_name(self):
587db96d56Sopenharmony_ci        return self._display_name
597db96d56Sopenharmony_ci
607db96d56Sopenharmony_ci    @property
617db96d56Sopenharmony_ci    def username(self):
627db96d56Sopenharmony_ci        return self._username
637db96d56Sopenharmony_ci
647db96d56Sopenharmony_ci    @property
657db96d56Sopenharmony_ci    def domain(self):
667db96d56Sopenharmony_ci        return self._domain
677db96d56Sopenharmony_ci
687db96d56Sopenharmony_ci    @property
697db96d56Sopenharmony_ci    def addr_spec(self):
707db96d56Sopenharmony_ci        """The addr_spec (username@domain) portion of the address, quoted
717db96d56Sopenharmony_ci        according to RFC 5322 rules, but with no Content Transfer Encoding.
727db96d56Sopenharmony_ci        """
737db96d56Sopenharmony_ci        lp = self.username
747db96d56Sopenharmony_ci        if not parser.DOT_ATOM_ENDS.isdisjoint(lp):
757db96d56Sopenharmony_ci            lp = parser.quote_string(lp)
767db96d56Sopenharmony_ci        if self.domain:
777db96d56Sopenharmony_ci            return lp + '@' + self.domain
787db96d56Sopenharmony_ci        if not lp:
797db96d56Sopenharmony_ci            return '<>'
807db96d56Sopenharmony_ci        return lp
817db96d56Sopenharmony_ci
827db96d56Sopenharmony_ci    def __repr__(self):
837db96d56Sopenharmony_ci        return "{}(display_name={!r}, username={!r}, domain={!r})".format(
847db96d56Sopenharmony_ci                        self.__class__.__name__,
857db96d56Sopenharmony_ci                        self.display_name, self.username, self.domain)
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ci    def __str__(self):
887db96d56Sopenharmony_ci        disp = self.display_name
897db96d56Sopenharmony_ci        if not parser.SPECIALS.isdisjoint(disp):
907db96d56Sopenharmony_ci            disp = parser.quote_string(disp)
917db96d56Sopenharmony_ci        if disp:
927db96d56Sopenharmony_ci            addr_spec = '' if self.addr_spec=='<>' else self.addr_spec
937db96d56Sopenharmony_ci            return "{} <{}>".format(disp, addr_spec)
947db96d56Sopenharmony_ci        return self.addr_spec
957db96d56Sopenharmony_ci
967db96d56Sopenharmony_ci    def __eq__(self, other):
977db96d56Sopenharmony_ci        if not isinstance(other, Address):
987db96d56Sopenharmony_ci            return NotImplemented
997db96d56Sopenharmony_ci        return (self.display_name == other.display_name and
1007db96d56Sopenharmony_ci                self.username == other.username and
1017db96d56Sopenharmony_ci                self.domain == other.domain)
1027db96d56Sopenharmony_ci
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ciclass Group:
1057db96d56Sopenharmony_ci
1067db96d56Sopenharmony_ci    def __init__(self, display_name=None, addresses=None):
1077db96d56Sopenharmony_ci        """Create an object representing an address group.
1087db96d56Sopenharmony_ci
1097db96d56Sopenharmony_ci        An address group consists of a display_name followed by colon and a
1107db96d56Sopenharmony_ci        list of addresses (see Address) terminated by a semi-colon.  The Group
1117db96d56Sopenharmony_ci        is created by specifying a display_name and a possibly empty list of
1127db96d56Sopenharmony_ci        Address objects.  A Group can also be used to represent a single
1137db96d56Sopenharmony_ci        address that is not in a group, which is convenient when manipulating
1147db96d56Sopenharmony_ci        lists that are a combination of Groups and individual Addresses.  In
1157db96d56Sopenharmony_ci        this case the display_name should be set to None.  In particular, the
1167db96d56Sopenharmony_ci        string representation of a Group whose display_name is None is the same
1177db96d56Sopenharmony_ci        as the Address object, if there is one and only one Address object in
1187db96d56Sopenharmony_ci        the addresses list.
1197db96d56Sopenharmony_ci
1207db96d56Sopenharmony_ci        """
1217db96d56Sopenharmony_ci        self._display_name = display_name
1227db96d56Sopenharmony_ci        self._addresses = tuple(addresses) if addresses else tuple()
1237db96d56Sopenharmony_ci
1247db96d56Sopenharmony_ci    @property
1257db96d56Sopenharmony_ci    def display_name(self):
1267db96d56Sopenharmony_ci        return self._display_name
1277db96d56Sopenharmony_ci
1287db96d56Sopenharmony_ci    @property
1297db96d56Sopenharmony_ci    def addresses(self):
1307db96d56Sopenharmony_ci        return self._addresses
1317db96d56Sopenharmony_ci
1327db96d56Sopenharmony_ci    def __repr__(self):
1337db96d56Sopenharmony_ci        return "{}(display_name={!r}, addresses={!r}".format(
1347db96d56Sopenharmony_ci                 self.__class__.__name__,
1357db96d56Sopenharmony_ci                 self.display_name, self.addresses)
1367db96d56Sopenharmony_ci
1377db96d56Sopenharmony_ci    def __str__(self):
1387db96d56Sopenharmony_ci        if self.display_name is None and len(self.addresses)==1:
1397db96d56Sopenharmony_ci            return str(self.addresses[0])
1407db96d56Sopenharmony_ci        disp = self.display_name
1417db96d56Sopenharmony_ci        if disp is not None and not parser.SPECIALS.isdisjoint(disp):
1427db96d56Sopenharmony_ci            disp = parser.quote_string(disp)
1437db96d56Sopenharmony_ci        adrstr = ", ".join(str(x) for x in self.addresses)
1447db96d56Sopenharmony_ci        adrstr = ' ' + adrstr if adrstr else adrstr
1457db96d56Sopenharmony_ci        return "{}:{};".format(disp, adrstr)
1467db96d56Sopenharmony_ci
1477db96d56Sopenharmony_ci    def __eq__(self, other):
1487db96d56Sopenharmony_ci        if not isinstance(other, Group):
1497db96d56Sopenharmony_ci            return NotImplemented
1507db96d56Sopenharmony_ci        return (self.display_name == other.display_name and
1517db96d56Sopenharmony_ci                self.addresses == other.addresses)
1527db96d56Sopenharmony_ci
1537db96d56Sopenharmony_ci
1547db96d56Sopenharmony_ci# Header Classes #
1557db96d56Sopenharmony_ci
1567db96d56Sopenharmony_ciclass BaseHeader(str):
1577db96d56Sopenharmony_ci
1587db96d56Sopenharmony_ci    """Base class for message headers.
1597db96d56Sopenharmony_ci
1607db96d56Sopenharmony_ci    Implements generic behavior and provides tools for subclasses.
1617db96d56Sopenharmony_ci
1627db96d56Sopenharmony_ci    A subclass must define a classmethod named 'parse' that takes an unfolded
1637db96d56Sopenharmony_ci    value string and a dictionary as its arguments.  The dictionary will
1647db96d56Sopenharmony_ci    contain one key, 'defects', initialized to an empty list.  After the call
1657db96d56Sopenharmony_ci    the dictionary must contain two additional keys: parse_tree, set to the
1667db96d56Sopenharmony_ci    parse tree obtained from parsing the header, and 'decoded', set to the
1677db96d56Sopenharmony_ci    string value of the idealized representation of the data from the value.
1687db96d56Sopenharmony_ci    (That is, encoded words are decoded, and values that have canonical
1697db96d56Sopenharmony_ci    representations are so represented.)
1707db96d56Sopenharmony_ci
1717db96d56Sopenharmony_ci    The defects key is intended to collect parsing defects, which the message
1727db96d56Sopenharmony_ci    parser will subsequently dispose of as appropriate.  The parser should not,
1737db96d56Sopenharmony_ci    insofar as practical, raise any errors.  Defects should be added to the
1747db96d56Sopenharmony_ci    list instead.  The standard header parsers register defects for RFC
1757db96d56Sopenharmony_ci    compliance issues, for obsolete RFC syntax, and for unrecoverable parsing
1767db96d56Sopenharmony_ci    errors.
1777db96d56Sopenharmony_ci
1787db96d56Sopenharmony_ci    The parse method may add additional keys to the dictionary.  In this case
1797db96d56Sopenharmony_ci    the subclass must define an 'init' method, which will be passed the
1807db96d56Sopenharmony_ci    dictionary as its keyword arguments.  The method should use (usually by
1817db96d56Sopenharmony_ci    setting them as the value of similarly named attributes) and remove all the
1827db96d56Sopenharmony_ci    extra keys added by its parse method, and then use super to call its parent
1837db96d56Sopenharmony_ci    class with the remaining arguments and keywords.
1847db96d56Sopenharmony_ci
1857db96d56Sopenharmony_ci    The subclass should also make sure that a 'max_count' attribute is defined
1867db96d56Sopenharmony_ci    that is either None or 1. XXX: need to better define this API.
1877db96d56Sopenharmony_ci
1887db96d56Sopenharmony_ci    """
1897db96d56Sopenharmony_ci
1907db96d56Sopenharmony_ci    def __new__(cls, name, value):
1917db96d56Sopenharmony_ci        kwds = {'defects': []}
1927db96d56Sopenharmony_ci        cls.parse(value, kwds)
1937db96d56Sopenharmony_ci        if utils._has_surrogates(kwds['decoded']):
1947db96d56Sopenharmony_ci            kwds['decoded'] = utils._sanitize(kwds['decoded'])
1957db96d56Sopenharmony_ci        self = str.__new__(cls, kwds['decoded'])
1967db96d56Sopenharmony_ci        del kwds['decoded']
1977db96d56Sopenharmony_ci        self.init(name, **kwds)
1987db96d56Sopenharmony_ci        return self
1997db96d56Sopenharmony_ci
2007db96d56Sopenharmony_ci    def init(self, name, *, parse_tree, defects):
2017db96d56Sopenharmony_ci        self._name = name
2027db96d56Sopenharmony_ci        self._parse_tree = parse_tree
2037db96d56Sopenharmony_ci        self._defects = defects
2047db96d56Sopenharmony_ci
2057db96d56Sopenharmony_ci    @property
2067db96d56Sopenharmony_ci    def name(self):
2077db96d56Sopenharmony_ci        return self._name
2087db96d56Sopenharmony_ci
2097db96d56Sopenharmony_ci    @property
2107db96d56Sopenharmony_ci    def defects(self):
2117db96d56Sopenharmony_ci        return tuple(self._defects)
2127db96d56Sopenharmony_ci
2137db96d56Sopenharmony_ci    def __reduce__(self):
2147db96d56Sopenharmony_ci        return (
2157db96d56Sopenharmony_ci            _reconstruct_header,
2167db96d56Sopenharmony_ci            (
2177db96d56Sopenharmony_ci                self.__class__.__name__,
2187db96d56Sopenharmony_ci                self.__class__.__bases__,
2197db96d56Sopenharmony_ci                str(self),
2207db96d56Sopenharmony_ci            ),
2217db96d56Sopenharmony_ci            self.__getstate__())
2227db96d56Sopenharmony_ci
2237db96d56Sopenharmony_ci    @classmethod
2247db96d56Sopenharmony_ci    def _reconstruct(cls, value):
2257db96d56Sopenharmony_ci        return str.__new__(cls, value)
2267db96d56Sopenharmony_ci
2277db96d56Sopenharmony_ci    def fold(self, *, policy):
2287db96d56Sopenharmony_ci        """Fold header according to policy.
2297db96d56Sopenharmony_ci
2307db96d56Sopenharmony_ci        The parsed representation of the header is folded according to
2317db96d56Sopenharmony_ci        RFC5322 rules, as modified by the policy.  If the parse tree
2327db96d56Sopenharmony_ci        contains surrogateescaped bytes, the bytes are CTE encoded using
2337db96d56Sopenharmony_ci        the charset 'unknown-8bit".
2347db96d56Sopenharmony_ci
2357db96d56Sopenharmony_ci        Any non-ASCII characters in the parse tree are CTE encoded using
2367db96d56Sopenharmony_ci        charset utf-8. XXX: make this a policy setting.
2377db96d56Sopenharmony_ci
2387db96d56Sopenharmony_ci        The returned value is an ASCII-only string possibly containing linesep
2397db96d56Sopenharmony_ci        characters, and ending with a linesep character.  The string includes
2407db96d56Sopenharmony_ci        the header name and the ': ' separator.
2417db96d56Sopenharmony_ci
2427db96d56Sopenharmony_ci        """
2437db96d56Sopenharmony_ci        # At some point we need to put fws here if it was in the source.
2447db96d56Sopenharmony_ci        header = parser.Header([
2457db96d56Sopenharmony_ci            parser.HeaderLabel([
2467db96d56Sopenharmony_ci                parser.ValueTerminal(self.name, 'header-name'),
2477db96d56Sopenharmony_ci                parser.ValueTerminal(':', 'header-sep')]),
2487db96d56Sopenharmony_ci            ])
2497db96d56Sopenharmony_ci        if self._parse_tree:
2507db96d56Sopenharmony_ci            header.append(
2517db96d56Sopenharmony_ci                parser.CFWSList([parser.WhiteSpaceTerminal(' ', 'fws')]))
2527db96d56Sopenharmony_ci        header.append(self._parse_tree)
2537db96d56Sopenharmony_ci        return header.fold(policy=policy)
2547db96d56Sopenharmony_ci
2557db96d56Sopenharmony_ci
2567db96d56Sopenharmony_cidef _reconstruct_header(cls_name, bases, value):
2577db96d56Sopenharmony_ci    return type(cls_name, bases, {})._reconstruct(value)
2587db96d56Sopenharmony_ci
2597db96d56Sopenharmony_ci
2607db96d56Sopenharmony_ciclass UnstructuredHeader:
2617db96d56Sopenharmony_ci
2627db96d56Sopenharmony_ci    max_count = None
2637db96d56Sopenharmony_ci    value_parser = staticmethod(parser.get_unstructured)
2647db96d56Sopenharmony_ci
2657db96d56Sopenharmony_ci    @classmethod
2667db96d56Sopenharmony_ci    def parse(cls, value, kwds):
2677db96d56Sopenharmony_ci        kwds['parse_tree'] = cls.value_parser(value)
2687db96d56Sopenharmony_ci        kwds['decoded'] = str(kwds['parse_tree'])
2697db96d56Sopenharmony_ci
2707db96d56Sopenharmony_ci
2717db96d56Sopenharmony_ciclass UniqueUnstructuredHeader(UnstructuredHeader):
2727db96d56Sopenharmony_ci
2737db96d56Sopenharmony_ci    max_count = 1
2747db96d56Sopenharmony_ci
2757db96d56Sopenharmony_ci
2767db96d56Sopenharmony_ciclass DateHeader:
2777db96d56Sopenharmony_ci
2787db96d56Sopenharmony_ci    """Header whose value consists of a single timestamp.
2797db96d56Sopenharmony_ci
2807db96d56Sopenharmony_ci    Provides an additional attribute, datetime, which is either an aware
2817db96d56Sopenharmony_ci    datetime using a timezone, or a naive datetime if the timezone
2827db96d56Sopenharmony_ci    in the input string is -0000.  Also accepts a datetime as input.
2837db96d56Sopenharmony_ci    The 'value' attribute is the normalized form of the timestamp,
2847db96d56Sopenharmony_ci    which means it is the output of format_datetime on the datetime.
2857db96d56Sopenharmony_ci    """
2867db96d56Sopenharmony_ci
2877db96d56Sopenharmony_ci    max_count = None
2887db96d56Sopenharmony_ci
2897db96d56Sopenharmony_ci    # This is used only for folding, not for creating 'decoded'.
2907db96d56Sopenharmony_ci    value_parser = staticmethod(parser.get_unstructured)
2917db96d56Sopenharmony_ci
2927db96d56Sopenharmony_ci    @classmethod
2937db96d56Sopenharmony_ci    def parse(cls, value, kwds):
2947db96d56Sopenharmony_ci        if not value:
2957db96d56Sopenharmony_ci            kwds['defects'].append(errors.HeaderMissingRequiredValue())
2967db96d56Sopenharmony_ci            kwds['datetime'] = None
2977db96d56Sopenharmony_ci            kwds['decoded'] = ''
2987db96d56Sopenharmony_ci            kwds['parse_tree'] = parser.TokenList()
2997db96d56Sopenharmony_ci            return
3007db96d56Sopenharmony_ci        if isinstance(value, str):
3017db96d56Sopenharmony_ci            kwds['decoded'] = value
3027db96d56Sopenharmony_ci            try:
3037db96d56Sopenharmony_ci                value = utils.parsedate_to_datetime(value)
3047db96d56Sopenharmony_ci            except ValueError:
3057db96d56Sopenharmony_ci                kwds['defects'].append(errors.InvalidDateDefect('Invalid date value or format'))
3067db96d56Sopenharmony_ci                kwds['datetime'] = None
3077db96d56Sopenharmony_ci                kwds['parse_tree'] = parser.TokenList()
3087db96d56Sopenharmony_ci                return
3097db96d56Sopenharmony_ci        kwds['datetime'] = value
3107db96d56Sopenharmony_ci        kwds['decoded'] = utils.format_datetime(kwds['datetime'])
3117db96d56Sopenharmony_ci        kwds['parse_tree'] = cls.value_parser(kwds['decoded'])
3127db96d56Sopenharmony_ci
3137db96d56Sopenharmony_ci    def init(self, *args, **kw):
3147db96d56Sopenharmony_ci        self._datetime = kw.pop('datetime')
3157db96d56Sopenharmony_ci        super().init(*args, **kw)
3167db96d56Sopenharmony_ci
3177db96d56Sopenharmony_ci    @property
3187db96d56Sopenharmony_ci    def datetime(self):
3197db96d56Sopenharmony_ci        return self._datetime
3207db96d56Sopenharmony_ci
3217db96d56Sopenharmony_ci
3227db96d56Sopenharmony_ciclass UniqueDateHeader(DateHeader):
3237db96d56Sopenharmony_ci
3247db96d56Sopenharmony_ci    max_count = 1
3257db96d56Sopenharmony_ci
3267db96d56Sopenharmony_ci
3277db96d56Sopenharmony_ciclass AddressHeader:
3287db96d56Sopenharmony_ci
3297db96d56Sopenharmony_ci    max_count = None
3307db96d56Sopenharmony_ci
3317db96d56Sopenharmony_ci    @staticmethod
3327db96d56Sopenharmony_ci    def value_parser(value):
3337db96d56Sopenharmony_ci        address_list, value = parser.get_address_list(value)
3347db96d56Sopenharmony_ci        assert not value, 'this should not happen'
3357db96d56Sopenharmony_ci        return address_list
3367db96d56Sopenharmony_ci
3377db96d56Sopenharmony_ci    @classmethod
3387db96d56Sopenharmony_ci    def parse(cls, value, kwds):
3397db96d56Sopenharmony_ci        if isinstance(value, str):
3407db96d56Sopenharmony_ci            # We are translating here from the RFC language (address/mailbox)
3417db96d56Sopenharmony_ci            # to our API language (group/address).
3427db96d56Sopenharmony_ci            kwds['parse_tree'] = address_list = cls.value_parser(value)
3437db96d56Sopenharmony_ci            groups = []
3447db96d56Sopenharmony_ci            for addr in address_list.addresses:
3457db96d56Sopenharmony_ci                groups.append(Group(addr.display_name,
3467db96d56Sopenharmony_ci                                    [Address(mb.display_name or '',
3477db96d56Sopenharmony_ci                                             mb.local_part or '',
3487db96d56Sopenharmony_ci                                             mb.domain or '')
3497db96d56Sopenharmony_ci                                     for mb in addr.all_mailboxes]))
3507db96d56Sopenharmony_ci            defects = list(address_list.all_defects)
3517db96d56Sopenharmony_ci        else:
3527db96d56Sopenharmony_ci            # Assume it is Address/Group stuff
3537db96d56Sopenharmony_ci            if not hasattr(value, '__iter__'):
3547db96d56Sopenharmony_ci                value = [value]
3557db96d56Sopenharmony_ci            groups = [Group(None, [item]) if not hasattr(item, 'addresses')
3567db96d56Sopenharmony_ci                                          else item
3577db96d56Sopenharmony_ci                                    for item in value]
3587db96d56Sopenharmony_ci            defects = []
3597db96d56Sopenharmony_ci        kwds['groups'] = groups
3607db96d56Sopenharmony_ci        kwds['defects'] = defects
3617db96d56Sopenharmony_ci        kwds['decoded'] = ', '.join([str(item) for item in groups])
3627db96d56Sopenharmony_ci        if 'parse_tree' not in kwds:
3637db96d56Sopenharmony_ci            kwds['parse_tree'] = cls.value_parser(kwds['decoded'])
3647db96d56Sopenharmony_ci
3657db96d56Sopenharmony_ci    def init(self, *args, **kw):
3667db96d56Sopenharmony_ci        self._groups = tuple(kw.pop('groups'))
3677db96d56Sopenharmony_ci        self._addresses = None
3687db96d56Sopenharmony_ci        super().init(*args, **kw)
3697db96d56Sopenharmony_ci
3707db96d56Sopenharmony_ci    @property
3717db96d56Sopenharmony_ci    def groups(self):
3727db96d56Sopenharmony_ci        return self._groups
3737db96d56Sopenharmony_ci
3747db96d56Sopenharmony_ci    @property
3757db96d56Sopenharmony_ci    def addresses(self):
3767db96d56Sopenharmony_ci        if self._addresses is None:
3777db96d56Sopenharmony_ci            self._addresses = tuple(address for group in self._groups
3787db96d56Sopenharmony_ci                                            for address in group.addresses)
3797db96d56Sopenharmony_ci        return self._addresses
3807db96d56Sopenharmony_ci
3817db96d56Sopenharmony_ci
3827db96d56Sopenharmony_ciclass UniqueAddressHeader(AddressHeader):
3837db96d56Sopenharmony_ci
3847db96d56Sopenharmony_ci    max_count = 1
3857db96d56Sopenharmony_ci
3867db96d56Sopenharmony_ci
3877db96d56Sopenharmony_ciclass SingleAddressHeader(AddressHeader):
3887db96d56Sopenharmony_ci
3897db96d56Sopenharmony_ci    @property
3907db96d56Sopenharmony_ci    def address(self):
3917db96d56Sopenharmony_ci        if len(self.addresses)!=1:
3927db96d56Sopenharmony_ci            raise ValueError(("value of single address header {} is not "
3937db96d56Sopenharmony_ci                "a single address").format(self.name))
3947db96d56Sopenharmony_ci        return self.addresses[0]
3957db96d56Sopenharmony_ci
3967db96d56Sopenharmony_ci
3977db96d56Sopenharmony_ciclass UniqueSingleAddressHeader(SingleAddressHeader):
3987db96d56Sopenharmony_ci
3997db96d56Sopenharmony_ci    max_count = 1
4007db96d56Sopenharmony_ci
4017db96d56Sopenharmony_ci
4027db96d56Sopenharmony_ciclass MIMEVersionHeader:
4037db96d56Sopenharmony_ci
4047db96d56Sopenharmony_ci    max_count = 1
4057db96d56Sopenharmony_ci
4067db96d56Sopenharmony_ci    value_parser = staticmethod(parser.parse_mime_version)
4077db96d56Sopenharmony_ci
4087db96d56Sopenharmony_ci    @classmethod
4097db96d56Sopenharmony_ci    def parse(cls, value, kwds):
4107db96d56Sopenharmony_ci        kwds['parse_tree'] = parse_tree = cls.value_parser(value)
4117db96d56Sopenharmony_ci        kwds['decoded'] = str(parse_tree)
4127db96d56Sopenharmony_ci        kwds['defects'].extend(parse_tree.all_defects)
4137db96d56Sopenharmony_ci        kwds['major'] = None if parse_tree.minor is None else parse_tree.major
4147db96d56Sopenharmony_ci        kwds['minor'] = parse_tree.minor
4157db96d56Sopenharmony_ci        if parse_tree.minor is not None:
4167db96d56Sopenharmony_ci            kwds['version'] = '{}.{}'.format(kwds['major'], kwds['minor'])
4177db96d56Sopenharmony_ci        else:
4187db96d56Sopenharmony_ci            kwds['version'] = None
4197db96d56Sopenharmony_ci
4207db96d56Sopenharmony_ci    def init(self, *args, **kw):
4217db96d56Sopenharmony_ci        self._version = kw.pop('version')
4227db96d56Sopenharmony_ci        self._major = kw.pop('major')
4237db96d56Sopenharmony_ci        self._minor = kw.pop('minor')
4247db96d56Sopenharmony_ci        super().init(*args, **kw)
4257db96d56Sopenharmony_ci
4267db96d56Sopenharmony_ci    @property
4277db96d56Sopenharmony_ci    def major(self):
4287db96d56Sopenharmony_ci        return self._major
4297db96d56Sopenharmony_ci
4307db96d56Sopenharmony_ci    @property
4317db96d56Sopenharmony_ci    def minor(self):
4327db96d56Sopenharmony_ci        return self._minor
4337db96d56Sopenharmony_ci
4347db96d56Sopenharmony_ci    @property
4357db96d56Sopenharmony_ci    def version(self):
4367db96d56Sopenharmony_ci        return self._version
4377db96d56Sopenharmony_ci
4387db96d56Sopenharmony_ci
4397db96d56Sopenharmony_ciclass ParameterizedMIMEHeader:
4407db96d56Sopenharmony_ci
4417db96d56Sopenharmony_ci    # Mixin that handles the params dict.  Must be subclassed and
4427db96d56Sopenharmony_ci    # a property value_parser for the specific header provided.
4437db96d56Sopenharmony_ci
4447db96d56Sopenharmony_ci    max_count = 1
4457db96d56Sopenharmony_ci
4467db96d56Sopenharmony_ci    @classmethod
4477db96d56Sopenharmony_ci    def parse(cls, value, kwds):
4487db96d56Sopenharmony_ci        kwds['parse_tree'] = parse_tree = cls.value_parser(value)
4497db96d56Sopenharmony_ci        kwds['decoded'] = str(parse_tree)
4507db96d56Sopenharmony_ci        kwds['defects'].extend(parse_tree.all_defects)
4517db96d56Sopenharmony_ci        if parse_tree.params is None:
4527db96d56Sopenharmony_ci            kwds['params'] = {}
4537db96d56Sopenharmony_ci        else:
4547db96d56Sopenharmony_ci            # The MIME RFCs specify that parameter ordering is arbitrary.
4557db96d56Sopenharmony_ci            kwds['params'] = {utils._sanitize(name).lower():
4567db96d56Sopenharmony_ci                                    utils._sanitize(value)
4577db96d56Sopenharmony_ci                               for name, value in parse_tree.params}
4587db96d56Sopenharmony_ci
4597db96d56Sopenharmony_ci    def init(self, *args, **kw):
4607db96d56Sopenharmony_ci        self._params = kw.pop('params')
4617db96d56Sopenharmony_ci        super().init(*args, **kw)
4627db96d56Sopenharmony_ci
4637db96d56Sopenharmony_ci    @property
4647db96d56Sopenharmony_ci    def params(self):
4657db96d56Sopenharmony_ci        return MappingProxyType(self._params)
4667db96d56Sopenharmony_ci
4677db96d56Sopenharmony_ci
4687db96d56Sopenharmony_ciclass ContentTypeHeader(ParameterizedMIMEHeader):
4697db96d56Sopenharmony_ci
4707db96d56Sopenharmony_ci    value_parser = staticmethod(parser.parse_content_type_header)
4717db96d56Sopenharmony_ci
4727db96d56Sopenharmony_ci    def init(self, *args, **kw):
4737db96d56Sopenharmony_ci        super().init(*args, **kw)
4747db96d56Sopenharmony_ci        self._maintype = utils._sanitize(self._parse_tree.maintype)
4757db96d56Sopenharmony_ci        self._subtype = utils._sanitize(self._parse_tree.subtype)
4767db96d56Sopenharmony_ci
4777db96d56Sopenharmony_ci    @property
4787db96d56Sopenharmony_ci    def maintype(self):
4797db96d56Sopenharmony_ci        return self._maintype
4807db96d56Sopenharmony_ci
4817db96d56Sopenharmony_ci    @property
4827db96d56Sopenharmony_ci    def subtype(self):
4837db96d56Sopenharmony_ci        return self._subtype
4847db96d56Sopenharmony_ci
4857db96d56Sopenharmony_ci    @property
4867db96d56Sopenharmony_ci    def content_type(self):
4877db96d56Sopenharmony_ci        return self.maintype + '/' + self.subtype
4887db96d56Sopenharmony_ci
4897db96d56Sopenharmony_ci
4907db96d56Sopenharmony_ciclass ContentDispositionHeader(ParameterizedMIMEHeader):
4917db96d56Sopenharmony_ci
4927db96d56Sopenharmony_ci    value_parser = staticmethod(parser.parse_content_disposition_header)
4937db96d56Sopenharmony_ci
4947db96d56Sopenharmony_ci    def init(self, *args, **kw):
4957db96d56Sopenharmony_ci        super().init(*args, **kw)
4967db96d56Sopenharmony_ci        cd = self._parse_tree.content_disposition
4977db96d56Sopenharmony_ci        self._content_disposition = cd if cd is None else utils._sanitize(cd)
4987db96d56Sopenharmony_ci
4997db96d56Sopenharmony_ci    @property
5007db96d56Sopenharmony_ci    def content_disposition(self):
5017db96d56Sopenharmony_ci        return self._content_disposition
5027db96d56Sopenharmony_ci
5037db96d56Sopenharmony_ci
5047db96d56Sopenharmony_ciclass ContentTransferEncodingHeader:
5057db96d56Sopenharmony_ci
5067db96d56Sopenharmony_ci    max_count = 1
5077db96d56Sopenharmony_ci
5087db96d56Sopenharmony_ci    value_parser = staticmethod(parser.parse_content_transfer_encoding_header)
5097db96d56Sopenharmony_ci
5107db96d56Sopenharmony_ci    @classmethod
5117db96d56Sopenharmony_ci    def parse(cls, value, kwds):
5127db96d56Sopenharmony_ci        kwds['parse_tree'] = parse_tree = cls.value_parser(value)
5137db96d56Sopenharmony_ci        kwds['decoded'] = str(parse_tree)
5147db96d56Sopenharmony_ci        kwds['defects'].extend(parse_tree.all_defects)
5157db96d56Sopenharmony_ci
5167db96d56Sopenharmony_ci    def init(self, *args, **kw):
5177db96d56Sopenharmony_ci        super().init(*args, **kw)
5187db96d56Sopenharmony_ci        self._cte = utils._sanitize(self._parse_tree.cte)
5197db96d56Sopenharmony_ci
5207db96d56Sopenharmony_ci    @property
5217db96d56Sopenharmony_ci    def cte(self):
5227db96d56Sopenharmony_ci        return self._cte
5237db96d56Sopenharmony_ci
5247db96d56Sopenharmony_ci
5257db96d56Sopenharmony_ciclass MessageIDHeader:
5267db96d56Sopenharmony_ci
5277db96d56Sopenharmony_ci    max_count = 1
5287db96d56Sopenharmony_ci    value_parser = staticmethod(parser.parse_message_id)
5297db96d56Sopenharmony_ci
5307db96d56Sopenharmony_ci    @classmethod
5317db96d56Sopenharmony_ci    def parse(cls, value, kwds):
5327db96d56Sopenharmony_ci        kwds['parse_tree'] = parse_tree = cls.value_parser(value)
5337db96d56Sopenharmony_ci        kwds['decoded'] = str(parse_tree)
5347db96d56Sopenharmony_ci        kwds['defects'].extend(parse_tree.all_defects)
5357db96d56Sopenharmony_ci
5367db96d56Sopenharmony_ci
5377db96d56Sopenharmony_ci# The header factory #
5387db96d56Sopenharmony_ci
5397db96d56Sopenharmony_ci_default_header_map = {
5407db96d56Sopenharmony_ci    'subject':                      UniqueUnstructuredHeader,
5417db96d56Sopenharmony_ci    'date':                         UniqueDateHeader,
5427db96d56Sopenharmony_ci    'resent-date':                  DateHeader,
5437db96d56Sopenharmony_ci    'orig-date':                    UniqueDateHeader,
5447db96d56Sopenharmony_ci    'sender':                       UniqueSingleAddressHeader,
5457db96d56Sopenharmony_ci    'resent-sender':                SingleAddressHeader,
5467db96d56Sopenharmony_ci    'to':                           UniqueAddressHeader,
5477db96d56Sopenharmony_ci    'resent-to':                    AddressHeader,
5487db96d56Sopenharmony_ci    'cc':                           UniqueAddressHeader,
5497db96d56Sopenharmony_ci    'resent-cc':                    AddressHeader,
5507db96d56Sopenharmony_ci    'bcc':                          UniqueAddressHeader,
5517db96d56Sopenharmony_ci    'resent-bcc':                   AddressHeader,
5527db96d56Sopenharmony_ci    'from':                         UniqueAddressHeader,
5537db96d56Sopenharmony_ci    'resent-from':                  AddressHeader,
5547db96d56Sopenharmony_ci    'reply-to':                     UniqueAddressHeader,
5557db96d56Sopenharmony_ci    'mime-version':                 MIMEVersionHeader,
5567db96d56Sopenharmony_ci    'content-type':                 ContentTypeHeader,
5577db96d56Sopenharmony_ci    'content-disposition':          ContentDispositionHeader,
5587db96d56Sopenharmony_ci    'content-transfer-encoding':    ContentTransferEncodingHeader,
5597db96d56Sopenharmony_ci    'message-id':                   MessageIDHeader,
5607db96d56Sopenharmony_ci    }
5617db96d56Sopenharmony_ci
5627db96d56Sopenharmony_ciclass HeaderRegistry:
5637db96d56Sopenharmony_ci
5647db96d56Sopenharmony_ci    """A header_factory and header registry."""
5657db96d56Sopenharmony_ci
5667db96d56Sopenharmony_ci    def __init__(self, base_class=BaseHeader, default_class=UnstructuredHeader,
5677db96d56Sopenharmony_ci                       use_default_map=True):
5687db96d56Sopenharmony_ci        """Create a header_factory that works with the Policy API.
5697db96d56Sopenharmony_ci
5707db96d56Sopenharmony_ci        base_class is the class that will be the last class in the created
5717db96d56Sopenharmony_ci        header class's __bases__ list.  default_class is the class that will be
5727db96d56Sopenharmony_ci        used if "name" (see __call__) does not appear in the registry.
5737db96d56Sopenharmony_ci        use_default_map controls whether or not the default mapping of names to
5747db96d56Sopenharmony_ci        specialized classes is copied in to the registry when the factory is
5757db96d56Sopenharmony_ci        created.  The default is True.
5767db96d56Sopenharmony_ci
5777db96d56Sopenharmony_ci        """
5787db96d56Sopenharmony_ci        self.registry = {}
5797db96d56Sopenharmony_ci        self.base_class = base_class
5807db96d56Sopenharmony_ci        self.default_class = default_class
5817db96d56Sopenharmony_ci        if use_default_map:
5827db96d56Sopenharmony_ci            self.registry.update(_default_header_map)
5837db96d56Sopenharmony_ci
5847db96d56Sopenharmony_ci    def map_to_type(self, name, cls):
5857db96d56Sopenharmony_ci        """Register cls as the specialized class for handling "name" headers.
5867db96d56Sopenharmony_ci
5877db96d56Sopenharmony_ci        """
5887db96d56Sopenharmony_ci        self.registry[name.lower()] = cls
5897db96d56Sopenharmony_ci
5907db96d56Sopenharmony_ci    def __getitem__(self, name):
5917db96d56Sopenharmony_ci        cls = self.registry.get(name.lower(), self.default_class)
5927db96d56Sopenharmony_ci        return type('_'+cls.__name__, (cls, self.base_class), {})
5937db96d56Sopenharmony_ci
5947db96d56Sopenharmony_ci    def __call__(self, name, value):
5957db96d56Sopenharmony_ci        """Create a header instance for header 'name' from 'value'.
5967db96d56Sopenharmony_ci
5977db96d56Sopenharmony_ci        Creates a header instance by creating a specialized class for parsing
5987db96d56Sopenharmony_ci        and representing the specified header by combining the factory
5997db96d56Sopenharmony_ci        base_class with a specialized class from the registry or the
6007db96d56Sopenharmony_ci        default_class, and passing the name and value to the constructed
6017db96d56Sopenharmony_ci        class's constructor.
6027db96d56Sopenharmony_ci
6037db96d56Sopenharmony_ci        """
6047db96d56Sopenharmony_ci        return self[name](name, value)
605