| // Copyright (c) 2009 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 "chrome/common/zip.h" |
| |
| #include "base/file_util.h" |
| #include "base/logging.h" |
| #include "base/string_split.h" |
| #include "base/string_util.h" |
| #include "base/utf_string_conversions.h" |
| #include "net/base/file_stream.h" |
| #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" |
| #endif |
| |
| static const int kZipMaxPath = 256; |
| static const int kZipBufSize = 8192; |
| |
| // Extract the 'current' selected file from the zip into dest_dir. |
| // Output filename is stored in out_file. Returns true on success. |
| static bool ExtractCurrentFile(unzFile zip_file, |
| const FilePath& dest_dir) { |
| char filename_inzip[kZipMaxPath] = {0}; |
| unz_file_info file_info; |
| int err = unzGetCurrentFileInfo(zip_file, &file_info, filename_inzip, |
| sizeof(filename_inzip) - 1, NULL, 0, NULL, 0); |
| if (err != UNZ_OK) |
| return false; |
| if (filename_inzip[0] == '\0') |
| return false; |
| |
| err = unzOpenCurrentFile(zip_file); |
| if (err != UNZ_OK) |
| return false; |
| |
| FilePath::StringType filename; |
| std::vector<FilePath::StringType> filename_parts; |
| #if defined(OS_WIN) |
| filename = UTF8ToWide(filename_inzip); |
| #elif defined(OS_POSIX) |
| filename = filename_inzip; |
| #endif |
| |
| // Check the filename here for directory traversal issues. In the name of |
| // simplicity and security, we might reject a valid filename such as "a..b". |
| if (filename.find(FILE_PATH_LITERAL("..")) != FilePath::StringType::npos) |
| return false; |
| |
| base::SplitString(filename, '/', &filename_parts); |
| |
| FilePath dest_file(dest_dir); |
| std::vector<FilePath::StringType>::iterator iter; |
| for (iter = filename_parts.begin(); iter != filename_parts.end(); ++iter) |
| dest_file = dest_file.Append(*iter); |
| |
| // If this is a directory, just create it and return. |
| if (filename_inzip[strlen(filename_inzip) - 1] == '/') { |
| if (!file_util::CreateDirectory(dest_file)) |
| return false; |
| return true; |
| } |
| |
| // We can't rely on parent directory entries being specified in the zip, so we |
| // make sure they are created. |
| FilePath dir = dest_file.DirName(); |
| if (!file_util::CreateDirectory(dir)) |
| return false; |
| |
| net::FileStream stream; |
| int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE; |
| if (stream.Open(dest_file, flags) != 0) |
| return false; |
| |
| bool ret = true; |
| int num_bytes = 0; |
| char buf[kZipBufSize]; |
| do { |
| num_bytes = unzReadCurrentFile(zip_file, buf, kZipBufSize); |
| if (num_bytes < 0) { |
| // If num_bytes < 0, then it's a specific UNZ_* error code. |
| // While we're not currently handling these codes specifically, save |
| // it away in case we want to in the future. |
| err = num_bytes; |
| break; |
| } |
| if (num_bytes > 0) { |
| if (num_bytes != stream.Write(buf, num_bytes, NULL)) { |
| ret = false; |
| break; |
| } |
| } |
| } while (num_bytes > 0); |
| |
| stream.Close(); |
| if (err == UNZ_OK) |
| err = unzCloseCurrentFile(zip_file); |
| else |
| unzCloseCurrentFile(zip_file); // Don't lose the original error code. |
| if (err != UNZ_OK) |
| ret = false; |
| return ret; |
| } |
| |
| #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. |
| static 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; |
| } |
| |
| std::wstring filename_wstr = UTF8ToWide(filename); |
| if ((filename != NULL) && (desired_access != 0)) { |
| file = CreateFile(filename_wstr.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 |
| |
| bool Unzip(const FilePath& src_file, const FilePath& dest_dir) { |
| #if defined(OS_WIN) |
| zlib_filefunc_def zip_funcs; |
| fill_win32_filefunc(&zip_funcs); |
| zip_funcs.zopen_file = ZipOpenFunc; |
| #endif |
| |
| #if defined(OS_POSIX) |
| std::string src_file_str = src_file.value(); |
| unzFile zip_file = unzOpen(src_file_str.c_str()); |
| #elif defined(OS_WIN) |
| std::string src_file_str = WideToUTF8(src_file.value()); |
| unzFile zip_file = unzOpen2(src_file_str.c_str(), &zip_funcs); |
| #endif |
| if (!zip_file) { |
| LOG(WARNING) << "couldn't create file " << src_file_str; |
| return false; |
| } |
| unz_global_info zip_info; |
| int err; |
| err = unzGetGlobalInfo(zip_file, &zip_info); |
| if (err != UNZ_OK) { |
| LOG(WARNING) << "couldn't open zip " << src_file_str; |
| return false; |
| } |
| bool ret = true; |
| for (unsigned int i = 0; i < zip_info.number_entry; ++i) { |
| if (!ExtractCurrentFile(zip_file, dest_dir)) { |
| ret = false; |
| break; |
| } |
| |
| if (i + 1 < zip_info.number_entry) { |
| err = unzGoToNextFile(zip_file); |
| if (err != UNZ_OK) { |
| LOG(WARNING) << "error %d in unzGoToNextFile"; |
| ret = false; |
| break; |
| } |
| } |
| } |
| unzClose(zip_file); |
| return ret; |
| } |
| |
| static bool AddFileToZip(zipFile zip_file, const FilePath& src_dir) { |
| net::FileStream stream; |
| int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ; |
| if (stream.Open(src_dir, flags) != 0) { |
| LOG(ERROR) << "Could not open stream for path " |
| << src_dir.value(); |
| return false; |
| } |
| |
| int num_bytes; |
| char buf[kZipBufSize]; |
| do { |
| num_bytes = stream.Read(buf, kZipBufSize, NULL); |
| if (num_bytes > 0) { |
| if (ZIP_OK != zipWriteInFileInZip(zip_file, buf, num_bytes)) { |
| LOG(ERROR) << "Could not write data to zip for path " |
| << src_dir.value(); |
| return false; |
| } |
| } |
| } while (num_bytes > 0); |
| |
| return true; |
| } |
| |
| static bool AddEntryToZip(zipFile zip_file, const FilePath& path, |
| const FilePath& root_path) { |
| #if defined(OS_WIN) |
| std::string str_path = |
| WideToUTF8(path.value().substr(root_path.value().length() + 1)); |
| ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); |
| #else |
| std::string str_path = path.value().substr(root_path.value().length() + 1); |
| #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)) { |
| LOG(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)) { |
| LOG(ERROR) << "Could not close zip file entry " << str_path; |
| return false; |
| } |
| |
| return success; |
| } |
| |
| bool Zip(const FilePath& src_dir, const FilePath& dest_file, |
| bool include_hidden_files) { |
| DCHECK(file_util::DirectoryExists(src_dir)); |
| |
| #if defined(OS_WIN) |
| zlib_filefunc_def zip_funcs; |
| fill_win32_filefunc(&zip_funcs); |
| zip_funcs.zopen_file = ZipOpenFunc; |
| #endif |
| |
| #if defined(OS_POSIX) |
| std::string dest_file_str = dest_file.value(); |
| std::string src_dir_str = src_dir.value(); |
| zipFile zip_file = zipOpen(dest_file_str.c_str(), APPEND_STATUS_CREATE); |
| #elif defined(OS_WIN) |
| std::string dest_file_str = WideToUTF8(dest_file.value()); |
| zipFile zip_file = zipOpen2(dest_file_str.c_str(), APPEND_STATUS_CREATE, |
| NULL, // global comment |
| &zip_funcs); |
| #endif |
| |
| if (!zip_file) { |
| LOG(WARNING) << "couldn't create file " << dest_file_str; |
| return false; |
| } |
| |
| bool success = true; |
| file_util::FileEnumerator file_enumerator( |
| src_dir, true, // recursive |
| static_cast<file_util::FileEnumerator::FILE_TYPE>( |
| file_util::FileEnumerator::FILES | |
| file_util::FileEnumerator::DIRECTORIES)); |
| for (FilePath path = file_enumerator.Next(); !path.value().empty(); |
| path = file_enumerator.Next()) { |
| if (!include_hidden_files && path.BaseName().value()[0] == '.') |
| continue; |
| |
| if (!AddEntryToZip(zip_file, path, src_dir)) { |
| success = false; |
| return false; |
| } |
| } |
| |
| if (ZIP_OK != zipClose(zip_file, NULL)) { // global comment |
| LOG(ERROR) << "Error closing zip file " << dest_file_str; |
| return false; |
| } |
| |
| return success; |
| } |