xref: /aosp_15_r20/external/zlib/google/zip_writer.cc (revision 86ee64e75fa5f8bce2c8c356138035642429cd05)
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