PCQRSCANER/venv/Lib/site-packages/extract_msg/utils.py

373 lines
12 KiB
Python
Raw Normal View History

2019-12-22 21:51:47 +01:00
"""
Utility functions of extract_msg.
"""
import argparse
import datetime
import json
import logging
import logging.config
import sys
import tzlocal
from extract_msg import constants
from extract_msg.compat import os_ as os
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
logging.addLevelName(5, 'DEVELOPER')
if sys.version_info[0] >= 3: # Python 3
stri = (str,)
get_input = input
def encode(inp):
return inp
def properHex(inp):
"""
Taken (with permission) from https://github.com/TheElementalOfCreation/creatorUtils
"""
a = ''
if isinstance(inp, stri):
a = ''.join([hex(ord(inp[x]))[2:].rjust(2, '0') for x in range(len(inp))])
elif isinstance(inp, bytes):
a = inp.hex()
elif isinstance(inp, int):
a = hex(inp)[2:]
if len(a) % 2 != 0:
a = '0' + a
return a
def windowsUnicode(string):
return str(string, 'utf_16_le') if string is not None else None
def xstr(s):
return '' if s is None else str(s)
else: # Python 2
stri = (str, unicode)
get_input = raw_input
def encode(inp):
return inp.encode('utf-8') if inp is not None else None
def properHex(inp):
"""
Taken (with permission) from https://github.com/TheElementalOfCreation/creatorUtils
"""
a = ''
if isinstance(inp, stri):
a = ''.join([hex(ord(inp[x]))[2:].rjust(2, '0') for x in range(len(inp))])
elif isinstance(inp, int):
a = hex(inp)[2:]
elif isinstance(inp, long):
a = hex(inp)[2:-1]
if len(a) % 2 != 0:
a = '0' + a
return a
def windowsUnicode(string):
return unicode(string, 'utf_16_le') if string is not None else None
def xstr(s):
if isinstance(s, unicode):
return s.encode('utf-8')
else:
return '' if s is None else str(s)
def addNumToDir(dirName):
"""
Attempt to create the directory with a '(n)' appended.
"""
for i in range(2, 100):
try:
newDirName = dirName + ' (' + str(i) + ')'
os.makedirs(newDirName)
return newDirName
except Exception as e:
pass
return None
def divide(string, length):
"""
Taken (with permission) from https://github.com/TheElementalOfCreation/creatorUtils
Divides a string into multiple substrings of equal length
:param string: string to be divided.
:param length: length of each division.
:returns: list containing the divided strings.
Example:
>>>> a = divide('Hello World!', 2)
>>>> print(a)
['He', 'll', 'o ', 'Wo', 'rl', 'd!']
"""
return [string[length * x:length * (x + 1)] for x in range(int(len(string) / length))]
def fromTimeStamp(stamp):
return datetime.datetime.fromtimestamp(stamp, tzlocal.get_localzone())
def get_command_args(args):
"""
Parse command-line arguments
"""
parser = argparse.ArgumentParser(description=constants.MAINDOC, prog='extract_msg')
# --use-content-id, --cid
parser.add_argument('--use-content-id', '--cid', dest='cid', action='store_true',
help='Save attachments by their Content ID, if they have one. Useful when working with the HTML body.')
# --dev
parser.add_argument('--dev', dest='dev', action='store_true',
help='Changes to use developer mode. Automatically enables the --verbose flag. Takes precedence over the --validate flag.')
# --validate
parser.add_argument('--validate', dest='validate', action='store_true',
help='Turns on file validation mode. Turns off regular file output.')
# --json
parser.add_argument('--json', dest='json', action='store_true',
help='Changes to write output files as json.')
# --file-logging
parser.add_argument('--file-logging', dest='file_logging', action='store_true',
help='Enables file logging. Implies --verbose')
# --verbose
parser.add_argument('--verbose', dest='verbose', action='store_true',
help='Turns on console logging.')
# --log PATH
parser.add_argument('--log', dest='log',
help='Set the path to write the file log to.')
# --config PATH
parser.add_argument('--config', dest='config_path',
help='Set the path to load the logging config from.')
# --out PATH
parser.add_argument('--out', dest='out_path',
help='Set the folder to use for the program output. (Default: Current directory)')
# --use-filename
parser.add_argument('--use-filename', dest='use_filename', action='store_true',
help='Sets whether the name of each output is based on the msg filename.')
# --out-name NAME
# parser.add_argument('--out-name', dest = 'out_name',
# help = 'Name to be used with saving the file output. Should come immediately after the file name')
# [msg files]
parser.add_argument('msgs', metavar='msg', nargs='+',
help='An msg file to be parsed')
options = parser.parse_args(args)
if options.dev or options.file_logging:
options.verbose = True
file_args = options.msgs
file_tables = [] # This is where we will store the separated files and their arguments
temp_table = [] # temp_table will store each table while it is still being built.
need_arg = True # This tells us if the last argument was something like
# --out-name which requires a string name after it.
# We start on true to make it so that we use don't have to have something checking if we are on the first table.
for x in file_args: # Iterate through each
if need_arg:
temp_table.append(x)
need_arg = False
elif x in constants.KNOWN_FILE_FLAGS:
temp_table.append(x)
if x in constants.NEEDS_ARG:
need_arg = True
else:
file_tables.append(temp_table)
temp_table = [x]
file_tables.append(temp_table)
options.msgs = file_tables
return options
def has_len(obj):
"""
Checks if :param obj: has a __len__ attribute.
"""
try:
obj.__len__
return True
except AttributeError:
return False
def msgEpoch(inp):
"""
Taken (with permission) from https://github.com/TheElementalOfCreation/creatorUtils
"""
return (inp - 116444736000000000) / 10000000.0
def parse_type(_type, stream):
"""
Converts the data in :param stream: to a
much more accurate type, specified by
:param _type:, if possible.
:param stream # TODO what is stream?
Some types require that :param prop_value: be specified. This can be retrieved from the Properties instance.
WARNING: Not done. Do not try to implement anywhere where it is not already implemented
"""
# WARNING Not done. Do not try to implement anywhere where it is not already implemented
value = stream
if _type == 0x0000: # PtypUnspecified
pass
elif _type == 0x0001: # PtypNull
if value != b'\x00\x00\x00\x00\x00\x00\x00\x00':
# DEBUG
logger.warning('Property type is PtypNull, but is not equal to 0.')
value = None
elif _type == 0x0002: # PtypInteger16
value = constants.STI16.unpack(value)[0]
elif _type == 0x0003: # PtypInteger32
value = constants.STI32.unpack(value)[0]
elif _type == 0x0004: # PtypFloating32
value = constants.STF32.unpack(value)[0]
elif _type == 0x0005: # PtypFloating64
value = constants.STF64.unpack(value)[0]
elif _type == 0x0006: # PtypCurrency
value = (constants.STI64.unpack(value)[0]) / 10000.0
elif _type == 0x0007: # PtypFloatingTime
value = constants.STF64.unpack(value)[0]
# TODO parsing for this
pass
elif _type == 0x000A: # PtypErrorCode
value = constants.STI32.unpack(value)[0]
# TODO parsing for this
pass
elif _type == 0x000B: # PtypBoolean
value = bool(constants.ST3.unpack(value)[0])
elif _type == 0x000D: # PtypObject/PtypEmbeddedTable
# TODO parsing for this
pass
elif _type == 0x0014: # PtypInteger64
value = constants.STI64.unpack(value)[0]
elif _type == 0x001E: # PtypString8
# TODO parsing for this
pass
elif _type == 0x001F: # PtypString
value = value.decode('utf_16_le')
elif _type == 0x0040: # PtypTime
value = constants.ST3.unpack(value)[0]
elif _type == 0x0048: # PtypGuid
# TODO parsing for this
pass
elif _type == 0x00FB: # PtypServerId
# TODO parsing for this
pass
elif _type == 0x00FD: # PtypRestriction
# TODO parsing for this
pass
elif _type == 0x00FE: # PtypRuleAction
# TODO parsing for this
pass
elif _type == 0x0102: # PtypBinary
# TODO parsing for this
# Smh, how on earth am I going to code this???
pass
elif _type & 0x1000 == 0x1000: # PtypMultiple
# TODO parsing for `multiple` types
pass
return value
def getContFileDir(_file_):
"""
Takes in the path to a file and tries to return the containing folder.
"""
return '/'.join(_file_.replace('\\', '/').split('/')[:-1])
def setup_logging(default_path=None, default_level=logging.WARN, logfile=None, enable_file_logging=False,
env_key='EXTRACT_MSG_LOG_CFG'):
"""
Setup logging configuration
Args:
default_path (str): Default path to use for the logging configuration file
default_level (int): Default logging level
env_key (str): Environment variable name to search for, for setting logfile path
Returns:
bool: True if the configuration file was found and applied, False otherwise
"""
shipped_config = getContFileDir(__file__) + '/logging-config/'
if os.name == 'nt':
null = 'NUL'
shipped_config += 'logging-nt.json'
elif os.name == 'posix':
null = '/dev/null'
shipped_config += 'logging-posix.json'
# Find logging.json if not provided
if not default_path:
default_path = shipped_config
paths = [
default_path,
'logging.json',
'../logging.json',
'../../logging.json',
shipped_config,
]
path = None
for config_path in paths:
if os.path.exists(config_path):
path = config_path
break
value = os.getenv(env_key, None)
if value and os.path.exists(value):
path = value
if path is None:
print('Unable to find logging.json configuration file')
print('Make sure a valid logging configuration file is referenced in the default_path'
' argument, is inside the extract_msg install location, or is available at one '
'of the following file-paths:')
print(str(paths[1:]))
logging.basicConfig(level=default_level)
logging.warning('The extract_msg logging configuration was not found - using a basic configuration.'
'Please check the extract_msg installation directory for "logging-{}.json".'.format(os.name))
return False
with open(path, 'rt') as f:
config = json.load(f)
for x in config['handlers']:
if 'filename' in config['handlers'][x]:
if enable_file_logging:
config['handlers'][x]['filename'] = tmp = os.path.expanduser(
os.path.expandvars(logfile if logfile else config['handlers'][x]['filename']))
tmp = getContFileDir(tmp)
if not os.path.exists(tmp):
os.makedirs(tmp)
else:
config['handlers'][x]['filename'] = null
try:
logging.config.dictConfig(config)
except ValueError as e:
print('Failed to configure the logger. Did your installation get messed up?')
print(e)
logging.getLogger().setLevel(default_level)
return True
def get_full_class_name(inp):
return inp.__class__.__module__ + '.' + inp.__class__.__name__