Index: third_party/zlib/google/zip.cc |
diff --git a/third_party/zlib/google/zip.cc b/third_party/zlib/google/zip.cc |
index 52fc6948bb6aa38a68c5dec74284786d3faca707..a6661272938b41e75578b8eecd16e3075c2b5178 100644 |
--- a/third_party/zlib/google/zip.cc |
+++ b/third_party/zlib/google/zip.cc |
@@ -11,6 +11,7 @@ |
#include "base/file_util.h" |
#include "base/files/file_enumerator.h" |
#include "base/logging.h" |
+#include "base/memory/ref_counted_memory.h" |
#include "base/strings/string16.h" |
#include "base/strings/string_util.h" |
#include "net/base/file_stream.h" |
@@ -84,30 +85,32 @@ bool AddFileToZip(zipFile zip_file, const base::FilePath& src_dir) { |
return true; |
} |
-bool AddEntryToZip(zipFile zip_file, const base::FilePath& path, |
- const base::FilePath& root_path) { |
- base::FilePath relative_path; |
- bool result = root_path.AppendRelativePath(path, &relative_path); |
- DCHECK(result); |
- std::string str_path = relative_path.AsUTF8Unsafe(); |
-#if defined(OS_WIN) |
- ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); |
-#endif |
+bool AddMemContentsToZip(zipFile zip_file, |
+ const base::FilePath& file_path, |
+ base::RefCountedMemory* contents) { |
+ DCHECK(contents); |
+ if (contents->size() != 0 && |
+ ZIP_OK != zipWriteInFileInZip( |
+ zip_file, contents->front(), contents->size())) { |
+ DLOG(ERROR) << "Could not write data to zip for path " |
+ << file_path.value(); |
+ return false; |
+ } |
- bool is_directory = base::DirectoryExists(path); |
- if (is_directory) |
- str_path += "/"; |
+ return true; |
+} |
+bool ZipOpenNewFileWrapper(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; |
- zip_fileinfo file_info = GetFileInfoForZipping(path); |
- |
if (ZIP_OK != zipOpenNewFileInZip4( |
zip_file, // file |
str_path.c_str(), // filename |
- &file_info, // zipfi |
+ file_info, // zipfi |
NULL, // extrafield_local, |
0u, // size_extrafield_local |
NULL, // extrafield_global |
@@ -126,6 +129,26 @@ bool AddEntryToZip(zipFile zip_file, const base::FilePath& path, |
DLOG(ERROR) << "Could not open zip file entry " << str_path; |
return false; |
} |
+ return true; |
+} |
+ |
+bool AddEntryToZip(zipFile zip_file, const base::FilePath& path, |
+ const base::FilePath& root_path) { |
+ base::FilePath relative_path; |
+ bool result = root_path.AppendRelativePath(path, &relative_path); |
+ DCHECK(result); |
+ std::string str_path = relative_path.AsUTF8Unsafe(); |
+#if defined(OS_WIN) |
+ ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); |
+#endif |
+ |
+ bool is_directory = base::DirectoryExists(path); |
+ if (is_directory) |
+ str_path += "/"; |
+ |
+ zip_fileinfo file_info = GetFileInfoForZipping(path); |
+ if (!ZipOpenNewFileWrapper(zip_file, str_path, &file_info)) |
+ return false; |
bool success = true; |
if (!is_directory) { |
@@ -148,6 +171,133 @@ bool ExcludeHiddenFilesFilter(const base::FilePath& file_path) { |
return file_path.BaseName().value()[0] != '.'; |
} |
+// 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::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::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::ZipContents& contents) { |
+ for (zip::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.get() && |
+ itr->second.get()->size() != 0) { |
+ // Can be NULL (remove) or 0-byte (add). |
+ DLOG(ERROR) << "Folder must have empty contents '" << |
+ itr->first.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, |
+ base::RefCountedMemory* contents) { |
+ 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 (!ZipOpenNewFileWrapper(zip_file, str_path, &file_info)) |
+ return false; |
+ |
+ bool success = true; |
+ if (!path.EndsWithSeparator()) { |
+ // Not a folder. |
+ DCHECK(contents); |
+ success = AddMemContentsToZip(zip_file, path, contents); |
+ } |
+ |
+ 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::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::ZipContents other_contents; |
+ |
+ 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()) { |
+ scoped_refptr<base::RefCountedMemory> mem; |
+ if (!reader.ExtractCurrentEntryToRefCountedMemory( |
+ 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.get())) |
+ return false; |
+ } |
+ reader.AdvanceToNextEntry(); |
+ } |
+ reader.Close(); |
+ |
+ return true; |
+} |
+ |
} // namespace |
namespace zip { |
@@ -197,7 +347,8 @@ bool ZipWithFilterCallback(const base::FilePath& src_dir, |
bool success = true; |
base::FileEnumerator file_enumerator(src_dir, true /* recursive */, |
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); |
- for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); |
+ for (base::FilePath path = file_enumerator.Next(); |
+ !path.value().empty() && success; |
path = file_enumerator.Next()) { |
if (!filter_cb.Run(path)) { |
continue; |
@@ -205,7 +356,6 @@ bool ZipWithFilterCallback(const base::FilePath& src_dir, |
if (!AddEntryToZip(zip_file, path, src_dir)) { |
success = false; |
- return false; |
} |
} |
@@ -243,7 +393,7 @@ bool ZipFiles(const base::FilePath& src_dir, |
bool success = true; |
for (std::vector<base::FilePath>::const_iterator iter = |
src_relative_paths.begin(); |
- iter != src_relative_paths.end(); ++iter) { |
+ iter != src_relative_paths.end() && success; ++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. |
@@ -261,4 +411,94 @@ bool ZipFiles(const base::FilePath& src_dir, |
} |
#endif // defined(OS_POSIX) |
+bool ZipFromMemory(const base::FilePath& zip_path, |
+ const ZipContents& contents, |
+ bool append) { |
+ DCHECK(!zip_path.empty()); |
+ |
+ // 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; |
+ |
+ // This check saves us having to worry with invalid input after. |
+ if (!ValidateZipContents(contents)) |
+ return false; |
+ |
+ 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.get()) { |
+ success = AddEntryToZipFromMemory( |
+ zip_file, itr->first, itr->second.get()); |
+ } |
+ } |
+ |
+ 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 zip |