blob: 7c9bd6190ce185f09034f4086f914a4dbbb6fa35 [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
joaoe@opera.comce54c1b2013-12-17 14:40:31 +00007#include <string>
8#include <vector>
9
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000010#include "base/bind.h"
hashimoto@chromium.org516d1852014-03-24 09:32:13 +000011#include "base/files/file.h"
brettw@chromium.orgb5aa4e72013-06-08 04:53:36 +000012#include "base/files/file_enumerator.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000013#include "base/logging.h"
Joshua Pawlickie31b5032018-02-06 20:24:51 +000014#include "base/memory/ptr_util.h"
avi@chromium.orga9a93952013-06-11 08:04:16 +000015#include "base/strings/string_util.h"
aviaa969482015-12-27 13:36:49 -080016#include "build/build_config.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000017#include "third_party/zlib/google/zip_internal.h"
18#include "third_party/zlib/google/zip_reader.h"
Jay Civelli945bc9f2017-10-18 05:30:37 +000019#include "third_party/zlib/google/zip_writer.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000020
Jay Civelli4f9e8e82017-10-13 16:38:08 +000021namespace zip {
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000022namespace {
23
Jay Civelli4f9e8e82017-10-13 16:38:08 +000024bool IsHiddenFile(const base::FilePath& file_path) {
25 return file_path.BaseName().value()[0] == '.';
26}
27
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000028bool ExcludeNoFilesFilter(const base::FilePath& file_path) {
29 return true;
30}
31
Joshua Pawlickie31b5032018-02-06 20:24:51 +000032// Creates a directory at |extract_dir|/|entry_path|, including any parents.
33bool 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|.
39std::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
Jay Civelli4f9e8e82017-10-13 16:38:08 +000046class DirectFileAccessor : public FileAccessor {
47 public:
François Degros4d0fedc2021-05-25 01:58:46 +000048 explicit DirectFileAccessor(base::FilePath src_dir)
49 : src_dir_(std::move(src_dir)) {}
50
Jay Civelli4f9e8e82017-10-13 16:38:08 +000051 ~DirectFileAccessor() override = default;
52
François Degros4d0fedc2021-05-25 01:58:46 +000053 bool Open(const Paths paths, std::vector<base::File>* const files) override {
54 DCHECK(files);
55 files->reserve(files->size() + paths.size());
François Degros5b8d4332021-05-18 05:56:20 +000056
François Degros4d0fedc2021-05-25 01:58:46 +000057 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 << "'";
Jay Civelli945bc9f2017-10-18 05:30:37 +000068 }
Jay Civelli945bc9f2017-10-18 05:30:37 +000069 }
François Degros5b8d4332021-05-18 05:56:20 +000070
François Degros4d0fedc2021-05-25 01:58:46 +000071 return true;
Jay Civelli4f9e8e82017-10-13 16:38:08 +000072 }
73
François Degros4d0fedc2021-05-25 01:58:46 +000074 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);
François Degros5b8d4332021-05-18 05:56:20 +000080
Jay Civelli4f9e8e82017-10-13 16:38:08 +000081 base::FileEnumerator file_enumerator(
François Degros4d0fedc2021-05-25 01:58:46 +000082 src_dir_.Append(path), false /* recursive */,
Jay Civelli4f9e8e82017-10-13 16:38:08 +000083 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
François Degros4d0fedc2021-05-25 01:58:46 +000084
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()));
Jay Civelli4f9e8e82017-10-13 16:38:08 +000089 }
François Degros5b8d4332021-05-18 05:56:20 +000090
François Degros4d0fedc2021-05-25 01:58:46 +000091 return true;
Jay Civelli4f9e8e82017-10-13 16:38:08 +000092 }
93
François Degros4d0fedc2021-05-25 01:58:46 +000094 bool GetInfo(const base::FilePath& path, Info* const info) override {
95 DCHECK(!path.IsAbsolute());
96 DCHECK(info);
97
Jay Civelli4f9e8e82017-10-13 16:38:08 +000098 base::File::Info file_info;
François Degros4d0fedc2021-05-25 01:58:46 +000099 if (!base::GetFileInfo(src_dir_.Append(path), &file_info)) {
100 LOG(ERROR) << "Cannot get info of '" << path << "'";
101 return false;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000102 }
François Degros4d0fedc2021-05-25 01:58:46 +0000103
104 info->is_directory = file_info.is_directory;
105 info->last_modified = file_info.last_modified;
106
107 return true;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000108 }
François Degros4d0fedc2021-05-25 01:58:46 +0000109
110 private:
111 const base::FilePath src_dir_;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000112};
113
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000114} // namespace
115
François Degros2b99b372021-05-14 07:01:47 +0000116std::ostream& operator<<(std::ostream& out, const Progress& progress) {
117 return out << progress.bytes << " bytes, " << progress.files << " files, "
118 << progress.directories << " dirs";
119}
120
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000121bool Zip(const ZipParams& params) {
François Degros4d0fedc2021-05-25 01:58:46 +0000122 DirectFileAccessor default_accessor(params.src_dir);
François Degros16cd46c2021-05-14 05:07:27 +0000123 FileAccessor* const file_accessor = params.file_accessor ?: &default_accessor;
124
Jay Civelli945bc9f2017-10-18 05:30:37 +0000125 std::unique_ptr<internal::ZipWriter> zip_writer;
François Degros16cd46c2021-05-14 05:07:27 +0000126
Jay Civelli945bc9f2017-10-18 05:30:37 +0000127#if defined(OS_POSIX)
François Degros16cd46c2021-05-14 05:07:27 +0000128 if (params.dest_fd != base::kInvalidPlatformFile) {
129 DCHECK(params.dest_file.empty());
François Degros4d0fedc2021-05-25 01:58:46 +0000130 zip_writer =
131 internal::ZipWriter::CreateWithFd(params.dest_fd, file_accessor);
Jay Civelli945bc9f2017-10-18 05:30:37 +0000132 if (!zip_writer)
133 return false;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000134 }
Jay Civelli945bc9f2017-10-18 05:30:37 +0000135#endif
François Degros16cd46c2021-05-14 05:07:27 +0000136
Jay Civelli945bc9f2017-10-18 05:30:37 +0000137 if (!zip_writer) {
François Degros4d0fedc2021-05-25 01:58:46 +0000138 zip_writer = internal::ZipWriter::Create(params.dest_file, file_accessor);
Jay Civelli945bc9f2017-10-18 05:30:37 +0000139 if (!zip_writer)
140 return false;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000141 }
François Degros16cd46c2021-05-14 05:07:27 +0000142
François Degros2b99b372021-05-14 07:01:47 +0000143 zip_writer->SetProgressCallback(params.progress_callback,
144 params.progress_period);
François Degros2865d442021-05-25 03:35:57 +0000145 zip_writer->SetRecursive(params.recursive);
François Degros2b99b372021-05-14 07:01:47 +0000146
François Degros2865d442021-05-25 03:35:57 +0000147 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();
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000168}
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000169
170bool Unzip(const base::FilePath& src_file, const base::FilePath& dest_dir) {
Sylvain Defresneddebad22019-10-01 15:40:56 +0000171 return UnzipWithFilterCallback(
172 src_file, dest_dir, base::BindRepeating(&ExcludeNoFilesFilter), true);
meacerf502ca72016-09-08 20:20:20 -0700173}
174
175bool UnzipWithFilterCallback(const base::FilePath& src_file,
176 const base::FilePath& dest_dir,
François Degros16cd46c2021-05-14 05:07:27 +0000177 FilterCallback filter_cb,
meacer8086cc82016-11-22 13:25:55 -0800178 bool log_skipped_files) {
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000179 base::File file(src_file, base::File::FLAG_OPEN | base::File::FLAG_READ);
180 if (!file.IsValid()) {
François Degros2865d442021-05-25 03:35:57 +0000181 DLOG(WARNING) << "Cannot open '" << src_file << "'";
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000182 return false;
183 }
François Degros2865d442021-05-25 03:35:57 +0000184
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000185 return UnzipWithFilterAndWriters(
186 file.GetPlatformFile(),
187 base::BindRepeating(&CreateFilePathWriterDelegate, dest_dir),
François Degros16cd46c2021-05-14 05:07:27 +0000188 base::BindRepeating(&CreateDirectory, dest_dir), std::move(filter_cb),
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000189 log_skipped_files);
190}
191
192bool UnzipWithFilterAndWriters(const base::PlatformFile& src_file,
François Degros16cd46c2021-05-14 05:07:27 +0000193 WriterFactory writer_factory,
194 DirectoryCreator directory_creator,
195 FilterCallback filter_cb,
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000196 bool log_skipped_files) {
197 ZipReader reader;
198 if (!reader.OpenFromPlatformFile(src_file)) {
François Degros2865d442021-05-25 03:35:57 +0000199 DLOG(WARNING) << "Cannot open '" << src_file << "'";
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000200 return false;
201 }
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000202 while (reader.HasMore()) {
203 if (!reader.OpenCurrentEntryInZip()) {
204 DLOG(WARNING) << "Failed to open the current file in zip";
205 return false;
206 }
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000207 const base::FilePath& entry_path = reader.current_entry_info()->file_path();
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000208 if (reader.current_entry_info()->is_unsafe()) {
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000209 DLOG(WARNING) << "Found an unsafe file in zip " << entry_path;
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000210 return false;
211 }
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000212 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 }
meacerf502ca72016-09-08 20:20:20 -0700223 }
meacer8086cc82016-11-22 13:25:55 -0800224 } else if (log_skipped_files) {
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000225 DLOG(WARNING) << "Skipped file " << entry_path;
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000226 }
meacerf502ca72016-09-08 20:20:20 -0700227
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000228 if (!reader.AdvanceToNextEntry()) {
229 DLOG(WARNING) << "Failed to advance to the next file";
230 return false;
231 }
232 }
233 return true;
234}
235
236bool ZipWithFilterCallback(const base::FilePath& src_dir,
237 const base::FilePath& dest_file,
François Degros16cd46c2021-05-14 05:07:27 +0000238 FilterCallback filter_cb) {
brettw@chromium.org9a352f62013-07-15 20:18:09 +0000239 DCHECK(base::DirectoryExists(src_dir));
François Degros16cd46c2021-05-14 05:07:27 +0000240 return Zip({.src_dir = src_dir,
241 .dest_file = dest_file,
242 .filter_callback = std::move(filter_cb)});
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000243}
244
François Degros16cd46c2021-05-14 05:07:27 +0000245bool Zip(const base::FilePath& src_dir,
246 const base::FilePath& dest_file,
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000247 bool include_hidden_files) {
François Degros2865d442021-05-25 03:35:57 +0000248 return Zip({.src_dir = src_dir,
249 .dest_file = dest_file,
250 .include_hidden_files = include_hidden_files});
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000251}
252
253#if defined(OS_POSIX)
254bool ZipFiles(const base::FilePath& src_dir,
François Degros16cd46c2021-05-14 05:07:27 +0000255 Paths src_relative_paths,
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000256 int dest_fd) {
brettw@chromium.org9a352f62013-07-15 20:18:09 +0000257 DCHECK(base::DirectoryExists(src_dir));
François Degros16cd46c2021-05-14 05:07:27 +0000258 return Zip({.src_dir = src_dir,
259 .dest_fd = dest_fd,
260 .src_files = src_relative_paths});
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000261}
262#endif // defined(OS_POSIX)
263
264} // namespace zip