OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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_internal.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/strings/utf_string_conversions.h" | 10 #include "base/strings/utf_string_conversions.h" |
| 11 #include "base/time/time.h" |
| 12 #include "third_party/zlib/google/zip_reader.h" |
11 | 13 |
12 #if defined(USE_SYSTEM_MINIZIP) | 14 #if defined(USE_SYSTEM_MINIZIP) |
13 #include <minizip/ioapi.h> | 15 #include <minizip/ioapi.h> |
14 #include <minizip/unzip.h> | 16 #include <minizip/unzip.h> |
15 #include <minizip/zip.h> | 17 #include <minizip/zip.h> |
16 #else | 18 #else |
17 #include "third_party/zlib/contrib/minizip/unzip.h" | 19 #include "third_party/zlib/contrib/minizip/unzip.h" |
18 #include "third_party/zlib/contrib/minizip/zip.h" | 20 #include "third_party/zlib/contrib/minizip/zip.h" |
19 #if defined(OS_WIN) | 21 #if defined(OS_WIN) |
20 #include "third_party/zlib/contrib/minizip/iowin32.h" | 22 #include "third_party/zlib/contrib/minizip/iowin32.h" |
(...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
223 free(opaque); | 225 free(opaque); |
224 return 0; | 226 return 0; |
225 } | 227 } |
226 | 228 |
227 // Returns the last error happened when reading or writing data. This function | 229 // Returns the last error happened when reading or writing data. This function |
228 // always returns zero, which means there are not any errors. | 230 // always returns zero, which means there are not any errors. |
229 int GetErrorOfZipBuffer(void* /*opaque*/, void* /*stream*/) { | 231 int GetErrorOfZipBuffer(void* /*opaque*/, void* /*stream*/) { |
230 return 0; | 232 return 0; |
231 } | 233 } |
232 | 234 |
| 235 // Returns a zip_fileinfo struct with the time represented by |file_time|. |
| 236 zip_fileinfo TimeToZipFileInfo(const base::Time& file_time) { |
| 237 base::Time::Exploded file_time_parts; |
| 238 file_time.LocalExplode(&file_time_parts); |
| 239 |
| 240 zip_fileinfo zip_info = {}; |
| 241 if (file_time_parts.year >= 1980) { |
| 242 // This if check works around the handling of the year value in |
| 243 // contrib/minizip/zip.c in function zip64local_TmzDateToDosDate |
| 244 // It assumes that dates below 1980 are in the double digit format. |
| 245 // Hence the fail safe option is to leave the date unset. Some programs |
| 246 // might show the unset date as 1980-0-0 which is invalid. |
| 247 zip_info.tmz_date.tm_year = file_time_parts.year; |
| 248 zip_info.tmz_date.tm_mon = file_time_parts.month - 1; |
| 249 zip_info.tmz_date.tm_mday = file_time_parts.day_of_month; |
| 250 zip_info.tmz_date.tm_hour = file_time_parts.hour; |
| 251 zip_info.tmz_date.tm_min = file_time_parts.minute; |
| 252 zip_info.tmz_date.tm_sec = file_time_parts.second; |
| 253 } |
| 254 |
| 255 return zip_info; |
| 256 } |
| 257 |
| 258 // Tells if there is at least one file in the |zip_path| archive which also |
| 259 // exists in |contents|. |
| 260 // Returns false if there was a problem opening the zip file. |
| 261 // The result of whether there is an intersection between the paths in |
| 262 // |zip_path| and |contents| is stored in |has_files|. |
| 263 bool HasFileInZip(const base::FilePath& zip_path, |
| 264 const zip::internal::ZipContents& contents, |
| 265 bool* has_files) { |
| 266 DCHECK(has_files); |
| 267 *has_files = false; |
| 268 |
| 269 zip::ZipReader reader; |
| 270 if (!reader.Open(zip_path)) { |
| 271 DLOG(ERROR) << "Can't open zip file '" << zip_path.value(); |
| 272 return false; |
| 273 } |
| 274 |
| 275 for (zip::internal::ZipContents::const_iterator itr = contents.begin(); |
| 276 itr != contents.end(); ++itr) |
| 277 if (reader.LocateAndOpenEntry(itr->first)) { |
| 278 *has_files = true; |
| 279 return true; |
| 280 } |
| 281 |
| 282 return true; |
| 283 } |
| 284 |
| 285 // Validates the contents in a zip::ZipContents to be used by ZipFromMemory: |
| 286 // Makes sure that all paths are relative and safe. Verifies that folders do not |
| 287 // have data (such would perhaps be a bug when utilizing the API). |
| 288 bool ValidateZipContents(const zip::internal::ZipContents& contents) { |
| 289 for (zip::internal::ZipContents::const_iterator itr = contents.begin(); |
| 290 itr != contents.end(); ++itr) { |
| 291 if (itr->first.empty() || |
| 292 itr->first.IsAbsolute() || |
| 293 itr->first.ReferencesParent()) { |
| 294 DLOG(ERROR) << "Invalid file path '" << itr->first.value() << "'"; |
| 295 return false; |
| 296 } |
| 297 if (itr->first.EndsWithSeparator() && |
| 298 itr->second.size() != 0) { |
| 299 // Can be NULL (remove) or 0-byte (add). |
| 300 DLOG(ERROR) << "Folder must have empty contents '" << |
| 301 itr->first.value() << "'"; |
| 302 return false; |
| 303 } |
| 304 } |
| 305 return true; |
| 306 } |
| 307 |
| 308 // Appends contents to the currently open zip entry in |zip_file| |
| 309 bool AddMemContentsToZip(zipFile zip_file, |
| 310 const base::FilePath& file_path, |
| 311 const void* contents, |
| 312 const size_t contents_length) { |
| 313 if (contents_length != 0 && |
| 314 ZIP_OK != zipWriteInFileInZip( |
| 315 zip_file, contents, contents_length)) { |
| 316 DCHECK(contents); |
| 317 DLOG(ERROR) << "Could not write data to zip for path " |
| 318 << file_path.value(); |
| 319 return false; |
| 320 } |
| 321 |
| 322 return true; |
| 323 } |
| 324 |
| 325 // Creates a new file or folder entry in |zip_file| with |path| as the file |
| 326 // path and |contents| as the file contents. If |path| ends with a separator |
| 327 // it is considered to be a folder entry. Returns false in case of error. |
| 328 bool AddEntryToZipFromMemory(zipFile zip_file, |
| 329 const base::FilePath& path, |
| 330 const void* contents, |
| 331 const size_t contents_length) { |
| 332 DCHECK(!path.IsAbsolute()); |
| 333 std::string str_path = path.AsUTF8Unsafe(); |
| 334 #if defined(OS_WIN) |
| 335 ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); |
| 336 #endif |
| 337 |
| 338 zip_fileinfo file_info = TimeToZipFileInfo(base::Time::Now()); |
| 339 if (!zip::internal::ZipOpenNewFileInZipWrapper( |
| 340 zip_file, str_path, &file_info)) |
| 341 return false; |
| 342 |
| 343 bool success = true; |
| 344 if (!path.EndsWithSeparator()) { |
| 345 // Not a folder. |
| 346 success = AddMemContentsToZip(zip_file, path, contents, contents_length); |
| 347 } |
| 348 |
| 349 if (ZIP_OK != zipCloseFileInZip(zip_file)) { |
| 350 DLOG(ERROR) << "Could not close zip file entry " << str_path; |
| 351 return false; |
| 352 } |
| 353 |
| 354 return success; |
| 355 } |
| 356 |
| 357 // Finds all files (path and contents) present in |zip_path| which are not |
| 358 // present in |contents| and adds them to |output_zip_file| |
| 359 // The function returns false in case of error handling the zip file. |
| 360 bool CopyZipContentsNotInMap(const base::FilePath& zip_path, |
| 361 const zip::internal::ZipContents& contents, |
| 362 zipFile output_zip_file) { |
| 363 // Arbitrary big size, but not that big, of the maximum amount of memory this |
| 364 // function can use to load a file's contents into memory. |
| 365 const int64 max_allowed_file_size = 128 * 1024 * 1024; |
| 366 |
| 367 zip::ZipReader reader; |
| 368 if (!reader.Open(zip_path)) { |
| 369 DLOG(ERROR) << "Can't open zip file '" << zip_path.value(); |
| 370 return false; |
| 371 } |
| 372 |
| 373 while (reader.HasMore()) { |
| 374 if (!reader.OpenCurrentEntryInZip()) { |
| 375 DLOG(ERROR) << "Can't open entry in zip file '" << zip_path.value(); |
| 376 return false; |
| 377 } |
| 378 |
| 379 zip::ZipReader::EntryInfo* entry = reader.current_entry_info(); |
| 380 DCHECK(entry); |
| 381 if (contents.find(entry->file_path()) == contents.end()) { |
| 382 std::string mem; |
| 383 if (!reader.ExtractCurrentEntryToString( |
| 384 max_allowed_file_size, &mem)) { |
| 385 DLOG(ERROR) << "Failed to read contents of " |
| 386 << entry->file_path().value(); |
| 387 return false; |
| 388 } |
| 389 if (!AddEntryToZipFromMemory( |
| 390 output_zip_file, entry->file_path(), mem.c_str(), mem.size())) |
| 391 return false; |
| 392 } |
| 393 reader.AdvanceToNextEntry(); |
| 394 } |
| 395 reader.Close(); |
| 396 |
| 397 return true; |
| 398 } |
| 399 |
233 } // namespace | 400 } // namespace |
234 | 401 |
235 namespace zip { | 402 namespace zip { |
236 namespace internal { | 403 namespace internal { |
237 | 404 |
238 unzFile OpenForUnzipping(const std::string& file_name_utf8) { | 405 unzFile OpenForUnzipping(const std::string& file_name_utf8) { |
239 zlib_filefunc_def* zip_func_ptrs = NULL; | 406 zlib_filefunc_def* zip_func_ptrs = NULL; |
240 #if defined(OS_WIN) | 407 #if defined(OS_WIN) |
241 zlib_filefunc_def zip_funcs; | 408 zlib_filefunc_def zip_funcs; |
242 fill_win32_filefunc(&zip_funcs); | 409 fill_win32_filefunc(&zip_funcs); |
(...skipping 16 matching lines...) Expand all Loading... |
259 unzFile OpenHandleForUnzipping(HANDLE zip_handle) { | 426 unzFile OpenHandleForUnzipping(HANDLE zip_handle) { |
260 zlib_filefunc_def zip_funcs; | 427 zlib_filefunc_def zip_funcs; |
261 fill_win32_filefunc(&zip_funcs); | 428 fill_win32_filefunc(&zip_funcs); |
262 zip_funcs.zopen_file = HandleOpenFileFunc; | 429 zip_funcs.zopen_file = HandleOpenFileFunc; |
263 zip_funcs.opaque = zip_handle; | 430 zip_funcs.opaque = zip_handle; |
264 return unzOpen2("fd", &zip_funcs); | 431 return unzOpen2("fd", &zip_funcs); |
265 } | 432 } |
266 #endif | 433 #endif |
267 | 434 |
268 // static | 435 // static |
269 unzFile PreprareMemoryForUnzipping(const std::string& data) { | 436 unzFile PrepareMemoryForUnzipping(const std::string& data) { |
270 if (data.empty()) | 437 if (data.empty()) |
271 return NULL; | 438 return NULL; |
272 | 439 |
273 ZipBuffer* buffer = static_cast<ZipBuffer*>(malloc(sizeof(ZipBuffer))); | 440 ZipBuffer* buffer = static_cast<ZipBuffer*>(malloc(sizeof(ZipBuffer))); |
274 if (!buffer) | 441 if (!buffer) |
275 return NULL; | 442 return NULL; |
276 buffer->data = data.data(); | 443 buffer->data = data.data(); |
277 buffer->length = data.length(); | 444 buffer->length = data.length(); |
278 buffer->offset = 0; | 445 buffer->offset = 0; |
279 | 446 |
(...skipping 25 matching lines...) Expand all Loading... |
305 | 472 |
306 #if defined(OS_POSIX) | 473 #if defined(OS_POSIX) |
307 zipFile OpenFdForZipping(int zip_fd, int append_flag) { | 474 zipFile OpenFdForZipping(int zip_fd, int append_flag) { |
308 zlib_filefunc_def zip_funcs; | 475 zlib_filefunc_def zip_funcs; |
309 FillFdOpenFileFunc(&zip_funcs, zip_fd); | 476 FillFdOpenFileFunc(&zip_funcs, zip_fd); |
310 // Passing dummy "fd" filename to zlib. | 477 // Passing dummy "fd" filename to zlib. |
311 return zipOpen2("fd", append_flag, NULL, &zip_funcs); | 478 return zipOpen2("fd", append_flag, NULL, &zip_funcs); |
312 } | 479 } |
313 #endif | 480 #endif |
314 | 481 |
| 482 zip_fileinfo GetFileInfoForZipping(const base::FilePath& path) { |
| 483 base::Time file_time; |
| 484 base::File::Info file_info; |
| 485 if (base::GetFileInfo(path, &file_info)) |
| 486 file_time = file_info.last_modified; |
| 487 return TimeToZipFileInfo(file_time); |
| 488 } |
| 489 |
| 490 bool ZipOpenNewFileInZipWrapper(zipFile zip_file, |
| 491 const std::string& str_path, |
| 492 const zip_fileinfo* file_info) { |
| 493 // Section 4.4.4 http://www.pkware.com/documents/casestudies/APPNOTE.TXT |
| 494 // Setting the Language encoding flag so the file is told to be in utf-8. |
| 495 const uLong LANGUAGE_ENCODING_FLAG = 0x1 << 11; |
| 496 |
| 497 if (ZIP_OK != zipOpenNewFileInZip4( |
| 498 zip_file, // file |
| 499 str_path.c_str(), // filename |
| 500 file_info, // zipfi |
| 501 NULL, // extrafield_local, |
| 502 0u, // size_extrafield_local |
| 503 NULL, // extrafield_global |
| 504 0u, // size_extrafield_global |
| 505 NULL, // comment |
| 506 Z_DEFLATED, // method |
| 507 Z_DEFAULT_COMPRESSION, // level |
| 508 0, // raw |
| 509 -MAX_WBITS, // windowBits |
| 510 DEF_MEM_LEVEL, // memLevel |
| 511 Z_DEFAULT_STRATEGY, // strategy |
| 512 NULL, // password |
| 513 0, // crcForCrypting |
| 514 0, // versionMadeBy |
| 515 LANGUAGE_ENCODING_FLAG)) { // flagBase |
| 516 DLOG(ERROR) << "Could not open zip file entry " << str_path; |
| 517 return false; |
| 518 } |
| 519 return true; |
| 520 } |
| 521 |
| 522 bool ZipFromMemory(const base::FilePath& zip_path, |
| 523 const ZipContents& contents, |
| 524 bool append) { |
| 525 |
| 526 // There is really no point in calling this with no input, and |
| 527 // there are simpler ways to create a 0 byte file. |
| 528 if (contents.size() == 0) |
| 529 return true; |
| 530 |
| 531 CHECK(!zip_path.empty()); |
| 532 // This check saves us having to worry with invalid input after. |
| 533 CHECK(ValidateZipContents(contents)); |
| 534 |
| 535 int64 file_size = 0; |
| 536 if (append && |
| 537 (!base::PathExists(zip_path) || |
| 538 (base::GetFileSize(zip_path, &file_size) && file_size < 1))) { |
| 539 // If the size is 0, this might be a new temporary file. |
| 540 append = false; |
| 541 } |
| 542 |
| 543 // The path to the zip path that will actually be used to write the new data. |
| 544 base::FilePath zip_path_used = zip_path; |
| 545 |
| 546 bool has_intersection = false; |
| 547 if (append) { |
| 548 // NOTE: zipOpenNewFileInZip4 is not clever enough to replace files, |
| 549 // therefore this has to explicitly create a new file and reinsert the |
| 550 // data we want without duplications, which is what the minizip headers |
| 551 // tell to do. |
| 552 if (!HasFileInZip(zip_path, contents, &has_intersection)) |
| 553 return false; |
| 554 |
| 555 if (has_intersection) { |
| 556 append = false; |
| 557 if (!base::CreateTemporaryFile(&zip_path_used)) { |
| 558 DLOG(WARNING) << "Couldn't create temporary file for " |
| 559 << zip_path.value(); |
| 560 return false; |
| 561 } |
| 562 } |
| 563 } |
| 564 |
| 565 zipFile zip_file = internal::OpenForZipping( |
| 566 zip_path_used.AsUTF8Unsafe(), |
| 567 append ? APPEND_STATUS_ADDINZIP : APPEND_STATUS_CREATE); |
| 568 if (!zip_file) { |
| 569 DLOG(WARNING) << "Couldn't create file " << zip_path_used.value(); |
| 570 return false; |
| 571 } |
| 572 |
| 573 bool success = true; |
| 574 |
| 575 if (has_intersection) { |
| 576 // There was an intersection. There are files in the old archive with |
| 577 // same paths as found in |contents| therefore the old ones need to be |
| 578 // replaced. |
| 579 success = CopyZipContentsNotInMap(zip_path, contents, zip_file); |
| 580 } |
| 581 |
| 582 for (ZipContents::const_iterator itr = contents.begin(); |
| 583 itr != contents.end() && success; |
| 584 ++itr) { |
| 585 if (itr->second.data() != NULL) { |
| 586 success = AddEntryToZipFromMemory( |
| 587 zip_file, itr->first, itr->second.data(), itr->second.size()); |
| 588 } |
| 589 } |
| 590 |
| 591 if (ZIP_OK != zipClose(zip_file, NULL)) { |
| 592 DLOG(ERROR) << "Error closing zip file " << zip_path_used.value(); |
| 593 success = false; |
| 594 } |
| 595 |
| 596 if (zip_path_used != zip_path) { |
| 597 if (success) { |
| 598 if (!base::Move(zip_path_used, zip_path)) { |
| 599 base::DeleteFile(zip_path_used, false); |
| 600 DLOG(ERROR) << "Error moving zip file " << zip_path_used.value(); |
| 601 return false; |
| 602 } |
| 603 } else { |
| 604 base::DeleteFile(zip_path_used, false); |
| 605 } |
| 606 } |
| 607 |
| 608 return success; |
| 609 } |
| 610 |
315 } // namespace internal | 611 } // namespace internal |
316 } // namespace zip | 612 } // namespace zip |
OLD | NEW |