1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/zlib/google/zip_reader.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 #include <string.h>
10 
11 #include <set>
12 #include <string>
13 
14 #include "base/bind.h"
15 #include "base/files/file.h"
16 #include "base/files/file_util.h"
17 #include "base/files/scoped_temp_dir.h"
18 #include "base/hash/md5.h"
19 #include "base/logging.h"
20 #include "base/path_service.h"
21 #include "base/run_loop.h"
22 #include "base/stl_util.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/test/task_environment.h"
26 #include "base/time/time.h"
27 #include "testing/gmock/include/gmock/gmock.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 #include "testing/platform_test.h"
30 #include "third_party/zlib/google/zip_internal.h"
31 
32 using ::testing::Return;
33 using ::testing::_;
34 
35 namespace {
36 
37 const static std::string kQuuxExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
38 
39 class FileWrapper {
40  public:
41   typedef enum {
42     READ_ONLY,
43     READ_WRITE
44   } AccessMode;
45 
FileWrapper(const base::FilePath & path,AccessMode mode)46   FileWrapper(const base::FilePath& path, AccessMode mode) {
47     int flags = base::File::FLAG_READ;
48     if (mode == READ_ONLY)
49       flags |= base::File::FLAG_OPEN;
50     else
51       flags |= base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS;
52 
53     file_.Initialize(path, flags);
54   }
55 
~FileWrapper()56   ~FileWrapper() {}
57 
platform_file()58   base::PlatformFile platform_file() { return file_.GetPlatformFile(); }
59 
file()60   base::File* file() { return &file_; }
61 
62  private:
63   base::File file_;
64 };
65 
66 // A mock that provides methods that can be used as callbacks in asynchronous
67 // unzip functions.  Tracks the number of calls and number of bytes reported.
68 // Assumes that progress callbacks will be executed in-order.
69 class MockUnzipListener : public base::SupportsWeakPtr<MockUnzipListener> {
70  public:
MockUnzipListener()71   MockUnzipListener()
72       : success_calls_(0),
73         failure_calls_(0),
74         progress_calls_(0),
75         current_progress_(0) {
76   }
77 
78   // Success callback for async functions.
OnUnzipSuccess()79   void OnUnzipSuccess() {
80     success_calls_++;
81   }
82 
83   // Failure callback for async functions.
OnUnzipFailure()84   void OnUnzipFailure() {
85     failure_calls_++;
86   }
87 
88   // Progress callback for async functions.
OnUnzipProgress(int64_t progress)89   void OnUnzipProgress(int64_t progress) {
90     DCHECK(progress > current_progress_);
91     progress_calls_++;
92     current_progress_ = progress;
93   }
94 
success_calls()95   int success_calls() { return success_calls_; }
failure_calls()96   int failure_calls() { return failure_calls_; }
progress_calls()97   int progress_calls() { return progress_calls_; }
current_progress()98   int current_progress() { return current_progress_; }
99 
100  private:
101   int success_calls_;
102   int failure_calls_;
103   int progress_calls_;
104 
105   int64_t current_progress_;
106 };
107 
108 class MockWriterDelegate : public zip::WriterDelegate {
109  public:
110   MOCK_METHOD0(PrepareOutput, bool());
111   MOCK_METHOD2(WriteBytes, bool(const char*, int));
112   MOCK_METHOD1(SetTimeModified, void(const base::Time&));
113 };
114 
ExtractCurrentEntryToFilePath(zip::ZipReader * reader,base::FilePath path)115 bool ExtractCurrentEntryToFilePath(zip::ZipReader* reader,
116                                    base::FilePath path) {
117   zip::FilePathWriterDelegate writer(path);
118   return reader->ExtractCurrentEntry(&writer,
119                                      std::numeric_limits<uint64_t>::max());
120 }
121 
LocateAndOpenEntry(zip::ZipReader * reader,const base::FilePath & path_in_zip)122 bool LocateAndOpenEntry(zip::ZipReader* reader,
123                         const base::FilePath& path_in_zip) {
124   // The underlying library can do O(1) access, but ZipReader does not expose
125   // that. O(N) access is acceptable for these tests.
126   while (reader->HasMore()) {
127     if (!reader->OpenCurrentEntryInZip())
128       return false;
129     if (reader->current_entry_info()->file_path() == path_in_zip)
130       return true;
131     reader->AdvanceToNextEntry();
132   }
133   return false;
134 }
135 
136 }   // namespace
137 
138 namespace zip {
139 
140 // Make the test a PlatformTest to setup autorelease pools properly on Mac.
141 class ZipReaderTest : public PlatformTest {
142  protected:
SetUp()143   virtual void SetUp() {
144     PlatformTest::SetUp();
145 
146     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
147     test_dir_ = temp_dir_.GetPath();
148 
149     ASSERT_TRUE(GetTestDataDirectory(&test_data_dir_));
150 
151     test_zip_file_ = test_data_dir_.AppendASCII("test.zip");
152     encrypted_zip_file_ = test_data_dir_.AppendASCII("test_encrypted.zip");
153     evil_zip_file_ = test_data_dir_.AppendASCII("evil.zip");
154     evil_via_invalid_utf8_zip_file_ = test_data_dir_.AppendASCII(
155         "evil_via_invalid_utf8.zip");
156     evil_via_absolute_file_name_zip_file_ = test_data_dir_.AppendASCII(
157         "evil_via_absolute_file_name.zip");
158 
159     test_zip_contents_.insert(base::FilePath(FILE_PATH_LITERAL("foo/")));
160     test_zip_contents_.insert(base::FilePath(FILE_PATH_LITERAL("foo/bar/")));
161     test_zip_contents_.insert(
162         base::FilePath(FILE_PATH_LITERAL("foo/bar/baz.txt")));
163     test_zip_contents_.insert(
164         base::FilePath(FILE_PATH_LITERAL("foo/bar/quux.txt")));
165     test_zip_contents_.insert(
166         base::FilePath(FILE_PATH_LITERAL("foo/bar.txt")));
167     test_zip_contents_.insert(base::FilePath(FILE_PATH_LITERAL("foo.txt")));
168     test_zip_contents_.insert(
169         base::FilePath(FILE_PATH_LITERAL("foo/bar/.hidden")));
170   }
171 
TearDown()172   virtual void TearDown() {
173     PlatformTest::TearDown();
174   }
175 
GetTestDataDirectory(base::FilePath * path)176   bool GetTestDataDirectory(base::FilePath* path) {
177     bool success = base::PathService::Get(base::DIR_SOURCE_ROOT, path);
178     EXPECT_TRUE(success);
179     if (!success)
180       return false;
181     *path = path->AppendASCII("third_party");
182     *path = path->AppendASCII("zlib");
183     *path = path->AppendASCII("google");
184     *path = path->AppendASCII("test");
185     *path = path->AppendASCII("data");
186     return true;
187   }
188 
CompareFileAndMD5(const base::FilePath & path,const std::string expected_md5)189   bool CompareFileAndMD5(const base::FilePath& path,
190                          const std::string expected_md5) {
191     // Read the output file and compute the MD5.
192     std::string output;
193     if (!base::ReadFileToString(path, &output))
194       return false;
195     const std::string md5 = base::MD5String(output);
196     return expected_md5 == md5;
197   }
198 
199   // The path to temporary directory used to contain the test operations.
200   base::FilePath test_dir_;
201   // The path to the test data directory where test.zip etc. are located.
202   base::FilePath test_data_dir_;
203   // The path to test.zip in the test data directory.
204   base::FilePath test_zip_file_;
205   // The path to test_encrypted.zip in the test data directory.
206   base::FilePath encrypted_zip_file_;
207   // The path to evil.zip in the test data directory.
208   base::FilePath evil_zip_file_;
209   // The path to evil_via_invalid_utf8.zip in the test data directory.
210   base::FilePath evil_via_invalid_utf8_zip_file_;
211   // The path to evil_via_absolute_file_name.zip in the test data directory.
212   base::FilePath evil_via_absolute_file_name_zip_file_;
213   std::set<base::FilePath> test_zip_contents_;
214 
215   base::ScopedTempDir temp_dir_;
216 
217   base::test::TaskEnvironment task_environment_;
218 };
219 
TEST_F(ZipReaderTest,Open_ValidZipFile)220 TEST_F(ZipReaderTest, Open_ValidZipFile) {
221   ZipReader reader;
222   ASSERT_TRUE(reader.Open(test_zip_file_));
223 }
224 
TEST_F(ZipReaderTest,Open_ValidZipPlatformFile)225 TEST_F(ZipReaderTest, Open_ValidZipPlatformFile) {
226   ZipReader reader;
227   FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
228   ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
229 }
230 
TEST_F(ZipReaderTest,Open_NonExistentFile)231 TEST_F(ZipReaderTest, Open_NonExistentFile) {
232   ZipReader reader;
233   ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("nonexistent.zip")));
234 }
235 
TEST_F(ZipReaderTest,Open_ExistentButNonZipFile)236 TEST_F(ZipReaderTest, Open_ExistentButNonZipFile) {
237   ZipReader reader;
238   ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("create_test_zip.sh")));
239 }
240 
241 // Iterate through the contents in the test zip file, and compare that the
242 // contents collected from the zip reader matches the expected contents.
TEST_F(ZipReaderTest,Iteration)243 TEST_F(ZipReaderTest, Iteration) {
244   std::set<base::FilePath> actual_contents;
245   ZipReader reader;
246   ASSERT_TRUE(reader.Open(test_zip_file_));
247   while (reader.HasMore()) {
248     ASSERT_TRUE(reader.OpenCurrentEntryInZip());
249     actual_contents.insert(reader.current_entry_info()->file_path());
250     ASSERT_TRUE(reader.AdvanceToNextEntry());
251   }
252   EXPECT_FALSE(reader.AdvanceToNextEntry());  // Shouldn't go further.
253   EXPECT_EQ(test_zip_contents_.size(),
254             static_cast<size_t>(reader.num_entries()));
255   EXPECT_EQ(test_zip_contents_.size(), actual_contents.size());
256   EXPECT_EQ(test_zip_contents_, actual_contents);
257 }
258 
259 // Open the test zip file from a file descriptor, iterate through its contents,
260 // and compare that they match the expected contents.
TEST_F(ZipReaderTest,PlatformFileIteration)261 TEST_F(ZipReaderTest, PlatformFileIteration) {
262   std::set<base::FilePath> actual_contents;
263   ZipReader reader;
264   FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
265   ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
266   while (reader.HasMore()) {
267     ASSERT_TRUE(reader.OpenCurrentEntryInZip());
268     actual_contents.insert(reader.current_entry_info()->file_path());
269     ASSERT_TRUE(reader.AdvanceToNextEntry());
270   }
271   EXPECT_FALSE(reader.AdvanceToNextEntry());  // Shouldn't go further.
272   EXPECT_EQ(test_zip_contents_.size(),
273             static_cast<size_t>(reader.num_entries()));
274   EXPECT_EQ(test_zip_contents_.size(), actual_contents.size());
275   EXPECT_EQ(test_zip_contents_, actual_contents);
276 }
277 
TEST_F(ZipReaderTest,current_entry_info_RegularFile)278 TEST_F(ZipReaderTest, current_entry_info_RegularFile) {
279   ZipReader reader;
280   ASSERT_TRUE(reader.Open(test_zip_file_));
281   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
282   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
283   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
284 
285   EXPECT_EQ(target_path, current_entry_info->file_path());
286   EXPECT_EQ(13527, current_entry_info->original_size());
287 
288   // The expected time stamp: 2009-05-29 06:22:20
289   base::Time::Exploded exploded = {};  // Zero-clear.
290   current_entry_info->last_modified().LocalExplode(&exploded);
291   EXPECT_EQ(2009, exploded.year);
292   EXPECT_EQ(5, exploded.month);
293   EXPECT_EQ(29, exploded.day_of_month);
294   EXPECT_EQ(6, exploded.hour);
295   EXPECT_EQ(22, exploded.minute);
296   EXPECT_EQ(20, exploded.second);
297   EXPECT_EQ(0, exploded.millisecond);
298 
299   EXPECT_FALSE(current_entry_info->is_unsafe());
300   EXPECT_FALSE(current_entry_info->is_directory());
301 }
302 
TEST_F(ZipReaderTest,current_entry_info_DotDotFile)303 TEST_F(ZipReaderTest, current_entry_info_DotDotFile) {
304   ZipReader reader;
305   ASSERT_TRUE(reader.Open(evil_zip_file_));
306   base::FilePath target_path(FILE_PATH_LITERAL(
307       "../levilevilevilevilevilevilevilevilevilevilevilevil"));
308   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
309   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
310   EXPECT_EQ(target_path, current_entry_info->file_path());
311 
312   // This file is unsafe because of ".." in the file name.
313   EXPECT_TRUE(current_entry_info->is_unsafe());
314   EXPECT_FALSE(current_entry_info->is_directory());
315 }
316 
TEST_F(ZipReaderTest,current_entry_info_InvalidUTF8File)317 TEST_F(ZipReaderTest, current_entry_info_InvalidUTF8File) {
318   ZipReader reader;
319   ASSERT_TRUE(reader.Open(evil_via_invalid_utf8_zip_file_));
320   // The evil file is the 2nd file in the zip file.
321   // We cannot locate by the file name ".\x80.\\evil.txt",
322   // as FilePath may internally convert the string.
323   ASSERT_TRUE(reader.AdvanceToNextEntry());
324   ASSERT_TRUE(reader.OpenCurrentEntryInZip());
325   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
326 
327   // This file is unsafe because of invalid UTF-8 in the file name.
328   EXPECT_TRUE(current_entry_info->is_unsafe());
329   EXPECT_FALSE(current_entry_info->is_directory());
330 }
331 
TEST_F(ZipReaderTest,current_entry_info_AbsoluteFile)332 TEST_F(ZipReaderTest, current_entry_info_AbsoluteFile) {
333   ZipReader reader;
334   ASSERT_TRUE(reader.Open(evil_via_absolute_file_name_zip_file_));
335   base::FilePath target_path(FILE_PATH_LITERAL("/evil.txt"));
336   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
337   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
338   EXPECT_EQ(target_path, current_entry_info->file_path());
339 
340   // This file is unsafe because of the absolute file name.
341   EXPECT_TRUE(current_entry_info->is_unsafe());
342   EXPECT_FALSE(current_entry_info->is_directory());
343 }
344 
TEST_F(ZipReaderTest,current_entry_info_Directory)345 TEST_F(ZipReaderTest, current_entry_info_Directory) {
346   ZipReader reader;
347   ASSERT_TRUE(reader.Open(test_zip_file_));
348   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/"));
349   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
350   ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
351 
352   EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("foo/bar/")),
353             current_entry_info->file_path());
354   // The directory size should be zero.
355   EXPECT_EQ(0, current_entry_info->original_size());
356 
357   // The expected time stamp: 2009-05-31 15:49:52
358   base::Time::Exploded exploded = {};  // Zero-clear.
359   current_entry_info->last_modified().LocalExplode(&exploded);
360   EXPECT_EQ(2009, exploded.year);
361   EXPECT_EQ(5, exploded.month);
362   EXPECT_EQ(31, exploded.day_of_month);
363   EXPECT_EQ(15, exploded.hour);
364   EXPECT_EQ(49, exploded.minute);
365   EXPECT_EQ(52, exploded.second);
366   EXPECT_EQ(0, exploded.millisecond);
367 
368   EXPECT_FALSE(current_entry_info->is_unsafe());
369   EXPECT_TRUE(current_entry_info->is_directory());
370 }
371 
TEST_F(ZipReaderTest,current_entry_info_EncryptedFile)372 TEST_F(ZipReaderTest, current_entry_info_EncryptedFile) {
373   ZipReader reader;
374   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
375 
376   ASSERT_TRUE(reader.Open(encrypted_zip_file_));
377   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
378   EXPECT_TRUE(reader.current_entry_info()->is_encrypted());
379   reader.Close();
380 
381   ASSERT_TRUE(reader.Open(test_zip_file_));
382   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
383   EXPECT_FALSE(reader.current_entry_info()->is_encrypted());
384 }
385 
386 // Verifies that the ZipReader class can extract a file from a zip archive
387 // stored in memory. This test opens a zip archive in a std::string object,
388 // extracts its content, and verifies the content is the same as the expected
389 // text.
TEST_F(ZipReaderTest,OpenFromString)390 TEST_F(ZipReaderTest, OpenFromString) {
391   // A zip archive consisting of one file "test.txt", which is a 16-byte text
392   // file that contains "This is a test.\n".
393   const char kTestData[] =
394       "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\xa4\x66\x24\x41\x13\xe8"
395       "\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00\x1c\x00\x74\x65"
396       "\x73\x74\x2e\x74\x78\x74\x55\x54\x09\x00\x03\x34\x89\x45\x50\x34"
397       "\x89\x45\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13"
398       "\x00\x00\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74"
399       "\x2e\x0a\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\xa4\x66"
400       "\x24\x41\x13\xe8\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00"
401       "\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa4\x81\x00\x00\x00\x00"
402       "\x74\x65\x73\x74\x2e\x74\x78\x74\x55\x54\x05\x00\x03\x34\x89\x45"
403       "\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13\x00\x00"
404       "\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4e\x00\x00\x00"
405       "\x52\x00\x00\x00\x00\x00";
406   std::string data(kTestData, base::size(kTestData));
407   ZipReader reader;
408   ASSERT_TRUE(reader.OpenFromString(data));
409   base::FilePath target_path(FILE_PATH_LITERAL("test.txt"));
410   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
411   ASSERT_TRUE(ExtractCurrentEntryToFilePath(&reader,
412                                             test_dir_.AppendASCII("test.txt")));
413 
414   std::string actual;
415   ASSERT_TRUE(base::ReadFileToString(
416       test_dir_.AppendASCII("test.txt"), &actual));
417   EXPECT_EQ(std::string("This is a test.\n"), actual);
418 }
419 
420 // Verifies that the asynchronous extraction to a file works.
TEST_F(ZipReaderTest,ExtractToFileAsync_RegularFile)421 TEST_F(ZipReaderTest, ExtractToFileAsync_RegularFile) {
422   MockUnzipListener listener;
423 
424   ZipReader reader;
425   base::FilePath target_file = test_dir_.AppendASCII("quux.txt");
426   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
427   ASSERT_TRUE(reader.Open(test_zip_file_));
428   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
429   reader.ExtractCurrentEntryToFilePathAsync(
430       target_file,
431       base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
432       base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
433       base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
434                           listener.AsWeakPtr()));
435 
436   EXPECT_EQ(0, listener.success_calls());
437   EXPECT_EQ(0, listener.failure_calls());
438   EXPECT_EQ(0, listener.progress_calls());
439 
440   base::RunLoop().RunUntilIdle();
441 
442   EXPECT_EQ(1, listener.success_calls());
443   EXPECT_EQ(0, listener.failure_calls());
444   EXPECT_LE(1, listener.progress_calls());
445 
446   std::string output;
447   ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
448                                      &output));
449   const std::string md5 = base::MD5String(output);
450   EXPECT_EQ(kQuuxExpectedMD5, md5);
451 
452   int64_t file_size = 0;
453   ASSERT_TRUE(base::GetFileSize(target_file, &file_size));
454 
455   EXPECT_EQ(file_size, listener.current_progress());
456 }
457 
458 // Verifies that the asynchronous extraction to a file works.
TEST_F(ZipReaderTest,ExtractToFileAsync_Directory)459 TEST_F(ZipReaderTest, ExtractToFileAsync_Directory) {
460   MockUnzipListener listener;
461 
462   ZipReader reader;
463   base::FilePath target_file = test_dir_.AppendASCII("foo");
464   base::FilePath target_path(FILE_PATH_LITERAL("foo/"));
465   ASSERT_TRUE(reader.Open(test_zip_file_));
466   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
467   reader.ExtractCurrentEntryToFilePathAsync(
468       target_file,
469       base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
470       base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
471       base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
472                           listener.AsWeakPtr()));
473 
474   EXPECT_EQ(0, listener.success_calls());
475   EXPECT_EQ(0, listener.failure_calls());
476   EXPECT_EQ(0, listener.progress_calls());
477 
478   base::RunLoop().RunUntilIdle();
479 
480   EXPECT_EQ(1, listener.success_calls());
481   EXPECT_EQ(0, listener.failure_calls());
482   EXPECT_GE(0, listener.progress_calls());
483 
484   ASSERT_TRUE(base::DirectoryExists(target_file));
485 }
486 
TEST_F(ZipReaderTest,ExtractCurrentEntryToString)487 TEST_F(ZipReaderTest, ExtractCurrentEntryToString) {
488   // test_mismatch_size.zip contains files with names from 0.txt to 7.txt with
489   // sizes from 0 to 7 bytes respectively, being the contents of each file a
490   // substring of "0123456" starting at '0'.
491   base::FilePath test_zip_file =
492       test_data_dir_.AppendASCII("test_mismatch_size.zip");
493 
494   ZipReader reader;
495   std::string contents;
496   ASSERT_TRUE(reader.Open(test_zip_file));
497 
498   for (size_t i = 0; i < 8; i++) {
499     SCOPED_TRACE(base::StringPrintf("Processing %d.txt", static_cast<int>(i)));
500 
501     base::FilePath file_name = base::FilePath::FromUTF8Unsafe(
502         base::StringPrintf("%d.txt", static_cast<int>(i)));
503     ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name));
504 
505     if (i > 1) {
506       // Off by one byte read limit: must fail.
507       EXPECT_FALSE(reader.ExtractCurrentEntryToString(i - 1, &contents));
508     }
509 
510     if (i > 0) {
511       // Exact byte read limit: must pass.
512       EXPECT_TRUE(reader.ExtractCurrentEntryToString(i, &contents));
513       EXPECT_EQ(base::StringPiece("0123456", i).as_string(), contents);
514     }
515 
516     // More than necessary byte read limit: must pass.
517     EXPECT_TRUE(reader.ExtractCurrentEntryToString(16, &contents));
518     EXPECT_EQ(base::StringPiece("0123456", i).as_string(), contents);
519   }
520   reader.Close();
521 }
522 
TEST_F(ZipReaderTest,ExtractPartOfCurrentEntry)523 TEST_F(ZipReaderTest, ExtractPartOfCurrentEntry) {
524   // test_mismatch_size.zip contains files with names from 0.txt to 7.txt with
525   // sizes from 0 to 7 bytes respectively, being the contents of each file a
526   // substring of "0123456" starting at '0'.
527   base::FilePath test_zip_file =
528       test_data_dir_.AppendASCII("test_mismatch_size.zip");
529 
530   ZipReader reader;
531   std::string contents;
532   ASSERT_TRUE(reader.Open(test_zip_file));
533 
534   base::FilePath file_name0 = base::FilePath::FromUTF8Unsafe("0.txt");
535   ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name0));
536   EXPECT_TRUE(reader.ExtractCurrentEntryToString(0, &contents));
537   EXPECT_EQ("", contents);
538   EXPECT_TRUE(reader.ExtractCurrentEntryToString(1, &contents));
539   EXPECT_EQ("", contents);
540 
541   base::FilePath file_name1 = base::FilePath::FromUTF8Unsafe("1.txt");
542   ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name1));
543   EXPECT_TRUE(reader.ExtractCurrentEntryToString(0, &contents));
544   EXPECT_EQ("", contents);
545   EXPECT_TRUE(reader.ExtractCurrentEntryToString(1, &contents));
546   EXPECT_EQ("0", contents);
547   EXPECT_TRUE(reader.ExtractCurrentEntryToString(2, &contents));
548   EXPECT_EQ("0", contents);
549 
550   base::FilePath file_name4 = base::FilePath::FromUTF8Unsafe("4.txt");
551   ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name4));
552   EXPECT_TRUE(reader.ExtractCurrentEntryToString(0, &contents));
553   EXPECT_EQ("", contents);
554   EXPECT_FALSE(reader.ExtractCurrentEntryToString(2, &contents));
555   EXPECT_EQ("01", contents);
556   EXPECT_TRUE(reader.ExtractCurrentEntryToString(4, &contents));
557   EXPECT_EQ("0123", contents);
558   // Checks that entire file is extracted and function returns true when
559   // |max_read_bytes| is larger than file size.
560   EXPECT_TRUE(reader.ExtractCurrentEntryToString(5, &contents));
561   EXPECT_EQ("0123", contents);
562 
563   reader.Close();
564 }
565 
566 // This test exposes http://crbug.com/430959, at least on OS X
TEST_F(ZipReaderTest,DISABLED_LeakDetectionTest)567 TEST_F(ZipReaderTest, DISABLED_LeakDetectionTest) {
568   for (int i = 0; i < 100000; ++i) {
569     FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
570     ZipReader reader;
571     ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
572   }
573 }
574 
575 // Test that when WriterDelegate::PrepareMock returns false, no other methods on
576 // the delegate are called and the extraction fails.
TEST_F(ZipReaderTest,ExtractCurrentEntryPrepareFailure)577 TEST_F(ZipReaderTest, ExtractCurrentEntryPrepareFailure) {
578   testing::StrictMock<MockWriterDelegate> mock_writer;
579 
580   EXPECT_CALL(mock_writer, PrepareOutput())
581       .WillOnce(Return(false));
582 
583   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
584   ZipReader reader;
585 
586   ASSERT_TRUE(reader.Open(test_zip_file_));
587   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
588   ASSERT_FALSE(reader.ExtractCurrentEntry(
589       &mock_writer, std::numeric_limits<uint64_t>::max()));
590 }
591 
592 // Test that when WriterDelegate::WriteBytes returns false, no other methods on
593 // the delegate are called and the extraction fails.
TEST_F(ZipReaderTest,ExtractCurrentEntryWriteBytesFailure)594 TEST_F(ZipReaderTest, ExtractCurrentEntryWriteBytesFailure) {
595   testing::StrictMock<MockWriterDelegate> mock_writer;
596 
597   EXPECT_CALL(mock_writer, PrepareOutput())
598       .WillOnce(Return(true));
599   EXPECT_CALL(mock_writer, WriteBytes(_, _))
600       .WillOnce(Return(false));
601 
602   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
603   ZipReader reader;
604 
605   ASSERT_TRUE(reader.Open(test_zip_file_));
606   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
607   ASSERT_FALSE(reader.ExtractCurrentEntry(
608       &mock_writer, std::numeric_limits<uint64_t>::max()));
609 }
610 
611 // Test that extraction succeeds when the writer delegate reports all is well.
TEST_F(ZipReaderTest,ExtractCurrentEntrySuccess)612 TEST_F(ZipReaderTest, ExtractCurrentEntrySuccess) {
613   testing::StrictMock<MockWriterDelegate> mock_writer;
614 
615   EXPECT_CALL(mock_writer, PrepareOutput())
616       .WillOnce(Return(true));
617   EXPECT_CALL(mock_writer, WriteBytes(_, _))
618       .WillRepeatedly(Return(true));
619   EXPECT_CALL(mock_writer, SetTimeModified(_));
620 
621   base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
622   ZipReader reader;
623 
624   ASSERT_TRUE(reader.Open(test_zip_file_));
625   ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
626   ASSERT_TRUE(reader.ExtractCurrentEntry(&mock_writer,
627                                          std::numeric_limits<uint64_t>::max()));
628 }
629 
630 class FileWriterDelegateTest : public ::testing::Test {
631  protected:
SetUp()632   void SetUp() override {
633     ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_));
634     file_.Initialize(temp_file_path_, (base::File::FLAG_CREATE_ALWAYS |
635                                        base::File::FLAG_READ |
636                                        base::File::FLAG_WRITE |
637                                        base::File::FLAG_TEMPORARY |
638                                        base::File::FLAG_DELETE_ON_CLOSE));
639     ASSERT_TRUE(file_.IsValid());
640   }
641 
642   // Writes data to the file, leaving the current position at the end of the
643   // write.
PopulateFile()644   void PopulateFile() {
645     static const char kSomeData[] = "this sure is some data.";
646     static const size_t kSomeDataLen = sizeof(kSomeData) - 1;
647     ASSERT_NE(-1LL, file_.Write(0LL, kSomeData, kSomeDataLen));
648   }
649 
650   base::FilePath temp_file_path_;
651   base::File file_;
652 };
653 
TEST_F(FileWriterDelegateTest,WriteToStartAndTruncate)654 TEST_F(FileWriterDelegateTest, WriteToStartAndTruncate) {
655   // Write stuff and advance.
656   PopulateFile();
657 
658   // This should rewind, write, then truncate.
659   static const char kSomeData[] = "short";
660   static const int kSomeDataLen = sizeof(kSomeData) - 1;
661   {
662     FileWriterDelegate writer(&file_);
663     ASSERT_TRUE(writer.PrepareOutput());
664     ASSERT_TRUE(writer.WriteBytes(kSomeData, kSomeDataLen));
665   }
666   ASSERT_EQ(kSomeDataLen, file_.GetLength());
667   char buf[kSomeDataLen] = {};
668   ASSERT_EQ(kSomeDataLen, file_.Read(0LL, buf, kSomeDataLen));
669   ASSERT_EQ(std::string(kSomeData), std::string(buf, kSomeDataLen));
670 }
671 
672 }  // namespace zip
673