Example #1
0
 def send_message (self, email, envelope = None, mail_opts = (), rcpt_opts = None) :
     assert isinstance (email, message.Message)
     if envelope is None :
         envelope = email
     to_addrs = envelope ["To"]
     to       = set (t.strip () for t in to_addrs.split (","))
     for k in "cc", "bcc", "dcc" :
         for h in envelope.get_all (k, []) :
             if h :
                 to.update (t.strip () for t in h.split (","))
         if k != "cc" :
             del email [k]
     if "Date" not in email :
         email ["Date"] = formatdate ()
     if "Content-Type" not in email :
         charset = self.charset
         email ["Content-Type"] = \
             """text/plain; charset="%s" """ % (charset, )
     else :
         charset = email.get_charset ()
     if not email.is_multipart () :
         encoders.encode_7or8bit (email)
     for k in "Subject", "To", "From", "CC", "BCC" :
         vs = email.get_all (k)
         if vs :
             del email [k]
             for v in vs :
                 email [k] = self.header (v, charset, header_name = k)
     ### In Python 3, `email.as_string` is useless because it returns a
     ### base64 encoded body if there are any non-ASCII characters
     ### (setting Content-Transfer-Encoding to "8bit" does *not* help)
     self.send \
         ( envelope ["From"], list (to), pyk.email_as_bytes (email)
         , mail_opts, rcpt_opts
         )
Example #2
0
    def _multipart(self, depth, atchs, encts):

        base_msg = MIMEMultipart()

        for atch_id, encode_type in zip(atchs, encts):
            ctypes, encoding = mimetypes.guess_type(
                self.cfg[ATCH][atch_id]['file'])
            if not ctypes or encoding:
                ctypes = 'application/octet-stream'
            maintype, subtype = ctypes.split('/', 1)

            attach = MIMEBase(maintype, subtype)
            attach.set_payload(self.atch_files[atch_id])

            if encode_type == 'base64':
                encoders.encode_base64(attach)
            elif encode_type == 'qp':
                encoders.encode_quopri(attach)
            else:
                encoders.encode_7or8bit(attach)
            attach.add_header('Content-Disposition',
                              'attachment',
                              filename=self.cfg[ATCH][atch_id]['file'])
            base_msg.attach(attach)

        outer = []
        outer.append(base_msg)
        for i in range(depth):
            outer.append(MIMEMultipart())
            outer[-1].attach(outer[-2])
        return outer[-1]
Example #3
0
File: models.py Project: wri/moto
    def render_template(self, render_data):
        template_name = render_data.get("name", "")
        template = self.templates.get(template_name, None)
        if not template:
            raise TemplateDoesNotExist("Invalid Template Name.")

        template_data = render_data.get("data")
        try:
            template_data = json.loads(template_data)
        except ValueError:
            raise InvalidRenderingParameterException(
                "Template rendering data is invalid")

        var, are_variables_present = are_all_variables_present(
            template, template_data)
        if not are_variables_present:
            raise MissingRenderingAttributeException(var)

        subject_part = template["subject_part"]
        text_part = template["text_part"]
        html_part = template["html_part"]

        for key, value in template_data.items():
            subject_part = str.replace(str(subject_part), "{{%s}}" % key,
                                       value)
            text_part = str.replace(str(text_part), "{{%s}}" % key, value)
            html_part = str.replace(str(html_part), "{{%s}}" % key, value)

        email = MIMEMultipart("alternative")

        mime_text = MIMEBase("text", "plain;charset=UTF-8")
        mime_text.set_payload(text_part.encode("utf-8"))
        encode_7or8bit(mime_text)
        email.attach(mime_text)

        mime_html = MIMEBase("text", "html;charset=UTF-8")
        mime_html.set_payload(html_part.encode("utf-8"))
        encode_7or8bit(mime_html)
        email.attach(mime_html)

        now = datetime.datetime.now().isoformat()

        rendered_template = "Date: %s\r\nSubject: %s\r\n%s" % (
            now,
            subject_part,
            email.as_string(),
        )
        return rendered_template
Example #4
0
    def mbox(self):
        from email.mime.nonmultipart import MIMENonMultipart
        from email.encoders import encode_7or8bit

        body = ''
        if self.description:
            body += self.description.content + '\n'
        body += self.content

        mbox = MIMENonMultipart('text', 'plain', charset='utf-8')

        mbox['Subject'] = ": ".join([t.name for t in self.tags] + [self.name.strip().capitalize()])
        mbox['From'] = '%s <%s>' % (self.submitter.name, self.submitter.email)
        mbox['Message-Id'] = self.msgid

        mbox.set_payload(body.encode('utf-8'))
        encode_7or8bit(mbox)

        return mbox.as_string()
Example #5
0
    def mbox(self):
        from email.mime.nonmultipart import MIMENonMultipart
        from email.encoders import encode_7or8bit

        body = ''
        if self.comments[0].msgid == self.msgid:
            body += self.comments[0].content + '\n'
        body += self.content

        mbox = MIMENonMultipart('text', 'plain', charset='utf-8')

        mbox['Subject'] = self.name
        mbox['From'] = '%s <%s>' % (self.submitter.name, self.submitter.email)
        mbox['Message-Id'] = self.msgid

        mbox.set_payload(body.encode('utf-8'))
        encode_7or8bit(mbox)

        return mbox.as_string()
Example #6
0
    def extract_payload(self, mail):
        if mail.body == None: return  # only None, '' is still ok

        ctype, ctype_params = mail.content_encoding['Content-Type']
        cdisp, cdisp_params = mail.content_encoding['Content-Disposition']

        assert ctype, "Extract payload requires that mail.content_encoding have a valid Content-Type."

        if ctype.startswith("text/"):
            self.add_text(mail.body)
        else:
            if cdisp:
                # replicate the content-disposition settings
                self.add_header('Content-Disposition', cdisp, **cdisp_params)

            self.set_payload(mail.body)
            if ctype == "message/rfc822":
                encoders.encode_7or8bit(self)
            else:
                encoders.encode_base64(self)
Example #7
0
 def __init__(self, _text):
     MIMENonMultipart.__init__(self, 'text', 'plain',
                               **{'charset': self.patch_charset})
     self.set_payload(_text.encode(self.patch_charset))
     encode_7or8bit(self)
Example #8
0
def apply_mtom(headers, envelope, params, paramvals):
    '''
    Apply MTOM to a SOAP envelope, separating attachments into a
    MIME multipart message.

    References:
    XOP     http://www.w3.org/TR/xop10/
    MTOM    http://www.w3.org/TR/soap12-mtom/
            http://www.w3.org/Submission/soap11mtom10/

    @param headers   Headers dictionary of the SOAP message that would
                     originally be sent.
    @param envelope  SOAP envelope string that would have originally been sent.
    @param params    params attribute from the Message object used for the SOAP
    @param paramvals values of the params, passed to Message.to_xml
    @return          tuple of length 2 with dictionary of headers and
                     string of body that can be sent with HTTPConnection
    '''

    # grab the XML element of the message in the SOAP body
    soapmsg = StringIO(envelope)
    soaptree = ElementTree.parse(soapmsg)
    soapns = soaptree.getroot().tag.split('}')[0].strip('{')
    soapbody = soaptree.getroot().find("{%s}Body" % soapns)
    message = None
    for child in list(soapbody):
        if child.tag != "%sFault" % (soapns, ):
            message = child
            break

    # Get additional parameters from original Content-Type
    ctarray = []
    for n, v in headers.items():
        if n.lower() == 'content-type':
            ctarray = v.split(';')
            break
    roottype = ctarray[0].strip()
    rootparams = {}
    for ctparam in ctarray[1:]:
        n, v = ctparam.strip().split('=')
        rootparams[n] = v.strip("\"'")

    # Set up initial MIME parts
    mtompkg = MIMEMultipart('related',
        boundary='?//<><>soaplib_MIME_boundary<>')
    rootpkg = None
    try:
        rootpkg = MIMEApplication(envelope, 'xop+xml', encode_7or8bit)
    except NameError:
        rootpkg = MIMENonMultipart("application", "xop+xml")
        rootpkg.set_payload(envelope)
        encode_7or8bit(rootpkg)

    # Set up multipart headers.
    del(mtompkg['mime-version'])
    mtompkg.set_param('start-info', roottype)
    mtompkg.set_param('start', '<soaplibEnvelope>')
    if 'SOAPAction' in headers:
        mtompkg.add_header('SOAPAction', headers.get('SOAPAction'))

    # Set up root SOAP part headers.
    del(rootpkg['mime-version'])
    rootpkg.add_header('Content-ID', '<soaplibEnvelope>')
    for n, v in rootparams.items():
        rootpkg.set_param(n, v)
    rootpkg.set_param('type', roottype)

    mtompkg.attach(rootpkg)

    # Extract attachments from SOAP envelope.
    for i in range(len(params)):
        name, typ = params[i]
        if typ == Attachment:
            id = "soaplibAttachment_%s" % (len(mtompkg.get_payload()), )
            param = message[i]
            param.text = ""
            incl = create_xml_subelement(param,
                "{http://www.w3.org/2004/08/xop/include}Include")
            incl.attrib["href"] = "cid:%s" % id
            if paramvals[i].fileName and not paramvals[i].data:
                paramvals[i].load_from_file()
            data = paramvals[i].data
            attachment = None
            try:
                attachment = MIMEApplication(data, _encoder=encode_7or8bit)
            except NameError:
                attachment = MIMENonMultipart("application", "octet-stream")
                attachment.set_payload(data)
                encode_7or8bit(attachment)
            del(attachment['mime-version'])
            attachment.add_header('Content-ID', '<%s>' % (id, ))
            mtompkg.attach(attachment)

    # Update SOAP envelope.
    soapmsg.close()
    soapmsg = StringIO()
    soaptree.write(soapmsg)
    rootpkg.set_payload(soapmsg.getvalue())
    soapmsg.close()

    # extract body string from MIMEMultipart message
    bound = '--%s' % (mtompkg.get_boundary(), )
    marray = mtompkg.as_string().split(bound)
    mtombody = bound
    mtombody += bound.join(marray[1:])

    # set Content-Length
    mtompkg.add_header("Content-Length", str(len(mtombody)))

    # extract dictionary of headers from MIMEMultipart message
    mtomheaders = {}
    for name, value in mtompkg.items():
        mtomheaders[name] = value

    if len(mtompkg.get_payload()) <= 1:
        return (headers, envelope)

    return (mtomheaders, mtombody)
Example #9
0
 def __init__(self, _text):
     MIMENonMultipart.__init__(self, 'text', 'plain',
                               **{'charset': self.patch_charset})
     self.set_payload(_text.encode(self.patch_charset))
     encode_7or8bit(self)
Example #10
0
        encode_quopri(part)
      else:
        encode_base64(part)
    else:
      part = MIMEBase(main_type, sub_type)
      part.set_payload(data)
      encode_base64(part)
  else:
    part = MIMEBase(main_type, sub_type)
    part.set_payload(data)
    if encoding == "base64":
      encode_base64(part)
    elif encoding == "quoted-printable":
      encode_quopri(part)
    elif encoding == "7or8bit":
      encode_7or8bit(part)
    else:  # elif encoding == "noop":
      encode_noop(part)
  for key, value in attachment.get("replace_header_list", []):
    part.replace_header(key, value)
  for key, value in attachment.get("header_dict", {}).items():  # adds headers, does not replace or set
    part.add_header(key, value)
  for key, value in attachment.get("add_header_list", []):
    part.add_header(key, value)
  if attachment.get("filename", None) is not None:
    part.add_header("Content-Disposition", "attachment", attachment["filename"])
  outer.attach(part)

#return outer.as_string()
return formatMultipartMessageToRFC2822String(outer)
Example #11
0
    def Compile(self):
        """Check message for error and compile it"""
        # self._Message = Message()
        self._Message = MIMEMultipart()
        m = self._Message
        if len(self._Body) == 0:
            raise EmptyBody("Please set Body")
        if len(self._Recipients['To']) + len(self._Recipients['Cc']) + len(self._Recipients['Bcc']) == 0:
            raise EmptyAddr("Recipients address not set")
        # if len(self._AltBody) > 0:
        #     self.ContentType = "multipart/alternative"
        if len(self._MessageDate) != 0:
            m.add_header("Date", self._MessageDate)
        else:
            m.add_header("Date", time.strftime("%a, %d %b %Y %T %z"))
        if len(self._ReturnPath) != 0:
            m.add_header("Return-path", self._ReturnPath)
        elif len(self._Sender) != 0:
            m.add_header("Return-path", self._Sender)
        else:
            m.add_header("Return-path", self._From)
            self._Sender = self._From

        for rcpt in self._Recipients.keys():
            if len(self._Recipients[rcpt]) > 0:
                for each in self._Recipients[rcpt]:
                    if len(each['text']) > 0:
                        m.add_header(rcpt, '"%s" <%s>' % (str(Header(each['text'], 'utf-8')), each['email']))
                    else:
                        m.add_header(rcpt, '<%s>' % (each['email']))
        if m.get("To") in (None, ''):
            m.add_header("To", 'undisclosed-recipients:;')
        m.add_header("From", self._From)

        if self._ReplyTo != '':
            reply_to = self._ReplyTo
            if len(reply_to['text']) > 0:
                m.add_header("Reply-To", '<%s>' (reply_to['email']))
            else:
                m.add_header("Reply-To", '"%s" <%s>' % (str(Header(reply_to['text'], 'utf-8')), reply_to['email']))

        if len(self._MessageID) != 0:
            m.add_header("Message-ID", self._MessageID)
        else:
            m.add_header("Message-ID", '<%s@%s>' % (self.uniqid(), self._Hostname))

        m.add_header('X-Priority', str(self._Priority))
        m.add_header("X-Mailer", self._XMailer)
        m.add_header("Subject", str(Header(self._Subject, 'utf-8')))


        if len(self._AltBody) > 0:
            # alt_body = MIMEText(self._AltBody, )
            # alt_body.set_type(self._AltBodyType)
            alt_body = MIMEBase(self._AltBodyType.split('/')[0], self._AltBodyType.split('/')[1])
            alt_body.set_payload(self._AltBody)
            if self._Encoding == 'base64':
                encoders.encode_base64(alt_body)
            elif self._Encoding == 'quoted':
                encoders.encode_quopri(alt_body)                
            elif self._Encoding in ('8bit', '7bit'):
                encoders.encode_7or8bit(alt_body)
            alt_body.set_charset(self._CharSet)

            # body = MIMEText(self._Body)
            # body.set_type(self._BodyType)
            body = MIMEBase(self._BodyType.split('/')[0], self._BodyType.split('/')[1])
            body.set_payload(self._Body)
            if self._Encoding == 'base64':
                encoders.encode_base64(body)
            elif self._Encoding == 'quoted':
                encoders.encode_quopri(body)
            elif self._Encoding in ('8bit', '7bit'):
                encoders.encode_7or8bit(body)            
            body.set_charset(self._CharSet)

            m.attach(alt_body)
            m.attach(body)
        else:
            # body = MIMEText(self._Body)
            # body.set_type(self._BodyType)
            body = MIMEBase(self._BodyType.split('/')[0], self._BodyType.split('/')[1])
            body.set_payload(self._Body)
            encoders.encode_base64(body)
            body.set_charset(self._CharSet)
            m.attach(body)

        # m.set_charset(self._CharSet)
        m.set_type('multipart/alternative')
        
        self._isCompile = True
Example #12
0
File: soap.py Project: caot/soaplib
def apply_mtom(headers, envelope, params, paramvals):
    '''
    Apply MTOM to a SOAP envelope, separating attachments into a
    MIME multipart message.

    References:
    XOP     http://www.w3.org/TR/xop10/
    MTOM    http://www.w3.org/TR/soap12-mtom/
            http://www.w3.org/Submission/soap11mtom10/

    @param headers   Headers dictionary of the SOAP message that would
                     originally be sent.
    @param envelope  SOAP envelope string that would have originally been sent.
    @param params    params attribute from the Message object used for the SOAP
    @param paramvals values of the params, passed to Message.to_xml
    @return          tuple of length 2 with dictionary of headers and
                     string of body that can be sent with HTTPConnection
    '''

    # grab the XML element of the message in the SOAP body
    soapmsg = StringIO(envelope)
    soaptree = ElementTree.parse(soapmsg)
    soapns = soaptree.getroot().tag.split('}')[0].strip('{')
    soapbody = soaptree.getroot().find("{%s}Body" % soapns)
    message = None
    for child in list(soapbody):
        if child.tag != "%sFault" % (soapns, ):
            message = child
            break

    # Get additional parameters from original Content-Type
    ctarray = []
    for n, v in headers.items():
        if n.lower() == 'content-type':
            ctarray = v.split(';')
            break
    roottype = ctarray[0].strip()
    rootparams = {}
    for ctparam in ctarray[1:]:
        n, v = ctparam.strip().split('=')
        rootparams[n] = v.strip("\"'")

    # Set up initial MIME parts
    mtompkg = MIMEMultipart('related',
                            boundary='?//<><>soaplib_MIME_boundary<>')
    rootpkg = None
    try:
        rootpkg = MIMEApplication(envelope, 'xop+xml', encode_7or8bit)
    except NameError:
        rootpkg = MIMENonMultipart("application", "xop+xml")
        rootpkg.set_payload(envelope)
        encode_7or8bit(rootpkg)

    # Set up multipart headers.
    del (mtompkg['mime-version'])
    mtompkg.set_param('start-info', roottype)
    mtompkg.set_param('start', '<soaplibEnvelope>')
    if 'SOAPAction' in headers:
        mtompkg.add_header('SOAPAction', headers.get('SOAPAction'))

    # Set up root SOAP part headers.
    del (rootpkg['mime-version'])
    rootpkg.add_header('Content-ID', '<soaplibEnvelope>')
    for n, v in rootparams.items():
        rootpkg.set_param(n, v)
    rootpkg.set_param('type', roottype)

    mtompkg.attach(rootpkg)

    # Extract attachments from SOAP envelope.
    for i in range(len(params)):
        name, typ = params[i]
        if typ == Attachment:
            id = "soaplibAttachment_%s" % (len(mtompkg.get_payload()), )
            param = message[i]
            param.text = ""
            incl = create_xml_subelement(
                param, "{http://www.w3.org/2004/08/xop/include}Include")
            incl.attrib["href"] = "cid:%s" % id
            if paramvals[i].fileName and not paramvals[i].data:
                paramvals[i].load_from_file()
            data = paramvals[i].data
            attachment = None
            try:
                attachment = MIMEApplication(data, _encoder=encode_7or8bit)
            except NameError:
                attachment = MIMENonMultipart("application", "octet-stream")
                attachment.set_payload(data)
                encode_7or8bit(attachment)
            del (attachment['mime-version'])
            attachment.add_header('Content-ID', '<%s>' % (id, ))
            mtompkg.attach(attachment)

    # Update SOAP envelope.
    soapmsg.close()
    soapmsg = StringIO()
    soaptree.write(soapmsg)
    rootpkg.set_payload(soapmsg.getvalue())
    soapmsg.close()

    # extract body string from MIMEMultipart message
    bound = '--%s' % (mtompkg.get_boundary(), )
    marray = mtompkg.as_string().split(bound)
    mtombody = bound
    mtombody += bound.join(marray[1:])

    # set Content-Length
    mtompkg.add_header("Content-Length", str(len(mtombody)))

    # extract dictionary of headers from MIMEMultipart message
    mtomheaders = {}
    for name, value in mtompkg.items():
        mtomheaders[name] = value

    if len(mtompkg.get_payload()) <= 1:
        return (headers, envelope)

    return (mtomheaders, mtombody)
Example #13
0
    def build(self, message, status, detailed_status=None):
        """Function builds and signs an AS2 MDN message.

        :param message: The received AS2 message for which this is an MDN.

        :param status: The status of processing of the received AS2 message.

        :param detailed_status:
            The optional detailed status of processing of the received AS2
            message. Used to give additional error info (default "None")

        """

        # Generate message id using UUID 1 as it uses both hostname and time
        self.message_id = email_utils.make_msgid().lstrip('<').rstrip('>')
        self.orig_message_id = message.message_id

        # Set up the message headers
        mdn_headers = {
            'AS2-Version': AS2_VERSION,
            'ediint-features': EDIINT_FEATURES,
            'Message-ID': '<{}>'.format(self.message_id),
            'AS2-From': quote_as2name(message.headers.get('as2-to')),
            'AS2-To': quote_as2name(message.headers.get('as2-from')),
            'Date': email_utils.formatdate(localtime=True),
            'user-agent': 'pyAS2 Open Source AS2 Software'
        }

        # Set the confirmation text message here
        confirmation_text = MDN_CONFIRM_TEXT

        # overwrite with organization specific message
        if message.receiver and message.receiver.mdn_confirm_text:
            confirmation_text = message.receiver.mdn_confirm_text

        # overwrite with partner specific message
        if message.sender and message.sender.mdn_confirm_text:
            confirmation_text = message.sender.mdn_confirm_text

        if status != 'processed':
            confirmation_text = MDN_FAILED_TEXT

        self.payload = MIMEMultipart(
            'report', report_type='disposition-notification')

        # Create and attach the MDN Text Message
        mdn_text = email_message.Message()
        mdn_text.set_payload('%s\n' % confirmation_text)
        mdn_text.set_type('text/plain')
        del mdn_text['MIME-Version']
        encoders.encode_7or8bit(mdn_text)
        self.payload.attach(mdn_text)

        # Create and attache the MDN Report Message
        mdn_base = email_message.Message()
        mdn_base.set_type('message/disposition-notification')
        mdn_report = 'Reporting-UA: pyAS2 Open Source AS2 Software\n'
        mdn_report += 'Original-Recipient: rfc822; {}\n'.format(
            message.headers.get('as2-to'))
        mdn_report += 'Final-Recipient: rfc822; {}\n'.format(
            message.headers.get('as2-to'))
        mdn_report += 'Original-Message-ID: <{}>\n'.format(message.message_id)
        mdn_report += 'Disposition: automatic-action/' \
                      'MDN-sent-automatically; {}'.format(status)
        if detailed_status:
            mdn_report += ': {}'.format(detailed_status)
        mdn_report += '\n'
        if message.mic:
            mdn_report += 'Received-content-MIC: {}, {}\n'.format(
                message.mic.decode(), message.digest_alg)
        mdn_base.set_payload(mdn_report)
        del mdn_base['MIME-Version']
        encoders.encode_7or8bit(mdn_base)
        self.payload.attach(mdn_base)

        # logger.debug('MDN for message %s created:\n%s' % (
        #     message.message_id, mdn_base.as_string()))

        # Sign the MDN if it is requested by the sender
        if message.headers.get('disposition-notification-options') and \
                message.receiver and message.receiver.sign_key:
            self.digest_alg = \
                message.headers['disposition-notification-options'].split(
                    ';')[-1].split(',')[-1].strip().replace('-', '')
            signed_mdn = MIMEMultipart(
                'signed', protocol="application/pkcs7-signature")
            del signed_mdn['MIME-Version']
            signed_mdn.attach(self.payload)

            # Create the signature mime message
            signature = email_message.Message()
            signature.set_type('application/pkcs7-signature')
            signature.set_param('name', 'smime.p7s')
            signature.set_param('smime-type', 'signed-data')
            signature.add_header(
                'Content-Disposition', 'attachment', filename='smime.p7s')
            del signature['MIME-Version']
            signature.set_payload(sign_message(
                canonicalize(self.payload),
                self.digest_alg,
                message.receiver.sign_key
            ))
            encoders.encode_base64(signature)
            # logger.debug(
            #     'Signature for MDN created:\n%s' % signature.as_string())
            signed_mdn.set_param('micalg', self.digest_alg)
            signed_mdn.attach(signature)

            self.payload = signed_mdn

        # Update the headers of the final payload and set message boundary
        for k, v in mdn_headers.items():
            if self.payload.get(k):
                self.payload.replace_header(k, v)
            else:
                self.payload.add_header(k, v)
        if self.payload.is_multipart():
            self.payload.set_boundary(make_mime_boundary())
Example #14
0
    def build(self, data, filename=None, subject='AS2 Message',
              content_type='application/edi-consent', additional_headers=None):

        """Function builds the AS2 message. Compresses, signs and encrypts
        the payload if applicable.

        :param data: A byte string of the data to be transmitted.

        :param filename:
            Optional filename to be included in the Content-disposition header.

        :param subject:
            The subject for the AS2 message, used by some AS2 servers for
            additional routing of messages. (default "AS2 Message")

        :param content_type:
            The content type for the AS2 message, to be used in the MIME
            header. (default "application/edi-consent")

        :param additional_headers:
            Any additional headers to be included as part of the AS2 message.

        """

        # Validations
        assert type(data) is byte_cls, \
            'Parameter data must be of type {}'.format(byte_cls)

        additional_headers = additional_headers if additional_headers else {}
        assert type(additional_headers) is dict

        if self.receiver.sign and not self.sender.sign_key:
            raise ImproperlyConfigured(
                'Signing of messages is enabled but sign key is not set '
                'for the sender.')

        if self.receiver.encrypt and not self.receiver.encrypt_cert:
            raise ImproperlyConfigured(
                'Encryption of messages is enabled but encrypt key is not set '
                'for the receiver.')

        # Generate message id using UUID 1 as it uses both hostname and time
        self.message_id = email_utils.make_msgid().lstrip('<').rstrip('>')

        # Set up the message headers
        as2_headers = {
            'AS2-Version': AS2_VERSION,
            'ediint-features': EDIINT_FEATURES,
            'Message-ID': '<{}>'.format(self.message_id),
            'AS2-From': quote_as2name(self.sender.as2_name),
            'AS2-To': quote_as2name(self.receiver.as2_name),
            'Subject': subject,
            'Date': email_utils.formatdate(localtime=True),
            # 'recipient-address': message.partner.target_url,
        }
        as2_headers.update(additional_headers)

        # Read the input and convert to bytes if value is unicode/str
        # using utf-8 encoding and finally Canonicalize the payload
        self.payload = email_message.Message()
        self.payload.set_payload(data)
        self.payload.set_type(content_type)
        encoders.encode_7or8bit(self.payload)

        if filename:
            self.payload.add_header(
                'Content-Disposition', 'attachment', filename=filename)
        del self.payload['MIME-Version']

        if self.receiver.compress:
            self.compressed = True
            compressed_message = email_message.Message()
            compressed_message.set_type('application/pkcs7-mime')
            compressed_message.set_param('name', 'smime.p7z')
            compressed_message.set_param('smime-type', 'compressed-data')
            compressed_message.add_header(
                'Content-Disposition', 'attachment', filename='smime.p7z')
            compressed_message.set_payload(
                compress_message(canonicalize(self.payload)))
            encoders.encode_base64(compressed_message)
            self.payload = compressed_message
            # logger.debug(b'Compressed message %s payload as:\n%s' % (
            #     self.message_id, self.payload.as_string()))

        if self.receiver.sign:
            self.signed, self.digest_alg = True, self.receiver.digest_alg
            signed_message = MIMEMultipart(
                'signed', protocol="application/pkcs7-signature")
            del signed_message['MIME-Version']
            signed_message.attach(self.payload)

            # Calculate the MIC Hash of the message to be verified
            mic_content = canonicalize(self.payload)
            digest_func = hashlib.new(self.digest_alg)
            digest_func.update(mic_content)
            self.mic = binascii.b2a_base64(digest_func.digest()).strip()

            # Create the signature mime message
            signature = email_message.Message()
            signature.set_type('application/pkcs7-signature')
            signature.set_param('name', 'smime.p7s')
            signature.set_param('smime-type', 'signed-data')
            signature.add_header(
                'Content-Disposition', 'attachment', filename='smime.p7s')
            del signature['MIME-Version']
            signature.set_payload(sign_message(
                mic_content, self.digest_alg, self.sender.sign_key))
            encoders.encode_base64(signature)
            signed_message.set_param('micalg', self.digest_alg)
            signed_message.attach(signature)
            self.payload = signed_message

            # logger.debug(b'Signed message %s payload as:\n%s' % (
            #    self.message_id, self.payload.as_string()))

        if self.receiver.encrypt:
            self.encrypted, self.enc_alg = True, self.receiver.enc_alg
            encrypted_message = email_message.Message()
            encrypted_message.set_type('application/pkcs7-mime')
            encrypted_message.set_param('name', 'smime.p7m')
            encrypted_message.set_param('smime-type', 'enveloped-data')
            encrypted_message.add_header(
                'Content-Disposition', 'attachment', filename='smime.p7m')
            encrypt_cert = self.receiver.load_encrypt_cert()
            encrypted_message.set_payload(encrypt_message(
                canonicalize(self.payload),
                self.enc_alg,
                encrypt_cert
            ))
            encoders.encode_base64(encrypted_message)
            self.payload = encrypted_message
            # logger.debug(b'Encrypted message %s payload as:\n%s' % (
            #     self.message_id, self.payload.as_string()))

        if self.receiver.mdn_mode:
            as2_headers['disposition-notification-to'] = '*****@*****.**'
            if self.receiver.mdn_digest_alg:
                as2_headers['disposition-notification-options'] = \
                    'signed-receipt-protocol=required, pkcs7-signature; ' \
                    'signed-receipt-micalg=optional, {}'.format(
                        self.receiver.mdn_digest_alg)
            if self.receiver.mdn_mode == 'ASYNC':
                if not self.sender.mdn_url:
                    raise ImproperlyConfigured(
                        'MDN URL must be set in the organization when MDN mode '
                        'is set to ASYNC')
                as2_headers['receipt-delivery-option'] = self.sender.mdn_url

        # Update the headers of the final payload and set its boundary
        for k, v in as2_headers.items():
            if self.payload.get(k):
                self.payload.replace_header(k, v)
            else:
                self.payload.add_header(k, v)
        if self.payload.is_multipart():
            self.payload.set_boundary(make_mime_boundary())
Example #15
0
 def __init__(self, _text):
     MIMENonMultipart.__init__(self, "text", "plain", **{"charset": self.patch_charset})
     self.set_payload(_text.encode(self.patch_charset))
     encode_7or8bit(self)
Example #16
0
    def replace(self, find, replace, trash_folder, callback=None):
        """Performs a body-wide string search and replace

        Note that this search-and-replace is pretty dumb, and will fail
        in, for example, HTML messages where HTML tags would alter the search
        string.

        Args:
            find         -- the search term to look for as a string, or a tuple
                            of items to replace with corresponding items in the
                            replace tuple
            replace      -- the string to replace instances of the "find" term
                            with, or a tuple of terms to replace the
                            corresponding strings in the find tuple
            trash_folder -- the name of the folder / label that is, in the
                            current account, the trash container

        Returns:
            True on success, and in all other instances an error object
        """
        def _set_content_transfer_encoding(part, encoding):
            try:
                del part['Content-Transfer-Encoding']
            except:
                ""
            part.add_header('Content-Transfer-Encoding', encoding)

        valid_content_types = ('plain', 'html')

        for valid_type in valid_content_types:

            for part in typed_subpart_iterator(self.raw, 'text', valid_type):

                section_encoding = part['Content-Transfer-Encoding']

                # If the message section doesn't advertise an encoding,
                # then default to quoted printable. Otherwise the module
                # will default to base64, which can cause problems
                if not section_encoding:
                    section_encoding = "quoted-printable"
                else:
                    section_encoding = section_encoding.lower()

                section_charset = message_part_charset(part, self.raw)
                new_payload_section = utf8_encode_message_part(
                    part, self.raw, section_charset)

                if is_encoding_error(new_payload_section):
                    self.encoding_error = new_payload_section
                    return _cmd(callback, self.encoding_error)

                if isinstance(find, tuple) or isinstance(find, list):
                    for i in range(0, len(find)):
                        new_payload_section = new_payload_section.replace(
                            find[i], replace[i])
                else:
                    new_payload_section = new_payload_section.replace(
                        find, replace)

                new_payload_section = new_payload_section.encode(
                    part._orig_charset, errors="replace")

                if section_encoding == "quoted-printable":
                    new_payload_section = encodestring(new_payload_section,
                                                       quotetabs=0)
                    part.set_payload(new_payload_section, part._orig_charset)
                    _set_content_transfer_encoding(part, "quoted-printable")
                elif section_encoding == "base64":
                    part.set_payload(new_payload_section, part._orig_charset)
                    ENC.encode_base64(part)
                    _set_content_transfer_encoding(part, "base64")
                elif section_encoding in ('7bit', '8bit'):
                    part.set_payload(new_payload_section, part._orig_charset)
                    ENC.encode_7or8bit(part)
                    _set_content_transfer_encoding(part, section_encoding)
                elif section_encoding == "binary":
                    part.set_payload(new_payload_section, part._orig_charset)
                    part['Content-Transfer-Encoding'] = 'binary'
                    _set_content_transfer_encoding(part, 'binary')

                del part._normalized
                del part._orig_charset

        def _on_save(was_success):
            return _cmd(callback, was_success)

        return _cmd_cb(self.save, _on_save, bool(callback), trash_folder)
Example #17
0
def apply_mtom(headers, envelope, params, paramvals):
    """
    Apply MTOM to a SOAP envelope, separating attachments into a
    MIME multipart message.

    References:
    XOP     http://www.w3.org/TR/xop10/
    MTOM    http://www.w3.org/TR/soap12-mtom/
            http://www.w3.org/Submission/soap11mtom10/

    @param headers   Headers dictionary of the SOAP message that would
                     originally be sent.
    @param envelope  SOAP envelope string that would have originally been sent.
    @param params    params attribute from the Message object used for the SOAP
    @param paramvals values of the params, passed to Message.to_xml
    @return          tuple of length 2 with dictionary of headers and
                     string of body that can be sent with HTTPConnection
    """

    # grab the XML element of the message in the SOAP body
    soaptree = etree.fromstring(envelope)
    soapbody = soaptree.find("{%s}Body" % soaplib.ns_soap_env)

    message = None
    for child in list(soapbody):
        if child.tag != "%sFault" % (soaplib.ns_soap_env,):
            message = child
            break

    # Get additional parameters from original Content-Type
    ctarray = []
    for n, v in headers.items():
        if n.lower() == "content-type":
            ctarray = v.split(";")
            break
    roottype = ctarray[0].strip()
    rootparams = {}
    for ctparam in ctarray[1:]:
        n, v = ctparam.strip().split("=")
        rootparams[n] = v.strip("\"'")

    # Set up initial MIME parts.
    mtompkg = MIMEMultipart("related", boundary="?//<><>soaplib_MIME_boundary<>")
    rootpkg = None
    try:
        rootpkg = MIMEApplication(envelope, "xop+xml", encode_7or8bit)
    except NameError:
        rootpkg = MIMENonMultipart("application", "xop+xml")
        rootpkg.set_payload(envelope)
        encode_7or8bit(rootpkg)

    # Set up multipart headers.
    del (mtompkg["mime-version"])
    mtompkg.set_param("start-info", roottype)
    mtompkg.set_param("start", "<soaplibEnvelope>")
    if "SOAPAction" in headers:
        mtompkg.add_header("SOAPAction", headers.get("SOAPAction"))

    # Set up root SOAP part headers.
    del (rootpkg["mime-version"])

    rootpkg.add_header("Content-ID", "<soaplibEnvelope>")

    for n, v in rootparams.items():
        rootpkg.set_param(n, v)

    rootpkg.set_param("type", roottype)

    mtompkg.attach(rootpkg)

    # Extract attachments from SOAP envelope.
    for i in range(len(params)):
        name, typ = params[i]

        if typ == Attachment:
            id = "soaplibAttachment_%s" % (len(mtompkg.get_payload()),)

            param = message[i]
            param.text = ""

            incl = etree.SubElement(param, "{%s}Include" % soaplib.ns_xop)
            incl.attrib["href"] = "cid:%s" % id

            if paramvals[i].fileName and not paramvals[i].data:
                paramvals[i].load_from_file()

            data = paramvals[i].data
            attachment = None

            try:
                attachment = MIMEApplication(data, _encoder=encode_7or8bit)

            except NameError:
                attachment = MIMENonMultipart("application", "octet-stream")
                attachment.set_payload(data)
                encode_7or8bit(attachment)

            del (attachment["mime-version"])

            attachment.add_header("Content-ID", "<%s>" % (id,))
            mtompkg.attach(attachment)

    # Update SOAP envelope.
    rootpkg.set_payload(etree.tostring(soaptree))

    # extract body string from MIMEMultipart message
    bound = "--%s" % (mtompkg.get_boundary(),)
    marray = mtompkg.as_string().split(bound)
    mtombody = bound
    mtombody += bound.join(marray[1:])

    # set Content-Length
    mtompkg.add_header("Content-Length", str(len(mtombody)))

    # extract dictionary of headers from MIMEMultipart message
    mtomheaders = {}
    for name, value in mtompkg.items():
        mtomheaders[name] = value

    if len(mtompkg.get_payload()) <= 1:
        return (headers, envelope)

    return (mtomheaders, mtombody)
Example #18
0
    def build(
        self,
        message,
        status,
        detailed_status=None,
        confirmation_text=MDN_CONFIRM_TEXT,
        failed_text=MDN_FAILED_TEXT,
    ):
        """Function builds and signs an AS2 MDN message.

        :param message: The received AS2 message for which this is an MDN.

        :param status: The status of processing of the received AS2 message.

        :param detailed_status: The optional detailed status of processing of the received AS2
        message. Used to give additional error info (default "None")

        :param confirmation_text: The confirmation message sent in the first part of the MDN.

        :param failed_text: The failure message sent in the first part of the failed MDN.
        """

        # Generate message id using UUID 1 as it uses both hostname and time
        self.message_id = email_utils.make_msgid().lstrip("<").rstrip(">")
        self.orig_message_id = message.message_id

        # Set up the message headers
        mdn_headers = {
            "AS2-Version": AS2_VERSION,
            "ediint-features": EDIINT_FEATURES,
            "Message-ID": f"<{self.message_id}>",
            "AS2-From": quote_as2name(message.headers.get("as2-to")),
            "AS2-To": quote_as2name(message.headers.get("as2-from")),
            "Date": email_utils.formatdate(localtime=True),
            "user-agent": "pyAS2 Open Source AS2 Software",
        }

        # Set the confirmation text message here
        # overwrite with organization specific message
        if message.receiver and message.receiver.mdn_confirm_text:
            confirmation_text = message.receiver.mdn_confirm_text

        # overwrite with partner specific message
        if message.sender and message.sender.mdn_confirm_text:
            confirmation_text = message.sender.mdn_confirm_text

        if status != "processed":
            confirmation_text = failed_text

        self.payload = MIMEMultipart("report",
                                     report_type="disposition-notification")

        # Create and attach the MDN Text Message
        mdn_text = email_message.Message()
        mdn_text.set_payload(f"{confirmation_text}\r\n")
        mdn_text.set_type("text/plain")
        del mdn_text["MIME-Version"]
        encoders.encode_7or8bit(mdn_text)
        self.payload.attach(mdn_text)

        # Create and attache the MDN Report Message
        mdn_base = email_message.Message()
        mdn_base.set_type("message/disposition-notification")
        mdn_report = "Reporting-UA: pyAS2 Open Source AS2 Software\r\n"
        mdn_report += f'Original-Recipient: rfc822; {message.headers.get("as2-to")}\r\n'
        mdn_report += f'Final-Recipient: rfc822; {message.headers.get("as2-to")}\r\n'
        mdn_report += f"Original-Message-ID: <{message.message_id}>\r\n"
        mdn_report += f"Disposition: automatic-action/MDN-sent-automatically; {status}"
        if detailed_status:
            mdn_report += f": {detailed_status}"
        mdn_report += "\r\n"
        if message.mic:
            mdn_report += f"Received-content-MIC: {message.mic.decode()}, {message.digest_alg}\r\n"
        mdn_base.set_payload(mdn_report)
        del mdn_base["MIME-Version"]
        encoders.encode_7or8bit(mdn_base)
        self.payload.attach(mdn_base)

        logger.debug(
            f"MDN report for message {message.message_id} created:\n{mime_to_bytes(mdn_base)}"
        )

        # Sign the MDN if it is requested by the sender
        if (message.headers.get("disposition-notification-options")
                and message.receiver and message.receiver.sign_key):
            self.digest_alg = (
                message.headers["disposition-notification-options"].split(
                    ";")[-1].split(",")[-1].strip().replace("-", ""))
            signed_mdn = MIMEMultipart("signed",
                                       protocol="application/pkcs7-signature")
            del signed_mdn["MIME-Version"]
            signed_mdn.attach(self.payload)

            # Create the signature mime message
            signature = email_message.Message()
            signature.set_type("application/pkcs7-signature")
            signature.set_param("name", "smime.p7s")
            signature.set_param("smime-type", "signed-data")
            signature.add_header("Content-Disposition",
                                 "attachment",
                                 filename="smime.p7s")
            del signature["MIME-Version"]

            signed_data = sign_message(canonicalize(self.payload),
                                       self.digest_alg,
                                       message.receiver.sign_key)
            signature.set_payload(signed_data)
            encoders.encode_base64(signature)

            signed_mdn.set_param("micalg", self.digest_alg)
            signed_mdn.attach(signature)

            self.payload = signed_mdn
            logger.debug(f"Signing the MDN for message {message.message_id}")

        # Update the headers of the final payload and set message boundary
        for k, v in mdn_headers.items():
            if self.payload.get(k):
                self.payload.replace_header(k, v)
            else:
                self.payload.add_header(k, v)
        self.payload.set_boundary(make_mime_boundary())
        logger.debug(f"MDN generated for message {message.message_id} with "
                     f"content:\n {mime_to_bytes(self.payload)}")
Example #19
0
    def build(
        self,
        data,
        filename=None,
        subject="AS2 Message",
        content_type="application/edi-consent",
        additional_headers=None,
        disposition_notification_to="*****@*****.**",
    ):
        """Function builds the AS2 message. Compresses, signs and encrypts
        the payload if applicable.

        :param data: A byte string of the data to be transmitted.

        :param filename:
            Optional filename to be included in the Content-disposition header.

        :param subject:
            The subject for the AS2 message, used by some AS2 servers for
            additional routing of messages. (default "AS2 Message")

        :param content_type:
            The content type for the AS2 message, to be used in the MIME
            header. (default "application/edi-consent")

        :param additional_headers:
            Any additional headers to be included as part of the AS2 message.

        :param disposition_notification_to:
            Email address for disposition-notification-to header entry.
            (default "*****@*****.**")

        """

        # Validations
        assert isinstance(data, bytes), "Parameter data must be of bytes type."

        additional_headers = additional_headers if additional_headers else {}
        assert isinstance(additional_headers, dict)

        if self.receiver.sign and not self.sender.sign_key:
            raise ImproperlyConfigured(
                "Signing of messages is enabled but sign key is not set for the sender."
            )

        if self.receiver.encrypt and not self.receiver.encrypt_cert:
            raise ImproperlyConfigured(
                "Encryption of messages is enabled but encrypt key is not set for the receiver."
            )

        # Generate message id using UUID 1 as it uses both hostname and time
        self.message_id = email_utils.make_msgid().lstrip("<").rstrip(">")

        # Set up the message headers
        as2_headers = {
            "AS2-Version": AS2_VERSION,
            "ediint-features": EDIINT_FEATURES,
            "Message-ID": f"<{self.message_id}>",
            "AS2-From": quote_as2name(self.sender.as2_name),
            "AS2-To": quote_as2name(self.receiver.as2_name),
            "Subject": subject,
            "Date": email_utils.formatdate(localtime=True),
        }
        as2_headers.update(additional_headers)

        # Read the input and convert to bytes if value is unicode/str
        # using utf-8 encoding and finally Canonicalize the payload
        self.payload = email_message.Message()
        self.payload.set_payload(data)
        self.payload.set_type(content_type)

        if content_type.lower().startswith("application/octet-stream"):
            self.payload["Content-Transfer-Encoding"] = "binary"
        else:
            encoders.encode_7or8bit(self.payload)

        if filename:
            self.payload.add_header("Content-Disposition",
                                    "attachment",
                                    filename=filename)
        del self.payload["MIME-Version"]

        if self.receiver.compress:
            self.compressed = True
            compressed_message = email_message.Message()
            compressed_message.set_type("application/pkcs7-mime")
            compressed_message.set_param("name", "smime.p7z")
            compressed_message.set_param("smime-type", "compressed-data")
            compressed_message.add_header("Content-Disposition",
                                          "attachment",
                                          filename="smime.p7z")
            compressed_message.add_header("Content-Transfer-Encoding",
                                          "binary")
            compressed_message.set_payload(
                compress_message(mime_to_bytes(self.payload)))
            self.payload = compressed_message

            logger.debug(
                f"Compressed message {self.message_id} payload as:\n{mime_to_bytes(self.payload)}"
            )

        if self.receiver.sign:
            self.signed, self.digest_alg = True, self.receiver.digest_alg
            signed_message = MIMEMultipart(
                "signed", protocol="application/pkcs7-signature")
            del signed_message["MIME-Version"]
            signed_message.attach(self.payload)

            # Calculate the MIC Hash of the message to be verified
            mic_content = canonicalize(self.payload)
            digest_func = hashlib.new(self.digest_alg)
            digest_func.update(mic_content)
            self.mic = binascii.b2a_base64(digest_func.digest()).strip()

            # Create the signature mime message
            signature = email_message.Message()
            signature.set_type("application/pkcs7-signature")
            signature.set_param("name", "smime.p7s")
            signature.set_param("smime-type", "signed-data")
            signature.add_header("Content-Disposition",
                                 "attachment",
                                 filename="smime.p7s")
            del signature["MIME-Version"]
            signature_data = sign_message(mic_content, self.digest_alg,
                                          self.sender.sign_key)
            signature.set_payload(signature_data)
            encoders.encode_base64(signature)

            signed_message.set_param("micalg", self.digest_alg)
            signed_message.attach(signature)
            self.payload = signed_message

            logger.debug(
                f"Signed message {self.message_id} payload as:\n{mime_to_bytes(self.payload)}"
            )

        if self.receiver.encrypt:
            self.encrypted, self.enc_alg = True, self.receiver.enc_alg
            encrypted_message = email_message.Message()
            encrypted_message.set_type("application/pkcs7-mime")
            encrypted_message.set_param("name", "smime.p7m")
            encrypted_message.set_param("smime-type", "enveloped-data")
            encrypted_message.add_header("Content-Disposition",
                                         "attachment",
                                         filename="smime.p7m")
            encrypted_message.add_header("Content-Transfer-Encoding", "binary")
            encrypt_cert = self.receiver.load_encrypt_cert()
            encrypted_data = encrypt_message(mime_to_bytes(self.payload),
                                             self.enc_alg, encrypt_cert)
            encrypted_message.set_payload(encrypted_data)

            self.payload = encrypted_message
            logger.debug(
                f"Encrypted message {self.message_id} payload as:\n{mime_to_bytes(self.payload)}"
            )

        if self.receiver.mdn_mode:
            as2_headers[
                "disposition-notification-to"] = disposition_notification_to
            if self.receiver.mdn_digest_alg:
                as2_headers["disposition-notification-options"] = (
                    f"signed-receipt-protocol=required, pkcs7-signature; "
                    f"signed-receipt-micalg=optional, {self.receiver.mdn_digest_alg}"
                )
            if self.receiver.mdn_mode == "ASYNC":
                if not self.sender.mdn_url:
                    raise ImproperlyConfigured(
                        "MDN URL must be set in the organization when MDN mode is set to ASYNC"
                    )
                as2_headers["receipt-delivery-option"] = self.sender.mdn_url

        # Update the headers of the final payload and set its boundary
        for k, v in as2_headers.items():
            if self.payload.get(k):
                self.payload.replace_header(k, v)
            else:
                self.payload.add_header(k, v)

        if self.payload.is_multipart():
            self.payload.set_boundary(make_mime_boundary())