def test_invoke(self): """ L{ComposeFragment.invoke} accepts a browser-generated structure representing the values in the compose form, coerces it according to the L{Parameters} it defines, and passes the result to the LiveForm callable. """ # The from addresses are web ids for FromAddress items. fromAddr = IWebTranslator(self.userStore).toWebID(self.defaultFromAddr) # Override the callable to see what happens. sent = [] expectedResult = object() def fakeSend(fromAddress, toAddresses, subject, messageBody, cc, bcc, files, draft): sent.append((fromAddress, toAddresses, subject, messageBody, cc, bcc, files, draft)) return expectedResult self.cf.callable = fakeSend toAddresses = [ mimeutil.EmailAddress(u'*****@*****.**', False), mimeutil.EmailAddress(u'*****@*****.**', False) ] subject = u'Hello World' body = u'How are you' cc = [mimeutil.EmailAddress(u'*****@*****.**', False)] bcc = [mimeutil.EmailAddress(u'*****@*****.**', False)] draft = True invokeDeferred = self.cf.invoke({ u'fromAddress': [fromAddr], u'toAddresses': [mimeutil.flattenEmailAddresses(toAddresses)], u'subject': [subject], u'messageBody': [body], u'cc': [mimeutil.flattenEmailAddresses(cc)], u'bcc': [mimeutil.flattenEmailAddresses(bcc)], u'draft': [draft] }) def cbInvoked(invokeResult): self.assertEquals( sent, [(self.defaultFromAddr, toAddresses, subject, body, cc, bcc, (), draft)]) self.assertIdentical(invokeResult, expectedResult) invokeDeferred.addCallback(cbInvoked) return invokeDeferred
def insertResentHeaders(i): m._headers.insert(i, ('resent-from', MH.Header( fromAddress.address).encode())) m._headers.insert(i, ('resent-to', MH.Header( mimeutil.flattenEmailAddresses(toAddresses)).encode())) m._headers.insert(i, ('resent-date', EU.formatdate())) m._headers.insert(i, ('resent-message-id', smtp.messageid('divmod.xquotient')))
def test_flattenEmailAddresses(self): """ Test that L{xquotient.mimeutil.flattenEmailAddresses} works as expected """ self.assertEquals( mimeutil.flattenEmailAddresses( (mimeutil.EmailAddress('One <one@two>'), mimeutil.EmailAddress('two@three'))), 'One <one@two>, two@three')
def test_invoke(self): """ L{ComposeFragment.invoke} accepts a browser-generated structure representing the values in the compose form, coerces it according to the L{Parameters} it defines, and passes the result to the LiveForm callable. """ # The from addresses are web ids for FromAddress items. fromAddr = IWebTranslator(self.userStore).toWebID(self.defaultFromAddr) # Override the callable to see what happens. sent = [] expectedResult = object() def fakeSend(fromAddress, toAddresses, subject, messageBody, cc, bcc, files, draft): sent.append((fromAddress, toAddresses, subject, messageBody, cc, bcc, files, draft)) return expectedResult self.cf.callable = fakeSend toAddresses = [mimeutil.EmailAddress(u'*****@*****.**', False), mimeutil.EmailAddress(u'*****@*****.**', False)] subject = u'Hello World' body = u'How are you' cc = [mimeutil.EmailAddress(u'*****@*****.**', False)] bcc = [mimeutil.EmailAddress(u'*****@*****.**', False)] draft = True invokeDeferred = self.cf.invoke({ u'fromAddress': [fromAddr], u'toAddresses': [mimeutil.flattenEmailAddresses(toAddresses)], u'subject': [subject], u'messageBody': [body], u'cc': [mimeutil.flattenEmailAddresses(cc)], u'bcc': [mimeutil.flattenEmailAddresses(bcc)], u'draft': [draft]}) def cbInvoked(invokeResult): self.assertEquals( sent, [(self.defaultFromAddr, toAddresses, subject, body, cc, bcc, (), draft)]) self.assertIdentical(invokeResult, expectedResult) invokeDeferred.addCallback(cbInvoked) return invokeDeferred
def slotData(self): """ @return: a C{dict} of data to be used to fill slots during rendering. The keys in this dictionary will be:: C{'to'}: a C{unicode} string giving a comma-delimited list of addresses to which this message is directly addressed. C{'from'}: a C{list} of C{FromAddress} instances giving the possible values for the from address for this message. C{'subject'}: the subject of this message. C{'message-body'}: a C{unicode} string giving the body of this message. C{'cc'}: a C{unicode} string giving a comma-delimited list of address to which this message is indirectly addressed. C{'bcc'}: a C{unicode} string giving a comma-delimited list of addresses to which this message is secretly addressed. C{'attachments'}: a C{list} of C{dict} which the keys C{'id'} and C{'name'} giving storeIDs and filenames for the attachments of this message. """ attachments = [] for a in self.attachments: attachments.append(dict(id=a.part.storeID, name=a.filename)) addrs = {} for k in ('to', 'cc', 'bcc'): if k in self.recipients: # XXX This is mis-factored. The calling code should # be flattening this, if it wants it flattened. addrs[k] = mimeutil.flattenEmailAddresses( self.recipients[k]) else: addrs[k] = '' return {'to': addrs['to'], 'from': self._getFromAddresses(), 'subject': self.subject, 'message-body': self.messageBody, 'cc': addrs['cc'], 'bcc': addrs['bcc'], 'attachments': attachments}
def createMessage(composer, cabinet, msgRepliedTo, fromAddress, toAddresses, subject, messageBody, cc, bcc, files, createMessageObject=None): """ Create an outgoing message, format the body into MIME parts, and populate its headers. @param createMessageObject: A one-argument callable which will be invoked with a file-like object containing MIME text and which should return a Message instance associated with objects representing that MIME data. """ MC.add_charset('utf-8', None, MC.QP, 'utf-8') encode = lambda s: MH.Header(s).encode() s = S.StringIO() wrappedMsgBody = FlowedParagraph.fromRFC2646(messageBody).asRFC2646() m = MT.MIMEText(wrappedMsgBody, 'plain', 'utf-8') m.set_param("format", "flowed") fileItems = [] if files: attachmentParts = [] for storeID in files: a = composer.store.getItemByID(long(storeID)) if isinstance(a, Part): a = cabinet.createFileItem( a.getParam('filename', default=u'', header=u'content-disposition'), unicode(a.getContentType()), a.getBody(decode=True)) fileItems.append(a) attachmentParts.append(_fileItemToEmailPart(a)) m = MMP.MIMEMultipart('mixed', None, [m] + attachmentParts) m['From'] = encode(fromAddress.address) m['To'] = encode(mimeutil.flattenEmailAddresses(toAddresses)) m['Subject'] = encode(subject) m['Date'] = EU.formatdate() m['Message-ID'] = smtp.messageid('divmod.xquotient') if cc: m['Cc'] = encode(mimeutil.flattenEmailAddresses(cc)) if msgRepliedTo is not None: #our parser does not remove continuation whitespace, so to #avoid duplicating it -- refs = [ hdr.value for hdr in msgRepliedTo.impl.getHeaders("References") ] if len(refs) == 0: irt = [ hdr.value for hdr in msgRepliedTo.impl.getHeaders("In-Reply-To") ] if len(irt) == 1: refs = irt else: refs = [] msgids = msgRepliedTo.impl.getHeaders("Message-ID") for hdr in msgids: msgid = hdr.value refs.append(msgid) #As far as I can tell, the email package doesn't handle #multiple values for headers automatically, so here's some #continuation whitespace. m['References'] = u'\n\t'.join(refs) m['In-Reply-To'] = msgid break G.Generator(s).flatten(m) s.seek(0) if createMessageObject is None: def createMessageObject(messageFile): return composer.createMessageAndQueueIt(fromAddress.address, messageFile, True) msg = createMessageObject(s) # there is probably a better way than this, but there # isn't a way to associate the same file item with multiple # messages anyway, so there isn't a need to reflect that here for fileItem in fileItems: fileItem.message = msg return msg
def createMessage( composer, cabinet, msgRepliedTo, fromAddress, toAddresses, subject, messageBody, cc, bcc, files, createMessageObject=None, ): """ Create an outgoing message, format the body into MIME parts, and populate its headers. @param createMessageObject: A one-argument callable which will be invoked with a file-like object containing MIME text and which should return a Message instance associated with objects representing that MIME data. """ MC.add_charset("utf-8", None, MC.QP, "utf-8") encode = lambda s: MH.Header(s).encode() s = S.StringIO() wrappedMsgBody = FlowedParagraph.fromRFC2646(messageBody).asRFC2646() m = MT.MIMEText(wrappedMsgBody, "plain", "utf-8") m.set_param("format", "flowed") fileItems = [] if files: attachmentParts = [] for storeID in files: a = composer.store.getItemByID(long(storeID)) if isinstance(a, Part): a = cabinet.createFileItem( a.getParam("filename", default=u"", header=u"content-disposition"), unicode(a.getContentType()), a.getBody(decode=True), ) fileItems.append(a) attachmentParts.append(_fileItemToEmailPart(a)) m = MMP.MIMEMultipart("mixed", None, [m] + attachmentParts) m["From"] = encode(fromAddress.address) m["To"] = encode(mimeutil.flattenEmailAddresses(toAddresses)) m["Subject"] = encode(subject) m["Date"] = EU.formatdate() m["Message-ID"] = smtp.messageid("divmod.xquotient") if cc: m["Cc"] = encode(mimeutil.flattenEmailAddresses(cc)) if msgRepliedTo is not None: # our parser does not remove continuation whitespace, so to # avoid duplicating it -- refs = [hdr.value for hdr in msgRepliedTo.impl.getHeaders("References")] if len(refs) == 0: irt = [hdr.value for hdr in msgRepliedTo.impl.getHeaders("In-Reply-To")] if len(irt) == 1: refs = irt else: refs = [] msgids = msgRepliedTo.impl.getHeaders("Message-ID") for hdr in msgids: msgid = hdr.value refs.append(msgid) # As far as I can tell, the email package doesn't handle # multiple values for headers automatically, so here's some # continuation whitespace. m["References"] = u"\n\t".join(refs) m["In-Reply-To"] = msgid break G.Generator(s).flatten(m) s.seek(0) if createMessageObject is None: def createMessageObject(messageFile): return composer.createMessageAndQueueIt(fromAddress.address, messageFile, True) msg = createMessageObject(s) # there is probably a better way than this, but there # isn't a way to associate the same file item with multiple # messages anyway, so there isn't a need to reflect that here for fileItem in fileItems: fileItem.message = msg return msg