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         &params));
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