17db96d56Sopenharmony_ci"""
27db96d56Sopenharmony_ciRead and write ZIP files.
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_ciXXX references to utf-8 need further investigation.
57db96d56Sopenharmony_ci"""
67db96d56Sopenharmony_ciimport binascii
77db96d56Sopenharmony_ciimport importlib.util
87db96d56Sopenharmony_ciimport io
97db96d56Sopenharmony_ciimport itertools
107db96d56Sopenharmony_ciimport os
117db96d56Sopenharmony_ciimport posixpath
127db96d56Sopenharmony_ciimport re
137db96d56Sopenharmony_ciimport shutil
147db96d56Sopenharmony_ciimport stat
157db96d56Sopenharmony_ciimport struct
167db96d56Sopenharmony_ciimport sys
177db96d56Sopenharmony_ciimport threading
187db96d56Sopenharmony_ciimport time
197db96d56Sopenharmony_ciimport contextlib
207db96d56Sopenharmony_ciimport pathlib
217db96d56Sopenharmony_ci
227db96d56Sopenharmony_citry:
237db96d56Sopenharmony_ci    import zlib # We may need its compression method
247db96d56Sopenharmony_ci    crc32 = zlib.crc32
257db96d56Sopenharmony_ciexcept ImportError:
267db96d56Sopenharmony_ci    zlib = None
277db96d56Sopenharmony_ci    crc32 = binascii.crc32
287db96d56Sopenharmony_ci
297db96d56Sopenharmony_citry:
307db96d56Sopenharmony_ci    import bz2 # We may need its compression method
317db96d56Sopenharmony_ciexcept ImportError:
327db96d56Sopenharmony_ci    bz2 = None
337db96d56Sopenharmony_ci
347db96d56Sopenharmony_citry:
357db96d56Sopenharmony_ci    import lzma # We may need its compression method
367db96d56Sopenharmony_ciexcept ImportError:
377db96d56Sopenharmony_ci    lzma = None
387db96d56Sopenharmony_ci
397db96d56Sopenharmony_ci__all__ = ["BadZipFile", "BadZipfile", "error",
407db96d56Sopenharmony_ci           "ZIP_STORED", "ZIP_DEFLATED", "ZIP_BZIP2", "ZIP_LZMA",
417db96d56Sopenharmony_ci           "is_zipfile", "ZipInfo", "ZipFile", "PyZipFile", "LargeZipFile",
427db96d56Sopenharmony_ci           "Path"]
437db96d56Sopenharmony_ci
447db96d56Sopenharmony_ciclass BadZipFile(Exception):
457db96d56Sopenharmony_ci    pass
467db96d56Sopenharmony_ci
477db96d56Sopenharmony_ci
487db96d56Sopenharmony_ciclass LargeZipFile(Exception):
497db96d56Sopenharmony_ci    """
507db96d56Sopenharmony_ci    Raised when writing a zipfile, the zipfile requires ZIP64 extensions
517db96d56Sopenharmony_ci    and those extensions are disabled.
527db96d56Sopenharmony_ci    """
537db96d56Sopenharmony_ci
547db96d56Sopenharmony_cierror = BadZipfile = BadZipFile      # Pre-3.2 compatibility names
557db96d56Sopenharmony_ci
567db96d56Sopenharmony_ci
577db96d56Sopenharmony_ciZIP64_LIMIT = (1 << 31) - 1
587db96d56Sopenharmony_ciZIP_FILECOUNT_LIMIT = (1 << 16) - 1
597db96d56Sopenharmony_ciZIP_MAX_COMMENT = (1 << 16) - 1
607db96d56Sopenharmony_ci
617db96d56Sopenharmony_ci# constants for Zip file compression methods
627db96d56Sopenharmony_ciZIP_STORED = 0
637db96d56Sopenharmony_ciZIP_DEFLATED = 8
647db96d56Sopenharmony_ciZIP_BZIP2 = 12
657db96d56Sopenharmony_ciZIP_LZMA = 14
667db96d56Sopenharmony_ci# Other ZIP compression methods not supported
677db96d56Sopenharmony_ci
687db96d56Sopenharmony_ciDEFAULT_VERSION = 20
697db96d56Sopenharmony_ciZIP64_VERSION = 45
707db96d56Sopenharmony_ciBZIP2_VERSION = 46
717db96d56Sopenharmony_ciLZMA_VERSION = 63
727db96d56Sopenharmony_ci# we recognize (but not necessarily support) all features up to that version
737db96d56Sopenharmony_ciMAX_EXTRACT_VERSION = 63
747db96d56Sopenharmony_ci
757db96d56Sopenharmony_ci# Below are some formats and associated data for reading/writing headers using
767db96d56Sopenharmony_ci# the struct module.  The names and structures of headers/records are those used
777db96d56Sopenharmony_ci# in the PKWARE description of the ZIP file format:
787db96d56Sopenharmony_ci#     http://www.pkware.com/documents/casestudies/APPNOTE.TXT
797db96d56Sopenharmony_ci# (URL valid as of January 2008)
807db96d56Sopenharmony_ci
817db96d56Sopenharmony_ci# The "end of central directory" structure, magic number, size, and indices
827db96d56Sopenharmony_ci# (section V.I in the format document)
837db96d56Sopenharmony_cistructEndArchive = b"<4s4H2LH"
847db96d56Sopenharmony_cistringEndArchive = b"PK\005\006"
857db96d56Sopenharmony_cisizeEndCentDir = struct.calcsize(structEndArchive)
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ci_ECD_SIGNATURE = 0
887db96d56Sopenharmony_ci_ECD_DISK_NUMBER = 1
897db96d56Sopenharmony_ci_ECD_DISK_START = 2
907db96d56Sopenharmony_ci_ECD_ENTRIES_THIS_DISK = 3
917db96d56Sopenharmony_ci_ECD_ENTRIES_TOTAL = 4
927db96d56Sopenharmony_ci_ECD_SIZE = 5
937db96d56Sopenharmony_ci_ECD_OFFSET = 6
947db96d56Sopenharmony_ci_ECD_COMMENT_SIZE = 7
957db96d56Sopenharmony_ci# These last two indices are not part of the structure as defined in the
967db96d56Sopenharmony_ci# spec, but they are used internally by this module as a convenience
977db96d56Sopenharmony_ci_ECD_COMMENT = 8
987db96d56Sopenharmony_ci_ECD_LOCATION = 9
997db96d56Sopenharmony_ci
1007db96d56Sopenharmony_ci# The "central directory" structure, magic number, size, and indices
1017db96d56Sopenharmony_ci# of entries in the structure (section V.F in the format document)
1027db96d56Sopenharmony_cistructCentralDir = "<4s4B4HL2L5H2L"
1037db96d56Sopenharmony_cistringCentralDir = b"PK\001\002"
1047db96d56Sopenharmony_cisizeCentralDir = struct.calcsize(structCentralDir)
1057db96d56Sopenharmony_ci
1067db96d56Sopenharmony_ci# indexes of entries in the central directory structure
1077db96d56Sopenharmony_ci_CD_SIGNATURE = 0
1087db96d56Sopenharmony_ci_CD_CREATE_VERSION = 1
1097db96d56Sopenharmony_ci_CD_CREATE_SYSTEM = 2
1107db96d56Sopenharmony_ci_CD_EXTRACT_VERSION = 3
1117db96d56Sopenharmony_ci_CD_EXTRACT_SYSTEM = 4
1127db96d56Sopenharmony_ci_CD_FLAG_BITS = 5
1137db96d56Sopenharmony_ci_CD_COMPRESS_TYPE = 6
1147db96d56Sopenharmony_ci_CD_TIME = 7
1157db96d56Sopenharmony_ci_CD_DATE = 8
1167db96d56Sopenharmony_ci_CD_CRC = 9
1177db96d56Sopenharmony_ci_CD_COMPRESSED_SIZE = 10
1187db96d56Sopenharmony_ci_CD_UNCOMPRESSED_SIZE = 11
1197db96d56Sopenharmony_ci_CD_FILENAME_LENGTH = 12
1207db96d56Sopenharmony_ci_CD_EXTRA_FIELD_LENGTH = 13
1217db96d56Sopenharmony_ci_CD_COMMENT_LENGTH = 14
1227db96d56Sopenharmony_ci_CD_DISK_NUMBER_START = 15
1237db96d56Sopenharmony_ci_CD_INTERNAL_FILE_ATTRIBUTES = 16
1247db96d56Sopenharmony_ci_CD_EXTERNAL_FILE_ATTRIBUTES = 17
1257db96d56Sopenharmony_ci_CD_LOCAL_HEADER_OFFSET = 18
1267db96d56Sopenharmony_ci
1277db96d56Sopenharmony_ci# General purpose bit flags
1287db96d56Sopenharmony_ci# Zip Appnote: 4.4.4 general purpose bit flag: (2 bytes)
1297db96d56Sopenharmony_ci_MASK_ENCRYPTED = 1 << 0
1307db96d56Sopenharmony_ci# Bits 1 and 2 have different meanings depending on the compression used.
1317db96d56Sopenharmony_ci_MASK_COMPRESS_OPTION_1 = 1 << 1
1327db96d56Sopenharmony_ci# _MASK_COMPRESS_OPTION_2 = 1 << 2
1337db96d56Sopenharmony_ci# _MASK_USE_DATA_DESCRIPTOR: If set, crc-32, compressed size and uncompressed
1347db96d56Sopenharmony_ci# size are zero in the local header and the real values are written in the data
1357db96d56Sopenharmony_ci# descriptor immediately following the compressed data.
1367db96d56Sopenharmony_ci_MASK_USE_DATA_DESCRIPTOR = 1 << 3
1377db96d56Sopenharmony_ci# Bit 4: Reserved for use with compression method 8, for enhanced deflating.
1387db96d56Sopenharmony_ci# _MASK_RESERVED_BIT_4 = 1 << 4
1397db96d56Sopenharmony_ci_MASK_COMPRESSED_PATCH = 1 << 5
1407db96d56Sopenharmony_ci_MASK_STRONG_ENCRYPTION = 1 << 6
1417db96d56Sopenharmony_ci# _MASK_UNUSED_BIT_7 = 1 << 7
1427db96d56Sopenharmony_ci# _MASK_UNUSED_BIT_8 = 1 << 8
1437db96d56Sopenharmony_ci# _MASK_UNUSED_BIT_9 = 1 << 9
1447db96d56Sopenharmony_ci# _MASK_UNUSED_BIT_10 = 1 << 10
1457db96d56Sopenharmony_ci_MASK_UTF_FILENAME = 1 << 11
1467db96d56Sopenharmony_ci# Bit 12: Reserved by PKWARE for enhanced compression.
1477db96d56Sopenharmony_ci# _MASK_RESERVED_BIT_12 = 1 << 12
1487db96d56Sopenharmony_ci# _MASK_ENCRYPTED_CENTRAL_DIR = 1 << 13
1497db96d56Sopenharmony_ci# Bit 14, 15: Reserved by PKWARE
1507db96d56Sopenharmony_ci# _MASK_RESERVED_BIT_14 = 1 << 14
1517db96d56Sopenharmony_ci# _MASK_RESERVED_BIT_15 = 1 << 15
1527db96d56Sopenharmony_ci
1537db96d56Sopenharmony_ci# The "local file header" structure, magic number, size, and indices
1547db96d56Sopenharmony_ci# (section V.A in the format document)
1557db96d56Sopenharmony_cistructFileHeader = "<4s2B4HL2L2H"
1567db96d56Sopenharmony_cistringFileHeader = b"PK\003\004"
1577db96d56Sopenharmony_cisizeFileHeader = struct.calcsize(structFileHeader)
1587db96d56Sopenharmony_ci
1597db96d56Sopenharmony_ci_FH_SIGNATURE = 0
1607db96d56Sopenharmony_ci_FH_EXTRACT_VERSION = 1
1617db96d56Sopenharmony_ci_FH_EXTRACT_SYSTEM = 2
1627db96d56Sopenharmony_ci_FH_GENERAL_PURPOSE_FLAG_BITS = 3
1637db96d56Sopenharmony_ci_FH_COMPRESSION_METHOD = 4
1647db96d56Sopenharmony_ci_FH_LAST_MOD_TIME = 5
1657db96d56Sopenharmony_ci_FH_LAST_MOD_DATE = 6
1667db96d56Sopenharmony_ci_FH_CRC = 7
1677db96d56Sopenharmony_ci_FH_COMPRESSED_SIZE = 8
1687db96d56Sopenharmony_ci_FH_UNCOMPRESSED_SIZE = 9
1697db96d56Sopenharmony_ci_FH_FILENAME_LENGTH = 10
1707db96d56Sopenharmony_ci_FH_EXTRA_FIELD_LENGTH = 11
1717db96d56Sopenharmony_ci
1727db96d56Sopenharmony_ci# The "Zip64 end of central directory locator" structure, magic number, and size
1737db96d56Sopenharmony_cistructEndArchive64Locator = "<4sLQL"
1747db96d56Sopenharmony_cistringEndArchive64Locator = b"PK\x06\x07"
1757db96d56Sopenharmony_cisizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator)
1767db96d56Sopenharmony_ci
1777db96d56Sopenharmony_ci# The "Zip64 end of central directory" record, magic number, size, and indices
1787db96d56Sopenharmony_ci# (section V.G in the format document)
1797db96d56Sopenharmony_cistructEndArchive64 = "<4sQ2H2L4Q"
1807db96d56Sopenharmony_cistringEndArchive64 = b"PK\x06\x06"
1817db96d56Sopenharmony_cisizeEndCentDir64 = struct.calcsize(structEndArchive64)
1827db96d56Sopenharmony_ci
1837db96d56Sopenharmony_ci_CD64_SIGNATURE = 0
1847db96d56Sopenharmony_ci_CD64_DIRECTORY_RECSIZE = 1
1857db96d56Sopenharmony_ci_CD64_CREATE_VERSION = 2
1867db96d56Sopenharmony_ci_CD64_EXTRACT_VERSION = 3
1877db96d56Sopenharmony_ci_CD64_DISK_NUMBER = 4
1887db96d56Sopenharmony_ci_CD64_DISK_NUMBER_START = 5
1897db96d56Sopenharmony_ci_CD64_NUMBER_ENTRIES_THIS_DISK = 6
1907db96d56Sopenharmony_ci_CD64_NUMBER_ENTRIES_TOTAL = 7
1917db96d56Sopenharmony_ci_CD64_DIRECTORY_SIZE = 8
1927db96d56Sopenharmony_ci_CD64_OFFSET_START_CENTDIR = 9
1937db96d56Sopenharmony_ci
1947db96d56Sopenharmony_ci_DD_SIGNATURE = 0x08074b50
1957db96d56Sopenharmony_ci
1967db96d56Sopenharmony_ci_EXTRA_FIELD_STRUCT = struct.Struct('<HH')
1977db96d56Sopenharmony_ci
1987db96d56Sopenharmony_cidef _strip_extra(extra, xids):
1997db96d56Sopenharmony_ci    # Remove Extra Fields with specified IDs.
2007db96d56Sopenharmony_ci    unpack = _EXTRA_FIELD_STRUCT.unpack
2017db96d56Sopenharmony_ci    modified = False
2027db96d56Sopenharmony_ci    buffer = []
2037db96d56Sopenharmony_ci    start = i = 0
2047db96d56Sopenharmony_ci    while i + 4 <= len(extra):
2057db96d56Sopenharmony_ci        xid, xlen = unpack(extra[i : i + 4])
2067db96d56Sopenharmony_ci        j = i + 4 + xlen
2077db96d56Sopenharmony_ci        if xid in xids:
2087db96d56Sopenharmony_ci            if i != start:
2097db96d56Sopenharmony_ci                buffer.append(extra[start : i])
2107db96d56Sopenharmony_ci            start = j
2117db96d56Sopenharmony_ci            modified = True
2127db96d56Sopenharmony_ci        i = j
2137db96d56Sopenharmony_ci    if not modified:
2147db96d56Sopenharmony_ci        return extra
2157db96d56Sopenharmony_ci    if start != len(extra):
2167db96d56Sopenharmony_ci        buffer.append(extra[start:])
2177db96d56Sopenharmony_ci    return b''.join(buffer)
2187db96d56Sopenharmony_ci
2197db96d56Sopenharmony_cidef _check_zipfile(fp):
2207db96d56Sopenharmony_ci    try:
2217db96d56Sopenharmony_ci        if _EndRecData(fp):
2227db96d56Sopenharmony_ci            return True         # file has correct magic number
2237db96d56Sopenharmony_ci    except OSError:
2247db96d56Sopenharmony_ci        pass
2257db96d56Sopenharmony_ci    return False
2267db96d56Sopenharmony_ci
2277db96d56Sopenharmony_cidef is_zipfile(filename):
2287db96d56Sopenharmony_ci    """Quickly see if a file is a ZIP file by checking the magic number.
2297db96d56Sopenharmony_ci
2307db96d56Sopenharmony_ci    The filename argument may be a file or file-like object too.
2317db96d56Sopenharmony_ci    """
2327db96d56Sopenharmony_ci    result = False
2337db96d56Sopenharmony_ci    try:
2347db96d56Sopenharmony_ci        if hasattr(filename, "read"):
2357db96d56Sopenharmony_ci            result = _check_zipfile(fp=filename)
2367db96d56Sopenharmony_ci        else:
2377db96d56Sopenharmony_ci            with open(filename, "rb") as fp:
2387db96d56Sopenharmony_ci                result = _check_zipfile(fp)
2397db96d56Sopenharmony_ci    except OSError:
2407db96d56Sopenharmony_ci        pass
2417db96d56Sopenharmony_ci    return result
2427db96d56Sopenharmony_ci
2437db96d56Sopenharmony_cidef _EndRecData64(fpin, offset, endrec):
2447db96d56Sopenharmony_ci    """
2457db96d56Sopenharmony_ci    Read the ZIP64 end-of-archive records and use that to update endrec
2467db96d56Sopenharmony_ci    """
2477db96d56Sopenharmony_ci    try:
2487db96d56Sopenharmony_ci        fpin.seek(offset - sizeEndCentDir64Locator, 2)
2497db96d56Sopenharmony_ci    except OSError:
2507db96d56Sopenharmony_ci        # If the seek fails, the file is not large enough to contain a ZIP64
2517db96d56Sopenharmony_ci        # end-of-archive record, so just return the end record we were given.
2527db96d56Sopenharmony_ci        return endrec
2537db96d56Sopenharmony_ci
2547db96d56Sopenharmony_ci    data = fpin.read(sizeEndCentDir64Locator)
2557db96d56Sopenharmony_ci    if len(data) != sizeEndCentDir64Locator:
2567db96d56Sopenharmony_ci        return endrec
2577db96d56Sopenharmony_ci    sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data)
2587db96d56Sopenharmony_ci    if sig != stringEndArchive64Locator:
2597db96d56Sopenharmony_ci        return endrec
2607db96d56Sopenharmony_ci
2617db96d56Sopenharmony_ci    if diskno != 0 or disks > 1:
2627db96d56Sopenharmony_ci        raise BadZipFile("zipfiles that span multiple disks are not supported")
2637db96d56Sopenharmony_ci
2647db96d56Sopenharmony_ci    # Assume no 'zip64 extensible data'
2657db96d56Sopenharmony_ci    fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2)
2667db96d56Sopenharmony_ci    data = fpin.read(sizeEndCentDir64)
2677db96d56Sopenharmony_ci    if len(data) != sizeEndCentDir64:
2687db96d56Sopenharmony_ci        return endrec
2697db96d56Sopenharmony_ci    sig, sz, create_version, read_version, disk_num, disk_dir, \
2707db96d56Sopenharmony_ci        dircount, dircount2, dirsize, diroffset = \
2717db96d56Sopenharmony_ci        struct.unpack(structEndArchive64, data)
2727db96d56Sopenharmony_ci    if sig != stringEndArchive64:
2737db96d56Sopenharmony_ci        return endrec
2747db96d56Sopenharmony_ci
2757db96d56Sopenharmony_ci    # Update the original endrec using data from the ZIP64 record
2767db96d56Sopenharmony_ci    endrec[_ECD_SIGNATURE] = sig
2777db96d56Sopenharmony_ci    endrec[_ECD_DISK_NUMBER] = disk_num
2787db96d56Sopenharmony_ci    endrec[_ECD_DISK_START] = disk_dir
2797db96d56Sopenharmony_ci    endrec[_ECD_ENTRIES_THIS_DISK] = dircount
2807db96d56Sopenharmony_ci    endrec[_ECD_ENTRIES_TOTAL] = dircount2
2817db96d56Sopenharmony_ci    endrec[_ECD_SIZE] = dirsize
2827db96d56Sopenharmony_ci    endrec[_ECD_OFFSET] = diroffset
2837db96d56Sopenharmony_ci    return endrec
2847db96d56Sopenharmony_ci
2857db96d56Sopenharmony_ci
2867db96d56Sopenharmony_cidef _EndRecData(fpin):
2877db96d56Sopenharmony_ci    """Return data from the "End of Central Directory" record, or None.
2887db96d56Sopenharmony_ci
2897db96d56Sopenharmony_ci    The data is a list of the nine items in the ZIP "End of central dir"
2907db96d56Sopenharmony_ci    record followed by a tenth item, the file seek offset of this record."""
2917db96d56Sopenharmony_ci
2927db96d56Sopenharmony_ci    # Determine file size
2937db96d56Sopenharmony_ci    fpin.seek(0, 2)
2947db96d56Sopenharmony_ci    filesize = fpin.tell()
2957db96d56Sopenharmony_ci
2967db96d56Sopenharmony_ci    # Check to see if this is ZIP file with no archive comment (the
2977db96d56Sopenharmony_ci    # "end of central directory" structure should be the last item in the
2987db96d56Sopenharmony_ci    # file if this is the case).
2997db96d56Sopenharmony_ci    try:
3007db96d56Sopenharmony_ci        fpin.seek(-sizeEndCentDir, 2)
3017db96d56Sopenharmony_ci    except OSError:
3027db96d56Sopenharmony_ci        return None
3037db96d56Sopenharmony_ci    data = fpin.read()
3047db96d56Sopenharmony_ci    if (len(data) == sizeEndCentDir and
3057db96d56Sopenharmony_ci        data[0:4] == stringEndArchive and
3067db96d56Sopenharmony_ci        data[-2:] == b"\000\000"):
3077db96d56Sopenharmony_ci        # the signature is correct and there's no comment, unpack structure
3087db96d56Sopenharmony_ci        endrec = struct.unpack(structEndArchive, data)
3097db96d56Sopenharmony_ci        endrec=list(endrec)
3107db96d56Sopenharmony_ci
3117db96d56Sopenharmony_ci        # Append a blank comment and record start offset
3127db96d56Sopenharmony_ci        endrec.append(b"")
3137db96d56Sopenharmony_ci        endrec.append(filesize - sizeEndCentDir)
3147db96d56Sopenharmony_ci
3157db96d56Sopenharmony_ci        # Try to read the "Zip64 end of central directory" structure
3167db96d56Sopenharmony_ci        return _EndRecData64(fpin, -sizeEndCentDir, endrec)
3177db96d56Sopenharmony_ci
3187db96d56Sopenharmony_ci    # Either this is not a ZIP file, or it is a ZIP file with an archive
3197db96d56Sopenharmony_ci    # comment.  Search the end of the file for the "end of central directory"
3207db96d56Sopenharmony_ci    # record signature. The comment is the last item in the ZIP file and may be
3217db96d56Sopenharmony_ci    # up to 64K long.  It is assumed that the "end of central directory" magic
3227db96d56Sopenharmony_ci    # number does not appear in the comment.
3237db96d56Sopenharmony_ci    maxCommentStart = max(filesize - (1 << 16) - sizeEndCentDir, 0)
3247db96d56Sopenharmony_ci    fpin.seek(maxCommentStart, 0)
3257db96d56Sopenharmony_ci    data = fpin.read()
3267db96d56Sopenharmony_ci    start = data.rfind(stringEndArchive)
3277db96d56Sopenharmony_ci    if start >= 0:
3287db96d56Sopenharmony_ci        # found the magic number; attempt to unpack and interpret
3297db96d56Sopenharmony_ci        recData = data[start:start+sizeEndCentDir]
3307db96d56Sopenharmony_ci        if len(recData) != sizeEndCentDir:
3317db96d56Sopenharmony_ci            # Zip file is corrupted.
3327db96d56Sopenharmony_ci            return None
3337db96d56Sopenharmony_ci        endrec = list(struct.unpack(structEndArchive, recData))
3347db96d56Sopenharmony_ci        commentSize = endrec[_ECD_COMMENT_SIZE] #as claimed by the zip file
3357db96d56Sopenharmony_ci        comment = data[start+sizeEndCentDir:start+sizeEndCentDir+commentSize]
3367db96d56Sopenharmony_ci        endrec.append(comment)
3377db96d56Sopenharmony_ci        endrec.append(maxCommentStart + start)
3387db96d56Sopenharmony_ci
3397db96d56Sopenharmony_ci        # Try to read the "Zip64 end of central directory" structure
3407db96d56Sopenharmony_ci        return _EndRecData64(fpin, maxCommentStart + start - filesize,
3417db96d56Sopenharmony_ci                             endrec)
3427db96d56Sopenharmony_ci
3437db96d56Sopenharmony_ci    # Unable to find a valid end of central directory structure
3447db96d56Sopenharmony_ci    return None
3457db96d56Sopenharmony_ci
3467db96d56Sopenharmony_ci
3477db96d56Sopenharmony_ciclass ZipInfo (object):
3487db96d56Sopenharmony_ci    """Class with attributes describing each file in the ZIP archive."""
3497db96d56Sopenharmony_ci
3507db96d56Sopenharmony_ci    __slots__ = (
3517db96d56Sopenharmony_ci        'orig_filename',
3527db96d56Sopenharmony_ci        'filename',
3537db96d56Sopenharmony_ci        'date_time',
3547db96d56Sopenharmony_ci        'compress_type',
3557db96d56Sopenharmony_ci        '_compresslevel',
3567db96d56Sopenharmony_ci        'comment',
3577db96d56Sopenharmony_ci        'extra',
3587db96d56Sopenharmony_ci        'create_system',
3597db96d56Sopenharmony_ci        'create_version',
3607db96d56Sopenharmony_ci        'extract_version',
3617db96d56Sopenharmony_ci        'reserved',
3627db96d56Sopenharmony_ci        'flag_bits',
3637db96d56Sopenharmony_ci        'volume',
3647db96d56Sopenharmony_ci        'internal_attr',
3657db96d56Sopenharmony_ci        'external_attr',
3667db96d56Sopenharmony_ci        'header_offset',
3677db96d56Sopenharmony_ci        'CRC',
3687db96d56Sopenharmony_ci        'compress_size',
3697db96d56Sopenharmony_ci        'file_size',
3707db96d56Sopenharmony_ci        '_raw_time',
3717db96d56Sopenharmony_ci        '_end_offset',
3727db96d56Sopenharmony_ci    )
3737db96d56Sopenharmony_ci
3747db96d56Sopenharmony_ci    def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
3757db96d56Sopenharmony_ci        self.orig_filename = filename   # Original file name in archive
3767db96d56Sopenharmony_ci
3777db96d56Sopenharmony_ci        # Terminate the file name at the first null byte.  Null bytes in file
3787db96d56Sopenharmony_ci        # names are used as tricks by viruses in archives.
3797db96d56Sopenharmony_ci        null_byte = filename.find(chr(0))
3807db96d56Sopenharmony_ci        if null_byte >= 0:
3817db96d56Sopenharmony_ci            filename = filename[0:null_byte]
3827db96d56Sopenharmony_ci        # This is used to ensure paths in generated ZIP files always use
3837db96d56Sopenharmony_ci        # forward slashes as the directory separator, as required by the
3847db96d56Sopenharmony_ci        # ZIP format specification.
3857db96d56Sopenharmony_ci        if os.sep != "/" and os.sep in filename:
3867db96d56Sopenharmony_ci            filename = filename.replace(os.sep, "/")
3877db96d56Sopenharmony_ci
3887db96d56Sopenharmony_ci        self.filename = filename        # Normalized file name
3897db96d56Sopenharmony_ci        self.date_time = date_time      # year, month, day, hour, min, sec
3907db96d56Sopenharmony_ci
3917db96d56Sopenharmony_ci        if date_time[0] < 1980:
3927db96d56Sopenharmony_ci            raise ValueError('ZIP does not support timestamps before 1980')
3937db96d56Sopenharmony_ci
3947db96d56Sopenharmony_ci        # Standard values:
3957db96d56Sopenharmony_ci        self.compress_type = ZIP_STORED # Type of compression for the file
3967db96d56Sopenharmony_ci        self._compresslevel = None      # Level for the compressor
3977db96d56Sopenharmony_ci        self.comment = b""              # Comment for each file
3987db96d56Sopenharmony_ci        self.extra = b""                # ZIP extra data
3997db96d56Sopenharmony_ci        if sys.platform == 'win32':
4007db96d56Sopenharmony_ci            self.create_system = 0          # System which created ZIP archive
4017db96d56Sopenharmony_ci        else:
4027db96d56Sopenharmony_ci            # Assume everything else is unix-y
4037db96d56Sopenharmony_ci            self.create_system = 3          # System which created ZIP archive
4047db96d56Sopenharmony_ci        self.create_version = DEFAULT_VERSION  # Version which created ZIP archive
4057db96d56Sopenharmony_ci        self.extract_version = DEFAULT_VERSION # Version needed to extract archive
4067db96d56Sopenharmony_ci        self.reserved = 0               # Must be zero
4077db96d56Sopenharmony_ci        self.flag_bits = 0              # ZIP flag bits
4087db96d56Sopenharmony_ci        self.volume = 0                 # Volume number of file header
4097db96d56Sopenharmony_ci        self.internal_attr = 0          # Internal attributes
4107db96d56Sopenharmony_ci        self.external_attr = 0          # External file attributes
4117db96d56Sopenharmony_ci        self.compress_size = 0          # Size of the compressed file
4127db96d56Sopenharmony_ci        self.file_size = 0              # Size of the uncompressed file
4137db96d56Sopenharmony_ci        self._end_offset = None         # Start of the next local header or central directory
4147db96d56Sopenharmony_ci        # Other attributes are set by class ZipFile:
4157db96d56Sopenharmony_ci        # header_offset         Byte offset to the file header
4167db96d56Sopenharmony_ci        # CRC                   CRC-32 of the uncompressed file
4177db96d56Sopenharmony_ci
4187db96d56Sopenharmony_ci    def __repr__(self):
4197db96d56Sopenharmony_ci        result = ['<%s filename=%r' % (self.__class__.__name__, self.filename)]
4207db96d56Sopenharmony_ci        if self.compress_type != ZIP_STORED:
4217db96d56Sopenharmony_ci            result.append(' compress_type=%s' %
4227db96d56Sopenharmony_ci                          compressor_names.get(self.compress_type,
4237db96d56Sopenharmony_ci                                               self.compress_type))
4247db96d56Sopenharmony_ci        hi = self.external_attr >> 16
4257db96d56Sopenharmony_ci        lo = self.external_attr & 0xFFFF
4267db96d56Sopenharmony_ci        if hi:
4277db96d56Sopenharmony_ci            result.append(' filemode=%r' % stat.filemode(hi))
4287db96d56Sopenharmony_ci        if lo:
4297db96d56Sopenharmony_ci            result.append(' external_attr=%#x' % lo)
4307db96d56Sopenharmony_ci        isdir = self.is_dir()
4317db96d56Sopenharmony_ci        if not isdir or self.file_size:
4327db96d56Sopenharmony_ci            result.append(' file_size=%r' % self.file_size)
4337db96d56Sopenharmony_ci        if ((not isdir or self.compress_size) and
4347db96d56Sopenharmony_ci            (self.compress_type != ZIP_STORED or
4357db96d56Sopenharmony_ci             self.file_size != self.compress_size)):
4367db96d56Sopenharmony_ci            result.append(' compress_size=%r' % self.compress_size)
4377db96d56Sopenharmony_ci        result.append('>')
4387db96d56Sopenharmony_ci        return ''.join(result)
4397db96d56Sopenharmony_ci
4407db96d56Sopenharmony_ci    def FileHeader(self, zip64=None):
4417db96d56Sopenharmony_ci        """Return the per-file header as a bytes object.
4427db96d56Sopenharmony_ci
4437db96d56Sopenharmony_ci        When the optional zip64 arg is None rather than a bool, we will
4447db96d56Sopenharmony_ci        decide based upon the file_size and compress_size, if known,
4457db96d56Sopenharmony_ci        False otherwise.
4467db96d56Sopenharmony_ci        """
4477db96d56Sopenharmony_ci        dt = self.date_time
4487db96d56Sopenharmony_ci        dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
4497db96d56Sopenharmony_ci        dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
4507db96d56Sopenharmony_ci        if self.flag_bits & _MASK_USE_DATA_DESCRIPTOR:
4517db96d56Sopenharmony_ci            # Set these to zero because we write them after the file data
4527db96d56Sopenharmony_ci            CRC = compress_size = file_size = 0
4537db96d56Sopenharmony_ci        else:
4547db96d56Sopenharmony_ci            CRC = self.CRC
4557db96d56Sopenharmony_ci            compress_size = self.compress_size
4567db96d56Sopenharmony_ci            file_size = self.file_size
4577db96d56Sopenharmony_ci
4587db96d56Sopenharmony_ci        extra = self.extra
4597db96d56Sopenharmony_ci
4607db96d56Sopenharmony_ci        min_version = 0
4617db96d56Sopenharmony_ci        if zip64 is None:
4627db96d56Sopenharmony_ci            # We always explicitly pass zip64 within this module.... This
4637db96d56Sopenharmony_ci            # remains for anyone using ZipInfo.FileHeader as a public API.
4647db96d56Sopenharmony_ci            zip64 = file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT
4657db96d56Sopenharmony_ci        if zip64:
4667db96d56Sopenharmony_ci            fmt = '<HHQQ'
4677db96d56Sopenharmony_ci            extra = extra + struct.pack(fmt,
4687db96d56Sopenharmony_ci                                        1, struct.calcsize(fmt)-4, file_size, compress_size)
4697db96d56Sopenharmony_ci            file_size = 0xffffffff
4707db96d56Sopenharmony_ci            compress_size = 0xffffffff
4717db96d56Sopenharmony_ci            min_version = ZIP64_VERSION
4727db96d56Sopenharmony_ci
4737db96d56Sopenharmony_ci        if self.compress_type == ZIP_BZIP2:
4747db96d56Sopenharmony_ci            min_version = max(BZIP2_VERSION, min_version)
4757db96d56Sopenharmony_ci        elif self.compress_type == ZIP_LZMA:
4767db96d56Sopenharmony_ci            min_version = max(LZMA_VERSION, min_version)
4777db96d56Sopenharmony_ci
4787db96d56Sopenharmony_ci        self.extract_version = max(min_version, self.extract_version)
4797db96d56Sopenharmony_ci        self.create_version = max(min_version, self.create_version)
4807db96d56Sopenharmony_ci        filename, flag_bits = self._encodeFilenameFlags()
4817db96d56Sopenharmony_ci        header = struct.pack(structFileHeader, stringFileHeader,
4827db96d56Sopenharmony_ci                             self.extract_version, self.reserved, flag_bits,
4837db96d56Sopenharmony_ci                             self.compress_type, dostime, dosdate, CRC,
4847db96d56Sopenharmony_ci                             compress_size, file_size,
4857db96d56Sopenharmony_ci                             len(filename), len(extra))
4867db96d56Sopenharmony_ci        return header + filename + extra
4877db96d56Sopenharmony_ci
4887db96d56Sopenharmony_ci    def _encodeFilenameFlags(self):
4897db96d56Sopenharmony_ci        try:
4907db96d56Sopenharmony_ci            return self.filename.encode('ascii'), self.flag_bits
4917db96d56Sopenharmony_ci        except UnicodeEncodeError:
4927db96d56Sopenharmony_ci            return self.filename.encode('utf-8'), self.flag_bits | _MASK_UTF_FILENAME
4937db96d56Sopenharmony_ci
4947db96d56Sopenharmony_ci    def _decodeExtra(self):
4957db96d56Sopenharmony_ci        # Try to decode the extra field.
4967db96d56Sopenharmony_ci        extra = self.extra
4977db96d56Sopenharmony_ci        unpack = struct.unpack
4987db96d56Sopenharmony_ci        while len(extra) >= 4:
4997db96d56Sopenharmony_ci            tp, ln = unpack('<HH', extra[:4])
5007db96d56Sopenharmony_ci            if ln+4 > len(extra):
5017db96d56Sopenharmony_ci                raise BadZipFile("Corrupt extra field %04x (size=%d)" % (tp, ln))
5027db96d56Sopenharmony_ci            if tp == 0x0001:
5037db96d56Sopenharmony_ci                data = extra[4:ln+4]
5047db96d56Sopenharmony_ci                # ZIP64 extension (large files and/or large archives)
5057db96d56Sopenharmony_ci                try:
5067db96d56Sopenharmony_ci                    if self.file_size in (0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF):
5077db96d56Sopenharmony_ci                        field = "File size"
5087db96d56Sopenharmony_ci                        self.file_size, = unpack('<Q', data[:8])
5097db96d56Sopenharmony_ci                        data = data[8:]
5107db96d56Sopenharmony_ci                    if self.compress_size == 0xFFFF_FFFF:
5117db96d56Sopenharmony_ci                        field = "Compress size"
5127db96d56Sopenharmony_ci                        self.compress_size, = unpack('<Q', data[:8])
5137db96d56Sopenharmony_ci                        data = data[8:]
5147db96d56Sopenharmony_ci                    if self.header_offset == 0xFFFF_FFFF:
5157db96d56Sopenharmony_ci                        field = "Header offset"
5167db96d56Sopenharmony_ci                        self.header_offset, = unpack('<Q', data[:8])
5177db96d56Sopenharmony_ci                except struct.error:
5187db96d56Sopenharmony_ci                    raise BadZipFile(f"Corrupt zip64 extra field. "
5197db96d56Sopenharmony_ci                                     f"{field} not found.") from None
5207db96d56Sopenharmony_ci
5217db96d56Sopenharmony_ci            extra = extra[ln+4:]
5227db96d56Sopenharmony_ci
5237db96d56Sopenharmony_ci    @classmethod
5247db96d56Sopenharmony_ci    def from_file(cls, filename, arcname=None, *, strict_timestamps=True):
5257db96d56Sopenharmony_ci        """Construct an appropriate ZipInfo for a file on the filesystem.
5267db96d56Sopenharmony_ci
5277db96d56Sopenharmony_ci        filename should be the path to a file or directory on the filesystem.
5287db96d56Sopenharmony_ci
5297db96d56Sopenharmony_ci        arcname is the name which it will have within the archive (by default,
5307db96d56Sopenharmony_ci        this will be the same as filename, but without a drive letter and with
5317db96d56Sopenharmony_ci        leading path separators removed).
5327db96d56Sopenharmony_ci        """
5337db96d56Sopenharmony_ci        if isinstance(filename, os.PathLike):
5347db96d56Sopenharmony_ci            filename = os.fspath(filename)
5357db96d56Sopenharmony_ci        st = os.stat(filename)
5367db96d56Sopenharmony_ci        isdir = stat.S_ISDIR(st.st_mode)
5377db96d56Sopenharmony_ci        mtime = time.localtime(st.st_mtime)
5387db96d56Sopenharmony_ci        date_time = mtime[0:6]
5397db96d56Sopenharmony_ci        if not strict_timestamps and date_time[0] < 1980:
5407db96d56Sopenharmony_ci            date_time = (1980, 1, 1, 0, 0, 0)
5417db96d56Sopenharmony_ci        elif not strict_timestamps and date_time[0] > 2107:
5427db96d56Sopenharmony_ci            date_time = (2107, 12, 31, 23, 59, 59)
5437db96d56Sopenharmony_ci        # Create ZipInfo instance to store file information
5447db96d56Sopenharmony_ci        if arcname is None:
5457db96d56Sopenharmony_ci            arcname = filename
5467db96d56Sopenharmony_ci        arcname = os.path.normpath(os.path.splitdrive(arcname)[1])
5477db96d56Sopenharmony_ci        while arcname[0] in (os.sep, os.altsep):
5487db96d56Sopenharmony_ci            arcname = arcname[1:]
5497db96d56Sopenharmony_ci        if isdir:
5507db96d56Sopenharmony_ci            arcname += '/'
5517db96d56Sopenharmony_ci        zinfo = cls(arcname, date_time)
5527db96d56Sopenharmony_ci        zinfo.external_attr = (st.st_mode & 0xFFFF) << 16  # Unix attributes
5537db96d56Sopenharmony_ci        if isdir:
5547db96d56Sopenharmony_ci            zinfo.file_size = 0
5557db96d56Sopenharmony_ci            zinfo.external_attr |= 0x10  # MS-DOS directory flag
5567db96d56Sopenharmony_ci        else:
5577db96d56Sopenharmony_ci            zinfo.file_size = st.st_size
5587db96d56Sopenharmony_ci
5597db96d56Sopenharmony_ci        return zinfo
5607db96d56Sopenharmony_ci
5617db96d56Sopenharmony_ci    def is_dir(self):
5627db96d56Sopenharmony_ci        """Return True if this archive member is a directory."""
5637db96d56Sopenharmony_ci        return self.filename[-1] == '/'
5647db96d56Sopenharmony_ci
5657db96d56Sopenharmony_ci
5667db96d56Sopenharmony_ci# ZIP encryption uses the CRC32 one-byte primitive for scrambling some
5677db96d56Sopenharmony_ci# internal keys. We noticed that a direct implementation is faster than
5687db96d56Sopenharmony_ci# relying on binascii.crc32().
5697db96d56Sopenharmony_ci
5707db96d56Sopenharmony_ci_crctable = None
5717db96d56Sopenharmony_cidef _gen_crc(crc):
5727db96d56Sopenharmony_ci    for j in range(8):
5737db96d56Sopenharmony_ci        if crc & 1:
5747db96d56Sopenharmony_ci            crc = (crc >> 1) ^ 0xEDB88320
5757db96d56Sopenharmony_ci        else:
5767db96d56Sopenharmony_ci            crc >>= 1
5777db96d56Sopenharmony_ci    return crc
5787db96d56Sopenharmony_ci
5797db96d56Sopenharmony_ci# ZIP supports a password-based form of encryption. Even though known
5807db96d56Sopenharmony_ci# plaintext attacks have been found against it, it is still useful
5817db96d56Sopenharmony_ci# to be able to get data out of such a file.
5827db96d56Sopenharmony_ci#
5837db96d56Sopenharmony_ci# Usage:
5847db96d56Sopenharmony_ci#     zd = _ZipDecrypter(mypwd)
5857db96d56Sopenharmony_ci#     plain_bytes = zd(cypher_bytes)
5867db96d56Sopenharmony_ci
5877db96d56Sopenharmony_cidef _ZipDecrypter(pwd):
5887db96d56Sopenharmony_ci    key0 = 305419896
5897db96d56Sopenharmony_ci    key1 = 591751049
5907db96d56Sopenharmony_ci    key2 = 878082192
5917db96d56Sopenharmony_ci
5927db96d56Sopenharmony_ci    global _crctable
5937db96d56Sopenharmony_ci    if _crctable is None:
5947db96d56Sopenharmony_ci        _crctable = list(map(_gen_crc, range(256)))
5957db96d56Sopenharmony_ci    crctable = _crctable
5967db96d56Sopenharmony_ci
5977db96d56Sopenharmony_ci    def crc32(ch, crc):
5987db96d56Sopenharmony_ci        """Compute the CRC32 primitive on one byte."""
5997db96d56Sopenharmony_ci        return (crc >> 8) ^ crctable[(crc ^ ch) & 0xFF]
6007db96d56Sopenharmony_ci
6017db96d56Sopenharmony_ci    def update_keys(c):
6027db96d56Sopenharmony_ci        nonlocal key0, key1, key2
6037db96d56Sopenharmony_ci        key0 = crc32(c, key0)
6047db96d56Sopenharmony_ci        key1 = (key1 + (key0 & 0xFF)) & 0xFFFFFFFF
6057db96d56Sopenharmony_ci        key1 = (key1 * 134775813 + 1) & 0xFFFFFFFF
6067db96d56Sopenharmony_ci        key2 = crc32(key1 >> 24, key2)
6077db96d56Sopenharmony_ci
6087db96d56Sopenharmony_ci    for p in pwd:
6097db96d56Sopenharmony_ci        update_keys(p)
6107db96d56Sopenharmony_ci
6117db96d56Sopenharmony_ci    def decrypter(data):
6127db96d56Sopenharmony_ci        """Decrypt a bytes object."""
6137db96d56Sopenharmony_ci        result = bytearray()
6147db96d56Sopenharmony_ci        append = result.append
6157db96d56Sopenharmony_ci        for c in data:
6167db96d56Sopenharmony_ci            k = key2 | 2
6177db96d56Sopenharmony_ci            c ^= ((k * (k^1)) >> 8) & 0xFF
6187db96d56Sopenharmony_ci            update_keys(c)
6197db96d56Sopenharmony_ci            append(c)
6207db96d56Sopenharmony_ci        return bytes(result)
6217db96d56Sopenharmony_ci
6227db96d56Sopenharmony_ci    return decrypter
6237db96d56Sopenharmony_ci
6247db96d56Sopenharmony_ci
6257db96d56Sopenharmony_ciclass LZMACompressor:
6267db96d56Sopenharmony_ci
6277db96d56Sopenharmony_ci    def __init__(self):
6287db96d56Sopenharmony_ci        self._comp = None
6297db96d56Sopenharmony_ci
6307db96d56Sopenharmony_ci    def _init(self):
6317db96d56Sopenharmony_ci        props = lzma._encode_filter_properties({'id': lzma.FILTER_LZMA1})
6327db96d56Sopenharmony_ci        self._comp = lzma.LZMACompressor(lzma.FORMAT_RAW, filters=[
6337db96d56Sopenharmony_ci            lzma._decode_filter_properties(lzma.FILTER_LZMA1, props)
6347db96d56Sopenharmony_ci        ])
6357db96d56Sopenharmony_ci        return struct.pack('<BBH', 9, 4, len(props)) + props
6367db96d56Sopenharmony_ci
6377db96d56Sopenharmony_ci    def compress(self, data):
6387db96d56Sopenharmony_ci        if self._comp is None:
6397db96d56Sopenharmony_ci            return self._init() + self._comp.compress(data)
6407db96d56Sopenharmony_ci        return self._comp.compress(data)
6417db96d56Sopenharmony_ci
6427db96d56Sopenharmony_ci    def flush(self):
6437db96d56Sopenharmony_ci        if self._comp is None:
6447db96d56Sopenharmony_ci            return self._init() + self._comp.flush()
6457db96d56Sopenharmony_ci        return self._comp.flush()
6467db96d56Sopenharmony_ci
6477db96d56Sopenharmony_ci
6487db96d56Sopenharmony_ciclass LZMADecompressor:
6497db96d56Sopenharmony_ci
6507db96d56Sopenharmony_ci    def __init__(self):
6517db96d56Sopenharmony_ci        self._decomp = None
6527db96d56Sopenharmony_ci        self._unconsumed = b''
6537db96d56Sopenharmony_ci        self.eof = False
6547db96d56Sopenharmony_ci
6557db96d56Sopenharmony_ci    def decompress(self, data):
6567db96d56Sopenharmony_ci        if self._decomp is None:
6577db96d56Sopenharmony_ci            self._unconsumed += data
6587db96d56Sopenharmony_ci            if len(self._unconsumed) <= 4:
6597db96d56Sopenharmony_ci                return b''
6607db96d56Sopenharmony_ci            psize, = struct.unpack('<H', self._unconsumed[2:4])
6617db96d56Sopenharmony_ci            if len(self._unconsumed) <= 4 + psize:
6627db96d56Sopenharmony_ci                return b''
6637db96d56Sopenharmony_ci
6647db96d56Sopenharmony_ci            self._decomp = lzma.LZMADecompressor(lzma.FORMAT_RAW, filters=[
6657db96d56Sopenharmony_ci                lzma._decode_filter_properties(lzma.FILTER_LZMA1,
6667db96d56Sopenharmony_ci                                               self._unconsumed[4:4 + psize])
6677db96d56Sopenharmony_ci            ])
6687db96d56Sopenharmony_ci            data = self._unconsumed[4 + psize:]
6697db96d56Sopenharmony_ci            del self._unconsumed
6707db96d56Sopenharmony_ci
6717db96d56Sopenharmony_ci        result = self._decomp.decompress(data)
6727db96d56Sopenharmony_ci        self.eof = self._decomp.eof
6737db96d56Sopenharmony_ci        return result
6747db96d56Sopenharmony_ci
6757db96d56Sopenharmony_ci
6767db96d56Sopenharmony_cicompressor_names = {
6777db96d56Sopenharmony_ci    0: 'store',
6787db96d56Sopenharmony_ci    1: 'shrink',
6797db96d56Sopenharmony_ci    2: 'reduce',
6807db96d56Sopenharmony_ci    3: 'reduce',
6817db96d56Sopenharmony_ci    4: 'reduce',
6827db96d56Sopenharmony_ci    5: 'reduce',
6837db96d56Sopenharmony_ci    6: 'implode',
6847db96d56Sopenharmony_ci    7: 'tokenize',
6857db96d56Sopenharmony_ci    8: 'deflate',
6867db96d56Sopenharmony_ci    9: 'deflate64',
6877db96d56Sopenharmony_ci    10: 'implode',
6887db96d56Sopenharmony_ci    12: 'bzip2',
6897db96d56Sopenharmony_ci    14: 'lzma',
6907db96d56Sopenharmony_ci    18: 'terse',
6917db96d56Sopenharmony_ci    19: 'lz77',
6927db96d56Sopenharmony_ci    97: 'wavpack',
6937db96d56Sopenharmony_ci    98: 'ppmd',
6947db96d56Sopenharmony_ci}
6957db96d56Sopenharmony_ci
6967db96d56Sopenharmony_cidef _check_compression(compression):
6977db96d56Sopenharmony_ci    if compression == ZIP_STORED:
6987db96d56Sopenharmony_ci        pass
6997db96d56Sopenharmony_ci    elif compression == ZIP_DEFLATED:
7007db96d56Sopenharmony_ci        if not zlib:
7017db96d56Sopenharmony_ci            raise RuntimeError(
7027db96d56Sopenharmony_ci                "Compression requires the (missing) zlib module")
7037db96d56Sopenharmony_ci    elif compression == ZIP_BZIP2:
7047db96d56Sopenharmony_ci        if not bz2:
7057db96d56Sopenharmony_ci            raise RuntimeError(
7067db96d56Sopenharmony_ci                "Compression requires the (missing) bz2 module")
7077db96d56Sopenharmony_ci    elif compression == ZIP_LZMA:
7087db96d56Sopenharmony_ci        if not lzma:
7097db96d56Sopenharmony_ci            raise RuntimeError(
7107db96d56Sopenharmony_ci                "Compression requires the (missing) lzma module")
7117db96d56Sopenharmony_ci    else:
7127db96d56Sopenharmony_ci        raise NotImplementedError("That compression method is not supported")
7137db96d56Sopenharmony_ci
7147db96d56Sopenharmony_ci
7157db96d56Sopenharmony_cidef _get_compressor(compress_type, compresslevel=None):
7167db96d56Sopenharmony_ci    if compress_type == ZIP_DEFLATED:
7177db96d56Sopenharmony_ci        if compresslevel is not None:
7187db96d56Sopenharmony_ci            return zlib.compressobj(compresslevel, zlib.DEFLATED, -15)
7197db96d56Sopenharmony_ci        return zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15)
7207db96d56Sopenharmony_ci    elif compress_type == ZIP_BZIP2:
7217db96d56Sopenharmony_ci        if compresslevel is not None:
7227db96d56Sopenharmony_ci            return bz2.BZ2Compressor(compresslevel)
7237db96d56Sopenharmony_ci        return bz2.BZ2Compressor()
7247db96d56Sopenharmony_ci    # compresslevel is ignored for ZIP_LZMA
7257db96d56Sopenharmony_ci    elif compress_type == ZIP_LZMA:
7267db96d56Sopenharmony_ci        return LZMACompressor()
7277db96d56Sopenharmony_ci    else:
7287db96d56Sopenharmony_ci        return None
7297db96d56Sopenharmony_ci
7307db96d56Sopenharmony_ci
7317db96d56Sopenharmony_cidef _get_decompressor(compress_type):
7327db96d56Sopenharmony_ci    _check_compression(compress_type)
7337db96d56Sopenharmony_ci    if compress_type == ZIP_STORED:
7347db96d56Sopenharmony_ci        return None
7357db96d56Sopenharmony_ci    elif compress_type == ZIP_DEFLATED:
7367db96d56Sopenharmony_ci        return zlib.decompressobj(-15)
7377db96d56Sopenharmony_ci    elif compress_type == ZIP_BZIP2:
7387db96d56Sopenharmony_ci        return bz2.BZ2Decompressor()
7397db96d56Sopenharmony_ci    elif compress_type == ZIP_LZMA:
7407db96d56Sopenharmony_ci        return LZMADecompressor()
7417db96d56Sopenharmony_ci    else:
7427db96d56Sopenharmony_ci        descr = compressor_names.get(compress_type)
7437db96d56Sopenharmony_ci        if descr:
7447db96d56Sopenharmony_ci            raise NotImplementedError("compression type %d (%s)" % (compress_type, descr))
7457db96d56Sopenharmony_ci        else:
7467db96d56Sopenharmony_ci            raise NotImplementedError("compression type %d" % (compress_type,))
7477db96d56Sopenharmony_ci
7487db96d56Sopenharmony_ci
7497db96d56Sopenharmony_ciclass _SharedFile:
7507db96d56Sopenharmony_ci    def __init__(self, file, pos, close, lock, writing):
7517db96d56Sopenharmony_ci        self._file = file
7527db96d56Sopenharmony_ci        self._pos = pos
7537db96d56Sopenharmony_ci        self._close = close
7547db96d56Sopenharmony_ci        self._lock = lock
7557db96d56Sopenharmony_ci        self._writing = writing
7567db96d56Sopenharmony_ci        self.seekable = file.seekable
7577db96d56Sopenharmony_ci
7587db96d56Sopenharmony_ci    def tell(self):
7597db96d56Sopenharmony_ci        return self._pos
7607db96d56Sopenharmony_ci
7617db96d56Sopenharmony_ci    def seek(self, offset, whence=0):
7627db96d56Sopenharmony_ci        with self._lock:
7637db96d56Sopenharmony_ci            if self._writing():
7647db96d56Sopenharmony_ci                raise ValueError("Can't reposition in the ZIP file while "
7657db96d56Sopenharmony_ci                        "there is an open writing handle on it. "
7667db96d56Sopenharmony_ci                        "Close the writing handle before trying to read.")
7677db96d56Sopenharmony_ci            self._file.seek(offset, whence)
7687db96d56Sopenharmony_ci            self._pos = self._file.tell()
7697db96d56Sopenharmony_ci            return self._pos
7707db96d56Sopenharmony_ci
7717db96d56Sopenharmony_ci    def read(self, n=-1):
7727db96d56Sopenharmony_ci        with self._lock:
7737db96d56Sopenharmony_ci            if self._writing():
7747db96d56Sopenharmony_ci                raise ValueError("Can't read from the ZIP file while there "
7757db96d56Sopenharmony_ci                        "is an open writing handle on it. "
7767db96d56Sopenharmony_ci                        "Close the writing handle before trying to read.")
7777db96d56Sopenharmony_ci            self._file.seek(self._pos)
7787db96d56Sopenharmony_ci            data = self._file.read(n)
7797db96d56Sopenharmony_ci            self._pos = self._file.tell()
7807db96d56Sopenharmony_ci            return data
7817db96d56Sopenharmony_ci
7827db96d56Sopenharmony_ci    def close(self):
7837db96d56Sopenharmony_ci        if self._file is not None:
7847db96d56Sopenharmony_ci            fileobj = self._file
7857db96d56Sopenharmony_ci            self._file = None
7867db96d56Sopenharmony_ci            self._close(fileobj)
7877db96d56Sopenharmony_ci
7887db96d56Sopenharmony_ci# Provide the tell method for unseekable stream
7897db96d56Sopenharmony_ciclass _Tellable:
7907db96d56Sopenharmony_ci    def __init__(self, fp):
7917db96d56Sopenharmony_ci        self.fp = fp
7927db96d56Sopenharmony_ci        self.offset = 0
7937db96d56Sopenharmony_ci
7947db96d56Sopenharmony_ci    def write(self, data):
7957db96d56Sopenharmony_ci        n = self.fp.write(data)
7967db96d56Sopenharmony_ci        self.offset += n
7977db96d56Sopenharmony_ci        return n
7987db96d56Sopenharmony_ci
7997db96d56Sopenharmony_ci    def tell(self):
8007db96d56Sopenharmony_ci        return self.offset
8017db96d56Sopenharmony_ci
8027db96d56Sopenharmony_ci    def flush(self):
8037db96d56Sopenharmony_ci        self.fp.flush()
8047db96d56Sopenharmony_ci
8057db96d56Sopenharmony_ci    def close(self):
8067db96d56Sopenharmony_ci        self.fp.close()
8077db96d56Sopenharmony_ci
8087db96d56Sopenharmony_ci
8097db96d56Sopenharmony_ciclass ZipExtFile(io.BufferedIOBase):
8107db96d56Sopenharmony_ci    """File-like object for reading an archive member.
8117db96d56Sopenharmony_ci       Is returned by ZipFile.open().
8127db96d56Sopenharmony_ci    """
8137db96d56Sopenharmony_ci
8147db96d56Sopenharmony_ci    # Max size supported by decompressor.
8157db96d56Sopenharmony_ci    MAX_N = 1 << 31 - 1
8167db96d56Sopenharmony_ci
8177db96d56Sopenharmony_ci    # Read from compressed files in 4k blocks.
8187db96d56Sopenharmony_ci    MIN_READ_SIZE = 4096
8197db96d56Sopenharmony_ci
8207db96d56Sopenharmony_ci    # Chunk size to read during seek
8217db96d56Sopenharmony_ci    MAX_SEEK_READ = 1 << 24
8227db96d56Sopenharmony_ci
8237db96d56Sopenharmony_ci    def __init__(self, fileobj, mode, zipinfo, pwd=None,
8247db96d56Sopenharmony_ci                 close_fileobj=False):
8257db96d56Sopenharmony_ci        self._fileobj = fileobj
8267db96d56Sopenharmony_ci        self._pwd = pwd
8277db96d56Sopenharmony_ci        self._close_fileobj = close_fileobj
8287db96d56Sopenharmony_ci
8297db96d56Sopenharmony_ci        self._compress_type = zipinfo.compress_type
8307db96d56Sopenharmony_ci        self._compress_left = zipinfo.compress_size
8317db96d56Sopenharmony_ci        self._left = zipinfo.file_size
8327db96d56Sopenharmony_ci
8337db96d56Sopenharmony_ci        self._decompressor = _get_decompressor(self._compress_type)
8347db96d56Sopenharmony_ci
8357db96d56Sopenharmony_ci        self._eof = False
8367db96d56Sopenharmony_ci        self._readbuffer = b''
8377db96d56Sopenharmony_ci        self._offset = 0
8387db96d56Sopenharmony_ci
8397db96d56Sopenharmony_ci        self.newlines = None
8407db96d56Sopenharmony_ci
8417db96d56Sopenharmony_ci        self.mode = mode
8427db96d56Sopenharmony_ci        self.name = zipinfo.filename
8437db96d56Sopenharmony_ci
8447db96d56Sopenharmony_ci        if hasattr(zipinfo, 'CRC'):
8457db96d56Sopenharmony_ci            self._expected_crc = zipinfo.CRC
8467db96d56Sopenharmony_ci            self._running_crc = crc32(b'')
8477db96d56Sopenharmony_ci        else:
8487db96d56Sopenharmony_ci            self._expected_crc = None
8497db96d56Sopenharmony_ci
8507db96d56Sopenharmony_ci        self._seekable = False
8517db96d56Sopenharmony_ci        try:
8527db96d56Sopenharmony_ci            if fileobj.seekable():
8537db96d56Sopenharmony_ci                self._orig_compress_start = fileobj.tell()
8547db96d56Sopenharmony_ci                self._orig_compress_size = zipinfo.compress_size
8557db96d56Sopenharmony_ci                self._orig_file_size = zipinfo.file_size
8567db96d56Sopenharmony_ci                self._orig_start_crc = self._running_crc
8577db96d56Sopenharmony_ci                self._seekable = True
8587db96d56Sopenharmony_ci        except AttributeError:
8597db96d56Sopenharmony_ci            pass
8607db96d56Sopenharmony_ci
8617db96d56Sopenharmony_ci        self._decrypter = None
8627db96d56Sopenharmony_ci        if pwd:
8637db96d56Sopenharmony_ci            if zipinfo.flag_bits & _MASK_USE_DATA_DESCRIPTOR:
8647db96d56Sopenharmony_ci                # compare against the file type from extended local headers
8657db96d56Sopenharmony_ci                check_byte = (zipinfo._raw_time >> 8) & 0xff
8667db96d56Sopenharmony_ci            else:
8677db96d56Sopenharmony_ci                # compare against the CRC otherwise
8687db96d56Sopenharmony_ci                check_byte = (zipinfo.CRC >> 24) & 0xff
8697db96d56Sopenharmony_ci            h = self._init_decrypter()
8707db96d56Sopenharmony_ci            if h != check_byte:
8717db96d56Sopenharmony_ci                raise RuntimeError("Bad password for file %r" % zipinfo.orig_filename)
8727db96d56Sopenharmony_ci
8737db96d56Sopenharmony_ci
8747db96d56Sopenharmony_ci    def _init_decrypter(self):
8757db96d56Sopenharmony_ci        self._decrypter = _ZipDecrypter(self._pwd)
8767db96d56Sopenharmony_ci        # The first 12 bytes in the cypher stream is an encryption header
8777db96d56Sopenharmony_ci        #  used to strengthen the algorithm. The first 11 bytes are
8787db96d56Sopenharmony_ci        #  completely random, while the 12th contains the MSB of the CRC,
8797db96d56Sopenharmony_ci        #  or the MSB of the file time depending on the header type
8807db96d56Sopenharmony_ci        #  and is used to check the correctness of the password.
8817db96d56Sopenharmony_ci        header = self._fileobj.read(12)
8827db96d56Sopenharmony_ci        self._compress_left -= 12
8837db96d56Sopenharmony_ci        return self._decrypter(header)[11]
8847db96d56Sopenharmony_ci
8857db96d56Sopenharmony_ci    def __repr__(self):
8867db96d56Sopenharmony_ci        result = ['<%s.%s' % (self.__class__.__module__,
8877db96d56Sopenharmony_ci                              self.__class__.__qualname__)]
8887db96d56Sopenharmony_ci        if not self.closed:
8897db96d56Sopenharmony_ci            result.append(' name=%r mode=%r' % (self.name, self.mode))
8907db96d56Sopenharmony_ci            if self._compress_type != ZIP_STORED:
8917db96d56Sopenharmony_ci                result.append(' compress_type=%s' %
8927db96d56Sopenharmony_ci                              compressor_names.get(self._compress_type,
8937db96d56Sopenharmony_ci                                                   self._compress_type))
8947db96d56Sopenharmony_ci        else:
8957db96d56Sopenharmony_ci            result.append(' [closed]')
8967db96d56Sopenharmony_ci        result.append('>')
8977db96d56Sopenharmony_ci        return ''.join(result)
8987db96d56Sopenharmony_ci
8997db96d56Sopenharmony_ci    def readline(self, limit=-1):
9007db96d56Sopenharmony_ci        """Read and return a line from the stream.
9017db96d56Sopenharmony_ci
9027db96d56Sopenharmony_ci        If limit is specified, at most limit bytes will be read.
9037db96d56Sopenharmony_ci        """
9047db96d56Sopenharmony_ci
9057db96d56Sopenharmony_ci        if limit < 0:
9067db96d56Sopenharmony_ci            # Shortcut common case - newline found in buffer.
9077db96d56Sopenharmony_ci            i = self._readbuffer.find(b'\n', self._offset) + 1
9087db96d56Sopenharmony_ci            if i > 0:
9097db96d56Sopenharmony_ci                line = self._readbuffer[self._offset: i]
9107db96d56Sopenharmony_ci                self._offset = i
9117db96d56Sopenharmony_ci                return line
9127db96d56Sopenharmony_ci
9137db96d56Sopenharmony_ci        return io.BufferedIOBase.readline(self, limit)
9147db96d56Sopenharmony_ci
9157db96d56Sopenharmony_ci    def peek(self, n=1):
9167db96d56Sopenharmony_ci        """Returns buffered bytes without advancing the position."""
9177db96d56Sopenharmony_ci        if n > len(self._readbuffer) - self._offset:
9187db96d56Sopenharmony_ci            chunk = self.read(n)
9197db96d56Sopenharmony_ci            if len(chunk) > self._offset:
9207db96d56Sopenharmony_ci                self._readbuffer = chunk + self._readbuffer[self._offset:]
9217db96d56Sopenharmony_ci                self._offset = 0
9227db96d56Sopenharmony_ci            else:
9237db96d56Sopenharmony_ci                self._offset -= len(chunk)
9247db96d56Sopenharmony_ci
9257db96d56Sopenharmony_ci        # Return up to 512 bytes to reduce allocation overhead for tight loops.
9267db96d56Sopenharmony_ci        return self._readbuffer[self._offset: self._offset + 512]
9277db96d56Sopenharmony_ci
9287db96d56Sopenharmony_ci    def readable(self):
9297db96d56Sopenharmony_ci        if self.closed:
9307db96d56Sopenharmony_ci            raise ValueError("I/O operation on closed file.")
9317db96d56Sopenharmony_ci        return True
9327db96d56Sopenharmony_ci
9337db96d56Sopenharmony_ci    def read(self, n=-1):
9347db96d56Sopenharmony_ci        """Read and return up to n bytes.
9357db96d56Sopenharmony_ci        If the argument is omitted, None, or negative, data is read and returned until EOF is reached.
9367db96d56Sopenharmony_ci        """
9377db96d56Sopenharmony_ci        if self.closed:
9387db96d56Sopenharmony_ci            raise ValueError("read from closed file.")
9397db96d56Sopenharmony_ci        if n is None or n < 0:
9407db96d56Sopenharmony_ci            buf = self._readbuffer[self._offset:]
9417db96d56Sopenharmony_ci            self._readbuffer = b''
9427db96d56Sopenharmony_ci            self._offset = 0
9437db96d56Sopenharmony_ci            while not self._eof:
9447db96d56Sopenharmony_ci                buf += self._read1(self.MAX_N)
9457db96d56Sopenharmony_ci            return buf
9467db96d56Sopenharmony_ci
9477db96d56Sopenharmony_ci        end = n + self._offset
9487db96d56Sopenharmony_ci        if end < len(self._readbuffer):
9497db96d56Sopenharmony_ci            buf = self._readbuffer[self._offset:end]
9507db96d56Sopenharmony_ci            self._offset = end
9517db96d56Sopenharmony_ci            return buf
9527db96d56Sopenharmony_ci
9537db96d56Sopenharmony_ci        n = end - len(self._readbuffer)
9547db96d56Sopenharmony_ci        buf = self._readbuffer[self._offset:]
9557db96d56Sopenharmony_ci        self._readbuffer = b''
9567db96d56Sopenharmony_ci        self._offset = 0
9577db96d56Sopenharmony_ci        while n > 0 and not self._eof:
9587db96d56Sopenharmony_ci            data = self._read1(n)
9597db96d56Sopenharmony_ci            if n < len(data):
9607db96d56Sopenharmony_ci                self._readbuffer = data
9617db96d56Sopenharmony_ci                self._offset = n
9627db96d56Sopenharmony_ci                buf += data[:n]
9637db96d56Sopenharmony_ci                break
9647db96d56Sopenharmony_ci            buf += data
9657db96d56Sopenharmony_ci            n -= len(data)
9667db96d56Sopenharmony_ci        return buf
9677db96d56Sopenharmony_ci
9687db96d56Sopenharmony_ci    def _update_crc(self, newdata):
9697db96d56Sopenharmony_ci        # Update the CRC using the given data.
9707db96d56Sopenharmony_ci        if self._expected_crc is None:
9717db96d56Sopenharmony_ci            # No need to compute the CRC if we don't have a reference value
9727db96d56Sopenharmony_ci            return
9737db96d56Sopenharmony_ci        self._running_crc = crc32(newdata, self._running_crc)
9747db96d56Sopenharmony_ci        # Check the CRC if we're at the end of the file
9757db96d56Sopenharmony_ci        if self._eof and self._running_crc != self._expected_crc:
9767db96d56Sopenharmony_ci            raise BadZipFile("Bad CRC-32 for file %r" % self.name)
9777db96d56Sopenharmony_ci
9787db96d56Sopenharmony_ci    def read1(self, n):
9797db96d56Sopenharmony_ci        """Read up to n bytes with at most one read() system call."""
9807db96d56Sopenharmony_ci
9817db96d56Sopenharmony_ci        if n is None or n < 0:
9827db96d56Sopenharmony_ci            buf = self._readbuffer[self._offset:]
9837db96d56Sopenharmony_ci            self._readbuffer = b''
9847db96d56Sopenharmony_ci            self._offset = 0
9857db96d56Sopenharmony_ci            while not self._eof:
9867db96d56Sopenharmony_ci                data = self._read1(self.MAX_N)
9877db96d56Sopenharmony_ci                if data:
9887db96d56Sopenharmony_ci                    buf += data
9897db96d56Sopenharmony_ci                    break
9907db96d56Sopenharmony_ci            return buf
9917db96d56Sopenharmony_ci
9927db96d56Sopenharmony_ci        end = n + self._offset
9937db96d56Sopenharmony_ci        if end < len(self._readbuffer):
9947db96d56Sopenharmony_ci            buf = self._readbuffer[self._offset:end]
9957db96d56Sopenharmony_ci            self._offset = end
9967db96d56Sopenharmony_ci            return buf
9977db96d56Sopenharmony_ci
9987db96d56Sopenharmony_ci        n = end - len(self._readbuffer)
9997db96d56Sopenharmony_ci        buf = self._readbuffer[self._offset:]
10007db96d56Sopenharmony_ci        self._readbuffer = b''
10017db96d56Sopenharmony_ci        self._offset = 0
10027db96d56Sopenharmony_ci        if n > 0:
10037db96d56Sopenharmony_ci            while not self._eof:
10047db96d56Sopenharmony_ci                data = self._read1(n)
10057db96d56Sopenharmony_ci                if n < len(data):
10067db96d56Sopenharmony_ci                    self._readbuffer = data
10077db96d56Sopenharmony_ci                    self._offset = n
10087db96d56Sopenharmony_ci                    buf += data[:n]
10097db96d56Sopenharmony_ci                    break
10107db96d56Sopenharmony_ci                if data:
10117db96d56Sopenharmony_ci                    buf += data
10127db96d56Sopenharmony_ci                    break
10137db96d56Sopenharmony_ci        return buf
10147db96d56Sopenharmony_ci
10157db96d56Sopenharmony_ci    def _read1(self, n):
10167db96d56Sopenharmony_ci        # Read up to n compressed bytes with at most one read() system call,
10177db96d56Sopenharmony_ci        # decrypt and decompress them.
10187db96d56Sopenharmony_ci        if self._eof or n <= 0:
10197db96d56Sopenharmony_ci            return b''
10207db96d56Sopenharmony_ci
10217db96d56Sopenharmony_ci        # Read from file.
10227db96d56Sopenharmony_ci        if self._compress_type == ZIP_DEFLATED:
10237db96d56Sopenharmony_ci            ## Handle unconsumed data.
10247db96d56Sopenharmony_ci            data = self._decompressor.unconsumed_tail
10257db96d56Sopenharmony_ci            if n > len(data):
10267db96d56Sopenharmony_ci                data += self._read2(n - len(data))
10277db96d56Sopenharmony_ci        else:
10287db96d56Sopenharmony_ci            data = self._read2(n)
10297db96d56Sopenharmony_ci
10307db96d56Sopenharmony_ci        if self._compress_type == ZIP_STORED:
10317db96d56Sopenharmony_ci            self._eof = self._compress_left <= 0
10327db96d56Sopenharmony_ci        elif self._compress_type == ZIP_DEFLATED:
10337db96d56Sopenharmony_ci            n = max(n, self.MIN_READ_SIZE)
10347db96d56Sopenharmony_ci            data = self._decompressor.decompress(data, n)
10357db96d56Sopenharmony_ci            self._eof = (self._decompressor.eof or
10367db96d56Sopenharmony_ci                         self._compress_left <= 0 and
10377db96d56Sopenharmony_ci                         not self._decompressor.unconsumed_tail)
10387db96d56Sopenharmony_ci            if self._eof:
10397db96d56Sopenharmony_ci                data += self._decompressor.flush()
10407db96d56Sopenharmony_ci        else:
10417db96d56Sopenharmony_ci            data = self._decompressor.decompress(data)
10427db96d56Sopenharmony_ci            self._eof = self._decompressor.eof or self._compress_left <= 0
10437db96d56Sopenharmony_ci
10447db96d56Sopenharmony_ci        data = data[:self._left]
10457db96d56Sopenharmony_ci        self._left -= len(data)
10467db96d56Sopenharmony_ci        if self._left <= 0:
10477db96d56Sopenharmony_ci            self._eof = True
10487db96d56Sopenharmony_ci        self._update_crc(data)
10497db96d56Sopenharmony_ci        return data
10507db96d56Sopenharmony_ci
10517db96d56Sopenharmony_ci    def _read2(self, n):
10527db96d56Sopenharmony_ci        if self._compress_left <= 0:
10537db96d56Sopenharmony_ci            return b''
10547db96d56Sopenharmony_ci
10557db96d56Sopenharmony_ci        n = max(n, self.MIN_READ_SIZE)
10567db96d56Sopenharmony_ci        n = min(n, self._compress_left)
10577db96d56Sopenharmony_ci
10587db96d56Sopenharmony_ci        data = self._fileobj.read(n)
10597db96d56Sopenharmony_ci        self._compress_left -= len(data)
10607db96d56Sopenharmony_ci        if not data:
10617db96d56Sopenharmony_ci            raise EOFError
10627db96d56Sopenharmony_ci
10637db96d56Sopenharmony_ci        if self._decrypter is not None:
10647db96d56Sopenharmony_ci            data = self._decrypter(data)
10657db96d56Sopenharmony_ci        return data
10667db96d56Sopenharmony_ci
10677db96d56Sopenharmony_ci    def close(self):
10687db96d56Sopenharmony_ci        try:
10697db96d56Sopenharmony_ci            if self._close_fileobj:
10707db96d56Sopenharmony_ci                self._fileobj.close()
10717db96d56Sopenharmony_ci        finally:
10727db96d56Sopenharmony_ci            super().close()
10737db96d56Sopenharmony_ci
10747db96d56Sopenharmony_ci    def seekable(self):
10757db96d56Sopenharmony_ci        if self.closed:
10767db96d56Sopenharmony_ci            raise ValueError("I/O operation on closed file.")
10777db96d56Sopenharmony_ci        return self._seekable
10787db96d56Sopenharmony_ci
10797db96d56Sopenharmony_ci    def seek(self, offset, whence=0):
10807db96d56Sopenharmony_ci        if self.closed:
10817db96d56Sopenharmony_ci            raise ValueError("seek on closed file.")
10827db96d56Sopenharmony_ci        if not self._seekable:
10837db96d56Sopenharmony_ci            raise io.UnsupportedOperation("underlying stream is not seekable")
10847db96d56Sopenharmony_ci        curr_pos = self.tell()
10857db96d56Sopenharmony_ci        if whence == 0: # Seek from start of file
10867db96d56Sopenharmony_ci            new_pos = offset
10877db96d56Sopenharmony_ci        elif whence == 1: # Seek from current position
10887db96d56Sopenharmony_ci            new_pos = curr_pos + offset
10897db96d56Sopenharmony_ci        elif whence == 2: # Seek from EOF
10907db96d56Sopenharmony_ci            new_pos = self._orig_file_size + offset
10917db96d56Sopenharmony_ci        else:
10927db96d56Sopenharmony_ci            raise ValueError("whence must be os.SEEK_SET (0), "
10937db96d56Sopenharmony_ci                             "os.SEEK_CUR (1), or os.SEEK_END (2)")
10947db96d56Sopenharmony_ci
10957db96d56Sopenharmony_ci        if new_pos > self._orig_file_size:
10967db96d56Sopenharmony_ci            new_pos = self._orig_file_size
10977db96d56Sopenharmony_ci
10987db96d56Sopenharmony_ci        if new_pos < 0:
10997db96d56Sopenharmony_ci            new_pos = 0
11007db96d56Sopenharmony_ci
11017db96d56Sopenharmony_ci        read_offset = new_pos - curr_pos
11027db96d56Sopenharmony_ci        buff_offset = read_offset + self._offset
11037db96d56Sopenharmony_ci
11047db96d56Sopenharmony_ci        if buff_offset >= 0 and buff_offset < len(self._readbuffer):
11057db96d56Sopenharmony_ci            # Just move the _offset index if the new position is in the _readbuffer
11067db96d56Sopenharmony_ci            self._offset = buff_offset
11077db96d56Sopenharmony_ci            read_offset = 0
11087db96d56Sopenharmony_ci        elif read_offset < 0:
11097db96d56Sopenharmony_ci            # Position is before the current position. Reset the ZipExtFile
11107db96d56Sopenharmony_ci            self._fileobj.seek(self._orig_compress_start)
11117db96d56Sopenharmony_ci            self._running_crc = self._orig_start_crc
11127db96d56Sopenharmony_ci            self._compress_left = self._orig_compress_size
11137db96d56Sopenharmony_ci            self._left = self._orig_file_size
11147db96d56Sopenharmony_ci            self._readbuffer = b''
11157db96d56Sopenharmony_ci            self._offset = 0
11167db96d56Sopenharmony_ci            self._decompressor = _get_decompressor(self._compress_type)
11177db96d56Sopenharmony_ci            self._eof = False
11187db96d56Sopenharmony_ci            read_offset = new_pos
11197db96d56Sopenharmony_ci            if self._decrypter is not None:
11207db96d56Sopenharmony_ci                self._init_decrypter()
11217db96d56Sopenharmony_ci
11227db96d56Sopenharmony_ci        while read_offset > 0:
11237db96d56Sopenharmony_ci            read_len = min(self.MAX_SEEK_READ, read_offset)
11247db96d56Sopenharmony_ci            self.read(read_len)
11257db96d56Sopenharmony_ci            read_offset -= read_len
11267db96d56Sopenharmony_ci
11277db96d56Sopenharmony_ci        return self.tell()
11287db96d56Sopenharmony_ci
11297db96d56Sopenharmony_ci    def tell(self):
11307db96d56Sopenharmony_ci        if self.closed:
11317db96d56Sopenharmony_ci            raise ValueError("tell on closed file.")
11327db96d56Sopenharmony_ci        if not self._seekable:
11337db96d56Sopenharmony_ci            raise io.UnsupportedOperation("underlying stream is not seekable")
11347db96d56Sopenharmony_ci        filepos = self._orig_file_size - self._left - len(self._readbuffer) + self._offset
11357db96d56Sopenharmony_ci        return filepos
11367db96d56Sopenharmony_ci
11377db96d56Sopenharmony_ci
11387db96d56Sopenharmony_ciclass _ZipWriteFile(io.BufferedIOBase):
11397db96d56Sopenharmony_ci    def __init__(self, zf, zinfo, zip64):
11407db96d56Sopenharmony_ci        self._zinfo = zinfo
11417db96d56Sopenharmony_ci        self._zip64 = zip64
11427db96d56Sopenharmony_ci        self._zipfile = zf
11437db96d56Sopenharmony_ci        self._compressor = _get_compressor(zinfo.compress_type,
11447db96d56Sopenharmony_ci                                           zinfo._compresslevel)
11457db96d56Sopenharmony_ci        self._file_size = 0
11467db96d56Sopenharmony_ci        self._compress_size = 0
11477db96d56Sopenharmony_ci        self._crc = 0
11487db96d56Sopenharmony_ci
11497db96d56Sopenharmony_ci    @property
11507db96d56Sopenharmony_ci    def _fileobj(self):
11517db96d56Sopenharmony_ci        return self._zipfile.fp
11527db96d56Sopenharmony_ci
11537db96d56Sopenharmony_ci    def writable(self):
11547db96d56Sopenharmony_ci        return True
11557db96d56Sopenharmony_ci
11567db96d56Sopenharmony_ci    def write(self, data):
11577db96d56Sopenharmony_ci        if self.closed:
11587db96d56Sopenharmony_ci            raise ValueError('I/O operation on closed file.')
11597db96d56Sopenharmony_ci
11607db96d56Sopenharmony_ci        # Accept any data that supports the buffer protocol
11617db96d56Sopenharmony_ci        if isinstance(data, (bytes, bytearray)):
11627db96d56Sopenharmony_ci            nbytes = len(data)
11637db96d56Sopenharmony_ci        else:
11647db96d56Sopenharmony_ci            data = memoryview(data)
11657db96d56Sopenharmony_ci            nbytes = data.nbytes
11667db96d56Sopenharmony_ci        self._file_size += nbytes
11677db96d56Sopenharmony_ci
11687db96d56Sopenharmony_ci        self._crc = crc32(data, self._crc)
11697db96d56Sopenharmony_ci        if self._compressor:
11707db96d56Sopenharmony_ci            data = self._compressor.compress(data)
11717db96d56Sopenharmony_ci            self._compress_size += len(data)
11727db96d56Sopenharmony_ci        self._fileobj.write(data)
11737db96d56Sopenharmony_ci        return nbytes
11747db96d56Sopenharmony_ci
11757db96d56Sopenharmony_ci    def close(self):
11767db96d56Sopenharmony_ci        if self.closed:
11777db96d56Sopenharmony_ci            return
11787db96d56Sopenharmony_ci        try:
11797db96d56Sopenharmony_ci            super().close()
11807db96d56Sopenharmony_ci            # Flush any data from the compressor, and update header info
11817db96d56Sopenharmony_ci            if self._compressor:
11827db96d56Sopenharmony_ci                buf = self._compressor.flush()
11837db96d56Sopenharmony_ci                self._compress_size += len(buf)
11847db96d56Sopenharmony_ci                self._fileobj.write(buf)
11857db96d56Sopenharmony_ci                self._zinfo.compress_size = self._compress_size
11867db96d56Sopenharmony_ci            else:
11877db96d56Sopenharmony_ci                self._zinfo.compress_size = self._file_size
11887db96d56Sopenharmony_ci            self._zinfo.CRC = self._crc
11897db96d56Sopenharmony_ci            self._zinfo.file_size = self._file_size
11907db96d56Sopenharmony_ci
11917db96d56Sopenharmony_ci            if not self._zip64:
11927db96d56Sopenharmony_ci                if self._file_size > ZIP64_LIMIT:
11937db96d56Sopenharmony_ci                    raise RuntimeError("File size too large, try using force_zip64")
11947db96d56Sopenharmony_ci                if self._compress_size > ZIP64_LIMIT:
11957db96d56Sopenharmony_ci                    raise RuntimeError("Compressed size too large, try using force_zip64")
11967db96d56Sopenharmony_ci
11977db96d56Sopenharmony_ci            # Write updated header info
11987db96d56Sopenharmony_ci            if self._zinfo.flag_bits & _MASK_USE_DATA_DESCRIPTOR:
11997db96d56Sopenharmony_ci                # Write CRC and file sizes after the file data
12007db96d56Sopenharmony_ci                fmt = '<LLQQ' if self._zip64 else '<LLLL'
12017db96d56Sopenharmony_ci                self._fileobj.write(struct.pack(fmt, _DD_SIGNATURE, self._zinfo.CRC,
12027db96d56Sopenharmony_ci                    self._zinfo.compress_size, self._zinfo.file_size))
12037db96d56Sopenharmony_ci                self._zipfile.start_dir = self._fileobj.tell()
12047db96d56Sopenharmony_ci            else:
12057db96d56Sopenharmony_ci                # Seek backwards and write file header (which will now include
12067db96d56Sopenharmony_ci                # correct CRC and file sizes)
12077db96d56Sopenharmony_ci
12087db96d56Sopenharmony_ci                # Preserve current position in file
12097db96d56Sopenharmony_ci                self._zipfile.start_dir = self._fileobj.tell()
12107db96d56Sopenharmony_ci                self._fileobj.seek(self._zinfo.header_offset)
12117db96d56Sopenharmony_ci                self._fileobj.write(self._zinfo.FileHeader(self._zip64))
12127db96d56Sopenharmony_ci                self._fileobj.seek(self._zipfile.start_dir)
12137db96d56Sopenharmony_ci
12147db96d56Sopenharmony_ci            # Successfully written: Add file to our caches
12157db96d56Sopenharmony_ci            self._zipfile.filelist.append(self._zinfo)
12167db96d56Sopenharmony_ci            self._zipfile.NameToInfo[self._zinfo.filename] = self._zinfo
12177db96d56Sopenharmony_ci        finally:
12187db96d56Sopenharmony_ci            self._zipfile._writing = False
12197db96d56Sopenharmony_ci
12207db96d56Sopenharmony_ci
12217db96d56Sopenharmony_ci
12227db96d56Sopenharmony_ciclass ZipFile:
12237db96d56Sopenharmony_ci    """ Class with methods to open, read, write, close, list zip files.
12247db96d56Sopenharmony_ci
12257db96d56Sopenharmony_ci    z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=True,
12267db96d56Sopenharmony_ci                compresslevel=None)
12277db96d56Sopenharmony_ci
12287db96d56Sopenharmony_ci    file: Either the path to the file, or a file-like object.
12297db96d56Sopenharmony_ci          If it is a path, the file will be opened and closed by ZipFile.
12307db96d56Sopenharmony_ci    mode: The mode can be either read 'r', write 'w', exclusive create 'x',
12317db96d56Sopenharmony_ci          or append 'a'.
12327db96d56Sopenharmony_ci    compression: ZIP_STORED (no compression), ZIP_DEFLATED (requires zlib),
12337db96d56Sopenharmony_ci                 ZIP_BZIP2 (requires bz2) or ZIP_LZMA (requires lzma).
12347db96d56Sopenharmony_ci    allowZip64: if True ZipFile will create files with ZIP64 extensions when
12357db96d56Sopenharmony_ci                needed, otherwise it will raise an exception when this would
12367db96d56Sopenharmony_ci                be necessary.
12377db96d56Sopenharmony_ci    compresslevel: None (default for the given compression type) or an integer
12387db96d56Sopenharmony_ci                   specifying the level to pass to the compressor.
12397db96d56Sopenharmony_ci                   When using ZIP_STORED or ZIP_LZMA this keyword has no effect.
12407db96d56Sopenharmony_ci                   When using ZIP_DEFLATED integers 0 through 9 are accepted.
12417db96d56Sopenharmony_ci                   When using ZIP_BZIP2 integers 1 through 9 are accepted.
12427db96d56Sopenharmony_ci
12437db96d56Sopenharmony_ci    """
12447db96d56Sopenharmony_ci
12457db96d56Sopenharmony_ci    fp = None                   # Set here since __del__ checks it
12467db96d56Sopenharmony_ci    _windows_illegal_name_trans_table = None
12477db96d56Sopenharmony_ci
12487db96d56Sopenharmony_ci    def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True,
12497db96d56Sopenharmony_ci                 compresslevel=None, *, strict_timestamps=True, metadata_encoding=None):
12507db96d56Sopenharmony_ci        """Open the ZIP file with mode read 'r', write 'w', exclusive create 'x',
12517db96d56Sopenharmony_ci        or append 'a'."""
12527db96d56Sopenharmony_ci        if mode not in ('r', 'w', 'x', 'a'):
12537db96d56Sopenharmony_ci            raise ValueError("ZipFile requires mode 'r', 'w', 'x', or 'a'")
12547db96d56Sopenharmony_ci
12557db96d56Sopenharmony_ci        _check_compression(compression)
12567db96d56Sopenharmony_ci
12577db96d56Sopenharmony_ci        self._allowZip64 = allowZip64
12587db96d56Sopenharmony_ci        self._didModify = False
12597db96d56Sopenharmony_ci        self.debug = 0  # Level of printing: 0 through 3
12607db96d56Sopenharmony_ci        self.NameToInfo = {}    # Find file info given name
12617db96d56Sopenharmony_ci        self.filelist = []      # List of ZipInfo instances for archive
12627db96d56Sopenharmony_ci        self.compression = compression  # Method of compression
12637db96d56Sopenharmony_ci        self.compresslevel = compresslevel
12647db96d56Sopenharmony_ci        self.mode = mode
12657db96d56Sopenharmony_ci        self.pwd = None
12667db96d56Sopenharmony_ci        self._comment = b''
12677db96d56Sopenharmony_ci        self._strict_timestamps = strict_timestamps
12687db96d56Sopenharmony_ci        self.metadata_encoding = metadata_encoding
12697db96d56Sopenharmony_ci
12707db96d56Sopenharmony_ci        # Check that we don't try to write with nonconforming codecs
12717db96d56Sopenharmony_ci        if self.metadata_encoding and mode != 'r':
12727db96d56Sopenharmony_ci            raise ValueError(
12737db96d56Sopenharmony_ci                "metadata_encoding is only supported for reading files")
12747db96d56Sopenharmony_ci
12757db96d56Sopenharmony_ci        # Check if we were passed a file-like object
12767db96d56Sopenharmony_ci        if isinstance(file, os.PathLike):
12777db96d56Sopenharmony_ci            file = os.fspath(file)
12787db96d56Sopenharmony_ci        if isinstance(file, str):
12797db96d56Sopenharmony_ci            # No, it's a filename
12807db96d56Sopenharmony_ci            self._filePassed = 0
12817db96d56Sopenharmony_ci            self.filename = file
12827db96d56Sopenharmony_ci            modeDict = {'r' : 'rb', 'w': 'w+b', 'x': 'x+b', 'a' : 'r+b',
12837db96d56Sopenharmony_ci                        'r+b': 'w+b', 'w+b': 'wb', 'x+b': 'xb'}
12847db96d56Sopenharmony_ci            filemode = modeDict[mode]
12857db96d56Sopenharmony_ci            while True:
12867db96d56Sopenharmony_ci                try:
12877db96d56Sopenharmony_ci                    self.fp = io.open(file, filemode)
12887db96d56Sopenharmony_ci                except OSError:
12897db96d56Sopenharmony_ci                    if filemode in modeDict:
12907db96d56Sopenharmony_ci                        filemode = modeDict[filemode]
12917db96d56Sopenharmony_ci                        continue
12927db96d56Sopenharmony_ci                    raise
12937db96d56Sopenharmony_ci                break
12947db96d56Sopenharmony_ci        else:
12957db96d56Sopenharmony_ci            self._filePassed = 1
12967db96d56Sopenharmony_ci            self.fp = file
12977db96d56Sopenharmony_ci            self.filename = getattr(file, 'name', None)
12987db96d56Sopenharmony_ci        self._fileRefCnt = 1
12997db96d56Sopenharmony_ci        self._lock = threading.RLock()
13007db96d56Sopenharmony_ci        self._seekable = True
13017db96d56Sopenharmony_ci        self._writing = False
13027db96d56Sopenharmony_ci
13037db96d56Sopenharmony_ci        try:
13047db96d56Sopenharmony_ci            if mode == 'r':
13057db96d56Sopenharmony_ci                self._RealGetContents()
13067db96d56Sopenharmony_ci            elif mode in ('w', 'x'):
13077db96d56Sopenharmony_ci                # set the modified flag so central directory gets written
13087db96d56Sopenharmony_ci                # even if no files are added to the archive
13097db96d56Sopenharmony_ci                self._didModify = True
13107db96d56Sopenharmony_ci                try:
13117db96d56Sopenharmony_ci                    self.start_dir = self.fp.tell()
13127db96d56Sopenharmony_ci                except (AttributeError, OSError):
13137db96d56Sopenharmony_ci                    self.fp = _Tellable(self.fp)
13147db96d56Sopenharmony_ci                    self.start_dir = 0
13157db96d56Sopenharmony_ci                    self._seekable = False
13167db96d56Sopenharmony_ci                else:
13177db96d56Sopenharmony_ci                    # Some file-like objects can provide tell() but not seek()
13187db96d56Sopenharmony_ci                    try:
13197db96d56Sopenharmony_ci                        self.fp.seek(self.start_dir)
13207db96d56Sopenharmony_ci                    except (AttributeError, OSError):
13217db96d56Sopenharmony_ci                        self._seekable = False
13227db96d56Sopenharmony_ci            elif mode == 'a':
13237db96d56Sopenharmony_ci                try:
13247db96d56Sopenharmony_ci                    # See if file is a zip file
13257db96d56Sopenharmony_ci                    self._RealGetContents()
13267db96d56Sopenharmony_ci                    # seek to start of directory and overwrite
13277db96d56Sopenharmony_ci                    self.fp.seek(self.start_dir)
13287db96d56Sopenharmony_ci                except BadZipFile:
13297db96d56Sopenharmony_ci                    # file is not a zip file, just append
13307db96d56Sopenharmony_ci                    self.fp.seek(0, 2)
13317db96d56Sopenharmony_ci
13327db96d56Sopenharmony_ci                    # set the modified flag so central directory gets written
13337db96d56Sopenharmony_ci                    # even if no files are added to the archive
13347db96d56Sopenharmony_ci                    self._didModify = True
13357db96d56Sopenharmony_ci                    self.start_dir = self.fp.tell()
13367db96d56Sopenharmony_ci            else:
13377db96d56Sopenharmony_ci                raise ValueError("Mode must be 'r', 'w', 'x', or 'a'")
13387db96d56Sopenharmony_ci        except:
13397db96d56Sopenharmony_ci            fp = self.fp
13407db96d56Sopenharmony_ci            self.fp = None
13417db96d56Sopenharmony_ci            self._fpclose(fp)
13427db96d56Sopenharmony_ci            raise
13437db96d56Sopenharmony_ci
13447db96d56Sopenharmony_ci    def __enter__(self):
13457db96d56Sopenharmony_ci        return self
13467db96d56Sopenharmony_ci
13477db96d56Sopenharmony_ci    def __exit__(self, type, value, traceback):
13487db96d56Sopenharmony_ci        self.close()
13497db96d56Sopenharmony_ci
13507db96d56Sopenharmony_ci    def __repr__(self):
13517db96d56Sopenharmony_ci        result = ['<%s.%s' % (self.__class__.__module__,
13527db96d56Sopenharmony_ci                              self.__class__.__qualname__)]
13537db96d56Sopenharmony_ci        if self.fp is not None:
13547db96d56Sopenharmony_ci            if self._filePassed:
13557db96d56Sopenharmony_ci                result.append(' file=%r' % self.fp)
13567db96d56Sopenharmony_ci            elif self.filename is not None:
13577db96d56Sopenharmony_ci                result.append(' filename=%r' % self.filename)
13587db96d56Sopenharmony_ci            result.append(' mode=%r' % self.mode)
13597db96d56Sopenharmony_ci        else:
13607db96d56Sopenharmony_ci            result.append(' [closed]')
13617db96d56Sopenharmony_ci        result.append('>')
13627db96d56Sopenharmony_ci        return ''.join(result)
13637db96d56Sopenharmony_ci
13647db96d56Sopenharmony_ci    def _RealGetContents(self):
13657db96d56Sopenharmony_ci        """Read in the table of contents for the ZIP file."""
13667db96d56Sopenharmony_ci        fp = self.fp
13677db96d56Sopenharmony_ci        try:
13687db96d56Sopenharmony_ci            endrec = _EndRecData(fp)
13697db96d56Sopenharmony_ci        except OSError:
13707db96d56Sopenharmony_ci            raise BadZipFile("File is not a zip file")
13717db96d56Sopenharmony_ci        if not endrec:
13727db96d56Sopenharmony_ci            raise BadZipFile("File is not a zip file")
13737db96d56Sopenharmony_ci        if self.debug > 1:
13747db96d56Sopenharmony_ci            print(endrec)
13757db96d56Sopenharmony_ci        size_cd = endrec[_ECD_SIZE]             # bytes in central directory
13767db96d56Sopenharmony_ci        offset_cd = endrec[_ECD_OFFSET]         # offset of central directory
13777db96d56Sopenharmony_ci        self._comment = endrec[_ECD_COMMENT]    # archive comment
13787db96d56Sopenharmony_ci
13797db96d56Sopenharmony_ci        # "concat" is zero, unless zip was concatenated to another file
13807db96d56Sopenharmony_ci        concat = endrec[_ECD_LOCATION] - size_cd - offset_cd
13817db96d56Sopenharmony_ci        if endrec[_ECD_SIGNATURE] == stringEndArchive64:
13827db96d56Sopenharmony_ci            # If Zip64 extension structures are present, account for them
13837db96d56Sopenharmony_ci            concat -= (sizeEndCentDir64 + sizeEndCentDir64Locator)
13847db96d56Sopenharmony_ci
13857db96d56Sopenharmony_ci        if self.debug > 2:
13867db96d56Sopenharmony_ci            inferred = concat + offset_cd
13877db96d56Sopenharmony_ci            print("given, inferred, offset", offset_cd, inferred, concat)
13887db96d56Sopenharmony_ci        # self.start_dir:  Position of start of central directory
13897db96d56Sopenharmony_ci        self.start_dir = offset_cd + concat
13907db96d56Sopenharmony_ci        if self.start_dir < 0:
13917db96d56Sopenharmony_ci            raise BadZipFile("Bad offset for central directory")
13927db96d56Sopenharmony_ci        fp.seek(self.start_dir, 0)
13937db96d56Sopenharmony_ci        data = fp.read(size_cd)
13947db96d56Sopenharmony_ci        fp = io.BytesIO(data)
13957db96d56Sopenharmony_ci        total = 0
13967db96d56Sopenharmony_ci        while total < size_cd:
13977db96d56Sopenharmony_ci            centdir = fp.read(sizeCentralDir)
13987db96d56Sopenharmony_ci            if len(centdir) != sizeCentralDir:
13997db96d56Sopenharmony_ci                raise BadZipFile("Truncated central directory")
14007db96d56Sopenharmony_ci            centdir = struct.unpack(structCentralDir, centdir)
14017db96d56Sopenharmony_ci            if centdir[_CD_SIGNATURE] != stringCentralDir:
14027db96d56Sopenharmony_ci                raise BadZipFile("Bad magic number for central directory")
14037db96d56Sopenharmony_ci            if self.debug > 2:
14047db96d56Sopenharmony_ci                print(centdir)
14057db96d56Sopenharmony_ci            filename = fp.read(centdir[_CD_FILENAME_LENGTH])
14067db96d56Sopenharmony_ci            flags = centdir[_CD_FLAG_BITS]
14077db96d56Sopenharmony_ci            if flags & _MASK_UTF_FILENAME:
14087db96d56Sopenharmony_ci                # UTF-8 file names extension
14097db96d56Sopenharmony_ci                filename = filename.decode('utf-8')
14107db96d56Sopenharmony_ci            else:
14117db96d56Sopenharmony_ci                # Historical ZIP filename encoding
14127db96d56Sopenharmony_ci                filename = filename.decode(self.metadata_encoding or 'cp437')
14137db96d56Sopenharmony_ci            # Create ZipInfo instance to store file information
14147db96d56Sopenharmony_ci            x = ZipInfo(filename)
14157db96d56Sopenharmony_ci            x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH])
14167db96d56Sopenharmony_ci            x.comment = fp.read(centdir[_CD_COMMENT_LENGTH])
14177db96d56Sopenharmony_ci            x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET]
14187db96d56Sopenharmony_ci            (x.create_version, x.create_system, x.extract_version, x.reserved,
14197db96d56Sopenharmony_ci             x.flag_bits, x.compress_type, t, d,
14207db96d56Sopenharmony_ci             x.CRC, x.compress_size, x.file_size) = centdir[1:12]
14217db96d56Sopenharmony_ci            if x.extract_version > MAX_EXTRACT_VERSION:
14227db96d56Sopenharmony_ci                raise NotImplementedError("zip file version %.1f" %
14237db96d56Sopenharmony_ci                                          (x.extract_version / 10))
14247db96d56Sopenharmony_ci            x.volume, x.internal_attr, x.external_attr = centdir[15:18]
14257db96d56Sopenharmony_ci            # Convert date/time code to (year, month, day, hour, min, sec)
14267db96d56Sopenharmony_ci            x._raw_time = t
14277db96d56Sopenharmony_ci            x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
14287db96d56Sopenharmony_ci                            t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
14297db96d56Sopenharmony_ci
14307db96d56Sopenharmony_ci            x._decodeExtra()
14317db96d56Sopenharmony_ci            x.header_offset = x.header_offset + concat
14327db96d56Sopenharmony_ci            self.filelist.append(x)
14337db96d56Sopenharmony_ci            self.NameToInfo[x.filename] = x
14347db96d56Sopenharmony_ci
14357db96d56Sopenharmony_ci            # update total bytes read from central directory
14367db96d56Sopenharmony_ci            total = (total + sizeCentralDir + centdir[_CD_FILENAME_LENGTH]
14377db96d56Sopenharmony_ci                     + centdir[_CD_EXTRA_FIELD_LENGTH]
14387db96d56Sopenharmony_ci                     + centdir[_CD_COMMENT_LENGTH])
14397db96d56Sopenharmony_ci
14407db96d56Sopenharmony_ci            if self.debug > 2:
14417db96d56Sopenharmony_ci                print("total", total)
14427db96d56Sopenharmony_ci
14437db96d56Sopenharmony_ci        end_offset = self.start_dir
14447db96d56Sopenharmony_ci        for zinfo in sorted(self.filelist,
14457db96d56Sopenharmony_ci                            key=lambda zinfo: zinfo.header_offset,
14467db96d56Sopenharmony_ci                            reverse=True):
14477db96d56Sopenharmony_ci            zinfo._end_offset = end_offset
14487db96d56Sopenharmony_ci            end_offset = zinfo.header_offset
14497db96d56Sopenharmony_ci
14507db96d56Sopenharmony_ci    def namelist(self):
14517db96d56Sopenharmony_ci        """Return a list of file names in the archive."""
14527db96d56Sopenharmony_ci        return [data.filename for data in self.filelist]
14537db96d56Sopenharmony_ci
14547db96d56Sopenharmony_ci    def infolist(self):
14557db96d56Sopenharmony_ci        """Return a list of class ZipInfo instances for files in the
14567db96d56Sopenharmony_ci        archive."""
14577db96d56Sopenharmony_ci        return self.filelist
14587db96d56Sopenharmony_ci
14597db96d56Sopenharmony_ci    def printdir(self, file=None):
14607db96d56Sopenharmony_ci        """Print a table of contents for the zip file."""
14617db96d56Sopenharmony_ci        print("%-46s %19s %12s" % ("File Name", "Modified    ", "Size"),
14627db96d56Sopenharmony_ci              file=file)
14637db96d56Sopenharmony_ci        for zinfo in self.filelist:
14647db96d56Sopenharmony_ci            date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6]
14657db96d56Sopenharmony_ci            print("%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size),
14667db96d56Sopenharmony_ci                  file=file)
14677db96d56Sopenharmony_ci
14687db96d56Sopenharmony_ci    def testzip(self):
14697db96d56Sopenharmony_ci        """Read all the files and check the CRC."""
14707db96d56Sopenharmony_ci        chunk_size = 2 ** 20
14717db96d56Sopenharmony_ci        for zinfo in self.filelist:
14727db96d56Sopenharmony_ci            try:
14737db96d56Sopenharmony_ci                # Read by chunks, to avoid an OverflowError or a
14747db96d56Sopenharmony_ci                # MemoryError with very large embedded files.
14757db96d56Sopenharmony_ci                with self.open(zinfo.filename, "r") as f:
14767db96d56Sopenharmony_ci                    while f.read(chunk_size):     # Check CRC-32
14777db96d56Sopenharmony_ci                        pass
14787db96d56Sopenharmony_ci            except BadZipFile:
14797db96d56Sopenharmony_ci                return zinfo.filename
14807db96d56Sopenharmony_ci
14817db96d56Sopenharmony_ci    def getinfo(self, name):
14827db96d56Sopenharmony_ci        """Return the instance of ZipInfo given 'name'."""
14837db96d56Sopenharmony_ci        info = self.NameToInfo.get(name)
14847db96d56Sopenharmony_ci        if info is None:
14857db96d56Sopenharmony_ci            raise KeyError(
14867db96d56Sopenharmony_ci                'There is no item named %r in the archive' % name)
14877db96d56Sopenharmony_ci
14887db96d56Sopenharmony_ci        return info
14897db96d56Sopenharmony_ci
14907db96d56Sopenharmony_ci    def setpassword(self, pwd):
14917db96d56Sopenharmony_ci        """Set default password for encrypted files."""
14927db96d56Sopenharmony_ci        if pwd and not isinstance(pwd, bytes):
14937db96d56Sopenharmony_ci            raise TypeError("pwd: expected bytes, got %s" % type(pwd).__name__)
14947db96d56Sopenharmony_ci        if pwd:
14957db96d56Sopenharmony_ci            self.pwd = pwd
14967db96d56Sopenharmony_ci        else:
14977db96d56Sopenharmony_ci            self.pwd = None
14987db96d56Sopenharmony_ci
14997db96d56Sopenharmony_ci    @property
15007db96d56Sopenharmony_ci    def comment(self):
15017db96d56Sopenharmony_ci        """The comment text associated with the ZIP file."""
15027db96d56Sopenharmony_ci        return self._comment
15037db96d56Sopenharmony_ci
15047db96d56Sopenharmony_ci    @comment.setter
15057db96d56Sopenharmony_ci    def comment(self, comment):
15067db96d56Sopenharmony_ci        if not isinstance(comment, bytes):
15077db96d56Sopenharmony_ci            raise TypeError("comment: expected bytes, got %s" % type(comment).__name__)
15087db96d56Sopenharmony_ci        # check for valid comment length
15097db96d56Sopenharmony_ci        if len(comment) > ZIP_MAX_COMMENT:
15107db96d56Sopenharmony_ci            import warnings
15117db96d56Sopenharmony_ci            warnings.warn('Archive comment is too long; truncating to %d bytes'
15127db96d56Sopenharmony_ci                          % ZIP_MAX_COMMENT, stacklevel=2)
15137db96d56Sopenharmony_ci            comment = comment[:ZIP_MAX_COMMENT]
15147db96d56Sopenharmony_ci        self._comment = comment
15157db96d56Sopenharmony_ci        self._didModify = True
15167db96d56Sopenharmony_ci
15177db96d56Sopenharmony_ci    def read(self, name, pwd=None):
15187db96d56Sopenharmony_ci        """Return file bytes for name."""
15197db96d56Sopenharmony_ci        with self.open(name, "r", pwd) as fp:
15207db96d56Sopenharmony_ci            return fp.read()
15217db96d56Sopenharmony_ci
15227db96d56Sopenharmony_ci    def open(self, name, mode="r", pwd=None, *, force_zip64=False):
15237db96d56Sopenharmony_ci        """Return file-like object for 'name'.
15247db96d56Sopenharmony_ci
15257db96d56Sopenharmony_ci        name is a string for the file name within the ZIP file, or a ZipInfo
15267db96d56Sopenharmony_ci        object.
15277db96d56Sopenharmony_ci
15287db96d56Sopenharmony_ci        mode should be 'r' to read a file already in the ZIP file, or 'w' to
15297db96d56Sopenharmony_ci        write to a file newly added to the archive.
15307db96d56Sopenharmony_ci
15317db96d56Sopenharmony_ci        pwd is the password to decrypt files (only used for reading).
15327db96d56Sopenharmony_ci
15337db96d56Sopenharmony_ci        When writing, if the file size is not known in advance but may exceed
15347db96d56Sopenharmony_ci        2 GiB, pass force_zip64 to use the ZIP64 format, which can handle large
15357db96d56Sopenharmony_ci        files.  If the size is known in advance, it is best to pass a ZipInfo
15367db96d56Sopenharmony_ci        instance for name, with zinfo.file_size set.
15377db96d56Sopenharmony_ci        """
15387db96d56Sopenharmony_ci        if mode not in {"r", "w"}:
15397db96d56Sopenharmony_ci            raise ValueError('open() requires mode "r" or "w"')
15407db96d56Sopenharmony_ci        if pwd and (mode == "w"):
15417db96d56Sopenharmony_ci            raise ValueError("pwd is only supported for reading files")
15427db96d56Sopenharmony_ci        if not self.fp:
15437db96d56Sopenharmony_ci            raise ValueError(
15447db96d56Sopenharmony_ci                "Attempt to use ZIP archive that was already closed")
15457db96d56Sopenharmony_ci
15467db96d56Sopenharmony_ci        # Make sure we have an info object
15477db96d56Sopenharmony_ci        if isinstance(name, ZipInfo):
15487db96d56Sopenharmony_ci            # 'name' is already an info object
15497db96d56Sopenharmony_ci            zinfo = name
15507db96d56Sopenharmony_ci        elif mode == 'w':
15517db96d56Sopenharmony_ci            zinfo = ZipInfo(name)
15527db96d56Sopenharmony_ci            zinfo.compress_type = self.compression
15537db96d56Sopenharmony_ci            zinfo._compresslevel = self.compresslevel
15547db96d56Sopenharmony_ci        else:
15557db96d56Sopenharmony_ci            # Get info object for name
15567db96d56Sopenharmony_ci            zinfo = self.getinfo(name)
15577db96d56Sopenharmony_ci
15587db96d56Sopenharmony_ci        if mode == 'w':
15597db96d56Sopenharmony_ci            return self._open_to_write(zinfo, force_zip64=force_zip64)
15607db96d56Sopenharmony_ci
15617db96d56Sopenharmony_ci        if self._writing:
15627db96d56Sopenharmony_ci            raise ValueError("Can't read from the ZIP file while there "
15637db96d56Sopenharmony_ci                    "is an open writing handle on it. "
15647db96d56Sopenharmony_ci                    "Close the writing handle before trying to read.")
15657db96d56Sopenharmony_ci
15667db96d56Sopenharmony_ci        # Open for reading:
15677db96d56Sopenharmony_ci        self._fileRefCnt += 1
15687db96d56Sopenharmony_ci        zef_file = _SharedFile(self.fp, zinfo.header_offset,
15697db96d56Sopenharmony_ci                               self._fpclose, self._lock, lambda: self._writing)
15707db96d56Sopenharmony_ci        try:
15717db96d56Sopenharmony_ci            # Skip the file header:
15727db96d56Sopenharmony_ci            fheader = zef_file.read(sizeFileHeader)
15737db96d56Sopenharmony_ci            if len(fheader) != sizeFileHeader:
15747db96d56Sopenharmony_ci                raise BadZipFile("Truncated file header")
15757db96d56Sopenharmony_ci            fheader = struct.unpack(structFileHeader, fheader)
15767db96d56Sopenharmony_ci            if fheader[_FH_SIGNATURE] != stringFileHeader:
15777db96d56Sopenharmony_ci                raise BadZipFile("Bad magic number for file header")
15787db96d56Sopenharmony_ci
15797db96d56Sopenharmony_ci            fname = zef_file.read(fheader[_FH_FILENAME_LENGTH])
15807db96d56Sopenharmony_ci            if fheader[_FH_EXTRA_FIELD_LENGTH]:
15817db96d56Sopenharmony_ci                zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH])
15827db96d56Sopenharmony_ci
15837db96d56Sopenharmony_ci            if zinfo.flag_bits & _MASK_COMPRESSED_PATCH:
15847db96d56Sopenharmony_ci                # Zip 2.7: compressed patched data
15857db96d56Sopenharmony_ci                raise NotImplementedError("compressed patched data (flag bit 5)")
15867db96d56Sopenharmony_ci
15877db96d56Sopenharmony_ci            if zinfo.flag_bits & _MASK_STRONG_ENCRYPTION:
15887db96d56Sopenharmony_ci                # strong encryption
15897db96d56Sopenharmony_ci                raise NotImplementedError("strong encryption (flag bit 6)")
15907db96d56Sopenharmony_ci
15917db96d56Sopenharmony_ci            if fheader[_FH_GENERAL_PURPOSE_FLAG_BITS] & _MASK_UTF_FILENAME:
15927db96d56Sopenharmony_ci                # UTF-8 filename
15937db96d56Sopenharmony_ci                fname_str = fname.decode("utf-8")
15947db96d56Sopenharmony_ci            else:
15957db96d56Sopenharmony_ci                fname_str = fname.decode(self.metadata_encoding or "cp437")
15967db96d56Sopenharmony_ci
15977db96d56Sopenharmony_ci            if fname_str != zinfo.orig_filename:
15987db96d56Sopenharmony_ci                raise BadZipFile(
15997db96d56Sopenharmony_ci                    'File name in directory %r and header %r differ.'
16007db96d56Sopenharmony_ci                    % (zinfo.orig_filename, fname))
16017db96d56Sopenharmony_ci
16027db96d56Sopenharmony_ci            if (zinfo._end_offset is not None and
16037db96d56Sopenharmony_ci                zef_file.tell() + zinfo.compress_size > zinfo._end_offset):
16047db96d56Sopenharmony_ci                raise BadZipFile(f"Overlapped entries: {zinfo.orig_filename!r} (possible zip bomb)")
16057db96d56Sopenharmony_ci
16067db96d56Sopenharmony_ci            # check for encrypted flag & handle password
16077db96d56Sopenharmony_ci            is_encrypted = zinfo.flag_bits & _MASK_ENCRYPTED
16087db96d56Sopenharmony_ci            if is_encrypted:
16097db96d56Sopenharmony_ci                if not pwd:
16107db96d56Sopenharmony_ci                    pwd = self.pwd
16117db96d56Sopenharmony_ci                if pwd and not isinstance(pwd, bytes):
16127db96d56Sopenharmony_ci                    raise TypeError("pwd: expected bytes, got %s" % type(pwd).__name__)
16137db96d56Sopenharmony_ci                if not pwd:
16147db96d56Sopenharmony_ci                    raise RuntimeError("File %r is encrypted, password "
16157db96d56Sopenharmony_ci                                       "required for extraction" % name)
16167db96d56Sopenharmony_ci            else:
16177db96d56Sopenharmony_ci                pwd = None
16187db96d56Sopenharmony_ci
16197db96d56Sopenharmony_ci            return ZipExtFile(zef_file, mode, zinfo, pwd, True)
16207db96d56Sopenharmony_ci        except:
16217db96d56Sopenharmony_ci            zef_file.close()
16227db96d56Sopenharmony_ci            raise
16237db96d56Sopenharmony_ci
16247db96d56Sopenharmony_ci    def _open_to_write(self, zinfo, force_zip64=False):
16257db96d56Sopenharmony_ci        if force_zip64 and not self._allowZip64:
16267db96d56Sopenharmony_ci            raise ValueError(
16277db96d56Sopenharmony_ci                "force_zip64 is True, but allowZip64 was False when opening "
16287db96d56Sopenharmony_ci                "the ZIP file."
16297db96d56Sopenharmony_ci            )
16307db96d56Sopenharmony_ci        if self._writing:
16317db96d56Sopenharmony_ci            raise ValueError("Can't write to the ZIP file while there is "
16327db96d56Sopenharmony_ci                             "another write handle open on it. "
16337db96d56Sopenharmony_ci                             "Close the first handle before opening another.")
16347db96d56Sopenharmony_ci
16357db96d56Sopenharmony_ci        # Size and CRC are overwritten with correct data after processing the file
16367db96d56Sopenharmony_ci        zinfo.compress_size = 0
16377db96d56Sopenharmony_ci        zinfo.CRC = 0
16387db96d56Sopenharmony_ci
16397db96d56Sopenharmony_ci        zinfo.flag_bits = 0x00
16407db96d56Sopenharmony_ci        if zinfo.compress_type == ZIP_LZMA:
16417db96d56Sopenharmony_ci            # Compressed data includes an end-of-stream (EOS) marker
16427db96d56Sopenharmony_ci            zinfo.flag_bits |= _MASK_COMPRESS_OPTION_1
16437db96d56Sopenharmony_ci        if not self._seekable:
16447db96d56Sopenharmony_ci            zinfo.flag_bits |= _MASK_USE_DATA_DESCRIPTOR
16457db96d56Sopenharmony_ci
16467db96d56Sopenharmony_ci        if not zinfo.external_attr:
16477db96d56Sopenharmony_ci            zinfo.external_attr = 0o600 << 16  # permissions: ?rw-------
16487db96d56Sopenharmony_ci
16497db96d56Sopenharmony_ci        # Compressed size can be larger than uncompressed size
16507db96d56Sopenharmony_ci        zip64 = force_zip64 or (zinfo.file_size * 1.05 > ZIP64_LIMIT)
16517db96d56Sopenharmony_ci        if not self._allowZip64 and zip64:
16527db96d56Sopenharmony_ci            raise LargeZipFile("Filesize would require ZIP64 extensions")
16537db96d56Sopenharmony_ci
16547db96d56Sopenharmony_ci        if self._seekable:
16557db96d56Sopenharmony_ci            self.fp.seek(self.start_dir)
16567db96d56Sopenharmony_ci        zinfo.header_offset = self.fp.tell()
16577db96d56Sopenharmony_ci
16587db96d56Sopenharmony_ci        self._writecheck(zinfo)
16597db96d56Sopenharmony_ci        self._didModify = True
16607db96d56Sopenharmony_ci
16617db96d56Sopenharmony_ci        self.fp.write(zinfo.FileHeader(zip64))
16627db96d56Sopenharmony_ci
16637db96d56Sopenharmony_ci        self._writing = True
16647db96d56Sopenharmony_ci        return _ZipWriteFile(self, zinfo, zip64)
16657db96d56Sopenharmony_ci
16667db96d56Sopenharmony_ci    def extract(self, member, path=None, pwd=None):
16677db96d56Sopenharmony_ci        """Extract a member from the archive to the current working directory,
16687db96d56Sopenharmony_ci           using its full name. Its file information is extracted as accurately
16697db96d56Sopenharmony_ci           as possible. `member' may be a filename or a ZipInfo object. You can
16707db96d56Sopenharmony_ci           specify a different directory using `path'.
16717db96d56Sopenharmony_ci        """
16727db96d56Sopenharmony_ci        if path is None:
16737db96d56Sopenharmony_ci            path = os.getcwd()
16747db96d56Sopenharmony_ci        else:
16757db96d56Sopenharmony_ci            path = os.fspath(path)
16767db96d56Sopenharmony_ci
16777db96d56Sopenharmony_ci        return self._extract_member(member, path, pwd)
16787db96d56Sopenharmony_ci
16797db96d56Sopenharmony_ci    def extractall(self, path=None, members=None, pwd=None):
16807db96d56Sopenharmony_ci        """Extract all members from the archive to the current working
16817db96d56Sopenharmony_ci           directory. `path' specifies a different directory to extract to.
16827db96d56Sopenharmony_ci           `members' is optional and must be a subset of the list returned
16837db96d56Sopenharmony_ci           by namelist().
16847db96d56Sopenharmony_ci        """
16857db96d56Sopenharmony_ci        if members is None:
16867db96d56Sopenharmony_ci            members = self.namelist()
16877db96d56Sopenharmony_ci
16887db96d56Sopenharmony_ci        if path is None:
16897db96d56Sopenharmony_ci            path = os.getcwd()
16907db96d56Sopenharmony_ci        else:
16917db96d56Sopenharmony_ci            path = os.fspath(path)
16927db96d56Sopenharmony_ci
16937db96d56Sopenharmony_ci        for zipinfo in members:
16947db96d56Sopenharmony_ci            self._extract_member(zipinfo, path, pwd)
16957db96d56Sopenharmony_ci
16967db96d56Sopenharmony_ci    @classmethod
16977db96d56Sopenharmony_ci    def _sanitize_windows_name(cls, arcname, pathsep):
16987db96d56Sopenharmony_ci        """Replace bad characters and remove trailing dots from parts."""
16997db96d56Sopenharmony_ci        table = cls._windows_illegal_name_trans_table
17007db96d56Sopenharmony_ci        if not table:
17017db96d56Sopenharmony_ci            illegal = ':<>|"?*'
17027db96d56Sopenharmony_ci            table = str.maketrans(illegal, '_' * len(illegal))
17037db96d56Sopenharmony_ci            cls._windows_illegal_name_trans_table = table
17047db96d56Sopenharmony_ci        arcname = arcname.translate(table)
17057db96d56Sopenharmony_ci        # remove trailing dots
17067db96d56Sopenharmony_ci        arcname = (x.rstrip('.') for x in arcname.split(pathsep))
17077db96d56Sopenharmony_ci        # rejoin, removing empty parts.
17087db96d56Sopenharmony_ci        arcname = pathsep.join(x for x in arcname if x)
17097db96d56Sopenharmony_ci        return arcname
17107db96d56Sopenharmony_ci
17117db96d56Sopenharmony_ci    def _extract_member(self, member, targetpath, pwd):
17127db96d56Sopenharmony_ci        """Extract the ZipInfo object 'member' to a physical
17137db96d56Sopenharmony_ci           file on the path targetpath.
17147db96d56Sopenharmony_ci        """
17157db96d56Sopenharmony_ci        if not isinstance(member, ZipInfo):
17167db96d56Sopenharmony_ci            member = self.getinfo(member)
17177db96d56Sopenharmony_ci
17187db96d56Sopenharmony_ci        # build the destination pathname, replacing
17197db96d56Sopenharmony_ci        # forward slashes to platform specific separators.
17207db96d56Sopenharmony_ci        arcname = member.filename.replace('/', os.path.sep)
17217db96d56Sopenharmony_ci
17227db96d56Sopenharmony_ci        if os.path.altsep:
17237db96d56Sopenharmony_ci            arcname = arcname.replace(os.path.altsep, os.path.sep)
17247db96d56Sopenharmony_ci        # interpret absolute pathname as relative, remove drive letter or
17257db96d56Sopenharmony_ci        # UNC path, redundant separators, "." and ".." components.
17267db96d56Sopenharmony_ci        arcname = os.path.splitdrive(arcname)[1]
17277db96d56Sopenharmony_ci        invalid_path_parts = ('', os.path.curdir, os.path.pardir)
17287db96d56Sopenharmony_ci        arcname = os.path.sep.join(x for x in arcname.split(os.path.sep)
17297db96d56Sopenharmony_ci                                   if x not in invalid_path_parts)
17307db96d56Sopenharmony_ci        if os.path.sep == '\\':
17317db96d56Sopenharmony_ci            # filter illegal characters on Windows
17327db96d56Sopenharmony_ci            arcname = self._sanitize_windows_name(arcname, os.path.sep)
17337db96d56Sopenharmony_ci
17347db96d56Sopenharmony_ci        targetpath = os.path.join(targetpath, arcname)
17357db96d56Sopenharmony_ci        targetpath = os.path.normpath(targetpath)
17367db96d56Sopenharmony_ci
17377db96d56Sopenharmony_ci        # Create all upper directories if necessary.
17387db96d56Sopenharmony_ci        upperdirs = os.path.dirname(targetpath)
17397db96d56Sopenharmony_ci        if upperdirs and not os.path.exists(upperdirs):
17407db96d56Sopenharmony_ci            os.makedirs(upperdirs)
17417db96d56Sopenharmony_ci
17427db96d56Sopenharmony_ci        if member.is_dir():
17437db96d56Sopenharmony_ci            if not os.path.isdir(targetpath):
17447db96d56Sopenharmony_ci                os.mkdir(targetpath)
17457db96d56Sopenharmony_ci            return targetpath
17467db96d56Sopenharmony_ci
17477db96d56Sopenharmony_ci        with self.open(member, pwd=pwd) as source, \
17487db96d56Sopenharmony_ci             open(targetpath, "wb") as target:
17497db96d56Sopenharmony_ci            shutil.copyfileobj(source, target)
17507db96d56Sopenharmony_ci
17517db96d56Sopenharmony_ci        return targetpath
17527db96d56Sopenharmony_ci
17537db96d56Sopenharmony_ci    def _writecheck(self, zinfo):
17547db96d56Sopenharmony_ci        """Check for errors before writing a file to the archive."""
17557db96d56Sopenharmony_ci        if zinfo.filename in self.NameToInfo:
17567db96d56Sopenharmony_ci            import warnings
17577db96d56Sopenharmony_ci            warnings.warn('Duplicate name: %r' % zinfo.filename, stacklevel=3)
17587db96d56Sopenharmony_ci        if self.mode not in ('w', 'x', 'a'):
17597db96d56Sopenharmony_ci            raise ValueError("write() requires mode 'w', 'x', or 'a'")
17607db96d56Sopenharmony_ci        if not self.fp:
17617db96d56Sopenharmony_ci            raise ValueError(
17627db96d56Sopenharmony_ci                "Attempt to write ZIP archive that was already closed")
17637db96d56Sopenharmony_ci        _check_compression(zinfo.compress_type)
17647db96d56Sopenharmony_ci        if not self._allowZip64:
17657db96d56Sopenharmony_ci            requires_zip64 = None
17667db96d56Sopenharmony_ci            if len(self.filelist) >= ZIP_FILECOUNT_LIMIT:
17677db96d56Sopenharmony_ci                requires_zip64 = "Files count"
17687db96d56Sopenharmony_ci            elif zinfo.file_size > ZIP64_LIMIT:
17697db96d56Sopenharmony_ci                requires_zip64 = "Filesize"
17707db96d56Sopenharmony_ci            elif zinfo.header_offset > ZIP64_LIMIT:
17717db96d56Sopenharmony_ci                requires_zip64 = "Zipfile size"
17727db96d56Sopenharmony_ci            if requires_zip64:
17737db96d56Sopenharmony_ci                raise LargeZipFile(requires_zip64 +
17747db96d56Sopenharmony_ci                                   " would require ZIP64 extensions")
17757db96d56Sopenharmony_ci
17767db96d56Sopenharmony_ci    def write(self, filename, arcname=None,
17777db96d56Sopenharmony_ci              compress_type=None, compresslevel=None):
17787db96d56Sopenharmony_ci        """Put the bytes from filename into the archive under the name
17797db96d56Sopenharmony_ci        arcname."""
17807db96d56Sopenharmony_ci        if not self.fp:
17817db96d56Sopenharmony_ci            raise ValueError(
17827db96d56Sopenharmony_ci                "Attempt to write to ZIP archive that was already closed")
17837db96d56Sopenharmony_ci        if self._writing:
17847db96d56Sopenharmony_ci            raise ValueError(
17857db96d56Sopenharmony_ci                "Can't write to ZIP archive while an open writing handle exists"
17867db96d56Sopenharmony_ci            )
17877db96d56Sopenharmony_ci
17887db96d56Sopenharmony_ci        zinfo = ZipInfo.from_file(filename, arcname,
17897db96d56Sopenharmony_ci                                  strict_timestamps=self._strict_timestamps)
17907db96d56Sopenharmony_ci
17917db96d56Sopenharmony_ci        if zinfo.is_dir():
17927db96d56Sopenharmony_ci            zinfo.compress_size = 0
17937db96d56Sopenharmony_ci            zinfo.CRC = 0
17947db96d56Sopenharmony_ci            self.mkdir(zinfo)
17957db96d56Sopenharmony_ci        else:
17967db96d56Sopenharmony_ci            if compress_type is not None:
17977db96d56Sopenharmony_ci                zinfo.compress_type = compress_type
17987db96d56Sopenharmony_ci            else:
17997db96d56Sopenharmony_ci                zinfo.compress_type = self.compression
18007db96d56Sopenharmony_ci
18017db96d56Sopenharmony_ci            if compresslevel is not None:
18027db96d56Sopenharmony_ci                zinfo._compresslevel = compresslevel
18037db96d56Sopenharmony_ci            else:
18047db96d56Sopenharmony_ci                zinfo._compresslevel = self.compresslevel
18057db96d56Sopenharmony_ci
18067db96d56Sopenharmony_ci            with open(filename, "rb") as src, self.open(zinfo, 'w') as dest:
18077db96d56Sopenharmony_ci                shutil.copyfileobj(src, dest, 1024*8)
18087db96d56Sopenharmony_ci
18097db96d56Sopenharmony_ci    def writestr(self, zinfo_or_arcname, data,
18107db96d56Sopenharmony_ci                 compress_type=None, compresslevel=None):
18117db96d56Sopenharmony_ci        """Write a file into the archive.  The contents is 'data', which
18127db96d56Sopenharmony_ci        may be either a 'str' or a 'bytes' instance; if it is a 'str',
18137db96d56Sopenharmony_ci        it is encoded as UTF-8 first.
18147db96d56Sopenharmony_ci        'zinfo_or_arcname' is either a ZipInfo instance or
18157db96d56Sopenharmony_ci        the name of the file in the archive."""
18167db96d56Sopenharmony_ci        if isinstance(data, str):
18177db96d56Sopenharmony_ci            data = data.encode("utf-8")
18187db96d56Sopenharmony_ci        if not isinstance(zinfo_or_arcname, ZipInfo):
18197db96d56Sopenharmony_ci            zinfo = ZipInfo(filename=zinfo_or_arcname,
18207db96d56Sopenharmony_ci                            date_time=time.localtime(time.time())[:6])
18217db96d56Sopenharmony_ci            zinfo.compress_type = self.compression
18227db96d56Sopenharmony_ci            zinfo._compresslevel = self.compresslevel
18237db96d56Sopenharmony_ci            if zinfo.filename[-1] == '/':
18247db96d56Sopenharmony_ci                zinfo.external_attr = 0o40775 << 16   # drwxrwxr-x
18257db96d56Sopenharmony_ci                zinfo.external_attr |= 0x10           # MS-DOS directory flag
18267db96d56Sopenharmony_ci            else:
18277db96d56Sopenharmony_ci                zinfo.external_attr = 0o600 << 16     # ?rw-------
18287db96d56Sopenharmony_ci        else:
18297db96d56Sopenharmony_ci            zinfo = zinfo_or_arcname
18307db96d56Sopenharmony_ci
18317db96d56Sopenharmony_ci        if not self.fp:
18327db96d56Sopenharmony_ci            raise ValueError(
18337db96d56Sopenharmony_ci                "Attempt to write to ZIP archive that was already closed")
18347db96d56Sopenharmony_ci        if self._writing:
18357db96d56Sopenharmony_ci            raise ValueError(
18367db96d56Sopenharmony_ci                "Can't write to ZIP archive while an open writing handle exists."
18377db96d56Sopenharmony_ci            )
18387db96d56Sopenharmony_ci
18397db96d56Sopenharmony_ci        if compress_type is not None:
18407db96d56Sopenharmony_ci            zinfo.compress_type = compress_type
18417db96d56Sopenharmony_ci
18427db96d56Sopenharmony_ci        if compresslevel is not None:
18437db96d56Sopenharmony_ci            zinfo._compresslevel = compresslevel
18447db96d56Sopenharmony_ci
18457db96d56Sopenharmony_ci        zinfo.file_size = len(data)            # Uncompressed size
18467db96d56Sopenharmony_ci        with self._lock:
18477db96d56Sopenharmony_ci            with self.open(zinfo, mode='w') as dest:
18487db96d56Sopenharmony_ci                dest.write(data)
18497db96d56Sopenharmony_ci
18507db96d56Sopenharmony_ci    def mkdir(self, zinfo_or_directory_name, mode=511):
18517db96d56Sopenharmony_ci        """Creates a directory inside the zip archive."""
18527db96d56Sopenharmony_ci        if isinstance(zinfo_or_directory_name, ZipInfo):
18537db96d56Sopenharmony_ci            zinfo = zinfo_or_directory_name
18547db96d56Sopenharmony_ci            if not zinfo.is_dir():
18557db96d56Sopenharmony_ci                raise ValueError("The given ZipInfo does not describe a directory")
18567db96d56Sopenharmony_ci        elif isinstance(zinfo_or_directory_name, str):
18577db96d56Sopenharmony_ci            directory_name = zinfo_or_directory_name
18587db96d56Sopenharmony_ci            if not directory_name.endswith("/"):
18597db96d56Sopenharmony_ci                directory_name += "/"
18607db96d56Sopenharmony_ci            zinfo = ZipInfo(directory_name)
18617db96d56Sopenharmony_ci            zinfo.compress_size = 0
18627db96d56Sopenharmony_ci            zinfo.CRC = 0
18637db96d56Sopenharmony_ci            zinfo.external_attr = ((0o40000 | mode) & 0xFFFF) << 16
18647db96d56Sopenharmony_ci            zinfo.file_size = 0
18657db96d56Sopenharmony_ci            zinfo.external_attr |= 0x10
18667db96d56Sopenharmony_ci        else:
18677db96d56Sopenharmony_ci            raise TypeError("Expected type str or ZipInfo")
18687db96d56Sopenharmony_ci
18697db96d56Sopenharmony_ci        with self._lock:
18707db96d56Sopenharmony_ci            if self._seekable:
18717db96d56Sopenharmony_ci                self.fp.seek(self.start_dir)
18727db96d56Sopenharmony_ci            zinfo.header_offset = self.fp.tell()  # Start of header bytes
18737db96d56Sopenharmony_ci            if zinfo.compress_type == ZIP_LZMA:
18747db96d56Sopenharmony_ci            # Compressed data includes an end-of-stream (EOS) marker
18757db96d56Sopenharmony_ci                zinfo.flag_bits |= _MASK_COMPRESS_OPTION_1
18767db96d56Sopenharmony_ci
18777db96d56Sopenharmony_ci            self._writecheck(zinfo)
18787db96d56Sopenharmony_ci            self._didModify = True
18797db96d56Sopenharmony_ci
18807db96d56Sopenharmony_ci            self.filelist.append(zinfo)
18817db96d56Sopenharmony_ci            self.NameToInfo[zinfo.filename] = zinfo
18827db96d56Sopenharmony_ci            self.fp.write(zinfo.FileHeader(False))
18837db96d56Sopenharmony_ci            self.start_dir = self.fp.tell()
18847db96d56Sopenharmony_ci
18857db96d56Sopenharmony_ci    def __del__(self):
18867db96d56Sopenharmony_ci        """Call the "close()" method in case the user forgot."""
18877db96d56Sopenharmony_ci        self.close()
18887db96d56Sopenharmony_ci
18897db96d56Sopenharmony_ci    def close(self):
18907db96d56Sopenharmony_ci        """Close the file, and for mode 'w', 'x' and 'a' write the ending
18917db96d56Sopenharmony_ci        records."""
18927db96d56Sopenharmony_ci        if self.fp is None:
18937db96d56Sopenharmony_ci            return
18947db96d56Sopenharmony_ci
18957db96d56Sopenharmony_ci        if self._writing:
18967db96d56Sopenharmony_ci            raise ValueError("Can't close the ZIP file while there is "
18977db96d56Sopenharmony_ci                             "an open writing handle on it. "
18987db96d56Sopenharmony_ci                             "Close the writing handle before closing the zip.")
18997db96d56Sopenharmony_ci
19007db96d56Sopenharmony_ci        try:
19017db96d56Sopenharmony_ci            if self.mode in ('w', 'x', 'a') and self._didModify: # write ending records
19027db96d56Sopenharmony_ci                with self._lock:
19037db96d56Sopenharmony_ci                    if self._seekable:
19047db96d56Sopenharmony_ci                        self.fp.seek(self.start_dir)
19057db96d56Sopenharmony_ci                    self._write_end_record()
19067db96d56Sopenharmony_ci        finally:
19077db96d56Sopenharmony_ci            fp = self.fp
19087db96d56Sopenharmony_ci            self.fp = None
19097db96d56Sopenharmony_ci            self._fpclose(fp)
19107db96d56Sopenharmony_ci
19117db96d56Sopenharmony_ci    def _write_end_record(self):
19127db96d56Sopenharmony_ci        for zinfo in self.filelist:         # write central directory
19137db96d56Sopenharmony_ci            dt = zinfo.date_time
19147db96d56Sopenharmony_ci            dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
19157db96d56Sopenharmony_ci            dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
19167db96d56Sopenharmony_ci            extra = []
19177db96d56Sopenharmony_ci            if zinfo.file_size > ZIP64_LIMIT \
19187db96d56Sopenharmony_ci               or zinfo.compress_size > ZIP64_LIMIT:
19197db96d56Sopenharmony_ci                extra.append(zinfo.file_size)
19207db96d56Sopenharmony_ci                extra.append(zinfo.compress_size)
19217db96d56Sopenharmony_ci                file_size = 0xffffffff
19227db96d56Sopenharmony_ci                compress_size = 0xffffffff
19237db96d56Sopenharmony_ci            else:
19247db96d56Sopenharmony_ci                file_size = zinfo.file_size
19257db96d56Sopenharmony_ci                compress_size = zinfo.compress_size
19267db96d56Sopenharmony_ci
19277db96d56Sopenharmony_ci            if zinfo.header_offset > ZIP64_LIMIT:
19287db96d56Sopenharmony_ci                extra.append(zinfo.header_offset)
19297db96d56Sopenharmony_ci                header_offset = 0xffffffff
19307db96d56Sopenharmony_ci            else:
19317db96d56Sopenharmony_ci                header_offset = zinfo.header_offset
19327db96d56Sopenharmony_ci
19337db96d56Sopenharmony_ci            extra_data = zinfo.extra
19347db96d56Sopenharmony_ci            min_version = 0
19357db96d56Sopenharmony_ci            if extra:
19367db96d56Sopenharmony_ci                # Append a ZIP64 field to the extra's
19377db96d56Sopenharmony_ci                extra_data = _strip_extra(extra_data, (1,))
19387db96d56Sopenharmony_ci                extra_data = struct.pack(
19397db96d56Sopenharmony_ci                    '<HH' + 'Q'*len(extra),
19407db96d56Sopenharmony_ci                    1, 8*len(extra), *extra) + extra_data
19417db96d56Sopenharmony_ci
19427db96d56Sopenharmony_ci                min_version = ZIP64_VERSION
19437db96d56Sopenharmony_ci
19447db96d56Sopenharmony_ci            if zinfo.compress_type == ZIP_BZIP2:
19457db96d56Sopenharmony_ci                min_version = max(BZIP2_VERSION, min_version)
19467db96d56Sopenharmony_ci            elif zinfo.compress_type == ZIP_LZMA:
19477db96d56Sopenharmony_ci                min_version = max(LZMA_VERSION, min_version)
19487db96d56Sopenharmony_ci
19497db96d56Sopenharmony_ci            extract_version = max(min_version, zinfo.extract_version)
19507db96d56Sopenharmony_ci            create_version = max(min_version, zinfo.create_version)
19517db96d56Sopenharmony_ci            filename, flag_bits = zinfo._encodeFilenameFlags()
19527db96d56Sopenharmony_ci            centdir = struct.pack(structCentralDir,
19537db96d56Sopenharmony_ci                                  stringCentralDir, create_version,
19547db96d56Sopenharmony_ci                                  zinfo.create_system, extract_version, zinfo.reserved,
19557db96d56Sopenharmony_ci                                  flag_bits, zinfo.compress_type, dostime, dosdate,
19567db96d56Sopenharmony_ci                                  zinfo.CRC, compress_size, file_size,
19577db96d56Sopenharmony_ci                                  len(filename), len(extra_data), len(zinfo.comment),
19587db96d56Sopenharmony_ci                                  0, zinfo.internal_attr, zinfo.external_attr,
19597db96d56Sopenharmony_ci                                  header_offset)
19607db96d56Sopenharmony_ci            self.fp.write(centdir)
19617db96d56Sopenharmony_ci            self.fp.write(filename)
19627db96d56Sopenharmony_ci            self.fp.write(extra_data)
19637db96d56Sopenharmony_ci            self.fp.write(zinfo.comment)
19647db96d56Sopenharmony_ci
19657db96d56Sopenharmony_ci        pos2 = self.fp.tell()
19667db96d56Sopenharmony_ci        # Write end-of-zip-archive record
19677db96d56Sopenharmony_ci        centDirCount = len(self.filelist)
19687db96d56Sopenharmony_ci        centDirSize = pos2 - self.start_dir
19697db96d56Sopenharmony_ci        centDirOffset = self.start_dir
19707db96d56Sopenharmony_ci        requires_zip64 = None
19717db96d56Sopenharmony_ci        if centDirCount > ZIP_FILECOUNT_LIMIT:
19727db96d56Sopenharmony_ci            requires_zip64 = "Files count"
19737db96d56Sopenharmony_ci        elif centDirOffset > ZIP64_LIMIT:
19747db96d56Sopenharmony_ci            requires_zip64 = "Central directory offset"
19757db96d56Sopenharmony_ci        elif centDirSize > ZIP64_LIMIT:
19767db96d56Sopenharmony_ci            requires_zip64 = "Central directory size"
19777db96d56Sopenharmony_ci        if requires_zip64:
19787db96d56Sopenharmony_ci            # Need to write the ZIP64 end-of-archive records
19797db96d56Sopenharmony_ci            if not self._allowZip64:
19807db96d56Sopenharmony_ci                raise LargeZipFile(requires_zip64 +
19817db96d56Sopenharmony_ci                                   " would require ZIP64 extensions")
19827db96d56Sopenharmony_ci            zip64endrec = struct.pack(
19837db96d56Sopenharmony_ci                structEndArchive64, stringEndArchive64,
19847db96d56Sopenharmony_ci                44, 45, 45, 0, 0, centDirCount, centDirCount,
19857db96d56Sopenharmony_ci                centDirSize, centDirOffset)
19867db96d56Sopenharmony_ci            self.fp.write(zip64endrec)
19877db96d56Sopenharmony_ci
19887db96d56Sopenharmony_ci            zip64locrec = struct.pack(
19897db96d56Sopenharmony_ci                structEndArchive64Locator,
19907db96d56Sopenharmony_ci                stringEndArchive64Locator, 0, pos2, 1)
19917db96d56Sopenharmony_ci            self.fp.write(zip64locrec)
19927db96d56Sopenharmony_ci            centDirCount = min(centDirCount, 0xFFFF)
19937db96d56Sopenharmony_ci            centDirSize = min(centDirSize, 0xFFFFFFFF)
19947db96d56Sopenharmony_ci            centDirOffset = min(centDirOffset, 0xFFFFFFFF)
19957db96d56Sopenharmony_ci
19967db96d56Sopenharmony_ci        endrec = struct.pack(structEndArchive, stringEndArchive,
19977db96d56Sopenharmony_ci                             0, 0, centDirCount, centDirCount,
19987db96d56Sopenharmony_ci                             centDirSize, centDirOffset, len(self._comment))
19997db96d56Sopenharmony_ci        self.fp.write(endrec)
20007db96d56Sopenharmony_ci        self.fp.write(self._comment)
20017db96d56Sopenharmony_ci        if self.mode == "a":
20027db96d56Sopenharmony_ci            self.fp.truncate()
20037db96d56Sopenharmony_ci        self.fp.flush()
20047db96d56Sopenharmony_ci
20057db96d56Sopenharmony_ci    def _fpclose(self, fp):
20067db96d56Sopenharmony_ci        assert self._fileRefCnt > 0
20077db96d56Sopenharmony_ci        self._fileRefCnt -= 1
20087db96d56Sopenharmony_ci        if not self._fileRefCnt and not self._filePassed:
20097db96d56Sopenharmony_ci            fp.close()
20107db96d56Sopenharmony_ci
20117db96d56Sopenharmony_ci
20127db96d56Sopenharmony_ciclass PyZipFile(ZipFile):
20137db96d56Sopenharmony_ci    """Class to create ZIP archives with Python library files and packages."""
20147db96d56Sopenharmony_ci
20157db96d56Sopenharmony_ci    def __init__(self, file, mode="r", compression=ZIP_STORED,
20167db96d56Sopenharmony_ci                 allowZip64=True, optimize=-1):
20177db96d56Sopenharmony_ci        ZipFile.__init__(self, file, mode=mode, compression=compression,
20187db96d56Sopenharmony_ci                         allowZip64=allowZip64)
20197db96d56Sopenharmony_ci        self._optimize = optimize
20207db96d56Sopenharmony_ci
20217db96d56Sopenharmony_ci    def writepy(self, pathname, basename="", filterfunc=None):
20227db96d56Sopenharmony_ci        """Add all files from "pathname" to the ZIP archive.
20237db96d56Sopenharmony_ci
20247db96d56Sopenharmony_ci        If pathname is a package directory, search the directory and
20257db96d56Sopenharmony_ci        all package subdirectories recursively for all *.py and enter
20267db96d56Sopenharmony_ci        the modules into the archive.  If pathname is a plain
20277db96d56Sopenharmony_ci        directory, listdir *.py and enter all modules.  Else, pathname
20287db96d56Sopenharmony_ci        must be a Python *.py file and the module will be put into the
20297db96d56Sopenharmony_ci        archive.  Added modules are always module.pyc.
20307db96d56Sopenharmony_ci        This method will compile the module.py into module.pyc if
20317db96d56Sopenharmony_ci        necessary.
20327db96d56Sopenharmony_ci        If filterfunc(pathname) is given, it is called with every argument.
20337db96d56Sopenharmony_ci        When it is False, the file or directory is skipped.
20347db96d56Sopenharmony_ci        """
20357db96d56Sopenharmony_ci        pathname = os.fspath(pathname)
20367db96d56Sopenharmony_ci        if filterfunc and not filterfunc(pathname):
20377db96d56Sopenharmony_ci            if self.debug:
20387db96d56Sopenharmony_ci                label = 'path' if os.path.isdir(pathname) else 'file'
20397db96d56Sopenharmony_ci                print('%s %r skipped by filterfunc' % (label, pathname))
20407db96d56Sopenharmony_ci            return
20417db96d56Sopenharmony_ci        dir, name = os.path.split(pathname)
20427db96d56Sopenharmony_ci        if os.path.isdir(pathname):
20437db96d56Sopenharmony_ci            initname = os.path.join(pathname, "__init__.py")
20447db96d56Sopenharmony_ci            if os.path.isfile(initname):
20457db96d56Sopenharmony_ci                # This is a package directory, add it
20467db96d56Sopenharmony_ci                if basename:
20477db96d56Sopenharmony_ci                    basename = "%s/%s" % (basename, name)
20487db96d56Sopenharmony_ci                else:
20497db96d56Sopenharmony_ci                    basename = name
20507db96d56Sopenharmony_ci                if self.debug:
20517db96d56Sopenharmony_ci                    print("Adding package in", pathname, "as", basename)
20527db96d56Sopenharmony_ci                fname, arcname = self._get_codename(initname[0:-3], basename)
20537db96d56Sopenharmony_ci                if self.debug:
20547db96d56Sopenharmony_ci                    print("Adding", arcname)
20557db96d56Sopenharmony_ci                self.write(fname, arcname)
20567db96d56Sopenharmony_ci                dirlist = sorted(os.listdir(pathname))
20577db96d56Sopenharmony_ci                dirlist.remove("__init__.py")
20587db96d56Sopenharmony_ci                # Add all *.py files and package subdirectories
20597db96d56Sopenharmony_ci                for filename in dirlist:
20607db96d56Sopenharmony_ci                    path = os.path.join(pathname, filename)
20617db96d56Sopenharmony_ci                    root, ext = os.path.splitext(filename)
20627db96d56Sopenharmony_ci                    if os.path.isdir(path):
20637db96d56Sopenharmony_ci                        if os.path.isfile(os.path.join(path, "__init__.py")):
20647db96d56Sopenharmony_ci                            # This is a package directory, add it
20657db96d56Sopenharmony_ci                            self.writepy(path, basename,
20667db96d56Sopenharmony_ci                                         filterfunc=filterfunc)  # Recursive call
20677db96d56Sopenharmony_ci                    elif ext == ".py":
20687db96d56Sopenharmony_ci                        if filterfunc and not filterfunc(path):
20697db96d56Sopenharmony_ci                            if self.debug:
20707db96d56Sopenharmony_ci                                print('file %r skipped by filterfunc' % path)
20717db96d56Sopenharmony_ci                            continue
20727db96d56Sopenharmony_ci                        fname, arcname = self._get_codename(path[0:-3],
20737db96d56Sopenharmony_ci                                                            basename)
20747db96d56Sopenharmony_ci                        if self.debug:
20757db96d56Sopenharmony_ci                            print("Adding", arcname)
20767db96d56Sopenharmony_ci                        self.write(fname, arcname)
20777db96d56Sopenharmony_ci            else:
20787db96d56Sopenharmony_ci                # This is NOT a package directory, add its files at top level
20797db96d56Sopenharmony_ci                if self.debug:
20807db96d56Sopenharmony_ci                    print("Adding files from directory", pathname)
20817db96d56Sopenharmony_ci                for filename in sorted(os.listdir(pathname)):
20827db96d56Sopenharmony_ci                    path = os.path.join(pathname, filename)
20837db96d56Sopenharmony_ci                    root, ext = os.path.splitext(filename)
20847db96d56Sopenharmony_ci                    if ext == ".py":
20857db96d56Sopenharmony_ci                        if filterfunc and not filterfunc(path):
20867db96d56Sopenharmony_ci                            if self.debug:
20877db96d56Sopenharmony_ci                                print('file %r skipped by filterfunc' % path)
20887db96d56Sopenharmony_ci                            continue
20897db96d56Sopenharmony_ci                        fname, arcname = self._get_codename(path[0:-3],
20907db96d56Sopenharmony_ci                                                            basename)
20917db96d56Sopenharmony_ci                        if self.debug:
20927db96d56Sopenharmony_ci                            print("Adding", arcname)
20937db96d56Sopenharmony_ci                        self.write(fname, arcname)
20947db96d56Sopenharmony_ci        else:
20957db96d56Sopenharmony_ci            if pathname[-3:] != ".py":
20967db96d56Sopenharmony_ci                raise RuntimeError(
20977db96d56Sopenharmony_ci                    'Files added with writepy() must end with ".py"')
20987db96d56Sopenharmony_ci            fname, arcname = self._get_codename(pathname[0:-3], basename)
20997db96d56Sopenharmony_ci            if self.debug:
21007db96d56Sopenharmony_ci                print("Adding file", arcname)
21017db96d56Sopenharmony_ci            self.write(fname, arcname)
21027db96d56Sopenharmony_ci
21037db96d56Sopenharmony_ci    def _get_codename(self, pathname, basename):
21047db96d56Sopenharmony_ci        """Return (filename, archivename) for the path.
21057db96d56Sopenharmony_ci
21067db96d56Sopenharmony_ci        Given a module name path, return the correct file path and
21077db96d56Sopenharmony_ci        archive name, compiling if necessary.  For example, given
21087db96d56Sopenharmony_ci        /python/lib/string, return (/python/lib/string.pyc, string).
21097db96d56Sopenharmony_ci        """
21107db96d56Sopenharmony_ci        def _compile(file, optimize=-1):
21117db96d56Sopenharmony_ci            import py_compile
21127db96d56Sopenharmony_ci            if self.debug:
21137db96d56Sopenharmony_ci                print("Compiling", file)
21147db96d56Sopenharmony_ci            try:
21157db96d56Sopenharmony_ci                py_compile.compile(file, doraise=True, optimize=optimize)
21167db96d56Sopenharmony_ci            except py_compile.PyCompileError as err:
21177db96d56Sopenharmony_ci                print(err.msg)
21187db96d56Sopenharmony_ci                return False
21197db96d56Sopenharmony_ci            return True
21207db96d56Sopenharmony_ci
21217db96d56Sopenharmony_ci        file_py  = pathname + ".py"
21227db96d56Sopenharmony_ci        file_pyc = pathname + ".pyc"
21237db96d56Sopenharmony_ci        pycache_opt0 = importlib.util.cache_from_source(file_py, optimization='')
21247db96d56Sopenharmony_ci        pycache_opt1 = importlib.util.cache_from_source(file_py, optimization=1)
21257db96d56Sopenharmony_ci        pycache_opt2 = importlib.util.cache_from_source(file_py, optimization=2)
21267db96d56Sopenharmony_ci        if self._optimize == -1:
21277db96d56Sopenharmony_ci            # legacy mode: use whatever file is present
21287db96d56Sopenharmony_ci            if (os.path.isfile(file_pyc) and
21297db96d56Sopenharmony_ci                  os.stat(file_pyc).st_mtime >= os.stat(file_py).st_mtime):
21307db96d56Sopenharmony_ci                # Use .pyc file.
21317db96d56Sopenharmony_ci                arcname = fname = file_pyc
21327db96d56Sopenharmony_ci            elif (os.path.isfile(pycache_opt0) and
21337db96d56Sopenharmony_ci                  os.stat(pycache_opt0).st_mtime >= os.stat(file_py).st_mtime):
21347db96d56Sopenharmony_ci                # Use the __pycache__/*.pyc file, but write it to the legacy pyc
21357db96d56Sopenharmony_ci                # file name in the archive.
21367db96d56Sopenharmony_ci                fname = pycache_opt0
21377db96d56Sopenharmony_ci                arcname = file_pyc
21387db96d56Sopenharmony_ci            elif (os.path.isfile(pycache_opt1) and
21397db96d56Sopenharmony_ci                  os.stat(pycache_opt1).st_mtime >= os.stat(file_py).st_mtime):
21407db96d56Sopenharmony_ci                # Use the __pycache__/*.pyc file, but write it to the legacy pyc
21417db96d56Sopenharmony_ci                # file name in the archive.
21427db96d56Sopenharmony_ci                fname = pycache_opt1
21437db96d56Sopenharmony_ci                arcname = file_pyc
21447db96d56Sopenharmony_ci            elif (os.path.isfile(pycache_opt2) and
21457db96d56Sopenharmony_ci                  os.stat(pycache_opt2).st_mtime >= os.stat(file_py).st_mtime):
21467db96d56Sopenharmony_ci                # Use the __pycache__/*.pyc file, but write it to the legacy pyc
21477db96d56Sopenharmony_ci                # file name in the archive.
21487db96d56Sopenharmony_ci                fname = pycache_opt2
21497db96d56Sopenharmony_ci                arcname = file_pyc
21507db96d56Sopenharmony_ci            else:
21517db96d56Sopenharmony_ci                # Compile py into PEP 3147 pyc file.
21527db96d56Sopenharmony_ci                if _compile(file_py):
21537db96d56Sopenharmony_ci                    if sys.flags.optimize == 0:
21547db96d56Sopenharmony_ci                        fname = pycache_opt0
21557db96d56Sopenharmony_ci                    elif sys.flags.optimize == 1:
21567db96d56Sopenharmony_ci                        fname = pycache_opt1
21577db96d56Sopenharmony_ci                    else:
21587db96d56Sopenharmony_ci                        fname = pycache_opt2
21597db96d56Sopenharmony_ci                    arcname = file_pyc
21607db96d56Sopenharmony_ci                else:
21617db96d56Sopenharmony_ci                    fname = arcname = file_py
21627db96d56Sopenharmony_ci        else:
21637db96d56Sopenharmony_ci            # new mode: use given optimization level
21647db96d56Sopenharmony_ci            if self._optimize == 0:
21657db96d56Sopenharmony_ci                fname = pycache_opt0
21667db96d56Sopenharmony_ci                arcname = file_pyc
21677db96d56Sopenharmony_ci            else:
21687db96d56Sopenharmony_ci                arcname = file_pyc
21697db96d56Sopenharmony_ci                if self._optimize == 1:
21707db96d56Sopenharmony_ci                    fname = pycache_opt1
21717db96d56Sopenharmony_ci                elif self._optimize == 2:
21727db96d56Sopenharmony_ci                    fname = pycache_opt2
21737db96d56Sopenharmony_ci                else:
21747db96d56Sopenharmony_ci                    msg = "invalid value for 'optimize': {!r}".format(self._optimize)
21757db96d56Sopenharmony_ci                    raise ValueError(msg)
21767db96d56Sopenharmony_ci            if not (os.path.isfile(fname) and
21777db96d56Sopenharmony_ci                    os.stat(fname).st_mtime >= os.stat(file_py).st_mtime):
21787db96d56Sopenharmony_ci                if not _compile(file_py, optimize=self._optimize):
21797db96d56Sopenharmony_ci                    fname = arcname = file_py
21807db96d56Sopenharmony_ci        archivename = os.path.split(arcname)[1]
21817db96d56Sopenharmony_ci        if basename:
21827db96d56Sopenharmony_ci            archivename = "%s/%s" % (basename, archivename)
21837db96d56Sopenharmony_ci        return (fname, archivename)
21847db96d56Sopenharmony_ci
21857db96d56Sopenharmony_ci
21867db96d56Sopenharmony_cidef _parents(path):
21877db96d56Sopenharmony_ci    """
21887db96d56Sopenharmony_ci    Given a path with elements separated by
21897db96d56Sopenharmony_ci    posixpath.sep, generate all parents of that path.
21907db96d56Sopenharmony_ci
21917db96d56Sopenharmony_ci    >>> list(_parents('b/d'))
21927db96d56Sopenharmony_ci    ['b']
21937db96d56Sopenharmony_ci    >>> list(_parents('/b/d/'))
21947db96d56Sopenharmony_ci    ['/b']
21957db96d56Sopenharmony_ci    >>> list(_parents('b/d/f/'))
21967db96d56Sopenharmony_ci    ['b/d', 'b']
21977db96d56Sopenharmony_ci    >>> list(_parents('b'))
21987db96d56Sopenharmony_ci    []
21997db96d56Sopenharmony_ci    >>> list(_parents(''))
22007db96d56Sopenharmony_ci    []
22017db96d56Sopenharmony_ci    """
22027db96d56Sopenharmony_ci    return itertools.islice(_ancestry(path), 1, None)
22037db96d56Sopenharmony_ci
22047db96d56Sopenharmony_ci
22057db96d56Sopenharmony_cidef _ancestry(path):
22067db96d56Sopenharmony_ci    """
22077db96d56Sopenharmony_ci    Given a path with elements separated by
22087db96d56Sopenharmony_ci    posixpath.sep, generate all elements of that path
22097db96d56Sopenharmony_ci
22107db96d56Sopenharmony_ci    >>> list(_ancestry('b/d'))
22117db96d56Sopenharmony_ci    ['b/d', 'b']
22127db96d56Sopenharmony_ci    >>> list(_ancestry('/b/d/'))
22137db96d56Sopenharmony_ci    ['/b/d', '/b']
22147db96d56Sopenharmony_ci    >>> list(_ancestry('b/d/f/'))
22157db96d56Sopenharmony_ci    ['b/d/f', 'b/d', 'b']
22167db96d56Sopenharmony_ci    >>> list(_ancestry('b'))
22177db96d56Sopenharmony_ci    ['b']
22187db96d56Sopenharmony_ci    >>> list(_ancestry(''))
22197db96d56Sopenharmony_ci    []
22207db96d56Sopenharmony_ci    """
22217db96d56Sopenharmony_ci    path = path.rstrip(posixpath.sep)
22227db96d56Sopenharmony_ci    while path and path != posixpath.sep:
22237db96d56Sopenharmony_ci        yield path
22247db96d56Sopenharmony_ci        path, tail = posixpath.split(path)
22257db96d56Sopenharmony_ci
22267db96d56Sopenharmony_ci
22277db96d56Sopenharmony_ci_dedupe = dict.fromkeys
22287db96d56Sopenharmony_ci"""Deduplicate an iterable in original order"""
22297db96d56Sopenharmony_ci
22307db96d56Sopenharmony_ci
22317db96d56Sopenharmony_cidef _difference(minuend, subtrahend):
22327db96d56Sopenharmony_ci    """
22337db96d56Sopenharmony_ci    Return items in minuend not in subtrahend, retaining order
22347db96d56Sopenharmony_ci    with O(1) lookup.
22357db96d56Sopenharmony_ci    """
22367db96d56Sopenharmony_ci    return itertools.filterfalse(set(subtrahend).__contains__, minuend)
22377db96d56Sopenharmony_ci
22387db96d56Sopenharmony_ci
22397db96d56Sopenharmony_ciclass SanitizedNames:
22407db96d56Sopenharmony_ci    """
22417db96d56Sopenharmony_ci    ZipFile mix-in to ensure names are sanitized.
22427db96d56Sopenharmony_ci    """
22437db96d56Sopenharmony_ci
22447db96d56Sopenharmony_ci    def namelist(self):
22457db96d56Sopenharmony_ci        return list(map(self._sanitize, super().namelist()))
22467db96d56Sopenharmony_ci
22477db96d56Sopenharmony_ci    @staticmethod
22487db96d56Sopenharmony_ci    def _sanitize(name):
22497db96d56Sopenharmony_ci        r"""
22507db96d56Sopenharmony_ci        Ensure a relative path with posix separators and no dot names.
22517db96d56Sopenharmony_ci        Modeled after
22527db96d56Sopenharmony_ci        https://github.com/python/cpython/blob/bcc1be39cb1d04ad9fc0bd1b9193d3972835a57c/Lib/zipfile/__init__.py#L1799-L1813
22537db96d56Sopenharmony_ci        but provides consistent cross-platform behavior.
22547db96d56Sopenharmony_ci        >>> san = SanitizedNames._sanitize
22557db96d56Sopenharmony_ci        >>> san('/foo/bar')
22567db96d56Sopenharmony_ci        'foo/bar'
22577db96d56Sopenharmony_ci        >>> san('//foo.txt')
22587db96d56Sopenharmony_ci        'foo.txt'
22597db96d56Sopenharmony_ci        >>> san('foo/.././bar.txt')
22607db96d56Sopenharmony_ci        'foo/bar.txt'
22617db96d56Sopenharmony_ci        >>> san('foo../.bar.txt')
22627db96d56Sopenharmony_ci        'foo../.bar.txt'
22637db96d56Sopenharmony_ci        >>> san('\\foo\\bar.txt')
22647db96d56Sopenharmony_ci        'foo/bar.txt'
22657db96d56Sopenharmony_ci        >>> san('D:\\foo.txt')
22667db96d56Sopenharmony_ci        'D/foo.txt'
22677db96d56Sopenharmony_ci        >>> san('\\\\server\\share\\file.txt')
22687db96d56Sopenharmony_ci        'server/share/file.txt'
22697db96d56Sopenharmony_ci        >>> san('\\\\?\\GLOBALROOT\\Volume3')
22707db96d56Sopenharmony_ci        '?/GLOBALROOT/Volume3'
22717db96d56Sopenharmony_ci        >>> san('\\\\.\\PhysicalDrive1\\root')
22727db96d56Sopenharmony_ci        'PhysicalDrive1/root'
22737db96d56Sopenharmony_ci        Retain any trailing slash.
22747db96d56Sopenharmony_ci        >>> san('abc/')
22757db96d56Sopenharmony_ci        'abc/'
22767db96d56Sopenharmony_ci        Raises a ValueError if the result is empty.
22777db96d56Sopenharmony_ci        >>> san('../..')
22787db96d56Sopenharmony_ci        Traceback (most recent call last):
22797db96d56Sopenharmony_ci        ...
22807db96d56Sopenharmony_ci        ValueError: Empty filename
22817db96d56Sopenharmony_ci        """
22827db96d56Sopenharmony_ci
22837db96d56Sopenharmony_ci        def allowed(part):
22847db96d56Sopenharmony_ci            return part and part not in {'..', '.'}
22857db96d56Sopenharmony_ci
22867db96d56Sopenharmony_ci        # Remove the drive letter.
22877db96d56Sopenharmony_ci        # Don't use ntpath.splitdrive, because that also strips UNC paths
22887db96d56Sopenharmony_ci        bare = re.sub('^([A-Z]):', r'\1', name, flags=re.IGNORECASE)
22897db96d56Sopenharmony_ci        clean = bare.replace('\\', '/')
22907db96d56Sopenharmony_ci        parts = clean.split('/')
22917db96d56Sopenharmony_ci        joined = '/'.join(filter(allowed, parts))
22927db96d56Sopenharmony_ci        if not joined:
22937db96d56Sopenharmony_ci            raise ValueError("Empty filename")
22947db96d56Sopenharmony_ci        return joined + '/' * name.endswith('/')
22957db96d56Sopenharmony_ci
22967db96d56Sopenharmony_ci
22977db96d56Sopenharmony_ciclass CompleteDirs(SanitizedNames, ZipFile):
22987db96d56Sopenharmony_ci    """
22997db96d56Sopenharmony_ci    A ZipFile subclass that ensures that implied directories
23007db96d56Sopenharmony_ci    are always included in the namelist.
23017db96d56Sopenharmony_ci    """
23027db96d56Sopenharmony_ci
23037db96d56Sopenharmony_ci    @staticmethod
23047db96d56Sopenharmony_ci    def _implied_dirs(names):
23057db96d56Sopenharmony_ci        parents = itertools.chain.from_iterable(map(_parents, names))
23067db96d56Sopenharmony_ci        as_dirs = (p + posixpath.sep for p in parents)
23077db96d56Sopenharmony_ci        return _dedupe(_difference(as_dirs, names))
23087db96d56Sopenharmony_ci
23097db96d56Sopenharmony_ci    def namelist(self):
23107db96d56Sopenharmony_ci        names = super(CompleteDirs, self).namelist()
23117db96d56Sopenharmony_ci        return names + list(self._implied_dirs(names))
23127db96d56Sopenharmony_ci
23137db96d56Sopenharmony_ci    def _name_set(self):
23147db96d56Sopenharmony_ci        return set(self.namelist())
23157db96d56Sopenharmony_ci
23167db96d56Sopenharmony_ci    def resolve_dir(self, name):
23177db96d56Sopenharmony_ci        """
23187db96d56Sopenharmony_ci        If the name represents a directory, return that name
23197db96d56Sopenharmony_ci        as a directory (with the trailing slash).
23207db96d56Sopenharmony_ci        """
23217db96d56Sopenharmony_ci        names = self._name_set()
23227db96d56Sopenharmony_ci        dirname = name + '/'
23237db96d56Sopenharmony_ci        dir_match = name not in names and dirname in names
23247db96d56Sopenharmony_ci        return dirname if dir_match else name
23257db96d56Sopenharmony_ci
23267db96d56Sopenharmony_ci    def getinfo(self, name):
23277db96d56Sopenharmony_ci        """
23287db96d56Sopenharmony_ci        Supplement getinfo for implied dirs.
23297db96d56Sopenharmony_ci        """
23307db96d56Sopenharmony_ci        try:
23317db96d56Sopenharmony_ci            return super().getinfo(name)
23327db96d56Sopenharmony_ci        except KeyError:
23337db96d56Sopenharmony_ci            if not name.endswith('/') or name not in self._name_set():
23347db96d56Sopenharmony_ci                raise
23357db96d56Sopenharmony_ci            return ZipInfo(filename=name)
23367db96d56Sopenharmony_ci
23377db96d56Sopenharmony_ci    @classmethod
23387db96d56Sopenharmony_ci    def make(cls, source):
23397db96d56Sopenharmony_ci        """
23407db96d56Sopenharmony_ci        Given a source (filename or zipfile), return an
23417db96d56Sopenharmony_ci        appropriate CompleteDirs subclass.
23427db96d56Sopenharmony_ci        """
23437db96d56Sopenharmony_ci        if isinstance(source, CompleteDirs):
23447db96d56Sopenharmony_ci            return source
23457db96d56Sopenharmony_ci
23467db96d56Sopenharmony_ci        if not isinstance(source, ZipFile):
23477db96d56Sopenharmony_ci            return cls(source)
23487db96d56Sopenharmony_ci
23497db96d56Sopenharmony_ci        # Only allow for FastLookup when supplied zipfile is read-only
23507db96d56Sopenharmony_ci        if 'r' not in source.mode:
23517db96d56Sopenharmony_ci            cls = CompleteDirs
23527db96d56Sopenharmony_ci
23537db96d56Sopenharmony_ci        source.__class__ = cls
23547db96d56Sopenharmony_ci        return source
23557db96d56Sopenharmony_ci
23567db96d56Sopenharmony_ci
23577db96d56Sopenharmony_ciclass FastLookup(CompleteDirs):
23587db96d56Sopenharmony_ci    """
23597db96d56Sopenharmony_ci    ZipFile subclass to ensure implicit
23607db96d56Sopenharmony_ci    dirs exist and are resolved rapidly.
23617db96d56Sopenharmony_ci    """
23627db96d56Sopenharmony_ci
23637db96d56Sopenharmony_ci    def namelist(self):
23647db96d56Sopenharmony_ci        with contextlib.suppress(AttributeError):
23657db96d56Sopenharmony_ci            return self.__names
23667db96d56Sopenharmony_ci        self.__names = super(FastLookup, self).namelist()
23677db96d56Sopenharmony_ci        return self.__names
23687db96d56Sopenharmony_ci
23697db96d56Sopenharmony_ci    def _name_set(self):
23707db96d56Sopenharmony_ci        with contextlib.suppress(AttributeError):
23717db96d56Sopenharmony_ci            return self.__lookup
23727db96d56Sopenharmony_ci        self.__lookup = super(FastLookup, self)._name_set()
23737db96d56Sopenharmony_ci        return self.__lookup
23747db96d56Sopenharmony_ci
23757db96d56Sopenharmony_ci
23767db96d56Sopenharmony_cidef _extract_text_encoding(encoding=None, *args, **kwargs):
23777db96d56Sopenharmony_ci    # stacklevel=3 so that the caller of the caller see any warning.
23787db96d56Sopenharmony_ci    return io.text_encoding(encoding, 3), args, kwargs
23797db96d56Sopenharmony_ci
23807db96d56Sopenharmony_ci
23817db96d56Sopenharmony_ciclass Path:
23827db96d56Sopenharmony_ci    """
23837db96d56Sopenharmony_ci    A pathlib-compatible interface for zip files.
23847db96d56Sopenharmony_ci
23857db96d56Sopenharmony_ci    Consider a zip file with this structure::
23867db96d56Sopenharmony_ci
23877db96d56Sopenharmony_ci        .
23887db96d56Sopenharmony_ci        ├── a.txt
23897db96d56Sopenharmony_ci        └── b
23907db96d56Sopenharmony_ci            ├── c.txt
23917db96d56Sopenharmony_ci            └── d
23927db96d56Sopenharmony_ci                └── e.txt
23937db96d56Sopenharmony_ci
23947db96d56Sopenharmony_ci    >>> data = io.BytesIO()
23957db96d56Sopenharmony_ci    >>> zf = ZipFile(data, 'w')
23967db96d56Sopenharmony_ci    >>> zf.writestr('a.txt', 'content of a')
23977db96d56Sopenharmony_ci    >>> zf.writestr('b/c.txt', 'content of c')
23987db96d56Sopenharmony_ci    >>> zf.writestr('b/d/e.txt', 'content of e')
23997db96d56Sopenharmony_ci    >>> zf.filename = 'mem/abcde.zip'
24007db96d56Sopenharmony_ci
24017db96d56Sopenharmony_ci    Path accepts the zipfile object itself or a filename
24027db96d56Sopenharmony_ci
24037db96d56Sopenharmony_ci    >>> root = Path(zf)
24047db96d56Sopenharmony_ci
24057db96d56Sopenharmony_ci    From there, several path operations are available.
24067db96d56Sopenharmony_ci
24077db96d56Sopenharmony_ci    Directory iteration (including the zip file itself):
24087db96d56Sopenharmony_ci
24097db96d56Sopenharmony_ci    >>> a, b = root.iterdir()
24107db96d56Sopenharmony_ci    >>> a
24117db96d56Sopenharmony_ci    Path('mem/abcde.zip', 'a.txt')
24127db96d56Sopenharmony_ci    >>> b
24137db96d56Sopenharmony_ci    Path('mem/abcde.zip', 'b/')
24147db96d56Sopenharmony_ci
24157db96d56Sopenharmony_ci    name property:
24167db96d56Sopenharmony_ci
24177db96d56Sopenharmony_ci    >>> b.name
24187db96d56Sopenharmony_ci    'b'
24197db96d56Sopenharmony_ci
24207db96d56Sopenharmony_ci    join with divide operator:
24217db96d56Sopenharmony_ci
24227db96d56Sopenharmony_ci    >>> c = b / 'c.txt'
24237db96d56Sopenharmony_ci    >>> c
24247db96d56Sopenharmony_ci    Path('mem/abcde.zip', 'b/c.txt')
24257db96d56Sopenharmony_ci    >>> c.name
24267db96d56Sopenharmony_ci    'c.txt'
24277db96d56Sopenharmony_ci
24287db96d56Sopenharmony_ci    Read text:
24297db96d56Sopenharmony_ci
24307db96d56Sopenharmony_ci    >>> c.read_text()
24317db96d56Sopenharmony_ci    'content of c'
24327db96d56Sopenharmony_ci
24337db96d56Sopenharmony_ci    existence:
24347db96d56Sopenharmony_ci
24357db96d56Sopenharmony_ci    >>> c.exists()
24367db96d56Sopenharmony_ci    True
24377db96d56Sopenharmony_ci    >>> (b / 'missing.txt').exists()
24387db96d56Sopenharmony_ci    False
24397db96d56Sopenharmony_ci
24407db96d56Sopenharmony_ci    Coercion to string:
24417db96d56Sopenharmony_ci
24427db96d56Sopenharmony_ci    >>> import os
24437db96d56Sopenharmony_ci    >>> str(c).replace(os.sep, posixpath.sep)
24447db96d56Sopenharmony_ci    'mem/abcde.zip/b/c.txt'
24457db96d56Sopenharmony_ci
24467db96d56Sopenharmony_ci    At the root, ``name``, ``filename``, and ``parent``
24477db96d56Sopenharmony_ci    resolve to the zipfile. Note these attributes are not
24487db96d56Sopenharmony_ci    valid and will raise a ``ValueError`` if the zipfile
24497db96d56Sopenharmony_ci    has no filename.
24507db96d56Sopenharmony_ci
24517db96d56Sopenharmony_ci    >>> root.name
24527db96d56Sopenharmony_ci    'abcde.zip'
24537db96d56Sopenharmony_ci    >>> str(root.filename).replace(os.sep, posixpath.sep)
24547db96d56Sopenharmony_ci    'mem/abcde.zip'
24557db96d56Sopenharmony_ci    >>> str(root.parent)
24567db96d56Sopenharmony_ci    'mem'
24577db96d56Sopenharmony_ci    """
24587db96d56Sopenharmony_ci
24597db96d56Sopenharmony_ci    __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})"
24607db96d56Sopenharmony_ci
24617db96d56Sopenharmony_ci    def __init__(self, root, at=""):
24627db96d56Sopenharmony_ci        """
24637db96d56Sopenharmony_ci        Construct a Path from a ZipFile or filename.
24647db96d56Sopenharmony_ci
24657db96d56Sopenharmony_ci        Note: When the source is an existing ZipFile object,
24667db96d56Sopenharmony_ci        its type (__class__) will be mutated to a
24677db96d56Sopenharmony_ci        specialized type. If the caller wishes to retain the
24687db96d56Sopenharmony_ci        original type, the caller should either create a
24697db96d56Sopenharmony_ci        separate ZipFile object or pass a filename.
24707db96d56Sopenharmony_ci        """
24717db96d56Sopenharmony_ci        self.root = FastLookup.make(root)
24727db96d56Sopenharmony_ci        self.at = at
24737db96d56Sopenharmony_ci
24747db96d56Sopenharmony_ci    def open(self, mode='r', *args, pwd=None, **kwargs):
24757db96d56Sopenharmony_ci        """
24767db96d56Sopenharmony_ci        Open this entry as text or binary following the semantics
24777db96d56Sopenharmony_ci        of ``pathlib.Path.open()`` by passing arguments through
24787db96d56Sopenharmony_ci        to io.TextIOWrapper().
24797db96d56Sopenharmony_ci        """
24807db96d56Sopenharmony_ci        if self.is_dir():
24817db96d56Sopenharmony_ci            raise IsADirectoryError(self)
24827db96d56Sopenharmony_ci        zip_mode = mode[0]
24837db96d56Sopenharmony_ci        if not self.exists() and zip_mode == 'r':
24847db96d56Sopenharmony_ci            raise FileNotFoundError(self)
24857db96d56Sopenharmony_ci        stream = self.root.open(self.at, zip_mode, pwd=pwd)
24867db96d56Sopenharmony_ci        if 'b' in mode:
24877db96d56Sopenharmony_ci            if args or kwargs:
24887db96d56Sopenharmony_ci                raise ValueError("encoding args invalid for binary operation")
24897db96d56Sopenharmony_ci            return stream
24907db96d56Sopenharmony_ci        # Text mode:
24917db96d56Sopenharmony_ci        encoding, args, kwargs = _extract_text_encoding(*args, **kwargs)
24927db96d56Sopenharmony_ci        return io.TextIOWrapper(stream, encoding, *args, **kwargs)
24937db96d56Sopenharmony_ci
24947db96d56Sopenharmony_ci    @property
24957db96d56Sopenharmony_ci    def name(self):
24967db96d56Sopenharmony_ci        return pathlib.Path(self.at).name or self.filename.name
24977db96d56Sopenharmony_ci
24987db96d56Sopenharmony_ci    @property
24997db96d56Sopenharmony_ci    def suffix(self):
25007db96d56Sopenharmony_ci        return pathlib.Path(self.at).suffix or self.filename.suffix
25017db96d56Sopenharmony_ci
25027db96d56Sopenharmony_ci    @property
25037db96d56Sopenharmony_ci    def suffixes(self):
25047db96d56Sopenharmony_ci        return pathlib.Path(self.at).suffixes or self.filename.suffixes
25057db96d56Sopenharmony_ci
25067db96d56Sopenharmony_ci    @property
25077db96d56Sopenharmony_ci    def stem(self):
25087db96d56Sopenharmony_ci        return pathlib.Path(self.at).stem or self.filename.stem
25097db96d56Sopenharmony_ci
25107db96d56Sopenharmony_ci    @property
25117db96d56Sopenharmony_ci    def filename(self):
25127db96d56Sopenharmony_ci        return pathlib.Path(self.root.filename).joinpath(self.at)
25137db96d56Sopenharmony_ci
25147db96d56Sopenharmony_ci    def read_text(self, *args, **kwargs):
25157db96d56Sopenharmony_ci        encoding, args, kwargs = _extract_text_encoding(*args, **kwargs)
25167db96d56Sopenharmony_ci        with self.open('r', encoding, *args, **kwargs) as strm:
25177db96d56Sopenharmony_ci            return strm.read()
25187db96d56Sopenharmony_ci
25197db96d56Sopenharmony_ci    def read_bytes(self):
25207db96d56Sopenharmony_ci        with self.open('rb') as strm:
25217db96d56Sopenharmony_ci            return strm.read()
25227db96d56Sopenharmony_ci
25237db96d56Sopenharmony_ci    def _is_child(self, path):
25247db96d56Sopenharmony_ci        return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/")
25257db96d56Sopenharmony_ci
25267db96d56Sopenharmony_ci    def _next(self, at):
25277db96d56Sopenharmony_ci        return self.__class__(self.root, at)
25287db96d56Sopenharmony_ci
25297db96d56Sopenharmony_ci    def is_dir(self):
25307db96d56Sopenharmony_ci        return not self.at or self.at.endswith("/")
25317db96d56Sopenharmony_ci
25327db96d56Sopenharmony_ci    def is_file(self):
25337db96d56Sopenharmony_ci        return self.exists() and not self.is_dir()
25347db96d56Sopenharmony_ci
25357db96d56Sopenharmony_ci    def exists(self):
25367db96d56Sopenharmony_ci        return self.at in self.root._name_set()
25377db96d56Sopenharmony_ci
25387db96d56Sopenharmony_ci    def iterdir(self):
25397db96d56Sopenharmony_ci        if not self.is_dir():
25407db96d56Sopenharmony_ci            raise ValueError("Can't listdir a file")
25417db96d56Sopenharmony_ci        subs = map(self._next, self.root.namelist())
25427db96d56Sopenharmony_ci        return filter(self._is_child, subs)
25437db96d56Sopenharmony_ci
25447db96d56Sopenharmony_ci    def __str__(self):
25457db96d56Sopenharmony_ci        return posixpath.join(self.root.filename, self.at)
25467db96d56Sopenharmony_ci
25477db96d56Sopenharmony_ci    def __repr__(self):
25487db96d56Sopenharmony_ci        return self.__repr.format(self=self)
25497db96d56Sopenharmony_ci
25507db96d56Sopenharmony_ci    def joinpath(self, *other):
25517db96d56Sopenharmony_ci        next = posixpath.join(self.at, *other)
25527db96d56Sopenharmony_ci        return self._next(self.root.resolve_dir(next))
25537db96d56Sopenharmony_ci
25547db96d56Sopenharmony_ci    __truediv__ = joinpath
25557db96d56Sopenharmony_ci
25567db96d56Sopenharmony_ci    @property
25577db96d56Sopenharmony_ci    def parent(self):
25587db96d56Sopenharmony_ci        if not self.at:
25597db96d56Sopenharmony_ci            return self.filename.parent
25607db96d56Sopenharmony_ci        parent_at = posixpath.dirname(self.at.rstrip('/'))
25617db96d56Sopenharmony_ci        if parent_at:
25627db96d56Sopenharmony_ci            parent_at += '/'
25637db96d56Sopenharmony_ci        return self._next(parent_at)
25647db96d56Sopenharmony_ci
25657db96d56Sopenharmony_ci
25667db96d56Sopenharmony_cidef main(args=None):
25677db96d56Sopenharmony_ci    import argparse
25687db96d56Sopenharmony_ci
25697db96d56Sopenharmony_ci    description = 'A simple command-line interface for zipfile module.'
25707db96d56Sopenharmony_ci    parser = argparse.ArgumentParser(description=description)
25717db96d56Sopenharmony_ci    group = parser.add_mutually_exclusive_group(required=True)
25727db96d56Sopenharmony_ci    group.add_argument('-l', '--list', metavar='<zipfile>',
25737db96d56Sopenharmony_ci                       help='Show listing of a zipfile')
25747db96d56Sopenharmony_ci    group.add_argument('-e', '--extract', nargs=2,
25757db96d56Sopenharmony_ci                       metavar=('<zipfile>', '<output_dir>'),
25767db96d56Sopenharmony_ci                       help='Extract zipfile into target dir')
25777db96d56Sopenharmony_ci    group.add_argument('-c', '--create', nargs='+',
25787db96d56Sopenharmony_ci                       metavar=('<name>', '<file>'),
25797db96d56Sopenharmony_ci                       help='Create zipfile from sources')
25807db96d56Sopenharmony_ci    group.add_argument('-t', '--test', metavar='<zipfile>',
25817db96d56Sopenharmony_ci                       help='Test if a zipfile is valid')
25827db96d56Sopenharmony_ci    parser.add_argument('--metadata-encoding', metavar='<encoding>',
25837db96d56Sopenharmony_ci                        help='Specify encoding of member names for -l, -e and -t')
25847db96d56Sopenharmony_ci    args = parser.parse_args(args)
25857db96d56Sopenharmony_ci
25867db96d56Sopenharmony_ci    encoding = args.metadata_encoding
25877db96d56Sopenharmony_ci
25887db96d56Sopenharmony_ci    if args.test is not None:
25897db96d56Sopenharmony_ci        src = args.test
25907db96d56Sopenharmony_ci        with ZipFile(src, 'r', metadata_encoding=encoding) as zf:
25917db96d56Sopenharmony_ci            badfile = zf.testzip()
25927db96d56Sopenharmony_ci        if badfile:
25937db96d56Sopenharmony_ci            print("The following enclosed file is corrupted: {!r}".format(badfile))
25947db96d56Sopenharmony_ci        print("Done testing")
25957db96d56Sopenharmony_ci
25967db96d56Sopenharmony_ci    elif args.list is not None:
25977db96d56Sopenharmony_ci        src = args.list
25987db96d56Sopenharmony_ci        with ZipFile(src, 'r', metadata_encoding=encoding) as zf:
25997db96d56Sopenharmony_ci            zf.printdir()
26007db96d56Sopenharmony_ci
26017db96d56Sopenharmony_ci    elif args.extract is not None:
26027db96d56Sopenharmony_ci        src, curdir = args.extract
26037db96d56Sopenharmony_ci        with ZipFile(src, 'r', metadata_encoding=encoding) as zf:
26047db96d56Sopenharmony_ci            zf.extractall(curdir)
26057db96d56Sopenharmony_ci
26067db96d56Sopenharmony_ci    elif args.create is not None:
26077db96d56Sopenharmony_ci        if encoding:
26087db96d56Sopenharmony_ci            print("Non-conforming encodings not supported with -c.",
26097db96d56Sopenharmony_ci                  file=sys.stderr)
26107db96d56Sopenharmony_ci            sys.exit(1)
26117db96d56Sopenharmony_ci
26127db96d56Sopenharmony_ci        zip_name = args.create.pop(0)
26137db96d56Sopenharmony_ci        files = args.create
26147db96d56Sopenharmony_ci
26157db96d56Sopenharmony_ci        def addToZip(zf, path, zippath):
26167db96d56Sopenharmony_ci            if os.path.isfile(path):
26177db96d56Sopenharmony_ci                zf.write(path, zippath, ZIP_DEFLATED)
26187db96d56Sopenharmony_ci            elif os.path.isdir(path):
26197db96d56Sopenharmony_ci                if zippath:
26207db96d56Sopenharmony_ci                    zf.write(path, zippath)
26217db96d56Sopenharmony_ci                for nm in sorted(os.listdir(path)):
26227db96d56Sopenharmony_ci                    addToZip(zf,
26237db96d56Sopenharmony_ci                             os.path.join(path, nm), os.path.join(zippath, nm))
26247db96d56Sopenharmony_ci            # else: ignore
26257db96d56Sopenharmony_ci
26267db96d56Sopenharmony_ci        with ZipFile(zip_name, 'w') as zf:
26277db96d56Sopenharmony_ci            for path in files:
26287db96d56Sopenharmony_ci                zippath = os.path.basename(path)
26297db96d56Sopenharmony_ci                if not zippath:
26307db96d56Sopenharmony_ci                    zippath = os.path.basename(os.path.dirname(path))
26317db96d56Sopenharmony_ci                if zippath in ('', os.curdir, os.pardir):
26327db96d56Sopenharmony_ci                    zippath = ''
26337db96d56Sopenharmony_ci                addToZip(zf, path, zippath)
26347db96d56Sopenharmony_ci
26357db96d56Sopenharmony_ci
26367db96d56Sopenharmony_ciif __name__ == "__main__":
26377db96d56Sopenharmony_ci    main()
2638