blob: 1a43196e99d5afb773b2fa03089dff39eeb83eb8 [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"
François Degros923f5eb2022-03-17 05:46:45 +000013#include "base/files/file_util.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000014#include "base/logging.h"
Joshua Pawlickie31b5032018-02-06 20:24:51 +000015#include "base/memory/ptr_util.h"
avi@chromium.orga9a93952013-06-11 08:04:16 +000016#include "base/strings/string_util.h"
aviaa969482015-12-27 13:36:49 -080017#include "build/build_config.h"
François Degrose4664462022-03-14 06:34:38 +000018#include "third_party/zlib/google/redact.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000019#include "third_party/zlib/google/zip_internal.h"
20#include "third_party/zlib/google/zip_reader.h"
Jay Civelli945bc9f2017-10-18 05:30:37 +000021#include "third_party/zlib/google/zip_writer.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000022
Jay Civelli4f9e8e82017-10-13 16:38:08 +000023namespace zip {
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000024namespace {
25
Jay Civelli4f9e8e82017-10-13 16:38:08 +000026bool IsHiddenFile(const base::FilePath& file_path) {
27 return file_path.BaseName().value()[0] == '.';
28}
29
Joshua Pawlickie31b5032018-02-06 20:24:51 +000030// Creates a directory at |extract_dir|/|entry_path|, including any parents.
31bool CreateDirectory(const base::FilePath& extract_dir,
32 const base::FilePath& entry_path) {
François Degrosfbff51a2022-03-16 23:42:24 +000033 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;
Joshua Pawlickie31b5032018-02-06 20:24:51 +000037}
38
39// Creates a WriterDelegate that can write a file at |extract_dir|/|entry_path|.
40std::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
Jay Civelli4f9e8e82017-10-13 16:38:08 +000047class DirectFileAccessor : public FileAccessor {
48 public:
François Degros4d0fedc2021-05-25 01:58:46 +000049 explicit DirectFileAccessor(base::FilePath src_dir)
50 : src_dir_(std::move(src_dir)) {}
51
Jay Civelli4f9e8e82017-10-13 16:38:08 +000052 ~DirectFileAccessor() override = default;
53
François Degros4d0fedc2021-05-25 01:58:46 +000054 bool Open(const Paths paths, std::vector<base::File>* const files) override {
55 DCHECK(files);
56 files->reserve(files->size() + paths.size());
François Degros5b8d4332021-05-18 05:56:20 +000057
François Degros4d0fedc2021-05-25 01:58:46 +000058 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();
François Degrose4664462022-03-14 06:34:38 +000063 LOG(ERROR) << "Cannot open " << Redact(path) << ": It is a directory";
François Degros4d0fedc2021-05-25 01:58:46 +000064 } else {
François Degrose4664462022-03-14 06:34:38 +000065 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());
Jay Civelli945bc9f2017-10-18 05:30:37 +000070 }
Jay Civelli945bc9f2017-10-18 05:30:37 +000071 }
François Degros5b8d4332021-05-18 05:56:20 +000072
François Degros4d0fedc2021-05-25 01:58:46 +000073 return true;
Jay Civelli4f9e8e82017-10-13 16:38:08 +000074 }
75
François Degros4d0fedc2021-05-25 01:58:46 +000076 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);
François Degros5b8d4332021-05-18 05:56:20 +000082
Jay Civelli4f9e8e82017-10-13 16:38:08 +000083 base::FileEnumerator file_enumerator(
François Degros4d0fedc2021-05-25 01:58:46 +000084 src_dir_.Append(path), false /* recursive */,
Jay Civelli4f9e8e82017-10-13 16:38:08 +000085 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
François Degros4d0fedc2021-05-25 01:58:46 +000086
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()));
Jay Civelli4f9e8e82017-10-13 16:38:08 +000091 }
François Degros5b8d4332021-05-18 05:56:20 +000092
François Degros4d0fedc2021-05-25 01:58:46 +000093 return true;
Jay Civelli4f9e8e82017-10-13 16:38:08 +000094 }
95
François Degros4d0fedc2021-05-25 01:58:46 +000096 bool GetInfo(const base::FilePath& path, Info* const info) override {
97 DCHECK(!path.IsAbsolute());
98 DCHECK(info);
99
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000100 base::File::Info file_info;
François Degros4d0fedc2021-05-25 01:58:46 +0000101 if (!base::GetFileInfo(src_dir_.Append(path), &file_info)) {
François Degrose4664462022-03-14 06:34:38 +0000102 PLOG(ERROR) << "Cannot get info of " << Redact(path);
François Degros4d0fedc2021-05-25 01:58:46 +0000103 return false;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000104 }
François Degros4d0fedc2021-05-25 01:58:46 +0000105
106 info->is_directory = file_info.is_directory;
107 info->last_modified = file_info.last_modified;
108
109 return true;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000110 }
François Degros4d0fedc2021-05-25 01:58:46 +0000111
112 private:
113 const base::FilePath src_dir_;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000114};
115
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000116} // namespace
117
François Degros2b99b372021-05-14 07:01:47 +0000118std::ostream& operator<<(std::ostream& out, const Progress& progress) {
119 return out << progress.bytes << " bytes, " << progress.files << " files, "
François Degros2231b7c2021-11-29 00:49:43 +0000120 << progress.directories << " dirs, " << progress.errors
121 << " errors";
François Degros2b99b372021-05-14 07:01:47 +0000122}
123
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000124bool Zip(const ZipParams& params) {
François Degros4d0fedc2021-05-25 01:58:46 +0000125 DirectFileAccessor default_accessor(params.src_dir);
François Degros16cd46c2021-05-14 05:07:27 +0000126 FileAccessor* const file_accessor = params.file_accessor ?: &default_accessor;
127
Jay Civelli945bc9f2017-10-18 05:30:37 +0000128 std::unique_ptr<internal::ZipWriter> zip_writer;
François Degros16cd46c2021-05-14 05:07:27 +0000129
Fabrice de Gans4823a852022-02-22 17:17:22 +0000130#if defined(OS_POSIX) || defined(OS_FUCHSIA)
François Degros16cd46c2021-05-14 05:07:27 +0000131 if (params.dest_fd != base::kInvalidPlatformFile) {
132 DCHECK(params.dest_file.empty());
François Degros4d0fedc2021-05-25 01:58:46 +0000133 zip_writer =
134 internal::ZipWriter::CreateWithFd(params.dest_fd, file_accessor);
Jay Civelli945bc9f2017-10-18 05:30:37 +0000135 if (!zip_writer)
136 return false;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000137 }
Jay Civelli945bc9f2017-10-18 05:30:37 +0000138#endif
François Degros16cd46c2021-05-14 05:07:27 +0000139
Jay Civelli945bc9f2017-10-18 05:30:37 +0000140 if (!zip_writer) {
François Degros4d0fedc2021-05-25 01:58:46 +0000141 zip_writer = internal::ZipWriter::Create(params.dest_file, file_accessor);
Jay Civelli945bc9f2017-10-18 05:30:37 +0000142 if (!zip_writer)
143 return false;
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000144 }
François Degros16cd46c2021-05-14 05:07:27 +0000145
François Degros2b99b372021-05-14 07:01:47 +0000146 zip_writer->SetProgressCallback(params.progress_callback,
147 params.progress_period);
François Degros2865d442021-05-25 03:35:57 +0000148 zip_writer->SetRecursive(params.recursive);
François Degros2231b7c2021-11-29 00:49:43 +0000149 zip_writer->ContinueOnError(params.continue_on_error);
François Degros2b99b372021-05-14 07:01:47 +0000150
François Degros2865d442021-05-25 03:35:57 +0000151 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();
Jay Civelli4f9e8e82017-10-13 16:38:08 +0000172}
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000173
François Degros9538f412022-02-15 22:08:31 +0000174bool Unzip(const base::FilePath& src_file,
175 const base::FilePath& dest_dir,
176 UnzipOptions options) {
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000177 base::File file(src_file, base::File::FLAG_OPEN | base::File::FLAG_READ);
178 if (!file.IsValid()) {
François Degrosfbff51a2022-03-16 23:42:24 +0000179 PLOG(ERROR) << "Cannot open " << Redact(src_file) << ": "
180 << base::File::ErrorToString(file.error_details());
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000181 return false;
182 }
François Degros2865d442021-05-25 03:35:57 +0000183
François Degros923f5eb2022-03-17 05:46:45 +0000184 DLOG_IF(WARNING, !base::IsDirectoryEmpty(dest_dir))
185 << "ZIP extraction directory is not empty: " << dest_dir;
186
François Degros9538f412022-02-15 22:08:31 +0000187 return Unzip(file.GetPlatformFile(),
188 base::BindRepeating(&CreateFilePathWriterDelegate, dest_dir),
189 base::BindRepeating(&CreateDirectory, dest_dir),
190 std::move(options));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000191}
192
François Degros9538f412022-02-15 22:08:31 +0000193bool Unzip(const base::PlatformFile& src_file,
194 WriterFactory writer_factory,
195 DirectoryCreator directory_creator,
196 UnzipOptions options) {
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000197 ZipReader reader;
François Degroscd494c42022-02-22 02:17:02 +0000198 reader.SetEncoding(std::move(options.encoding));
François Degros9538f412022-02-15 22:08:31 +0000199 reader.SetPassword(std::move(options.password));
200
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000201 if (!reader.OpenFromPlatformFile(src_file)) {
François Degrose4664462022-03-14 06:34:38 +0000202 LOG(ERROR) << "Cannot open ZIP from file handle " << src_file;
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000203 return false;
204 }
François Degros96f80622022-02-11 00:06:17 +0000205
206 while (const ZipReader::Entry* const entry = reader.Next()) {
207 if (entry->is_unsafe) {
François Degrose4664462022-03-14 06:34:38 +0000208 LOG(ERROR) << "Found unsafe entry " << Redact(entry->path) << " in ZIP";
François Degros8b4114d2022-03-18 11:52:59 +0000209 if (!options.continue_on_error)
210 return false;
211 continue;
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000212 }
meacerf502ca72016-09-08 20:20:20 -0700213
François Degros9538f412022-02-15 22:08:31 +0000214 if (options.filter && !options.filter.Run(entry->path)) {
François Degrose4664462022-03-14 06:34:38 +0000215 VLOG(1) << "Skipped ZIP entry " << Redact(entry->path);
François Degros96f80622022-02-11 00:06:17 +0000216 continue;
217 }
218
219 if (entry->is_directory) {
220 // It's a directory.
François Degrosfbff51a2022-03-16 23:42:24 +0000221 if (!directory_creator.Run(entry->path)) {
222 LOG(ERROR) << "Cannot create directory " << Redact(entry->path);
François Degros8b4114d2022-03-18 11:52:59 +0000223 if (!options.continue_on_error)
224 return false;
François Degrosfbff51a2022-03-16 23:42:24 +0000225 }
François Degros96f80622022-02-11 00:06:17 +0000226
227 continue;
228 }
229
230 // It's a file.
231 std::unique_ptr<WriterDelegate> writer = writer_factory.Run(entry->path);
François Degrosad402262022-02-22 04:14:12 +0000232 if (!writer || !reader.ExtractCurrentEntry(writer.get())) {
François Degrose4664462022-03-14 06:34:38 +0000233 LOG(ERROR) << "Cannot extract file " << Redact(entry->path)
234 << " from ZIP";
François Degros8b4114d2022-03-18 11:52:59 +0000235 if (!options.continue_on_error)
236 return false;
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000237 }
238 }
François Degros96f80622022-02-11 00:06:17 +0000239
240 return reader.ok();
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000241}
242
243bool ZipWithFilterCallback(const base::FilePath& src_dir,
244 const base::FilePath& dest_file,
François Degros9538f412022-02-15 22:08:31 +0000245 FilterCallback filter) {
brettw@chromium.org9a352f62013-07-15 20:18:09 +0000246 DCHECK(base::DirectoryExists(src_dir));
François Degros16cd46c2021-05-14 05:07:27 +0000247 return Zip({.src_dir = src_dir,
248 .dest_file = dest_file,
François Degros9538f412022-02-15 22:08:31 +0000249 .filter_callback = std::move(filter)});
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000250}
251
François Degros16cd46c2021-05-14 05:07:27 +0000252bool Zip(const base::FilePath& src_dir,
253 const base::FilePath& dest_file,
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000254 bool include_hidden_files) {
François Degros2865d442021-05-25 03:35:57 +0000255 return Zip({.src_dir = src_dir,
256 .dest_file = dest_file,
257 .include_hidden_files = include_hidden_files});
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000258}
259
Fabrice de Gans4823a852022-02-22 17:17:22 +0000260#if defined(OS_POSIX) || defined(OS_FUCHSIA)
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000261bool ZipFiles(const base::FilePath& src_dir,
François Degros16cd46c2021-05-14 05:07:27 +0000262 Paths src_relative_paths,
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000263 int dest_fd) {
brettw@chromium.org9a352f62013-07-15 20:18:09 +0000264 DCHECK(base::DirectoryExists(src_dir));
François Degros16cd46c2021-05-14 05:07:27 +0000265 return Zip({.src_dir = src_dir,
266 .dest_fd = dest_fd,
267 .src_files = src_relative_paths});
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000268}
Fabrice de Gans4823a852022-02-22 17:17:22 +0000269#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000270
271} // namespace zip