// Copyright 2017 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 "third_party/zlib/google/zip_writer.h" #include "base/files/file.h" #include "base/logging.h" #include "base/strings/string_util.h" #include "third_party/zlib/google/zip_internal.h" namespace zip { namespace internal { namespace { // Numbers of pending entries that trigger writting them to the ZIP file. constexpr size_t kMaxPendingEntriesCount = 50; bool AddFileContentToZip(zipFile zip_file, base::File file, const base::FilePath& file_path) { int num_bytes; char buf[zip::internal::kZipBufSize]; do { num_bytes = file.ReadAtCurrentPos(buf, zip::internal::kZipBufSize); if (num_bytes > 0) { if (zipWriteInFileInZip(zip_file, buf, num_bytes) != ZIP_OK) { DLOG(ERROR) << "Could not write data to zip for path " << file_path.value(); return false; } } } while (num_bytes > 0); return true; } bool OpenNewFileEntry(zipFile zip_file, const base::FilePath& path, bool is_directory, base::Time last_modified) { std::string str_path = path.AsUTF8Unsafe(); #if defined(OS_WIN) base::ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); #endif if (is_directory) str_path += "/"; return zip::internal::ZipOpenNewFileInZip(zip_file, str_path, last_modified); } bool CloseNewFileEntry(zipFile zip_file) { return zipCloseFileInZip(zip_file) == ZIP_OK; } bool AddFileEntryToZip(zipFile zip_file, const base::FilePath& path, base::File file) { base::File::Info file_info; if (!file.GetInfo(&file_info)) return false; if (!OpenNewFileEntry(zip_file, path, /*is_directory=*/false, file_info.last_modified)) return false; bool success = AddFileContentToZip(zip_file, std::move(file), path); if (!CloseNewFileEntry(zip_file)) return false; return success; } bool AddDirectoryEntryToZip(zipFile zip_file, const base::FilePath& path, base::Time last_modified) { return OpenNewFileEntry(zip_file, path, /*is_directory=*/true, last_modified) && CloseNewFileEntry(zip_file); } } // namespace #if defined(OS_POSIX) // static std::unique_ptr ZipWriter::CreateWithFd( int zip_file_fd, const base::FilePath& root_dir, FileAccessor* file_accessor) { DCHECK(zip_file_fd != base::kInvalidPlatformFile); zipFile zip_file = internal::OpenFdForZipping(zip_file_fd, APPEND_STATUS_CREATE); if (!zip_file) { DLOG(ERROR) << "Couldn't create ZIP file for FD " << zip_file_fd; return nullptr; } return std::unique_ptr( new ZipWriter(zip_file, root_dir, file_accessor)); } #endif // static std::unique_ptr ZipWriter::Create( const base::FilePath& zip_file_path, const base::FilePath& root_dir, FileAccessor* file_accessor) { DCHECK(!zip_file_path.empty()); zipFile zip_file = internal::OpenForZipping(zip_file_path.AsUTF8Unsafe(), APPEND_STATUS_CREATE); if (!zip_file) { DLOG(ERROR) << "Couldn't create ZIP file at path " << zip_file_path; return nullptr; } return std::unique_ptr( new ZipWriter(zip_file, root_dir, file_accessor)); } ZipWriter::ZipWriter(zipFile zip_file, const base::FilePath& root_dir, FileAccessor* file_accessor) : zip_file_(zip_file), root_dir_(root_dir), file_accessor_(file_accessor) {} ZipWriter::~ZipWriter() { DCHECK(pending_entries_.empty()); } bool ZipWriter::WriteEntries(const std::vector& paths) { return AddEntries(paths) && Close(); } bool ZipWriter::AddEntries(const std::vector& paths) { DCHECK(zip_file_); pending_entries_.insert(pending_entries_.end(), paths.begin(), paths.end()); return FlushEntriesIfNeeded(/*force=*/false); } bool ZipWriter::Close() { bool success = FlushEntriesIfNeeded(/*force=*/true) && zipClose(zip_file_, nullptr) == ZIP_OK; zip_file_ = nullptr; return success; } bool ZipWriter::FlushEntriesIfNeeded(bool force) { if (pending_entries_.size() < kMaxPendingEntriesCount && !force) return true; while (pending_entries_.size() >= kMaxPendingEntriesCount || (force && !pending_entries_.empty())) { size_t entry_count = std::min(pending_entries_.size(), kMaxPendingEntriesCount); std::vector relative_paths; std::vector absolute_paths; relative_paths.insert(relative_paths.begin(), pending_entries_.begin(), pending_entries_.begin() + entry_count); for (auto iter = pending_entries_.begin(); iter != pending_entries_.begin() + entry_count; ++iter) { // The FileAccessor requires absolute paths. absolute_paths.push_back(root_dir_.Append(*iter)); } pending_entries_.erase(pending_entries_.begin(), pending_entries_.begin() + entry_count); // We don't know which paths are files and which ones are directories, and // we want to avoid making a call to file_accessor_ for each entry. Open the // files instead, invalid files are returned for directories. std::vector files = file_accessor_->OpenFilesForReading(absolute_paths); DCHECK_EQ(files.size(), relative_paths.size()); for (size_t i = 0; i < files.size(); i++) { const base::FilePath& relative_path = relative_paths[i]; const base::FilePath& absolute_path = absolute_paths[i]; base::File file = std::move(files[i]); if (file.IsValid()) { if (!AddFileEntryToZip(zip_file_, relative_path, std::move(file))) { LOG(ERROR) << "Failed to write file " << relative_path.value() << " to ZIP file."; return false; } } else { // Missing file or directory case. base::Time last_modified = file_accessor_->GetLastModifiedTime(absolute_path); if (last_modified.is_null()) { LOG(ERROR) << "Failed to write entry " << relative_path.value() << " to ZIP file."; return false; } DCHECK(file_accessor_->DirectoryExists(absolute_path)); if (!AddDirectoryEntryToZip(zip_file_, relative_path, last_modified)) { LOG(ERROR) << "Failed to write directory " << relative_path.value() << " to ZIP file."; return false; } } } } return true; } } // namespace internal } // namespace zip