unzip-http 0.7__tar.gz → 0.8__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.
- {unzip_http-0.7/unzip_http.egg-info → unzip_http-0.8}/PKG-INFO +1 -1
- {unzip_http-0.7 → unzip_http-0.8}/pyproject.toml +1 -1
- unzip_http-0.8/tests/test_unzip_http.py +37 -0
- {unzip_http-0.7 → unzip_http-0.8/unzip_http.egg-info}/PKG-INFO +1 -1
- {unzip_http-0.7 → unzip_http-0.8}/unzip_http.egg-info/SOURCES.txt +1 -0
- {unzip_http-0.7 → unzip_http-0.8}/unzip_http.py +12 -3
- {unzip_http-0.7 → unzip_http-0.8}/LICENSE-mit.txt +0 -0
- {unzip_http-0.7 → unzip_http-0.8}/MANIFEST.in +0 -0
- {unzip_http-0.7 → unzip_http-0.8}/README.md +0 -0
- {unzip_http-0.7 → unzip_http-0.8}/setup.cfg +0 -0
- {unzip_http-0.7 → unzip_http-0.8}/unzip_http.egg-info/dependency_links.txt +0 -0
- {unzip_http-0.7 → unzip_http-0.8}/unzip_http.egg-info/entry_points.txt +0 -0
- {unzip_http-0.7 → unzip_http-0.8}/unzip_http.egg-info/requires.txt +0 -0
- {unzip_http-0.7 → unzip_http-0.8}/unzip_http.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import pathlib
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import unzip_http
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def fake_rzf(contents):
|
|
11
|
+
rzf = unzip_http.RemoteZipFile.__new__(unzip_http.RemoteZipFile)
|
|
12
|
+
rzf.open = lambda fn: io.BytesIO(contents)
|
|
13
|
+
return rzf
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize('member', ['../escaped.txt', '/tmp/absolute.txt', 'sub/../../escaped.txt'])
|
|
17
|
+
def test_extract_refuses_escaping_member(tmp_path, member):
|
|
18
|
+
target = tmp_path/'target'
|
|
19
|
+
target.mkdir()
|
|
20
|
+
with pytest.raises(Exception, match='unsafe path'):
|
|
21
|
+
fake_rzf(b'x').extract(member, path=target)
|
|
22
|
+
assert list(tmp_path.rglob('*')) == [target]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_extract_writes_inside_target(tmp_path):
|
|
26
|
+
fake_rzf(b'hello').extract('sub/ok.txt', path=tmp_path)
|
|
27
|
+
assert (tmp_path/'sub'/'ok.txt').read_bytes() == b'hello'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_cli_full_filepaths_refuses_escaping_member(tmp_path, monkeypatch):
|
|
31
|
+
monkeypatch.chdir(tmp_path)
|
|
32
|
+
monkeypatch.setattr(unzip_http, 'extract_one', lambda outfile, rzf, f, ofname: outfile.write(b'x'))
|
|
33
|
+
f = SimpleNamespace(filename='../escaped.txt', compress_size=1)
|
|
34
|
+
args = SimpleNamespace(files=['*'], stdout=False, full_filepaths=True)
|
|
35
|
+
with pytest.raises(Exception, match='unsafe path'):
|
|
36
|
+
unzip_http.download_file(f, None, args)
|
|
37
|
+
assert not (tmp_path.parent/'escaped.txt').exists()
|
|
@@ -56,7 +56,7 @@ import pathlib
|
|
|
56
56
|
import urllib.parse
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
__version__ = '0.
|
|
59
|
+
__version__ = '0.8'
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
def error(s):
|
|
@@ -117,6 +117,14 @@ class RemoteZipInfo:
|
|
|
117
117
|
i += fieldsz
|
|
118
118
|
|
|
119
119
|
|
|
120
|
+
def safe_extract_path(base, member):
|
|
121
|
+
base = pathlib.Path(base).resolve()
|
|
122
|
+
target = (base / member).resolve()
|
|
123
|
+
if base != target and base not in target.parents:
|
|
124
|
+
raise Exception(f'refusing unsafe path in zip member: {member!r}')
|
|
125
|
+
return target
|
|
126
|
+
|
|
127
|
+
|
|
120
128
|
class RemoteZipFile:
|
|
121
129
|
fmt_eocd = '<IHHHHIIH' # end of central directory
|
|
122
130
|
fmt_eocd64 = '<IQHHIIQQQQ' # end of central directory ZIP64
|
|
@@ -223,10 +231,10 @@ class RemoteZipFile:
|
|
|
223
231
|
|
|
224
232
|
path = path or pathlib.Path('.')
|
|
225
233
|
|
|
226
|
-
outpath = path
|
|
234
|
+
outpath = safe_extract_path(path, member)
|
|
227
235
|
os.makedirs(outpath.parent, exist_ok=True)
|
|
228
236
|
with self.open(member) as fpin:
|
|
229
|
-
with open(
|
|
237
|
+
with open(outpath, mode='wb') as fpout:
|
|
230
238
|
while True:
|
|
231
239
|
r = fpin.read(65536)
|
|
232
240
|
if not r:
|
|
@@ -354,6 +362,7 @@ def download_file(f, rzf, args):
|
|
|
354
362
|
else:
|
|
355
363
|
path = pathlib.Path(f.filename)
|
|
356
364
|
if args.full_filepaths:
|
|
365
|
+
path = safe_extract_path('.', path)
|
|
357
366
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
358
367
|
else:
|
|
359
368
|
path = path.name
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|