OLD | NEW |
(Empty) | |
| 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_reader.h" |
| 6 |
| 7 #include <utility> |
| 8 |
| 9 #include "base/bind.h" |
| 10 #include "base/files/file.h" |
| 11 #include "base/logging.h" |
| 12 #include "base/macros.h" |
| 13 #include "base/message_loop/message_loop.h" |
| 14 #include "base/strings/string_util.h" |
| 15 #include "base/strings/utf_string_conversions.h" |
| 16 #include "base/threading/thread_task_runner_handle.h" |
| 17 #include "build/build_config.h" |
| 18 #include "third_party/zlib/google/zip_internal.h" |
| 19 |
| 20 #if defined(USE_SYSTEM_MINIZIP) |
| 21 #include <minizip/unzip.h> |
| 22 #else |
| 23 #include "third_party/zlib/contrib/minizip/unzip.h" |
| 24 #if defined(OS_WIN) |
| 25 #include "third_party/zlib/contrib/minizip/iowin32.h" |
| 26 #endif // defined(OS_WIN) |
| 27 #endif // defined(USE_SYSTEM_MINIZIP) |
| 28 |
| 29 namespace zip { |
| 30 |
| 31 namespace { |
| 32 |
| 33 // FilePathWriterDelegate ------------------------------------------------------ |
| 34 |
| 35 // A writer delegate that writes a file at a given path. |
| 36 class FilePathWriterDelegate : public WriterDelegate { |
| 37 public: |
| 38 explicit FilePathWriterDelegate(const base::FilePath& output_file_path); |
| 39 ~FilePathWriterDelegate() override; |
| 40 |
| 41 // WriterDelegate methods: |
| 42 |
| 43 // Creates the output file and any necessary intermediate directories. |
| 44 bool PrepareOutput() override; |
| 45 |
| 46 // Writes |num_bytes| bytes of |data| to the file, returning false if not all |
| 47 // bytes could be written. |
| 48 bool WriteBytes(const char* data, int num_bytes) override; |
| 49 |
| 50 private: |
| 51 base::FilePath output_file_path_; |
| 52 base::File file_; |
| 53 |
| 54 DISALLOW_COPY_AND_ASSIGN(FilePathWriterDelegate); |
| 55 }; |
| 56 |
| 57 FilePathWriterDelegate::FilePathWriterDelegate( |
| 58 const base::FilePath& output_file_path) |
| 59 : output_file_path_(output_file_path) { |
| 60 } |
| 61 |
| 62 FilePathWriterDelegate::~FilePathWriterDelegate() { |
| 63 } |
| 64 |
| 65 bool FilePathWriterDelegate::PrepareOutput() { |
| 66 // We can't rely on parent directory entries being specified in the |
| 67 // zip, so we make sure they are created. |
| 68 if (!base::CreateDirectory(output_file_path_.DirName())) |
| 69 return false; |
| 70 |
| 71 file_.Initialize(output_file_path_, |
| 72 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| 73 return file_.IsValid(); |
| 74 } |
| 75 |
| 76 bool FilePathWriterDelegate::WriteBytes(const char* data, int num_bytes) { |
| 77 return num_bytes == file_.WriteAtCurrentPos(data, num_bytes); |
| 78 } |
| 79 |
| 80 |
| 81 // StringWriterDelegate -------------------------------------------------------- |
| 82 |
| 83 // A writer delegate that writes no more than |max_read_bytes| to a given |
| 84 // std::string. |
| 85 class StringWriterDelegate : public WriterDelegate { |
| 86 public: |
| 87 StringWriterDelegate(size_t max_read_bytes, std::string* output); |
| 88 ~StringWriterDelegate() override; |
| 89 |
| 90 // WriterDelegate methods: |
| 91 |
| 92 // Returns true. |
| 93 bool PrepareOutput() override; |
| 94 |
| 95 // Appends |num_bytes| bytes from |data| to the output string. Returns false |
| 96 // if |num_bytes| will cause the string to exceed |max_read_bytes|. |
| 97 bool WriteBytes(const char* data, int num_bytes) override; |
| 98 |
| 99 private: |
| 100 size_t max_read_bytes_; |
| 101 std::string* output_; |
| 102 |
| 103 DISALLOW_COPY_AND_ASSIGN(StringWriterDelegate); |
| 104 }; |
| 105 |
| 106 StringWriterDelegate::StringWriterDelegate(size_t max_read_bytes, |
| 107 std::string* output) |
| 108 : max_read_bytes_(max_read_bytes), |
| 109 output_(output) { |
| 110 } |
| 111 |
| 112 StringWriterDelegate::~StringWriterDelegate() { |
| 113 } |
| 114 |
| 115 bool StringWriterDelegate::PrepareOutput() { |
| 116 return true; |
| 117 } |
| 118 |
| 119 bool StringWriterDelegate::WriteBytes(const char* data, int num_bytes) { |
| 120 if (output_->size() + num_bytes > max_read_bytes_) |
| 121 return false; |
| 122 output_->append(data, num_bytes); |
| 123 return true; |
| 124 } |
| 125 |
| 126 } // namespace |
| 127 |
| 128 // TODO(satorux): The implementation assumes that file names in zip files |
| 129 // are encoded in UTF-8. This is true for zip files created by Zip() |
| 130 // function in zip.h, but not true for user-supplied random zip files. |
| 131 ZipReader::EntryInfo::EntryInfo(const std::string& file_name_in_zip, |
| 132 const unz_file_info& raw_file_info) |
| 133 : file_path_(base::FilePath::FromUTF8Unsafe(file_name_in_zip)), |
| 134 is_directory_(false) { |
| 135 original_size_ = raw_file_info.uncompressed_size; |
| 136 |
| 137 // Directory entries in zip files end with "/". |
| 138 is_directory_ = base::EndsWith(file_name_in_zip, "/", |
| 139 base::CompareCase::INSENSITIVE_ASCII); |
| 140 |
| 141 // Check the file name here for directory traversal issues. |
| 142 is_unsafe_ = file_path_.ReferencesParent(); |
| 143 |
| 144 // We also consider that the file name is unsafe, if it's invalid UTF-8. |
| 145 base::string16 file_name_utf16; |
| 146 if (!base::UTF8ToUTF16(file_name_in_zip.data(), file_name_in_zip.size(), |
| 147 &file_name_utf16)) { |
| 148 is_unsafe_ = true; |
| 149 } |
| 150 |
| 151 // We also consider that the file name is unsafe, if it's absolute. |
| 152 // On Windows, IsAbsolute() returns false for paths starting with "/". |
| 153 if (file_path_.IsAbsolute() || |
| 154 base::StartsWith(file_name_in_zip, "/", |
| 155 base::CompareCase::INSENSITIVE_ASCII)) |
| 156 is_unsafe_ = true; |
| 157 |
| 158 // Construct the last modified time. The timezone info is not present in |
| 159 // zip files, so we construct the time as local time. |
| 160 base::Time::Exploded exploded_time = {}; // Zero-clear. |
| 161 exploded_time.year = raw_file_info.tmu_date.tm_year; |
| 162 // The month in zip file is 0-based, whereas ours is 1-based. |
| 163 exploded_time.month = raw_file_info.tmu_date.tm_mon + 1; |
| 164 exploded_time.day_of_month = raw_file_info.tmu_date.tm_mday; |
| 165 exploded_time.hour = raw_file_info.tmu_date.tm_hour; |
| 166 exploded_time.minute = raw_file_info.tmu_date.tm_min; |
| 167 exploded_time.second = raw_file_info.tmu_date.tm_sec; |
| 168 exploded_time.millisecond = 0; |
| 169 if (exploded_time.HasValidValues()) { |
| 170 last_modified_ = base::Time::FromLocalExploded(exploded_time); |
| 171 } else { |
| 172 // Use Unix time epoch if the time stamp data is invalid. |
| 173 last_modified_ = base::Time::UnixEpoch(); |
| 174 } |
| 175 } |
| 176 |
| 177 ZipReader::ZipReader() |
| 178 : weak_ptr_factory_(this) { |
| 179 Reset(); |
| 180 } |
| 181 |
| 182 ZipReader::~ZipReader() { |
| 183 Close(); |
| 184 } |
| 185 |
| 186 bool ZipReader::Open(const base::FilePath& zip_file_path) { |
| 187 DCHECK(!zip_file_); |
| 188 |
| 189 // Use of "Unsafe" function does not look good, but there is no way to do |
| 190 // this safely on Linux. See file_util.h for details. |
| 191 zip_file_ = internal::OpenForUnzipping(zip_file_path.AsUTF8Unsafe()); |
| 192 if (!zip_file_) { |
| 193 return false; |
| 194 } |
| 195 |
| 196 return OpenInternal(); |
| 197 } |
| 198 |
| 199 bool ZipReader::OpenFromPlatformFile(base::PlatformFile zip_fd) { |
| 200 DCHECK(!zip_file_); |
| 201 |
| 202 #if defined(OS_POSIX) |
| 203 zip_file_ = internal::OpenFdForUnzipping(zip_fd); |
| 204 #elif defined(OS_WIN) |
| 205 zip_file_ = internal::OpenHandleForUnzipping(zip_fd); |
| 206 #endif |
| 207 if (!zip_file_) { |
| 208 return false; |
| 209 } |
| 210 |
| 211 return OpenInternal(); |
| 212 } |
| 213 |
| 214 bool ZipReader::OpenFromString(const std::string& data) { |
| 215 zip_file_ = internal::PrepareMemoryForUnzipping(data); |
| 216 if (!zip_file_) |
| 217 return false; |
| 218 return OpenInternal(); |
| 219 } |
| 220 |
| 221 void ZipReader::Close() { |
| 222 if (zip_file_) { |
| 223 unzClose(zip_file_); |
| 224 } |
| 225 Reset(); |
| 226 } |
| 227 |
| 228 bool ZipReader::HasMore() { |
| 229 return !reached_end_; |
| 230 } |
| 231 |
| 232 bool ZipReader::AdvanceToNextEntry() { |
| 233 DCHECK(zip_file_); |
| 234 |
| 235 // Should not go further if we already reached the end. |
| 236 if (reached_end_) |
| 237 return false; |
| 238 |
| 239 unz_file_pos position = {}; |
| 240 if (unzGetFilePos(zip_file_, &position) != UNZ_OK) |
| 241 return false; |
| 242 const int current_entry_index = position.num_of_file; |
| 243 // If we are currently at the last entry, then the next position is the |
| 244 // end of the zip file, so mark that we reached the end. |
| 245 if (current_entry_index + 1 == num_entries_) { |
| 246 reached_end_ = true; |
| 247 } else { |
| 248 DCHECK_LT(current_entry_index + 1, num_entries_); |
| 249 if (unzGoToNextFile(zip_file_) != UNZ_OK) { |
| 250 return false; |
| 251 } |
| 252 } |
| 253 current_entry_info_.reset(); |
| 254 return true; |
| 255 } |
| 256 |
| 257 bool ZipReader::OpenCurrentEntryInZip() { |
| 258 DCHECK(zip_file_); |
| 259 |
| 260 unz_file_info raw_file_info = {}; |
| 261 char raw_file_name_in_zip[internal::kZipMaxPath] = {}; |
| 262 const int result = unzGetCurrentFileInfo(zip_file_, |
| 263 &raw_file_info, |
| 264 raw_file_name_in_zip, |
| 265 sizeof(raw_file_name_in_zip) - 1, |
| 266 NULL, // extraField. |
| 267 0, // extraFieldBufferSize. |
| 268 NULL, // szComment. |
| 269 0); // commentBufferSize. |
| 270 if (result != UNZ_OK) |
| 271 return false; |
| 272 if (raw_file_name_in_zip[0] == '\0') |
| 273 return false; |
| 274 current_entry_info_.reset( |
| 275 new EntryInfo(raw_file_name_in_zip, raw_file_info)); |
| 276 return true; |
| 277 } |
| 278 |
| 279 bool ZipReader::LocateAndOpenEntry(const base::FilePath& path_in_zip) { |
| 280 DCHECK(zip_file_); |
| 281 |
| 282 current_entry_info_.reset(); |
| 283 reached_end_ = false; |
| 284 const int kDefaultCaseSensivityOfOS = 0; |
| 285 const int result = unzLocateFile(zip_file_, |
| 286 path_in_zip.AsUTF8Unsafe().c_str(), |
| 287 kDefaultCaseSensivityOfOS); |
| 288 if (result != UNZ_OK) |
| 289 return false; |
| 290 |
| 291 // Then Open the entry. |
| 292 return OpenCurrentEntryInZip(); |
| 293 } |
| 294 |
| 295 bool ZipReader::ExtractCurrentEntry(WriterDelegate* delegate) const { |
| 296 DCHECK(zip_file_); |
| 297 |
| 298 const int open_result = unzOpenCurrentFile(zip_file_); |
| 299 if (open_result != UNZ_OK) |
| 300 return false; |
| 301 |
| 302 if (!delegate->PrepareOutput()) |
| 303 return false; |
| 304 |
| 305 bool success = true; // This becomes false when something bad happens. |
| 306 std::unique_ptr<char[]> buf(new char[internal::kZipBufSize]); |
| 307 while (true) { |
| 308 const int num_bytes_read = unzReadCurrentFile(zip_file_, buf.get(), |
| 309 internal::kZipBufSize); |
| 310 if (num_bytes_read == 0) { |
| 311 // Reached the end of the file. |
| 312 break; |
| 313 } else if (num_bytes_read < 0) { |
| 314 // If num_bytes_read < 0, then it's a specific UNZ_* error code. |
| 315 success = false; |
| 316 break; |
| 317 } else if (num_bytes_read > 0) { |
| 318 // Some data is read. |
| 319 if (!delegate->WriteBytes(buf.get(), num_bytes_read)) { |
| 320 success = false; |
| 321 break; |
| 322 } |
| 323 } |
| 324 } |
| 325 |
| 326 unzCloseCurrentFile(zip_file_); |
| 327 |
| 328 return success; |
| 329 } |
| 330 |
| 331 bool ZipReader::ExtractCurrentEntryToFilePath( |
| 332 const base::FilePath& output_file_path) const { |
| 333 DCHECK(zip_file_); |
| 334 |
| 335 // If this is a directory, just create it and return. |
| 336 if (current_entry_info()->is_directory()) |
| 337 return base::CreateDirectory(output_file_path); |
| 338 |
| 339 bool success = false; |
| 340 { |
| 341 FilePathWriterDelegate writer(output_file_path); |
| 342 success = ExtractCurrentEntry(&writer); |
| 343 } |
| 344 |
| 345 if (success && |
| 346 current_entry_info()->last_modified() != base::Time::UnixEpoch()) { |
| 347 base::TouchFile(output_file_path, |
| 348 base::Time::Now(), |
| 349 current_entry_info()->last_modified()); |
| 350 } |
| 351 |
| 352 return success; |
| 353 } |
| 354 |
| 355 void ZipReader::ExtractCurrentEntryToFilePathAsync( |
| 356 const base::FilePath& output_file_path, |
| 357 const SuccessCallback& success_callback, |
| 358 const FailureCallback& failure_callback, |
| 359 const ProgressCallback& progress_callback) { |
| 360 DCHECK(zip_file_); |
| 361 DCHECK(current_entry_info_.get()); |
| 362 |
| 363 // If this is a directory, just create it and return. |
| 364 if (current_entry_info()->is_directory()) { |
| 365 if (base::CreateDirectory(output_file_path)) { |
| 366 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, success_callback)
; |
| 367 } else { |
| 368 DVLOG(1) << "Unzip failed: unable to create directory."; |
| 369 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, failure_callback)
; |
| 370 } |
| 371 return; |
| 372 } |
| 373 |
| 374 if (unzOpenCurrentFile(zip_file_) != UNZ_OK) { |
| 375 DVLOG(1) << "Unzip failed: unable to open current zip entry."; |
| 376 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, failure_callback); |
| 377 return; |
| 378 } |
| 379 |
| 380 base::FilePath output_dir_path = output_file_path.DirName(); |
| 381 if (!base::CreateDirectory(output_dir_path)) { |
| 382 DVLOG(1) << "Unzip failed: unable to create containing directory."; |
| 383 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, failure_callback); |
| 384 return; |
| 385 } |
| 386 |
| 387 const int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE; |
| 388 base::File output_file(output_file_path, flags); |
| 389 |
| 390 if (!output_file.IsValid()) { |
| 391 DVLOG(1) << "Unzip failed: unable to create platform file at " |
| 392 << output_file_path.value(); |
| 393 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, failure_callback); |
| 394 return; |
| 395 } |
| 396 |
| 397 base::MessageLoop::current()->PostTask( |
| 398 FROM_HERE, |
| 399 base::Bind(&ZipReader::ExtractChunk, weak_ptr_factory_.GetWeakPtr(), |
| 400 Passed(std::move(output_file)), success_callback, |
| 401 failure_callback, progress_callback, 0 /* initial offset */)); |
| 402 } |
| 403 |
| 404 bool ZipReader::ExtractCurrentEntryIntoDirectory( |
| 405 const base::FilePath& output_directory_path) const { |
| 406 DCHECK(current_entry_info_.get()); |
| 407 |
| 408 base::FilePath output_file_path = output_directory_path.Append( |
| 409 current_entry_info()->file_path()); |
| 410 return ExtractCurrentEntryToFilePath(output_file_path); |
| 411 } |
| 412 |
| 413 bool ZipReader::ExtractCurrentEntryToFile(base::File* file) const { |
| 414 DCHECK(zip_file_); |
| 415 |
| 416 // If this is a directory, there's nothing to extract to the file, so return |
| 417 // false. |
| 418 if (current_entry_info()->is_directory()) |
| 419 return false; |
| 420 |
| 421 FileWriterDelegate writer(file); |
| 422 return ExtractCurrentEntry(&writer); |
| 423 } |
| 424 |
| 425 bool ZipReader::ExtractCurrentEntryToString(size_t max_read_bytes, |
| 426 std::string* output) const { |
| 427 DCHECK(output); |
| 428 DCHECK(zip_file_); |
| 429 DCHECK_NE(0U, max_read_bytes); |
| 430 |
| 431 if (current_entry_info()->is_directory()) { |
| 432 output->clear(); |
| 433 return true; |
| 434 } |
| 435 |
| 436 // The original_size() is the best hint for the real size, so it saves |
| 437 // doing reallocations for the common case when the uncompressed size is |
| 438 // correct. However, we need to assume that the uncompressed size could be |
| 439 // incorrect therefore this function needs to read as much data as possible. |
| 440 std::string contents; |
| 441 contents.reserve( |
| 442 static_cast<size_t>(std::min(static_cast<int64_t>(max_read_bytes), |
| 443 current_entry_info()->original_size()))); |
| 444 |
| 445 StringWriterDelegate writer(max_read_bytes, &contents); |
| 446 if (!ExtractCurrentEntry(&writer)) |
| 447 return false; |
| 448 output->swap(contents); |
| 449 return true; |
| 450 } |
| 451 |
| 452 bool ZipReader::OpenInternal() { |
| 453 DCHECK(zip_file_); |
| 454 |
| 455 unz_global_info zip_info = {}; // Zero-clear. |
| 456 if (unzGetGlobalInfo(zip_file_, &zip_info) != UNZ_OK) { |
| 457 return false; |
| 458 } |
| 459 num_entries_ = zip_info.number_entry; |
| 460 if (num_entries_ < 0) |
| 461 return false; |
| 462 |
| 463 // We are already at the end if the zip file is empty. |
| 464 reached_end_ = (num_entries_ == 0); |
| 465 return true; |
| 466 } |
| 467 |
| 468 void ZipReader::Reset() { |
| 469 zip_file_ = NULL; |
| 470 num_entries_ = 0; |
| 471 reached_end_ = false; |
| 472 current_entry_info_.reset(); |
| 473 } |
| 474 |
| 475 void ZipReader::ExtractChunk(base::File output_file, |
| 476 const SuccessCallback& success_callback, |
| 477 const FailureCallback& failure_callback, |
| 478 const ProgressCallback& progress_callback, |
| 479 const int64_t offset) { |
| 480 char buffer[internal::kZipBufSize]; |
| 481 |
| 482 const int num_bytes_read = unzReadCurrentFile(zip_file_, |
| 483 buffer, |
| 484 internal::kZipBufSize); |
| 485 |
| 486 if (num_bytes_read == 0) { |
| 487 unzCloseCurrentFile(zip_file_); |
| 488 success_callback.Run(); |
| 489 } else if (num_bytes_read < 0) { |
| 490 DVLOG(1) << "Unzip failed: error while reading zipfile " |
| 491 << "(" << num_bytes_read << ")"; |
| 492 failure_callback.Run(); |
| 493 } else { |
| 494 if (num_bytes_read != output_file.Write(offset, buffer, num_bytes_read)) { |
| 495 DVLOG(1) << "Unzip failed: unable to write all bytes to target."; |
| 496 failure_callback.Run(); |
| 497 return; |
| 498 } |
| 499 |
| 500 int64_t current_progress = offset + num_bytes_read; |
| 501 |
| 502 progress_callback.Run(current_progress); |
| 503 |
| 504 base::MessageLoop::current()->PostTask( |
| 505 FROM_HERE, |
| 506 base::Bind(&ZipReader::ExtractChunk, weak_ptr_factory_.GetWeakPtr(), |
| 507 Passed(std::move(output_file)), success_callback, |
| 508 failure_callback, progress_callback, current_progress)); |
| 509 } |
| 510 } |
| 511 |
| 512 // FileWriterDelegate ---------------------------------------------------------- |
| 513 |
| 514 FileWriterDelegate::FileWriterDelegate(base::File* file) |
| 515 : file_(file), |
| 516 file_length_(0) { |
| 517 } |
| 518 |
| 519 FileWriterDelegate::~FileWriterDelegate() { |
| 520 #if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) |
| 521 const bool success = |
| 522 #endif |
| 523 file_->SetLength(file_length_); |
| 524 DPLOG_IF(ERROR, !success) << "Failed updating length of written file"; |
| 525 } |
| 526 |
| 527 bool FileWriterDelegate::PrepareOutput() { |
| 528 return file_->Seek(base::File::FROM_BEGIN, 0) >= 0; |
| 529 } |
| 530 |
| 531 bool FileWriterDelegate::WriteBytes(const char* data, int num_bytes) { |
| 532 int bytes_written = file_->WriteAtCurrentPos(data, num_bytes); |
| 533 if (bytes_written > 0) |
| 534 file_length_ += bytes_written; |
| 535 return bytes_written == num_bytes; |
| 536 } |
| 537 |
| 538 } // namespace zip |
OLD | NEW |