| 456 | # Python 3 also takes PathLike[str] for the path arg, but we only ever pass str since we support |
| 457 | # Python 2.7 and don't use pathlib as a result. |
| 458 | def extractall( # type: ignore[override] |
| 459 | self, |
| 460 | path=None, # type: Optional[str] |
| 461 | members=None, # type: Optional[Iterable[Union[str, ZipInfo]]] |
| 462 | pwd=None, # type: Optional[bytes] |
| 463 | ): |
| 464 | # type: (...) -> None |
| 465 | if sys.version_info[0] != 2: |
| 466 | return super(ZipFileEx, self).extractall(path=path, members=members, pwd=pwd) |
| 467 | |
| 468 | # Under Python 2.7, ZipFile does not handle Zip entry name encoding correctly. Older Zip |
| 469 | # standards supported IBM code page 437 and newer support UTF-8. The newer support is |
| 470 | # indicated by the bit 11 flag in the file header. |
| 471 | # From https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT section |
| 472 | # "4.4.4 general purpose bit flag: (2 bytes)": |
| 473 | # |
| 474 | # Bit 11: Language encoding flag (EFS). If this bit is set, |
| 475 | # the filename and comment fields for this file |
| 476 | # MUST be encoded using UTF-8. (see APPENDIX D) |
| 477 | # |
| 478 | # N.B.: MyPy fails to see this code can be reached for Python 2.7. |
| 479 | efs_bit = 1 << 11 # type: ignore[unreachable] |
| 480 | |
| 481 | target_path = path or os.getcwd() |
| 482 | for member in members or self.infolist(): |
| 483 | info = member if isinstance(member, ZipInfo) else self.getinfo(member) |
| 484 | encoding = "utf-8" if info.flag_bits & efs_bit else "cp437" |
| 485 | member_path = info.filename.encode(encoding) |
| 486 | target = target_path.encode(encoding) |
| 487 | |
| 488 | rel_dir = os.path.dirname(member_path) |
| 489 | abs_dir = os.path.join(target, rel_dir) |
| 490 | abs_path = os.path.join(abs_dir, os.path.basename(member_path)) |
| 491 | if member_path.endswith(b"/"): |
| 492 | safe_mkdir(abs_path) |
| 493 | else: |
| 494 | safe_mkdir(abs_dir) |
| 495 | with open(abs_path, "wb") as tfp, self.open(info) as zf_entry: |
| 496 | shutil.copyfileobj(zf_entry, tfp) |
| 497 | self._chmod(info, abs_path) |
| 498 | |
| 499 | |
| 500 | @contextlib.contextmanager |