1 // Copyright 2011 The Chromium Authors
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 <stddef.h>
8 #include <stdint.h>
9 #include <string.h>
10
11 #include <iterator>
12 #include <string>
13 #include <vector>
14
15 #include "base/check.h"
16 #include "base/files/file.h"
17 #include "base/files/file_path.h"
18 #include "base/files/file_util.h"
19 #include "base/files/scoped_temp_dir.h"
20 #include "base/functional/bind.h"
21 #include "base/hash/md5.h"
22 #include "base/i18n/time_formatting.h"
23 #include "base/path_service.h"
24 #include "base/run_loop.h"
25 #include "base/strings/string_piece.h"
26 #include "base/strings/stringprintf.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "base/test/bind.h"
29 #include "base/test/task_environment.h"
30 #include "base/time/time.h"
31 #include "build/build_config.h"
32 #include "testing/gmock/include/gmock/gmock.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "testing/platform_test.h"
35 #include "third_party/icu/source/i18n/unicode/timezone.h"
36 #include "third_party/zlib/google/zip_internal.h"
37
38 using ::testing::_;
39 using ::testing::ElementsAre;
40 using ::testing::ElementsAreArray;
41 using ::testing::Return;
42 using ::testing::SizeIs;
43
44 namespace {
45
46 const static std::string kQuuxExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
47
48 class FileWrapper {
49 public:
50 typedef enum { READ_ONLY, READ_WRITE } AccessMode;
51
FileWrapper(const base::FilePath & path,AccessMode mode)52 FileWrapper(const base::FilePath& path, AccessMode mode) {
53 int flags = base::File::FLAG_READ;
54 if (mode == READ_ONLY)
55 flags |= base::File::FLAG_OPEN;
56 else
57 flags |= base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS;
58
59 file_.Initialize(path, flags);
60 }
61
~FileWrapper()62 ~FileWrapper() {}
63
platform_file()64 base::PlatformFile platform_file() { return file_.GetPlatformFile(); }
65
file()66 base::File* file() { return &file_; }
67
68 private:
69 base::File file_;
70 };
71
72 // A mock that provides methods that can be used as callbacks in asynchronous
73 // unzip functions. Tracks the number of calls and number of bytes reported.
74 // Assumes that progress callbacks will be executed in-order.
75 class MockUnzipListener final {
76 public:
MockUnzipListener()77 MockUnzipListener()
78 : success_calls_(0),
79 failure_calls_(0),
80 progress_calls_(0),
81 current_progress_(0) {}
82
83 // Success callback for async functions.
OnUnzipSuccess()84 void OnUnzipSuccess() { success_calls_++; }
85
86 // Failure callback for async functions.
OnUnzipFailure()87 void OnUnzipFailure() { failure_calls_++; }
88
89 // Progress callback for async functions.
OnUnzipProgress(int64_t progress)90 void OnUnzipProgress(int64_t progress) {
91 DCHECK(progress > current_progress_);
92 progress_calls_++;
93 current_progress_ = progress;
94 }
95
success_calls()96 int success_calls() { return success_calls_; }
failure_calls()97 int failure_calls() { return failure_calls_; }
progress_calls()98 int progress_calls() { return progress_calls_; }
current_progress()99 int current_progress() { return current_progress_; }
100
AsWeakPtr()101 base::WeakPtr<MockUnzipListener> AsWeakPtr() {
102 return weak_ptr_factory_.GetWeakPtr();
103 }
104
105 private:
106 int success_calls_;
107 int failure_calls_;
108 int progress_calls_;
109
110 int64_t current_progress_;
111
112 base::WeakPtrFactory<MockUnzipListener> weak_ptr_factory_{this};
113 };
114
115 class MockWriterDelegate : public zip::WriterDelegate {
116 public:
117 MOCK_METHOD0(PrepareOutput, bool());
118 MOCK_METHOD2(WriteBytes, bool(const char*, int));
119 MOCK_METHOD1(SetTimeModified, void(const base::Time&));
120 MOCK_METHOD1(SetPosixFilePermissions, void(int));
121 MOCK_METHOD0(OnError, void());
122 };
123
ExtractCurrentEntryToFilePath(zip::ZipReader * reader,base::FilePath path)124 bool ExtractCurrentEntryToFilePath(zip::ZipReader* reader,
125 base::FilePath path) {
126 zip::FilePathWriterDelegate writer(path);
127 return reader->ExtractCurrentEntry(&writer);
128 }
129
LocateAndOpenEntry(zip::ZipReader * const reader,const base::FilePath & path_in_zip)130 const zip::ZipReader::Entry* LocateAndOpenEntry(
131 zip::ZipReader* const reader,
132 const base::FilePath& path_in_zip) {
133 DCHECK(reader);
134 EXPECT_TRUE(reader->ok());
135
136 // The underlying library can do O(1) access, but ZipReader does not expose
137 // that. O(N) access is acceptable for these tests.
138 while (const zip::ZipReader::Entry* const entry = reader->Next()) {
139 EXPECT_TRUE(reader->ok());
140 if (entry->path == path_in_zip)
141 return entry;
142 }
143
144 EXPECT_TRUE(reader->ok());
145 return nullptr;
146 }
147
148 using Paths = std::vector<base::FilePath>;
149
150 } // namespace
151
152 namespace zip {
153
154 // Make the test a PlatformTest to setup autorelease pools properly on Mac.
155 class ZipReaderTest : public PlatformTest {
156 protected:
SetUp()157 void SetUp() override {
158 PlatformTest::SetUp();
159
160 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
161 test_dir_ = temp_dir_.GetPath();
162 }
163
GetTestDataDirectory()164 static base::FilePath GetTestDataDirectory() {
165 base::FilePath path;
166 CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &path));
167 return path.AppendASCII("third_party")
168 .AppendASCII("zlib")
169 .AppendASCII("google")
170 .AppendASCII("test")
171 .AppendASCII("data");
172 }
173
GetPaths(const base::FilePath & zip_path,base::StringPiece encoding={})174 static Paths GetPaths(const base::FilePath& zip_path,
175 base::StringPiece encoding = {}) {
176 Paths paths;
177
178 if (ZipReader reader; reader.Open(zip_path)) {
179 if (!encoding.empty())
180 reader.SetEncoding(std::string(encoding));
181
182 while (const ZipReader::Entry* const entry = reader.Next()) {
183 EXPECT_TRUE(reader.ok());
184 paths.push_back(entry->path);
185 }
186
187 EXPECT_TRUE(reader.ok());
188 }
189
190 return paths;
191 }
192
193 // The path to temporary directory used to contain the test operations.
194 base::FilePath test_dir_;
195 // The path to the test data directory where test.zip etc. are located.
196 const base::FilePath data_dir_ = GetTestDataDirectory();
197 // The path to test.zip in the test data directory.
198 const base::FilePath test_zip_file_ = data_dir_.AppendASCII("test.zip");
199 const Paths test_zip_contents_ = {
200 base::FilePath(FILE_PATH_LITERAL("foo/")),
201 base::FilePath(FILE_PATH_LITERAL("foo/bar/")),
202 base::FilePath(FILE_PATH_LITERAL("foo/bar/baz.txt")),
203 base::FilePath(FILE_PATH_LITERAL("foo/bar/quux.txt")),
204 base::FilePath(FILE_PATH_LITERAL("foo/bar.txt")),
205 base::FilePath(FILE_PATH_LITERAL("foo.txt")),
206 base::FilePath(FILE_PATH_LITERAL("foo/bar/.hidden")),
207 };
208 base::ScopedTempDir temp_dir_;
209 base::test::TaskEnvironment task_environment_;
210 };
211
TEST_F(ZipReaderTest,Open_ValidZipFile)212 TEST_F(ZipReaderTest, Open_ValidZipFile) {
213 ZipReader reader;
214 EXPECT_TRUE(reader.Open(test_zip_file_));
215 EXPECT_TRUE(reader.ok());
216 }
217
TEST_F(ZipReaderTest,Open_ValidZipPlatformFile)218 TEST_F(ZipReaderTest, Open_ValidZipPlatformFile) {
219 ZipReader reader;
220 EXPECT_FALSE(reader.ok());
221 FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
222 EXPECT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
223 EXPECT_TRUE(reader.ok());
224 }
225
TEST_F(ZipReaderTest,Open_NonExistentFile)226 TEST_F(ZipReaderTest, Open_NonExistentFile) {
227 ZipReader reader;
228 EXPECT_FALSE(reader.ok());
229 EXPECT_FALSE(reader.Open(data_dir_.AppendASCII("nonexistent.zip")));
230 EXPECT_FALSE(reader.ok());
231 }
232
TEST_F(ZipReaderTest,Open_ExistentButNonZipFile)233 TEST_F(ZipReaderTest, Open_ExistentButNonZipFile) {
234 ZipReader reader;
235 EXPECT_FALSE(reader.ok());
236 EXPECT_FALSE(reader.Open(data_dir_.AppendASCII("create_test_zip.sh")));
237 EXPECT_FALSE(reader.ok());
238 }
239
TEST_F(ZipReaderTest,Open_EmptyFile)240 TEST_F(ZipReaderTest, Open_EmptyFile) {
241 ZipReader reader;
242 EXPECT_FALSE(reader.ok());
243 EXPECT_TRUE(reader.Open(data_dir_.AppendASCII("empty.zip")));
244 EXPECT_TRUE(reader.ok());
245 EXPECT_EQ(0, reader.num_entries());
246 EXPECT_EQ(nullptr, reader.Next());
247 }
248
249 // Iterate through the contents in the test ZIP archive, and compare that the
250 // contents collected from the ZipReader matches the expected contents.
TEST_F(ZipReaderTest,Iteration)251 TEST_F(ZipReaderTest, Iteration) {
252 Paths actual_contents;
253 ZipReader reader;
254 EXPECT_FALSE(reader.ok());
255 EXPECT_TRUE(reader.Open(test_zip_file_));
256 EXPECT_TRUE(reader.ok());
257 while (const ZipReader::Entry* const entry = reader.Next()) {
258 EXPECT_TRUE(reader.ok());
259 actual_contents.push_back(entry->path);
260 }
261
262 EXPECT_TRUE(reader.ok());
263 EXPECT_FALSE(reader.Next()); // Shouldn't go further.
264 EXPECT_TRUE(reader.ok());
265
266 EXPECT_THAT(actual_contents, SizeIs(reader.num_entries()));
267 EXPECT_THAT(actual_contents, ElementsAreArray(test_zip_contents_));
268 }
269
270 // Open the test ZIP archive from a file descriptor, iterate through its
271 // contents, and compare that they match the expected contents.
TEST_F(ZipReaderTest,PlatformFileIteration)272 TEST_F(ZipReaderTest, PlatformFileIteration) {
273 Paths actual_contents;
274 ZipReader reader;
275 FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
276 EXPECT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
277 EXPECT_TRUE(reader.ok());
278 while (const ZipReader::Entry* const entry = reader.Next()) {
279 EXPECT_TRUE(reader.ok());
280 actual_contents.push_back(entry->path);
281 }
282
283 EXPECT_TRUE(reader.ok());
284 EXPECT_FALSE(reader.Next()); // Shouldn't go further.
285 EXPECT_TRUE(reader.ok());
286
287 EXPECT_THAT(actual_contents, SizeIs(reader.num_entries()));
288 EXPECT_THAT(actual_contents, ElementsAreArray(test_zip_contents_));
289 }
290
TEST_F(ZipReaderTest,RegularFile)291 TEST_F(ZipReaderTest, RegularFile) {
292 ZipReader reader;
293 ASSERT_TRUE(reader.Open(test_zip_file_));
294 base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
295
296 const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
297 ASSERT_TRUE(entry);
298
299 EXPECT_EQ(target_path, entry->path);
300 EXPECT_EQ(13527, entry->original_size);
301 EXPECT_EQ("2009-05-29 06:22:20.000",
302 base::UnlocalizedTimeFormatWithPattern(entry->last_modified,
303 "y-MM-dd HH:mm:ss.SSS",
304 icu::TimeZone::getGMT()));
305 EXPECT_FALSE(entry->is_unsafe);
306 EXPECT_FALSE(entry->is_directory);
307 }
308
TEST_F(ZipReaderTest,DotDotFile)309 TEST_F(ZipReaderTest, DotDotFile) {
310 ZipReader reader;
311 ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("evil.zip")));
312 base::FilePath target_path(FILE_PATH_LITERAL(
313 "UP/levilevilevilevilevilevilevilevilevilevilevilevil"));
314 const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
315 ASSERT_TRUE(entry);
316 EXPECT_EQ(target_path, entry->path);
317 EXPECT_FALSE(entry->is_unsafe);
318 EXPECT_FALSE(entry->is_directory);
319 }
320
TEST_F(ZipReaderTest,InvalidUTF8File)321 TEST_F(ZipReaderTest, InvalidUTF8File) {
322 ZipReader reader;
323 ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("evil_via_invalid_utf8.zip")));
324 base::FilePath target_path = base::FilePath::FromUTF8Unsafe(".�.�evil.txt");
325 const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
326 ASSERT_TRUE(entry);
327 EXPECT_EQ(target_path, entry->path);
328 EXPECT_FALSE(entry->is_unsafe);
329 EXPECT_FALSE(entry->is_directory);
330 }
331
332 // By default, file paths in ZIPs are interpreted as UTF-8. But in this test,
333 // the ZIP archive contains file paths that are actually encoded in Shift JIS.
334 // The SJIS-encoded paths are thus wrongly interpreted as UTF-8, resulting in
335 // garbled paths. Invalid UTF-8 sequences are safely converted to the
336 // replacement character �.
TEST_F(ZipReaderTest,EncodingSjisAsUtf8)337 TEST_F(ZipReaderTest, EncodingSjisAsUtf8) {
338 EXPECT_THAT(
339 GetPaths(data_dir_.AppendASCII("SJIS Bug 846195.zip")),
340 ElementsAre(
341 base::FilePath::FromUTF8Unsafe("�V�����t�H���_/SJIS_835C_��.txt"),
342 base::FilePath::FromUTF8Unsafe(
343 "�V�����t�H���_/�V�����e�L�X�g �h�L�������g.txt")));
344 }
345
346 // In this test, SJIS-encoded paths are interpreted as Code Page 1252. This
347 // results in garbled paths. Note the presence of C1 control codes U+0090 and
348 // U+0081 in the garbled paths.
TEST_F(ZipReaderTest,EncodingSjisAs1252)349 TEST_F(ZipReaderTest, EncodingSjisAs1252) {
350 EXPECT_THAT(
351 GetPaths(data_dir_.AppendASCII("SJIS Bug 846195.zip"), "windows-1252"),
352 ElementsAre(base::FilePath::FromUTF8Unsafe(
353 "\u0090V‚µ‚¢ƒtƒHƒ‹ƒ_/SJIS_835C_ƒ�.txt"),
354 base::FilePath::FromUTF8Unsafe(
355 "\u0090V‚µ‚¢ƒtƒHƒ‹ƒ_/\u0090V‚µ‚¢ƒeƒLƒXƒg "
356 "ƒhƒLƒ…ƒ\u0081ƒ“ƒg.txt")));
357 }
358
359 // In this test, SJIS-encoded paths are interpreted as Code Page 866. This
360 // results in garbled paths.
TEST_F(ZipReaderTest,EncodingSjisAsIbm866)361 TEST_F(ZipReaderTest, EncodingSjisAsIbm866) {
362 EXPECT_THAT(
363 GetPaths(data_dir_.AppendASCII("SJIS Bug 846195.zip"), "IBM866"),
364 ElementsAre(
365 base::FilePath::FromUTF8Unsafe("РVВ╡ВвГtГHГЛГ_/SJIS_835C_Г�.txt"),
366 base::FilePath::FromUTF8Unsafe(
367 "РVВ╡ВвГtГHГЛГ_/РVВ╡ВвГeГLГXГg ГhГLГЕГБГУГg.txt")));
368 }
369
370 // Tests that SJIS-encoded paths are correctly converted to Unicode.
TEST_F(ZipReaderTest,EncodingSjis)371 TEST_F(ZipReaderTest, EncodingSjis) {
372 EXPECT_THAT(
373 GetPaths(data_dir_.AppendASCII("SJIS Bug 846195.zip"), "Shift_JIS"),
374 ElementsAre(
375 base::FilePath::FromUTF8Unsafe("新しいフォルダ/SJIS_835C_ソ.txt"),
376 base::FilePath::FromUTF8Unsafe(
377 "新しいフォルダ/新しいテキスト ドキュメント.txt")));
378 }
379
TEST_F(ZipReaderTest,AbsoluteFile)380 TEST_F(ZipReaderTest, AbsoluteFile) {
381 ZipReader reader;
382 ASSERT_TRUE(
383 reader.Open(data_dir_.AppendASCII("evil_via_absolute_file_name.zip")));
384 base::FilePath target_path(FILE_PATH_LITERAL("ROOT/evil.txt"));
385 const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
386 ASSERT_TRUE(entry);
387 EXPECT_EQ(target_path, entry->path);
388 EXPECT_FALSE(entry->is_unsafe);
389 EXPECT_FALSE(entry->is_directory);
390 }
391
TEST_F(ZipReaderTest,Directory)392 TEST_F(ZipReaderTest, Directory) {
393 ZipReader reader;
394 ASSERT_TRUE(reader.Open(test_zip_file_));
395 base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/"));
396 const ZipReader::Entry* entry = LocateAndOpenEntry(&reader, target_path);
397 ASSERT_TRUE(entry);
398 EXPECT_EQ(target_path, entry->path);
399 // The directory size should be zero.
400 EXPECT_EQ(0, entry->original_size);
401 EXPECT_EQ("2009-05-31 15:49:52.000",
402 base::UnlocalizedTimeFormatWithPattern(entry->last_modified,
403 "y-MM-dd HH:mm:ss.SSS",
404 icu::TimeZone::getGMT()));
405 EXPECT_FALSE(entry->is_unsafe);
406 EXPECT_TRUE(entry->is_directory);
407 }
408
TEST_F(ZipReaderTest,EncryptedFile_WrongPassword)409 TEST_F(ZipReaderTest, EncryptedFile_WrongPassword) {
410 ZipReader reader;
411 ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Different Encryptions.zip")));
412 reader.SetPassword("wrong password");
413
414 {
415 const ZipReader::Entry* entry = reader.Next();
416 ASSERT_TRUE(entry);
417 EXPECT_EQ(base::FilePath::FromASCII("ClearText.txt"), entry->path);
418 EXPECT_FALSE(entry->is_directory);
419 EXPECT_FALSE(entry->is_encrypted);
420 std::string contents = "dummy";
421 EXPECT_TRUE(reader.ExtractCurrentEntryToString(&contents));
422 EXPECT_EQ("This is not encrypted.\n", contents);
423 }
424
425 for (const base::StringPiece path : {
426 "Encrypted AES-128.txt",
427 "Encrypted AES-192.txt",
428 "Encrypted AES-256.txt",
429 "Encrypted ZipCrypto.txt",
430 }) {
431 const ZipReader::Entry* entry = reader.Next();
432 ASSERT_TRUE(entry);
433 EXPECT_EQ(base::FilePath::FromASCII(path), entry->path);
434 EXPECT_FALSE(entry->is_directory);
435 EXPECT_TRUE(entry->is_encrypted);
436 std::string contents = "dummy";
437 EXPECT_FALSE(reader.ExtractCurrentEntryToString(&contents));
438 }
439
440 EXPECT_FALSE(reader.Next());
441 EXPECT_TRUE(reader.ok());
442 }
443
TEST_F(ZipReaderTest,EncryptedFile_RightPassword)444 TEST_F(ZipReaderTest, EncryptedFile_RightPassword) {
445 ZipReader reader;
446 ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Different Encryptions.zip")));
447 reader.SetPassword("password");
448
449 {
450 const ZipReader::Entry* entry = reader.Next();
451 ASSERT_TRUE(entry);
452 EXPECT_EQ(base::FilePath::FromASCII("ClearText.txt"), entry->path);
453 EXPECT_FALSE(entry->is_directory);
454 EXPECT_FALSE(entry->is_encrypted);
455 std::string contents = "dummy";
456 EXPECT_TRUE(reader.ExtractCurrentEntryToString(&contents));
457 EXPECT_EQ("This is not encrypted.\n", contents);
458 }
459
460 // TODO(crbug.com/1296838) Support AES encryption.
461 for (const base::StringPiece path : {
462 "Encrypted AES-128.txt",
463 "Encrypted AES-192.txt",
464 "Encrypted AES-256.txt",
465 }) {
466 const ZipReader::Entry* entry = reader.Next();
467 ASSERT_TRUE(entry);
468 EXPECT_EQ(base::FilePath::FromASCII(path), entry->path);
469 EXPECT_FALSE(entry->is_directory);
470 EXPECT_TRUE(entry->is_encrypted);
471 std::string contents = "dummy";
472 EXPECT_FALSE(reader.ExtractCurrentEntryToString(&contents));
473 EXPECT_EQ("", contents);
474 }
475
476 {
477 const ZipReader::Entry* entry = reader.Next();
478 ASSERT_TRUE(entry);
479 EXPECT_EQ(base::FilePath::FromASCII("Encrypted ZipCrypto.txt"),
480 entry->path);
481 EXPECT_FALSE(entry->is_directory);
482 EXPECT_TRUE(entry->is_encrypted);
483 std::string contents = "dummy";
484 EXPECT_TRUE(reader.ExtractCurrentEntryToString(&contents));
485 EXPECT_EQ("This is encrypted with ZipCrypto.\n", contents);
486 }
487
488 EXPECT_FALSE(reader.Next());
489 EXPECT_TRUE(reader.ok());
490 }
491
492 // Verifies that the ZipReader class can extract a file from a zip archive
493 // stored in memory. This test opens a zip archive in a std::string object,
494 // extracts its content, and verifies the content is the same as the expected
495 // text.
TEST_F(ZipReaderTest,OpenFromString)496 TEST_F(ZipReaderTest, OpenFromString) {
497 // A zip archive consisting of one file "test.txt", which is a 16-byte text
498 // file that contains "This is a test.\n".
499 const char kTestData[] =
500 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\xa4\x66\x24\x41\x13\xe8"
501 "\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00\x1c\x00\x74\x65"
502 "\x73\x74\x2e\x74\x78\x74\x55\x54\x09\x00\x03\x34\x89\x45\x50\x34"
503 "\x89\x45\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13"
504 "\x00\x00\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74"
505 "\x2e\x0a\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\xa4\x66"
506 "\x24\x41\x13\xe8\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00"
507 "\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa4\x81\x00\x00\x00\x00"
508 "\x74\x65\x73\x74\x2e\x74\x78\x74\x55\x54\x05\x00\x03\x34\x89\x45"
509 "\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13\x00\x00"
510 "\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4e\x00\x00\x00"
511 "\x52\x00\x00\x00\x00\x00";
512 std::string data(kTestData, std::size(kTestData));
513 ZipReader reader;
514 ASSERT_TRUE(reader.OpenFromString(data));
515 base::FilePath target_path(FILE_PATH_LITERAL("test.txt"));
516 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
517 ASSERT_TRUE(ExtractCurrentEntryToFilePath(&reader,
518 test_dir_.AppendASCII("test.txt")));
519
520 std::string actual;
521 ASSERT_TRUE(
522 base::ReadFileToString(test_dir_.AppendASCII("test.txt"), &actual));
523 EXPECT_EQ(std::string("This is a test.\n"), actual);
524 }
525
526 // Verifies that the asynchronous extraction to a file works.
TEST_F(ZipReaderTest,ExtractToFileAsync_RegularFile)527 TEST_F(ZipReaderTest, ExtractToFileAsync_RegularFile) {
528 MockUnzipListener listener;
529
530 ZipReader reader;
531 base::FilePath target_file = test_dir_.AppendASCII("quux.txt");
532 base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
533 ASSERT_TRUE(reader.Open(test_zip_file_));
534 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
535 reader.ExtractCurrentEntryToFilePathAsync(
536 target_file,
537 base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
538 base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
539 base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
540 listener.AsWeakPtr()));
541
542 EXPECT_EQ(0, listener.success_calls());
543 EXPECT_EQ(0, listener.failure_calls());
544 EXPECT_EQ(0, listener.progress_calls());
545
546 base::RunLoop().RunUntilIdle();
547
548 EXPECT_EQ(1, listener.success_calls());
549 EXPECT_EQ(0, listener.failure_calls());
550 EXPECT_LE(1, listener.progress_calls());
551
552 std::string output;
553 ASSERT_TRUE(
554 base::ReadFileToString(test_dir_.AppendASCII("quux.txt"), &output));
555 const std::string md5 = base::MD5String(output);
556 EXPECT_EQ(kQuuxExpectedMD5, md5);
557
558 int64_t file_size = 0;
559 ASSERT_TRUE(base::GetFileSize(target_file, &file_size));
560
561 EXPECT_EQ(file_size, listener.current_progress());
562 }
563
TEST_F(ZipReaderTest,ExtractToFileAsync_Encrypted_NoPassword)564 TEST_F(ZipReaderTest, ExtractToFileAsync_Encrypted_NoPassword) {
565 MockUnzipListener listener;
566
567 ZipReader reader;
568 ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Different Encryptions.zip")));
569 ASSERT_TRUE(LocateAndOpenEntry(
570 &reader, base::FilePath::FromASCII("Encrypted ZipCrypto.txt")));
571 const base::FilePath target_path = test_dir_.AppendASCII("extracted");
572 reader.ExtractCurrentEntryToFilePathAsync(
573 target_path,
574 base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
575 base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
576 base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
577 listener.AsWeakPtr()));
578
579 EXPECT_EQ(0, listener.success_calls());
580 EXPECT_EQ(0, listener.failure_calls());
581 EXPECT_EQ(0, listener.progress_calls());
582
583 base::RunLoop().RunUntilIdle();
584
585 EXPECT_EQ(0, listener.success_calls());
586 EXPECT_EQ(1, listener.failure_calls());
587 EXPECT_LE(1, listener.progress_calls());
588
589 // The extracted file contains rubbish data.
590 // We probably shouldn't even look at it.
591 std::string contents;
592 ASSERT_TRUE(base::ReadFileToString(target_path, &contents));
593 EXPECT_NE("", contents);
594 EXPECT_EQ(contents.size(), listener.current_progress());
595 }
596
TEST_F(ZipReaderTest,ExtractToFileAsync_Encrypted_RightPassword)597 TEST_F(ZipReaderTest, ExtractToFileAsync_Encrypted_RightPassword) {
598 MockUnzipListener listener;
599
600 ZipReader reader;
601 reader.SetPassword("password");
602 ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Different Encryptions.zip")));
603 ASSERT_TRUE(LocateAndOpenEntry(
604 &reader, base::FilePath::FromASCII("Encrypted ZipCrypto.txt")));
605 const base::FilePath target_path = test_dir_.AppendASCII("extracted");
606 reader.ExtractCurrentEntryToFilePathAsync(
607 target_path,
608 base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
609 base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
610 base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
611 listener.AsWeakPtr()));
612
613 EXPECT_EQ(0, listener.success_calls());
614 EXPECT_EQ(0, listener.failure_calls());
615 EXPECT_EQ(0, listener.progress_calls());
616
617 base::RunLoop().RunUntilIdle();
618
619 EXPECT_EQ(1, listener.success_calls());
620 EXPECT_EQ(0, listener.failure_calls());
621 EXPECT_LE(1, listener.progress_calls());
622
623 std::string contents;
624 ASSERT_TRUE(base::ReadFileToString(target_path, &contents));
625 EXPECT_EQ("This is encrypted with ZipCrypto.\n", contents);
626 EXPECT_EQ(contents.size(), listener.current_progress());
627 }
628
TEST_F(ZipReaderTest,ExtractToFileAsync_WrongCrc)629 TEST_F(ZipReaderTest, ExtractToFileAsync_WrongCrc) {
630 MockUnzipListener listener;
631
632 ZipReader reader;
633 ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Wrong CRC.zip")));
634 ASSERT_TRUE(
635 LocateAndOpenEntry(&reader, base::FilePath::FromASCII("Corrupted.txt")));
636 const base::FilePath target_path = test_dir_.AppendASCII("extracted");
637 reader.ExtractCurrentEntryToFilePathAsync(
638 target_path,
639 base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
640 base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
641 base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
642 listener.AsWeakPtr()));
643
644 EXPECT_EQ(0, listener.success_calls());
645 EXPECT_EQ(0, listener.failure_calls());
646 EXPECT_EQ(0, listener.progress_calls());
647
648 base::RunLoop().RunUntilIdle();
649
650 EXPECT_EQ(0, listener.success_calls());
651 EXPECT_EQ(1, listener.failure_calls());
652 EXPECT_LE(1, listener.progress_calls());
653
654 std::string contents;
655 ASSERT_TRUE(base::ReadFileToString(target_path, &contents));
656 EXPECT_EQ("This file has been changed after its CRC was computed.\n",
657 contents);
658 EXPECT_EQ(contents.size(), listener.current_progress());
659 }
660
661 // Verifies that the asynchronous extraction to a file works.
TEST_F(ZipReaderTest,ExtractToFileAsync_Directory)662 TEST_F(ZipReaderTest, ExtractToFileAsync_Directory) {
663 MockUnzipListener listener;
664
665 ZipReader reader;
666 base::FilePath target_file = test_dir_.AppendASCII("foo");
667 base::FilePath target_path(FILE_PATH_LITERAL("foo/"));
668 ASSERT_TRUE(reader.Open(test_zip_file_));
669 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
670 reader.ExtractCurrentEntryToFilePathAsync(
671 target_file,
672 base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
673 base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
674 base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
675 listener.AsWeakPtr()));
676
677 EXPECT_EQ(0, listener.success_calls());
678 EXPECT_EQ(0, listener.failure_calls());
679 EXPECT_EQ(0, listener.progress_calls());
680
681 base::RunLoop().RunUntilIdle();
682
683 EXPECT_EQ(1, listener.success_calls());
684 EXPECT_EQ(0, listener.failure_calls());
685 EXPECT_GE(0, listener.progress_calls());
686
687 ASSERT_TRUE(base::DirectoryExists(target_file));
688 }
689
TEST_F(ZipReaderTest,ExtractCurrentEntryToString)690 TEST_F(ZipReaderTest, ExtractCurrentEntryToString) {
691 // test_mismatch_size.zip contains files with names from 0.txt to 7.txt with
692 // sizes from 0 to 7 bytes respectively, being the contents of each file a
693 // substring of "0123456" starting at '0'.
694 base::FilePath test_zip_file =
695 data_dir_.AppendASCII("test_mismatch_size.zip");
696
697 ZipReader reader;
698 std::string contents;
699 ASSERT_TRUE(reader.Open(test_zip_file));
700
701 for (size_t i = 0; i < 8; i++) {
702 SCOPED_TRACE(base::StringPrintf("Processing %d.txt", static_cast<int>(i)));
703
704 base::FilePath file_name = base::FilePath::FromUTF8Unsafe(
705 base::StringPrintf("%d.txt", static_cast<int>(i)));
706 ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name));
707
708 if (i > 1) {
709 // Off by one byte read limit: must fail.
710 EXPECT_FALSE(reader.ExtractCurrentEntryToString(i - 1, &contents));
711 }
712
713 if (i > 0) {
714 // Exact byte read limit: must pass.
715 EXPECT_TRUE(reader.ExtractCurrentEntryToString(i, &contents));
716 EXPECT_EQ(std::string(base::StringPiece("0123456", i)), contents);
717 }
718
719 // More than necessary byte read limit: must pass.
720 EXPECT_TRUE(reader.ExtractCurrentEntryToString(&contents));
721 EXPECT_EQ(std::string(base::StringPiece("0123456", i)), contents);
722 }
723 reader.Close();
724 }
725
TEST_F(ZipReaderTest,ExtractPartOfCurrentEntry)726 TEST_F(ZipReaderTest, ExtractPartOfCurrentEntry) {
727 // test_mismatch_size.zip contains files with names from 0.txt to 7.txt with
728 // sizes from 0 to 7 bytes respectively, being the contents of each file a
729 // substring of "0123456" starting at '0'.
730 base::FilePath test_zip_file =
731 data_dir_.AppendASCII("test_mismatch_size.zip");
732
733 ZipReader reader;
734 std::string contents;
735 ASSERT_TRUE(reader.Open(test_zip_file));
736
737 base::FilePath file_name0 = base::FilePath::FromUTF8Unsafe("0.txt");
738 ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name0));
739 EXPECT_TRUE(reader.ExtractCurrentEntryToString(0, &contents));
740 EXPECT_EQ("", contents);
741 EXPECT_TRUE(reader.ExtractCurrentEntryToString(1, &contents));
742 EXPECT_EQ("", contents);
743
744 base::FilePath file_name1 = base::FilePath::FromUTF8Unsafe("1.txt");
745 ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name1));
746 EXPECT_TRUE(reader.ExtractCurrentEntryToString(0, &contents));
747 EXPECT_EQ("", contents);
748 EXPECT_TRUE(reader.ExtractCurrentEntryToString(1, &contents));
749 EXPECT_EQ("0", contents);
750 EXPECT_TRUE(reader.ExtractCurrentEntryToString(2, &contents));
751 EXPECT_EQ("0", contents);
752
753 base::FilePath file_name4 = base::FilePath::FromUTF8Unsafe("4.txt");
754 ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name4));
755 EXPECT_TRUE(reader.ExtractCurrentEntryToString(0, &contents));
756 EXPECT_EQ("", contents);
757 EXPECT_FALSE(reader.ExtractCurrentEntryToString(2, &contents));
758 EXPECT_EQ("01", contents);
759 EXPECT_TRUE(reader.ExtractCurrentEntryToString(4, &contents));
760 EXPECT_EQ("0123", contents);
761 // Checks that entire file is extracted and function returns true when
762 // |max_read_bytes| is larger than file size.
763 EXPECT_TRUE(reader.ExtractCurrentEntryToString(5, &contents));
764 EXPECT_EQ("0123", contents);
765
766 reader.Close();
767 }
768
TEST_F(ZipReaderTest,ExtractPosixPermissions)769 TEST_F(ZipReaderTest, ExtractPosixPermissions) {
770 base::ScopedTempDir temp_dir;
771 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
772
773 ZipReader reader;
774 ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("test_posix_permissions.zip")));
775 for (auto entry : {"0.txt", "1.txt", "2.txt", "3.txt"}) {
776 ASSERT_TRUE(LocateAndOpenEntry(&reader, base::FilePath::FromASCII(entry)));
777 FilePathWriterDelegate delegate(temp_dir.GetPath().AppendASCII(entry));
778 ASSERT_TRUE(reader.ExtractCurrentEntry(&delegate));
779 }
780 reader.Close();
781
782 #if defined(OS_POSIX)
783 // This assumes a umask of at least 0400.
784 int mode = 0;
785 EXPECT_TRUE(base::GetPosixFilePermissions(
786 temp_dir.GetPath().AppendASCII("0.txt"), &mode));
787 EXPECT_EQ(mode & 0700, 0700);
788 EXPECT_TRUE(base::GetPosixFilePermissions(
789 temp_dir.GetPath().AppendASCII("1.txt"), &mode));
790 EXPECT_EQ(mode & 0700, 0600);
791 EXPECT_TRUE(base::GetPosixFilePermissions(
792 temp_dir.GetPath().AppendASCII("2.txt"), &mode));
793 EXPECT_EQ(mode & 0700, 0700);
794 EXPECT_TRUE(base::GetPosixFilePermissions(
795 temp_dir.GetPath().AppendASCII("3.txt"), &mode));
796 EXPECT_EQ(mode & 0700, 0600);
797 #endif
798 }
799
800 // This test exposes http://crbug.com/430959, at least on OS X
TEST_F(ZipReaderTest,DISABLED_LeakDetectionTest)801 TEST_F(ZipReaderTest, DISABLED_LeakDetectionTest) {
802 for (int i = 0; i < 100000; ++i) {
803 FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
804 ZipReader reader;
805 ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
806 }
807 }
808
809 // Test that when WriterDelegate::PrepareMock returns false, no other methods on
810 // the delegate are called and the extraction fails.
TEST_F(ZipReaderTest,ExtractCurrentEntryPrepareFailure)811 TEST_F(ZipReaderTest, ExtractCurrentEntryPrepareFailure) {
812 testing::StrictMock<MockWriterDelegate> mock_writer;
813
814 EXPECT_CALL(mock_writer, PrepareOutput()).WillOnce(Return(false));
815
816 base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
817 ZipReader reader;
818
819 ASSERT_TRUE(reader.Open(test_zip_file_));
820 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
821 ASSERT_FALSE(reader.ExtractCurrentEntry(&mock_writer));
822 }
823
824 // Test that when WriterDelegate::WriteBytes returns false, only the OnError
825 // method on the delegate is called and the extraction fails.
TEST_F(ZipReaderTest,ExtractCurrentEntryWriteBytesFailure)826 TEST_F(ZipReaderTest, ExtractCurrentEntryWriteBytesFailure) {
827 testing::StrictMock<MockWriterDelegate> mock_writer;
828
829 EXPECT_CALL(mock_writer, PrepareOutput()).WillOnce(Return(true));
830 EXPECT_CALL(mock_writer, WriteBytes(_, _)).WillOnce(Return(false));
831 EXPECT_CALL(mock_writer, OnError());
832
833 base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
834 ZipReader reader;
835
836 ASSERT_TRUE(reader.Open(test_zip_file_));
837 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
838 ASSERT_FALSE(reader.ExtractCurrentEntry(&mock_writer));
839 }
840
841 // Test that extraction succeeds when the writer delegate reports all is well.
TEST_F(ZipReaderTest,ExtractCurrentEntrySuccess)842 TEST_F(ZipReaderTest, ExtractCurrentEntrySuccess) {
843 testing::StrictMock<MockWriterDelegate> mock_writer;
844
845 EXPECT_CALL(mock_writer, PrepareOutput()).WillOnce(Return(true));
846 EXPECT_CALL(mock_writer, WriteBytes(_, _)).WillRepeatedly(Return(true));
847 EXPECT_CALL(mock_writer, SetPosixFilePermissions(_));
848 EXPECT_CALL(mock_writer, SetTimeModified(_));
849
850 base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
851 ZipReader reader;
852
853 ASSERT_TRUE(reader.Open(test_zip_file_));
854 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
855 ASSERT_TRUE(reader.ExtractCurrentEntry(&mock_writer));
856 }
857
TEST_F(ZipReaderTest,WrongCrc)858 TEST_F(ZipReaderTest, WrongCrc) {
859 ZipReader reader;
860 ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("Wrong CRC.zip")));
861
862 const ZipReader::Entry* const entry =
863 LocateAndOpenEntry(&reader, base::FilePath::FromASCII("Corrupted.txt"));
864 ASSERT_TRUE(entry);
865
866 std::string contents = "dummy";
867 EXPECT_FALSE(reader.ExtractCurrentEntryToString(&contents));
868 EXPECT_EQ("This file has been changed after its CRC was computed.\n",
869 contents);
870
871 contents = "dummy";
872 EXPECT_FALSE(
873 reader.ExtractCurrentEntryToString(entry->original_size + 1, &contents));
874 EXPECT_EQ("This file has been changed after its CRC was computed.\n",
875 contents);
876
877 contents = "dummy";
878 EXPECT_FALSE(
879 reader.ExtractCurrentEntryToString(entry->original_size, &contents));
880 EXPECT_EQ("This file has been changed after its CRC was computed.\n",
881 contents);
882
883 contents = "dummy";
884 EXPECT_FALSE(
885 reader.ExtractCurrentEntryToString(entry->original_size - 1, &contents));
886 EXPECT_EQ("This file has been changed after its CRC was computed.", contents);
887 }
888
889 class FileWriterDelegateTest : public ::testing::Test {
890 protected:
SetUp()891 void SetUp() override {
892 ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
893 file_.Initialize(temp_file_path_,
894 (base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ |
895 base::File::FLAG_WRITE | base::File::FLAG_WIN_TEMPORARY |
896 base::File::FLAG_DELETE_ON_CLOSE));
897 ASSERT_TRUE(file_.IsValid());
898 }
899
900 base::FilePath temp_file_path_;
901 base::File file_;
902 };
903
TEST_F(FileWriterDelegateTest,WriteToEnd)904 TEST_F(FileWriterDelegateTest, WriteToEnd) {
905 const std::string payload = "This is the actualy payload data.\n";
906
907 {
908 FileWriterDelegate writer(&file_);
909 EXPECT_EQ(0, writer.file_length());
910 ASSERT_TRUE(writer.PrepareOutput());
911 ASSERT_TRUE(writer.WriteBytes(payload.data(), payload.size()));
912 EXPECT_EQ(payload.size(), writer.file_length());
913 }
914
915 EXPECT_EQ(payload.size(), file_.GetLength());
916 }
917
TEST_F(FileWriterDelegateTest,EmptyOnError)918 TEST_F(FileWriterDelegateTest, EmptyOnError) {
919 const std::string payload = "This is the actualy payload data.\n";
920
921 {
922 FileWriterDelegate writer(&file_);
923 EXPECT_EQ(0, writer.file_length());
924 ASSERT_TRUE(writer.PrepareOutput());
925 ASSERT_TRUE(writer.WriteBytes(payload.data(), payload.size()));
926 EXPECT_EQ(payload.size(), writer.file_length());
927 EXPECT_EQ(payload.size(), file_.GetLength());
928 writer.OnError();
929 EXPECT_EQ(0, writer.file_length());
930 }
931
932 EXPECT_EQ(0, file_.GetLength());
933 }
934
935 } // namespace zip
936