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 <set> 6 #include <string> 7 #include <vector> 8 9 #include "base/files/file.h" 10 #include "base/files/file_enumerator.h" 11 #include "base/files/file_path.h" 12 #include "base/files/file_util.h" 13 #include "base/files/scoped_temp_dir.h" 14 #include "base/path_service.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/stringprintf.h" 17 #include "testing/gtest/include/gtest/gtest.h" 18 #include "testing/platform_test.h" 19 #include "third_party/zlib/google/zip.h" 20 #include "third_party/zlib/google/zip_reader.h" 21 22 namespace { 23 24 // Make the test a PlatformTest to setup autorelease pools properly on Mac. 25 class ZipTest : public PlatformTest { 26 protected: 27 enum ValidYearType { 28 VALID_YEAR, 29 INVALID_YEAR 30 }; 31 32 virtual void SetUp() { 33 PlatformTest::SetUp(); 34 35 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 36 test_dir_ = temp_dir_.path(); 37 38 base::FilePath zip_path(test_dir_); 39 zip_contents_.insert(zip_path.AppendASCII("foo.txt")); 40 zip_path = zip_path.AppendASCII("foo"); 41 zip_contents_.insert(zip_path); 42 zip_contents_.insert(zip_path.AppendASCII("bar.txt")); 43 zip_path = zip_path.AppendASCII("bar"); 44 zip_contents_.insert(zip_path); 45 zip_contents_.insert(zip_path.AppendASCII("baz.txt")); 46 zip_contents_.insert(zip_path.AppendASCII("quux.txt")); 47 zip_contents_.insert(zip_path.AppendASCII(".hidden")); 48 49 // Include a subset of files in |zip_file_list_| to test ZipFiles(). 50 zip_file_list_.push_back(base::FilePath(FILE_PATH_LITERAL("foo.txt"))); 51 zip_file_list_.push_back( 52 base::FilePath(FILE_PATH_LITERAL("foo/bar/quux.txt"))); 53 zip_file_list_.push_back( 54 base::FilePath(FILE_PATH_LITERAL("foo/bar/.hidden"))); 55 } 56 57 virtual void TearDown() { 58 PlatformTest::TearDown(); 59 } 60 61 bool GetTestDataDirectory(base::FilePath* path) { 62 bool success = PathService::Get(base::DIR_SOURCE_ROOT, path); 63 EXPECT_TRUE(success); 64 if (!success) 65 return false; 66 *path = path->AppendASCII("third_party"); 67 *path = path->AppendASCII("zlib"); 68 *path = path->AppendASCII("google"); 69 *path = path->AppendASCII("test"); 70 *path = path->AppendASCII("data"); 71 return true; 72 } 73 74 void TestUnzipFile(const base::FilePath::StringType& filename, 75 bool expect_hidden_files) { 76 base::FilePath test_dir; 77 ASSERT_TRUE(GetTestDataDirectory(&test_dir)); 78 TestUnzipFile(test_dir.Append(filename), expect_hidden_files); 79 } 80 81 void TestUnzipFile(const base::FilePath& path, bool expect_hidden_files) { 82 ASSERT_TRUE(base::PathExists(path)) << "no file " << path.value(); 83 ASSERT_TRUE(zip::Unzip(path, test_dir_)); 84 85 base::FileEnumerator files(test_dir_, true, 86 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); 87 base::FilePath next_path = files.Next(); 88 size_t count = 0; 89 while (!next_path.value().empty()) { 90 if (next_path.value().find(FILE_PATH_LITERAL(".svn")) == 91 base::FilePath::StringType::npos) { 92 EXPECT_EQ(zip_contents_.count(next_path), 1U) << 93 "Couldn't find " << next_path.value(); 94 count++; 95 } 96 next_path = files.Next(); 97 } 98 99 size_t expected_count = 0; 100 for (std::set<base::FilePath>::iterator iter = zip_contents_.begin(); 101 iter != zip_contents_.end(); ++iter) { 102 if (expect_hidden_files || iter->BaseName().value()[0] != '.') 103 ++expected_count; 104 } 105 106 EXPECT_EQ(expected_count, count); 107 } 108 109 // This function does the following: 110 // 1) Creates a test.txt file with the given last modification timestamp 111 // 2) Zips test.txt and extracts it back into a different location. 112 // 3) Confirms that test.txt in the output directory has the specified 113 // last modification timestamp if it is valid (|valid_year| is true). 114 // If the timestamp is not supported by the zip format, the last 115 // modification defaults to the current time. 116 void TestTimeStamp(const char* date_time, ValidYearType valid_year) { 117 SCOPED_TRACE(std::string("TestTimeStamp(") + date_time + ")"); 118 base::ScopedTempDir temp_dir; 119 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 120 121 base::FilePath zip_file = temp_dir.path().AppendASCII("out.zip"); 122 base::FilePath src_dir = temp_dir.path().AppendASCII("input"); 123 base::FilePath out_dir = temp_dir.path().AppendASCII("output"); 124 125 base::FilePath src_file = src_dir.AppendASCII("test.txt"); 126 base::FilePath out_file = out_dir.AppendASCII("test.txt"); 127 128 EXPECT_TRUE(base::CreateDirectory(src_dir)); 129 EXPECT_TRUE(base::CreateDirectory(out_dir)); 130 131 base::Time test_mtime; 132 ASSERT_TRUE(base::Time::FromString(date_time, &test_mtime)); 133 134 // Adjusting the current timestamp to the resolution that the zip file 135 // supports, which is 2 seconds. Note that between this call to Time::Now() 136 // and zip::Zip() the clock can advance a bit, hence the use of EXPECT_GE. 137 base::Time::Exploded now_parts; 138 base::Time::Now().LocalExplode(&now_parts); 139 now_parts.second = now_parts.second & ~1; 140 now_parts.millisecond = 0; 141 base::Time now_time = base::Time::FromLocalExploded(now_parts); 142 143 EXPECT_EQ(1, base::WriteFile(src_file, "1", 1)); 144 EXPECT_TRUE(base::TouchFile(src_file, base::Time::Now(), test_mtime)); 145 146 EXPECT_TRUE(zip::Zip(src_dir, zip_file, true)); 147 ASSERT_TRUE(zip::Unzip(zip_file, out_dir)); 148 149 base::File::Info file_info; 150 EXPECT_TRUE(base::GetFileInfo(out_file, &file_info)); 151 EXPECT_EQ(file_info.size, 1); 152 153 if (valid_year == VALID_YEAR) { 154 EXPECT_EQ(file_info.last_modified, test_mtime); 155 } else { 156 // Invalid date means the modification time will default to 'now'. 157 EXPECT_GE(file_info.last_modified, now_time); 158 } 159 } 160 161 // The path to temporary directory used to contain the test operations. 162 base::FilePath test_dir_; 163 164 base::ScopedTempDir temp_dir_; 165 166 // Hard-coded contents of a known zip file. 167 std::set<base::FilePath> zip_contents_; 168 169 // Hard-coded list of relative paths for a zip file created with ZipFiles. 170 std::vector<base::FilePath> zip_file_list_; 171 }; 172 173 TEST_F(ZipTest, Unzip) { 174 TestUnzipFile(FILE_PATH_LITERAL("test.zip"), true); 175 } 176 177 TEST_F(ZipTest, UnzipUncompressed) { 178 TestUnzipFile(FILE_PATH_LITERAL("test_nocompress.zip"), true); 179 } 180 181 TEST_F(ZipTest, UnzipEvil) { 182 base::FilePath path; 183 ASSERT_TRUE(GetTestDataDirectory(&path)); 184 path = path.AppendASCII("evil.zip"); 185 // Unzip the zip file into a sub directory of test_dir_ so evil.zip 186 // won't create a persistent file outside test_dir_ in case of a 187 // failure. 188 base::FilePath output_dir = test_dir_.AppendASCII("out"); 189 ASSERT_FALSE(zip::Unzip(path, output_dir)); 190 base::FilePath evil_file = output_dir; 191 evil_file = evil_file.AppendASCII( 192 "../levilevilevilevilevilevilevilevilevilevilevilevil"); 193 ASSERT_FALSE(base::PathExists(evil_file)); 194 } 195 196 TEST_F(ZipTest, UnzipEvil2) { 197 base::FilePath path; 198 ASSERT_TRUE(GetTestDataDirectory(&path)); 199 // The zip file contains an evil file with invalid UTF-8 in its file 200 // name. 201 path = path.AppendASCII("evil_via_invalid_utf8.zip"); 202 // See the comment at UnzipEvil() for why we do this. 203 base::FilePath output_dir = test_dir_.AppendASCII("out"); 204 // This should fail as it contains an evil file. 205 ASSERT_FALSE(zip::Unzip(path, output_dir)); 206 base::FilePath evil_file = output_dir; 207 evil_file = evil_file.AppendASCII("../evil.txt"); 208 ASSERT_FALSE(base::PathExists(evil_file)); 209 } 210 211 TEST_F(ZipTest, Zip) { 212 base::FilePath src_dir; 213 ASSERT_TRUE(GetTestDataDirectory(&src_dir)); 214 src_dir = src_dir.AppendASCII("test"); 215 216 base::ScopedTempDir temp_dir; 217 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 218 base::FilePath zip_file = temp_dir.path().AppendASCII("out.zip"); 219 220 EXPECT_TRUE(zip::Zip(src_dir, zip_file, true)); 221 TestUnzipFile(zip_file, true); 222 } 223 224 TEST_F(ZipTest, ZipIgnoreHidden) { 225 base::FilePath src_dir; 226 ASSERT_TRUE(GetTestDataDirectory(&src_dir)); 227 src_dir = src_dir.AppendASCII("test"); 228 229 base::ScopedTempDir temp_dir; 230 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 231 base::FilePath zip_file = temp_dir.path().AppendASCII("out.zip"); 232 233 EXPECT_TRUE(zip::Zip(src_dir, zip_file, false)); 234 TestUnzipFile(zip_file, false); 235 } 236 237 TEST_F(ZipTest, ZipNonASCIIDir) { 238 base::FilePath src_dir; 239 ASSERT_TRUE(GetTestDataDirectory(&src_dir)); 240 src_dir = src_dir.AppendASCII("test"); 241 242 base::ScopedTempDir temp_dir; 243 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 244 // Append '' (in cyrillic). 245 base::FilePath src_dir_russian = 246 temp_dir.path().Append(base::FilePath::FromUTF8Unsafe( 247 "\xD0\xA2\xD0\xB5\xD1\x81\xD1\x82")); 248 base::CopyDirectory(src_dir, src_dir_russian, true); 249 base::FilePath zip_file = temp_dir.path().AppendASCII("out_russian.zip"); 250 251 EXPECT_TRUE(zip::Zip(src_dir_russian, zip_file, true)); 252 TestUnzipFile(zip_file, true); 253 } 254 255 TEST_F(ZipTest, ZipTimeStamp) { 256 // The dates tested are arbitrary, with some constraints. The zip format can 257 // only store years from 1980 to 2107 and an even number of seconds, due to it 258 // using the ms dos date format. 259 260 // Valid arbitrary date. 261 TestTimeStamp("23 Oct 1997 23:22:20", VALID_YEAR); 262 263 // Date before 1980, zip format limitation, must default to unix epoch. 264 TestTimeStamp("29 Dec 1979 21:00:10", INVALID_YEAR); 265 266 // Despite the minizip headers telling the maximum year should be 2044, it 267 // can actually go up to 2107. Beyond that, the dos date format cannot store 268 // the year (2107-1980=127). To test that limit, the input file needs to be 269 // touched, but the code that modifies the file access and modification times 270 // relies on time_t which is defined as long, therefore being in many 271 // platforms just a 4-byte integer, like 32-bit Mac OSX or linux. As such, it 272 // suffers from the year-2038 bug. Therefore 2038 is the highest we can test 273 // in all platforms reliably. 274 TestTimeStamp("02 Jan 2038 23:59:58", VALID_YEAR); 275 } 276 277 #if defined(OS_POSIX) 278 TEST_F(ZipTest, ZipFiles) { 279 base::FilePath src_dir; 280 ASSERT_TRUE(GetTestDataDirectory(&src_dir)); 281 src_dir = src_dir.AppendASCII("test"); 282 283 base::ScopedTempDir temp_dir; 284 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 285 base::FilePath zip_name = temp_dir.path().AppendASCII("out.zip"); 286 287 base::File zip_file(zip_name, 288 base::File::FLAG_CREATE | base::File::FLAG_WRITE); 289 ASSERT_TRUE(zip_file.IsValid()); 290 EXPECT_TRUE(zip::ZipFiles(src_dir, zip_file_list_, 291 zip_file.GetPlatformFile())); 292 zip_file.Close(); 293 294 zip::ZipReader reader; 295 EXPECT_TRUE(reader.Open(zip_name)); 296 EXPECT_EQ(zip_file_list_.size(), static_cast<size_t>(reader.num_entries())); 297 for (size_t i = 0; i < zip_file_list_.size(); ++i) { 298 EXPECT_TRUE(reader.LocateAndOpenEntry(zip_file_list_[i])); 299 // Check the path in the entry just in case. 300 const zip::ZipReader::EntryInfo* entry_info = reader.current_entry_info(); 301 EXPECT_EQ(entry_info->file_path(), zip_file_list_[i]); 302 } 303 } 304 #endif // defined(OS_POSIX) 305 306 TEST_F(ZipTest, UnzipFilesWithIncorrectSize) { 307 base::FilePath test_data_folder; 308 ASSERT_TRUE(GetTestDataDirectory(&test_data_folder)); 309 310 // test_mismatch_size.zip contains files with names from 0.txt to 7.txt with 311 // sizes from 0 to 7 bytes respectively, but the metadata in the zip file says 312 // the uncompressed size is 3 bytes. The ZipReader and minizip code needs to 313 // be clever enough to get all the data out. 314 base::FilePath test_zip_file = 315 test_data_folder.AppendASCII("test_mismatch_size.zip"); 316 317 base::ScopedTempDir scoped_temp_dir; 318 ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir()); 319 const base::FilePath& temp_dir = scoped_temp_dir.path(); 320 321 ASSERT_TRUE(zip::Unzip(test_zip_file, temp_dir)); 322 EXPECT_TRUE(base::DirectoryExists(temp_dir.AppendASCII("d"))); 323 324 for (int i = 0; i < 8; i++) { 325 SCOPED_TRACE(base::StringPrintf("Processing %d.txt", i)); 326 base::FilePath file_path = temp_dir.AppendASCII( 327 base::StringPrintf("%d.txt", i)); 328 int64 file_size = -1; 329 EXPECT_TRUE(base::GetFileSize(file_path, &file_size)); 330 EXPECT_EQ(static_cast<int64>(i), file_size); 331 } 332 } 333 334 } // namespace 335