1*3c875a21SAndroid Build Coastguard Worker# Copyright (C) 2018 The Android Open Source Project 2*3c875a21SAndroid Build Coastguard Worker# 3*3c875a21SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*3c875a21SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*3c875a21SAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*3c875a21SAndroid Build Coastguard Worker# 7*3c875a21SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*3c875a21SAndroid Build Coastguard Worker# 9*3c875a21SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*3c875a21SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*3c875a21SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*3c875a21SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*3c875a21SAndroid Build Coastguard Worker# limitations under the License. 14*3c875a21SAndroid Build Coastguard Worker"""Functions to process archive files.""" 15*3c875a21SAndroid Build Coastguard Worker 16*3c875a21SAndroid Build Coastguard Workerimport os 17*3c875a21SAndroid Build Coastguard Workerimport tempfile 18*3c875a21SAndroid Build Coastguard Workerimport tarfile 19*3c875a21SAndroid Build Coastguard Workerimport urllib.parse 20*3c875a21SAndroid Build Coastguard Workerimport zipfile 21*3c875a21SAndroid Build Coastguard Worker 22*3c875a21SAndroid Build Coastguard Worker 23*3c875a21SAndroid Build Coastguard Workerclass ZipFileWithPermission(zipfile.ZipFile): 24*3c875a21SAndroid Build Coastguard Worker """Subclassing Zipfile to preserve file permission. 25*3c875a21SAndroid Build Coastguard Worker 26*3c875a21SAndroid Build Coastguard Worker See https://bugs.python.org/issue15795 27*3c875a21SAndroid Build Coastguard Worker """ 28*3c875a21SAndroid Build Coastguard Worker def _extract_member(self, member, targetpath, pwd): 29*3c875a21SAndroid Build Coastguard Worker ret_val = super()._extract_member(member, targetpath, pwd) 30*3c875a21SAndroid Build Coastguard Worker 31*3c875a21SAndroid Build Coastguard Worker if not isinstance(member, zipfile.ZipInfo): 32*3c875a21SAndroid Build Coastguard Worker member = self.getinfo(member) 33*3c875a21SAndroid Build Coastguard Worker attr = member.external_attr >> 16 34*3c875a21SAndroid Build Coastguard Worker if attr != 0: 35*3c875a21SAndroid Build Coastguard Worker os.chmod(ret_val, attr) 36*3c875a21SAndroid Build Coastguard Worker return ret_val 37*3c875a21SAndroid Build Coastguard Worker 38*3c875a21SAndroid Build Coastguard Worker 39*3c875a21SAndroid Build Coastguard Workerdef unzip(archive_path, target_path): 40*3c875a21SAndroid Build Coastguard Worker """Extracts zip file to a path. 41*3c875a21SAndroid Build Coastguard Worker 42*3c875a21SAndroid Build Coastguard Worker Args: 43*3c875a21SAndroid Build Coastguard Worker archive_path: Path to the zip file. 44*3c875a21SAndroid Build Coastguard Worker target_path: Path to extract files to. 45*3c875a21SAndroid Build Coastguard Worker """ 46*3c875a21SAndroid Build Coastguard Worker 47*3c875a21SAndroid Build Coastguard Worker with ZipFileWithPermission(archive_path) as zfile: 48*3c875a21SAndroid Build Coastguard Worker zfile.extractall(target_path) 49*3c875a21SAndroid Build Coastguard Worker 50*3c875a21SAndroid Build Coastguard Worker 51*3c875a21SAndroid Build Coastguard Workerdef untar(archive_path, target_path): 52*3c875a21SAndroid Build Coastguard Worker """Extracts tar file to a path. 53*3c875a21SAndroid Build Coastguard Worker 54*3c875a21SAndroid Build Coastguard Worker Args: 55*3c875a21SAndroid Build Coastguard Worker archive_path: Path to the tar file. 56*3c875a21SAndroid Build Coastguard Worker target_path: Path to extract files to. 57*3c875a21SAndroid Build Coastguard Worker """ 58*3c875a21SAndroid Build Coastguard Worker 59*3c875a21SAndroid Build Coastguard Worker with tarfile.open(archive_path, mode='r') as tfile: 60*3c875a21SAndroid Build Coastguard Worker tfile.extractall(target_path) 61*3c875a21SAndroid Build Coastguard Worker 62*3c875a21SAndroid Build Coastguard Worker 63*3c875a21SAndroid Build Coastguard WorkerARCHIVE_TYPES = { 64*3c875a21SAndroid Build Coastguard Worker '.zip': unzip, 65*3c875a21SAndroid Build Coastguard Worker '.tar.gz': untar, 66*3c875a21SAndroid Build Coastguard Worker '.tar.bz2': untar, 67*3c875a21SAndroid Build Coastguard Worker '.tar.xz': untar, 68*3c875a21SAndroid Build Coastguard Worker} 69*3c875a21SAndroid Build Coastguard Worker 70*3c875a21SAndroid Build Coastguard Worker 71*3c875a21SAndroid Build Coastguard Workerdef is_supported_archive(url): 72*3c875a21SAndroid Build Coastguard Worker """Checks whether the url points to a supported archive.""" 73*3c875a21SAndroid Build Coastguard Worker return get_extract_func(url) is not None 74*3c875a21SAndroid Build Coastguard Worker 75*3c875a21SAndroid Build Coastguard Worker 76*3c875a21SAndroid Build Coastguard Workerdef get_extract_func(url): 77*3c875a21SAndroid Build Coastguard Worker """Gets the function to extract an archive. 78*3c875a21SAndroid Build Coastguard Worker 79*3c875a21SAndroid Build Coastguard Worker Args: 80*3c875a21SAndroid Build Coastguard Worker url: The url to the archive file. 81*3c875a21SAndroid Build Coastguard Worker 82*3c875a21SAndroid Build Coastguard Worker Returns: 83*3c875a21SAndroid Build Coastguard Worker A function to extract the archive. None if not found. 84*3c875a21SAndroid Build Coastguard Worker """ 85*3c875a21SAndroid Build Coastguard Worker 86*3c875a21SAndroid Build Coastguard Worker parsed_url = urllib.parse.urlparse(url) 87*3c875a21SAndroid Build Coastguard Worker filename = os.path.basename(parsed_url.path) 88*3c875a21SAndroid Build Coastguard Worker for ext, func in ARCHIVE_TYPES.items(): 89*3c875a21SAndroid Build Coastguard Worker if filename.endswith(ext): 90*3c875a21SAndroid Build Coastguard Worker return func 91*3c875a21SAndroid Build Coastguard Worker # crates.io download url does not have file suffix 92*3c875a21SAndroid Build Coastguard Worker # e.g., https://crates.io/api/v1/crates/syn/1.0.16/download 93*3c875a21SAndroid Build Coastguard Worker if url.find('/crates.io/api/') > 0 or url.find('/static.crates.io/crates/'): 94*3c875a21SAndroid Build Coastguard Worker return untar 95*3c875a21SAndroid Build Coastguard Worker return None 96*3c875a21SAndroid Build Coastguard Worker 97*3c875a21SAndroid Build Coastguard Worker 98*3c875a21SAndroid Build Coastguard Workerdef download_and_extract(url): 99*3c875a21SAndroid Build Coastguard Worker """Downloads and extracts an archive file to a temporary directory. 100*3c875a21SAndroid Build Coastguard Worker 101*3c875a21SAndroid Build Coastguard Worker Args: 102*3c875a21SAndroid Build Coastguard Worker url: Url to download. 103*3c875a21SAndroid Build Coastguard Worker 104*3c875a21SAndroid Build Coastguard Worker Returns: 105*3c875a21SAndroid Build Coastguard Worker Path to the temporary directory. 106*3c875a21SAndroid Build Coastguard Worker """ 107*3c875a21SAndroid Build Coastguard Worker 108*3c875a21SAndroid Build Coastguard Worker print(f'Downloading {url}') 109*3c875a21SAndroid Build Coastguard Worker archive_file, _headers = urllib.request.urlretrieve(url) 110*3c875a21SAndroid Build Coastguard Worker 111*3c875a21SAndroid Build Coastguard Worker temporary_dir = tempfile.mkdtemp() 112*3c875a21SAndroid Build Coastguard Worker print(f'Extracting {archive_file} to {temporary_dir}') 113*3c875a21SAndroid Build Coastguard Worker get_extract_func(url)(archive_file, temporary_dir) 114*3c875a21SAndroid Build Coastguard Worker 115*3c875a21SAndroid Build Coastguard Worker return temporary_dir 116*3c875a21SAndroid Build Coastguard Worker 117*3c875a21SAndroid Build Coastguard Worker 118*3c875a21SAndroid Build Coastguard Workerdef find_archive_root(path): 119*3c875a21SAndroid Build Coastguard Worker """Finds the real root of an extracted archive. 120*3c875a21SAndroid Build Coastguard Worker 121*3c875a21SAndroid Build Coastguard Worker Sometimes archives has additional layers of directories. This function tries 122*3c875a21SAndroid Build Coastguard Worker to guess the right 'root' path by entering all single subdirectories. 123*3c875a21SAndroid Build Coastguard Worker 124*3c875a21SAndroid Build Coastguard Worker Args: 125*3c875a21SAndroid Build Coastguard Worker path: Path to the extracted archive. 126*3c875a21SAndroid Build Coastguard Worker 127*3c875a21SAndroid Build Coastguard Worker Returns: 128*3c875a21SAndroid Build Coastguard Worker The root path we found. 129*3c875a21SAndroid Build Coastguard Worker """ 130*3c875a21SAndroid Build Coastguard Worker for root, dirs, files in os.walk(path): 131*3c875a21SAndroid Build Coastguard Worker if files or len(dirs) > 1: 132*3c875a21SAndroid Build Coastguard Worker return root 133*3c875a21SAndroid Build Coastguard Worker return path 134