• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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