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 <stddef.h>
6 #include <stdint.h>
7
8 #include <iomanip>
9 #include <limits>
10 #include <string>
11 #include <unordered_map>
12 #include <unordered_set>
13 #include <vector>
14
15 #include "base/bind.h"
16 #include "base/files/file.h"
17 #include "base/files/file_enumerator.h"
18 #include "base/files/file_path.h"
19 #include "base/files/file_util.h"
20 #include "base/files/scoped_temp_dir.h"
21 #include "base/logging.h"
22 #include "base/path_service.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/test/bind.h"
26 #include "base/time/time.h"
27 #include "build/build_config.h"
28 #include "testing/gmock/include/gmock/gmock.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "testing/platform_test.h"
31 #include "third_party/zlib/google/zip.h"
32 #include "third_party/zlib/google/zip_internal.h"
33 #include "third_party/zlib/google/zip_reader.h"
34
35 // Convenience macro to create a file path from a string literal.
36 #define FP(path) base::FilePath(FILE_PATH_LITERAL(path))
37
38 namespace {
39
40 using testing::UnorderedElementsAre;
41
GetRelativePaths(const base::FilePath & dir,base::FileEnumerator::FileType type)42 std::vector<std::string> GetRelativePaths(const base::FilePath& dir,
43 base::FileEnumerator::FileType type) {
44 std::vector<std::string> got_paths;
45 base::FileEnumerator files(dir, true, type);
46 for (base::FilePath path = files.Next(); !path.empty(); path = files.Next()) {
47 base::FilePath relative;
48 EXPECT_TRUE(dir.AppendRelativePath(path, &relative));
49 got_paths.push_back(relative.AsUTF8Unsafe());
50 }
51
52 EXPECT_EQ(base::File::FILE_OK, files.GetError());
53 return got_paths;
54 }
55
CreateFile(const std::string & content,base::FilePath * file_path,base::File * file)56 bool CreateFile(const std::string& content,
57 base::FilePath* file_path,
58 base::File* file) {
59 if (!base::CreateTemporaryFile(file_path))
60 return false;
61
62 if (base::WriteFile(*file_path, content.data(), content.size()) == -1)
63 return false;
64
65 *file = base::File(
66 *file_path, base::File::Flags::FLAG_OPEN | base::File::Flags::FLAG_READ);
67 return file->IsValid();
68 }
69
70 // A WriterDelegate that logs progress once per second.
71 class ProgressWriterDelegate : public zip::WriterDelegate {
72 public:
ProgressWriterDelegate(int64_t expected_size)73 explicit ProgressWriterDelegate(int64_t expected_size)
74 : expected_size_(expected_size) {
75 CHECK_GT(expected_size_, 0);
76 }
77
WriteBytes(const char * data,int num_bytes)78 bool WriteBytes(const char* data, int num_bytes) override {
79 received_bytes_ += num_bytes;
80 LogProgressIfNecessary();
81 return true;
82 }
83
SetTimeModified(const base::Time & time)84 void SetTimeModified(const base::Time& time) override { LogProgress(); }
85
received_bytes() const86 int64_t received_bytes() const { return received_bytes_; }
87
88 private:
LogProgressIfNecessary()89 void LogProgressIfNecessary() {
90 const base::TimeTicks now = base::TimeTicks::Now();
91 if (next_progress_report_time_ > now)
92 return;
93
94 next_progress_report_time_ = now + progress_period_;
95 LogProgress();
96 }
97
LogProgress() const98 void LogProgress() const {
99 LOG(INFO) << "Unzipping... " << std::setw(3)
100 << (100 * received_bytes_ / expected_size_) << "%";
101 }
102
103 const base::TimeDelta progress_period_ = base::Seconds(1);
104 base::TimeTicks next_progress_report_time_ =
105 base::TimeTicks::Now() + progress_period_;
106 const uint64_t expected_size_;
107 int64_t received_bytes_ = 0;
108 };
109
110 // A virtual file system containing:
111 // /test
112 // /test/foo.txt
113 // /test/bar/bar1.txt
114 // /test/bar/bar2.txt
115 // Used to test providing a custom zip::FileAccessor when unzipping.
116 class VirtualFileSystem : public zip::FileAccessor {
117 public:
118 static constexpr char kFooContent[] = "This is foo.";
119 static constexpr char kBar1Content[] = "This is bar.";
120 static constexpr char kBar2Content[] = "This is bar too.";
121
VirtualFileSystem()122 VirtualFileSystem() {
123 base::FilePath test_dir;
124 base::FilePath foo_txt_path = test_dir.AppendASCII("foo.txt");
125
126 base::FilePath file_path;
127 base::File file;
128 bool success = CreateFile(kFooContent, &file_path, &file);
129 DCHECK(success);
130 files_[foo_txt_path] = std::move(file);
131
132 base::FilePath bar_dir = test_dir.AppendASCII("bar");
133 base::FilePath bar1_txt_path = bar_dir.AppendASCII("bar1.txt");
134 success = CreateFile(kBar1Content, &file_path, &file);
135 DCHECK(success);
136 files_[bar1_txt_path] = std::move(file);
137
138 base::FilePath bar2_txt_path = bar_dir.AppendASCII("bar2.txt");
139 success = CreateFile(kBar2Content, &file_path, &file);
140 DCHECK(success);
141 files_[bar2_txt_path] = std::move(file);
142
143 file_tree_[base::FilePath()] = {{foo_txt_path}, {bar_dir}};
144 file_tree_[bar_dir] = {{bar1_txt_path, bar2_txt_path}};
145 file_tree_[foo_txt_path] = {};
146 file_tree_[bar1_txt_path] = {};
147 file_tree_[bar2_txt_path] = {};
148 }
149
150 VirtualFileSystem(const VirtualFileSystem&) = delete;
151 VirtualFileSystem& operator=(const VirtualFileSystem&) = delete;
152
153 ~VirtualFileSystem() override = default;
154
155 private:
Open(const zip::Paths paths,std::vector<base::File> * const files)156 bool Open(const zip::Paths paths,
157 std::vector<base::File>* const files) override {
158 DCHECK(files);
159 files->reserve(files->size() + paths.size());
160
161 for (const base::FilePath& path : paths) {
162 const auto it = files_.find(path);
163 if (it == files_.end()) {
164 files->emplace_back();
165 } else {
166 EXPECT_TRUE(it->second.IsValid());
167 files->push_back(std::move(it->second));
168 }
169 }
170
171 return true;
172 }
173
List(const base::FilePath & path,std::vector<base::FilePath> * const files,std::vector<base::FilePath> * const subdirs)174 bool List(const base::FilePath& path,
175 std::vector<base::FilePath>* const files,
176 std::vector<base::FilePath>* const subdirs) override {
177 DCHECK(!path.IsAbsolute());
178 DCHECK(files);
179 DCHECK(subdirs);
180
181 const auto it = file_tree_.find(path);
182 if (it == file_tree_.end())
183 return false;
184
185 for (const base::FilePath& file : it->second.files) {
186 DCHECK(!file.empty());
187 files->push_back(file);
188 }
189
190 for (const base::FilePath& subdir : it->second.subdirs) {
191 DCHECK(!subdir.empty());
192 subdirs->push_back(subdir);
193 }
194
195 return true;
196 }
197
GetInfo(const base::FilePath & path,Info * const info)198 bool GetInfo(const base::FilePath& path, Info* const info) override {
199 DCHECK(!path.IsAbsolute());
200 DCHECK(info);
201
202 if (!file_tree_.count(path))
203 return false;
204
205 info->is_directory = !files_.count(path);
206 info->last_modified =
207 base::Time::FromDoubleT(172097977); // Some random date.
208
209 return true;
210 }
211
212 struct DirContents {
213 std::vector<base::FilePath> files, subdirs;
214 };
215
216 std::unordered_map<base::FilePath, DirContents> file_tree_;
217 std::unordered_map<base::FilePath, base::File> files_;
218 };
219
220 // static
221 constexpr char VirtualFileSystem::kFooContent[];
222 constexpr char VirtualFileSystem::kBar1Content[];
223 constexpr char VirtualFileSystem::kBar2Content[];
224
225 // Make the test a PlatformTest to setup autorelease pools properly on Mac.
226 class ZipTest : public PlatformTest {
227 protected:
228 enum ValidYearType { VALID_YEAR, INVALID_YEAR };
229
SetUp()230 virtual void SetUp() {
231 PlatformTest::SetUp();
232
233 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
234 test_dir_ = temp_dir_.GetPath();
235
236 base::FilePath zip_path(test_dir_);
237 zip_contents_.insert(zip_path.AppendASCII("foo.txt"));
238 zip_path = zip_path.AppendASCII("foo");
239 zip_contents_.insert(zip_path);
240 zip_contents_.insert(zip_path.AppendASCII("bar.txt"));
241 zip_path = zip_path.AppendASCII("bar");
242 zip_contents_.insert(zip_path);
243 zip_contents_.insert(zip_path.AppendASCII("baz.txt"));
244 zip_contents_.insert(zip_path.AppendASCII("quux.txt"));
245 zip_contents_.insert(zip_path.AppendASCII(".hidden"));
246
247 // Include a subset of files in |zip_file_list_| to test ZipFiles().
248 zip_file_list_.push_back(FP("foo.txt"));
249 zip_file_list_.push_back(FP("foo/bar/quux.txt"));
250 zip_file_list_.push_back(FP("foo/bar/.hidden"));
251 }
252
TearDown()253 virtual void TearDown() { PlatformTest::TearDown(); }
254
GetDataDirectory()255 static base::FilePath GetDataDirectory() {
256 base::FilePath path;
257 bool success = base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
258 EXPECT_TRUE(success);
259 return std::move(path)
260 .AppendASCII("third_party")
261 .AppendASCII("zlib")
262 .AppendASCII("google")
263 .AppendASCII("test")
264 .AppendASCII("data");
265 }
266
TestUnzipFile(const base::FilePath::StringType & filename,bool expect_hidden_files)267 void TestUnzipFile(const base::FilePath::StringType& filename,
268 bool expect_hidden_files) {
269 TestUnzipFile(GetDataDirectory().Append(filename), expect_hidden_files);
270 }
271
TestUnzipFile(const base::FilePath & path,bool expect_hidden_files)272 void TestUnzipFile(const base::FilePath& path, bool expect_hidden_files) {
273 ASSERT_TRUE(base::PathExists(path)) << "no file " << path;
274 ASSERT_TRUE(zip::Unzip(path, test_dir_));
275
276 base::FilePath original_dir = GetDataDirectory().AppendASCII("test");
277
278 base::FileEnumerator files(
279 test_dir_, true,
280 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
281
282 size_t count = 0;
283 for (base::FilePath unzipped_entry_path = files.Next();
284 !unzipped_entry_path.empty(); unzipped_entry_path = files.Next()) {
285 EXPECT_EQ(zip_contents_.count(unzipped_entry_path), 1U)
286 << "Couldn't find " << unzipped_entry_path;
287 count++;
288
289 if (base::PathExists(unzipped_entry_path) &&
290 !base::DirectoryExists(unzipped_entry_path)) {
291 // It's a file, check its contents are what we zipped.
292 base::FilePath relative_path;
293 ASSERT_TRUE(
294 test_dir_.AppendRelativePath(unzipped_entry_path, &relative_path))
295 << "Cannot append relative path failed, params: '" << test_dir_
296 << "' and '" << unzipped_entry_path << "'";
297 base::FilePath original_path = original_dir.Append(relative_path);
298 EXPECT_TRUE(base::ContentsEqual(original_path, unzipped_entry_path))
299 << "Original file '" << original_path << "' and unzipped file '"
300 << unzipped_entry_path << "' have different contents";
301 }
302 }
303 EXPECT_EQ(base::File::FILE_OK, files.GetError());
304
305 size_t expected_count = 0;
306 for (const base::FilePath& path : zip_contents_) {
307 if (expect_hidden_files || path.BaseName().value()[0] != '.')
308 ++expected_count;
309 }
310
311 EXPECT_EQ(expected_count, count);
312 }
313
314 // This function does the following:
315 // 1) Creates a test.txt file with the given last modification timestamp
316 // 2) Zips test.txt and extracts it back into a different location.
317 // 3) Confirms that test.txt in the output directory has the specified
318 // last modification timestamp if it is valid (|valid_year| is true).
319 // If the timestamp is not supported by the zip format, the last
320 // modification defaults to the current time.
TestTimeStamp(const char * date_time,ValidYearType valid_year)321 void TestTimeStamp(const char* date_time, ValidYearType valid_year) {
322 SCOPED_TRACE(std::string("TestTimeStamp(") + date_time + ")");
323 base::ScopedTempDir temp_dir;
324 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
325
326 base::FilePath zip_file = temp_dir.GetPath().AppendASCII("out.zip");
327 base::FilePath src_dir = temp_dir.GetPath().AppendASCII("input");
328 base::FilePath out_dir = temp_dir.GetPath().AppendASCII("output");
329
330 base::FilePath src_file = src_dir.AppendASCII("test.txt");
331 base::FilePath out_file = out_dir.AppendASCII("test.txt");
332
333 EXPECT_TRUE(base::CreateDirectory(src_dir));
334 EXPECT_TRUE(base::CreateDirectory(out_dir));
335
336 base::Time test_mtime;
337 ASSERT_TRUE(base::Time::FromString(date_time, &test_mtime));
338
339 // Adjusting the current timestamp to the resolution that the zip file
340 // supports, which is 2 seconds. Note that between this call to Time::Now()
341 // and zip::Zip() the clock can advance a bit, hence the use of EXPECT_GE.
342 base::Time::Exploded now_parts;
343 base::Time::Now().UTCExplode(&now_parts);
344 now_parts.second = now_parts.second & ~1;
345 now_parts.millisecond = 0;
346 base::Time now_time;
347 EXPECT_TRUE(base::Time::FromUTCExploded(now_parts, &now_time));
348
349 EXPECT_EQ(1, base::WriteFile(src_file, "1", 1));
350 EXPECT_TRUE(base::TouchFile(src_file, base::Time::Now(), test_mtime));
351
352 EXPECT_TRUE(zip::Zip(src_dir, zip_file, true));
353 ASSERT_TRUE(zip::Unzip(zip_file, out_dir));
354
355 base::File::Info file_info;
356 EXPECT_TRUE(base::GetFileInfo(out_file, &file_info));
357 EXPECT_EQ(file_info.size, 1);
358
359 if (valid_year == VALID_YEAR) {
360 EXPECT_EQ(file_info.last_modified, test_mtime);
361 } else {
362 // Invalid date means the modification time will default to 'now'.
363 EXPECT_GE(file_info.last_modified, now_time);
364 }
365 }
366
367 // The path to temporary directory used to contain the test operations.
368 base::FilePath test_dir_;
369
370 base::ScopedTempDir temp_dir_;
371
372 // Hard-coded contents of a known zip file.
373 std::unordered_set<base::FilePath> zip_contents_;
374
375 // Hard-coded list of relative paths for a zip file created with ZipFiles.
376 std::vector<base::FilePath> zip_file_list_;
377 };
378
TEST_F(ZipTest,UnzipNoSuchFile)379 TEST_F(ZipTest, UnzipNoSuchFile) {
380 EXPECT_FALSE(zip::Unzip(GetDataDirectory().AppendASCII("No Such File.zip"),
381 test_dir_));
382 EXPECT_THAT(
383 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
384 UnorderedElementsAre());
385 EXPECT_THAT(
386 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::DIRECTORIES),
387 UnorderedElementsAre());
388 }
389
TEST_F(ZipTest,Unzip)390 TEST_F(ZipTest, Unzip) {
391 TestUnzipFile(FILE_PATH_LITERAL("test.zip"), true);
392 }
393
TEST_F(ZipTest,UnzipUncompressed)394 TEST_F(ZipTest, UnzipUncompressed) {
395 TestUnzipFile(FILE_PATH_LITERAL("test_nocompress.zip"), true);
396 }
397
TEST_F(ZipTest,UnzipEvil)398 TEST_F(ZipTest, UnzipEvil) {
399 base::FilePath path = GetDataDirectory().AppendASCII("evil.zip");
400 // Unzip the zip file into a sub directory of test_dir_ so evil.zip
401 // won't create a persistent file outside test_dir_ in case of a
402 // failure.
403 base::FilePath output_dir = test_dir_.AppendASCII("out");
404 ASSERT_FALSE(zip::Unzip(path, output_dir));
405 base::FilePath evil_file = output_dir;
406 evil_file = evil_file.AppendASCII(
407 "../levilevilevilevilevilevilevilevilevilevilevilevil");
408 ASSERT_FALSE(base::PathExists(evil_file));
409 }
410
TEST_F(ZipTest,UnzipEvil2)411 TEST_F(ZipTest, UnzipEvil2) {
412 // The ZIP file contains a file with invalid UTF-8 in its file name.
413 base::FilePath path =
414 GetDataDirectory().AppendASCII("evil_via_invalid_utf8.zip");
415 // See the comment at UnzipEvil() for why we do this.
416 base::FilePath output_dir = test_dir_.AppendASCII("out");
417 ASSERT_TRUE(zip::Unzip(path, output_dir));
418 ASSERT_TRUE(base::PathExists(
419 output_dir.Append(base::FilePath::FromUTF8Unsafe(".�.\\evil.txt"))));
420 ASSERT_FALSE(base::PathExists(output_dir.AppendASCII("../evil.txt")));
421 }
422
TEST_F(ZipTest,UnzipWithFilter)423 TEST_F(ZipTest, UnzipWithFilter) {
424 auto filter = base::BindRepeating([](const base::FilePath& path) {
425 return path.BaseName().MaybeAsASCII() == "foo.txt";
426 });
427 ASSERT_TRUE(zip::Unzip(GetDataDirectory().AppendASCII("test.zip"), test_dir_,
428 {.filter = std::move(filter)}));
429 // Only foo.txt should have been extracted.
430 EXPECT_THAT(
431 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
432 UnorderedElementsAre("foo.txt"));
433 EXPECT_THAT(
434 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::DIRECTORIES),
435 UnorderedElementsAre());
436 }
437
TEST_F(ZipTest,UnzipEncryptedWithRightPassword)438 TEST_F(ZipTest, UnzipEncryptedWithRightPassword) {
439 // TODO(crbug.com/1296838) Also check the AES-encrypted files.
440 auto filter = base::BindRepeating([](const base::FilePath& path) {
441 return !base::StartsWith(path.MaybeAsASCII(), "Encrypted AES");
442 });
443
444 ASSERT_TRUE(zip::Unzip(
445 GetDataDirectory().AppendASCII("Different Encryptions.zip"), test_dir_,
446 {.filter = std::move(filter), .password = "password"}));
447
448 std::string contents;
449 ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("ClearText.txt"),
450 &contents));
451 EXPECT_EQ("This is not encrypted.\n", contents);
452
453 ASSERT_TRUE(base::ReadFileToString(
454 test_dir_.AppendASCII("Encrypted ZipCrypto.txt"), &contents));
455 EXPECT_EQ("This is encrypted with ZipCrypto.\n", contents);
456 }
457
TEST_F(ZipTest,UnzipEncryptedWithWrongPassword)458 TEST_F(ZipTest, UnzipEncryptedWithWrongPassword) {
459 // TODO(crbug.com/1296838) Also check the AES-encrypted files.
460 auto filter = base::BindRepeating([](const base::FilePath& path) {
461 return !base::StartsWith(path.MaybeAsASCII(), "Encrypted AES");
462 });
463
464 ASSERT_FALSE(zip::Unzip(
465 GetDataDirectory().AppendASCII("Different Encryptions.zip"), test_dir_,
466 {.filter = std::move(filter), .password = "wrong"}));
467
468 std::string contents;
469 ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("ClearText.txt"),
470 &contents));
471 EXPECT_EQ("This is not encrypted.\n", contents);
472
473 // No rubbish file should be left behind.
474 EXPECT_THAT(
475 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
476 UnorderedElementsAre("ClearText.txt"));
477 }
478
TEST_F(ZipTest,UnzipEncryptedWithNoPassword)479 TEST_F(ZipTest, UnzipEncryptedWithNoPassword) {
480 // TODO(crbug.com/1296838) Also check the AES-encrypted files.
481 auto filter = base::BindRepeating([](const base::FilePath& path) {
482 return !base::StartsWith(path.MaybeAsASCII(), "Encrypted AES");
483 });
484
485 ASSERT_FALSE(
486 zip::Unzip(GetDataDirectory().AppendASCII("Different Encryptions.zip"),
487 test_dir_, {.filter = std::move(filter)}));
488
489 std::string contents;
490 ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("ClearText.txt"),
491 &contents));
492 EXPECT_EQ("This is not encrypted.\n", contents);
493
494 // No rubbish file should be left behind.
495 EXPECT_THAT(
496 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
497 UnorderedElementsAre("ClearText.txt"));
498 }
499
TEST_F(ZipTest,UnzipEncryptedContinueOnError)500 TEST_F(ZipTest, UnzipEncryptedContinueOnError) {
501 EXPECT_TRUE(
502 zip::Unzip(GetDataDirectory().AppendASCII("Different Encryptions.zip"),
503 test_dir_, {.continue_on_error = true}));
504
505 std::string contents;
506 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("ClearText.txt"),
507 &contents));
508 EXPECT_EQ("This is not encrypted.\n", contents);
509
510 // No rubbish file should be left behind.
511 EXPECT_THAT(
512 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
513 UnorderedElementsAre("ClearText.txt"));
514 }
515
TEST_F(ZipTest,UnzipWrongCrc)516 TEST_F(ZipTest, UnzipWrongCrc) {
517 ASSERT_FALSE(
518 zip::Unzip(GetDataDirectory().AppendASCII("Wrong CRC.zip"), test_dir_));
519
520 // No rubbish file should be left behind.
521 EXPECT_THAT(
522 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
523 UnorderedElementsAre());
524 }
525
TEST_F(ZipTest,UnzipRepeatedDirName)526 TEST_F(ZipTest, UnzipRepeatedDirName) {
527 EXPECT_TRUE(zip::Unzip(
528 GetDataDirectory().AppendASCII("Repeated Dir Name.zip"), test_dir_));
529
530 EXPECT_THAT(
531 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
532 UnorderedElementsAre());
533
534 EXPECT_THAT(
535 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::DIRECTORIES),
536 UnorderedElementsAre("repeated"));
537 }
538
TEST_F(ZipTest,UnzipRepeatedFileName)539 TEST_F(ZipTest, UnzipRepeatedFileName) {
540 EXPECT_FALSE(zip::Unzip(
541 GetDataDirectory().AppendASCII("Repeated File Name.zip"), test_dir_));
542
543 EXPECT_THAT(
544 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
545 UnorderedElementsAre("repeated"));
546
547 std::string contents;
548 EXPECT_TRUE(
549 base::ReadFileToString(test_dir_.AppendASCII("repeated"), &contents));
550 EXPECT_EQ("First file", contents);
551 }
552
TEST_F(ZipTest,UnzipCannotCreateEmptyDir)553 TEST_F(ZipTest, UnzipCannotCreateEmptyDir) {
554 EXPECT_FALSE(zip::Unzip(
555 GetDataDirectory().AppendASCII("Empty Dir Same Name As File.zip"),
556 test_dir_));
557
558 EXPECT_THAT(
559 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
560 UnorderedElementsAre("repeated"));
561
562 EXPECT_THAT(
563 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::DIRECTORIES),
564 UnorderedElementsAre());
565
566 std::string contents;
567 EXPECT_TRUE(
568 base::ReadFileToString(test_dir_.AppendASCII("repeated"), &contents));
569 EXPECT_EQ("First file", contents);
570 }
571
TEST_F(ZipTest,UnzipCannotCreateParentDir)572 TEST_F(ZipTest, UnzipCannotCreateParentDir) {
573 EXPECT_FALSE(zip::Unzip(
574 GetDataDirectory().AppendASCII("Parent Dir Same Name As File.zip"),
575 test_dir_));
576
577 EXPECT_THAT(
578 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
579 UnorderedElementsAre("repeated"));
580
581 EXPECT_THAT(
582 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::DIRECTORIES),
583 UnorderedElementsAre());
584
585 std::string contents;
586 EXPECT_TRUE(
587 base::ReadFileToString(test_dir_.AppendASCII("repeated"), &contents));
588 EXPECT_EQ("First file", contents);
589 }
590
TEST_F(ZipTest,UnzipWindowsSpecialNames)591 TEST_F(ZipTest, UnzipWindowsSpecialNames) {
592 EXPECT_TRUE(zip::Unzip(
593 GetDataDirectory().AppendASCII("Windows Special Names.zip"), test_dir_));
594
595 std::string contents;
596 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("Last"), &contents));
597 EXPECT_EQ("Last file", contents);
598
599 #ifdef OS_WIN
600 // On Windows, the NUL* files are simply missing. No error is reported. Not
601 // even an error message in the logs.
602 EXPECT_THAT(
603 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
604 UnorderedElementsAre("First", "Last"));
605 #else
606 EXPECT_THAT(
607 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
608 UnorderedElementsAre("First", "Last", "NUL", "Nul.txt",
609 "nul.very long extension"));
610
611 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("NUL"), &contents));
612 EXPECT_EQ("This is: NUL", contents);
613
614 EXPECT_TRUE(
615 base::ReadFileToString(test_dir_.AppendASCII("Nul.txt"), &contents));
616 EXPECT_EQ("This is: Nul.txt", contents);
617
618 EXPECT_TRUE(base::ReadFileToString(
619 test_dir_.AppendASCII("nul.very long extension"), &contents));
620 EXPECT_EQ("This is: nul.very long extension", contents);
621 #endif
622 }
623
TEST_F(ZipTest,UnzipDifferentCases)624 TEST_F(ZipTest, UnzipDifferentCases) {
625 #if defined(OS_WIN) || defined(OS_MAC)
626 // Only the first file (with mixed case) is extracted.
627 EXPECT_FALSE(zip::Unzip(GetDataDirectory().AppendASCII(
628 "Repeated File Name With Different Cases.zip"),
629 test_dir_));
630
631 EXPECT_THAT(
632 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
633 UnorderedElementsAre("Case"));
634
635 std::string contents;
636 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("Case"), &contents));
637 EXPECT_EQ("Mixed case 111", contents);
638 #else
639 // All the files are extracted.
640 EXPECT_TRUE(zip::Unzip(GetDataDirectory().AppendASCII(
641 "Repeated File Name With Different Cases.zip"),
642 test_dir_));
643
644 EXPECT_THAT(
645 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
646 UnorderedElementsAre("Case", "case", "CASE"));
647
648 std::string contents;
649 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("Case"), &contents));
650 EXPECT_EQ("Mixed case 111", contents);
651
652 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("case"), &contents));
653 EXPECT_EQ("Lower case 22", contents);
654
655 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("CASE"), &contents));
656 EXPECT_EQ("Upper case 3", contents);
657 #endif
658 }
659
TEST_F(ZipTest,UnzipDifferentCasesContinueOnError)660 TEST_F(ZipTest, UnzipDifferentCasesContinueOnError) {
661 EXPECT_TRUE(zip::Unzip(GetDataDirectory().AppendASCII(
662 "Repeated File Name With Different Cases.zip"),
663 test_dir_, {.continue_on_error = true}));
664
665 std::string contents;
666
667 #if defined(OS_WIN) || defined(OS_MAC)
668 // Only the first file (with mixed case) has been extracted.
669 EXPECT_THAT(
670 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
671 UnorderedElementsAre("Case"));
672
673 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("Case"), &contents));
674 EXPECT_EQ("Mixed case 111", contents);
675 #else
676 // All the files have been extracted.
677 EXPECT_THAT(
678 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES),
679 UnorderedElementsAre("Case", "case", "CASE"));
680
681 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("Case"), &contents));
682 EXPECT_EQ("Mixed case 111", contents);
683
684 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("case"), &contents));
685 EXPECT_EQ("Lower case 22", contents);
686
687 EXPECT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("CASE"), &contents));
688 EXPECT_EQ("Upper case 3", contents);
689 #endif
690 }
691
TEST_F(ZipTest,UnzipMixedPaths)692 TEST_F(ZipTest, UnzipMixedPaths) {
693 EXPECT_TRUE(zip::Unzip(GetDataDirectory().AppendASCII("Mixed Paths.zip"),
694 test_dir_, {.continue_on_error = true}));
695
696 std::unordered_set<std::string> want_paths = {
697 #ifdef OS_WIN
698 "Dot", //
699 "Space→", //
700 "a\\b", //
701 "u\\v\\w\\x\\y\\z", //
702 "←Backslash2", //
703 #else
704 " ", // Invalid on Windows
705 "Angle <>", // Invalid on Windows
706 "Backslash1→\\", //
707 "Backspace \x08", // Invalid on Windows
708 "Bell \a", // Invalid on Windows
709 "C:", // Invalid on Windows
710 "C:\\", // Absolute path on Windows
711 "C:\\Temp", // Absolute path on Windows
712 "C:\\Temp\\", // Absolute path on Windows
713 "C:\\Temp\\File", // Absolute path on Windows
714 "Carriage Return \r", // Invalid on Windows
715 "Colon :", // Invalid on Windows
716 "Dot .", // Becomes "Dot" on Windows
717 "Double quote \"", // Invalid on Windows
718 "Escape \x1B", // Invalid on Windows
719 "Line Feed \n", // Invalid on Windows
720 "NUL .txt", // Disappears on Windows
721 "NUL", // Disappears on Windows
722 "NUL..txt", // Disappears on Windows
723 "NUL.tar.gz", // Disappears on Windows
724 "NUL.txt", // Disappears on Windows
725 "Pipe |", // Invalid on Windows
726 "Question ?", // Invalid on Windows
727 "Space→ ", // Becomes "Space→" on Windows
728 "Star *", // Invalid on Windows
729 "Tab \t", // Invalid on Windows
730 "\\\\server\\share\\file", // Absolute path on Windows
731 "\\←Backslash2", // Becomes "←Backslash2" on Windows
732 "a/b", //
733 "u/v/w/x/y/z", //
734 #ifndef OS_MAC
735 "CASE", //
736 "Case", //
737 #endif
738 #endif
739 " NUL.txt", //
740 " ←Space", //
741 "$HOME", //
742 "%TMP", //
743 "-", //
744 "...Tree", //
745 "..Two", //
746 ".One", //
747 "Ampersand &", //
748 "At @", //
749 "Backslash3→\\←Backslash4", //
750 "Backtick `", //
751 "Caret ^", //
752 "Comma ,", //
753 "Curly {}", //
754 "Dash -", //
755 "Delete \x7F", //
756 "Dollar $", //
757 "Equal =", //
758 "Euro €", //
759 "Exclamation !", //
760 "FileOrDir", //
761 "First", //
762 "Hash #", //
763 "Last", //
764 "Percent %", //
765 "Plus +", //
766 "Quote '", //
767 "Round ()", //
768 "Semicolon ;", //
769 "Smile \U0001F642", //
770 "Square []", //
771 "String Terminator \u009C", //
772 "Tilde ~", //
773 "Underscore _", //
774 "case", //
775 "~", //
776 };
777
778 const std::vector<std::string> got_paths =
779 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::FILES);
780
781 for (const std::string& path : got_paths) {
782 EXPECT_TRUE(want_paths.erase(path))
783 << "Found unexpected file: " << std::quoted(path);
784 }
785
786 for (const std::string& path : want_paths) {
787 EXPECT_TRUE(false) << "Cannot find expected file: " << std::quoted(path);
788 }
789
790 EXPECT_THAT(
791 GetRelativePaths(test_dir_, base::FileEnumerator::FileType::DIRECTORIES),
792 UnorderedElementsAre(
793 #ifdef OS_WIN
794 "Backslash3→", "Empty", "a", "u", "u\\v", "u\\v\\w", "u\\v\\w\\x",
795 "u\\v\\w\\x\\y"
796 #else
797 "Empty", "a", "u", "u/v", "u/v/w", "u/v/w/x", "u/v/w/x/y"
798 #endif
799 ));
800 }
801
TEST_F(ZipTest,UnzipWithDelegates)802 TEST_F(ZipTest, UnzipWithDelegates) {
803 auto dir_creator =
804 base::BindLambdaForTesting([this](const base::FilePath& entry_path) {
805 return base::CreateDirectory(test_dir_.Append(entry_path));
806 });
807 auto writer =
808 base::BindLambdaForTesting([this](const base::FilePath& entry_path)
809 -> std::unique_ptr<zip::WriterDelegate> {
810 return std::make_unique<zip::FilePathWriterDelegate>(
811 test_dir_.Append(entry_path));
812 });
813
814 base::File file(GetDataDirectory().AppendASCII("test.zip"),
815 base::File::Flags::FLAG_OPEN | base::File::Flags::FLAG_READ);
816 EXPECT_TRUE(zip::Unzip(file.GetPlatformFile(), writer, dir_creator));
817 base::FilePath dir = test_dir_;
818 base::FilePath dir_foo = dir.AppendASCII("foo");
819 base::FilePath dir_foo_bar = dir_foo.AppendASCII("bar");
820 EXPECT_TRUE(base::PathExists(dir.AppendASCII("foo.txt")));
821 EXPECT_TRUE(base::DirectoryExists(dir_foo));
822 EXPECT_TRUE(base::PathExists(dir_foo.AppendASCII("bar.txt")));
823 EXPECT_TRUE(base::DirectoryExists(dir_foo_bar));
824 EXPECT_TRUE(base::PathExists(dir_foo_bar.AppendASCII(".hidden")));
825 EXPECT_TRUE(base::PathExists(dir_foo_bar.AppendASCII("baz.txt")));
826 EXPECT_TRUE(base::PathExists(dir_foo_bar.AppendASCII("quux.txt")));
827 }
828
TEST_F(ZipTest,UnzipOnlyDirectories)829 TEST_F(ZipTest, UnzipOnlyDirectories) {
830 auto dir_creator =
831 base::BindLambdaForTesting([this](const base::FilePath& entry_path) {
832 return base::CreateDirectory(test_dir_.Append(entry_path));
833 });
834
835 // Always return a null WriterDelegate.
836 auto writer =
837 base::BindLambdaForTesting([](const base::FilePath& entry_path) {
838 return std::unique_ptr<zip::WriterDelegate>();
839 });
840
841 base::File file(GetDataDirectory().AppendASCII("test.zip"),
842 base::File::Flags::FLAG_OPEN | base::File::Flags::FLAG_READ);
843 EXPECT_TRUE(zip::Unzip(file.GetPlatformFile(), writer, dir_creator,
844 {.continue_on_error = true}));
845 base::FilePath dir = test_dir_;
846 base::FilePath dir_foo = dir.AppendASCII("foo");
847 base::FilePath dir_foo_bar = dir_foo.AppendASCII("bar");
848 EXPECT_FALSE(base::PathExists(dir.AppendASCII("foo.txt")));
849 EXPECT_TRUE(base::DirectoryExists(dir_foo));
850 EXPECT_FALSE(base::PathExists(dir_foo.AppendASCII("bar.txt")));
851 EXPECT_TRUE(base::DirectoryExists(dir_foo_bar));
852 EXPECT_FALSE(base::PathExists(dir_foo_bar.AppendASCII(".hidden")));
853 EXPECT_FALSE(base::PathExists(dir_foo_bar.AppendASCII("baz.txt")));
854 EXPECT_FALSE(base::PathExists(dir_foo_bar.AppendASCII("quux.txt")));
855 }
856
857 // Tests that a ZIP archive containing SJIS-encoded file names can be correctly
858 // extracted if the encoding is specified.
TEST_F(ZipTest,UnzipSjis)859 TEST_F(ZipTest, UnzipSjis) {
860 ASSERT_TRUE(zip::Unzip(GetDataDirectory().AppendASCII("SJIS Bug 846195.zip"),
861 test_dir_, {.encoding = "Shift_JIS"}));
862
863 const base::FilePath dir =
864 test_dir_.Append(base::FilePath::FromUTF8Unsafe("新しいフォルダ"));
865 EXPECT_TRUE(base::DirectoryExists(dir));
866
867 std::string contents;
868 ASSERT_TRUE(base::ReadFileToString(
869 dir.Append(base::FilePath::FromUTF8Unsafe("SJIS_835C_ソ.txt")),
870 &contents));
871 EXPECT_EQ(
872 "This file's name contains 0x5c (backslash) as the 2nd byte of Japanese "
873 "characater \"\x83\x5c\" when encoded in Shift JIS.",
874 contents);
875
876 ASSERT_TRUE(base::ReadFileToString(dir.Append(base::FilePath::FromUTF8Unsafe(
877 "新しいテキスト ドキュメント.txt")),
878 &contents));
879 EXPECT_EQ("This file name is coded in Shift JIS in the archive.", contents);
880 }
881
882 // Tests that a ZIP archive containing SJIS-encoded file names can be extracted
883 // even if the encoding is not specified. In this case, file names are
884 // interpreted as UTF-8, which leads to garbled names where invalid UTF-8
885 // sequences are replaced with the character �. Nevertheless, the files are
886 // safely extracted and readable.
TEST_F(ZipTest,UnzipSjisAsUtf8)887 TEST_F(ZipTest, UnzipSjisAsUtf8) {
888 ASSERT_TRUE(zip::Unzip(GetDataDirectory().AppendASCII("SJIS Bug 846195.zip"),
889 test_dir_));
890
891 EXPECT_FALSE(base::DirectoryExists(
892 test_dir_.Append(base::FilePath::FromUTF8Unsafe("新しいフォルダ"))));
893
894 const base::FilePath dir =
895 test_dir_.Append(base::FilePath::FromUTF8Unsafe("�V�����t�H���_"));
896 EXPECT_TRUE(base::DirectoryExists(dir));
897
898 std::string contents;
899 ASSERT_TRUE(base::ReadFileToString(
900 dir.Append(base::FilePath::FromUTF8Unsafe("SJIS_835C_�\\.txt")),
901 &contents));
902 EXPECT_EQ(
903 "This file's name contains 0x5c (backslash) as the 2nd byte of Japanese "
904 "characater \"\x83\x5c\" when encoded in Shift JIS.",
905 contents);
906
907 ASSERT_TRUE(base::ReadFileToString(dir.Append(base::FilePath::FromUTF8Unsafe(
908 "�V�����e�L�X�g �h�L�������g.txt")),
909 &contents));
910 EXPECT_EQ("This file name is coded in Shift JIS in the archive.", contents);
911 }
912
TEST_F(ZipTest,Zip)913 TEST_F(ZipTest, Zip) {
914 base::FilePath src_dir = GetDataDirectory().AppendASCII("test");
915
916 base::ScopedTempDir temp_dir;
917 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
918 base::FilePath zip_file = temp_dir.GetPath().AppendASCII("out.zip");
919
920 EXPECT_TRUE(zip::Zip(src_dir, zip_file, /*include_hidden_files=*/true));
921 TestUnzipFile(zip_file, true);
922 }
923
TEST_F(ZipTest,ZipIgnoreHidden)924 TEST_F(ZipTest, ZipIgnoreHidden) {
925 base::FilePath src_dir = GetDataDirectory().AppendASCII("test");
926
927 base::ScopedTempDir temp_dir;
928 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
929 base::FilePath zip_file = temp_dir.GetPath().AppendASCII("out.zip");
930
931 EXPECT_TRUE(zip::Zip(src_dir, zip_file, /*include_hidden_files=*/false));
932 TestUnzipFile(zip_file, false);
933 }
934
TEST_F(ZipTest,ZipNonASCIIDir)935 TEST_F(ZipTest, ZipNonASCIIDir) {
936 base::FilePath src_dir = GetDataDirectory().AppendASCII("test");
937
938 base::ScopedTempDir temp_dir;
939 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
940 // Append 'Тест' (in cyrillic).
941 base::FilePath src_dir_russian = temp_dir.GetPath().Append(
942 base::FilePath::FromUTF8Unsafe("\xD0\xA2\xD0\xB5\xD1\x81\xD1\x82"));
943 base::CopyDirectory(src_dir, src_dir_russian, true);
944 base::FilePath zip_file = temp_dir.GetPath().AppendASCII("out_russian.zip");
945
946 EXPECT_TRUE(zip::Zip(src_dir_russian, zip_file, true));
947 TestUnzipFile(zip_file, true);
948 }
949
TEST_F(ZipTest,ZipTimeStamp)950 TEST_F(ZipTest, ZipTimeStamp) {
951 // The dates tested are arbitrary, with some constraints. The zip format can
952 // only store years from 1980 to 2107 and an even number of seconds, due to it
953 // using the ms dos date format.
954
955 // Valid arbitrary date.
956 TestTimeStamp("23 Oct 1997 23:22:20", VALID_YEAR);
957
958 // Date before 1980, zip format limitation, must default to unix epoch.
959 TestTimeStamp("29 Dec 1979 21:00:10", INVALID_YEAR);
960
961 // Despite the minizip headers telling the maximum year should be 2044, it
962 // can actually go up to 2107. Beyond that, the dos date format cannot store
963 // the year (2107-1980=127). To test that limit, the input file needs to be
964 // touched, but the code that modifies the file access and modification times
965 // relies on time_t which is defined as long, therefore being in many
966 // platforms just a 4-byte integer, like 32-bit Mac OSX or linux. As such, it
967 // suffers from the year-2038 bug. Therefore 2038 is the highest we can test
968 // in all platforms reliably.
969 TestTimeStamp("02 Jan 2038 23:59:58", VALID_YEAR);
970 }
971
972 #if defined(OS_POSIX) || defined(OS_FUCHSIA)
TEST_F(ZipTest,ZipFiles)973 TEST_F(ZipTest, ZipFiles) {
974 base::FilePath src_dir = GetDataDirectory().AppendASCII("test");
975
976 base::ScopedTempDir temp_dir;
977 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
978 base::FilePath zip_name = temp_dir.GetPath().AppendASCII("out.zip");
979
980 base::File zip_file(zip_name,
981 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
982 ASSERT_TRUE(zip_file.IsValid());
983 EXPECT_TRUE(
984 zip::ZipFiles(src_dir, zip_file_list_, zip_file.GetPlatformFile()));
985 zip_file.Close();
986
987 zip::ZipReader reader;
988 EXPECT_TRUE(reader.Open(zip_name));
989 EXPECT_EQ(zip_file_list_.size(), static_cast<size_t>(reader.num_entries()));
990 for (size_t i = 0; i < zip_file_list_.size(); ++i) {
991 const zip::ZipReader::Entry* const entry = reader.Next();
992 ASSERT_TRUE(entry);
993 EXPECT_EQ(entry->path, zip_file_list_[i]);
994 }
995 }
996 #endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
997
TEST_F(ZipTest,UnzipFilesWithIncorrectSize)998 TEST_F(ZipTest, UnzipFilesWithIncorrectSize) {
999 // test_mismatch_size.zip contains files with names from 0.txt to 7.txt with
1000 // sizes from 0 to 7 bytes respectively, but the metadata in the zip file says
1001 // the uncompressed size is 3 bytes. The ZipReader and minizip code needs to
1002 // be clever enough to get all the data out.
1003 base::FilePath test_zip_file =
1004 GetDataDirectory().AppendASCII("test_mismatch_size.zip");
1005
1006 base::ScopedTempDir scoped_temp_dir;
1007 ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
1008 const base::FilePath& temp_dir = scoped_temp_dir.GetPath();
1009
1010 ASSERT_TRUE(zip::Unzip(test_zip_file, temp_dir));
1011 EXPECT_TRUE(base::DirectoryExists(temp_dir.AppendASCII("d")));
1012
1013 for (int i = 0; i < 8; i++) {
1014 SCOPED_TRACE(base::StringPrintf("Processing %d.txt", i));
1015 base::FilePath file_path =
1016 temp_dir.AppendASCII(base::StringPrintf("%d.txt", i));
1017 int64_t file_size = -1;
1018 EXPECT_TRUE(base::GetFileSize(file_path, &file_size));
1019 EXPECT_EQ(static_cast<int64_t>(i), file_size);
1020 }
1021 }
1022
TEST_F(ZipTest,ZipWithFileAccessor)1023 TEST_F(ZipTest, ZipWithFileAccessor) {
1024 base::FilePath zip_file;
1025 ASSERT_TRUE(base::CreateTemporaryFile(&zip_file));
1026 VirtualFileSystem file_accessor;
1027 const zip::ZipParams params{.file_accessor = &file_accessor,
1028 .dest_file = zip_file};
1029 ASSERT_TRUE(zip::Zip(params));
1030
1031 base::ScopedTempDir scoped_temp_dir;
1032 ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
1033 const base::FilePath& temp_dir = scoped_temp_dir.GetPath();
1034 ASSERT_TRUE(zip::Unzip(zip_file, temp_dir));
1035 base::FilePath bar_dir = temp_dir.AppendASCII("bar");
1036 EXPECT_TRUE(base::DirectoryExists(bar_dir));
1037 std::string file_content;
1038 EXPECT_TRUE(
1039 base::ReadFileToString(temp_dir.AppendASCII("foo.txt"), &file_content));
1040 EXPECT_EQ(VirtualFileSystem::kFooContent, file_content);
1041 EXPECT_TRUE(
1042 base::ReadFileToString(bar_dir.AppendASCII("bar1.txt"), &file_content));
1043 EXPECT_EQ(VirtualFileSystem::kBar1Content, file_content);
1044 EXPECT_TRUE(
1045 base::ReadFileToString(bar_dir.AppendASCII("bar2.txt"), &file_content));
1046 EXPECT_EQ(VirtualFileSystem::kBar2Content, file_content);
1047 }
1048
1049 // Tests progress reporting while zipping files.
TEST_F(ZipTest,ZipProgress)1050 TEST_F(ZipTest, ZipProgress) {
1051 base::FilePath src_dir = GetDataDirectory().AppendASCII("test");
1052
1053 base::ScopedTempDir temp_dir;
1054 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
1055 base::FilePath zip_file = temp_dir.GetPath().AppendASCII("out.zip");
1056
1057 int progress_count = 0;
1058 zip::Progress last_progress;
1059
1060 zip::ProgressCallback progress_callback =
1061 base::BindLambdaForTesting([&](const zip::Progress& progress) {
1062 progress_count++;
1063 LOG(INFO) << "Progress #" << progress_count << ": " << progress;
1064
1065 // Progress should only go forwards.
1066 EXPECT_GE(progress.bytes, last_progress.bytes);
1067 EXPECT_GE(progress.files, last_progress.files);
1068 EXPECT_GE(progress.directories, last_progress.directories);
1069
1070 last_progress = progress;
1071 return true;
1072 });
1073
1074 EXPECT_TRUE(zip::Zip({.src_dir = src_dir,
1075 .dest_file = zip_file,
1076 .progress_callback = std::move(progress_callback)}));
1077
1078 EXPECT_EQ(progress_count, 14);
1079 EXPECT_EQ(last_progress.bytes, 13546);
1080 EXPECT_EQ(last_progress.files, 5);
1081 EXPECT_EQ(last_progress.directories, 2);
1082
1083 TestUnzipFile(zip_file, true);
1084 }
1085
1086 // Tests throttling of progress reporting while zipping files.
TEST_F(ZipTest,ZipProgressPeriod)1087 TEST_F(ZipTest, ZipProgressPeriod) {
1088 base::FilePath src_dir = GetDataDirectory().AppendASCII("test");
1089
1090 base::ScopedTempDir temp_dir;
1091 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
1092 base::FilePath zip_file = temp_dir.GetPath().AppendASCII("out.zip");
1093
1094 int progress_count = 0;
1095 zip::Progress last_progress;
1096
1097 zip::ProgressCallback progress_callback =
1098 base::BindLambdaForTesting([&](const zip::Progress& progress) {
1099 progress_count++;
1100 LOG(INFO) << "Progress #" << progress_count << ": " << progress;
1101
1102 // Progress should only go forwards.
1103 EXPECT_GE(progress.bytes, last_progress.bytes);
1104 EXPECT_GE(progress.files, last_progress.files);
1105 EXPECT_GE(progress.directories, last_progress.directories);
1106
1107 last_progress = progress;
1108 return true;
1109 });
1110
1111 EXPECT_TRUE(zip::Zip({.src_dir = src_dir,
1112 .dest_file = zip_file,
1113 .progress_callback = std::move(progress_callback),
1114 .progress_period = base::Hours(1)}));
1115
1116 // We expect only 2 progress reports: the first one, and the last one.
1117 EXPECT_EQ(progress_count, 2);
1118 EXPECT_EQ(last_progress.bytes, 13546);
1119 EXPECT_EQ(last_progress.files, 5);
1120 EXPECT_EQ(last_progress.directories, 2);
1121
1122 TestUnzipFile(zip_file, true);
1123 }
1124
1125 // Tests cancellation while zipping files.
TEST_F(ZipTest,ZipCancel)1126 TEST_F(ZipTest, ZipCancel) {
1127 base::FilePath src_dir = GetDataDirectory().AppendASCII("test");
1128
1129 base::ScopedTempDir temp_dir;
1130 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
1131 base::FilePath zip_file = temp_dir.GetPath().AppendASCII("out.zip");
1132
1133 // First: establish the number of possible interruption points.
1134 int progress_count = 0;
1135
1136 EXPECT_TRUE(zip::Zip({.src_dir = src_dir,
1137 .dest_file = zip_file,
1138 .progress_callback = base::BindLambdaForTesting(
1139 [&progress_count](const zip::Progress&) {
1140 progress_count++;
1141 return true;
1142 })}));
1143
1144 EXPECT_EQ(progress_count, 14);
1145
1146 // Second: exercise each and every interruption point.
1147 for (int i = progress_count; i > 0; i--) {
1148 int j = 0;
1149 EXPECT_FALSE(zip::Zip({.src_dir = src_dir,
1150 .dest_file = zip_file,
1151 .progress_callback = base::BindLambdaForTesting(
1152 [i, &j](const zip::Progress&) {
1153 j++;
1154 // Callback shouldn't be called again after
1155 // having returned false once.
1156 EXPECT_LE(j, i);
1157 return j < i;
1158 })}));
1159
1160 EXPECT_EQ(j, i);
1161 }
1162 }
1163
1164 // Tests zip::internal::GetCompressionMethod()
TEST_F(ZipTest,GetCompressionMethod)1165 TEST_F(ZipTest, GetCompressionMethod) {
1166 using zip::internal::GetCompressionMethod;
1167 using zip::internal::kDeflated;
1168 using zip::internal::kStored;
1169
1170 EXPECT_EQ(GetCompressionMethod(FP("")), kDeflated);
1171 EXPECT_EQ(GetCompressionMethod(FP("NoExtension")), kDeflated);
1172 EXPECT_EQ(GetCompressionMethod(FP("Folder.zip").Append(FP("NoExtension"))),
1173 kDeflated);
1174 EXPECT_EQ(GetCompressionMethod(FP("Name.txt")), kDeflated);
1175 EXPECT_EQ(GetCompressionMethod(FP("Name.zip")), kStored);
1176 EXPECT_EQ(GetCompressionMethod(FP("Name....zip")), kStored);
1177 EXPECT_EQ(GetCompressionMethod(FP("Name.zip")), kStored);
1178 EXPECT_EQ(GetCompressionMethod(FP("NAME.ZIP")), kStored);
1179 EXPECT_EQ(GetCompressionMethod(FP("Name.gz")), kStored);
1180 EXPECT_EQ(GetCompressionMethod(FP("Name.tar.gz")), kStored);
1181 EXPECT_EQ(GetCompressionMethod(FP("Name.tar")), kDeflated);
1182
1183 // This one is controversial.
1184 EXPECT_EQ(GetCompressionMethod(FP(".zip")), kStored);
1185 }
1186
1187 // Tests that files put inside a ZIP are effectively compressed.
TEST_F(ZipTest,Compressed)1188 TEST_F(ZipTest, Compressed) {
1189 base::ScopedTempDir temp_dir;
1190 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
1191
1192 const base::FilePath src_dir = temp_dir.GetPath().AppendASCII("input");
1193 EXPECT_TRUE(base::CreateDirectory(src_dir));
1194
1195 // Create some dummy source files.
1196 for (const base::StringPiece s : {"foo", "bar.txt", ".hidden"}) {
1197 base::File f(src_dir.AppendASCII(s),
1198 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
1199 ASSERT_TRUE(f.SetLength(5000));
1200 }
1201
1202 // Zip the source files.
1203 const base::FilePath dest_file = temp_dir.GetPath().AppendASCII("dest.zip");
1204 EXPECT_TRUE(zip::Zip({.src_dir = src_dir,
1205 .dest_file = dest_file,
1206 .include_hidden_files = true}));
1207
1208 // Since the source files compress well, the destination ZIP file should be
1209 // smaller than the source files.
1210 int64_t dest_file_size;
1211 ASSERT_TRUE(base::GetFileSize(dest_file, &dest_file_size));
1212 EXPECT_GT(dest_file_size, 300);
1213 EXPECT_LT(dest_file_size, 1000);
1214 }
1215
1216 // Tests that a ZIP put inside a ZIP is simply stored instead of being
1217 // compressed.
TEST_F(ZipTest,NestedZip)1218 TEST_F(ZipTest, NestedZip) {
1219 base::ScopedTempDir temp_dir;
1220 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
1221
1222 const base::FilePath src_dir = temp_dir.GetPath().AppendASCII("input");
1223 EXPECT_TRUE(base::CreateDirectory(src_dir));
1224
1225 // Create a dummy ZIP file. This is not a valid ZIP file, but for the purpose
1226 // of this test, it doesn't really matter.
1227 const int64_t src_size = 5000;
1228
1229 {
1230 base::File f(src_dir.AppendASCII("src.zip"),
1231 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
1232 ASSERT_TRUE(f.SetLength(src_size));
1233 }
1234
1235 // Zip the dummy ZIP file.
1236 const base::FilePath dest_file = temp_dir.GetPath().AppendASCII("dest.zip");
1237 EXPECT_TRUE(zip::Zip({.src_dir = src_dir, .dest_file = dest_file}));
1238
1239 // Since the dummy source (inner) ZIP file should simply be stored in the
1240 // destination (outer) ZIP file, the destination file should be bigger than
1241 // the source file, but not much bigger.
1242 int64_t dest_file_size;
1243 ASSERT_TRUE(base::GetFileSize(dest_file, &dest_file_size));
1244 EXPECT_GT(dest_file_size, src_size + 100);
1245 EXPECT_LT(dest_file_size, src_size + 300);
1246 }
1247
1248 // Tests that there is no 2GB or 4GB limits. Tests that big files can be zipped
1249 // (crbug.com/1207737) and that big ZIP files can be created
1250 // (crbug.com/1221447). Tests that the big ZIP can be opened, that its entries
1251 // are correctly enumerated (crbug.com/1298347), and that the big file can be
1252 // extracted.
1253 //
1254 // Because this test is dealing with big files, it tends to take a lot of disk
1255 // space and time (crbug.com/1299736). Therefore, it only gets run on a few bots
1256 // (ChromeOS and Windows).
1257 //
1258 // This test is too slow with TSAN.
1259 // OS Fuchsia does not seem to support large files.
1260 // Some 32-bit Android waterfall and CQ try bots are running out of space when
1261 // performing this test (android-asan, android-11-x86-rel,
1262 // android-marshmallow-x86-rel-non-cq).
1263 // Some Mac, Linux and Debug (dbg) bots tend to time out when performing this
1264 // test (crbug.com/1299736, crbug.com/1300448).
1265 #if defined(THREAD_SANITIZER) || BUILDFLAG(IS_FUCHSIA) || \
1266 BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
1267 BUILDFLAG(IS_CHROMEOS_LACROS) || !defined(NDEBUG)
TEST_F(ZipTest,DISABLED_BigFile)1268 TEST_F(ZipTest, DISABLED_BigFile) {
1269 #else
1270 TEST_F(ZipTest, BigFile) {
1271 #endif
1272 base::ScopedTempDir temp_dir;
1273 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
1274
1275 const base::FilePath src_dir = temp_dir.GetPath().AppendASCII("input");
1276 EXPECT_TRUE(base::CreateDirectory(src_dir));
1277
1278 // Create a big dummy ZIP file. This is not a valid ZIP file, but for the
1279 // purpose of this test, it doesn't really matter.
1280 const int64_t src_size = 5'000'000'000;
1281
1282 const base::FilePath src_file = src_dir.AppendASCII("src.zip");
1283 LOG(INFO) << "Creating big file " << src_file;
1284 {
1285 base::File f(src_file, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
1286 ASSERT_TRUE(f.SetLength(src_size));
1287 }
1288
1289 // Zip the dummy ZIP file.
1290 const base::FilePath dest_file = temp_dir.GetPath().AppendASCII("dest.zip");
1291 LOG(INFO) << "Zipping big file into " << dest_file;
1292 zip::ProgressCallback progress_callback =
1293 base::BindLambdaForTesting([&](const zip::Progress& progress) {
1294 LOG(INFO) << "Zipping... " << std::setw(3)
1295 << (100 * progress.bytes / src_size) << "%";
1296 return true;
1297 });
1298 EXPECT_TRUE(zip::Zip({.src_dir = src_dir,
1299 .dest_file = dest_file,
1300 .progress_callback = std::move(progress_callback),
1301 .progress_period = base::Seconds(1)}));
1302
1303 // Since the dummy source (inner) ZIP file should simply be stored in the
1304 // destination (outer) ZIP file, the destination file should be bigger than
1305 // the source file, but not much bigger.
1306 int64_t dest_file_size;
1307 ASSERT_TRUE(base::GetFileSize(dest_file, &dest_file_size));
1308 EXPECT_GT(dest_file_size, src_size + 100);
1309 EXPECT_LT(dest_file_size, src_size + 300);
1310
1311 LOG(INFO) << "Reading big ZIP " << dest_file;
1312 zip::ZipReader reader;
1313 ASSERT_TRUE(reader.Open(dest_file));
1314
1315 const zip::ZipReader::Entry* const entry = reader.Next();
1316 ASSERT_TRUE(entry);
1317 EXPECT_EQ(FP("src.zip"), entry->path);
1318 EXPECT_EQ(src_size, entry->original_size);
1319 EXPECT_FALSE(entry->is_directory);
1320 EXPECT_FALSE(entry->is_encrypted);
1321
1322 ProgressWriterDelegate writer(src_size);
1323 EXPECT_TRUE(reader.ExtractCurrentEntry(&writer,
1324 std::numeric_limits<uint64_t>::max()));
1325 EXPECT_EQ(src_size, writer.received_bytes());
1326
1327 EXPECT_FALSE(reader.Next());
1328 EXPECT_TRUE(reader.ok());
1329 }
1330
1331 } // namespace
1332