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