176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller/* 276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * Copyright (C) 2015 The Android Open Source Project 376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * 476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * Licensed under the Apache License, Version 2.0 (the "License"); 576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * you may not use this file except in compliance with the License. 676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * You may obtain a copy of the License at 776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * 876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * http://www.apache.org/licenses/LICENSE-2.0 976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * 1076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * Unless required by applicable law or agreed to in writing, software 1176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * distributed under the License is distributed on an "AS IS" BASIS, 1276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * See the License for the specific language governing permissions and 1476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * limitations under the License. 1576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller */ 1676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerpackage com.android.timezone.distro; 1776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 1876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.ByteArrayInputStream; 1976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.ByteArrayOutputStream; 2076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.File; 2176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.FileOutputStream; 2276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.IOException; 2376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.io.InputStream; 2476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.util.Arrays; 2547d149abc26ae3ddd6322ad852610aba7f061bafNeil Fullerimport java.util.function.Supplier; 2676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.util.zip.ZipEntry; 2776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerimport java.util.zip.ZipInputStream; 2876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 2976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller/** 306e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * A time zone distro. This is a thin wrapper around an {@link InputStream} containing a zip archive 316e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * with knowledge of its expected structure and logic for its safe extraction. One of 326e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * {@link #extractTo(File)} or {@link #getDistroVersion()} must be called for the 336e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * {@link InputStream} to be closed. 3476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller */ 3576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fullerpublic final class TimeZoneDistro { 3676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 37ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller /** The standard name of Android time zone distro files. */ 38ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller public static final String FILE_NAME = "distro.zip"; 39ad7e81c00260b6dcb9d9ecf2c3c8aa161f555059Neil Fuller 4076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** The name of the file inside the distro containing bionic/libcore TZ data. */ 4176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public static final String TZDATA_FILE_NAME = "tzdata"; 4276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 4376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** The name of the file inside the distro containing ICU TZ data. */ 4476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public static final String ICU_DATA_FILE_NAME = "icu/icu_tzdata.dat"; 4576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 4676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** The name of the file inside the distro containing time zone lookup data. */ 4776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public static final String TZLOOKUP_FILE_NAME = "tzlookup.xml"; 4876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 4976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** 5076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * The name of the file inside the distro containing the distro version information. 5176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * The content is ASCII bytes representing a set of version numbers. See {@link DistroVersion}. 5276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * This constant must match the one in system/core/tzdatacheck/tzdatacheck.cpp. 5376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller */ 5476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public static final String DISTRO_VERSION_FILE_NAME = "distro_version"; 5576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 5676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private static final int BUFFER_SIZE = 8192; 5776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 5876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** 5976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * Maximum size of entry getEntryContents() will pull into a byte array. To avoid exhausting 6076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller * heap memory when encountering unexpectedly large entries. 128k should be enough for anyone. 6176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller */ 6276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private static final long MAX_GET_ENTRY_CONTENTS_SIZE = 128 * 1024; 6376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 646e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller private final InputStream inputStream; 6576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 6647d149abc26ae3ddd6322ad852610aba7f061bafNeil Fuller /** 676e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * Creates a TimeZoneDistro using a byte array. A convenience for 686e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * {@code new TimeZoneDistro(new ByteArrayInputStream(bytes))}. 6947d149abc26ae3ddd6322ad852610aba7f061bafNeil Fuller */ 7076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public TimeZoneDistro(byte[] bytes) { 716e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller this(new ByteArrayInputStream(bytes)); 7247d149abc26ae3ddd6322ad852610aba7f061bafNeil Fuller } 7347d149abc26ae3ddd6322ad852610aba7f061bafNeil Fuller 7447d149abc26ae3ddd6322ad852610aba7f061bafNeil Fuller /** 756e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * Creates a TimeZoneDistro wrapping an {@link InputStream}. 7647d149abc26ae3ddd6322ad852610aba7f061bafNeil Fuller */ 776e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller public TimeZoneDistro(InputStream inputStream) { 786e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller this.inputStream = inputStream; 7976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 8076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 816e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller /** 826e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * Consumes the wrapped {@link InputStream} returning only the {@link DistroVersion}. 836e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * The wrapped {@link InputStream} is closed after this call. 846e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller */ 8576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public DistroVersion getDistroVersion() throws DistroException, IOException { 866e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller byte[] contents = getEntryContents(inputStream, DISTRO_VERSION_FILE_NAME); 8776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (contents == null) { 8876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throw new DistroException("Distro version file entry not found"); 8976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 9076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return DistroVersion.fromBytes(contents); 9176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 9276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 9376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller private static byte[] getEntryContents(InputStream is, String entryName) throws IOException { 9476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller try (ZipInputStream zipInputStream = new ZipInputStream(is)) { 9576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller ZipEntry entry; 9676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller while ((entry = zipInputStream.getNextEntry()) != null) { 9776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller String name = entry.getName(); 9876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 9976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (!entryName.equals(name)) { 10076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller continue; 10176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 10276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // Guard against massive entries consuming too much heap memory. 10376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (entry.getSize() > MAX_GET_ENTRY_CONTENTS_SIZE) { 10476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throw new IOException("Entry " + entryName + " too large: " + entry.getSize()); 10576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 10676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller byte[] buffer = new byte[BUFFER_SIZE]; 10776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 10876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller int count; 10976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller while ((count = zipInputStream.read(buffer)) != -1) { 11076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller baos.write(buffer, 0, count); 11176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 11276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return baos.toByteArray(); 11376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 11476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 11576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // Entry not found. 11676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller return null; 11776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 11876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 11976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 1206e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller /** 1216e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * Consumes the wrapped {@link InputStream}, extracting the content to {@code targetDir}. 1226e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller * The wrapped {@link InputStream} is closed after this call. 1236e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller */ 12476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller public void extractTo(File targetDir) throws IOException { 1256e484a7cdddda4035ca36826437cf3718dd7fa47Neil Fuller extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */); 12676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 12776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 12876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller /** Visible for testing */ 12976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller static void extractZipSafely(InputStream is, File targetDir, boolean makeWorldReadable) 13076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller throws IOException { 13176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 13276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // Create the extraction dir, if needed. 13376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller FileUtils.ensureDirectoriesExist(targetDir, makeWorldReadable); 13476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 13576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller try (ZipInputStream zipInputStream = new ZipInputStream(is)) { 13676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller byte[] buffer = new byte[BUFFER_SIZE]; 13776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller ZipEntry entry; 13876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller while ((entry = zipInputStream.getNextEntry()) != null) { 13976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // Validate the entry name: make sure the unpacked file will exist beneath the 14076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // targetDir. 14176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller String name = entry.getName(); 14276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // Note, we assume that nothing will quickly insert a symlink after createSubFile() 14376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // that might invalidate the guarantees about name existing beneath targetDir. 14476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller File entryFile = FileUtils.createSubFile(targetDir, name); 14576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 14676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (entry.isDirectory()) { 14776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller FileUtils.ensureDirectoriesExist(entryFile, makeWorldReadable); 14876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } else { 14976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // Create the path if there was no directory entry. 15076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (!entryFile.getParentFile().exists()) { 15176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller FileUtils.ensureDirectoriesExist( 15276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller entryFile.getParentFile(), makeWorldReadable); 15376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 15476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller 15576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller try (FileOutputStream fos = new FileOutputStream(entryFile)) { 15676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller int count; 15776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller while ((count = zipInputStream.read(buffer)) != -1) { 15876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller fos.write(buffer, 0, count); 15976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 16076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // sync to disk 16176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller fos.getFD().sync(); 16276e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 16376e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller // mark entryFile -rw-r--r-- 16476e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller if (makeWorldReadable) { 16576e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller FileUtils.makeWorldReadable(entryFile); 16676e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 16776e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 16876e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 16976e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 17076e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller } 17176e2f39dc8dd9f2b0c2fb081a86ef6ad3e4e5632Neil Fuller} 172