Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / zlib / google / zip_reader.cc
blob83086a3467fb0e21201edf36aaa80a8908ab839b
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.
5 #include "third_party/zlib/google/zip_reader.h"
7 #include "base/bind.h"
8 #include "base/files/file.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "third_party/zlib/google/zip_internal.h"
16 #if defined(USE_SYSTEM_MINIZIP)
17 #include <minizip/unzip.h>
18 #else
19 #include "third_party/zlib/contrib/minizip/unzip.h"
20 #if defined(OS_WIN)
21 #include "third_party/zlib/contrib/minizip/iowin32.h"
22 #endif // defined(OS_WIN)
23 #endif // defined(USE_SYSTEM_MINIZIP)
25 namespace zip {
27 namespace {
29 // FilePathWriterDelegate ------------------------------------------------------
31 // A writer delegate that writes a file at a given path.
32 class FilePathWriterDelegate : public WriterDelegate {
33 public:
34 explicit FilePathWriterDelegate(const base::FilePath& output_file_path);
35 ~FilePathWriterDelegate() override;
37 // WriterDelegate methods:
39 // Creates the output file and any necessary intermediate directories.
40 bool PrepareOutput() override;
42 // Writes |num_bytes| bytes of |data| to the file, returning false if not all
43 // bytes could be written.
44 bool WriteBytes(const char* data, int num_bytes) override;
46 private:
47 base::FilePath output_file_path_;
48 base::File file_;
50 DISALLOW_COPY_AND_ASSIGN(FilePathWriterDelegate);
53 FilePathWriterDelegate::FilePathWriterDelegate(
54 const base::FilePath& output_file_path)
55 : output_file_path_(output_file_path) {
58 FilePathWriterDelegate::~FilePathWriterDelegate() {
61 bool FilePathWriterDelegate::PrepareOutput() {
62 // We can't rely on parent directory entries being specified in the
63 // zip, so we make sure they are created.
64 if (!base::CreateDirectory(output_file_path_.DirName()))
65 return false;
67 file_.Initialize(output_file_path_,
68 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
69 return file_.IsValid();
72 bool FilePathWriterDelegate::WriteBytes(const char* data, int num_bytes) {
73 return num_bytes == file_.WriteAtCurrentPos(data, num_bytes);
77 // StringWriterDelegate --------------------------------------------------------
79 // A writer delegate that writes no more than |max_read_bytes| to a given
80 // std::string.
81 class StringWriterDelegate : public WriterDelegate {
82 public:
83 StringWriterDelegate(size_t max_read_bytes, std::string* output);
84 ~StringWriterDelegate() override;
86 // WriterDelegate methods:
88 // Returns true.
89 bool PrepareOutput() override;
91 // Appends |num_bytes| bytes from |data| to the output string. Returns false
92 // if |num_bytes| will cause the string to exceed |max_read_bytes|.
93 bool WriteBytes(const char* data, int num_bytes) override;
95 private:
96 size_t max_read_bytes_;
97 std::string* output_;
99 DISALLOW_COPY_AND_ASSIGN(StringWriterDelegate);
102 StringWriterDelegate::StringWriterDelegate(size_t max_read_bytes,
103 std::string* output)
104 : max_read_bytes_(max_read_bytes),
105 output_(output) {
108 StringWriterDelegate::~StringWriterDelegate() {
111 bool StringWriterDelegate::PrepareOutput() {
112 return true;
115 bool StringWriterDelegate::WriteBytes(const char* data, int num_bytes) {
116 if (output_->size() + num_bytes > max_read_bytes_)
117 return false;
118 output_->append(data, num_bytes);
119 return true;
122 } // namespace
124 // TODO(satorux): The implementation assumes that file names in zip files
125 // are encoded in UTF-8. This is true for zip files created by Zip()
126 // function in zip.h, but not true for user-supplied random zip files.
127 ZipReader::EntryInfo::EntryInfo(const std::string& file_name_in_zip,
128 const unz_file_info& raw_file_info)
129 : file_path_(base::FilePath::FromUTF8Unsafe(file_name_in_zip)),
130 is_directory_(false) {
131 original_size_ = raw_file_info.uncompressed_size;
133 // Directory entries in zip files end with "/".
134 is_directory_ = base::EndsWith(file_name_in_zip, "/",
135 base::CompareCase::INSENSITIVE_ASCII);
137 // Check the file name here for directory traversal issues.
138 is_unsafe_ = file_path_.ReferencesParent();
140 // We also consider that the file name is unsafe, if it's invalid UTF-8.
141 base::string16 file_name_utf16;
142 if (!base::UTF8ToUTF16(file_name_in_zip.data(), file_name_in_zip.size(),
143 &file_name_utf16)) {
144 is_unsafe_ = true;
147 // We also consider that the file name is unsafe, if it's absolute.
148 // On Windows, IsAbsolute() returns false for paths starting with "/".
149 if (file_path_.IsAbsolute() ||
150 base::StartsWith(file_name_in_zip, "/",
151 base::CompareCase::INSENSITIVE_ASCII))
152 is_unsafe_ = true;
154 // Construct the last modified time. The timezone info is not present in
155 // zip files, so we construct the time as local time.
156 base::Time::Exploded exploded_time = {}; // Zero-clear.
157 exploded_time.year = raw_file_info.tmu_date.tm_year;
158 // The month in zip file is 0-based, whereas ours is 1-based.
159 exploded_time.month = raw_file_info.tmu_date.tm_mon + 1;
160 exploded_time.day_of_month = raw_file_info.tmu_date.tm_mday;
161 exploded_time.hour = raw_file_info.tmu_date.tm_hour;
162 exploded_time.minute = raw_file_info.tmu_date.tm_min;
163 exploded_time.second = raw_file_info.tmu_date.tm_sec;
164 exploded_time.millisecond = 0;
165 if (exploded_time.HasValidValues()) {
166 last_modified_ = base::Time::FromLocalExploded(exploded_time);
167 } else {
168 // Use Unix time epoch if the time stamp data is invalid.
169 last_modified_ = base::Time::UnixEpoch();
173 ZipReader::ZipReader()
174 : weak_ptr_factory_(this) {
175 Reset();
178 ZipReader::~ZipReader() {
179 Close();
182 bool ZipReader::Open(const base::FilePath& zip_file_path) {
183 DCHECK(!zip_file_);
185 // Use of "Unsafe" function does not look good, but there is no way to do
186 // this safely on Linux. See file_util.h for details.
187 zip_file_ = internal::OpenForUnzipping(zip_file_path.AsUTF8Unsafe());
188 if (!zip_file_) {
189 return false;
192 return OpenInternal();
195 bool ZipReader::OpenFromPlatformFile(base::PlatformFile zip_fd) {
196 DCHECK(!zip_file_);
198 #if defined(OS_POSIX)
199 zip_file_ = internal::OpenFdForUnzipping(zip_fd);
200 #elif defined(OS_WIN)
201 zip_file_ = internal::OpenHandleForUnzipping(zip_fd);
202 #endif
203 if (!zip_file_) {
204 return false;
207 return OpenInternal();
210 bool ZipReader::OpenFromString(const std::string& data) {
211 zip_file_ = internal::PrepareMemoryForUnzipping(data);
212 if (!zip_file_)
213 return false;
214 return OpenInternal();
217 void ZipReader::Close() {
218 if (zip_file_) {
219 unzClose(zip_file_);
221 Reset();
224 bool ZipReader::HasMore() {
225 return !reached_end_;
228 bool ZipReader::AdvanceToNextEntry() {
229 DCHECK(zip_file_);
231 // Should not go further if we already reached the end.
232 if (reached_end_)
233 return false;
235 unz_file_pos position = {};
236 if (unzGetFilePos(zip_file_, &position) != UNZ_OK)
237 return false;
238 const int current_entry_index = position.num_of_file;
239 // If we are currently at the last entry, then the next position is the
240 // end of the zip file, so mark that we reached the end.
241 if (current_entry_index + 1 == num_entries_) {
242 reached_end_ = true;
243 } else {
244 DCHECK_LT(current_entry_index + 1, num_entries_);
245 if (unzGoToNextFile(zip_file_) != UNZ_OK) {
246 return false;
249 current_entry_info_.reset();
250 return true;
253 bool ZipReader::OpenCurrentEntryInZip() {
254 DCHECK(zip_file_);
256 unz_file_info raw_file_info = {};
257 char raw_file_name_in_zip[internal::kZipMaxPath] = {};
258 const int result = unzGetCurrentFileInfo(zip_file_,
259 &raw_file_info,
260 raw_file_name_in_zip,
261 sizeof(raw_file_name_in_zip) - 1,
262 NULL, // extraField.
263 0, // extraFieldBufferSize.
264 NULL, // szComment.
265 0); // commentBufferSize.
266 if (result != UNZ_OK)
267 return false;
268 if (raw_file_name_in_zip[0] == '\0')
269 return false;
270 current_entry_info_.reset(
271 new EntryInfo(raw_file_name_in_zip, raw_file_info));
272 return true;
275 bool ZipReader::LocateAndOpenEntry(const base::FilePath& path_in_zip) {
276 DCHECK(zip_file_);
278 current_entry_info_.reset();
279 reached_end_ = false;
280 const int kDefaultCaseSensivityOfOS = 0;
281 const int result = unzLocateFile(zip_file_,
282 path_in_zip.AsUTF8Unsafe().c_str(),
283 kDefaultCaseSensivityOfOS);
284 if (result != UNZ_OK)
285 return false;
287 // Then Open the entry.
288 return OpenCurrentEntryInZip();
291 bool ZipReader::ExtractCurrentEntry(WriterDelegate* delegate) const {
292 DCHECK(zip_file_);
294 const int open_result = unzOpenCurrentFile(zip_file_);
295 if (open_result != UNZ_OK)
296 return false;
298 if (!delegate->PrepareOutput())
299 return false;
301 bool success = true; // This becomes false when something bad happens.
302 scoped_ptr<char[]> buf(new char[internal::kZipBufSize]);
303 while (true) {
304 const int num_bytes_read = unzReadCurrentFile(zip_file_, buf.get(),
305 internal::kZipBufSize);
306 if (num_bytes_read == 0) {
307 // Reached the end of the file.
308 break;
309 } else if (num_bytes_read < 0) {
310 // If num_bytes_read < 0, then it's a specific UNZ_* error code.
311 success = false;
312 break;
313 } else if (num_bytes_read > 0) {
314 // Some data is read.
315 if (!delegate->WriteBytes(buf.get(), num_bytes_read)) {
316 success = false;
317 break;
322 unzCloseCurrentFile(zip_file_);
324 return success;
327 bool ZipReader::ExtractCurrentEntryToFilePath(
328 const base::FilePath& output_file_path) const {
329 DCHECK(zip_file_);
331 // If this is a directory, just create it and return.
332 if (current_entry_info()->is_directory())
333 return base::CreateDirectory(output_file_path);
335 bool success = false;
337 FilePathWriterDelegate writer(output_file_path);
338 success = ExtractCurrentEntry(&writer);
341 if (success &&
342 current_entry_info()->last_modified() != base::Time::UnixEpoch()) {
343 base::TouchFile(output_file_path,
344 base::Time::Now(),
345 current_entry_info()->last_modified());
348 return success;
351 void ZipReader::ExtractCurrentEntryToFilePathAsync(
352 const base::FilePath& output_file_path,
353 const SuccessCallback& success_callback,
354 const FailureCallback& failure_callback,
355 const ProgressCallback& progress_callback) {
356 DCHECK(zip_file_);
357 DCHECK(current_entry_info_.get());
359 // If this is a directory, just create it and return.
360 if (current_entry_info()->is_directory()) {
361 if (base::CreateDirectory(output_file_path)) {
362 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, success_callback);
363 } else {
364 DVLOG(1) << "Unzip failed: unable to create directory.";
365 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, failure_callback);
367 return;
370 if (unzOpenCurrentFile(zip_file_) != UNZ_OK) {
371 DVLOG(1) << "Unzip failed: unable to open current zip entry.";
372 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, failure_callback);
373 return;
376 base::FilePath output_dir_path = output_file_path.DirName();
377 if (!base::CreateDirectory(output_dir_path)) {
378 DVLOG(1) << "Unzip failed: unable to create containing directory.";
379 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, failure_callback);
380 return;
383 const int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE;
384 base::File output_file(output_file_path, flags);
386 if (!output_file.IsValid()) {
387 DVLOG(1) << "Unzip failed: unable to create platform file at "
388 << output_file_path.value();
389 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, failure_callback);
390 return;
393 base::MessageLoop::current()->PostTask(
394 FROM_HERE,
395 base::Bind(&ZipReader::ExtractChunk,
396 weak_ptr_factory_.GetWeakPtr(),
397 Passed(output_file.Pass()),
398 success_callback,
399 failure_callback,
400 progress_callback,
401 0 /* initial offset */));
404 bool ZipReader::ExtractCurrentEntryIntoDirectory(
405 const base::FilePath& output_directory_path) const {
406 DCHECK(current_entry_info_.get());
408 base::FilePath output_file_path = output_directory_path.Append(
409 current_entry_info()->file_path());
410 return ExtractCurrentEntryToFilePath(output_file_path);
413 bool ZipReader::ExtractCurrentEntryToFile(base::File* file) const {
414 DCHECK(zip_file_);
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;
421 FileWriterDelegate writer(file);
422 return ExtractCurrentEntry(&writer);
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);
431 if (current_entry_info()->is_directory()) {
432 output->clear();
433 return true;
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(static_cast<size_t>(std::min(
442 static_cast<int64>(max_read_bytes),
443 current_entry_info()->original_size())));
445 StringWriterDelegate writer(max_read_bytes, &contents);
446 if (!ExtractCurrentEntry(&writer))
447 return false;
448 output->swap(contents);
449 return true;
452 bool ZipReader::OpenInternal() {
453 DCHECK(zip_file_);
455 unz_global_info zip_info = {}; // Zero-clear.
456 if (unzGetGlobalInfo(zip_file_, &zip_info) != UNZ_OK) {
457 return false;
459 num_entries_ = zip_info.number_entry;
460 if (num_entries_ < 0)
461 return false;
463 // We are already at the end if the zip file is empty.
464 reached_end_ = (num_entries_ == 0);
465 return true;
468 void ZipReader::Reset() {
469 zip_file_ = NULL;
470 num_entries_ = 0;
471 reached_end_ = false;
472 current_entry_info_.reset();
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 offset) {
480 char buffer[internal::kZipBufSize];
482 const int num_bytes_read = unzReadCurrentFile(zip_file_,
483 buffer,
484 internal::kZipBufSize);
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;
500 int64 current_progress = offset + num_bytes_read;
502 progress_callback.Run(current_progress);
504 base::MessageLoop::current()->PostTask(
505 FROM_HERE,
506 base::Bind(&ZipReader::ExtractChunk,
507 weak_ptr_factory_.GetWeakPtr(),
508 Passed(output_file.Pass()),
509 success_callback,
510 failure_callback,
511 progress_callback,
512 current_progress));
517 // FileWriterDelegate ----------------------------------------------------------
519 FileWriterDelegate::FileWriterDelegate(base::File* file)
520 : file_(file),
521 file_length_(0) {
524 FileWriterDelegate::~FileWriterDelegate() {
525 #if !defined(NDEBUG)
526 const bool success =
527 #endif
528 file_->SetLength(file_length_);
529 DPLOG_IF(ERROR, !success) << "Failed updating length of written file";
532 bool FileWriterDelegate::PrepareOutput() {
533 return file_->Seek(base::File::FROM_BEGIN, 0) >= 0;
536 bool FileWriterDelegate::WriteBytes(const char* data, int num_bytes) {
537 int bytes_written = file_->WriteAtCurrentPos(data, num_bytes);
538 if (bytes_written > 0)
539 file_length_ += bytes_written;
540 return bytes_written == num_bytes;
543 } // namespace zip