Revert 248827 "android: Migrate old content readback to use asyn..."
[chromium-blink-merge.git] / third_party / zlib / google / zip_reader.cc
blob7b7870ee981daf4287255dc50475890f094901e7
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/file_util.h"
8 #include "base/logging.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "net/base/file_stream.h"
13 #include "third_party/zlib/google/zip_internal.h"
15 #if defined(USE_SYSTEM_MINIZIP)
16 #include <minizip/unzip.h>
17 #else
18 #include "third_party/zlib/contrib/minizip/unzip.h"
19 #if defined(OS_WIN)
20 #include "third_party/zlib/contrib/minizip/iowin32.h"
21 #endif // defined(OS_WIN)
22 #endif // defined(USE_SYSTEM_MINIZIP)
24 namespace zip {
26 // TODO(satorux): The implementation assumes that file names in zip files
27 // are encoded in UTF-8. This is true for zip files created by Zip()
28 // function in zip.h, but not true for user-supplied random zip files.
29 ZipReader::EntryInfo::EntryInfo(const std::string& file_name_in_zip,
30 const unz_file_info& raw_file_info)
31 : file_path_(base::FilePath::FromUTF8Unsafe(file_name_in_zip)),
32 is_directory_(false) {
33 original_size_ = raw_file_info.uncompressed_size;
35 // Directory entries in zip files end with "/".
36 is_directory_ = EndsWith(file_name_in_zip, "/", false);
38 // Check the file name here for directory traversal issues.
39 is_unsafe_ = file_path_.ReferencesParent();
41 // We also consider that the file name is unsafe, if it's invalid UTF-8.
42 base::string16 file_name_utf16;
43 if (!base::UTF8ToUTF16(file_name_in_zip.data(), file_name_in_zip.size(),
44 &file_name_utf16)) {
45 is_unsafe_ = true;
48 // We also consider that the file name is unsafe, if it's absolute.
49 // On Windows, IsAbsolute() returns false for paths starting with "/".
50 if (file_path_.IsAbsolute() || StartsWithASCII(file_name_in_zip, "/", false))
51 is_unsafe_ = true;
53 // Construct the last modified time. The timezone info is not present in
54 // zip files, so we construct the time as local time.
55 base::Time::Exploded exploded_time = {}; // Zero-clear.
56 exploded_time.year = raw_file_info.tmu_date.tm_year;
57 // The month in zip file is 0-based, whereas ours is 1-based.
58 exploded_time.month = raw_file_info.tmu_date.tm_mon + 1;
59 exploded_time.day_of_month = raw_file_info.tmu_date.tm_mday;
60 exploded_time.hour = raw_file_info.tmu_date.tm_hour;
61 exploded_time.minute = raw_file_info.tmu_date.tm_min;
62 exploded_time.second = raw_file_info.tmu_date.tm_sec;
63 exploded_time.millisecond = 0;
64 if (exploded_time.HasValidValues()) {
65 last_modified_ = base::Time::FromLocalExploded(exploded_time);
66 } else {
67 // Use Unix time epoch if the time stamp data is invalid.
68 last_modified_ = base::Time::UnixEpoch();
72 ZipReader::ZipReader()
73 : weak_ptr_factory_(this) {
74 Reset();
77 ZipReader::~ZipReader() {
78 Close();
81 bool ZipReader::Open(const base::FilePath& zip_file_path) {
82 DCHECK(!zip_file_);
84 // Use of "Unsafe" function does not look good, but there is no way to do
85 // this safely on Linux. See file_util.h for details.
86 zip_file_ = internal::OpenForUnzipping(zip_file_path.AsUTF8Unsafe());
87 if (!zip_file_) {
88 return false;
91 return OpenInternal();
94 bool ZipReader::OpenFromPlatformFile(base::PlatformFile zip_fd) {
95 DCHECK(!zip_file_);
97 #if defined(OS_POSIX)
98 zip_file_ = internal::OpenFdForUnzipping(zip_fd);
99 #elif defined(OS_WIN)
100 zip_file_ = internal::OpenHandleForUnzipping(zip_fd);
101 #endif
102 if (!zip_file_) {
103 return false;
106 return OpenInternal();
109 bool ZipReader::OpenFromString(const std::string& data) {
110 zip_file_ = internal::PreprareMemoryForUnzipping(data);
111 if (!zip_file_)
112 return false;
113 return OpenInternal();
116 void ZipReader::Close() {
117 if (zip_file_) {
118 unzClose(zip_file_);
120 Reset();
123 bool ZipReader::HasMore() {
124 return !reached_end_;
127 bool ZipReader::AdvanceToNextEntry() {
128 DCHECK(zip_file_);
130 // Should not go further if we already reached the end.
131 if (reached_end_)
132 return false;
134 unz_file_pos position = {};
135 if (unzGetFilePos(zip_file_, &position) != UNZ_OK)
136 return false;
137 const int current_entry_index = position.num_of_file;
138 // If we are currently at the last entry, then the next position is the
139 // end of the zip file, so mark that we reached the end.
140 if (current_entry_index + 1 == num_entries_) {
141 reached_end_ = true;
142 } else {
143 DCHECK_LT(current_entry_index + 1, num_entries_);
144 if (unzGoToNextFile(zip_file_) != UNZ_OK) {
145 return false;
148 current_entry_info_.reset();
149 return true;
152 bool ZipReader::OpenCurrentEntryInZip() {
153 DCHECK(zip_file_);
155 unz_file_info raw_file_info = {};
156 char raw_file_name_in_zip[internal::kZipMaxPath] = {};
157 const int result = unzGetCurrentFileInfo(zip_file_,
158 &raw_file_info,
159 raw_file_name_in_zip,
160 sizeof(raw_file_name_in_zip) - 1,
161 NULL, // extraField.
162 0, // extraFieldBufferSize.
163 NULL, // szComment.
164 0); // commentBufferSize.
165 if (result != UNZ_OK)
166 return false;
167 if (raw_file_name_in_zip[0] == '\0')
168 return false;
169 current_entry_info_.reset(
170 new EntryInfo(raw_file_name_in_zip, raw_file_info));
171 return true;
174 bool ZipReader::LocateAndOpenEntry(const base::FilePath& path_in_zip) {
175 DCHECK(zip_file_);
177 current_entry_info_.reset();
178 reached_end_ = false;
179 const int kDefaultCaseSensivityOfOS = 0;
180 const int result = unzLocateFile(zip_file_,
181 path_in_zip.AsUTF8Unsafe().c_str(),
182 kDefaultCaseSensivityOfOS);
183 if (result != UNZ_OK)
184 return false;
186 // Then Open the entry.
187 return OpenCurrentEntryInZip();
190 bool ZipReader::ExtractCurrentEntryToFilePath(
191 const base::FilePath& output_file_path) {
192 DCHECK(zip_file_);
194 // If this is a directory, just create it and return.
195 if (current_entry_info()->is_directory())
196 return base::CreateDirectory(output_file_path);
198 const int open_result = unzOpenCurrentFile(zip_file_);
199 if (open_result != UNZ_OK)
200 return false;
202 // We can't rely on parent directory entries being specified in the
203 // zip, so we make sure they are created.
204 base::FilePath output_dir_path = output_file_path.DirName();
205 if (!base::CreateDirectory(output_dir_path))
206 return false;
208 net::FileStream stream(NULL);
209 const int flags = (base::PLATFORM_FILE_CREATE_ALWAYS |
210 base::PLATFORM_FILE_WRITE);
211 if (stream.OpenSync(output_file_path, flags) != 0)
212 return false;
214 bool success = true; // This becomes false when something bad happens.
215 while (true) {
216 char buf[internal::kZipBufSize];
217 const int num_bytes_read = unzReadCurrentFile(zip_file_, buf,
218 internal::kZipBufSize);
219 if (num_bytes_read == 0) {
220 // Reached the end of the file.
221 break;
222 } else if (num_bytes_read < 0) {
223 // If num_bytes_read < 0, then it's a specific UNZ_* error code.
224 success = false;
225 break;
226 } else if (num_bytes_read > 0) {
227 // Some data is read. Write it to the output file.
228 if (num_bytes_read != stream.WriteSync(buf, num_bytes_read)) {
229 success = false;
230 break;
235 stream.CloseSync();
236 unzCloseCurrentFile(zip_file_);
238 if (current_entry_info()->last_modified() != base::Time::UnixEpoch())
239 base::TouchFile(output_file_path,
240 base::Time::Now(),
241 current_entry_info()->last_modified());
243 return success;
246 void ZipReader::ExtractCurrentEntryToFilePathAsync(
247 const base::FilePath& output_file_path,
248 const SuccessCallback& success_callback,
249 const FailureCallback& failure_callback,
250 const ProgressCallback& progress_callback) {
251 DCHECK(zip_file_);
252 DCHECK(current_entry_info_.get());
254 // If this is a directory, just create it and return.
255 if (current_entry_info()->is_directory()) {
256 if (base::CreateDirectory(output_file_path)) {
257 base::MessageLoopProxy::current()->PostTask(FROM_HERE, success_callback);
258 } else {
259 DVLOG(1) << "Unzip failed: unable to create directory.";
260 base::MessageLoopProxy::current()->PostTask(FROM_HERE, failure_callback);
262 return;
265 if (unzOpenCurrentFile(zip_file_) != UNZ_OK) {
266 DVLOG(1) << "Unzip failed: unable to open current zip entry.";
267 base::MessageLoopProxy::current()->PostTask(FROM_HERE, failure_callback);
268 return;
271 base::FilePath output_dir_path = output_file_path.DirName();
272 if (!base::CreateDirectory(output_dir_path)) {
273 DVLOG(1) << "Unzip failed: unable to create containing directory.";
274 base::MessageLoopProxy::current()->PostTask(FROM_HERE, failure_callback);
275 return;
278 const int flags = (base::PLATFORM_FILE_CREATE_ALWAYS |
279 base::PLATFORM_FILE_WRITE);
280 bool created = false;
281 base::PlatformFileError platform_file_error;
282 base::PlatformFile output_file = CreatePlatformFile(output_file_path,
283 flags,
284 &created,
285 &platform_file_error);
287 if (platform_file_error != base::PLATFORM_FILE_OK) {
288 DVLOG(1) << "Unzip failed: unable to create platform file at "
289 << output_file_path.value();
290 base::MessageLoopProxy::current()->PostTask(FROM_HERE, failure_callback);
291 return;
294 base::MessageLoop::current()->PostTask(
295 FROM_HERE,
296 base::Bind(&ZipReader::ExtractChunk,
297 weak_ptr_factory_.GetWeakPtr(),
298 output_file,
299 success_callback,
300 failure_callback,
301 progress_callback,
302 0 /* initial offset */));
305 bool ZipReader::ExtractCurrentEntryIntoDirectory(
306 const base::FilePath& output_directory_path) {
307 DCHECK(current_entry_info_.get());
309 base::FilePath output_file_path = output_directory_path.Append(
310 current_entry_info()->file_path());
311 return ExtractCurrentEntryToFilePath(output_file_path);
314 #if defined(OS_POSIX)
315 bool ZipReader::ExtractCurrentEntryToFd(const int fd) {
316 DCHECK(zip_file_);
318 // If this is a directory, there's nothing to extract to the file descriptor,
319 // so return false.
320 if (current_entry_info()->is_directory())
321 return false;
323 const int open_result = unzOpenCurrentFile(zip_file_);
324 if (open_result != UNZ_OK)
325 return false;
327 bool success = true; // This becomes false when something bad happens.
328 while (true) {
329 char buf[internal::kZipBufSize];
330 const int num_bytes_read = unzReadCurrentFile(zip_file_, buf,
331 internal::kZipBufSize);
332 if (num_bytes_read == 0) {
333 // Reached the end of the file.
334 break;
335 } else if (num_bytes_read < 0) {
336 // If num_bytes_read < 0, then it's a specific UNZ_* error code.
337 success = false;
338 break;
339 } else if (num_bytes_read > 0) {
340 // Some data is read. Write it to the output file descriptor.
341 if (num_bytes_read !=
342 file_util::WriteFileDescriptor(fd, buf, num_bytes_read)) {
343 success = false;
344 break;
349 unzCloseCurrentFile(zip_file_);
350 return success;
352 #endif // defined(OS_POSIX)
354 bool ZipReader::OpenInternal() {
355 DCHECK(zip_file_);
357 unz_global_info zip_info = {}; // Zero-clear.
358 if (unzGetGlobalInfo(zip_file_, &zip_info) != UNZ_OK) {
359 return false;
361 num_entries_ = zip_info.number_entry;
362 if (num_entries_ < 0)
363 return false;
365 // We are already at the end if the zip file is empty.
366 reached_end_ = (num_entries_ == 0);
367 return true;
370 void ZipReader::Reset() {
371 zip_file_ = NULL;
372 num_entries_ = 0;
373 reached_end_ = false;
374 current_entry_info_.reset();
377 void ZipReader::ExtractChunk(base::PlatformFile output_file,
378 const SuccessCallback& success_callback,
379 const FailureCallback& failure_callback,
380 const ProgressCallback& progress_callback,
381 const int64 offset) {
382 char buffer[internal::kZipBufSize];
384 const int num_bytes_read = unzReadCurrentFile(zip_file_,
385 buffer,
386 internal::kZipBufSize);
388 if (num_bytes_read == 0) {
389 unzCloseCurrentFile(zip_file_);
390 base::ClosePlatformFile(output_file);
391 success_callback.Run();
392 } else if (num_bytes_read < 0) {
393 DVLOG(1) << "Unzip failed: error while reading zipfile "
394 << "(" << num_bytes_read << ")";
395 base::ClosePlatformFile(output_file);
396 failure_callback.Run();
397 } else {
398 if (num_bytes_read != base::WritePlatformFile(output_file,
399 offset,
400 buffer,
401 num_bytes_read)) {
402 DVLOG(1) << "Unzip failed: unable to write all bytes to target.";
403 base::ClosePlatformFile(output_file);
404 failure_callback.Run();
405 return;
408 int64 current_progress = offset + num_bytes_read;
410 progress_callback.Run(current_progress);
412 base::MessageLoop::current()->PostTask(
413 FROM_HERE,
414 base::Bind(&ZipReader::ExtractChunk,
415 weak_ptr_factory_.GetWeakPtr(),
416 output_file,
417 success_callback,
418 failure_callback,
419 progress_callback,
420 current_progress));
426 } // namespace zip