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 |