1 // Copyright 2017 The Chromium Authors
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 <algorithm>
8
9 #include "base/files/file.h"
10 #include "base/logging.h"
11 #include "base/strings/strcat.h"
12 #include "base/strings/string_util.h"
13 #include "third_party/zlib/google/redact.h"
14 #include "third_party/zlib/google/zip_internal.h"
15
16 namespace zip {
17 namespace internal {
18
ShouldContinue()19 bool ZipWriter::ShouldContinue() {
20 if (!progress_callback_)
21 return true;
22
23 const base::TimeTicks now = base::TimeTicks::Now();
24 if (next_progress_report_time_ > now)
25 return true;
26
27 next_progress_report_time_ = now + progress_period_;
28 if (progress_callback_.Run(progress_))
29 return true;
30
31 LOG(ERROR) << "Cancelling ZIP creation";
32 return false;
33 }
34
AddFileContent(const base::FilePath & path,base::File file)35 bool ZipWriter::AddFileContent(const base::FilePath& path, base::File file) {
36 char buf[zip::internal::kZipBufSize];
37
38 while (ShouldContinue()) {
39 const int num_bytes =
40 file.ReadAtCurrentPos(buf, zip::internal::kZipBufSize);
41
42 if (num_bytes < 0) {
43 PLOG(ERROR) << "Cannot read file " << Redact(path);
44 return false;
45 }
46
47 if (num_bytes == 0)
48 return true;
49
50 if (zipWriteInFileInZip(zip_file_, buf, num_bytes) != ZIP_OK) {
51 PLOG(ERROR) << "Cannot write data from file " << Redact(path)
52 << " to ZIP";
53 return false;
54 }
55
56 progress_.bytes += num_bytes;
57 }
58
59 return false;
60 }
61
OpenNewFileEntry(const base::FilePath & path,bool is_directory,base::Time last_modified)62 bool ZipWriter::OpenNewFileEntry(const base::FilePath& path,
63 bool is_directory,
64 base::Time last_modified) {
65 std::string str_path = path.AsUTF8Unsafe();
66
67 #if defined(OS_WIN)
68 base::ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/");
69 #endif
70
71 Compression compression = kDeflated;
72
73 if (is_directory) {
74 str_path += "/";
75 } else {
76 compression = GetCompressionMethod(path);
77 }
78
79 return zip::internal::ZipOpenNewFileInZip(zip_file_, str_path, last_modified,
80 compression);
81 }
82
CloseNewFileEntry()83 bool ZipWriter::CloseNewFileEntry() {
84 return zipCloseFileInZip(zip_file_) == ZIP_OK;
85 }
86
AddFileEntry(const base::FilePath & path,base::File file)87 bool ZipWriter::AddFileEntry(const base::FilePath& path, base::File file) {
88 base::File::Info info;
89 if (!file.GetInfo(&info))
90 return false;
91
92 if (!OpenNewFileEntry(path, /*is_directory=*/false, info.last_modified))
93 return false;
94
95 if (!AddFileContent(path, std::move(file)))
96 return false;
97
98 progress_.files++;
99 return CloseNewFileEntry();
100 }
101
AddDirectoryEntry(const base::FilePath & path)102 bool ZipWriter::AddDirectoryEntry(const base::FilePath& path) {
103 FileAccessor::Info info;
104 if (!file_accessor_->GetInfo(path, &info) || !info.is_directory) {
105 LOG(ERROR) << "Not a directory: " << Redact(path);
106 progress_.errors++;
107 return continue_on_error_;
108 }
109
110 if (!OpenNewFileEntry(path, /*is_directory=*/true, info.last_modified))
111 return false;
112
113 if (!CloseNewFileEntry())
114 return false;
115
116 progress_.directories++;
117 if (!ShouldContinue())
118 return false;
119
120 if (!recursive_)
121 return true;
122
123 return AddDirectoryContents(path);
124 }
125
126 #if defined(OS_POSIX) || defined(OS_FUCHSIA)
127 // static
CreateWithFd(int zip_file_fd,FileAccessor * file_accessor)128 std::unique_ptr<ZipWriter> ZipWriter::CreateWithFd(
129 int zip_file_fd,
130 FileAccessor* file_accessor) {
131 DCHECK(zip_file_fd != base::kInvalidPlatformFile);
132 zipFile zip_file =
133 internal::OpenFdForZipping(zip_file_fd, APPEND_STATUS_CREATE);
134
135 if (!zip_file) {
136 DLOG(ERROR) << "Cannot create ZIP file for FD " << zip_file_fd;
137 return nullptr;
138 }
139
140 return std::unique_ptr<ZipWriter>(new ZipWriter(zip_file, file_accessor));
141 }
142 #endif
143
144 // static
Create(const base::FilePath & zip_file_path,FileAccessor * file_accessor)145 std::unique_ptr<ZipWriter> ZipWriter::Create(
146 const base::FilePath& zip_file_path,
147 FileAccessor* file_accessor) {
148 DCHECK(!zip_file_path.empty());
149 zipFile zip_file = internal::OpenForZipping(zip_file_path.AsUTF8Unsafe(),
150 APPEND_STATUS_CREATE);
151
152 if (!zip_file) {
153 PLOG(ERROR) << "Cannot create ZIP file " << Redact(zip_file_path);
154 return nullptr;
155 }
156
157 return std::unique_ptr<ZipWriter>(new ZipWriter(zip_file, file_accessor));
158 }
159
ZipWriter(zipFile zip_file,FileAccessor * file_accessor)160 ZipWriter::ZipWriter(zipFile zip_file, FileAccessor* file_accessor)
161 : zip_file_(zip_file), file_accessor_(file_accessor) {}
162
~ZipWriter()163 ZipWriter::~ZipWriter() {
164 if (zip_file_)
165 zipClose(zip_file_, nullptr);
166 }
167
Close()168 bool ZipWriter::Close() {
169 const bool success = zipClose(zip_file_, nullptr) == ZIP_OK;
170 zip_file_ = nullptr;
171
172 // Call the progress callback one last time with the final progress status.
173 if (progress_callback_ && !progress_callback_.Run(progress_)) {
174 LOG(ERROR) << "Cancelling ZIP creation at the end";
175 return false;
176 }
177
178 return success;
179 }
180
AddMixedEntries(Paths paths)181 bool ZipWriter::AddMixedEntries(Paths paths) {
182 // Pointers to directory paths in |paths|.
183 std::vector<const base::FilePath*> directories;
184
185 // Declared outside loop to reuse internal buffer.
186 std::vector<base::File> files;
187
188 // First pass. We don't know which paths are files and which ones are
189 // directories, and we want to avoid making a call to file_accessor_ for each
190 // path. Try to open all of the paths as files. We'll get invalid file
191 // descriptors for directories, and we'll process these directories in the
192 // second pass.
193 while (!paths.empty()) {
194 // Work with chunks of 50 paths at most.
195 const size_t n = std::min<size_t>(paths.size(), 50);
196 const Paths relative_paths = paths.subspan(0, n);
197 paths = paths.subspan(n, paths.size() - n);
198
199 files.clear();
200 if (!file_accessor_->Open(relative_paths, &files) || files.size() != n)
201 return false;
202
203 for (size_t i = 0; i < n; i++) {
204 const base::FilePath& relative_path = relative_paths[i];
205 DCHECK(!relative_path.empty());
206 base::File& file = files[i];
207
208 if (file.IsValid()) {
209 // It's a file.
210 if (!AddFileEntry(relative_path, std::move(file)))
211 return false;
212 } else {
213 // It's probably a directory. Remember its path for the second pass.
214 directories.push_back(&relative_path);
215 }
216 }
217 }
218
219 // Second pass for directories discovered during the first pass.
220 for (const base::FilePath* const path : directories) {
221 DCHECK(path);
222 if (!AddDirectoryEntry(*path))
223 return false;
224 }
225
226 return true;
227 }
228
AddFileEntries(Paths paths)229 bool ZipWriter::AddFileEntries(Paths paths) {
230 // Declared outside loop to reuse internal buffer.
231 std::vector<base::File> files;
232
233 while (!paths.empty()) {
234 // Work with chunks of 50 paths at most.
235 const size_t n = std::min<size_t>(paths.size(), 50);
236 const Paths relative_paths = paths.subspan(0, n);
237 paths = paths.subspan(n, paths.size() - n);
238
239 DCHECK_EQ(relative_paths.size(), n);
240
241 files.clear();
242 if (!file_accessor_->Open(relative_paths, &files) || files.size() != n)
243 return false;
244
245 for (size_t i = 0; i < n; i++) {
246 const base::FilePath& relative_path = relative_paths[i];
247 DCHECK(!relative_path.empty());
248 base::File& file = files[i];
249
250 if (!file.IsValid()) {
251 LOG(ERROR) << "Cannot open " << Redact(relative_path);
252 progress_.errors++;
253
254 if (continue_on_error_)
255 continue;
256
257 return false;
258 }
259
260 if (!AddFileEntry(relative_path, std::move(file)))
261 return false;
262 }
263 }
264
265 return true;
266 }
267
AddDirectoryEntries(Paths paths)268 bool ZipWriter::AddDirectoryEntries(Paths paths) {
269 for (const base::FilePath& path : paths) {
270 if (!AddDirectoryEntry(path))
271 return false;
272 }
273
274 return true;
275 }
276
AddDirectoryContents(const base::FilePath & path)277 bool ZipWriter::AddDirectoryContents(const base::FilePath& path) {
278 std::vector<base::FilePath> files, subdirs;
279
280 if (!file_accessor_->List(path, &files, &subdirs)) {
281 progress_.errors++;
282 return continue_on_error_;
283 }
284
285 Filter(&files);
286 Filter(&subdirs);
287
288 if (!AddFileEntries(files))
289 return false;
290
291 return AddDirectoryEntries(subdirs);
292 }
293
Filter(std::vector<base::FilePath> * const paths)294 void ZipWriter::Filter(std::vector<base::FilePath>* const paths) {
295 DCHECK(paths);
296
297 if (!filter_callback_)
298 return;
299
300 const auto end = std::remove_if(paths->begin(), paths->end(),
301 [this](const base::FilePath& path) {
302 return !filter_callback_.Run(path);
303 });
304 paths->erase(end, paths->end());
305 }
306
307 } // namespace internal
308 } // namespace zip
309