blob: abdfcb1e7563752918b26685a8b51beceb7613d6 [file] [log] [blame]
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +00001// Copyright (c) 2012 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.h"
6
Jay Civelli4f9e8e82017-10-13 16:38:08 +00007#include <list>
joaoe@opera.comce54c1b2013-12-17 14:40:31 +00008#include <string>
9#include <vector>
10
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000011#include "base/bind.h"
hashimoto@chromium.org516d1852014-03-24 09:32:13 +000012#include "base/files/file.h"
brettw@chromium.orgb5aa4e72013-06-08 04:53:36 +000013#include "base/files/file_enumerator.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000014#include "base/logging.h"
avi@chromium.orga9a93952013-06-11 08:04:16 +000015#include "base/strings/string16.h"
16#include "base/strings/string_util.h"
aviaa969482015-12-27 13:36:49 -080017#include "build/build_config.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000018#include "third_party/zlib/google/zip_internal.h"
19#include "third_party/zlib/google/zip_reader.h"
20
21#if defined(USE_SYSTEM_MINIZIP)
22#include <minizip/unzip.h>
23#include <minizip/zip.h>
24#else
25#include "third_party/zlib/contrib/minizip/unzip.h"
26#include "third_party/zlib/contrib/minizip/zip.h"
27#endif
28
Jay Civelli4f9e8e82017-10-13 16:38:08 +000029namespace zip {
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000030namespace {
31
Jay Civelli4f9e8e82017-10-13 16:38:08 +000032bool AddFileToZip(zipFile zip_file,
33 const base::FilePath& src_dir,
34 FileAccessor* file_accessor) {
35 base::File file = file_accessor->OpenFileForReading(src_dir);
hashimoto@chromium.org516d1852014-03-24 09:32:13 +000036 if (!file.IsValid()) {
37 DLOG(ERROR) << "Could not open file for path " << src_dir.value();
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000038 return false;
39 }
40
41 int num_bytes;
42 char buf[zip::internal::kZipBufSize];
43 do {
hashimoto@chromium.org516d1852014-03-24 09:32:13 +000044 num_bytes = file.ReadAtCurrentPos(buf, zip::internal::kZipBufSize);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000045 if (num_bytes > 0) {
46 if (ZIP_OK != zipWriteInFileInZip(zip_file, buf, num_bytes)) {
47 DLOG(ERROR) << "Could not write data to zip for path "
48 << src_dir.value();
49 return false;
50 }
51 }
52 } while (num_bytes > 0);
53
54 return true;
55}
56
Jay Civelli4f9e8e82017-10-13 16:38:08 +000057bool AddEntryToZip(zipFile zip_file,
58 const base::FilePath& path,
59 const base::FilePath& root_path,
60 FileAccessor* file_accessor) {
joaoe@opera.comd62e59c2013-12-09 12:20:40 +000061 base::FilePath relative_path;
62 bool result = root_path.AppendRelativePath(path, &relative_path);
63 DCHECK(result);
64 std::string str_path = relative_path.AsUTF8Unsafe();
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000065#if defined(OS_WIN)
brettw5d237e82015-06-24 13:54:45 -070066 base::ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/");
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000067#endif
68
Jay Civelli4f9e8e82017-10-13 16:38:08 +000069 bool is_directory = file_accessor->DirectoryExists(path);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000070 if (is_directory)
71 str_path += "/";
72
Jay Civelli4f9e8e82017-10-13 16:38:08 +000073 if (!zip::internal::ZipOpenNewFileInZip(
74 zip_file, str_path, file_accessor->GetLastModifiedTime(path)))
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000075 return false;
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000076
77 bool success = true;
78 if (!is_directory) {
Jay Civelli4f9e8e82017-10-13 16:38:08 +000079 success = AddFileToZip(zip_file, path, file_accessor);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000080 }
81
82 if (ZIP_OK != zipCloseFileInZip(zip_file)) {
83 DLOG(ERROR) << "Could not close zip file entry " << str_path;
84 return false;
85 }
86
87 return success;
88}
89
Jay Civelli4f9e8e82017-10-13 16:38:08 +000090bool IsHiddenFile(const base::FilePath& file_path) {
91 return file_path.BaseName().value()[0] == '.';
92}
93
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000094bool ExcludeNoFilesFilter(const base::FilePath& file_path) {
95 return true;
96}
97
98bool ExcludeHiddenFilesFilter(const base::FilePath& file_path) {
Jay Civelli4f9e8e82017-10-13 16:38:08 +000099 return !IsHiddenFile(file_path);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000100}
101
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000102class DirectFileAccessor : public FileAccessor {
103 public:
104 ~DirectFileAccessor() override = default;
105
106 base::File OpenFileForReading(const base::FilePath& file) override {
107 return base::File(file, base::File::FLAG_OPEN | base::File::FLAG_READ);
108 }
109
110 bool DirectoryExists(const base::FilePath& file) override {
111 return base::DirectoryExists(file);
112 }
113
114 std::vector<DirectoryContentEntry> ListDirectoryContent(
115 const base::FilePath& dir) {
116 std::vector<DirectoryContentEntry> files;
117 base::FileEnumerator file_enumerator(
118 dir, false /* recursive */,
119 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
120 for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
121 path = file_enumerator.Next()) {
122 files.push_back(DirectoryContentEntry(path, base::DirectoryExists(path)));
123 }
124 return files;
125 }
126
127 base::Time GetLastModifiedTime(const base::FilePath& path) override {
128 base::File::Info file_info;
129 if (!base::GetFileInfo(path, &file_info)) {
130 LOG(ERROR) << "Failed to retrieve file modification time for "
131 << path.value();
132 }
133 return file_info.last_modified;
134 }
135};
136
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000137} // namespace
138
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000139ZipParams::ZipParams(const base::FilePath& src_dir,
140 const base::FilePath& dest_file)
141 : src_dir_(src_dir),
142 dest_file_(dest_file),
143 file_accessor_(new DirectFileAccessor()) {}
144
145#if defined(OS_POSIX)
146// Does not take ownership of |fd|.
147ZipParams::ZipParams(const base::FilePath& src_dir, int dest_fd)
148 : src_dir_(src_dir),
149 dest_fd_(dest_fd),
150 file_accessor_(new DirectFileAccessor()) {}
151#endif
152
153bool Zip(const ZipParams& params) {
154 DCHECK(params.file_accessor()->DirectoryExists(params.src_dir()));
155
156 zipFile zip_file = nullptr;
157#if defined(OS_POSIX)
158 int dest_fd = params.dest_fd();
159 if (dest_fd != base::kInvalidPlatformFile) {
160 DCHECK(params.dest_file().empty());
161 zip_file = internal::OpenFdForZipping(dest_fd, APPEND_STATUS_CREATE);
162 if (!zip_file) {
163 DLOG(ERROR) << "Couldn't create ZIP file for FD " << dest_fd;
164 return false;
165 }
166 }
167#endif
168 if (!zip_file) {
169 const base::FilePath& dest_file = params.dest_file();
170 DCHECK(!dest_file.empty());
171 zip_file = internal::OpenForZipping(dest_file.AsUTF8Unsafe(),
172 APPEND_STATUS_CREATE);
173 if (!zip_file) {
174 DLOG(WARNING) << "Couldn't create ZIP file at path " << dest_file;
175 return false;
176 }
177 }
178
179 // Using a pointer to avoid copies of a potentially large array.
180 const std::vector<base::FilePath>* files_to_add = &params.files_to_zip();
181 std::vector<base::FilePath> all_files;
182 if (files_to_add->empty()) {
183 // Include all files from the src_dir (modulo the src_dir itself and
184 // filtered and hidden files).
185
186 files_to_add = &all_files;
187 // Using a list so we can call push_back while iterating.
188 std::list<FileAccessor::DirectoryContentEntry> entries;
189 entries.push_back(FileAccessor::DirectoryContentEntry(
190 params.src_dir(), true /* is directory*/));
191 const FilterCallback& filter_callback = params.filter_callback();
192 for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
193 const base::FilePath& entry_path = iter->path;
194 if (iter != entries.begin() && // Don't filter the root dir.
195 ((!params.include_hidden_files() && IsHiddenFile(entry_path)) ||
196 (filter_callback && !filter_callback.Run(entry_path)))) {
197 continue;
198 }
199
200 if (iter != entries.begin()) { // Exclude the root dir from the ZIP file.
201 // Make the path relative for AddEntryToZip.
202 base::FilePath relative_path;
203 bool success =
204 params.src_dir().AppendRelativePath(entry_path, &relative_path);
205 DCHECK(success);
206 all_files.push_back(relative_path);
207 }
208
209 if (iter->is_directory) {
210 std::vector<FileAccessor::DirectoryContentEntry> subentries =
211 params.file_accessor()->ListDirectoryContent(entry_path);
212 entries.insert(entries.end(), subentries.begin(), subentries.end());
213 }
214 }
215 }
216
217 bool success = true;
218 for (auto iter = files_to_add->begin(); iter != files_to_add->end(); ++iter) {
219 const base::FilePath& path = params.src_dir().Append(*iter);
220 if (!AddEntryToZip(zip_file, path, params.src_dir(),
221 params.file_accessor())) {
222 // TODO(hshi): clean up the partial zip file when error occurs.
223 success = false;
224 break;
225 }
226 }
227
228 if (ZIP_OK != zipClose(zip_file, NULL)) {
229 DLOG(ERROR) << "Error closing zip file " << params.dest_file().value();
230 return false;
231 }
232
233 return success;
234}
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000235
236bool Unzip(const base::FilePath& src_file, const base::FilePath& dest_dir) {
meacerf502ca72016-09-08 20:20:20 -0700237 return UnzipWithFilterCallback(src_file, dest_dir,
meacer8086cc82016-11-22 13:25:55 -0800238 base::Bind(&ExcludeNoFilesFilter), true);
meacerf502ca72016-09-08 20:20:20 -0700239}
240
241bool UnzipWithFilterCallback(const base::FilePath& src_file,
242 const base::FilePath& dest_dir,
meacer8086cc82016-11-22 13:25:55 -0800243 const FilterCallback& filter_cb,
244 bool log_skipped_files) {
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000245 ZipReader reader;
246 if (!reader.Open(src_file)) {
247 DLOG(WARNING) << "Failed to open " << src_file.value();
248 return false;
249 }
250 while (reader.HasMore()) {
251 if (!reader.OpenCurrentEntryInZip()) {
252 DLOG(WARNING) << "Failed to open the current file in zip";
253 return false;
254 }
255 if (reader.current_entry_info()->is_unsafe()) {
256 DLOG(WARNING) << "Found an unsafe file in zip "
257 << reader.current_entry_info()->file_path().value();
258 return false;
259 }
meacerf502ca72016-09-08 20:20:20 -0700260 if (filter_cb.Run(reader.current_entry_info()->file_path())) {
261 if (!reader.ExtractCurrentEntryIntoDirectory(dest_dir)) {
262 DLOG(WARNING) << "Failed to extract "
263 << reader.current_entry_info()->file_path().value();
264 return false;
265 }
meacer8086cc82016-11-22 13:25:55 -0800266 } else if (log_skipped_files) {
meacerf502ca72016-09-08 20:20:20 -0700267 DLOG(WARNING) << "Skipped file "
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000268 << reader.current_entry_info()->file_path().value();
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000269 }
meacerf502ca72016-09-08 20:20:20 -0700270
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000271 if (!reader.AdvanceToNextEntry()) {
272 DLOG(WARNING) << "Failed to advance to the next file";
273 return false;
274 }
275 }
276 return true;
277}
278
279bool ZipWithFilterCallback(const base::FilePath& src_dir,
280 const base::FilePath& dest_file,
281 const FilterCallback& filter_cb) {
brettw@chromium.org9a352f62013-07-15 20:18:09 +0000282 DCHECK(base::DirectoryExists(src_dir));
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000283 ZipParams params(src_dir, dest_file);
284 params.set_filter_callback(filter_cb);
285 return Zip(params);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000286}
287
288bool Zip(const base::FilePath& src_dir, const base::FilePath& dest_file,
289 bool include_hidden_files) {
290 if (include_hidden_files) {
291 return ZipWithFilterCallback(
292 src_dir, dest_file, base::Bind(&ExcludeNoFilesFilter));
293 } else {
294 return ZipWithFilterCallback(
295 src_dir, dest_file, base::Bind(&ExcludeHiddenFilesFilter));
296 }
297}
298
299#if defined(OS_POSIX)
300bool ZipFiles(const base::FilePath& src_dir,
301 const std::vector<base::FilePath>& src_relative_paths,
302 int dest_fd) {
brettw@chromium.org9a352f62013-07-15 20:18:09 +0000303 DCHECK(base::DirectoryExists(src_dir));
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000304 ZipParams params(src_dir, dest_fd);
305 params.set_files_to_zip(src_relative_paths);
306 return Zip(params);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000307}
308#endif // defined(OS_POSIX)
309
310} // namespace zip