Skip to content
This repository was archived by the owner on Jun 23, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
.idea/*
# Ignore pypi creds file
.pypirc
*.pyc
*.pyo
__pycache__/
32 changes: 3 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Usage

This will produce a new folder named according to the date, time and subject of the message (for example "2013-07-24_0915 Example"). The email itself can be found inside the new folder along with the attachments. As of version 0.2, it is capable of extracting both ASCII and Unicode data.

The script uses <a href="http://www.decalage.info/python/olefileio">Philippe Lagadec's Python module</a> that reads Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format). This is the underlying format of Outlook's .msg files. This library currently supports up to Python 2.7 and 3.4.
The script uses <a href="http://www.decalage.info/python/olefileio">Philippe Lagadec's Python module</a> that reads Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format). This is the underlying format of Outlook's .msg files. This library currently supports up to Python 2.7 and 3.4.

The script was built using <a href="http://www.fileformat.info/format/outlookmsg/index.htm">Peter Fiskerstrand's documentation of the .msg format</a>. <a href="http://www.dimastr.com/redemption/utils.htm">Redemption's discussion of the different property types used within Extended MAPI</a> was also useful. For future reference, I note that Microsoft have opened up <a href="http://msdn.microsoft.com/en-us/library/cc463912%28v=exchg.80%29.aspx">their documentation of the file format</a>.

Expand All @@ -34,7 +34,7 @@ Joel also added a --use-file-name flag, which allows you to specify that the scr
python extract_msg --use-file-name example.msg
```

Creation also added a --use-content-id flag, which allows you to specify that attachments should be saved under the name of their content id, should they have one. This can be useful for mathich attachments to the names used in the HTML body, and can be done like so:
Creation also added a --use-content-id flag, which allows you to specify that attachments should be saved under the name of their content id, should they have one. This can be useful for matching attachments to the names used in the HTML body, and can be done like so:
```
python extract_msg --use-content-id example.msg
```
Expand All @@ -48,10 +48,6 @@ From there, initialize an instance of the Message class:
```
msg = extract_msg.Message("path/to/msg/file.msg")
```
or
```
msg = extract_msg.Message(msg_data_stream)
```

Alternatively, if you wish to send a msg binary string instead of a file to the ExtractMsg.Message Method:
```
Expand All @@ -69,7 +65,7 @@ where `CustomAttachmentClass` is your custom class.


If you have any questions feel free to contact me, Matthew Walker, at mattgwwalker at gmail.com.
NOTE: Due to time constraints, <a href="https://github.com/TheElementalOfCreation">The Elemental of Creation</a> has been added as a contributer to help manage the project. As such, it may be helpful to send emails to [email protected] as well.
NOTE: Due to time constraints, <a href="https://github.com/TheElementalOfCreation">The Elemental of Creation</a> has been added as a contributor to help manage the project. As such, it may be helpful to send emails to [email protected] as well.

If you have issues, it would be best to get help for them by opening a new github issue.

Expand Down Expand Up @@ -103,31 +99,9 @@ Todo
------------
Here is a list of things that are currently on our todo list:
* Tests (ie. unittest)
* Coming in version 0.50
* Finish writing a usage guide
* Improve the intelligence of the saving functions
* Create a Pypi package
* Provide way to save attachments and messages into a custom location under a custom name
* Coming in version 0.21
* Implement better property handling that will convert each type into a python equivelent if possible
* Coming in version 0.30
* Implement handling of named properties


Coming Soon
------------
#### Version 0.21 - The Saving Update ####
* Improves save mechanics of the `Message` class and the `Attachment` class to make saving files to a custom path and/or saving files under a custom name possible

#### Version 0.25 - The Debugger Update ####
* Make the changing of `ExtractMsg.debug` to `True` output A LOT more information that should help to make bug fixes much faster

#### Version 0.30 - The Intelligence Update ####
* Majorly increase the intelligence of nearly all of the classes

#### Version 0.50 - The Testing Update ####
* Added tests for most platforms (win, mac, linux)

#### Version 1.00 - The Release Update ####
* ExtractMsg is fully ready for its first full release

16 changes: 16 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Version 0.20.6:
* Separated classes into their own files to make things more manageable.
* Placed `__doc__` back inside of `__init__.py`.
* Rewrote the `Prop` class to be two different classes that extend from a base class.
* Made decent progress on completing the `parse_type` function of the `FixedLengthProp` class (formerly a function of the `Prop` class).
* Improved exception handling code throughout most of the module.
* Updated the `.gitignore`.
* Updated README.

* Added `# DEBUG` comments before debugging lines to make them easier to find in the future.
* Added function `create_prop` in `prop.py` which should be used for creating what used to be an instance of the `Prop` class.
* Added more constants to reflect some of the changes made.

* Fixed a major bug that was causing the header to generate after things like "to" and "cc" which would force those fields to not use the header.
* Fixed the debug variable.
* Fixed many small bugs in many of the classes.
38 changes: 31 additions & 7 deletions extract_msg/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
#!/usr/bin/env python
# -*- coding: latin-1 -*-
# Date Format: YYYY-MM-DD

"""
extract_msg:
Extracts emails and attachments saved in Microsoft Outlook's .msg files

https://github.com/mattgwwalker/msg-extractor
"""

# --- LICENSE -----------------------------------------------------------------
#
# Copyright 2013 Matthew Walker
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

__author__ = 'Matthew Walker & The Elemental of Creation'
__date__ = '2018-05-22'
__version__ = '0.20.3'
__date__ = '2018-12-04'
__version__ = '0.20.6'

import glob
import sys
import traceback
from .base import Attachment, Properties, Prop, Recipient, Message, parse_type, properHex
import constants
from extract_msg import constants
from extract_msg.attachment import Attachment
from extract_msg.debug import debug
from extract_msg.message import Message
from extract_msg.prop import create_prop
from extract_msg.properties import Properties
from extract_msg.recipient import Recipient
from extract_msg.utils import parse_type, properHex
8 changes: 6 additions & 2 deletions extract_msg/__main__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import glob
import sys
import traceback
from extract_msg.base import Message
from extract_msg import __doc__
from extract_msg import debug
from extract_msg import Message



if __name__ == '__main__':
if len(sys.argv) <= 1:
Expand Down Expand Up @@ -56,4 +60,4 @@
except Exception as e:
# msg.debug()
print("Error with file '" + filename + "': " +
traceback.format_exc())
traceback.format_exc())
163 changes: 163 additions & 0 deletions extract_msg/attachment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import random
import string
from extract_msg import constants
from extract_msg.debug import debug
from extract_msg.properties import Properties
from extract_msg.utils import properHex



class Attachment(object):
"""
Stores the attachment data of a Message instance.
Should the attachment be an embeded message, the
class used to create it will be the same as the
Message class used to create the attachment.
"""

def __init__(self, msg, dir_):
"""
:param msg: the Message instance that the attachment belongs to.
:param dir_: the directory inside the msg file where the attachment is located.
"""
object.__init__(self)
self.__msg = msg
self.__dir = dir_
self.__props = Properties(
self.msg._getStream(self.msg.prefixList + [self.__dir, '__properties_version1.0']),
constants.TYPE_ATTACHMENT)
# Get long filename
self.__longFilename = msg._getStringStream([dir_, '__substg1.0_3707'])

# Get short filename
self.__shortFilename = msg._getStringStream([dir_, '__substg1.0_3704'])

# Get Content-ID
self.__cid = msg._getStringStream([dir_, '__substg1.0_3712'])

# Get attachment data
if msg.Exists([dir_, '__substg1.0_37010102']):
self.__type = 'data'
self.__data = msg._getStream([dir_, '__substg1.0_37010102'])
elif msg.Exists([dir_, '__substg1.0_3701000D']):
if (self.props['37050003'].value & 0x7) != 0x5:
if not debug:
raise NotImplementedError(
'Current version of extract_msg does not support extraction of containers that are not embeded msg files.')
# TODO add implementation
else:
# DEBUG
print('DEBUG: Debugging is true, ignoring NotImplementedError and printing debug info...')
print('DEBUG: _dir = {}'.format(_dir))
print('DEBUG: Writing properties stream to output:')
print('DEBUG: --------Start-Properties-Stream--------')
print(properHex(self.props.stream))
print('DEBUG: ---------End-Properties-Stream---------')
print('DEBUG: Writing directory contents to output:')
print('DEBUG: --------Start-Directory-Content--------')
for x in msg.listDir(True, True): print(x)
print('DEBUG: ---------End-Directory-Content---------')
else:
self.__prefix = msg.prefixList + [dir_, '__substg1.0_3701000D']
self.__type = 'msg'
self.__data = msg.__class__(self.msg.path, self.__prefix, self.__class__)
else:
raise TypeError('Unknown attachment type.')

def save(self, contentId=False, json=False, useFileName=False, raw=False, customPath=None, customFilename=None):
# Check if the user has specified a custom filename
if customFilename != None and customFilename != '':
filename = customFilename
else:
# If not...
# Check if user wants to save the file under the Content-id
if contentId:
filename = self.__cid
# If filename is None at this point, use long filename as first preference
filename = self.__longFilename
# Otherwise use the short filename
if filename is None:
filename = self.__shortFilename
# Otherwise just make something up!
if filename is None:
filename = 'UnknownFilename ' + \
''.join(random.choice(string.ascii_uppercase + string.digits)
for _ in range(5)) + '.bin'

if customPath != None and customPath != '':
if customPath[-1] != '/' or customPath[-1] != '\\':
customPath += '/'
filename = customPath + filename

if self.__type == "data":
f = open(filename, 'wb')
f.write(self.__data)
f.close()
else:
self.saveEmbededMessage(contentId, json, useFileName, raw, customPath, customFilename)
return filename

def saveEmbededMessage(self, contentId=False, json=False, useFileName=False, raw=False, customPath=None, customFilename=None):
"""
Seperate function from save to allow it to
easily be overridden by a subclass.
"""
self.data.save(json, useFileName, raw, contentId, customPath, customFilename)

@property
def cid(self):
"""
Returns the content ID of the attachment, if it exists.
"""
return self.__cid

contend_id = cid

@property
def data(self):
"""
Returns the attachment data.
"""
return self.__data

@property
def dir(self):
"""
Returns the directory inside the msg file where the attachment is located.
"""
return self.__dir

@property
def longFilename(self):
"""
Returns the long file name of the attachment, if it exists.
"""
return self.__longFilename

@property
def msg(self):
"""
Returns the Message instance the attachment belongs to.
"""
return self.__msg

@property
def props(self):
"""
Returns the Properties instance of the attachment.
"""
return self.__props

@property
def shortFilename(self):
"""
Returns the short file name of the attachment, if it exists.
"""
return self.__shortFilename

@property
def type(self):
"""
Returns the type of the data.
"""
return self.__type
Loading