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