blob: bba4365298ba42f545e0b577038f43264a721b8e [file] [log] [blame]
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +00001// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "third_party/zlib/google/zip_reader.h"
6
aviaa969482015-12-27 13:36:49 -08007#include <stddef.h>
8#include <stdint.h>
9#include <string.h>
10
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000011#include <set>
12#include <string>
13
haven@chromium.org84ed2652014-01-17 00:36:28 +000014#include "base/bind.h"
Hans Wennborg4bc42bd2020-06-24 13:59:45 +000015#include "base/check.h"
rvargas@chromium.org0d737652014-02-27 05:58:13 +000016#include "base/files/file.h"
thestigf6981592014-09-22 12:06:21 -070017#include "base/files/file_util.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000018#include "base/files/scoped_temp_dir.h"
Daniel Chengdfcc8e52019-03-28 23:13:41 +000019#include "base/hash/md5.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000020#include "base/path_service.h"
haven@chromium.org84ed2652014-01-17 00:36:28 +000021#include "base/run_loop.h"
Avi Drissmanf8556a72019-01-08 04:52:16 +000022#include "base/stl_util.h"
joaoe@opera.com00024292014-06-20 18:12:13 +000023#include "base/strings/stringprintf.h"
avi@chromium.org5cb24772013-06-07 22:40:45 +000024#include "base/strings/utf_string_conversions.h"
Gabriel Charette4b23ce82019-08-23 03:31:40 +000025#include "base/test/task_environment.h"
avi@chromium.org47f1b552013-06-28 15:23:55 +000026#include "base/time/time.h"
grtebc765a2015-03-18 14:22:34 -070027#include "testing/gmock/include/gmock/gmock.h"
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000028#include "testing/gtest/include/gtest/gtest.h"
29#include "testing/platform_test.h"
30#include "third_party/zlib/google/zip_internal.h"
31
grtebc765a2015-03-18 14:22:34 -070032using ::testing::Return;
33using ::testing::_;
34
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000035namespace {
36
haven@chromium.org84ed2652014-01-17 00:36:28 +000037const static std::string kQuuxExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
38
rvargas@chromium.org0d737652014-02-27 05:58:13 +000039class FileWrapper {
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000040 public:
41 typedef enum {
42 READ_ONLY,
43 READ_WRITE
44 } AccessMode;
45
rvargas@chromium.org0d737652014-02-27 05:58:13 +000046 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);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000054 }
55
rvargas@chromium.org0d737652014-02-27 05:58:13 +000056 ~FileWrapper() {}
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000057
rvargas@chromium.org0d737652014-02-27 05:58:13 +000058 base::PlatformFile platform_file() { return file_.GetPlatformFile(); }
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000059
grtebc765a2015-03-18 14:22:34 -070060 base::File* file() { return &file_; }
61
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000062 private:
rvargas@chromium.org0d737652014-02-27 05:58:13 +000063 base::File file_;
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +000064};
65
haven@chromium.org84ed2652014-01-17 00:36:28 +000066// 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.
69class MockUnzipListener : public base::SupportsWeakPtr<MockUnzipListener> {
70 public:
rvargas@chromium.org0d737652014-02-27 05:58:13 +000071 MockUnzipListener()
haven@chromium.org84ed2652014-01-17 00:36:28 +000072 : success_calls_(0),
73 failure_calls_(0),
74 progress_calls_(0),
75 current_progress_(0) {
76 }
77
78 // Success callback for async functions.
79 void OnUnzipSuccess() {
80 success_calls_++;
81 }
82
83 // Failure callback for async functions.
84 void OnUnzipFailure() {
85 failure_calls_++;
86 }
87
88 // Progress callback for async functions.
aviaa969482015-12-27 13:36:49 -080089 void OnUnzipProgress(int64_t progress) {
haven@chromium.org84ed2652014-01-17 00:36:28 +000090 DCHECK(progress > current_progress_);
91 progress_calls_++;
92 current_progress_ = progress;
93 }
94
95 int success_calls() { return success_calls_; }
96 int failure_calls() { return failure_calls_; }
97 int progress_calls() { return progress_calls_; }
98 int current_progress() { return current_progress_; }
99
100 private:
101 int success_calls_;
102 int failure_calls_;
103 int progress_calls_;
104
aviaa969482015-12-27 13:36:49 -0800105 int64_t current_progress_;
haven@chromium.org84ed2652014-01-17 00:36:28 +0000106};
107
grtebc765a2015-03-18 14:22:34 -0700108class MockWriterDelegate : public zip::WriterDelegate {
109 public:
110 MOCK_METHOD0(PrepareOutput, bool());
111 MOCK_METHOD2(WriteBytes, bool(const char*, int));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000112 MOCK_METHOD1(SetTimeModified, void(const base::Time&));
grtebc765a2015-03-18 14:22:34 -0700113};
114
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000115bool 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
122bool 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
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000136} // namespace
137
138namespace zip {
139
140// Make the test a PlatformTest to setup autorelease pools properly on Mac.
141class ZipReaderTest : public PlatformTest {
142 protected:
143 virtual void SetUp() {
144 PlatformTest::SetUp();
145
146 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
vabr40925c72016-09-28 01:44:50 -0700147 test_dir_ = temp_dir_.GetPath();
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000148
149 ASSERT_TRUE(GetTestDataDirectory(&test_data_dir_));
150
151 test_zip_file_ = test_data_dir_.AppendASCII("test.zip");
Daniel Ruberyad6f5862018-08-22 16:53:41 +0000152 encrypted_zip_file_ = test_data_dir_.AppendASCII("test_encrypted.zip");
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000153 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
172 virtual void TearDown() {
173 PlatformTest::TearDown();
174 }
175
176 bool GetTestDataDirectory(base::FilePath* path) {
Avi Drissmanda0819d2018-05-07 18:55:12 +0000177 bool success = base::PathService::Get(base::DIR_SOURCE_ROOT, path);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000178 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
haven@chromium.org84ed2652014-01-17 00:36:28 +0000189 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
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000199 // 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_;
Daniel Ruberyad6f5862018-08-22 16:53:41 +0000205 // The path to test_encrypted.zip in the test data directory.
206 base::FilePath encrypted_zip_file_;
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000207 // 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_;
haven@chromium.org84ed2652014-01-17 00:36:28 +0000216
Gabriel Charette8c79b6c2019-09-10 00:28:54 +0000217 base::test::TaskEnvironment task_environment_;
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000218};
219
220TEST_F(ZipReaderTest, Open_ValidZipFile) {
221 ZipReader reader;
222 ASSERT_TRUE(reader.Open(test_zip_file_));
223}
224
225TEST_F(ZipReaderTest, Open_ValidZipPlatformFile) {
226 ZipReader reader;
rvargas@chromium.org0d737652014-02-27 05:58:13 +0000227 FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000228 ASSERT_TRUE(reader.OpenFromPlatformFile(zip_fd_wrapper.platform_file()));
229}
230
231TEST_F(ZipReaderTest, Open_NonExistentFile) {
232 ZipReader reader;
233 ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("nonexistent.zip")));
234}
235
236TEST_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.
243TEST_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.
261TEST_F(ZipReaderTest, PlatformFileIteration) {
262 std::set<base::FilePath> actual_contents;
263 ZipReader reader;
rvargas@chromium.org0d737652014-02-27 05:58:13 +0000264 FileWrapper zip_fd_wrapper(test_zip_file_, FileWrapper::READ_ONLY);
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000265 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
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000278TEST_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"));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000282 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000283 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
303TEST_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"));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000308 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000309 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
317TEST_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
332TEST_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"));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000336 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000337 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
345TEST_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/"));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000349 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000350 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
Daniel Ruberyad6f5862018-08-22 16:53:41 +0000372TEST_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
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000386// 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.
390TEST_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";
Avi Drissmanf8556a72019-01-08 04:52:16 +0000406 std::string data(kTestData, base::size(kTestData));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000407 ZipReader reader;
408 ASSERT_TRUE(reader.OpenFromString(data));
409 base::FilePath target_path(FILE_PATH_LITERAL("test.txt"));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000410 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
411 ASSERT_TRUE(ExtractCurrentEntryToFilePath(&reader,
412 test_dir_.AppendASCII("test.txt")));
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000413
414 std::string actual;
brettw@chromium.org997408f2013-08-30 18:23:50 +0000415 ASSERT_TRUE(base::ReadFileToString(
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000416 test_dir_.AppendASCII("test.txt"), &actual));
417 EXPECT_EQ(std::string("This is a test.\n"), actual);
418}
419
haven@chromium.org84ed2652014-01-17 00:36:28 +0000420// Verifies that the asynchronous extraction to a file works.
421TEST_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_));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000428 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
haven@chromium.org84ed2652014-01-17 00:36:28 +0000429 reader.ExtractCurrentEntryToFilePathAsync(
430 target_file,
Sylvain Defresneddebad22019-10-01 15:40:56 +0000431 base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
432 base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
433 base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
434 listener.AsWeakPtr()));
haven@chromium.org84ed2652014-01-17 00:36:28 +0000435
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
aviaa969482015-12-27 13:36:49 -0800452 int64_t file_size = 0;
haven@chromium.org84ed2652014-01-17 00:36:28 +0000453 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.
459TEST_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_));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000466 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
haven@chromium.org84ed2652014-01-17 00:36:28 +0000467 reader.ExtractCurrentEntryToFilePathAsync(
468 target_file,
Sylvain Defresneddebad22019-10-01 15:40:56 +0000469 base::BindOnce(&MockUnzipListener::OnUnzipSuccess, listener.AsWeakPtr()),
470 base::BindOnce(&MockUnzipListener::OnUnzipFailure, listener.AsWeakPtr()),
471 base::BindRepeating(&MockUnzipListener::OnUnzipProgress,
472 listener.AsWeakPtr()));
haven@chromium.org84ed2652014-01-17 00:36:28 +0000473
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
joaoe@opera.com00024292014-06-20 18:12:13 +0000487TEST_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)));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000503 ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name));
joaoe@opera.com00024292014-06-20 18:12:13 +0000504
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));
mortonmb4298b02017-08-04 07:57:41 -0700513 EXPECT_EQ(base::StringPiece("0123456", i).as_string(), contents);
joaoe@opera.com00024292014-06-20 18:12:13 +0000514 }
515
516 // More than necessary byte read limit: must pass.
517 EXPECT_TRUE(reader.ExtractCurrentEntryToString(16, &contents));
mortonmb4298b02017-08-04 07:57:41 -0700518 EXPECT_EQ(base::StringPiece("0123456", i).as_string(), contents);
joaoe@opera.com00024292014-06-20 18:12:13 +0000519 }
520 reader.Close();
521}
522
mortonmb4298b02017-08-04 07:57:41 -0700523TEST_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");
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000535 ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name0));
mortonmb4298b02017-08-04 07:57:41 -0700536 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");
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000542 ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name1));
mortonmb4298b02017-08-04 07:57:41 -0700543 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");
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000551 ASSERT_TRUE(LocateAndOpenEntry(&reader, file_name4));
mortonmb4298b02017-08-04 07:57:41 -0700552 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
jeremysspiegela6bba372014-11-19 15:53:16 -0800566// This test exposes http://crbug.com/430959, at least on OS X
567TEST_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
grtebc765a2015-03-18 14:22:34 -0700575// Test that when WriterDelegate::PrepareMock returns false, no other methods on
576// the delegate are called and the extraction fails.
577TEST_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_));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000587 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
mortonmb4298b02017-08-04 07:57:41 -0700588 ASSERT_FALSE(reader.ExtractCurrentEntry(
589 &mock_writer, std::numeric_limits<uint64_t>::max()));
grtebc765a2015-03-18 14:22:34 -0700590}
591
592// Test that when WriterDelegate::WriteBytes returns false, no other methods on
593// the delegate are called and the extraction fails.
594TEST_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_));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000606 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
mortonmb4298b02017-08-04 07:57:41 -0700607 ASSERT_FALSE(reader.ExtractCurrentEntry(
608 &mock_writer, std::numeric_limits<uint64_t>::max()));
grtebc765a2015-03-18 14:22:34 -0700609}
610
611// Test that extraction succeeds when the writer delegate reports all is well.
612TEST_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));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000619 EXPECT_CALL(mock_writer, SetTimeModified(_));
grtebc765a2015-03-18 14:22:34 -0700620
621 base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
622 ZipReader reader;
623
624 ASSERT_TRUE(reader.Open(test_zip_file_));
Joshua Pawlickie31b5032018-02-06 20:24:51 +0000625 ASSERT_TRUE(LocateAndOpenEntry(&reader, target_path));
mortonmb4298b02017-08-04 07:57:41 -0700626 ASSERT_TRUE(reader.ExtractCurrentEntry(&mock_writer,
627 std::numeric_limits<uint64_t>::max()));
grtebc765a2015-03-18 14:22:34 -0700628}
629
630class FileWriterDelegateTest : public ::testing::Test {
631 protected:
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.
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
654TEST_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
alecflett@chromium.orgd6d082e2013-05-03 23:02:57 +0000672} // namespace zip