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 "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/files/file_enumerator.h"
10 #include "base/logging.h"
11 #include "base/strings/string16.h"
12 #include "base/strings/string_util.h"
13 #include "net/base/file_stream.h"
14 #include "third_party/zlib/google/zip_internal.h"
15 #include "third_party/zlib/google/zip_reader.h"
16
17 #if defined(USE_SYSTEM_MINIZIP)
18 #include <minizip/unzip.h>
19 #include <minizip/zip.h>
20 #else
21 #include "third_party/zlib/contrib/minizip/unzip.h"
22 #include "third_party/zlib/contrib/minizip/zip.h"
23 #endif
24
25 namespace {
26
AddFileToZip(zipFile zip_file,const base::FilePath & src_dir)27 bool AddFileToZip(zipFile zip_file, const base::FilePath& src_dir) {
28 net::FileStream stream(NULL);
29 int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ;
30 if (stream.OpenSync(src_dir, flags) != 0) {
31 DLOG(ERROR) << "Could not open stream for path "
32 << src_dir.value();
33 return false;
34 }
35
36 int num_bytes;
37 char buf[zip::internal::kZipBufSize];
38 do {
39 num_bytes = stream.ReadSync(buf, zip::internal::kZipBufSize);
40 if (num_bytes > 0) {
41 if (ZIP_OK != zipWriteInFileInZip(zip_file, buf, num_bytes)) {
42 DLOG(ERROR) << "Could not write data to zip for path "
43 << src_dir.value();
44 return false;
45 }
46 }
47 } while (num_bytes > 0);
48
49 return true;
50 }
51
AddEntryToZip(zipFile zip_file,const base::FilePath & path,const base::FilePath & root_path)52 bool AddEntryToZip(zipFile zip_file, const base::FilePath& path,
53 const base::FilePath& root_path) {
54 base::FilePath relative_path;
55 bool result = root_path.AppendRelativePath(path, &relative_path);
56 DCHECK(result);
57 std::string str_path = relative_path.AsUTF8Unsafe();
58 #if defined(OS_WIN)
59 ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/");
60 #endif
61
62 bool is_directory = base::DirectoryExists(path);
63 if (is_directory)
64 str_path += "/";
65
66 // Section 4.4.4 http://www.pkware.com/documents/casestudies/APPNOTE.TXT
67 // Setting the Language encoding flag so the file is told to be in utf-8.
68 const unsigned long LANGUAGE_ENCODING_FLAG = 0x1 << 11;
69
70 if (ZIP_OK != zipOpenNewFileInZip4(
71 zip_file, //file
72 str_path.c_str(), // filename
73 NULL, // zipfi (file_info)
74 NULL, // extrafield_local,
75 0u, // size_extrafield_local
76 NULL, // extrafield_global
77 0u, // size_extrafield_global
78 NULL, // comment
79 Z_DEFLATED, // method
80 Z_DEFAULT_COMPRESSION, // level
81 0, // raw
82 -MAX_WBITS, // windowBits
83 DEF_MEM_LEVEL, // memLevel
84 Z_DEFAULT_STRATEGY, // strategy
85 NULL, //password
86 0, // crcForCrypting
87 0, // versionMadeBy
88 LANGUAGE_ENCODING_FLAG)) { // flagBase
89 DLOG(ERROR) << "Could not open zip file entry " << str_path;
90 return false;
91 }
92
93 bool success = true;
94 if (!is_directory) {
95 success = AddFileToZip(zip_file, path);
96 }
97
98 if (ZIP_OK != zipCloseFileInZip(zip_file)) {
99 DLOG(ERROR) << "Could not close zip file entry " << str_path;
100 return false;
101 }
102
103 return success;
104 }
105
ExcludeNoFilesFilter(const base::FilePath & file_path)106 bool ExcludeNoFilesFilter(const base::FilePath& file_path) {
107 return true;
108 }
109
ExcludeHiddenFilesFilter(const base::FilePath & file_path)110 bool ExcludeHiddenFilesFilter(const base::FilePath& file_path) {
111 return file_path.BaseName().value()[0] != '.';
112 }
113
114 } // namespace
115
116 namespace zip {
117
Unzip(const base::FilePath & src_file,const base::FilePath & dest_dir)118 bool Unzip(const base::FilePath& src_file, const base::FilePath& dest_dir) {
119 ZipReader reader;
120 if (!reader.Open(src_file)) {
121 DLOG(WARNING) << "Failed to open " << src_file.value();
122 return false;
123 }
124 while (reader.HasMore()) {
125 if (!reader.OpenCurrentEntryInZip()) {
126 DLOG(WARNING) << "Failed to open the current file in zip";
127 return false;
128 }
129 if (reader.current_entry_info()->is_unsafe()) {
130 DLOG(WARNING) << "Found an unsafe file in zip "
131 << reader.current_entry_info()->file_path().value();
132 return false;
133 }
134 if (!reader.ExtractCurrentEntryIntoDirectory(dest_dir)) {
135 DLOG(WARNING) << "Failed to extract "
136 << reader.current_entry_info()->file_path().value();
137 return false;
138 }
139 if (!reader.AdvanceToNextEntry()) {
140 DLOG(WARNING) << "Failed to advance to the next file";
141 return false;
142 }
143 }
144 return true;
145 }
146
ZipWithFilterCallback(const base::FilePath & src_dir,const base::FilePath & dest_file,const FilterCallback & filter_cb)147 bool ZipWithFilterCallback(const base::FilePath& src_dir,
148 const base::FilePath& dest_file,
149 const FilterCallback& filter_cb) {
150 DCHECK(base::DirectoryExists(src_dir));
151
152 zipFile zip_file = internal::OpenForZipping(dest_file.AsUTF8Unsafe(),
153 APPEND_STATUS_CREATE);
154
155 if (!zip_file) {
156 DLOG(WARNING) << "couldn't create file " << dest_file.value();
157 return false;
158 }
159
160 bool success = true;
161 base::FileEnumerator file_enumerator(src_dir, true /* recursive */,
162 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
163 for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
164 path = file_enumerator.Next()) {
165 if (!filter_cb.Run(path)) {
166 continue;
167 }
168
169 if (!AddEntryToZip(zip_file, path, src_dir)) {
170 success = false;
171 return false;
172 }
173 }
174
175 if (ZIP_OK != zipClose(zip_file, NULL)) {
176 DLOG(ERROR) << "Error closing zip file " << dest_file.value();
177 return false;
178 }
179
180 return success;
181 }
182
Zip(const base::FilePath & src_dir,const base::FilePath & dest_file,bool include_hidden_files)183 bool Zip(const base::FilePath& src_dir, const base::FilePath& dest_file,
184 bool include_hidden_files) {
185 if (include_hidden_files) {
186 return ZipWithFilterCallback(
187 src_dir, dest_file, base::Bind(&ExcludeNoFilesFilter));
188 } else {
189 return ZipWithFilterCallback(
190 src_dir, dest_file, base::Bind(&ExcludeHiddenFilesFilter));
191 }
192 }
193
194 #if defined(OS_POSIX)
ZipFiles(const base::FilePath & src_dir,const std::vector<base::FilePath> & src_relative_paths,int dest_fd)195 bool ZipFiles(const base::FilePath& src_dir,
196 const std::vector<base::FilePath>& src_relative_paths,
197 int dest_fd) {
198 DCHECK(base::DirectoryExists(src_dir));
199 zipFile zip_file = internal::OpenFdForZipping(dest_fd, APPEND_STATUS_CREATE);
200
201 if (!zip_file) {
202 DLOG(ERROR) << "couldn't create file for fd " << dest_fd;
203 return false;
204 }
205
206 bool success = true;
207 for (std::vector<base::FilePath>::const_iterator iter =
208 src_relative_paths.begin();
209 iter != src_relative_paths.end(); ++iter) {
210 const base::FilePath& path = src_dir.Append(*iter);
211 if (!AddEntryToZip(zip_file, path, src_dir)) {
212 // TODO(hshi): clean up the partial zip file when error occurs.
213 success = false;
214 break;
215 }
216 }
217
218 if (ZIP_OK != zipClose(zip_file, NULL)) {
219 DLOG(ERROR) << "Error closing zip file for fd " << dest_fd;
220 success = false;
221 }
222
223 return success;
224 }
225 #endif // defined(OS_POSIX)
226
227 } // namespace zip
228