1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/zlib/google/zip_writer.h"
6
7 #include "base/files/file.h"
8 #include "base/logging.h"
9 #include "base/strings/string_util.h"
10 #include "third_party/zlib/google/zip_internal.h"
11
12 namespace zip {
13 namespace internal {
14
15 namespace {
16
17 // Numbers of pending entries that trigger writting them to the ZIP file.
18 constexpr size_t kMaxPendingEntriesCount = 50;
19
AddFileContentToZip(zipFile zip_file,base::File file,const base::FilePath & file_path)20 bool AddFileContentToZip(zipFile zip_file,
21 base::File file,
22 const base::FilePath& file_path) {
23 int num_bytes;
24 char buf[zip::internal::kZipBufSize];
25 do {
26 num_bytes = file.ReadAtCurrentPos(buf, zip::internal::kZipBufSize);
27
28 if (num_bytes > 0) {
29 if (zipWriteInFileInZip(zip_file, buf, num_bytes) != ZIP_OK) {
30 DLOG(ERROR) << "Could not write data to zip for path "
31 << file_path.value();
32 return false;
33 }
34 }
35 } while (num_bytes > 0);
36
37 return true;
38 }
39
OpenNewFileEntry(zipFile zip_file,const base::FilePath & path,bool is_directory,base::Time last_modified)40 bool OpenNewFileEntry(zipFile zip_file,
41 const base::FilePath& path,
42 bool is_directory,
43 base::Time last_modified) {
44 std::string str_path = path.AsUTF8Unsafe();
45 #if defined(OS_WIN)
46 base::ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/");
47 #endif
48 if (is_directory)
49 str_path += "/";
50
51 return zip::internal::ZipOpenNewFileInZip(zip_file, str_path, last_modified);
52 }
53
CloseNewFileEntry(zipFile zip_file)54 bool CloseNewFileEntry(zipFile zip_file) {
55 return zipCloseFileInZip(zip_file) == ZIP_OK;
56 }
57
AddFileEntryToZip(zipFile zip_file,const base::FilePath & path,base::File file)58 bool AddFileEntryToZip(zipFile zip_file,
59 const base::FilePath& path,
60 base::File file) {
61 base::File::Info file_info;
62 if (!file.GetInfo(&file_info))
63 return false;
64
65 if (!OpenNewFileEntry(zip_file, path, /*is_directory=*/false,
66 file_info.last_modified))
67 return false;
68
69 bool success = AddFileContentToZip(zip_file, std::move(file), path);
70 if (!CloseNewFileEntry(zip_file))
71 return false;
72
73 return success;
74 }
75
AddDirectoryEntryToZip(zipFile zip_file,const base::FilePath & path,base::Time last_modified)76 bool AddDirectoryEntryToZip(zipFile zip_file,
77 const base::FilePath& path,
78 base::Time last_modified) {
79 return OpenNewFileEntry(zip_file, path, /*is_directory=*/true,
80 last_modified) &&
81 CloseNewFileEntry(zip_file);
82 }
83
84 } // namespace
85
86 #if defined(OS_POSIX)
87 // static
CreateWithFd(int zip_file_fd,const base::FilePath & root_dir,FileAccessor * file_accessor)88 std::unique_ptr<ZipWriter> ZipWriter::CreateWithFd(
89 int zip_file_fd,
90 const base::FilePath& root_dir,
91 FileAccessor* file_accessor) {
92 DCHECK(zip_file_fd != base::kInvalidPlatformFile);
93 zipFile zip_file =
94 internal::OpenFdForZipping(zip_file_fd, APPEND_STATUS_CREATE);
95 if (!zip_file) {
96 DLOG(ERROR) << "Couldn't create ZIP file for FD " << zip_file_fd;
97 return nullptr;
98 }
99 return std::unique_ptr<ZipWriter>(
100 new ZipWriter(zip_file, root_dir, file_accessor));
101 }
102 #endif
103
104 // static
Create(const base::FilePath & zip_file_path,const base::FilePath & root_dir,FileAccessor * file_accessor)105 std::unique_ptr<ZipWriter> ZipWriter::Create(
106 const base::FilePath& zip_file_path,
107 const base::FilePath& root_dir,
108 FileAccessor* file_accessor) {
109 DCHECK(!zip_file_path.empty());
110 zipFile zip_file = internal::OpenForZipping(zip_file_path.AsUTF8Unsafe(),
111 APPEND_STATUS_CREATE);
112 if (!zip_file) {
113 DLOG(ERROR) << "Couldn't create ZIP file at path " << zip_file_path;
114 return nullptr;
115 }
116 return std::unique_ptr<ZipWriter>(
117 new ZipWriter(zip_file, root_dir, file_accessor));
118 }
119
ZipWriter(zipFile zip_file,const base::FilePath & root_dir,FileAccessor * file_accessor)120 ZipWriter::ZipWriter(zipFile zip_file,
121 const base::FilePath& root_dir,
122 FileAccessor* file_accessor)
123 : zip_file_(zip_file), root_dir_(root_dir), file_accessor_(file_accessor) {}
124
~ZipWriter()125 ZipWriter::~ZipWriter() {
126 DCHECK(pending_entries_.empty());
127 }
128
WriteEntries(const std::vector<base::FilePath> & paths)129 bool ZipWriter::WriteEntries(const std::vector<base::FilePath>& paths) {
130 return AddEntries(paths) && Close();
131 }
132
AddEntries(const std::vector<base::FilePath> & paths)133 bool ZipWriter::AddEntries(const std::vector<base::FilePath>& paths) {
134 DCHECK(zip_file_);
135 pending_entries_.insert(pending_entries_.end(), paths.begin(), paths.end());
136 return FlushEntriesIfNeeded(/*force=*/false);
137 }
138
Close()139 bool ZipWriter::Close() {
140 bool success = FlushEntriesIfNeeded(/*force=*/true) &&
141 zipClose(zip_file_, nullptr) == ZIP_OK;
142 zip_file_ = nullptr;
143 return success;
144 }
145
FlushEntriesIfNeeded(bool force)146 bool ZipWriter::FlushEntriesIfNeeded(bool force) {
147 if (pending_entries_.size() < kMaxPendingEntriesCount && !force)
148 return true;
149
150 while (pending_entries_.size() >= kMaxPendingEntriesCount ||
151 (force && !pending_entries_.empty())) {
152 size_t entry_count =
153 std::min(pending_entries_.size(), kMaxPendingEntriesCount);
154 std::vector<base::FilePath> relative_paths;
155 std::vector<base::FilePath> absolute_paths;
156 relative_paths.insert(relative_paths.begin(), pending_entries_.begin(),
157 pending_entries_.begin() + entry_count);
158 for (auto iter = pending_entries_.begin();
159 iter != pending_entries_.begin() + entry_count; ++iter) {
160 // The FileAccessor requires absolute paths.
161 absolute_paths.push_back(root_dir_.Append(*iter));
162 }
163 pending_entries_.erase(pending_entries_.begin(),
164 pending_entries_.begin() + entry_count);
165
166 // We don't know which paths are files and which ones are directories, and
167 // we want to avoid making a call to file_accessor_ for each entry. Open the
168 // files instead, invalid files are returned for directories.
169 std::vector<base::File> files =
170 file_accessor_->OpenFilesForReading(absolute_paths);
171 DCHECK_EQ(files.size(), relative_paths.size());
172 for (size_t i = 0; i < files.size(); i++) {
173 const base::FilePath& relative_path = relative_paths[i];
174 const base::FilePath& absolute_path = absolute_paths[i];
175 base::File file = std::move(files[i]);
176 if (file.IsValid()) {
177 if (!AddFileEntryToZip(zip_file_, relative_path, std::move(file))) {
178 LOG(ERROR) << "Failed to write file " << relative_path.value()
179 << " to ZIP file.";
180 return false;
181 }
182 } else {
183 // Missing file or directory case.
184 base::Time last_modified =
185 file_accessor_->GetLastModifiedTime(absolute_path);
186 if (last_modified.is_null()) {
187 LOG(ERROR) << "Failed to write entry " << relative_path.value()
188 << " to ZIP file.";
189 return false;
190 }
191 DCHECK(file_accessor_->DirectoryExists(absolute_path));
192 if (!AddDirectoryEntryToZip(zip_file_, relative_path, last_modified)) {
193 LOG(ERROR) << "Failed to write directory " << relative_path.value()
194 << " to ZIP file.";
195 return false;
196 }
197 }
198 }
199 }
200 return true;
201 }
202
203 } // namespace internal
204 } // namespace zip
205