diff --git a/libziparchive/.clang-format b/libziparchive/.clang-format deleted file mode 120000 index fd0645fdf..000000000 --- a/libziparchive/.clang-format +++ /dev/null @@ -1 +0,0 @@ -../.clang-format-2 \ No newline at end of file diff --git a/libziparchive/Android.bp b/libziparchive/Android.bp deleted file mode 100644 index c5a968aac..000000000 --- a/libziparchive/Android.bp +++ /dev/null @@ -1,237 +0,0 @@ -// -// Copyright (C) 2013 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -cc_defaults { - name: "libziparchive_flags", - cflags: [ - // ZLIB_CONST turns on const for input buffers, which is pretty standard. - "-DZLIB_CONST", - "-Werror", - "-Wall", - "-D_FILE_OFFSET_BITS=64", - ], - cppflags: [ - // Incorrectly warns when C++11 empty brace {} initializer is used. - // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61489 - "-Wno-missing-field-initializers", - "-Wconversion", - "-Wno-sign-conversion", - ], - - // Enable -Wold-style-cast only for non-Windows targets. _islower_l, - // _isupper_l etc. in MinGW locale_win32.h (included from - // libcxx/include/__locale) has an old-style-cast. - target: { - not_windows: { - cppflags: [ - "-Wold-style-cast", - ], - }, - }, - sanitize: { - misc_undefined: [ - "signed-integer-overflow", - "unsigned-integer-overflow", - "shift", - "integer-divide-by-zero", - "implicit-signed-integer-truncation", - // TODO: Fix crash when we enable this option - // "implicit-unsigned-integer-truncation", - // TODO: not tested yet. - // "implicit-integer-sign-change", - ], - }, -} - -cc_defaults { - name: "libziparchive_defaults", - srcs: [ - "zip_archive.cc", - "zip_archive_stream_entry.cc", - "zip_cd_entry_map.cc", - "zip_error.cpp", - "zip_writer.cc", - ], - - target: { - windows: { - cflags: ["-mno-ms-bitfields"], - - enabled: true, - }, - }, - - shared_libs: [ - "libbase", - "liblog", - ], - - // for FRIEND_TEST - static_libs: ["libgtest_prod"], - export_static_lib_headers: ["libgtest_prod"], - - export_include_dirs: ["include"], -} - -cc_library { - name: "libziparchive", - host_supported: true, - vendor_available: true, - recovery_available: true, - native_bridge_supported: true, - vndk: { - enabled: true, - }, - double_loadable: true, - export_shared_lib_headers: ["libbase"], - - defaults: [ - "libziparchive_defaults", - "libziparchive_flags", - ], - shared_libs: [ - "liblog", - "libbase", - "libz", - ], - target: { - linux_bionic: { - enabled: true, - }, - }, - - apex_available: [ - "//apex_available:platform", - "com.android.art.debug", - "com.android.art.release", - ], -} - -// Tests. -cc_test { - name: "ziparchive-tests", - host_supported: true, - defaults: ["libziparchive_flags"], - - data: [ - "testdata/**/*", - ], - - srcs: [ - "entry_name_utils_test.cc", - "zip_archive_test.cc", - "zip_writer_test.cc", - ], - shared_libs: [ - "libbase", - "liblog", - ], - - static_libs: [ - "libziparchive", - "libz", - "libutils", - ], - - target: { - host: { - cppflags: ["-Wno-unnamed-type-template-args"], - }, - windows: { - enabled: true, - }, - }, - test_suites: ["device-tests"], -} - -// Performance benchmarks. -cc_benchmark { - name: "ziparchive-benchmarks", - defaults: ["libziparchive_flags"], - - srcs: [ - "zip_archive_benchmark.cpp", - ], - shared_libs: [ - "libbase", - "liblog", - ], - - static_libs: [ - "libziparchive", - "libz", - "libutils", - ], - - target: { - host: { - cppflags: ["-Wno-unnamed-type-template-args"], - }, - }, -} - -cc_binary { - name: "ziptool", - defaults: ["libziparchive_flags"], - srcs: ["ziptool.cpp"], - shared_libs: [ - "libbase", - "libziparchive", - ], - recovery_available: true, - host_supported: true, - target: { - android: { - symlinks: ["unzip", "zipinfo"], - }, - }, -} - -cc_fuzz { - name: "libziparchive_fuzzer", - srcs: ["libziparchive_fuzzer.cpp"], - static_libs: ["libziparchive", "libbase", "libz", "liblog"], - host_supported: true, - corpus: ["testdata/*"], -} - -sh_test { - name: "ziptool-tests", - src: "run-ziptool-tests-on-android.sh", - filename: "run-ziptool-tests-on-android.sh", - test_suites: ["general-tests"], - host_supported: true, - device_supported: false, - test_config: "ziptool-tests.xml", - data: ["cli-tests/**/*"], - target_required: ["cli-test", "ziptool"], -} - -python_test_host { - name: "ziparchive_tests_large", - srcs: ["test_ziparchive_large.py"], - main: "test_ziparchive_large.py", - version: { - py2: { - enabled: true, - embedded_launcher: false, - }, - py3: { - enabled: false, - embedded_launcher: false, - }, - }, - test_suites: ["general-tests"], -} diff --git a/libziparchive/OWNERS b/libziparchive/OWNERS deleted file mode 100644 index fcc567aa2..000000000 --- a/libziparchive/OWNERS +++ /dev/null @@ -1 +0,0 @@ -narayan@google.com diff --git a/libziparchive/cli-tests/files/example.zip b/libziparchive/cli-tests/files/example.zip deleted file mode 100644 index c3292e929..000000000 Binary files a/libziparchive/cli-tests/files/example.zip and /dev/null differ diff --git a/libziparchive/cli-tests/unzip.test b/libziparchive/cli-tests/unzip.test deleted file mode 100755 index 6e5cbf213..000000000 --- a/libziparchive/cli-tests/unzip.test +++ /dev/null @@ -1,148 +0,0 @@ -# unzip tests. - -# Note: since "master key", Android uses libziparchive for all zip file -# handling, and that scans the whole central directory immediately. Not only -# lookups by name but also iteration is implemented using the resulting hash -# table, meaning that any test that makes assumptions about iteration order -# will fail on Android. - -name: unzip -l -command: unzip -l $FILES/example.zip d1/d2/x.txt -after: [ ! -f d1/d2/x.txt ] -expected-stdout: - Archive: $FILES/example.zip - Length Date Time Name - --------- ---------- ----- ---- - 1024 2017-06-04 08:45 d1/d2/x.txt - --------- ------- - 1024 1 file ---- - -name: unzip -lq -command: unzip -lq $FILES/example.zip d1/d2/x.txt -after: [ ! -f d1/d2/x.txt ] -expected-stdout: - Length Date Time Name - --------- ---------- ----- ---- - 1024 2017-06-04 08:45 d1/d2/x.txt - --------- ------- - 1024 1 file ---- - -name: unzip -lv -command: unzip -lv $FILES/example.zip d1/d2/x.txt -after: [ ! -f d1/d2/file ] -expected-stdout: - Archive: $FILES/example.zip - Length Method Size Cmpr Date Time CRC-32 Name - -------- ------ ------- ---- ---------- ----- -------- ---- - 1024 Defl:N 11 99% 2017-06-04 08:45 48d7f063 d1/d2/x.txt - -------- ------- --- ------- - 1024 11 99% 1 file ---- - -name: unzip -v -command: unzip -v $FILES/example.zip d1/d2/x.txt -after: [ ! -f d1/d2/file ] -expected-stdout: - Archive: $FILES/example.zip - Length Method Size Cmpr Date Time CRC-32 Name - -------- ------ ------- ---- ---------- ----- -------- ---- - 1024 Defl:N 11 99% 2017-06-04 08:45 48d7f063 d1/d2/x.txt - -------- ------- --- ------- - 1024 11 99% 1 file ---- - -name: unzip one file -command: unzip -q $FILES/example.zip d1/d2/a.txt && cat d1/d2/a.txt -after: [ ! -f d1/d2/b.txt ] -expected-stdout: - a ---- - -name: unzip all files -command: unzip -q $FILES/example.zip -after: [ -f d1/d2/a.txt ] -after: [ -f d1/d2/b.txt ] -after: [ -f d1/d2/c.txt ] -after: [ -f d1/d2/empty.txt ] -after: [ -f d1/d2/x.txt ] -after: [ -d d1/d2/dir ] -expected-stdout: ---- - -name: unzip -o -before: mkdir -p d1/d2 -before: echo b > d1/d2/a.txt -command: unzip -q -o $FILES/example.zip d1/d2/a.txt && cat d1/d2/a.txt -expected-stdout: - a ---- - -name: unzip -n -before: mkdir -p d1/d2 -before: echo b > d1/d2/a.txt -command: unzip -q -n $FILES/example.zip d1/d2/a.txt && cat d1/d2/a.txt -expected-stdout: - b ---- - -# The reference implementation will create *one* level of missing directories, -# so this succeeds. -name: unzip -d shallow non-existent -command: unzip -q -d will-be-created $FILES/example.zip d1/d2/a.txt -after: [ -d will-be-created ] -after: [ -f will-be-created/d1/d2/a.txt ] ---- - -# The reference implementation will *only* create one level of missing -# directories, so this fails. -name: unzip -d deep non-existent -command: unzip -q -d oh-no/will-not-be-created $FILES/example.zip d1/d2/a.txt 2> stderr ; echo $? > status -after: [ ! -d oh-no ] -after: [ ! -d oh-no/will-not-be-created ] -after: [ ! -f oh-no/will-not-be-created/d1/d2/a.txt ] -after: grep -q "oh-no/will-not-be-created" stderr -after: grep -q "No such file or directory" stderr -# The reference implementation has *lots* of non-zero exit values, but we stick to 0 and 1. -after: [ $(cat status) -gt 0 ] ---- - -name: unzip -d exists -before: mkdir dir -command: unzip -q -d dir $FILES/example.zip d1/d2/a.txt && cat dir/d1/d2/a.txt -after: [ ! -f d1/d2/a.txt ] -expected-stdout: - a ---- - -name: unzip -p -command: unzip -p $FILES/example.zip d1/d2/a.txt -after: [ ! -f d1/d2/a.txt ] -expected-stdout: - a ---- - -name: unzip -x FILE... -# Note: the RI ignores -x DIR for some reason, but it's not obvious we should. -command: unzip -q $FILES/example.zip -x d1/d2/a.txt d1/d2/b.txt d1/d2/empty.txt d1/d2/x.txt && cat d1/d2/c.txt -after: [ ! -f d1/d2/a.txt ] -after: [ ! -f d1/d2/b.txt ] -after: [ ! -f d1/d2/empty.txt ] -after: [ ! -f d1/d2/x.txt ] -after: [ -d d1/d2/dir ] -expected-stdout: - ccc ---- - -name: unzip FILE -x FILE... -command: unzip -q $FILES/example.zip d1/d2/a.txt d1/d2/b.txt -x d1/d2/a.txt && cat d1/d2/b.txt -after: [ ! -f d1/d2/a.txt ] -after: [ -f d1/d2/b.txt ] -after: [ ! -f d1/d2/c.txt ] -after: [ ! -f d1/d2/empty.txt ] -after: [ ! -f d1/d2/x.txt ] -after: [ ! -d d1/d2/dir ] -expected-stdout: - bb ---- diff --git a/libziparchive/cli-tests/zipinfo.test b/libziparchive/cli-tests/zipinfo.test deleted file mode 100755 index d5bce1c01..000000000 --- a/libziparchive/cli-tests/zipinfo.test +++ /dev/null @@ -1,53 +0,0 @@ -# zipinfo tests. - -# Note: since "master key", Android uses libziparchive for all zip file -# handling, and that scans the whole central directory immediately. Not only -# lookups by name but also iteration is implemented using the resulting hash -# table, meaning that any test that makes assumptions about iteration order -# will fail on Android. - -name: zipinfo -1 -command: zipinfo -1 $FILES/example.zip | sort -expected-stdout: - d1/ - d1/d2/a.txt - d1/d2/b.txt - d1/d2/c.txt - d1/d2/dir/ - d1/d2/empty.txt - d1/d2/x.txt ---- - -name: zipinfo header -command: zipinfo $FILES/example.zip | head -2 -expected-stdout: - Archive: $FILES/example.zip - Zip file size: 1082 bytes, number of entries: 7 ---- - -name: zipinfo footer -command: zipinfo $FILES/example.zip | tail -1 -expected-stdout: - 7 files, 1033 bytes uncompressed, 20 bytes compressed: 98.1% ---- - -name: zipinfo directory -# The RI doesn't use ISO dates. -command: zipinfo $FILES/example.zip d1/ | sed s/17-Jun-/2017-06-/ -expected-stdout: - drwxr-x--- 3.0 unx 0 bx stor 2017-06-04 08:40 d1/ ---- - -name: zipinfo stored -# The RI doesn't use ISO dates. -command: zipinfo $FILES/example.zip d1/d2/empty.txt | sed s/17-Jun-/2017-06-/ -expected-stdout: - -rw-r----- 3.0 unx 0 bx stor 2017-06-04 08:43 d1/d2/empty.txt ---- - -name: zipinfo deflated -# The RI doesn't use ISO dates. -command: zipinfo $FILES/example.zip d1/d2/x.txt | sed s/17-Jun-/2017-06-/ -expected-stdout: - -rw-r----- 3.0 unx 1024 tx defN 2017-06-04 08:45 d1/d2/x.txt ---- diff --git a/libziparchive/entry_name_utils-inl.h b/libziparchive/entry_name_utils-inl.h deleted file mode 100644 index 10311b565..000000000 --- a/libziparchive/entry_name_utils-inl.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef LIBZIPARCHIVE_ENTRY_NAME_UTILS_INL_H_ -#define LIBZIPARCHIVE_ENTRY_NAME_UTILS_INL_H_ - -#include -#include - -#include - -// Check if |length| bytes at |entry_name| constitute a valid entry name. -// Entry names must be valid UTF-8 and must not contain '0'. They also must -// fit into the central directory record. -inline bool IsValidEntryName(const uint8_t* entry_name, const size_t length) { - if (length > std::numeric_limits::max()) { - return false; - } - for (size_t i = 0; i < length; ++i) { - const uint8_t byte = entry_name[i]; - if (byte == 0) { - return false; - } else if ((byte & 0x80) == 0) { - // Single byte sequence. - continue; - } else if ((byte & 0xc0) == 0x80 || (byte & 0xfe) == 0xfe) { - // Invalid sequence. - return false; - } else { - // 2-5 byte sequences. - for (uint8_t first = static_cast((byte & 0x7f) << 1); first & 0x80; - first = static_cast((first & 0x7f) << 1)) { - ++i; - - // Missing continuation byte.. - if (i == length) { - return false; - } - - // Invalid continuation byte. - const uint8_t continuation_byte = entry_name[i]; - if ((continuation_byte & 0xc0) != 0x80) { - return false; - } - } - } - } - - return true; -} - -#endif // LIBZIPARCHIVE_ENTRY_NAME_UTILS_INL_H_ diff --git a/libziparchive/entry_name_utils_test.cc b/libziparchive/entry_name_utils_test.cc deleted file mode 100644 index d83d8540c..000000000 --- a/libziparchive/entry_name_utils_test.cc +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "entry_name_utils-inl.h" - -#include - -TEST(entry_name_utils, NullChars) { - // 'A', 'R', '\0', 'S', 'E' - const uint8_t zeroes[] = {0x41, 0x52, 0x00, 0x53, 0x45}; - ASSERT_FALSE(IsValidEntryName(zeroes, sizeof(zeroes))); - - const uint8_t zeroes_continuation_chars[] = {0xc2, 0xa1, 0xc2, 0x00}; - ASSERT_FALSE(IsValidEntryName(zeroes_continuation_chars, sizeof(zeroes_continuation_chars))); -} - -TEST(entry_name_utils, InvalidSequence) { - // 0xfe is an invalid start byte - const uint8_t invalid[] = {0x41, 0xfe}; - ASSERT_FALSE(IsValidEntryName(invalid, sizeof(invalid))); - - // 0x91 is an invalid start byte (it's a valid continuation byte). - const uint8_t invalid2[] = {0x41, 0x91}; - ASSERT_FALSE(IsValidEntryName(invalid2, sizeof(invalid2))); -} - -TEST(entry_name_utils, TruncatedContinuation) { - // Malayalam script with truncated bytes. There should be 2 bytes - // after 0xe0 - const uint8_t truncated[] = {0xe0, 0xb4, 0x85, 0xe0, 0xb4}; - ASSERT_FALSE(IsValidEntryName(truncated, sizeof(truncated))); - - // 0xc2 is the start of a 2 byte sequence that we've subsequently - // dropped. - const uint8_t truncated2[] = {0xc2, 0xc2, 0xa1}; - ASSERT_FALSE(IsValidEntryName(truncated2, sizeof(truncated2))); -} - -TEST(entry_name_utils, BadContinuation) { - // 0x41 is an invalid continuation char, since it's MSBs - // aren't "10..." (are 01). - const uint8_t bad[] = {0xc2, 0xa1, 0xc2, 0x41}; - ASSERT_FALSE(IsValidEntryName(bad, sizeof(bad))); - - // 0x41 is an invalid continuation char, since it's MSBs - // aren't "10..." (are 11). - const uint8_t bad2[] = {0xc2, 0xa1, 0xc2, 0xfe}; - ASSERT_FALSE(IsValidEntryName(bad2, sizeof(bad2))); -} diff --git a/libziparchive/include/ziparchive/zip_archive.h b/libziparchive/include/ziparchive/zip_archive.h deleted file mode 100644 index 005d6977d..000000000 --- a/libziparchive/include/ziparchive/zip_archive.h +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -/* - * Read-only access to Zip archives, with minimal heap allocation. - */ - -#include -#include -#include -#include - -#include -#include -#include - -#include "android-base/off64_t.h" - -/* Zip compression methods we support */ -enum { - kCompressStored = 0, // no compression - kCompressDeflated = 8, // standard deflate -}; - -// This struct holds the common information of a zip entry other than the -// the entry size. The compressed and uncompressed length will be handled -// separately in the derived class. -struct ZipEntryCommon { - // Compression method. One of kCompressStored or kCompressDeflated. - // See also `gpbf` for deflate subtypes. - uint16_t method; - - // Modification time. The zipfile format specifies - // that the first two little endian bytes contain the time - // and the last two little endian bytes contain the date. - // See `GetModificationTime`. Use signed integer to avoid the - // sub-overflow. - // TODO: should be overridden by extra time field, if present. - int32_t mod_time; - - // Returns `mod_time` as a broken-down struct tm. - struct tm GetModificationTime() const; - - // Suggested Unix mode for this entry, from the zip archive if created on - // Unix, or a default otherwise. See also `external_file_attributes`. - mode_t unix_mode; - - // 1 if this entry contains a data descriptor segment, 0 - // otherwise. - uint8_t has_data_descriptor; - - // Crc32 value of this ZipEntry. This information might - // either be stored in the local file header or in a special - // Data descriptor footer at the end of the file entry. - uint32_t crc32; - - // If the value of uncompressed length and compressed length are stored in - // the zip64 extended info of the extra field. - bool zip64_format_size{false}; - - // The offset to the start of data for this ZipEntry. - off64_t offset; - - // The version of zip and the host file system this came from (for zipinfo). - uint16_t version_made_by; - - // The raw attributes, whose interpretation depends on the host - // file system in `version_made_by` (for zipinfo). See also `unix_mode`. - uint32_t external_file_attributes; - - // Specifics about the deflation (for zipinfo). - uint16_t gpbf; - // Whether this entry is believed to be text or binary (for zipinfo). - bool is_text; -}; - -struct ZipEntry64; -// Many users of the library assume the entry size is capped at UNIT32_MAX. So we keep -// the interface for the old ZipEntry here; and we could switch them over to the new -// ZipEntry64 later. -struct ZipEntry : public ZipEntryCommon { - // Compressed length of this ZipEntry. The maximum value is UNIT32_MAX. - // Might be present either in the local file header or in the data - // descriptor footer. - uint32_t compressed_length{0}; - - // Uncompressed length of this ZipEntry. The maximum value is UNIT32_MAX. - // Might be present either in the local file header or in the data - // descriptor footer. - uint32_t uncompressed_length{0}; - - // Copies the contents of a ZipEntry64 object to a 32 bits ZipEntry. Returns 0 if the - // size of the entry fits into uint32_t, returns a negative error code - // (kUnsupportedEntrySize) otherwise. - static int32_t CopyFromZipEntry64(ZipEntry* dst, const ZipEntry64* src); - - private: - ZipEntry& operator=(const ZipEntryCommon& other) { - ZipEntryCommon::operator=(other); - return *this; - } -}; - -// Represents information about a zip entry in a zip file. -struct ZipEntry64 : public ZipEntryCommon { - // Compressed length of this ZipEntry. The maximum value is UNIT64_MAX. - // Might be present either in the local file header, the zip64 extended field, - // or in the data descriptor footer. - uint64_t compressed_length{0}; - - // Uncompressed length of this ZipEntry. The maximum value is UNIT64_MAX. - // Might be present either in the local file header, the zip64 extended field, - // or in the data descriptor footer. - uint64_t uncompressed_length{0}; - - explicit ZipEntry64() = default; - explicit ZipEntry64(const ZipEntry& zip_entry) : ZipEntryCommon(zip_entry) { - compressed_length = zip_entry.compressed_length; - uncompressed_length = zip_entry.uncompressed_length; - } -}; - -struct ZipArchive; -typedef ZipArchive* ZipArchiveHandle; - -/* - * Open a Zip archive, and sets handle to the value of the opaque - * handle for the file. This handle must be released by calling - * CloseArchive with this handle. - * - * Returns 0 on success, and negative values on failure. - */ -int32_t OpenArchive(const char* fileName, ZipArchiveHandle* handle); - -/* - * Like OpenArchive, but takes a file descriptor open for reading - * at the start of the file. The descriptor must be mappable (this does - * not allow access to a stream). - * - * Sets handle to the value of the opaque handle for this file descriptor. - * This handle must be released by calling CloseArchive with this handle. - * - * If assume_ownership parameter is 'true' calling CloseArchive will close - * the file. - * - * This function maps and scans the central directory and builds a table - * of entries for future lookups. - * - * "debugFileName" will appear in error messages, but is not otherwise used. - * - * Returns 0 on success, and negative values on failure. - */ -int32_t OpenArchiveFd(const int fd, const char* debugFileName, ZipArchiveHandle* handle, - bool assume_ownership = true); - -int32_t OpenArchiveFdRange(const int fd, const char* debugFileName, ZipArchiveHandle* handle, - off64_t length, off64_t offset, bool assume_ownership = true); - -int32_t OpenArchiveFromMemory(const void* address, size_t length, const char* debugFileName, - ZipArchiveHandle* handle); -/* - * Close archive, releasing resources associated with it. This will - * unmap the central directory of the zipfile and free all internal - * data structures associated with the file. It is an error to use - * this handle for any further operations without an intervening - * call to one of the OpenArchive variants. - */ -void CloseArchive(ZipArchiveHandle archive); - -/** See GetArchiveInfo(). */ -struct ZipArchiveInfo { - /** The size in bytes of the archive itself. Used by zipinfo. */ - off64_t archive_size; - /** The number of entries in the archive. */ - uint64_t entry_count; -}; - -/** - * Returns information about the given archive. - */ -ZipArchiveInfo GetArchiveInfo(ZipArchiveHandle archive); - -/* - * Find an entry in the Zip archive, by name. |data| must be non-null. - * - * Returns 0 if an entry is found, and populates |data| with information - * about this entry. Returns negative values otherwise. - * - * It's important to note that |data->crc32|, |data->compLen| and - * |data->uncompLen| might be set to values from the central directory - * if this file entry contains a data descriptor footer. To verify crc32s - * and length, a call to VerifyCrcAndLengths must be made after entry data - * has been processed. - * - * On non-Windows platforms this method does not modify internal state and - * can be called concurrently. - */ -int32_t FindEntry(const ZipArchiveHandle archive, const std::string_view entryName, - ZipEntry64* data); - -/* - * Start iterating over all entries of a zip file. The order of iteration - * is not guaranteed to be the same as the order of elements - * in the central directory but is stable for a given zip file. |cookie| will - * contain the value of an opaque cookie which can be used to make one or more - * calls to Next. All calls to StartIteration must be matched by a call to - * EndIteration to free any allocated memory. - * - * This method also accepts optional prefix and suffix to restrict iteration to - * entry names that start with |optional_prefix| or end with |optional_suffix|. - * - * Returns 0 on success and negative values on failure. - */ -int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr, - const std::string_view optional_prefix = "", - const std::string_view optional_suffix = ""); - -/* - * Start iterating over all entries of a zip file. Use the matcher functor to - * restrict iteration to entry names that make the functor return true. - * - * Returns 0 on success and negative values on failure. - */ -int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr, - std::function matcher); - -/* - * Advance to the next element in the zipfile in iteration order. - * - * Returns 0 on success, -1 if there are no more elements in this - * archive and lower negative values on failure. - */ -int32_t Next(void* cookie, ZipEntry64* data, std::string_view* name); -int32_t Next(void* cookie, ZipEntry64* data, std::string* name); - -/* - * End iteration over all entries of a zip file and frees the memory allocated - * in StartIteration. - */ -void EndIteration(void* cookie); - -/* - * Uncompress and write an entry to an open file identified by |fd|. - * |entry->uncompressed_length| bytes will be written to the file at - * its current offset, and the file will be truncated at the end of - * the uncompressed data (no truncation if |fd| references a block - * device). - * - * Returns 0 on success and negative values on failure. - */ -int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry64* entry, int fd); - -/** - * Uncompress a given zip entry to the memory region at |begin| and of - * size |size|. This size is expected to be the same as the *declared* - * uncompressed length of the zip entry. It is an error if the *actual* - * number of uncompressed bytes differs from this number. - * - * Returns 0 on success and negative values on failure. - */ -int32_t ExtractToMemory(ZipArchiveHandle archive, const ZipEntry64* entry, uint8_t* begin, - size_t size); - -int GetFileDescriptor(const ZipArchiveHandle archive); - -/** - * Returns the offset of the zip archive in the backing file descriptor, or 0 if the zip archive is - * not backed by a file descriptor. - */ -off64_t GetFileDescriptorOffset(const ZipArchiveHandle archive); - -const char* ErrorCodeString(int32_t error_code); - -// Many users of libziparchive assume the entry size to be 32 bits long. So we keep these -// interfaces that use 32 bit ZipEntry to make old code work. TODO(xunchang) Remove the 32 bit -// wrapper functions once we switch all users to recognize ZipEntry64. -int32_t FindEntry(const ZipArchiveHandle archive, const std::string_view entryName, ZipEntry* data); -int32_t Next(void* cookie, ZipEntry* data, std::string* name); -int32_t Next(void* cookie, ZipEntry* data, std::string_view* name); -int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry* entry, int fd); -int32_t ExtractToMemory(ZipArchiveHandle archive, const ZipEntry* entry, uint8_t* begin, - size_t size); - -#if !defined(_WIN32) -typedef bool (*ProcessZipEntryFunction)(const uint8_t* buf, size_t buf_size, void* cookie); - -/* - * Stream the uncompressed data through the supplied function, - * passing cookie to it each time it gets called. - */ -int32_t ProcessZipEntryContents(ZipArchiveHandle archive, const ZipEntry* entry, - ProcessZipEntryFunction func, void* cookie); -int32_t ProcessZipEntryContents(ZipArchiveHandle archive, const ZipEntry64* entry, - ProcessZipEntryFunction func, void* cookie); -#endif - -namespace zip_archive { - -class Writer { - public: - virtual bool Append(uint8_t* buf, size_t buf_size) = 0; - virtual ~Writer(); - - protected: - Writer() = default; - - private: - Writer(const Writer&) = delete; - void operator=(const Writer&) = delete; -}; - -class Reader { - public: - virtual bool ReadAtOffset(uint8_t* buf, size_t len, off64_t offset) const = 0; - virtual ~Reader(); - - protected: - Reader() = default; - - private: - Reader(const Reader&) = delete; - void operator=(const Reader&) = delete; -}; - -/* - * Inflates the first |compressed_length| bytes of |reader| to a given |writer|. - * |crc_out| is set to the CRC32 checksum of the uncompressed data. - * - * Returns 0 on success and negative values on failure, for example if |reader| - * cannot supply the right amount of data, or if the number of bytes written to - * data does not match |uncompressed_length|. - * - * If |crc_out| is not nullptr, it is set to the crc32 checksum of the - * uncompressed data. - */ -int32_t Inflate(const Reader& reader, const uint64_t compressed_length, - const uint64_t uncompressed_length, Writer* writer, uint64_t* crc_out); -} // namespace zip_archive diff --git a/libziparchive/include/ziparchive/zip_archive_stream_entry.h b/libziparchive/include/ziparchive/zip_archive_stream_entry.h deleted file mode 100644 index 8c6ca795a..000000000 --- a/libziparchive/include/ziparchive/zip_archive_stream_entry.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Read-only stream access to Zip archives entries. -#pragma once - -#include - -#include - -#include "android-base/off64_t.h" - -class ZipArchiveStreamEntry { - public: - virtual ~ZipArchiveStreamEntry() {} - - virtual const std::vector* Read() = 0; - - virtual bool Verify() = 0; - - static ZipArchiveStreamEntry* Create(ZipArchiveHandle handle, const ZipEntry& entry); - static ZipArchiveStreamEntry* CreateRaw(ZipArchiveHandle handle, const ZipEntry& entry); - - protected: - ZipArchiveStreamEntry(ZipArchiveHandle handle) : handle_(handle) {} - - virtual bool Init(const ZipEntry& entry); - - ZipArchiveHandle handle_; - - off64_t offset_ = 0; - uint32_t crc32_ = 0u; -}; diff --git a/libziparchive/include/ziparchive/zip_writer.h b/libziparchive/include/ziparchive/zip_writer.h deleted file mode 100644 index d68683dfe..000000000 --- a/libziparchive/include/ziparchive/zip_writer.h +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include -#include -#include -#include -#include - -#include "android-base/macros.h" -#include "android-base/off64_t.h" - -struct z_stream_s; -typedef struct z_stream_s z_stream; - -/** - * Writes a Zip file via a stateful interface. - * - * Example: - * - * FILE* file = fopen("path/to/zip.zip", "wb"); - * - * ZipWriter writer(file); - * - * writer.StartEntry("test.txt", ZipWriter::kCompress | ZipWriter::kAlign); - * writer.WriteBytes(buffer, bufferLen); - * writer.WriteBytes(buffer2, bufferLen2); - * writer.FinishEntry(); - * - * writer.StartEntry("empty.txt", 0); - * writer.FinishEntry(); - * - * writer.Finish(); - * - * fclose(file); - */ -class ZipWriter { - public: - enum { - /** - * Flag to compress the zip entry using deflate. - */ - kCompress = 0x01, - - /** - * Flag to align the zip entry data on a 32bit boundary. Useful for - * mmapping the data at runtime. - */ - kAlign32 = 0x02, - }; - - /** - * A struct representing a zip file entry. - */ - struct FileEntry { - std::string path; - uint16_t compression_method; - uint32_t crc32; - uint32_t compressed_size; - uint32_t uncompressed_size; - uint16_t last_mod_time; - uint16_t last_mod_date; - uint16_t padding_length; - off64_t local_file_header_offset; - }; - - static const char* ErrorCodeString(int32_t error_code); - - /** - * Create a ZipWriter that will write into a FILE stream. The file should be opened with - * open mode of "wb" or "w+b". ZipWriter does not take ownership of the file stream. The - * caller is responsible for closing the file. - */ - explicit ZipWriter(FILE* f); - - // Move constructor. - ZipWriter(ZipWriter&& zipWriter) noexcept; - - // Move assignment. - ZipWriter& operator=(ZipWriter&& zipWriter) noexcept; - - /** - * Starts a new zip entry with the given path and flags. - * Flags can be a bitwise OR of ZipWriter::kCompress and ZipWriter::kAlign. - * Subsequent calls to WriteBytes(const void*, size_t) will add data to this entry. - * Returns 0 on success, and an error value < 0 on failure. - */ - int32_t StartEntry(std::string_view path, size_t flags); - - /** - * Starts a new zip entry with the given path and flags, where the - * entry will be aligned to the given alignment. - * Flags can only be ZipWriter::kCompress. Using the flag ZipWriter::kAlign32 - * will result in an error. - * Subsequent calls to WriteBytes(const void*, size_t) will add data to this entry. - * Returns 0 on success, and an error value < 0 on failure. - */ - int32_t StartAlignedEntry(std::string_view path, size_t flags, uint32_t alignment); - - /** - * Same as StartEntry(const char*, size_t), but sets a last modified time for the entry. - */ - int32_t StartEntryWithTime(std::string_view path, size_t flags, time_t time); - - /** - * Same as StartAlignedEntry(const char*, size_t), but sets a last modified time for the entry. - */ - int32_t StartAlignedEntryWithTime(std::string_view path, size_t flags, time_t time, uint32_t alignment); - - /** - * Writes bytes to the zip file for the previously started zip entry. - * Returns 0 on success, and an error value < 0 on failure. - */ - int32_t WriteBytes(const void* data, size_t len); - - /** - * Finish a zip entry started with StartEntry(const char*, size_t) or - * StartEntryWithTime(const char*, size_t, time_t). This must be called before - * any new zip entries are started, or before Finish() is called. - * Returns 0 on success, and an error value < 0 on failure. - */ - int32_t FinishEntry(); - - /** - * Discards the last-written entry. Can only be called after an entry has been written using - * FinishEntry(). - * Returns 0 on success, and an error value < 0 on failure. - */ - int32_t DiscardLastEntry(); - - /** - * Sets `out_entry` to the last entry written after a call to FinishEntry(). - * Returns 0 on success, and an error value < 0 if no entries have been written. - */ - int32_t GetLastEntry(FileEntry* out_entry); - - /** - * Writes the Central Directory Headers and flushes the zip file stream. - * Returns 0 on success, and an error value < 0 on failure. - */ - int32_t Finish(); - - private: - DISALLOW_COPY_AND_ASSIGN(ZipWriter); - - int32_t HandleError(int32_t error_code); - int32_t PrepareDeflate(); - int32_t StoreBytes(FileEntry* file, const void* data, uint32_t len); - int32_t CompressBytes(FileEntry* file, const void* data, uint32_t len); - int32_t FlushCompressedBytes(FileEntry* file); - bool ShouldUseDataDescriptor() const; - - enum class State { - kWritingZip, - kWritingEntry, - kDone, - kError, - }; - - FILE* file_; - bool seekable_; - off64_t current_offset_; - State state_; - std::vector files_; - FileEntry current_file_entry_; - - std::unique_ptr z_stream_; - std::vector buffer_; - - FRIEND_TEST(zipwriter, WriteToUnseekableFile); -}; diff --git a/libziparchive/libziparchive_fuzzer.cpp b/libziparchive/libziparchive_fuzzer.cpp deleted file mode 100644 index 75e7939da..000000000 --- a/libziparchive/libziparchive_fuzzer.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include - -#include - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - ZipArchiveHandle handle = nullptr; - OpenArchiveFromMemory(data, size, "fuzz", &handle); - CloseArchive(handle); - return 0; -} diff --git a/libziparchive/run-ziptool-tests-on-android.sh b/libziparchive/run-ziptool-tests-on-android.sh deleted file mode 100755 index 3c23d4373..000000000 --- a/libziparchive/run-ziptool-tests-on-android.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# Copy the tests across. -adb shell rm -rf /data/local/tmp/ziptool-tests/ -adb shell mkdir /data/local/tmp/ziptool-tests/ -adb push cli-tests/ /data/local/tmp/ziptool-tests/ -#adb push cli-test /data/local/tmp/ziptool-tests/ - -if tty -s; then - dash_t="-t" -else - dash_t="" -fi - -exec adb shell $dash_t cli-test /data/local/tmp/ziptool-tests/cli-tests/*.test diff --git a/libziparchive/test_ziparchive_large.py b/libziparchive/test_ziparchive_large.py deleted file mode 100644 index 46d02aa3a..000000000 --- a/libziparchive/test_ziparchive_large.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2020 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -"""Unittests for parsing files in zip64 format""" - -import os -import subprocess -import tempfile -import unittest -import zipfile -import time - -class Zip64Test(unittest.TestCase): - @staticmethod - def _WriteFile(path, size_in_kib): - contents = os.path.basename(path)[0] * 1024 - with open(path, 'w') as f: - for it in range(0, size_in_kib): - f.write(contents) - - @staticmethod - def _AddEntriesToZip(output_zip, entries_dict=None): - for name, size in entries_dict.items(): - file_path = tempfile.NamedTemporaryFile() - Zip64Test._WriteFile(file_path.name, size) - output_zip.write(file_path.name, arcname = name) - - def _getEntryNames(self, zip_name): - cmd = ['ziptool', 'zipinfo', '-1', zip_name] - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - output, _ = proc.communicate() - self.assertEquals(0, proc.returncode) - self.assertNotEqual(None, output) - return output.split() - - def _ExtractEntries(self, zip_name): - temp_dir = tempfile.mkdtemp() - cmd = ['ziptool', 'unzip', '-d', temp_dir, zip_name] - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - proc.communicate() - self.assertEquals(0, proc.returncode) - - def test_entriesSmallerThan2G(self): - zip_path = tempfile.NamedTemporaryFile(suffix='.zip') - # Add a few entries with each of them smaller than 2GiB. But the entire zip file is larger - # than 4GiB in size. - with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip: - entry_dict = {'a.txt': 1025 * 1024, 'b.txt': 1025 * 1024, 'c.txt': 1025 * 1024, - 'd.txt': 1025 * 1024, 'e.txt': 1024} - self._AddEntriesToZip(output_zip, entry_dict) - - read_names = self._getEntryNames(zip_path.name) - self.assertEquals(sorted(entry_dict.keys()), sorted(read_names)) - self._ExtractEntries(zip_path.name) - - - def test_largeNumberOfEntries(self): - zip_path = tempfile.NamedTemporaryFile(suffix='.zip') - entry_dict = {} - # Add 100k entries (more than 65535|UINT16_MAX). - for num in range(0, 100 * 1024): - entry_dict[str(num)] = 50 - - with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip: - self._AddEntriesToZip(output_zip, entry_dict) - - read_names = self._getEntryNames(zip_path.name) - self.assertEquals(sorted(entry_dict.keys()), sorted(read_names)) - self._ExtractEntries(zip_path.name) - - - def test_largeCompressedEntriesSmallerThan4G(self): - zip_path = tempfile.NamedTemporaryFile(suffix='.zip') - with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED, - allowZip64=True) as output_zip: - # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed - # sizes in the extra field. Test if our ziptool should be able to parse it. - entry_dict = {'e.txt': 4095 * 1024, 'f.txt': 4095 * 1024} - self._AddEntriesToZip(output_zip, entry_dict) - - read_names = self._getEntryNames(zip_path.name) - self.assertEquals(sorted(entry_dict.keys()), sorted(read_names)) - self._ExtractEntries(zip_path.name) - - - def test_forceDataDescriptor(self): - file_path = tempfile.NamedTemporaryFile(suffix='.txt') - self._WriteFile(file_path.name, 5000 * 1024) - - zip_path = tempfile.NamedTemporaryFile(suffix='.zip') - with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip: - pass - # The fd option force writes a data descriptor - cmd = ['zip', '-fd', zip_path.name, file_path.name] - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - proc.communicate() - read_names = self._getEntryNames(zip_path.name) - self.assertEquals([file_path.name[1:]], read_names) - self._ExtractEntries(zip_path.name) - - - def test_largeUncompressedEntriesLargerThan4G(self): - zip_path = tempfile.NamedTemporaryFile(suffix='.zip') - with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_STORED, - allowZip64=True) as output_zip: - # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed - # sizes in the extra field. Test if our ziptool should be able to parse it. - entry_dict = {'g.txt': 5000 * 1024, 'h.txt': 6000 * 1024} - self._AddEntriesToZip(output_zip, entry_dict) - - read_names = self._getEntryNames(zip_path.name) - self.assertEquals(sorted(entry_dict.keys()), sorted(read_names)) - self._ExtractEntries(zip_path.name) - - - def test_largeCompressedEntriesLargerThan4G(self): - zip_path = tempfile.NamedTemporaryFile(suffix='.zip') - with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED, - allowZip64=True) as output_zip: - # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed - # sizes in the extra field. Test if our ziptool should be able to parse it. - entry_dict = {'i.txt': 4096 * 1024, 'j.txt': 7000 * 1024} - self._AddEntriesToZip(output_zip, entry_dict) - - read_names = self._getEntryNames(zip_path.name) - self.assertEquals(sorted(entry_dict.keys()), sorted(read_names)) - self._ExtractEntries(zip_path.name) - - -if __name__ == '__main__': - testsuite = unittest.TestLoader().discover( - os.path.dirname(os.path.realpath(__file__))) - unittest.TextTestRunner(verbosity=2).run(testsuite) diff --git a/libziparchive/testdata/bad_crc.zip b/libziparchive/testdata/bad_crc.zip deleted file mode 100644 index e12ba07bd..000000000 Binary files a/libziparchive/testdata/bad_crc.zip and /dev/null differ diff --git a/libziparchive/testdata/bad_filename.zip b/libziparchive/testdata/bad_filename.zip deleted file mode 100644 index 294eaf562..000000000 Binary files a/libziparchive/testdata/bad_filename.zip and /dev/null differ diff --git a/libziparchive/testdata/crash.apk b/libziparchive/testdata/crash.apk deleted file mode 100644 index d6dd52dd7..000000000 Binary files a/libziparchive/testdata/crash.apk and /dev/null differ diff --git a/libziparchive/testdata/declaredlength.zip b/libziparchive/testdata/declaredlength.zip deleted file mode 100644 index 773380c45..000000000 Binary files a/libziparchive/testdata/declaredlength.zip and /dev/null differ diff --git a/libziparchive/testdata/dummy-update.zip b/libziparchive/testdata/dummy-update.zip deleted file mode 100644 index 6976bf155..000000000 Binary files a/libziparchive/testdata/dummy-update.zip and /dev/null differ diff --git a/libziparchive/testdata/empty.zip b/libziparchive/testdata/empty.zip deleted file mode 100644 index 15cb0ecb3..000000000 Binary files a/libziparchive/testdata/empty.zip and /dev/null differ diff --git a/libziparchive/testdata/large.zip b/libziparchive/testdata/large.zip deleted file mode 100644 index 49659c832..000000000 Binary files a/libziparchive/testdata/large.zip and /dev/null differ diff --git a/libziparchive/testdata/valid.zip b/libziparchive/testdata/valid.zip deleted file mode 100644 index 9e7cb7800..000000000 Binary files a/libziparchive/testdata/valid.zip and /dev/null differ diff --git a/libziparchive/testdata/zero-size-cd.zip b/libziparchive/testdata/zero-size-cd.zip deleted file mode 100644 index b6c8cbeac..000000000 Binary files a/libziparchive/testdata/zero-size-cd.zip and /dev/null differ diff --git a/libziparchive/testdata/zip64.zip b/libziparchive/testdata/zip64.zip deleted file mode 100644 index 3f25a4c25..000000000 Binary files a/libziparchive/testdata/zip64.zip and /dev/null differ diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc deleted file mode 100644 index 014f88131..000000000 --- a/libziparchive/zip_archive.cc +++ /dev/null @@ -1,1586 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Read-only access to Zip archives, with minimal heap allocation. - */ - -#define LOG_TAG "ziparchive" - -#include "ziparchive/zip_archive.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#if defined(__APPLE__) -#define lseek64 lseek -#endif - -#if defined(__BIONIC__) -#include -#endif - -#include -#include -#include // TEMP_FAILURE_RETRY may or may not be in unistd -#include -#include -#include -#include -#include -#include "zlib.h" - -#include "entry_name_utils-inl.h" -#include "zip_archive_common.h" -#include "zip_archive_private.h" - -// Used to turn on crc checks - verify that the content CRC matches the values -// specified in the local file header and the central directory. -static constexpr bool kCrcChecksEnabled = false; - -// The maximum number of bytes to scan backwards for the EOCD start. -static const uint32_t kMaxEOCDSearch = kMaxCommentLen + sizeof(EocdRecord); - -// Set a reasonable cap (256 GiB) for the zip file size. So the data is always valid when -// we parse the fields in cd or local headers as 64 bits signed integers. -static constexpr uint64_t kMaxFileLength = 256 * static_cast(1u << 30u); - -/* - * A Read-only Zip archive. - * - * We want "open" and "find entry by name" to be fast operations, and - * we want to use as little memory as possible. We memory-map the zip - * central directory, and load a hash table with pointers to the filenames - * (which aren't null-terminated). The other fields are at a fixed offset - * from the filename, so we don't need to extract those (but we do need - * to byte-read and endian-swap them every time we want them). - * - * It's possible that somebody has handed us a massive (~1GB) zip archive, - * so we can't expect to mmap the entire file. - * - * To speed comparisons when doing a lookup by name, we could make the mapping - * "private" (copy-on-write) and null-terminate the filenames after verifying - * the record structure. However, this requires a private mapping of - * every page that the Central Directory touches. Easier to tuck a copy - * of the string length into the hash table entry. - */ - -#if defined(__BIONIC__) -uint64_t GetOwnerTag(const ZipArchive* archive) { - return android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_ZIPARCHIVE, - reinterpret_cast(archive)); -} -#endif - -ZipArchive::ZipArchive(MappedZipFile&& map, bool assume_ownership) - : mapped_zip(map), - close_file(assume_ownership), - directory_offset(0), - central_directory(), - directory_map(), - num_entries(0) { -#if defined(__BIONIC__) - if (assume_ownership) { - CHECK(mapped_zip.HasFd()); - android_fdsan_exchange_owner_tag(mapped_zip.GetFileDescriptor(), 0, GetOwnerTag(this)); - } -#endif -} - -ZipArchive::ZipArchive(const void* address, size_t length) - : mapped_zip(address, length), - close_file(false), - directory_offset(0), - central_directory(), - directory_map(), - num_entries(0) {} - -ZipArchive::~ZipArchive() { - if (close_file && mapped_zip.GetFileDescriptor() >= 0) { -#if defined(__BIONIC__) - android_fdsan_close_with_tag(mapped_zip.GetFileDescriptor(), GetOwnerTag(this)); -#else - close(mapped_zip.GetFileDescriptor()); -#endif - } -} - -struct CentralDirectoryInfo { - uint64_t num_records; - // The size of the central directory (in bytes). - uint64_t cd_size; - // The offset of the start of the central directory, relative - // to the start of the file. - uint64_t cd_start_offset; -}; - -// Reads |T| at |readPtr| and increments |readPtr|. Returns std::nullopt if the boundary check -// fails. -template -static std::optional TryConsumeUnaligned(uint8_t** readPtr, const uint8_t* bufStart, - size_t bufSize) { - if (bufSize < sizeof(T) || *readPtr - bufStart > bufSize - sizeof(T)) { - ALOGW("Zip: %zu byte read exceeds the boundary of allocated buf, offset %zu, bufSize %zu", - sizeof(T), *readPtr - bufStart, bufSize); - return std::nullopt; - } - return ConsumeUnaligned(readPtr); -} - -static ZipError FindCentralDirectoryInfoForZip64(const char* debugFileName, ZipArchive* archive, - off64_t eocdOffset, CentralDirectoryInfo* cdInfo) { - if (eocdOffset <= sizeof(Zip64EocdLocator)) { - ALOGW("Zip: %s: Not enough space for zip64 eocd locator", debugFileName); - return kInvalidFile; - } - // We expect to find the zip64 eocd locator immediately before the zip eocd. - const int64_t locatorOffset = eocdOffset - sizeof(Zip64EocdLocator); - Zip64EocdLocator zip64EocdLocator{}; - if (!archive->mapped_zip.ReadAtOffset(reinterpret_cast((&zip64EocdLocator)), - sizeof(Zip64EocdLocator), locatorOffset)) { - ALOGW("Zip: %s: Read %zu from offset %" PRId64 " failed %s", debugFileName, - sizeof(Zip64EocdLocator), locatorOffset, debugFileName); - return kIoError; - } - - if (zip64EocdLocator.locator_signature != Zip64EocdLocator::kSignature) { - ALOGW("Zip: %s: Zip64 eocd locator signature not found at offset %" PRId64, debugFileName, - locatorOffset); - return kInvalidFile; - } - - const int64_t zip64EocdOffset = zip64EocdLocator.zip64_eocd_offset; - if (locatorOffset <= sizeof(Zip64EocdRecord) || - zip64EocdOffset > locatorOffset - sizeof(Zip64EocdRecord)) { - ALOGW("Zip: %s: Bad zip64 eocd offset %" PRId64 ", eocd locator offset %" PRId64, debugFileName, - zip64EocdOffset, locatorOffset); - return kInvalidOffset; - } - - Zip64EocdRecord zip64EocdRecord{}; - if (!archive->mapped_zip.ReadAtOffset(reinterpret_cast(&zip64EocdRecord), - sizeof(Zip64EocdRecord), zip64EocdOffset)) { - ALOGW("Zip: %s: read %zu from offset %" PRId64 " failed %s", debugFileName, - sizeof(Zip64EocdLocator), zip64EocdOffset, debugFileName); - return kIoError; - } - - if (zip64EocdRecord.record_signature != Zip64EocdRecord::kSignature) { - ALOGW("Zip: %s: Zip64 eocd record signature not found at offset %" PRId64, debugFileName, - zip64EocdOffset); - return kInvalidFile; - } - - if (zip64EocdOffset <= zip64EocdRecord.cd_size || - zip64EocdRecord.cd_start_offset > zip64EocdOffset - zip64EocdRecord.cd_size) { - ALOGW("Zip: %s: Bad offset for zip64 central directory. cd offset %" PRIu64 ", cd size %" PRIu64 - ", zip64 eocd offset %" PRIu64, - debugFileName, zip64EocdRecord.cd_start_offset, zip64EocdRecord.cd_size, zip64EocdOffset); - return kInvalidOffset; - } - - *cdInfo = {.num_records = zip64EocdRecord.num_records, - .cd_size = zip64EocdRecord.cd_size, - .cd_start_offset = zip64EocdRecord.cd_start_offset}; - - return kSuccess; -} - -static ZipError FindCentralDirectoryInfo(const char* debug_file_name, ZipArchive* archive, - off64_t file_length, uint32_t read_amount, - CentralDirectoryInfo* cdInfo) { - std::vector scan_buffer(read_amount); - const off64_t search_start = file_length - read_amount; - - if (!archive->mapped_zip.ReadAtOffset(scan_buffer.data(), read_amount, search_start)) { - ALOGE("Zip: read %" PRId64 " from offset %" PRId64 " failed", static_cast(read_amount), - static_cast(search_start)); - return kIoError; - } - - /* - * Scan backward for the EOCD magic. In an archive without a trailing - * comment, we'll find it on the first try. (We may want to consider - * doing an initial minimal read; if we don't find it, retry with a - * second read as above.) - */ - CHECK_LE(read_amount, std::numeric_limits::max()); - int32_t i = read_amount - sizeof(EocdRecord); - for (; i >= 0; i--) { - if (scan_buffer[i] == 0x50) { - uint32_t* sig_addr = reinterpret_cast(&scan_buffer[i]); - if (android::base::get_unaligned(sig_addr) == EocdRecord::kSignature) { - ALOGV("+++ Found EOCD at buf+%d", i); - break; - } - } - } - if (i < 0) { - ALOGD("Zip: EOCD not found, %s is not zip", debug_file_name); - return kInvalidFile; - } - - const off64_t eocd_offset = search_start + i; - auto eocd = reinterpret_cast(scan_buffer.data() + i); - /* - * Verify that there's no trailing space at the end of the central directory - * and its comment. - */ - const off64_t calculated_length = eocd_offset + sizeof(EocdRecord) + eocd->comment_length; - if (calculated_length != file_length) { - ALOGW("Zip: %" PRId64 " extraneous bytes at the end of the central directory", - static_cast(file_length - calculated_length)); - return kInvalidFile; - } - - // One of the field is 0xFFFFFFFF, look for the zip64 EOCD instead. - if (eocd->cd_size == UINT32_MAX || eocd->cd_start_offset == UINT32_MAX) { - ALOGV("Looking for the zip64 EOCD, cd_size: %" PRIu32 "cd_start_offset: %" PRId32, - eocd->cd_size, eocd->cd_start_offset); - return FindCentralDirectoryInfoForZip64(debug_file_name, archive, eocd_offset, cdInfo); - } - - /* - * Grab the CD offset and size, and the number of entries in the - * archive and verify that they look reasonable. - */ - if (static_cast(eocd->cd_start_offset) + eocd->cd_size > eocd_offset) { - ALOGW("Zip: bad offsets (dir %" PRIu32 ", size %" PRIu32 ", eocd %" PRId64 ")", - eocd->cd_start_offset, eocd->cd_size, static_cast(eocd_offset)); - return kInvalidOffset; - } - - *cdInfo = {.num_records = eocd->num_records, - .cd_size = eocd->cd_size, - .cd_start_offset = eocd->cd_start_offset}; - return kSuccess; -} - -/* - * Find the zip Central Directory and memory-map it. - * - * On success, returns kSuccess after populating fields from the EOCD area: - * directory_offset - * directory_ptr - * num_entries - */ -static ZipError MapCentralDirectory(const char* debug_file_name, ZipArchive* archive) { - // Test file length. We use lseek64 to make sure the file is small enough to be a zip file. - off64_t file_length = archive->mapped_zip.GetFileLength(); - if (file_length == -1) { - return kInvalidFile; - } - - if (file_length > kMaxFileLength) { - ALOGV("Zip: zip file too long %" PRId64, static_cast(file_length)); - return kInvalidFile; - } - - if (file_length < static_cast(sizeof(EocdRecord))) { - ALOGV("Zip: length %" PRId64 " is too small to be zip", static_cast(file_length)); - return kInvalidFile; - } - - /* - * Perform the traditional EOCD snipe hunt. - * - * We're searching for the End of Central Directory magic number, - * which appears at the start of the EOCD block. It's followed by - * 18 bytes of EOCD stuff and up to 64KB of archive comment. We - * need to read the last part of the file into a buffer, dig through - * it to find the magic number, parse some values out, and use those - * to determine the extent of the CD. - * - * We start by pulling in the last part of the file. - */ - uint32_t read_amount = kMaxEOCDSearch; - if (file_length < read_amount) { - read_amount = static_cast(file_length); - } - - CentralDirectoryInfo cdInfo = {}; - if (auto result = - FindCentralDirectoryInfo(debug_file_name, archive, file_length, read_amount, &cdInfo); - result != kSuccess) { - return result; - } - - if (cdInfo.num_records == 0) { -#if defined(__ANDROID__) - ALOGW("Zip: empty archive?"); -#endif - return kEmptyArchive; - } - - if (cdInfo.cd_size >= SIZE_MAX) { - ALOGW("Zip: The size of central directory doesn't fit in range of size_t: %" PRIu64, - cdInfo.cd_size); - return kInvalidFile; - } - - ALOGV("+++ num_entries=%" PRIu64 " dir_size=%" PRIu64 " dir_offset=%" PRIu64, cdInfo.num_records, - cdInfo.cd_size, cdInfo.cd_start_offset); - - // It all looks good. Create a mapping for the CD, and set the fields in archive. - if (!archive->InitializeCentralDirectory(static_cast(cdInfo.cd_start_offset), - static_cast(cdInfo.cd_size))) { - return kMmapFailed; - } - - archive->num_entries = cdInfo.num_records; - archive->directory_offset = cdInfo.cd_start_offset; - - return kSuccess; -} - -static ZipError ParseZip64ExtendedInfoInExtraField( - const uint8_t* extraFieldStart, uint16_t extraFieldLength, uint32_t zip32UncompressedSize, - uint32_t zip32CompressedSize, std::optional zip32LocalFileHeaderOffset, - Zip64ExtendedInfo* zip64Info) { - if (extraFieldLength <= 4) { - ALOGW("Zip: Extra field isn't large enough to hold zip64 info, size %" PRIu16, - extraFieldLength); - return kInvalidFile; - } - - // Each header MUST consist of: - // Header ID - 2 bytes - // Data Size - 2 bytes - uint16_t offset = 0; - while (offset < extraFieldLength - 4) { - auto readPtr = const_cast(extraFieldStart + offset); - auto headerId = ConsumeUnaligned(&readPtr); - auto dataSize = ConsumeUnaligned(&readPtr); - - offset += 4; - if (dataSize > extraFieldLength - offset) { - ALOGW("Zip: Data size exceeds the boundary of extra field, data size %" PRIu16, dataSize); - return kInvalidOffset; - } - - // Skip the other types of extensible data fields. Details in - // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT section 4.5 - if (headerId != Zip64ExtendedInfo::kHeaderId) { - offset += dataSize; - continue; - } - - std::optional uncompressedFileSize; - std::optional compressedFileSize; - std::optional localHeaderOffset; - if (zip32UncompressedSize == UINT32_MAX) { - uncompressedFileSize = - TryConsumeUnaligned(&readPtr, extraFieldStart, extraFieldLength); - if (!uncompressedFileSize.has_value()) return kInvalidOffset; - } - if (zip32CompressedSize == UINT32_MAX) { - compressedFileSize = - TryConsumeUnaligned(&readPtr, extraFieldStart, extraFieldLength); - if (!compressedFileSize.has_value()) return kInvalidOffset; - } - if (zip32LocalFileHeaderOffset == UINT32_MAX) { - localHeaderOffset = - TryConsumeUnaligned(&readPtr, extraFieldStart, extraFieldLength); - if (!localHeaderOffset.has_value()) return kInvalidOffset; - } - - // calculate how many bytes we read after the data size field. - size_t bytesRead = readPtr - (extraFieldStart + offset); - if (bytesRead == 0) { - ALOGW("Zip: Data size should not be 0 in zip64 extended field"); - return kInvalidFile; - } - - if (dataSize != bytesRead) { - auto localOffsetString = zip32LocalFileHeaderOffset.has_value() - ? std::to_string(zip32LocalFileHeaderOffset.value()) - : "missing"; - ALOGW("Zip: Invalid data size in zip64 extended field, expect %zu , get %" PRIu16 - ", uncompressed size %" PRIu32 ", compressed size %" PRIu32 ", local header offset %s", - bytesRead, dataSize, zip32UncompressedSize, zip32CompressedSize, - localOffsetString.c_str()); - return kInvalidFile; - } - - zip64Info->uncompressed_file_size = uncompressedFileSize; - zip64Info->compressed_file_size = compressedFileSize; - zip64Info->local_header_offset = localHeaderOffset; - return kSuccess; - } - - ALOGW("Zip: zip64 extended info isn't found in the extra field."); - return kInvalidFile; -} - -/* - * Parses the Zip archive's Central Directory. Allocates and populates the - * hash table. - * - * Returns 0 on success. - */ -static ZipError ParseZipArchive(ZipArchive* archive) { - const uint8_t* const cd_ptr = archive->central_directory.GetBasePtr(); - const size_t cd_length = archive->central_directory.GetMapLength(); - const uint64_t num_entries = archive->num_entries; - - if (num_entries <= UINT16_MAX) { - archive->cd_entry_map = CdEntryMapZip32::Create(static_cast(num_entries)); - } else { - archive->cd_entry_map = CdEntryMapZip64::Create(); - } - if (archive->cd_entry_map == nullptr) { - return kAllocationFailed; - } - - /* - * Walk through the central directory, adding entries to the hash - * table and verifying values. - */ - const uint8_t* const cd_end = cd_ptr + cd_length; - const uint8_t* ptr = cd_ptr; - for (uint64_t i = 0; i < num_entries; i++) { - if (ptr > cd_end - sizeof(CentralDirectoryRecord)) { - ALOGW("Zip: ran off the end (item #%" PRIu64 ", %zu bytes of central directory)", i, - cd_length); -#if defined(__ANDROID__) - android_errorWriteLog(0x534e4554, "36392138"); -#endif - return kInvalidFile; - } - - auto cdr = reinterpret_cast(ptr); - if (cdr->record_signature != CentralDirectoryRecord::kSignature) { - ALOGW("Zip: missed a central dir sig (at %" PRIu64 ")", i); - return kInvalidFile; - } - - const uint16_t file_name_length = cdr->file_name_length; - const uint16_t extra_length = cdr->extra_field_length; - const uint16_t comment_length = cdr->comment_length; - const uint8_t* file_name = ptr + sizeof(CentralDirectoryRecord); - - if (file_name_length >= cd_length || file_name > cd_end - file_name_length) { - ALOGW("Zip: file name for entry %" PRIu64 - " exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu", - i, file_name_length, cd_length); - return kInvalidEntryName; - } - - const uint8_t* extra_field = file_name + file_name_length; - if (extra_length >= cd_length || extra_field > cd_end - extra_length) { - ALOGW("Zip: extra field for entry %" PRIu64 - " exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu", - i, extra_length, cd_length); - return kInvalidFile; - } - - off64_t local_header_offset = cdr->local_file_header_offset; - if (local_header_offset == UINT32_MAX) { - Zip64ExtendedInfo zip64_info{}; - if (auto status = ParseZip64ExtendedInfoInExtraField( - extra_field, extra_length, cdr->uncompressed_size, cdr->compressed_size, - cdr->local_file_header_offset, &zip64_info); - status != kSuccess) { - return status; - } - CHECK(zip64_info.local_header_offset.has_value()); - local_header_offset = zip64_info.local_header_offset.value(); - } - - if (local_header_offset >= archive->directory_offset) { - ALOGW("Zip: bad LFH offset %" PRId64 " at entry %" PRIu64, - static_cast(local_header_offset), i); - return kInvalidFile; - } - - // Check that file name is valid UTF-8 and doesn't contain NUL (U+0000) characters. - if (!IsValidEntryName(file_name, file_name_length)) { - ALOGW("Zip: invalid file name at entry %" PRIu64, i); - return kInvalidEntryName; - } - - // Add the CDE filename to the hash table. - std::string_view entry_name{reinterpret_cast(file_name), file_name_length}; - if (auto add_result = - archive->cd_entry_map->AddToMap(entry_name, archive->central_directory.GetBasePtr()); - add_result != 0) { - ALOGW("Zip: Error adding entry to hash table %d", add_result); - return add_result; - } - - ptr += sizeof(CentralDirectoryRecord) + file_name_length + extra_length + comment_length; - if ((ptr - cd_ptr) > static_cast(cd_length)) { - ALOGW("Zip: bad CD advance (%tu vs %zu) at entry %" PRIu64, ptr - cd_ptr, cd_length, i); - return kInvalidFile; - } - } - - uint32_t lfh_start_bytes; - if (!archive->mapped_zip.ReadAtOffset(reinterpret_cast(&lfh_start_bytes), - sizeof(uint32_t), 0)) { - ALOGW("Zip: Unable to read header for entry at offset == 0."); - return kInvalidFile; - } - - if (lfh_start_bytes != LocalFileHeader::kSignature) { - ALOGW("Zip: Entry at offset zero has invalid LFH signature %" PRIx32, lfh_start_bytes); -#if defined(__ANDROID__) - android_errorWriteLog(0x534e4554, "64211847"); -#endif - return kInvalidFile; - } - - ALOGV("+++ zip good scan %" PRIu64 " entries", num_entries); - - return kSuccess; -} - -static int32_t OpenArchiveInternal(ZipArchive* archive, const char* debug_file_name) { - int32_t result = MapCentralDirectory(debug_file_name, archive); - return result != kSuccess ? result : ParseZipArchive(archive); -} - -int32_t OpenArchiveFd(int fd, const char* debug_file_name, ZipArchiveHandle* handle, - bool assume_ownership) { - ZipArchive* archive = new ZipArchive(MappedZipFile(fd), assume_ownership); - *handle = archive; - return OpenArchiveInternal(archive, debug_file_name); -} - -int32_t OpenArchiveFdRange(int fd, const char* debug_file_name, ZipArchiveHandle* handle, - off64_t length, off64_t offset, bool assume_ownership) { - ZipArchive* archive = new ZipArchive(MappedZipFile(fd, length, offset), assume_ownership); - *handle = archive; - - if (length < 0) { - ALOGW("Invalid zip length %" PRId64, length); - return kIoError; - } - - if (offset < 0) { - ALOGW("Invalid zip offset %" PRId64, offset); - return kIoError; - } - - return OpenArchiveInternal(archive, debug_file_name); -} - -int32_t OpenArchive(const char* fileName, ZipArchiveHandle* handle) { - const int fd = ::android::base::utf8::open(fileName, O_RDONLY | O_BINARY | O_CLOEXEC, 0); - ZipArchive* archive = new ZipArchive(MappedZipFile(fd), true); - *handle = archive; - - if (fd < 0) { - ALOGW("Unable to open '%s': %s", fileName, strerror(errno)); - return kIoError; - } - - return OpenArchiveInternal(archive, fileName); -} - -int32_t OpenArchiveFromMemory(const void* address, size_t length, const char* debug_file_name, - ZipArchiveHandle* handle) { - ZipArchive* archive = new ZipArchive(address, length); - *handle = archive; - return OpenArchiveInternal(archive, debug_file_name); -} - -ZipArchiveInfo GetArchiveInfo(ZipArchiveHandle archive) { - ZipArchiveInfo result; - result.archive_size = archive->mapped_zip.GetFileLength(); - result.entry_count = archive->num_entries; - return result; -} - -/* - * Close a ZipArchive, closing the file and freeing the contents. - */ -void CloseArchive(ZipArchiveHandle archive) { - ALOGV("Closing archive %p", archive); - delete archive; -} - -static int32_t ValidateDataDescriptor(MappedZipFile& mapped_zip, const ZipEntry64* entry) { - // Maximum possible size for data descriptor: 2 * 4 + 2 * 8 = 24 bytes - // The zip format doesn't specify the size of data descriptor. But we won't read OOB here even - // if the descriptor isn't present. Because the size cd + eocd in the end of the zipfile is - // larger than 24 bytes. And if the descriptor contains invalid data, we'll abort due to - // kInconsistentInformation. - uint8_t ddBuf[24]; - off64_t offset = entry->offset; - if (entry->method != kCompressStored) { - offset += entry->compressed_length; - } else { - offset += entry->uncompressed_length; - } - - if (!mapped_zip.ReadAtOffset(ddBuf, sizeof(ddBuf), offset)) { - return kIoError; - } - - const uint32_t ddSignature = *(reinterpret_cast(ddBuf)); - uint8_t* ddReadPtr = (ddSignature == DataDescriptor::kOptSignature) ? ddBuf + 4 : ddBuf; - DataDescriptor descriptor{}; - descriptor.crc32 = ConsumeUnaligned(&ddReadPtr); - if (entry->zip64_format_size) { - descriptor.compressed_size = ConsumeUnaligned(&ddReadPtr); - descriptor.uncompressed_size = ConsumeUnaligned(&ddReadPtr); - } else { - descriptor.compressed_size = ConsumeUnaligned(&ddReadPtr); - descriptor.uncompressed_size = ConsumeUnaligned(&ddReadPtr); - } - - // Validate that the values in the data descriptor match those in the central - // directory. - if (entry->compressed_length != descriptor.compressed_size || - entry->uncompressed_length != descriptor.uncompressed_size || - entry->crc32 != descriptor.crc32) { - ALOGW("Zip: size/crc32 mismatch. expected {%" PRIu64 ", %" PRIu64 ", %" PRIx32 - "}, was {%" PRIu64 ", %" PRIu64 ", %" PRIx32 "}", - entry->compressed_length, entry->uncompressed_length, entry->crc32, - descriptor.compressed_size, descriptor.uncompressed_size, descriptor.crc32); - return kInconsistentInformation; - } - - return 0; -} - -static int32_t FindEntry(const ZipArchive* archive, std::string_view entryName, - const uint64_t nameOffset, ZipEntry64* data) { - // Recover the start of the central directory entry from the filename - // pointer. The filename is the first entry past the fixed-size data, - // so we can just subtract back from that. - const uint8_t* base_ptr = archive->central_directory.GetBasePtr(); - const uint8_t* ptr = base_ptr + nameOffset; - ptr -= sizeof(CentralDirectoryRecord); - - // This is the base of our mmapped region, we have to sanity check that - // the name that's in the hash table is a pointer to a location within - // this mapped region. - if (ptr < base_ptr || ptr > base_ptr + archive->central_directory.GetMapLength()) { - ALOGW("Zip: Invalid entry pointer"); - return kInvalidOffset; - } - - auto cdr = reinterpret_cast(ptr); - - // The offset of the start of the central directory in the zipfile. - // We keep this lying around so that we can sanity check all our lengths - // and our per-file structures. - const off64_t cd_offset = archive->directory_offset; - - // Fill out the compression method, modification time, crc32 - // and other interesting attributes from the central directory. These - // will later be compared against values from the local file header. - data->method = cdr->compression_method; - data->mod_time = cdr->last_mod_date << 16 | cdr->last_mod_time; - data->crc32 = cdr->crc32; - data->compressed_length = cdr->compressed_size; - data->uncompressed_length = cdr->uncompressed_size; - - // Figure out the local header offset from the central directory. The - // actual file data will begin after the local header and the name / - // extra comments. - off64_t local_header_offset = cdr->local_file_header_offset; - // One of the info field is UINT32_MAX, try to parse the real value in the zip64 extended info in - // the extra field. - if (cdr->uncompressed_size == UINT32_MAX || cdr->compressed_size == UINT32_MAX || - cdr->local_file_header_offset == UINT32_MAX) { - const uint8_t* extra_field = ptr + sizeof(CentralDirectoryRecord) + cdr->file_name_length; - Zip64ExtendedInfo zip64_info{}; - if (auto status = ParseZip64ExtendedInfoInExtraField( - extra_field, cdr->extra_field_length, cdr->uncompressed_size, cdr->compressed_size, - cdr->local_file_header_offset, &zip64_info); - status != kSuccess) { - return status; - } - - data->uncompressed_length = zip64_info.uncompressed_file_size.value_or(cdr->uncompressed_size); - data->compressed_length = zip64_info.compressed_file_size.value_or(cdr->compressed_size); - local_header_offset = zip64_info.local_header_offset.value_or(local_header_offset); - data->zip64_format_size = - cdr->uncompressed_size == UINT32_MAX || cdr->compressed_size == UINT32_MAX; - } - - if (local_header_offset + static_cast(sizeof(LocalFileHeader)) >= cd_offset) { - ALOGW("Zip: bad local hdr offset in zip"); - return kInvalidOffset; - } - - uint8_t lfh_buf[sizeof(LocalFileHeader)]; - if (!archive->mapped_zip.ReadAtOffset(lfh_buf, sizeof(lfh_buf), local_header_offset)) { - ALOGW("Zip: failed reading lfh name from offset %" PRId64, - static_cast(local_header_offset)); - return kIoError; - } - - auto lfh = reinterpret_cast(lfh_buf); - if (lfh->lfh_signature != LocalFileHeader::kSignature) { - ALOGW("Zip: didn't find signature at start of lfh, offset=%" PRId64, - static_cast(local_header_offset)); - return kInvalidOffset; - } - - // Check that the local file header name matches the declared name in the central directory. - CHECK_LE(entryName.size(), UINT16_MAX); - auto nameLen = static_cast(entryName.size()); - if (lfh->file_name_length != nameLen) { - ALOGW("Zip: lfh name length did not match central directory for %s: %" PRIu16 " %" PRIu16, - std::string(entryName).c_str(), lfh->file_name_length, nameLen); - return kInconsistentInformation; - } - const off64_t name_offset = local_header_offset + sizeof(LocalFileHeader); - if (name_offset > cd_offset - lfh->file_name_length) { - ALOGW("Zip: lfh name has invalid declared length"); - return kInvalidOffset; - } - - std::vector name_buf(nameLen); - if (!archive->mapped_zip.ReadAtOffset(name_buf.data(), nameLen, name_offset)) { - ALOGW("Zip: failed reading lfh name from offset %" PRId64, static_cast(name_offset)); - return kIoError; - } - if (memcmp(entryName.data(), name_buf.data(), nameLen) != 0) { - ALOGW("Zip: lfh name did not match central directory"); - return kInconsistentInformation; - } - - uint64_t lfh_uncompressed_size = lfh->uncompressed_size; - uint64_t lfh_compressed_size = lfh->compressed_size; - if (lfh_uncompressed_size == UINT32_MAX || lfh_compressed_size == UINT32_MAX) { - if (lfh_uncompressed_size != UINT32_MAX || lfh_compressed_size != UINT32_MAX) { - ALOGW( - "Zip: The zip64 extended field in the local header MUST include BOTH original and " - "compressed file size fields."); - return kInvalidFile; - } - - const off64_t lfh_extra_field_offset = name_offset + lfh->file_name_length; - const uint16_t lfh_extra_field_size = lfh->extra_field_length; - if (lfh_extra_field_offset > cd_offset - lfh_extra_field_size) { - ALOGW("Zip: extra field has a bad size for entry %s", std::string(entryName).c_str()); - return kInvalidOffset; - } - - std::vector local_extra_field(lfh_extra_field_size); - if (!archive->mapped_zip.ReadAtOffset(local_extra_field.data(), lfh_extra_field_size, - lfh_extra_field_offset)) { - ALOGW("Zip: failed reading lfh extra field from offset %" PRId64, lfh_extra_field_offset); - return kIoError; - } - - Zip64ExtendedInfo zip64_info{}; - if (auto status = ParseZip64ExtendedInfoInExtraField( - local_extra_field.data(), lfh_extra_field_size, lfh->uncompressed_size, - lfh->compressed_size, std::nullopt, &zip64_info); - status != kSuccess) { - return status; - } - - CHECK(zip64_info.uncompressed_file_size.has_value()); - CHECK(zip64_info.compressed_file_size.has_value()); - lfh_uncompressed_size = zip64_info.uncompressed_file_size.value(); - lfh_compressed_size = zip64_info.compressed_file_size.value(); - } - - // Paranoia: Match the values specified in the local file header - // to those specified in the central directory. - - // Warn if central directory and local file header don't agree on the use - // of a trailing Data Descriptor. The reference implementation is inconsistent - // and appears to use the LFH value during extraction (unzip) but the CD value - // while displayng information about archives (zipinfo). The spec remains - // silent on this inconsistency as well. - // - // For now, always use the version from the LFH but make sure that the values - // specified in the central directory match those in the data descriptor. - // - // NOTE: It's also worth noting that unzip *does* warn about inconsistencies in - // bit 11 (EFS: The language encoding flag, marking that filename and comment are - // encoded using UTF-8). This implementation does not check for the presence of - // that flag and always enforces that entry names are valid UTF-8. - if ((lfh->gpb_flags & kGPBDDFlagMask) != (cdr->gpb_flags & kGPBDDFlagMask)) { - ALOGW("Zip: gpb flag mismatch at bit 3. expected {%04" PRIx16 "}, was {%04" PRIx16 "}", - cdr->gpb_flags, lfh->gpb_flags); - } - - // If there is no trailing data descriptor, verify that the central directory and local file - // header agree on the crc, compressed, and uncompressed sizes of the entry. - if ((lfh->gpb_flags & kGPBDDFlagMask) == 0) { - data->has_data_descriptor = 0; - if (data->compressed_length != lfh_compressed_size || - data->uncompressed_length != lfh_uncompressed_size || data->crc32 != lfh->crc32) { - ALOGW("Zip: size/crc32 mismatch. expected {%" PRIu64 ", %" PRIu64 ", %" PRIx32 - "}, was {%" PRIu64 ", %" PRIu64 ", %" PRIx32 "}", - data->compressed_length, data->uncompressed_length, data->crc32, lfh_compressed_size, - lfh_uncompressed_size, lfh->crc32); - return kInconsistentInformation; - } - } else { - data->has_data_descriptor = 1; - } - - // 4.4.2.1: the upper byte of `version_made_by` gives the source OS. Unix is 3. - data->version_made_by = cdr->version_made_by; - data->external_file_attributes = cdr->external_file_attributes; - if ((data->version_made_by >> 8) == 3) { - data->unix_mode = (cdr->external_file_attributes >> 16) & 0xffff; - } else { - data->unix_mode = 0777; - } - - // 4.4.4: general purpose bit flags. - data->gpbf = lfh->gpb_flags; - - // 4.4.14: the lowest bit of the internal file attributes field indicates text. - // Currently only needed to implement zipinfo. - data->is_text = (cdr->internal_file_attributes & 1); - - const off64_t data_offset = local_header_offset + sizeof(LocalFileHeader) + - lfh->file_name_length + lfh->extra_field_length; - if (data_offset > cd_offset) { - ALOGW("Zip: bad data offset %" PRId64 " in zip", static_cast(data_offset)); - return kInvalidOffset; - } - - if (data->compressed_length > cd_offset - data_offset) { - ALOGW("Zip: bad compressed length in zip (%" PRId64 " + %" PRIu64 " > %" PRId64 ")", - static_cast(data_offset), data->compressed_length, - static_cast(cd_offset)); - return kInvalidOffset; - } - - if (data->method == kCompressStored && data->uncompressed_length > cd_offset - data_offset) { - ALOGW("Zip: bad uncompressed length in zip (%" PRId64 " + %" PRIu64 " > %" PRId64 ")", - static_cast(data_offset), data->uncompressed_length, - static_cast(cd_offset)); - return kInvalidOffset; - } - - data->offset = data_offset; - return 0; -} - -struct IterationHandle { - ZipArchive* archive; - - std::function matcher; - - uint32_t position = 0; - - IterationHandle(ZipArchive* archive, std::function in_matcher) - : archive(archive), matcher(std::move(in_matcher)) {} - - bool Match(std::string_view entry_name) const { return matcher(entry_name); } -}; - -int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr, - const std::string_view optional_prefix, - const std::string_view optional_suffix) { - if (optional_prefix.size() > static_cast(UINT16_MAX) || - optional_suffix.size() > static_cast(UINT16_MAX)) { - ALOGW("Zip: prefix/suffix too long"); - return kInvalidEntryName; - } - auto matcher = [prefix = std::string(optional_prefix), - suffix = std::string(optional_suffix)](std::string_view name) mutable { - return android::base::StartsWith(name, prefix) && android::base::EndsWith(name, suffix); - }; - return StartIteration(archive, cookie_ptr, std::move(matcher)); -} - -int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr, - std::function matcher) { - if (archive == nullptr || archive->cd_entry_map == nullptr) { - ALOGW("Zip: Invalid ZipArchiveHandle"); - return kInvalidHandle; - } - - archive->cd_entry_map->ResetIteration(); - *cookie_ptr = new IterationHandle(archive, matcher); - return 0; -} - -void EndIteration(void* cookie) { - delete reinterpret_cast(cookie); -} - -int32_t ZipEntry::CopyFromZipEntry64(ZipEntry* dst, const ZipEntry64* src) { - if (src->compressed_length > UINT32_MAX || src->uncompressed_length > UINT32_MAX) { - ALOGW( - "Zip: the entry size is too large to fit into the 32 bits ZipEntry, uncompressed " - "length %" PRIu64 ", compressed length %" PRIu64, - src->uncompressed_length, src->compressed_length); - return kUnsupportedEntrySize; - } - - *dst = *src; - dst->uncompressed_length = static_cast(src->uncompressed_length); - dst->compressed_length = static_cast(src->compressed_length); - return kSuccess; -} - -int32_t FindEntry(const ZipArchiveHandle archive, const std::string_view entryName, - ZipEntry* data) { - ZipEntry64 entry64; - if (auto status = FindEntry(archive, entryName, &entry64); status != kSuccess) { - return status; - } - - return ZipEntry::CopyFromZipEntry64(data, &entry64); -} - -int32_t FindEntry(const ZipArchiveHandle archive, const std::string_view entryName, - ZipEntry64* data) { - if (entryName.empty() || entryName.size() > static_cast(UINT16_MAX)) { - ALOGW("Zip: Invalid filename of length %zu", entryName.size()); - return kInvalidEntryName; - } - - const auto [result, offset] = - archive->cd_entry_map->GetCdEntryOffset(entryName, archive->central_directory.GetBasePtr()); - if (result != 0) { - ALOGV("Zip: Could not find entry %.*s", static_cast(entryName.size()), entryName.data()); - return static_cast(result); // kEntryNotFound is safe to truncate. - } - // We know there are at most hash_table_size entries, safe to truncate. - return FindEntry(archive, entryName, offset, data); -} - -int32_t Next(void* cookie, ZipEntry* data, std::string* name) { - ZipEntry64 entry64; - if (auto status = Next(cookie, &entry64, name); status != kSuccess) { - return status; - } - - return ZipEntry::CopyFromZipEntry64(data, &entry64); -} - -int32_t Next(void* cookie, ZipEntry* data, std::string_view* name) { - ZipEntry64 entry64; - if (auto status = Next(cookie, &entry64, name); status != kSuccess) { - return status; - } - - return ZipEntry::CopyFromZipEntry64(data, &entry64); -} - -int32_t Next(void* cookie, ZipEntry64* data, std::string* name) { - std::string_view sv; - int32_t result = Next(cookie, data, &sv); - if (result == 0 && name) { - *name = std::string(sv); - } - return result; -} - -int32_t Next(void* cookie, ZipEntry64* data, std::string_view* name) { - IterationHandle* handle = reinterpret_cast(cookie); - if (handle == nullptr) { - ALOGW("Zip: Null ZipArchiveHandle"); - return kInvalidHandle; - } - - ZipArchive* archive = handle->archive; - if (archive == nullptr || archive->cd_entry_map == nullptr) { - ALOGW("Zip: Invalid ZipArchiveHandle"); - return kInvalidHandle; - } - - auto entry = archive->cd_entry_map->Next(archive->central_directory.GetBasePtr()); - while (entry != std::pair()) { - const auto [entry_name, offset] = entry; - if (handle->Match(entry_name)) { - const int error = FindEntry(archive, entry_name, offset, data); - if (!error && name) { - *name = entry_name; - } - return error; - } - entry = archive->cd_entry_map->Next(archive->central_directory.GetBasePtr()); - } - - archive->cd_entry_map->ResetIteration(); - return kIterationEnd; -} - -// A Writer that writes data to a fixed size memory region. -// The size of the memory region must be equal to the total size of -// the data appended to it. -class MemoryWriter : public zip_archive::Writer { - public: - static std::unique_ptr Create(uint8_t* buf, size_t size, const ZipEntry64* entry) { - const uint64_t declared_length = entry->uncompressed_length; - if (declared_length > size) { - ALOGW("Zip: file size %" PRIu64 " is larger than the buffer size %zu.", declared_length, - size); - return nullptr; - } - - return std::unique_ptr(new MemoryWriter(buf, size)); - } - - virtual bool Append(uint8_t* buf, size_t buf_size) override { - if (size_ < buf_size || bytes_written_ > size_ - buf_size) { - ALOGW("Zip: Unexpected size %zu (declared) vs %zu (actual)", size_, - bytes_written_ + buf_size); - return false; - } - - memcpy(buf_ + bytes_written_, buf, buf_size); - bytes_written_ += buf_size; - return true; - } - - private: - MemoryWriter(uint8_t* buf, size_t size) : Writer(), buf_(buf), size_(size), bytes_written_(0) {} - - uint8_t* const buf_{nullptr}; - const size_t size_; - size_t bytes_written_; -}; - -// A Writer that appends data to a file |fd| at its current position. -// The file will be truncated to the end of the written data. -class FileWriter : public zip_archive::Writer { - public: - // Creates a FileWriter for |fd| and prepare to write |entry| to it, - // guaranteeing that the file descriptor is valid and that there's enough - // space on the volume to write out the entry completely and that the file - // is truncated to the correct length (no truncation if |fd| references a - // block device). - // - // Returns a valid FileWriter on success, |nullptr| if an error occurred. - static std::unique_ptr Create(int fd, const ZipEntry64* entry) { - const uint64_t declared_length = entry->uncompressed_length; - const off64_t current_offset = lseek64(fd, 0, SEEK_CUR); - if (current_offset == -1) { - ALOGW("Zip: unable to seek to current location on fd %d: %s", fd, strerror(errno)); - return nullptr; - } - - if (declared_length > SIZE_MAX || declared_length > INT64_MAX) { - ALOGW("Zip: file size %" PRIu64 " is too large to extract.", declared_length); - return nullptr; - } - -#if defined(__linux__) - if (declared_length > 0) { - // Make sure we have enough space on the volume to extract the compressed - // entry. Note that the call to ftruncate below will change the file size but - // will not allocate space on disk and this call to fallocate will not - // change the file size. - // Note: fallocate is only supported by the following filesystems - - // btrfs, ext4, ocfs2, and xfs. Therefore fallocate might fail with - // EOPNOTSUPP error when issued in other filesystems. - // Hence, check for the return error code before concluding that the - // disk does not have enough space. - long result = TEMP_FAILURE_RETRY(fallocate(fd, 0, current_offset, declared_length)); - if (result == -1 && errno == ENOSPC) { - ALOGW("Zip: unable to allocate %" PRIu64 " bytes at offset %" PRId64 ": %s", - declared_length, static_cast(current_offset), strerror(errno)); - return nullptr; - } - } -#endif // __linux__ - - struct stat sb; - if (fstat(fd, &sb) == -1) { - ALOGW("Zip: unable to fstat file: %s", strerror(errno)); - return nullptr; - } - - // Block device doesn't support ftruncate(2). - if (!S_ISBLK(sb.st_mode)) { - long result = TEMP_FAILURE_RETRY(ftruncate(fd, declared_length + current_offset)); - if (result == -1) { - ALOGW("Zip: unable to truncate file to %" PRId64 ": %s", - static_cast(declared_length + current_offset), strerror(errno)); - return nullptr; - } - } - - return std::unique_ptr(new FileWriter(fd, declared_length)); - } - - FileWriter(FileWriter&& other) noexcept - : fd_(other.fd_), - declared_length_(other.declared_length_), - total_bytes_written_(other.total_bytes_written_) { - other.fd_ = -1; - } - - virtual bool Append(uint8_t* buf, size_t buf_size) override { - if (declared_length_ < buf_size || total_bytes_written_ > declared_length_ - buf_size) { - ALOGW("Zip: Unexpected size %zu (declared) vs %zu (actual)", declared_length_, - total_bytes_written_ + buf_size); - return false; - } - - const bool result = android::base::WriteFully(fd_, buf, buf_size); - if (result) { - total_bytes_written_ += buf_size; - } else { - ALOGW("Zip: unable to write %zu bytes to file; %s", buf_size, strerror(errno)); - } - - return result; - } - - private: - explicit FileWriter(const int fd = -1, const uint64_t declared_length = 0) - : Writer(), - fd_(fd), - declared_length_(static_cast(declared_length)), - total_bytes_written_(0) { - CHECK_LE(declared_length, SIZE_MAX); - } - - int fd_; - const size_t declared_length_; - size_t total_bytes_written_; -}; - -class EntryReader : public zip_archive::Reader { - public: - EntryReader(const MappedZipFile& zip_file, const ZipEntry64* entry) - : Reader(), zip_file_(zip_file), entry_(entry) {} - - virtual bool ReadAtOffset(uint8_t* buf, size_t len, off64_t offset) const { - return zip_file_.ReadAtOffset(buf, len, entry_->offset + offset); - } - - virtual ~EntryReader() {} - - private: - const MappedZipFile& zip_file_; - const ZipEntry64* entry_; -}; - -// This method is using libz macros with old-style-casts -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -static inline int zlib_inflateInit2(z_stream* stream, int window_bits) { - return inflateInit2(stream, window_bits); -} -#pragma GCC diagnostic pop - -namespace zip_archive { - -// Moved out of line to avoid -Wweak-vtables. -Reader::~Reader() {} -Writer::~Writer() {} - -int32_t Inflate(const Reader& reader, const uint64_t compressed_length, - const uint64_t uncompressed_length, Writer* writer, uint64_t* crc_out) { - const size_t kBufSize = 32768; - std::vector read_buf(kBufSize); - std::vector write_buf(kBufSize); - z_stream zstream; - int zerr; - - /* - * Initialize the zlib stream struct. - */ - memset(&zstream, 0, sizeof(zstream)); - zstream.zalloc = Z_NULL; - zstream.zfree = Z_NULL; - zstream.opaque = Z_NULL; - zstream.next_in = NULL; - zstream.avail_in = 0; - zstream.next_out = &write_buf[0]; - zstream.avail_out = kBufSize; - zstream.data_type = Z_UNKNOWN; - - /* - * Use the undocumented "negative window bits" feature to tell zlib - * that there's no zlib header waiting for it. - */ - zerr = zlib_inflateInit2(&zstream, -MAX_WBITS); - if (zerr != Z_OK) { - if (zerr == Z_VERSION_ERROR) { - ALOGE("Installed zlib is not compatible with linked version (%s)", ZLIB_VERSION); - } else { - ALOGW("Call to inflateInit2 failed (zerr=%d)", zerr); - } - - return kZlibError; - } - - auto zstream_deleter = [](z_stream* stream) { - inflateEnd(stream); /* free up any allocated structures */ - }; - - std::unique_ptr zstream_guard(&zstream, zstream_deleter); - - const bool compute_crc = (crc_out != nullptr); - uLong crc = 0; - uint64_t remaining_bytes = compressed_length; - uint64_t total_output = 0; - do { - /* read as much as we can */ - if (zstream.avail_in == 0) { - const uint32_t read_size = - (remaining_bytes > kBufSize) ? kBufSize : static_cast(remaining_bytes); - const off64_t offset = (compressed_length - remaining_bytes); - // Make sure to read at offset to ensure concurrent access to the fd. - if (!reader.ReadAtOffset(read_buf.data(), read_size, offset)) { - ALOGW("Zip: inflate read failed, getSize = %u: %s", read_size, strerror(errno)); - return kIoError; - } - - remaining_bytes -= read_size; - - zstream.next_in = &read_buf[0]; - zstream.avail_in = read_size; - } - - /* uncompress the data */ - zerr = inflate(&zstream, Z_NO_FLUSH); - if (zerr != Z_OK && zerr != Z_STREAM_END) { - ALOGW("Zip: inflate zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)", zerr, zstream.next_in, - zstream.avail_in, zstream.next_out, zstream.avail_out); - return kZlibError; - } - - /* write when we're full or when we're done */ - if (zstream.avail_out == 0 || (zerr == Z_STREAM_END && zstream.avail_out != kBufSize)) { - const size_t write_size = zstream.next_out - &write_buf[0]; - if (!writer->Append(&write_buf[0], write_size)) { - return kIoError; - } else if (compute_crc) { - DCHECK_LE(write_size, kBufSize); - crc = crc32(crc, &write_buf[0], static_cast(write_size)); - } - - total_output += kBufSize - zstream.avail_out; - zstream.next_out = &write_buf[0]; - zstream.avail_out = kBufSize; - } - } while (zerr == Z_OK); - - CHECK_EQ(zerr, Z_STREAM_END); /* other errors should've been caught */ - - // NOTE: zstream.adler is always set to 0, because we're using the -MAX_WBITS - // "feature" of zlib to tell it there won't be a zlib file header. zlib - // doesn't bother calculating the checksum in that scenario. We just do - // it ourselves above because there are no additional gains to be made by - // having zlib calculate it for us, since they do it by calling crc32 in - // the same manner that we have above. - if (compute_crc) { - *crc_out = crc; - } - if (total_output != uncompressed_length || remaining_bytes != 0) { - ALOGW("Zip: size mismatch on inflated file (%lu vs %" PRIu64 ")", zstream.total_out, - uncompressed_length); - return kInconsistentInformation; - } - - return 0; -} -} // namespace zip_archive - -static int32_t InflateEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry64* entry, - zip_archive::Writer* writer, uint64_t* crc_out) { - const EntryReader reader(mapped_zip, entry); - - return zip_archive::Inflate(reader, entry->compressed_length, entry->uncompressed_length, writer, - crc_out); -} - -static int32_t CopyEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry64* entry, - zip_archive::Writer* writer, uint64_t* crc_out) { - static const uint32_t kBufSize = 32768; - std::vector buf(kBufSize); - - const uint64_t length = entry->uncompressed_length; - uint64_t count = 0; - uLong crc = 0; - while (count < length) { - uint64_t remaining = length - count; - off64_t offset = entry->offset + count; - - // Safe conversion because kBufSize is narrow enough for a 32 bit signed value. - const uint32_t block_size = - (remaining > kBufSize) ? kBufSize : static_cast(remaining); - - // Make sure to read at offset to ensure concurrent access to the fd. - if (!mapped_zip.ReadAtOffset(buf.data(), block_size, offset)) { - ALOGW("CopyFileToFile: copy read failed, block_size = %u, offset = %" PRId64 ": %s", - block_size, static_cast(offset), strerror(errno)); - return kIoError; - } - - if (!writer->Append(&buf[0], block_size)) { - return kIoError; - } - if (crc_out) { - crc = crc32(crc, &buf[0], block_size); - } - count += block_size; - } - - if (crc_out) { - *crc_out = crc; - } - - return 0; -} - -int32_t ExtractToWriter(ZipArchiveHandle handle, const ZipEntry64* entry, - zip_archive::Writer* writer) { - const uint16_t method = entry->method; - - // this should default to kUnknownCompressionMethod. - int32_t return_value = -1; - uint64_t crc = 0; - if (method == kCompressStored) { - return_value = - CopyEntryToWriter(handle->mapped_zip, entry, writer, kCrcChecksEnabled ? &crc : nullptr); - } else if (method == kCompressDeflated) { - return_value = - InflateEntryToWriter(handle->mapped_zip, entry, writer, kCrcChecksEnabled ? &crc : nullptr); - } - - if (!return_value && entry->has_data_descriptor) { - return_value = ValidateDataDescriptor(handle->mapped_zip, entry); - if (return_value) { - return return_value; - } - } - - // Validate that the CRC matches the calculated value. - if (kCrcChecksEnabled && (entry->crc32 != static_cast(crc))) { - ALOGW("Zip: crc mismatch: expected %" PRIu32 ", was %" PRIu64, entry->crc32, crc); - return kInconsistentInformation; - } - - return return_value; -} - -int32_t ExtractToMemory(ZipArchiveHandle archive, const ZipEntry* entry, uint8_t* begin, - size_t size) { - ZipEntry64 entry64(*entry); - return ExtractToMemory(archive, &entry64, begin, size); -} - -int32_t ExtractToMemory(ZipArchiveHandle archive, const ZipEntry64* entry, uint8_t* begin, - size_t size) { - auto writer = MemoryWriter::Create(begin, size, entry); - if (!writer) { - return kIoError; - } - - return ExtractToWriter(archive, entry, writer.get()); -} - -int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry* entry, int fd) { - ZipEntry64 entry64(*entry); - return ExtractEntryToFile(archive, &entry64, fd); -} - -int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry64* entry, int fd) { - auto writer = FileWriter::Create(fd, entry); - if (!writer) { - return kIoError; - } - - return ExtractToWriter(archive, entry, writer.get()); -} - -int GetFileDescriptor(const ZipArchiveHandle archive) { - return archive->mapped_zip.GetFileDescriptor(); -} - -off64_t GetFileDescriptorOffset(const ZipArchiveHandle archive) { - return archive->mapped_zip.GetFileOffset(); -} - -#if !defined(_WIN32) -class ProcessWriter : public zip_archive::Writer { - public: - ProcessWriter(ProcessZipEntryFunction func, void* cookie) - : Writer(), proc_function_(func), cookie_(cookie) {} - - virtual bool Append(uint8_t* buf, size_t buf_size) override { - return proc_function_(buf, buf_size, cookie_); - } - - private: - ProcessZipEntryFunction proc_function_; - void* cookie_; -}; - -int32_t ProcessZipEntryContents(ZipArchiveHandle archive, const ZipEntry* entry, - ProcessZipEntryFunction func, void* cookie) { - ZipEntry64 entry64(*entry); - return ProcessZipEntryContents(archive, &entry64, func, cookie); -} - -int32_t ProcessZipEntryContents(ZipArchiveHandle archive, const ZipEntry64* entry, - ProcessZipEntryFunction func, void* cookie) { - ProcessWriter writer(func, cookie); - return ExtractToWriter(archive, entry, &writer); -} - -#endif //! defined(_WIN32) - -int MappedZipFile::GetFileDescriptor() const { - if (!has_fd_) { - ALOGW("Zip: MappedZipFile doesn't have a file descriptor."); - return -1; - } - return fd_; -} - -const void* MappedZipFile::GetBasePtr() const { - if (has_fd_) { - ALOGW("Zip: MappedZipFile doesn't have a base pointer."); - return nullptr; - } - return base_ptr_; -} - -off64_t MappedZipFile::GetFileOffset() const { - return fd_offset_; -} - -off64_t MappedZipFile::GetFileLength() const { - if (has_fd_) { - if (data_length_ != -1) { - return data_length_; - } - data_length_ = lseek64(fd_, 0, SEEK_END); - if (data_length_ == -1) { - ALOGE("Zip: lseek on fd %d failed: %s", fd_, strerror(errno)); - } - return data_length_; - } else { - if (base_ptr_ == nullptr) { - ALOGE("Zip: invalid file map"); - return -1; - } - return data_length_; - } -} - -// Attempts to read |len| bytes into |buf| at offset |off|. -bool MappedZipFile::ReadAtOffset(uint8_t* buf, size_t len, off64_t off) const { - if (has_fd_) { - if (off < 0) { - ALOGE("Zip: invalid offset %" PRId64, off); - return false; - } - - off64_t read_offset; - if (__builtin_add_overflow(fd_offset_, off, &read_offset)) { - ALOGE("Zip: invalid read offset %" PRId64 " overflows, fd offset %" PRId64, off, fd_offset_); - return false; - } - - if (data_length_ != -1) { - off64_t read_end; - if (len > std::numeric_limits::max() || - __builtin_add_overflow(off, static_cast(len), &read_end)) { - ALOGE("Zip: invalid read length %" PRId64 " overflows, offset %" PRId64, - static_cast(len), off); - return false; - } - - if (read_end > data_length_) { - ALOGE("Zip: invalid read length %" PRId64 " exceeds data length %" PRId64 ", offset %" - PRId64, static_cast(len), data_length_, off); - return false; - } - } - - if (!android::base::ReadFullyAtOffset(fd_, buf, len, read_offset)) { - ALOGE("Zip: failed to read at offset %" PRId64, off); - return false; - } - } else { - if (off < 0 || data_length_ < len || off > data_length_ - len) { - ALOGE("Zip: invalid offset: %" PRId64 ", read length: %zu, data length: %" PRId64, off, len, - data_length_); - return false; - } - memcpy(buf, static_cast(base_ptr_) + off, len); - } - return true; -} - -void CentralDirectory::Initialize(const void* map_base_ptr, off64_t cd_start_offset, - size_t cd_size) { - base_ptr_ = static_cast(map_base_ptr) + cd_start_offset; - length_ = cd_size; -} - -bool ZipArchive::InitializeCentralDirectory(off64_t cd_start_offset, size_t cd_size) { - if (mapped_zip.HasFd()) { - directory_map = android::base::MappedFile::FromFd(mapped_zip.GetFileDescriptor(), - mapped_zip.GetFileOffset() + cd_start_offset, - cd_size, PROT_READ); - if (!directory_map) { - ALOGE("Zip: failed to map central directory (offset %" PRId64 ", size %zu): %s", - cd_start_offset, cd_size, strerror(errno)); - return false; - } - - CHECK_EQ(directory_map->size(), cd_size); - central_directory.Initialize(directory_map->data(), 0 /*offset*/, cd_size); - } else { - if (mapped_zip.GetBasePtr() == nullptr) { - ALOGE("Zip: Failed to map central directory, bad mapped_zip base pointer"); - return false; - } - if (static_cast(cd_start_offset) + static_cast(cd_size) > - mapped_zip.GetFileLength()) { - ALOGE( - "Zip: Failed to map central directory, offset exceeds mapped memory region (" - "start_offset %" PRId64 ", cd_size %zu, mapped_region_size %" PRId64 ")", - static_cast(cd_start_offset), cd_size, mapped_zip.GetFileLength()); - return false; - } - - central_directory.Initialize(mapped_zip.GetBasePtr(), cd_start_offset, cd_size); - } - return true; -} - -// This function returns the embedded timestamp as is; and doesn't perform validations. -tm ZipEntryCommon::GetModificationTime() const { - tm t = {}; - - t.tm_hour = (mod_time >> 11) & 0x1f; - t.tm_min = (mod_time >> 5) & 0x3f; - t.tm_sec = (mod_time & 0x1f) << 1; - - t.tm_year = ((mod_time >> 25) & 0x7f) + 80; - t.tm_mon = ((mod_time >> 21) & 0xf) - 1; - t.tm_mday = (mod_time >> 16) & 0x1f; - - return t; -} diff --git a/libziparchive/zip_archive_benchmark.cpp b/libziparchive/zip_archive_benchmark.cpp deleted file mode 100644 index cfa5912a2..000000000 --- a/libziparchive/zip_archive_benchmark.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -static std::unique_ptr CreateZip(int size = 4, int count = 1000) { - auto result = std::make_unique(); - FILE* fp = fdopen(result->fd, "w"); - - ZipWriter writer(fp); - std::string lastName = "file"; - for (size_t i = 0; i < count; i++) { - // Make file names longer and longer. - lastName = lastName + std::to_string(i); - writer.StartEntry(lastName.c_str(), ZipWriter::kCompress); - while (size > 0) { - writer.WriteBytes("helo", 4); - size -= 4; - } - writer.FinishEntry(); - } - writer.Finish(); - fclose(fp); - - return result; -} - -static void FindEntry_no_match(benchmark::State& state) { - // Create a temporary zip archive. - std::unique_ptr temp_file(CreateZip()); - ZipArchiveHandle handle; - ZipEntry data; - - // In order to walk through all file names in the archive, look for a name - // that does not exist in the archive. - std::string_view name("thisFileNameDoesNotExist"); - - // Start the benchmark. - for (auto _ : state) { - OpenArchive(temp_file->path, &handle); - FindEntry(handle, name, &data); - CloseArchive(handle); - } -} -BENCHMARK(FindEntry_no_match); - -static void Iterate_all_files(benchmark::State& state) { - std::unique_ptr temp_file(CreateZip()); - ZipArchiveHandle handle; - void* iteration_cookie; - ZipEntry data; - std::string name; - - for (auto _ : state) { - OpenArchive(temp_file->path, &handle); - StartIteration(handle, &iteration_cookie); - while (Next(iteration_cookie, &data, &name) == 0) { - } - EndIteration(iteration_cookie); - CloseArchive(handle); - } -} -BENCHMARK(Iterate_all_files); - -static void StartAlignedEntry(benchmark::State& state) { - TemporaryFile file; - FILE* fp = fdopen(file.fd, "w"); - - ZipWriter writer(fp); - - auto alignment = uint32_t(state.range(0)); - std::string name = "name"; - int counter = 0; - for (auto _ : state) { - writer.StartAlignedEntry(name + std::to_string(counter++), 0, alignment); - state.PauseTiming(); - writer.WriteBytes("hola", 4); - writer.FinishEntry(); - state.ResumeTiming(); - } - - writer.Finish(); - fclose(fp); -} -BENCHMARK(StartAlignedEntry)->Arg(2)->Arg(16)->Arg(1024)->Arg(4096); - -static void ExtractEntry(benchmark::State& state) { - std::unique_ptr temp_file(CreateZip(1024 * 1024, 1)); - - ZipArchiveHandle handle; - ZipEntry data; - if (OpenArchive(temp_file->path, &handle)) { - state.SkipWithError("Failed to open archive"); - } - if (FindEntry(handle, "file0", &data)) { - state.SkipWithError("Failed to find archive entry"); - } - - std::vector buffer(1024 * 1024); - for (auto _ : state) { - if (ExtractToMemory(handle, &data, buffer.data(), uint32_t(buffer.size()))) { - state.SkipWithError("Failed to extract archive entry"); - break; - } - } - CloseArchive(handle); -} - -BENCHMARK(ExtractEntry)->Arg(2)->Arg(16)->Arg(1024); - -BENCHMARK_MAIN(); diff --git a/libziparchive/zip_archive_common.h b/libziparchive/zip_archive_common.h deleted file mode 100644 index d46185603..000000000 --- a/libziparchive/zip_archive_common.h +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef LIBZIPARCHIVE_ZIPARCHIVECOMMON_H_ -#define LIBZIPARCHIVE_ZIPARCHIVECOMMON_H_ - -#include "android-base/macros.h" - -#include - -#include - -// The "end of central directory" (EOCD) record. Each archive -// contains exactly once such record which appears at the end of -// the archive. It contains archive wide information like the -// number of entries in the archive and the offset to the central -// directory of the offset. -struct EocdRecord { - static const uint32_t kSignature = 0x06054b50; - - // End of central directory signature, should always be - // |kSignature|. - uint32_t eocd_signature; - // The number of the current "disk", i.e, the "disk" that this - // central directory is on. - // - // This implementation assumes that each archive spans a single - // disk only. i.e, that disk_num == 1. - uint16_t disk_num; - // The disk where the central directory starts. - // - // This implementation assumes that each archive spans a single - // disk only. i.e, that cd_start_disk == 1. - uint16_t cd_start_disk; - // The number of central directory records on this disk. - // - // This implementation assumes that each archive spans a single - // disk only. i.e, that num_records_on_disk == num_records. - uint16_t num_records_on_disk; - // The total number of central directory records. - uint16_t num_records; - // The size of the central directory (in bytes). - uint32_t cd_size; - // The offset of the start of the central directory, relative - // to the start of the file. - uint32_t cd_start_offset; - // Length of the central directory comment. - uint16_t comment_length; - - private: - EocdRecord() = default; - DISALLOW_COPY_AND_ASSIGN(EocdRecord); -} __attribute__((packed)); - -// A structure representing the fixed length fields for a single -// record in the central directory of the archive. In addition to -// the fixed length fields listed here, each central directory -// record contains a variable length "file_name" and "extra_field" -// whose lengths are given by |file_name_length| and |extra_field_length| -// respectively. -struct CentralDirectoryRecord { - static const uint32_t kSignature = 0x02014b50; - - // The start of record signature. Must be |kSignature|. - uint32_t record_signature; - // Source tool version. Top byte gives source OS. - uint16_t version_made_by; - // Tool version. Ignored by this implementation. - uint16_t version_needed; - // The "general purpose bit flags" for this entry. The only - // flag value that we currently check for is the "data descriptor" - // flag. - uint16_t gpb_flags; - // The compression method for this entry, one of |kCompressStored| - // and |kCompressDeflated|. - uint16_t compression_method; - // The file modification time and date for this entry. - uint16_t last_mod_time; - uint16_t last_mod_date; - // The CRC-32 checksum for this entry. - uint32_t crc32; - // The compressed size (in bytes) of this entry. - uint32_t compressed_size; - // The uncompressed size (in bytes) of this entry. - uint32_t uncompressed_size; - // The length of the entry file name in bytes. The file name - // will appear immediately after this record. - uint16_t file_name_length; - // The length of the extra field info (in bytes). This data - // will appear immediately after the entry file name. - uint16_t extra_field_length; - // The length of the entry comment (in bytes). This data will - // appear immediately after the extra field. - uint16_t comment_length; - // The start disk for this entry. Ignored by this implementation). - uint16_t file_start_disk; - // File attributes. Ignored by this implementation. - uint16_t internal_file_attributes; - // File attributes. For archives created on Unix, the top bits are the mode. - uint32_t external_file_attributes; - // The offset to the local file header for this entry, from the - // beginning of this archive. - uint32_t local_file_header_offset; - - private: - CentralDirectoryRecord() = default; - DISALLOW_COPY_AND_ASSIGN(CentralDirectoryRecord); -} __attribute__((packed)); - -// The local file header for a given entry. This duplicates information -// present in the central directory of the archive. It is an error for -// the information here to be different from the central directory -// information for a given entry. -struct LocalFileHeader { - static const uint32_t kSignature = 0x04034b50; - - // The local file header signature, must be |kSignature|. - uint32_t lfh_signature; - // Tool version. Ignored by this implementation. - uint16_t version_needed; - // The "general purpose bit flags" for this entry. The only - // flag value that we currently check for is the "data descriptor" - // flag. - uint16_t gpb_flags; - // The compression method for this entry, one of |kCompressStored| - // and |kCompressDeflated|. - uint16_t compression_method; - // The file modification time and date for this entry. - uint16_t last_mod_time; - uint16_t last_mod_date; - // The CRC-32 checksum for this entry. - uint32_t crc32; - // The compressed size (in bytes) of this entry. - uint32_t compressed_size; - // The uncompressed size (in bytes) of this entry. - uint32_t uncompressed_size; - // The length of the entry file name in bytes. The file name - // will appear immediately after this record. - uint16_t file_name_length; - // The length of the extra field info (in bytes). This data - // will appear immediately after the entry file name. - uint16_t extra_field_length; - - private: - LocalFileHeader() = default; - DISALLOW_COPY_AND_ASSIGN(LocalFileHeader); -} __attribute__((packed)); - -struct DataDescriptor { - // The *optional* data descriptor start signature. - static const uint32_t kOptSignature = 0x08074b50; - - // CRC-32 checksum of the entry. - uint32_t crc32; - - // For ZIP64 format archives, the compressed and uncompressed sizes are 8 - // bytes each. Also, the ZIP64 format MAY be used regardless of the size - // of a file. When extracting, if the zip64 extended information extra field - // is present for the file the compressed and uncompressed sizes will be 8 - // byte values. - - // Compressed size of the entry, the field can be either 4 bytes or 8 bytes - // in the zip file. - uint64_t compressed_size; - // Uncompressed size of the entry, the field can be either 4 bytes or 8 bytes - // in the zip file. - uint64_t uncompressed_size; - - private: - DataDescriptor() = default; - DISALLOW_COPY_AND_ASSIGN(DataDescriptor); -}; - -// The zip64 end of central directory locator helps to find the zip64 EOCD. -struct Zip64EocdLocator { - static constexpr uint32_t kSignature = 0x07064b50; - - // The signature of zip64 eocd locator, must be |kSignature| - uint32_t locator_signature; - // The start disk of the zip64 eocd. This implementation assumes that each - // archive spans a single disk only. - uint32_t eocd_start_disk; - // The offset offset of the zip64 end of central directory record. - uint64_t zip64_eocd_offset; - // The total number of disks. This implementation assumes that each archive - // spans a single disk only. - uint32_t num_of_disks; - - private: - Zip64EocdLocator() = default; - DISALLOW_COPY_AND_ASSIGN(Zip64EocdLocator); -} __attribute__((packed)); - -// The optional zip64 EOCD. If one of the fields in the end of central directory -// record is too small to hold required data, the field SHOULD be set to -1 -// (0xFFFF or 0xFFFFFFFF) and the ZIP64 format record SHOULD be created. -struct Zip64EocdRecord { - static constexpr uint32_t kSignature = 0x06064b50; - - // The signature of zip64 eocd record, must be |kSignature| - uint32_t record_signature; - // Size of zip64 end of central directory record. It SHOULD be the size of the - // remaining record and SHOULD NOT include the leading 12 bytes. - uint64_t record_size; - // The version of the tool that make this archive. - uint16_t version_made_by; - // Tool version needed to extract this archive. - uint16_t version_needed; - // Number of this disk. - uint32_t disk_num; - // Number of the disk with the start of the central directory. - uint32_t cd_start_disk; - // Total number of entries in the central directory on this disk. - // This implementation assumes that each archive spans a single - // disk only. i.e, that num_records_on_disk == num_records. - uint64_t num_records_on_disk; - // The total number of central directory records. - uint64_t num_records; - // The size of the central directory in bytes. - uint64_t cd_size; - // The offset of the start of the central directory, relative to the start of - // the file. - uint64_t cd_start_offset; - - private: - Zip64EocdRecord() = default; - DISALLOW_COPY_AND_ASSIGN(Zip64EocdRecord); -} __attribute__((packed)); - -// The possible contents of the Zip64 Extended Information Extra Field. It may appear in -// the 'extra' field of a central directory record or local file header. The order of -// the fields in the zip64 extended information record is fixed, but the fields MUST -// only appear if the corresponding local or central directory record field is set to -// 0xFFFF or 0xFFFFFFFF. And this entry in the Local header MUST include BOTH original -// and compressed file size fields. -struct Zip64ExtendedInfo { - static constexpr uint16_t kHeaderId = 0x0001; - // The header tag for this 'extra' block, should be |kHeaderId|. - uint16_t header_id; - // The size in bytes of the remaining data (excluding the top 4 bytes). - uint16_t data_size; - // Size in bytes of the uncompressed file. - std::optional uncompressed_file_size; - // Size in bytes of the compressed file. - std::optional compressed_file_size; - // Local file header offset relative to the start of the zip file. - std::optional local_header_offset; - - // This implementation assumes that each archive spans a single disk only. So - // the disk_number is not used. - // uint32_t disk_num; - private: - Zip64ExtendedInfo() = default; - DISALLOW_COPY_AND_ASSIGN(Zip64ExtendedInfo); -}; - -// mask value that signifies that the entry has a DD -static const uint32_t kGPBDDFlagMask = 0x0008; - -// The maximum size of a central directory or a file -// comment in bytes. -static const uint32_t kMaxCommentLen = 65535; - -#endif /* LIBZIPARCHIVE_ZIPARCHIVECOMMON_H_ */ diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h deleted file mode 100644 index 4b43cba67..000000000 --- a/libziparchive/zip_archive_private.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include -#include -#include - -#include -#include -#include - -#include "android-base/macros.h" -#include "android-base/mapped_file.h" -#include "android-base/memory.h" -#include "zip_cd_entry_map.h" -#include "zip_error.h" - -class MappedZipFile { - public: - explicit MappedZipFile(const int fd) - : has_fd_(true), fd_(fd), fd_offset_(0), base_ptr_(nullptr), data_length_(-1) {} - - explicit MappedZipFile(const int fd, off64_t length, off64_t offset) - : has_fd_(true), fd_(fd), fd_offset_(offset), base_ptr_(nullptr), data_length_(length) {} - - explicit MappedZipFile(const void* address, size_t length) - : has_fd_(false), fd_(-1), fd_offset_(0), base_ptr_(address), - data_length_(static_cast(length)) {} - - bool HasFd() const { return has_fd_; } - - int GetFileDescriptor() const; - - const void* GetBasePtr() const; - - off64_t GetFileOffset() const; - - off64_t GetFileLength() const; - - bool ReadAtOffset(uint8_t* buf, size_t len, off64_t off) const; - - private: - // If has_fd_ is true, fd is valid and we'll read contents of a zip archive - // from the file. Otherwise, we're opening the archive from a memory mapped - // file. In that case, base_ptr_ points to the start of the memory region and - // data_length_ defines the file length. - const bool has_fd_; - - const int fd_; - const off64_t fd_offset_; - - const void* const base_ptr_; - mutable off64_t data_length_; -}; - -class CentralDirectory { - public: - CentralDirectory(void) : base_ptr_(nullptr), length_(0) {} - - const uint8_t* GetBasePtr() const { return base_ptr_; } - - size_t GetMapLength() const { return length_; } - - void Initialize(const void* map_base_ptr, off64_t cd_start_offset, size_t cd_size); - - private: - const uint8_t* base_ptr_; - size_t length_; -}; - -struct ZipArchive { - // open Zip archive - mutable MappedZipFile mapped_zip; - const bool close_file; - - // mapped central directory area - off64_t directory_offset; - CentralDirectory central_directory; - std::unique_ptr directory_map; - - // number of entries in the Zip archive - uint64_t num_entries; - std::unique_ptr cd_entry_map; - - ZipArchive(MappedZipFile&& map, bool assume_ownership); - ZipArchive(const void* address, size_t length); - ~ZipArchive(); - - bool InitializeCentralDirectory(off64_t cd_start_offset, size_t cd_size); -}; - -int32_t ExtractToWriter(ZipArchiveHandle handle, const ZipEntry64* entry, - zip_archive::Writer* writer); - -// Reads the unaligned data of type |T| and auto increment the offset. -template -static T ConsumeUnaligned(uint8_t** address) { - auto ret = android::base::get_unaligned(*address); - *address += sizeof(T); - return ret; -} - -// Writes the unaligned data of type |T| and auto increment the offset. -template -void EmitUnaligned(uint8_t** address, T data) { - android::base::put_unaligned(*address, data); - *address += sizeof(T); -} diff --git a/libziparchive/zip_archive_stream_entry.cc b/libziparchive/zip_archive_stream_entry.cc deleted file mode 100644 index 1ec95b6eb..000000000 --- a/libziparchive/zip_archive_stream_entry.cc +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "ZIPARCHIVE" - -// Read-only stream access to Zip Archive entries. -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include "zip_archive_private.h" - -static constexpr size_t kBufSize = 65535; - -bool ZipArchiveStreamEntry::Init(const ZipEntry& entry) { - crc32_ = entry.crc32; - offset_ = entry.offset; - return true; -} - -class ZipArchiveStreamEntryUncompressed : public ZipArchiveStreamEntry { - public: - explicit ZipArchiveStreamEntryUncompressed(ZipArchiveHandle handle) - : ZipArchiveStreamEntry(handle) {} - virtual ~ZipArchiveStreamEntryUncompressed() {} - - const std::vector* Read() override; - - bool Verify() override; - - protected: - bool Init(const ZipEntry& entry) override; - - uint32_t length_ = 0u; - - private: - std::vector data_; - uint32_t computed_crc32_ = 0u; -}; - -bool ZipArchiveStreamEntryUncompressed::Init(const ZipEntry& entry) { - if (!ZipArchiveStreamEntry::Init(entry)) { - return false; - } - - length_ = entry.uncompressed_length; - - data_.resize(kBufSize); - computed_crc32_ = 0; - - return true; -} - -const std::vector* ZipArchiveStreamEntryUncompressed::Read() { - // Simple sanity check. The vector should *only* be handled by this code. A caller - // should not const-cast and modify the capacity. This may invalidate next_out. - // - // Note: it would be better to store the results of data() across Read calls. - CHECK_EQ(data_.capacity(), kBufSize); - - if (length_ == 0) { - return nullptr; - } - - size_t bytes = (length_ > data_.size()) ? data_.size() : length_; - ZipArchive* archive = reinterpret_cast(handle_); - errno = 0; - if (!archive->mapped_zip.ReadAtOffset(data_.data(), bytes, offset_)) { - if (errno != 0) { - ALOGE("Error reading from archive fd: %s", strerror(errno)); - } else { - ALOGE("Short read of zip file, possibly corrupted zip?"); - } - length_ = 0; - return nullptr; - } - - if (bytes < data_.size()) { - data_.resize(bytes); - } - computed_crc32_ = static_cast( - crc32(computed_crc32_, data_.data(), static_cast(data_.size()))); - length_ -= bytes; - offset_ += bytes; - return &data_; -} - -bool ZipArchiveStreamEntryUncompressed::Verify() { - return length_ == 0 && crc32_ == computed_crc32_; -} - -class ZipArchiveStreamEntryCompressed : public ZipArchiveStreamEntry { - public: - explicit ZipArchiveStreamEntryCompressed(ZipArchiveHandle handle) - : ZipArchiveStreamEntry(handle) {} - virtual ~ZipArchiveStreamEntryCompressed(); - - const std::vector* Read() override; - - bool Verify() override; - - protected: - bool Init(const ZipEntry& entry) override; - - private: - bool z_stream_init_ = false; - z_stream z_stream_; - std::vector in_; - std::vector out_; - uint32_t uncompressed_length_ = 0u; - uint32_t compressed_length_ = 0u; - uint32_t computed_crc32_ = 0u; -}; - -// This method is using libz macros with old-style-casts -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -static inline int zlib_inflateInit2(z_stream* stream, int window_bits) { - return inflateInit2(stream, window_bits); -} -#pragma GCC diagnostic pop - -bool ZipArchiveStreamEntryCompressed::Init(const ZipEntry& entry) { - if (!ZipArchiveStreamEntry::Init(entry)) { - return false; - } - - // Initialize the zlib stream struct. - memset(&z_stream_, 0, sizeof(z_stream_)); - z_stream_.zalloc = Z_NULL; - z_stream_.zfree = Z_NULL; - z_stream_.opaque = Z_NULL; - z_stream_.next_in = nullptr; - z_stream_.avail_in = 0; - z_stream_.avail_out = 0; - z_stream_.data_type = Z_UNKNOWN; - - // Use the undocumented "negative window bits" feature to tell zlib - // that there's no zlib header waiting for it. - int zerr = zlib_inflateInit2(&z_stream_, -MAX_WBITS); - if (zerr != Z_OK) { - if (zerr == Z_VERSION_ERROR) { - ALOGE("Installed zlib is not compatible with linked version (%s)", ZLIB_VERSION); - } else { - ALOGE("Call to inflateInit2 failed (zerr=%d)", zerr); - } - - return false; - } - - z_stream_init_ = true; - - uncompressed_length_ = entry.uncompressed_length; - compressed_length_ = entry.compressed_length; - - out_.resize(kBufSize); - in_.resize(kBufSize); - - computed_crc32_ = 0; - - return true; -} - -ZipArchiveStreamEntryCompressed::~ZipArchiveStreamEntryCompressed() { - if (z_stream_init_) { - inflateEnd(&z_stream_); - z_stream_init_ = false; - } -} - -bool ZipArchiveStreamEntryCompressed::Verify() { - return z_stream_init_ && uncompressed_length_ == 0 && compressed_length_ == 0 && - crc32_ == computed_crc32_; -} - -const std::vector* ZipArchiveStreamEntryCompressed::Read() { - // Simple sanity check. The vector should *only* be handled by this code. A caller - // should not const-cast and modify the capacity. This may invalidate next_out. - // - // Note: it would be better to store the results of data() across Read calls. - CHECK_EQ(out_.capacity(), kBufSize); - - if (z_stream_.avail_out == 0) { - z_stream_.next_out = out_.data(); - z_stream_.avail_out = static_cast(out_.size()); - ; - } - - while (true) { - if (z_stream_.avail_in == 0) { - if (compressed_length_ == 0) { - return nullptr; - } - DCHECK_LE(in_.size(), std::numeric_limits::max()); // Should be buf size = 64k. - uint32_t bytes = (compressed_length_ > in_.size()) ? static_cast(in_.size()) - : compressed_length_; - ZipArchive* archive = reinterpret_cast(handle_); - errno = 0; - if (!archive->mapped_zip.ReadAtOffset(in_.data(), bytes, offset_)) { - if (errno != 0) { - ALOGE("Error reading from archive fd: %s", strerror(errno)); - } else { - ALOGE("Short read of zip file, possibly corrupted zip?"); - } - return nullptr; - } - - compressed_length_ -= bytes; - offset_ += bytes; - z_stream_.next_in = in_.data(); - z_stream_.avail_in = bytes; - } - - int zerr = inflate(&z_stream_, Z_NO_FLUSH); - if (zerr != Z_OK && zerr != Z_STREAM_END) { - ALOGE("inflate zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)", zerr, z_stream_.next_in, - z_stream_.avail_in, z_stream_.next_out, z_stream_.avail_out); - return nullptr; - } - - if (z_stream_.avail_out == 0) { - uncompressed_length_ -= out_.size(); - computed_crc32_ = static_cast( - crc32(computed_crc32_, out_.data(), static_cast(out_.size()))); - return &out_; - } - if (zerr == Z_STREAM_END) { - if (z_stream_.avail_out != 0) { - // Resize the vector down to the actual size of the data. - out_.resize(out_.size() - z_stream_.avail_out); - computed_crc32_ = static_cast( - crc32(computed_crc32_, out_.data(), static_cast(out_.size()))); - uncompressed_length_ -= out_.size(); - return &out_; - } - return nullptr; - } - } - return nullptr; -} - -class ZipArchiveStreamEntryRawCompressed : public ZipArchiveStreamEntryUncompressed { - public: - explicit ZipArchiveStreamEntryRawCompressed(ZipArchiveHandle handle) - : ZipArchiveStreamEntryUncompressed(handle) {} - virtual ~ZipArchiveStreamEntryRawCompressed() {} - - bool Verify() override; - - protected: - bool Init(const ZipEntry& entry) override; -}; - -bool ZipArchiveStreamEntryRawCompressed::Init(const ZipEntry& entry) { - if (!ZipArchiveStreamEntryUncompressed::Init(entry)) { - return false; - } - length_ = entry.compressed_length; - - return true; -} - -bool ZipArchiveStreamEntryRawCompressed::Verify() { - return length_ == 0; -} - -ZipArchiveStreamEntry* ZipArchiveStreamEntry::Create(ZipArchiveHandle handle, - const ZipEntry& entry) { - ZipArchiveStreamEntry* stream = nullptr; - if (entry.method != kCompressStored) { - stream = new ZipArchiveStreamEntryCompressed(handle); - } else { - stream = new ZipArchiveStreamEntryUncompressed(handle); - } - if (stream && !stream->Init(entry)) { - delete stream; - stream = nullptr; - } - - return stream; -} - -ZipArchiveStreamEntry* ZipArchiveStreamEntry::CreateRaw(ZipArchiveHandle handle, - const ZipEntry& entry) { - ZipArchiveStreamEntry* stream = nullptr; - if (entry.method == kCompressStored) { - // Not compressed, don't need to do anything special. - stream = new ZipArchiveStreamEntryUncompressed(handle); - } else { - stream = new ZipArchiveStreamEntryRawCompressed(handle); - } - if (stream && !stream->Init(entry)) { - delete stream; - stream = nullptr; - } - return stream; -} diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc deleted file mode 100644 index f5429be0d..000000000 --- a/libziparchive/zip_archive_test.cc +++ /dev/null @@ -1,1327 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zip_archive_common.h" -#include "zip_archive_private.h" - -static std::string test_data_dir = android::base::GetExecutableDirectory() + "/testdata"; - -static const std::string kValidZip = "valid.zip"; -static const std::string kLargeZip = "large.zip"; -static const std::string kBadCrcZip = "bad_crc.zip"; - -static const std::vector kATxtContents{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', - 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\n'}; - -static const std::vector kATxtContentsCompressed{'K', 'L', 'J', 'N', 'I', 'M', 'K', - 207, 'H', 132, 210, '\\', '\0'}; - -static const std::vector kBTxtContents{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\n'}; - -static int32_t OpenArchiveWrapper(const std::string& name, ZipArchiveHandle* handle) { - const std::string abs_path = test_data_dir + "/" + name; - return OpenArchive(abs_path.c_str(), handle); -} - -class CdEntryMapTest : public ::testing::Test { - protected: - void SetUp() override { - names_ = { - "a.txt", "b.txt", "b/", "b/c.txt", "b/d.txt", - }; - separator_ = "separator"; - header_ = "metadata"; - joined_names_ = header_ + android::base::Join(names_, separator_); - base_ptr_ = reinterpret_cast(&joined_names_[0]); - - entry_maps_.emplace_back(CdEntryMapZip32::Create(static_cast(names_.size()))); - entry_maps_.emplace_back(CdEntryMapZip64::Create()); - for (auto& cd_map : entry_maps_) { - ASSERT_NE(nullptr, cd_map); - size_t offset = header_.size(); - for (const auto& name : names_) { - auto status = cd_map->AddToMap( - std::string_view{joined_names_.c_str() + offset, name.size()}, base_ptr_); - ASSERT_EQ(0, status); - offset += name.size() + separator_.size(); - } - } - } - - std::vector names_; - // A continuous region of memory serves as a mock of the central directory. - std::string joined_names_; - // We expect some metadata at the beginning of the central directory and between filenames. - std::string header_; - std::string separator_; - - std::vector> entry_maps_; - uint8_t* base_ptr_{nullptr}; // Points to the start of the central directory. -}; - -TEST_F(CdEntryMapTest, AddDuplicatedEntry) { - for (auto& cd_map : entry_maps_) { - std::string_view name = "b.txt"; - ASSERT_NE(0, cd_map->AddToMap(name, base_ptr_)); - } -} - -TEST_F(CdEntryMapTest, FindEntry) { - for (auto& cd_map : entry_maps_) { - uint64_t expected_offset = header_.size(); - for (const auto& name : names_) { - auto [status, offset] = cd_map->GetCdEntryOffset(name, base_ptr_); - ASSERT_EQ(status, kSuccess); - ASSERT_EQ(offset, expected_offset); - expected_offset += name.size() + separator_.size(); - } - } -} - -TEST_F(CdEntryMapTest, Iteration) { - std::set expected(names_.begin(), names_.end()); - for (auto& cd_map : entry_maps_) { - cd_map->ResetIteration(); - std::set entry_set; - auto ret = cd_map->Next(base_ptr_); - while (ret != std::pair{}) { - auto [it, insert_status] = entry_set.insert(ret.first); - ASSERT_TRUE(insert_status); - ret = cd_map->Next(base_ptr_); - } - ASSERT_EQ(expected, entry_set); - } -} - -TEST(ziparchive, Open) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - CloseArchive(handle); - - ASSERT_EQ(kInvalidEntryName, OpenArchiveWrapper("bad_filename.zip", &handle)); - CloseArchive(handle); -} - -TEST(ziparchive, OutOfBound) { - ZipArchiveHandle handle; - ASSERT_EQ(kInvalidOffset, OpenArchiveWrapper("crash.apk", &handle)); - CloseArchive(handle); -} - -TEST(ziparchive, EmptyArchive) { - ZipArchiveHandle handle; - ASSERT_EQ(kEmptyArchive, OpenArchiveWrapper("empty.zip", &handle)); - CloseArchive(handle); -} - -TEST(ziparchive, ZeroSizeCentralDirectory) { - ZipArchiveHandle handle; - ASSERT_EQ(kInvalidFile, OpenArchiveWrapper("zero-size-cd.zip", &handle)); - CloseArchive(handle); -} - -TEST(ziparchive, OpenMissing) { - ZipArchiveHandle handle; - ASSERT_NE(0, OpenArchiveWrapper("missing.zip", &handle)); - - // Confirm the file descriptor is not going to be mistaken for a valid one. - ASSERT_EQ(-1, GetFileDescriptor(handle)); -} - -TEST(ziparchive, OpenAssumeFdOwnership) { - int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY | O_BINARY); - ASSERT_NE(-1, fd); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd, "OpenWithAssumeFdOwnership", &handle)); - CloseArchive(handle); - ASSERT_EQ(-1, lseek(fd, 0, SEEK_SET)); - ASSERT_EQ(EBADF, errno); -} - -TEST(ziparchive, OpenDoNotAssumeFdOwnership) { - int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY | O_BINARY); - ASSERT_NE(-1, fd); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd, "OpenWithAssumeFdOwnership", &handle, false)); - CloseArchive(handle); - ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)); - close(fd); -} - -TEST(ziparchive, OpenAssumeFdRangeOwnership) { - int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY | O_BINARY); - ASSERT_NE(-1, fd); - const off64_t length = lseek64(fd, 0, SEEK_END); - ASSERT_NE(-1, length); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFdRange(fd, "OpenWithAssumeFdOwnership", &handle, - static_cast(length), 0)); - CloseArchive(handle); - ASSERT_EQ(-1, lseek(fd, 0, SEEK_SET)); - ASSERT_EQ(EBADF, errno); -} - -TEST(ziparchive, OpenDoNotAssumeFdRangeOwnership) { - int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY | O_BINARY); - ASSERT_NE(-1, fd); - const off64_t length = lseek(fd, 0, SEEK_END); - ASSERT_NE(-1, length); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFdRange(fd, "OpenWithAssumeFdOwnership", &handle, - static_cast(length), 0, false)); - CloseArchive(handle); - ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)); - close(fd); -} - -TEST(ziparchive, Iteration_std_string_view) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - - void* iteration_cookie; - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie)); - - ZipEntry64 data; - std::vector names; - std::string_view name; - while (Next(iteration_cookie, &data, &name) == 0) names.push_back(name); - - // Assert that the names are as expected. - std::vector expected_names{"a.txt", "b.txt", "b/", "b/c.txt", "b/d.txt"}; - std::sort(names.begin(), names.end()); - ASSERT_EQ(expected_names, names); - - CloseArchive(handle); -} - -static void AssertIterationNames(void* iteration_cookie, - const std::vector& expected_names_sorted) { - ZipEntry64 data; - std::vector names; - std::string_view name; - for (size_t i = 0; i < expected_names_sorted.size(); ++i) { - ASSERT_EQ(0, Next(iteration_cookie, &data, &name)); - names.push_back(std::string(name)); - } - // End of iteration. - ASSERT_EQ(-1, Next(iteration_cookie, &data, &name)); - // Assert that the names are as expected. - std::sort(names.begin(), names.end()); - ASSERT_EQ(expected_names_sorted, names); -} - -static void AssertIterationOrder(const std::string_view prefix, const std::string_view suffix, - const std::vector& expected_names_sorted) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - - void* iteration_cookie; - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, prefix, suffix)); - AssertIterationNames(iteration_cookie, expected_names_sorted); - CloseArchive(handle); -} - -static void AssertIterationOrderWithMatcher(std::function matcher, - const std::vector& expected_names_sorted) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - - void* iteration_cookie; - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, matcher)); - AssertIterationNames(iteration_cookie, expected_names_sorted); - CloseArchive(handle); -} - -TEST(ziparchive, Iteration) { - static const std::vector kExpectedMatchesSorted = {"a.txt", "b.txt", "b/", "b/c.txt", - "b/d.txt"}; - - AssertIterationOrder("", "", kExpectedMatchesSorted); -} - -TEST(ziparchive, IterationWithPrefix) { - static const std::vector kExpectedMatchesSorted = {"b/", "b/c.txt", "b/d.txt"}; - - AssertIterationOrder("b/", "", kExpectedMatchesSorted); -} - -TEST(ziparchive, IterationWithSuffix) { - static const std::vector kExpectedMatchesSorted = {"a.txt", "b.txt", "b/c.txt", - "b/d.txt"}; - - AssertIterationOrder("", ".txt", kExpectedMatchesSorted); -} - -TEST(ziparchive, IterationWithPrefixAndSuffix) { - static const std::vector kExpectedMatchesSorted = {"b.txt", "b/c.txt", "b/d.txt"}; - - AssertIterationOrder("b", ".txt", kExpectedMatchesSorted); -} - -TEST(ziparchive, IterationWithAdditionalMatchesExactly) { - static const std::vector kExpectedMatchesSorted = {"a.txt"}; - auto matcher = [](std::string_view name) { return name == "a.txt"; }; - AssertIterationOrderWithMatcher(matcher, kExpectedMatchesSorted); -} - -TEST(ziparchive, IterationWithAdditionalMatchesWithSuffix) { - static const std::vector kExpectedMatchesSorted = {"a.txt", "b.txt", "b/c.txt", - "b/d.txt"}; - auto matcher = [](std::string_view name) { - return name == "a.txt" || android::base::EndsWith(name, ".txt"); - }; - AssertIterationOrderWithMatcher(matcher, kExpectedMatchesSorted); -} - -TEST(ziparchive, IterationWithAdditionalMatchesWithPrefixAndSuffix) { - static const std::vector kExpectedMatchesSorted = {"a.txt", "b/c.txt", "b/d.txt"}; - auto matcher = [](std::string_view name) { - return name == "a.txt" || - (android::base::EndsWith(name, ".txt") && android::base::StartsWith(name, "b/")); - }; - AssertIterationOrderWithMatcher(matcher, kExpectedMatchesSorted); -} - -TEST(ziparchive, IterationWithBadPrefixAndSuffix) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - - void* iteration_cookie; - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, "x", "y")); - - ZipEntry64 data; - std::string_view name; - - // End of iteration. - ASSERT_EQ(-1, Next(iteration_cookie, &data, &name)); - - CloseArchive(handle); -} - -TEST(ziparchive, FindEntry) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - - ZipEntry64 data; - ASSERT_EQ(0, FindEntry(handle, "a.txt", &data)); - - // Known facts about a.txt, from zipinfo -v. - ASSERT_EQ(63, data.offset); - ASSERT_EQ(kCompressDeflated, data.method); - ASSERT_EQ(17u, data.uncompressed_length); - ASSERT_EQ(13u, data.compressed_length); - ASSERT_EQ(0x950821c5, data.crc32); - ASSERT_EQ(static_cast(0x438a8005), data.mod_time); - - // An entry that doesn't exist. Should be a negative return code. - ASSERT_LT(FindEntry(handle, "this file does not exist", &data), 0); - - CloseArchive(handle); -} - -TEST(ziparchive, FindEntry_empty) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - - ZipEntry64 data; - ASSERT_EQ(kInvalidEntryName, FindEntry(handle, "", &data)); - - CloseArchive(handle); -} - -TEST(ziparchive, FindEntry_too_long) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - - std::string very_long_name(65536, 'x'); - ZipEntry64 data; - ASSERT_EQ(kInvalidEntryName, FindEntry(handle, very_long_name, &data)); - - CloseArchive(handle); -} - -TEST(ziparchive, TestInvalidDeclaredLength) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper("declaredlength.zip", &handle)); - - void* iteration_cookie; - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie)); - - std::string_view name; - ZipEntry64 data; - - ASSERT_EQ(Next(iteration_cookie, &data, &name), 0); - ASSERT_EQ(Next(iteration_cookie, &data, &name), 0); - - CloseArchive(handle); -} - -TEST(ziparchive, OpenArchiveFdRange) { - TemporaryFile tmp_file; - ASSERT_NE(-1, tmp_file.fd); - - const std::string leading_garbage(21, 'x'); - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, leading_garbage.c_str(), - leading_garbage.size())); - - std::string valid_content; - ASSERT_TRUE(android::base::ReadFileToString(test_data_dir + "/" + kValidZip, &valid_content)); - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, valid_content.c_str(), valid_content.size())); - - const std::string ending_garbage(42, 'x'); - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, ending_garbage.c_str(), - ending_garbage.size())); - - ZipArchiveHandle handle; - ASSERT_EQ(0, lseek(tmp_file.fd, 0, SEEK_SET)); - ASSERT_EQ(0, OpenArchiveFdRange(tmp_file.fd, "OpenArchiveFdRange", &handle, - valid_content.size(), - static_cast(leading_garbage.size()))); - - // An entry that's deflated. - ZipEntry64 data; - ASSERT_EQ(0, FindEntry(handle, "a.txt", &data)); - const auto a_size = static_cast(data.uncompressed_length); - ASSERT_EQ(a_size, kATxtContents.size()); - auto buffer = std::unique_ptr(new uint8_t[a_size]); - ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer.get(), a_size)); - ASSERT_EQ(0, memcmp(buffer.get(), kATxtContents.data(), a_size)); - - // An entry that's stored. - ASSERT_EQ(0, FindEntry(handle, "b.txt", &data)); - const auto b_size = static_cast(data.uncompressed_length); - ASSERT_EQ(b_size, kBTxtContents.size()); - buffer = std::unique_ptr(new uint8_t[b_size]); - ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer.get(), b_size)); - ASSERT_EQ(0, memcmp(buffer.get(), kBTxtContents.data(), b_size)); - - CloseArchive(handle); -} - -TEST(ziparchive, ExtractToMemory) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - - // An entry that's deflated. - ZipEntry64 data; - ASSERT_EQ(0, FindEntry(handle, "a.txt", &data)); - const auto a_size = static_cast(data.uncompressed_length); - ASSERT_EQ(a_size, kATxtContents.size()); - uint8_t* buffer = new uint8_t[a_size]; - ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, a_size)); - ASSERT_EQ(0, memcmp(buffer, kATxtContents.data(), a_size)); - delete[] buffer; - - // An entry that's stored. - ASSERT_EQ(0, FindEntry(handle, "b.txt", &data)); - const auto b_size = static_cast(data.uncompressed_length); - ASSERT_EQ(b_size, kBTxtContents.size()); - buffer = new uint8_t[b_size]; - ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, b_size)); - ASSERT_EQ(0, memcmp(buffer, kBTxtContents.data(), b_size)); - delete[] buffer; - - CloseArchive(handle); -} - -static const uint32_t kEmptyEntriesZip[] = { - 0x04034b50, 0x0000000a, 0x63600000, 0x00004438, 0x00000000, 0x00000000, 0x00090000, - 0x6d65001c, 0x2e797470, 0x55747874, 0x03000954, 0x52e25c13, 0x52e25c24, 0x000b7875, - 0x42890401, 0x88040000, 0x50000013, 0x1e02014b, 0x00000a03, 0x60000000, 0x00443863, - 0x00000000, 0x00000000, 0x09000000, 0x00001800, 0x00000000, 0xa0000000, 0x00000081, - 0x706d6500, 0x742e7974, 0x54557478, 0x13030005, 0x7552e25c, 0x01000b78, 0x00428904, - 0x13880400, 0x4b500000, 0x00000605, 0x00010000, 0x004f0001, 0x00430000, 0x00000000}; - -// This is a zip file containing a single entry (ab.txt) that contains -// 90072 repetitions of the string "ab\n" and has an uncompressed length -// of 270216 bytes. -static const uint16_t kAbZip[] = { - 0x4b50, 0x0403, 0x0014, 0x0000, 0x0008, 0x51d2, 0x4698, 0xc4b0, 0x2cda, 0x011b, 0x0000, 0x1f88, - 0x0004, 0x0006, 0x001c, 0x6261, 0x742e, 0x7478, 0x5455, 0x0009, 0x7c03, 0x3a09, 0x7c55, 0x3a09, - 0x7555, 0x0b78, 0x0100, 0x8904, 0x0042, 0x0400, 0x1388, 0x0000, 0xc2ed, 0x0d31, 0x0000, 0x030c, - 0x7fa0, 0x3b2e, 0x22ff, 0xa2aa, 0x841f, 0x45fc, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, 0x5555, - 0x5555, 0x5555, 0x5555, 0x5555, 0xdd55, 0x502c, 0x014b, 0x1e02, 0x1403, 0x0000, 0x0800, 0xd200, - 0x9851, 0xb046, 0xdac4, 0x1b2c, 0x0001, 0x8800, 0x041f, 0x0600, 0x1800, 0x0000, 0x0000, 0x0100, - 0x0000, 0xa000, 0x0081, 0x0000, 0x6100, 0x2e62, 0x7874, 0x5574, 0x0554, 0x0300, 0x097c, 0x553a, - 0x7875, 0x000b, 0x0401, 0x4289, 0x0000, 0x8804, 0x0013, 0x5000, 0x054b, 0x0006, 0x0000, 0x0100, - 0x0100, 0x4c00, 0x0000, 0x5b00, 0x0001, 0x0000, 0x0000}; - -static const std::string kAbTxtName("ab.txt"); -static const size_t kAbUncompressedSize = 270216; - -TEST(ziparchive, EmptyEntries) { - TemporaryFile tmp_file; - ASSERT_NE(-1, tmp_file.fd); - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, kEmptyEntriesZip, sizeof(kEmptyEntriesZip))); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(tmp_file.fd, "EmptyEntriesTest", &handle, false)); - - ZipEntry64 entry; - ASSERT_EQ(0, FindEntry(handle, "empty.txt", &entry)); - ASSERT_EQ(0u, entry.uncompressed_length); - // Extraction to a 1 byte buffer should succeed. - uint8_t buffer[1]; - ASSERT_EQ(0, ExtractToMemory(handle, &entry, buffer, 1)); - // Extraction to an empty buffer should succeed. - ASSERT_EQ(0, ExtractToMemory(handle, &entry, nullptr, 0)); - - TemporaryFile tmp_output_file; - ASSERT_NE(-1, tmp_output_file.fd); - ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, tmp_output_file.fd)); - - struct stat stat_buf; - ASSERT_EQ(0, fstat(tmp_output_file.fd, &stat_buf)); - ASSERT_EQ(0, stat_buf.st_size); -} - -TEST(ziparchive, EntryLargerThan32K) { - TemporaryFile tmp_file; - ASSERT_NE(-1, tmp_file.fd); - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, reinterpret_cast(kAbZip), - sizeof(kAbZip) - 1)); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(tmp_file.fd, "EntryLargerThan32KTest", &handle, false)); - - ZipEntry64 entry; - ASSERT_EQ(0, FindEntry(handle, kAbTxtName, &entry)); - ASSERT_EQ(kAbUncompressedSize, entry.uncompressed_length); - - // Extract the entry to memory. - std::vector buffer(kAbUncompressedSize); - ASSERT_EQ(0, ExtractToMemory(handle, &entry, &buffer[0], static_cast(buffer.size()))); - - // Extract the entry to a file. - TemporaryFile tmp_output_file; - ASSERT_NE(-1, tmp_output_file.fd); - ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, tmp_output_file.fd)); - - // Make sure the extracted file size is as expected. - struct stat stat_buf; - ASSERT_EQ(0, fstat(tmp_output_file.fd, &stat_buf)); - ASSERT_EQ(kAbUncompressedSize, static_cast(stat_buf.st_size)); - - // Read the file back to a buffer and make sure the contents are - // the same as the memory buffer we extracted directly to. - std::vector file_contents(kAbUncompressedSize); - ASSERT_EQ(0, lseek(tmp_output_file.fd, 0, SEEK_SET)); - ASSERT_TRUE(android::base::ReadFully(tmp_output_file.fd, &file_contents[0], file_contents.size())); - ASSERT_EQ(file_contents, buffer); - - for (int i = 0; i < 90072; ++i) { - const uint8_t* line = &file_contents[0] + (3 * i); - ASSERT_EQ('a', line[0]); - ASSERT_EQ('b', line[1]); - ASSERT_EQ('\n', line[2]); - } -} - -TEST(ziparchive, TrailerAfterEOCD) { - TemporaryFile tmp_file; - ASSERT_NE(-1, tmp_file.fd); - - // Create a file with 8 bytes of random garbage. - static const uint8_t trailer[] = {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'z'}; - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, kEmptyEntriesZip, sizeof(kEmptyEntriesZip))); - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, trailer, sizeof(trailer))); - - ZipArchiveHandle handle; - ASSERT_GT(0, OpenArchiveFd(tmp_file.fd, "EmptyEntriesTest", &handle, false)); -} - -TEST(ziparchive, ExtractToFile) { - TemporaryFile tmp_file; - ASSERT_NE(-1, tmp_file.fd); - const uint8_t data[8] = {'1', '2', '3', '4', '5', '6', '7', '8'}; - const size_t data_size = sizeof(data); - - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, data, data_size)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); - - ZipEntry64 entry; - ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry)); - ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, tmp_file.fd)); - - // Assert that the first 8 bytes of the file haven't been clobbered. - uint8_t read_buffer[data_size]; - ASSERT_EQ(0, lseek(tmp_file.fd, 0, SEEK_SET)); - ASSERT_TRUE(android::base::ReadFully(tmp_file.fd, read_buffer, data_size)); - ASSERT_EQ(0, memcmp(read_buffer, data, data_size)); - - // Assert that the remainder of the file contains the incompressed data. - std::vector uncompressed_data(static_cast(entry.uncompressed_length)); - ASSERT_TRUE(android::base::ReadFully(tmp_file.fd, uncompressed_data.data(), - static_cast(entry.uncompressed_length))); - ASSERT_EQ(0, memcmp(&uncompressed_data[0], kATxtContents.data(), kATxtContents.size())); - - // Assert that the total length of the file is sane - ASSERT_EQ(static_cast(data_size + kATxtContents.size()), - lseek(tmp_file.fd, 0, SEEK_END)); -} - -#if !defined(_WIN32) -TEST(ziparchive, OpenFromMemory) { - const std::string zip_path = test_data_dir + "/dummy-update.zip"; - android::base::unique_fd fd(open(zip_path.c_str(), O_RDONLY | O_BINARY)); - ASSERT_NE(-1, fd); - struct stat sb; - ASSERT_EQ(0, fstat(fd, &sb)); - - // Memory map the file first and open the archive from the memory region. - auto file_map{ - android::base::MappedFile::FromFd(fd, 0, static_cast(sb.st_size), PROT_READ)}; - ZipArchiveHandle handle; - ASSERT_EQ(0, - OpenArchiveFromMemory(file_map->data(), file_map->size(), zip_path.c_str(), &handle)); - - // Assert one entry can be found and extracted correctly. - ZipEntry64 binary_entry; - ASSERT_EQ(0, FindEntry(handle, "META-INF/com/google/android/update-binary", &binary_entry)); - TemporaryFile tmp_binary; - ASSERT_NE(-1, tmp_binary.fd); - ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd)); -} -#endif - -static void ZipArchiveStreamTest(ZipArchiveHandle& handle, const std::string& entry_name, bool raw, - bool verified, ZipEntry* entry, std::vector* read_data) { - ASSERT_EQ(0, FindEntry(handle, entry_name, entry)); - std::unique_ptr stream; - if (raw) { - stream.reset(ZipArchiveStreamEntry::CreateRaw(handle, *entry)); - if (entry->method == kCompressStored) { - read_data->resize(static_cast(entry->uncompressed_length)); - } else { - read_data->resize(static_cast(entry->compressed_length)); - } - } else { - stream.reset(ZipArchiveStreamEntry::Create(handle, *entry)); - read_data->resize(static_cast(entry->uncompressed_length)); - } - uint8_t* read_data_ptr = read_data->data(); - ASSERT_TRUE(stream.get() != nullptr); - const std::vector* data; - uint64_t total_size = 0; - while ((data = stream->Read()) != nullptr) { - total_size += data->size(); - memcpy(read_data_ptr, data->data(), data->size()); - read_data_ptr += data->size(); - } - ASSERT_EQ(verified, stream->Verify()); - ASSERT_EQ(total_size, read_data->size()); -} - -static void ZipArchiveStreamTestUsingContents(const std::string& zip_file, - const std::string& entry_name, - const std::vector& contents, bool raw) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(zip_file, &handle)); - - ZipEntry entry; - std::vector read_data; - ZipArchiveStreamTest(handle, entry_name, raw, true, &entry, &read_data); - - ASSERT_EQ(contents.size(), read_data.size()); - ASSERT_TRUE(memcmp(read_data.data(), contents.data(), read_data.size()) == 0); - - CloseArchive(handle); -} - -static void ZipArchiveStreamTestUsingMemory(const std::string& zip_file, - const std::string& entry_name) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(zip_file, &handle)); - - ZipEntry entry; - std::vector read_data; - ZipArchiveStreamTest(handle, entry_name, false, true, &entry, &read_data); - - std::vector cmp_data(static_cast(entry.uncompressed_length)); - ASSERT_EQ(entry.uncompressed_length, read_data.size()); - ASSERT_EQ( - 0, ExtractToMemory(handle, &entry, cmp_data.data(), static_cast(cmp_data.size()))); - ASSERT_TRUE(memcmp(read_data.data(), cmp_data.data(), read_data.size()) == 0); - - CloseArchive(handle); -} - -TEST(ziparchive, StreamCompressed) { - ZipArchiveStreamTestUsingContents(kValidZip, "a.txt", kATxtContents, false); -} - -TEST(ziparchive, StreamUncompressed) { - ZipArchiveStreamTestUsingContents(kValidZip, "b.txt", kBTxtContents, false); -} - -TEST(ziparchive, StreamRawCompressed) { - ZipArchiveStreamTestUsingContents(kValidZip, "a.txt", kATxtContentsCompressed, true); -} - -TEST(ziparchive, StreamRawUncompressed) { - ZipArchiveStreamTestUsingContents(kValidZip, "b.txt", kBTxtContents, true); -} - -TEST(ziparchive, StreamLargeCompressed) { - ZipArchiveStreamTestUsingMemory(kLargeZip, "compress.txt"); -} - -TEST(ziparchive, StreamLargeUncompressed) { - ZipArchiveStreamTestUsingMemory(kLargeZip, "uncompress.txt"); -} - -TEST(ziparchive, StreamCompressedBadCrc) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kBadCrcZip, &handle)); - - ZipEntry entry; - std::vector read_data; - ZipArchiveStreamTest(handle, "a.txt", false, false, &entry, &read_data); - - CloseArchive(handle); -} - -TEST(ziparchive, StreamUncompressedBadCrc) { - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveWrapper(kBadCrcZip, &handle)); - - ZipEntry entry; - std::vector read_data; - ZipArchiveStreamTest(handle, "b.txt", false, false, &entry, &read_data); - - CloseArchive(handle); -} - -// Generated using the following Java program: -// public static void main(String[] foo) throws Exception { -// FileOutputStream fos = new -// FileOutputStream("/tmp/data_descriptor.zip"); -// ZipOutputStream zos = new ZipOutputStream(fos); -// ZipEntry64 ze = new ZipEntry64("name"); -// ze.setMethod(ZipEntry64.DEFLATED); -// zos.putNextEntry(ze); -// zos.write("abdcdefghijk".getBytes()); -// zos.closeEntry(); -// zos.close(); -// } -// -// cat /tmp/data_descriptor.zip | xxd -i -// -static const std::vector kDataDescriptorZipFile{ - 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x08, 0x08, 0x00, 0x30, 0x59, 0xce, 0x4a, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6e, 0x61, - 0x6d, 0x65, 0x4b, 0x4c, 0x4a, 0x49, 0x4e, 0x49, 0x4d, 0x4b, 0xcf, 0xc8, 0xcc, 0xca, 0x06, 0x00, - //[sig---------------], [crc32---------------], [csize---------------], [size----------------] - 0x50, 0x4b, 0x07, 0x08, 0x3d, 0x4e, 0x0e, 0xf9, 0x0e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x50, 0x4b, 0x01, 0x02, 0x14, 0x00, 0x14, 0x00, 0x08, 0x08, 0x08, 0x00, 0x30, 0x59, 0xce, 0x4a, - 0x3d, 0x4e, 0x0e, 0xf9, 0x0e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x61, - 0x6d, 0x65, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x32, 0x00, - 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00}; - -// The offsets of the data descriptor in this file, so we can mess with -// them later in the test. -static constexpr uint32_t kDataDescriptorOffset = 48; -static constexpr uint32_t kCSizeOffset = kDataDescriptorOffset + 8; -static constexpr uint32_t kSizeOffset = kCSizeOffset + 4; - -static void ExtractEntryToMemory(const std::vector& zip_data, - std::vector* entry_out, int32_t* error_code_out) { - TemporaryFile tmp_file; - ASSERT_NE(-1, tmp_file.fd); - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, &zip_data[0], zip_data.size())); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(tmp_file.fd, "ExtractEntryToMemory", &handle, false)); - - // This function expects a variant of kDataDescriptorZipFile, for look for - // an entry whose name is "name" and whose size is 12 (contents = - // "abdcdefghijk"). - ZipEntry64 entry; - ASSERT_EQ(0, FindEntry(handle, "name", &entry)); - ASSERT_EQ(12u, entry.uncompressed_length); - - entry_out->resize(12); - (*error_code_out) = ExtractToMemory(handle, &entry, &((*entry_out)[0]), 12); - - CloseArchive(handle); -} - -TEST(ziparchive, ValidDataDescriptors) { - std::vector entry; - int32_t error_code = 0; - ExtractEntryToMemory(kDataDescriptorZipFile, &entry, &error_code); - - ASSERT_EQ(0, error_code); - ASSERT_EQ(12u, entry.size()); - ASSERT_EQ('a', entry[0]); - ASSERT_EQ('k', entry[11]); -} - -TEST(ziparchive, InvalidDataDescriptors_csize) { - std::vector invalid_csize = kDataDescriptorZipFile; - invalid_csize[kCSizeOffset] = 0xfe; - - std::vector entry; - int32_t error_code = 0; - ExtractEntryToMemory(invalid_csize, &entry, &error_code); - - ASSERT_EQ(kInconsistentInformation, error_code); -} - -TEST(ziparchive, InvalidDataDescriptors_size) { - std::vector invalid_size = kDataDescriptorZipFile; - invalid_size[kSizeOffset] = 0xfe; - - std::vector entry; - int32_t error_code = 0; - ExtractEntryToMemory(invalid_size, &entry, &error_code); - - ASSERT_EQ(kInconsistentInformation, error_code); -} - -TEST(ziparchive, ErrorCodeString) { - ASSERT_STREQ("Success", ErrorCodeString(0)); - - // Out of bounds. - ASSERT_STREQ("Unknown return code", ErrorCodeString(1)); - ASSERT_STRNE("Unknown return code", ErrorCodeString(kLastErrorCode)); - ASSERT_STREQ("Unknown return code", ErrorCodeString(kLastErrorCode - 1)); - - ASSERT_STREQ("I/O error", ErrorCodeString(kIoError)); -} - -// A zip file whose local file header at offset zero is corrupted. -// -// --------------- -// cat foo > a.txt -// zip a.zip a.txt -// cat a.zip | xxd -i -// -// Manual changes : -// [2] = 0xff // Corrupt the LFH signature of entry 0. -// [3] = 0xff // Corrupt the LFH signature of entry 0. -static const std::vector kZipFileWithBrokenLfhSignature{ - //[lfh-sig-----------], [lfh contents--------------------------------- - 0x50, 0x4b, 0xff, 0xff, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x80, - //-------------------------------------------------------------------- - 0x09, 0x4b, 0xa8, 0x65, 0x32, 0x7e, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, - //-------------------------------] [file-name-----------------], [--- - 0x00, 0x00, 0x05, 0x00, 0x1c, 0x00, 0x61, 0x2e, 0x74, 0x78, 0x74, 0x55, - // entry-contents------------------------------------------------------ - 0x54, 0x09, 0x00, 0x03, 0x51, 0x24, 0x8b, 0x59, 0x51, 0x24, 0x8b, 0x59, - //-------------------------------------------------------------------- - 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0x89, 0x42, 0x00, 0x00, 0x04, 0x88, - //-------------------------------------], [cd-record-sig-------], [--- - 0x13, 0x00, 0x00, 0x66, 0x6f, 0x6f, 0x0a, 0x50, 0x4b, 0x01, 0x02, 0x1e, - // cd-record----------------------------------------------------------- - 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x80, 0x09, 0x4b, 0xa8, - //-------------------------------------------------------------------- - 0x65, 0x32, 0x7e, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, - //-------------------------------------------------------------------- - 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa0, - //-] [lfh-file-header-off-], [file-name-----------------], [extra---- - 0x81, 0x00, 0x00, 0x00, 0x00, 0x61, 0x2e, 0x74, 0x78, 0x74, 0x55, 0x54, - //-------------------------------------------------------------------- - 0x05, 0x00, 0x03, 0x51, 0x24, 0x8b, 0x59, 0x75, 0x78, 0x0b, 0x00, 0x01, - //-------------------------------------------------------], [eocd-sig- - 0x04, 0x89, 0x42, 0x00, 0x00, 0x04, 0x88, 0x13, 0x00, 0x00, 0x50, 0x4b, - //-------], [--------------------------------------------------------- - 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x4b, 0x00, - //-------------------------------------------] - 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00}; - -TEST(ziparchive, BrokenLfhSignature) { - TemporaryFile tmp_file; - ASSERT_NE(-1, tmp_file.fd); - ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, &kZipFileWithBrokenLfhSignature[0], - kZipFileWithBrokenLfhSignature.size())); - ZipArchiveHandle handle; - ASSERT_EQ(kInvalidFile, OpenArchiveFd(tmp_file.fd, "LeadingNonZipBytes", &handle, false)); -} - -class VectorReader : public zip_archive::Reader { - public: - VectorReader(const std::vector& input) : Reader(), input_(input) {} - - bool ReadAtOffset(uint8_t* buf, size_t len, off64_t offset) const { - if ((offset + len) < input_.size()) { - return false; - } - - memcpy(buf, &input_[static_cast(offset)], len); - return true; - } - - private: - const std::vector& input_; -}; - -class VectorWriter : public zip_archive::Writer { - public: - VectorWriter() : Writer() {} - - bool Append(uint8_t* buf, size_t size) { - output_.insert(output_.end(), buf, buf + size); - return true; - } - - std::vector& GetOutput() { return output_; } - - private: - std::vector output_; -}; - -class BadReader : public zip_archive::Reader { - public: - BadReader() : Reader() {} - - bool ReadAtOffset(uint8_t*, size_t, off64_t) const { return false; } -}; - -class BadWriter : public zip_archive::Writer { - public: - BadWriter() : Writer() {} - - bool Append(uint8_t*, size_t) { return false; } -}; - -TEST(ziparchive, Inflate) { - const uint32_t compressed_length = static_cast(kATxtContentsCompressed.size()); - const uint32_t uncompressed_length = static_cast(kATxtContents.size()); - - const VectorReader reader(kATxtContentsCompressed); - { - VectorWriter writer; - uint64_t crc_out = 0; - - int32_t ret = - zip_archive::Inflate(reader, compressed_length, uncompressed_length, &writer, &crc_out); - ASSERT_EQ(0, ret); - ASSERT_EQ(kATxtContents, writer.GetOutput()); - ASSERT_EQ(0x950821C5u, crc_out); - } - - { - VectorWriter writer; - int32_t ret = - zip_archive::Inflate(reader, compressed_length, uncompressed_length, &writer, nullptr); - ASSERT_EQ(0, ret); - ASSERT_EQ(kATxtContents, writer.GetOutput()); - } - - { - BadWriter writer; - int32_t ret = - zip_archive::Inflate(reader, compressed_length, uncompressed_length, &writer, nullptr); - ASSERT_EQ(kIoError, ret); - } - - { - BadReader reader; - VectorWriter writer; - int32_t ret = - zip_archive::Inflate(reader, compressed_length, uncompressed_length, &writer, nullptr); - ASSERT_EQ(kIoError, ret); - ASSERT_EQ(0u, writer.GetOutput().size()); - } -} - -// The class constructs a zipfile with zip64 format, and test the parsing logic. -class Zip64ParseTest : public ::testing::Test { - protected: - struct LocalFileEntry { - std::vector local_file_header; - std::string file_name; - std::vector extended_field; - // Fake data to mimic the compressed bytes in the zipfile. - std::vector compressed_bytes; - std::vector data_descriptor; - - size_t GetSize() const { - return local_file_header.size() + file_name.size() + extended_field.size() + - compressed_bytes.size() + data_descriptor.size(); - } - - void CopyToOutput(std::vector* output) const { - std::copy(local_file_header.begin(), local_file_header.end(), std::back_inserter(*output)); - std::copy(file_name.begin(), file_name.end(), std::back_inserter(*output)); - std::copy(extended_field.begin(), extended_field.end(), std::back_inserter(*output)); - std::copy(compressed_bytes.begin(), compressed_bytes.end(), std::back_inserter(*output)); - std::copy(data_descriptor.begin(), data_descriptor.end(), std::back_inserter(*output)); - } - }; - - struct CdRecordEntry { - std::vector central_directory_record; - std::string file_name; - std::vector extended_field; - - size_t GetSize() const { - return central_directory_record.size() + file_name.size() + extended_field.size(); - } - - void CopyToOutput(std::vector* output) const { - std::copy(central_directory_record.begin(), central_directory_record.end(), - std::back_inserter(*output)); - std::copy(file_name.begin(), file_name.end(), std::back_inserter(*output)); - std::copy(extended_field.begin(), extended_field.end(), std::back_inserter(*output)); - } - }; - - static void ConstructLocalFileHeader(const std::string& name, std::vector* output, - uint32_t uncompressed_size, uint32_t compressed_size) { - LocalFileHeader lfh = {}; - lfh.lfh_signature = LocalFileHeader::kSignature; - lfh.compressed_size = compressed_size; - lfh.uncompressed_size = uncompressed_size; - lfh.file_name_length = static_cast(name.size()); - lfh.extra_field_length = 20; - *output = std::vector(reinterpret_cast(&lfh), - reinterpret_cast(&lfh) + sizeof(LocalFileHeader)); - } - - // Put one zip64 extended info in the extended field. - static void ConstructExtendedField(const std::vector& zip64_fields, - std::vector* output) { - ASSERT_FALSE(zip64_fields.empty()); - uint16_t data_size = 8 * static_cast(zip64_fields.size()); - std::vector extended_field(data_size + 4); - android::base::put_unaligned(extended_field.data(), Zip64ExtendedInfo::kHeaderId); - android::base::put_unaligned(extended_field.data() + 2, data_size); - size_t offset = 4; - for (const auto& field : zip64_fields) { - android::base::put_unaligned(extended_field.data() + offset, field); - offset += 8; - } - - *output = std::move(extended_field); - } - - static void ConstructCentralDirectoryRecord(const std::string& name, uint32_t uncompressed_size, - uint32_t compressed_size, uint32_t local_offset, - std::vector* output) { - CentralDirectoryRecord cdr = {}; - cdr.record_signature = CentralDirectoryRecord::kSignature; - cdr.compressed_size = uncompressed_size; - cdr.uncompressed_size = compressed_size; - cdr.file_name_length = static_cast(name.size()); - cdr.extra_field_length = local_offset == UINT32_MAX ? 28 : 20; - cdr.local_file_header_offset = local_offset; - *output = - std::vector(reinterpret_cast(&cdr), - reinterpret_cast(&cdr) + sizeof(CentralDirectoryRecord)); - } - - // Add an entry to the zipfile, construct the corresponding local header and cd entry. - void AddEntry(const std::string& name, const std::vector& content, - bool uncompressed_size_in_extended, bool compressed_size_in_extended, - bool local_offset_in_extended, bool include_data_descriptor = false) { - auto uncompressed_size = static_cast(content.size()); - auto compressed_size = static_cast(content.size()); - uint32_t local_file_header_offset = 0; - std::for_each(file_entries_.begin(), file_entries_.end(), - [&local_file_header_offset](const LocalFileEntry& file_entry) { - local_file_header_offset += file_entry.GetSize(); - }); - - std::vector zip64_fields; - if (uncompressed_size_in_extended) { - zip64_fields.push_back(uncompressed_size); - uncompressed_size = UINT32_MAX; - } - if (compressed_size_in_extended) { - zip64_fields.push_back(compressed_size); - compressed_size = UINT32_MAX; - } - LocalFileEntry local_entry = { - .local_file_header = {}, - .file_name = name, - .extended_field = {}, - .compressed_bytes = content, - }; - ConstructLocalFileHeader(name, &local_entry.local_file_header, uncompressed_size, - compressed_size); - ConstructExtendedField(zip64_fields, &local_entry.extended_field); - if (include_data_descriptor) { - size_t descriptor_size = compressed_size_in_extended ? 24 : 16; - local_entry.data_descriptor.resize(descriptor_size); - uint8_t* write_ptr = local_entry.data_descriptor.data(); - EmitUnaligned(&write_ptr, DataDescriptor::kOptSignature); - EmitUnaligned(&write_ptr, 0 /* crc */); - if (compressed_size_in_extended) { - EmitUnaligned(&write_ptr, compressed_size_in_extended); - EmitUnaligned(&write_ptr, uncompressed_size_in_extended); - } else { - EmitUnaligned(&write_ptr, compressed_size_in_extended); - EmitUnaligned(&write_ptr, uncompressed_size_in_extended); - } - } - - file_entries_.push_back(std::move(local_entry)); - - if (local_offset_in_extended) { - zip64_fields.push_back(local_file_header_offset); - local_file_header_offset = UINT32_MAX; - } - CdRecordEntry cd_entry = { - .central_directory_record = {}, - .file_name = name, - .extended_field = {}, - }; - ConstructCentralDirectoryRecord(name, uncompressed_size, compressed_size, - local_file_header_offset, &cd_entry.central_directory_record); - ConstructExtendedField(zip64_fields, &cd_entry.extended_field); - cd_entries_.push_back(std::move(cd_entry)); - } - - void ConstructEocd() { - ASSERT_EQ(file_entries_.size(), cd_entries_.size()); - Zip64EocdRecord zip64_eocd = {}; - zip64_eocd.record_signature = Zip64EocdRecord::kSignature; - zip64_eocd.num_records = file_entries_.size(); - zip64_eocd.cd_size = 0; - std::for_each( - cd_entries_.begin(), cd_entries_.end(), - [&zip64_eocd](const CdRecordEntry& cd_entry) { zip64_eocd.cd_size += cd_entry.GetSize(); }); - zip64_eocd.cd_start_offset = 0; - std::for_each(file_entries_.begin(), file_entries_.end(), - [&zip64_eocd](const LocalFileEntry& file_entry) { - zip64_eocd.cd_start_offset += file_entry.GetSize(); - }); - zip64_eocd_record_ = - std::vector(reinterpret_cast(&zip64_eocd), - reinterpret_cast(&zip64_eocd) + sizeof(Zip64EocdRecord)); - - Zip64EocdLocator zip64_locator = {}; - zip64_locator.locator_signature = Zip64EocdLocator::kSignature; - zip64_locator.zip64_eocd_offset = zip64_eocd.cd_start_offset + zip64_eocd.cd_size; - zip64_eocd_locator_ = - std::vector(reinterpret_cast(&zip64_locator), - reinterpret_cast(&zip64_locator) + sizeof(Zip64EocdLocator)); - - EocdRecord eocd = {}; - eocd.eocd_signature = EocdRecord::kSignature, - eocd.num_records = file_entries_.size() > UINT16_MAX - ? UINT16_MAX - : static_cast(file_entries_.size()); - eocd.cd_size = UINT32_MAX; - eocd.cd_start_offset = UINT32_MAX; - eocd_record_ = std::vector(reinterpret_cast(&eocd), - reinterpret_cast(&eocd) + sizeof(EocdRecord)); - } - - // Concatenate all the local file entries, cd entries, and eocd metadata. - void ConstructZipFile() { - for (const auto& file_entry : file_entries_) { - file_entry.CopyToOutput(&zip_content_); - } - for (const auto& cd_entry : cd_entries_) { - cd_entry.CopyToOutput(&zip_content_); - } - std::copy(zip64_eocd_record_.begin(), zip64_eocd_record_.end(), - std::back_inserter(zip_content_)); - std::copy(zip64_eocd_locator_.begin(), zip64_eocd_locator_.end(), - std::back_inserter(zip_content_)); - std::copy(eocd_record_.begin(), eocd_record_.end(), std::back_inserter(zip_content_)); - } - - std::vector zip_content_; - - std::vector file_entries_; - std::vector cd_entries_; - std::vector zip64_eocd_record_; - std::vector zip64_eocd_locator_; - std::vector eocd_record_; -}; - -TEST_F(Zip64ParseTest, openFile) { - AddEntry("a.txt", std::vector(100, 'a'), true, true, false); - ConstructEocd(); - ConstructZipFile(); - - ZipArchiveHandle handle; - ASSERT_EQ( - 0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle)); - CloseArchive(handle); -} - -TEST_F(Zip64ParseTest, openFilelocalOffsetInExtendedField) { - AddEntry("a.txt", std::vector(100, 'a'), true, true, true); - AddEntry("b.txt", std::vector(200, 'b'), true, true, true); - ConstructEocd(); - ConstructZipFile(); - - ZipArchiveHandle handle; - ASSERT_EQ( - 0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle)); - CloseArchive(handle); -} - -TEST_F(Zip64ParseTest, openFileCompressedNotInExtendedField) { - AddEntry("a.txt", std::vector(100, 'a'), true, false, false); - ConstructEocd(); - ConstructZipFile(); - - ZipArchiveHandle handle; - // Zip64 extended fields must include both uncompressed and compressed size. - ASSERT_NE( - 0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle)); - CloseArchive(handle); -} - -TEST_F(Zip64ParseTest, findEntry) { - AddEntry("a.txt", std::vector(200, 'a'), true, true, true); - AddEntry("b.txt", std::vector(300, 'b'), true, true, false); - ConstructEocd(); - ConstructZipFile(); - - ZipArchiveHandle handle; - ASSERT_EQ( - 0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle)); - ZipEntry64 entry; - ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry)); - ASSERT_EQ(200, entry.uncompressed_length); - ASSERT_EQ(200, entry.compressed_length); - - ASSERT_EQ(0, FindEntry(handle, "b.txt", &entry)); - ASSERT_EQ(300, entry.uncompressed_length); - ASSERT_EQ(300, entry.compressed_length); - CloseArchive(handle); -} - -TEST_F(Zip64ParseTest, openFileIncorrectDataSizeInLocalExtendedField) { - AddEntry("a.txt", std::vector(100, 'a'), true, true, false); - ASSERT_EQ(1, file_entries_.size()); - auto& extended_field = file_entries_[0].extended_field; - // data size exceeds the extended field size in local header. - android::base::put_unaligned(extended_field.data() + 2, 30); - ConstructEocd(); - ConstructZipFile(); - - ZipArchiveHandle handle; - ASSERT_EQ( - 0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle)); - ZipEntry64 entry; - ASSERT_NE(0, FindEntry(handle, "a.txt", &entry)); - - CloseArchive(handle); -} - -TEST_F(Zip64ParseTest, iterates) { - std::set names{"a.txt", "b.txt", "c.txt", "d.txt", "e.txt"}; - for (const auto& name : names) { - AddEntry(std::string(name), std::vector(100, name[0]), true, true, true); - } - ConstructEocd(); - ConstructZipFile(); - - ZipArchiveHandle handle; - ASSERT_EQ( - 0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle)); - - void* iteration_cookie; - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie)); - std::set result; - std::string_view name; - ZipEntry64 entry; - while (Next(iteration_cookie, &entry, &name) == 0) result.emplace(name); - ASSERT_EQ(names, result); - - CloseArchive(handle); -} - -TEST_F(Zip64ParseTest, zip64EocdWrongLocatorOffset) { - AddEntry("a.txt", std::vector(1, 'a'), true, true, true); - ConstructEocd(); - zip_content_.resize(20, 'a'); - std::copy(zip64_eocd_locator_.begin(), zip64_eocd_locator_.end(), - std::back_inserter(zip_content_)); - std::copy(eocd_record_.begin(), eocd_record_.end(), std::back_inserter(zip_content_)); - - ZipArchiveHandle handle; - ASSERT_NE( - 0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle)); - CloseArchive(handle); -} - -TEST_F(Zip64ParseTest, extract) { - std::vector content(200, 'a'); - AddEntry("a.txt", content, true, true, true); - ConstructEocd(); - ConstructZipFile(); - - ZipArchiveHandle handle; - ASSERT_EQ( - 0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle)); - ZipEntry64 entry; - ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry)); - - VectorWriter writer; - ASSERT_EQ(0, ExtractToWriter(handle, &entry, &writer)); - ASSERT_EQ(content, writer.GetOutput()); -} - -TEST_F(Zip64ParseTest, extractWithDataDescriptor) { - std::vector content(300, 'b'); - AddEntry("a.txt", std::vector(200, 'a'), true, true, true); - AddEntry("b.txt", content, true, true, true, true /* data descriptor */); - ConstructEocd(); - ConstructZipFile(); - - ZipArchiveHandle handle; - ASSERT_EQ( - 0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle)); - ZipEntry64 entry; - ASSERT_EQ(0, FindEntry(handle, "b.txt", &entry)); - - VectorWriter writer; - ASSERT_EQ(0, ExtractToWriter(handle, &entry, &writer)); - ASSERT_EQ(content, writer.GetOutput()); -} diff --git a/libziparchive/zip_cd_entry_map.cc b/libziparchive/zip_cd_entry_map.cc deleted file mode 100644 index f187c0690..000000000 --- a/libziparchive/zip_cd_entry_map.cc +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "zip_cd_entry_map.h" - -#include -#include - -/* - * Round up to the next highest power of 2. - * - * Found on http://graphics.stanford.edu/~seander/bithacks.html. - */ -static uint32_t RoundUpPower2(uint32_t val) { - val--; - val |= val >> 1; - val |= val >> 2; - val |= val >> 4; - val |= val >> 8; - val |= val >> 16; - val++; - - return val; -} - -static uint32_t ComputeHash(std::string_view name) { - return static_cast(std::hash{}(name)); -} - -// Convert a ZipEntry to a hash table index, verifying that it's in a valid range. -std::pair CdEntryMapZip32::GetCdEntryOffset(std::string_view name, - const uint8_t* start) const { - const uint32_t hash = ComputeHash(name); - - // NOTE: (hash_table_size - 1) is guaranteed to be non-negative. - uint32_t ent = hash & (hash_table_size_ - 1); - while (hash_table_[ent].name_offset != 0) { - if (hash_table_[ent].ToStringView(start) == name) { - return {kSuccess, hash_table_[ent].name_offset}; - } - ent = (ent + 1) & (hash_table_size_ - 1); - } - - ALOGV("Zip: Unable to find entry %.*s", static_cast(name.size()), name.data()); - return {kEntryNotFound, 0}; -} - -ZipError CdEntryMapZip32::AddToMap(std::string_view name, const uint8_t* start) { - const uint64_t hash = ComputeHash(name); - uint32_t ent = hash & (hash_table_size_ - 1); - - /* - * We over-allocated the table, so we're guaranteed to find an empty slot. - * Further, we guarantee that the hashtable size is not 0. - */ - while (hash_table_[ent].name_offset != 0) { - if (hash_table_[ent].ToStringView(start) == name) { - // We've found a duplicate entry. We don't accept duplicates. - ALOGW("Zip: Found duplicate entry %.*s", static_cast(name.size()), name.data()); - return kDuplicateEntry; - } - ent = (ent + 1) & (hash_table_size_ - 1); - } - - // `name` has already been validated before entry. - const char* start_char = reinterpret_cast(start); - hash_table_[ent].name_offset = static_cast(name.data() - start_char); - hash_table_[ent].name_length = static_cast(name.size()); - return kSuccess; -} - -void CdEntryMapZip32::ResetIteration() { - current_position_ = 0; -} - -std::pair CdEntryMapZip32::Next(const uint8_t* cd_start) { - while (current_position_ < hash_table_size_) { - const auto& entry = hash_table_[current_position_]; - current_position_ += 1; - - if (entry.name_offset != 0) { - return {entry.ToStringView(cd_start), entry.name_offset}; - } - } - // We have reached the end of the hash table. - return {}; -} - -CdEntryMapZip32::CdEntryMapZip32(uint16_t num_entries) { - /* - * Create hash table. We have a minimum 75% load factor, possibly as - * low as 50% after we round off to a power of 2. There must be at - * least one unused entry to avoid an infinite loop during creation. - */ - hash_table_size_ = RoundUpPower2(1 + (num_entries * 4) / 3); - hash_table_ = { - reinterpret_cast(calloc(hash_table_size_, sizeof(ZipStringOffset))), free}; -} - -std::unique_ptr CdEntryMapZip32::Create(uint16_t num_entries) { - auto entry_map = new CdEntryMapZip32(num_entries); - CHECK(entry_map->hash_table_ != nullptr) - << "Zip: unable to allocate the " << entry_map->hash_table_size_ - << " entry hash_table, entry size: " << sizeof(ZipStringOffset); - return std::unique_ptr(entry_map); -} - -std::unique_ptr CdEntryMapZip64::Create() { - return std::unique_ptr(new CdEntryMapZip64()); -} - -ZipError CdEntryMapZip64::AddToMap(std::string_view name, const uint8_t* start) { - const auto [it, added] = - entry_table_.insert({name, name.data() - reinterpret_cast(start)}); - if (!added) { - ALOGW("Zip: Found duplicate entry %.*s", static_cast(name.size()), name.data()); - return kDuplicateEntry; - } - return kSuccess; -} - -std::pair CdEntryMapZip64::GetCdEntryOffset(std::string_view name, - const uint8_t* /*cd_start*/) const { - const auto it = entry_table_.find(name); - if (it == entry_table_.end()) { - ALOGV("Zip: Could not find entry %.*s", static_cast(name.size()), name.data()); - return {kEntryNotFound, 0}; - } - - return {kSuccess, it->second}; -} - -void CdEntryMapZip64::ResetIteration() { - iterator_ = entry_table_.begin(); -} - -std::pair CdEntryMapZip64::Next(const uint8_t* /*cd_start*/) { - if (iterator_ == entry_table_.end()) { - return {}; - } - - return *iterator_++; -} diff --git a/libziparchive/zip_cd_entry_map.h b/libziparchive/zip_cd_entry_map.h deleted file mode 100644 index 4957f754e..000000000 --- a/libziparchive/zip_cd_entry_map.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include -#include -#include -#include - -#include "zip_error.h" - -// This class is the interface of the central directory entries map. The map -// helps to locate a particular cd entry based on the filename. -class CdEntryMapInterface { - public: - virtual ~CdEntryMapInterface() = default; - // Adds an entry to the map. The |name| should internally points to the - // filename field of a cd entry. And |start| points to the beginning of the - // central directory. Returns 0 on success. - virtual ZipError AddToMap(std::string_view name, const uint8_t* start) = 0; - // For the zip entry |entryName|, finds the offset of its filename field in - // the central directory. Returns a pair of [status, offset]. The value of - // the status is 0 on success. - virtual std::pair GetCdEntryOffset(std::string_view name, - const uint8_t* cd_start) const = 0; - // Resets the iterator to the beginning of the map. - virtual void ResetIteration() = 0; - // Returns the [name, cd offset] of the current element. Also increments the - // iterator to points to the next element. Returns an empty pair we have read - // past boundary. - virtual std::pair Next(const uint8_t* cd_start) = 0; -}; - -/** - * More space efficient string representation of strings in an mmaped zipped - * file than std::string_view. Using std::string_view as an entry in the - * ZipArchive hash table wastes space. std::string_view stores a pointer to a - * string (on 64 bit, 8 bytes) and the length to read from that pointer, - * 2 bytes. Because of alignment, the structure consumes 16 bytes, wasting - * 6 bytes. - * - * ZipStringOffset stores a 4 byte offset from a fixed location in the memory - * mapped file instead of the entire address, consuming 8 bytes with alignment. - */ -struct ZipStringOffset { - uint32_t name_offset; - uint16_t name_length; - - const std::string_view ToStringView(const uint8_t* start) const { - return std::string_view{reinterpret_cast(start + name_offset), name_length}; - } -}; - -// This implementation of CdEntryMap uses an array hash table. It uses less -// memory than std::map; and it's used as the default implementation for zip -// archives without zip64 extension. -class CdEntryMapZip32 : public CdEntryMapInterface { - public: - static std::unique_ptr Create(uint16_t num_entries); - - ZipError AddToMap(std::string_view name, const uint8_t* start) override; - std::pair GetCdEntryOffset(std::string_view name, - const uint8_t* cd_start) const override; - void ResetIteration() override; - std::pair Next(const uint8_t* cd_start) override; - - private: - explicit CdEntryMapZip32(uint16_t num_entries); - - // We know how many entries are in the Zip archive, so we can have a - // fixed-size hash table. We define a load factor of 0.75 and over - // allocate so the maximum number entries can never be higher than - // ((4 * UINT16_MAX) / 3 + 1) which can safely fit into a uint32_t. - uint32_t hash_table_size_{0}; - std::unique_ptr hash_table_{nullptr, free}; - - // The position of element for the current iteration. - uint32_t current_position_{0}; -}; - -// This implementation of CdEntryMap uses a std::map -class CdEntryMapZip64 : public CdEntryMapInterface { - public: - static std::unique_ptr Create(); - - ZipError AddToMap(std::string_view name, const uint8_t* start) override; - std::pair GetCdEntryOffset(std::string_view name, - const uint8_t* cd_start) const override; - void ResetIteration() override; - std::pair Next(const uint8_t* cd_start) override; - - private: - CdEntryMapZip64() = default; - - std::map entry_table_; - - std::map::iterator iterator_; -}; diff --git a/libziparchive/zip_error.cpp b/libziparchive/zip_error.cpp deleted file mode 100644 index 14e49bb40..000000000 --- a/libziparchive/zip_error.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "zip_error.h" - -#include - -static const char* kErrorMessages[] = { - "Success", - "Iteration ended", - "Zlib error", - "Invalid file", - "Invalid handle", - "Duplicate entries in archive", - "Empty archive", - "Entry not found", - "Invalid offset", - "Inconsistent information", - "Invalid entry name", - "I/O error", - "File mapping failed", - "Allocation failed", - "Unsupported zip entry size", -}; - -const char* ErrorCodeString(int32_t error_code) { - // Make sure that the number of entries in kErrorMessages and the ZipError - // enum match. - static_assert((-kLastErrorCode + 1) == arraysize(kErrorMessages), - "(-kLastErrorCode + 1) != arraysize(kErrorMessages)"); - - const uint32_t idx = -error_code; - if (idx < arraysize(kErrorMessages)) { - return kErrorMessages[idx]; - } - - return "Unknown return code"; -} diff --git a/libziparchive/zip_error.h b/libziparchive/zip_error.h deleted file mode 100644 index 3d7285d5e..000000000 --- a/libziparchive/zip_error.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -enum ZipError : int32_t { - kSuccess = 0, - - kIterationEnd = -1, - - // We encountered a Zlib error when inflating a stream from this file. - // Usually indicates file corruption. - kZlibError = -2, - - // The input file cannot be processed as a zip archive. Usually because - // it's too small, too large or does not have a valid signature. - kInvalidFile = -3, - - // An invalid iteration / ziparchive handle was passed in as an input - // argument. - kInvalidHandle = -4, - - // The zip archive contained two (or possibly more) entries with the same - // name. - kDuplicateEntry = -5, - - // The zip archive contains no entries. - kEmptyArchive = -6, - - // The specified entry was not found in the archive. - kEntryNotFound = -7, - - // The zip archive contained an invalid local file header pointer. - kInvalidOffset = -8, - - // The zip archive contained inconsistent entry information. This could - // be because the central directory & local file header did not agree, or - // if the actual uncompressed length or crc32 do not match their declared - // values. - kInconsistentInformation = -9, - - // An invalid entry name was encountered. - kInvalidEntryName = -10, - - // An I/O related system call (read, lseek, ftruncate, map) failed. - kIoError = -11, - - // We were not able to mmap the central directory or entry contents. - kMmapFailed = -12, - - // An allocation failed. - kAllocationFailed = -13, - - // The compressed or uncompressed size is larger than UINT32_MAX and - // doesn't fit into the 32 bits zip entry. - kUnsupportedEntrySize = -14, - - kLastErrorCode = kUnsupportedEntrySize, -}; diff --git a/libziparchive/zip_writer.cc b/libziparchive/zip_writer.cc deleted file mode 100644 index 25b1da4ca..000000000 --- a/libziparchive/zip_writer.cc +++ /dev/null @@ -1,579 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ziparchive/zip_writer.h" - -#include -#include -#include -#include -#define DEF_MEM_LEVEL 8 // normally in zutil.h? - -#include -#include - -#include "android-base/logging.h" - -#include "entry_name_utils-inl.h" -#include "zip_archive_common.h" - -#undef powerof2 -#define powerof2(x) \ - ({ \ - __typeof__(x) _x = (x); \ - __typeof__(x) _x2; \ - __builtin_add_overflow(_x, -1, &_x2) ? 1 : ((_x2 & _x) == 0); \ - }) - -/* Zip compression methods we support */ -enum { - kCompressStored = 0, // no compression - kCompressDeflated = 8, // standard deflate -}; - -// Size of the output buffer used for compression. -static const size_t kBufSize = 32768u; - -// No error, operation completed successfully. -static const int32_t kNoError = 0; - -// The ZipWriter is in a bad state. -static const int32_t kInvalidState = -1; - -// There was an IO error while writing to disk. -static const int32_t kIoError = -2; - -// The zip entry name was invalid. -static const int32_t kInvalidEntryName = -3; - -// An error occurred in zlib. -static const int32_t kZlibError = -4; - -// The start aligned function was called with the aligned flag. -static const int32_t kInvalidAlign32Flag = -5; - -// The alignment parameter is not a power of 2. -static const int32_t kInvalidAlignment = -6; - -static const char* sErrorCodes[] = { - "Invalid state", "IO error", "Invalid entry name", "Zlib error", -}; - -const char* ZipWriter::ErrorCodeString(int32_t error_code) { - if (error_code < 0 && (-error_code) < static_cast(arraysize(sErrorCodes))) { - return sErrorCodes[-error_code]; - } - return nullptr; -} - -static void DeleteZStream(z_stream* stream) { - deflateEnd(stream); - delete stream; -} - -ZipWriter::ZipWriter(FILE* f) - : file_(f), - seekable_(false), - current_offset_(0), - state_(State::kWritingZip), - z_stream_(nullptr, DeleteZStream), - buffer_(kBufSize) { - // Check if the file is seekable (regular file). If fstat fails, that's fine, subsequent calls - // will fail as well. - struct stat file_stats; - if (fstat(fileno(f), &file_stats) == 0) { - seekable_ = S_ISREG(file_stats.st_mode); - } -} - -ZipWriter::ZipWriter(ZipWriter&& writer) noexcept - : file_(writer.file_), - seekable_(writer.seekable_), - current_offset_(writer.current_offset_), - state_(writer.state_), - files_(std::move(writer.files_)), - z_stream_(std::move(writer.z_stream_)), - buffer_(std::move(writer.buffer_)) { - writer.file_ = nullptr; - writer.state_ = State::kError; -} - -ZipWriter& ZipWriter::operator=(ZipWriter&& writer) noexcept { - file_ = writer.file_; - seekable_ = writer.seekable_; - current_offset_ = writer.current_offset_; - state_ = writer.state_; - files_ = std::move(writer.files_); - z_stream_ = std::move(writer.z_stream_); - buffer_ = std::move(writer.buffer_); - writer.file_ = nullptr; - writer.state_ = State::kError; - return *this; -} - -int32_t ZipWriter::HandleError(int32_t error_code) { - state_ = State::kError; - z_stream_.reset(); - return error_code; -} - -int32_t ZipWriter::StartEntry(std::string_view path, size_t flags) { - uint32_t alignment = 0; - if (flags & kAlign32) { - flags &= ~kAlign32; - alignment = 4; - } - return StartAlignedEntryWithTime(path, flags, time_t(), alignment); -} - -int32_t ZipWriter::StartAlignedEntry(std::string_view path, size_t flags, uint32_t alignment) { - return StartAlignedEntryWithTime(path, flags, time_t(), alignment); -} - -int32_t ZipWriter::StartEntryWithTime(std::string_view path, size_t flags, time_t time) { - uint32_t alignment = 0; - if (flags & kAlign32) { - flags &= ~kAlign32; - alignment = 4; - } - return StartAlignedEntryWithTime(path, flags, time, alignment); -} - -static void ExtractTimeAndDate(time_t when, uint16_t* out_time, uint16_t* out_date) { - /* round up to an even number of seconds */ - when = static_cast((static_cast(when) + 1) & (~1)); - - struct tm* ptm; -#if !defined(_WIN32) - struct tm tm_result; - ptm = localtime_r(&when, &tm_result); -#else - ptm = localtime(&when); -#endif - - int year = ptm->tm_year; - if (year < 80) { - year = 80; - } - - *out_date = static_cast((year - 80) << 9 | (ptm->tm_mon + 1) << 5 | ptm->tm_mday); - *out_time = static_cast(ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1); -} - -static void CopyFromFileEntry(const ZipWriter::FileEntry& src, bool use_data_descriptor, - LocalFileHeader* dst) { - dst->lfh_signature = LocalFileHeader::kSignature; - if (use_data_descriptor) { - // Set this flag to denote that a DataDescriptor struct will appear after the data, - // containing the crc and size fields. - dst->gpb_flags |= kGPBDDFlagMask; - - // The size and crc fields must be 0. - dst->compressed_size = 0u; - dst->uncompressed_size = 0u; - dst->crc32 = 0u; - } else { - dst->compressed_size = src.compressed_size; - dst->uncompressed_size = src.uncompressed_size; - dst->crc32 = src.crc32; - } - dst->compression_method = src.compression_method; - dst->last_mod_time = src.last_mod_time; - dst->last_mod_date = src.last_mod_date; - DCHECK_LE(src.path.size(), std::numeric_limits::max()); - dst->file_name_length = static_cast(src.path.size()); - dst->extra_field_length = src.padding_length; -} - -int32_t ZipWriter::StartAlignedEntryWithTime(std::string_view path, size_t flags, time_t time, - uint32_t alignment) { - if (state_ != State::kWritingZip) { - return kInvalidState; - } - - // Can only have 16535 entries because of zip records. - if (files_.size() == std::numeric_limits::max()) { - return HandleError(kIoError); - } - - if (flags & kAlign32) { - return kInvalidAlign32Flag; - } - - if (powerof2(alignment) == 0) { - return kInvalidAlignment; - } - if (alignment > std::numeric_limits::max()) { - return kInvalidAlignment; - } - - FileEntry file_entry = {}; - file_entry.local_file_header_offset = current_offset_; - file_entry.path = path; - // No support for larger than 4GB files. - if (file_entry.local_file_header_offset > std::numeric_limits::max()) { - return HandleError(kIoError); - } - - if (!IsValidEntryName(reinterpret_cast(file_entry.path.data()), - file_entry.path.size())) { - return kInvalidEntryName; - } - - if (flags & ZipWriter::kCompress) { - file_entry.compression_method = kCompressDeflated; - - int32_t result = PrepareDeflate(); - if (result != kNoError) { - return result; - } - } else { - file_entry.compression_method = kCompressStored; - } - - ExtractTimeAndDate(time, &file_entry.last_mod_time, &file_entry.last_mod_date); - - off_t offset = current_offset_ + sizeof(LocalFileHeader) + file_entry.path.size(); - // prepare a pre-zeroed memory page in case when we need to pad some aligned data. - static constexpr auto kPageSize = 4096; - static constexpr char kSmallZeroPadding[kPageSize] = {}; - // use this buffer if our preallocated one is too small - std::vector zero_padding_big; - const char* zero_padding = nullptr; - - if (alignment != 0 && (offset & (alignment - 1))) { - // Pad the extra field so the data will be aligned. - uint16_t padding = static_cast(alignment - (offset % alignment)); - file_entry.padding_length = padding; - offset += padding; - if (padding <= std::size(kSmallZeroPadding)) { - zero_padding = kSmallZeroPadding; - } else { - zero_padding_big.resize(padding, 0); - zero_padding = zero_padding_big.data(); - } - } - - LocalFileHeader header = {}; - // Always start expecting a data descriptor. When the data has finished being written, - // if it is possible to seek back, the GPB flag will reset and the sizes written. - CopyFromFileEntry(file_entry, true /*use_data_descriptor*/, &header); - - if (fwrite(&header, sizeof(header), 1, file_) != 1) { - return HandleError(kIoError); - } - - if (fwrite(path.data(), 1, path.size(), file_) != path.size()) { - return HandleError(kIoError); - } - - if (file_entry.padding_length != 0 && fwrite(zero_padding, 1, file_entry.padding_length, - file_) != file_entry.padding_length) { - return HandleError(kIoError); - } - - current_file_entry_ = std::move(file_entry); - current_offset_ = offset; - state_ = State::kWritingEntry; - return kNoError; -} - -int32_t ZipWriter::DiscardLastEntry() { - if (state_ != State::kWritingZip || files_.empty()) { - return kInvalidState; - } - - FileEntry& last_entry = files_.back(); - current_offset_ = last_entry.local_file_header_offset; - if (fseeko(file_, current_offset_, SEEK_SET) != 0) { - return HandleError(kIoError); - } - files_.pop_back(); - return kNoError; -} - -int32_t ZipWriter::GetLastEntry(FileEntry* out_entry) { - CHECK(out_entry != nullptr); - - if (files_.empty()) { - return kInvalidState; - } - *out_entry = files_.back(); - return kNoError; -} - -int32_t ZipWriter::PrepareDeflate() { - CHECK(state_ == State::kWritingZip); - - // Initialize the z_stream for compression. - z_stream_ = std::unique_ptr(new z_stream(), DeleteZStream); - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" - int zerr = deflateInit2(z_stream_.get(), Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, - DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); -#pragma GCC diagnostic pop - - if (zerr != Z_OK) { - if (zerr == Z_VERSION_ERROR) { - LOG(ERROR) << "Installed zlib is not compatible with linked version (" << ZLIB_VERSION << ")"; - return HandleError(kZlibError); - } else { - LOG(ERROR) << "deflateInit2 failed (zerr=" << zerr << ")"; - return HandleError(kZlibError); - } - } - - z_stream_->next_out = buffer_.data(); - DCHECK_EQ(buffer_.size(), kBufSize); - z_stream_->avail_out = static_cast(buffer_.size()); - return kNoError; -} - -int32_t ZipWriter::WriteBytes(const void* data, size_t len) { - if (state_ != State::kWritingEntry) { - return HandleError(kInvalidState); - } - // Need to be able to mark down data correctly. - if (len + static_cast(current_file_entry_.uncompressed_size) > - std::numeric_limits::max()) { - return HandleError(kIoError); - } - uint32_t len32 = static_cast(len); - - int32_t result = kNoError; - if (current_file_entry_.compression_method & kCompressDeflated) { - result = CompressBytes(¤t_file_entry_, data, len32); - } else { - result = StoreBytes(¤t_file_entry_, data, len32); - } - - if (result != kNoError) { - return result; - } - - current_file_entry_.crc32 = static_cast( - crc32(current_file_entry_.crc32, reinterpret_cast(data), len32)); - current_file_entry_.uncompressed_size += len32; - return kNoError; -} - -int32_t ZipWriter::StoreBytes(FileEntry* file, const void* data, uint32_t len) { - CHECK(state_ == State::kWritingEntry); - - if (fwrite(data, 1, len, file_) != len) { - return HandleError(kIoError); - } - file->compressed_size += len; - current_offset_ += len; - return kNoError; -} - -int32_t ZipWriter::CompressBytes(FileEntry* file, const void* data, uint32_t len) { - CHECK(state_ == State::kWritingEntry); - CHECK(z_stream_); - CHECK(z_stream_->next_out != nullptr); - CHECK(z_stream_->avail_out != 0); - - // Prepare the input. - z_stream_->next_in = reinterpret_cast(data); - z_stream_->avail_in = len; - - while (z_stream_->avail_in > 0) { - // We have more data to compress. - int zerr = deflate(z_stream_.get(), Z_NO_FLUSH); - if (zerr != Z_OK) { - return HandleError(kZlibError); - } - - if (z_stream_->avail_out == 0) { - // The output is full, let's write it to disk. - size_t write_bytes = z_stream_->next_out - buffer_.data(); - if (fwrite(buffer_.data(), 1, write_bytes, file_) != write_bytes) { - return HandleError(kIoError); - } - file->compressed_size += write_bytes; - current_offset_ += write_bytes; - - // Reset the output buffer for the next input. - z_stream_->next_out = buffer_.data(); - DCHECK_EQ(buffer_.size(), kBufSize); - z_stream_->avail_out = static_cast(buffer_.size()); - } - } - return kNoError; -} - -int32_t ZipWriter::FlushCompressedBytes(FileEntry* file) { - CHECK(state_ == State::kWritingEntry); - CHECK(z_stream_); - CHECK(z_stream_->next_out != nullptr); - CHECK(z_stream_->avail_out != 0); - - // Keep deflating while there isn't enough space in the buffer to - // to complete the compress. - int zerr; - while ((zerr = deflate(z_stream_.get(), Z_FINISH)) == Z_OK) { - CHECK(z_stream_->avail_out == 0); - size_t write_bytes = z_stream_->next_out - buffer_.data(); - if (fwrite(buffer_.data(), 1, write_bytes, file_) != write_bytes) { - return HandleError(kIoError); - } - file->compressed_size += write_bytes; - current_offset_ += write_bytes; - - z_stream_->next_out = buffer_.data(); - DCHECK_EQ(buffer_.size(), kBufSize); - z_stream_->avail_out = static_cast(buffer_.size()); - } - if (zerr != Z_STREAM_END) { - return HandleError(kZlibError); - } - - size_t write_bytes = z_stream_->next_out - buffer_.data(); - if (write_bytes != 0) { - if (fwrite(buffer_.data(), 1, write_bytes, file_) != write_bytes) { - return HandleError(kIoError); - } - file->compressed_size += write_bytes; - current_offset_ += write_bytes; - } - z_stream_.reset(); - return kNoError; -} - -bool ZipWriter::ShouldUseDataDescriptor() const { - // Only use a trailing "data descriptor" if the output isn't seekable. - return !seekable_; -} - -int32_t ZipWriter::FinishEntry() { - if (state_ != State::kWritingEntry) { - return kInvalidState; - } - - if (current_file_entry_.compression_method & kCompressDeflated) { - int32_t result = FlushCompressedBytes(¤t_file_entry_); - if (result != kNoError) { - return result; - } - } - - if (ShouldUseDataDescriptor()) { - // Some versions of ZIP don't allow STORED data to have a trailing DataDescriptor. - // If this file is not seekable, or if the data is compressed, write a DataDescriptor. - // We haven't supported zip64 format yet. Write both uncompressed size and compressed - // size as uint32_t. - std::vector dataDescriptor = { - DataDescriptor::kOptSignature, current_file_entry_.crc32, - current_file_entry_.compressed_size, current_file_entry_.uncompressed_size}; - if (fwrite(dataDescriptor.data(), dataDescriptor.size() * sizeof(uint32_t), 1, file_) != 1) { - return HandleError(kIoError); - } - - current_offset_ += sizeof(uint32_t) * dataDescriptor.size(); - } else { - // Seek back to the header and rewrite to include the size. - if (fseeko(file_, current_file_entry_.local_file_header_offset, SEEK_SET) != 0) { - return HandleError(kIoError); - } - - LocalFileHeader header = {}; - CopyFromFileEntry(current_file_entry_, false /*use_data_descriptor*/, &header); - - if (fwrite(&header, sizeof(header), 1, file_) != 1) { - return HandleError(kIoError); - } - - if (fseeko(file_, current_offset_, SEEK_SET) != 0) { - return HandleError(kIoError); - } - } - - files_.emplace_back(std::move(current_file_entry_)); - state_ = State::kWritingZip; - return kNoError; -} - -int32_t ZipWriter::Finish() { - if (state_ != State::kWritingZip) { - return kInvalidState; - } - - off_t startOfCdr = current_offset_; - for (FileEntry& file : files_) { - CentralDirectoryRecord cdr = {}; - cdr.record_signature = CentralDirectoryRecord::kSignature; - if (ShouldUseDataDescriptor()) { - cdr.gpb_flags |= kGPBDDFlagMask; - } - cdr.compression_method = file.compression_method; - cdr.last_mod_time = file.last_mod_time; - cdr.last_mod_date = file.last_mod_date; - cdr.crc32 = file.crc32; - cdr.compressed_size = file.compressed_size; - cdr.uncompressed_size = file.uncompressed_size; - // Checked in IsValidEntryName. - DCHECK_LE(file.path.size(), std::numeric_limits::max()); - cdr.file_name_length = static_cast(file.path.size()); - // Checked in StartAlignedEntryWithTime. - DCHECK_LE(file.local_file_header_offset, std::numeric_limits::max()); - cdr.local_file_header_offset = static_cast(file.local_file_header_offset); - if (fwrite(&cdr, sizeof(cdr), 1, file_) != 1) { - return HandleError(kIoError); - } - - if (fwrite(file.path.data(), 1, file.path.size(), file_) != file.path.size()) { - return HandleError(kIoError); - } - - current_offset_ += sizeof(cdr) + file.path.size(); - } - - EocdRecord er = {}; - er.eocd_signature = EocdRecord::kSignature; - er.disk_num = 0; - er.cd_start_disk = 0; - // Checked when adding entries. - DCHECK_LE(files_.size(), std::numeric_limits::max()); - er.num_records_on_disk = static_cast(files_.size()); - er.num_records = static_cast(files_.size()); - if (current_offset_ > std::numeric_limits::max()) { - return HandleError(kIoError); - } - er.cd_size = static_cast(current_offset_ - startOfCdr); - er.cd_start_offset = static_cast(startOfCdr); - - if (fwrite(&er, sizeof(er), 1, file_) != 1) { - return HandleError(kIoError); - } - - current_offset_ += sizeof(er); - - // Since we can BackUp() and potentially finish writing at an offset less than one we had - // already written at, we must truncate the file. - - if (ftruncate(fileno(file_), current_offset_) != 0) { - return HandleError(kIoError); - } - - if (fflush(file_) != 0) { - return HandleError(kIoError); - } - - state_ = State::kDone; - return kNoError; -} diff --git a/libziparchive/zip_writer_test.cc b/libziparchive/zip_writer_test.cc deleted file mode 100644 index d324d4bfd..000000000 --- a/libziparchive/zip_writer_test.cc +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ziparchive/zip_writer.h" -#include "ziparchive/zip_archive.h" - -#include -#include -#include -#include -#include - -static ::testing::AssertionResult AssertFileEntryContentsEq(const std::string& expected, - ZipArchiveHandle handle, - ZipEntry* zip_entry); - -struct zipwriter : public ::testing::Test { - TemporaryFile* temp_file_; - int fd_; - FILE* file_; - - void SetUp() override { - temp_file_ = new TemporaryFile(); - fd_ = temp_file_->fd; - file_ = fdopen(fd_, "w"); - ASSERT_NE(file_, nullptr); - } - - void TearDown() override { - fclose(file_); - delete temp_file_; - } -}; - -TEST_F(zipwriter, WriteUncompressedZipWithOneFile) { - ZipWriter writer(file_); - - const char* expected = "hello"; - - ASSERT_EQ(0, writer.StartEntry("file.txt", 0)); - ASSERT_EQ(0, writer.WriteBytes("he", 2)); - ASSERT_EQ(0, writer.WriteBytes("llo", 3)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - - ZipEntry data; - ASSERT_EQ(0, FindEntry(handle, "file.txt", &data)); - EXPECT_EQ(kCompressStored, data.method); - EXPECT_EQ(0u, data.has_data_descriptor); - EXPECT_EQ(strlen(expected), data.compressed_length); - ASSERT_EQ(strlen(expected), data.uncompressed_length); - ASSERT_TRUE(AssertFileEntryContentsEq(expected, handle, &data)); - - CloseArchive(handle); -} - -TEST_F(zipwriter, WriteUncompressedZipWithMultipleFiles) { - ZipWriter writer(file_); - - ASSERT_EQ(0, writer.StartEntry("file.txt", 0)); - ASSERT_EQ(0, writer.WriteBytes("he", 2)); - ASSERT_EQ(0, writer.FinishEntry()); - - ASSERT_EQ(0, writer.StartEntry("file/file.txt", 0)); - ASSERT_EQ(0, writer.WriteBytes("llo", 3)); - ASSERT_EQ(0, writer.FinishEntry()); - - ASSERT_EQ(0, writer.StartEntry("file/file2.txt", 0)); - ASSERT_EQ(0, writer.FinishEntry()); - - ASSERT_EQ(0, writer.Finish()); - - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - - ZipEntry data; - - ASSERT_EQ(0, FindEntry(handle, "file.txt", &data)); - EXPECT_EQ(kCompressStored, data.method); - EXPECT_EQ(2u, data.compressed_length); - ASSERT_EQ(2u, data.uncompressed_length); - ASSERT_TRUE(AssertFileEntryContentsEq("he", handle, &data)); - - ASSERT_EQ(0, FindEntry(handle, "file/file.txt", &data)); - EXPECT_EQ(kCompressStored, data.method); - EXPECT_EQ(3u, data.compressed_length); - ASSERT_EQ(3u, data.uncompressed_length); - ASSERT_TRUE(AssertFileEntryContentsEq("llo", handle, &data)); - - ASSERT_EQ(0, FindEntry(handle, "file/file2.txt", &data)); - EXPECT_EQ(kCompressStored, data.method); - EXPECT_EQ(0u, data.compressed_length); - EXPECT_EQ(0u, data.uncompressed_length); - - CloseArchive(handle); -} - -TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedFlag) { - ZipWriter writer(file_); - - ASSERT_EQ(0, writer.StartEntry("align.txt", ZipWriter::kAlign32)); - ASSERT_EQ(0, writer.WriteBytes("he", 2)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - - ZipEntry data; - ASSERT_EQ(0, FindEntry(handle, "align.txt", &data)); - EXPECT_EQ(0, data.offset & 0x03); - - CloseArchive(handle); -} - -static struct tm MakeTm() { - struct tm tm; - memset(&tm, 0, sizeof(struct tm)); - tm.tm_year = 2001 - 1900; - tm.tm_mon = 1; - tm.tm_mday = 12; - tm.tm_hour = 18; - tm.tm_min = 30; - tm.tm_sec = 20; - return tm; -} - -TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedFlagAndTime) { - ZipWriter writer(file_); - - struct tm tm = MakeTm(); - time_t time = mktime(&tm); - ASSERT_EQ(0, writer.StartEntryWithTime("align.txt", ZipWriter::kAlign32, time)); - ASSERT_EQ(0, writer.WriteBytes("he", 2)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - - ZipEntry data; - ASSERT_EQ(0, FindEntry(handle, "align.txt", &data)); - EXPECT_EQ(0, data.offset & 0x03); - - struct tm mod = data.GetModificationTime(); - EXPECT_EQ(tm.tm_sec, mod.tm_sec); - EXPECT_EQ(tm.tm_min, mod.tm_min); - EXPECT_EQ(tm.tm_hour, mod.tm_hour); - EXPECT_EQ(tm.tm_mday, mod.tm_mday); - EXPECT_EQ(tm.tm_mon, mod.tm_mon); - EXPECT_EQ(tm.tm_year, mod.tm_year); - - CloseArchive(handle); -} - -TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedValue) { - ZipWriter writer(file_); - - ASSERT_EQ(0, writer.StartAlignedEntry("align.txt", 0, 4096)); - ASSERT_EQ(0, writer.WriteBytes("he", 2)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - - ZipEntry data; - ASSERT_EQ(0, FindEntry(handle, "align.txt", &data)); - EXPECT_EQ(0, data.offset & 0xfff); - - CloseArchive(handle); -} - -TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedValueAndTime) { - ZipWriter writer(file_); - - struct tm tm = MakeTm(); - time_t time = mktime(&tm); - ASSERT_EQ(0, writer.StartAlignedEntryWithTime("align.txt", 0, time, 4096)); - ASSERT_EQ(0, writer.WriteBytes("he", 2)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - - ZipEntry data; - ASSERT_EQ(0, FindEntry(handle, "align.txt", &data)); - EXPECT_EQ(0, data.offset & 0xfff); - - struct tm mod = data.GetModificationTime(); - EXPECT_EQ(tm.tm_sec, mod.tm_sec); - EXPECT_EQ(tm.tm_min, mod.tm_min); - EXPECT_EQ(tm.tm_hour, mod.tm_hour); - EXPECT_EQ(tm.tm_mday, mod.tm_mday); - EXPECT_EQ(tm.tm_mon, mod.tm_mon); - EXPECT_EQ(tm.tm_year, mod.tm_year); - - CloseArchive(handle); -} - -TEST_F(zipwriter, WriteCompressedZipWithOneFile) { - ZipWriter writer(file_); - - ASSERT_EQ(0, writer.StartEntry("file.txt", ZipWriter::kCompress)); - ASSERT_EQ(0, writer.WriteBytes("helo", 4)); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - - ZipEntry data; - ASSERT_EQ(0, FindEntry(handle, "file.txt", &data)); - EXPECT_EQ(kCompressDeflated, data.method); - EXPECT_EQ(0u, data.has_data_descriptor); - ASSERT_EQ(4u, data.uncompressed_length); - ASSERT_TRUE(AssertFileEntryContentsEq("helo", handle, &data)); - - CloseArchive(handle); -} - -TEST_F(zipwriter, WriteCompressedZipFlushFull) { - // This exact data will cause the Finish() to require multiple calls - // to deflate() because the ZipWriter buffer isn't big enough to hold - // the entire compressed data buffer. - constexpr size_t kBufSize = 10000000; - std::vector buffer(kBufSize); - size_t prev = 1; - for (size_t i = 0; i < kBufSize; i++) { - buffer[i] = static_cast(i + prev); - prev = i; - } - - ZipWriter writer(file_); - ASSERT_EQ(0, writer.StartEntry("file.txt", ZipWriter::kCompress)); - ASSERT_EQ(0, writer.WriteBytes(buffer.data(), buffer.size())); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - - ZipEntry data; - ASSERT_EQ(0, FindEntry(handle, "file.txt", &data)); - EXPECT_EQ(kCompressDeflated, data.method); - EXPECT_EQ(kBufSize, data.uncompressed_length); - - std::vector decompress(kBufSize); - memset(decompress.data(), 0, kBufSize); - ASSERT_EQ(0, ExtractToMemory(handle, &data, decompress.data(), - static_cast(decompress.size()))); - EXPECT_EQ(0, memcmp(decompress.data(), buffer.data(), kBufSize)) - << "Input buffer and output buffer are different."; - - CloseArchive(handle); -} - -TEST_F(zipwriter, CheckStartEntryErrors) { - ZipWriter writer(file_); - - ASSERT_EQ(-5, writer.StartAlignedEntry("align.txt", ZipWriter::kAlign32, 4096)); - ASSERT_EQ(-6, writer.StartAlignedEntry("align.txt", 0, 3)); -} - -TEST_F(zipwriter, BackupRemovesTheLastFile) { - ZipWriter writer(file_); - - const char* kKeepThis = "keep this"; - const char* kDropThis = "drop this"; - const char* kReplaceWithThis = "replace with this"; - - ZipWriter::FileEntry entry; - EXPECT_LT(writer.GetLastEntry(&entry), 0); - - ASSERT_EQ(0, writer.StartEntry("keep.txt", 0)); - ASSERT_EQ(0, writer.WriteBytes(kKeepThis, strlen(kKeepThis))); - ASSERT_EQ(0, writer.FinishEntry()); - - ASSERT_EQ(0, writer.GetLastEntry(&entry)); - EXPECT_EQ("keep.txt", entry.path); - - ASSERT_EQ(0, writer.StartEntry("drop.txt", 0)); - ASSERT_EQ(0, writer.WriteBytes(kDropThis, strlen(kDropThis))); - ASSERT_EQ(0, writer.FinishEntry()); - - ASSERT_EQ(0, writer.GetLastEntry(&entry)); - EXPECT_EQ("drop.txt", entry.path); - - ASSERT_EQ(0, writer.DiscardLastEntry()); - - ASSERT_EQ(0, writer.GetLastEntry(&entry)); - EXPECT_EQ("keep.txt", entry.path); - - ASSERT_EQ(0, writer.StartEntry("replace.txt", 0)); - ASSERT_EQ(0, writer.WriteBytes(kReplaceWithThis, strlen(kReplaceWithThis))); - ASSERT_EQ(0, writer.FinishEntry()); - - ASSERT_EQ(0, writer.GetLastEntry(&entry)); - EXPECT_EQ("replace.txt", entry.path); - - ASSERT_EQ(0, writer.Finish()); - - // Verify that "drop.txt" does not exist. - - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - - ZipEntry data; - ASSERT_EQ(0, FindEntry(handle, "keep.txt", &data)); - ASSERT_TRUE(AssertFileEntryContentsEq(kKeepThis, handle, &data)); - - ASSERT_NE(0, FindEntry(handle, "drop.txt", &data)); - - ASSERT_EQ(0, FindEntry(handle, "replace.txt", &data)); - ASSERT_TRUE(AssertFileEntryContentsEq(kReplaceWithThis, handle, &data)); - - CloseArchive(handle); -} - -TEST_F(zipwriter, WriteToUnseekableFile) { - const char* expected = "hello"; - ZipWriter writer(file_); - writer.seekable_ = false; - - ASSERT_EQ(0, writer.StartEntry("file.txt", 0)); - ASSERT_EQ(0, writer.WriteBytes(expected, strlen(expected))); - ASSERT_EQ(0, writer.FinishEntry()); - ASSERT_EQ(0, writer.Finish()); - ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); - - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); - ZipEntry data; - ASSERT_EQ(0, FindEntry(handle, "file.txt", &data)); - EXPECT_EQ(kCompressStored, data.method); - EXPECT_EQ(1u, data.has_data_descriptor); - EXPECT_EQ(strlen(expected), data.compressed_length); - ASSERT_EQ(strlen(expected), data.uncompressed_length); - ASSERT_TRUE(AssertFileEntryContentsEq(expected, handle, &data)); - CloseArchive(handle); -} - -TEST_F(zipwriter, TruncateFileAfterBackup) { - ZipWriter writer(file_); - - const char* kSmall = "small"; - - ASSERT_EQ(0, writer.StartEntry("small.txt", 0)); - ASSERT_EQ(0, writer.WriteBytes(kSmall, strlen(kSmall))); - ASSERT_EQ(0, writer.FinishEntry()); - - ASSERT_EQ(0, writer.StartEntry("large.txt", 0)); - std::vector data; - data.resize(1024 * 1024, 0xef); - ASSERT_EQ(0, writer.WriteBytes(data.data(), data.size())); - ASSERT_EQ(0, writer.FinishEntry()); - - off_t before_len = ftello(file_); - - ZipWriter::FileEntry entry; - ASSERT_EQ(0, writer.GetLastEntry(&entry)); - ASSERT_EQ(0, writer.DiscardLastEntry()); - - ASSERT_EQ(0, writer.Finish()); - - off_t after_len = ftello(file_); - - ASSERT_GT(before_len, after_len); -} - -static ::testing::AssertionResult AssertFileEntryContentsEq(const std::string& expected, - ZipArchiveHandle handle, - ZipEntry* zip_entry) { - if (expected.size() != zip_entry->uncompressed_length) { - return ::testing::AssertionFailure() - << "uncompressed entry size " << zip_entry->uncompressed_length - << " does not match expected size " << expected.size(); - } - - std::string actual; - actual.resize(expected.size()); - - uint8_t* buffer = reinterpret_cast(&*actual.begin()); - if (ExtractToMemory(handle, zip_entry, buffer, static_cast(actual.size())) != 0) { - return ::testing::AssertionFailure() << "failed to extract entry"; - } - - if (expected != actual) { - return ::testing::AssertionFailure() << "actual zip_entry data '" << actual - << "' does not match expected '" << expected << "'"; - } - return ::testing::AssertionSuccess(); -} diff --git a/libziparchive/ziptool-tests.xml b/libziparchive/ziptool-tests.xml deleted file mode 100644 index 211119fe3..000000000 --- a/libziparchive/ziptool-tests.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - diff --git a/libziparchive/ziptool.cpp b/libziparchive/ziptool.cpp deleted file mode 100644 index a2615359a..000000000 --- a/libziparchive/ziptool.cpp +++ /dev/null @@ -1,532 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -using android::base::EndsWith; -using android::base::StartsWith; - -enum OverwriteMode { - kAlways, - kNever, - kPrompt, -}; - -enum Role { - kUnzip, - kZipinfo, -}; - -static Role role; -static OverwriteMode overwrite_mode = kPrompt; -static bool flag_1 = false; -static std::string flag_d; -static bool flag_l = false; -static bool flag_p = false; -static bool flag_q = false; -static bool flag_v = false; -static bool flag_x = false; -static const char* archive_name = nullptr; -static std::set includes; -static std::set excludes; -static uint64_t total_uncompressed_length = 0; -static uint64_t total_compressed_length = 0; -static size_t file_count = 0; - -static const char* g_progname; - -static void die(int error, const char* fmt, ...) { - va_list ap; - - va_start(ap, fmt); - fprintf(stderr, "%s: ", g_progname); - vfprintf(stderr, fmt, ap); - if (error != 0) fprintf(stderr, ": %s", strerror(error)); - fprintf(stderr, "\n"); - va_end(ap); - exit(1); -} - -static bool ShouldInclude(const std::string& name) { - // Explicitly excluded? - if (!excludes.empty()) { - for (const auto& exclude : excludes) { - if (!fnmatch(exclude.c_str(), name.c_str(), 0)) return false; - } - } - - // Implicitly included? - if (includes.empty()) return true; - - // Explicitly included? - for (const auto& include : includes) { - if (!fnmatch(include.c_str(), name.c_str(), 0)) return true; - } - return false; -} - -static bool MakeDirectoryHierarchy(const std::string& path) { - // stat rather than lstat because a symbolic link to a directory is fine too. - struct stat sb; - if (stat(path.c_str(), &sb) != -1 && S_ISDIR(sb.st_mode)) return true; - - // Ensure the parent directories exist first. - if (!MakeDirectoryHierarchy(android::base::Dirname(path))) return false; - - // Then try to create this directory. - return (mkdir(path.c_str(), 0777) != -1); -} - -static float CompressionRatio(int64_t uncompressed, int64_t compressed) { - if (uncompressed == 0) return 0; - return static_cast(100LL * (uncompressed - compressed)) / - static_cast(uncompressed); -} - -static void MaybeShowHeader(ZipArchiveHandle zah) { - if (role == kUnzip) { - // unzip has three formats. - if (!flag_q) printf("Archive: %s\n", archive_name); - if (flag_v) { - printf( - " Length Method Size Cmpr Date Time CRC-32 Name\n" - "-------- ------ ------- ---- ---------- ----- -------- ----\n"); - } else if (flag_l) { - printf( - " Length Date Time Name\n" - "--------- ---------- ----- ----\n"); - } - } else { - // zipinfo. - if (!flag_1 && includes.empty() && excludes.empty()) { - ZipArchiveInfo info{GetArchiveInfo(zah)}; - printf("Archive: %s\n", archive_name); - printf("Zip file size: %" PRId64 " bytes, number of entries: %" PRIu64 "\n", - info.archive_size, info.entry_count); - } - } -} - -static void MaybeShowFooter() { - if (role == kUnzip) { - if (flag_v) { - printf( - "-------- ------- --- -------\n" - "%8" PRId64 " %8" PRId64 " %3.0f%% %zu file%s\n", - total_uncompressed_length, total_compressed_length, - CompressionRatio(total_uncompressed_length, total_compressed_length), file_count, - (file_count == 1) ? "" : "s"); - } else if (flag_l) { - printf( - "--------- -------\n" - "%9" PRId64 " %zu file%s\n", - total_uncompressed_length, file_count, (file_count == 1) ? "" : "s"); - } - } else { - if (!flag_1 && includes.empty() && excludes.empty()) { - printf("%zu files, %" PRId64 " bytes uncompressed, %" PRId64 " bytes compressed: %.1f%%\n", - file_count, total_uncompressed_length, total_compressed_length, - CompressionRatio(total_uncompressed_length, total_compressed_length)); - } - } -} - -static bool PromptOverwrite(const std::string& dst) { - // TODO: [r]ename not implemented because it doesn't seem useful. - printf("replace %s? [y]es, [n]o, [A]ll, [N]one: ", dst.c_str()); - fflush(stdout); - while (true) { - char* line = nullptr; - size_t n; - if (getline(&line, &n, stdin) == -1) { - die(0, "(EOF/read error; assuming [N]one...)"); - overwrite_mode = kNever; - return false; - } - if (n == 0) continue; - char cmd = line[0]; - free(line); - switch (cmd) { - case 'y': - return true; - case 'n': - return false; - case 'A': - overwrite_mode = kAlways; - return true; - case 'N': - overwrite_mode = kNever; - return false; - } - } -} - -static void ExtractToPipe(ZipArchiveHandle zah, const ZipEntry64& entry, const std::string& name) { - // We need to extract to memory because ExtractEntryToFile insists on - // being able to seek and truncate, and you can't do that with stdout. - if (entry.uncompressed_length > SIZE_MAX) { - die(0, "entry size %" PRIu64 " is too large to extract.", entry.uncompressed_length); - } - auto uncompressed_length = static_cast(entry.uncompressed_length); - uint8_t* buffer = new uint8_t[uncompressed_length]; - int err = ExtractToMemory(zah, &entry, buffer, uncompressed_length); - if (err < 0) { - die(0, "failed to extract %s: %s", name.c_str(), ErrorCodeString(err)); - } - if (!android::base::WriteFully(1, buffer, uncompressed_length)) { - die(errno, "failed to write %s to stdout", name.c_str()); - } - delete[] buffer; -} - -static void ExtractOne(ZipArchiveHandle zah, const ZipEntry64& entry, const std::string& name) { - // Bad filename? - if (StartsWith(name, "/") || StartsWith(name, "../") || name.find("/../") != std::string::npos) { - die(0, "bad filename %s", name.c_str()); - } - - // Where are we actually extracting to (for human-readable output)? - // flag_d is the empty string if -d wasn't used, or has a trailing '/' - // otherwise. - std::string dst = flag_d + name; - - // Ensure the directory hierarchy exists. - if (!MakeDirectoryHierarchy(android::base::Dirname(name))) { - die(errno, "couldn't create directory hierarchy for %s", dst.c_str()); - } - - // An entry in a zip file can just be a directory itself. - if (EndsWith(name, "/")) { - if (mkdir(name.c_str(), entry.unix_mode) == -1) { - // If the directory already exists, that's fine. - if (errno == EEXIST) { - struct stat sb; - if (stat(name.c_str(), &sb) != -1 && S_ISDIR(sb.st_mode)) return; - } - die(errno, "couldn't extract directory %s", dst.c_str()); - } - return; - } - - // Create the file. - int fd = open(name.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC | O_EXCL, entry.unix_mode); - if (fd == -1 && errno == EEXIST) { - if (overwrite_mode == kNever) return; - if (overwrite_mode == kPrompt && !PromptOverwrite(dst)) return; - // Either overwrite_mode is kAlways or the user consented to this specific case. - fd = open(name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | O_TRUNC, entry.unix_mode); - } - if (fd == -1) die(errno, "couldn't create file %s", dst.c_str()); - - // Actually extract into the file. - if (!flag_q) printf(" inflating: %s\n", dst.c_str()); - int err = ExtractEntryToFile(zah, &entry, fd); - if (err < 0) die(0, "failed to extract %s: %s", dst.c_str(), ErrorCodeString(err)); - close(fd); -} - -static void ListOne(const ZipEntry64& entry, const std::string& name) { - tm t = entry.GetModificationTime(); - char time[32]; - snprintf(time, sizeof(time), "%04d-%02d-%02d %02d:%02d", t.tm_year + 1900, t.tm_mon + 1, - t.tm_mday, t.tm_hour, t.tm_min); - if (flag_v) { - printf("%8" PRIu64 " %s %8" PRIu64 " %3.0f%% %s %08x %s\n", entry.uncompressed_length, - (entry.method == kCompressStored) ? "Stored" : "Defl:N", entry.compressed_length, - CompressionRatio(entry.uncompressed_length, entry.compressed_length), time, entry.crc32, - name.c_str()); - } else { - printf("%9" PRIu64 " %s %s\n", entry.uncompressed_length, time, name.c_str()); - } -} - -static void InfoOne(const ZipEntry64& entry, const std::string& name) { - if (flag_1) { - // "android-ndk-r19b/sources/android/NOTICE" - printf("%s\n", name.c_str()); - return; - } - - int version = entry.version_made_by & 0xff; - int os = (entry.version_made_by >> 8) & 0xff; - - // TODO: Support suid/sgid? Non-Unix/non-FAT host file system attributes? - const char* src_fs = "???"; - char mode[] = "??? "; - if (os == 0) { - src_fs = "fat"; - // https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants - int attrs = entry.external_file_attributes & 0xff; - mode[0] = (attrs & 0x10) ? 'd' : '-'; - mode[1] = 'r'; - mode[2] = (attrs & 0x01) ? '-' : 'w'; - // The man page also mentions ".btm", but that seems to be obsolete? - mode[3] = EndsWith(name, ".exe") || EndsWith(name, ".com") || EndsWith(name, ".bat") || - EndsWith(name, ".cmd") - ? 'x' - : '-'; - mode[4] = (attrs & 0x20) ? 'a' : '-'; - mode[5] = (attrs & 0x02) ? 'h' : '-'; - mode[6] = (attrs & 0x04) ? 's' : '-'; - } else if (os == 3) { - src_fs = "unx"; - mode[0] = S_ISDIR(entry.unix_mode) ? 'd' : (S_ISREG(entry.unix_mode) ? '-' : '?'); - mode[1] = entry.unix_mode & S_IRUSR ? 'r' : '-'; - mode[2] = entry.unix_mode & S_IWUSR ? 'w' : '-'; - mode[3] = entry.unix_mode & S_IXUSR ? 'x' : '-'; - mode[4] = entry.unix_mode & S_IRGRP ? 'r' : '-'; - mode[5] = entry.unix_mode & S_IWGRP ? 'w' : '-'; - mode[6] = entry.unix_mode & S_IXGRP ? 'x' : '-'; - mode[7] = entry.unix_mode & S_IROTH ? 'r' : '-'; - mode[8] = entry.unix_mode & S_IWOTH ? 'w' : '-'; - mode[9] = entry.unix_mode & S_IXOTH ? 'x' : '-'; - } - - char method[5] = "stor"; - if (entry.method == kCompressDeflated) { - snprintf(method, sizeof(method), "def%c", "NXFS"[(entry.gpbf >> 1) & 0x3]); - } - - // TODO: zipinfo (unlike unzip) sometimes uses time zone? - // TODO: this uses 4-digit years because we're not barbarians unless interoperability forces it. - tm t = entry.GetModificationTime(); - char time[32]; - snprintf(time, sizeof(time), "%04d-%02d-%02d %02d:%02d", t.tm_year + 1900, t.tm_mon + 1, - t.tm_mday, t.tm_hour, t.tm_min); - - // "-rw-r--r-- 3.0 unx 577 t- defX 19-Feb-12 16:09 android-ndk-r19b/sources/android/NOTICE" - printf("%s %2d.%d %s %8" PRIu64 " %c%c %s %s %s\n", mode, version / 10, version % 10, src_fs, - entry.uncompressed_length, entry.is_text ? 't' : 'b', - entry.has_data_descriptor ? 'X' : 'x', method, time, name.c_str()); -} - -static void ProcessOne(ZipArchiveHandle zah, const ZipEntry64& entry, const std::string& name) { - if (role == kUnzip) { - if (flag_l || flag_v) { - // -l or -lv or -lq or -v. - ListOne(entry, name); - } else { - // Actually extract. - if (flag_p) { - ExtractToPipe(zah, entry, name); - } else { - ExtractOne(zah, entry, name); - } - } - } else { - // zipinfo or zipinfo -1. - InfoOne(entry, name); - } - total_uncompressed_length += entry.uncompressed_length; - total_compressed_length += entry.compressed_length; - ++file_count; -} - -static void ProcessAll(ZipArchiveHandle zah) { - MaybeShowHeader(zah); - - // libziparchive iteration order doesn't match the central directory. - // We could sort, but that would cost extra and wouldn't match either. - void* cookie; - int err = StartIteration(zah, &cookie); - if (err != 0) { - die(0, "couldn't iterate %s: %s", archive_name, ErrorCodeString(err)); - } - - ZipEntry64 entry; - std::string name; - while ((err = Next(cookie, &entry, &name)) >= 0) { - if (ShouldInclude(name)) ProcessOne(zah, entry, name); - } - - if (err < -1) die(0, "failed iterating %s: %s", archive_name, ErrorCodeString(err)); - EndIteration(cookie); - - MaybeShowFooter(); -} - -static void ShowHelp(bool full) { - if (role == kUnzip) { - fprintf(full ? stdout : stderr, "usage: unzip [-d DIR] [-lnopqv] ZIP [FILE...] [-x FILE...]\n"); - if (!full) exit(EXIT_FAILURE); - - printf( - "\n" - "Extract FILEs from ZIP archive. Default is all files. Both the include and\n" - "exclude (-x) lists use shell glob patterns.\n" - "\n" - "-d DIR Extract into DIR\n" - "-l List contents (-lq excludes archive name, -lv is verbose)\n" - "-n Never overwrite files (default: prompt)\n" - "-o Always overwrite files\n" - "-p Pipe to stdout\n" - "-q Quiet\n" - "-v List contents verbosely\n" - "-x FILE Exclude files\n"); - } else { - fprintf(full ? stdout : stderr, "usage: zipinfo [-1] ZIP [FILE...] [-x FILE...]\n"); - if (!full) exit(EXIT_FAILURE); - - printf( - "\n" - "Show information about FILEs from ZIP archive. Default is all files.\n" - "Both the include and exclude (-x) lists use shell glob patterns.\n" - "\n" - "-1 Show filenames only, one per line\n" - "-x FILE Exclude files\n"); - } - exit(EXIT_SUCCESS); -} - -static void HandleCommonOption(int opt) { - switch (opt) { - case 'h': - ShowHelp(true); - break; - case 'x': - flag_x = true; - break; - case 1: - // -x swallows all following arguments, so we use '-' in the getopt - // string and collect files here. - if (!archive_name) { - archive_name = optarg; - } else if (flag_x) { - excludes.insert(optarg); - } else { - includes.insert(optarg); - } - break; - default: - ShowHelp(false); - break; - } -} - -int main(int argc, char* argv[]) { - // Who am I, and what am I doing? - g_progname = basename(argv[0]); - if (!strcmp(g_progname, "ziptool") && argc > 1) return main(argc - 1, argv + 1); - if (!strcmp(g_progname, "unzip")) { - role = kUnzip; - } else if (!strcmp(g_progname, "zipinfo")) { - role = kZipinfo; - } else { - die(0, "run as ziptool with unzip or zipinfo as the first argument, or symlink"); - } - - static const struct option opts[] = { - {"help", no_argument, 0, 'h'}, - {}, - }; - - if (role == kUnzip) { - // `unzip -Z` is "zipinfo mode", so in that case just restart... - if (argc > 1 && !strcmp(argv[1], "-Z")) { - argv[1] = const_cast("zipinfo"); - return main(argc - 1, argv + 1); - } - - int opt; - while ((opt = getopt_long(argc, argv, "-d:hlnopqvx", opts, nullptr)) != -1) { - switch (opt) { - case 'd': - flag_d = optarg; - if (!EndsWith(flag_d, "/")) flag_d += '/'; - break; - case 'l': - flag_l = true; - break; - case 'n': - overwrite_mode = kNever; - break; - case 'o': - overwrite_mode = kAlways; - break; - case 'p': - flag_p = flag_q = true; - break; - case 'q': - flag_q = true; - break; - case 'v': - flag_v = true; - break; - default: - HandleCommonOption(opt); - break; - } - } - } else { - int opt; - while ((opt = getopt_long(argc, argv, "-1hx", opts, nullptr)) != -1) { - switch (opt) { - case '1': - flag_1 = true; - break; - default: - HandleCommonOption(opt); - break; - } - } - } - - if (!archive_name) die(0, "missing archive filename"); - - // We can't support "-" to unzip from stdin because libziparchive relies on mmap. - ZipArchiveHandle zah; - int32_t err; - if ((err = OpenArchive(archive_name, &zah)) != 0) { - die(0, "couldn't open %s: %s", archive_name, ErrorCodeString(err)); - } - - // Implement -d by changing into that directory. - // We'll create implicit directories based on paths in the zip file, and we'll create - // the -d directory itself, but we require that *parents* of the -d directory already exists. - // This is pretty arbitrary, but it's the behavior of the original unzip. - if (!flag_d.empty()) { - if (mkdir(flag_d.c_str(), 0777) == -1 && errno != EEXIST) { - die(errno, "couldn't created %s", flag_d.c_str()); - } - if (chdir(flag_d.c_str()) == -1) { - die(errno, "couldn't chdir to %s", flag_d.c_str()); - } - } - - ProcessAll(zah); - - CloseArchive(zah); - return 0; -}