| Index: third_party/zlib/google/zip_internal.cc
|
| diff --git a/third_party/zlib/google/zip_internal.cc b/third_party/zlib/google/zip_internal.cc
|
| index a01ae8cea6f52cf9cf3e9d222e42220733b1bf63..e1ac2a00330a3fda1a5ab8eeeaad2a9042fd6018 100644
|
| --- a/third_party/zlib/google/zip_internal.cc
|
| +++ b/third_party/zlib/google/zip_internal.cc
|
| @@ -2,12 +2,14 @@
|
| // 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 "third_party/zlib/google/zip_internal.h"
|
|
|
| #include <algorithm>
|
|
|
| #include "base/logging.h"
|
| #include "base/strings/utf_string_conversions.h"
|
| +#include "base/time/time.h"
|
| +#include "third_party/zlib/google/zip_reader.h"
|
|
|
| #if defined(USE_SYSTEM_MINIZIP)
|
| #include <minizip/ioapi.h>
|
| @@ -230,6 +232,171 @@ 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.LocalExplode(&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;
|
| +}
|
| +
|
| +// Tells if there is at least one file in the |zip_path| archive which also
|
| +// exists in |contents|.
|
| +// Returns false if there was a problem opening the zip file.
|
| +// The result of whether there is an intersection between the paths in
|
| +// |zip_path| and |contents| is stored in |has_files|.
|
| +bool HasFileInZip(const base::FilePath& zip_path,
|
| + const zip::internal::ZipContents& contents,
|
| + bool* has_files) {
|
| + DCHECK(has_files);
|
| + *has_files = false;
|
| +
|
| + zip::ZipReader reader;
|
| + if (!reader.Open(zip_path)) {
|
| + DLOG(ERROR) << "Can't open zip file '" << zip_path.value();
|
| + return false;
|
| + }
|
| +
|
| + for (zip::internal::ZipContents::const_iterator itr = contents.begin();
|
| + itr != contents.end(); ++itr)
|
| + if (reader.LocateAndOpenEntry(itr->first)) {
|
| + *has_files = true;
|
| + return true;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +// Validates the contents in a zip::ZipContents to be used by ZipFromMemory:
|
| +// Makes sure that all paths are relative and safe. Verifies that folders do not
|
| +// have data (such would perhaps be a bug when utilizing the API).
|
| +bool ValidateZipContents(const zip::internal::ZipContents& contents) {
|
| + for (zip::internal::ZipContents::const_iterator itr = contents.begin();
|
| + itr != contents.end(); ++itr) {
|
| + if (itr->first.empty() ||
|
| + itr->first.IsAbsolute() ||
|
| + itr->first.ReferencesParent()) {
|
| + DLOG(ERROR) << "Invalid file path '" << itr->first.value() << "'";
|
| + return false;
|
| + }
|
| + if (itr->first.EndsWithSeparator() &&
|
| + itr->second.size() != 0) {
|
| + // Can be NULL (remove) or 0-byte (add).
|
| + DLOG(ERROR) << "Folder must have empty contents '" <<
|
| + itr->first.value() << "'";
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +// Appends contents to the currently open zip entry in |zip_file|
|
| +bool AddMemContentsToZip(zipFile zip_file,
|
| + const base::FilePath& file_path,
|
| + const void* contents,
|
| + const size_t contents_length) {
|
| + if (contents_length != 0 &&
|
| + ZIP_OK != zipWriteInFileInZip(
|
| + zip_file, contents, contents_length)) {
|
| + DCHECK(contents);
|
| + DLOG(ERROR) << "Could not write data to zip for path "
|
| + << file_path.value();
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +// Creates a new file or folder entry in |zip_file| with |path| as the file
|
| +// path and |contents| as the file contents. If |path| ends with a separator
|
| +// it is considered to be a folder entry. Returns false in case of error.
|
| +bool AddEntryToZipFromMemory(zipFile zip_file,
|
| + const base::FilePath& path,
|
| + const void* contents,
|
| + const size_t contents_length) {
|
| + DCHECK(!path.IsAbsolute());
|
| + std::string str_path = path.AsUTF8Unsafe();
|
| +#if defined(OS_WIN)
|
| + ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/");
|
| +#endif
|
| +
|
| + zip_fileinfo file_info = TimeToZipFileInfo(base::Time::Now());
|
| + if (!zip::internal::ZipOpenNewFileInZipWrapper(
|
| + zip_file, str_path, &file_info))
|
| + return false;
|
| +
|
| + bool success = true;
|
| + if (!path.EndsWithSeparator()) {
|
| + // Not a folder.
|
| + success = AddMemContentsToZip(zip_file, path, contents, contents_length);
|
| + }
|
| +
|
| + if (ZIP_OK != zipCloseFileInZip(zip_file)) {
|
| + DLOG(ERROR) << "Could not close zip file entry " << str_path;
|
| + return false;
|
| + }
|
| +
|
| + return success;
|
| +}
|
| +
|
| +// Finds all files (path and contents) present in |zip_path| which are not
|
| +// present in |contents| and adds them to |output_zip_file|
|
| +// The function returns false in case of error handling the zip file.
|
| +bool CopyZipContentsNotInMap(const base::FilePath& zip_path,
|
| + const zip::internal::ZipContents& contents,
|
| + zipFile output_zip_file) {
|
| + // Arbitrary big size, but not that big, of the maximum amount of memory this
|
| + // function can use to load a file's contents into memory.
|
| + const int64 max_allowed_file_size = 128 * 1024 * 1024;
|
| +
|
| + zip::ZipReader reader;
|
| + if (!reader.Open(zip_path)) {
|
| + DLOG(ERROR) << "Can't open zip file '" << zip_path.value();
|
| + return false;
|
| + }
|
| +
|
| + while (reader.HasMore()) {
|
| + if (!reader.OpenCurrentEntryInZip()) {
|
| + DLOG(ERROR) << "Can't open entry in zip file '" << zip_path.value();
|
| + return false;
|
| + }
|
| +
|
| + zip::ZipReader::EntryInfo* entry = reader.current_entry_info();
|
| + DCHECK(entry);
|
| + if (contents.find(entry->file_path()) == contents.end()) {
|
| + std::string mem;
|
| + if (!reader.ExtractCurrentEntryToString(
|
| + max_allowed_file_size, &mem)) {
|
| + DLOG(ERROR) << "Failed to read contents of "
|
| + << entry->file_path().value();
|
| + return false;
|
| + }
|
| + if (!AddEntryToZipFromMemory(
|
| + output_zip_file, entry->file_path(), mem.c_str(), mem.size()))
|
| + return false;
|
| + }
|
| + reader.AdvanceToNextEntry();
|
| + }
|
| + reader.Close();
|
| +
|
| + return true;
|
| +}
|
| +
|
| } // namespace
|
|
|
| namespace zip {
|
| @@ -266,7 +433,7 @@ unzFile OpenHandleForUnzipping(HANDLE zip_handle) {
|
| #endif
|
|
|
| // static
|
| -unzFile PreprareMemoryForUnzipping(const std::string& data) {
|
| +unzFile PrepareMemoryForUnzipping(const std::string& data) {
|
| if (data.empty())
|
| return NULL;
|
|
|
| @@ -312,5 +479,134 @@ zipFile OpenFdForZipping(int zip_fd, int append_flag) {
|
| }
|
| #endif
|
|
|
| +zip_fileinfo GetFileInfoForZipping(const base::FilePath& path) {
|
| + base::Time file_time;
|
| + base::File::Info file_info;
|
| + if (base::GetFileInfo(path, &file_info))
|
| + file_time = file_info.last_modified;
|
| + return TimeToZipFileInfo(file_time);
|
| +}
|
| +
|
| +bool ZipOpenNewFileInZipWrapper(zipFile zip_file,
|
| + const std::string& str_path,
|
| + const zip_fileinfo* file_info) {
|
| + // 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;
|
| +
|
| + if (ZIP_OK != zipOpenNewFileInZip4(
|
| + zip_file, // file
|
| + str_path.c_str(), // filename
|
| + file_info, // zipfi
|
| + NULL, // extrafield_local,
|
| + 0u, // size_extrafield_local
|
| + NULL, // extrafield_global
|
| + 0u, // size_extrafield_global
|
| + NULL, // comment
|
| + Z_DEFLATED, // method
|
| + Z_DEFAULT_COMPRESSION, // level
|
| + 0, // raw
|
| + -MAX_WBITS, // windowBits
|
| + DEF_MEM_LEVEL, // memLevel
|
| + Z_DEFAULT_STRATEGY, // strategy
|
| + NULL, // password
|
| + 0, // crcForCrypting
|
| + 0, // versionMadeBy
|
| + LANGUAGE_ENCODING_FLAG)) { // flagBase
|
| + DLOG(ERROR) << "Could not open zip file entry " << str_path;
|
| + return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +bool ZipFromMemory(const base::FilePath& zip_path,
|
| + const ZipContents& contents,
|
| + bool append) {
|
| +
|
| + // There is really no point in calling this with no input, and
|
| + // there are simpler ways to create a 0 byte file.
|
| + if (contents.size() == 0)
|
| + return true;
|
| +
|
| + CHECK(!zip_path.empty());
|
| + // This check saves us having to worry with invalid input after.
|
| + CHECK(ValidateZipContents(contents));
|
| +
|
| + int64 file_size = 0;
|
| + if (append &&
|
| + (!base::PathExists(zip_path) ||
|
| + (base::GetFileSize(zip_path, &file_size) && file_size < 1))) {
|
| + // If the size is 0, this might be a new temporary file.
|
| + append = false;
|
| + }
|
| +
|
| + // The path to the zip path that will actually be used to write the new data.
|
| + base::FilePath zip_path_used = zip_path;
|
| +
|
| + bool has_intersection = false;
|
| + if (append) {
|
| + // NOTE: zipOpenNewFileInZip4 is not clever enough to replace files,
|
| + // therefore this has to explicitly create a new file and reinsert the
|
| + // data we want without duplications, which is what the minizip headers
|
| + // tell to do.
|
| + if (!HasFileInZip(zip_path, contents, &has_intersection))
|
| + return false;
|
| +
|
| + if (has_intersection) {
|
| + append = false;
|
| + if (!base::CreateTemporaryFile(&zip_path_used)) {
|
| + DLOG(WARNING) << "Couldn't create temporary file for "
|
| + << zip_path.value();
|
| + return false;
|
| + }
|
| + }
|
| + }
|
| +
|
| + zipFile zip_file = internal::OpenForZipping(
|
| + zip_path_used.AsUTF8Unsafe(),
|
| + append ? APPEND_STATUS_ADDINZIP : APPEND_STATUS_CREATE);
|
| + if (!zip_file) {
|
| + DLOG(WARNING) << "Couldn't create file " << zip_path_used.value();
|
| + return false;
|
| + }
|
| +
|
| + bool success = true;
|
| +
|
| + if (has_intersection) {
|
| + // There was an intersection. There are files in the old archive with
|
| + // same paths as found in |contents| therefore the old ones need to be
|
| + // replaced.
|
| + success = CopyZipContentsNotInMap(zip_path, contents, zip_file);
|
| + }
|
| +
|
| + for (ZipContents::const_iterator itr = contents.begin();
|
| + itr != contents.end() && success;
|
| + ++itr) {
|
| + if (itr->second.data() != NULL) {
|
| + success = AddEntryToZipFromMemory(
|
| + zip_file, itr->first, itr->second.data(), itr->second.size());
|
| + }
|
| + }
|
| +
|
| + if (ZIP_OK != zipClose(zip_file, NULL)) {
|
| + DLOG(ERROR) << "Error closing zip file " << zip_path_used.value();
|
| + success = false;
|
| + }
|
| +
|
| + if (zip_path_used != zip_path) {
|
| + if (success) {
|
| + if (!base::Move(zip_path_used, zip_path)) {
|
| + base::DeleteFile(zip_path_used, false);
|
| + DLOG(ERROR) << "Error moving zip file " << zip_path_used.value();
|
| + return false;
|
| + }
|
| + } else {
|
| + base::DeleteFile(zip_path_used, false);
|
| + }
|
| + }
|
| +
|
| + return success;
|
| +}
|
| +
|
| } // namespace internal
|
| } // namespace zip
|
|
|