1 // Copyright (c) 2009 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 "chrome/common/zip.h" 6 7 #include "base/file_util.h" 8 #include "base/logging.h" 9 #include "base/string_split.h" 10 #include "base/string_util.h" 11 #include "base/utf_string_conversions.h" 12 #include "net/base/file_stream.h" 13 #include "third_party/zlib/contrib/minizip/unzip.h" 14 #include "third_party/zlib/contrib/minizip/zip.h" 15 #if defined(OS_WIN) 16 #include "third_party/zlib/contrib/minizip/iowin32.h" 17 #endif 18 19 static const int kZipMaxPath = 256; 20 static const int kZipBufSize = 8192; 21 22 // Extract the 'current' selected file from the zip into dest_dir. 23 // Output filename is stored in out_file. Returns true on success. 24 static bool ExtractCurrentFile(unzFile zip_file, 25 const FilePath& dest_dir) { 26 char filename_inzip[kZipMaxPath] = {0}; 27 unz_file_info file_info; 28 int err = unzGetCurrentFileInfo(zip_file, &file_info, filename_inzip, 29 sizeof(filename_inzip) - 1, NULL, 0, NULL, 0); 30 if (err != UNZ_OK) 31 return false; 32 if (filename_inzip[0] == '\0') 33 return false; 34 35 err = unzOpenCurrentFile(zip_file); 36 if (err != UNZ_OK) 37 return false; 38 39 FilePath::StringType filename; 40 std::vector<FilePath::StringType> filename_parts; 41 #if defined(OS_WIN) 42 filename = UTF8ToWide(filename_inzip); 43 #elif defined(OS_POSIX) 44 filename = filename_inzip; 45 #endif 46 47 // Check the filename here for directory traversal issues. In the name of 48 // simplicity and security, we might reject a valid filename such as "a..b". 49 if (filename.find(FILE_PATH_LITERAL("..")) != FilePath::StringType::npos) 50 return false; 51 52 base::SplitString(filename, '/', &filename_parts); 53 54 FilePath dest_file(dest_dir); 55 std::vector<FilePath::StringType>::iterator iter; 56 for (iter = filename_parts.begin(); iter != filename_parts.end(); ++iter) 57 dest_file = dest_file.Append(*iter); 58 59 // If this is a directory, just create it and return. 60 if (filename_inzip[strlen(filename_inzip) - 1] == '/') { 61 if (!file_util::CreateDirectory(dest_file)) 62 return false; 63 return true; 64 } 65 66 // We can't rely on parent directory entries being specified in the zip, so we 67 // make sure they are created. 68 FilePath dir = dest_file.DirName(); 69 if (!file_util::CreateDirectory(dir)) 70 return false; 71 72 net::FileStream stream; 73 int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE; 74 if (stream.Open(dest_file, flags) != 0) 75 return false; 76 77 bool ret = true; 78 int num_bytes = 0; 79 char buf[kZipBufSize]; 80 do { 81 num_bytes = unzReadCurrentFile(zip_file, buf, kZipBufSize); 82 if (num_bytes < 0) { 83 // If num_bytes < 0, then it's a specific UNZ_* error code. 84 // While we're not currently handling these codes specifically, save 85 // it away in case we want to in the future. 86 err = num_bytes; 87 break; 88 } 89 if (num_bytes > 0) { 90 if (num_bytes != stream.Write(buf, num_bytes, NULL)) { 91 ret = false; 92 break; 93 } 94 } 95 } while (num_bytes > 0); 96 97 stream.Close(); 98 if (err == UNZ_OK) 99 err = unzCloseCurrentFile(zip_file); 100 else 101 unzCloseCurrentFile(zip_file); // Don't lose the original error code. 102 if (err != UNZ_OK) 103 ret = false; 104 return ret; 105 } 106 107 #if defined(OS_WIN) 108 typedef struct { 109 HANDLE hf; 110 int error; 111 } WIN32FILE_IOWIN; 112 113 // This function is derived from third_party/minizip/iowin32.c. 114 // Its only difference is that it treats the char* as UTF8 and 115 // uses the Unicode version of CreateFile. 116 static void* ZipOpenFunc(void *opaque, const char* filename, int mode) { 117 DWORD desired_access, creation_disposition; 118 DWORD share_mode, flags_and_attributes; 119 HANDLE file = 0; 120 void* ret = NULL; 121 122 desired_access = share_mode = flags_and_attributes = 0; 123 124 if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) { 125 desired_access = GENERIC_READ; 126 creation_disposition = OPEN_EXISTING; 127 share_mode = FILE_SHARE_READ; 128 } else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) { 129 desired_access = GENERIC_WRITE | GENERIC_READ; 130 creation_disposition = OPEN_EXISTING; 131 } else if (mode & ZLIB_FILEFUNC_MODE_CREATE) { 132 desired_access = GENERIC_WRITE | GENERIC_READ; 133 creation_disposition = CREATE_ALWAYS; 134 } 135 136 std::wstring filename_wstr = UTF8ToWide(filename); 137 if ((filename != NULL) && (desired_access != 0)) { 138 file = CreateFile(filename_wstr.c_str(), desired_access, share_mode, 139 NULL, creation_disposition, flags_and_attributes, NULL); 140 } 141 142 if (file == INVALID_HANDLE_VALUE) 143 file = NULL; 144 145 if (file != NULL) { 146 WIN32FILE_IOWIN file_ret; 147 file_ret.hf = file; 148 file_ret.error = 0; 149 ret = malloc(sizeof(WIN32FILE_IOWIN)); 150 if (ret == NULL) 151 CloseHandle(file); 152 else 153 *(static_cast<WIN32FILE_IOWIN*>(ret)) = file_ret; 154 } 155 return ret; 156 } 157 #endif 158 159 bool Unzip(const FilePath& src_file, const FilePath& dest_dir) { 160 #if defined(OS_WIN) 161 zlib_filefunc_def zip_funcs; 162 fill_win32_filefunc(&zip_funcs); 163 zip_funcs.zopen_file = ZipOpenFunc; 164 #endif 165 166 #if defined(OS_POSIX) 167 std::string src_file_str = src_file.value(); 168 unzFile zip_file = unzOpen(src_file_str.c_str()); 169 #elif defined(OS_WIN) 170 std::string src_file_str = WideToUTF8(src_file.value()); 171 unzFile zip_file = unzOpen2(src_file_str.c_str(), &zip_funcs); 172 #endif 173 if (!zip_file) { 174 LOG(WARNING) << "couldn't create file " << src_file_str; 175 return false; 176 } 177 unz_global_info zip_info; 178 int err; 179 err = unzGetGlobalInfo(zip_file, &zip_info); 180 if (err != UNZ_OK) { 181 LOG(WARNING) << "couldn't open zip " << src_file_str; 182 return false; 183 } 184 bool ret = true; 185 for (unsigned int i = 0; i < zip_info.number_entry; ++i) { 186 if (!ExtractCurrentFile(zip_file, dest_dir)) { 187 ret = false; 188 break; 189 } 190 191 if (i + 1 < zip_info.number_entry) { 192 err = unzGoToNextFile(zip_file); 193 if (err != UNZ_OK) { 194 LOG(WARNING) << "error %d in unzGoToNextFile"; 195 ret = false; 196 break; 197 } 198 } 199 } 200 unzClose(zip_file); 201 return ret; 202 } 203 204 static bool AddFileToZip(zipFile zip_file, const FilePath& src_dir) { 205 net::FileStream stream; 206 int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ; 207 if (stream.Open(src_dir, flags) != 0) { 208 LOG(ERROR) << "Could not open stream for path " 209 << src_dir.value(); 210 return false; 211 } 212 213 int num_bytes; 214 char buf[kZipBufSize]; 215 do { 216 num_bytes = stream.Read(buf, kZipBufSize, NULL); 217 if (num_bytes > 0) { 218 if (ZIP_OK != zipWriteInFileInZip(zip_file, buf, num_bytes)) { 219 LOG(ERROR) << "Could not write data to zip for path " 220 << src_dir.value(); 221 return false; 222 } 223 } 224 } while (num_bytes > 0); 225 226 return true; 227 } 228 229 static bool AddEntryToZip(zipFile zip_file, const FilePath& path, 230 const FilePath& root_path) { 231 #if defined(OS_WIN) 232 std::string str_path = 233 WideToUTF8(path.value().substr(root_path.value().length() + 1)); 234 ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/"); 235 #else 236 std::string str_path = path.value().substr(root_path.value().length() + 1); 237 #endif 238 239 bool is_directory = file_util::DirectoryExists(path); 240 if (is_directory) 241 str_path += "/"; 242 243 if (ZIP_OK != zipOpenNewFileInZip( 244 zip_file, str_path.c_str(), 245 NULL, NULL, 0u, NULL, 0u, NULL, // file info, extrafield local, length, 246 // extrafield global, length, comment 247 Z_DEFLATED, Z_DEFAULT_COMPRESSION)) { 248 LOG(ERROR) << "Could not open zip file entry " << str_path; 249 return false; 250 } 251 252 bool success = true; 253 if (!is_directory) { 254 success = AddFileToZip(zip_file, path); 255 } 256 257 if (ZIP_OK != zipCloseFileInZip(zip_file)) { 258 LOG(ERROR) << "Could not close zip file entry " << str_path; 259 return false; 260 } 261 262 return success; 263 } 264 265 bool Zip(const FilePath& src_dir, const FilePath& dest_file, 266 bool include_hidden_files) { 267 DCHECK(file_util::DirectoryExists(src_dir)); 268 269 #if defined(OS_WIN) 270 zlib_filefunc_def zip_funcs; 271 fill_win32_filefunc(&zip_funcs); 272 zip_funcs.zopen_file = ZipOpenFunc; 273 #endif 274 275 #if defined(OS_POSIX) 276 std::string dest_file_str = dest_file.value(); 277 std::string src_dir_str = src_dir.value(); 278 zipFile zip_file = zipOpen(dest_file_str.c_str(), APPEND_STATUS_CREATE); 279 #elif defined(OS_WIN) 280 std::string dest_file_str = WideToUTF8(dest_file.value()); 281 zipFile zip_file = zipOpen2(dest_file_str.c_str(), APPEND_STATUS_CREATE, 282 NULL, // global comment 283 &zip_funcs); 284 #endif 285 286 if (!zip_file) { 287 LOG(WARNING) << "couldn't create file " << dest_file_str; 288 return false; 289 } 290 291 bool success = true; 292 file_util::FileEnumerator file_enumerator( 293 src_dir, true, // recursive 294 static_cast<file_util::FileEnumerator::FILE_TYPE>( 295 file_util::FileEnumerator::FILES | 296 file_util::FileEnumerator::DIRECTORIES)); 297 for (FilePath path = file_enumerator.Next(); !path.value().empty(); 298 path = file_enumerator.Next()) { 299 if (!include_hidden_files && path.BaseName().value()[0] == '.') 300 continue; 301 302 if (!AddEntryToZip(zip_file, path, src_dir)) { 303 success = false; 304 return false; 305 } 306 } 307 308 if (ZIP_OK != zipClose(zip_file, NULL)) { // global comment 309 LOG(ERROR) << "Error closing zip file " << dest_file_str; 310 return false; 311 } 312 313 return success; 314 } 315