Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #88 and add zipfile test #103

Merged
merged 4 commits into from
Jan 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ tnefparse 1.4.0 (unreleased)
- fix `test_zip` deprecation warning for bytes (1nF0rmed)
- correctly handle attachments of embedded objects (jrideout)
- add expirimental support for parsing embedded message objects (jrideout)
- zipped output now uses long filename when possible (jrideout)

tnefparse 1.3.1 (2020-09-30)
=============================
Expand Down
Binary file added tests/examples/duplicate_filename.tnef
Binary file not shown.
6 changes: 3 additions & 3 deletions tests/test_cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ def test_cmdline_attch_extract(script_runner):

def test_cmdline_zip_extract(script_runner):
tmpdir = tempfile.mkdtemp()
ret = script_runner.run('tnefparse', '-z', '-p', tmpdir, 'tests/examples/one-file.tnef')
ret = script_runner.run('tnefparse', '-z', '-p', tmpdir, 'tests/examples/duplicate_filename.tnef')
assert os.path.isfile(tmpdir + '/attachments.zip')
assert ret.stderr == 'Successfully wrote attachments.zip\n'
with open(tmpdir + '/attachments.zip', 'rb') as zip_fp:
zip_stream = io.BytesIO(zip_fp.read())
zip_file = zipfile.ZipFile(zip_stream)
assert zip_file.namelist() == ['AUTHORS']
assert zip_file.namelist() == ['file_abcdefgh.txt', 'file_abcdefgh-2.txt', 'VIA_Nytt_14021.htm']
assert ret.success
shutil.rmtree(tmpdir)

Expand Down Expand Up @@ -85,7 +85,7 @@ def test_dump(script_runner):
ret = script_runner.run('tnefparse', '-d', 'tests/examples/two-files.tnef')
assert ret.success
dump = json.loads(ret.stdout)
assert sorted(list(dump.keys())) == ['attachments', 'attributes', 'extended_attributes']
assert sorted(dump.keys()) == ['attachments', 'attributes', 'extended_attributes']
assert len(dump['attachments']) == 2


Expand Down
9 changes: 5 additions & 4 deletions tnefparse/mapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,13 @@ def parse_property(data, offset, attr_name, attr_type, codepage, is_multi):
r = length % 4
if r != 0:
length += 4 - r

item = data[offset: offset + length]
if attr_type == SZMAPI_UNICODE_STRING:
attr_data.append(data[offset: offset + length].decode('utf-16'))
item = item.decode('utf-16')
elif attr_type == SZMAPI_STRING:
attr_data.append(data[offset: offset + length].decode(codepage))
else:
attr_data.append(data[offset: offset + length])
item = item.decode(codepage)
attr_data.append(item)
offset += length

else:
Expand Down
27 changes: 12 additions & 15 deletions tnefparse/tnef.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import logging
import os
import warnings
from typing import Union
from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
from io import BytesIO
from datetime import datetime
from io import BytesIO
from typing import Union
from uuid import UUID
from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED

from . import properties as Attribute
from .codepage import Codepage
Expand Down Expand Up @@ -331,7 +331,7 @@ def __init__(self, data, do_checksum=True):
logger.debug("Unhandled TNEF Object: %s", obj)

def has_body(self):
return True if (self.body or self.htmlbody or self._rtfbody) else False
return any((self.body, self.htmlbody, self._rtfbody))

@property
def rtfbody(self):
Expand Down Expand Up @@ -377,9 +377,7 @@ def get_data(a):
if o.name == TNEF.ATTRECIPTABLE:
data = []
for recipient in o.data:
rec = {}
for att in recipient:
rec[att.name_str] = get_data(att)
rec = {att.name_str: get_data(att) for att in recipient}
data.append(rec)
out['attributes'][o.name_str] = data
for att in self.mapiprops:
Expand Down Expand Up @@ -410,21 +408,20 @@ def to_zip(tnef: Union[TNEF, bytes], default_name='no-name', deflate=True):
# Extract attachments found in the TNEF object
tozip = {}
for attachment in tnef.attachments:
filename = attachment.name or default_name
L = len(tozip.get(filename, []))
if L > 0:
filename = attachment.long_filename() or default_name
if tozip.get(filename):
# uniqify this file name by adding -<num> before the extension
length = len(tozip.get(filename))
root, ext = os.path.splitext(filename)
tozip[filename].append((attachment.data, str("%s-%d%s" % (root, L + 1, ext))))
tozip[filename].append((f"{root}-{length + 1}{ext}", attachment.data))
else:
tozip[filename] = [(attachment.data, filename)]
tozip[filename] = [(filename, attachment.data)]

# Add each attachment to the zip file
sfp = BytesIO()
with ZipFile(sfp, "w", ZIP_DEFLATED if deflate else ZIP_STORED) as zf:
for filename, entries in list(tozip.items()):
for entry in entries:
data, name = entry
for entries in tozip.values():
for name, data in entries:
zf.writestr(name, data)

# Return the binary data for the zip file
Expand Down
2 changes: 1 addition & 1 deletion tnefparse/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def systime(byte_arr, offset=0):
return datetime.utcfromtimestamp((ft - EPOCH_AS_FILETIME) / HUNDREDS_OF_NANOSECONDS)
except: # noqa: E722
microseconds = ft / 10
return (datetime(1601, 1, 1) + timedelta(microseconds=microseconds))
return datetime(1601, 1, 1) + timedelta(microseconds=microseconds)


def apptime(byte_arr, offset=0):
Expand Down