ยปCore Development>Code coverage>Lib/email/message.py

Python code coverage for Lib/email/message.py

#countcontent
1n/a# Copyright (C) 2001-2007 Python Software Foundation
2n/a# Author: Barry Warsaw
3n/a# Contact: email-sig@python.org
4n/a
5n/a"""Basic message object for the email package object model."""
6n/a
7n/a__all__ = ['Message', 'EmailMessage']
8n/a
9n/aimport re
10n/aimport uu
11n/aimport quopri
12n/afrom io import BytesIO, StringIO
13n/a
14n/a# Intrapackage imports
15n/afrom email import utils
16n/afrom email import errors
17n/afrom email._policybase import Policy, compat32
18n/afrom email import charset as _charset
19n/afrom email._encoded_words import decode_b
20n/aCharset = _charset.Charset
21n/a
22n/aSEMISPACE = '; '
23n/a
24n/a# Regular expression that matches `special' characters in parameters, the
25n/a# existence of which force quoting of the parameter value.
26n/atspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
27n/a
28n/a
29n/adef _splitparam(param):
30n/a # Split header parameters. BAW: this may be too simple. It isn't
31n/a # strictly RFC 2045 (section 5.1) compliant, but it catches most headers
32n/a # found in the wild. We may eventually need a full fledged parser.
33n/a # RDM: we might have a Header here; for now just stringify it.
34n/a a, sep, b = str(param).partition(';')
35n/a if not sep:
36n/a return a.strip(), None
37n/a return a.strip(), b.strip()
38n/a
39n/adef _formatparam(param, value=None, quote=True):
40n/a """Convenience function to format and return a key=value pair.
41n/a
42n/a This will quote the value if needed or if quote is true. If value is a
43n/a three tuple (charset, language, value), it will be encoded according
44n/a to RFC2231 rules. If it contains non-ascii characters it will likewise
45n/a be encoded according to RFC2231 rules, using the utf-8 charset and
46n/a a null language.
47n/a """
48n/a if value is not None and len(value) > 0:
49n/a # A tuple is used for RFC 2231 encoded parameter values where items
50n/a # are (charset, language, value). charset is a string, not a Charset
51n/a # instance. RFC 2231 encoded values are never quoted, per RFC.
52n/a if isinstance(value, tuple):
53n/a # Encode as per RFC 2231
54n/a param += '*'
55n/a value = utils.encode_rfc2231(value[2], value[0], value[1])
56n/a return '%s=%s' % (param, value)
57n/a else:
58n/a try:
59n/a value.encode('ascii')
60n/a except UnicodeEncodeError:
61n/a param += '*'
62n/a value = utils.encode_rfc2231(value, 'utf-8', '')
63n/a return '%s=%s' % (param, value)
64n/a # BAW: Please check this. I think that if quote is set it should
65n/a # force quoting even if not necessary.
66n/a if quote or tspecials.search(value):
67n/a return '%s="%s"' % (param, utils.quote(value))
68n/a else:
69n/a return '%s=%s' % (param, value)
70n/a else:
71n/a return param
72n/a
73n/adef _parseparam(s):
74n/a # RDM This might be a Header, so for now stringify it.
75n/a s = ';' + str(s)
76n/a plist = []
77n/a while s[:1] == ';':
78n/a s = s[1:]
79n/a end = s.find(';')
80n/a while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
81n/a end = s.find(';', end + 1)
82n/a if end < 0:
83n/a end = len(s)
84n/a f = s[:end]
85n/a if '=' in f:
86n/a i = f.index('=')
87n/a f = f[:i].strip().lower() + '=' + f[i+1:].strip()
88n/a plist.append(f.strip())
89n/a s = s[end:]
90n/a return plist
91n/a
92n/a
93n/adef _unquotevalue(value):
94n/a # This is different than utils.collapse_rfc2231_value() because it doesn't
95n/a # try to convert the value to a unicode. Message.get_param() and
96n/a # Message.get_params() are both currently defined to return the tuple in
97n/a # the face of RFC 2231 parameters.
98n/a if isinstance(value, tuple):
99n/a return value[0], value[1], utils.unquote(value[2])
100n/a else:
101n/a return utils.unquote(value)
102n/a
103n/a
104n/a
105n/aclass Message:
106n/a """Basic message object.
107n/a
108n/a A message object is defined as something that has a bunch of RFC 2822
109n/a headers and a payload. It may optionally have an envelope header
110n/a (a.k.a. Unix-From or From_ header). If the message is a container (i.e. a
111n/a multipart or a message/rfc822), then the payload is a list of Message
112n/a objects, otherwise it is a string.
113n/a
114n/a Message objects implement part of the `mapping' interface, which assumes
115n/a there is exactly one occurrence of the header per message. Some headers
116n/a do in fact appear multiple times (e.g. Received) and for those headers,
117n/a you must use the explicit API to set or get all the headers. Not all of
118n/a the mapping methods are implemented.
119n/a """
120n/a def __init__(self, policy=compat32):
121n/a self.policy = policy
122n/a self._headers = []
123n/a self._unixfrom = None
124n/a self._payload = None
125n/a self._charset = None
126n/a # Defaults for multipart messages
127n/a self.preamble = self.epilogue = None
128n/a self.defects = []
129n/a # Default content type
130n/a self._default_type = 'text/plain'
131n/a
132n/a def __str__(self):
133n/a """Return the entire formatted message as a string.
134n/a """
135n/a return self.as_string()
136n/a
137n/a def as_string(self, unixfrom=False, maxheaderlen=0, policy=None):
138n/a """Return the entire formatted message as a string.
139n/a
140n/a Optional 'unixfrom', when true, means include the Unix From_ envelope
141n/a header. For backward compatibility reasons, if maxheaderlen is
142n/a not specified it defaults to 0, so you must override it explicitly
143n/a if you want a different maxheaderlen. 'policy' is passed to the
144n/a Generator instance used to serialize the mesasge; if it is not
145n/a specified the policy associated with the message instance is used.
146n/a
147n/a If the message object contains binary data that is not encoded
148n/a according to RFC standards, the non-compliant data will be replaced by
149n/a unicode "unknown character" code points.
150n/a """
151n/a from email.generator import Generator
152n/a policy = self.policy if policy is None else policy
153n/a fp = StringIO()
154n/a g = Generator(fp,
155n/a mangle_from_=False,
156n/a maxheaderlen=maxheaderlen,
157n/a policy=policy)
158n/a g.flatten(self, unixfrom=unixfrom)
159n/a return fp.getvalue()
160n/a
161n/a def __bytes__(self):
162n/a """Return the entire formatted message as a bytes object.
163n/a """
164n/a return self.as_bytes()
165n/a
166n/a def as_bytes(self, unixfrom=False, policy=None):
167n/a """Return the entire formatted message as a bytes object.
168n/a
169n/a Optional 'unixfrom', when true, means include the Unix From_ envelope
170n/a header. 'policy' is passed to the BytesGenerator instance used to
171n/a serialize the message; if not specified the policy associated with
172n/a the message instance is used.
173n/a """
174n/a from email.generator import BytesGenerator
175n/a policy = self.policy if policy is None else policy
176n/a fp = BytesIO()
177n/a g = BytesGenerator(fp, mangle_from_=False, policy=policy)
178n/a g.flatten(self, unixfrom=unixfrom)
179n/a return fp.getvalue()
180n/a
181n/a def is_multipart(self):
182n/a """Return True if the message consists of multiple parts."""
183n/a return isinstance(self._payload, list)
184n/a
185n/a #
186n/a # Unix From_ line
187n/a #
188n/a def set_unixfrom(self, unixfrom):
189n/a self._unixfrom = unixfrom
190n/a
191n/a def get_unixfrom(self):
192n/a return self._unixfrom
193n/a
194n/a #
195n/a # Payload manipulation.
196n/a #
197n/a def attach(self, payload):
198n/a """Add the given payload to the current payload.
199n/a
200n/a The current payload will always be a list of objects after this method
201n/a is called. If you want to set the payload to a scalar object, use
202n/a set_payload() instead.
203n/a """
204n/a if self._payload is None:
205n/a self._payload = [payload]
206n/a else:
207n/a try:
208n/a self._payload.append(payload)
209n/a except AttributeError:
210n/a raise TypeError("Attach is not valid on a message with a"
211n/a " non-multipart payload")
212n/a
213n/a def get_payload(self, i=None, decode=False):
214n/a """Return a reference to the payload.
215n/a
216n/a The payload will either be a list object or a string. If you mutate
217n/a the list object, you modify the message's payload in place. Optional
218n/a i returns that index into the payload.
219n/a
220n/a Optional decode is a flag indicating whether the payload should be
221n/a decoded or not, according to the Content-Transfer-Encoding header
222n/a (default is False).
223n/a
224n/a When True and the message is not a multipart, the payload will be
225n/a decoded if this header's value is `quoted-printable' or `base64'. If
226n/a some other encoding is used, or the header is missing, or if the
227n/a payload has bogus data (i.e. bogus base64 or uuencoded data), the
228n/a payload is returned as-is.
229n/a
230n/a If the message is a multipart and the decode flag is True, then None
231n/a is returned.
232n/a """
233n/a # Here is the logic table for this code, based on the email5.0.0 code:
234n/a # i decode is_multipart result
235n/a # ------ ------ ------------ ------------------------------
236n/a # None True True None
237n/a # i True True None
238n/a # None False True _payload (a list)
239n/a # i False True _payload element i (a Message)
240n/a # i False False error (not a list)
241n/a # i True False error (not a list)
242n/a # None False False _payload
243n/a # None True False _payload decoded (bytes)
244n/a # Note that Barry planned to factor out the 'decode' case, but that
245n/a # isn't so easy now that we handle the 8 bit data, which needs to be
246n/a # converted in both the decode and non-decode path.
247n/a if self.is_multipart():
248n/a if decode:
249n/a return None
250n/a if i is None:
251n/a return self._payload
252n/a else:
253n/a return self._payload[i]
254n/a # For backward compatibility, Use isinstance and this error message
255n/a # instead of the more logical is_multipart test.
256n/a if i is not None and not isinstance(self._payload, list):
257n/a raise TypeError('Expected list, got %s' % type(self._payload))
258n/a payload = self._payload
259n/a # cte might be a Header, so for now stringify it.
260n/a cte = str(self.get('content-transfer-encoding', '')).lower()
261n/a # payload may be bytes here.
262n/a if isinstance(payload, str):
263n/a if utils._has_surrogates(payload):
264n/a bpayload = payload.encode('ascii', 'surrogateescape')
265n/a if not decode:
266n/a try:
267n/a payload = bpayload.decode(self.get_param('charset', 'ascii'), 'replace')
268n/a except LookupError:
269n/a payload = bpayload.decode('ascii', 'replace')
270n/a elif decode:
271n/a try:
272n/a bpayload = payload.encode('ascii')
273n/a except UnicodeError:
274n/a # This won't happen for RFC compliant messages (messages
275n/a # containing only ASCII code points in the unicode input).
276n/a # If it does happen, turn the string into bytes in a way
277n/a # guaranteed not to fail.
278n/a bpayload = payload.encode('raw-unicode-escape')
279n/a if not decode:
280n/a return payload
281n/a if cte == 'quoted-printable':
282n/a return quopri.decodestring(bpayload)
283n/a elif cte == 'base64':
284n/a # XXX: this is a bit of a hack; decode_b should probably be factored
285n/a # out somewhere, but I haven't figured out where yet.
286n/a value, defects = decode_b(b''.join(bpayload.splitlines()))
287n/a for defect in defects:
288n/a self.policy.handle_defect(self, defect)
289n/a return value
290n/a elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
291n/a in_file = BytesIO(bpayload)
292n/a out_file = BytesIO()
293n/a try:
294n/a uu.decode(in_file, out_file, quiet=True)
295n/a return out_file.getvalue()
296n/a except uu.Error:
297n/a # Some decoding problem
298n/a return bpayload
299n/a if isinstance(payload, str):
300n/a return bpayload
301n/a return payload
302n/a
303n/a def set_payload(self, payload, charset=None):
304n/a """Set the payload to the given value.
305n/a
306n/a Optional charset sets the message's default character set. See
307n/a set_charset() for details.
308n/a """
309n/a if hasattr(payload, 'encode'):
310n/a if charset is None:
311n/a self._payload = payload
312n/a return
313n/a if not isinstance(charset, Charset):
314n/a charset = Charset(charset)
315n/a payload = payload.encode(charset.output_charset)
316n/a if hasattr(payload, 'decode'):
317n/a self._payload = payload.decode('ascii', 'surrogateescape')
318n/a else:
319n/a self._payload = payload
320n/a if charset is not None:
321n/a self.set_charset(charset)
322n/a
323n/a def set_charset(self, charset):
324n/a """Set the charset of the payload to a given character set.
325n/a
326n/a charset can be a Charset instance, a string naming a character set, or
327n/a None. If it is a string it will be converted to a Charset instance.
328n/a If charset is None, the charset parameter will be removed from the
329n/a Content-Type field. Anything else will generate a TypeError.
330n/a
331n/a The message will be assumed to be of type text/* encoded with
332n/a charset.input_charset. It will be converted to charset.output_charset
333n/a and encoded properly, if needed, when generating the plain text
334n/a representation of the message. MIME headers (MIME-Version,
335n/a Content-Type, Content-Transfer-Encoding) will be added as needed.
336n/a """
337n/a if charset is None:
338n/a self.del_param('charset')
339n/a self._charset = None
340n/a return
341n/a if not isinstance(charset, Charset):
342n/a charset = Charset(charset)
343n/a self._charset = charset
344n/a if 'MIME-Version' not in self:
345n/a self.add_header('MIME-Version', '1.0')
346n/a if 'Content-Type' not in self:
347n/a self.add_header('Content-Type', 'text/plain',
348n/a charset=charset.get_output_charset())
349n/a else:
350n/a self.set_param('charset', charset.get_output_charset())
351n/a if charset != charset.get_output_charset():
352n/a self._payload = charset.body_encode(self._payload)
353n/a if 'Content-Transfer-Encoding' not in self:
354n/a cte = charset.get_body_encoding()
355n/a try:
356n/a cte(self)
357n/a except TypeError:
358n/a # This 'if' is for backward compatibility, it allows unicode
359n/a # through even though that won't work correctly if the
360n/a # message is serialized.
361n/a payload = self._payload
362n/a if payload:
363n/a try:
364n/a payload = payload.encode('ascii', 'surrogateescape')
365n/a except UnicodeError:
366n/a payload = payload.encode(charset.output_charset)
367n/a self._payload = charset.body_encode(payload)
368n/a self.add_header('Content-Transfer-Encoding', cte)
369n/a
370n/a def get_charset(self):
371n/a """Return the Charset instance associated with the message's payload.
372n/a """
373n/a return self._charset
374n/a
375n/a #
376n/a # MAPPING INTERFACE (partial)
377n/a #
378n/a def __len__(self):
379n/a """Return the total number of headers, including duplicates."""
380n/a return len(self._headers)
381n/a
382n/a def __getitem__(self, name):
383n/a """Get a header value.
384n/a
385n/a Return None if the header is missing instead of raising an exception.
386n/a
387n/a Note that if the header appeared multiple times, exactly which
388n/a occurrence gets returned is undefined. Use get_all() to get all
389n/a the values matching a header field name.
390n/a """
391n/a return self.get(name)
392n/a
393n/a def __setitem__(self, name, val):
394n/a """Set the value of a header.
395n/a
396n/a Note: this does not overwrite an existing header with the same field
397n/a name. Use __delitem__() first to delete any existing headers.
398n/a """
399n/a max_count = self.policy.header_max_count(name)
400n/a if max_count:
401n/a lname = name.lower()
402n/a found = 0
403n/a for k, v in self._headers:
404n/a if k.lower() == lname:
405n/a found += 1
406n/a if found >= max_count:
407n/a raise ValueError("There may be at most {} {} headers "
408n/a "in a message".format(max_count, name))
409n/a self._headers.append(self.policy.header_store_parse(name, val))
410n/a
411n/a def __delitem__(self, name):
412n/a """Delete all occurrences of a header, if present.
413n/a
414n/a Does not raise an exception if the header is missing.
415n/a """
416n/a name = name.lower()
417n/a newheaders = []
418n/a for k, v in self._headers:
419n/a if k.lower() != name:
420n/a newheaders.append((k, v))
421n/a self._headers = newheaders
422n/a
423n/a def __contains__(self, name):
424n/a return name.lower() in [k.lower() for k, v in self._headers]
425n/a
426n/a def __iter__(self):
427n/a for field, value in self._headers:
428n/a yield field
429n/a
430n/a def keys(self):
431n/a """Return a list of all the message's header field names.
432n/a
433n/a These will be sorted in the order they appeared in the original
434n/a message, or were added to the message, and may contain duplicates.
435n/a Any fields deleted and re-inserted are always appended to the header
436n/a list.
437n/a """
438n/a return [k for k, v in self._headers]
439n/a
440n/a def values(self):
441n/a """Return a list of all the message's header values.
442n/a
443n/a These will be sorted in the order they appeared in the original
444n/a message, or were added to the message, and may contain duplicates.
445n/a Any fields deleted and re-inserted are always appended to the header
446n/a list.
447n/a """
448n/a return [self.policy.header_fetch_parse(k, v)
449n/a for k, v in self._headers]
450n/a
451n/a def items(self):
452n/a """Get all the message's header fields and values.
453n/a
454n/a These will be sorted in the order they appeared in the original
455n/a message, or were added to the message, and may contain duplicates.
456n/a Any fields deleted and re-inserted are always appended to the header
457n/a list.
458n/a """
459n/a return [(k, self.policy.header_fetch_parse(k, v))
460n/a for k, v in self._headers]
461n/a
462n/a def get(self, name, failobj=None):
463n/a """Get a header value.
464n/a
465n/a Like __getitem__() but return failobj instead of None when the field
466n/a is missing.
467n/a """
468n/a name = name.lower()
469n/a for k, v in self._headers:
470n/a if k.lower() == name:
471n/a return self.policy.header_fetch_parse(k, v)
472n/a return failobj
473n/a
474n/a #
475n/a # "Internal" methods (public API, but only intended for use by a parser
476n/a # or generator, not normal application code.
477n/a #
478n/a
479n/a def set_raw(self, name, value):
480n/a """Store name and value in the model without modification.
481n/a
482n/a This is an "internal" API, intended only for use by a parser.
483n/a """
484n/a self._headers.append((name, value))
485n/a
486n/a def raw_items(self):
487n/a """Return the (name, value) header pairs without modification.
488n/a
489n/a This is an "internal" API, intended only for use by a generator.
490n/a """
491n/a return iter(self._headers.copy())
492n/a
493n/a #
494n/a # Additional useful stuff
495n/a #
496n/a
497n/a def get_all(self, name, failobj=None):
498n/a """Return a list of all the values for the named field.
499n/a
500n/a These will be sorted in the order they appeared in the original
501n/a message, and may contain duplicates. Any fields deleted and
502n/a re-inserted are always appended to the header list.
503n/a
504n/a If no such fields exist, failobj is returned (defaults to None).
505n/a """
506n/a values = []
507n/a name = name.lower()
508n/a for k, v in self._headers:
509n/a if k.lower() == name:
510n/a values.append(self.policy.header_fetch_parse(k, v))
511n/a if not values:
512n/a return failobj
513n/a return values
514n/a
515n/a def add_header(self, _name, _value, **_params):
516n/a """Extended header setting.
517n/a
518n/a name is the header field to add. keyword arguments can be used to set
519n/a additional parameters for the header field, with underscores converted
520n/a to dashes. Normally the parameter will be added as key="value" unless
521n/a value is None, in which case only the key will be added. If a
522n/a parameter value contains non-ASCII characters it can be specified as a
523n/a three-tuple of (charset, language, value), in which case it will be
524n/a encoded according to RFC2231 rules. Otherwise it will be encoded using
525n/a the utf-8 charset and a language of ''.
526n/a
527n/a Examples:
528n/a
529n/a msg.add_header('content-disposition', 'attachment', filename='bud.gif')
530n/a msg.add_header('content-disposition', 'attachment',
531n/a filename=('utf-8', '', FuรƒŸballer.ppt'))
532n/a msg.add_header('content-disposition', 'attachment',
533n/a filename='FuรƒŸballer.ppt'))
534n/a """
535n/a parts = []
536n/a for k, v in _params.items():
537n/a if v is None:
538n/a parts.append(k.replace('_', '-'))
539n/a else:
540n/a parts.append(_formatparam(k.replace('_', '-'), v))
541n/a if _value is not None:
542n/a parts.insert(0, _value)
543n/a self[_name] = SEMISPACE.join(parts)
544n/a
545n/a def replace_header(self, _name, _value):
546n/a """Replace a header.
547n/a
548n/a Replace the first matching header found in the message, retaining
549n/a header order and case. If no matching header was found, a KeyError is
550n/a raised.
551n/a """
552n/a _name = _name.lower()
553n/a for i, (k, v) in zip(range(len(self._headers)), self._headers):
554n/a if k.lower() == _name:
555n/a self._headers[i] = self.policy.header_store_parse(k, _value)
556n/a break
557n/a else:
558n/a raise KeyError(_name)
559n/a
560n/a #
561n/a # Use these three methods instead of the three above.
562n/a #
563n/a
564n/a def get_content_type(self):
565n/a """Return the message's content type.
566n/a
567n/a The returned string is coerced to lower case of the form
568n/a `maintype/subtype'. If there was no Content-Type header in the
569n/a message, the default type as given by get_default_type() will be
570n/a returned. Since according to RFC 2045, messages always have a default
571n/a type this will always return a value.
572n/a
573n/a RFC 2045 defines a message's default type to be text/plain unless it
574n/a appears inside a multipart/digest container, in which case it would be
575n/a message/rfc822.
576n/a """
577n/a missing = object()
578n/a value = self.get('content-type', missing)
579n/a if value is missing:
580n/a # This should have no parameters
581n/a return self.get_default_type()
582n/a ctype = _splitparam(value)[0].lower()
583n/a # RFC 2045, section 5.2 says if its invalid, use text/plain
584n/a if ctype.count('/') != 1:
585n/a return 'text/plain'
586n/a return ctype
587n/a
588n/a def get_content_maintype(self):
589n/a """Return the message's main content type.
590n/a
591n/a This is the `maintype' part of the string returned by
592n/a get_content_type().
593n/a """
594n/a ctype = self.get_content_type()
595n/a return ctype.split('/')[0]
596n/a
597n/a def get_content_subtype(self):
598n/a """Returns the message's sub-content type.
599n/a
600n/a This is the `subtype' part of the string returned by
601n/a get_content_type().
602n/a """
603n/a ctype = self.get_content_type()
604n/a return ctype.split('/')[1]
605n/a
606n/a def get_default_type(self):
607n/a """Return the `default' content type.
608n/a
609n/a Most messages have a default content type of text/plain, except for
610n/a messages that are subparts of multipart/digest containers. Such
611n/a subparts have a default content type of message/rfc822.
612n/a """
613n/a return self._default_type
614n/a
615n/a def set_default_type(self, ctype):
616n/a """Set the `default' content type.
617n/a
618n/a ctype should be either "text/plain" or "message/rfc822", although this
619n/a is not enforced. The default content type is not stored in the
620n/a Content-Type header.
621n/a """
622n/a self._default_type = ctype
623n/a
624n/a def _get_params_preserve(self, failobj, header):
625n/a # Like get_params() but preserves the quoting of values. BAW:
626n/a # should this be part of the public interface?
627n/a missing = object()
628n/a value = self.get(header, missing)
629n/a if value is missing:
630n/a return failobj
631n/a params = []
632n/a for p in _parseparam(value):
633n/a try:
634n/a name, val = p.split('=', 1)
635n/a name = name.strip()
636n/a val = val.strip()
637n/a except ValueError:
638n/a # Must have been a bare attribute
639n/a name = p.strip()
640n/a val = ''
641n/a params.append((name, val))
642n/a params = utils.decode_params(params)
643n/a return params
644n/a
645n/a def get_params(self, failobj=None, header='content-type', unquote=True):
646n/a """Return the message's Content-Type parameters, as a list.
647n/a
648n/a The elements of the returned list are 2-tuples of key/value pairs, as
649n/a split on the `=' sign. The left hand side of the `=' is the key,
650n/a while the right hand side is the value. If there is no `=' sign in
651n/a the parameter the value is the empty string. The value is as
652n/a described in the get_param() method.
653n/a
654n/a Optional failobj is the object to return if there is no Content-Type
655n/a header. Optional header is the header to search instead of
656n/a Content-Type. If unquote is True, the value is unquoted.
657n/a """
658n/a missing = object()
659n/a params = self._get_params_preserve(missing, header)
660n/a if params is missing:
661n/a return failobj
662n/a if unquote:
663n/a return [(k, _unquotevalue(v)) for k, v in params]
664n/a else:
665n/a return params
666n/a
667n/a def get_param(self, param, failobj=None, header='content-type',
668n/a unquote=True):
669n/a """Return the parameter value if found in the Content-Type header.
670n/a
671n/a Optional failobj is the object to return if there is no Content-Type
672n/a header, or the Content-Type header has no such parameter. Optional
673n/a header is the header to search instead of Content-Type.
674n/a
675n/a Parameter keys are always compared case insensitively. The return
676n/a value can either be a string, or a 3-tuple if the parameter was RFC
677n/a 2231 encoded. When it's a 3-tuple, the elements of the value are of
678n/a the form (CHARSET, LANGUAGE, VALUE). Note that both CHARSET and
679n/a LANGUAGE can be None, in which case you should consider VALUE to be
680n/a encoded in the us-ascii charset. You can usually ignore LANGUAGE.
681n/a The parameter value (either the returned string, or the VALUE item in
682n/a the 3-tuple) is always unquoted, unless unquote is set to False.
683n/a
684n/a If your application doesn't care whether the parameter was RFC 2231
685n/a encoded, it can turn the return value into a string as follows:
686n/a
687n/a rawparam = msg.get_param('foo')
688n/a param = email.utils.collapse_rfc2231_value(rawparam)
689n/a
690n/a """
691n/a if header not in self:
692n/a return failobj
693n/a for k, v in self._get_params_preserve(failobj, header):
694n/a if k.lower() == param.lower():
695n/a if unquote:
696n/a return _unquotevalue(v)
697n/a else:
698n/a return v
699n/a return failobj
700n/a
701n/a def set_param(self, param, value, header='Content-Type', requote=True,
702n/a charset=None, language='', replace=False):
703n/a """Set a parameter in the Content-Type header.
704n/a
705n/a If the parameter already exists in the header, its value will be
706n/a replaced with the new value.
707n/a
708n/a If header is Content-Type and has not yet been defined for this
709n/a message, it will be set to "text/plain" and the new parameter and
710n/a value will be appended as per RFC 2045.
711n/a
712n/a An alternate header can be specified in the header argument, and all
713n/a parameters will be quoted as necessary unless requote is False.
714n/a
715n/a If charset is specified, the parameter will be encoded according to RFC
716n/a 2231. Optional language specifies the RFC 2231 language, defaulting
717n/a to the empty string. Both charset and language should be strings.
718n/a """
719n/a if not isinstance(value, tuple) and charset:
720n/a value = (charset, language, value)
721n/a
722n/a if header not in self and header.lower() == 'content-type':
723n/a ctype = 'text/plain'
724n/a else:
725n/a ctype = self.get(header)
726n/a if not self.get_param(param, header=header):
727n/a if not ctype:
728n/a ctype = _formatparam(param, value, requote)
729n/a else:
730n/a ctype = SEMISPACE.join(
731n/a [ctype, _formatparam(param, value, requote)])
732n/a else:
733n/a ctype = ''
734n/a for old_param, old_value in self.get_params(header=header,
735n/a unquote=requote):
736n/a append_param = ''
737n/a if old_param.lower() == param.lower():
738n/a append_param = _formatparam(param, value, requote)
739n/a else:
740n/a append_param = _formatparam(old_param, old_value, requote)
741n/a if not ctype:
742n/a ctype = append_param
743n/a else:
744n/a ctype = SEMISPACE.join([ctype, append_param])
745n/a if ctype != self.get(header):
746n/a if replace:
747n/a self.replace_header(header, ctype)
748n/a else:
749n/a del self[header]
750n/a self[header] = ctype
751n/a
752n/a def del_param(self, param, header='content-type', requote=True):
753n/a """Remove the given parameter completely from the Content-Type header.
754n/a
755n/a The header will be re-written in place without the parameter or its
756n/a value. All values will be quoted as necessary unless requote is
757n/a False. Optional header specifies an alternative to the Content-Type
758n/a header.
759n/a """
760n/a if header not in self:
761n/a return
762n/a new_ctype = ''
763n/a for p, v in self.get_params(header=header, unquote=requote):
764n/a if p.lower() != param.lower():
765n/a if not new_ctype:
766n/a new_ctype = _formatparam(p, v, requote)
767n/a else:
768n/a new_ctype = SEMISPACE.join([new_ctype,
769n/a _formatparam(p, v, requote)])
770n/a if new_ctype != self.get(header):
771n/a del self[header]
772n/a self[header] = new_ctype
773n/a
774n/a def set_type(self, type, header='Content-Type', requote=True):
775n/a """Set the main type and subtype for the Content-Type header.
776n/a
777n/a type must be a string in the form "maintype/subtype", otherwise a
778n/a ValueError is raised.
779n/a
780n/a This method replaces the Content-Type header, keeping all the
781n/a parameters in place. If requote is False, this leaves the existing
782n/a header's quoting as is. Otherwise, the parameters will be quoted (the
783n/a default).
784n/a
785n/a An alternative header can be specified in the header argument. When
786n/a the Content-Type header is set, we'll always also add a MIME-Version
787n/a header.
788n/a """
789n/a # BAW: should we be strict?
790n/a if not type.count('/') == 1:
791n/a raise ValueError
792n/a # Set the Content-Type, you get a MIME-Version
793n/a if header.lower() == 'content-type':
794n/a del self['mime-version']
795n/a self['MIME-Version'] = '1.0'
796n/a if header not in self:
797n/a self[header] = type
798n/a return
799n/a params = self.get_params(header=header, unquote=requote)
800n/a del self[header]
801n/a self[header] = type
802n/a # Skip the first param; it's the old type.
803n/a for p, v in params[1:]:
804n/a self.set_param(p, v, header, requote)
805n/a
806n/a def get_filename(self, failobj=None):
807n/a """Return the filename associated with the payload if present.
808n/a
809n/a The filename is extracted from the Content-Disposition header's
810n/a `filename' parameter, and it is unquoted. If that header is missing
811n/a the `filename' parameter, this method falls back to looking for the
812n/a `name' parameter.
813n/a """
814n/a missing = object()
815n/a filename = self.get_param('filename', missing, 'content-disposition')
816n/a if filename is missing:
817n/a filename = self.get_param('name', missing, 'content-type')
818n/a if filename is missing:
819n/a return failobj
820n/a return utils.collapse_rfc2231_value(filename).strip()
821n/a
822n/a def get_boundary(self, failobj=None):
823n/a """Return the boundary associated with the payload if present.
824n/a
825n/a The boundary is extracted from the Content-Type header's `boundary'
826n/a parameter, and it is unquoted.
827n/a """
828n/a missing = object()
829n/a boundary = self.get_param('boundary', missing)
830n/a if boundary is missing:
831n/a return failobj
832n/a # RFC 2046 says that boundaries may begin but not end in w/s
833n/a return utils.collapse_rfc2231_value(boundary).rstrip()
834n/a
835n/a def set_boundary(self, boundary):
836n/a """Set the boundary parameter in Content-Type to 'boundary'.
837n/a
838n/a This is subtly different than deleting the Content-Type header and
839n/a adding a new one with a new boundary parameter via add_header(). The
840n/a main difference is that using the set_boundary() method preserves the
841n/a order of the Content-Type header in the original message.
842n/a
843n/a HeaderParseError is raised if the message has no Content-Type header.
844n/a """
845n/a missing = object()
846n/a params = self._get_params_preserve(missing, 'content-type')
847n/a if params is missing:
848n/a # There was no Content-Type header, and we don't know what type
849n/a # to set it to, so raise an exception.
850n/a raise errors.HeaderParseError('No Content-Type header found')
851n/a newparams = []
852n/a foundp = False
853n/a for pk, pv in params:
854n/a if pk.lower() == 'boundary':
855n/a newparams.append(('boundary', '"%s"' % boundary))
856n/a foundp = True
857n/a else:
858n/a newparams.append((pk, pv))
859n/a if not foundp:
860n/a # The original Content-Type header had no boundary attribute.
861n/a # Tack one on the end. BAW: should we raise an exception
862n/a # instead???
863n/a newparams.append(('boundary', '"%s"' % boundary))
864n/a # Replace the existing Content-Type header with the new value
865n/a newheaders = []
866n/a for h, v in self._headers:
867n/a if h.lower() == 'content-type':
868n/a parts = []
869n/a for k, v in newparams:
870n/a if v == '':
871n/a parts.append(k)
872n/a else:
873n/a parts.append('%s=%s' % (k, v))
874n/a val = SEMISPACE.join(parts)
875n/a newheaders.append(self.policy.header_store_parse(h, val))
876n/a
877n/a else:
878n/a newheaders.append((h, v))
879n/a self._headers = newheaders
880n/a
881n/a def get_content_charset(self, failobj=None):
882n/a """Return the charset parameter of the Content-Type header.
883n/a
884n/a The returned string is always coerced to lower case. If there is no
885n/a Content-Type header, or if that header has no charset parameter,
886n/a failobj is returned.
887n/a """
888n/a missing = object()
889n/a charset = self.get_param('charset', missing)
890n/a if charset is missing:
891n/a return failobj
892n/a if isinstance(charset, tuple):
893n/a # RFC 2231 encoded, so decode it, and it better end up as ascii.
894n/a pcharset = charset[0] or 'us-ascii'
895n/a try:
896n/a # LookupError will be raised if the charset isn't known to
897n/a # Python. UnicodeError will be raised if the encoded text
898n/a # contains a character not in the charset.
899n/a as_bytes = charset[2].encode('raw-unicode-escape')
900n/a charset = str(as_bytes, pcharset)
901n/a except (LookupError, UnicodeError):
902n/a charset = charset[2]
903n/a # charset characters must be in us-ascii range
904n/a try:
905n/a charset.encode('us-ascii')
906n/a except UnicodeError:
907n/a return failobj
908n/a # RFC 2046, $4.1.2 says charsets are not case sensitive
909n/a return charset.lower()
910n/a
911n/a def get_charsets(self, failobj=None):
912n/a """Return a list containing the charset(s) used in this message.
913n/a
914n/a The returned list of items describes the Content-Type headers'
915n/a charset parameter for this message and all the subparts in its
916n/a payload.
917n/a
918n/a Each item will either be a string (the value of the charset parameter
919n/a in the Content-Type header of that part) or the value of the
920n/a 'failobj' parameter (defaults to None), if the part does not have a
921n/a main MIME type of "text", or the charset is not defined.
922n/a
923n/a The list will contain one string for each part of the message, plus
924n/a one for the container message (i.e. self), so that a non-multipart
925n/a message will still return a list of length 1.
926n/a """
927n/a return [part.get_content_charset(failobj) for part in self.walk()]
928n/a
929n/a def get_content_disposition(self):
930n/a """Return the message's content-disposition if it exists, or None.
931n/a
932n/a The return values can be either 'inline', 'attachment' or None
933n/a according to the rfc2183.
934n/a """
935n/a value = self.get('content-disposition')
936n/a if value is None:
937n/a return None
938n/a c_d = _splitparam(value)[0].lower()
939n/a return c_d
940n/a
941n/a # I.e. def walk(self): ...
942n/a from email.iterators import walk
943n/a
944n/a
945n/aclass MIMEPart(Message):
946n/a
947n/a def __init__(self, policy=None):
948n/a if policy is None:
949n/a from email.policy import default
950n/a policy = default
951n/a Message.__init__(self, policy)
952n/a
953n/a
954n/a def as_string(self, unixfrom=False, maxheaderlen=None, policy=None):
955n/a """Return the entire formatted message as a string.
956n/a
957n/a Optional 'unixfrom', when true, means include the Unix From_ envelope
958n/a header. maxheaderlen is retained for backward compatibility with the
959n/a base Message class, but defaults to None, meaning that the policy value
960n/a for max_line_length controls the header maximum length. 'policy' is
961n/a passed to the Generator instance used to serialize the mesasge; if it
962n/a is not specified the policy associated with the message instance is
963n/a used.
964n/a """
965n/a policy = self.policy if policy is None else policy
966n/a if maxheaderlen is None:
967n/a maxheaderlen = policy.max_line_length
968n/a return super().as_string(maxheaderlen=maxheaderlen, policy=policy)
969n/a
970n/a def __str__(self):
971n/a return self.as_string(policy=self.policy.clone(utf8=True))
972n/a
973n/a def is_attachment(self):
974n/a c_d = self.get('content-disposition')
975n/a return False if c_d is None else c_d.content_disposition == 'attachment'
976n/a
977n/a def _find_body(self, part, preferencelist):
978n/a if part.is_attachment():
979n/a return
980n/a maintype, subtype = part.get_content_type().split('/')
981n/a if maintype == 'text':
982n/a if subtype in preferencelist:
983n/a yield (preferencelist.index(subtype), part)
984n/a return
985n/a if maintype != 'multipart':
986n/a return
987n/a if subtype != 'related':
988n/a for subpart in part.iter_parts():
989n/a yield from self._find_body(subpart, preferencelist)
990n/a return
991n/a if 'related' in preferencelist:
992n/a yield (preferencelist.index('related'), part)
993n/a candidate = None
994n/a start = part.get_param('start')
995n/a if start:
996n/a for subpart in part.iter_parts():
997n/a if subpart['content-id'] == start:
998n/a candidate = subpart
999n/a break
1000n/a if candidate is None:
1001n/a subparts = part.get_payload()
1002n/a candidate = subparts[0] if subparts else None
1003n/a if candidate is not None:
1004n/a yield from self._find_body(candidate, preferencelist)
1005n/a
1006n/a def get_body(self, preferencelist=('related', 'html', 'plain')):
1007n/a """Return best candidate mime part for display as 'body' of message.
1008n/a
1009n/a Do a depth first search, starting with self, looking for the first part
1010n/a matching each of the items in preferencelist, and return the part
1011n/a corresponding to the first item that has a match, or None if no items
1012n/a have a match. If 'related' is not included in preferencelist, consider
1013n/a the root part of any multipart/related encountered as a candidate
1014n/a match. Ignore parts with 'Content-Disposition: attachment'.
1015n/a """
1016n/a best_prio = len(preferencelist)
1017n/a body = None
1018n/a for prio, part in self._find_body(self, preferencelist):
1019n/a if prio < best_prio:
1020n/a best_prio = prio
1021n/a body = part
1022n/a if prio == 0:
1023n/a break
1024n/a return body
1025n/a
1026n/a _body_types = {('text', 'plain'),
1027n/a ('text', 'html'),
1028n/a ('multipart', 'related'),
1029n/a ('multipart', 'alternative')}
1030n/a def iter_attachments(self):
1031n/a """Return an iterator over the non-main parts of a multipart.
1032n/a
1033n/a Skip the first of each occurrence of text/plain, text/html,
1034n/a multipart/related, or multipart/alternative in the multipart (unless
1035n/a they have a 'Content-Disposition: attachment' header) and include all
1036n/a remaining subparts in the returned iterator. When applied to a
1037n/a multipart/related, return all parts except the root part. Return an
1038n/a empty iterator when applied to a multipart/alternative or a
1039n/a non-multipart.
1040n/a """
1041n/a maintype, subtype = self.get_content_type().split('/')
1042n/a if maintype != 'multipart' or subtype == 'alternative':
1043n/a return
1044n/a parts = self.get_payload().copy()
1045n/a if maintype == 'multipart' and subtype == 'related':
1046n/a # For related, we treat everything but the root as an attachment.
1047n/a # The root may be indicated by 'start'; if there's no start or we
1048n/a # can't find the named start, treat the first subpart as the root.
1049n/a start = self.get_param('start')
1050n/a if start:
1051n/a found = False
1052n/a attachments = []
1053n/a for part in parts:
1054n/a if part.get('content-id') == start:
1055n/a found = True
1056n/a else:
1057n/a attachments.append(part)
1058n/a if found:
1059n/a yield from attachments
1060n/a return
1061n/a parts.pop(0)
1062n/a yield from parts
1063n/a return
1064n/a # Otherwise we more or less invert the remaining logic in get_body.
1065n/a # This only really works in edge cases (ex: non-text related or
1066n/a # alternatives) if the sending agent sets content-disposition.
1067n/a seen = [] # Only skip the first example of each candidate type.
1068n/a for part in parts:
1069n/a maintype, subtype = part.get_content_type().split('/')
1070n/a if ((maintype, subtype) in self._body_types and
1071n/a not part.is_attachment() and subtype not in seen):
1072n/a seen.append(subtype)
1073n/a continue
1074n/a yield part
1075n/a
1076n/a def iter_parts(self):
1077n/a """Return an iterator over all immediate subparts of a multipart.
1078n/a
1079n/a Return an empty iterator for a non-multipart.
1080n/a """
1081n/a if self.get_content_maintype() == 'multipart':
1082n/a yield from self.get_payload()
1083n/a
1084n/a def get_content(self, *args, content_manager=None, **kw):
1085n/a if content_manager is None:
1086n/a content_manager = self.policy.content_manager
1087n/a return content_manager.get_content(self, *args, **kw)
1088n/a
1089n/a def set_content(self, *args, content_manager=None, **kw):
1090n/a if content_manager is None:
1091n/a content_manager = self.policy.content_manager
1092n/a content_manager.set_content(self, *args, **kw)
1093n/a
1094n/a def _make_multipart(self, subtype, disallowed_subtypes, boundary):
1095n/a if self.get_content_maintype() == 'multipart':
1096n/a existing_subtype = self.get_content_subtype()
1097n/a disallowed_subtypes = disallowed_subtypes + (subtype,)
1098n/a if existing_subtype in disallowed_subtypes:
1099n/a raise ValueError("Cannot convert {} to {}".format(
1100n/a existing_subtype, subtype))
1101n/a keep_headers = []
1102n/a part_headers = []
1103n/a for name, value in self._headers:
1104n/a if name.lower().startswith('content-'):
1105n/a part_headers.append((name, value))
1106n/a else:
1107n/a keep_headers.append((name, value))
1108n/a if part_headers:
1109n/a # There is existing content, move it to the first subpart.
1110n/a part = type(self)(policy=self.policy)
1111n/a part._headers = part_headers
1112n/a part._payload = self._payload
1113n/a self._payload = [part]
1114n/a else:
1115n/a self._payload = []
1116n/a self._headers = keep_headers
1117n/a self['Content-Type'] = 'multipart/' + subtype
1118n/a if boundary is not None:
1119n/a self.set_param('boundary', boundary)
1120n/a
1121n/a def make_related(self, boundary=None):
1122n/a self._make_multipart('related', ('alternative', 'mixed'), boundary)
1123n/a
1124n/a def make_alternative(self, boundary=None):
1125n/a self._make_multipart('alternative', ('mixed',), boundary)
1126n/a
1127n/a def make_mixed(self, boundary=None):
1128n/a self._make_multipart('mixed', (), boundary)
1129n/a
1130n/a def _add_multipart(self, _subtype, *args, _disp=None, **kw):
1131n/a if (self.get_content_maintype() != 'multipart' or
1132n/a self.get_content_subtype() != _subtype):
1133n/a getattr(self, 'make_' + _subtype)()
1134n/a part = type(self)(policy=self.policy)
1135n/a part.set_content(*args, **kw)
1136n/a if _disp and 'content-disposition' not in part:
1137n/a part['Content-Disposition'] = _disp
1138n/a self.attach(part)
1139n/a
1140n/a def add_related(self, *args, **kw):
1141n/a self._add_multipart('related', *args, _disp='inline', **kw)
1142n/a
1143n/a def add_alternative(self, *args, **kw):
1144n/a self._add_multipart('alternative', *args, **kw)
1145n/a
1146n/a def add_attachment(self, *args, **kw):
1147n/a self._add_multipart('mixed', *args, _disp='attachment', **kw)
1148n/a
1149n/a def clear(self):
1150n/a self._headers = []
1151n/a self._payload = None
1152n/a
1153n/a def clear_content(self):
1154n/a self._headers = [(n, v) for n, v in self._headers
1155n/a if not n.lower().startswith('content-')]
1156n/a self._payload = None
1157n/a
1158n/a
1159n/aclass EmailMessage(MIMEPart):
1160n/a
1161n/a def set_content(self, *args, **kw):
1162n/a super().set_content(*args, **kw)
1163n/a if 'MIME-Version' not in self:
1164n/a self['MIME-Version'] = '1.0'