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
Example #2
0
 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')))
Example #3
0
 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_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')
Example #5
0
    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
Example #6
0
    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}
Example #7
0
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
Example #8
0
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