unzip-http 0.6__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.6/unzip_http.egg-info → unzip_http-0.8}/PKG-INFO +14 -7
- {unzip-http-0.6 → unzip_http-0.8}/README.md +6 -1
- unzip_http-0.8/pyproject.toml +26 -0
- unzip_http-0.8/tests/test_unzip_http.py +37 -0
- {unzip-http-0.6 → unzip_http-0.8/unzip_http.egg-info}/PKG-INFO +14 -7
- {unzip-http-0.6 → unzip_http-0.8}/unzip_http.egg-info/SOURCES.txt +2 -2
- {unzip-http-0.6 → unzip_http-0.8}/unzip_http.py +18 -3
- unzip-http-0.6/requirements.txt +0 -1
- unzip-http-0.6/setup.py +0 -35
- {unzip-http-0.6 → unzip_http-0.8}/LICENSE-mit.txt +0 -0
- {unzip-http-0.6 → unzip_http-0.8}/MANIFEST.in +0 -0
- {unzip-http-0.6 → unzip_http-0.8}/setup.cfg +0 -0
- {unzip-http-0.6 → unzip_http-0.8}/unzip_http.egg-info/dependency_links.txt +0 -0
- {unzip-http-0.6 → unzip_http-0.8}/unzip_http.egg-info/entry_points.txt +0 -0
- {unzip-http-0.6 → unzip_http-0.8}/unzip_http.egg-info/requires.txt +0 -0
- {unzip-http-0.6 → unzip_http-0.8}/unzip_http.egg-info/top_level.txt +0 -0
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: unzip-http
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8
|
|
4
4
|
Summary: extract files from .zip files over http without downloading entire archive
|
|
5
|
-
Home-page: https://github.com/saulpw/unzip-http
|
|
6
5
|
Author: Saul Pwanson
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Project-URL: Homepage, https://github.com/saulpw/unzip-http
|
|
7
|
+
Keywords: http,zip,unzip
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE-mit.txt
|
|
13
|
+
Requires-Dist: urllib3
|
|
14
|
+
Dynamic: license-file
|
|
13
15
|
|
|
14
16
|
# unzip-http
|
|
15
17
|
|
|
@@ -34,7 +36,7 @@ Note: HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in heade
|
|
|
34
36
|
Options:
|
|
35
37
|
|
|
36
38
|
- `-l`: List files in remote .zip file (default if no filenames given)
|
|
37
|
-
- `-f`: Recreate folder structure from .zip file when extracting (instead of extracting files
|
|
39
|
+
- `-f`: Recreate folder structure from .zip file when extracting (instead of extracting files directly to the current directory)
|
|
38
40
|
- `-o`: Write files to stdout (if multiple files, concatenate them in zipfile order)
|
|
39
41
|
|
|
40
42
|
# Python module `unzip_http`
|
|
@@ -48,3 +50,8 @@ Options:
|
|
|
48
50
|
# Credits
|
|
49
51
|
|
|
50
52
|
`unzip-http` was written by [Saul Pwanson](https://saul.pw) and made available for use under the MIT License.
|
|
53
|
+
|
|
54
|
+
# Similar Libraries
|
|
55
|
+
|
|
56
|
+
- [python-remotezip](https://github.com/gtsystem/python-remotezip)
|
|
57
|
+
- [PyRemoteZip](https://github.com/fcvarela/pyremotezip)
|
|
@@ -21,7 +21,7 @@ Note: HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in heade
|
|
|
21
21
|
Options:
|
|
22
22
|
|
|
23
23
|
- `-l`: List files in remote .zip file (default if no filenames given)
|
|
24
|
-
- `-f`: Recreate folder structure from .zip file when extracting (instead of extracting files
|
|
24
|
+
- `-f`: Recreate folder structure from .zip file when extracting (instead of extracting files directly to the current directory)
|
|
25
25
|
- `-o`: Write files to stdout (if multiple files, concatenate them in zipfile order)
|
|
26
26
|
|
|
27
27
|
# Python module `unzip_http`
|
|
@@ -35,3 +35,8 @@ Options:
|
|
|
35
35
|
# Credits
|
|
36
36
|
|
|
37
37
|
`unzip-http` was written by [Saul Pwanson](https://saul.pw) and made available for use under the MIT License.
|
|
38
|
+
|
|
39
|
+
# Similar Libraries
|
|
40
|
+
|
|
41
|
+
- [python-remotezip](https://github.com/gtsystem/python-remotezip)
|
|
42
|
+
- [PyRemoteZip](https://github.com/fcvarela/pyremotezip)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "unzip-http"
|
|
7
|
+
version = "0.8"
|
|
8
|
+
description = "extract files from .zip files over http without downloading entire archive"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
keywords = ["http", "zip", "unzip"]
|
|
12
|
+
authors = [{name = "Saul Pwanson"}]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 5 - Production/Stable",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
]
|
|
17
|
+
dependencies = ["urllib3"]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/saulpw/unzip-http"
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
unzip_http = "unzip_http:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
py-modules = ["unzip_http"]
|
|
@@ -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()
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: unzip-http
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8
|
|
4
4
|
Summary: extract files from .zip files over http without downloading entire archive
|
|
5
|
-
Home-page: https://github.com/saulpw/unzip-http
|
|
6
5
|
Author: Saul Pwanson
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Project-URL: Homepage, https://github.com/saulpw/unzip-http
|
|
7
|
+
Keywords: http,zip,unzip
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE-mit.txt
|
|
13
|
+
Requires-Dist: urllib3
|
|
14
|
+
Dynamic: license-file
|
|
13
15
|
|
|
14
16
|
# unzip-http
|
|
15
17
|
|
|
@@ -34,7 +36,7 @@ Note: HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in heade
|
|
|
34
36
|
Options:
|
|
35
37
|
|
|
36
38
|
- `-l`: List files in remote .zip file (default if no filenames given)
|
|
37
|
-
- `-f`: Recreate folder structure from .zip file when extracting (instead of extracting files
|
|
39
|
+
- `-f`: Recreate folder structure from .zip file when extracting (instead of extracting files directly to the current directory)
|
|
38
40
|
- `-o`: Write files to stdout (if multiple files, concatenate them in zipfile order)
|
|
39
41
|
|
|
40
42
|
# Python module `unzip_http`
|
|
@@ -48,3 +50,8 @@ Options:
|
|
|
48
50
|
# Credits
|
|
49
51
|
|
|
50
52
|
`unzip-http` was written by [Saul Pwanson](https://saul.pw) and made available for use under the MIT License.
|
|
53
|
+
|
|
54
|
+
# Similar Libraries
|
|
55
|
+
|
|
56
|
+
- [python-remotezip](https://github.com/gtsystem/python-remotezip)
|
|
57
|
+
- [PyRemoteZip](https://github.com/fcvarela/pyremotezip)
|
|
@@ -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
|
|
@@ -151,11 +159,17 @@ class RemoteZipFile:
|
|
|
151
159
|
|
|
152
160
|
def infoiter(self):
|
|
153
161
|
resp = self.http.request('HEAD', self.url)
|
|
162
|
+
if not (200 <= resp.status <= 299):
|
|
163
|
+
error(f'cannot open URL: HTTP status {resp.status}')
|
|
164
|
+
|
|
154
165
|
r = resp.headers.get('Accept-Ranges', '')
|
|
155
166
|
if r != 'bytes':
|
|
156
167
|
hostname = urllib.parse.urlparse(self.url).netloc
|
|
157
168
|
warning(f"{hostname} Accept-Ranges header ('{r}') is not 'bytes'--trying anyway")
|
|
158
169
|
|
|
170
|
+
if 'Content-Length' not in resp.headers:
|
|
171
|
+
error('cannot open URL: missing Content-Length header')
|
|
172
|
+
|
|
159
173
|
self.zip_size = int(resp.headers['Content-Length'])
|
|
160
174
|
resp = self.get_range(
|
|
161
175
|
max(self.zip_size-65536, 0),
|
|
@@ -217,10 +231,10 @@ class RemoteZipFile:
|
|
|
217
231
|
|
|
218
232
|
path = path or pathlib.Path('.')
|
|
219
233
|
|
|
220
|
-
outpath = path
|
|
234
|
+
outpath = safe_extract_path(path, member)
|
|
221
235
|
os.makedirs(outpath.parent, exist_ok=True)
|
|
222
236
|
with self.open(member) as fpin:
|
|
223
|
-
with open(
|
|
237
|
+
with open(outpath, mode='wb') as fpout:
|
|
224
238
|
while True:
|
|
225
239
|
r = fpin.read(65536)
|
|
226
240
|
if not r:
|
|
@@ -348,6 +362,7 @@ def download_file(f, rzf, args):
|
|
|
348
362
|
else:
|
|
349
363
|
path = pathlib.Path(f.filename)
|
|
350
364
|
if args.full_filepaths:
|
|
365
|
+
path = safe_extract_path('.', path)
|
|
351
366
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
352
367
|
else:
|
|
353
368
|
path = path.name
|
unzip-http-0.6/requirements.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
urllib3
|
unzip-http-0.6/setup.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
|
|
3
|
-
from setuptools import setup
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def readme():
|
|
7
|
-
with open("README.md") as f:
|
|
8
|
-
return f.read()
|
|
9
|
-
|
|
10
|
-
def requirements():
|
|
11
|
-
with open("requirements.txt") as f:
|
|
12
|
-
return f.read().split("\n")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
setup(
|
|
16
|
-
name="unzip-http",
|
|
17
|
-
version="0.6",
|
|
18
|
-
description="extract files from .zip files over http without downloading entire archive",
|
|
19
|
-
long_description=readme(),
|
|
20
|
-
long_description_content_type="text/markdown",
|
|
21
|
-
classifiers=[
|
|
22
|
-
"Development Status :: 4 - Beta",
|
|
23
|
-
"Programming Language :: Python :: 3",
|
|
24
|
-
],
|
|
25
|
-
keywords="http zip unzip",
|
|
26
|
-
author="Saul Pwanson",
|
|
27
|
-
url="https://github.com/saulpw/unzip-http",
|
|
28
|
-
python_requires=">=3.8",
|
|
29
|
-
py_modules=["unzip_http"],
|
|
30
|
-
scripts=["unzip_http.py"],
|
|
31
|
-
entry_points={
|
|
32
|
-
"console_scripts": ["unzip_http=unzip_http:main"],
|
|
33
|
-
},
|
|
34
|
-
install_requires=requirements(),
|
|
35
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|