def test_encodeOptimally_with_ascii_text(self): """Mostly-ascii attachments should be encoded as quoted-printable.""" text = 'I went to the cafe today.\n\r' part = Message() part.set_payload(text) MailController.encodeOptimally(part, exact=False) self.assertEqual(part.get_payload(), part.get_payload(decode=True)) self.assertIs(None, part['Content-Transfer-Encoding'])
def test_encodeOptimally_with_7_bit_binary(self): """Mostly-ascii attachments should be encoded as quoted-printable.""" text = 'I went to the cafe today.\n\r' part = Message() part.set_payload(text) MailController.encodeOptimally(part) self.assertEqual(text, part.get_payload(decode=True)) self.assertEqual('I went to the cafe today.=0A=0D', part.get_payload()) self.assertEqual('quoted-printable', part['Content-Transfer-Encoding'])
def test_payload_encoding(self): jhello = '\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc\xa5\xeb\xa5\xc9\xa1\xaa' jcode = 'euc-jp' msg = Message() msg.set_payload(jhello, jcode) ustr = unicode(msg.get_payload(), msg.get_content_charset()) self.assertEqual(jhello, ustr.encode(jcode))
def test_encodeOptimally_with_binary(self): """Significantly non-ascii attachments should be base64-encoded.""" bytes = '\x00\xff\x44\x55\xaa\x99' part = Message() part.set_payload(bytes) MailController.encodeOptimally(part) self.assertEqual(bytes, part.get_payload(decode=True)) self.assertEqual('base64', part['Content-Transfer-Encoding'])
def test_encodeOptimally_with_text(self): """Mostly-ascii attachments should be encoded as quoted-printable.""" text = u'I went to the caf\u00e9 today.'.encode('utf-8') part = Message() part.set_payload(text) MailController.encodeOptimally(part) self.assertEqual(text, part.get_payload(decode=True)) self.assertEqual('quoted-printable', part['Content-Transfer-Encoding'])
def construct_message(imap, msg_format, msg_to, msg_from, msg_date, msg_subject, msg_body): msg = Message() msg.add_header('Date', formatdate(time.mktime(msg_date.timetuple()))) msg.add_header('Message-Id', create_id(msg_date, msg_from)) msg.add_header('To', msg_to) msg.add_header('From', msg_from) msg.add_header('MIME-Version', '1.0') msg.add_header('Subject', msg_subject) payload = Message() payload.add_header('Content-Type', msg_format) if msg_format in ('text/html', 'text/plain'): payload.add_header('Content-Transfer-Encoding', '8bit') payload.set_payload(''.join(msg_body)) else: payload.add_header('Content-Transfer-Encoding', 'base64') payload.add_header('Content-Disposition', 'attachment; filename="%s"' % msg_subject) payload.set_payload(encodestring(''.join(msg_body)).decode()) for item in payload.items(): msg.add_header(item[0], item[1]) msg.set_payload(payload.get_payload()) try: msg.as_string() except Exception, e: print e
def verpdeliver(mlist, msg, msgdata, envsender, failures, conn): for recip in msgdata['recips']: # We now need to stitch together the message with its header and # footer. If we're VERPIng, we have to calculate the envelope sender # for each recipient. Note that the list of recipients must be of # length 1. # # BAW: ezmlm includes the message number in the envelope, used when # sending a notification to the user telling her how many messages # they missed due to bouncing. Neat idea. msgdata['recips'] = [recip] # Make a copy of the message and decorate + delivery that msgcopy = copy.deepcopy(msg) Decorate.process(mlist, msgcopy, msgdata) # Calculate the envelope sender, which we may be VERPing if msgdata.get('verp'): bmailbox, bdomain = Utils.ParseEmail(envsender) rmailbox, rdomain = Utils.ParseEmail(recip) if rdomain is None: # The recipient address is not fully-qualified. We can't # deliver it to this person, nor can we craft a valid verp # header. I don't think there's much we can do except ignore # this recipient. syslog('smtp', 'Skipping VERP delivery to unqual recip: %s', recip) continue d = {'bounces': bmailbox, 'mailbox': rmailbox, 'host' : DOT.join(rdomain), } envsender = '%s@%s' % ((mm_cfg.VERP_FORMAT % d), DOT.join(bdomain)) if mlist.personalize == 2: # When fully personalizing, we want the To address to point to the # recipient, not to the mailing list del msgcopy['to'] name = None if mlist.isMember(recip): name = mlist.getMemberName(recip) if name: # Convert the name to an email-safe representation. If the # name is a byte string, convert it first to Unicode, given # the character set of the member's language, replacing bad # characters for which we can do nothing about. Once we have # the name as Unicode, we can create a Header instance for it # so that it's properly encoded for email transport. charset = Utils.GetCharSet(mlist.getMemberLanguage(recip)) if charset == 'us-ascii': # Since Header already tries both us-ascii and utf-8, # let's add something a bit more useful. charset = 'iso-8859-1' charset = Charset(charset) codec = charset.input_codec or 'ascii' if not isinstance(name, UnicodeType): name = unicode(name, codec, 'replace') name = Header(name, charset).encode() msgcopy['To'] = formataddr((name, recip)) else: msgcopy['To'] = recip # We can flag the mail as a duplicate for each member, if they've # already received this message, as calculated by Message-ID. See # AvoidDuplicates.py for details. del msgcopy['x-mailman-copy'] if msgdata.get('add-dup-header', {}).has_key(recip): msgcopy['X-Mailman-Copy'] = 'yes' # GPG encryption if 'encrypted_gpg' in msgdata and msgdata['encrypted_gpg'] and mlist.encrypt_policy!=0: # Encryption is not forbidden in config try: keyids=mlist.getGPGKeyIDs(recip) except: keyids=None if enforceEncryptPolicy(mlist,msg,msgdata) and keyids==None: syslog('gpg','Encryption mandatory, but no keys found for %s: '\ 'Discarding message',recip) failures[recip]=(550,'Encryption mandatory, but no keys found') return gh = GPGUtils.GPGHelper(mlist) # Extract / generate plaintext gpg_use_inlineformat = False # TODO: Create config setting if not msgcopy.is_multipart() and gpg_use_inlineformat: plaintext=msgcopy.get_payload() else: if not msgcopy.is_multipart(): plaintext = 'Content-Type: %s\n' \ 'Content-Disposition: inline\n' \ % msgcopy.get('Content-Type') if not msgcopy.get('Content-Transfer-Encoding') is None: plaintext += 'Content-Transfer-Encoding: %s\n' \ % msgcopy.get('Content-Transfer-Encoding') plaintext += '\n%s' % msgcopy.get_payload() else: hp = HeaderParser() tmp = msgcopy.as_string() tmpmsg = hp.parsestr(tmp) plaintext = 'Content-Type: %s\n' \ 'Content-Disposition: inline\n\n%s' \ % (msgcopy.get('Content-Type'),tmpmsg.get_payload()) # Do encryption, report errors ciphertext = None if not keyids is None: # Can encrypt. # No signing policy, or voluntary and original wasn't signed: just encrypt if mlist.sign_policy == 0 or \ (mlist.sign_policy==1 and not msgdata['signed_gpg']): ciphertext = gh.encryptMessage(plaintext,keyids) else: ciphertext = gh.encryptSignMessage(plaintext,keyids) if ciphertext==None: # Must always encrypt, since if we arrived here encrypt_policy # is either Mantatory or (Voluntary and incoming msg was encrypted). syslog('gpg',"Can't encrypt message to %s: " \ "Discarding message",keyids) failures[recip]=(550,'Unable to encrypt message') return # Compile encrypted message if not ciphertext is None: if msgcopy.has_key('Content-Transfer-Encoding'): msgcopy.replace_header('Content-Transfer-Encoding','7bit') else: msgcopy.add_header('Content-Transfer-Encoding','7bit') if not msgcopy.is_multipart() and gpg_use_inlineformat: msgcopy.set_payload(ciphertext) msgcopy.set_param('x-action','pgp-encrypted') else: msgcopy.replace_header('Content-Type','multipart/encrypted') msgcopy.set_param('protocol','application/pgp-encrypted') msgcopy.set_payload(None) submsg = Message() submsg.add_header('Content-Type','application/pgp-encrypted') submsg.set_payload('Version: 1\n') msgcopy.attach(submsg) submsg = Message() submsg.add_header('Content-Type','application/octet-stream; name="encrypted.asc"') submsg.add_header('Content-Disposition','inline; filename="encrypted.asc"') submsg.set_payload(ciphertext) msgcopy.attach(submsg) syslog('gpg','Sending encrypted message to %s',recip) else: syslog('gpg','Sending unencrypted message to %s',recip) if 'encrypted_smime' in msgdata and msgdata['encrypted_smime'] and mlist.encrypt_policy != 0: # FIXME: this is as crude as can be sm = SMIMEUtils.SMIMEHelper(mlist) recipfile = sm.getSMIMEMemberCertFile(recip) if not recipfile: failures[recip]=(550,'No S/MIME key found') return else: plaintext=msgcopy.get_payload() if not msgcopy.is_multipart(): plaintext = msgcopy.get_payload() syslog('gpg', "About to S/MIME encrypt plaintext from singlepart") else: # message contains e.g. signature? # FIXME we fetch only the first attachment. We search for # attachments only 2 levels deep. That's suboptimal... # perhaps the PGP way (invoking # hp = HeaderParser() # ) is better. submsgs = msgcopy.get_payload() submsg = submsgs[0] if not submsg.is_multipart(): plaintext = submsg.get_payload() else: subsubmsgs = submsg.get_payload() subsubmsg = subsubmsgs[0] plaintext = subsubmsg.get_payload() syslog('gpg', "About to S/MIME encrypt plaintext from multipart") if mlist.sign_policy == 0 or \ (mlist.sign_policy==1 and not msgdata['signed_smime']): ciphertext = sm.encryptMessage(plaintext,recipfile) else: ciphertext = sm.encryptSignMessage(plaintext,recipfile) # deal with both header and body-part of ciphertext (header, body) = ciphertext.split("\n\n", 1) for l in header.split("\n"): (k, v) = l.split(": ", 1) # behave sane with borken openssl like 0.9.7e (e.g. Debian's 0.9.7e-3sarge1) # openssl 0.9.8a-4a0.sarge.1 is known to work OK. # A borken openssl (and therefore sm.encryptMessage) returns # Content-Type: application/x-pkcs7-mime; name="smime.p7m" # while we need a # Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m" if v == 'application/x-pkcs7-mime; name="smime.p7m"': v = 'application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"' try: msgcopy.replace_header(k, v) except KeyError: msgcopy.add_header(k, v) msgcopy.set_payload(body) # For the final delivery stage, we can just bulk deliver to a party of # one. ;) bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn)
class SmartMessage: """Uproszczony interfejs dla bibliotek Pythona, który potrafi tworzyæ wiadomoœci tekstowe i z za³¹cznikami MIME.""" def __init__(self, fromAddr, toAddrs, subject, body, enc='iso-8859-2'): """Zacznij od za³o¿enia, i¿ bêdzie to prosta wiadomoœæ tekstowa zgodna z RFC 2822 i bez MIME.""" self.msg = Message() self.msg.set_payload(body) self['Subject'] = subject self.setFrom(fromAddr) self.setTo(toAddrs) self.hasAttachments = False self.enc = enc def setFrom(self, fromAddr): "Ustawia adres nadawcy wiadomoœci." if not fromAddr or not type(fromAddr) == type(''): raise Exception, 'Wiadomoœæ musi mieæ jednego i tylko jednego nadawcê.' self['From'] = fromAddr def setTo(self, to): "Ustawia adresy osób, które maj¹ otrzymaæ wiadomoœæ." if not to: raise Exception, 'Wiadomoœæ musi mieæ co najmniej jednego odbiorcê.' self._addresses(to, 'To') #Dodatkowo przechowuj adresy jako listê. Byæ mo¿e #skorzysta z niej kod, który zajmie siê wysy³aniem wiadomoœci. self.to = to def setCc(self, cc): """Ustawia adresy osób, które maj¹ otrzymaæ kopiê wiadomoœc. choæ nie jest ona adresowana do nich w sposób bezpoœredni.""" self._addresses(cc, 'Cc') def addAttachment(self, attachment, filename, mimetype=None): "Do³¹cza do wiadomoœci wskazany plik." #Odgadnij g³ówny i dodatkowy typ MIME na podstawie nazwy pliku. if not mimetype: mimetype = mimetypes.guess_type(filename)[0] if not mimetype: raise Exception, "Nie uda³o siê okreœliæ typu MIME dla", filename if '/' in mimetype: major, minor = mimetype.split('/') else: major = mimetype minor = None #Wiadomoœæ by³a konstruowana z za³o¿eniem, i¿ bêdzie zawieraæ #tylko i wy³¹cznie tekst. Poniewa¿ wiem, ¿e bêdzie zawieraæ #co najmniej jeden za³¹cznik, musimy zmieniæ j¹ na wiadomoœæ #wieloczêœciow¹ i wkleiæ tekst jako pierwsz¹ czêœæ. if not self.hasAttachments: body = self.msg.get_payload() newMsg = MIMEMultipart() newMsg.attach(MIMEText(body, 'plain', self.enc)) #Skopiuj stare nag³ówki do nowego obiektu. for header, value in self.msg.items(): newMsg[header] = value self.msg = newMsg self.hasAttachments = True subMessage = MIMENonMultipart(major, minor, name=filename) subMessage.set_payload(attachment) #Zakoduj teksty jako quoted printable natomiast wszystkie #inne typy jako base64. if major == 'text': encoder = Encoders.encode_quopri else: encoder = Encoders.encode_base64 encoder(subMessage) #Powi¹¿ fragment MIME z g³ówn¹ wiadomoœci¹. self.msg.attach(subMessage) def _addresses(self, addresses, key): """Ustawia zawartoœæ nag³ówka na podstawie listy przekazanych adresów.""" if hasattr(addresses, '__iter__'): addresses = ', '.join(addresses) self[key] = addresses #Kilka metod dodatkowych umo¿liwiaj¹cych traktowanie klasy w podobny #sposób, jak klasy Message lub MultipartMessage, stosuj¹c odpowiedni¹ #delegacjê poleceñ do tych klas. def __getitem__(self, key): "Zwróæ nag³ówek o podanym kluczu." return self.msg[key] def __setitem__(self, key, value): "Ustaw nag³ówek o wskazanej nazwie." self.msg[key] = value def __getattr__(self, key): return getattr(self.msg, key) def __str__(self): "Zwróæ tekstow¹ reprezentacjê wiadomoœci." return self.msg.as_string()
def comment(self, text='', username='', time='', note=None, use_heading=None, REQUEST=None, subject_heading='', message_id=None, in_reply_to=None, exclude_address=None, sendmail=1): """Add a comment to this page. We try to do this efficiently, avoiding re-rendering the full page if possible. The comment will be mailed out to any subscribers. If auto-subscription is in effect, we subscribe the poster to this page. subject_heading is so named to avoid a clash with some existing zope subject attribute. note and use_heading are not used and kept only for backwards compatibility. """ if not self.checkSufficientId(REQUEST): return self.denied( _("Sorry, this wiki doesn't allow anonymous edits. Please configure a username in options first.")) if self.isDavLocked(): return self.davLockDialog() # gather info oldtext = self.read() text = text and self.cleanupText(text) subject_heading = subject_heading and self.cleanupText(subject_heading) if not username: username = self.usernameFrom(REQUEST) if re.match(r'^(?:\d{1,3}\.){3}\d{1,3}$',username): username = '' username = username and self.tounicode(username) firstcomment = self.messageCount()==0 # ensure the page comment and mail-out will have the same # message-id, and the same timestamp if possible (helps threading # & troubleshooting) if time: dtime = DateTime(time) else: dtime = self.ZopeTime() time = dtime.rfc822() if not message_id: message_id = self.messageIdFromTime(dtime) # format this comment as standard rfc2822 m = Message() m.set_charset(self.encoding()) m.set_payload(self.toencoded(text)) m['From'] = self.toencoded(username) m['Date'] = time m['Subject'] = self.toencoded(subject_heading) m['Message-ID'] = message_id if in_reply_to: m['In-Reply-To'] = in_reply_to m.set_unixfrom(self.fromLineFrom(m['From'],m['Date'])[:-1]) t = self.tounicode(str(m)) # discard junk comments if not (m['Subject'] or m.get_payload()): return self.checkForSpam(t) # do it self.saveRevision() # append to the raw source t = '\n\n' + t self.raw += t # and to the _prerendered cache, carefully mimicking a full # prerender. This works with current page types at least. t = self.pageType().preRenderMessage(self,m) if firstcomment: t=self.pageType().discussionSeparator(self) + t t = self.pageType().preRender(self,t) self.setPreRendered(self.preRendered()+t) self.cookDtmlIfNeeded() # extras self.setLastEditor(REQUEST) self.setLastLog(subject_heading) if self.autoSubscriptionEnabled(): self.subscribeThisUser(REQUEST) self.index_object() if REQUEST: REQUEST.cookies['zwiki_username'] = m['From'] # use real from address if sendmail: self.sendMailToSubscribers( m.get_payload(), REQUEST, subject=m['Subject'], message_id=m['Message-ID'], in_reply_to=m['In-Reply-To'], exclude_address=exclude_address) if REQUEST: REQUEST.RESPONSE.redirect(REQUEST['URL1']+'#bottom')