Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,59 @@
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build_overrides/build.gni")
if (build_with_chromium && is_ios) {
import("//build/config/ios/bundle_data_from_filelist.gni")
}
if (build_with_chromium) {
static_library("zip") {
sources = [
"redact.h",
"zip.cc",
"zip.h",
"zip_internal.cc",
"zip_internal.h",
"zip_reader.cc",
"zip_reader.h",
"zip_writer.cc",
"zip_writer.h",
]
deps = [
"..:minizip",
"//base",
"//base:i18n",
]
}
static_library("compression_utils") {
sources = [
"compression_utils.cc",
"compression_utils.h",
]
deps = [
"..",
"//base",
]
public_deps = [ ":compression_utils_portable" ]
}
if (is_ios) {
bundle_data_from_filelist("zlib_pak_bundle_data") {
testonly = true
filelist_name = "test_data.filelist"
}
}
}
# This allows other users of Chromium's zlib library, but don't use Chromium's
# //base, to reuse some boilerplate code.
static_library("compression_utils_portable") {
sources = [
"compression_utils_portable.cc",
"compression_utils_portable.h",
]
public_deps = [ ".." ]
}

7
deps/v8/third_party/zlib/google/DEPS vendored Normal file
View File

@ -0,0 +1,7 @@
include_rules = [
'+base',
'+build',
'+testing',
'+third_party/icu/source/i18n/unicode',
'+third_party/zlib/zlib.h',
]

View File

@ -0,0 +1,6 @@
satorux@chromium.org
# compression_utils*
asvitkine@chromium.org
isherman@chromium.org
cavalcantii@chromium.org

View File

@ -0,0 +1,132 @@
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/zlib/google/compression_utils.h"
#include "base/check_op.h"
#include "base/process/memory.h"
#include "third_party/zlib/google/compression_utils_portable.h"
namespace compression {
bool GzipCompress(base::span<const char> input,
char* output_buffer,
size_t output_buffer_size,
size_t* compressed_size,
void* (*malloc_fn)(size_t),
void (*free_fn)(void*)) {
static_assert(sizeof(Bytef) == 1, "");
// uLongf can be larger than size_t.
uLongf compressed_size_long = static_cast<uLongf>(output_buffer_size);
if (zlib_internal::GzipCompressHelper(
reinterpret_cast<Bytef*>(output_buffer), &compressed_size_long,
reinterpret_cast<const Bytef*>(input.data()),
static_cast<uLongf>(input.size()), malloc_fn, free_fn) != Z_OK) {
return false;
}
// No overflow, as compressed_size_long <= output.size() which is a size_t.
*compressed_size = static_cast<size_t>(compressed_size_long);
return true;
}
bool GzipCompress(base::span<const char> input, std::string* output) {
return GzipCompress(base::as_bytes(input), output);
}
bool GzipCompress(base::span<const uint8_t> input, std::string* output) {
// Not using std::vector<> because allocation failures are recoverable,
// which is hidden by std::vector<>.
static_assert(sizeof(Bytef) == 1, "");
const uLongf input_size = static_cast<uLongf>(input.size());
uLongf compressed_data_size =
zlib_internal::GzipExpectedCompressedSize(input_size);
Bytef* compressed_data;
if (!base::UncheckedMalloc(compressed_data_size,
reinterpret_cast<void**>(&compressed_data))) {
return false;
}
if (zlib_internal::GzipCompressHelper(
compressed_data, &compressed_data_size,
reinterpret_cast<const Bytef*>(input.data()), input_size, nullptr,
nullptr) != Z_OK) {
free(compressed_data);
return false;
}
Bytef* resized_data =
reinterpret_cast<Bytef*>(realloc(compressed_data, compressed_data_size));
if (!resized_data) {
free(compressed_data);
return false;
}
output->assign(resized_data, resized_data + compressed_data_size);
DCHECK_EQ(input_size, GetUncompressedSize(*output));
free(resized_data);
return true;
}
bool GzipUncompress(const std::string& input, std::string* output) {
std::string uncompressed_output;
uLongf uncompressed_size = static_cast<uLongf>(GetUncompressedSize(input));
if (size_t{uncompressed_size} > uncompressed_output.max_size())
return false;
uncompressed_output.resize(uncompressed_size);
if (zlib_internal::GzipUncompressHelper(
reinterpret_cast<Bytef*>(uncompressed_output.data()),
&uncompressed_size, reinterpret_cast<const Bytef*>(input.data()),
static_cast<uLongf>(input.length())) == Z_OK) {
output->swap(uncompressed_output);
return true;
}
return false;
}
bool GzipUncompress(base::span<const char> input, base::span<char> output) {
return GzipUncompress(base::as_bytes(input), base::as_writable_bytes(output));
}
bool GzipUncompress(base::span<const uint8_t> input,
base::span<uint8_t> output) {
uLongf uncompressed_size = GetUncompressedSize(input);
if (uncompressed_size > output.size())
return false;
return zlib_internal::GzipUncompressHelper(
reinterpret_cast<Bytef*>(output.data()), &uncompressed_size,
reinterpret_cast<const Bytef*>(input.data()),
static_cast<uLongf>(input.size())) == Z_OK;
}
bool GzipUncompress(base::span<const char> input, std::string* output) {
return GzipUncompress(base::as_bytes(input), output);
}
bool GzipUncompress(base::span<const uint8_t> input, std::string* output) {
// Disallow in-place usage, i.e., |input| using |*output| as underlying data.
DCHECK_NE(reinterpret_cast<const char*>(input.data()), output->data());
uLongf uncompressed_size = GetUncompressedSize(input);
output->resize(uncompressed_size);
return zlib_internal::GzipUncompressHelper(
reinterpret_cast<Bytef*>(output->data()), &uncompressed_size,
reinterpret_cast<const Bytef*>(input.data()),
static_cast<uLongf>(input.size())) == Z_OK;
}
uint32_t GetUncompressedSize(base::span<const char> compressed_data) {
return GetUncompressedSize(base::as_bytes(compressed_data));
}
uint32_t GetUncompressedSize(base::span<const uint8_t> compressed_data) {
return zlib_internal::GetGzipUncompressedSize(
reinterpret_cast<const Bytef*>(compressed_data.data()),
compressed_data.size());
}
} // namespace compression

View File

@ -0,0 +1,69 @@
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_ZLIB_GOOGLE_COMPRESSION_UTILS_H_
#define THIRD_PARTY_ZLIB_GOOGLE_COMPRESSION_UTILS_H_
#include <string>
#include "base/containers/span.h"
namespace compression {
// Compresses the data in |input| using gzip, storing the result in
// |output_buffer|, of size |output_buffer_size|. If the buffer is large enough
// and compression succeeds, |compressed_size| points to the compressed data
// size after the call.
// |malloc_fn| and |free_fn| are pointers to malloc() and free()-like functions,
// or nullptr to use the standard ones.
// Returns true for success.
bool GzipCompress(base::span<const char> input,
char* output_buffer,
size_t output_buffer_size,
size_t* compressed_size,
void* (*malloc_fn)(size_t),
void (*free_fn)(void*));
// Compresses the data in |input| using gzip, storing the result in |output|.
// |input| and |output| are allowed to point to the same string (in-place
// operation).
// Returns true for success.
bool GzipCompress(base::span<const char> input, std::string* output);
// Like the above method, but using uint8_t instead.
bool GzipCompress(base::span<const uint8_t> input, std::string* output);
// Uncompresses the data in |input| using gzip, storing the result in |output|.
// |input| and |output| are allowed to be the same string (in-place operation).
// Returns true for success.
bool GzipUncompress(const std::string& input, std::string* output);
// Like the above method, but uses base::span to avoid allocations if
// needed. |output|'s size must be at least as large as the return value from
// GetUncompressedSize.
// Returns true for success.
bool GzipUncompress(base::span<const char> input, base::span<char> output);
// Like the above method, but using uint8_t instead.
bool GzipUncompress(base::span<const uint8_t> input,
base::span<uint8_t> output);
// Uncompresses the data in |input| using gzip, and writes the results to
// |output|, which must NOT be the underlying string of |input|, and is resized
// if necessary.
// Returns true for success.
bool GzipUncompress(base::span<const char> input, std::string* output);
// Like the above method, but using uint8_t instead.
bool GzipUncompress(base::span<const uint8_t> input, std::string* output);
// Returns the uncompressed size from GZIP-compressed |compressed_data|.
uint32_t GetUncompressedSize(base::span<const char> compressed_data);
// Like the above method, but using uint8_t instead.
uint32_t GetUncompressedSize(base::span<const uint8_t> compressed_data);
} // namespace compression
#endif // THIRD_PARTY_ZLIB_GOOGLE_COMPRESSION_UTILS_H_

View File

@ -0,0 +1,205 @@
/* compression_utils_portable.cc
*
* Copyright 2019 The Chromium Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the Chromium source repository LICENSE file.
*/
#include "compression_utils_portable.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
namespace zlib_internal {
// The difference in bytes between a zlib header and a gzip header.
const size_t kGzipZlibHeaderDifferenceBytes = 16;
// Pass an integer greater than the following get a gzip header instead of a
// zlib header when calling deflateInit2() and inflateInit2().
const int kWindowBitsToGetGzipHeader = 16;
// This describes the amount of memory zlib uses to compress data. It can go
// from 1 to 9, with 8 being the default. For details, see:
// http://www.zlib.net/manual.html (search for memLevel).
const int kZlibMemoryLevel = 8;
// The expected compressed size is based on the input size factored by
// internal Zlib constants (e.g. window size, etc) plus the wrapper
// header size.
uLongf GzipExpectedCompressedSize(uLongf input_size) {
return kGzipZlibHeaderDifferenceBytes + compressBound(input_size);
}
// The expected decompressed size is stored in the last
// 4 bytes of |input| in LE. See https://tools.ietf.org/html/rfc1952#page-5
uint32_t GetGzipUncompressedSize(const Bytef* compressed_data, size_t length) {
uint32_t size;
if (length < sizeof(size))
return 0;
memcpy(&size, &compressed_data[length - sizeof(size)], sizeof(size));
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return size;
#else
return __builtin_bswap32(size);
#endif
}
// The number of window bits determines the type of wrapper to use - see
// https://cs.chromium.org/chromium/src/third_party/zlib/zlib.h?l=566
inline int ZlibStreamWrapperType(WrapperType type) {
if (type == ZLIB) // zlib DEFLATE stream wrapper
return MAX_WBITS;
if (type == GZIP) // gzip DEFLATE stream wrapper
return MAX_WBITS + kWindowBitsToGetGzipHeader;
if (type == ZRAW) // no wrapper, use raw DEFLATE
return -MAX_WBITS;
return 0;
}
int GzipCompressHelper(Bytef* dest,
uLongf* dest_length,
const Bytef* source,
uLong source_length,
void* (*malloc_fn)(size_t),
void (*free_fn)(void*)) {
return CompressHelper(GZIP, dest, dest_length, source, source_length,
Z_DEFAULT_COMPRESSION, malloc_fn, free_fn);
}
// This code is taken almost verbatim from third_party/zlib/compress.c. The only
// difference is deflateInit2() is called which allows different window bits to
// be set. > 16 causes a gzip header to be emitted rather than a zlib header,
// and negative causes no header to emitted.
//
// Compression level can be a number from 1-9, with 1 being the fastest, 9 being
// the best compression. The default, which the GZIP helper uses, is 6.
int CompressHelper(WrapperType wrapper_type,
Bytef* dest,
uLongf* dest_length,
const Bytef* source,
uLong source_length,
int compression_level,
void* (*malloc_fn)(size_t),
void (*free_fn)(void*)) {
if (compression_level < 0 || compression_level > 9) {
compression_level = Z_DEFAULT_COMPRESSION;
}
z_stream stream;
// FIXME(cavalcantii): z_const is not defined as 'const'.
stream.next_in = static_cast<z_const Bytef*>(const_cast<Bytef*>(source));
stream.avail_in = static_cast<uInt>(source_length);
stream.next_out = dest;
stream.avail_out = static_cast<uInt>(*dest_length);
if (static_cast<uLong>(stream.avail_out) != *dest_length)
return Z_BUF_ERROR;
// Cannot convert capturing lambdas to function pointers directly, hence the
// structure.
struct MallocFreeFunctions {
void* (*malloc_fn)(size_t);
void (*free_fn)(void*);
} malloc_free = {malloc_fn, free_fn};
if (malloc_fn) {
if (!free_fn)
return Z_BUF_ERROR;
auto zalloc = [](void* opaque, uInt items, uInt size) {
return reinterpret_cast<MallocFreeFunctions*>(opaque)->malloc_fn(items *
size);
};
auto zfree = [](void* opaque, void* address) {
return reinterpret_cast<MallocFreeFunctions*>(opaque)->free_fn(address);
};
stream.zalloc = static_cast<alloc_func>(zalloc);
stream.zfree = static_cast<free_func>(zfree);
stream.opaque = static_cast<voidpf>(&malloc_free);
} else {
stream.zalloc = static_cast<alloc_func>(0);
stream.zfree = static_cast<free_func>(0);
stream.opaque = static_cast<voidpf>(0);
}
int err = deflateInit2(&stream, compression_level, Z_DEFLATED,
ZlibStreamWrapperType(wrapper_type), kZlibMemoryLevel,
Z_DEFAULT_STRATEGY);
if (err != Z_OK)
return err;
// This has to exist outside of the if statement to prevent it going off the
// stack before deflate(), which will use this object.
gz_header gzip_header;
if (wrapper_type == GZIP) {
memset(&gzip_header, 0, sizeof(gzip_header));
err = deflateSetHeader(&stream, &gzip_header);
if (err != Z_OK)
return err;
}
err = deflate(&stream, Z_FINISH);
if (err != Z_STREAM_END) {
deflateEnd(&stream);
return err == Z_OK ? Z_BUF_ERROR : err;
}
*dest_length = stream.total_out;
err = deflateEnd(&stream);
return err;
}
int GzipUncompressHelper(Bytef* dest,
uLongf* dest_length,
const Bytef* source,
uLong source_length) {
return UncompressHelper(GZIP, dest, dest_length, source, source_length);
}
// This code is taken almost verbatim from third_party/zlib/uncompr.c. The only
// difference is inflateInit2() is called which allows different window bits to
// be set. > 16 causes a gzip header to be emitted rather than a zlib header,
// and negative causes no header to emitted.
int UncompressHelper(WrapperType wrapper_type,
Bytef* dest,
uLongf* dest_length,
const Bytef* source,
uLong source_length) {
z_stream stream;
// FIXME(cavalcantii): z_const is not defined as 'const'.
stream.next_in = static_cast<z_const Bytef*>(const_cast<Bytef*>(source));
stream.avail_in = static_cast<uInt>(source_length);
if (static_cast<uLong>(stream.avail_in) != source_length)
return Z_BUF_ERROR;
stream.next_out = dest;
stream.avail_out = static_cast<uInt>(*dest_length);
if (static_cast<uLong>(stream.avail_out) != *dest_length)
return Z_BUF_ERROR;
stream.zalloc = static_cast<alloc_func>(0);
stream.zfree = static_cast<free_func>(0);
int err = inflateInit2(&stream, ZlibStreamWrapperType(wrapper_type));
if (err != Z_OK)
return err;
err = inflate(&stream, Z_FINISH);
if (err != Z_STREAM_END) {
inflateEnd(&stream);
if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
return Z_DATA_ERROR;
return err;
}
*dest_length = stream.total_out;
err = inflateEnd(&stream);
return err;
}
} // namespace zlib_internal

View File

@ -0,0 +1,63 @@
/* compression_utils_portable.h
*
* Copyright 2019 The Chromium Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the Chromium source repository LICENSE file.
*/
#ifndef THIRD_PARTY_ZLIB_GOOGLE_COMPRESSION_UTILS_PORTABLE_H_
#define THIRD_PARTY_ZLIB_GOOGLE_COMPRESSION_UTILS_PORTABLE_H_
#include <stdint.h>
/* TODO(cavalcantii): remove support for Chromium ever building with a system
* zlib.
*/
#if defined(USE_SYSTEM_ZLIB)
#include <zlib.h>
/* AOSP build requires relative paths. */
#else
#include "zlib.h"
#endif
namespace zlib_internal {
enum WrapperType {
ZLIB,
GZIP,
ZRAW,
};
uLongf GzipExpectedCompressedSize(uLongf input_size);
uint32_t GetGzipUncompressedSize(const Bytef* compressed_data, size_t length);
int GzipCompressHelper(Bytef* dest,
uLongf* dest_length,
const Bytef* source,
uLong source_length,
void* (*malloc_fn)(size_t),
void (*free_fn)(void*));
int CompressHelper(WrapperType wrapper_type,
Bytef* dest,
uLongf* dest_length,
const Bytef* source,
uLong source_length,
int compression_level,
void* (*malloc_fn)(size_t),
void (*free_fn)(void*));
int GzipUncompressHelper(Bytef* dest,
uLongf* dest_length,
const Bytef* source,
uLong source_length);
int UncompressHelper(WrapperType wrapper_type,
Bytef* dest,
uLongf* dest_length,
const Bytef* source,
uLong source_length);
} // namespace zlib_internal
#endif // THIRD_PARTY_ZLIB_GOOGLE_COMPRESSION_UTILS_PORTABLE_H_

View File

@ -0,0 +1,99 @@
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/zlib/google/compression_utils.h"
#include <stddef.h>
#include <stdint.h>
#include <iterator>
#include <string>
#include "testing/gtest/include/gtest/gtest.h"
namespace compression {
namespace {
// The data to be compressed by gzip. This is the hex representation of "hello
// world".
const uint8_t kData[] = {0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20,
0x77, 0x6f, 0x72, 0x6c, 0x64};
// This is the string representation of gzip compressed string above. It was
// obtained by running echo -n "hello world" | gzip -c | hexdump -e '8 1 ",
// 0x%x"' followed by 0'ing out the OS byte (10th byte) in the header. This is
// so that the test passes on all platforms (that run various OS'es).
const uint8_t kCompressedData[] = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb,
0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, 0x2f, 0xca, 0x49, 0x01,
0x00, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00};
} // namespace
TEST(CompressionUtilsTest, GzipCompression) {
std::string data(reinterpret_cast<const char*>(kData), std::size(kData));
std::string compressed_data;
EXPECT_TRUE(GzipCompress(data, &compressed_data));
std::string golden_compressed_data(
reinterpret_cast<const char*>(kCompressedData),
std::size(kCompressedData));
EXPECT_EQ(golden_compressed_data, compressed_data);
}
TEST(CompressionUtilsTest, GzipUncompression) {
std::string compressed_data(reinterpret_cast<const char*>(kCompressedData),
std::size(kCompressedData));
std::string uncompressed_data;
EXPECT_TRUE(GzipUncompress(compressed_data, &uncompressed_data));
std::string golden_data(reinterpret_cast<const char*>(kData),
std::size(kData));
EXPECT_EQ(golden_data, uncompressed_data);
}
TEST(CompressionUtilsTest, GzipUncompressionFromSpanToString) {
std::string uncompressed_data;
EXPECT_TRUE(GzipUncompress(kCompressedData, &uncompressed_data));
std::string golden_data(reinterpret_cast<const char*>(kData),
std::size(kData));
EXPECT_EQ(golden_data, uncompressed_data);
}
// Checks that compressing/decompressing input > 256 bytes works as expected.
TEST(CompressionUtilsTest, LargeInput) {
const size_t kSize = 32 * 1024;
// Generate a data string of |kSize| for testing.
std::string data;
data.resize(kSize);
for (size_t i = 0; i < kSize; ++i)
data[i] = static_cast<char>(i & 0xFF);
std::string compressed_data;
EXPECT_TRUE(GzipCompress(data, &compressed_data));
std::string uncompressed_data;
EXPECT_TRUE(GzipUncompress(compressed_data, &uncompressed_data));
EXPECT_EQ(data, uncompressed_data);
}
TEST(CompressionUtilsTest, InPlace) {
const std::string original_data(reinterpret_cast<const char*>(kData),
std::size(kData));
const std::string golden_compressed_data(
reinterpret_cast<const char*>(kCompressedData),
std::size(kCompressedData));
std::string data(original_data);
EXPECT_TRUE(GzipCompress(data, &data));
EXPECT_EQ(golden_compressed_data, data);
EXPECT_TRUE(GzipUncompress(data, &data));
EXPECT_EQ(original_data, data);
}
} // namespace compression

View File

@ -0,0 +1,31 @@
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_ZLIB_GOOGLE_REDACT_H_
#define THIRD_PARTY_ZLIB_GOOGLE_REDACT_H_
#include <ostream>
#include "base/files/file_path.h"
#include "base/logging.h"
namespace zip {
// Redacts file paths in log messages.
// Example:
// LOG(ERROR) << "Cannot open " << Redact(path);
class Redact {
public:
explicit Redact(const base::FilePath& path) : path_(path) {}
friend std::ostream& operator<<(std::ostream& out, const Redact&& r) {
return LOG_IS_ON(INFO) ? out << "'" << r.path_ << "'" : out << "(redacted)";
}
private:
const base::FilePath& path_;
};
} // namespace zip
#endif // THIRD_PARTY_ZLIB_GOOGLE_REDACT_H_

View File

@ -0,0 +1,32 @@
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# NOTE: this file is generated by build/ios/update_bundle_filelist.py
# If it requires updating, you should get a presubmit error with
# instructions on how to regenerate. Otherwise, do not edit.
test/data/Different Encryptions.zip
test/data/Empty Dir Same Name As File.zip
test/data/Mixed Paths.zip
test/data/Parent Dir Same Name As File.zip
test/data/README.md
test/data/Repeated Dir Name.zip
test/data/Repeated File Name With Different Cases.zip
test/data/Repeated File Name.zip
test/data/SJIS Bug 846195.zip
test/data/Windows Special Names.zip
test/data/Wrong CRC.zip
test/data/create_test_zip.sh
test/data/empty.zip
test/data/evil.zip
test/data/evil_via_absolute_file_name.zip
test/data/evil_via_invalid_utf8.zip
test/data/test.zip
test/data/test/foo.txt
test/data/test/foo/bar.txt
test/data/test/foo/bar/.hidden
test/data/test/foo/bar/baz.txt
test/data/test/foo/bar/quux.txt
test/data/test_encrypted.zip
test/data/test_mismatch_size.zip
test/data/test_nocompress.zip
test/data/test_posix_permissions.zip

View File

@ -0,0 +1,8 @@
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# See build/ios/update_bundle_filelist.py for details on how .globlist
# files are used to update their .filelist counterparts.
test/data/**
test/data/test/foo/bar/.hidden

274
deps/v8/third_party/zlib/google/zip.cc vendored Normal file
View File

@ -0,0 +1,274 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/zlib/google/zip.h"
#include <string>
#include <vector>
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "third_party/zlib/google/redact.h"
#include "third_party/zlib/google/zip_internal.h"
#include "third_party/zlib/google/zip_reader.h"
#include "third_party/zlib/google/zip_writer.h"
namespace zip {
namespace {
bool IsHiddenFile(const base::FilePath& file_path) {
return file_path.BaseName().value()[0] == '.';
}
// Creates a directory at |extract_dir|/|entry_path|, including any parents.
bool CreateDirectory(const base::FilePath& extract_dir,
const base::FilePath& entry_path) {
const base::FilePath dir = extract_dir.Append(entry_path);
const bool ok = base::CreateDirectory(dir);
PLOG_IF(ERROR, !ok) << "Cannot create directory " << Redact(dir);
return ok;
}
// Creates a WriterDelegate that can write a file at |extract_dir|/|entry_path|.
std::unique_ptr<WriterDelegate> CreateFilePathWriterDelegate(
const base::FilePath& extract_dir,
const base::FilePath& entry_path) {
return std::make_unique<FilePathWriterDelegate>(
extract_dir.Append(entry_path));
}
class DirectFileAccessor : public FileAccessor {
public:
explicit DirectFileAccessor(base::FilePath src_dir)
: src_dir_(std::move(src_dir)) {}
~DirectFileAccessor() override = default;
bool Open(const Paths paths, std::vector<base::File>* const files) override {
DCHECK(files);
files->reserve(files->size() + paths.size());
for (const base::FilePath& path : paths) {
DCHECK(!path.IsAbsolute());
const base::FilePath absolute_path = src_dir_.Append(path);
if (base::DirectoryExists(absolute_path)) {
files->emplace_back();
LOG(ERROR) << "Cannot open " << Redact(path) << ": It is a directory";
} else {
const base::File& file = files->emplace_back(
absolute_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
LOG_IF(ERROR, !file.IsValid())
<< "Cannot open " << Redact(path) << ": "
<< base::File::ErrorToString(file.error_details());
}
}
return true;
}
bool List(const base::FilePath& path,
std::vector<base::FilePath>* const files,
std::vector<base::FilePath>* const subdirs) override {
DCHECK(!path.IsAbsolute());
DCHECK(files);
DCHECK(subdirs);
base::FileEnumerator file_enumerator(
src_dir_.Append(path), false /* recursive */,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
while (!file_enumerator.Next().empty()) {
const base::FileEnumerator::FileInfo info = file_enumerator.GetInfo();
(info.IsDirectory() ? subdirs : files)
->push_back(path.Append(info.GetName()));
}
return true;
}
bool GetInfo(const base::FilePath& path, Info* const info) override {
DCHECK(!path.IsAbsolute());
DCHECK(info);
base::File::Info file_info;
if (!base::GetFileInfo(src_dir_.Append(path), &file_info)) {
PLOG(ERROR) << "Cannot get info of " << Redact(path);
return false;
}
info->is_directory = file_info.is_directory;
info->last_modified = file_info.last_modified;
return true;
}
private:
const base::FilePath src_dir_;
};
} // namespace
std::ostream& operator<<(std::ostream& out, const Progress& progress) {
return out << progress.bytes << " bytes, " << progress.files << " files, "
<< progress.directories << " dirs, " << progress.errors
<< " errors";
}
bool Zip(const ZipParams& params) {
DirectFileAccessor default_accessor(params.src_dir);
FileAccessor* const file_accessor = params.file_accessor ?: &default_accessor;
std::unique_ptr<internal::ZipWriter> zip_writer;
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
if (params.dest_fd != base::kInvalidPlatformFile) {
DCHECK(params.dest_file.empty());
zip_writer =
internal::ZipWriter::CreateWithFd(params.dest_fd, file_accessor);
if (!zip_writer)
return false;
}
#endif
if (!zip_writer) {
zip_writer = internal::ZipWriter::Create(params.dest_file, file_accessor);
if (!zip_writer)
return false;
}
zip_writer->SetProgressCallback(params.progress_callback,
params.progress_period);
zip_writer->SetRecursive(params.recursive);
zip_writer->ContinueOnError(params.continue_on_error);
if (!params.include_hidden_files || params.filter_callback)
zip_writer->SetFilterCallback(base::BindRepeating(
[](const ZipParams* const params, const base::FilePath& path) -> bool {
return (params->include_hidden_files || !IsHiddenFile(path)) &&
(!params->filter_callback ||
params->filter_callback.Run(params->src_dir.Append(path)));
},
&params));
if (params.src_files.empty()) {
// No source items are specified. Zip the entire source directory.
zip_writer->SetRecursive(true);
if (!zip_writer->AddDirectoryContents(base::FilePath()))
return false;
} else {
// Only zip the specified source items.
if (!zip_writer->AddMixedEntries(params.src_files))
return false;
}
return zip_writer->Close();
}
bool Unzip(const base::FilePath& src_file,
const base::FilePath& dest_dir,
UnzipOptions options) {
base::File file(src_file, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
PLOG(ERROR) << "Cannot open " << Redact(src_file) << ": "
<< base::File::ErrorToString(file.error_details());
return false;
}
DLOG_IF(WARNING, !base::IsDirectoryEmpty(dest_dir))
<< "ZIP extraction directory is not empty: " << dest_dir;
return Unzip(file.GetPlatformFile(),
base::BindRepeating(&CreateFilePathWriterDelegate, dest_dir),
base::BindRepeating(&CreateDirectory, dest_dir),
std::move(options));
}
bool Unzip(const base::PlatformFile& src_file,
WriterFactory writer_factory,
DirectoryCreator directory_creator,
UnzipOptions options) {
ZipReader reader;
reader.SetEncoding(std::move(options.encoding));
reader.SetPassword(std::move(options.password));
if (!reader.OpenFromPlatformFile(src_file)) {
LOG(ERROR) << "Cannot open ZIP from file handle " << src_file;
return false;
}
while (const ZipReader::Entry* const entry = reader.Next()) {
if (entry->is_unsafe) {
LOG(ERROR) << "Found unsafe entry " << Redact(entry->path) << " in ZIP";
if (!options.continue_on_error)
return false;
continue;
}
if (options.filter && !options.filter.Run(entry->path)) {
VLOG(1) << "Skipped ZIP entry " << Redact(entry->path);
continue;
}
if (entry->is_directory) {
// It's a directory.
if (!directory_creator.Run(entry->path)) {
LOG(ERROR) << "Cannot create directory " << Redact(entry->path);
if (!options.continue_on_error)
return false;
}
continue;
}
// It's a file.
std::unique_ptr<WriterDelegate> writer = writer_factory.Run(entry->path);
if (!writer ||
(options.progress ? !reader.ExtractCurrentEntryWithListener(
writer.get(), options.progress)
: !reader.ExtractCurrentEntry(writer.get()))) {
LOG(ERROR) << "Cannot extract file " << Redact(entry->path)
<< " from ZIP";
if (!options.continue_on_error)
return false;
}
}
return reader.ok();
}
bool ZipWithFilterCallback(const base::FilePath& src_dir,
const base::FilePath& dest_file,
FilterCallback filter) {
DCHECK(base::DirectoryExists(src_dir));
return Zip({.src_dir = src_dir,
.dest_file = dest_file,
.filter_callback = std::move(filter)});
}
bool Zip(const base::FilePath& src_dir,
const base::FilePath& dest_file,
bool include_hidden_files) {
return Zip({.src_dir = src_dir,
.dest_file = dest_file,
.include_hidden_files = include_hidden_files});
}
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
bool ZipFiles(const base::FilePath& src_dir,
Paths src_relative_paths,
int dest_fd) {
DCHECK(base::DirectoryExists(src_dir));
return Zip({.src_dir = src_dir,
.dest_fd = dest_fd,
.src_files = src_relative_paths});
}
#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
} // namespace zip

219
deps/v8/third_party/zlib/google/zip.h vendored Normal file
View File

@ -0,0 +1,219 @@
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_ZLIB_GOOGLE_ZIP_H_
#define THIRD_PARTY_ZLIB_GOOGLE_ZIP_H_
#include <cstdint>
#include <ostream>
#include <utility>
#include <vector>
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/platform_file.h"
#include "base/functional/callback.h"
#include "base/time/time.h"
#include "build/build_config.h"
namespace base {
class File;
}
namespace zip {
class WriterDelegate;
// Paths passed as span to avoid copying them.
using Paths = base::span<const base::FilePath>;
// Abstraction for file access operation required by Zip().
//
// Can be passed to the ZipParams for providing custom access to the files,
// for example over IPC.
//
// All parameters paths are expected to be relative to the source directory.
class FileAccessor {
public:
virtual ~FileAccessor() = default;
struct Info {
bool is_directory = false;
base::Time last_modified;
};
// Opens files specified in |paths|.
// Directories should be mapped to invalid files.
virtual bool Open(Paths paths, std::vector<base::File>* files) = 0;
// Lists contents of a directory at |path|.
virtual bool List(const base::FilePath& path,
std::vector<base::FilePath>* files,
std::vector<base::FilePath>* subdirs) = 0;
// Gets info about a file or directory.
virtual bool GetInfo(const base::FilePath& path, Info* info) = 0;
};
// Progress of a ZIP creation operation.
struct Progress {
// Total number of bytes read from files getting zipped so far.
std::int64_t bytes = 0;
// Number of file entries added to the ZIP so far.
// A file entry is added after its bytes have been processed.
int files = 0;
// Number of directory entries added to the ZIP so far.
// A directory entry is added before items in it.
int directories = 0;
// Number of errors encountered so far (files that cannot be opened,
// directories that cannot be listed).
int errors = 0;
};
// Prints Progress to output stream.
std::ostream& operator<<(std::ostream& out, const Progress& progress);
// Callback reporting the progress of a ZIP creation operation.
//
// This callback returns a boolean indicating whether the ZIP creation operation
// should continue. If it returns false once, then the ZIP creation operation is
// immediately cancelled and the callback won't be called again.
using ProgressCallback = base::RepeatingCallback<bool(const Progress&)>;
using FilterCallback = base::RepeatingCallback<bool(const base::FilePath&)>;
// ZIP creation parameters and options.
struct ZipParams {
// Source directory. Ignored if |file_accessor| is set.
base::FilePath src_dir;
// Abstraction around file system access used to read files.
// If left null, an implementation that accesses files directly is used.
FileAccessor* file_accessor = nullptr; // Not owned
// Destination file path.
// Either dest_file or dest_fd should be set, but not both.
base::FilePath dest_file;
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Destination file passed a file descriptor.
// Either dest_file or dest_fd should be set, but not both.
int dest_fd = base::kInvalidPlatformFile;
#endif
// The relative paths to the files and directories that should be included in
// the ZIP file. If this is empty, the whole contents of |src_dir| are
// included.
//
// These paths must be relative to |src_dir| and will be used as the file
// names in the created ZIP file. All files must be under |src_dir| in the
// file system hierarchy.
//
// All the paths in |src_files| are included in the created ZIP file,
// irrespective of |include_hidden_files| and |filter_callback|.
Paths src_files;
// Filter used to exclude files from the ZIP file. This is only taken in
// account when recursively adding subdirectory contents.
FilterCallback filter_callback;
// Optional progress reporting callback.
ProgressCallback progress_callback;
// Progress reporting period. The final callback is always called when the ZIP
// creation operation completes.
base::TimeDelta progress_period;
// Should add hidden files? This is only taken in account when recursively
// adding subdirectory contents.
bool include_hidden_files = true;
// Should recursively add subdirectory contents?
bool recursive = false;
// Should ignore errors when discovering files and zipping them?
bool continue_on_error = false;
};
// Zip files specified into a ZIP archives. The source files and ZIP destination
// files (as well as other settings) are specified in |params|.
bool Zip(const ZipParams& params);
// Zip the contents of src_dir into dest_file. src_path must be a directory.
// An entry will *not* be created in the zip for the root folder -- children
// of src_dir will be at the root level of the created zip. For each file in
// src_dir, include it only if the callback |filter_cb| returns true. Otherwise
// omit it.
bool ZipWithFilterCallback(const base::FilePath& src_dir,
const base::FilePath& dest_file,
FilterCallback filter_cb);
// Convenience method for callers who don't need to set up the filter callback.
// If |include_hidden_files| is true, files starting with "." are included.
// Otherwise they are omitted.
bool Zip(const base::FilePath& src_dir,
const base::FilePath& dest_file,
bool include_hidden_files);
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Zips files listed in |src_relative_paths| to destination specified by file
// descriptor |dest_fd|, without taking ownership of |dest_fd|. The paths listed
// in |src_relative_paths| are relative to the |src_dir| and will be used as the
// file names in the created zip file. All source paths must be under |src_dir|
// in the file system hierarchy.
bool ZipFiles(const base::FilePath& src_dir,
Paths src_relative_paths,
int dest_fd);
#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
// Callback reporting the number of bytes written during Unzip.
using UnzipProgressCallback = base::RepeatingCallback<void(uint64_t bytes)>;
// Options of the Unzip function, with valid default values.
struct UnzipOptions {
// Encoding of entry paths in the ZIP archive. By default, paths are assumed
// to be in UTF-8.
std::string encoding;
// Only extract the entries for which |filter_cb| returns true. By default,
// everything gets extracted.
FilterCallback filter;
// Callback to report bytes extracted from the ZIP.
UnzipProgressCallback progress;
// Password to decrypt the encrypted files.
std::string password;
// Should ignore errors when extracting files?
bool continue_on_error = false;
};
typedef base::RepeatingCallback<std::unique_ptr<WriterDelegate>(
const base::FilePath&)>
WriterFactory;
typedef base::RepeatingCallback<bool(const base::FilePath&)> DirectoryCreator;
// Unzips the contents of |zip_file|, using the writers provided by
// |writer_factory|.
bool Unzip(const base::PlatformFile& zip_file,
WriterFactory writer_factory,
DirectoryCreator directory_creator,
UnzipOptions options = {});
// Unzips the contents of |zip_file| into |dest_dir|.
// This function does not overwrite any existing file.
// A filename collision will result in an error.
// Therefore, |dest_dir| should initially be an empty directory.
bool Unzip(const base::FilePath& zip_file,
const base::FilePath& dest_dir,
UnzipOptions options = {});
} // namespace zip
#endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_H_

View File

@ -0,0 +1,465 @@
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/zlib/google/zip_internal.h"
#include <stddef.h>
#include <string.h>
#include <algorithm>
#include <string_view>
#include "base/containers/fixed_flat_set.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#if defined(USE_SYSTEM_MINIZIP)
#include <minizip/ioapi.h>
#include <minizip/unzip.h>
#include <minizip/zip.h>
#else
#include "third_party/zlib/contrib/minizip/unzip.h"
#include "third_party/zlib/contrib/minizip/zip.h"
#if defined(OS_WIN)
#include "third_party/zlib/contrib/minizip/iowin32.h"
#elif defined(OS_POSIX)
#include "third_party/zlib/contrib/minizip/ioapi.h"
#endif // defined(OS_POSIX)
#endif // defined(USE_SYSTEM_MINIZIP)
namespace {
#if defined(OS_WIN)
typedef struct {
HANDLE hf;
int error;
} WIN32FILE_IOWIN;
// This function is derived from third_party/minizip/iowin32.c.
// Its only difference is that it treats the filename as UTF-8 and
// uses the Unicode version of CreateFile.
void* ZipOpenFunc(void* opaque, const void* filename, int mode) {
DWORD desired_access = 0, creation_disposition = 0;
DWORD share_mode = 0, flags_and_attributes = 0;
HANDLE file = 0;
void* ret = NULL;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) {
desired_access = GENERIC_READ;
creation_disposition = OPEN_EXISTING;
share_mode = FILE_SHARE_READ;
} else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) {
desired_access = GENERIC_WRITE | GENERIC_READ;
creation_disposition = OPEN_EXISTING;
} else if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
desired_access = GENERIC_WRITE | GENERIC_READ;
creation_disposition = CREATE_ALWAYS;
}
if (filename != nullptr && desired_access != 0) {
file = CreateFileW(
base::UTF8ToWide(static_cast<const char*>(filename)).c_str(),
desired_access, share_mode, nullptr, creation_disposition,
flags_and_attributes, nullptr);
}
if (file == INVALID_HANDLE_VALUE)
file = NULL;
if (file != NULL) {
WIN32FILE_IOWIN file_ret;
file_ret.hf = file;
file_ret.error = 0;
ret = malloc(sizeof(WIN32FILE_IOWIN));
if (ret == NULL)
CloseHandle(file);
else
*(static_cast<WIN32FILE_IOWIN*>(ret)) = file_ret;
}
return ret;
}
#endif
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Callback function for zlib that opens a file stream from a file descriptor.
// Since we do not own the file descriptor, dup it so that we can fdopen/fclose
// a file stream.
void* FdOpenFileFunc(void* opaque, const void* filename, int mode) {
FILE* file = NULL;
const char* mode_fopen = NULL;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
mode_fopen = "rb";
else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
mode_fopen = "r+b";
else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
mode_fopen = "wb";
if ((filename != NULL) && (mode_fopen != NULL)) {
int fd = dup(*static_cast<int*>(opaque));
if (fd != -1)
file = fdopen(fd, mode_fopen);
}
return file;
}
int FdCloseFileFunc(void* opaque, void* stream) {
fclose(static_cast<FILE*>(stream));
free(opaque); // malloc'ed in FillFdOpenFileFunc()
return 0;
}
// Fills |pzlib_filecunc_def| appropriately to handle the zip file
// referred to by |fd|.
void FillFdOpenFileFunc(zlib_filefunc64_def* pzlib_filefunc_def, int fd) {
fill_fopen64_filefunc(pzlib_filefunc_def);
pzlib_filefunc_def->zopen64_file = FdOpenFileFunc;
pzlib_filefunc_def->zclose_file = FdCloseFileFunc;
int* ptr_fd = static_cast<int*>(malloc(sizeof(fd)));
*ptr_fd = fd;
pzlib_filefunc_def->opaque = ptr_fd;
}
#endif // defined(OS_POSIX)
#if defined(OS_WIN)
// Callback function for zlib that opens a file stream from a Windows handle.
// Does not take ownership of the handle.
void* HandleOpenFileFunc(void* opaque, const void* /*filename*/, int mode) {
WIN32FILE_IOWIN file_ret;
file_ret.hf = static_cast<HANDLE>(opaque);
file_ret.error = 0;
if (file_ret.hf == INVALID_HANDLE_VALUE)
return NULL;
void* ret = malloc(sizeof(WIN32FILE_IOWIN));
if (ret != NULL)
*(static_cast<WIN32FILE_IOWIN*>(ret)) = file_ret;
return ret;
}
int HandleCloseFileFunc(void* opaque, void* stream) {
free(stream); // malloc'ed in HandleOpenFileFunc()
return 0;
}
#endif
// A struct that contains data required for zlib functions to extract files from
// a zip archive stored in memory directly. The following I/O API functions
// expect their opaque parameters refer to this struct.
struct ZipBuffer {
const char* data; // weak
ZPOS64_T length;
ZPOS64_T offset;
};
// Opens the specified file. When this function returns a non-NULL pointer, zlib
// uses this pointer as a stream parameter while compressing or uncompressing
// data. (Returning NULL represents an error.) This function initializes the
// given opaque parameter and returns it because this parameter stores all
// information needed for uncompressing data. (This function does not support
// writing compressed data and it returns NULL for this case.)
void* OpenZipBuffer(void* opaque, const void* /*filename*/, int mode) {
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) != ZLIB_FILEFUNC_MODE_READ) {
NOTREACHED();
}
ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
if (!buffer || !buffer->data || !buffer->length)
return NULL;
buffer->offset = 0;
return opaque;
}
// Reads compressed data from the specified stream. This function copies data
// refered by the opaque parameter and returns the size actually copied.
uLong ReadZipBuffer(void* opaque, void* /*stream*/, void* buf, uLong size) {
ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
DCHECK_LE(buffer->offset, buffer->length);
ZPOS64_T remaining_bytes = buffer->length - buffer->offset;
if (!buffer || !buffer->data || !remaining_bytes)
return 0;
if (size > remaining_bytes)
size = remaining_bytes;
memcpy(buf, &buffer->data[buffer->offset], size);
buffer->offset += size;
return size;
}
// Writes compressed data to the stream. This function always returns zero
// because this implementation is only for reading compressed data.
uLong WriteZipBuffer(void* /*opaque*/,
void* /*stream*/,
const void* /*buf*/,
uLong /*size*/) {
NOTREACHED();
}
// Returns the offset from the beginning of the data.
ZPOS64_T GetOffsetOfZipBuffer(void* opaque, void* /*stream*/) {
ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
if (!buffer)
return -1;
return buffer->offset;
}
// Moves the current offset to the specified position.
long SeekZipBuffer(void* opaque,
void* /*stream*/,
ZPOS64_T offset,
int origin) {
ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
if (!buffer)
return -1;
if (origin == ZLIB_FILEFUNC_SEEK_CUR) {
buffer->offset = std::min(buffer->offset + offset, buffer->length);
return 0;
}
if (origin == ZLIB_FILEFUNC_SEEK_END) {
buffer->offset = (buffer->length > offset) ? buffer->length - offset : 0;
return 0;
}
if (origin == ZLIB_FILEFUNC_SEEK_SET) {
buffer->offset = std::min(buffer->length, offset);
return 0;
}
NOTREACHED();
}
// Closes the input offset and deletes all resources used for compressing or
// uncompressing data. This function deletes the ZipBuffer object referred by
// the opaque parameter since zlib deletes the unzFile object and it does not
// use this object any longer.
int CloseZipBuffer(void* opaque, void* /*stream*/) {
if (opaque)
free(opaque);
return 0;
}
// Returns the last error happened when reading or writing data. This function
// always returns zero, which means there are not any errors.
int GetErrorOfZipBuffer(void* /*opaque*/, void* /*stream*/) {
return 0;
}
// Returns a zip_fileinfo struct with the time represented by |file_time|.
zip_fileinfo TimeToZipFileInfo(const base::Time& file_time) {
base::Time::Exploded file_time_parts;
file_time.UTCExplode(&file_time_parts);
zip_fileinfo zip_info = {};
if (file_time_parts.year >= 1980) {
// This if check works around the handling of the year value in
// contrib/minizip/zip.c in function zip64local_TmzDateToDosDate
// It assumes that dates below 1980 are in the double digit format.
// Hence the fail safe option is to leave the date unset. Some programs
// might show the unset date as 1980-0-0 which is invalid.
zip_info.tmz_date.tm_year = file_time_parts.year;
zip_info.tmz_date.tm_mon = file_time_parts.month - 1;
zip_info.tmz_date.tm_mday = file_time_parts.day_of_month;
zip_info.tmz_date.tm_hour = file_time_parts.hour;
zip_info.tmz_date.tm_min = file_time_parts.minute;
zip_info.tmz_date.tm_sec = file_time_parts.second;
}
return zip_info;
}
} // namespace
namespace zip {
namespace internal {
unzFile OpenForUnzipping(const std::string& file_name_utf8) {
zlib_filefunc64_def* zip_func_ptrs = nullptr;
#if defined(OS_WIN)
zlib_filefunc64_def zip_funcs;
fill_win32_filefunc64(&zip_funcs);
zip_funcs.zopen64_file = ZipOpenFunc;
zip_func_ptrs = &zip_funcs;
#endif
return unzOpen2_64(file_name_utf8.c_str(), zip_func_ptrs);
}
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
unzFile OpenFdForUnzipping(int zip_fd) {
zlib_filefunc64_def zip_funcs;
FillFdOpenFileFunc(&zip_funcs, zip_fd);
// Passing dummy "fd" filename to zlib.
return unzOpen2_64("fd", &zip_funcs);
}
#endif
#if defined(OS_WIN)
unzFile OpenHandleForUnzipping(HANDLE zip_handle) {
zlib_filefunc64_def zip_funcs;
fill_win32_filefunc64(&zip_funcs);
zip_funcs.zopen64_file = HandleOpenFileFunc;
zip_funcs.zclose_file = HandleCloseFileFunc;
zip_funcs.opaque = zip_handle;
return unzOpen2_64("fd", &zip_funcs);
}
#endif
// static
unzFile PrepareMemoryForUnzipping(const std::string& data) {
if (data.empty())
return NULL;
ZipBuffer* buffer = static_cast<ZipBuffer*>(malloc(sizeof(ZipBuffer)));
if (!buffer)
return NULL;
buffer->data = data.data();
buffer->length = data.length();
buffer->offset = 0;
zlib_filefunc64_def zip_functions;
zip_functions.zopen64_file = OpenZipBuffer;
zip_functions.zread_file = ReadZipBuffer;
zip_functions.zwrite_file = WriteZipBuffer;
zip_functions.ztell64_file = GetOffsetOfZipBuffer;
zip_functions.zseek64_file = SeekZipBuffer;
zip_functions.zclose_file = CloseZipBuffer;
zip_functions.zerror_file = GetErrorOfZipBuffer;
zip_functions.opaque = buffer;
return unzOpen2_64(nullptr, &zip_functions);
}
zipFile OpenForZipping(const std::string& file_name_utf8, int append_flag) {
zlib_filefunc64_def* zip_func_ptrs = nullptr;
#if defined(OS_WIN)
zlib_filefunc64_def zip_funcs;
fill_win32_filefunc64(&zip_funcs);
zip_funcs.zopen64_file = ZipOpenFunc;
zip_func_ptrs = &zip_funcs;
#endif
return zipOpen2_64(file_name_utf8.c_str(), append_flag, nullptr,
zip_func_ptrs);
}
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
zipFile OpenFdForZipping(int zip_fd, int append_flag) {
zlib_filefunc64_def zip_funcs;
FillFdOpenFileFunc(&zip_funcs, zip_fd);
// Passing dummy "fd" filename to zlib.
return zipOpen2_64("fd", append_flag, nullptr, &zip_funcs);
}
#endif
bool ZipOpenNewFileInZip(zipFile zip_file,
const std::string& str_path,
base::Time last_modified_time,
Compression compression) {
// Section 4.4.4 http://www.pkware.com/documents/casestudies/APPNOTE.TXT
// Setting the Language encoding flag so the file is told to be in utf-8.
const uLong LANGUAGE_ENCODING_FLAG = 0x1 << 11;
const zip_fileinfo file_info = TimeToZipFileInfo(last_modified_time);
const int err = zipOpenNewFileInZip4_64(
/*file=*/zip_file,
/*filename=*/str_path.c_str(),
/*zip_fileinfo=*/&file_info,
/*extrafield_local=*/nullptr,
/*size_extrafield_local=*/0u,
/*extrafield_global=*/nullptr,
/*size_extrafield_global=*/0u,
/*comment=*/nullptr,
/*method=*/compression,
/*level=*/Z_DEFAULT_COMPRESSION,
/*raw=*/0,
/*windowBits=*/-MAX_WBITS,
/*memLevel=*/DEF_MEM_LEVEL,
/*strategy=*/Z_DEFAULT_STRATEGY,
/*password=*/nullptr,
/*crcForCrypting=*/0,
/*versionMadeBy=*/0,
/*flagBase=*/LANGUAGE_ENCODING_FLAG,
/*zip64=*/1);
if (err != ZIP_OK) {
DLOG(ERROR) << "Cannot open ZIP file entry '" << str_path
<< "': zipOpenNewFileInZip4_64 returned " << err;
return false;
}
return true;
}
Compression GetCompressionMethod(const base::FilePath& path) {
// Get the filename extension in lower case.
const base::FilePath::StringType ext =
base::ToLowerASCII(path.FinalExtension());
if (ext.empty())
return kDeflated;
// Skip the leading dot.
base::FilePath::StringViewType ext_without_dot = ext;
DCHECK_EQ(ext_without_dot.front(), FILE_PATH_LITERAL('.'));
ext_without_dot.remove_prefix(1);
// Well known filename extensions of files that a likely to be already
// compressed. The extensions are in lower case without the leading dot.
static constexpr auto kExts =
base::MakeFixedFlatSet<base::FilePath::StringViewType>({
FILE_PATH_LITERAL("3g2"), //
FILE_PATH_LITERAL("3gp"), //
FILE_PATH_LITERAL("7z"), //
FILE_PATH_LITERAL("7zip"), //
FILE_PATH_LITERAL("aac"), //
FILE_PATH_LITERAL("avi"), //
FILE_PATH_LITERAL("bz"), //
FILE_PATH_LITERAL("bz2"), //
FILE_PATH_LITERAL("crx"), //
FILE_PATH_LITERAL("gif"), //
FILE_PATH_LITERAL("gz"), //
FILE_PATH_LITERAL("jar"), //
FILE_PATH_LITERAL("jpeg"), //
FILE_PATH_LITERAL("jpg"), //
FILE_PATH_LITERAL("lz"), //
FILE_PATH_LITERAL("m2v"), //
FILE_PATH_LITERAL("m4p"), //
FILE_PATH_LITERAL("m4v"), //
FILE_PATH_LITERAL("mng"), //
FILE_PATH_LITERAL("mov"), //
FILE_PATH_LITERAL("mp2"), //
FILE_PATH_LITERAL("mp3"), //
FILE_PATH_LITERAL("mp4"), //
FILE_PATH_LITERAL("mpe"), //
FILE_PATH_LITERAL("mpeg"), //
FILE_PATH_LITERAL("mpg"), //
FILE_PATH_LITERAL("mpv"), //
FILE_PATH_LITERAL("ogg"), //
FILE_PATH_LITERAL("ogv"), //
FILE_PATH_LITERAL("png"), //
FILE_PATH_LITERAL("qt"), //
FILE_PATH_LITERAL("rar"), //
FILE_PATH_LITERAL("taz"), //
FILE_PATH_LITERAL("tb2"), //
FILE_PATH_LITERAL("tbz"), //
FILE_PATH_LITERAL("tbz2"), //
FILE_PATH_LITERAL("tgz"), //
FILE_PATH_LITERAL("tlz"), //
FILE_PATH_LITERAL("tz"), //
FILE_PATH_LITERAL("tz2"), //
FILE_PATH_LITERAL("vob"), //
FILE_PATH_LITERAL("webm"), //
FILE_PATH_LITERAL("wma"), //
FILE_PATH_LITERAL("wmv"), //
FILE_PATH_LITERAL("xz"), //
FILE_PATH_LITERAL("z"), //
FILE_PATH_LITERAL("zip"), //
});
if (kExts.count(ext_without_dot)) {
return kStored;
}
return kDeflated;
}
} // namespace internal
} // namespace zip

View File

@ -0,0 +1,88 @@
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_ZLIB_GOOGLE_ZIP_INTERNAL_H_
#define THIRD_PARTY_ZLIB_GOOGLE_ZIP_INTERNAL_H_
#include <string>
#include "base/time/time.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include <windows.h>
#endif
#if defined(USE_SYSTEM_MINIZIP)
#include <minizip/unzip.h>
#include <minizip/zip.h>
#else
#include "third_party/zlib/contrib/minizip/unzip.h"
#include "third_party/zlib/contrib/minizip/zip.h"
#endif
namespace base {
class FilePath;
}
// Utility functions and constants used internally for the zip file
// library in the directory. Don't use them outside of the library.
namespace zip {
namespace internal {
// Opens the given file name in UTF-8 for unzipping, with some setup for
// Windows.
unzFile OpenForUnzipping(const std::string& file_name_utf8);
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Opens the file referred to by |zip_fd| for unzipping.
unzFile OpenFdForUnzipping(int zip_fd);
#endif
#if defined(OS_WIN)
// Opens the file referred to by |zip_handle| for unzipping.
unzFile OpenHandleForUnzipping(HANDLE zip_handle);
#endif
// Creates a custom unzFile object which reads data from the specified string.
// This custom unzFile object overrides the I/O API functions of zlib so it can
// read data from the specified string.
unzFile PrepareMemoryForUnzipping(const std::string& data);
// Opens the given file name in UTF-8 for zipping, with some setup for
// Windows. |append_flag| will be passed to zipOpen2().
zipFile OpenForZipping(const std::string& file_name_utf8, int append_flag);
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Opens the file referred to by |zip_fd| for zipping. |append_flag| will be
// passed to zipOpen2().
zipFile OpenFdForZipping(int zip_fd, int append_flag);
#endif
// Compression methods.
enum Compression {
kStored = 0, // Stored (no compression)
kDeflated = Z_DEFLATED, // Deflated
};
// Adds a file (or directory) entry to the ZIP archive.
bool ZipOpenNewFileInZip(zipFile zip_file,
const std::string& str_path,
base::Time last_modified_time,
Compression compression);
// Selects the best compression method for the given file. The heuristic is
// based on the filename extension. By default, the compression method is
// kDeflated. But if the given path has an extension indicating a well known
// file format which is likely to be already compressed (eg ZIP, RAR, JPG,
// PNG...) then the compression method is simply kStored.
Compression GetCompressionMethod(const base::FilePath& path);
const int kZipMaxPath = 256;
const int kZipBufSize = 8192;
} // namespace internal
} // namespace zip
#endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_INTERNAL_H_

View File

@ -0,0 +1,723 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/zlib/google/zip_reader.h"
#include <algorithm>
#include <string_view>
#include <utility>
#include "base/check.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "third_party/zlib/google/redact.h"
#include "third_party/zlib/google/zip_internal.h"
#if defined(USE_SYSTEM_MINIZIP)
#include <minizip/unzip.h>
#else
#include "third_party/zlib/contrib/minizip/unzip.h"
#if defined(OS_WIN)
#include "third_party/zlib/contrib/minizip/iowin32.h"
#endif // defined(OS_WIN)
#endif // defined(USE_SYSTEM_MINIZIP)
#if defined(OS_POSIX)
#include <sys/stat.h>
#endif
namespace zip {
namespace {
enum UnzipError : int;
std::ostream& operator<<(std::ostream& out, UnzipError error) {
#define SWITCH_ERR(X) \
case X: \
return out << #X;
switch (error) {
SWITCH_ERR(UNZ_OK);
SWITCH_ERR(UNZ_END_OF_LIST_OF_FILE);
SWITCH_ERR(UNZ_ERRNO);
SWITCH_ERR(UNZ_PARAMERROR);
SWITCH_ERR(UNZ_BADZIPFILE);
SWITCH_ERR(UNZ_INTERNALERROR);
SWITCH_ERR(UNZ_CRCERROR);
default:
return out << "UNZ" << static_cast<int>(error);
}
#undef SWITCH_ERR
}
bool IsValidFileNameCharacterOnWindows(char16_t c) {
if (c < 32)
return false;
switch (c) {
case '<': // Less than
case '>': // Greater than
case ':': // Colon
case '"': // Double quote
case '|': // Vertical bar or pipe
case '?': // Question mark
case '*': // Asterisk
case '/': // Forward slash
case '\\': // Backslash
return false;
}
return true;
}
// A writer delegate that writes to a given string.
class StringWriterDelegate : public WriterDelegate {
public:
explicit StringWriterDelegate(std::string* output) : output_(output) {}
// WriterDelegate methods:
bool WriteBytes(const char* data, int num_bytes) override {
output_->append(data, num_bytes);
return true;
}
private:
std::string* const output_;
};
#if defined(OS_POSIX)
void SetPosixFilePermissions(int fd, int mode) {
base::stat_wrapper_t sb;
if (base::File::Fstat(fd, &sb)) {
return;
}
mode_t new_mode = sb.st_mode;
// Transfer the executable bit only if the file is readable.
if ((sb.st_mode & S_IRUSR) == S_IRUSR && (mode & S_IXUSR) == S_IXUSR) {
new_mode |= S_IXUSR;
}
if ((sb.st_mode & S_IRGRP) == S_IRGRP && (mode & S_IXGRP) == S_IXGRP) {
new_mode |= S_IXGRP;
}
if ((sb.st_mode & S_IROTH) == S_IROTH && (mode & S_IXOTH) == S_IXOTH) {
new_mode |= S_IXOTH;
}
if (new_mode != sb.st_mode) {
fchmod(fd, new_mode);
}
}
#endif
} // namespace
ZipReader::ZipReader() {
Reset();
}
ZipReader::~ZipReader() {
Close();
}
bool ZipReader::Open(const base::FilePath& zip_path) {
DCHECK(!zip_file_);
// Use of "Unsafe" function does not look good, but there is no way to do
// this safely on Linux. See file_util.h for details.
zip_file_ = internal::OpenForUnzipping(zip_path.AsUTF8Unsafe());
if (!zip_file_) {
LOG(ERROR) << "Cannot open ZIP archive " << Redact(zip_path);
return false;
}
return OpenInternal();
}
bool ZipReader::OpenFromPlatformFile(base::PlatformFile zip_fd) {
DCHECK(!zip_file_);
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
zip_file_ = internal::OpenFdForUnzipping(zip_fd);
#elif defined(OS_WIN)
zip_file_ = internal::OpenHandleForUnzipping(zip_fd);
#endif
if (!zip_file_) {
LOG(ERROR) << "Cannot open ZIP from file handle " << zip_fd;
return false;
}
return OpenInternal();
}
bool ZipReader::OpenFromString(const std::string& data) {
zip_file_ = internal::PrepareMemoryForUnzipping(data);
if (!zip_file_)
return false;
return OpenInternal();
}
void ZipReader::Close() {
if (zip_file_) {
if (const UnzipError err{unzClose(zip_file_)}; err != UNZ_OK) {
LOG(ERROR) << "Error while closing ZIP archive: " << err;
}
}
Reset();
}
const ZipReader::Entry* ZipReader::Next() {
DCHECK(zip_file_);
if (reached_end_)
return nullptr;
DCHECK(ok_);
// Move to the next entry if we're not trying to open the first entry.
if (next_index_ > 0) {
if (const UnzipError err{unzGoToNextFile(zip_file_)}; err != UNZ_OK) {
reached_end_ = true;
if (err != UNZ_END_OF_LIST_OF_FILE) {
LOG(ERROR) << "Cannot go to next entry in ZIP: " << err;
ok_ = false;
}
return nullptr;
}
}
next_index_++;
if (!OpenEntry()) {
reached_end_ = true;
ok_ = false;
return nullptr;
}
return &entry_;
}
bool ZipReader::OpenEntry() {
DCHECK(zip_file_);
// Get entry info.
unz_file_info64 info = {};
char path_in_zip[internal::kZipMaxPath] = {};
if (const UnzipError err{unzGetCurrentFileInfo64(
zip_file_, &info, path_in_zip, sizeof(path_in_zip) - 1, nullptr, 0,
nullptr, 0)};
err != UNZ_OK) {
LOG(ERROR) << "Cannot get entry from ZIP: " << err;
return false;
}
entry_.path_in_original_encoding = path_in_zip;
// Convert path from original encoding to Unicode.
std::u16string path_in_utf16;
const char* const encoding = encoding_.empty() ? "UTF-8" : encoding_.c_str();
if (!base::CodepageToUTF16(entry_.path_in_original_encoding, encoding,
base::OnStringConversionError::SUBSTITUTE,
&path_in_utf16)) {
LOG(ERROR) << "Cannot convert path from encoding " << encoding;
return false;
}
// Normalize path.
Normalize(path_in_utf16);
entry_.original_size = info.uncompressed_size;
// The file content of this entry is encrypted if flag bit 0 is set.
entry_.is_encrypted = info.flag & 1;
if (entry_.is_encrypted) {
// Is the entry AES encrypted.
entry_.uses_aes_encryption = info.compression_method == 99;
} else {
entry_.uses_aes_encryption = false;
}
// Construct the last modified time. The timezone info is not present in ZIP
// archives, so we construct the time as UTC.
const base::Time::Exploded exploded_time = {
.year = static_cast<int>(info.tmu_date.tm_year),
.month =
static_cast<int>(info.tmu_date.tm_mon + 1), // 0-based vs 1-based
.day_of_month = static_cast<int>(info.tmu_date.tm_mday),
.hour = static_cast<int>(info.tmu_date.tm_hour),
.minute = static_cast<int>(info.tmu_date.tm_min),
.second = static_cast<int>(info.tmu_date.tm_sec)};
if (!base::Time::FromUTCExploded(exploded_time, &entry_.last_modified))
entry_.last_modified = base::Time::UnixEpoch();
#if defined(OS_POSIX)
entry_.posix_mode = (info.external_fa >> 16L) & (S_IRWXU | S_IRWXG | S_IRWXO);
#else
entry_.posix_mode = 0;
#endif
return true;
}
void ZipReader::Normalize(std::u16string_view in) {
entry_.is_unsafe = true;
// Directory entries in ZIP have a path ending with "/".
entry_.is_directory = base::EndsWith(in, u"/");
std::u16string normalized_path;
if (base::StartsWith(in, u"/")) {
normalized_path = u"ROOT";
entry_.is_unsafe = false;
}
for (;;) {
// Consume initial path separators.
const std::u16string_view::size_type i = in.find_first_not_of(u'/');
if (i == std::u16string_view::npos) {
break;
}
in.remove_prefix(i);
DCHECK(!in.empty());
// Isolate next path component.
const std::u16string_view part = in.substr(0, in.find_first_of(u'/'));
DCHECK(!part.empty());
in.remove_prefix(part.size());
if (!normalized_path.empty())
normalized_path += u'/';
if (part == u".") {
normalized_path += u"DOT";
entry_.is_unsafe = true;
continue;
}
if (part == u"..") {
normalized_path += u"UP";
entry_.is_unsafe = true;
continue;
}
// Windows has more restrictions than other systems when it comes to valid
// file paths. Replace Windows-invalid characters on all systems for
// consistency. In particular, this prevents a path component containing
// colon and backslash from being misinterpreted as an absolute path on
// Windows.
for (const char16_t c : part) {
normalized_path += IsValidFileNameCharacterOnWindows(c) ? c : 0xFFFD;
}
entry_.is_unsafe = false;
}
// If the entry is a directory, add the final path separator to the entry
// path.
if (entry_.is_directory && !normalized_path.empty()) {
normalized_path += u'/';
entry_.is_unsafe = false;
}
entry_.path = base::FilePath::FromUTF16Unsafe(normalized_path);
// By construction, we should always get a relative path.
DCHECK(!entry_.path.IsAbsolute()) << entry_.path;
}
void ZipReader::ReportProgress(ListenerCallback listener_callback,
uint64_t bytes) const {
delta_bytes_read_ += bytes;
const base::TimeTicks now = base::TimeTicks::Now();
if (next_progress_report_time_ > now)
return;
next_progress_report_time_ = now + progress_period_;
listener_callback.Run(delta_bytes_read_);
delta_bytes_read_ = 0;
}
bool ZipReader::ExtractCurrentEntry(WriterDelegate* delegate,
ListenerCallback listener_callback,
uint64_t num_bytes_to_extract) const {
DCHECK(zip_file_);
DCHECK_LT(0, next_index_);
DCHECK(ok_);
DCHECK(!reached_end_);
// Use password only for encrypted files. For non-encrypted files, no password
// is needed, and must be nullptr.
const char* const password =
entry_.is_encrypted ? password_.c_str() : nullptr;
if (const UnzipError err{unzOpenCurrentFilePassword(zip_file_, password)};
err != UNZ_OK) {
LOG(ERROR) << "Cannot open file " << Redact(entry_.path)
<< " from ZIP: " << err;
return false;
}
DCHECK(delegate);
if (!delegate->PrepareOutput())
return false;
uint64_t remaining_capacity = num_bytes_to_extract;
bool entire_file_extracted = false;
while (remaining_capacity > 0) {
char buf[internal::kZipBufSize];
const int num_bytes_read =
unzReadCurrentFile(zip_file_, buf, internal::kZipBufSize);
if (num_bytes_read == 0) {
entire_file_extracted = true;
break;
}
if (num_bytes_read < 0) {
LOG(ERROR) << "Cannot read file " << Redact(entry_.path)
<< " from ZIP: " << UnzipError(num_bytes_read);
break;
}
if (listener_callback) {
ReportProgress(listener_callback, num_bytes_read);
}
DCHECK_LT(0, num_bytes_read);
CHECK_LE(num_bytes_read, internal::kZipBufSize);
uint64_t num_bytes_to_write = std::min<uint64_t>(
remaining_capacity, base::checked_cast<uint64_t>(num_bytes_read));
if (!delegate->WriteBytes(buf, num_bytes_to_write))
break;
if (remaining_capacity == base::checked_cast<uint64_t>(num_bytes_read)) {
// Ensures function returns true if the entire file has been read.
const int n = unzReadCurrentFile(zip_file_, buf, 1);
entire_file_extracted = (n == 0);
LOG_IF(ERROR, n < 0) << "Cannot read file " << Redact(entry_.path)
<< " from ZIP: " << UnzipError(n);
}
CHECK_GE(remaining_capacity, num_bytes_to_write);
remaining_capacity -= num_bytes_to_write;
}
if (const UnzipError err{unzCloseCurrentFile(zip_file_)}; err != UNZ_OK) {
LOG(ERROR) << "Cannot extract file " << Redact(entry_.path)
<< " from ZIP: " << err;
entire_file_extracted = false;
}
if (entire_file_extracted) {
delegate->SetPosixFilePermissions(entry_.posix_mode);
if (entry_.last_modified != base::Time::UnixEpoch()) {
delegate->SetTimeModified(entry_.last_modified);
}
} else {
delegate->OnError();
}
if (listener_callback) {
listener_callback.Run(delta_bytes_read_);
delta_bytes_read_ = 0;
}
return entire_file_extracted;
}
bool ZipReader::ExtractCurrentEntry(WriterDelegate* delegate,
uint64_t num_bytes_to_extract) const {
return ExtractCurrentEntry(delegate, ListenerCallback(),
num_bytes_to_extract);
}
bool ZipReader::ExtractCurrentEntryWithListener(
WriterDelegate* delegate,
ListenerCallback listener_callback) const {
return ExtractCurrentEntry(delegate, listener_callback);
}
void ZipReader::ExtractCurrentEntryToFilePathAsync(
const base::FilePath& output_file_path,
SuccessCallback success_callback,
FailureCallback failure_callback,
ProgressCallback progress_callback) {
DCHECK(zip_file_);
DCHECK_LT(0, next_index_);
DCHECK(ok_);
DCHECK(!reached_end_);
// If this is a directory, just create it and return.
if (entry_.is_directory) {
if (base::CreateDirectory(output_file_path)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(success_callback));
} else {
LOG(ERROR) << "Cannot create directory " << Redact(output_file_path);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(failure_callback));
}
return;
}
// Use password only for encrypted files. For non-encrypted files, no password
// is needed, and must be nullptr.
const char* const password =
entry_.is_encrypted ? password_.c_str() : nullptr;
if (const UnzipError err{unzOpenCurrentFilePassword(zip_file_, password)};
err != UNZ_OK) {
LOG(ERROR) << "Cannot open file " << Redact(entry_.path)
<< " from ZIP: " << err;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(failure_callback));
return;
}
base::FilePath output_dir_path = output_file_path.DirName();
if (!base::CreateDirectory(output_dir_path)) {
LOG(ERROR) << "Cannot create directory " << Redact(output_dir_path);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(failure_callback));
return;
}
const int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE;
base::File output_file(output_file_path, flags);
if (!output_file.IsValid()) {
LOG(ERROR) << "Cannot create file " << Redact(output_file_path);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(failure_callback));
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ZipReader::ExtractChunk, weak_ptr_factory_.GetWeakPtr(),
std::move(output_file), std::move(success_callback),
std::move(failure_callback), std::move(progress_callback),
0 /* initial offset */));
}
bool ZipReader::ExtractCurrentEntryToString(uint64_t max_read_bytes,
std::string* output) const {
DCHECK(output);
DCHECK(zip_file_);
DCHECK_LT(0, next_index_);
DCHECK(ok_);
DCHECK(!reached_end_);
output->clear();
if (max_read_bytes == 0)
return true;
if (entry_.is_directory)
return true;
// The original_size is the best hint for the real size, so it saves doing
// reallocations for the common case when the uncompressed size is correct.
// However, we need to assume that the uncompressed size could be incorrect
// therefore this function needs to read as much data as possible.
output->reserve(base::checked_cast<size_t>(std::min<uint64_t>(
max_read_bytes, base::checked_cast<uint64_t>(entry_.original_size))));
StringWriterDelegate writer(output);
return ExtractCurrentEntry(&writer, max_read_bytes);
}
bool ZipReader::OpenInternal() {
DCHECK(zip_file_);
unz_global_info zip_info = {}; // Zero-clear.
if (const UnzipError err{unzGetGlobalInfo(zip_file_, &zip_info)};
err != UNZ_OK) {
LOG(ERROR) << "Cannot get ZIP info: " << err;
return false;
}
num_entries_ = zip_info.number_entry;
reached_end_ = (num_entries_ <= 0);
ok_ = true;
return true;
}
void ZipReader::Reset() {
zip_file_ = nullptr;
num_entries_ = 0;
next_index_ = 0;
reached_end_ = true;
ok_ = false;
delta_bytes_read_ = 0;
entry_ = {};
}
void ZipReader::ExtractChunk(base::File output_file,
SuccessCallback success_callback,
FailureCallback failure_callback,
ProgressCallback progress_callback,
int64_t offset) {
char buffer[internal::kZipBufSize];
const int num_bytes_read =
unzReadCurrentFile(zip_file_, buffer, internal::kZipBufSize);
if (num_bytes_read == 0) {
if (const UnzipError err{unzCloseCurrentFile(zip_file_)}; err != UNZ_OK) {
LOG(ERROR) << "Cannot extract file " << Redact(entry_.path)
<< " from ZIP: " << err;
std::move(failure_callback).Run();
return;
}
std::move(success_callback).Run();
return;
}
if (num_bytes_read < 0) {
LOG(ERROR) << "Cannot read file " << Redact(entry_.path)
<< " from ZIP: " << UnzipError(num_bytes_read);
std::move(failure_callback).Run();
return;
}
if (num_bytes_read != output_file.Write(offset, buffer, num_bytes_read)) {
LOG(ERROR) << "Cannot write " << num_bytes_read
<< " bytes to file at offset " << offset;
std::move(failure_callback).Run();
return;
}
offset += num_bytes_read;
progress_callback.Run(offset);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ZipReader::ExtractChunk, weak_ptr_factory_.GetWeakPtr(),
std::move(output_file), std::move(success_callback),
std::move(failure_callback), std::move(progress_callback),
offset));
}
// FileWriterDelegate ----------------------------------------------------------
FileWriterDelegate::FileWriterDelegate(base::File* file) : file_(file) {
DCHECK(file_);
}
FileWriterDelegate::FileWriterDelegate(base::File owned_file)
: owned_file_(std::move(owned_file)) {
DCHECK_EQ(file_, &owned_file_);
}
FileWriterDelegate::~FileWriterDelegate() {}
bool FileWriterDelegate::PrepareOutput() {
DCHECK(file_);
if (!file_->IsValid()) {
LOG(ERROR) << "File is not valid";
return false;
}
const int64_t length = file_->GetLength();
if (length < 0) {
PLOG(ERROR) << "Cannot get length of file handle "
<< file_->GetPlatformFile();
return false;
}
// Just log a warning if the file is not empty.
// See crbug.com/1309879 and crbug.com/774762.
LOG_IF(WARNING, length > 0)
<< "File handle " << file_->GetPlatformFile()
<< " is not empty: Its length is " << length << " bytes";
return true;
}
bool FileWriterDelegate::WriteBytes(const char* data, int num_bytes) {
int bytes_written = file_->WriteAtCurrentPos(data, num_bytes);
if (bytes_written > 0)
file_length_ += bytes_written;
return bytes_written == num_bytes;
}
void FileWriterDelegate::SetTimeModified(const base::Time& time) {
file_->SetTimes(base::Time::Now(), time);
}
void FileWriterDelegate::SetPosixFilePermissions(int mode) {
#if defined(OS_POSIX)
zip::SetPosixFilePermissions(file_->GetPlatformFile(), mode);
#endif
}
void FileWriterDelegate::OnError() {
file_length_ = 0;
file_->SetLength(0);
}
// FilePathWriterDelegate ------------------------------------------------------
FilePathWriterDelegate::FilePathWriterDelegate(base::FilePath output_file_path)
: FileWriterDelegate(base::File()),
output_file_path_(std::move(output_file_path)) {}
FilePathWriterDelegate::~FilePathWriterDelegate() {}
bool FilePathWriterDelegate::PrepareOutput() {
// We can't rely on parent directory entries being specified in the
// zip, so we make sure they are created.
if (const base::FilePath dir = output_file_path_.DirName();
!base::CreateDirectory(dir)) {
PLOG(ERROR) << "Cannot create directory " << Redact(dir);
return false;
}
owned_file_.Initialize(output_file_path_,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
if (!owned_file_.IsValid()) {
PLOG(ERROR) << "Cannot create file " << Redact(output_file_path_) << ": "
<< base::File::ErrorToString(owned_file_.error_details());
return false;
}
const int64_t length = owned_file_.GetLength();
if (length < 0) {
PLOG(ERROR) << "Cannot get length of file " << Redact(output_file_path_);
return false;
}
if (length > 0) {
LOG(ERROR) << "File " << Redact(output_file_path_)
<< " is not empty: Its length is " << length << " bytes";
return false;
}
return true;
}
void FilePathWriterDelegate::OnError() {
FileWriterDelegate::OnError();
owned_file_.Close();
if (!base::DeleteFile(output_file_path_)) {
LOG(ERROR) << "Cannot delete partially extracted file "
<< Redact(output_file_path_);
}
}
} // namespace zip

View File

@ -0,0 +1,405 @@
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_
#define THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <memory>
#include <string>
#include <string_view>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#if defined(USE_SYSTEM_MINIZIP)
#include <minizip/unzip.h>
#else
#include "third_party/zlib/contrib/minizip/unzip.h"
#endif
namespace zip {
// A delegate interface used to stream out an entry; see
// ZipReader::ExtractCurrentEntry.
class WriterDelegate {
public:
virtual ~WriterDelegate() {}
// Invoked once before any data is streamed out to pave the way (e.g., to open
// the output file). Return false on failure to cancel extraction.
virtual bool PrepareOutput() { return true; }
// Invoked to write the next chunk of data. Return false on failure to cancel
// extraction.
virtual bool WriteBytes(const char* data, int num_bytes) { return true; }
// Sets the last-modified time of the data.
virtual void SetTimeModified(const base::Time& time) {}
// Called with the POSIX file permissions of the data; POSIX implementations
// may apply some of the permissions (for example, the executable bit) to the
// output file.
virtual void SetPosixFilePermissions(int mode) {}
// Called if an error occurred while extracting the file. The WriterDelegate
// can then remove and clean up the partially extracted data.
virtual void OnError() {}
};
// This class is used for reading ZIP archives. A typical use case of this class
// is to scan entries in a ZIP archive and extract them. The code will look
// like:
//
// ZipReader reader;
// if (!reader.Open(zip_path)) {
// // Cannot open
// return;
// }
//
// while (const ZipReader::entry* entry = reader.Next()) {
// auto writer = CreateFilePathWriterDelegate(extract_dir, entry->path);
// if (!reader.ExtractCurrentEntry(writer)) {
// // Cannot extract
// return;
// }
// }
//
// if (!reader.ok()) {
// // Error while enumerating entries
// return;
// }
//
class ZipReader {
public:
// A callback that is called when the operation is successful.
using SuccessCallback = base::OnceClosure;
// A callback that is called when the operation fails.
using FailureCallback = base::OnceClosure;
// A callback that is called periodically during the operation with the number
// of bytes that have been processed so far.
using ProgressCallback = base::RepeatingCallback<void(int64_t)>;
// A callback that is called periodically during the operation with the number
// of bytes that have been processed since the previous call (i.e. delta).
using ListenerCallback = base::RepeatingCallback<void(uint64_t)>;
// Information of an entry (file or directory) in a ZIP archive.
struct Entry {
// Path of this entry, in its original encoding as it is stored in the ZIP
// archive. The encoding is not specified here. It might or might not be
// UTF-8, and the caller needs to use other means to determine the encoding
// if it wants to interpret this path correctly.
std::string path_in_original_encoding;
// Path of the entry, converted to Unicode. This path is relative (eg
// "foo/bar.txt"). Absolute paths (eg "/foo/bar.txt") or paths containing
// ".." or "." components (eg "../foo/bar.txt") are converted to safe
// relative paths. Eg:
// (In ZIP) -> (Entry.path)
// /foo/bar -> ROOT/foo/bar
// ../a -> UP/a
// ./a -> DOT/a
base::FilePath path;
// Size of the original uncompressed file, or 0 if the entry is a directory.
// This value should not be trusted, because it is stored as metadata in the
// ZIP archive and can be different from the real uncompressed size.
int64_t original_size;
// Last modified time. If the timestamp stored in the ZIP archive is not
// valid, the Unix epoch will be returned.
//
// The timestamp stored in the ZIP archive uses the MS-DOS date and time
// format.
//
// http://msdn.microsoft.com/en-us/library/ms724247(v=vs.85).aspx
//
// As such the following limitations apply:
// * Only years from 1980 to 2107 can be represented.
// * The timestamp has a 2-second resolution.
// * There is no timezone information, so the time is interpreted as UTC.
base::Time last_modified;
// True if the entry is a directory.
// False if the entry is a file.
bool is_directory = false;
// True if the entry path cannot be converted to a safe relative path. This
// happens if a file entry (not a directory) has a filename "." or "..".
bool is_unsafe = false;
// True if the file content is encrypted.
bool is_encrypted = false;
// True if the encryption scheme is AES.
bool uses_aes_encryption = false;
// Entry POSIX permissions (POSIX systems only).
int posix_mode;
};
ZipReader();
ZipReader(const ZipReader&) = delete;
ZipReader& operator=(const ZipReader&) = delete;
~ZipReader();
// Opens the ZIP archive specified by |zip_path|. Returns true on
// success.
bool Open(const base::FilePath& zip_path);
// Opens the ZIP archive referred to by the platform file |zip_fd|, without
// taking ownership of |zip_fd|. Returns true on success.
bool OpenFromPlatformFile(base::PlatformFile zip_fd);
// Opens the zip data stored in |data|. This class uses a weak reference to
// the given sring while extracting files, i.e. the caller should keep the
// string until it finishes extracting files.
bool OpenFromString(const std::string& data);
// Closes the currently opened ZIP archive. This function is called in the
// destructor of the class, so you usually don't need to call this.
void Close();
// Sets the encoding of entry paths in the ZIP archive.
// By default, paths are assumed to be in UTF-8.
void SetEncoding(std::string encoding) { encoding_ = std::move(encoding); }
// Sets the decryption password that will be used to decrypt encrypted file in
// the ZIP archive.
void SetPassword(std::string password) { password_ = std::move(password); }
// Gets the next entry. Returns null if there is no more entry, or if an error
// occurred while scanning entries. The returned Entry is owned by this
// ZipReader, and is valid until Next() is called again or until this
// ZipReader is closed.
//
// This function should be called before operations over the current entry
// like ExtractCurrentEntryToFile().
//
// while (const ZipReader::Entry* entry = reader.Next()) {
// // Do something with the current entry here.
// ...
// }
//
// // Finished scanning entries.
// // Check if the scanning stopped because of an error.
// if (!reader.ok()) {
// // There was an error.
// ...
// }
const Entry* Next();
// Returns true if the enumeration of entries was successful, or false if it
// stopped because of an error.
bool ok() const { return ok_; }
// Extracts |num_bytes_to_extract| bytes of the current entry to |delegate|,
// starting from the beginning of the entry.
//
// Returns true if the entire file was extracted without error.
//
// Precondition: Next() returned a non-null Entry.
bool ExtractCurrentEntry(WriterDelegate* delegate,
uint64_t num_bytes_to_extract =
std::numeric_limits<uint64_t>::max()) const;
// Extracts the current entry to |delegate|, starting from the beginning
// of the entry, calling |listener_callback| regularly with the number of
// bytes extracted.
//
// Returns true if the entire file was extracted without error.
//
// Precondition: Next() returned a non-null Entry.
bool ExtractCurrentEntryWithListener(
WriterDelegate* delegate,
ListenerCallback listener_callback) const;
// Asynchronously extracts the current entry to the given output file path. If
// the current entry is a directory it just creates the directory
// synchronously instead.
//
// |success_callback| will be called on success and |failure_callback| will be
// called on failure. |progress_callback| will be called at least once.
// Callbacks will be posted to the current MessageLoop in-order.
//
// Precondition: Next() returned a non-null Entry.
void ExtractCurrentEntryToFilePathAsync(
const base::FilePath& output_file_path,
SuccessCallback success_callback,
FailureCallback failure_callback,
ProgressCallback progress_callback);
// Extracts the current entry into memory. If the current entry is a
// directory, |*output| is set to the empty string. If the current entry is a
// file, |*output| is filled with its contents.
//
// The value in |Entry::original_size| cannot be trusted, so the real size of
// the uncompressed contents can be different. |max_read_bytes| limits the
// amount of memory used to carry the entry.
//
// Returns true if the entire content is read without error. If the content is
// bigger than |max_read_bytes|, this function returns false and |*output| is
// filled with |max_read_bytes| of data. If an error occurs, this function
// returns false and |*output| contains the content extracted so far, which
// might be garbage data.
//
// Precondition: Next() returned a non-null Entry.
bool ExtractCurrentEntryToString(uint64_t max_read_bytes,
std::string* output) const;
bool ExtractCurrentEntryToString(std::string* output) const {
return ExtractCurrentEntryToString(
base::checked_cast<uint64_t>(output->max_size()), output);
}
// Returns the number of entries in the ZIP archive.
//
// Precondition: one of the Open() methods returned true.
int num_entries() const { return num_entries_; }
private:
// Common code used both in Open and OpenFromFd.
bool OpenInternal();
// Resets the internal state.
void Reset();
// Opens the current entry in the ZIP archive. On success, returns true and
// updates the current entry state |entry_|.
//
// Note that there is no matching CloseEntry(). The current entry state is
// reset automatically as needed.
bool OpenEntry();
// Normalizes the given path passed as UTF-16 string piece. Sets entry_.path,
// entry_.is_directory and entry_.is_unsafe.
void Normalize(std::u16string_view in);
// Runs the ListenerCallback at a throttled rate.
void ReportProgress(ListenerCallback listener_callback, uint64_t bytes) const;
// Extracts |num_bytes_to_extract| bytes of the current entry to |delegate|,
// starting from the beginning of the entry calling |listener_callback| if
// its supplied.
//
// Returns true if the entire file was extracted without error.
//
// Precondition: Next() returned a non-null Entry.
bool ExtractCurrentEntry(WriterDelegate* delegate,
ListenerCallback listener_callback,
uint64_t num_bytes_to_extract =
std::numeric_limits<uint64_t>::max()) const;
// Extracts a chunk of the file to the target. Will post a task for the next
// chunk and success/failure/progress callbacks as necessary.
void ExtractChunk(base::File target_file,
SuccessCallback success_callback,
FailureCallback failure_callback,
ProgressCallback progress_callback,
const int64_t offset);
std::string encoding_;
std::string password_;
unzFile zip_file_;
int num_entries_;
int next_index_;
bool reached_end_;
bool ok_;
Entry entry_;
// Next time to report progress.
mutable base::TimeTicks next_progress_report_time_ = base::TimeTicks::Now();
// Progress time delta.
// TODO(crbug.com/953256) Add this as parameter to the unzip options.
base::TimeDelta progress_period_ = base::Milliseconds(1000);
// Number of bytes read since last progress report callback executed.
mutable uint64_t delta_bytes_read_ = 0;
base::WeakPtrFactory<ZipReader> weak_ptr_factory_{this};
};
// A writer delegate that writes to a given File. It is recommended that this
// file be initially empty.
class FileWriterDelegate : public WriterDelegate {
public:
// Constructs a FileWriterDelegate that manipulates |file|. The delegate will
// not own |file|, therefore the caller must guarantee |file| will outlive the
// delegate.
explicit FileWriterDelegate(base::File* file);
// Constructs a FileWriterDelegate that takes ownership of |file|.
explicit FileWriterDelegate(base::File owned_file);
FileWriterDelegate(const FileWriterDelegate&) = delete;
FileWriterDelegate& operator=(const FileWriterDelegate&) = delete;
~FileWriterDelegate() override;
// Returns true if the file handle passed to the constructor is valid.
bool PrepareOutput() override;
// Writes |num_bytes| bytes of |data| to the file, returning false on error or
// if not all bytes could be written.
bool WriteBytes(const char* data, int num_bytes) override;
// Sets the last-modified time of the data.
void SetTimeModified(const base::Time& time) override;
// On POSIX systems, sets the file to be executable if the source file was
// executable.
void SetPosixFilePermissions(int mode) override;
// Empties the file to avoid leaving garbage data in it.
void OnError() override;
// Gets the number of bytes written into the file.
int64_t file_length() { return file_length_; }
protected:
// The delegate can optionally own the file it modifies, in which case
// owned_file_ is set and file_ is an alias for owned_file_.
base::File owned_file_;
// The file the delegate modifies.
base::File* const file_ = &owned_file_;
int64_t file_length_ = 0;
};
// A writer delegate that creates and writes a file at a given path. This does
// not overwrite any existing file.
class FilePathWriterDelegate : public FileWriterDelegate {
public:
explicit FilePathWriterDelegate(base::FilePath output_file_path);
FilePathWriterDelegate(const FilePathWriterDelegate&) = delete;
FilePathWriterDelegate& operator=(const FilePathWriterDelegate&) = delete;
~FilePathWriterDelegate() override;
// Creates the output file and any necessary intermediate directories. Does
// not overwrite any existing file, and returns false if the output file
// cannot be created because another file conflicts with it.
bool PrepareOutput() override;
// Deletes the output file.
void OnError() override;
private:
const base::FilePath output_file_path_;
};
} // namespace zip
#endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_

View File

@ -0,0 +1,936 @@
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/zlib/google/zip_reader.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <iterator>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "base/check.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/hash/md5.h"
#include "base/i18n/time_formatting.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
#include "third_party/zlib/google/zip_internal.h"
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Return;
using ::testing::SizeIs;
namespace {
const static std::string kQuuxExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
class FileWrapper {
public:
typedef enum { READ_ONLY, READ_WRITE } AccessMode;
FileWrapper(const base::FilePath& path, AccessMode mode) {
int flags = base::File::FLAG_READ;
if (mode == READ_ONLY)
flags |= base::File::FLAG_OPEN;
else
flags |= base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS;
file_.Initialize(path, flags);
}
~FileWrapper() {}
base::PlatformFile platform_file() { return file_.GetPlatformFile(); }
base::File* file() { return &file_; }
private:
base::File file_;
};
// A mock that provides methods that can be used as callbacks in asynchronous
// unzip functions. Tracks the number of calls and number of bytes reported.
// Assumes that progress callbacks will be executed in-order.
class MockUnzipListener final {
public:
MockUnzipListener()
: success_calls_(0),
failure_calls_(0),
progress_calls_(0),
current_progress_(0) {}
// Success callback for async functions.
void OnUnzipSuccess() { success_calls_++; }
// Failure callback for async functions.
void OnUnzipFailure() { failure_calls_++; }
// Progress callback for async functions.
void OnUnzipProgress(int64_t progress) {
DCHECK(progress > current_progress_);
progress_calls_++;
current_progress_ = progress;
}
int success_calls() { return success_calls_; }
int failure_calls() { return failure_calls_; }
int progress_calls() { return progress_calls_; }
int current_progress() { return current_progress_; }
base::WeakPtr<MockUnzipListener> AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
private:
int success_calls_;
int failure_calls_;
int progress_calls_;
int64_t current_progress_;
base::WeakPtrFactory<MockUnzipListener> weak_ptr_factory_{this};
};
class MockWriterDelegate : public zip::WriterDelegate {
public:
MOCK_METHOD0(PrepareOutput, bool());
MOCK_METHOD2(WriteBytes, bool(const char*, int));
MOCK_METHOD1(SetTimeModified, void(const base::Time&));
MOCK_METHOD1(SetPosixFilePermissions, void(int));
MOCK_METHOD0(OnError, void());
};
bool ExtractCurrentEntryToFilePath(zip::ZipReader* reader,
base::FilePath path) {
zip::FilePathWriterDelegate writer(path);
return reader->ExtractCurrentEntry(&writer);
}
const zip::ZipReader::Entry* LocateAndOpenEntry(
zip::ZipReader* const reader,
const base::FilePath& path_in_zip) {
DCHECK(reader);
EXPECT_TRUE(reader->ok());
// The underlying library can do O(1) access, but ZipReader does not expose
// that. O(N) access is acceptable for these tests.
while (const zip::ZipReader::Entry* const entry = reader->Next()) {
EXPECT_TRUE(reader->ok());
if (entry->path == path_in_zip)
return entry;
}
EXPECT_TRUE(reader->ok());
return nullptr;
}
using Paths = std::vector<base::FilePath>;
} // namespace
namespace zip {
// Make the test a PlatformTest to setup autorelease pools properly on Mac.
class ZipReaderTest : public PlatformTest {
protected:
void SetUp() override {
PlatformTest::SetUp();
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
test_dir_ = temp_dir_.GetPath();
}
static base::FilePath GetTestDataDirectory() {
base::FilePath path;
CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &path));
return path.AppendASCII("third_party")
.AppendASCII("zlib")
.AppendASCII("google")
.AppendASCII("test")
.AppendASCII("data");
}
static Paths GetPaths(const base::FilePath& zip_path,
std::string_view encoding = {}) {
Paths paths;
if (ZipReader reader; reader.Open(zip_path)) {
if (!encoding.empty())
reader.SetEncoding(std::string(encoding));
while (const ZipReader::Entry* const entry = reader.Next()) {
EXPECT_TRUE(reader.ok());
paths.push_back(entry->path);
}
EXPECT_TRUE(reader.ok());
}
return paths;
}
// The path to temporary directory used to contain the test operations.
base::FilePath test_dir_;
// The path to the test data directory where test.zip etc. are located.
const base::FilePath data_dir_ = GetTestDataDirectory();
// The path to test.zip in the test data directory.
const base::FilePath test_zip_file_ = data_dir_.AppendASCII("test.zip");
const Paths test_zip_contents_ = {
base::FilePath(FILE_PATH_LITERAL("foo/")),
base::FilePath(FILE_PATH_LITERAL("foo/bar/")),
base::FilePath(FILE_PATH_LITERAL("foo/bar/baz.txt")),
base::FilePath(FILE_PATH_LITERAL("foo/bar/quux.txt")),
base::FilePath(FILE_PATH_LITERAL("foo/bar.txt")),
base::FilePath(FILE_PATH_LITERAL("foo.txt")),
base::FilePath(FILE_PATH_LITERAL("foo/bar/.hidden")),
};
base::ScopedTempDir temp_dir_;
base::test::TaskEnvironment task_environment_;
};
TEST_F(ZipReaderTest, Open_ValidZipFile) {
ZipReader reader;
EXPECT_TRUE(reader.Open(test_zip_file_));
EXPECT_TRUE(reader.ok());
}
TEST_F(ZipReaderTest, Open_ValidZipPlatformFile) {
ZipReader reader;
EXPECT_FALSE(reader.ok());
FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
EXPECT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
EXPECT_TRUE(reader.ok());
}
TEST_F(ZipReaderTest, Open_NonExistentFile) {
ZipReader reader;
EXPECT_FALSE(reader.ok());
EXPECT_FALSE(reader.Open(data_dir_.AppendASCII("nonexistent.zip")));
EXPECT_FALSE(reader.ok());
}
TEST_F(ZipReaderTest, Open_ExistentButNonZipFile) {
ZipReader reader;
EXPECT_FALSE(reader.ok());
EXPECT_FALSE(reader.Open(data_dir_.AppendASCII("create_test_zip.sh")));
EXPECT_FALSE(reader.ok());
}
TEST_F(ZipReaderTest, Open_EmptyFile) {
ZipReader reader;
EXPECT_FALSE(reader.ok());
EXPECT_TRUE(reader.Open(data_dir_.AppendASCII("empty.zip")));
EXPECT_TRUE(reader.ok());
EXPECT_EQ(0, reader.num_entries());
EXPECT_EQ(nullptr, reader.Next());
}
// Iterate through the contents in the test ZIP archive, and compare that the
// contents collected from the ZipReader matches the expected contents.
TEST_F(ZipReaderTest, Iteration) {
Paths actual_contents;
ZipReader reader;
EXPECT_FALSE(reader.ok());
EXPECT_TRUE(reader.Open(test_zip_file_));
EXPECT_TRUE(reader.ok());
while (const ZipReader::Entry* const entry = reader.Next()) {
EXPECT_TRUE(reader.ok());
actual_contents.push_back(entry->path);
}
EXPECT_TRUE(reader.ok());
EXPECT_FALSE(reader.Next()); // Shouldn't go further.
EXPECT_TRUE(reader.ok());
EXPECT_THAT(actual_contents, SizeIs(reader.num_entries()));
EXPECT_THAT(actual_contents, ElementsAreArray(test_zip_contents_));
}
// Open the test ZIP archive from a file descriptor, iterate through its
// contents, and compare that they match the expected contents.
TEST_F(ZipReaderTest, PlatformFileIteration) {
Paths actual_contents;
ZipReader reader;
FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
EXPECT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
EXPECT_TRUE(reader.ok());
while (const ZipReader::Entry* const entry = reader.Next()) {
EXPECT_TRUE(reader.ok());
actual_contents.push_back(entry->path);
}
EXPECT_TRUE(reader.ok());
EXPECT_FALSE(reader.Next()); // Shouldn't go further.
EXPECT_TRUE(reader.ok());
EXPECT_THAT(actual_contents, SizeIs(reader.num_entries()));
EXPECT_THAT(actual_contents, ElementsAreArray(test_zip_contents_));
}
TEST_F(ZipReaderTest, RegularFile) {
ZipReader reader;
ASSERT_TRUE(reader.Open(test_zip_file_));
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
ASSERT_TRUE(entry);
EXPECT_EQ(target_path, entry->path);
EXPECT_EQ(13527, entry->original_size);
EXPECT_EQ("2009-05-29 06:22:20.000",
base::UnlocalizedTimeFormatWithPattern(entry->last_modified,
"y-MM-dd HH:mm:ss.SSS",
icu::TimeZone::getGMT()));
EXPECT_FALSE(entry->is_unsafe);
EXPECT_FALSE(entry->is_directory);
}
TEST_F(ZipReaderTest, DotDotFile) {
ZipReader reader;
ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("evil.zip")));
base::FilePath target_path(FILE_PATH_LITERAL(
"UP/levilevilevilevilevilevilevilevilevilevilevilevil"));
const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
ASSERT_TRUE(entry);
EXPECT_EQ(target_path, entry->path);
EXPECT_FALSE(entry->is_unsafe);
EXPECT_FALSE(entry->is_directory);
}
TEST_F(ZipReaderTest, InvalidUTF8File) {
ZipReader reader;
ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("evil_via_invalid_utf8.zip")));
base::FilePath target_path = base::FilePath::FromUTF8Unsafe(".<2E>.<2E>evil.txt");
const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
ASSERT_TRUE(entry);
EXPECT_EQ(target_path, entry->path);
EXPECT_FALSE(entry->is_unsafe);
EXPECT_FALSE(entry->is_directory);
}
// By default, file paths in ZIPs are interpreted as UTF-8. But in this test,
// the ZIP archive contains file paths that are actually encoded in Shift JIS.
// The SJIS-encoded paths are thus wrongly interpreted as UTF-8, resulting in
// garbled paths. Invalid UTF-8 sequences are safely converted to the
// replacement character <20>.
TEST_F(ZipReaderTest, EncodingSjisAsUtf8) {
EXPECT_THAT(
GetPaths(data_dir_.AppendASCII("SJIS Bug 846195.zip")),
ElementsAre(
base::FilePath::FromUTF8Unsafe("<EFBFBD>V<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>t<EFBFBD>H<EFBFBD><EFBFBD><EFBFBD>_/SJIS_835C_<43><5F>.txt"),
base::FilePath::FromUTF8Unsafe(
"<EFBFBD>V<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>t<EFBFBD>H<EFBFBD><EFBFBD><EFBFBD>_/<2F>V<EFBFBD><56><EFBFBD><EFBFBD><EFBFBD>e<EFBFBD>L<EFBFBD>X<EFBFBD>g <20>h<EFBFBD>L<EFBFBD><4C><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>g.txt")));
}
// In this test, SJIS-encoded paths are interpreted as Code Page 1252. This
// results in garbled paths. Note the presence of C1 control codes U+0090 and
// U+0081 in the garbled paths.
TEST_F(ZipReaderTest, EncodingSjisAs1252) {
EXPECT_THAT(
GetPaths(data_dir_.AppendASCII("SJIS Bug 846195.zip"), "windows-1252"),
ElementsAre(base::FilePath::FromUTF8Unsafe(
"\u0090V‚µ‚¢ƒtƒHƒ‹ƒ_/SJIS_835C_ƒ<5F>.txt"),
base::FilePath::FromUTF8Unsafe(
"\u0090V‚µ‚¢ƒtƒHƒ‹ƒ_/\u0090V‚µ‚¢ƒeƒLƒXƒg "
"ƒhƒLƒ…ƒ\u0081ƒ“ƒg.txt")));
}
// In this test, SJIS-encoded paths are interpreted as Code Page 866. This
// results in garbled paths.
TEST_F(ZipReaderTest, EncodingSjisAsIbm866) {
EXPECT_THAT(
GetPaths(data_dir_.AppendASCII("SJIS Bug 846195.zip"), "IBM866"),
ElementsAre(
base::FilePath::FromUTF8Unsafe("РVВ╡ВвГtГHГЛГ_/SJIS_835C_Г<5F>.txt"),
base::FilePath::FromUTF8Unsafe(
"РVВ╡ВвГtГHГЛГ_/РVВ╡ВвГeГLГXГg ГhГLГЕГБГУГg.txt")));
}
// Tests that SJIS-encoded paths are correctly converted to Unicode.
TEST_F(ZipReaderTest, EncodingSjis) {
EXPECT_THAT(
GetPaths(data_dir_.AppendASCII("SJIS Bug 846195.zip"), "Shift_JIS"),
ElementsAre(
base::FilePath::FromUTF8Unsafe("新しいフォルダ/SJIS_835C_ソ.txt"),
base::FilePath::FromUTF8Unsafe(
"新しいフォルダ/新しいテキスト ドキュメント.txt")));
}
TEST_F(ZipReaderTest, AbsoluteFile) {
ZipReader reader;
ASSERT_TRUE(
reader.Open(data_dir_.AppendASCII("evil_via_absolute_file_name.zip")));
base::FilePath target_path(FILE_PATH_LITERAL("ROOT/evil.txt"));
const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
ASSERT_TRUE(entry);
EXPECT_EQ(target_path, entry->path);
EXPECT_FALSE(entry->is_unsafe);
EXPECT_FALSE(entry->is_directory);
}
TEST_F(ZipReaderTest, Directory) {
ZipReader reader;
ASSERT_TRUE(reader.Open(test_zip_file_));
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/"));
const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
ASSERT_TRUE(entry);
EXPECT_EQ(target_path, entry->path);
// The directory size should be zero.
EXPECT_EQ(0, entry->original_size);
EXPECT_EQ("2009-05-31 15:49:52.000",
base::UnlocalizedTimeFormatWithPattern(entry->last_modified,
"y-MM-dd HH:mm:ss.SSS",
icu::TimeZone::getGMT()));
EXPECT_FALSE(entry->is_unsafe);
EXPECT_TRUE(entry->is_directory);
}
TEST_F(ZipReaderTest, EncryptedFile_WrongPassword) {
ZipReader reader;
ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Different Encryptions.zip")));
reader.SetPassword("wrong password");
{
const ZipReader::Entry* entry = reader.Next();
ASSERT_TRUE(entry);
EXPECT_EQ(base::FilePath::FromASCII("ClearText.txt"), entry->path);
EXPECT_FALSE(entry->is_directory);
EXPECT_FALSE(entry->is_encrypted);
std::string contents = "dummy";
EXPECT_TRUE(reader.ExtractCurrentEntryToString(&contents));
EXPECT_EQ("This is not encrypted.\n", contents);
}
for (const std::string_view path : {
"Encrypted AES-128.txt",
"Encrypted AES-192.txt",
"Encrypted AES-256.txt",
"Encrypted ZipCrypto.txt",
}) {
const ZipReader::Entry* entry = reader.Next();
ASSERT_TRUE(entry);
EXPECT_EQ(base::FilePath::FromASCII(path), entry->path);
EXPECT_FALSE(entry->is_directory);
EXPECT_TRUE(entry->is_encrypted);
std::string contents = "dummy";
EXPECT_FALSE(reader.ExtractCurrentEntryToString(&contents));
}
EXPECT_FALSE(reader.Next());
EXPECT_TRUE(reader.ok());
}
TEST_F(ZipReaderTest, EncryptedFile_RightPassword) {
ZipReader reader;
ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Different Encryptions.zip")));
reader.SetPassword("password");
{
const ZipReader::Entry* entry = reader.Next();
ASSERT_TRUE(entry);
EXPECT_EQ(base::FilePath::FromASCII("ClearText.txt"), entry->path);
EXPECT_FALSE(entry->is_directory);
EXPECT_FALSE(entry->is_encrypted);
std::string contents = "dummy";
EXPECT_TRUE(reader.ExtractCurrentEntryToString(&contents));
EXPECT_EQ("This is not encrypted.\n", contents);
}
// TODO(crbug.com/1296838) Support AES encryption.
for (const std::string_view path : {
"Encrypted AES-128.txt",
"Encrypted AES-192.txt",
"Encrypted AES-256.txt",
}) {
const ZipReader::Entry* entry = reader.Next();
ASSERT_TRUE(entry);
EXPECT_EQ(base::FilePath::FromASCII(path), entry->path);
EXPECT_FALSE(entry->is_directory);
EXPECT_TRUE(entry->is_encrypted);
std::string contents = "dummy";
EXPECT_FALSE(reader.ExtractCurrentEntryToString(&contents));
EXPECT_EQ("", contents);
}
{
const ZipReader::Entry* entry = reader.Next();
ASSERT_TRUE(entry);
EXPECT_EQ(base::FilePath::FromASCII("Encrypted ZipCrypto.txt"),
entry->path);
EXPECT_FALSE(entry->is_directory);
EXPECT_TRUE(entry->is_encrypted);
std::string contents = "dummy";
EXPECT_TRUE(reader.ExtractCurrentEntryToString(&contents));
EXPECT_EQ("This is encrypted with ZipCrypto.\n", contents);
}
EXPECT_FALSE(reader.Next());
EXPECT_TRUE(reader.ok());
}
// Verifies that the ZipReader class can extract a file from a zip archive
// stored in memory. This test opens a zip archive in a std::string object,
// extracts its content, and verifies the content is the same as the expected
// text.
TEST_F(ZipReaderTest, OpenFromString) {
// A zip archive consisting of one file "test.txt", which is a 16-byte text
// file that contains "This is a test.\n".
const char kTestData[] =
"\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\xa4\x66\x24\x41\x13\xe8"
"\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00\x1c\x00\x74\x65"
"\x73\x74\x2e\x74\x78\x74\x55\x54\x09\x00\x03\x34\x89\x45\x50\x34"
"\x89\x45\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13"
"\x00\x00\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74"
"\x2e\x0a\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\xa4\x66"
"\x24\x41\x13\xe8\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00"
"\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa4\x81\x00\x00\x00\x00"
"\x74\x65\x73\x74\x2e\x74\x78\x74\x55\x54\x05\x00\x03\x34\x89\x45"
"\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13\x00\x00"
"\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4e\x00\x00\x00"
"\x52\x00\x00\x00\x00\x00";
std::string data(kTestData, std::size(kTestData));
ZipReader reader;
ASSERT_TRUE(reader.OpenFromString(data));
base::FilePath target_path(FILE_PATH_LITERAL("test.txt"));
ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
ASSERT_TRUE(ExtractCurrentEntryToFilePath(&reader,
test_dir_.AppendASCII("test.txt")));
std::string actual;
ASSERT_TRUE(
base::ReadFileToString(test_dir_.AppendASCII("test.txt"), &actual));
EXPECT_EQ(std::string("This is a test.\n"), actual);
}
// Verifies that the asynchronous extraction to a file works.
TEST_F(ZipReaderTest, ExtractToFileAsync_RegularFile) {
MockUnzipListener listener;
ZipReader reader;
base::FilePath target_file = test_dir_.AppendASCII("quux.txt");
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
reader.ExtractCurrentEntryToFilePathAsync(
target_file,
base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
listener.AsWeakPtr()));
EXPECT_EQ(0, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_EQ(0, listener.progress_calls());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_LE(1, listener.progress_calls());
std::string output;
ASSERT_TRUE(
base::ReadFileToString(test_dir_.AppendASCII("quux.txt"), &output));
const std::string md5 = base::MD5String(output);
EXPECT_EQ(kQuuxExpectedMD5, md5);
std::optional<int64_t> file_size = base::GetFileSize(target_file);
ASSERT_TRUE(file_size.has_value());
EXPECT_EQ(file_size.value(), listener.current_progress());
}
TEST_F(ZipReaderTest, ExtractToFileAsync_Encrypted_NoPassword) {
MockUnzipListener listener;
ZipReader reader;
ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Different Encryptions.zip")));
ASSERT_TRUE(LocateAndOpenEntry(
&reader, base::FilePath::FromASCII("Encrypted ZipCrypto.txt")));
const base::FilePath target_path = test_dir_.AppendASCII("extracted");
reader.ExtractCurrentEntryToFilePathAsync(
target_path,
base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
listener.AsWeakPtr()));
EXPECT_EQ(0, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_EQ(0, listener.progress_calls());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, listener.success_calls());
EXPECT_EQ(1, listener.failure_calls());
EXPECT_LE(1, listener.progress_calls());
// The extracted file contains rubbish data.
// We probably shouldn't even look at it.
std::string contents;
ASSERT_TRUE(base::ReadFileToString(target_path, &contents));
EXPECT_NE("", contents);
EXPECT_EQ(contents.size(), listener.current_progress());
}
TEST_F(ZipReaderTest, ExtractToFileAsync_Encrypted_RightPassword) {
MockUnzipListener listener;
ZipReader reader;
reader.SetPassword("password");
ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Different Encryptions.zip")));
ASSERT_TRUE(LocateAndOpenEntry(
&reader, base::FilePath::FromASCII("Encrypted ZipCrypto.txt")));
const base::FilePath target_path = test_dir_.AppendASCII("extracted");
reader.ExtractCurrentEntryToFilePathAsync(
target_path,
base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
listener.AsWeakPtr()));
EXPECT_EQ(0, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_EQ(0, listener.progress_calls());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_LE(1, listener.progress_calls());
std::string contents;
ASSERT_TRUE(base::ReadFileToString(target_path, &contents));
EXPECT_EQ("This is encrypted with ZipCrypto.\n", contents);
EXPECT_EQ(contents.size(), listener.current_progress());
}
TEST_F(ZipReaderTest, ExtractToFileAsync_WrongCrc) {
MockUnzipListener listener;
ZipReader reader;
ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Wrong CRC.zip")));
ASSERT_TRUE(
LocateAndOpenEntry(&reader, base::FilePath::FromASCII("Corrupted.txt")));
const base::FilePath target_path = test_dir_.AppendASCII("extracted");
reader.ExtractCurrentEntryToFilePathAsync(
target_path,
base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
listener.AsWeakPtr()));
EXPECT_EQ(0, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_EQ(0, listener.progress_calls());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, listener.success_calls());
EXPECT_EQ(1, listener.failure_calls());
EXPECT_LE(1, listener.progress_calls());
std::string contents;
ASSERT_TRUE(base::ReadFileToString(target_path, &contents));
EXPECT_EQ("This file has been changed after its CRC was computed.\n",
contents);
EXPECT_EQ(contents.size(), listener.current_progress());
}
// Verifies that the asynchronous extraction to a file works.
TEST_F(ZipReaderTest, ExtractToFileAsync_Directory) {
MockUnzipListener listener;
ZipReader reader;
base::FilePath target_file = test_dir_.AppendASCII("foo");
base::FilePath target_path(FILE_PATH_LITERAL("foo/"));
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
reader.ExtractCurrentEntryToFilePathAsync(
target_file,
base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
listener.AsWeakPtr()));
EXPECT_EQ(0, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_EQ(0, listener.progress_calls());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_GE(0, listener.progress_calls());
ASSERT_TRUE(base::DirectoryExists(target_file));
}
TEST_F(ZipReaderTest, ExtractCurrentEntryToString) {
// test_mismatch_size.zip contains files with names from 0.txt to 7.txt with
// sizes from 0 to 7 bytes respectively, being the contents of each file a
// substring of "0123456" starting at '0'.
base::FilePath test_zip_file =
data_dir_.AppendASCII("test_mismatch_size.zip");
ZipReader reader;
std::string contents;
ASSERT_TRUE(reader.Open(test_zip_file));
for (size_t i = 0; i < 8; i++) {
SCOPED_TRACE(base::StringPrintf("Processing %d.txt", static_cast<int>(i)));
base::FilePath file_name = base::FilePath::FromUTF8Unsafe(
base::StringPrintf("%d.txt", static_cast<int>(i)));
ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name));
if (i > 1) {
// Off by one byte read limit: must fail.
EXPECT_FALSE(reader.ExtractCurrentEntryToString(i - 1, &contents));
}
if (i > 0) {
// Exact byte read limit: must pass.
EXPECT_TRUE(reader.ExtractCurrentEntryToString(i, &contents));
EXPECT_EQ(std::string(std::string_view("0123456", i)), contents);
}
// More than necessary byte read limit: must pass.
EXPECT_TRUE(reader.ExtractCurrentEntryToString(&contents));
EXPECT_EQ(std::string(std::string_view("0123456", i)), contents);
}
reader.Close();
}
TEST_F(ZipReaderTest, ExtractPartOfCurrentEntry) {
// test_mismatch_size.zip contains files with names from 0.txt to 7.txt with
// sizes from 0 to 7 bytes respectively, being the contents of each file a
// substring of "0123456" starting at '0'.
base::FilePath test_zip_file =
data_dir_.AppendASCII("test_mismatch_size.zip");
ZipReader reader;
std::string contents;
ASSERT_TRUE(reader.Open(test_zip_file));
base::FilePath file_name0 = base::FilePath::FromUTF8Unsafe("0.txt");
ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name0));
EXPECT_TRUE(reader.ExtractCurrentEntryToString(0, &contents));
EXPECT_EQ("", contents);
EXPECT_TRUE(reader.ExtractCurrentEntryToString(1, &contents));
EXPECT_EQ("", contents);
base::FilePath file_name1 = base::FilePath::FromUTF8Unsafe("1.txt");
ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name1));
EXPECT_TRUE(reader.ExtractCurrentEntryToString(0, &contents));
EXPECT_EQ("", contents);
EXPECT_TRUE(reader.ExtractCurrentEntryToString(1, &contents));
EXPECT_EQ("0", contents);
EXPECT_TRUE(reader.ExtractCurrentEntryToString(2, &contents));
EXPECT_EQ("0", contents);
base::FilePath file_name4 = base::FilePath::FromUTF8Unsafe("4.txt");
ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name4));
EXPECT_TRUE(reader.ExtractCurrentEntryToString(0, &contents));
EXPECT_EQ("", contents);
EXPECT_FALSE(reader.ExtractCurrentEntryToString(2, &contents));
EXPECT_EQ("01", contents);
EXPECT_TRUE(reader.ExtractCurrentEntryToString(4, &contents));
EXPECT_EQ("0123", contents);
// Checks that entire file is extracted and function returns true when
// |max_read_bytes| is larger than file size.
EXPECT_TRUE(reader.ExtractCurrentEntryToString(5, &contents));
EXPECT_EQ("0123", contents);
reader.Close();
}
TEST_F(ZipReaderTest, ExtractPosixPermissions) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ZipReader reader;
ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("test_posix_permissions.zip")));
for (auto entry : {"0.txt", "1.txt", "2.txt", "3.txt"}) {
ASSERT_TRUE(LocateAndOpenEntry(&reader, base::FilePath::FromASCII(entry)));
FilePathWriterDelegate delegate(temp_dir.GetPath().AppendASCII(entry));
ASSERT_TRUE(reader.ExtractCurrentEntry(&delegate));
}
reader.Close();
#if defined(OS_POSIX)
// This assumes a umask of at least 0400.
int mode = 0;
EXPECT_TRUE(base::GetPosixFilePermissions(
temp_dir.GetPath().AppendASCII("0.txt"), &mode));
EXPECT_EQ(mode & 0700, 0700);
EXPECT_TRUE(base::GetPosixFilePermissions(
temp_dir.GetPath().AppendASCII("1.txt"), &mode));
EXPECT_EQ(mode & 0700, 0600);
EXPECT_TRUE(base::GetPosixFilePermissions(
temp_dir.GetPath().AppendASCII("2.txt"), &mode));
EXPECT_EQ(mode & 0700, 0700);
EXPECT_TRUE(base::GetPosixFilePermissions(
temp_dir.GetPath().AppendASCII("3.txt"), &mode));
EXPECT_EQ(mode & 0700, 0600);
#endif
}
// This test exposes http://crbug.com/430959, at least on OS X
TEST_F(ZipReaderTest, DISABLED_LeakDetectionTest) {
for (int i = 0; i < 100000; ++i) {
FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
ZipReader reader;
ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
}
}
// Test that when WriterDelegate::PrepareMock returns false, no other methods on
// the delegate are called and the extraction fails.
TEST_F(ZipReaderTest, ExtractCurrentEntryPrepareFailure) {
testing::StrictMock<MockWriterDelegate> mock_writer;
EXPECT_CALL(mock_writer, PrepareOutput()).WillOnce(Return(false));
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
ZipReader reader;
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
ASSERT_FALSE(reader.ExtractCurrentEntry(&mock_writer));
}
// Test that when WriterDelegate::WriteBytes returns false, only the OnError
// method on the delegate is called and the extraction fails.
TEST_F(ZipReaderTest, ExtractCurrentEntryWriteBytesFailure) {
testing::StrictMock<MockWriterDelegate> mock_writer;
EXPECT_CALL(mock_writer, PrepareOutput()).WillOnce(Return(true));
EXPECT_CALL(mock_writer, WriteBytes(_, _)).WillOnce(Return(false));
EXPECT_CALL(mock_writer, OnError());
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
ZipReader reader;
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
ASSERT_FALSE(reader.ExtractCurrentEntry(&mock_writer));
}
// Test that extraction succeeds when the writer delegate reports all is well.
TEST_F(ZipReaderTest, ExtractCurrentEntrySuccess) {
testing::StrictMock<MockWriterDelegate> mock_writer;
EXPECT_CALL(mock_writer, PrepareOutput()).WillOnce(Return(true));
EXPECT_CALL(mock_writer, WriteBytes(_, _)).WillRepeatedly(Return(true));
EXPECT_CALL(mock_writer, SetPosixFilePermissions(_));
EXPECT_CALL(mock_writer, SetTimeModified(_));
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
ZipReader reader;
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
ASSERT_TRUE(reader.ExtractCurrentEntry(&mock_writer));
}
TEST_F(ZipReaderTest, WrongCrc) {
ZipReader reader;
ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Wrong CRC.zip")));
const ZipReader::Entry* const entry =
LocateAndOpenEntry(&reader, base::FilePath::FromASCII("Corrupted.txt"));
ASSERT_TRUE(entry);
std::string contents = "dummy";
EXPECT_FALSE(reader.ExtractCurrentEntryToString(&contents));
EXPECT_EQ("This file has been changed after its CRC was computed.\n",
contents);
contents = "dummy";
EXPECT_FALSE(
reader.ExtractCurrentEntryToString(entry->original_size + 1, &contents));
EXPECT_EQ("This file has been changed after its CRC was computed.\n",
contents);
contents = "dummy";
EXPECT_FALSE(
reader.ExtractCurrentEntryToString(entry->original_size, &contents));
EXPECT_EQ("This file has been changed after its CRC was computed.\n",
contents);
contents = "dummy";
EXPECT_FALSE(
reader.ExtractCurrentEntryToString(entry->original_size - 1, &contents));
EXPECT_EQ("This file has been changed after its CRC was computed.", contents);
}
class FileWriterDelegateTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
file_.Initialize(temp_file_path_,
(base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ |
base::File::FLAG_WRITE | base::File::FLAG_WIN_TEMPORARY |
base::File::FLAG_DELETE_ON_CLOSE));
ASSERT_TRUE(file_.IsValid());
}
base::FilePath temp_file_path_;
base::File file_;
};
TEST_F(FileWriterDelegateTest, WriteToEnd) {
const std::string payload = "This is the actualy payload data.\n";
{
FileWriterDelegate writer(&file_);
EXPECT_EQ(0, writer.file_length());
ASSERT_TRUE(writer.PrepareOutput());
ASSERT_TRUE(writer.WriteBytes(payload.data(), payload.size()));
EXPECT_EQ(payload.size(), writer.file_length());
}
EXPECT_EQ(payload.size(), file_.GetLength());
}
TEST_F(FileWriterDelegateTest, EmptyOnError) {
const std::string payload = "This is the actualy payload data.\n";
{
FileWriterDelegate writer(&file_);
EXPECT_EQ(0, writer.file_length());
ASSERT_TRUE(writer.PrepareOutput());
ASSERT_TRUE(writer.WriteBytes(payload.data(), payload.size()));
EXPECT_EQ(payload.size(), writer.file_length());
EXPECT_EQ(payload.size(), file_.GetLength());
writer.OnError();
EXPECT_EQ(0, writer.file_length());
}
EXPECT_EQ(0, file_.GetLength());
}
} // namespace zip

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,309 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/zlib/google/zip_writer.h"
#include <algorithm>
#include <tuple>
#include "base/files/file.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "third_party/zlib/google/redact.h"
#include "third_party/zlib/google/zip_internal.h"
namespace zip {
namespace internal {
bool ZipWriter::ShouldContinue() {
if (!progress_callback_)
return true;
const base::TimeTicks now = base::TimeTicks::Now();
if (next_progress_report_time_ > now)
return true;
next_progress_report_time_ = now + progress_period_;
if (progress_callback_.Run(progress_))
return true;
LOG(ERROR) << "Cancelling ZIP creation";
return false;
}
bool ZipWriter::AddFileContent(const base::FilePath& path, base::File file) {
char buf[zip::internal::kZipBufSize];
while (ShouldContinue()) {
const int num_bytes =
file.ReadAtCurrentPos(buf, zip::internal::kZipBufSize);
if (num_bytes < 0) {
PLOG(ERROR) << "Cannot read file " << Redact(path);
return false;
}
if (num_bytes == 0)
return true;
if (zipWriteInFileInZip(zip_file_, buf, num_bytes) != ZIP_OK) {
PLOG(ERROR) << "Cannot write data from file " << Redact(path)
<< " to ZIP";
return false;
}
progress_.bytes += num_bytes;
}
return false;
}
bool ZipWriter::OpenNewFileEntry(const base::FilePath& path,
bool is_directory,
base::Time last_modified) {
std::string str_path = path.AsUTF8Unsafe();
#if defined(OS_WIN)
base::ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/");
#endif
Compression compression = kDeflated;
if (is_directory) {
str_path += "/";
} else {
compression = GetCompressionMethod(path);
}
return zip::internal::ZipOpenNewFileInZip(zip_file_, str_path, last_modified,
compression);
}
bool ZipWriter::CloseNewFileEntry() {
return zipCloseFileInZip(zip_file_) == ZIP_OK;
}
bool ZipWriter::AddFileEntry(const base::FilePath& path, base::File file) {
base::File::Info info;
if (!file.GetInfo(&info))
return false;
if (!OpenNewFileEntry(path, /*is_directory=*/false, info.last_modified))
return false;
if (!AddFileContent(path, std::move(file)))
return false;
progress_.files++;
return CloseNewFileEntry();
}
bool ZipWriter::AddDirectoryEntry(const base::FilePath& path) {
FileAccessor::Info info;
if (!file_accessor_->GetInfo(path, &info) || !info.is_directory) {
LOG(ERROR) << "Not a directory: " << Redact(path);
progress_.errors++;
return continue_on_error_;
}
if (!OpenNewFileEntry(path, /*is_directory=*/true, info.last_modified))
return false;
if (!CloseNewFileEntry())
return false;
progress_.directories++;
if (!ShouldContinue())
return false;
if (!recursive_)
return true;
return AddDirectoryContents(path);
}
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// static
std::unique_ptr<ZipWriter> ZipWriter::CreateWithFd(
int zip_file_fd,
FileAccessor* file_accessor) {
DCHECK(zip_file_fd != base::kInvalidPlatformFile);
zipFile zip_file =
internal::OpenFdForZipping(zip_file_fd, APPEND_STATUS_CREATE);
if (!zip_file) {
DLOG(ERROR) << "Cannot create ZIP file for FD " << zip_file_fd;
return nullptr;
}
return std::unique_ptr<ZipWriter>(new ZipWriter(zip_file, file_accessor));
}
#endif
// static
std::unique_ptr<ZipWriter> ZipWriter::Create(
const base::FilePath& zip_file_path,
FileAccessor* file_accessor) {
DCHECK(!zip_file_path.empty());
zipFile zip_file = internal::OpenForZipping(zip_file_path.AsUTF8Unsafe(),
APPEND_STATUS_CREATE);
if (!zip_file) {
PLOG(ERROR) << "Cannot create ZIP file " << Redact(zip_file_path);
return nullptr;
}
return std::unique_ptr<ZipWriter>(new ZipWriter(zip_file, file_accessor));
}
ZipWriter::ZipWriter(zipFile zip_file, FileAccessor* file_accessor)
: zip_file_(zip_file), file_accessor_(file_accessor) {}
ZipWriter::~ZipWriter() {
if (zip_file_)
zipClose(zip_file_, nullptr);
}
bool ZipWriter::Close() {
const bool success = zipClose(zip_file_, nullptr) == ZIP_OK;
zip_file_ = nullptr;
// Call the progress callback one last time with the final progress status.
if (progress_callback_ && !progress_callback_.Run(progress_)) {
LOG(ERROR) << "Cancelling ZIP creation at the end";
return false;
}
return success;
}
bool ZipWriter::AddMixedEntries(Paths paths) {
// Pointers to directory paths in |paths|.
std::vector<const base::FilePath*> directories;
// Declared outside loop to reuse internal buffer.
std::vector<base::File> files;
// First pass. We don't know which paths are files and which ones are
// directories, and we want to avoid making a call to file_accessor_ for each
// path. Try to open all of the paths as files. We'll get invalid file
// descriptors for directories, and we'll process these directories in the
// second pass.
while (!paths.empty()) {
// Work with chunks of 50 paths at most.
const size_t n = std::min<size_t>(paths.size(), 50);
Paths relative_paths;
std::tie(relative_paths, paths) = paths.split_at(n);
files.clear();
if (!file_accessor_->Open(relative_paths, &files) || files.size() != n)
return false;
for (size_t i = 0; i < n; i++) {
const base::FilePath& relative_path = relative_paths[i];
DCHECK(!relative_path.empty());
base::File& file = files[i];
if (file.IsValid()) {
// It's a file.
if (!AddFileEntry(relative_path, std::move(file)))
return false;
} else {
// It's probably a directory. Remember its path for the second pass.
directories.push_back(&relative_path);
}
}
}
// Second pass for directories discovered during the first pass.
for (const base::FilePath* const path : directories) {
DCHECK(path);
if (!AddDirectoryEntry(*path))
return false;
}
return true;
}
bool ZipWriter::AddFileEntries(Paths paths) {
// Declared outside loop to reuse internal buffer.
std::vector<base::File> files;
while (!paths.empty()) {
// Work with chunks of 50 paths at most.
const size_t n = std::min<size_t>(paths.size(), 50);
Paths relative_paths;
std::tie(relative_paths, paths) = paths.split_at(n);
DCHECK_EQ(relative_paths.size(), n);
files.clear();
if (!file_accessor_->Open(relative_paths, &files) || files.size() != n)
return false;
for (size_t i = 0; i < n; i++) {
const base::FilePath& relative_path = relative_paths[i];
DCHECK(!relative_path.empty());
base::File& file = files[i];
if (!file.IsValid()) {
LOG(ERROR) << "Cannot open " << Redact(relative_path);
progress_.errors++;
if (continue_on_error_)
continue;
return false;
}
if (!AddFileEntry(relative_path, std::move(file)))
return false;
}
}
return true;
}
bool ZipWriter::AddDirectoryEntries(Paths paths) {
for (const base::FilePath& path : paths) {
if (!AddDirectoryEntry(path))
return false;
}
return true;
}
bool ZipWriter::AddDirectoryContents(const base::FilePath& path) {
std::vector<base::FilePath> files, subdirs;
if (!file_accessor_->List(path, &files, &subdirs)) {
progress_.errors++;
return continue_on_error_;
}
Filter(&files);
Filter(&subdirs);
if (!AddFileEntries(files))
return false;
return AddDirectoryEntries(subdirs);
}
void ZipWriter::Filter(std::vector<base::FilePath>* const paths) {
DCHECK(paths);
if (!filter_callback_)
return;
const auto end = std::remove_if(paths->begin(), paths->end(),
[this](const base::FilePath& path) {
return !filter_callback_.Run(path);
});
paths->erase(end, paths->end());
}
} // namespace internal
} // namespace zip

View File

@ -0,0 +1,154 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_ZLIB_GOOGLE_ZIP_WRITER_H_
#define THIRD_PARTY_ZLIB_GOOGLE_ZIP_WRITER_H_
#include <memory>
#include <vector>
#include "base/files/file_path.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "third_party/zlib/google/zip.h"
#if defined(USE_SYSTEM_MINIZIP)
#include <minizip/unzip.h>
#include <minizip/zip.h>
#else
#include "third_party/zlib/contrib/minizip/unzip.h"
#include "third_party/zlib/contrib/minizip/zip.h"
#endif
namespace zip {
namespace internal {
// A class used to write entries to a ZIP file and buffering the reading of
// files to limit the number of calls to the FileAccessor. This is for
// performance reasons as these calls may be expensive when IPC based).
// This class is so far internal and only used by zip.cc, but could be made
// public if needed.
//
// All methods returning a bool return true on success and false on error.
class ZipWriter {
public:
// Creates a writer that will write a ZIP file to |zip_file_fd| or |zip_file|
// and which entries are relative to |file_accessor|'s source directory.
// All file reads are performed using |file_accessor|.
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
static std::unique_ptr<ZipWriter> CreateWithFd(int zip_file_fd,
FileAccessor* file_accessor);
#endif
static std::unique_ptr<ZipWriter> Create(const base::FilePath& zip_file,
FileAccessor* file_accessor);
ZipWriter(const ZipWriter&) = delete;
ZipWriter& operator=(const ZipWriter&) = delete;
~ZipWriter();
// Sets the optional progress callback. The callback is called once for each
// time |period|. The final callback is always called when the ZIP operation
// completes.
void SetProgressCallback(ProgressCallback callback, base::TimeDelta period) {
progress_callback_ = std::move(callback);
progress_period_ = std::move(period);
}
// Should ignore missing files and directories?
void ContinueOnError(bool continue_on_error) {
continue_on_error_ = continue_on_error;
}
// Sets the recursive flag, indicating whether the contents of subdirectories
// should be included.
void SetRecursive(bool b) { recursive_ = b; }
// Sets the filter callback.
void SetFilterCallback(FilterCallback callback) {
filter_callback_ = std::move(callback);
}
// Adds the contents of a directory. If the recursive flag is set, the
// contents of subdirectories are also added.
bool AddDirectoryContents(const base::FilePath& path);
// Adds the entries at |paths| to the ZIP file. These can be a mixed bag of
// files and directories. If the recursive flag is set, the contents of
// subdirectories is also added.
bool AddMixedEntries(Paths paths);
// Closes the ZIP file.
bool Close();
private:
// Takes ownership of |zip_file|.
ZipWriter(zipFile zip_file, FileAccessor* file_accessor);
// Regularly called during processing to check whether zipping should continue
// or should be cancelled.
bool ShouldContinue();
// Adds file content to currently open file entry.
bool AddFileContent(const base::FilePath& path, base::File file);
// Adds a file entry (including file contents).
bool AddFileEntry(const base::FilePath& path, base::File file);
// Adds file entries. All the paths should be existing files.
bool AddFileEntries(Paths paths);
// Adds a directory entry. If the recursive flag is set, the contents of this
// directory are also added.
bool AddDirectoryEntry(const base::FilePath& path);
// Adds directory entries. All the paths should be existing directories. If
// the recursive flag is set, the contents of these directories are also
// added.
bool AddDirectoryEntries(Paths paths);
// Opens a file or directory entry.
bool OpenNewFileEntry(const base::FilePath& path,
bool is_directory,
base::Time last_modified);
// Closes the currently open entry.
bool CloseNewFileEntry();
// Filters entries.
void Filter(std::vector<base::FilePath>* paths);
// The actual zip file.
zipFile zip_file_;
// Abstraction over file access methods used to read files.
FileAccessor* const file_accessor_;
// Progress stats.
Progress progress_;
// Optional progress callback.
ProgressCallback progress_callback_;
// Optional progress reporting period.
base::TimeDelta progress_period_;
// Next time to report progress.
base::TimeTicks next_progress_report_time_ = base::TimeTicks::Now();
// Filter used to exclude files from the ZIP file.
FilterCallback filter_callback_;
// Should recursively add directories?
bool recursive_ = false;
// Should ignore missing files and directories?
bool continue_on_error_ = false;
};
} // namespace internal
} // namespace zip
#endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_WRITER_H_