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