Migrate platform/system/core/libziparchive to p/system/libziparchive

BUG: 149737100
Test: Forrest Run and TH
Change-Id: Id0872496eb9fcfb5075ddec22956a9a35d1514d4
Merged-In: I357a8c655855949bb0957675510bbaa26b47bc3c
This commit is contained in:
Baligh Uddin 2020-05-17 13:31:45 -07:00
parent bd05d33ce7
commit db6e6038d0
38 changed files with 0 additions and 7063 deletions

View File

@ -1 +0,0 @@
../.clang-format-2

View File

@ -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"],
}

View File

@ -1 +0,0 @@
narayan@google.com

View File

@ -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
---

View File

@ -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
---

View File

@ -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 <stddef.h>
#include <stdint.h>
#include <limits>
// 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<uint16_t>::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<uint8_t>((byte & 0x7f) << 1); first & 0x80;
first = static_cast<uint8_t>((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_

View File

@ -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 <gtest/gtest.h>
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)));
}

View File

@ -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 <stdint.h>
#include <string.h>
#include <sys/cdefs.h>
#include <sys/types.h>
#include <functional>
#include <string>
#include <string_view>
#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<bool(std::string_view entry_name)> 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

View File

@ -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 <ziparchive/zip_archive.h>
#include <vector>
#include "android-base/off64_t.h"
class ZipArchiveStreamEntry {
public:
virtual ~ZipArchiveStreamEntry() {}
virtual const std::vector<uint8_t>* 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;
};

View File

@ -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 <cstdio>
#include <ctime>
#include <gtest/gtest_prod.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#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<FileEntry> files_;
FileEntry current_file_entry_;
std::unique_ptr<z_stream, void (*)(z_stream*)> z_stream_;
std::vector<uint8_t> buffer_;
FRIEND_TEST(zipwriter, WriteToUnseekableFile);
};

View File

@ -1,13 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <stddef.h>
#include <stdint.h>
#include <ziparchive/zip_archive.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
ZipArchiveHandle handle = nullptr;
OpenArchiveFromMemory(data, size, "fuzz", &handle);
CloseArchive(handle);
return 0;
}

View File

@ -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

View File

@ -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)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <tuple>
#include <vector>
#include <android-base/test_utils.h>
#include <benchmark/benchmark.h>
#include <ziparchive/zip_archive.h>
#include <ziparchive/zip_archive_stream_entry.h>
#include <ziparchive/zip_writer.h>
static std::unique_ptr<TemporaryFile> CreateZip(int size = 4, int count = 1000) {
auto result = std::make_unique<TemporaryFile>();
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<TemporaryFile> 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<TemporaryFile> 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<TemporaryFile> 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<uint8_t> 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();

View File

@ -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 <inttypes.h>
#include <optional>
// 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<uint64_t> uncompressed_file_size;
// Size in bytes of the compressed file.
std::optional<uint64_t> compressed_file_size;
// Local file header offset relative to the start of the zip file.
std::optional<uint64_t> 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_ */

View File

@ -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 <ziparchive/zip_archive.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory>
#include <utility>
#include <vector>
#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<off64_t>(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<android::base::MappedFile> directory_map;
// number of entries in the Zip archive
uint64_t num_entries;
std::unique_ptr<CdEntryMapInterface> 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 <typename T>
static T ConsumeUnaligned(uint8_t** address) {
auto ret = android::base::get_unaligned<T>(*address);
*address += sizeof(T);
return ret;
}
// Writes the unaligned data of type |T| and auto increment the offset.
template <typename T>
void EmitUnaligned(uint8_t** address, T data) {
android::base::put_unaligned<T>(*address, data);
*address += sizeof(T);
}

View File

@ -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 <errno.h>
#include <inttypes.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <log/log.h>
#include <ziparchive/zip_archive.h>
#include <ziparchive/zip_archive_stream_entry.h>
#include <zlib.h>
#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<uint8_t>* Read() override;
bool Verify() override;
protected:
bool Init(const ZipEntry& entry) override;
uint32_t length_ = 0u;
private:
std::vector<uint8_t> 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<uint8_t>* 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<ZipArchive*>(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<uint32_t>(
crc32(computed_crc32_, data_.data(), static_cast<uint32_t>(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<uint8_t>* Read() override;
bool Verify() override;
protected:
bool Init(const ZipEntry& entry) override;
private:
bool z_stream_init_ = false;
z_stream z_stream_;
std::vector<uint8_t> in_;
std::vector<uint8_t> 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<uint8_t>* 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<uint32_t>(out_.size());
;
}
while (true) {
if (z_stream_.avail_in == 0) {
if (compressed_length_ == 0) {
return nullptr;
}
DCHECK_LE(in_.size(), std::numeric_limits<uint32_t>::max()); // Should be buf size = 64k.
uint32_t bytes = (compressed_length_ > in_.size()) ? static_cast<uint32_t>(in_.size())
: compressed_length_;
ZipArchive* archive = reinterpret_cast<ZipArchive*>(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<uint32_t>(
crc32(computed_crc32_, out_.data(), static_cast<uint32_t>(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<uint32_t>(
crc32(computed_crc32_, out_.data(), static_cast<uint32_t>(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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <android-base/logging.h>
#include <log/log.h>
/*
* 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<uint32_t>(std::hash<std::string_view>{}(name));
}
// Convert a ZipEntry to a hash table index, verifying that it's in a valid range.
std::pair<ZipError, uint64_t> 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<int>(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<int>(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<const char*>(start);
hash_table_[ent].name_offset = static_cast<uint32_t>(name.data() - start_char);
hash_table_[ent].name_length = static_cast<uint16_t>(name.size());
return kSuccess;
}
void CdEntryMapZip32::ResetIteration() {
current_position_ = 0;
}
std::pair<std::string_view, uint64_t> 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<ZipStringOffset*>(calloc(hash_table_size_, sizeof(ZipStringOffset))), free};
}
std::unique_ptr<CdEntryMapInterface> 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<CdEntryMapInterface>(entry_map);
}
std::unique_ptr<CdEntryMapInterface> CdEntryMapZip64::Create() {
return std::unique_ptr<CdEntryMapInterface>(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<const char*>(start)});
if (!added) {
ALOGW("Zip: Found duplicate entry %.*s", static_cast<int>(name.size()), name.data());
return kDuplicateEntry;
}
return kSuccess;
}
std::pair<ZipError, uint64_t> 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<int>(name.size()), name.data());
return {kEntryNotFound, 0};
}
return {kSuccess, it->second};
}
void CdEntryMapZip64::ResetIteration() {
iterator_ = entry_table_.begin();
}
std::pair<std::string_view, uint64_t> CdEntryMapZip64::Next(const uint8_t* /*cd_start*/) {
if (iterator_ == entry_table_.end()) {
return {};
}
return *iterator_++;
}

View File

@ -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 <stdint.h>
#include <map>
#include <memory>
#include <string_view>
#include <utility>
#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<ZipError, uint64_t> 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<std::string_view, uint64_t> 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<const char*>(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<CdEntryMapInterface> Create(uint16_t num_entries);
ZipError AddToMap(std::string_view name, const uint8_t* start) override;
std::pair<ZipError, uint64_t> GetCdEntryOffset(std::string_view name,
const uint8_t* cd_start) const override;
void ResetIteration() override;
std::pair<std::string_view, uint64_t> 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<ZipStringOffset[], decltype(&free)> 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<CdEntryMapInterface> Create();
ZipError AddToMap(std::string_view name, const uint8_t* start) override;
std::pair<ZipError, uint64_t> GetCdEntryOffset(std::string_view name,
const uint8_t* cd_start) const override;
void ResetIteration() override;
std::pair<std::string_view, uint64_t> Next(const uint8_t* cd_start) override;
private:
CdEntryMapZip64() = default;
std::map<std::string_view, uint64_t> entry_table_;
std::map<std::string_view, uint64_t>::iterator iterator_;
};

View File

@ -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 <android-base/macros.h>
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";
}

View File

@ -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 <stdint.h>
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,
};

View File

@ -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 <sys/param.h>
#include <sys/stat.h>
#include <zlib.h>
#include <cstdio>
#define DEF_MEM_LEVEL 8 // normally in zutil.h?
#include <memory>
#include <vector>
#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<int32_t>(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<time_t>((static_cast<unsigned long>(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<uint16_t>((year - 80) << 9 | (ptm->tm_mon + 1) << 5 | ptm->tm_mday);
*out_time = static_cast<uint16_t>(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<uint16_t>::max());
dst->file_name_length = static_cast<uint16_t>(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<uint16_t>::max()) {
return HandleError(kIoError);
}
if (flags & kAlign32) {
return kInvalidAlign32Flag;
}
if (powerof2(alignment) == 0) {
return kInvalidAlignment;
}
if (alignment > std::numeric_limits<uint16_t>::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<uint32_t>::max()) {
return HandleError(kIoError);
}
if (!IsValidEntryName(reinterpret_cast<const uint8_t*>(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<char> 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<uint16_t>(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<z_stream, void (*)(z_stream*)>(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<uint32_t>(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<uint64_t>(current_file_entry_.uncompressed_size) >
std::numeric_limits<uint32_t>::max()) {
return HandleError(kIoError);
}
uint32_t len32 = static_cast<uint32_t>(len);
int32_t result = kNoError;
if (current_file_entry_.compression_method & kCompressDeflated) {
result = CompressBytes(&current_file_entry_, data, len32);
} else {
result = StoreBytes(&current_file_entry_, data, len32);
}
if (result != kNoError) {
return result;
}
current_file_entry_.crc32 = static_cast<uint32_t>(
crc32(current_file_entry_.crc32, reinterpret_cast<const Bytef*>(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<const uint8_t*>(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<uint32_t>(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<uint32_t>(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(&current_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<uint32_t> 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<uint16_t>::max());
cdr.file_name_length = static_cast<uint16_t>(file.path.size());
// Checked in StartAlignedEntryWithTime.
DCHECK_LE(file.local_file_header_offset, std::numeric_limits<uint32_t>::max());
cdr.local_file_header_offset = static_cast<uint32_t>(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<uint16_t>::max());
er.num_records_on_disk = static_cast<uint16_t>(files_.size());
er.num_records = static_cast<uint16_t>(files_.size());
if (current_offset_ > std::numeric_limits<uint32_t>::max()) {
return HandleError(kIoError);
}
er.cd_size = static_cast<uint32_t>(current_offset_ - startOfCdr);
er.cd_start_offset = static_cast<uint32_t>(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;
}

View File

@ -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 <android-base/test_utils.h>
#include <gtest/gtest.h>
#include <time.h>
#include <memory>
#include <vector>
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<uint8_t> buffer(kBufSize);
size_t prev = 1;
for (size_t i = 0; i < kBufSize; i++) {
buffer[i] = static_cast<uint8_t>(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<uint8_t> decompress(kBufSize);
memset(decompress.data(), 0, kBufSize);
ASSERT_EQ(0, ExtractToMemory(handle, &data, decompress.data(),
static_cast<uint32_t>(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<uint8_t> 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<uint8_t*>(&*actual.begin());
if (ExtractToMemory(handle, zip_entry, buffer, static_cast<uint32_t>(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();
}

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<configuration description="Config for running ziptool-tests through Atest or in Infra">
<option name="test-suite-tag" value="ziptool-tests" />
<!-- This test requires a device, so it's not annotated with a null-device. -->
<test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
<option name="binary" value="run-ziptool-tests-on-android.sh" />
<!-- Test script assumes a relative path with the cli-tests/ folders. -->
<option name="relative-path-execution" value="true" />
<!-- Tests shouldn't be that long but set 15m to be safe. -->
<option name="per-binary-timeout" value="15m" />
</test>
</configuration>

View File

@ -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 <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <inttypes.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <set>
#include <string>
#include <android-base/file.h>
#include <android-base/strings.h>
#include <ziparchive/zip_archive.h>
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<std::string> includes;
static std::set<std::string> 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<float>(100LL * (uncompressed - compressed)) /
static_cast<float>(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<size_t>(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<char*>("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;
}