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