pylibmspack 0.1.0__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.
- pylibmspack-0.1.0/LICENSE +21 -0
- pylibmspack-0.1.0/MANIFEST.in +10 -0
- pylibmspack-0.1.0/NOTICE +3 -0
- pylibmspack-0.1.0/PKG-INFO +211 -0
- pylibmspack-0.1.0/README.md +182 -0
- pylibmspack-0.1.0/THIRD_PARTY_LICENSES/LGPL-2.1.txt +460 -0
- pylibmspack-0.1.0/pyproject.toml +53 -0
- pylibmspack-0.1.0/scripts/build_libmspack.py +227 -0
- pylibmspack-0.1.0/scripts/generate_fixtures.py +37 -0
- pylibmspack-0.1.0/setup.cfg +22 -0
- pylibmspack-0.1.0/setup.py +89 -0
- pylibmspack-0.1.0/src/pylibmspack/__init__.py +24 -0
- pylibmspack-0.1.0/src/pylibmspack/_cab.c +1145 -0
- pylibmspack-0.1.0/src/pylibmspack/_libmspack.py +11 -0
- pylibmspack-0.1.0/src/pylibmspack/cab.py +286 -0
- pylibmspack-0.1.0/src/pylibmspack/errors.py +17 -0
- pylibmspack-0.1.0/src/pylibmspack/vendor/__init__.py +1 -0
- pylibmspack-0.1.0/src/pylibmspack/vendor/libmspack_0.10.1.orig.tar.xz +0 -0
- pylibmspack-0.1.0/src/pylibmspack.egg-info/PKG-INFO +211 -0
- pylibmspack-0.1.0/src/pylibmspack.egg-info/SOURCES.txt +30 -0
- pylibmspack-0.1.0/src/pylibmspack.egg-info/dependency_links.txt +1 -0
- pylibmspack-0.1.0/src/pylibmspack.egg-info/not-zip-safe +1 -0
- pylibmspack-0.1.0/src/pylibmspack.egg-info/requires.txt +3 -0
- pylibmspack-0.1.0/src/pylibmspack.egg-info/top_level.txt +1 -0
- pylibmspack-0.1.0/tests/fixtures/expected/lzx.txt +3 -0
- pylibmspack-0.1.0/tests/fixtures/expected/mszip.txt +1 -0
- pylibmspack-0.1.0/tests/fixtures/expected/qtm.txt +1 -0
- pylibmspack-0.1.0/tests/fixtures/normal_2files_1folder.cab +0 -0
- pylibmspack-0.1.0/tests/fixtures/small_mszip.cab +0 -0
- pylibmspack-0.1.0/tests/fixtures/traversal.cab +0 -0
- pylibmspack-0.1.0/tests/test_cab.py +83 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
include README.md LICENSE NOTICE pyproject.toml setup.cfg setup.py
|
|
2
|
+
include THIRD_PARTY_LICENSES/LGPL-2.1.txt
|
|
3
|
+
recursive-include src/pylibmspack *.c *.py
|
|
4
|
+
recursive-include src/pylibmspack/.dylibs *
|
|
5
|
+
include src/pylibmspack/libmspack.dylib
|
|
6
|
+
recursive-include src/pylibmspack/vendor *.tar.xz
|
|
7
|
+
recursive-include scripts *.py
|
|
8
|
+
recursive-include tests *.py
|
|
9
|
+
recursive-include tests/fixtures *.cab
|
|
10
|
+
recursive-include tests/fixtures/expected *.txt
|
pylibmspack-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pylibmspack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: In-process libmspack bindings for Microsoft CAB files
|
|
5
|
+
Author-email: Example Author <dev@example.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bwhitn/pylibmspack
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: C
|
|
20
|
+
Classifier: Topic :: System :: Archiving
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
License-File: THIRD_PARTY_LICENSES/LGPL-2.1.txt
|
|
25
|
+
License-File: NOTICE
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# pylibmspack
|
|
31
|
+
|
|
32
|
+
`pylibmspack` provides in-process Python bindings to **libmspack** for reading and extracting Microsoft CAB files, including Quantum and LZX cabinets. It is a CPython extension (no subprocess calls).
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install pylibmspack
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Supports Python 3.9 through 3.13.
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from pylibmspack import CabArchive
|
|
46
|
+
|
|
47
|
+
cab = CabArchive("example.cab")
|
|
48
|
+
print(cab.files())
|
|
49
|
+
|
|
50
|
+
print(cab.read("hello.txt"))
|
|
51
|
+
|
|
52
|
+
cab.extract("hello.txt", "./out")
|
|
53
|
+
|
|
54
|
+
cab.extract_all("./out")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### In-memory usage
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from pylibmspack import CabArchive
|
|
61
|
+
|
|
62
|
+
data = open("example.cab", "rb").read()
|
|
63
|
+
cab = CabArchive.from_bytes(data)
|
|
64
|
+
|
|
65
|
+
info = cab.info()
|
|
66
|
+
print(info["files_count"], info["flags"])
|
|
67
|
+
|
|
68
|
+
payload = cab.read("hello.txt")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Safe vs raw extraction
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from pylibmspack import CabArchive, CabPathTraversalError
|
|
75
|
+
|
|
76
|
+
cab = CabArchive("example.cab")
|
|
77
|
+
try:
|
|
78
|
+
cab.extract_all("./out", safe=True)
|
|
79
|
+
except CabPathTraversalError as exc:
|
|
80
|
+
print("Blocked unsafe path:", exc)
|
|
81
|
+
|
|
82
|
+
# Raw extraction (no safety checks)
|
|
83
|
+
cab.extract_all_raw("./out-raw")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Multi-cabinet sets
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from pylibmspack import CabArchive
|
|
90
|
+
|
|
91
|
+
cab = CabArchive("part1.cab")
|
|
92
|
+
info = cab.info()
|
|
93
|
+
|
|
94
|
+
if info["has_next"]:
|
|
95
|
+
print("Next cabinet:", info["next_cabinet"])
|
|
96
|
+
print("Disk label:", info["next_disk"])
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### FAQ / troubleshooting
|
|
100
|
+
|
|
101
|
+
**Why do I get `CabPathTraversalError`?**
|
|
102
|
+
The archive contains absolute paths or `..` segments. Use `safe=False` only if you trust the archive contents.
|
|
103
|
+
|
|
104
|
+
**Can I read from bytes instead of a file path?**
|
|
105
|
+
Yes. Use `CabArchive.from_bytes(data)` and then call `files()`, `read()`, or `info()`.
|
|
106
|
+
|
|
107
|
+
**Why does extraction fail with `CabDecompressionError`?**
|
|
108
|
+
The CAB may be corrupt, truncated, or uses an unsupported compression method.
|
|
109
|
+
|
|
110
|
+
## API reference
|
|
111
|
+
|
|
112
|
+
### CabArchive(path: str)
|
|
113
|
+
|
|
114
|
+
Open a CAB archive on disk.
|
|
115
|
+
|
|
116
|
+
### CabArchive.files() -> list[CabFileInfo]
|
|
117
|
+
|
|
118
|
+
Return metadata for each member as a `CabFileInfo` TypedDict. Each entry includes:
|
|
119
|
+
|
|
120
|
+
- `name` (str)
|
|
121
|
+
- `size` (int)
|
|
122
|
+
- `dos_date` (int)
|
|
123
|
+
- `dos_time` (int)
|
|
124
|
+
- `date_y` / `date_m` / `date_d` (int)
|
|
125
|
+
- `time_h` / `time_m` / `time_s` (int)
|
|
126
|
+
- `datetime_utc` (str, ISO 8601)
|
|
127
|
+
- `attrs` (int)
|
|
128
|
+
- `is_readonly` / `is_hidden` / `is_system` / `is_archive` (bool)
|
|
129
|
+
- `folder_index` (int)
|
|
130
|
+
- `offset` (int)
|
|
131
|
+
- `compression` (str: `none`, `mszip`, `quantum`, `lzx`)
|
|
132
|
+
- `has_prev` / `has_next` (bool)
|
|
133
|
+
- `prev_cabinet` / `next_cabinet` (str | None)
|
|
134
|
+
- `cabinet_set_id` / `cabinet_set_index` (int | None)
|
|
135
|
+
|
|
136
|
+
### CabArchive.read(name: str, *, max_size: int = 256*1024*1024) -> bytes
|
|
137
|
+
|
|
138
|
+
Extract a member and return its bytes. Enforces a `max_size` limit and uses safe path validation.
|
|
139
|
+
|
|
140
|
+
### CabArchive.extract(name: str, dest_dir: str, *, safe: bool = True) -> str
|
|
141
|
+
|
|
142
|
+
Extract a member to disk and return the output path. When `safe=True`, absolute paths and traversal are rejected.
|
|
143
|
+
|
|
144
|
+
### CabArchive.extract_all(dest_dir: str, *, safe: bool = True) -> list[str]
|
|
145
|
+
|
|
146
|
+
Extract all members to disk and return output paths.
|
|
147
|
+
|
|
148
|
+
### CabArchive.extract_raw(name: str, dest_dir: str) -> str
|
|
149
|
+
|
|
150
|
+
Extract a member using the raw path (no safety checks).
|
|
151
|
+
|
|
152
|
+
### CabArchive.extract_all_raw(dest_dir: str) -> list[str]
|
|
153
|
+
|
|
154
|
+
Extract all members using raw paths (no safety checks).
|
|
155
|
+
|
|
156
|
+
### CabArchive.from_bytes(data: bytes) -> CabArchive
|
|
157
|
+
|
|
158
|
+
Create an archive backed by in-memory bytes instead of a file path.
|
|
159
|
+
|
|
160
|
+
### CabArchive.info() -> CabInfo
|
|
161
|
+
|
|
162
|
+
Return parsed CAB header metadata. The `CabInfo` dict includes:
|
|
163
|
+
|
|
164
|
+
- `filename` (str | None)
|
|
165
|
+
- `base_offset` (int)
|
|
166
|
+
- `length` (int)
|
|
167
|
+
- `set_id` (int)
|
|
168
|
+
- `set_index` (int)
|
|
169
|
+
- `header_resv` (int)
|
|
170
|
+
- `flags` (int)
|
|
171
|
+
- `has_prev` / `has_next` (bool)
|
|
172
|
+
- `prev_cabinet` / `next_cabinet` (str | None)
|
|
173
|
+
- `prev_disk` / `next_disk` (str | None)
|
|
174
|
+
- `files_count` (int)
|
|
175
|
+
- `folders_count` (int)
|
|
176
|
+
|
|
177
|
+
### Exceptions
|
|
178
|
+
|
|
179
|
+
All errors derive from `CabError`:
|
|
180
|
+
|
|
181
|
+
- `CabError`
|
|
182
|
+
- `CabFormatError`
|
|
183
|
+
- `CabDecompressionError`
|
|
184
|
+
- `CabPathTraversalError`
|
|
185
|
+
|
|
186
|
+
## Safe extraction
|
|
187
|
+
|
|
188
|
+
By default, `extract()` and `extract_all()` reject:
|
|
189
|
+
- absolute paths (`/`, `\`, drive letters, UNC paths)
|
|
190
|
+
- path traversal (`..` after normalization)
|
|
191
|
+
- mixed or odd separators (`/` and `\` are normalized)
|
|
192
|
+
|
|
193
|
+
Use `safe=False` to allow the original paths.
|
|
194
|
+
|
|
195
|
+
## Build from source
|
|
196
|
+
|
|
197
|
+
This project uses setuptools and builds a shared `libmspack` that is bundled into wheels. A pinned libmspack source tarball is included under `pylibmspack/vendor/` and used for offline builds (SHA-256 verified).
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
python -m pip install -U pip setuptools wheel
|
|
201
|
+
python -m pip install -e .
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
If you want to supply a local tarball, pass `--tarball` to `scripts/build_libmspack.py`. To allow a network download during builds, set `PYLIBMSPACK_ALLOW_DOWNLOAD=1` (disabled by default).
|
|
205
|
+
|
|
206
|
+
## Licensing
|
|
207
|
+
|
|
208
|
+
- **pylibmspack** code is MIT licensed.
|
|
209
|
+
- Wheels bundle **libmspack** under LGPL-2.1. The corresponding libmspack source tarball is included under `pylibmspack/vendor/`. You may replace the shared library inside `pylibmspack/.libs` with a compatible build.
|
|
210
|
+
|
|
211
|
+
See `THIRD_PARTY_LICENSES/LGPL-2.1.txt` and `NOTICE` for details.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# pylibmspack
|
|
2
|
+
|
|
3
|
+
`pylibmspack` provides in-process Python bindings to **libmspack** for reading and extracting Microsoft CAB files, including Quantum and LZX cabinets. It is a CPython extension (no subprocess calls).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install pylibmspack
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Supports Python 3.9 through 3.13.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from pylibmspack import CabArchive
|
|
17
|
+
|
|
18
|
+
cab = CabArchive("example.cab")
|
|
19
|
+
print(cab.files())
|
|
20
|
+
|
|
21
|
+
print(cab.read("hello.txt"))
|
|
22
|
+
|
|
23
|
+
cab.extract("hello.txt", "./out")
|
|
24
|
+
|
|
25
|
+
cab.extract_all("./out")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### In-memory usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from pylibmspack import CabArchive
|
|
32
|
+
|
|
33
|
+
data = open("example.cab", "rb").read()
|
|
34
|
+
cab = CabArchive.from_bytes(data)
|
|
35
|
+
|
|
36
|
+
info = cab.info()
|
|
37
|
+
print(info["files_count"], info["flags"])
|
|
38
|
+
|
|
39
|
+
payload = cab.read("hello.txt")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Safe vs raw extraction
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from pylibmspack import CabArchive, CabPathTraversalError
|
|
46
|
+
|
|
47
|
+
cab = CabArchive("example.cab")
|
|
48
|
+
try:
|
|
49
|
+
cab.extract_all("./out", safe=True)
|
|
50
|
+
except CabPathTraversalError as exc:
|
|
51
|
+
print("Blocked unsafe path:", exc)
|
|
52
|
+
|
|
53
|
+
# Raw extraction (no safety checks)
|
|
54
|
+
cab.extract_all_raw("./out-raw")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Multi-cabinet sets
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from pylibmspack import CabArchive
|
|
61
|
+
|
|
62
|
+
cab = CabArchive("part1.cab")
|
|
63
|
+
info = cab.info()
|
|
64
|
+
|
|
65
|
+
if info["has_next"]:
|
|
66
|
+
print("Next cabinet:", info["next_cabinet"])
|
|
67
|
+
print("Disk label:", info["next_disk"])
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### FAQ / troubleshooting
|
|
71
|
+
|
|
72
|
+
**Why do I get `CabPathTraversalError`?**
|
|
73
|
+
The archive contains absolute paths or `..` segments. Use `safe=False` only if you trust the archive contents.
|
|
74
|
+
|
|
75
|
+
**Can I read from bytes instead of a file path?**
|
|
76
|
+
Yes. Use `CabArchive.from_bytes(data)` and then call `files()`, `read()`, or `info()`.
|
|
77
|
+
|
|
78
|
+
**Why does extraction fail with `CabDecompressionError`?**
|
|
79
|
+
The CAB may be corrupt, truncated, or uses an unsupported compression method.
|
|
80
|
+
|
|
81
|
+
## API reference
|
|
82
|
+
|
|
83
|
+
### CabArchive(path: str)
|
|
84
|
+
|
|
85
|
+
Open a CAB archive on disk.
|
|
86
|
+
|
|
87
|
+
### CabArchive.files() -> list[CabFileInfo]
|
|
88
|
+
|
|
89
|
+
Return metadata for each member as a `CabFileInfo` TypedDict. Each entry includes:
|
|
90
|
+
|
|
91
|
+
- `name` (str)
|
|
92
|
+
- `size` (int)
|
|
93
|
+
- `dos_date` (int)
|
|
94
|
+
- `dos_time` (int)
|
|
95
|
+
- `date_y` / `date_m` / `date_d` (int)
|
|
96
|
+
- `time_h` / `time_m` / `time_s` (int)
|
|
97
|
+
- `datetime_utc` (str, ISO 8601)
|
|
98
|
+
- `attrs` (int)
|
|
99
|
+
- `is_readonly` / `is_hidden` / `is_system` / `is_archive` (bool)
|
|
100
|
+
- `folder_index` (int)
|
|
101
|
+
- `offset` (int)
|
|
102
|
+
- `compression` (str: `none`, `mszip`, `quantum`, `lzx`)
|
|
103
|
+
- `has_prev` / `has_next` (bool)
|
|
104
|
+
- `prev_cabinet` / `next_cabinet` (str | None)
|
|
105
|
+
- `cabinet_set_id` / `cabinet_set_index` (int | None)
|
|
106
|
+
|
|
107
|
+
### CabArchive.read(name: str, *, max_size: int = 256*1024*1024) -> bytes
|
|
108
|
+
|
|
109
|
+
Extract a member and return its bytes. Enforces a `max_size` limit and uses safe path validation.
|
|
110
|
+
|
|
111
|
+
### CabArchive.extract(name: str, dest_dir: str, *, safe: bool = True) -> str
|
|
112
|
+
|
|
113
|
+
Extract a member to disk and return the output path. When `safe=True`, absolute paths and traversal are rejected.
|
|
114
|
+
|
|
115
|
+
### CabArchive.extract_all(dest_dir: str, *, safe: bool = True) -> list[str]
|
|
116
|
+
|
|
117
|
+
Extract all members to disk and return output paths.
|
|
118
|
+
|
|
119
|
+
### CabArchive.extract_raw(name: str, dest_dir: str) -> str
|
|
120
|
+
|
|
121
|
+
Extract a member using the raw path (no safety checks).
|
|
122
|
+
|
|
123
|
+
### CabArchive.extract_all_raw(dest_dir: str) -> list[str]
|
|
124
|
+
|
|
125
|
+
Extract all members using raw paths (no safety checks).
|
|
126
|
+
|
|
127
|
+
### CabArchive.from_bytes(data: bytes) -> CabArchive
|
|
128
|
+
|
|
129
|
+
Create an archive backed by in-memory bytes instead of a file path.
|
|
130
|
+
|
|
131
|
+
### CabArchive.info() -> CabInfo
|
|
132
|
+
|
|
133
|
+
Return parsed CAB header metadata. The `CabInfo` dict includes:
|
|
134
|
+
|
|
135
|
+
- `filename` (str | None)
|
|
136
|
+
- `base_offset` (int)
|
|
137
|
+
- `length` (int)
|
|
138
|
+
- `set_id` (int)
|
|
139
|
+
- `set_index` (int)
|
|
140
|
+
- `header_resv` (int)
|
|
141
|
+
- `flags` (int)
|
|
142
|
+
- `has_prev` / `has_next` (bool)
|
|
143
|
+
- `prev_cabinet` / `next_cabinet` (str | None)
|
|
144
|
+
- `prev_disk` / `next_disk` (str | None)
|
|
145
|
+
- `files_count` (int)
|
|
146
|
+
- `folders_count` (int)
|
|
147
|
+
|
|
148
|
+
### Exceptions
|
|
149
|
+
|
|
150
|
+
All errors derive from `CabError`:
|
|
151
|
+
|
|
152
|
+
- `CabError`
|
|
153
|
+
- `CabFormatError`
|
|
154
|
+
- `CabDecompressionError`
|
|
155
|
+
- `CabPathTraversalError`
|
|
156
|
+
|
|
157
|
+
## Safe extraction
|
|
158
|
+
|
|
159
|
+
By default, `extract()` and `extract_all()` reject:
|
|
160
|
+
- absolute paths (`/`, `\`, drive letters, UNC paths)
|
|
161
|
+
- path traversal (`..` after normalization)
|
|
162
|
+
- mixed or odd separators (`/` and `\` are normalized)
|
|
163
|
+
|
|
164
|
+
Use `safe=False` to allow the original paths.
|
|
165
|
+
|
|
166
|
+
## Build from source
|
|
167
|
+
|
|
168
|
+
This project uses setuptools and builds a shared `libmspack` that is bundled into wheels. A pinned libmspack source tarball is included under `pylibmspack/vendor/` and used for offline builds (SHA-256 verified).
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
python -m pip install -U pip setuptools wheel
|
|
172
|
+
python -m pip install -e .
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
If you want to supply a local tarball, pass `--tarball` to `scripts/build_libmspack.py`. To allow a network download during builds, set `PYLIBMSPACK_ALLOW_DOWNLOAD=1` (disabled by default).
|
|
176
|
+
|
|
177
|
+
## Licensing
|
|
178
|
+
|
|
179
|
+
- **pylibmspack** code is MIT licensed.
|
|
180
|
+
- Wheels bundle **libmspack** under LGPL-2.1. The corresponding libmspack source tarball is included under `pylibmspack/vendor/`. You may replace the shared library inside `pylibmspack/.libs` with a compatible build.
|
|
181
|
+
|
|
182
|
+
See `THIRD_PARTY_LICENSES/LGPL-2.1.txt` and `NOTICE` for details.
|