Home | History | Annotate | Download | only in common
      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