def encodeOptimally(part, exact=True): """Encode a message part as needed. If the part is more than 10% high-bit characters, it will be encoded using base64 encoding. If the contents are 7-bit and exact is False, the part will not be encoded. Otherwise, the message will be encoded as quoted-printable. If quoted-printable encoding is used, exact will cause all line-ending characters to be quoted. :param part: The message part to encode. :param exact: If True, the encoding will ensure newlines are not mangled. If False, 7-bit attachments will not be encoded. """ # If encoding has already been done by virtue of a charset being # previously specified, then do nothing. if 'Content-Transfer-Encoding' in part: return orig_payload = part.get_payload() if not exact and is_ascii_only(orig_payload): return # Payloads which are completely ascii need no encoding. quopri_bytes = b2a_qp(orig_payload, istext=not exact) # If 10% of characters need to be encoded, len is 1.2 times # the original len. If more than 10% need encoding, the result # is unlikely to be readable. if len(quopri_bytes) < len(orig_payload) * 1.2: part.set_payload(quopri_bytes) part['Content-Transfer-Encoding'] = 'quoted-printable' else: encode_base64(part)
def test_MakeMessage_with_binary_attachment(self): """Message should still encode as ascii with non-ascii attachments.""" ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', u'Body') ctrl.addAttachment('\x00\xffattach') message = ctrl.makeMessage() self.assertTrue(is_ascii_only(message.as_string()), "Non-ascii message string.")
def test_MakeMessage_with_binary_attachment(self): """Message should still encode as ascii with non-ascii attachments.""" ctrl = MailController( '*****@*****.**', '*****@*****.**', 'subject', u'Body') ctrl.addAttachment('\x00\xffattach') message = ctrl.makeMessage() self.assertTrue( is_ascii_only(message.as_string()), "Non-ascii message string.")
def publishTraverse(self, request, name): """Shim, to set objects in the launchbag when traversing them. This needs moving into the publication component, once it has been refactored. """ # Launchpad only produces ascii URLs. If the name is not ascii, we # can say nothing is found here. if not is_ascii_only(name): raise NotFound(self.context, name) nextobj = self._publishTraverse(request, name) getUtility(IOpenLaunchBag).add(nextobj) return nextobj
def test_MakeMessage_unicode_body_with_attachment(self): # A message with an attachment with a unicode body gets sent as # UTF-8 encoded MIME text, and the message as a whole can be flattened # to a string with Unicode errors. ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', u'Bj\xf6rn') ctrl.addAttachment('attach') message = ctrl.makeMessage() # Make sure that the message can be flattened to a string as sendmail # does without raising a UnicodeEncodeError. message.as_string() body, attachment = message.get_payload() self.assertEqual('Bj\xc3\xb6rn', body.get_payload(decode=True)) self.assertTrue(is_ascii_only(message.as_string()))
def test_MakeMessage_unicode_body_with_attachment(self): # A message with an attachment with a unicode body gets sent as # UTF-8 encoded MIME text, and the message as a whole can be flattened # to a string with Unicode errors. ctrl = MailController( '*****@*****.**', '*****@*****.**', 'subject', u'Bj\xf6rn') ctrl.addAttachment('attach') message = ctrl.makeMessage() # Make sure that the message can be flattened to a string as sendmail # does without raising a UnicodeEncodeError. message.as_string() body, attachment = message.get_payload() self.assertEqual('Bj\xc3\xb6rn', body.get_payload(decode=True)) self.assertTrue(is_ascii_only(message.as_string()))
def makeMessage(self): # It's the caller's responsibility to either encode the address fields # to ASCII strings or pass in Unicode strings. # Using the maxlinelen for the Headers as we have paranoid checks to # make sure that we have no carriage returns in the to or from email # addresses. We use nice email addresses like 'Launchpad Community # Help Rotation team <*****@*****.**>' that # get broken over two lines in the header. RFC 5322 specified that # the lines MUST be no more than 998, so we use that as our maximum. from_addr = Header(self.from_addr, maxlinelen=998).encode() to_addrs = [ Header(address, maxlinelen=998).encode() for address in list(self.to_addrs) ] for address in [from_addr] + to_addrs: if not isinstance(address, str) or not is_ascii_only(address): raise AssertionError('Expected an ASCII str object, got: %r' % address) do_paranoid_email_content_validation(from_addr=from_addr, to_addrs=to_addrs, subject=self.subject, body=self.body) if len(self.attachments) == 0: msg = MIMEText(self.body.encode('utf-8'), 'plain', 'utf-8') else: msg = MIMEMultipart() body_part = MIMEText(self.body.encode('utf-8'), 'plain', 'utf-8') msg.attach(body_part) for attachment in self.attachments: msg.attach(attachment) # The header_body_values may be a list or tuple of values, so we will # add a header once for each value provided for that header. # (X-Launchpad-Bug, for example, may often be set more than once for a # bugmail.) for header, header_body_values in self.headers.items(): if not zisinstance(header_body_values, (list, tuple)): header_body_values = [header_body_values] for header_body_value in header_body_values: msg[header] = header_body_value msg['To'] = ','.join(to_addrs) msg['From'] = from_addr msg['Subject'] = self.subject return msg
def makeMessage(self): # It's the caller's responsibility to either encode the address fields # to ASCII strings or pass in Unicode strings. # Using the maxlinelen for the Headers as we have paranoid checks to # make sure that we have no carriage returns in the to or from email # addresses. We use nice email addresses like 'Launchpad Community # Help Rotation team <*****@*****.**>' that # get broken over two lines in the header. RFC 5322 specified that # the lines MUST be no more than 998, so we use that as our maximum. from_addr = Header(self.from_addr, maxlinelen=998).encode() to_addrs = [Header(address, maxlinelen=998).encode() for address in list(self.to_addrs)] for address in [from_addr] + to_addrs: if not isinstance(address, str) or not is_ascii_only(address): raise AssertionError( 'Expected an ASCII str object, got: %r' % address) do_paranoid_email_content_validation( from_addr=from_addr, to_addrs=to_addrs, subject=self.subject, body=self.body) if len(self.attachments) == 0: msg = MIMEText(self.body.encode('utf-8'), 'plain', 'utf-8') else: msg = MIMEMultipart() body_part = MIMEText(self.body.encode('utf-8'), 'plain', 'utf-8') msg.attach(body_part) for attachment in self.attachments: msg.attach(attachment) # The header_body_values may be a list or tuple of values, so we will # add a header once for each value provided for that header. # (X-Launchpad-Bug, for example, may often be set more than once for a # bugmail.) for header, header_body_values in self.headers.items(): if not zisinstance(header_body_values, (list, tuple)): header_body_values = [header_body_values] for header_body_value in header_body_values: msg[header] = header_body_value msg['To'] = ','.join(to_addrs) msg['From'] = from_addr msg['Subject'] = self.subject return msg