1 // Copyright 2012 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.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/files/file.h"
11 #include "base/files/file_enumerator.h"
12 #include "base/files/file_util.h"
13 #include "base/functional/bind.h"
14 #include "base/logging.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/strings/string_util.h"
17 #include "build/build_config.h"
18 #include "third_party/zlib/google/redact.h"
19 #include "third_party/zlib/google/zip_internal.h"
20 #include "third_party/zlib/google/zip_reader.h"
21 #include "third_party/zlib/google/zip_writer.h"
22
23 namespace zip {
24 namespace {
25
IsHiddenFile(const base::FilePath & file_path)26 bool IsHiddenFile(const base::FilePath& file_path) {
27 return file_path.BaseName().value()[0] == '.';
28 }
29
30 // Creates a directory at |extract_dir|/|entry_path|, including any parents.
CreateDirectory(const base::FilePath & extract_dir,const base::FilePath & entry_path)31 bool CreateDirectory(const base::FilePath& extract_dir,
32 const base::FilePath& entry_path) {
33 const base::FilePath dir = extract_dir.Append(entry_path);
34 const bool ok = base::CreateDirectory(dir);
35 PLOG_IF(ERROR, !ok) << "Cannot create directory " << Redact(dir);
36 return ok;
37 }
38
39 // Creates a WriterDelegate that can write a file at |extract_dir|/|entry_path|.
CreateFilePathWriterDelegate(const base::FilePath & extract_dir,const base::FilePath & entry_path)40 std::unique_ptr<WriterDelegate> CreateFilePathWriterDelegate(
41 const base::FilePath& extract_dir,
42 const base::FilePath& entry_path) {
43 return std::make_unique<FilePathWriterDelegate>(
44 extract_dir.Append(entry_path));
45 }
46
47 class DirectFileAccessor : public FileAccessor {
48 public:
DirectFileAccessor(base::FilePath src_dir)49 explicit DirectFileAccessor(base::FilePath src_dir)
50 : src_dir_(std::move(src_dir)) {}
51
52 ~DirectFileAccessor() override = default;
53
Open(const Paths paths,std::vector<base::File> * const files)54 bool Open(const Paths paths, std::vector<base::File>* const files) override {
55 DCHECK(files);
56 files->reserve(files->size() + paths.size());
57
58 for (const base::FilePath& path : paths) {
59 DCHECK(!path.IsAbsolute());
60 const base::FilePath absolute_path = src_dir_.Append(path);
61 if (base::DirectoryExists(absolute_path)) {
62 files->emplace_back();
63 LOG(ERROR) << "Cannot open " << Redact(path) << ": It is a directory";
64 } else {
65 const base::File& file = files->emplace_back(
66 absolute_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
67 LOG_IF(ERROR, !file.IsValid())
68 << "Cannot open " << Redact(path) << ": "
69 << base::File::ErrorToString(file.error_details());
70 }
71 }
72
73 return true;
74 }
75
List(const base::FilePath & path,std::vector<base::FilePath> * const files,std::vector<base::FilePath> * const subdirs)76 bool List(const base::FilePath& path,
77 std::vector<base::FilePath>* const files,
78 std::vector<base::FilePath>* const subdirs) override {
79 DCHECK(!path.IsAbsolute());
80 DCHECK(files);
81 DCHECK(subdirs);
82
83 base::FileEnumerator file_enumerator(
84 src_dir_.Append(path), false /* recursive */,
85 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
86
87 while (!file_enumerator.Next().empty()) {
88 const base::FileEnumerator::FileInfo info = file_enumerator.GetInfo();
89 (info.IsDirectory() ? subdirs : files)
90 ->push_back(path.Append(info.GetName()));
91 }
92
93 return true;
94 }
95
GetInfo(const base::FilePath & path,Info * const info)96 bool GetInfo(const base::FilePath& path, Info* const info) override {
97 DCHECK(!path.IsAbsolute());
98 DCHECK(info);
99
100 base::File::Info file_info;
101 if (!base::GetFileInfo(src_dir_.Append(path), &file_info)) {
102 PLOG(ERROR) << "Cannot get info of " << Redact(path);
103 return false;
104 }
105
106 info->is_directory = file_info.is_directory;
107 info->last_modified = file_info.last_modified;
108
109 return true;
110 }
111
112 private:
113 const base::FilePath src_dir_;
114 };
115
116 } // namespace
117
operator <<(std::ostream & out,const Progress & progress)118 std::ostream& operator<<(std::ostream& out, const Progress& progress) {
119 return out << progress.bytes << " bytes, " << progress.files << " files, "
120 << progress.directories << " dirs, " << progress.errors
121 << " errors";
122 }
123
Zip(const ZipParams & params)124 bool Zip(const ZipParams& params) {
125 DirectFileAccessor default_accessor(params.src_dir);
126 FileAccessor* const file_accessor = params.file_accessor ?: &default_accessor;
127
128 std::unique_ptr<internal::ZipWriter> zip_writer;
129
130 #if defined(OS_POSIX) || defined(OS_FUCHSIA)
131 if (params.dest_fd != base::kInvalidPlatformFile) {
132 DCHECK(params.dest_file.empty());
133 zip_writer =
134 internal::ZipWriter::CreateWithFd(params.dest_fd, file_accessor);
135 if (!zip_writer)
136 return false;
137 }
138 #endif
139
140 if (!zip_writer) {
141 zip_writer = internal::ZipWriter::Create(params.dest_file, file_accessor);
142 if (!zip_writer)
143 return false;
144 }
145
146 zip_writer->SetProgressCallback(params.progress_callback,
147 params.progress_period);
148 zip_writer->SetRecursive(params.recursive);
149 zip_writer->ContinueOnError(params.continue_on_error);
150
151 if (!params.include_hidden_files || params.filter_callback)
152 zip_writer->SetFilterCallback(base::BindRepeating(
153 [](const ZipParams* const params, const base::FilePath& path) -> bool {
154 return (params->include_hidden_files || !IsHiddenFile(path)) &&
155 (!params->filter_callback ||
156 params->filter_callback.Run(params->src_dir.Append(path)));
157 },
158 ¶ms));
159
160 if (params.src_files.empty()) {
161 // No source items are specified. Zip the entire source directory.
162 zip_writer->SetRecursive(true);
163 if (!zip_writer->AddDirectoryContents(base::FilePath()))
164 return false;
165 } else {
166 // Only zip the specified source items.
167 if (!zip_writer->AddMixedEntries(params.src_files))
168 return false;
169 }
170
171 return zip_writer->Close();
172 }
173
Unzip(const base::FilePath & src_file,const base::FilePath & dest_dir,UnzipOptions options)174 bool Unzip(const base::FilePath& src_file,
175 const base::FilePath& dest_dir,
176 UnzipOptions options) {
177 base::File file(src_file, base::File::FLAG_OPEN | base::File::FLAG_READ);
178 if (!file.IsValid()) {
179 PLOG(ERROR) << "Cannot open " << Redact(src_file) << ": "
180 << base::File::ErrorToString(file.error_details());
181 return false;
182 }
183
184 DLOG_IF(WARNING, !base::IsDirectoryEmpty(dest_dir))
185 << "ZIP extraction directory is not empty: " << dest_dir;
186
187 return Unzip(file.GetPlatformFile(),
188 base::BindRepeating(&CreateFilePathWriterDelegate, dest_dir),
189 base::BindRepeating(&CreateDirectory, dest_dir),
190 std::move(options));
191 }
192
Unzip(const base::PlatformFile & src_file,WriterFactory writer_factory,DirectoryCreator directory_creator,UnzipOptions options)193 bool Unzip(const base::PlatformFile& src_file,
194 WriterFactory writer_factory,
195 DirectoryCreator directory_creator,
196 UnzipOptions options) {
197 ZipReader reader;
198 reader.SetEncoding(std::move(options.encoding));
199 reader.SetPassword(std::move(options.password));
200
201 if (!reader.OpenFromPlatformFile(src_file)) {
202 LOG(ERROR) << "Cannot open ZIP from file handle " << src_file;
203 return false;
204 }
205
206 while (const ZipReader::Entry* const entry = reader.Next()) {
207 if (entry->is_unsafe) {
208 LOG(ERROR) << "Found unsafe entry " << Redact(entry->path) << " in ZIP";
209 if (!options.continue_on_error)
210 return false;
211 continue;
212 }
213
214 if (options.filter && !options.filter.Run(entry->path)) {
215 VLOG(1) << "Skipped ZIP entry " << Redact(entry->path);
216 continue;
217 }
218
219 if (entry->is_directory) {
220 // It's a directory.
221 if (!directory_creator.Run(entry->path)) {
222 LOG(ERROR) << "Cannot create directory " << Redact(entry->path);
223 if (!options.continue_on_error)
224 return false;
225 }
226
227 continue;
228 }
229
230 // It's a file.
231 std::unique_ptr<WriterDelegate> writer = writer_factory.Run(entry->path);
232 if (!writer ||
233 (options.progress ? !reader.ExtractCurrentEntryWithListener(
234 writer.get(), options.progress)
235 : !reader.ExtractCurrentEntry(writer.get()))) {
236 LOG(ERROR) << "Cannot extract file " << Redact(entry->path)
237 << " from ZIP";
238 if (!options.continue_on_error)
239 return false;
240 }
241 }
242
243 return reader.ok();
244 }
245
ZipWithFilterCallback(const base::FilePath & src_dir,const base::FilePath & dest_file,FilterCallback filter)246 bool ZipWithFilterCallback(const base::FilePath& src_dir,
247 const base::FilePath& dest_file,
248 FilterCallback filter) {
249 DCHECK(base::DirectoryExists(src_dir));
250 return Zip({.src_dir = src_dir,
251 .dest_file = dest_file,
252 .filter_callback = std::move(filter)});
253 }
254
Zip(const base::FilePath & src_dir,const base::FilePath & dest_file,bool include_hidden_files)255 bool Zip(const base::FilePath& src_dir,
256 const base::FilePath& dest_file,
257 bool include_hidden_files) {
258 return Zip({.src_dir = src_dir,
259 .dest_file = dest_file,
260 .include_hidden_files = include_hidden_files});
261 }
262
263 #if defined(OS_POSIX) || defined(OS_FUCHSIA)
ZipFiles(const base::FilePath & src_dir,Paths src_relative_paths,int dest_fd)264 bool ZipFiles(const base::FilePath& src_dir,
265 Paths src_relative_paths,
266 int dest_fd) {
267 DCHECK(base::DirectoryExists(src_dir));
268 return Zip({.src_dir = src_dir,
269 .dest_fd = dest_fd,
270 .src_files = src_relative_paths});
271 }
272 #endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
273
274 } // namespace zip
275