zip_reader_unittest.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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 22namespace { 23 24// Wrap PlatformFiles in a class so that we don't leak them in tests. 25class 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 66namespace zip { 67 68// Make the test a PlatformTest to setup autorelease pools properly on Mac. 69class 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 133TEST_F(ZipReaderTest, Open_ValidZipFile) { 134 ZipReader reader; 135 ASSERT_TRUE(reader.Open(test_zip_file_)); 136} 137 138TEST_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 145TEST_F(ZipReaderTest, Open_NonExistentFile) { 146 ZipReader reader; 147 ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("nonexistent.zip"))); 148} 149 150TEST_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. 157TEST_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. 175TEST_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 193TEST_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 202TEST_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 211TEST_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 230TEST_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) 252TEST_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 275TEST_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 286TEST_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 303TEST_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 328TEST_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 342TEST_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 357TEST_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 370TEST_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. 401TEST_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