diff options
author | alecflett@chromium.org <alecflett@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-03 23:02:57 +0000 |
---|---|---|
committer | alecflett@chromium.org <alecflett@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-03 23:02:57 +0000 |
commit | 4170d3a06feb349153149c05493756bfd3700cd3 (patch) | |
tree | c3b3b20ddd3689b1c177b44635fe3c2d27750f35 /third_party/zlib | |
parent | c1c416186e47a3caab464ffe8851e8ce5ff92b5b (diff) | |
download | chromium_src-4170d3a06feb349153149c05493756bfd3700cd3.zip chromium_src-4170d3a06feb349153149c05493756bfd3700cd3.tar.gz chromium_src-4170d3a06feb349153149c05493756bfd3700cd3.tar.bz2 |
Move components/zip to third_party/zip
Move components to zip as per the discussion here:
https://groups.google.com/a/chromium.org/d/topic/chromium-dev/MgbMTQCNzR0/discussion
BUG=
R=agl@chromium.org, gavinp@chromium.org, jam@chromium.org, joi@chromium.org
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=197964
Review URL: https://codereview.chromium.org/14021015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@198222 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'third_party/zlib')
-rw-r--r-- | third_party/zlib/google/DEPS | 3 | ||||
-rw-r--r-- | third_party/zlib/google/OWNERS | 2 | ||||
-rw-r--r-- | third_party/zlib/google/zip.cc | 207 | ||||
-rw-r--r-- | third_party/zlib/google/zip.h | 44 | ||||
-rw-r--r-- | third_party/zlib/google/zip_internal.cc | 316 | ||||
-rw-r--r-- | third_party/zlib/google/zip_internal.h | 62 | ||||
-rw-r--r-- | third_party/zlib/google/zip_reader.cc | 310 | ||||
-rw-r--r-- | third_party/zlib/google/zip_reader.h | 177 | ||||
-rw-r--r-- | third_party/zlib/google/zip_reader_unittest.cc | 431 | ||||
-rw-r--r-- | third_party/zlib/google/zip_unittest.cc | 207 | ||||
-rw-r--r-- | third_party/zlib/zlib.gyp | 21 |
11 files changed, 1779 insertions, 1 deletions
diff --git a/third_party/zlib/google/DEPS b/third_party/zlib/google/DEPS new file mode 100644 index 0000000..6a2f02e --- /dev/null +++ b/third_party/zlib/google/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+net/base", +] diff --git a/third_party/zlib/google/OWNERS b/third_party/zlib/google/OWNERS new file mode 100644 index 0000000..ff93254 --- /dev/null +++ b/third_party/zlib/google/OWNERS @@ -0,0 +1,2 @@ +hshi@chromium.org +satorux@chromium.org diff --git a/third_party/zlib/google/zip.cc b/third_party/zlib/google/zip.cc new file mode 100644 index 0000000..e24449b --- /dev/null +++ b/third_party/zlib/google/zip.cc @@ -0,0 +1,207 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// 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 "base/bind.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "net/base/file_stream.h" +#include "third_party/zlib/google/zip_internal.h" +#include "third_party/zlib/google/zip_reader.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 { + +bool AddFileToZip(zipFile zip_file, const base::FilePath& src_dir) { + net::FileStream stream(NULL); + int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ; + if (stream.OpenSync(src_dir, flags) != 0) { + DLOG(ERROR) << "Could not open stream for path " + << src_dir.value(); + return false; + } + + int num_bytes; + char buf[zip::internal::kZipBufSize]; + do { + num_bytes = stream.ReadSync(buf, zip::internal::kZipBufSize); + if (num_bytes > 0) { + if (ZIP_OK != zipWriteInFileInZip(zip_file, buf, num_bytes)) { + DLOG(ERROR) << "Could not write data to zip for path " + << src_dir.value(); + return false; + } + } + } while (num_bytes > 0); + + return true; +} + +bool AddEntryToZip(zipFile zip_file, const base::FilePath& path, + const base::FilePath& root_path) { + std::string str_path = + path.AsUTF8Unsafe().substr(root_path.value().length() + 1); +#if defined(OS_WIN) + ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); +#endif + + bool is_directory = file_util::DirectoryExists(path); + if (is_directory) + str_path += "/"; + + if (ZIP_OK != zipOpenNewFileInZip( + zip_file, str_path.c_str(), + NULL, NULL, 0u, NULL, 0u, NULL, // file info, extrafield local, length, + // extrafield global, length, comment + Z_DEFLATED, Z_DEFAULT_COMPRESSION)) { + DLOG(ERROR) << "Could not open zip file entry " << str_path; + return false; + } + + bool success = true; + if (!is_directory) { + success = AddFileToZip(zip_file, path); + } + + if (ZIP_OK != zipCloseFileInZip(zip_file)) { + DLOG(ERROR) << "Could not close zip file entry " << str_path; + return false; + } + + return success; +} + +bool ExcludeNoFilesFilter(const base::FilePath& file_path) { + return true; +} + +bool ExcludeHiddenFilesFilter(const base::FilePath& file_path) { + return file_path.BaseName().value()[0] != '.'; +} + +} // namespace + +namespace zip { + +bool Unzip(const base::FilePath& src_file, const base::FilePath& dest_dir) { + ZipReader reader; + if (!reader.Open(src_file)) { + DLOG(WARNING) << "Failed to open " << src_file.value(); + return false; + } + while (reader.HasMore()) { + if (!reader.OpenCurrentEntryInZip()) { + DLOG(WARNING) << "Failed to open the current file in zip"; + return false; + } + if (reader.current_entry_info()->is_unsafe()) { + DLOG(WARNING) << "Found an unsafe file in zip " + << reader.current_entry_info()->file_path().value(); + return false; + } + if (!reader.ExtractCurrentEntryIntoDirectory(dest_dir)) { + DLOG(WARNING) << "Failed to extract " + << reader.current_entry_info()->file_path().value(); + return false; + } + if (!reader.AdvanceToNextEntry()) { + DLOG(WARNING) << "Failed to advance to the next file"; + return false; + } + } + return true; +} + +bool ZipWithFilterCallback(const base::FilePath& src_dir, + const base::FilePath& dest_file, + const FilterCallback& filter_cb) { + DCHECK(file_util::DirectoryExists(src_dir)); + + zipFile zip_file = internal::OpenForZipping(dest_file.AsUTF8Unsafe(), + APPEND_STATUS_CREATE); + + if (!zip_file) { + DLOG(WARNING) << "couldn't create file " << dest_file.value(); + return false; + } + + bool success = true; + file_util::FileEnumerator file_enumerator(src_dir, true /* recursive */, + file_util::FileEnumerator::FILES | + file_util::FileEnumerator::DIRECTORIES); + for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); + path = file_enumerator.Next()) { + if (!filter_cb.Run(path)) { + continue; + } + + if (!AddEntryToZip(zip_file, path, src_dir)) { + success = false; + return false; + } + } + + if (ZIP_OK != zipClose(zip_file, NULL)) { + DLOG(ERROR) << "Error closing zip file " << dest_file.value(); + return false; + } + + return success; +} + +bool Zip(const base::FilePath& src_dir, const base::FilePath& dest_file, + bool include_hidden_files) { + if (include_hidden_files) { + return ZipWithFilterCallback( + src_dir, dest_file, base::Bind(&ExcludeNoFilesFilter)); + } else { + return ZipWithFilterCallback( + src_dir, dest_file, base::Bind(&ExcludeHiddenFilesFilter)); + } +} + +#if defined(OS_POSIX) +bool ZipFiles(const base::FilePath& src_dir, + const std::vector<base::FilePath>& src_relative_paths, + int dest_fd) { + DCHECK(file_util::DirectoryExists(src_dir)); + zipFile zip_file = internal::OpenFdForZipping(dest_fd, APPEND_STATUS_CREATE); + + if (!zip_file) { + DLOG(ERROR) << "couldn't create file for fd " << dest_fd; + return false; + } + + bool success = true; + for (std::vector<base::FilePath>::const_iterator iter = + src_relative_paths.begin(); + iter != src_relative_paths.end(); ++iter) { + const base::FilePath& path = src_dir.Append(*iter); + if (!AddEntryToZip(zip_file, path, src_dir)) { + // TODO(hshi): clean up the partial zip file when error occurs. + success = false; + break; + } + } + + if (ZIP_OK != zipClose(zip_file, NULL)) { + DLOG(ERROR) << "Error closing zip file for fd " << dest_fd; + success = false; + } + + return success; +} +#endif // defined(OS_POSIX) + +} // namespace zip diff --git a/third_party/zlib/google/zip.h b/third_party/zlib/google/zip.h new file mode 100644 index 0000000..9809fce --- /dev/null +++ b/third_party/zlib/google/zip.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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 "base/callback.h" +#include "base/files/file_path.h" + +namespace zip { + +// 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. +typedef base::Callback<bool(const base::FilePath&)> FilterCallback; +bool ZipWithFilterCallback(const base::FilePath& src_dir, + const base::FilePath& dest_file, + const 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) +// Zips files listed in |src_relative_paths| to destination specified by file +// descriptor |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, + const std::vector<base::FilePath>& src_relative_paths, + int dest_fd); +#endif // defined(OS_POSIX) + +// Unzip the contents of zip_file into dest_dir. +bool Unzip(const base::FilePath& zip_file, const base::FilePath& dest_dir); + +} // namespace zip + +#endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_H_ diff --git a/third_party/zlib/google/zip_internal.cc b/third_party/zlib/google/zip_internal.cc new file mode 100644 index 0000000..3a3a51a --- /dev/null +++ b/third_party/zlib/google/zip_internal.cc @@ -0,0 +1,316 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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 <algorithm> + +#include "base/logging.h" +#include "base/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 char* as UTF8 and +// uses the Unicode version of CreateFile. +void* ZipOpenFunc(void *opaque, const char* filename, int mode) { + DWORD desired_access, creation_disposition; + DWORD share_mode, flags_and_attributes; + HANDLE file = 0; + void* ret = NULL; + + desired_access = share_mode = flags_and_attributes = 0; + + 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; + } + + base::string16 filename16 = UTF8ToUTF16(filename); + if ((filename != NULL) && (desired_access != 0)) { + file = CreateFile(filename16.c_str(), desired_access, share_mode, + NULL, creation_disposition, flags_and_attributes, NULL); + } + + 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) +// Callback function for zlib that opens a file stream from a file descriptor. +void* FdOpenFileFunc(void* opaque, const char* 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)) + file = fdopen(*static_cast<int*>(opaque), mode_fopen); + + return file; +} + +// We don't actually close the file stream since that would close +// the underlying file descriptor, and we don't own it. However we do need to +// flush buffers and free |opaque| since we malloc'ed it in FillFdOpenFileFunc. +int CloseFileFunc(void* opaque, void* stream) { + fflush(static_cast<FILE*>(stream)); + free(opaque); + return 0; +} + +// Fills |pzlib_filecunc_def| appropriately to handle the zip file +// referred to by |fd|. +void FillFdOpenFileFunc(zlib_filefunc_def* pzlib_filefunc_def, int fd) { + fill_fopen_filefunc(pzlib_filefunc_def); + pzlib_filefunc_def->zopen_file = FdOpenFileFunc; + pzlib_filefunc_def->zclose_file = CloseFileFunc; + 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. +void* HandleOpenFileFunc(void* opaque, const char* 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; +} +#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 + size_t length; + size_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 char* /*filename*/, int mode) { + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) != ZLIB_FILEFUNC_MODE_READ) { + NOTREACHED(); + return NULL; + } + 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); + size_t remaining_bytes = buffer->length - buffer->offset; + if (!buffer || !buffer->data || !remaining_bytes) + return 0; + size = std::min(size, static_cast<uLong>(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(); + return 0; +} + +// Returns the offset from the beginning of the data. +long GetOffsetOfZipBuffer(void* opaque, void* /*stream*/) { + ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque); + if (!buffer) + return -1; + return static_cast<long>(buffer->offset); +} + +// Moves the current offset to the specified position. +long SeekZipBuffer(void* opaque, void* /*stream*/, uLong 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 + static_cast<size_t>(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, static_cast<size_t>(offset)); + return 0; + } + NOTREACHED(); + return -1; +} + +// 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; +} + +} // namespace + +namespace zip { +namespace internal { + +unzFile OpenForUnzipping(const std::string& file_name_utf8) { + zlib_filefunc_def* zip_func_ptrs = NULL; +#if defined(OS_WIN) + zlib_filefunc_def zip_funcs; + fill_win32_filefunc(&zip_funcs); + zip_funcs.zopen_file = ZipOpenFunc; + zip_func_ptrs = &zip_funcs; +#endif + return unzOpen2(file_name_utf8.c_str(), zip_func_ptrs); +} + +#if defined(OS_POSIX) +unzFile OpenFdForUnzipping(int zip_fd) { + zlib_filefunc_def zip_funcs; + FillFdOpenFileFunc(&zip_funcs, zip_fd); + // Passing dummy "fd" filename to zlib. + return unzOpen2("fd", &zip_funcs); +} +#endif + +#if defined(OS_WIN) +unzFile OpenHandleForUnzipping(HANDLE zip_handle) { + zlib_filefunc_def zip_funcs; + fill_win32_filefunc(&zip_funcs); + zip_funcs.zopen_file = HandleOpenFileFunc; + zip_funcs.opaque = zip_handle; + return unzOpen2("fd", &zip_funcs); +} +#endif + +// static +unzFile PreprareMemoryForUnzipping(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_filefunc_def zip_functions; + zip_functions.zopen_file = OpenZipBuffer; + zip_functions.zread_file = ReadZipBuffer; + zip_functions.zwrite_file = WriteZipBuffer; + zip_functions.ztell_file = GetOffsetOfZipBuffer; + zip_functions.zseek_file = SeekZipBuffer; + zip_functions.zclose_file = CloseZipBuffer; + zip_functions.zerror_file = GetErrorOfZipBuffer; + zip_functions.opaque = static_cast<void*>(buffer); + return unzOpen2(NULL, &zip_functions); +} + +zipFile OpenForZipping(const std::string& file_name_utf8, int append_flag) { + zlib_filefunc_def* zip_func_ptrs = NULL; +#if defined(OS_WIN) + zlib_filefunc_def zip_funcs; + fill_win32_filefunc(&zip_funcs); + zip_funcs.zopen_file = ZipOpenFunc; + zip_func_ptrs = &zip_funcs; +#endif + return zipOpen2(file_name_utf8.c_str(), + append_flag, + NULL, // global comment + zip_func_ptrs); +} + +#if defined(OS_POSIX) +zipFile OpenFdForZipping(int zip_fd, int append_flag) { + zlib_filefunc_def zip_funcs; + FillFdOpenFileFunc(&zip_funcs, zip_fd); + // Passing dummy "fd" filename to zlib. + return zipOpen2("fd", append_flag, NULL, &zip_funcs); +} +#endif + +} // namespace internal +} // namespace zip diff --git a/third_party/zlib/google/zip_internal.h b/third_party/zlib/google/zip_internal.h new file mode 100644 index 0000000..57894be --- /dev/null +++ b/third_party/zlib/google/zip_internal.h @@ -0,0 +1,62 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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_ + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <string> + +#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 + +// 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) +// 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 PreprareMemoryForUnzipping(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) +// 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 + +const int kZipMaxPath = 256; +const int kZipBufSize = 8192; + +} // namespace internal +} // namespace zip + +#endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_INTERNAL_H_ diff --git a/third_party/zlib/google/zip_reader.cc b/third_party/zlib/google/zip_reader.cc new file mode 100644 index 0000000..5433b04 --- /dev/null +++ b/third_party/zlib/google/zip_reader.cc @@ -0,0 +1,310 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// 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 "base/file_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "net/base/file_stream.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) + +namespace zip { + +// TODO(satorux): The implementation assumes that file names in zip files +// are encoded in UTF-8. This is true for zip files created by Zip() +// function in zip.h, but not true for user-supplied random zip files. +ZipReader::EntryInfo::EntryInfo(const std::string& file_name_in_zip, + const unz_file_info& raw_file_info) + : file_path_(base::FilePath::FromUTF8Unsafe(file_name_in_zip)), + is_directory_(false) { + original_size_ = raw_file_info.uncompressed_size; + + // Directory entries in zip files end with "/". + is_directory_ = EndsWith(file_name_in_zip, "/", false); + + // Check the file name here for directory traversal issues. In the name of + // simplicity and security, we might reject a valid file name such as "a..b". + is_unsafe_ = file_name_in_zip.find("..") != std::string::npos; + + // We also consider that the file name is unsafe, if it's invalid UTF-8. + string16 file_name_utf16; + if (!UTF8ToUTF16(file_name_in_zip.data(), file_name_in_zip.size(), + &file_name_utf16)) { + is_unsafe_ = true; + } + + // We also consider that the file name is unsafe, if it's absolute. + // On Windows, IsAbsolute() returns false for paths starting with "/". + if (file_path_.IsAbsolute() || StartsWithASCII(file_name_in_zip, "/", false)) + is_unsafe_ = true; + + // Construct the last modified time. The timezone info is not present in + // zip files, so we construct the time as local time. + base::Time::Exploded exploded_time = {}; // Zero-clear. + exploded_time.year = raw_file_info.tmu_date.tm_year; + // The month in zip file is 0-based, whereas ours is 1-based. + exploded_time.month = raw_file_info.tmu_date.tm_mon + 1; + exploded_time.day_of_month = raw_file_info.tmu_date.tm_mday; + exploded_time.hour = raw_file_info.tmu_date.tm_hour; + exploded_time.minute = raw_file_info.tmu_date.tm_min; + exploded_time.second = raw_file_info.tmu_date.tm_sec; + exploded_time.millisecond = 0; + if (exploded_time.HasValidValues()) { + last_modified_ = base::Time::FromLocalExploded(exploded_time); + } else { + // Use Unix time epoch if the time stamp data is invalid. + last_modified_ = base::Time::UnixEpoch(); + } +} + +ZipReader::ZipReader() { + Reset(); +} + +ZipReader::~ZipReader() { + Close(); +} + +bool ZipReader::Open(const base::FilePath& zip_file_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_file_path.AsUTF8Unsafe()); + if (!zip_file_) { + return false; + } + + return OpenInternal(); +} + +bool ZipReader::OpenFromPlatformFile(base::PlatformFile zip_fd) { + DCHECK(!zip_file_); + +#if defined(OS_POSIX) + zip_file_ = internal::OpenFdForUnzipping(zip_fd); +#elif defined(OS_WIN) + zip_file_ = internal::OpenHandleForUnzipping(zip_fd); +#endif + if (!zip_file_) { + return false; + } + + return OpenInternal(); +} + +bool ZipReader::OpenFromString(const std::string& data) { + zip_file_ = internal::PreprareMemoryForUnzipping(data); + if (!zip_file_) + return false; + return OpenInternal(); +} + +void ZipReader::Close() { + if (zip_file_) { + unzClose(zip_file_); + } + Reset(); +} + +bool ZipReader::HasMore() { + return !reached_end_; +} + +bool ZipReader::AdvanceToNextEntry() { + DCHECK(zip_file_); + + // Should not go further if we already reached the end. + if (reached_end_) + return false; + + unz_file_pos position = {}; + if (unzGetFilePos(zip_file_, &position) != UNZ_OK) + return false; + const int current_entry_index = position.num_of_file; + // If we are currently at the last entry, then the next position is the + // end of the zip file, so mark that we reached the end. + if (current_entry_index + 1 == num_entries_) { + reached_end_ = true; + } else { + DCHECK_LT(current_entry_index + 1, num_entries_); + if (unzGoToNextFile(zip_file_) != UNZ_OK) { + return false; + } + } + current_entry_info_.reset(); + return true; +} + +bool ZipReader::OpenCurrentEntryInZip() { + DCHECK(zip_file_); + + unz_file_info raw_file_info = {}; + char raw_file_name_in_zip[internal::kZipMaxPath] = {}; + const int result = unzGetCurrentFileInfo(zip_file_, + &raw_file_info, + raw_file_name_in_zip, + sizeof(raw_file_name_in_zip) - 1, + NULL, // extraField. + 0, // extraFieldBufferSize. + NULL, // szComment. + 0); // commentBufferSize. + if (result != UNZ_OK) + return false; + if (raw_file_name_in_zip[0] == '\0') + return false; + current_entry_info_.reset( + new EntryInfo(raw_file_name_in_zip, raw_file_info)); + return true; +} + +bool ZipReader::LocateAndOpenEntry(const base::FilePath& path_in_zip) { + DCHECK(zip_file_); + + current_entry_info_.reset(); + reached_end_ = false; + const int kDefaultCaseSensivityOfOS = 0; + const int result = unzLocateFile(zip_file_, + path_in_zip.AsUTF8Unsafe().c_str(), + kDefaultCaseSensivityOfOS); + if (result != UNZ_OK) + return false; + + // Then Open the entry. + return OpenCurrentEntryInZip(); +} + +bool ZipReader::ExtractCurrentEntryToFilePath( + const base::FilePath& output_file_path) { + DCHECK(zip_file_); + + // If this is a directory, just create it and return. + if (current_entry_info()->is_directory()) + return file_util::CreateDirectory(output_file_path); + + const int open_result = unzOpenCurrentFile(zip_file_); + if (open_result != UNZ_OK) + return false; + + // We can't rely on parent directory entries being specified in the + // zip, so we make sure they are created. + base::FilePath output_dir_path = output_file_path.DirName(); + if (!file_util::CreateDirectory(output_dir_path)) + return false; + + net::FileStream stream(NULL); + const int flags = (base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_WRITE); + if (stream.OpenSync(output_file_path, flags) != 0) + return false; + + bool success = true; // This becomes false when something bad happens. + while (true) { + char buf[internal::kZipBufSize]; + const int num_bytes_read = unzReadCurrentFile(zip_file_, buf, + internal::kZipBufSize); + if (num_bytes_read == 0) { + // Reached the end of the file. + break; + } else if (num_bytes_read < 0) { + // If num_bytes_read < 0, then it's a specific UNZ_* error code. + success = false; + break; + } else if (num_bytes_read > 0) { + // Some data is read. Write it to the output file. + if (num_bytes_read != stream.WriteSync(buf, num_bytes_read)) { + success = false; + break; + } + } + } + + unzCloseCurrentFile(zip_file_); + return success; +} + +bool ZipReader::ExtractCurrentEntryIntoDirectory( + const base::FilePath& output_directory_path) { + DCHECK(current_entry_info_.get()); + + base::FilePath output_file_path = output_directory_path.Append( + current_entry_info()->file_path()); + return ExtractCurrentEntryToFilePath(output_file_path); +} + +#if defined(OS_POSIX) +bool ZipReader::ExtractCurrentEntryToFd(const int fd) { + DCHECK(zip_file_); + + // If this is a directory, there's nothing to extract to the file descriptor, + // so return false. + if (current_entry_info()->is_directory()) + return false; + + const int open_result = unzOpenCurrentFile(zip_file_); + if (open_result != UNZ_OK) + return false; + + bool success = true; // This becomes false when something bad happens. + while (true) { + char buf[internal::kZipBufSize]; + const int num_bytes_read = unzReadCurrentFile(zip_file_, buf, + internal::kZipBufSize); + if (num_bytes_read == 0) { + // Reached the end of the file. + break; + } else if (num_bytes_read < 0) { + // If num_bytes_read < 0, then it's a specific UNZ_* error code. + success = false; + break; + } else if (num_bytes_read > 0) { + // Some data is read. Write it to the output file descriptor. + if (num_bytes_read != + file_util::WriteFileDescriptor(fd, buf, num_bytes_read)) { + success = false; + break; + } + } + } + + unzCloseCurrentFile(zip_file_); + return success; +} +#endif // defined(OS_POSIX) + +bool ZipReader::OpenInternal() { + DCHECK(zip_file_); + + unz_global_info zip_info = {}; // Zero-clear. + if (unzGetGlobalInfo(zip_file_, &zip_info) != UNZ_OK) { + return false; + } + num_entries_ = zip_info.number_entry; + if (num_entries_ < 0) + return false; + + // We are already at the end if the zip file is empty. + reached_end_ = (num_entries_ == 0); + return true; +} + +void ZipReader::Reset() { + zip_file_ = NULL; + num_entries_ = 0; + reached_end_ = false; + current_entry_info_.reset(); +} + +} // namespace zip diff --git a/third_party/zlib/google/zip_reader.h b/third_party/zlib/google/zip_reader.h new file mode 100644 index 0000000..4064da4 --- /dev/null +++ b/third_party/zlib/google/zip_reader.h @@ -0,0 +1,177 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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 <string> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "base/time.h" + +#if defined(USE_SYSTEM_MINIZIP) +#include <minizip/unzip.h> +#else +#include "third_party/zlib/contrib/minizip/unzip.h" +#endif + +namespace zip { + +// This class is used for reading zip files. A typical use case of this +// class is to scan entries in a zip file and extract them. The code will +// look like: +// +// ZipReader reader; +// reader.Open(zip_file_path); +// while (reader.HasMore()) { +// reader.OpenCurrentEntryInZip(); +// reader.ExtractCurrentEntryToDirectory(output_directory_path); +// reader.AdvanceToNextEntry(); +// } +// +// For simplicty, error checking is omitted in the example code above. The +// production code should check return values from all of these functions. +// +// This calls can also be used for random access of contents in a zip file +// using LocateAndOpenEntry(). +// +class ZipReader { + public: + // This class represents information of an entry (file or directory) in + // a zip file. + class EntryInfo { + public: + EntryInfo(const std::string& filename_in_zip, + const unz_file_info& raw_file_info); + + // Returns the file path. The path is usually relative like + // "foo/bar.txt", but if it's absolute, is_unsafe() returns true. + const base::FilePath& file_path() const { return file_path_; } + + // Returns the size of the original file (i.e. after uncompressed). + // Returns 0 if the entry is a directory. + int64 original_size() const { return original_size_; } + + // Returns the last modified time. + base::Time last_modified() const { return last_modified_; } + + // Returns true if the entry is a directory. + bool is_directory() const { return is_directory_; } + + // Returns true if the entry is unsafe, like having ".." or invalid + // UTF-8 characters in its file name, or the file path is absolute. + bool is_unsafe() const { return is_unsafe_; } + + private: + const base::FilePath file_path_; + int64 original_size_; + base::Time last_modified_; + bool is_directory_; + bool is_unsafe_; + DISALLOW_COPY_AND_ASSIGN(EntryInfo); + }; + + ZipReader(); + ~ZipReader(); + + // Opens the zip file specified by |zip_file_path|. Returns true on + // success. + bool Open(const base::FilePath& zip_file_path); + + // Opens the zip file referred to by the platform file |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 file. This function is called in the + // destructor of the class, so you usually don't need to call this. + void Close(); + + // Returns true if there is at least one entry to read. This function is + // used to scan entries with AdvanceToNextEntry(), like: + // + // while (reader.HasMore()) { + // // Do something with the current file here. + // reader.AdvanceToNextEntry(); + // } + bool HasMore(); + + // Advances the next entry. Returns true on success. + bool AdvanceToNextEntry(); + + // Opens the current entry in the zip file. On success, returns true and + // updates the the current entry state (i.e. current_entry_info() is + // updated). This function should be called before operations over the + // current entry like ExtractCurrentEntryToFile(). + // + // Note that there is no CloseCurrentEntryInZip(). The the current entry + // state is reset automatically as needed. + bool OpenCurrentEntryInZip(); + + // Locates an entry in the zip file and opens it. Returns true on + // success. This function internally calls OpenCurrentEntryInZip() on + // success. On failure, current_entry_info() becomes NULL. + bool LocateAndOpenEntry(const base::FilePath& path_in_zip); + + // Extracts the current entry to the given output file path. If the + // current file is a directory, just creates a directory + // instead. Returns true on success. OpenCurrentEntryInZip() must be + // called beforehand. + // + // This function does not preserve the timestamp of the original entry. + bool ExtractCurrentEntryToFilePath(const base::FilePath& output_file_path); + + // Extracts the current entry to the given output directory path using + // ExtractCurrentEntryToFilePath(). Sub directories are created as needed + // based on the file path of the current entry. For example, if the file + // path in zip is "foo/bar.txt", and the output directory is "output", + // "output/foo/bar.txt" will be created. + // + // Returns true on success. OpenCurrentEntryInZip() must be called + // beforehand. + bool ExtractCurrentEntryIntoDirectory( + const base::FilePath& output_directory_path); + +#if defined(OS_POSIX) + // Extracts the current entry by writing directly to a file descriptor. + // Does not close the file descriptor. Returns true on success. + bool ExtractCurrentEntryToFd(int fd); +#endif + + // Returns the current entry info. Returns NULL if the current entry is + // not yet opened. OpenCurrentEntryInZip() must be called beforehand. + EntryInfo* current_entry_info() const { + return current_entry_info_.get(); + } + + // Returns the number of entries in the zip file. + // Open() must be called beforehand. + int num_entries() const { return num_entries_; } + + private: + // Common code used both in Open and OpenFromFd. + bool OpenInternal(); + + // Resets the internal state. + void Reset(); + + unzFile zip_file_; + int num_entries_; + bool reached_end_; + scoped_ptr<EntryInfo> current_entry_info_; + + DISALLOW_COPY_AND_ASSIGN(ZipReader); +}; + +} // namespace zip + +#endif // THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_ diff --git a/third_party/zlib/google/zip_reader_unittest.cc b/third_party/zlib/google/zip_reader_unittest.cc new file mode 100644 index 0000000..a12b04e --- /dev/null +++ b/third_party/zlib/google/zip_reader_unittest.cc @@ -0,0 +1,431 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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 <set> +#include <string> + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/md5.h" +#include "base/path_service.h" +#include "base/platform_file.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" +#include "third_party/zlib/google/zip_internal.h" + +namespace { + +// Wrap PlatformFiles in a class so that we don't leak them in tests. +class PlatformFileWrapper { + public: + typedef enum { + READ_ONLY, + READ_WRITE + } AccessMode; + + PlatformFileWrapper(const base::FilePath& file, AccessMode mode) + : file_(base::kInvalidPlatformFileValue) { + switch (mode) { + case READ_ONLY: + file_ = base::CreatePlatformFile(file, + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ, + NULL, NULL); + break; + case READ_WRITE: + file_ = base::CreatePlatformFile(file, + base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE, + NULL, NULL); + break; + default: + NOTREACHED(); + } + return; + } + + ~PlatformFileWrapper() { + base::ClosePlatformFile(file_); + } + + base::PlatformFile platform_file() { return file_; } + + private: + base::PlatformFile file_; +}; + +} // namespace + +namespace zip { + +// Make the test a PlatformTest to setup autorelease pools properly on Mac. +class ZipReaderTest : public PlatformTest { + protected: + virtual void SetUp() { + PlatformTest::SetUp(); + + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + test_dir_ = temp_dir_.path(); + + ASSERT_TRUE(GetTestDataDirectory(&test_data_dir_)); + + test_zip_file_ = test_data_dir_.AppendASCII("test.zip"); + evil_zip_file_ = test_data_dir_.AppendASCII("evil.zip"); + evil_via_invalid_utf8_zip_file_ = test_data_dir_.AppendASCII( + "evil_via_invalid_utf8.zip"); + evil_via_absolute_file_name_zip_file_ = test_data_dir_.AppendASCII( + "evil_via_absolute_file_name.zip"); + + test_zip_contents_.insert(base::FilePath(FILE_PATH_LITERAL("foo/"))); + test_zip_contents_.insert(base::FilePath(FILE_PATH_LITERAL("foo/bar/"))); + test_zip_contents_.insert( + base::FilePath(FILE_PATH_LITERAL("foo/bar/baz.txt"))); + test_zip_contents_.insert( + base::FilePath(FILE_PATH_LITERAL("foo/bar/quux.txt"))); + test_zip_contents_.insert( + base::FilePath(FILE_PATH_LITERAL("foo/bar.txt"))); + test_zip_contents_.insert(base::FilePath(FILE_PATH_LITERAL("foo.txt"))); + test_zip_contents_.insert( + base::FilePath(FILE_PATH_LITERAL("foo/bar/.hidden"))); + } + + virtual void TearDown() { + PlatformTest::TearDown(); + } + + bool GetTestDataDirectory(base::FilePath* path) { + bool success = PathService::Get(base::DIR_SOURCE_ROOT, path); + EXPECT_TRUE(success); + if (!success) + return false; + *path = path->AppendASCII("third_party"); + *path = path->AppendASCII("zlib"); + *path = path->AppendASCII("google"); + *path = path->AppendASCII("test"); + *path = path->AppendASCII("data"); + return true; + } + + // 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. + base::FilePath test_data_dir_; + // The path to test.zip in the test data directory. + base::FilePath test_zip_file_; + // The path to evil.zip in the test data directory. + base::FilePath evil_zip_file_; + // The path to evil_via_invalid_utf8.zip in the test data directory. + base::FilePath evil_via_invalid_utf8_zip_file_; + // The path to evil_via_absolute_file_name.zip in the test data directory. + base::FilePath evil_via_absolute_file_name_zip_file_; + std::set<base::FilePath> test_zip_contents_; + + base::ScopedTempDir temp_dir_; +}; + +TEST_F(ZipReaderTest, Open_ValidZipFile) { + ZipReader reader; + ASSERT_TRUE(reader.Open(test_zip_file_)); +} + +TEST_F(ZipReaderTest, Open_ValidZipPlatformFile) { + ZipReader reader; + PlatformFileWrapper zip_fd_wrapper(test_zip_file_, + PlatformFileWrapper::READ_ONLY); + ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file())); +} + +TEST_F(ZipReaderTest, Open_NonExistentFile) { + ZipReader reader; + ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("nonexistent.zip"))); +} + +TEST_F(ZipReaderTest, Open_ExistentButNonZipFile) { + ZipReader reader; + ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("create_test_zip.sh"))); +} + +// Iterate through the contents in the test zip file, and compare that the +// contents collected from the zip reader matches the expected contents. +TEST_F(ZipReaderTest, Iteration) { + std::set<base::FilePath> actual_contents; + ZipReader reader; + ASSERT_TRUE(reader.Open(test_zip_file_)); + while (reader.HasMore()) { + ASSERT_TRUE(reader.OpenCurrentEntryInZip()); + actual_contents.insert(reader.current_entry_info()->file_path()); + ASSERT_TRUE(reader.AdvanceToNextEntry()); + } + EXPECT_FALSE(reader.AdvanceToNextEntry()); // Shouldn't go further. + EXPECT_EQ(test_zip_contents_.size(), + static_cast<size_t>(reader.num_entries())); + EXPECT_EQ(test_zip_contents_.size(), actual_contents.size()); + EXPECT_EQ(test_zip_contents_, actual_contents); +} + +// Open the test zip file from a file descriptor, iterate through its contents, +// and compare that they match the expected contents. +TEST_F(ZipReaderTest, PlatformFileIteration) { + std::set<base::FilePath> actual_contents; + ZipReader reader; + PlatformFileWrapper zip_fd_wrapper(test_zip_file_, + PlatformFileWrapper::READ_ONLY); + ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file())); + while (reader.HasMore()) { + ASSERT_TRUE(reader.OpenCurrentEntryInZip()); + actual_contents.insert(reader.current_entry_info()->file_path()); + ASSERT_TRUE(reader.AdvanceToNextEntry()); + } + EXPECT_FALSE(reader.AdvanceToNextEntry()); // Shouldn't go further. + EXPECT_EQ(test_zip_contents_.size(), + static_cast<size_t>(reader.num_entries())); + EXPECT_EQ(test_zip_contents_.size(), actual_contents.size()); + EXPECT_EQ(test_zip_contents_, actual_contents); +} + +TEST_F(ZipReaderTest, LocateAndOpenEntry_ValidFile) { + std::set<base::FilePath> actual_contents; + ZipReader reader; + ASSERT_TRUE(reader.Open(test_zip_file_)); + base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + EXPECT_EQ(target_path, reader.current_entry_info()->file_path()); +} + +TEST_F(ZipReaderTest, LocateAndOpenEntry_NonExistentFile) { + std::set<base::FilePath> actual_contents; + ZipReader reader; + ASSERT_TRUE(reader.Open(test_zip_file_)); + base::FilePath target_path(FILE_PATH_LITERAL("nonexistent.txt")); + ASSERT_FALSE(reader.LocateAndOpenEntry(target_path)); + EXPECT_EQ(NULL, reader.current_entry_info()); +} + +TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_RegularFile) { + ZipReader reader; + ASSERT_TRUE(reader.Open(test_zip_file_)); + base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath( + test_dir_.AppendASCII("quux.txt"))); + // Read the output file ans compute the MD5. + std::string output; + ASSERT_TRUE(file_util::ReadFileToString(test_dir_.AppendASCII("quux.txt"), + &output)); + const std::string md5 = base::MD5String(output); + const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; + EXPECT_EQ(kExpectedMD5, md5); + // quux.txt should be larger than kZipBufSize so that we can exercise + // the loop in ExtractCurrentEntry(). + EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); +} + +TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFilePath_RegularFile) { + ZipReader reader; + PlatformFileWrapper zip_fd_wrapper(test_zip_file_, + PlatformFileWrapper::READ_ONLY); + ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file())); + base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath( + test_dir_.AppendASCII("quux.txt"))); + // Read the output file and compute the MD5. + std::string output; + ASSERT_TRUE(file_util::ReadFileToString(test_dir_.AppendASCII("quux.txt"), + &output)); + const std::string md5 = base::MD5String(output); + const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; + EXPECT_EQ(kExpectedMD5, md5); + // quux.txt should be larger than kZipBufSize so that we can exercise + // the loop in ExtractCurrentEntry(). + EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); +} + +#if defined(OS_POSIX) +TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) { + ZipReader reader; + PlatformFileWrapper zip_fd_wrapper(test_zip_file_, + PlatformFileWrapper::READ_ONLY); + ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file())); + base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt")); + base::FilePath out_path = test_dir_.AppendASCII("quux.txt"); + PlatformFileWrapper out_fd_w(out_path, PlatformFileWrapper::READ_WRITE); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ASSERT_TRUE(reader.ExtractCurrentEntryToFd(out_fd_w.platform_file())); + // Read the output file and compute the MD5. + std::string output; + ASSERT_TRUE(file_util::ReadFileToString(test_dir_.AppendASCII("quux.txt"), + &output)); + const std::string md5 = base::MD5String(output); + const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; + EXPECT_EQ(kExpectedMD5, md5); + // quux.txt should be larger than kZipBufSize so that we can exercise + // the loop in ExtractCurrentEntry(). + EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); +} +#endif + +TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_Directory) { + ZipReader reader; + ASSERT_TRUE(reader.Open(test_zip_file_)); + base::FilePath target_path(FILE_PATH_LITERAL("foo/")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath( + test_dir_.AppendASCII("foo"))); + // The directory should be created. + ASSERT_TRUE(file_util::DirectoryExists(test_dir_.AppendASCII("foo"))); +} + +TEST_F(ZipReaderTest, ExtractCurrentEntryIntoDirectory_RegularFile) { + ZipReader reader; + ASSERT_TRUE(reader.Open(test_zip_file_)); + base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ASSERT_TRUE(reader.ExtractCurrentEntryIntoDirectory(test_dir_)); + // Sub directories should be created. + ASSERT_TRUE(file_util::DirectoryExists(test_dir_.AppendASCII("foo/bar"))); + // And the file should be created. + std::string output; + ASSERT_TRUE(file_util::ReadFileToString( + test_dir_.AppendASCII("foo/bar/quux.txt"), &output)); + const std::string md5 = base::MD5String(output); + const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; + EXPECT_EQ(kExpectedMD5, md5); +} + +TEST_F(ZipReaderTest, current_entry_info_RegularFile) { + ZipReader reader; + ASSERT_TRUE(reader.Open(test_zip_file_)); + base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ZipReader::EntryInfo* current_entry_info = reader.current_entry_info(); + + EXPECT_EQ(target_path, current_entry_info->file_path()); + EXPECT_EQ(13527, current_entry_info->original_size()); + + // The expected time stamp: 2009-05-29 06:22:20 + base::Time::Exploded exploded = {}; // Zero-clear. + current_entry_info->last_modified().LocalExplode(&exploded); + EXPECT_EQ(2009, exploded.year); + EXPECT_EQ(5, exploded.month); + EXPECT_EQ(29, exploded.day_of_month); + EXPECT_EQ(6, exploded.hour); + EXPECT_EQ(22, exploded.minute); + EXPECT_EQ(20, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + EXPECT_FALSE(current_entry_info->is_unsafe()); + EXPECT_FALSE(current_entry_info->is_directory()); +} + +TEST_F(ZipReaderTest, current_entry_info_DotDotFile) { + ZipReader reader; + ASSERT_TRUE(reader.Open(evil_zip_file_)); + base::FilePath target_path(FILE_PATH_LITERAL( + "../levilevilevilevilevilevilevilevilevilevilevilevil")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ZipReader::EntryInfo* current_entry_info = reader.current_entry_info(); + EXPECT_EQ(target_path, current_entry_info->file_path()); + + // This file is unsafe because of ".." in the file name. + EXPECT_TRUE(current_entry_info->is_unsafe()); + EXPECT_FALSE(current_entry_info->is_directory()); +} + +TEST_F(ZipReaderTest, current_entry_info_InvalidUTF8File) { + ZipReader reader; + ASSERT_TRUE(reader.Open(evil_via_invalid_utf8_zip_file_)); + // The evil file is the 2nd file in the zip file. + // We cannot locate by the file name ".\x80.\\evil.txt", + // as FilePath may internally convert the string. + ASSERT_TRUE(reader.AdvanceToNextEntry()); + ASSERT_TRUE(reader.OpenCurrentEntryInZip()); + ZipReader::EntryInfo* current_entry_info = reader.current_entry_info(); + + // This file is unsafe because of invalid UTF-8 in the file name. + EXPECT_TRUE(current_entry_info->is_unsafe()); + EXPECT_FALSE(current_entry_info->is_directory()); +} + +TEST_F(ZipReaderTest, current_entry_info_AbsoluteFile) { + ZipReader reader; + ASSERT_TRUE(reader.Open(evil_via_absolute_file_name_zip_file_)); + base::FilePath target_path(FILE_PATH_LITERAL("/evil.txt")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ZipReader::EntryInfo* current_entry_info = reader.current_entry_info(); + EXPECT_EQ(target_path, current_entry_info->file_path()); + + // This file is unsafe because of the absolute file name. + EXPECT_TRUE(current_entry_info->is_unsafe()); + EXPECT_FALSE(current_entry_info->is_directory()); +} + +TEST_F(ZipReaderTest, current_entry_info_Directory) { + ZipReader reader; + ASSERT_TRUE(reader.Open(test_zip_file_)); + base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ZipReader::EntryInfo* current_entry_info = reader.current_entry_info(); + + EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("foo/bar/")), + current_entry_info->file_path()); + // The directory size should be zero. + EXPECT_EQ(0, current_entry_info->original_size()); + + // The expected time stamp: 2009-05-31 15:49:52 + base::Time::Exploded exploded = {}; // Zero-clear. + current_entry_info->last_modified().LocalExplode(&exploded); + EXPECT_EQ(2009, exploded.year); + EXPECT_EQ(5, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(15, exploded.hour); + EXPECT_EQ(49, exploded.minute); + EXPECT_EQ(52, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + EXPECT_FALSE(current_entry_info->is_unsafe()); + EXPECT_TRUE(current_entry_info->is_directory()); +} + +// 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, arraysize(kTestData)); + ZipReader reader; + ASSERT_TRUE(reader.OpenFromString(data)); + base::FilePath target_path(FILE_PATH_LITERAL("test.txt")); + ASSERT_TRUE(reader.LocateAndOpenEntry(target_path)); + ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath( + test_dir_.AppendASCII("test.txt"))); + + std::string actual; + ASSERT_TRUE(file_util::ReadFileToString( + test_dir_.AppendASCII("test.txt"), &actual)); + EXPECT_EQ(std::string("This is a test.\n"), actual); +} + +} // namespace zip diff --git a/third_party/zlib/google/zip_unittest.cc b/third_party/zlib/google/zip_unittest.cc new file mode 100644 index 0000000..3f7911e --- /dev/null +++ b/third_party/zlib/google/zip_unittest.cc @@ -0,0 +1,207 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <set> +#include <vector> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" +#include "third_party/zlib/google/zip.h" +#include "third_party/zlib/google/zip_reader.h" + +namespace { + +// Make the test a PlatformTest to setup autorelease pools properly on Mac. +class ZipTest : public PlatformTest { + protected: + virtual void SetUp() { + PlatformTest::SetUp(); + + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + test_dir_ = temp_dir_.path(); + + base::FilePath zip_path(test_dir_); + zip_contents_.insert(zip_path.AppendASCII("foo.txt")); + zip_path = zip_path.AppendASCII("foo"); + zip_contents_.insert(zip_path); + zip_contents_.insert(zip_path.AppendASCII("bar.txt")); + zip_path = zip_path.AppendASCII("bar"); + zip_contents_.insert(zip_path); + zip_contents_.insert(zip_path.AppendASCII("baz.txt")); + zip_contents_.insert(zip_path.AppendASCII("quux.txt")); + zip_contents_.insert(zip_path.AppendASCII(".hidden")); + + // Include a subset of files in |zip_file_list_| to test ZipFiles(). + zip_file_list_.push_back(base::FilePath(FILE_PATH_LITERAL("foo.txt"))); + zip_file_list_.push_back( + base::FilePath(FILE_PATH_LITERAL("foo/bar/quux.txt"))); + zip_file_list_.push_back( + base::FilePath(FILE_PATH_LITERAL("foo/bar/.hidden"))); + } + + virtual void TearDown() { + PlatformTest::TearDown(); + } + + bool GetTestDataDirectory(base::FilePath* path) { + bool success = PathService::Get(base::DIR_SOURCE_ROOT, path); + EXPECT_TRUE(success); + if (!success) + return false; + *path = path->AppendASCII("third_party"); + *path = path->AppendASCII("zlib"); + *path = path->AppendASCII("google"); + *path = path->AppendASCII("test"); + *path = path->AppendASCII("data"); + return true; + } + + void TestUnzipFile(const base::FilePath::StringType& filename, + bool expect_hidden_files) { + base::FilePath test_dir; + ASSERT_TRUE(GetTestDataDirectory(&test_dir)); + TestUnzipFile(test_dir.Append(filename), expect_hidden_files); + } + + void TestUnzipFile(const base::FilePath& path, bool expect_hidden_files) { + ASSERT_TRUE(file_util::PathExists(path)) << "no file " << path.value(); + ASSERT_TRUE(zip::Unzip(path, test_dir_)); + + file_util::FileEnumerator files(test_dir_, true, + file_util::FileEnumerator::FILES | + file_util::FileEnumerator::DIRECTORIES); + base::FilePath next_path = files.Next(); + size_t count = 0; + while (!next_path.value().empty()) { + if (next_path.value().find(FILE_PATH_LITERAL(".svn")) == + base::FilePath::StringType::npos) { + EXPECT_EQ(zip_contents_.count(next_path), 1U) << + "Couldn't find " << next_path.value(); + count++; + } + next_path = files.Next(); + } + + size_t expected_count = 0; + for (std::set<base::FilePath>::iterator iter = zip_contents_.begin(); + iter != zip_contents_.end(); ++iter) { + if (expect_hidden_files || iter->BaseName().value()[0] != '.') + ++expected_count; + } + + EXPECT_EQ(expected_count, count); + } + + // The path to temporary directory used to contain the test operations. + base::FilePath test_dir_; + + base::ScopedTempDir temp_dir_; + + // Hard-coded contents of a known zip file. + std::set<base::FilePath> zip_contents_; + + // Hard-coded list of relative paths for a zip file created with ZipFiles. + std::vector<base::FilePath> zip_file_list_; +}; + +TEST_F(ZipTest, Unzip) { + TestUnzipFile(FILE_PATH_LITERAL("test.zip"), true); +} + +TEST_F(ZipTest, UnzipUncompressed) { + TestUnzipFile(FILE_PATH_LITERAL("test_nocompress.zip"), true); +} + +TEST_F(ZipTest, UnzipEvil) { + base::FilePath path; + ASSERT_TRUE(GetTestDataDirectory(&path)); + path = path.AppendASCII("evil.zip"); + // Unzip the zip file into a sub directory of test_dir_ so evil.zip + // won't create a persistent file outside test_dir_ in case of a + // failure. + base::FilePath output_dir = test_dir_.AppendASCII("out"); + ASSERT_FALSE(zip::Unzip(path, output_dir)); + base::FilePath evil_file = output_dir; + evil_file = evil_file.AppendASCII( + "../levilevilevilevilevilevilevilevilevilevilevilevil"); + ASSERT_FALSE(file_util::PathExists(evil_file)); +} + +TEST_F(ZipTest, UnzipEvil2) { + base::FilePath path; + ASSERT_TRUE(GetTestDataDirectory(&path)); + // The zip file contains an evil file with invalid UTF-8 in its file + // name. + path = path.AppendASCII("evil_via_invalid_utf8.zip"); + // See the comment at UnzipEvil() for why we do this. + base::FilePath output_dir = test_dir_.AppendASCII("out"); + // This should fail as it contains an evil file. + ASSERT_FALSE(zip::Unzip(path, output_dir)); + base::FilePath evil_file = output_dir; + evil_file = evil_file.AppendASCII("../evil.txt"); + ASSERT_FALSE(file_util::PathExists(evil_file)); +} + +TEST_F(ZipTest, Zip) { + base::FilePath src_dir; + ASSERT_TRUE(GetTestDataDirectory(&src_dir)); + src_dir = src_dir.AppendASCII("test"); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath zip_file = temp_dir.path().AppendASCII("out.zip"); + + EXPECT_TRUE(zip::Zip(src_dir, zip_file, true)); + TestUnzipFile(zip_file, true); +} + +TEST_F(ZipTest, ZipIgnoreHidden) { + base::FilePath src_dir; + ASSERT_TRUE(GetTestDataDirectory(&src_dir)); + src_dir = src_dir.AppendASCII("test"); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath zip_file = temp_dir.path().AppendASCII("out.zip"); + + EXPECT_TRUE(zip::Zip(src_dir, zip_file, false)); + TestUnzipFile(zip_file, false); +} + +#if defined(OS_POSIX) +TEST_F(ZipTest, ZipFiles) { + base::FilePath src_dir; + ASSERT_TRUE(GetTestDataDirectory(&src_dir)); + src_dir = src_dir.AppendASCII("test"); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath zip_file = temp_dir.path().AppendASCII("out.zip"); + + const int flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE; + const base::PlatformFile zip_fd = + base::CreatePlatformFile(zip_file, flags, NULL, NULL); + ASSERT_LE(0, zip_fd); + EXPECT_TRUE(zip::ZipFiles(src_dir, zip_file_list_, zip_fd)); + base::ClosePlatformFile(zip_fd); + + zip::ZipReader reader; + EXPECT_TRUE(reader.Open(zip_file)); + EXPECT_EQ(zip_file_list_.size(), static_cast<size_t>(reader.num_entries())); + for (size_t i = 0; i < zip_file_list_.size(); ++i) { + EXPECT_TRUE(reader.LocateAndOpenEntry(zip_file_list_[i])); + // Check the path in the entry just in case. + const zip::ZipReader::EntryInfo* entry_info = reader.current_entry_info(); + EXPECT_EQ(entry_info->file_path(), zip_file_list_[i]); + } +} +#endif // defined(OS_POSIX) + +} // namespace + diff --git a/third_party/zlib/zlib.gyp b/third_party/zlib/zlib.gyp index 069fb63..ebdad42 100644 --- a/third_party/zlib/zlib.gyp +++ b/third_party/zlib/zlib.gyp @@ -106,6 +106,25 @@ ], }], ], - } + }, + { + 'target_name': 'zip', + 'type': 'static_library', + 'dependencies': [ + 'minizip', + '../../base/base.gyp:base', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'google/zip.cc', + 'google/zip.h', + 'google/zip_internal.cc', + 'google/zip_internal.h', + 'google/zip_reader.cc', + 'google/zip_reader.h', + ], + }, ], } |