235 lines
7.6 KiB
Python
235 lines
7.6 KiB
Python
|
import copy
|
||
|
import logging
|
||
|
import olefile
|
||
|
|
||
|
from extract_msg import constants
|
||
|
from extract_msg.dev_classes.attachment import Attachment
|
||
|
from extract_msg.properties import Properties
|
||
|
from extract_msg.recipient import Recipient
|
||
|
from extract_msg.utils import encode, has_len, stri, windowsUnicode
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
logger.addHandler(logging.NullHandler())
|
||
|
|
||
|
|
||
|
class Message(olefile.OleFileIO):
|
||
|
"""
|
||
|
Developer version of the `extract_msg.message.Message` class.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, path, prefix=''):
|
||
|
"""
|
||
|
:param path: path to the msg file in the system or is the raw msg file.
|
||
|
:param prefix: used for extracting embedded msg files
|
||
|
inside the main one. Do not set manually unless
|
||
|
you know what you are doing.
|
||
|
"""
|
||
|
logger.log(5, 'prefix: {}'.format(prefix))
|
||
|
self.__path = path
|
||
|
olefile.OleFileIO.__init__(self, path)
|
||
|
prefixl = []
|
||
|
if prefix != '':
|
||
|
if not isinstance(prefix, stri):
|
||
|
try:
|
||
|
prefix = '/'.join(prefix)
|
||
|
except:
|
||
|
raise TypeError('Invalid prefix type: ' + str(type(prefix)) +
|
||
|
'\n(This was probably caused by you setting it manually).')
|
||
|
prefix = prefix.replace('\\', '/')
|
||
|
g = prefix.split("/")
|
||
|
if g[-1] == '':
|
||
|
g.pop()
|
||
|
prefixl = g
|
||
|
if prefix[-1] != '/':
|
||
|
prefix += '/'
|
||
|
filename = self._getStringStream(prefixl[:-1] + ['__substg1.0_3001'], prefix=False)
|
||
|
self.__prefix = prefix
|
||
|
self.__prefixList = prefixl
|
||
|
|
||
|
logger.log(5, ':param path: has __len__ attribute?: {}'.format(has_len(path)))
|
||
|
if has_len(path):
|
||
|
if len(path) < 1536:
|
||
|
self.filename = path
|
||
|
logger.log(5, ':param path: length is {}; Using :param path: as file path'.format(len(path)))
|
||
|
else:
|
||
|
logger.log(5, ':param path: length is {}; Using :param path: as raw msg stream'.format(len(path)))
|
||
|
self.filename = None
|
||
|
|
||
|
self.mainProperties
|
||
|
recipientDirs = []
|
||
|
|
||
|
for dir_ in self.listDir():
|
||
|
if dir_[len(self.__prefixList)].startswith('__recip') and\
|
||
|
dir_[len(self.__prefixList)] not in recipientDirs:
|
||
|
recipientDirs.append(dir_[len(self.__prefixList)])
|
||
|
|
||
|
self.recipients
|
||
|
self.attachments
|
||
|
self.date
|
||
|
|
||
|
def listDir(self, streams=True, storages=False):
|
||
|
"""
|
||
|
Replacement for OleFileIO.listdir that runs at the current prefix directory.
|
||
|
"""
|
||
|
temp = self.listdir(streams, storages)
|
||
|
if self.__prefix == '':
|
||
|
return temp
|
||
|
prefix = self.__prefix.split('/')
|
||
|
if prefix[-1] == '':
|
||
|
prefix.pop()
|
||
|
out = []
|
||
|
for x in temp:
|
||
|
good = True
|
||
|
if len(x) <= len(prefix):
|
||
|
good = False
|
||
|
if good:
|
||
|
for y in range(len(prefix)):
|
||
|
if x[y] != prefix[y]:
|
||
|
good = False
|
||
|
if good:
|
||
|
out.append(x)
|
||
|
return out
|
||
|
|
||
|
def Exists(self, inp):
|
||
|
"""
|
||
|
Checks if :param inp: exists in the msg file.
|
||
|
"""
|
||
|
if isinstance(inp, list):
|
||
|
inp = self.__prefixList + inp
|
||
|
else:
|
||
|
inp = self.__prefix + inp
|
||
|
return self.exists(inp)
|
||
|
|
||
|
def _getStream(self, filename, prefix=True):
|
||
|
if isinstance(filename, list):
|
||
|
filename = '/'.join(filename)
|
||
|
if prefix:
|
||
|
filename = self.__prefix + filename
|
||
|
if self.exists(filename):
|
||
|
stream = self.openstream(filename)
|
||
|
return stream.read()
|
||
|
else:
|
||
|
logger.info('Stream "{}" was requested but could not be found. Returning `None`.'.format(filename))
|
||
|
return None
|
||
|
|
||
|
def _getStringStream(self, filename, prefer='unicode', prefix=True):
|
||
|
"""
|
||
|
Gets a string representation of the requested filename.
|
||
|
Checks for both ASCII and Unicode representations and returns
|
||
|
a value if possible. If there are both ASCII and Unicode
|
||
|
versions, then :param prefer: specifies which will be
|
||
|
returned.
|
||
|
"""
|
||
|
|
||
|
if isinstance(filename, list):
|
||
|
# Join with slashes to make it easier to append the type
|
||
|
filename = '/'.join(filename)
|
||
|
|
||
|
asciiVersion = self._getStream(filename + '001E', prefix)
|
||
|
unicodeVersion = windowsUnicode(self._getStream(filename + '001F', prefix))
|
||
|
logger.log(5, '_getStringStream called for {}. Ascii version found: {}. Unicode version found: {}.'.format(
|
||
|
filename, asciiVersion is not None, unicodeVersion is not None))
|
||
|
if asciiVersion is None:
|
||
|
return unicodeVersion
|
||
|
elif unicodeVersion is None:
|
||
|
return asciiVersion
|
||
|
else:
|
||
|
if prefer == 'unicode':
|
||
|
return unicodeVersion
|
||
|
else:
|
||
|
return asciiVersion
|
||
|
|
||
|
@property
|
||
|
def path(self):
|
||
|
"""
|
||
|
Returns the message path if generated from a file,
|
||
|
otherwise returns the data used to generate the
|
||
|
Message instance.
|
||
|
"""
|
||
|
return self.__path
|
||
|
|
||
|
@property
|
||
|
def prefix(self):
|
||
|
"""
|
||
|
Returns the prefix of the Message instance.
|
||
|
Intended for developer use.
|
||
|
"""
|
||
|
return self.__prefix
|
||
|
|
||
|
@property
|
||
|
def prefixList(self):
|
||
|
"""
|
||
|
Returns the prefix list of the Message instance.
|
||
|
Intended for developer use.
|
||
|
"""
|
||
|
return copy.deepcopy(self.__prefixList)
|
||
|
|
||
|
@property
|
||
|
def mainProperties(self):
|
||
|
"""
|
||
|
Returns the Properties instance used by the Message instance.
|
||
|
"""
|
||
|
try:
|
||
|
return self._prop
|
||
|
except AttributeError:
|
||
|
self._prop = Properties(self._getStream('__properties_version1.0'),
|
||
|
constants.TYPE_MESSAGE if self.__prefix == '' else constants.TYPE_MESSAGE_EMBED)
|
||
|
return self._prop
|
||
|
|
||
|
@property
|
||
|
def date(self):
|
||
|
"""
|
||
|
Returns the send date, if it exists.
|
||
|
"""
|
||
|
try:
|
||
|
return self._date
|
||
|
except AttributeError:
|
||
|
self._date = self._prop.date
|
||
|
return self._date
|
||
|
|
||
|
@property
|
||
|
def attachments(self):
|
||
|
"""
|
||
|
Returns a list of all attachments.
|
||
|
"""
|
||
|
try:
|
||
|
return self._attachments
|
||
|
except AttributeError:
|
||
|
# Get the attachments
|
||
|
attachmentDirs = []
|
||
|
|
||
|
for dir_ in self.listDir():
|
||
|
if dir_[len(self.__prefixList)].startswith('__attach') and\
|
||
|
dir_[len(self.__prefixList)] not in attachmentDirs:
|
||
|
attachmentDirs.append(dir_[len(self.__prefixList)])
|
||
|
|
||
|
self._attachments = []
|
||
|
|
||
|
for attachmentDir in attachmentDirs:
|
||
|
self._attachments.append(Attachment(self, attachmentDir))
|
||
|
|
||
|
return self._attachments
|
||
|
|
||
|
@property
|
||
|
def recipients(self):
|
||
|
"""
|
||
|
Returns a list of all recipients.
|
||
|
"""
|
||
|
try:
|
||
|
return self._recipients
|
||
|
except AttributeError:
|
||
|
# Get the recipients
|
||
|
recipientDirs = []
|
||
|
|
||
|
for dir_ in self.listDir():
|
||
|
if dir_[len(self.__prefixList)].startswith('__recip') and\
|
||
|
dir_[len(self.__prefixList)] not in recipientDirs:
|
||
|
recipientDirs.append(dir_[len(self.__prefixList)])
|
||
|
|
||
|
self._recipients = []
|
||
|
|
||
|
for recipientDir in recipientDirs:
|
||
|
self._recipients.append(Recipient(recipientDir, self))
|
||
|
|
||
|
return self._recipients
|