Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(258)

Unified Diff: third_party/zlib/google/zip_internal.cc

Issue 179963002: New Zip::ZipFromMemory API. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: addressing comments Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698