• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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