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 "chrome/common/zip_reader.h"
7 #include "base/file_util.h"
8 #include "base/logging.h"
9 #include "base/string_util.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/common/zip_internal.h"
12 #include "net/base/file_stream.h"
14 #if defined(USE_SYSTEM_MINIZIP)
15 #include <minizip/unzip.h>
17 #include "third_party/zlib/contrib/minizip/unzip.h"
19 #include "third_party/zlib/contrib/minizip/iowin32.h"
20 #endif // defined(OS_WIN)
21 #endif // defined(USE_SYSTEM_MINIZIP)
25 // TODO(satorux): The implementation assumes that file names in zip files
26 // are encoded in UTF-8. This is true for zip files created by Zip()
27 // function in zip.h, but not true for user-supplied random zip files.
28 ZipReader::EntryInfo::EntryInfo(const std::string
& file_name_in_zip
,
29 const unz_file_info
& raw_file_info
)
30 : file_path_(FilePath::FromUTF8Unsafe(file_name_in_zip
)),
31 is_directory_(false) {
32 original_size_
= raw_file_info
.uncompressed_size
;
34 // Directory entries in zip files end with "/".
35 is_directory_
= EndsWith(file_name_in_zip
, "/", false);
37 // Check the file name here for directory traversal issues. In the name of
38 // simplicity and security, we might reject a valid file name such as "a..b".
39 is_unsafe_
= file_name_in_zip
.find("..") != std::string::npos
;
41 // We also consider that the file name is unsafe, if it's invalid UTF-8.
42 string16 file_name_utf16
;
43 if (!UTF8ToUTF16(file_name_in_zip
.data(), file_name_in_zip
.size(),
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))
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
);
67 // Use Unix time epoch if the time stamp data is invalid.
68 last_modified_
= base::Time::UnixEpoch();
72 ZipReader::ZipReader() {
76 ZipReader::~ZipReader() {
80 bool ZipReader::Open(const FilePath
& zip_file_path
) {
83 // Use of "Unsafe" function does not look good, but there is no way to do
84 // this safely on Linux. See file_util.h for details.
85 zip_file_
= internal::OpenForUnzipping(zip_file_path
.AsUTF8Unsafe());
90 return OpenInternal();
94 bool ZipReader::OpenFromFd(const int zip_fd
) {
97 zip_file_
= internal::OpenFdForUnzipping(zip_fd
);
102 return OpenInternal();
106 bool ZipReader::OpenFromString(const std::string
& data
) {
107 zip_file_
= internal::PreprareMemoryForUnzipping(data
);
110 return OpenInternal();
113 void ZipReader::Close() {
120 bool ZipReader::HasMore() {
121 return !reached_end_
;
124 bool ZipReader::AdvanceToNextEntry() {
127 // Should not go further if we already reached the end.
131 unz_file_pos position
= {};
132 if (unzGetFilePos(zip_file_
, &position
) != UNZ_OK
)
134 const int current_entry_index
= position
.num_of_file
;
135 // If we are currently at the last entry, then the next position is the
136 // end of the zip file, so mark that we reached the end.
137 if (current_entry_index
+ 1 == num_entries_
) {
140 DCHECK_LT(current_entry_index
+ 1, num_entries_
);
141 if (unzGoToNextFile(zip_file_
) != UNZ_OK
) {
145 current_entry_info_
.reset();
149 bool ZipReader::OpenCurrentEntryInZip() {
152 unz_file_info raw_file_info
= {};
153 char raw_file_name_in_zip
[internal::kZipMaxPath
] = {};
154 const int result
= unzGetCurrentFileInfo(zip_file_
,
156 raw_file_name_in_zip
,
157 sizeof(raw_file_name_in_zip
) - 1,
159 0, // extraFieldBufferSize.
161 0); // commentBufferSize.
162 if (result
!= UNZ_OK
)
164 if (raw_file_name_in_zip
[0] == '\0')
166 current_entry_info_
.reset(
167 new EntryInfo(raw_file_name_in_zip
, raw_file_info
));
171 bool ZipReader::LocateAndOpenEntry(const FilePath
& path_in_zip
) {
174 current_entry_info_
.reset();
175 reached_end_
= false;
176 const int kDefaultCaseSensivityOfOS
= 0;
177 const int result
= unzLocateFile(zip_file_
,
178 path_in_zip
.AsUTF8Unsafe().c_str(),
179 kDefaultCaseSensivityOfOS
);
180 if (result
!= UNZ_OK
)
183 // Then Open the entry.
184 return OpenCurrentEntryInZip();
187 bool ZipReader::ExtractCurrentEntryToFilePath(
188 const FilePath
& output_file_path
) {
191 // If this is a directory, just create it and return.
192 if (current_entry_info()->is_directory())
193 return file_util::CreateDirectory(output_file_path
);
195 const int open_result
= unzOpenCurrentFile(zip_file_
);
196 if (open_result
!= UNZ_OK
)
199 // We can't rely on parent directory entries being specified in the
200 // zip, so we make sure they are created.
201 FilePath output_dir_path
= output_file_path
.DirName();
202 if (!file_util::CreateDirectory(output_dir_path
))
205 net::FileStream
stream(NULL
);
206 const int flags
= (base::PLATFORM_FILE_CREATE_ALWAYS
|
207 base::PLATFORM_FILE_WRITE
);
208 if (stream
.OpenSync(output_file_path
, flags
) != 0)
211 bool success
= true; // This becomes false when something bad happens.
213 char buf
[internal::kZipBufSize
];
214 const int num_bytes_read
= unzReadCurrentFile(zip_file_
, buf
,
215 internal::kZipBufSize
);
216 if (num_bytes_read
== 0) {
217 // Reached the end of the file.
219 } else if (num_bytes_read
< 0) {
220 // If num_bytes_read < 0, then it's a specific UNZ_* error code.
223 } else if (num_bytes_read
> 0) {
224 // Some data is read. Write it to the output file.
225 if (num_bytes_read
!= stream
.WriteSync(buf
, num_bytes_read
)) {
232 unzCloseCurrentFile(zip_file_
);
236 bool ZipReader::ExtractCurrentEntryIntoDirectory(
237 const FilePath
& output_directory_path
) {
238 DCHECK(current_entry_info_
.get());
240 FilePath output_file_path
= output_directory_path
.Append(
241 current_entry_info()->file_path());
242 return ExtractCurrentEntryToFilePath(output_file_path
);
245 #if defined(OS_POSIX)
246 bool ZipReader::ExtractCurrentEntryToFd(const int fd
) {
249 // If this is a directory, there's nothing to extract to the file descriptor,
251 if (current_entry_info()->is_directory())
254 const int open_result
= unzOpenCurrentFile(zip_file_
);
255 if (open_result
!= UNZ_OK
)
258 bool success
= true; // This becomes false when something bad happens.
260 char buf
[internal::kZipBufSize
];
261 const int num_bytes_read
= unzReadCurrentFile(zip_file_
, buf
,
262 internal::kZipBufSize
);
263 if (num_bytes_read
== 0) {
264 // Reached the end of the file.
266 } else if (num_bytes_read
< 0) {
267 // If num_bytes_read < 0, then it's a specific UNZ_* error code.
270 } else if (num_bytes_read
> 0) {
271 // Some data is read. Write it to the output file descriptor.
272 if (num_bytes_read
!=
273 file_util::WriteFileDescriptor(fd
, buf
, num_bytes_read
)) {
280 unzCloseCurrentFile(zip_file_
);
283 #endif // defined(OS_POSIX)
285 bool ZipReader::OpenInternal() {
288 unz_global_info zip_info
= {}; // Zero-clear.
289 if (unzGetGlobalInfo(zip_file_
, &zip_info
) != UNZ_OK
) {
292 num_entries_
= zip_info
.number_entry
;
293 if (num_entries_
< 0)
296 // We are already at the end if the zip file is empty.
297 reached_end_
= (num_entries_
== 0);
301 void ZipReader::Reset() {
304 reached_end_
= false;
305 current_entry_info_
.reset();