Merge "Migrate platform/system/core/libziparchive to p/system/libziparchive"
This commit is contained in:
commit
1b1ac36214
38 changed files with 0 additions and 7063 deletions
|
|
@ -1 +0,0 @@
|
|||
../.clang-format-2
|
||||
|
|
@ -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"],
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
narayan@google.com
|
||||
Binary file not shown.
|
|
@ -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
|
||||
---
|
||||
|
|
@ -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
|
||||
---
|
||||
|
|
@ -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_
|
||||
|
|
@ -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)));
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
BIN
libziparchive/testdata/bad_crc.zip
vendored
BIN
libziparchive/testdata/bad_crc.zip
vendored
Binary file not shown.
BIN
libziparchive/testdata/bad_filename.zip
vendored
BIN
libziparchive/testdata/bad_filename.zip
vendored
Binary file not shown.
BIN
libziparchive/testdata/crash.apk
vendored
BIN
libziparchive/testdata/crash.apk
vendored
Binary file not shown.
BIN
libziparchive/testdata/declaredlength.zip
vendored
BIN
libziparchive/testdata/declaredlength.zip
vendored
Binary file not shown.
BIN
libziparchive/testdata/dummy-update.zip
vendored
BIN
libziparchive/testdata/dummy-update.zip
vendored
Binary file not shown.
BIN
libziparchive/testdata/empty.zip
vendored
BIN
libziparchive/testdata/empty.zip
vendored
Binary file not shown.
BIN
libziparchive/testdata/large.zip
vendored
BIN
libziparchive/testdata/large.zip
vendored
Binary file not shown.
BIN
libziparchive/testdata/valid.zip
vendored
BIN
libziparchive/testdata/valid.zip
vendored
Binary file not shown.
BIN
libziparchive/testdata/zero-size-cd.zip
vendored
BIN
libziparchive/testdata/zero-size-cd.zip
vendored
Binary file not shown.
BIN
libziparchive/testdata/zip64.zip
vendored
BIN
libziparchive/testdata/zip64.zip
vendored
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
|
@ -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();
|
||||
|
|
@ -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_ */
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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_++;
|
||||
}
|
||||
|
|
@ -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_;
|
||||
};
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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(¤t_file_entry_, data, len32);
|
||||
} else {
|
||||
result = StoreBytes(¤t_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(¤t_file_entry_);
|
||||
if (result != kNoError) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (ShouldUseDataDescriptor()) {
|
||||
// Some versions of ZIP don't allow STORED data to have a trailing DataDescriptor.
|
||||
// If this file is not seekable, or if the data is compressed, write a DataDescriptor.
|
||||
// We haven't supported zip64 format yet. Write both uncompressed size and compressed
|
||||
// size as uint32_t.
|
||||
std::vector<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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue