// 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 #include "base/files/file.h" #include "base/logging.h" #include "base/strings/strcat.h" #include "base/strings/string_util.h" #include "third_party/zlib/google/zip_internal.h" namespace zip { namespace internal { class Redact { public: explicit Redact(const base::FilePath& path) : path_(path) {} friend std::ostream& operator<<(std::ostream& out, const Redact&& r) { return LOG_IS_ON(INFO) ? out << "'" << r.path_ << "'" : out << "(redacted)"; } private: const base::FilePath& path_; }; bool ZipWriter::ShouldContinue() { if (!progress_callback_) return true; const base::TimeTicks now = base::TimeTicks::Now(); if (next_progress_report_time_ > now) return true; next_progress_report_time_ = now + progress_period_; if (progress_callback_.Run(progress_)) return true; LOG(ERROR) << "Cancelling ZIP creation"; return false; } bool ZipWriter::AddFileContent(const base::FilePath& path, base::File file) { char buf[zip::internal::kZipBufSize]; while (ShouldContinue()) { const int num_bytes = file.ReadAtCurrentPos(buf, zip::internal::kZipBufSize); if (num_bytes < 0) { PLOG(ERROR) << "Cannot read file " << Redact(path); return false; } if (num_bytes == 0) return true; if (zipWriteInFileInZip(zip_file_, buf, num_bytes) != ZIP_OK) { PLOG(ERROR) << "Cannot write data from file " << Redact(path) << " to ZIP"; return false; } progress_.bytes += num_bytes; } return false; } bool ZipWriter::OpenNewFileEntry(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 ZipWriter::CloseNewFileEntry() { return zipCloseFileInZip(zip_file_) == ZIP_OK; } bool ZipWriter::AddFileEntry(const base::FilePath& path, base::File file) { base::File::Info info; if (!file.GetInfo(&info)) return false; if (!OpenNewFileEntry(path, /*is_directory=*/false, info.last_modified)) return false; if (!AddFileContent(path, std::move(file))) return false; progress_.files++; return CloseNewFileEntry(); } bool ZipWriter::AddDirectoryEntry(const base::FilePath& path) { FileAccessor::Info info; if (!file_accessor_->GetInfo(path, &info)) return false; if (!info.is_directory) { LOG(ERROR) << "Not a directory: " << Redact(path); return false; } if (!OpenNewFileEntry(path, /*is_directory=*/true, info.last_modified)) return false; if (!CloseNewFileEntry()) return false; progress_.directories++; if (!ShouldContinue()) return false; if (!recursive_) return true; return AddDirectoryContents(path); } #if defined(OS_POSIX) // static std::unique_ptr ZipWriter::CreateWithFd( int zip_file_fd, 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) << "Cannot create ZIP file for FD " << zip_file_fd; return nullptr; } return std::unique_ptr(new ZipWriter(zip_file, file_accessor)); } #endif // static std::unique_ptr ZipWriter::Create( const base::FilePath& zip_file_path, FileAccessor* file_accessor) { DCHECK(!zip_file_path.empty()); zipFile zip_file = internal::OpenForZipping(zip_file_path.AsUTF8Unsafe(), APPEND_STATUS_CREATE); if (!zip_file) { PLOG(ERROR) << "Cannot create ZIP file " << Redact(zip_file_path); return nullptr; } return std::unique_ptr(new ZipWriter(zip_file, file_accessor)); } ZipWriter::ZipWriter(zipFile zip_file, FileAccessor* file_accessor) : zip_file_(zip_file), file_accessor_(file_accessor) {} ZipWriter::~ZipWriter() { if (zip_file_) zipClose(zip_file_, nullptr); } bool ZipWriter::Close() { const bool success = zipClose(zip_file_, nullptr) == ZIP_OK; zip_file_ = nullptr; // Call the progress callback one last time with the final progress status. if (progress_callback_ && !progress_callback_.Run(progress_)) { LOG(ERROR) << "Cancelling ZIP creation at the end"; return false; } return success; } bool ZipWriter::AddMixedEntries(Paths paths) { // Pointers to directory paths in |paths|. std::vector directories; // Declared outside loop to reuse internal buffer. std::vector files; // First pass. 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 // path. Try to open all of the paths as files. We'll get invalid file // descriptors for directories, and we'll process these directories in the // second pass. while (!paths.empty()) { // Work with chunks of 50 paths at most. const size_t n = std::min(paths.size(), 50); const Paths relative_paths = paths.subspan(0, n); paths = paths.subspan(n, paths.size() - n); files.clear(); if (!file_accessor_->Open(relative_paths, &files) || files.size() != n) return false; for (size_t i = 0; i < n; i++) { const base::FilePath& relative_path = relative_paths[i]; DCHECK(!relative_path.empty()); base::File& file = files[i]; if (file.IsValid()) { // It's a file. if (!AddFileEntry(relative_path, std::move(file))) return false; } else { // It's probably a directory. Remember its path for the second pass. directories.push_back(&relative_path); } } } // Second pass for directories discovered during the first pass. for (const base::FilePath* const path : directories) { DCHECK(path); if (!AddDirectoryEntry(*path)) return false; } return true; } bool ZipWriter::AddFileEntries(Paths paths) { // Declared outside loop to reuse internal buffer. std::vector files; while (!paths.empty()) { // Work with chunks of 50 paths at most. const size_t n = std::min(paths.size(), 50); const Paths relative_paths = paths.subspan(0, n); paths = paths.subspan(n, paths.size() - n); DCHECK_EQ(relative_paths.size(), n); files.clear(); if (!file_accessor_->Open(relative_paths, &files) || files.size() != n) return false; for (size_t i = 0; i < n; i++) { const base::FilePath& relative_path = relative_paths[i]; DCHECK(!relative_path.empty()); base::File& file = files[i]; if (!file.IsValid()) { LOG(ERROR) << "Cannot open " << Redact(relative_path); return false; } if (!AddFileEntry(relative_path, std::move(file))) return false; } } return true; } bool ZipWriter::AddDirectoryEntries(Paths paths) { for (const base::FilePath& path : paths) { if (!AddDirectoryEntry(path)) return false; } return true; } bool ZipWriter::AddDirectoryContents(const base::FilePath& path) { std::vector files, subdirs; if (!file_accessor_->List(path, &files, &subdirs)) return false; Filter(&files); Filter(&subdirs); if (!AddFileEntries(files)) return false; return AddDirectoryEntries(subdirs); } void ZipWriter::Filter(std::vector* const paths) { DCHECK(paths); if (!filter_callback_) return; const auto end = std::remove_if(paths->begin(), paths->end(), [this](const base::FilePath& path) { return !filter_callback_.Run(path); }); paths->erase(end, paths->end()); } } // namespace internal } // namespace zip