|
13 | 13 | from collections.abc import Mapping, Set |
14 | 14 | from email.message import Message |
15 | 15 | from email.policy import EmailPolicy |
16 | | -from pathlib import Path, PurePosixPath |
| 16 | +from pathlib import Path |
17 | 17 | from zipfile import ZipInfo |
18 | 18 |
|
19 | 19 | import packaging.utils |
@@ -72,8 +72,8 @@ class WheelWriter: |
72 | 72 | tags: Set[Tag] |
73 | 73 | wheel_metadata = WheelMetadata(root_is_purelib=False) |
74 | 74 | buildver: str = "" |
75 | | - zipfile: zipfile.ZipFile | None = None |
76 | 75 | license_files: Mapping[Path, bytes] = dataclasses.field(default_factory=dict) |
| 76 | + _zipfile: zipfile.ZipFile | None = None |
77 | 77 |
|
78 | 78 | @property |
79 | 79 | def name_ver(self) -> str: |
@@ -155,56 +155,69 @@ def build(self, wheel_dirs: dict[str, Path]) -> None: |
155 | 155 | if filename.is_file() and not is_in_dist_info and not is_python_cache: |
156 | 156 | relpath = filename.relative_to(path) |
157 | 157 | target = Path(data_dir) / key / relpath if key else relpath |
158 | | - # Zipfiles require Posix paths for the arcname |
159 | | - self.write(str(filename), str(PurePosixPath(target))) |
| 158 | + self.write(str(filename), str(target)) |
160 | 159 |
|
161 | 160 | dist_info_contents = self.dist_info_contents() |
162 | 161 | for key, data in dist_info_contents.items(): |
163 | 162 | self.writestr(f"{self.dist_info}/{key}", data) |
164 | 163 |
|
165 | 164 | def write(self, filename: str, arcname: str | None = None) -> None: |
166 | | - """Write a file to the archive.""" |
| 165 | + """Write a file to the archive. Paths are normalized to Posix paths.""" |
167 | 166 |
|
168 | 167 | with Path(filename).open("rb") as f: |
169 | 168 | st = os.fstat(f.fileno()) |
170 | 169 | data = f.read() |
171 | | - zinfo = ZipInfo(arcname or str(filename), date_time=self.timestamp(st.st_mtime)) |
| 170 | + |
| 171 | + # Zipfiles require Posix paths for the arcname |
| 172 | + zinfo = ZipInfo( |
| 173 | + (arcname or filename).replace("\\", "/"), |
| 174 | + date_time=self.timestamp(st.st_mtime), |
| 175 | + ) |
172 | 176 | zinfo.compress_type = zipfile.ZIP_DEFLATED |
173 | 177 | zinfo.external_attr = (stat.S_IMODE(st.st_mode) | stat.S_IFMT(st.st_mode)) << 16 |
174 | 178 | self.writestr(zinfo, data) |
175 | 179 |
|
176 | 180 | def writestr(self, zinfo_or_arcname: str | ZipInfo, data: bytes) -> None: |
177 | 181 | """Write bytes (not strings) to the archive.""" |
178 | 182 | assert isinstance(data, bytes) |
179 | | - assert self.zipfile is not None |
| 183 | + assert self._zipfile is not None |
180 | 184 | if isinstance(zinfo_or_arcname, zipfile.ZipInfo): |
181 | 185 | zinfo = zinfo_or_arcname |
182 | 186 | else: |
183 | | - zinfo = zipfile.ZipInfo(zinfo_or_arcname, date_time=self.timestamp()) |
| 187 | + zinfo = zipfile.ZipInfo( |
| 188 | + zinfo_or_arcname.replace("\\", "/"), |
| 189 | + date_time=self.timestamp(), |
| 190 | + ) |
184 | 191 | zinfo.compress_type = zipfile.ZIP_DEFLATED |
185 | 192 | zinfo.external_attr = (0o664 | stat.S_IFREG) << 16 |
186 | | - self.zipfile.writestr(zinfo, data) |
| 193 | + assert ( |
| 194 | + "\\" not in zinfo.filename |
| 195 | + ), f"\\ not supported in zip; got {zinfo.filename!r}" |
| 196 | + self._zipfile.writestr(zinfo, data) |
187 | 197 |
|
188 | 198 | def __enter__(self) -> Self: |
189 | 199 | if not self.wheelpath.parent.exists(): |
190 | 200 | self.wheelpath.parent.mkdir(parents=True) |
191 | 201 |
|
192 | | - self.zipfile = zipfile.ZipFile( |
| 202 | + self._zipfile = zipfile.ZipFile( |
193 | 203 | self.wheelpath, "w", compression=zipfile.ZIP_DEFLATED |
194 | 204 | ) |
195 | 205 | return self |
196 | 206 |
|
197 | 207 | def __exit__(self, *args: object) -> None: |
198 | | - assert self.zipfile is not None |
| 208 | + assert self._zipfile is not None |
199 | 209 | record = f"{self.dist_info}/RECORD" |
200 | 210 | data = io.StringIO() |
201 | 211 | writer = csv.writer(data, delimiter=",", quotechar='"', lineterminator="\n") |
202 | | - for member in self.zipfile.infolist(): |
203 | | - with self.zipfile.open(member) as f: |
| 212 | + for member in self._zipfile.infolist(): |
| 213 | + assert ( |
| 214 | + "\\" not in member.filename |
| 215 | + ), f"Invalid zip contents: {member.filename}" |
| 216 | + with self._zipfile.open(member) as f: |
204 | 217 | member_data = f.read() |
205 | 218 | sha = _b64encode(hashlib.sha256(member_data).digest()).decode("ascii") |
206 | 219 | writer.writerow((member.filename, f"sha256={sha}", member.file_size)) |
207 | 220 | writer.writerow((record, "", "")) |
208 | 221 | self.writestr(record, data.getvalue().encode("utf-8")) |
209 | | - self.zipfile.close() |
210 | | - self.zipfile = None |
| 222 | + self._zipfile.close() |
| 223 | + self._zipfile = None |
0 commit comments