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.
@@ -1,15 +1,17 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: unzip-http
3
- Version: 0.6
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
- Keywords: http zip unzip
8
- Classifier: Development Status :: 4 - Beta
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.8
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 to the current directly to the current directory)
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 to the current directly to the current directory)
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
1
+ Metadata-Version: 2.4
2
2
  Name: unzip-http
3
- Version: 0.6
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
- Keywords: http zip unzip
8
- Classifier: Development Status :: 4 - Beta
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.8
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 to the current directly to the current directory)
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)
@@ -1,9 +1,9 @@
1
1
  LICENSE-mit.txt
2
2
  MANIFEST.in
3
3
  README.md
4
- requirements.txt
5
- setup.py
4
+ pyproject.toml
6
5
  unzip_http.py
6
+ tests/test_unzip_http.py
7
7
  unzip_http.egg-info/PKG-INFO
8
8
  unzip_http.egg-info/SOURCES.txt
9
9
  unzip_http.egg-info/dependency_links.txt
@@ -56,7 +56,7 @@ import pathlib
56
56
  import urllib.parse
57
57
 
58
58
 
59
- __version__ = '0.6'
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/member
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(path/member, mode='wb') as fpout:
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
@@ -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