Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/future/backports/email/headerregistry.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:18:57 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d30785e31577 |
---|---|
1 """Representing and manipulating email headers via custom objects. | |
2 | |
3 This module provides an implementation of the HeaderRegistry API. | |
4 The implementation is designed to flexibly follow RFC5322 rules. | |
5 | |
6 Eventually HeaderRegistry will be a public API, but it isn't yet, | |
7 and will probably change some before that happens. | |
8 | |
9 """ | |
10 from __future__ import unicode_literals | |
11 from __future__ import division | |
12 from __future__ import absolute_import | |
13 | |
14 from future.builtins import super | |
15 from future.builtins import str | |
16 from future.utils import text_to_native_str | |
17 from future.backports.email import utils | |
18 from future.backports.email import errors | |
19 from future.backports.email import _header_value_parser as parser | |
20 | |
21 class Address(object): | |
22 | |
23 def __init__(self, display_name='', username='', domain='', addr_spec=None): | |
24 """Create an object represeting a full email address. | |
25 | |
26 An address can have a 'display_name', a 'username', and a 'domain'. In | |
27 addition to specifying the username and domain separately, they may be | |
28 specified together by using the addr_spec keyword *instead of* the | |
29 username and domain keywords. If an addr_spec string is specified it | |
30 must be properly quoted according to RFC 5322 rules; an error will be | |
31 raised if it is not. | |
32 | |
33 An Address object has display_name, username, domain, and addr_spec | |
34 attributes, all of which are read-only. The addr_spec and the string | |
35 value of the object are both quoted according to RFC5322 rules, but | |
36 without any Content Transfer Encoding. | |
37 | |
38 """ | |
39 # This clause with its potential 'raise' may only happen when an | |
40 # application program creates an Address object using an addr_spec | |
41 # keyword. The email library code itself must always supply username | |
42 # and domain. | |
43 if addr_spec is not None: | |
44 if username or domain: | |
45 raise TypeError("addrspec specified when username and/or " | |
46 "domain also specified") | |
47 a_s, rest = parser.get_addr_spec(addr_spec) | |
48 if rest: | |
49 raise ValueError("Invalid addr_spec; only '{}' " | |
50 "could be parsed from '{}'".format( | |
51 a_s, addr_spec)) | |
52 if a_s.all_defects: | |
53 raise a_s.all_defects[0] | |
54 username = a_s.local_part | |
55 domain = a_s.domain | |
56 self._display_name = display_name | |
57 self._username = username | |
58 self._domain = domain | |
59 | |
60 @property | |
61 def display_name(self): | |
62 return self._display_name | |
63 | |
64 @property | |
65 def username(self): | |
66 return self._username | |
67 | |
68 @property | |
69 def domain(self): | |
70 return self._domain | |
71 | |
72 @property | |
73 def addr_spec(self): | |
74 """The addr_spec (username@domain) portion of the address, quoted | |
75 according to RFC 5322 rules, but with no Content Transfer Encoding. | |
76 """ | |
77 nameset = set(self.username) | |
78 if len(nameset) > len(nameset-parser.DOT_ATOM_ENDS): | |
79 lp = parser.quote_string(self.username) | |
80 else: | |
81 lp = self.username | |
82 if self.domain: | |
83 return lp + '@' + self.domain | |
84 if not lp: | |
85 return '<>' | |
86 return lp | |
87 | |
88 def __repr__(self): | |
89 return "Address(display_name={!r}, username={!r}, domain={!r})".format( | |
90 self.display_name, self.username, self.domain) | |
91 | |
92 def __str__(self): | |
93 nameset = set(self.display_name) | |
94 if len(nameset) > len(nameset-parser.SPECIALS): | |
95 disp = parser.quote_string(self.display_name) | |
96 else: | |
97 disp = self.display_name | |
98 if disp: | |
99 addr_spec = '' if self.addr_spec=='<>' else self.addr_spec | |
100 return "{} <{}>".format(disp, addr_spec) | |
101 return self.addr_spec | |
102 | |
103 def __eq__(self, other): | |
104 if type(other) != type(self): | |
105 return False | |
106 return (self.display_name == other.display_name and | |
107 self.username == other.username and | |
108 self.domain == other.domain) | |
109 | |
110 | |
111 class Group(object): | |
112 | |
113 def __init__(self, display_name=None, addresses=None): | |
114 """Create an object representing an address group. | |
115 | |
116 An address group consists of a display_name followed by colon and an | |
117 list of addresses (see Address) terminated by a semi-colon. The Group | |
118 is created by specifying a display_name and a possibly empty list of | |
119 Address objects. A Group can also be used to represent a single | |
120 address that is not in a group, which is convenient when manipulating | |
121 lists that are a combination of Groups and individual Addresses. In | |
122 this case the display_name should be set to None. In particular, the | |
123 string representation of a Group whose display_name is None is the same | |
124 as the Address object, if there is one and only one Address object in | |
125 the addresses list. | |
126 | |
127 """ | |
128 self._display_name = display_name | |
129 self._addresses = tuple(addresses) if addresses else tuple() | |
130 | |
131 @property | |
132 def display_name(self): | |
133 return self._display_name | |
134 | |
135 @property | |
136 def addresses(self): | |
137 return self._addresses | |
138 | |
139 def __repr__(self): | |
140 return "Group(display_name={!r}, addresses={!r}".format( | |
141 self.display_name, self.addresses) | |
142 | |
143 def __str__(self): | |
144 if self.display_name is None and len(self.addresses)==1: | |
145 return str(self.addresses[0]) | |
146 disp = self.display_name | |
147 if disp is not None: | |
148 nameset = set(disp) | |
149 if len(nameset) > len(nameset-parser.SPECIALS): | |
150 disp = parser.quote_string(disp) | |
151 adrstr = ", ".join(str(x) for x in self.addresses) | |
152 adrstr = ' ' + adrstr if adrstr else adrstr | |
153 return "{}:{};".format(disp, adrstr) | |
154 | |
155 def __eq__(self, other): | |
156 if type(other) != type(self): | |
157 return False | |
158 return (self.display_name == other.display_name and | |
159 self.addresses == other.addresses) | |
160 | |
161 | |
162 # Header Classes # | |
163 | |
164 class BaseHeader(str): | |
165 | |
166 """Base class for message headers. | |
167 | |
168 Implements generic behavior and provides tools for subclasses. | |
169 | |
170 A subclass must define a classmethod named 'parse' that takes an unfolded | |
171 value string and a dictionary as its arguments. The dictionary will | |
172 contain one key, 'defects', initialized to an empty list. After the call | |
173 the dictionary must contain two additional keys: parse_tree, set to the | |
174 parse tree obtained from parsing the header, and 'decoded', set to the | |
175 string value of the idealized representation of the data from the value. | |
176 (That is, encoded words are decoded, and values that have canonical | |
177 representations are so represented.) | |
178 | |
179 The defects key is intended to collect parsing defects, which the message | |
180 parser will subsequently dispose of as appropriate. The parser should not, | |
181 insofar as practical, raise any errors. Defects should be added to the | |
182 list instead. The standard header parsers register defects for RFC | |
183 compliance issues, for obsolete RFC syntax, and for unrecoverable parsing | |
184 errors. | |
185 | |
186 The parse method may add additional keys to the dictionary. In this case | |
187 the subclass must define an 'init' method, which will be passed the | |
188 dictionary as its keyword arguments. The method should use (usually by | |
189 setting them as the value of similarly named attributes) and remove all the | |
190 extra keys added by its parse method, and then use super to call its parent | |
191 class with the remaining arguments and keywords. | |
192 | |
193 The subclass should also make sure that a 'max_count' attribute is defined | |
194 that is either None or 1. XXX: need to better define this API. | |
195 | |
196 """ | |
197 | |
198 def __new__(cls, name, value): | |
199 kwds = {'defects': []} | |
200 cls.parse(value, kwds) | |
201 if utils._has_surrogates(kwds['decoded']): | |
202 kwds['decoded'] = utils._sanitize(kwds['decoded']) | |
203 self = str.__new__(cls, kwds['decoded']) | |
204 # del kwds['decoded'] | |
205 self.init(name, **kwds) | |
206 return self | |
207 | |
208 def init(self, name, **_3to2kwargs): | |
209 defects = _3to2kwargs['defects']; del _3to2kwargs['defects'] | |
210 parse_tree = _3to2kwargs['parse_tree']; del _3to2kwargs['parse_tree'] | |
211 self._name = name | |
212 self._parse_tree = parse_tree | |
213 self._defects = defects | |
214 | |
215 @property | |
216 def name(self): | |
217 return self._name | |
218 | |
219 @property | |
220 def defects(self): | |
221 return tuple(self._defects) | |
222 | |
223 def __reduce__(self): | |
224 return ( | |
225 _reconstruct_header, | |
226 ( | |
227 self.__class__.__name__, | |
228 self.__class__.__bases__, | |
229 str(self), | |
230 ), | |
231 self.__dict__) | |
232 | |
233 @classmethod | |
234 def _reconstruct(cls, value): | |
235 return str.__new__(cls, value) | |
236 | |
237 def fold(self, **_3to2kwargs): | |
238 policy = _3to2kwargs['policy']; del _3to2kwargs['policy'] | |
239 """Fold header according to policy. | |
240 | |
241 The parsed representation of the header is folded according to | |
242 RFC5322 rules, as modified by the policy. If the parse tree | |
243 contains surrogateescaped bytes, the bytes are CTE encoded using | |
244 the charset 'unknown-8bit". | |
245 | |
246 Any non-ASCII characters in the parse tree are CTE encoded using | |
247 charset utf-8. XXX: make this a policy setting. | |
248 | |
249 The returned value is an ASCII-only string possibly containing linesep | |
250 characters, and ending with a linesep character. The string includes | |
251 the header name and the ': ' separator. | |
252 | |
253 """ | |
254 # At some point we need to only put fws here if it was in the source. | |
255 header = parser.Header([ | |
256 parser.HeaderLabel([ | |
257 parser.ValueTerminal(self.name, 'header-name'), | |
258 parser.ValueTerminal(':', 'header-sep')]), | |
259 parser.CFWSList([parser.WhiteSpaceTerminal(' ', 'fws')]), | |
260 self._parse_tree]) | |
261 return header.fold(policy=policy) | |
262 | |
263 | |
264 def _reconstruct_header(cls_name, bases, value): | |
265 return type(text_to_native_str(cls_name), bases, {})._reconstruct(value) | |
266 | |
267 | |
268 class UnstructuredHeader(object): | |
269 | |
270 max_count = None | |
271 value_parser = staticmethod(parser.get_unstructured) | |
272 | |
273 @classmethod | |
274 def parse(cls, value, kwds): | |
275 kwds['parse_tree'] = cls.value_parser(value) | |
276 kwds['decoded'] = str(kwds['parse_tree']) | |
277 | |
278 | |
279 class UniqueUnstructuredHeader(UnstructuredHeader): | |
280 | |
281 max_count = 1 | |
282 | |
283 | |
284 class DateHeader(object): | |
285 | |
286 """Header whose value consists of a single timestamp. | |
287 | |
288 Provides an additional attribute, datetime, which is either an aware | |
289 datetime using a timezone, or a naive datetime if the timezone | |
290 in the input string is -0000. Also accepts a datetime as input. | |
291 The 'value' attribute is the normalized form of the timestamp, | |
292 which means it is the output of format_datetime on the datetime. | |
293 """ | |
294 | |
295 max_count = None | |
296 | |
297 # This is used only for folding, not for creating 'decoded'. | |
298 value_parser = staticmethod(parser.get_unstructured) | |
299 | |
300 @classmethod | |
301 def parse(cls, value, kwds): | |
302 if not value: | |
303 kwds['defects'].append(errors.HeaderMissingRequiredValue()) | |
304 kwds['datetime'] = None | |
305 kwds['decoded'] = '' | |
306 kwds['parse_tree'] = parser.TokenList() | |
307 return | |
308 if isinstance(value, str): | |
309 value = utils.parsedate_to_datetime(value) | |
310 kwds['datetime'] = value | |
311 kwds['decoded'] = utils.format_datetime(kwds['datetime']) | |
312 kwds['parse_tree'] = cls.value_parser(kwds['decoded']) | |
313 | |
314 def init(self, *args, **kw): | |
315 self._datetime = kw.pop('datetime') | |
316 super().init(*args, **kw) | |
317 | |
318 @property | |
319 def datetime(self): | |
320 return self._datetime | |
321 | |
322 | |
323 class UniqueDateHeader(DateHeader): | |
324 | |
325 max_count = 1 | |
326 | |
327 | |
328 class AddressHeader(object): | |
329 | |
330 max_count = None | |
331 | |
332 @staticmethod | |
333 def value_parser(value): | |
334 address_list, value = parser.get_address_list(value) | |
335 assert not value, 'this should not happen' | |
336 return address_list | |
337 | |
338 @classmethod | |
339 def parse(cls, value, kwds): | |
340 if isinstance(value, str): | |
341 # We are translating here from the RFC language (address/mailbox) | |
342 # to our API language (group/address). | |
343 kwds['parse_tree'] = address_list = cls.value_parser(value) | |
344 groups = [] | |
345 for addr in address_list.addresses: | |
346 groups.append(Group(addr.display_name, | |
347 [Address(mb.display_name or '', | |
348 mb.local_part or '', | |
349 mb.domain or '') | |
350 for mb in addr.all_mailboxes])) | |
351 defects = list(address_list.all_defects) | |
352 else: | |
353 # Assume it is Address/Group stuff | |
354 if not hasattr(value, '__iter__'): | |
355 value = [value] | |
356 groups = [Group(None, [item]) if not hasattr(item, 'addresses') | |
357 else item | |
358 for item in value] | |
359 defects = [] | |
360 kwds['groups'] = groups | |
361 kwds['defects'] = defects | |
362 kwds['decoded'] = ', '.join([str(item) for item in groups]) | |
363 if 'parse_tree' not in kwds: | |
364 kwds['parse_tree'] = cls.value_parser(kwds['decoded']) | |
365 | |
366 def init(self, *args, **kw): | |
367 self._groups = tuple(kw.pop('groups')) | |
368 self._addresses = None | |
369 super().init(*args, **kw) | |
370 | |
371 @property | |
372 def groups(self): | |
373 return self._groups | |
374 | |
375 @property | |
376 def addresses(self): | |
377 if self._addresses is None: | |
378 self._addresses = tuple([address for group in self._groups | |
379 for address in group.addresses]) | |
380 return self._addresses | |
381 | |
382 | |
383 class UniqueAddressHeader(AddressHeader): | |
384 | |
385 max_count = 1 | |
386 | |
387 | |
388 class SingleAddressHeader(AddressHeader): | |
389 | |
390 @property | |
391 def address(self): | |
392 if len(self.addresses)!=1: | |
393 raise ValueError(("value of single address header {} is not " | |
394 "a single address").format(self.name)) | |
395 return self.addresses[0] | |
396 | |
397 | |
398 class UniqueSingleAddressHeader(SingleAddressHeader): | |
399 | |
400 max_count = 1 | |
401 | |
402 | |
403 class MIMEVersionHeader(object): | |
404 | |
405 max_count = 1 | |
406 | |
407 value_parser = staticmethod(parser.parse_mime_version) | |
408 | |
409 @classmethod | |
410 def parse(cls, value, kwds): | |
411 kwds['parse_tree'] = parse_tree = cls.value_parser(value) | |
412 kwds['decoded'] = str(parse_tree) | |
413 kwds['defects'].extend(parse_tree.all_defects) | |
414 kwds['major'] = None if parse_tree.minor is None else parse_tree.major | |
415 kwds['minor'] = parse_tree.minor | |
416 if parse_tree.minor is not None: | |
417 kwds['version'] = '{}.{}'.format(kwds['major'], kwds['minor']) | |
418 else: | |
419 kwds['version'] = None | |
420 | |
421 def init(self, *args, **kw): | |
422 self._version = kw.pop('version') | |
423 self._major = kw.pop('major') | |
424 self._minor = kw.pop('minor') | |
425 super().init(*args, **kw) | |
426 | |
427 @property | |
428 def major(self): | |
429 return self._major | |
430 | |
431 @property | |
432 def minor(self): | |
433 return self._minor | |
434 | |
435 @property | |
436 def version(self): | |
437 return self._version | |
438 | |
439 | |
440 class ParameterizedMIMEHeader(object): | |
441 | |
442 # Mixin that handles the params dict. Must be subclassed and | |
443 # a property value_parser for the specific header provided. | |
444 | |
445 max_count = 1 | |
446 | |
447 @classmethod | |
448 def parse(cls, value, kwds): | |
449 kwds['parse_tree'] = parse_tree = cls.value_parser(value) | |
450 kwds['decoded'] = str(parse_tree) | |
451 kwds['defects'].extend(parse_tree.all_defects) | |
452 if parse_tree.params is None: | |
453 kwds['params'] = {} | |
454 else: | |
455 # The MIME RFCs specify that parameter ordering is arbitrary. | |
456 kwds['params'] = dict((utils._sanitize(name).lower(), | |
457 utils._sanitize(value)) | |
458 for name, value in parse_tree.params) | |
459 | |
460 def init(self, *args, **kw): | |
461 self._params = kw.pop('params') | |
462 super().init(*args, **kw) | |
463 | |
464 @property | |
465 def params(self): | |
466 return self._params.copy() | |
467 | |
468 | |
469 class ContentTypeHeader(ParameterizedMIMEHeader): | |
470 | |
471 value_parser = staticmethod(parser.parse_content_type_header) | |
472 | |
473 def init(self, *args, **kw): | |
474 super().init(*args, **kw) | |
475 self._maintype = utils._sanitize(self._parse_tree.maintype) | |
476 self._subtype = utils._sanitize(self._parse_tree.subtype) | |
477 | |
478 @property | |
479 def maintype(self): | |
480 return self._maintype | |
481 | |
482 @property | |
483 def subtype(self): | |
484 return self._subtype | |
485 | |
486 @property | |
487 def content_type(self): | |
488 return self.maintype + '/' + self.subtype | |
489 | |
490 | |
491 class ContentDispositionHeader(ParameterizedMIMEHeader): | |
492 | |
493 value_parser = staticmethod(parser.parse_content_disposition_header) | |
494 | |
495 def init(self, *args, **kw): | |
496 super().init(*args, **kw) | |
497 cd = self._parse_tree.content_disposition | |
498 self._content_disposition = cd if cd is None else utils._sanitize(cd) | |
499 | |
500 @property | |
501 def content_disposition(self): | |
502 return self._content_disposition | |
503 | |
504 | |
505 class ContentTransferEncodingHeader(object): | |
506 | |
507 max_count = 1 | |
508 | |
509 value_parser = staticmethod(parser.parse_content_transfer_encoding_header) | |
510 | |
511 @classmethod | |
512 def parse(cls, value, kwds): | |
513 kwds['parse_tree'] = parse_tree = cls.value_parser(value) | |
514 kwds['decoded'] = str(parse_tree) | |
515 kwds['defects'].extend(parse_tree.all_defects) | |
516 | |
517 def init(self, *args, **kw): | |
518 super().init(*args, **kw) | |
519 self._cte = utils._sanitize(self._parse_tree.cte) | |
520 | |
521 @property | |
522 def cte(self): | |
523 return self._cte | |
524 | |
525 | |
526 # The header factory # | |
527 | |
528 _default_header_map = { | |
529 'subject': UniqueUnstructuredHeader, | |
530 'date': UniqueDateHeader, | |
531 'resent-date': DateHeader, | |
532 'orig-date': UniqueDateHeader, | |
533 'sender': UniqueSingleAddressHeader, | |
534 'resent-sender': SingleAddressHeader, | |
535 'to': UniqueAddressHeader, | |
536 'resent-to': AddressHeader, | |
537 'cc': UniqueAddressHeader, | |
538 'resent-cc': AddressHeader, | |
539 'bcc': UniqueAddressHeader, | |
540 'resent-bcc': AddressHeader, | |
541 'from': UniqueAddressHeader, | |
542 'resent-from': AddressHeader, | |
543 'reply-to': UniqueAddressHeader, | |
544 'mime-version': MIMEVersionHeader, | |
545 'content-type': ContentTypeHeader, | |
546 'content-disposition': ContentDispositionHeader, | |
547 'content-transfer-encoding': ContentTransferEncodingHeader, | |
548 } | |
549 | |
550 class HeaderRegistry(object): | |
551 | |
552 """A header_factory and header registry.""" | |
553 | |
554 def __init__(self, base_class=BaseHeader, default_class=UnstructuredHeader, | |
555 use_default_map=True): | |
556 """Create a header_factory that works with the Policy API. | |
557 | |
558 base_class is the class that will be the last class in the created | |
559 header class's __bases__ list. default_class is the class that will be | |
560 used if "name" (see __call__) does not appear in the registry. | |
561 use_default_map controls whether or not the default mapping of names to | |
562 specialized classes is copied in to the registry when the factory is | |
563 created. The default is True. | |
564 | |
565 """ | |
566 self.registry = {} | |
567 self.base_class = base_class | |
568 self.default_class = default_class | |
569 if use_default_map: | |
570 self.registry.update(_default_header_map) | |
571 | |
572 def map_to_type(self, name, cls): | |
573 """Register cls as the specialized class for handling "name" headers. | |
574 | |
575 """ | |
576 self.registry[name.lower()] = cls | |
577 | |
578 def __getitem__(self, name): | |
579 cls = self.registry.get(name.lower(), self.default_class) | |
580 return type(text_to_native_str('_'+cls.__name__), (cls, self.base_class), {}) | |
581 | |
582 def __call__(self, name, value): | |
583 """Create a header instance for header 'name' from 'value'. | |
584 | |
585 Creates a header instance by creating a specialized class for parsing | |
586 and representing the specified header by combining the factory | |
587 base_class with a specialized class from the registry or the | |
588 default_class, and passing the name and value to the constructed | |
589 class's constructor. | |
590 | |
591 """ | |
592 return self[name](name, value) |