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