• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "base/file_util.h"
8 #include "base/logging.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "net/base/file_stream.h"
12 #include "third_party/zlib/google/zip_internal.h"
13 
14 #if defined(USE_SYSTEM_MINIZIP)
15 #include <minizip/unzip.h>
16 #else
17 #include "third_party/zlib/contrib/minizip/unzip.h"
18 #if defined(OS_WIN)
19 #include "third_party/zlib/contrib/minizip/iowin32.h"
20 #endif  // defined(OS_WIN)
21 #endif  // defined(USE_SYSTEM_MINIZIP)
22 
23 namespace zip {
24 
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.
EntryInfo(const std::string & file_name_in_zip,const unz_file_info & raw_file_info)28 ZipReader::EntryInfo::EntryInfo(const std::string& file_name_in_zip,
29                                 const unz_file_info& raw_file_info)
30     : file_path_(base::FilePath::FromUTF8Unsafe(file_name_in_zip)),
31       is_directory_(false) {
32   original_size_ = raw_file_info.uncompressed_size;
33 
34   // Directory entries in zip files end with "/".
35   is_directory_ = EndsWith(file_name_in_zip, "/", false);
36 
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;
40 
41   // We also consider that the file name is unsafe, if it's invalid UTF-8.
42   base::string16 file_name_utf16;
43   if (!UTF8ToUTF16(file_name_in_zip.data(), file_name_in_zip.size(),
44                    &file_name_utf16)) {
45     is_unsafe_ = true;
46   }
47 
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;
52 
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();
69   }
70 }
71 
ZipReader()72 ZipReader::ZipReader() {
73   Reset();
74 }
75 
~ZipReader()76 ZipReader::~ZipReader() {
77   Close();
78 }
79 
Open(const base::FilePath & zip_file_path)80 bool ZipReader::Open(const base::FilePath& zip_file_path) {
81   DCHECK(!zip_file_);
82 
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());
86   if (!zip_file_) {
87     return false;
88   }
89 
90   return OpenInternal();
91 }
92 
OpenFromPlatformFile(base::PlatformFile zip_fd)93 bool ZipReader::OpenFromPlatformFile(base::PlatformFile zip_fd) {
94   DCHECK(!zip_file_);
95 
96 #if defined(OS_POSIX)
97   zip_file_ = internal::OpenFdForUnzipping(zip_fd);
98 #elif defined(OS_WIN)
99   zip_file_ = internal::OpenHandleForUnzipping(zip_fd);
100 #endif
101   if (!zip_file_) {
102     return false;
103   }
104 
105   return OpenInternal();
106 }
107 
OpenFromString(const std::string & data)108 bool ZipReader::OpenFromString(const std::string& data) {
109   zip_file_ = internal::PreprareMemoryForUnzipping(data);
110   if (!zip_file_)
111     return false;
112   return OpenInternal();
113 }
114 
Close()115 void ZipReader::Close() {
116   if (zip_file_) {
117     unzClose(zip_file_);
118   }
119   Reset();
120 }
121 
HasMore()122 bool ZipReader::HasMore() {
123   return !reached_end_;
124 }
125 
AdvanceToNextEntry()126 bool ZipReader::AdvanceToNextEntry() {
127   DCHECK(zip_file_);
128 
129   // Should not go further if we already reached the end.
130   if (reached_end_)
131     return false;
132 
133   unz_file_pos position = {};
134   if (unzGetFilePos(zip_file_, &position) != UNZ_OK)
135     return false;
136   const int current_entry_index = position.num_of_file;
137   // If we are currently at the last entry, then the next position is the
138   // end of the zip file, so mark that we reached the end.
139   if (current_entry_index + 1 == num_entries_) {
140     reached_end_ = true;
141   } else {
142     DCHECK_LT(current_entry_index + 1, num_entries_);
143     if (unzGoToNextFile(zip_file_) != UNZ_OK) {
144       return false;
145     }
146   }
147   current_entry_info_.reset();
148   return true;
149 }
150 
OpenCurrentEntryInZip()151 bool ZipReader::OpenCurrentEntryInZip() {
152   DCHECK(zip_file_);
153 
154   unz_file_info raw_file_info = {};
155   char raw_file_name_in_zip[internal::kZipMaxPath] = {};
156   const int result = unzGetCurrentFileInfo(zip_file_,
157                                            &raw_file_info,
158                                            raw_file_name_in_zip,
159                                            sizeof(raw_file_name_in_zip) - 1,
160                                            NULL,  // extraField.
161                                            0,  // extraFieldBufferSize.
162                                            NULL,  // szComment.
163                                            0);  // commentBufferSize.
164   if (result != UNZ_OK)
165     return false;
166   if (raw_file_name_in_zip[0] == '\0')
167     return false;
168   current_entry_info_.reset(
169       new EntryInfo(raw_file_name_in_zip, raw_file_info));
170   return true;
171 }
172 
LocateAndOpenEntry(const base::FilePath & path_in_zip)173 bool ZipReader::LocateAndOpenEntry(const base::FilePath& path_in_zip) {
174   DCHECK(zip_file_);
175 
176   current_entry_info_.reset();
177   reached_end_ = false;
178   const int kDefaultCaseSensivityOfOS = 0;
179   const int result = unzLocateFile(zip_file_,
180                                    path_in_zip.AsUTF8Unsafe().c_str(),
181                                    kDefaultCaseSensivityOfOS);
182   if (result != UNZ_OK)
183     return false;
184 
185   // Then Open the entry.
186   return OpenCurrentEntryInZip();
187 }
188 
ExtractCurrentEntryToFilePath(const base::FilePath & output_file_path)189 bool ZipReader::ExtractCurrentEntryToFilePath(
190     const base::FilePath& output_file_path) {
191   DCHECK(zip_file_);
192 
193   // If this is a directory, just create it and return.
194   if (current_entry_info()->is_directory())
195     return base::CreateDirectory(output_file_path);
196 
197   const int open_result = unzOpenCurrentFile(zip_file_);
198   if (open_result != UNZ_OK)
199     return false;
200 
201   // We can't rely on parent directory entries being specified in the
202   // zip, so we make sure they are created.
203   base::FilePath output_dir_path = output_file_path.DirName();
204   if (!base::CreateDirectory(output_dir_path))
205     return false;
206 
207   net::FileStream stream(NULL);
208   const int flags = (base::PLATFORM_FILE_CREATE_ALWAYS |
209                      base::PLATFORM_FILE_WRITE);
210   if (stream.OpenSync(output_file_path, flags) != 0)
211     return false;
212 
213   bool success = true;  // This becomes false when something bad happens.
214   while (true) {
215     char buf[internal::kZipBufSize];
216     const int num_bytes_read = unzReadCurrentFile(zip_file_, buf,
217                                                   internal::kZipBufSize);
218     if (num_bytes_read == 0) {
219       // Reached the end of the file.
220       break;
221     } else if (num_bytes_read < 0) {
222       // If num_bytes_read < 0, then it's a specific UNZ_* error code.
223       success = false;
224       break;
225     } else if (num_bytes_read > 0) {
226       // Some data is read. Write it to the output file.
227       if (num_bytes_read != stream.WriteSync(buf, num_bytes_read)) {
228         success = false;
229         break;
230       }
231     }
232   }
233 
234   unzCloseCurrentFile(zip_file_);
235   return success;
236 }
237 
ExtractCurrentEntryIntoDirectory(const base::FilePath & output_directory_path)238 bool ZipReader::ExtractCurrentEntryIntoDirectory(
239     const base::FilePath& output_directory_path) {
240   DCHECK(current_entry_info_.get());
241 
242   base::FilePath output_file_path = output_directory_path.Append(
243       current_entry_info()->file_path());
244   return ExtractCurrentEntryToFilePath(output_file_path);
245 }
246 
247 #if defined(OS_POSIX)
ExtractCurrentEntryToFd(const int fd)248 bool ZipReader::ExtractCurrentEntryToFd(const int fd) {
249   DCHECK(zip_file_);
250 
251   // If this is a directory, there's nothing to extract to the file descriptor,
252   // so return false.
253   if (current_entry_info()->is_directory())
254     return false;
255 
256   const int open_result = unzOpenCurrentFile(zip_file_);
257   if (open_result != UNZ_OK)
258     return false;
259 
260   bool success = true;  // This becomes false when something bad happens.
261   while (true) {
262     char buf[internal::kZipBufSize];
263     const int num_bytes_read = unzReadCurrentFile(zip_file_, buf,
264                                                   internal::kZipBufSize);
265     if (num_bytes_read == 0) {
266       // Reached the end of the file.
267       break;
268     } else if (num_bytes_read < 0) {
269       // If num_bytes_read < 0, then it's a specific UNZ_* error code.
270       success = false;
271       break;
272     } else if (num_bytes_read > 0) {
273       // Some data is read. Write it to the output file descriptor.
274       if (num_bytes_read !=
275           file_util::WriteFileDescriptor(fd, buf, num_bytes_read)) {
276         success = false;
277         break;
278       }
279     }
280   }
281 
282   unzCloseCurrentFile(zip_file_);
283   return success;
284 }
285 #endif  // defined(OS_POSIX)
286 
OpenInternal()287 bool ZipReader::OpenInternal() {
288   DCHECK(zip_file_);
289 
290   unz_global_info zip_info = {};  // Zero-clear.
291   if (unzGetGlobalInfo(zip_file_, &zip_info) != UNZ_OK) {
292     return false;
293   }
294   num_entries_ = zip_info.number_entry;
295   if (num_entries_ < 0)
296     return false;
297 
298   // We are already at the end if the zip file is empty.
299   reached_end_ = (num_entries_ == 0);
300   return true;
301 }
302 
Reset()303 void ZipReader::Reset() {
304   zip_file_ = NULL;
305   num_entries_ = 0;
306   reached_end_ = false;
307   current_entry_info_.reset();
308 }
309 
310 }  // namespace zip
311