def decodeMessageAsString(msg): """ This helper method takes Message object or string and returns string which does not contain base64 encoded parts Returns message without any encoding in parts """ if isinstance(msg, str): msg = Parser().parsestr(msg) new = deepcopy(msg) # From is utf8 encoded: '=?utf-8?q?Site_Administrator_=3C=3E?=' new.replace_header('From', decode_header(new['From'])[0][0]) new.replace_header('Subject', decode_header(new['Subject'])[0][0]) charset = Charset('utf-8') charset.header_encoding = SHORTEST charset.body_encoding = QP charset.output_charset = 'utf-8' for part in new.walk(): if part.get_content_maintype() == "multipart": continue decoded = part.get_payload(decode=1) del part['Content-Transfer-Encoding'] part.set_payload(decoded, charset) return new.as_string()
def decodeMessageAsString(msg): """ This helper method takes Message object or string and returns string which does not contain base64 encoded parts Returns message without any encoding in parts """ if isinstance(msg, str): msg = Parser().parsestr(msg) new = deepcopy(msg) # From is utf8 encoded: '=?utf-8?q?Site_Administrator_=3C=3E?=' new.replace_header('From', decode_header(new['From'])[0][0]) new.replace_header('Subject', decode_header(new['Subject'])[0][0]) charset = Charset('utf-8') charset.header_encoding = SHORTEST charset.body_encoding = QP charset.output_charset = 'utf-8' for part in new.walk(): if part.get_content_maintype() == "multipart": continue decoded = part.get_payload(decode=1) del part['Content-Transfer-Encoding'] part.set_payload(decoded, charset) return new.as_string()
def _set_charset(self, mime): from email.Charset import Charset, QP, BASE64, SHORTEST mime_encoding = self.config.get('notification', 'mime_encoding').lower() charset = Charset() charset.input_charset = 'utf-8' charset.output_charset = 'utf-8' charset.input_codec = 'utf-8' charset.output_codec = 'utf-8' if mime_encoding == 'base64': charset.header_encoding = BASE64 charset.body_encoding = BASE64 elif mime_encoding in ('qp', 'quoted-printable'): charset.header_encoding = QP charset.body_encoding = QP elif mime_encoding == 'none': charset.header_encoding = SHORTEST charset.body_encoding = None del mime['Content-Transfer-Encoding'] mime.set_charset(charset)
def create_message(self): if self.attach: self.create_multipart_message() else: self.create_text_message() """comment """ charset = Charset(self.encoding) charset.header_encoding = QP charset.body_encoding = QP self.msg.set_charset(charset) """
def create_message(self): if self.attach: self.create_multipart_message() else: self.create_text_message() """comment """ charset = Charset(self.encoding) charset.header_encoding = QP charset.body_encoding = QP self.msg.set_charset(charset) """
def prepare_message(template, variables, encoding="utf-8"): r"""Return a prepared email.Message object. >>> template = (u"Subject: @SUBJECT@\n"+ ... u"From: @FROM@\n"+ ... u"To: @TO@\n"+ ... u"BCC: @FROM@\n\n"+ ... u"Hello, @GREETED@!") >>> variables = dict(SUBJECT="Test", ... FROM="*****@*****.**", ... TO="*****@*****.**", ... GREETED="World") >>> message = prepare_message(template, variables) >>> message["SUBJECT"] == variables["SUBJECT"] True >>> message["TO"] == variables["TO"] True >>> message["FROM"] == message["BCC"] == variables["FROM"] True >>> message.get_payload() 'Hello, World!' """ template = u"\r\n".join(template.splitlines()) template = replace_variables(template, variables) template = template.encode(encoding) message = message_from_string(template) DEFAULT_HEADERS = {"to": "@INVITEDEMAIL@", "from": "@INVITEREMAIL@"} for key, value in DEFAULT_HEADERS.iteritems(): if key not in message: value = replace_variables(value, variables) if isinstance(value, unicode): value = value.encode(encoding) message[key] = value charset = Charset(encoding) charset.header_encoding = QP charset.body_encoding = QP message.set_charset(charset) for field in ("from", "to", "cc", "bcc"): try: encode_address_field(message, field, encoding, charset) except UnicodeEncodeError as error: raise InviteException("Invalid '{0}' address: {1}".format( field, error)) return message
def prepare_message(template, variables, encoding="utf-8"): r"""Return a prepared email.Message object. >>> template = (u"Subject: @SUBJECT@\n"+ ... u"From: @FROM@\n"+ ... u"To: @TO@\n"+ ... u"BCC: @FROM@\n\n"+ ... u"Hello, @GREETED@!") >>> variables = dict(SUBJECT="Test", ... FROM="*****@*****.**", ... TO="*****@*****.**", ... GREETED="World") >>> message = prepare_message(template, variables) >>> message["SUBJECT"] == variables["SUBJECT"] True >>> message["TO"] == variables["TO"] True >>> message["FROM"] == message["BCC"] == variables["FROM"] True >>> message.get_payload() 'Hello, World!' """ template = u"\r\n".join(template.splitlines()) template = replace_variables(template, variables) template = template.encode(encoding) message = message_from_string(template) DEFAULT_HEADERS = {"to": "@INVITEDEMAIL@", "from": "@INVITEREMAIL@"} for key, value in DEFAULT_HEADERS.iteritems(): if key not in message: value = replace_variables(value, variables) if isinstance(value, unicode): value = value.encode(encoding) message[key] = value charset = Charset(encoding) charset.header_encoding = QP charset.body_encoding = QP message.set_charset(charset) for field in ("from", "to", "cc", "bcc"): try: encode_address_field(message, field, encoding, charset) except UnicodeEncodeError as error: raise InviteException("Invalid '{0}' address: {1}".format(field, error)) return message
def sendmail(request, to, subject, text, mail_from=None): """ Create and send a text/plain message Return a tuple of success or error indicator and message. @param request: the request object @param to: recipients (list) @param subject: subject of email (unicode) @param text: email body text (unicode) @param mail_from: override default mail_from @type mail_from: unicode @rtype: tuple @return: (is_ok, Description of error or OK message) """ import smtplib, socket from email.Message import Message from email.Charset import Charset, QP from email.Utils import formatdate, make_msgid _ = request.getText cfg = request.cfg mail_from = mail_from or cfg.mail_from subject = subject.encode(config.charset) # Create a text/plain body using CRLF (see RFC2822) text = text.replace(u'\n', u'\r\n') text = text.encode(config.charset) # Create a message using config.charset and quoted printable # encoding, which should be supported better by mail clients. # TODO: check if its really works better for major mail clients msg = Message() charset = Charset(config.charset) charset.header_encoding = QP charset.body_encoding = QP msg.set_charset(charset) # work around a bug in python 2.4.3 and above: msg.set_payload('=') if msg.as_string().endswith('='): text = charset.body_encode(text) msg.set_payload(text) # Create message headers # Don't expose emails addreses of the other subscribers, instead we # use the same mail_from, e.g. u"Jürgen Wiki <*****@*****.**>" address = encodeAddress(mail_from, charset) msg['From'] = address msg['To'] = address msg['Date'] = formatdate() msg['Message-ID'] = make_msgid() msg['Subject'] = Header(subject, charset) if cfg.mail_sendmail: # Set the BCC. This will be stripped later by sendmail. msg['BCC'] = ','.join(to) # Set Return-Path so that it isn't set (generally incorrectly) for us. msg['Return-Path'] = address # Send the message if not cfg.mail_sendmail: try: logging.debug("trying to send mail (smtp) via smtp server '%s'" % cfg.mail_smarthost) host, port = (cfg.mail_smarthost + ':25').split(':')[:2] server = smtplib.SMTP(host, int(port)) try: #server.set_debuglevel(1) if cfg.mail_login: user, pwd = cfg.mail_login.split() try: # try to do tls server.ehlo() if server.has_extn('starttls'): server.starttls() server.ehlo() logging.debug("tls connection to smtp server established") except: logging.debug("could not establish a tls connection to smtp server, continuing without tls") logging.debug("trying to log in to smtp server using account '%s'" % user) server.login(user, pwd) server.sendmail(mail_from, to, msg.as_string()) finally: try: server.quit() except AttributeError: # in case the connection failed, SMTP has no "sock" attribute pass except smtplib.SMTPException, e: logging.exception("smtp mail failed with an exception.") return (0, str(e)) except (os.error, socket.error), e: logging.exception("smtp mail failed with an exception.") return (0, _("Connection to mailserver '%(server)s' failed: %(reason)s") % { 'server': cfg.mail_smarthost, 'reason': str(e) })
def fix_msg(msg, data): """ Scan the message recursively to replace the text/html by a multipart/related containing the original text/html and the new clip_payload png attachment. The attachment detected and moved at the first pass (with Header X-Mailman-Part) will be removed. """ if msg.is_multipart(): parts = msg.get_payload() # remove the next level parts, then process and reattach them msg.set_payload(None) for p in parts: # recursive call r = fix_msg(p, data) # don't embbed related twice if msg.get_content_type() == 'multipart/related' and \ r.get_content_type() == 'multipart/related': for newp in r.get_payload(): msg.attach(newp) elif r == None: # removed continue else: msg.attach(r) # finished return msg else: # process the 'leaf' parts ctype = msg.get_content_type() # will be used to write back payload with correct encoding charset = msg.get_content_charset() c = Charset('utf-8') c.body_encoding = QP debug('ctype:%s charset:%s', ctype, charset) if ctype == 'text/plain': if msg['X-Mailman-Part']: # remove it! return None if data['do_txt']: # A normal txt part, add footer to plain text new_footer = TXT_ATTACHT_REPLACE new_footer += data['footer_attach'] old_content = msg.get_payload(decode=True) debug('old_content:%s, new_footer:%s', \ type(old_content), type(new_footer)) del msg['Content-type'] del msg['content-transfer-encoding'] msg.set_payload(old_content + new_footer, charset=c) debug('add txt footer') data['do_txt'] = False return msg elif ctype == 'text/html' and data['do_html']: # build multipart/related for HTML, will be canceled by the # parent recursive call if needed related = MIMEMultipart('related') html_footer = HTML_ATTACHMENT_HOLDER % \ {'HTML_HERE': data['html_footer_attach'] } html_footer += '</body>' old_content = msg.get_payload(decode=True) new_content = re.sub(r'</body>', html_footer, old_content) if old_content != new_content: debug('add html footer') else: debug('no html footer added') del msg['content-transfer-encoding'] msg.set_payload(new_content, charset=c) related.attach(msg) related.attach(data['clip']) data['do_html'] = False return related # unmodified return msg
# NNTP message encoding trivia from email.Header import Header from email.Charset import Charset from StringIO import StringIO import email.Charset import base64 import quopri import re charset = Charset('utf-8') charset.header_encoding = email.Charset.SHORTEST charset.body_encoding = email.Charset.SHORTEST line_end_re = re.compile(r'\r\n|\n\r|\n(?!\r)|\r(?!\n)') def encode_header_word(word): if type(word) is unicode: # see if it is plain ascii first try: return word.encode('us-ascii') except: # try to encode non-ascii headers using email.Header. The # 1000000 value is a maximum line length, meaning never # fold header lines. This is important for the XOVER # response. return str(Header(word, charset, 1000000)) else: return word # base64 encode a string, splitting lines in the output
def sendmail(request, to, subject, text, mail_from=None): """ Create and send a text/plain message Return a tuple of success or error indicator and message. @param request: the request object @param to: recipients (list) @param subject: subject of email (unicode) @param text: email body text (unicode) @param mail_from: override default mail_from @type mail_from: unicode @rtype: tuple @return: (is_ok, Description of error or OK message) """ import smtplib, socket from email.Message import Message from email.Charset import Charset, QP from email.Utils import formatdate, make_msgid _ = request.getText cfg = request.cfg mail_from = mail_from or cfg.mail_from logging.debug("send mail, from: %r, subj: %r" % (mail_from, subject)) logging.debug("send mail, to: %r" % (to, )) if not to: return (1, _("No recipients, nothing to do")) subject = subject.encode(config.charset) # Create a text/plain body using CRLF (see RFC2822) text = text.replace(u'\n', u'\r\n') text = text.encode(config.charset) # Create a message using config.charset and quoted printable # encoding, which should be supported better by mail clients. # TODO: check if its really works better for major mail clients msg = Message() charset = Charset(config.charset) charset.header_encoding = QP charset.body_encoding = QP msg.set_charset(charset) # work around a bug in python 2.4.3 and above: msg.set_payload('=') if msg.as_string().endswith('='): text = charset.body_encode(text) msg.set_payload(text) # Create message headers # Don't expose emails addreses of the other subscribers, instead we # use the same mail_from, e.g. u"Jürgen Wiki <*****@*****.**>" address = encodeAddress(mail_from, charset) msg['From'] = address msg['To'] = address msg['Date'] = formatdate() msg['Message-ID'] = make_msgid() msg['Subject'] = Header(subject, charset) # See RFC 3834 section 5: msg['Auto-Submitted'] = 'auto-generated' if cfg.mail_sendmail: # Set the BCC. This will be stripped later by sendmail. msg['BCC'] = ','.join(to) # Set Return-Path so that it isn't set (generally incorrectly) for us. msg['Return-Path'] = address # Send the message if not cfg.mail_sendmail: try: logging.debug("trying to send mail (smtp) via smtp server '%s'" % cfg.mail_smarthost) host, port = (cfg.mail_smarthost + ':25').split(':')[:2] server = smtplib.SMTP(host, int(port)) try: #server.set_debuglevel(1) if cfg.mail_login: user, pwd = cfg.mail_login.split() try: # try to do tls server.ehlo() if server.has_extn('starttls'): server.starttls() server.ehlo() logging.debug( "tls connection to smtp server established") except: logging.debug( "could not establish a tls connection to smtp server, continuing without tls" ) logging.debug( "trying to log in to smtp server using account '%s'" % user) server.login(user, pwd) server.sendmail(mail_from, to, msg.as_string()) finally: try: server.quit() except AttributeError: # in case the connection failed, SMTP has no "sock" attribute pass except UnicodeError, e: logging.exception("unicode error [%r -> %r]" % ( mail_from, to, )) return (0, str(e)) except smtplib.SMTPException, e: logging.exception("smtp mail failed with an exception.") return (0, str(e))
def sendmail(subject, text, to=None, cc=None, bcc=None, mail_from=None): """ Create and send a text/plain message Return a tuple of success or error indicator and message. :param subject: subject of email :type subject: unicode :param text: email body text :type text: unicode :param to: recipients :type to: list :param cc: recipients (CC) :type cc: list :param bcc: recipients (BCC) :type bcc: list :param mail_from: override default mail_from :type mail_from: unicode :rtype: tuple :returns: (is_ok, Description of error or OK message) """ import smtplib, socket from email.Message import Message from email.Charset import Charset, QP from email.Utils import formatdate, make_msgid cfg = app.cfg mail_from = mail_from or cfg.mail_from logging.debug("send mail, from: {0!r}, subj: {1!r}".format(mail_from, subject)) logging.debug("send mail, to: {0!r}".format(to)) if not to and not cc and not bcc: return (1, _("No recipients, nothing to do")) subject = subject.encode(config.charset) # Create a text/plain body using CRLF (see RFC2822) text = text.replace(u'\n', u'\r\n') text = text.encode(config.charset) # Create a message using config.charset and quoted printable # encoding, which should be supported better by mail clients. # TODO: check if its really works better for major mail clients msg = Message() charset = Charset(config.charset) charset.header_encoding = QP charset.body_encoding = QP msg.set_charset(charset) # work around a bug in python 2.4.3 and above: msg.set_payload('=') if msg.as_string().endswith('='): text = charset.body_encode(text) msg.set_payload(text) address = encodeAddress(mail_from, charset) msg['From'] = address if to: msg['To'] = ','.join(to) if cc: msg['CC'] = ','.join(cc) msg['Date'] = formatdate() msg['Message-ID'] = make_msgid() msg['Subject'] = Header(subject, charset) # See RFC 3834 section 5: msg['Auto-Submitted'] = 'auto-generated' if cfg.mail_sendmail: if bcc: # Set the BCC. This will be stripped later by sendmail. msg['BCC'] = ','.join(bcc) # Set Return-Path so that it isn't set (generally incorrectly) for us. msg['Return-Path'] = address # Send the message if not cfg.mail_sendmail: try: logging.debug("trying to send mail (smtp) via smtp server '{0}'".format(cfg.mail_smarthost)) host, port = (cfg.mail_smarthost + ':25').split(':')[:2] server = smtplib.SMTP(host, int(port)) try: #server.set_debuglevel(1) if cfg.mail_username is not None and cfg.mail_password is not None: try: # try to do tls server.ehlo() if server.has_extn('starttls'): server.starttls() server.ehlo() logging.debug("tls connection to smtp server established") except: logging.debug("could not establish a tls connection to smtp server, continuing without tls") logging.debug("trying to log in to smtp server using account '{0}'".format(cfg.mail_username)) server.login(cfg.mail_username, cfg.mail_password) server.sendmail(mail_from, (to or []) + (cc or []) + (bcc or []), msg.as_string()) finally: try: server.quit() except AttributeError: # in case the connection failed, SMTP has no "sock" attribute pass except smtplib.SMTPException as e: logging.exception("smtp mail failed with an exception.") return (0, str(e)) except (os.error, socket.error) as e: logging.exception("smtp mail failed with an exception.") return (0, _("Connection to mailserver '%(server)s' failed: %(reason)s", server=cfg.mail_smarthost, reason=str(e) )) else: try: logging.debug("trying to send mail (sendmail)") sendmailp = os.popen(cfg.mail_sendmail, "w") # msg contains everything we need, so this is a simple write sendmailp.write(msg.as_string()) sendmail_status = sendmailp.close() if sendmail_status: logging.error("sendmail failed with status: {0!s}".format(sendmail_status)) return (0, str(sendmail_status)) except: logging.exception("sendmail failed with an exception.") return (0, _("Mail not sent")) logging.debug("Mail sent successfully") return (1, _("Mail sent successfully"))
def process(mlist, msg, msgdata): # Digests and Mailman-craft messages should not get additional headers if msgdata.get('isdigest') or msgdata.get('nodecorate'): return d = {} if msgdata.get('personalize'): # Calculate the extra personalization dictionary. Note that the # length of the recips list better be exactly 1. recips = msgdata.get('recips') assert type(recips) == ListType and len(recips) == 1 member = recips[0].lower() d['user_address'] = member try: d['user_delivered_to'] = mlist.getMemberCPAddress(member) # BAW: Hmm, should we allow this? d['user_password'] = mlist.getMemberPassword(member) d['user_language'] = mlist.getMemberLanguage(member) username = mlist.getMemberName(member) or None try: username = username.encode(Utils.GetCharSet(d['user_language'])) except (AttributeError, UnicodeError): username = member d['user_name'] = username d['user_optionsurl'] = mlist.GetOptionsURL(member) except Errors.NotAMemberError: pass # These strings are descriptive for the log file and shouldn't be i18n'd d.update(msgdata.get('decoration-data', {})) header = decorate(mlist, mlist.msg_header, 'non-digest header', d) footer = decorate(mlist, mlist.msg_footer, 'non-digest footer', d) # Escape hatch if both the footer and header are empty if not header and not footer: return # Be MIME smart here. We only attach the header and footer by # concatenation when the message is a non-multipart of type text/plain. # Otherwise, if it is not a multipart, we make it a multipart, and then we # add the header and footer as text/plain parts. # # BJG: In addition, only add the footer if the message's character set # matches the charset of the list's preferred language. This is a # suboptimal solution, and should be solved by allowing a list to have # multiple headers/footers, for each language the list supports. # # Also, if the list's preferred charset is us-ascii, we can always # safely add the header/footer to a plain text message since all # charsets Mailman supports are strict supersets of us-ascii -- # no, UTF-16 emails are not supported yet. # # TK: Message with 'charset=' cause trouble. So, instead of # mgs.get_content_charset('us-ascii') ... mcset = msg.get_content_charset() or 'us-ascii' lcset = Utils.GetCharSet(mlist.preferred_language) msgtype = msg.get_content_type() # BAW: If the charsets don't match, should we add the header and footer by # MIME multipart chroming the message? wrap = True if not msg.is_multipart() and msgtype == 'text/plain': # TK: Set up a list for Decorate charsets csets = [] for cs in mm_cfg.DECORATE_CHARSETS: if cs == mm_cfg.DECORATE_MCSET: cs = mcset if cs == mm_cfg.DECORATE_LCSET: cs = lcset cs = Charset(cs).output_charset if cs not in csets: csets.append(cs) # TK: Try to keep the message plain by converting the header/ # footer/oldpayload into unicode and encode with mcset/lcset. # Try to decode qp/base64 also. # It is possible header/footer is already unicode if it was # interpolated with a unicode. if isinstance(header, unicode): uheader = header else: uheader = unicode(header, lcset, 'ignore') if isinstance(footer, unicode): ufooter = footer else: ufooter = unicode(footer, lcset, 'ignore') try: oldpayload = unicode(msg.get_payload(decode=True), mcset) frontsep = endsep = u'' if header and not header.endswith('\n'): frontsep = u'\n' if footer and not oldpayload.endswith('\n'): endsep = u'\n' payload = uheader + frontsep + oldpayload + endsep + ufooter for cs in csets: try: newpayload = payload.encode(cs) format = msg.get_param('format') delsp = msg.get_param('delsp') cte = msg.get('content-transfer-encoding', '').lower() del msg['content-transfer-encoding'] del msg['content-type'] if mm_cfg.DECORATE_PREFER_8BIT or cs == mcset: cs = Charset(cs) if cte == 'quoted-printable': cs.body_encoding = QP elif cte == 'base64': cs.body_encoding = BASE64 else: cs.body_encoding = None msg.set_payload(newpayload, cs) if format: msg.set_param('Format', format) if delsp: msg.set_param('DelSp', delsp) wrap = False break except (UnicodeError, TypeError): continue except (LookupError, UnicodeError): pass elif msg.get_content_type() == 'multipart/mixed': # The next easiest thing to do is just prepend the header and append # the footer as additional subparts payload = msg.get_payload() if not isinstance(payload, ListType): payload = [payload] if footer: mimeftr = MIMEText(footer, 'plain', lcset) mimeftr['Content-Disposition'] = 'inline' payload.append(mimeftr) if header: mimehdr = MIMEText(header, 'plain', lcset) mimehdr['Content-Disposition'] = 'inline' payload.insert(0, mimehdr) msg.set_payload(payload) wrap = False # If we couldn't add the header or footer in a less intrusive way, we can # at least do it by MIME encapsulation. We want to keep as much of the # outer chrome as possible. if not wrap: return # Because of the way Message objects are passed around to process(), we # need to play tricks with the outer message -- i.e. the outer one must # remain the same instance. So we're going to create a clone of the outer # message, with all the header chrome intact, then copy the payload to it. # This will give us a clone of the original message, and it will form the # basis of the interior, wrapped Message. inner = Message() # Which headers to copy? Let's just do the Content-* headers copied = False for h, v in msg.items(): if h.lower().startswith('content-'): inner[h] = v copied = True inner.set_payload(msg.get_payload()) # For completeness inner.set_unixfrom(msg.get_unixfrom()) inner.preamble = msg.preamble inner.epilogue = msg.epilogue # Don't copy get_charset, as this might be None, even if # get_content_charset isn't. However, do make sure there is a default # content-type, even if the original message was not MIME. inner.set_default_type(msg.get_default_type()) if not copied: inner['Content-Type'] = inner.get_content_type() if msg['mime-version'] == None: msg['MIME-Version'] = '1.0' # BAW: HACK ALERT. if hasattr(msg, '__version__'): inner.__version__ = msg.__version__ # Now, play games with the outer message to make it contain three # subparts: the header (if any), the wrapped message, and the footer (if # any). payload = [inner] if header: mimehdr = MIMEText(header, 'plain', lcset) mimehdr['Content-Disposition'] = 'inline' payload.insert(0, mimehdr) if footer: mimeftr = MIMEText(footer, 'plain', lcset) mimeftr['Content-Disposition'] = 'inline' payload.append(mimeftr) msg.set_payload(payload) del msg['content-type'] del msg['content-transfer-encoding'] del msg['content-disposition'] msg['Content-Type'] = 'multipart/mixed'