From 24faebedea2f72aa3cb62e44f3a43726070b79dd Mon Sep 17 00:00:00 2001
From: Sascha Markus <sascha.markus@gmail.com>
Date: Sat, 27 Apr 2019 10:14:10 +0200
Subject: [PATCH] Attach debug info as individual files

---
 whisperBack/whisperback.py | 76 ++++++++++++++++++++++++--------------
 1 file changed, 49 insertions(+), 27 deletions(-)

diff --git a/whisperBack/whisperback.py b/whisperBack/whisperback.py
index d8a2c8c..7bfd2cc 100644
--- a/whisperBack/whisperback.py
+++ b/whisperBack/whisperback.py
@@ -25,12 +25,13 @@
 
 """
 
-import email.mime.text
 import json
 import logging
 import os
 import re
 import threading
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
 
 import gi
 from gi.repository import GLib
@@ -40,9 +41,11 @@ import whisperBack.exceptions
 import whisperBack.mail
 import whisperBack.encryption
 import whisperBack.utils
+from whisperBack.utils import sanitize_hardware_info as sanitize
 
 LOG = logging.getLogger(__name__)
 
+
 # pylint: disable=R0902
 class WhisperBack(object):
     """
@@ -123,8 +126,10 @@ class WhisperBack(object):
 
         # Get additional info through the callbacks and sanitize it
         self.prepended_data = whisperBack.utils.sanitize_hardware_info(self.mail_prepended_info())
-        self.appended_data = self.__get_debug_info(self.mail_appended_info())
-
+        self.debug_info = self.__get_debug_info(self.mail_appended_info())
+        self.attachments = [self.__build_message(i['key'], i['content']) for i in self.debug_info]
+        # Create text to display on second tab
+        self.appended_data = "\n".join([self.__build_text(i['key'], i['content']) for i in self.debug_info])
         # Initialize other variables
         self.subject = subject
         self.message = message
@@ -155,31 +160,46 @@ class WhisperBack(object):
         #pylint: disable=W0122
         exec(code, self.__dict__)
 
-    def __get_debug_info(self, raw_debug, prefix=''):
-        """ Deserializes the dicts from raw_debug and creates a string
-        with the header from the dict key and it's content
+
+    def __get_debug_info(self, raw_debug):
+        """ Deserializes the dicts from raw_debug and creates list of attachments.
 
         @param raw_debug The serialized json containing the debug info
         It is a list of dicts to keep the order of the different debug infos
         """
-        all_info = json.loads(raw_debug)
-        result = ''
-        for debug_info in all_info:
-            if prefix:
-                result += '\n{} === content of {} ===\n'.format(prefix, debug_info['key'])
-            else:
-                result += '\n======= content of {} =======\n'.format(debug_info['key'])
-            if type(debug_info['content']) is list:
-                for line in debug_info['content']:
-
-                    if isinstance(line, dict):
-                        result += self.__get_debug_info(json.dumps([line]), prefix + '> ')
-                    else:
-                        sanitized = '{}{}\n'.format(prefix, whisperBack.utils.sanitize_hardware_info(line))
-                        result += re.sub(r'^--\s*', '', sanitized)
-            else:
-                result += '{}{}\n'.format(prefix, whisperBack.utils.sanitize_hardware_info(debug_info['content']))
-        return result
+        all_info = []
+        try:
+            all_info = json.loads(raw_debug)
+        except ValueError:
+            # In case the text in raw_debug isn't valid json we attach it as one file
+            all_info.append({"appended_info": raw_debug})
+        return all_info
+
+    def __build_message(self, command, text):
+        """Create a text attachment.
+
+        @param command The executed command with an attached .txt will be the filename
+
+        @param text The text to attach
+        """
+
+        msg = MIMEText(self.__build_text(command, text))
+        filename = re.sub('[ \/]', '_', command) + '.txt'
+        msg.add_header('Content-Disposition', 'attachment', filename=filename)
+        return msg
+
+    def __build_text(self, command, lines):
+        text = ['\n======= content of {} ======='.format(command)]
+        if isinstance(lines, list):
+            for line in lines:
+                if isinstance(line, dict): # our content is a directory
+                    text.extend(['\t' + line['key']])
+                    text.extend(['\n'.join(sanitize(line['content']))])
+                else:
+                    text.extend([sanitize(line)])
+        else:
+            text.append('{}\n'.format(sanitize(lines)))
+        return '\n'.join(text)
 
     def __check_conf(self):
         """Check that all the required configuration variables are filled
@@ -284,21 +304,23 @@ class WhisperBack(object):
         body += "%s\n%s\n\n" % (self.prepended_data, self.message)
         if self.contact_gpgkey and len(self.contact_gpgkey.splitlines()) > 1:
             body += "%s\n\n" % self.contact_gpgkey
-        body += "%s\n" % self.appended_data
         return body
 
     def get_mime_message(self):
         """Returns the PGP/MIME message to be sent"""
+
         LOG.debug("Building mime message")
-        mime_message = email.mime.text.MIMEText(self.get_message_body())
+        mime_message = MIMEMultipart()
+        mime_message.attach(MIMEText(self.get_message_body()))
 
+        for msg in self.attachments:
+            mime_message.attach(msg)
         encrypter = whisperBack.encryption.Encryption(
                                         keyring=self.gnupg_keyring)
 
         encrypted_mime_message = encrypter.pgp_mime_encrypt(
                                         mime_message,
                                         [self.to_fingerprint])
-
         encrypted_mime_message['Subject'] = self.mail_subject
         encrypted_mime_message['From'] = self.from_address
         encrypted_mime_message['To'] = self.to_address
-- 
2.20.1

