OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "third_party/zlib/google/zip.h" | 5 #include "third_party/zlib/google/zip.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/bind.h" | 10 #include "base/bind.h" |
11 #include "base/file_util.h" | 11 #include "base/file_util.h" |
12 #include "base/files/file_enumerator.h" | 12 #include "base/files/file_enumerator.h" |
13 #include "base/logging.h" | 13 #include "base/logging.h" |
| 14 #include "base/memory/ref_counted_memory.h" |
14 #include "base/strings/string16.h" | 15 #include "base/strings/string16.h" |
15 #include "base/strings/string_util.h" | 16 #include "base/strings/string_util.h" |
16 #include "net/base/file_stream.h" | 17 #include "net/base/file_stream.h" |
17 #include "third_party/zlib/google/zip_internal.h" | 18 #include "third_party/zlib/google/zip_internal.h" |
18 #include "third_party/zlib/google/zip_reader.h" | 19 #include "third_party/zlib/google/zip_reader.h" |
19 | 20 |
20 #if defined(USE_SYSTEM_MINIZIP) | 21 #if defined(USE_SYSTEM_MINIZIP) |
21 #include <minizip/unzip.h> | 22 #include <minizip/unzip.h> |
22 #include <minizip/zip.h> | 23 #include <minizip/zip.h> |
23 #else | 24 #else |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
77 DLOG(ERROR) << "Could not write data to zip for path " | 78 DLOG(ERROR) << "Could not write data to zip for path " |
78 << src_dir.value(); | 79 << src_dir.value(); |
79 return false; | 80 return false; |
80 } | 81 } |
81 } | 82 } |
82 } while (num_bytes > 0); | 83 } while (num_bytes > 0); |
83 | 84 |
84 return true; | 85 return true; |
85 } | 86 } |
86 | 87 |
87 bool AddEntryToZip(zipFile zip_file, const base::FilePath& path, | 88 bool AddMemContentsToZip(zipFile zip_file, |
88 const base::FilePath& root_path) { | 89 const base::FilePath& file_path, |
89 base::FilePath relative_path; | 90 base::RefCountedMemory* contents) { |
90 bool result = root_path.AppendRelativePath(path, &relative_path); | 91 DCHECK(contents); |
91 DCHECK(result); | 92 if (contents->size() != 0 && |
92 std::string str_path = relative_path.AsUTF8Unsafe(); | 93 ZIP_OK != zipWriteInFileInZip( |
93 #if defined(OS_WIN) | 94 zip_file, contents->front(), contents->size())) { |
94 ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); | 95 DLOG(ERROR) << "Could not write data to zip for path " |
95 #endif | 96 << file_path.value(); |
| 97 return false; |
| 98 } |
96 | 99 |
97 bool is_directory = base::DirectoryExists(path); | 100 return true; |
98 if (is_directory) | 101 } |
99 str_path += "/"; | |
100 | 102 |
| 103 bool ZipOpenNewFileWrapper(zipFile zip_file, |
| 104 const std::string& str_path, |
| 105 const zip_fileinfo* file_info) { |
101 // Section 4.4.4 http://www.pkware.com/documents/casestudies/APPNOTE.TXT | 106 // Section 4.4.4 http://www.pkware.com/documents/casestudies/APPNOTE.TXT |
102 // Setting the Language encoding flag so the file is told to be in utf-8. | 107 // Setting the Language encoding flag so the file is told to be in utf-8. |
103 const uLong LANGUAGE_ENCODING_FLAG = 0x1 << 11; | 108 const uLong LANGUAGE_ENCODING_FLAG = 0x1 << 11; |
104 | 109 |
105 zip_fileinfo file_info = GetFileInfoForZipping(path); | |
106 | |
107 if (ZIP_OK != zipOpenNewFileInZip4( | 110 if (ZIP_OK != zipOpenNewFileInZip4( |
108 zip_file, // file | 111 zip_file, // file |
109 str_path.c_str(), // filename | 112 str_path.c_str(), // filename |
110 &file_info, // zipfi | 113 file_info, // zipfi |
111 NULL, // extrafield_local, | 114 NULL, // extrafield_local, |
112 0u, // size_extrafield_local | 115 0u, // size_extrafield_local |
113 NULL, // extrafield_global | 116 NULL, // extrafield_global |
114 0u, // size_extrafield_global | 117 0u, // size_extrafield_global |
115 NULL, // comment | 118 NULL, // comment |
116 Z_DEFLATED, // method | 119 Z_DEFLATED, // method |
117 Z_DEFAULT_COMPRESSION, // level | 120 Z_DEFAULT_COMPRESSION, // level |
118 0, // raw | 121 0, // raw |
119 -MAX_WBITS, // windowBits | 122 -MAX_WBITS, // windowBits |
120 DEF_MEM_LEVEL, // memLevel | 123 DEF_MEM_LEVEL, // memLevel |
121 Z_DEFAULT_STRATEGY, // strategy | 124 Z_DEFAULT_STRATEGY, // strategy |
122 NULL, // password | 125 NULL, // password |
123 0, // crcForCrypting | 126 0, // crcForCrypting |
124 0, // versionMadeBy | 127 0, // versionMadeBy |
125 LANGUAGE_ENCODING_FLAG)) { // flagBase | 128 LANGUAGE_ENCODING_FLAG)) { // flagBase |
126 DLOG(ERROR) << "Could not open zip file entry " << str_path; | 129 DLOG(ERROR) << "Could not open zip file entry " << str_path; |
127 return false; | 130 return false; |
128 } | 131 } |
| 132 return true; |
| 133 } |
| 134 |
| 135 bool AddEntryToZip(zipFile zip_file, const base::FilePath& path, |
| 136 const base::FilePath& root_path) { |
| 137 base::FilePath relative_path; |
| 138 bool result = root_path.AppendRelativePath(path, &relative_path); |
| 139 DCHECK(result); |
| 140 std::string str_path = relative_path.AsUTF8Unsafe(); |
| 141 #if defined(OS_WIN) |
| 142 ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); |
| 143 #endif |
| 144 |
| 145 bool is_directory = base::DirectoryExists(path); |
| 146 if (is_directory) |
| 147 str_path += "/"; |
| 148 |
| 149 zip_fileinfo file_info = GetFileInfoForZipping(path); |
| 150 if (!ZipOpenNewFileWrapper(zip_file, str_path, &file_info)) |
| 151 return false; |
129 | 152 |
130 bool success = true; | 153 bool success = true; |
131 if (!is_directory) { | 154 if (!is_directory) { |
132 success = AddFileToZip(zip_file, path); | 155 success = AddFileToZip(zip_file, path); |
133 } | 156 } |
134 | 157 |
135 if (ZIP_OK != zipCloseFileInZip(zip_file)) { | 158 if (ZIP_OK != zipCloseFileInZip(zip_file)) { |
136 DLOG(ERROR) << "Could not close zip file entry " << str_path; | 159 DLOG(ERROR) << "Could not close zip file entry " << str_path; |
137 return false; | 160 return false; |
138 } | 161 } |
139 | 162 |
140 return success; | 163 return success; |
141 } | 164 } |
142 | 165 |
143 bool ExcludeNoFilesFilter(const base::FilePath& file_path) { | 166 bool ExcludeNoFilesFilter(const base::FilePath& file_path) { |
144 return true; | 167 return true; |
145 } | 168 } |
146 | 169 |
147 bool ExcludeHiddenFilesFilter(const base::FilePath& file_path) { | 170 bool ExcludeHiddenFilesFilter(const base::FilePath& file_path) { |
148 return file_path.BaseName().value()[0] != '.'; | 171 return file_path.BaseName().value()[0] != '.'; |
149 } | 172 } |
150 | 173 |
| 174 // Tells if there is at least one file in the |zip_path| archive which also |
| 175 // exists in |contents|. |
| 176 // Returns false if there was a problem opening the zip file. |
| 177 // The result of whether there is an intersection between the paths in |
| 178 // |zip_path| and |contents| is stored in |has_files|. |
| 179 bool HasFileInZip(const base::FilePath& zip_path, |
| 180 const zip::ZipContents& contents, |
| 181 bool* has_files) { |
| 182 DCHECK(has_files); |
| 183 *has_files = false; |
| 184 |
| 185 zip::ZipReader reader; |
| 186 if (!reader.Open(zip_path)) { |
| 187 DLOG(ERROR) << "Can't open zip file '" << zip_path.value(); |
| 188 return false; |
| 189 } |
| 190 |
| 191 for (zip::ZipContents::const_iterator itr = contents.begin(); |
| 192 itr != contents.end(); ++itr) |
| 193 if (reader.LocateAndOpenEntry(itr->first)) { |
| 194 *has_files = true; |
| 195 return true; |
| 196 } |
| 197 |
| 198 return true; |
| 199 } |
| 200 |
| 201 // Validates the contents in a zip::ZipContents to be used by ZipFromMemory: |
| 202 // Makes sure that all paths are relative and safe. Verifies that folders do not |
| 203 // have data (such would perhaps be a bug when utilizing the API). |
| 204 bool ValidateZipContents(const zip::ZipContents& contents) { |
| 205 for (zip::ZipContents::const_iterator itr = contents.begin(); |
| 206 itr != contents.end(); ++itr) { |
| 207 if (itr->first.empty() || |
| 208 itr->first.IsAbsolute() || |
| 209 itr->first.ReferencesParent()) { |
| 210 DLOG(ERROR) << "Invalid file path '" << itr->first.value() << "'"; |
| 211 return false; |
| 212 } |
| 213 if (itr->first.EndsWithSeparator() && |
| 214 itr->second.get() && |
| 215 itr->second.get()->size() != 0) { |
| 216 // Can be NULL (remove) or 0-byte (add). |
| 217 DLOG(ERROR) << "Folder must have empty contents '" << |
| 218 itr->first.value() << "'"; |
| 219 return false; |
| 220 } |
| 221 } |
| 222 return true; |
| 223 } |
| 224 |
| 225 // Creates a new file or folder entry in |zip_file| with |path| as the file |
| 226 // path and |contents| as the file contents. If |path| ends with a separator |
| 227 // it is considered to be a folder entry. Returns false in case of error. |
| 228 bool AddEntryToZipFromMemory(zipFile zip_file, |
| 229 const base::FilePath& path, |
| 230 base::RefCountedMemory* contents) { |
| 231 DCHECK(!path.IsAbsolute()); |
| 232 std::string str_path = path.AsUTF8Unsafe(); |
| 233 #if defined(OS_WIN) |
| 234 ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); |
| 235 #endif |
| 236 |
| 237 zip_fileinfo file_info = TimeToZipFileInfo(base::Time::Now()); |
| 238 if (!ZipOpenNewFileWrapper(zip_file, str_path, &file_info)) |
| 239 return false; |
| 240 |
| 241 bool success = true; |
| 242 if (!path.EndsWithSeparator()) { |
| 243 // Not a folder. |
| 244 DCHECK(contents); |
| 245 success = AddMemContentsToZip(zip_file, path, contents); |
| 246 } |
| 247 |
| 248 if (ZIP_OK != zipCloseFileInZip(zip_file)) { |
| 249 DLOG(ERROR) << "Could not close zip file entry " << str_path; |
| 250 return false; |
| 251 } |
| 252 |
| 253 return success; |
| 254 } |
| 255 |
| 256 // Finds all files (path and contents) present in |zip_path| which are not |
| 257 // present in |contents| and adds them to |output_zip_file| |
| 258 // The function returns false in case of error handling the zip file. |
| 259 bool CopyZipContentsNotInMap(const base::FilePath& zip_path, |
| 260 const zip::ZipContents& contents, |
| 261 zipFile output_zip_file) { |
| 262 // Arbitrary big size, but not that big, of the maximum amount of memory this |
| 263 // function can use to load a file's contents into memory. |
| 264 const int64 max_allowed_file_size = 128 * 1024 * 1024; |
| 265 |
| 266 zip::ZipContents other_contents; |
| 267 |
| 268 zip::ZipReader reader; |
| 269 if (!reader.Open(zip_path)) { |
| 270 DLOG(ERROR) << "Can't open zip file '" << zip_path.value(); |
| 271 return false; |
| 272 } |
| 273 |
| 274 while (reader.HasMore()) { |
| 275 if (!reader.OpenCurrentEntryInZip()) { |
| 276 DLOG(ERROR) << "Can't open entry in zip file '" << zip_path.value(); |
| 277 return false; |
| 278 } |
| 279 |
| 280 zip::ZipReader::EntryInfo* entry = reader.current_entry_info(); |
| 281 DCHECK(entry); |
| 282 if (contents.find(entry->file_path()) == contents.end()) { |
| 283 scoped_refptr<base::RefCountedMemory> mem; |
| 284 if (!reader.ExtractCurrentEntryToRefCountedMemory( |
| 285 max_allowed_file_size, &mem)) { |
| 286 DLOG(ERROR) << "Failed to read contents of " |
| 287 << entry->file_path().value(); |
| 288 return false; |
| 289 } |
| 290 if (!AddEntryToZipFromMemory( |
| 291 output_zip_file, entry->file_path(), mem.get())) |
| 292 return false; |
| 293 } |
| 294 reader.AdvanceToNextEntry(); |
| 295 } |
| 296 reader.Close(); |
| 297 |
| 298 return true; |
| 299 } |
| 300 |
151 } // namespace | 301 } // namespace |
152 | 302 |
153 namespace zip { | 303 namespace zip { |
154 | 304 |
155 bool Unzip(const base::FilePath& src_file, const base::FilePath& dest_dir) { | 305 bool Unzip(const base::FilePath& src_file, const base::FilePath& dest_dir) { |
156 ZipReader reader; | 306 ZipReader reader; |
157 if (!reader.Open(src_file)) { | 307 if (!reader.Open(src_file)) { |
158 DLOG(WARNING) << "Failed to open " << src_file.value(); | 308 DLOG(WARNING) << "Failed to open " << src_file.value(); |
159 return false; | 309 return false; |
160 } | 310 } |
(...skipping 29 matching lines...) Expand all Loading... |
190 APPEND_STATUS_CREATE); | 340 APPEND_STATUS_CREATE); |
191 | 341 |
192 if (!zip_file) { | 342 if (!zip_file) { |
193 DLOG(WARNING) << "couldn't create file " << dest_file.value(); | 343 DLOG(WARNING) << "couldn't create file " << dest_file.value(); |
194 return false; | 344 return false; |
195 } | 345 } |
196 | 346 |
197 bool success = true; | 347 bool success = true; |
198 base::FileEnumerator file_enumerator(src_dir, true /* recursive */, | 348 base::FileEnumerator file_enumerator(src_dir, true /* recursive */, |
199 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); | 349 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); |
200 for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); | 350 for (base::FilePath path = file_enumerator.Next(); |
| 351 !path.value().empty() && success; |
201 path = file_enumerator.Next()) { | 352 path = file_enumerator.Next()) { |
202 if (!filter_cb.Run(path)) { | 353 if (!filter_cb.Run(path)) { |
203 continue; | 354 continue; |
204 } | 355 } |
205 | 356 |
206 if (!AddEntryToZip(zip_file, path, src_dir)) { | 357 if (!AddEntryToZip(zip_file, path, src_dir)) { |
207 success = false; | 358 success = false; |
208 return false; | |
209 } | 359 } |
210 } | 360 } |
211 | 361 |
212 if (ZIP_OK != zipClose(zip_file, NULL)) { | 362 if (ZIP_OK != zipClose(zip_file, NULL)) { |
213 DLOG(ERROR) << "Error closing zip file " << dest_file.value(); | 363 DLOG(ERROR) << "Error closing zip file " << dest_file.value(); |
214 return false; | 364 return false; |
215 } | 365 } |
216 | 366 |
217 return success; | 367 return success; |
218 } | 368 } |
(...skipping 17 matching lines...) Expand all Loading... |
236 zipFile zip_file = internal::OpenFdForZipping(dest_fd, APPEND_STATUS_CREATE); | 386 zipFile zip_file = internal::OpenFdForZipping(dest_fd, APPEND_STATUS_CREATE); |
237 | 387 |
238 if (!zip_file) { | 388 if (!zip_file) { |
239 DLOG(ERROR) << "couldn't create file for fd " << dest_fd; | 389 DLOG(ERROR) << "couldn't create file for fd " << dest_fd; |
240 return false; | 390 return false; |
241 } | 391 } |
242 | 392 |
243 bool success = true; | 393 bool success = true; |
244 for (std::vector<base::FilePath>::const_iterator iter = | 394 for (std::vector<base::FilePath>::const_iterator iter = |
245 src_relative_paths.begin(); | 395 src_relative_paths.begin(); |
246 iter != src_relative_paths.end(); ++iter) { | 396 iter != src_relative_paths.end() && success; ++iter) { |
247 const base::FilePath& path = src_dir.Append(*iter); | 397 const base::FilePath& path = src_dir.Append(*iter); |
248 if (!AddEntryToZip(zip_file, path, src_dir)) { | 398 if (!AddEntryToZip(zip_file, path, src_dir)) { |
249 // TODO(hshi): clean up the partial zip file when error occurs. | 399 // TODO(hshi): clean up the partial zip file when error occurs. |
250 success = false; | 400 success = false; |
251 break; | 401 break; |
252 } | 402 } |
253 } | 403 } |
254 | 404 |
255 if (ZIP_OK != zipClose(zip_file, NULL)) { | 405 if (ZIP_OK != zipClose(zip_file, NULL)) { |
256 DLOG(ERROR) << "Error closing zip file for fd " << dest_fd; | 406 DLOG(ERROR) << "Error closing zip file for fd " << dest_fd; |
257 success = false; | 407 success = false; |
258 } | 408 } |
259 | 409 |
260 return success; | 410 return success; |
261 } | 411 } |
262 #endif // defined(OS_POSIX) | 412 #endif // defined(OS_POSIX) |
263 | 413 |
| 414 bool ZipFromMemory(const base::FilePath& zip_path, |
| 415 const ZipContents& contents, |
| 416 bool append) { |
| 417 DCHECK(!zip_path.empty()); |
| 418 |
| 419 // There is really no point in calling this with no input, and |
| 420 // there are simpler ways to create a 0 byte file. |
| 421 if (contents.size() == 0) |
| 422 return true; |
| 423 |
| 424 // This check saves us having to worry with invalid input after. |
| 425 if (!ValidateZipContents(contents)) |
| 426 return false; |
| 427 |
| 428 int64 file_size = 0; |
| 429 if (append && |
| 430 (!base::PathExists(zip_path) || |
| 431 (base::GetFileSize(zip_path, &file_size) && file_size < 1))) { |
| 432 // If the size is 0, this might be a new temporary file. |
| 433 append = false; |
| 434 } |
| 435 |
| 436 // The path to the zip path that will actually be used to write the new data. |
| 437 base::FilePath zip_path_used = zip_path; |
| 438 |
| 439 bool has_intersection = false; |
| 440 if (append) { |
| 441 // NOTE: zipOpenNewFileInZip4 is not clever enough to replace files, |
| 442 // therefore this has to explicitly create a new file and reinsert the |
| 443 // data we want without duplications, which is what the minizip headers |
| 444 // tell to do. |
| 445 if (!HasFileInZip(zip_path, contents, &has_intersection)) |
| 446 return false; |
| 447 |
| 448 if (has_intersection) { |
| 449 append = false; |
| 450 if (!base::CreateTemporaryFile(&zip_path_used)) { |
| 451 DLOG(WARNING) << "Couldn't create temporary file for " |
| 452 << zip_path.value(); |
| 453 return false; |
| 454 } |
| 455 } |
| 456 } |
| 457 |
| 458 zipFile zip_file = internal::OpenForZipping( |
| 459 zip_path_used.AsUTF8Unsafe(), |
| 460 append ? APPEND_STATUS_ADDINZIP : APPEND_STATUS_CREATE); |
| 461 if (!zip_file) { |
| 462 DLOG(WARNING) << "Couldn't create file " << zip_path_used.value(); |
| 463 return false; |
| 464 } |
| 465 |
| 466 bool success = true; |
| 467 |
| 468 if (has_intersection) { |
| 469 // There was an intersection. There are files in the old archive with |
| 470 // same paths as found in |contents| therefore the old ones need to be |
| 471 // replaced. |
| 472 success = CopyZipContentsNotInMap(zip_path, contents, zip_file); |
| 473 } |
| 474 |
| 475 for (ZipContents::const_iterator itr = contents.begin(); |
| 476 itr != contents.end() && success; |
| 477 ++itr) { |
| 478 if (itr->second.get()) { |
| 479 success = AddEntryToZipFromMemory( |
| 480 zip_file, itr->first, itr->second.get()); |
| 481 } |
| 482 } |
| 483 |
| 484 if (ZIP_OK != zipClose(zip_file, NULL)) { |
| 485 DLOG(ERROR) << "Error closing zip file " << zip_path_used.value(); |
| 486 success = false; |
| 487 } |
| 488 |
| 489 if (zip_path_used != zip_path) { |
| 490 if (success) { |
| 491 if (!base::Move(zip_path_used, zip_path)) { |
| 492 base::DeleteFile(zip_path_used, false); |
| 493 DLOG(ERROR) << "Error moving zip file " << zip_path_used.value(); |
| 494 return false; |
| 495 } |
| 496 } else { |
| 497 base::DeleteFile(zip_path_used, false); |
| 498 } |
| 499 } |
| 500 |
| 501 return success; |
| 502 } |
| 503 |
264 } // namespace zip | 504 } // namespace zip |
OLD | NEW |