1#!/usr/bin/python -u
2#
3# Python Bindings for LZMA
4#
5# Copyright (c) 2004-2015 by Joachim Bauch, mail@joachim-bauch.de
6# 7-Zip Copyright (C) 1999-2010 Igor Pavlov
7# LZMA SDK Copyright (C) 1999-2010 Igor Pavlov
8#
9# This library is free software; you can redistribute it and/or
10# modify it under the terms of the GNU Lesser General Public
11# License as published by the Free Software Foundation; either
12# version 2.1 of the License, or (at your option) any later version.
13#
14# This library is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17# Lesser General Public License for more details.
18#
19# You should have received a copy of the GNU Lesser General Public
20# License along with this library; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22#
23# $Id$
24#
25from datetime import datetime
26import errno
27import os
28import pylzma
29from py7zlib import Archive7z, NoPasswordGivenError, WrongPasswordError, UTC
30import sys
31import unittest
32
33try:
34    sorted
35except NameError:
36    # Python 2.3 and older
37    def sorted(l):
38        l = list(l)
39        l.sort()
40        return l
41
42try:
43    xrange
44except NameError:
45    # Python 3.x
46    xrange = range
47
48if sys.version_info[:2] < (3, 0):
49    def bytes(s, encoding):
50        return s
51
52    def unicode_string(s):
53        return s.decode('latin-1')
54else:
55    def unicode_string(s):
56        return s
57
58ROOT = os.path.abspath(os.path.split(__file__)[0])
59
60class Test7ZipFiles(unittest.TestCase):
61
62    def setUp(self):
63        self._open_files = []
64        unittest.TestCase.setUp(self)
65
66    def tearDown(self):
67        while self._open_files:
68            fp = self._open_files.pop()
69            fp.close()
70        unittest.TestCase.tearDown(self)
71
72    def _open_file(self, filename, mode):
73        fp = open(filename, mode)
74        self._open_files.append(fp)
75        return fp
76
77    def _test_archive(self, filename):
78        fp = self._open_file(os.path.join(ROOT, 'data', filename), 'rb')
79        archive = Archive7z(fp)
80        self.assertEqual(sorted(archive.getnames()), ['test/test2.txt', 'test1.txt'])
81        self.assertEqual(archive.getmember('test2.txt'), None)
82        cf = archive.getmember('test1.txt')
83        self.assertTrue(cf.checkcrc())
84        self.assertEqual(cf.lastwritetime // 10000000, 12786932628)
85        self.assertEqual(cf.lastwritetime.as_datetime().replace(microsecond=0), \
86            datetime(2006, 3, 15, 21, 43, 48, 0, UTC))
87        self.assertEqual(cf.read(), bytes('This file is located in the root.', 'ascii'))
88        cf.reset()
89        self.assertEqual(cf.read(), bytes('This file is located in the root.', 'ascii'))
90
91        cf = archive.getmember('test/test2.txt')
92        self.assertTrue(cf.checkcrc())
93        self.assertEqual(cf.lastwritetime // 10000000, 12786932616)
94        self.assertEqual(cf.lastwritetime.as_datetime().replace(microsecond=0), \
95            datetime(2006, 3, 15, 21, 43, 36, 0, UTC))
96        self.assertEqual(cf.read(), bytes('This file is located in a folder.', 'ascii'))
97        cf.reset()
98        self.assertEqual(cf.read(), bytes('This file is located in a folder.', 'ascii'))
99
100    def _test_decode_all(self, archive):
101        filenames = archive.getnames()
102        for filename in filenames:
103            cf = archive.getmember(filename)
104            self.assertNotEqual(cf, None, filename)
105            self.assertTrue(cf.checkcrc(), 'crc failed for %s' % (filename))
106            self.assertEqual(len(cf.read()), cf.uncompressed)
107
108    def test_non_solid(self):
109        # test loading of a non-solid archive
110        self._test_archive('non_solid.7z')
111
112    def test_solid(self):
113        # test loading of a solid archive
114        self._test_archive('solid.7z')
115
116    def _test_umlaut_archive(self, filename):
117        fp = self._open_file(os.path.join(ROOT, 'data', filename), 'rb')
118        archive = Archive7z(fp)
119        self.assertEqual(sorted(archive.getnames()), [unicode_string('t\xe4st.txt')])
120        self.assertEqual(archive.getmember('test.txt'), None)
121        cf = archive.getmember(unicode_string('t\xe4st.txt'))
122        self.assertEqual(cf.read(), bytes('This file contains a german umlaut in the filename.', 'ascii'))
123        cf.reset()
124        self.assertEqual(cf.read(), bytes('This file contains a german umlaut in the filename.', 'ascii'))
125
126    def test_non_solid_umlaut(self):
127        # test loading of a non-solid archive containing files with umlauts
128        self._test_umlaut_archive('umlaut-non_solid.7z')
129
130    def test_solid_umlaut(self):
131        # test loading of a solid archive containing files with umlauts
132        self._test_umlaut_archive('umlaut-solid.7z')
133
134    def test_bugzilla_4(self):
135        # sample file for bugzilla #4
136        fp = self._open_file(os.path.join(ROOT, 'data', 'bugzilla_4.7z'), 'rb')
137        archive = Archive7z(fp)
138        self._test_decode_all(archive)
139
140    def test_encrypted_no_password(self):
141        # test loading of encrypted files without password (can read archived filenames)
142        fp = self._open_file(os.path.join(ROOT, 'data', 'encrypted.7z'), 'rb')
143        archive = Archive7z(fp)
144        filenames = archive.getnames()
145        self.assertEqual(sorted(archive.getnames()), sorted(['test1.txt', 'test/test2.txt']))
146        self.assertRaises(NoPasswordGivenError, archive.getmember('test1.txt').read)
147        self.assertRaises(NoPasswordGivenError, archive.getmember('test/test2.txt').read)
148
149    def test_encrypted_password(self):
150        # test loading of encrypted files with correct password
151        fp = self._open_file(os.path.join(ROOT, 'data', 'encrypted.7z'), 'rb')
152        archive = Archive7z(fp, password='secret')
153        self._test_decode_all(archive)
154
155    def test_encrypted_wong_password(self):
156        # test loading of encrypted files with wrong password
157        fp = self._open_file(os.path.join(ROOT, 'data', 'encrypted.7z'), 'rb')
158        archive = Archive7z(fp, password='password')
159        filenames = archive.getnames()
160        for filename in filenames:
161            cf = archive.getmember(filename)
162            self.assertNotEqual(cf, None, filename)
163            self.assertRaises(WrongPasswordError, cf.checkcrc)
164            self.assertRaises(WrongPasswordError, cf.read)
165
166    def test_encrypted_short(self):
167        # test loading of encrypted file with short content with correct password
168        fp = self._open_file(os.path.join(ROOT, 'data', 'encrypted-short.7z'), 'rb')
169        archive = Archive7z(fp, password='secret')
170        self._test_decode_all(archive)
171
172    def test_encrypted_names_no_password(self):
173        # test loading of files with encrypted names without password
174        fp = self._open_file(os.path.join(ROOT, 'data', 'encrypted-names.7z'), 'rb')
175        self.assertRaises(NoPasswordGivenError, Archive7z, fp)
176
177    def test_encrypted_names_password(self):
178        # test loading of files with encrypted names with correct password
179        fp = self._open_file(os.path.join(ROOT, 'data', 'encrypted-names.7z'), 'rb')
180        archive = Archive7z(fp, password='secret')
181        self._test_decode_all(archive)
182
183    def test_encrypted_names_wong_password(self):
184        # test loading of files with encrypted names with wrong password
185        fp = self._open_file(os.path.join(ROOT, 'data', 'encrypted-names.7z'), 'rb')
186        self.assertRaises(WrongPasswordError, Archive7z, fp, password='password')
187
188    def test_deflate(self):
189        # test loading of deflate compressed files
190        self._test_archive('deflate.7z')
191
192    def test_deflate64(self):
193        # test loading of deflate64 compressed files
194        self._test_archive('deflate64.7z')
195
196    def test_bzip2(self):
197        # test loading of bzip2 compressed files
198        self._test_archive('bzip2.7z')
199
200    def test_copy(self):
201        # test loading of copy compressed files
202        self._test_archive('copy.7z')
203
204    def test_regress_1(self):
205        # prevent regression bug #1 reported by mail
206        fp = self._open_file(os.path.join(ROOT, 'data', 'regress_1.7z'), 'rb')
207        archive = Archive7z(fp)
208        filenames = list(archive.getnames())
209        self.assertEqual(len(filenames), 1)
210        cf = archive.getmember(filenames[0])
211        self.assertNotEqual(cf, None)
212        self.assertTrue(cf.checkcrc())
213        data = cf.read()
214        self.assertEqual(len(data), cf.size)
215
216    def test_bugzilla_16(self):
217        # sample file for bugzilla #16
218        fp = self._open_file(os.path.join(ROOT, 'data', 'bugzilla_16.7z'), 'rb')
219        archive = Archive7z(fp)
220        self._test_decode_all(archive)
221
222    def test_empty(self):
223        # decompress empty archive
224        fp = self._open_file(os.path.join(ROOT, 'data', 'empty.7z'), 'rb')
225        archive = Archive7z(fp)
226        self.assertEqual(archive.getnames(), [])
227
228    def test_github_14(self):
229        # decompress content without filename
230        fp = self._open_file(os.path.join(ROOT, 'data', 'github_14.7z'), 'rb')
231        archive = Archive7z(fp)
232        self.assertEqual(archive.getnames(), ['github_14'])
233        cf = archive.getmember('github_14')
234        self.assertNotEqual(cf, None)
235        data = cf.read()
236        self.assertEqual(len(data), cf.uncompressed)
237        self.assertEqual(data, bytes('Hello GitHub issue #14.\n', 'ascii'))
238
239        # accessing by name returns an arbitrary compressed streams
240        # if both don't have a name in the archive
241        fp = self._open_file(os.path.join(ROOT, 'data', 'github_14_multi.7z'), 'rb')
242        archive = Archive7z(fp)
243        self.assertEqual(archive.getnames(), ['github_14_multi', 'github_14_multi'])
244        cf = archive.getmember('github_14_multi')
245        self.assertNotEqual(cf, None)
246        data = cf.read()
247        self.assertEqual(len(data), cf.uncompressed)
248        self.assertTrue(data in (bytes('Hello GitHub issue #14 1/2.\n', 'ascii'), bytes('Hello GitHub issue #14 2/2.\n', 'ascii')))
249
250        # accessing by index returns both values
251        cf = archive.getmember(0)
252        self.assertNotEqual(cf, None)
253        data = cf.read()
254        self.assertEqual(len(data), cf.uncompressed)
255        self.assertEqual(data, bytes('Hello GitHub issue #14 1/2.\n', 'ascii'))
256        cf = archive.getmember(1)
257        self.assertNotEqual(cf, None)
258        data = cf.read()
259        self.assertEqual(len(data), cf.uncompressed)
260        self.assertEqual(data, bytes('Hello GitHub issue #14 2/2.\n', 'ascii'))
261
262    def test_github_17(self):
263        try:
264            fp = self._open_file(os.path.join(ROOT, 'data', 'ux.stackexchange.com.7z'), 'rb')
265        except EnvironmentError:
266            e = sys.exc_info()[1]
267            if e.errno != errno.ENOENT:
268                raise
269
270            # test is optional
271            import warnings
272            warnings.warn("File 'ux.stackexchange.com.7z' was not found, please download manually. Test will be skipped.")
273            return
274
275        archive = Archive7z(fp)
276        self._test_decode_all(archive)
277
278    def test_github_37(self):
279        self._test_archive('github_37_dummy.7z')
280
281    def test_github_33(self):
282        fp = self._open_file(os.path.join(ROOT, 'data', 'github_33.7z'), 'rb')
283        archive = Archive7z(fp, password='abc')
284        # Archive only contains an empty file.
285        self.assertEqual(archive.getnames(), ['successs.txt'])
286        self.assertEqual(archive.header.files.numfiles, 1)
287        self.assertEqual([x['filename'] for x in archive.header.files.files], [u'successs.txt'])
288        cf = archive.getmember('successs.txt')
289        self.assertNotEqual(cf, None)
290        data = cf.read()
291        self.assertEqual(len(data), cf.uncompressed)
292        self.assertEqual(len(data), 0)
293        self._test_decode_all(archive)
294
295    def test_github_43(self):
296        # test loading of lzma2 compressed files
297        self._test_archive('github_43.7z')
298
299    def test_github_43_provided(self):
300        # test loading file submitted by @mikenye
301        fp = self._open_file(os.path.join(ROOT, 'data', 'test-issue-43.7z'), 'rb')
302        archive = Archive7z(fp)
303        self.assertEqual(sorted(archive.getnames()), ['blah.txt'] + ['blah%d.txt' % x for x in xrange(2, 10)])
304        self._test_decode_all(archive)
305
306    def test_github_53(self):
307        # test loading file submitted by @VinylChloride
308        fp = self._open_file(os.path.join(ROOT, 'data', 'github_53.7z'), 'rb')
309        archive = Archive7z(fp, password='1234')
310        self.assertEqual(sorted(archive.getnames()), ['test/test/archive7zip.py', 'test/test/mainPrg.py'])
311        self._test_decode_all(archive)
312
313def suite():
314    suite = unittest.TestSuite()
315
316    test_cases = [
317        Test7ZipFiles,
318    ]
319
320    for tc in test_cases:
321        suite.addTest(unittest.makeSuite(tc))
322
323    return suite
324
325if __name__ == '__main__':
326    unittest.main(defaultTest='suite')
327