zipremove 0.5.0__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zipremove-0.5.0/src/zipremove.egg-info → zipremove-0.6.1}/PKG-INFO +38 -35
- {zipremove-0.5.0 → zipremove-0.6.1}/README.md +37 -34
- {zipremove-0.5.0 → zipremove-0.6.1}/setup.cfg +1 -1
- {zipremove-0.5.0 → zipremove-0.6.1}/src/zipremove/__init__.py +4 -2
- {zipremove-0.5.0 → zipremove-0.6.1/src/zipremove.egg-info}/PKG-INFO +38 -35
- {zipremove-0.5.0 → zipremove-0.6.1}/tests/test_zipfile.py +187 -87
- {zipremove-0.5.0 → zipremove-0.6.1}/tests/test_zipfile64.py +4 -15
- {zipremove-0.5.0 → zipremove-0.6.1}/LICENSE.txt +0 -0
- {zipremove-0.5.0 → zipremove-0.6.1}/pyproject.toml +0 -0
- {zipremove-0.5.0 → zipremove-0.6.1}/setup.py +0 -0
- {zipremove-0.5.0 → zipremove-0.6.1}/src/zipremove.egg-info/SOURCES.txt +0 -0
- {zipremove-0.5.0 → zipremove-0.6.1}/src/zipremove.egg-info/dependency_links.txt +0 -0
- {zipremove-0.5.0 → zipremove-0.6.1}/src/zipremove.egg-info/requires.txt +0 -0
- {zipremove-0.5.0 → zipremove-0.6.1}/src/zipremove.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zipremove
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Extend `zipfile` with `remove`-related functionalities
|
|
5
5
|
Home-page: https://github.com/danny0838/zipremove
|
|
6
6
|
Author: Danny Lin
|
|
@@ -45,14 +45,10 @@ This package extends `zipfile` with `remove`-related functionalities.
|
|
|
45
45
|
|
|
46
46
|
* `ZipFile.remove(zinfo_or_arcname)`
|
|
47
47
|
|
|
48
|
-
Removes a member from the archive
|
|
49
|
-
of the member or a `ZipInfo`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
a path is provided.
|
|
53
|
-
|
|
54
|
-
This does not physically remove the local file entry from the archive.
|
|
55
|
-
Call `repack` afterwards to reclaim space.
|
|
48
|
+
Removes a member entry from the archive's central directory.
|
|
49
|
+
*zinfo_or_arcname* may be the full path of the member or a `ZipInfo`
|
|
50
|
+
instance. If multiple members share the same full path and the path is
|
|
51
|
+
provided, only one of them is removed.
|
|
56
52
|
|
|
57
53
|
The archive must be opened with mode ``'w'``, ``'x'`` or ``'a'``.
|
|
58
54
|
|
|
@@ -60,42 +56,49 @@ This package extends `zipfile` with `remove`-related functionalities.
|
|
|
60
56
|
|
|
61
57
|
Calling `remove` on a closed ZipFile will raise a `ValueError`.
|
|
62
58
|
|
|
59
|
+
> **Note:**
|
|
60
|
+
> This method only removes the member's entry from the central directory,
|
|
61
|
+
> making it inaccessible to most tools. The member's local file entry,
|
|
62
|
+
> including content and metadata, remains in the archive and is still
|
|
63
|
+
> recoverable using forensic tools. Call `repack` afterwards to completely
|
|
64
|
+
> remove the member and reclaim space.
|
|
65
|
+
|
|
63
66
|
* `ZipFile.repack(removed=None, *, strict_descriptor=False[, chunk_size])`
|
|
64
67
|
|
|
65
|
-
Rewrites the archive to remove
|
|
66
|
-
size.
|
|
68
|
+
Rewrites the archive to remove unreferenced local file entries, shrinking
|
|
69
|
+
its file size. The archive must be opened with mode ``'a'``.
|
|
67
70
|
|
|
68
71
|
If *removed* is provided, it must be a sequence of `ZipInfo` objects
|
|
69
|
-
representing removed
|
|
70
|
-
will be removed.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
2. Data between referenced entries is removed only when it appears to
|
|
81
|
-
be a sequence of consecutive entries with no extra preceding bytes; extra
|
|
82
|
-
following bytes are preserved.
|
|
83
|
-
3. Entries must not overlap. If any entry's data overlaps with another, a
|
|
84
|
-
`BadZipFile` error is raised and no changes are made.
|
|
85
|
-
|
|
86
|
-
When scanning, setting `strict_descriptor=True` disables detection of any
|
|
87
|
-
entry using an unsigned data descriptor (deprecated in the ZIP specification
|
|
88
|
-
since version 6.3.0, released on 2006-09-29, and used only by some legacy
|
|
89
|
-
tools). This improves performance, but may cause some stale entries to be
|
|
90
|
-
preserved.
|
|
72
|
+
representing the recently removed members, and only their corresponding
|
|
73
|
+
local file entries will be removed. Otherwise, the archive is scanned to
|
|
74
|
+
locate and remove local file entries that are no longer referenced in the
|
|
75
|
+
central directory.
|
|
76
|
+
|
|
77
|
+
When scanning, setting ``strict_descriptor=True`` disables detection of any
|
|
78
|
+
entry using an unsigned data descriptor (a format deprecated by the ZIP
|
|
79
|
+
specification since version 6.3.0, released on 2006-09-29, and used only by
|
|
80
|
+
some legacy tools), which is significantly slower to scan—around 100 to
|
|
81
|
+
1000 times in the worst case. This does not affect performance on entries
|
|
82
|
+
without such feature.
|
|
91
83
|
|
|
92
84
|
*chunk_size* may be specified to control the buffer size when moving
|
|
93
85
|
entry data (default is 1 MiB).
|
|
94
86
|
|
|
95
|
-
The archive must be opened with mode ``'a'``.
|
|
96
|
-
|
|
97
87
|
Calling `repack` on a closed ZipFile will raise a `ValueError`.
|
|
98
88
|
|
|
89
|
+
> **Note:**
|
|
90
|
+
> The scanning algorithm is heuristic-based and assumes that the ZIP file
|
|
91
|
+
> is normally structured—for example, with local file entries stored
|
|
92
|
+
> consecutively, without overlap or interleaved binary data. Prepended
|
|
93
|
+
> binary data, such as a self-extractor stub, is recognized and preserved
|
|
94
|
+
> unless it happens to contain bytes that coincidentally resemble a valid
|
|
95
|
+
> local file entry in multiple respects—an extremely rare case. Embedded
|
|
96
|
+
> ZIP payloads are also handled correctly, as long as they follow normal
|
|
97
|
+
> structure. However, the algorithm does not guarantee correctness or
|
|
98
|
+
> safety on untrusted or intentionally crafted input. It is generally
|
|
99
|
+
> recommended to provide the *removed* argument for better reliability and
|
|
100
|
+
> performance.
|
|
101
|
+
|
|
99
102
|
* `ZipFile.copy(zinfo_or_arcname, new_arcname[, chunk_size])`
|
|
100
103
|
|
|
101
104
|
Copies a member *zinfo_or_arcname* to *new_arcname* in the archive.
|
|
@@ -11,14 +11,10 @@ This package extends `zipfile` with `remove`-related functionalities.
|
|
|
11
11
|
|
|
12
12
|
* `ZipFile.remove(zinfo_or_arcname)`
|
|
13
13
|
|
|
14
|
-
Removes a member from the archive
|
|
15
|
-
of the member or a `ZipInfo`
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
a path is provided.
|
|
19
|
-
|
|
20
|
-
This does not physically remove the local file entry from the archive.
|
|
21
|
-
Call `repack` afterwards to reclaim space.
|
|
14
|
+
Removes a member entry from the archive's central directory.
|
|
15
|
+
*zinfo_or_arcname* may be the full path of the member or a `ZipInfo`
|
|
16
|
+
instance. If multiple members share the same full path and the path is
|
|
17
|
+
provided, only one of them is removed.
|
|
22
18
|
|
|
23
19
|
The archive must be opened with mode ``'w'``, ``'x'`` or ``'a'``.
|
|
24
20
|
|
|
@@ -26,42 +22,49 @@ This package extends `zipfile` with `remove`-related functionalities.
|
|
|
26
22
|
|
|
27
23
|
Calling `remove` on a closed ZipFile will raise a `ValueError`.
|
|
28
24
|
|
|
25
|
+
> **Note:**
|
|
26
|
+
> This method only removes the member's entry from the central directory,
|
|
27
|
+
> making it inaccessible to most tools. The member's local file entry,
|
|
28
|
+
> including content and metadata, remains in the archive and is still
|
|
29
|
+
> recoverable using forensic tools. Call `repack` afterwards to completely
|
|
30
|
+
> remove the member and reclaim space.
|
|
31
|
+
|
|
29
32
|
* `ZipFile.repack(removed=None, *, strict_descriptor=False[, chunk_size])`
|
|
30
33
|
|
|
31
|
-
Rewrites the archive to remove
|
|
32
|
-
size.
|
|
34
|
+
Rewrites the archive to remove unreferenced local file entries, shrinking
|
|
35
|
+
its file size. The archive must be opened with mode ``'a'``.
|
|
33
36
|
|
|
34
37
|
If *removed* is provided, it must be a sequence of `ZipInfo` objects
|
|
35
|
-
representing removed
|
|
36
|
-
will be removed.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
2. Data between referenced entries is removed only when it appears to
|
|
47
|
-
be a sequence of consecutive entries with no extra preceding bytes; extra
|
|
48
|
-
following bytes are preserved.
|
|
49
|
-
3. Entries must not overlap. If any entry's data overlaps with another, a
|
|
50
|
-
`BadZipFile` error is raised and no changes are made.
|
|
51
|
-
|
|
52
|
-
When scanning, setting `strict_descriptor=True` disables detection of any
|
|
53
|
-
entry using an unsigned data descriptor (deprecated in the ZIP specification
|
|
54
|
-
since version 6.3.0, released on 2006-09-29, and used only by some legacy
|
|
55
|
-
tools). This improves performance, but may cause some stale entries to be
|
|
56
|
-
preserved.
|
|
38
|
+
representing the recently removed members, and only their corresponding
|
|
39
|
+
local file entries will be removed. Otherwise, the archive is scanned to
|
|
40
|
+
locate and remove local file entries that are no longer referenced in the
|
|
41
|
+
central directory.
|
|
42
|
+
|
|
43
|
+
When scanning, setting ``strict_descriptor=True`` disables detection of any
|
|
44
|
+
entry using an unsigned data descriptor (a format deprecated by the ZIP
|
|
45
|
+
specification since version 6.3.0, released on 2006-09-29, and used only by
|
|
46
|
+
some legacy tools), which is significantly slower to scan—around 100 to
|
|
47
|
+
1000 times in the worst case. This does not affect performance on entries
|
|
48
|
+
without such feature.
|
|
57
49
|
|
|
58
50
|
*chunk_size* may be specified to control the buffer size when moving
|
|
59
51
|
entry data (default is 1 MiB).
|
|
60
52
|
|
|
61
|
-
The archive must be opened with mode ``'a'``.
|
|
62
|
-
|
|
63
53
|
Calling `repack` on a closed ZipFile will raise a `ValueError`.
|
|
64
54
|
|
|
55
|
+
> **Note:**
|
|
56
|
+
> The scanning algorithm is heuristic-based and assumes that the ZIP file
|
|
57
|
+
> is normally structured—for example, with local file entries stored
|
|
58
|
+
> consecutively, without overlap or interleaved binary data. Prepended
|
|
59
|
+
> binary data, such as a self-extractor stub, is recognized and preserved
|
|
60
|
+
> unless it happens to contain bytes that coincidentally resemble a valid
|
|
61
|
+
> local file entry in multiple respects—an extremely rare case. Embedded
|
|
62
|
+
> ZIP payloads are also handled correctly, as long as they follow normal
|
|
63
|
+
> structure. However, the algorithm does not guarantee correctness or
|
|
64
|
+
> safety on untrusted or intentionally crafted input. It is generally
|
|
65
|
+
> recommended to provide the *removed* argument for better reliability and
|
|
66
|
+
> performance.
|
|
67
|
+
|
|
65
68
|
* `ZipFile.copy(zinfo_or_arcname, new_arcname[, chunk_size])`
|
|
66
69
|
|
|
67
70
|
Copies a member *zinfo_or_arcname* to *new_arcname* in the archive.
|
|
@@ -300,7 +300,8 @@ class _ZipRepacker:
|
|
|
300
300
|
return entry_size
|
|
301
301
|
return 0
|
|
302
302
|
|
|
303
|
-
def _iter_scan_signature(self, fp, signature, start_offset, end_offset,
|
|
303
|
+
def _iter_scan_signature(self, fp, signature, start_offset, end_offset,
|
|
304
|
+
chunk_size=io.DEFAULT_BUFFER_SIZE):
|
|
304
305
|
sig_len = len(signature)
|
|
305
306
|
remainder = b''
|
|
306
307
|
pos = start_offset
|
|
@@ -506,7 +507,8 @@ class _ZipRepacker:
|
|
|
506
507
|
|
|
507
508
|
return crc, compress_size, file_size, dd_size
|
|
508
509
|
|
|
509
|
-
def _trace_compressed_block_end(self, fp, offset, end_offset, decompressor,
|
|
510
|
+
def _trace_compressed_block_end(self, fp, offset, end_offset, decompressor,
|
|
511
|
+
chunk_size=io.DEFAULT_BUFFER_SIZE):
|
|
510
512
|
fp.seek(offset)
|
|
511
513
|
read_size = 0
|
|
512
514
|
while True:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zipremove
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Extend `zipfile` with `remove`-related functionalities
|
|
5
5
|
Home-page: https://github.com/danny0838/zipremove
|
|
6
6
|
Author: Danny Lin
|
|
@@ -45,14 +45,10 @@ This package extends `zipfile` with `remove`-related functionalities.
|
|
|
45
45
|
|
|
46
46
|
* `ZipFile.remove(zinfo_or_arcname)`
|
|
47
47
|
|
|
48
|
-
Removes a member from the archive
|
|
49
|
-
of the member or a `ZipInfo`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
a path is provided.
|
|
53
|
-
|
|
54
|
-
This does not physically remove the local file entry from the archive.
|
|
55
|
-
Call `repack` afterwards to reclaim space.
|
|
48
|
+
Removes a member entry from the archive's central directory.
|
|
49
|
+
*zinfo_or_arcname* may be the full path of the member or a `ZipInfo`
|
|
50
|
+
instance. If multiple members share the same full path and the path is
|
|
51
|
+
provided, only one of them is removed.
|
|
56
52
|
|
|
57
53
|
The archive must be opened with mode ``'w'``, ``'x'`` or ``'a'``.
|
|
58
54
|
|
|
@@ -60,42 +56,49 @@ This package extends `zipfile` with `remove`-related functionalities.
|
|
|
60
56
|
|
|
61
57
|
Calling `remove` on a closed ZipFile will raise a `ValueError`.
|
|
62
58
|
|
|
59
|
+
> **Note:**
|
|
60
|
+
> This method only removes the member's entry from the central directory,
|
|
61
|
+
> making it inaccessible to most tools. The member's local file entry,
|
|
62
|
+
> including content and metadata, remains in the archive and is still
|
|
63
|
+
> recoverable using forensic tools. Call `repack` afterwards to completely
|
|
64
|
+
> remove the member and reclaim space.
|
|
65
|
+
|
|
63
66
|
* `ZipFile.repack(removed=None, *, strict_descriptor=False[, chunk_size])`
|
|
64
67
|
|
|
65
|
-
Rewrites the archive to remove
|
|
66
|
-
size.
|
|
68
|
+
Rewrites the archive to remove unreferenced local file entries, shrinking
|
|
69
|
+
its file size. The archive must be opened with mode ``'a'``.
|
|
67
70
|
|
|
68
71
|
If *removed* is provided, it must be a sequence of `ZipInfo` objects
|
|
69
|
-
representing removed
|
|
70
|
-
will be removed.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
2. Data between referenced entries is removed only when it appears to
|
|
81
|
-
be a sequence of consecutive entries with no extra preceding bytes; extra
|
|
82
|
-
following bytes are preserved.
|
|
83
|
-
3. Entries must not overlap. If any entry's data overlaps with another, a
|
|
84
|
-
`BadZipFile` error is raised and no changes are made.
|
|
85
|
-
|
|
86
|
-
When scanning, setting `strict_descriptor=True` disables detection of any
|
|
87
|
-
entry using an unsigned data descriptor (deprecated in the ZIP specification
|
|
88
|
-
since version 6.3.0, released on 2006-09-29, and used only by some legacy
|
|
89
|
-
tools). This improves performance, but may cause some stale entries to be
|
|
90
|
-
preserved.
|
|
72
|
+
representing the recently removed members, and only their corresponding
|
|
73
|
+
local file entries will be removed. Otherwise, the archive is scanned to
|
|
74
|
+
locate and remove local file entries that are no longer referenced in the
|
|
75
|
+
central directory.
|
|
76
|
+
|
|
77
|
+
When scanning, setting ``strict_descriptor=True`` disables detection of any
|
|
78
|
+
entry using an unsigned data descriptor (a format deprecated by the ZIP
|
|
79
|
+
specification since version 6.3.0, released on 2006-09-29, and used only by
|
|
80
|
+
some legacy tools), which is significantly slower to scan—around 100 to
|
|
81
|
+
1000 times in the worst case. This does not affect performance on entries
|
|
82
|
+
without such feature.
|
|
91
83
|
|
|
92
84
|
*chunk_size* may be specified to control the buffer size when moving
|
|
93
85
|
entry data (default is 1 MiB).
|
|
94
86
|
|
|
95
|
-
The archive must be opened with mode ``'a'``.
|
|
96
|
-
|
|
97
87
|
Calling `repack` on a closed ZipFile will raise a `ValueError`.
|
|
98
88
|
|
|
89
|
+
> **Note:**
|
|
90
|
+
> The scanning algorithm is heuristic-based and assumes that the ZIP file
|
|
91
|
+
> is normally structured—for example, with local file entries stored
|
|
92
|
+
> consecutively, without overlap or interleaved binary data. Prepended
|
|
93
|
+
> binary data, such as a self-extractor stub, is recognized and preserved
|
|
94
|
+
> unless it happens to contain bytes that coincidentally resemble a valid
|
|
95
|
+
> local file entry in multiple respects—an extremely rare case. Embedded
|
|
96
|
+
> ZIP payloads are also handled correctly, as long as they follow normal
|
|
97
|
+
> structure. However, the algorithm does not guarantee correctness or
|
|
98
|
+
> safety on untrusted or intentionally crafted input. It is generally
|
|
99
|
+
> recommended to provide the *removed* argument for better reliability and
|
|
100
|
+
> performance.
|
|
101
|
+
|
|
99
102
|
* `ZipFile.copy(zinfo_or_arcname, new_arcname[, chunk_size])`
|
|
100
103
|
|
|
101
104
|
Copies a member *zinfo_or_arcname* to *new_arcname* in the archive.
|
|
@@ -436,45 +436,49 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
436
436
|
# suppress duplicated name warning
|
|
437
437
|
with warnings.catch_warnings():
|
|
438
438
|
warnings.simplefilter("ignore")
|
|
439
|
-
|
|
440
439
|
zinfos = self._prepare_zip_from_test_files(TESTFN, test_files)
|
|
441
|
-
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
442
|
-
zh.remove('file.txt')
|
|
443
440
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
447
|
-
[ComparableZipInfo(zi) for zi in [zinfos[0], zinfos[2]]],
|
|
448
|
-
)
|
|
441
|
+
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
442
|
+
zh.remove('file.txt')
|
|
449
443
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
444
|
+
# check infolist
|
|
445
|
+
self.assertEqual(
|
|
446
|
+
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
447
|
+
[ComparableZipInfo(zi) for zi in [zinfos[0], zinfos[2]]],
|
|
448
|
+
)
|
|
455
449
|
|
|
456
|
-
#
|
|
457
|
-
|
|
458
|
-
|
|
450
|
+
# check NameToInfo cache
|
|
451
|
+
self.assertEqual(
|
|
452
|
+
ComparableZipInfo(zh.getinfo('file.txt')),
|
|
453
|
+
ComparableZipInfo(zinfos[0]),
|
|
454
|
+
)
|
|
459
455
|
|
|
456
|
+
# make sure the zip file is still valid
|
|
457
|
+
with zipfile.ZipFile(TESTFN) as zh:
|
|
458
|
+
self.assertIsNone(zh.testzip())
|
|
459
|
+
|
|
460
|
+
# suppress duplicated name warning
|
|
461
|
+
with warnings.catch_warnings():
|
|
462
|
+
warnings.simplefilter("ignore")
|
|
460
463
|
zinfos = self._prepare_zip_from_test_files(TESTFN, test_files)
|
|
461
|
-
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
462
|
-
zh.remove('file.txt')
|
|
463
|
-
zh.remove('file.txt')
|
|
464
464
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
465
|
+
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
466
|
+
zh.remove('file.txt')
|
|
467
|
+
zh.remove('file.txt')
|
|
468
|
+
|
|
469
|
+
# check infolist
|
|
470
|
+
self.assertEqual(
|
|
471
|
+
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
472
|
+
[ComparableZipInfo(zi) for zi in [zinfos[2]]],
|
|
473
|
+
)
|
|
470
474
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
475
|
+
# check NameToInfo cache
|
|
476
|
+
with self.assertRaises(KeyError):
|
|
477
|
+
zh.getinfo('file.txt')
|
|
474
478
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
479
|
+
# make sure the zip file is still valid
|
|
480
|
+
with zipfile.ZipFile(TESTFN) as zh:
|
|
481
|
+
self.assertIsNone(zh.testzip())
|
|
478
482
|
|
|
479
483
|
def test_remove_by_zinfo_duplicated(self):
|
|
480
484
|
test_files = [
|
|
@@ -486,66 +490,74 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
486
490
|
# suppress duplicated name warning
|
|
487
491
|
with warnings.catch_warnings():
|
|
488
492
|
warnings.simplefilter("ignore")
|
|
489
|
-
|
|
490
493
|
zinfos = self._prepare_zip_from_test_files(TESTFN, test_files)
|
|
491
|
-
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
492
|
-
zh.remove(zh.infolist()[0])
|
|
493
494
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
495
|
+
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
496
|
+
zh.remove(zh.infolist()[0])
|
|
497
|
+
|
|
498
|
+
# check infolist
|
|
499
|
+
self.assertEqual(
|
|
500
|
+
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
501
|
+
[ComparableZipInfo(zi) for zi in [zinfos[1], zinfos[2]]],
|
|
502
|
+
)
|
|
499
503
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
504
|
+
# check NameToInfo cache
|
|
505
|
+
self.assertEqual(
|
|
506
|
+
ComparableZipInfo(zh.getinfo('file.txt')),
|
|
507
|
+
ComparableZipInfo(zinfos[1]),
|
|
508
|
+
)
|
|
505
509
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
510
|
+
# make sure the zip file is still valid
|
|
511
|
+
with zipfile.ZipFile(TESTFN) as zh:
|
|
512
|
+
self.assertIsNone(zh.testzip())
|
|
509
513
|
|
|
514
|
+
# suppress duplicated name warning
|
|
515
|
+
with warnings.catch_warnings():
|
|
516
|
+
warnings.simplefilter("ignore")
|
|
510
517
|
zinfos = self._prepare_zip_from_test_files(TESTFN, test_files)
|
|
511
|
-
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
512
|
-
zh.remove(zh.infolist()[1])
|
|
513
518
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
517
|
-
[ComparableZipInfo(zi) for zi in [zinfos[0], zinfos[2]]],
|
|
518
|
-
)
|
|
519
|
+
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
520
|
+
zh.remove(zh.infolist()[1])
|
|
519
521
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
522
|
+
# check infolist
|
|
523
|
+
self.assertEqual(
|
|
524
|
+
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
525
|
+
[ComparableZipInfo(zi) for zi in [zinfos[0], zinfos[2]]],
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# check NameToInfo cache
|
|
529
|
+
self.assertEqual(
|
|
530
|
+
ComparableZipInfo(zh.getinfo('file.txt')),
|
|
531
|
+
ComparableZipInfo(zinfos[0]),
|
|
532
|
+
)
|
|
525
533
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
534
|
+
# make sure the zip file is still valid
|
|
535
|
+
with zipfile.ZipFile(TESTFN) as zh:
|
|
536
|
+
self.assertIsNone(zh.testzip())
|
|
529
537
|
|
|
538
|
+
# suppress duplicated name warning
|
|
539
|
+
with warnings.catch_warnings():
|
|
540
|
+
warnings.simplefilter("ignore")
|
|
530
541
|
zinfos = self._prepare_zip_from_test_files(TESTFN, test_files)
|
|
531
|
-
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
532
|
-
infolist = zh.infolist().copy()
|
|
533
|
-
zh.remove(infolist[0])
|
|
534
|
-
zh.remove(infolist[1])
|
|
535
542
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
)
|
|
543
|
+
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
544
|
+
infolist = zh.infolist().copy()
|
|
545
|
+
zh.remove(infolist[0])
|
|
546
|
+
zh.remove(infolist[1])
|
|
541
547
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
548
|
+
# check infolist
|
|
549
|
+
self.assertEqual(
|
|
550
|
+
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
551
|
+
[ComparableZipInfo(zi) for zi in [zinfos[2]]],
|
|
552
|
+
)
|
|
545
553
|
|
|
546
|
-
#
|
|
547
|
-
with
|
|
548
|
-
|
|
554
|
+
# check NameToInfo cache
|
|
555
|
+
with self.assertRaises(KeyError):
|
|
556
|
+
zh.getinfo('file.txt')
|
|
557
|
+
|
|
558
|
+
# make sure the zip file is still valid
|
|
559
|
+
with zipfile.ZipFile(TESTFN) as zh:
|
|
560
|
+
self.assertIsNone(zh.testzip())
|
|
549
561
|
|
|
550
562
|
@requires_zip64fix()
|
|
551
563
|
def test_remove_zip64(self):
|
|
@@ -823,7 +835,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
823
835
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
824
836
|
self.assertIsNone(zh.testzip())
|
|
825
837
|
|
|
826
|
-
@mock.patch.object(time, 'time', new=lambda:
|
|
838
|
+
@mock.patch.object(time, 'time', new=lambda: 315590400) # fix time for ZipFile.writestr()
|
|
827
839
|
def test_repack_bytes_before_removed_files(self):
|
|
828
840
|
"""Should preserve if there are bytes before stale local file entries."""
|
|
829
841
|
for ii in ([1], [1, 2], [2]):
|
|
@@ -867,7 +879,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
867
879
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
868
880
|
self.assertIsNone(zh.testzip())
|
|
869
881
|
|
|
870
|
-
@mock.patch.object(time, 'time', new=lambda:
|
|
882
|
+
@mock.patch.object(time, 'time', new=lambda: 315590400) # fix time for ZipFile.writestr()
|
|
871
883
|
def test_repack_bytes_after_removed_files(self):
|
|
872
884
|
"""Should keep extra bytes if there are bytes after stale local file entries."""
|
|
873
885
|
for ii in ([1], [1, 2], [2]):
|
|
@@ -910,7 +922,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
910
922
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
911
923
|
self.assertIsNone(zh.testzip())
|
|
912
924
|
|
|
913
|
-
@mock.patch.object(time, 'time', new=lambda:
|
|
925
|
+
@mock.patch.object(time, 'time', new=lambda: 315590400) # fix time for ZipFile.writestr()
|
|
914
926
|
def test_repack_bytes_between_removed_files(self):
|
|
915
927
|
"""Should strip only local file entries before random bytes."""
|
|
916
928
|
# calculate the expected results
|
|
@@ -954,8 +966,8 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
954
966
|
for ii in ([], [0], [0, 1], [1], [2]):
|
|
955
967
|
with self.subTest(remove=ii):
|
|
956
968
|
# calculate the expected results
|
|
957
|
-
test_files = [data for j, data in enumerate(self.test_files) if j not in ii]
|
|
958
969
|
fz = io.BytesIO()
|
|
970
|
+
test_files = [data for j, data in enumerate(self.test_files) if j not in ii]
|
|
959
971
|
self._prepare_zip_from_test_files(fz, test_files)
|
|
960
972
|
fz.seek(0)
|
|
961
973
|
with open(TESTFN, 'wb') as fh:
|
|
@@ -1068,7 +1080,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
1068
1080
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
1069
1081
|
self.assertIsNone(zh.testzip())
|
|
1070
1082
|
|
|
1071
|
-
@mock.patch.object(time, 'time', new=lambda:
|
|
1083
|
+
@mock.patch.object(time, 'time', new=lambda: 315590400) # fix time for ZipFile.writestr()
|
|
1072
1084
|
def test_repack_removed_bytes_between_files(self):
|
|
1073
1085
|
"""Should not remove bytes between local file entries."""
|
|
1074
1086
|
for ii in ([0], [1], [2]):
|
|
@@ -1468,11 +1480,14 @@ class ZipRepackerTests(unittest.TestCase):
|
|
|
1468
1480
|
fz = io.BytesIO()
|
|
1469
1481
|
f = Unseekable(fz) if dd else fz
|
|
1470
1482
|
cm = (mock.patch.object(struct, 'pack', side_effect=struct_pack_no_dd_sig)
|
|
1471
|
-
if not dd_sig else contextlib.nullcontext())
|
|
1483
|
+
if dd and not dd_sig else contextlib.nullcontext())
|
|
1472
1484
|
with zipfile.ZipFile(f, 'w', compression=compression) as zh:
|
|
1473
|
-
with cm:
|
|
1474
|
-
|
|
1475
|
-
|
|
1485
|
+
with cm, zh.open(arcname, 'w', force_zip64=force_zip64) as fh:
|
|
1486
|
+
fh.write(raw_bytes)
|
|
1487
|
+
if dd:
|
|
1488
|
+
zi = zh.infolist()[0]
|
|
1489
|
+
self.assertTrue(zi.flag_bits & zipfile._MASK_USE_DATA_DESCRIPTOR,
|
|
1490
|
+
f'data descriptor flag not set: {zi.filename}')
|
|
1476
1491
|
fz.seek(0)
|
|
1477
1492
|
return fz.read()
|
|
1478
1493
|
|
|
@@ -1578,10 +1593,10 @@ class ZipRepackerTests(unittest.TestCase):
|
|
|
1578
1593
|
m_sddnsbd.assert_not_called()
|
|
1579
1594
|
m_sddns.assert_not_called()
|
|
1580
1595
|
|
|
1581
|
-
# return None if
|
|
1596
|
+
# return None if truncated local file header
|
|
1582
1597
|
bytes_ = self._generate_local_file_entry(
|
|
1583
1598
|
'file.txt', b'dummy', compression=method)
|
|
1584
|
-
bytes_ = bytes_[:
|
|
1599
|
+
bytes_ = bytes_[:zipfile.sizeFileHeader - 1]
|
|
1585
1600
|
fz = io.BytesIO(bytes_)
|
|
1586
1601
|
with mock.patch.object(repacker, '_scan_data_descriptor',
|
|
1587
1602
|
wraps=repacker._scan_data_descriptor) as m_sdd, \
|
|
@@ -2125,6 +2140,10 @@ class ZipRepackerTests(unittest.TestCase):
|
|
|
2125
2140
|
def test_trace_compressed_block_end_bz2(self):
|
|
2126
2141
|
self._test_trace_compressed_block_end(zipfile.ZIP_BZIP2, OSError)
|
|
2127
2142
|
|
|
2143
|
+
@requires_lzma()
|
|
2144
|
+
def test_trace_compressed_block_end_lzma(self):
|
|
2145
|
+
self._test_trace_compressed_block_end(zipfile.ZIP_LZMA, EOFError)
|
|
2146
|
+
|
|
2128
2147
|
@requires_zstd()
|
|
2129
2148
|
def test_trace_compressed_block_end_zstd(self):
|
|
2130
2149
|
import compression.zstd
|
|
@@ -2193,6 +2212,87 @@ class ZipRepackerTests(unittest.TestCase):
|
|
|
2193
2212
|
comp_len,
|
|
2194
2213
|
)
|
|
2195
2214
|
|
|
2215
|
+
def test_calc_local_file_entry_size(self):
|
|
2216
|
+
repacker = zipfile._ZipRepacker()
|
|
2217
|
+
|
|
2218
|
+
# basic
|
|
2219
|
+
fz = io.BytesIO()
|
|
2220
|
+
with zipfile.ZipFile(fz, 'w') as zh:
|
|
2221
|
+
with zh.open('file.txt', 'w') as fh:
|
|
2222
|
+
fh.write(b'dummy')
|
|
2223
|
+
zi = zh.infolist()[-1]
|
|
2224
|
+
|
|
2225
|
+
self.assertEqual(
|
|
2226
|
+
repacker._calc_local_file_entry_size(fz, zi),
|
|
2227
|
+
(30, 8, 0, 5, 0),
|
|
2228
|
+
)
|
|
2229
|
+
|
|
2230
|
+
# data descriptor
|
|
2231
|
+
fz = io.BytesIO()
|
|
2232
|
+
with zipfile.ZipFile(Unseekable(fz), 'w') as zh:
|
|
2233
|
+
with zh.open('file.txt', 'w') as fh:
|
|
2234
|
+
fh.write(b'dummy')
|
|
2235
|
+
zi = zh.infolist()[-1]
|
|
2236
|
+
|
|
2237
|
+
self.assertEqual(
|
|
2238
|
+
repacker._calc_local_file_entry_size(fz, zi),
|
|
2239
|
+
(30, 8, 0, 5, 16),
|
|
2240
|
+
)
|
|
2241
|
+
|
|
2242
|
+
# data descriptor (unsigned)
|
|
2243
|
+
fz = io.BytesIO()
|
|
2244
|
+
with zipfile.ZipFile(Unseekable(fz), 'w') as zh:
|
|
2245
|
+
with mock.patch.object(struct, 'pack', side_effect=struct_pack_no_dd_sig), \
|
|
2246
|
+
zh.open('file.txt', 'w') as fh:
|
|
2247
|
+
fh.write(b'dummy')
|
|
2248
|
+
zi = zh.infolist()[-1]
|
|
2249
|
+
|
|
2250
|
+
self.assertEqual(
|
|
2251
|
+
repacker._calc_local_file_entry_size(fz, zi),
|
|
2252
|
+
(30, 8, 0, 5, 12),
|
|
2253
|
+
)
|
|
2254
|
+
|
|
2255
|
+
@requires_zip64fix()
|
|
2256
|
+
def test_calc_local_file_entry_size_zip64(self):
|
|
2257
|
+
repacker = zipfile._ZipRepacker()
|
|
2258
|
+
|
|
2259
|
+
# zip64
|
|
2260
|
+
fz = io.BytesIO()
|
|
2261
|
+
with zipfile.ZipFile(fz, 'w') as zh:
|
|
2262
|
+
with zh.open('file.txt', 'w', force_zip64=True) as fh:
|
|
2263
|
+
fh.write(b'dummy')
|
|
2264
|
+
zi = zh.infolist()[-1]
|
|
2265
|
+
|
|
2266
|
+
self.assertEqual(
|
|
2267
|
+
repacker._calc_local_file_entry_size(fz, zi),
|
|
2268
|
+
(30, 8, 20, 5, 0),
|
|
2269
|
+
)
|
|
2270
|
+
|
|
2271
|
+
# data descriptor + zip64
|
|
2272
|
+
fz = io.BytesIO()
|
|
2273
|
+
with zipfile.ZipFile(Unseekable(fz), 'w') as zh:
|
|
2274
|
+
with zh.open('file.txt', 'w', force_zip64=True) as fh:
|
|
2275
|
+
fh.write(b'dummy')
|
|
2276
|
+
zi = zh.infolist()[-1]
|
|
2277
|
+
|
|
2278
|
+
self.assertEqual(
|
|
2279
|
+
repacker._calc_local_file_entry_size(fz, zi),
|
|
2280
|
+
(30, 8, 20, 5, 24),
|
|
2281
|
+
)
|
|
2282
|
+
|
|
2283
|
+
# data descriptor (unsigned) + zip64
|
|
2284
|
+
fz = io.BytesIO()
|
|
2285
|
+
with zipfile.ZipFile(Unseekable(fz), 'w') as zh:
|
|
2286
|
+
with mock.patch.object(struct, 'pack', side_effect=struct_pack_no_dd_sig), \
|
|
2287
|
+
zh.open('file.txt', 'w', force_zip64=True) as fh:
|
|
2288
|
+
fh.write(b'dummy')
|
|
2289
|
+
zi = zh.infolist()[-1]
|
|
2290
|
+
|
|
2291
|
+
self.assertEqual(
|
|
2292
|
+
repacker._calc_local_file_entry_size(fz, zi),
|
|
2293
|
+
(30, 8, 20, 5, 20),
|
|
2294
|
+
)
|
|
2295
|
+
|
|
2196
2296
|
def test_copy_bytes(self):
|
|
2197
2297
|
repacker = zipfile._ZipRepacker()
|
|
2198
2298
|
|
|
@@ -132,10 +132,6 @@ class TestRepack(unittest.TestCase):
|
|
|
132
132
|
zh.writestr(file, data)
|
|
133
133
|
|
|
134
134
|
with zipfile.ZipFile(f, 'a') as zh:
|
|
135
|
-
# make sure data descriptor bit is really set (by making zip file unseekable)
|
|
136
|
-
for zi in zh.infolist():
|
|
137
|
-
self.assertTrue(zi.flag_bits & 8, f'data descriptor flag not set: {zi.filename}')
|
|
138
|
-
|
|
139
135
|
zh.remove(file1)
|
|
140
136
|
zh.repack()
|
|
141
137
|
self.assertIsNone(zh.testzip())
|
|
@@ -143,6 +139,10 @@ class TestRepack(unittest.TestCase):
|
|
|
143
139
|
def test_strip_removed_large_file_with_dd_no_sig(self):
|
|
144
140
|
"""Should scan for the data descriptor (without signature) of a removed
|
|
145
141
|
large file without causing a memory issue."""
|
|
142
|
+
# Reduce data scale for this test, as it's especially slow...
|
|
143
|
+
self.datacount = 30*1024**2 // len(self.data)
|
|
144
|
+
self.allowed_memory = 200*1024
|
|
145
|
+
|
|
146
146
|
# Try the temp file. If we do TESTFN2, then it hogs
|
|
147
147
|
# gigabytes of disk space for the duration of the test.
|
|
148
148
|
with TemporaryFile() as f:
|
|
@@ -154,9 +154,6 @@ class TestRepack(unittest.TestCase):
|
|
|
154
154
|
self.assertLess(peak, self.allowed_memory)
|
|
155
155
|
|
|
156
156
|
def _test_strip_removed_large_file_with_dd_no_sig(self, f):
|
|
157
|
-
# Reduce data to 400 MiB for this test, as it's especially slow...
|
|
158
|
-
self.datacount = 400*1024**2 // len(self.data)
|
|
159
|
-
|
|
160
157
|
file = 'file.txt'
|
|
161
158
|
file1 = 'largefile.txt'
|
|
162
159
|
data = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
|
|
@@ -167,10 +164,6 @@ class TestRepack(unittest.TestCase):
|
|
|
167
164
|
zh.writestr(file, data)
|
|
168
165
|
|
|
169
166
|
with zipfile.ZipFile(f, 'a') as zh:
|
|
170
|
-
# make sure data descriptor bit is really set (by making zip file unseekable)
|
|
171
|
-
for zi in zh.infolist():
|
|
172
|
-
self.assertTrue(zi.flag_bits & 8, f'data descriptor flag not set: {zi.filename}')
|
|
173
|
-
|
|
174
167
|
zh.remove(file1)
|
|
175
168
|
zh.repack()
|
|
176
169
|
self.assertIsNone(zh.testzip())
|
|
@@ -201,10 +194,6 @@ class TestRepack(unittest.TestCase):
|
|
|
201
194
|
zh.writestr(file, data)
|
|
202
195
|
|
|
203
196
|
with zipfile.ZipFile(f, 'a') as zh:
|
|
204
|
-
# make sure data descriptor bit is really set (by making zip file unseekable)
|
|
205
|
-
for zi in zh.infolist():
|
|
206
|
-
self.assertTrue(zi.flag_bits & 8, f'data descriptor flag not set: {zi.filename}')
|
|
207
|
-
|
|
208
197
|
zh.remove(file1)
|
|
209
198
|
zh.repack()
|
|
210
199
|
self.assertIsNone(zh.testzip())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|