blob: 09efd9a7a669ec0707853111f8394ed9998a61d9 [file] [log] [blame]
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +00001// 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"
avi@chromium.org5cb24772013-06-07 22:40:45 +000016#include "base/strings/utf_string_conversions.h"
avi@chromium.org47f1b552013-06-28 15:23:55 +000017#include "base/time/time.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000018#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;
brettw@chromium.org997408f2013-08-30 18:23:50 +0000220 ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
221 &output));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000222 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;
brettw@chromium.org997408f2013-08-30 18:23:50 +0000241 ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
242 &output));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000243 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;
brettw@chromium.org997408f2013-08-30 18:23:50 +0000264 ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
265 &output));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000266 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.
brettw@chromium.org9a352f62013-07-15 20:18:09 +0000283 ASSERT_TRUE(base::DirectoryExists(test_dir_.AppendASCII("foo")));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000284}
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.
brettw@chromium.org9a352f62013-07-15 20:18:09 +0000293 ASSERT_TRUE(base::DirectoryExists(test_dir_.AppendASCII("foo/bar")));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000294 // And the file should be created.
295 std::string output;
brettw@chromium.org997408f2013-08-30 18:23:50 +0000296 ASSERT_TRUE(base::ReadFileToString(
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000297 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;
brettw@chromium.org997408f2013-08-30 18:23:50 +0000426 ASSERT_TRUE(base::ReadFileToString(
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000427 test_dir_.AppendASCII("test.txt"), &actual));
428 EXPECT_EQ(std::string("This is a test.\n"), actual);
429}
430
431} // namespace zip