Home | History | Annotate | Download | only in google
      1 // Copyright (c) 2011 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 <set>
      8 #include <string>
      9 
     10 #include "base/file_util.h"
     11 #include "base/files/scoped_temp_dir.h"
     12 #include "base/logging.h"
     13 #include "base/md5.h"
     14 #include "base/path_service.h"
     15 #include "base/platform_file.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "base/time/time.h"
     18 #include "testing/gtest/include/gtest/gtest.h"
     19 #include "testing/platform_test.h"
     20 #include "third_party/zlib/google/zip_internal.h"
     21 
     22 namespace {
     23 
     24 // Wrap PlatformFiles in a class so that we don't leak them in tests.
     25 class PlatformFileWrapper {
     26  public:
     27   typedef enum {
     28     READ_ONLY,
     29     READ_WRITE
     30   } AccessMode;
     31 
     32   PlatformFileWrapper(const base::FilePath& file, AccessMode mode)
     33       : file_(base::kInvalidPlatformFileValue) {
     34     switch (mode) {
     35       case READ_ONLY:
     36         file_ = base::CreatePlatformFile(file,
     37                                          base::PLATFORM_FILE_OPEN |
     38                                          base::PLATFORM_FILE_READ,
     39                                          NULL, NULL);
     40         break;
     41       case READ_WRITE:
     42         file_ = base::CreatePlatformFile(file,
     43                                          base::PLATFORM_FILE_CREATE_ALWAYS |
     44                                          base::PLATFORM_FILE_READ |
     45                                          base::PLATFORM_FILE_WRITE,
     46                                          NULL, NULL);
     47         break;
     48       default:
     49         NOTREACHED();
     50     }
     51     return;
     52   }
     53 
     54   ~PlatformFileWrapper() {
     55     base::ClosePlatformFile(file_);
     56   }
     57 
     58   base::PlatformFile platform_file() { return file_; }
     59 
     60  private:
     61   base::PlatformFile file_;
     62 };
     63 
     64 }   // namespace
     65 
     66 namespace zip {
     67 
     68 // Make the test a PlatformTest to setup autorelease pools properly on Mac.
     69 class ZipReaderTest : public PlatformTest {
     70  protected:
     71   virtual void SetUp() {
     72     PlatformTest::SetUp();
     73 
     74     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     75     test_dir_ = temp_dir_.path();
     76 
     77     ASSERT_TRUE(GetTestDataDirectory(&test_data_dir_));
     78 
     79     test_zip_file_ = test_data_dir_.AppendASCII("test.zip");
     80     evil_zip_file_ = test_data_dir_.AppendASCII("evil.zip");
     81     evil_via_invalid_utf8_zip_file_ = test_data_dir_.AppendASCII(
     82         "evil_via_invalid_utf8.zip");
     83     evil_via_absolute_file_name_zip_file_ = test_data_dir_.AppendASCII(
     84         "evil_via_absolute_file_name.zip");
     85 
     86     test_zip_contents_.insert(base::FilePath(FILE_PATH_LITERAL("foo/")));
     87     test_zip_contents_.insert(base::FilePath(FILE_PATH_LITERAL("foo/bar/")));
     88     test_zip_contents_.insert(
     89         base::FilePath(FILE_PATH_LITERAL("foo/bar/baz.txt")));
     90     test_zip_contents_.insert(
     91         base::FilePath(FILE_PATH_LITERAL("foo/bar/quux.txt")));
     92     test_zip_contents_.insert(
     93         base::FilePath(FILE_PATH_LITERAL("foo/bar.txt")));
     94     test_zip_contents_.insert(base::FilePath(FILE_PATH_LITERAL("foo.txt")));
     95     test_zip_contents_.insert(
     96         base::FilePath(FILE_PATH_LITERAL("foo/bar/.hidden")));
     97   }
     98 
     99   virtual void TearDown() {
    100     PlatformTest::TearDown();
    101   }
    102 
    103   bool GetTestDataDirectory(base::FilePath* path) {
    104     bool success = PathService::Get(base::DIR_SOURCE_ROOT, path);
    105     EXPECT_TRUE(success);
    106     if (!success)
    107       return false;
    108     *path = path->AppendASCII("third_party");
    109     *path = path->AppendASCII("zlib");
    110     *path = path->AppendASCII("google");
    111     *path = path->AppendASCII("test");
    112     *path = path->AppendASCII("data");
    113     return true;
    114   }
    115 
    116   // The path to temporary directory used to contain the test operations.
    117   base::FilePath test_dir_;
    118   // The path to the test data directory where test.zip etc. are located.
    119   base::FilePath test_data_dir_;
    120   // The path to test.zip in the test data directory.
    121   base::FilePath test_zip_file_;
    122   // The path to evil.zip in the test data directory.
    123   base::FilePath evil_zip_file_;
    124   // The path to evil_via_invalid_utf8.zip in the test data directory.
    125   base::FilePath evil_via_invalid_utf8_zip_file_;
    126   // The path to evil_via_absolute_file_name.zip in the test data directory.
    127   base::FilePath evil_via_absolute_file_name_zip_file_;
    128   std::set<base::FilePath> test_zip_contents_;
    129 
    130   base::ScopedTempDir temp_dir_;
    131 };
    132 
    133 TEST_F(ZipReaderTest, Open_ValidZipFile) {
    134   ZipReader reader;
    135   ASSERT_TRUE(reader.Open(test_zip_file_));
    136 }
    137 
    138 TEST_F(ZipReaderTest, Open_ValidZipPlatformFile) {
    139   ZipReader reader;
    140   PlatformFileWrapper zip_fd_wrapper(test_zip_file_,
    141                                      PlatformFileWrapper::READ_ONLY);
    142   ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
    143 }
    144 
    145 TEST_F(ZipReaderTest, Open_NonExistentFile) {
    146   ZipReader reader;
    147   ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("nonexistent.zip")));
    148 }
    149 
    150 TEST_F(ZipReaderTest, Open_ExistentButNonZipFile) {
    151   ZipReader reader;
    152   ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("create_test_zip.sh")));
    153 }
    154 
    155 // Iterate through the contents in the test zip file, and compare that the
    156 // contents collected from the zip reader matches the expected contents.
    157 TEST_F(ZipReaderTest, Iteration) {
    158   std::set<base::FilePath> actual_contents;
    159   ZipReader reader;
    160   ASSERT_TRUE(reader.Open(test_zip_file_));
    161   while (reader.HasMore()) {
    162     ASSERT_TRUE(reader.OpenCurrentEntryInZip());
    163     actual_contents.insert(reader.current_entry_info()->file_path());
    164     ASSERT_TRUE(reader.AdvanceToNextEntry());
    165   }
    166   EXPECT_FALSE(reader.AdvanceToNextEntry());  // Shouldn't go further.
    167   EXPECT_EQ(test_zip_contents_.size(),
    168             static_cast<size_t>(reader.num_entries()));
    169   EXPECT_EQ(test_zip_contents_.size(), actual_contents.size());
    170   EXPECT_EQ(test_zip_contents_, actual_contents);
    171 }
    172 
    173 // Open the test zip file from a file descriptor, iterate through its contents,
    174 // and compare that they match the expected contents.
    175 TEST_F(ZipReaderTest, PlatformFileIteration) {
    176   std::set<base::FilePath> actual_contents;
    177   ZipReader reader;
    178   PlatformFileWrapper zip_fd_wrapper(test_zip_file_,
    179                                      PlatformFileWrapper::READ_ONLY);
    180   ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
    181   while (reader.HasMore()) {
    182     ASSERT_TRUE(reader.OpenCurrentEntryInZip());
    183     actual_contents.insert(reader.current_entry_info()->file_path());
    184     ASSERT_TRUE(reader.AdvanceToNextEntry());
    185   }
    186   EXPECT_FALSE(reader.AdvanceToNextEntry());  // Shouldn't go further.
    187   EXPECT_EQ(test_zip_contents_.size(),
    188             static_cast<size_t>(reader.num_entries()));
    189   EXPECT_EQ(test_zip_contents_.size(), actual_contents.size());
    190   EXPECT_EQ(test_zip_contents_, actual_contents);
    191 }
    192 
    193 TEST_F(ZipReaderTest, LocateAndOpenEntry_ValidFile) {
    194   std::set<base::FilePath> actual_contents;
    195   ZipReader reader;
    196   ASSERT_TRUE(reader.Open(test_zip_file_));
    197   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
    198   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    199   EXPECT_EQ(target_path, reader.current_entry_info()->file_path());
    200 }
    201 
    202 TEST_F(ZipReaderTest, LocateAndOpenEntry_NonExistentFile) {
    203   std::set<base::FilePath> actual_contents;
    204   ZipReader reader;
    205   ASSERT_TRUE(reader.Open(test_zip_file_));
    206   base::FilePath target_path(FILE_PATH_LITERAL("nonexistent.txt"));
    207   ASSERT_FALSE(reader.LocateAndOpenEntry(target_path));
    208   EXPECT_EQ(NULL, reader.current_entry_info());
    209 }
    210 
    211 TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_RegularFile) {
    212   ZipReader reader;
    213   ASSERT_TRUE(reader.Open(test_zip_file_));
    214   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
    215   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    216   ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath(
    217       test_dir_.AppendASCII("quux.txt")));
    218   // Read the output file ans compute the MD5.
    219   std::string output;
    220   ASSERT_TRUE(file_util::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
    221                                           &output));
    222   const std::string md5 = base::MD5String(output);
    223   const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
    224   EXPECT_EQ(kExpectedMD5, md5);
    225   // quux.txt should be larger than kZipBufSize so that we can exercise
    226   // the loop in ExtractCurrentEntry().
    227   EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
    228 }
    229 
    230 TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFilePath_RegularFile) {
    231   ZipReader reader;
    232   PlatformFileWrapper zip_fd_wrapper(test_zip_file_,
    233                                      PlatformFileWrapper::READ_ONLY);
    234   ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
    235   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
    236   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    237   ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath(
    238       test_dir_.AppendASCII("quux.txt")));
    239   // Read the output file and compute the MD5.
    240   std::string output;
    241   ASSERT_TRUE(file_util::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
    242                                           &output));
    243   const std::string md5 = base::MD5String(output);
    244   const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
    245   EXPECT_EQ(kExpectedMD5, md5);
    246   // quux.txt should be larger than kZipBufSize so that we can exercise
    247   // the loop in ExtractCurrentEntry().
    248   EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
    249 }
    250 
    251 #if defined(OS_POSIX)
    252 TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) {
    253   ZipReader reader;
    254   PlatformFileWrapper zip_fd_wrapper(test_zip_file_,
    255                                      PlatformFileWrapper::READ_ONLY);
    256   ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
    257   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
    258   base::FilePath out_path = test_dir_.AppendASCII("quux.txt");
    259   PlatformFileWrapper out_fd_w(out_path, PlatformFileWrapper::READ_WRITE);
    260   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    261   ASSERT_TRUE(reader.ExtractCurrentEntryToFd(out_fd_w.platform_file()));
    262   // Read the output file and compute the MD5.
    263   std::string output;
    264   ASSERT_TRUE(file_util::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
    265                                           &output));
    266   const std::string md5 = base::MD5String(output);
    267   const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
    268   EXPECT_EQ(kExpectedMD5, md5);
    269   // quux.txt should be larger than kZipBufSize so that we can exercise
    270   // the loop in ExtractCurrentEntry().
    271   EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
    272 }
    273 #endif
    274 
    275 TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_Directory) {
    276   ZipReader reader;
    277   ASSERT_TRUE(reader.Open(test_zip_file_));
    278   base::FilePath target_path(FILE_PATH_LITERAL("foo/"));
    279   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    280   ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath(
    281       test_dir_.AppendASCII("foo")));
    282   // The directory should be created.
    283   ASSERT_TRUE(base::DirectoryExists(test_dir_.AppendASCII("foo")));
    284 }
    285 
    286 TEST_F(ZipReaderTest, ExtractCurrentEntryIntoDirectory_RegularFile) {
    287   ZipReader reader;
    288   ASSERT_TRUE(reader.Open(test_zip_file_));
    289   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
    290   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    291   ASSERT_TRUE(reader.ExtractCurrentEntryIntoDirectory(test_dir_));
    292   // Sub directories should be created.
    293   ASSERT_TRUE(base::DirectoryExists(test_dir_.AppendASCII("foo/bar")));
    294   // And the file should be created.
    295   std::string output;
    296   ASSERT_TRUE(file_util::ReadFileToString(
    297       test_dir_.AppendASCII("foo/bar/quux.txt"), &output));
    298   const std::string md5 = base::MD5String(output);
    299   const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
    300   EXPECT_EQ(kExpectedMD5, md5);
    301 }
    302 
    303 TEST_F(ZipReaderTest, current_entry_info_RegularFile) {
    304   ZipReader reader;
    305   ASSERT_TRUE(reader.Open(test_zip_file_));
    306   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
    307   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    308   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
    309 
    310   EXPECT_EQ(target_path, current_entry_info->file_path());
    311   EXPECT_EQ(13527, current_entry_info->original_size());
    312 
    313   // The expected time stamp: 2009-05-29 06:22:20
    314   base::Time::Exploded exploded = {};  // Zero-clear.
    315   current_entry_info->last_modified().LocalExplode(&exploded);
    316   EXPECT_EQ(2009, exploded.year);
    317   EXPECT_EQ(5, exploded.month);
    318   EXPECT_EQ(29, exploded.day_of_month);
    319   EXPECT_EQ(6, exploded.hour);
    320   EXPECT_EQ(22, exploded.minute);
    321   EXPECT_EQ(20, exploded.second);
    322   EXPECT_EQ(0, exploded.millisecond);
    323 
    324   EXPECT_FALSE(current_entry_info->is_unsafe());
    325   EXPECT_FALSE(current_entry_info->is_directory());
    326 }
    327 
    328 TEST_F(ZipReaderTest, current_entry_info_DotDotFile) {
    329   ZipReader reader;
    330   ASSERT_TRUE(reader.Open(evil_zip_file_));
    331   base::FilePath target_path(FILE_PATH_LITERAL(
    332       "../levilevilevilevilevilevilevilevilevilevilevilevil"));
    333   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    334   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
    335   EXPECT_EQ(target_path, current_entry_info->file_path());
    336 
    337   // This file is unsafe because of ".." in the file name.
    338   EXPECT_TRUE(current_entry_info->is_unsafe());
    339   EXPECT_FALSE(current_entry_info->is_directory());
    340 }
    341 
    342 TEST_F(ZipReaderTest, current_entry_info_InvalidUTF8File) {
    343   ZipReader reader;
    344   ASSERT_TRUE(reader.Open(evil_via_invalid_utf8_zip_file_));
    345   // The evil file is the 2nd file in the zip file.
    346   // We cannot locate by the file name ".\x80.\\evil.txt",
    347   // as FilePath may internally convert the string.
    348   ASSERT_TRUE(reader.AdvanceToNextEntry());
    349   ASSERT_TRUE(reader.OpenCurrentEntryInZip());
    350   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
    351 
    352   // This file is unsafe because of invalid UTF-8 in the file name.
    353   EXPECT_TRUE(current_entry_info->is_unsafe());
    354   EXPECT_FALSE(current_entry_info->is_directory());
    355 }
    356 
    357 TEST_F(ZipReaderTest, current_entry_info_AbsoluteFile) {
    358   ZipReader reader;
    359   ASSERT_TRUE(reader.Open(evil_via_absolute_file_name_zip_file_));
    360   base::FilePath target_path(FILE_PATH_LITERAL("/evil.txt"));
    361   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    362   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
    363   EXPECT_EQ(target_path, current_entry_info->file_path());
    364 
    365   // This file is unsafe because of the absolute file name.
    366   EXPECT_TRUE(current_entry_info->is_unsafe());
    367   EXPECT_FALSE(current_entry_info->is_directory());
    368 }
    369 
    370 TEST_F(ZipReaderTest, current_entry_info_Directory) {
    371   ZipReader reader;
    372   ASSERT_TRUE(reader.Open(test_zip_file_));
    373   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/"));
    374   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    375   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
    376 
    377   EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("foo/bar/")),
    378             current_entry_info->file_path());
    379   // The directory size should be zero.
    380   EXPECT_EQ(0, current_entry_info->original_size());
    381 
    382   // The expected time stamp: 2009-05-31 15:49:52
    383   base::Time::Exploded exploded = {};  // Zero-clear.
    384   current_entry_info->last_modified().LocalExplode(&exploded);
    385   EXPECT_EQ(2009, exploded.year);
    386   EXPECT_EQ(5, exploded.month);
    387   EXPECT_EQ(31, exploded.day_of_month);
    388   EXPECT_EQ(15, exploded.hour);
    389   EXPECT_EQ(49, exploded.minute);
    390   EXPECT_EQ(52, exploded.second);
    391   EXPECT_EQ(0, exploded.millisecond);
    392 
    393   EXPECT_FALSE(current_entry_info->is_unsafe());
    394   EXPECT_TRUE(current_entry_info->is_directory());
    395 }
    396 
    397 // Verifies that the ZipReader class can extract a file from a zip archive
    398 // stored in memory. This test opens a zip archive in a std::string object,
    399 // extracts its content, and verifies the content is the same as the expected
    400 // text.
    401 TEST_F(ZipReaderTest, OpenFromString) {
    402   // A zip archive consisting of one file "test.txt", which is a 16-byte text
    403   // file that contains "This is a test.\n".
    404   const char kTestData[] =
    405       "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\xa4\x66\x24\x41\x13\xe8"
    406       "\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00\x1c\x00\x74\x65"
    407       "\x73\x74\x2e\x74\x78\x74\x55\x54\x09\x00\x03\x34\x89\x45\x50\x34"
    408       "\x89\x45\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13"
    409       "\x00\x00\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74"
    410       "\x2e\x0a\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\xa4\x66"
    411       "\x24\x41\x13\xe8\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00"
    412       "\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa4\x81\x00\x00\x00\x00"
    413       "\x74\x65\x73\x74\x2e\x74\x78\x74\x55\x54\x05\x00\x03\x34\x89\x45"
    414       "\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13\x00\x00"
    415       "\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4e\x00\x00\x00"
    416       "\x52\x00\x00\x00\x00\x00";
    417   std::string data(kTestData, arraysize(kTestData));
    418   ZipReader reader;
    419   ASSERT_TRUE(reader.OpenFromString(data));
    420   base::FilePath target_path(FILE_PATH_LITERAL("test.txt"));
    421   ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
    422   ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath(
    423       test_dir_.AppendASCII("test.txt")));
    424 
    425   std::string actual;
    426   ASSERT_TRUE(file_util::ReadFileToString(
    427       test_dir_.AppendASCII("test.txt"), &actual));
    428   EXPECT_EQ(std::string("This is a test.\n"), actual);
    429 }
    430 
    431 }  // namespace zip
    432