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