diff options
Diffstat (limited to 'Lib/email')
-rw-r--r-- | Lib/email/_header_value_parser.py | 16 | ||||
-rw-r--r-- | Lib/email/_policybase.py | 4 | ||||
-rw-r--r-- | Lib/email/contentmanager.py | 6 | ||||
-rw-r--r-- | Lib/email/feedparser.py | 19 | ||||
-rw-r--r-- | Lib/email/generator.py | 6 | ||||
-rw-r--r-- | Lib/email/header.py | 4 | ||||
-rw-r--r-- | Lib/email/message.py | 25 | ||||
-rw-r--r-- | Lib/email/mime/application.py | 5 | ||||
-rw-r--r-- | Lib/email/mime/audio.py | 5 | ||||
-rw-r--r-- | Lib/email/mime/base.py | 8 | ||||
-rw-r--r-- | Lib/email/mime/image.py | 5 | ||||
-rw-r--r-- | Lib/email/mime/message.py | 4 | ||||
-rw-r--r-- | Lib/email/mime/multipart.py | 3 | ||||
-rw-r--r-- | Lib/email/mime/text.py | 4 | ||||
-rw-r--r-- | Lib/email/policy.py | 2 |
15 files changed, 74 insertions, 42 deletions
diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index 5df9511..57d01fb 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -652,8 +652,8 @@ class Comment(WhiteSpaceTokenList): if value.token_type == 'comment': return str(value) return str(value).replace('\\', '\\\\').replace( - '(', '\(').replace( - ')', '\)') + '(', r'\(').replace( + ')', r'\)') @property def content(self): @@ -1356,15 +1356,15 @@ RouteComponentMarker = ValueTerminal('@', 'route-component-marker') _wsp_splitter = re.compile(r'([{}]+)'.format(''.join(WSP))).split _non_atom_end_matcher = re.compile(r"[^{}]+".format( - ''.join(ATOM_ENDS).replace('\\','\\\\').replace(']','\]'))).match + ''.join(ATOM_ENDS).replace('\\','\\\\').replace(']',r'\]'))).match _non_printable_finder = re.compile(r"[\x00-\x20\x7F]").findall _non_token_end_matcher = re.compile(r"[^{}]+".format( - ''.join(TOKEN_ENDS).replace('\\','\\\\').replace(']','\]'))).match + ''.join(TOKEN_ENDS).replace('\\','\\\\').replace(']',r'\]'))).match _non_attribute_end_matcher = re.compile(r"[^{}]+".format( - ''.join(ATTRIBUTE_ENDS).replace('\\','\\\\').replace(']','\]'))).match + ''.join(ATTRIBUTE_ENDS).replace('\\','\\\\').replace(']',r'\]'))).match _non_extended_attribute_end_matcher = re.compile(r"[^{}]+".format( ''.join(EXTENDED_ATTRIBUTE_ENDS).replace( - '\\','\\\\').replace(']','\]'))).match + '\\','\\\\').replace(']',r'\]'))).match def _validate_xtext(xtext): """If input token contains ASCII non-printables, register a defect.""" @@ -1517,7 +1517,7 @@ def get_unstructured(value): return unstructured def get_qp_ctext(value): - """ctext = <printable ascii except \ ( )> + r"""ctext = <printable ascii except \ ( )> This is not the RFC ctext, since we are handling nested comments in comment and unquoting quoted-pairs here. We allow anything except the '()' @@ -1878,7 +1878,7 @@ def get_obs_local_part(value): return obs_local_part, value def get_dtext(value): - """ dtext = <printable ascii except \ [ ]> / obs-dtext + r""" dtext = <printable ascii except \ [ ]> / obs-dtext obs-dtext = obs-NO-WS-CTL / quoted-pair We allow anything except the excluded characters, but if we find any diff --git a/Lib/email/_policybase.py b/Lib/email/_policybase.py index c0d98a4..df46496 100644 --- a/Lib/email/_policybase.py +++ b/Lib/email/_policybase.py @@ -154,6 +154,9 @@ class Policy(_PolicyBase, metaclass=abc.ABCMeta): them. This is used when the message is being serialized by a generator. Default: True. + message_factory -- the class to use to create new message objects. + If the value is None, the default is Message. + """ raise_on_defect = False @@ -161,6 +164,7 @@ class Policy(_PolicyBase, metaclass=abc.ABCMeta): cte_type = '8bit' max_line_length = 78 mangle_from_ = False + message_factory = None def handle_defect(self, obj, defect): """Based on policy, either raise defect or call register_defect. diff --git a/Lib/email/contentmanager.py b/Lib/email/contentmanager.py index b98ce27..b904ded 100644 --- a/Lib/email/contentmanager.py +++ b/Lib/email/contentmanager.py @@ -142,7 +142,7 @@ def _encode_base64(data, max_line_length): def _encode_text(string, charset, cte, policy): lines = string.encode(charset).splitlines() linesep = policy.linesep.encode('ascii') - def embeded_body(lines): return linesep.join(lines) + linesep + def embedded_body(lines): return linesep.join(lines) + linesep def normal_body(lines): return b'\n'.join(lines) + b'\n' if cte==None: # Use heuristics to decide on the "best" encoding. @@ -153,7 +153,7 @@ def _encode_text(string, charset, cte, policy): if (policy.cte_type == '8bit' and max(len(x) for x in lines) <= policy.max_line_length): return '8bit', normal_body(lines).decode('ascii', 'surrogateescape') - sniff = embeded_body(lines[:10]) + sniff = embedded_body(lines[:10]) sniff_qp = quoprimime.body_encode(sniff.decode('latin-1'), policy.max_line_length) sniff_base64 = binascii.b2a_base64(sniff) @@ -172,7 +172,7 @@ def _encode_text(string, charset, cte, policy): data = quoprimime.body_encode(normal_body(lines).decode('latin-1'), policy.max_line_length) elif cte == 'base64': - data = _encode_base64(embeded_body(lines), policy.max_line_length) + data = _encode_base64(embedded_body(lines), policy.max_line_length) else: raise ValueError("Unknown content transfer encoding {}".format(cte)) return cte, data diff --git a/Lib/email/feedparser.py b/Lib/email/feedparser.py index 0b312e5..7c07ca8 100644 --- a/Lib/email/feedparser.py +++ b/Lib/email/feedparser.py @@ -24,15 +24,14 @@ __all__ = ['FeedParser', 'BytesFeedParser'] import re from email import errors -from email import message from email._policybase import compat32 from collections import deque from io import StringIO -NLCRE = re.compile('\r\n|\r|\n') -NLCRE_bol = re.compile('(\r\n|\r|\n)') -NLCRE_eol = re.compile('(\r\n|\r|\n)\Z') -NLCRE_crack = re.compile('(\r\n|\r|\n)') +NLCRE = re.compile(r'\r\n|\r|\n') +NLCRE_bol = re.compile(r'(\r\n|\r|\n)') +NLCRE_eol = re.compile(r'(\r\n|\r|\n)\Z') +NLCRE_crack = re.compile(r'(\r\n|\r|\n)') # RFC 2822 $3.6.8 Optional fields. ftext is %d33-57 / %d59-126, Any character # except controls, SP, and ":". headerRE = re.compile(r'^(From |[\041-\071\073-\176]*:|[\t ])') @@ -148,13 +147,11 @@ class FeedParser: self.policy = policy self._old_style_factory = False if _factory is None: - # What this should be: - #self._factory = policy.default_message_factory - # but, because we are post 3.4 feature freeze, fix with temp hack: - if self.policy is compat32: - self._factory = message.Message + if policy.message_factory is None: + from email.message import Message + self._factory = Message else: - self._factory = message.EmailMessage + self._factory = policy.message_factory else: self._factory = _factory try: diff --git a/Lib/email/generator.py b/Lib/email/generator.py index 256278d..ae670c2 100644 --- a/Lib/email/generator.py +++ b/Lib/email/generator.py @@ -448,7 +448,8 @@ class DecodedGenerator(Generator): Like the Generator base class, except that non-text parts are substituted with a format string representing the part. """ - def __init__(self, outfp, mangle_from_=None, maxheaderlen=78, fmt=None): + def __init__(self, outfp, mangle_from_=None, maxheaderlen=None, fmt=None, *, + policy=None): """Like Generator.__init__() except that an additional optional argument is allowed. @@ -470,7 +471,8 @@ class DecodedGenerator(Generator): [Non-text (%(type)s) part of message omitted, filename %(filename)s] """ - Generator.__init__(self, outfp, mangle_from_, maxheaderlen) + Generator.__init__(self, outfp, mangle_from_, maxheaderlen, + policy=policy) if fmt is None: self._fmt = _FMT else: diff --git a/Lib/email/header.py b/Lib/email/header.py index 6820ea1..c7b2dd9 100644 --- a/Lib/email/header.py +++ b/Lib/email/header.py @@ -49,7 +49,7 @@ fcre = re.compile(r'[\041-\176]+:$') # Find a header embedded in a putative header value. Used to check for # header injection attack. -_embeded_header = re.compile(r'\n[^ \t]+:') +_embedded_header = re.compile(r'\n[^ \t]+:') @@ -385,7 +385,7 @@ class Header: if self._chunks: formatter.add_transition() value = formatter._str(linesep) - if _embeded_header.search(value): + if _embedded_header.search(value): raise HeaderParseError("header value appears to contain " "an embedded header: {!r}".format(value)) return value diff --git a/Lib/email/message.py b/Lib/email/message.py index 4b04283..b6512f2 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -4,18 +4,17 @@ """Basic message object for the email package object model.""" -__all__ = ['Message'] +__all__ = ['Message', 'EmailMessage'] import re import uu import quopri -import warnings from io import BytesIO, StringIO # Intrapackage imports from email import utils from email import errors -from email._policybase import compat32 +from email._policybase import Policy, compat32 from email import charset as _charset from email._encoded_words import decode_b Charset = _charset.Charset @@ -951,6 +950,26 @@ class MIMEPart(Message): policy = default Message.__init__(self, policy) + + def as_string(self, unixfrom=False, maxheaderlen=None, policy=None): + """Return the entire formatted message as a string. + + Optional 'unixfrom', when true, means include the Unix From_ envelope + header. maxheaderlen is retained for backward compatibility with the + base Message class, but defaults to None, meaning that the policy value + for max_line_length controls the header maximum length. 'policy' is + passed to the Generator instance used to serialize the mesasge; if it + is not specified the policy associated with the message instance is + used. + """ + policy = self.policy if policy is None else policy + if maxheaderlen is None: + maxheaderlen = policy.max_line_length + return super().as_string(maxheaderlen=maxheaderlen, policy=policy) + + def __str__(self): + return self.as_string(policy=self.policy.clone(utf8=True)) + def is_attachment(self): c_d = self.get('content-disposition') return False if c_d is None else c_d.content_disposition == 'attachment' diff --git a/Lib/email/mime/application.py b/Lib/email/mime/application.py index f5c5905..6877e55 100644 --- a/Lib/email/mime/application.py +++ b/Lib/email/mime/application.py @@ -14,7 +14,7 @@ class MIMEApplication(MIMENonMultipart): """Class for generating application/* MIME documents.""" def __init__(self, _data, _subtype='octet-stream', - _encoder=encoders.encode_base64, **_params): + _encoder=encoders.encode_base64, *, policy=None, **_params): """Create an application/* type MIME document. _data is a string containing the raw application data. @@ -31,6 +31,7 @@ class MIMEApplication(MIMENonMultipart): """ if _subtype is None: raise TypeError('Invalid application MIME subtype') - MIMENonMultipart.__init__(self, 'application', _subtype, **_params) + MIMENonMultipart.__init__(self, 'application', _subtype, policy=policy, + **_params) self.set_payload(_data) _encoder(self) diff --git a/Lib/email/mime/audio.py b/Lib/email/mime/audio.py index fbc1189..4bcd7b2 100644 --- a/Lib/email/mime/audio.py +++ b/Lib/email/mime/audio.py @@ -43,7 +43,7 @@ class MIMEAudio(MIMENonMultipart): """Class for generating audio/* MIME documents.""" def __init__(self, _audiodata, _subtype=None, - _encoder=encoders.encode_base64, **_params): + _encoder=encoders.encode_base64, *, policy=None, **_params): """Create an audio/* type MIME document. _audiodata is a string containing the raw audio data. If this data @@ -68,6 +68,7 @@ class MIMEAudio(MIMENonMultipart): _subtype = _whatsnd(_audiodata) if _subtype is None: raise TypeError('Could not find audio MIME subtype') - MIMENonMultipart.__init__(self, 'audio', _subtype, **_params) + MIMENonMultipart.__init__(self, 'audio', _subtype, policy=policy, + **_params) self.set_payload(_audiodata) _encoder(self) diff --git a/Lib/email/mime/base.py b/Lib/email/mime/base.py index ac91925..1a3f9b5 100644 --- a/Lib/email/mime/base.py +++ b/Lib/email/mime/base.py @@ -6,6 +6,8 @@ __all__ = ['MIMEBase'] +import email.policy + from email import message @@ -13,14 +15,16 @@ from email import message class MIMEBase(message.Message): """Base class for MIME specializations.""" - def __init__(self, _maintype, _subtype, **_params): + def __init__(self, _maintype, _subtype, *, policy=None, **_params): """This constructor adds a Content-Type: and a MIME-Version: header. The Content-Type: header is taken from the _maintype and _subtype arguments. Additional parameters for this header are taken from the keyword arguments. """ - message.Message.__init__(self) + if policy is None: + policy = email.policy.compat32 + message.Message.__init__(self, policy=policy) ctype = '%s/%s' % (_maintype, _subtype) self.add_header('Content-Type', ctype, **_params) self['MIME-Version'] = '1.0' diff --git a/Lib/email/mime/image.py b/Lib/email/mime/image.py index 5563823..9272464 100644 --- a/Lib/email/mime/image.py +++ b/Lib/email/mime/image.py @@ -17,7 +17,7 @@ class MIMEImage(MIMENonMultipart): """Class for generating image/* type MIME documents.""" def __init__(self, _imagedata, _subtype=None, - _encoder=encoders.encode_base64, **_params): + _encoder=encoders.encode_base64, *, policy=None, **_params): """Create an image/* type MIME document. _imagedata is a string containing the raw image data. If this data @@ -41,6 +41,7 @@ class MIMEImage(MIMENonMultipart): _subtype = imghdr.what(None, _imagedata) if _subtype is None: raise TypeError('Could not guess image MIME subtype') - MIMENonMultipart.__init__(self, 'image', _subtype, **_params) + MIMENonMultipart.__init__(self, 'image', _subtype, policy=policy, + **_params) self.set_payload(_imagedata) _encoder(self) diff --git a/Lib/email/mime/message.py b/Lib/email/mime/message.py index 275dbfd..07e4f2d 100644 --- a/Lib/email/mime/message.py +++ b/Lib/email/mime/message.py @@ -14,7 +14,7 @@ from email.mime.nonmultipart import MIMENonMultipart class MIMEMessage(MIMENonMultipart): """Class representing message/* MIME documents.""" - def __init__(self, _msg, _subtype='rfc822'): + def __init__(self, _msg, _subtype='rfc822', *, policy=None): """Create a message/* type MIME document. _msg is a message object and must be an instance of Message, or a @@ -24,7 +24,7 @@ class MIMEMessage(MIMENonMultipart): default is "rfc822" (this is defined by the MIME standard, even though the term "rfc822" is technically outdated by RFC 2822). """ - MIMENonMultipart.__init__(self, 'message', _subtype) + MIMENonMultipart.__init__(self, 'message', _subtype, policy=policy) if not isinstance(_msg, message.Message): raise TypeError('Argument is not an instance of Message') # It's convenient to use this base class method. We need to do it diff --git a/Lib/email/mime/multipart.py b/Lib/email/mime/multipart.py index 9661865..2d3f288 100644 --- a/Lib/email/mime/multipart.py +++ b/Lib/email/mime/multipart.py @@ -14,6 +14,7 @@ class MIMEMultipart(MIMEBase): """Base class for MIME multipart/* type messages.""" def __init__(self, _subtype='mixed', boundary=None, _subparts=None, + *, policy=None, **_params): """Creates a multipart/* type message. @@ -33,7 +34,7 @@ class MIMEMultipart(MIMEBase): Additional parameters for the Content-Type header are taken from the keyword arguments (or passed into the _params argument). """ - MIMEBase.__init__(self, 'multipart', _subtype, **_params) + MIMEBase.__init__(self, 'multipart', _subtype, policy=policy, **_params) # Initialise _payload to an empty list as the Message superclass's # implementation of is_multipart assumes that _payload is a list for diff --git a/Lib/email/mime/text.py b/Lib/email/mime/text.py index da03086..35b4423 100644 --- a/Lib/email/mime/text.py +++ b/Lib/email/mime/text.py @@ -14,7 +14,7 @@ from email.mime.nonmultipart import MIMENonMultipart class MIMEText(MIMENonMultipart): """Class for generating text/* type MIME documents.""" - def __init__(self, _text, _subtype='plain', _charset=None): + def __init__(self, _text, _subtype='plain', _charset=None, *, policy=None): """Create a text/* type MIME document. _text is the string for this message object. @@ -36,7 +36,7 @@ class MIMEText(MIMENonMultipart): except UnicodeEncodeError: _charset = 'utf-8' - MIMENonMultipart.__init__(self, 'text', _subtype, + MIMENonMultipart.__init__(self, 'text', _subtype, policy=policy, **{'charset': str(_charset)}) self.set_payload(_text, _charset) diff --git a/Lib/email/policy.py b/Lib/email/policy.py index 35d0e69..5131311ac 100644 --- a/Lib/email/policy.py +++ b/Lib/email/policy.py @@ -7,6 +7,7 @@ from email._policybase import Policy, Compat32, compat32, _extend_docstrings from email.utils import _has_surrogates from email.headerregistry import HeaderRegistry as HeaderRegistry from email.contentmanager import raw_data_manager +from email.message import EmailMessage __all__ = [ 'Compat32', @@ -82,6 +83,7 @@ class EmailPolicy(Policy): """ + message_factory = EmailMessage utf8 = False refold_source = 'long' header_factory = HeaderRegistry() |