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