1*adcb0a62SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*adcb0a62SAndroid Build Coastguard Worker# 3*adcb0a62SAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project 4*adcb0a62SAndroid Build Coastguard Worker# 5*adcb0a62SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*adcb0a62SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*adcb0a62SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*adcb0a62SAndroid Build Coastguard Worker# 9*adcb0a62SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*adcb0a62SAndroid Build Coastguard Worker# 11*adcb0a62SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*adcb0a62SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*adcb0a62SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*adcb0a62SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*adcb0a62SAndroid Build Coastguard Worker# limitations under the License. 16*adcb0a62SAndroid Build Coastguard Worker# 17*adcb0a62SAndroid Build Coastguard Worker 18*adcb0a62SAndroid Build Coastguard Worker"""Unittests for parsing files in zip64 format""" 19*adcb0a62SAndroid Build Coastguard Worker 20*adcb0a62SAndroid Build Coastguard Workerimport os 21*adcb0a62SAndroid Build Coastguard Workerimport subprocess 22*adcb0a62SAndroid Build Coastguard Workerimport tempfile 23*adcb0a62SAndroid Build Coastguard Workerimport unittest 24*adcb0a62SAndroid Build Coastguard Workerimport zipfile 25*adcb0a62SAndroid Build Coastguard Workerimport time 26*adcb0a62SAndroid Build Coastguard Worker 27*adcb0a62SAndroid Build Coastguard Workerclass Zip64Test(unittest.TestCase): 28*adcb0a62SAndroid Build Coastguard Worker @staticmethod 29*adcb0a62SAndroid Build Coastguard Worker def _WriteFile(path, size_in_kib): 30*adcb0a62SAndroid Build Coastguard Worker contents = b'X' * 1024 31*adcb0a62SAndroid Build Coastguard Worker with open(path, 'wb') as f: 32*adcb0a62SAndroid Build Coastguard Worker for i in range(size_in_kib): 33*adcb0a62SAndroid Build Coastguard Worker f.write(contents) 34*adcb0a62SAndroid Build Coastguard Worker 35*adcb0a62SAndroid Build Coastguard Worker @staticmethod 36*adcb0a62SAndroid Build Coastguard Worker def _AddEntriesToZip(output_zip, entries_dict=None): 37*adcb0a62SAndroid Build Coastguard Worker contents = b'X' * 1024 38*adcb0a62SAndroid Build Coastguard Worker for name, size in entries_dict.items(): 39*adcb0a62SAndroid Build Coastguard Worker # Need to pass a ZipInfo with a file_size 40*adcb0a62SAndroid Build Coastguard Worker # to .open() so that it adds the Zip64 header 41*adcb0a62SAndroid Build Coastguard Worker # on larger files 42*adcb0a62SAndroid Build Coastguard Worker info = zipfile.ZipInfo(name) 43*adcb0a62SAndroid Build Coastguard Worker info.file_size = size * 1024 44*adcb0a62SAndroid Build Coastguard Worker with output_zip.open(info, mode='w') as f: 45*adcb0a62SAndroid Build Coastguard Worker for i in range(size): 46*adcb0a62SAndroid Build Coastguard Worker f.write(contents) 47*adcb0a62SAndroid Build Coastguard Worker 48*adcb0a62SAndroid Build Coastguard Worker def _getEntryNames(self, zip_name): 49*adcb0a62SAndroid Build Coastguard Worker cmd = ['ziptool', 'zipinfo', '-1', zip_name] 50*adcb0a62SAndroid Build Coastguard Worker proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 51*adcb0a62SAndroid Build Coastguard Worker output, _ = proc.communicate() 52*adcb0a62SAndroid Build Coastguard Worker self.assertEqual(0, proc.returncode) 53*adcb0a62SAndroid Build Coastguard Worker self.assertNotEqual(None, output) 54*adcb0a62SAndroid Build Coastguard Worker return output.decode('utf-8').split() 55*adcb0a62SAndroid Build Coastguard Worker 56*adcb0a62SAndroid Build Coastguard Worker def _ExtractEntries(self, zip_name): 57*adcb0a62SAndroid Build Coastguard Worker with tempfile.TemporaryDirectory() as temp_dir: 58*adcb0a62SAndroid Build Coastguard Worker cmd = ['ziptool', 'unzip', '-d', temp_dir, zip_name] 59*adcb0a62SAndroid Build Coastguard Worker proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 60*adcb0a62SAndroid Build Coastguard Worker proc.communicate() 61*adcb0a62SAndroid Build Coastguard Worker self.assertEqual(0, proc.returncode) 62*adcb0a62SAndroid Build Coastguard Worker 63*adcb0a62SAndroid Build Coastguard Worker def test_entriesSmallerThan2G(self): 64*adcb0a62SAndroid Build Coastguard Worker with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path: 65*adcb0a62SAndroid Build Coastguard Worker # Add a few entries with each of them smaller than 2GiB. But the entire zip file is larger 66*adcb0a62SAndroid Build Coastguard Worker # than 4GiB in size. 67*adcb0a62SAndroid Build Coastguard Worker with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip: 68*adcb0a62SAndroid Build Coastguard Worker entry_dict = {'a.txt': 1025 * 1024, 'b.txt': 1025 * 1024, 'c.txt': 1025 * 1024, 69*adcb0a62SAndroid Build Coastguard Worker 'd.txt': 1025 * 1024, 'e.txt': 1024} 70*adcb0a62SAndroid Build Coastguard Worker self._AddEntriesToZip(output_zip, entry_dict) 71*adcb0a62SAndroid Build Coastguard Worker 72*adcb0a62SAndroid Build Coastguard Worker read_names = self._getEntryNames(zip_path.name) 73*adcb0a62SAndroid Build Coastguard Worker self.assertEqual(sorted(entry_dict.keys()), sorted(read_names)) 74*adcb0a62SAndroid Build Coastguard Worker self._ExtractEntries(zip_path.name) 75*adcb0a62SAndroid Build Coastguard Worker 76*adcb0a62SAndroid Build Coastguard Worker 77*adcb0a62SAndroid Build Coastguard Worker def test_largeNumberOfEntries(self): 78*adcb0a62SAndroid Build Coastguard Worker with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path: 79*adcb0a62SAndroid Build Coastguard Worker entry_dict = {} 80*adcb0a62SAndroid Build Coastguard Worker # Add 100k entries (more than 65535|UINT16_MAX). 81*adcb0a62SAndroid Build Coastguard Worker # We use empty files so that we don't hit any of the _other_ EOCD limits 82*adcb0a62SAndroid Build Coastguard Worker # and appear to be testing the file count when we're actually not. 83*adcb0a62SAndroid Build Coastguard Worker for num in range(0, 100 * 1024): 84*adcb0a62SAndroid Build Coastguard Worker entry_dict[str(num)] = 0 85*adcb0a62SAndroid Build Coastguard Worker 86*adcb0a62SAndroid Build Coastguard Worker with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip: 87*adcb0a62SAndroid Build Coastguard Worker self._AddEntriesToZip(output_zip, entry_dict) 88*adcb0a62SAndroid Build Coastguard Worker 89*adcb0a62SAndroid Build Coastguard Worker read_names = self._getEntryNames(zip_path.name) 90*adcb0a62SAndroid Build Coastguard Worker self.assertEqual(sorted(entry_dict.keys()), sorted(read_names)) 91*adcb0a62SAndroid Build Coastguard Worker self._ExtractEntries(zip_path.name) 92*adcb0a62SAndroid Build Coastguard Worker 93*adcb0a62SAndroid Build Coastguard Worker 94*adcb0a62SAndroid Build Coastguard Worker def test_largeCompressedEntriesSmallerThan4G(self): 95*adcb0a62SAndroid Build Coastguard Worker with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path: 96*adcb0a62SAndroid Build Coastguard Worker with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED, 97*adcb0a62SAndroid Build Coastguard Worker allowZip64=True) as output_zip: 98*adcb0a62SAndroid Build Coastguard Worker # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed 99*adcb0a62SAndroid Build Coastguard Worker # sizes in the extra field. Test if our ziptool should be able to parse it. 100*adcb0a62SAndroid Build Coastguard Worker entry_dict = {'e.txt': 4095 * 1024, 'f.txt': 4095 * 1024} 101*adcb0a62SAndroid Build Coastguard Worker self._AddEntriesToZip(output_zip, entry_dict) 102*adcb0a62SAndroid Build Coastguard Worker 103*adcb0a62SAndroid Build Coastguard Worker read_names = self._getEntryNames(zip_path.name) 104*adcb0a62SAndroid Build Coastguard Worker self.assertEqual(sorted(entry_dict.keys()), sorted(read_names)) 105*adcb0a62SAndroid Build Coastguard Worker self._ExtractEntries(zip_path.name) 106*adcb0a62SAndroid Build Coastguard Worker 107*adcb0a62SAndroid Build Coastguard Worker 108*adcb0a62SAndroid Build Coastguard Worker def test_forceDataDescriptor(self): 109*adcb0a62SAndroid Build Coastguard Worker with tempfile.NamedTemporaryFile(suffix='.txt') as file_path: 110*adcb0a62SAndroid Build Coastguard Worker self._WriteFile(file_path.name, 5000 * 1024) 111*adcb0a62SAndroid Build Coastguard Worker 112*adcb0a62SAndroid Build Coastguard Worker with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path: 113*adcb0a62SAndroid Build Coastguard Worker with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip: 114*adcb0a62SAndroid Build Coastguard Worker pass 115*adcb0a62SAndroid Build Coastguard Worker # The fd option force writes a data descriptor 116*adcb0a62SAndroid Build Coastguard Worker cmd = ['zip', '-fd', zip_path.name, file_path.name] 117*adcb0a62SAndroid Build Coastguard Worker proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 118*adcb0a62SAndroid Build Coastguard Worker proc.communicate() 119*adcb0a62SAndroid Build Coastguard Worker read_names = self._getEntryNames(zip_path.name) 120*adcb0a62SAndroid Build Coastguard Worker self.assertEqual([file_path.name[1:]], read_names) 121*adcb0a62SAndroid Build Coastguard Worker self._ExtractEntries(zip_path.name) 122*adcb0a62SAndroid Build Coastguard Worker 123*adcb0a62SAndroid Build Coastguard Worker 124*adcb0a62SAndroid Build Coastguard Worker def test_largeUncompressedEntriesLargerThan4G(self): 125*adcb0a62SAndroid Build Coastguard Worker with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path: 126*adcb0a62SAndroid Build Coastguard Worker with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_STORED, 127*adcb0a62SAndroid Build Coastguard Worker allowZip64=True) as output_zip: 128*adcb0a62SAndroid Build Coastguard Worker # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed 129*adcb0a62SAndroid Build Coastguard Worker # sizes in the extra field. Test if our ziptool should be able to parse it. 130*adcb0a62SAndroid Build Coastguard Worker entry_dict = {'g.txt': 5000 * 1024, 'h.txt': 6000 * 1024} 131*adcb0a62SAndroid Build Coastguard Worker self._AddEntriesToZip(output_zip, entry_dict) 132*adcb0a62SAndroid Build Coastguard Worker 133*adcb0a62SAndroid Build Coastguard Worker read_names = self._getEntryNames(zip_path.name) 134*adcb0a62SAndroid Build Coastguard Worker self.assertEqual(sorted(entry_dict.keys()), sorted(read_names)) 135*adcb0a62SAndroid Build Coastguard Worker self._ExtractEntries(zip_path.name) 136*adcb0a62SAndroid Build Coastguard Worker 137*adcb0a62SAndroid Build Coastguard Worker 138*adcb0a62SAndroid Build Coastguard Worker def test_largeCompressedEntriesLargerThan4G(self): 139*adcb0a62SAndroid Build Coastguard Worker with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path: 140*adcb0a62SAndroid Build Coastguard Worker with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED, 141*adcb0a62SAndroid Build Coastguard Worker allowZip64=True) as output_zip: 142*adcb0a62SAndroid Build Coastguard Worker # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed 143*adcb0a62SAndroid Build Coastguard Worker # sizes in the extra field. Test if our ziptool should be able to parse it. 144*adcb0a62SAndroid Build Coastguard Worker entry_dict = {'i.txt': 4096 * 1024, 'j.txt': 7000 * 1024} 145*adcb0a62SAndroid Build Coastguard Worker self._AddEntriesToZip(output_zip, entry_dict) 146*adcb0a62SAndroid Build Coastguard Worker 147*adcb0a62SAndroid Build Coastguard Worker read_names = self._getEntryNames(zip_path.name) 148*adcb0a62SAndroid Build Coastguard Worker self.assertEqual(sorted(entry_dict.keys()), sorted(read_names)) 149*adcb0a62SAndroid Build Coastguard Worker self._ExtractEntries(zip_path.name) 150*adcb0a62SAndroid Build Coastguard Worker 151*adcb0a62SAndroid Build Coastguard Worker 152*adcb0a62SAndroid Build Coastguard Workerif __name__ == '__main__': 153*adcb0a62SAndroid Build Coastguard Worker unittest.main(verbosity=2) 154