commit 9693b803ac4c9d01aeccab1f0b23e19f2b461aa3
parent 30a4a685c1d034cca0c905c174c5d6f7838dd842
Author: lash <dev@holbrook.no>
Date:   Fri,  2 Dec 2022 11:24:15 +0000
Retrieve correct signing material for verify
Diffstat:
7 files changed, 126 insertions(+), 102 deletions(-)
diff --git a/piknik/crypto.py b/piknik/crypto.py
@@ -9,7 +9,6 @@ import gnupg
 
 # local imports
 from piknik.error import VerifyError
-from piknik.msg import MessageEnvelope
 from piknik.wrap import Wrapper
 
 logg = logging.getLogger(__name__)
@@ -24,8 +23,7 @@ class PGPSigner(Wrapper):
         self.default_key = default_key
         self.passphrase = passphrase
         self.use_agent = use_agent
-        self.__envelope_state = -1 # -1 not in envelope, 0 in outer envelope, 1 inner envelope, not (yet) valid, 2 envelope valid (with signature)
-        self.__envelope = None
+        self.sign_material = None
         self.__skip_verify = skip_verify
 
 
@@ -42,7 +40,6 @@ class PGPSigner(Wrapper):
 
     def sign(self, msg, passphrase=None): # msg = IssueMessage object
         m = Message()
-        v = msg.as_string()
         m.set_type('multipart/relative')
         m.add_header('X-Piknik-Envelope', 'pgp')
         ms = Message()
@@ -51,6 +48,7 @@ class PGPSigner(Wrapper):
         ms.add_header('Content-Disposition', 'attachment', filename=fn)
 
         self.set_from(msg, passphrase=passphrase)
+        v = msg.as_string()
         sig = self.gpg.sign(v, keyid=self.default_key, detach=True, passphrase=self.passphrase)
         ms.set_payload(str(sig))
     
@@ -60,32 +58,32 @@ class PGPSigner(Wrapper):
         return m
 
     
-    def envelope_callback(self, msg, env_header):
-        self.__envelope = None
+    def process_envelope(self, msg, env_header):
+        self.envelope = None
         if env_header != 'pgp':
             raise VerifyError('expected envelope type "pgp", but got {}'.format(env_header))
-        if self.__envelope_state > -1 and self.__envelope_state < 2:
-            raise VerifyError('new envelope before previous was verified ({})'.format(self.__envelope_state))
-        self.__envelope = msg
-        self.__envelope_state = 0
-        return MessageEnvelope(msg)
+        if self.envelope_state > -1 and self.envelope_state < 2:
+            raise VerifyError('new envelope before previous was verified ({})'.format(self.envelope_state))
+        self.sign_material = None
+        super(PGPSigner, self).process_envelope(msg, env_header)
+        return self.envelope
 
 
-    def message_callback(self, envelope, msg, message_id, message_date):
+    def process_message(self, envelope, msg, message_id, message_date):
         if msg.get('From') != None:
-            envelope.sender = msg.get('From')
+            self.envelope.sender = msg.get('From')
 
-        if self.__envelope_state == 0:
-            self.add(message_id, msg)
-            self.__envelope_state = 1
-            self.__envelope = msg
-            return (envelope, msg,)
+        if self.envelope_state == 0:
+            self.envelope_state = 1
+            self.sign_material = msg
+            return (self.envelope, msg,)
 
         if msg.get('Content-Type') != 'application/pgp-signature':
-            self.add(message_id, msg)
-            return (envelope, msg,)
+            self.add(self.envelope, message_id, msg)
+            return (self.envelope, msg,)
+
+        v = self.sign_material.as_string()
 
-        v = self.__envelope.as_string()
         sig = msg.get_payload()
         (fd, fp) = tempfile.mkstemp()
         f = os.fdopen(fd, 'w')
@@ -105,8 +103,8 @@ class PGPSigner(Wrapper):
                 raise VerifyError('invalid signature for message {}'.format(message_id))
         else:
             logg.debug('signature ok from {}'.format(r.fingerprint))
-            envelope.valid = True
-        #envelope.sender = r.fingerprint
-        self.__envelope_state = 2
+            self.envelope.valid = True
+        self.envelope.sender = r.fingerprint
+        self.envelope_state = 2
 
-        return (envelope, msg,)
+        return (self.envelope, msg,)
diff --git a/piknik/msg.py b/piknik/msg.py
@@ -68,8 +68,7 @@ class IssueMessage:
                 message_ids.append(message_id)
                 d = m.get('Date')
                 message_date = parsedate_to_datetime(d)
-            else:
-                message_callback(envelope, m, message_id, message_date)
+            message_callback(m, message_id, message_date)
 
         if post_callback != None:
             post_callback(message_ids)
diff --git a/piknik/render/base.py b/piknik/render/base.py
@@ -14,12 +14,10 @@ def stream_accumulator(v, w=sys.stdout):
 
 class Renderer:
 
-    def __init__(self, basket, accumulator=None, envelope_callback=None, message_callback=None):
+    def __init__(self, basket, accumulator=None, wrapper=None):
         self.b = basket
-        #self.e = None
         self.a = accumulator
-        self.message_callback = message_callback
-        self.envelope_callback = envelope_callback
+        self.w = wrapper
 
 
     def add(self, v, accumulator=None):
@@ -49,7 +47,11 @@ class Renderer:
     def apply_message_post(self, state, issue, tags, envelope, message, message_id, message_date, accumulator=None):
         pass
 
-    
+ 
+    def apply_message_post(self, state, issue, tags, envelope, message, message_id, message_date, accumulator=None):
+        pass
+
+   
     def apply_message(self, state, issue, tags, envelope, message, message_id, message_date, accumulator=None):
         pass
 
@@ -65,8 +67,8 @@ class Renderer:
     def apply_issue(self, state, issue, tags, accumulator=None):
 
         def envelope_callback(envelope, envelope_type):
-            if self.envelope_callback != None:
-                envelope = self.envelope_callback(envelope, envelope_type)
+            if self.w != None:
+                envelope = self.w.process_envelope(envelope, envelope_type)
             else:
                 envelope = MessageEnvelope(envelope)
             r = self.apply_envelope_pre(state, issue, tags, envelope, accumulator=accumulator)
@@ -77,19 +79,32 @@ class Renderer:
             self.add(r)
             return envelope
 
-        def message_callback(envelope, message, message_id, message_date):
-            if self.message_callback != None:
-                (envelope, message) = self.message_callback(envelope, message, message_id, message_date)
-            r = self.apply_message_pre(state, issue, tags, envelope, message, message_id, message_date, accumulator=accumulator)
-            self.add(r)
-            r = self.apply_message(state, issue, tags, envelope, message, message_id, message_date, accumulator=accumulator)
-            self.add(r)
-            r = self.apply_message_post(state, issue, tags, envelope, message, message_id, message_date, accumulator=accumulator)
-            self.add(r)
+        def message_callback(message, message_id, message_date):
+            envelope = None
+            if self.w != None:
+                (envelope, message) = self.w.process_message(envelope, message, message_id, message_date)
+            else:
+                logg.warning('no wrapper defined. no message parts will be output')
+
+            initial = True
+            while True:
+                v = self.w.pop()
+                if v == None:
+                    break
+                if initial:
+                    r = self.apply_message_pre(state, issue, tags, envelope, message, message_id, message_date, accumulator=accumulator)
+                    self.add(r)
+                    r = self.apply_message(state, issue, tags, envelope, message, message_id, message_date, accumulator=accumulator)
+                    self.add(r)
+                    r = self.apply_message_post(state, issue, tags, envelope, message, message_id, message_date, accumulator=accumulator)
+                    self.add(r)
+                    initial = False
+
+                r = self.apply_message_part(state, issue, tags, envelope, message, message_date, v)
+                self.add(r)
+
             return (envelope, message,)
 
-        #for msg in self.b.get_msg(issue.id, envelope_callback=envelope_callback, message_callback=message_callback):
-        logg.debug('in msg')
         self.b.get_msg(issue.id, envelope_callback=envelope_callback, message_callback=message_callback)
 
 
diff --git a/piknik/render/ini.py b/piknik/render/ini.py
@@ -5,8 +5,8 @@ from .base import stream_accumulator
 
 class Renderer(BaseRenderer):
 
-    def __init__(self, basket, accumulator=stream_accumulator):
-        super(Renderer, self).__init__(basket, accumulator=accumulator)
+    def __init__(self, basket, accumulator=stream_accumulator, **kwargs):
+        super(Renderer, self).__init__(basket, accumulator=accumulator, **kwargs)
 
 
     def apply_state(self, state, accumulator=None):
diff --git a/piknik/render/plain.py b/piknik/render/plain.py
@@ -1,5 +1,7 @@
 # standard imports
 import logging
+import tempfile
+import os
 
 # external imports
 from mimeparse import parse_mime_type
@@ -10,6 +12,19 @@ from .base import stream_accumulator
 
 logg = logging.getLogger(__name__)
 
+def to_suffixed_file(d, s, data):
+    (v, ext) = os.path.splitext(s)
+    r = tempfile.mkstemp(suffix=ext, dir=d)
+
+    f = os.fdopen(r[0], 'wb')
+    try:
+        f.write(data)
+    except TypeError:
+        f.write(data.encode('utf-8'))
+    f.close()
+
+    return r[1]
+
 
 class Renderer(BaseRenderer):
 
@@ -51,37 +66,29 @@ id: {}
 
     def apply_message(self, state, issue, tags, envelope, message, message_id, message_date, accumulator=None):
         s = '\nmessage {} from {} {} - {}\n\n'.format(
-                message_date,
-                envelope.sender,
-                envelope.valid,
-                message_id,
-                )
-        self.add(s, accumulator=accumulator)
-
+            message_date,
+            envelope.sender,
+            envelope.valid,
+            message_id,
+            )
+        return s
 
-    def apply_message_part(self, state, issue, envelope, message, message_id, message_date, accumulator=None):
-        m = parse_mime_type(message.get_content_type())
-        filename = message.get_filename()
 
-        v = ''
-        if filename == None:
-            if m[0] == 'text':
-                if m[1] == 'plain':
-                    v = message.get_payload()
-                    if message.get('Content-Transfer-Encoding') == 'BASE64':
-                        v = b64decode(v).decode()
-                else:
-                    v = '[rich text]'
-        else:
+    def apply_message_part(self, state, issue, tags, envelope, message, message_date, message_content, accumulator=None):
+        if message_content['filename'] != None:
             if self.dump_dir != None:
-                v = message.get_payload()
-                if message.get('Content-Transfer-Encoding') == 'BASE64':
-                    v = b64decode(v).decode()
-                filename = to_suffixed_file(self.dump_dir, filename, v)
-            sz = message.get('Content-Length')
-            if sz == None:
+                filename = to_suffixed_file(self.dump_dir, message_content['filename'], message_content['contents'])
+            sz = message_content['size']
+            if sz == -1:
                 sz = 'unknown'
-            v = '[file: {}, type {}/{}, size: {}]'.format(filename, m[0], m[1], sz)
+            v = '[file: {}, type {}/{}, size: {}]'.format(
+                    message_content['filename'],
+                    message_content['type'][0],
+                    message_content['type'][1],
+                    sz,
+                    )
+        else:
+            v = message_content['contents']
 
         s = '\n\t' + v + '\n'
-        self.add(s, accumulator=accumulator)
+        return s
diff --git a/piknik/runnable/show.py b/piknik/runnable/show.py
@@ -15,7 +15,7 @@ from piknik.store import FileStoreFactory
 from piknik.crypto import PGPSigner
 from piknik.render.plain import Renderer
 
-logging.basicConfig(level=logging.DEBUG)
+logging.basicConfig(level=logging.INFO)
 logg = logging.getLogger()
 
 argp = argparse.ArgumentParser()
@@ -47,12 +47,9 @@ def to_suffixed_file(d, s, data):
     return r[1]
 
 
-# TODO can implement as email parser api instead?
 class PGPWrapper(PGPSigner):
 
-    #def __init__(self, renderer, state, issue, home_dir=None):
     def __init__(self, state, issue, home_dir=None):
-        #super(PGPWrapper, self).__init__(home_dir=home_dir)
         super(PGPWrapper, self).__init__(home_dir=home_dir, skip_verify=True)
         self.message_date = None
         self.messages = []
@@ -60,20 +57,10 @@ class PGPWrapper(PGPSigner):
         self.message_id = None
         self.sender = None
         self.valid = False
-        #self.renderer = renderer
         self.state = state
         self.issue = issue
 
 
-#    def render_message(self, envelope, messages, message_sender, message_date, message_id, dump_dir=None, w=sys.stdout):
-#        for message in messages:
-#            self.renderer.apply_message_part(self.state, self.issue, envelope, message, message_sender, message_date, message_id, self.valid, dump_dir=dump_dir, w=w)
-#        #valid = '[++]'
-#        #if not self.valid:
-#        #    valid = '[!!]'
-#        self.renderer.apply_message_post(self.state, self.issue, envelope, message, self.sender, message_date, message_id, self.valid, w=w)
-
-
     def envelope_callback(self, envelope, envelope_type):
         envelope = super(PGPWrapper, self).envelope_callback(envelope, envelope_type)
         envelope.valid = False
@@ -208,7 +195,7 @@ def main():
     verifier = PGPWrapper(state, issue, home_dir=gpg_home)
 
     import piknik.render.plain
-    renderer = piknik.render.plain.Renderer(basket, envelope_callback=verifier.envelope_callback, message_callback=verifier.message_callback)
+    renderer = piknik.render.plain.Renderer(basket, wrapper=verifier) #envelope_callback=verifier.envelope_callback, message_callback=verifier.message_callback)
 
     renderer.apply_issue(state, issue, tags)
     
diff --git a/piknik/wrap.py b/piknik/wrap.py
@@ -4,28 +4,42 @@ import logging
 # external imports
 from mimeparse import parse_mime_type
 
+# local imports
+from piknik.msg import MessageEnvelope
+
 logg = logging.getLogger(__name__)
 
 
 class Wrapper:
 
-    def __init__(self):
-        self.content_buffer = {}
+    def __init__(self, dump_dir=None):
+        self.content_buffer = []
+        self.envelope_state = -1 # -1 not in envelope, 0 in outer envelope, 1 inner envelope, not (yet) valid, 2 envelope valid (with signature)
+        self.envelope = None
+        self.dump_dir = dump_dir
+
+
+
+    def process_envelope(self, msg, env_header):
+        self.envelope = MessageEnvelope(msg)
+        self.envelope_state = 0
+
+
+    def process_message(self, envelope, msg, message_id, message_data):
+        raise NotImplementedError()
 
 
-    def add(self, message_id, message):
+    def add(self, envelope, message_id, message):
         m = parse_mime_type(message.get_content_type())
         filename = message.get_filename()
 
-        v = ''
+        v = None
         if filename == None:
             if m[0] == 'text':
                 if m[1] == 'plain':
                     v = message.get_payload()
                     if message.get('Content-Transfer-Encoding') == 'BASE64':
                         v = b64decode(v).decode()
-                else:
-                    v = '[rich text]'
         else:
             if self.dump_dir != None:
                 v = message.get_payload()
@@ -39,17 +53,21 @@ class Wrapper:
         else:
             sz = int(sz)
 
-        self.content_buffer[message_id] = {
+        o = {
+                'envelope': envelope,
+                'id': message_id,
                 'type': m,
                 'filename': filename,
                 'size': sz,
                 'contents': v,
                 }
 
-        logg.debug('buffered content {}'.format(self.content_buffer[message_id]))
+        logg.info('buffered content {}'.format(o))
+        self.content_buffer.append(o)
 
 
-    def pop(self, message_id):
-        r = self.content_buffer[message_id]
-        del self.content_buffer[message_id]
-        return r
+    def pop(self):
+        try:
+            return self.content_buffer.pop()
+        except IndexError:
+            return None