| // 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 <algorithm> |
| |
| #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/redact.h" |
| #include "third_party/zlib/google/zip_internal.h" |
| |
| namespace zip { |
| namespace internal { |
| |
| 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 |
| |
| Compression compression = kDeflated; |
| |
| if (is_directory) { |
| str_path += "/"; |
| } else { |
| compression = GetCompressionMethod(path); |
| } |
| |
| return zip::internal::ZipOpenNewFileInZip(zip_file_, str_path, last_modified, |
| compression); |
| } |
| |
| 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) || !info.is_directory) { |
| LOG(ERROR) << "Not a directory: " << Redact(path); |
| progress_.errors++; |
| return continue_on_error_; |
| } |
| |
| 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) || defined(OS_FUCHSIA) |
| // static |
| std::unique_ptr<ZipWriter> 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<ZipWriter>(new ZipWriter(zip_file, file_accessor)); |
| } |
| #endif |
| |
| // static |
| std::unique_ptr<ZipWriter> 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<ZipWriter>(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<const base::FilePath*> directories; |
| |
| // Declared outside loop to reuse internal buffer. |
| std::vector<base::File> 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<size_t>(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<base::File> files; |
| |
| while (!paths.empty()) { |
| // Work with chunks of 50 paths at most. |
| const size_t n = std::min<size_t>(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); |
| progress_.errors++; |
| |
| if (continue_on_error_) |
| continue; |
| |
| 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<base::FilePath> files, subdirs; |
| |
| if (!file_accessor_->List(path, &files, &subdirs)) { |
| progress_.errors++; |
| return continue_on_error_; |
| } |
| |
| Filter(&files); |
| Filter(&subdirs); |
| |
| if (!AddFileEntries(files)) |
| return false; |
| |
| return AddDirectoryEntries(subdirs); |
| } |
| |
| void ZipWriter::Filter(std::vector<base::FilePath>* 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 |