Add example for simple XML based message format. Release_2.1.0-RC0 PYADPT-69
authorHartmut Goebel <h.goebel@crazy-compilers.com>
Mon, 11 May 2020 11:51:20 +0200
changeset 376671d9e268e71
parent 375 29bb9f9c72ca
child 378 d5a785f05b44
Add example for simple XML based message format.
examples/simple_xml_msgformat.py
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/examples/simple_xml_msgformat.py	Mon May 11 11:51:20 2020 +0200
     1.3 @@ -0,0 +1,131 @@
     1.4 +#/!usr/bin/env python3
     1.5 +#
     1.6 +# Copyleft 2020, p≡p Security
     1.7 +# Copyleft 2020, Hartmut Goebel <h.goebel@crazy-compilers.com>
     1.8 +#
     1.9 +# This file is under GNU General Public License 3.0
    1.10 +"""
    1.11 +This examples shows how to transport pEp messages within a
    1.12 +simple XML based message format which is capable for attachments.
    1.13 +
    1.14 +pEp message elements loosely follow the "pEp for XML specification" and
    1.15 +https://pEp.software/ns/pEp-1.0.xsd.
    1.16 +
    1.17 +IMPORTANT: In this example, no error checking is done. Neither is
    1.18 +           the "pEp for XML" specification enforced.
    1.19 +
    1.20 +Structure of the simple XML based message:
    1.21 +<msg>
    1.22 +  <from>from-addr</from>
    1.23 +  <to>to-addr</to>
    1.24 +  <body>text of the messgage (must not be XML)</bod>
    1.25 +  <attachments>
    1.26 +    <attachment>textual data</attachment>
    1.27 +    …
    1.28 +  <attachments>
    1.29 +</msg>
    1.30 +"""
    1.31 +
    1.32 +from lxml import etree
    1.33 +import email.utils
    1.34 +import pEp
    1.35 +
    1.36 +__all__ = ["serialize_pEp_message", "parse_serialized_message"]
    1.37 +
    1.38 +CT2TAG = {
    1.39 +    'application/pgp-keys': 'keydata',
    1.40 +    'application/pEp.sync': 'sync',
    1.41 +    'application/pEp.distribution': 'distribution',
    1.42 +    'application/pgp-signature': 'signature',
    1.43 +}
    1.44 +
    1.45 +TAG2CT = dict((v,k)for (k,v) in CT2TAG.items())
    1.46 +
    1.47 +PEP_NAMESPACE = "https://pEp.software/ns/pEp-1.0.xsd"
    1.48 +PEP_NS = '{%s}' % PEP_NAMESPACE
    1.49 +NSMAP = {'pEp': PEP_NAMESPACE}
    1.50 +
    1.51 +INCOMING = 0
    1.52 +OUTGOING = 1
    1.53 +
    1.54 +UNENCRYPTED = 0
    1.55 +
    1.56 +def serialize_pEp_message(msg):
    1.57 +    root = etree.Element("msg", nsmap=NSMAP)
    1.58 +    etree.SubElement(root, "to").text = str(msg.to[0])
    1.59 +    etree.SubElement(root, "from").text = str(msg.from_)
    1.60 +
    1.61 +    if msg.enc_format == UNENCRYPTED:
    1.62 +        # unencrypted
    1.63 +        etree.SubElement(root, "body").text = msg.longmsg
    1.64 +        attachments = etree.SubElement(root, "attachments")
    1.65 +        # FIXME: Namespace
    1.66 +        attachments = etree.SubElement(attachments,
    1.67 +                                       PEP_NS + "attachments",
    1.68 +                                       version=msg.opt_fields['X-pEp-Version'])
    1.69 +        # TODO: opt_fields, esp. auto-consume
    1.70 +        # TODO: Order pEp attachments by type
    1.71 +        for attach in msg.attachments:
    1.72 +            # no need to base64-encode, attachement are ascii-armoured
    1.73 +            # already
    1.74 +            #attachment = base64_encode(attachment)
    1.75 +            # FIXME: Namespace
    1.76 +            a = etree.SubElement(attachments,
    1.77 +                                 PEP_NS + CT2TAG[attach.mime_type])
    1.78 +            a.text = attach.decode("ascii")
    1.79 +    else: # encrypted
    1.80 +        # forget about longmsg and original body
    1.81 +        # encrypted message is an attachment and there might be
    1.82 +        # further attachments, e.g. new keys
    1.83 +        # build a new message out of these attachments
    1.84 +        etree.SubElement(root, "body") # emptry body
    1.85 +        attachments = etree.SubElement(root, "attachments")
    1.86 +        assert len(msg.attachments) == 2
    1.87 +        # first attachment is "Version: 1"
    1.88 +        attach = msg.attachments[1]
    1.89 +        # no need to base64-encode, attachements are ascii-armoured
    1.90 +        # already
    1.91 +        n = etree.SubElement(root, PEP_NS + "message")
    1.92 +        n.text = attach.decode("ascii")
    1.93 +    return etree.tostring(root)
    1.94 +
    1.95 +
    1.96 +def parse_serialized_message(transport_message):
    1.97 +
    1.98 +    def addr2identity(text):
    1.99 +        name, addr = email.utils.parseaddr(text)
   1.100 +        ident = pEp.Identity(addr, name)
   1.101 +        ident.update()
   1.102 +        return ident
   1.103 +
   1.104 +    # parse the XML text, fetch from and to
   1.105 +    root = etree.fromstring(transport_message)
   1.106 +    from_ = addr2identity(root.xpath("./from/text()")[0])
   1.107 +    msg1 = pEp.Message(INCOMING, from_)
   1.108 +    msg1.to.append(addr2identity(root.xpath("./to/text()")[0]))
   1.109 +    enc_msg = root.find("{%s}message" % PEP_NAMESPACE)
   1.110 +    if enc_msg is not None:
   1.111 +        # this is an encrypted message, ignore all but the encrypted message
   1.112 +        msg1.attachments = [
   1.113 +            # As of Engine r4652 the encrypted message must be the second
   1.114 +            # attachment
   1.115 +            pEp.Blob(b"Version: 1", "application/pgp-encrypted"),
   1.116 +            pEp.Blob(enc_msg.text.encode(), "application/xxpgp-encrypted")]
   1.117 +    else:
   1.118 +        # this is an unencrypted message, might contain pEp attachments
   1.119 +        msg1.longmsg = root.findtext("body")
   1.120 +        pEp_attachments = None
   1.121 +        attachments = root.find("attachments")
   1.122 +        if attachments is not None:
   1.123 +            pEp_attachments = attachments.find("{%s}attachments" % PEP_NAMESPACE)
   1.124 +        if pEp_attachments is not None:
   1.125 +            msg1.opt_fields['X-pEp-Version'] = pEp_attachments.attrib["version"]
   1.126 +            pEp_attachs = []
   1.127 +            for tagname in ("keydata", "signature", "sync", "distribution"):
   1.128 +                for attach in pEp_attachments.iterfind(
   1.129 +                    "{%s}%s" % (PEP_NAMESPACE, tagname)):
   1.130 +                    pEp_attachs.append(
   1.131 +                        pEp.Blob(attach.text.encode(), TAG2CT[tagname]))
   1.132 +            msg1.attachments = pEp_attachs
   1.133 +    msg2, keys, rating, flags = msg1.decrypt()
   1.134 +    return msg2, rating