1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.h"
6 
7 #include <stdlib.h>
8 #include <time.h>
9 
10 #include <map>
11 #include <memory>
12 #include <string>
13 #include <utility>
14 #include <vector>
15 
16 #include "base/check.h"
17 #include "base/files/file.h"
18 #include "base/files/file_path.h"
19 #include "base/files/file_util.h"
20 #include "base/hash/md5.h"
21 #include "base/optional.h"
22 #include "base/path_service.h"
23 #include "base/strings/string_piece.h"
24 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive.h"
25 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/volume_reader.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 
28 namespace {
29 
30 constexpr char kEncryptedZipPassphrase[] = "test123";
31 
32 struct FileInfo {
33   int64_t size;
34   bool is_directory;
35   time_t mod_time;
36   const char* md5_sum;
37 };
38 using FileInfoMap = std::map<std::string, FileInfo>;
39 const FileInfoMap kSmallZipFiles = {
40     {"file1", {15, false, 1407920154, "b4d9b82bb1cd97aa6191843149df18e6"}},
41     {"file2", {33, false, 1407920174, "b864e9456deb246b018c49ef831f7ca7"}},
42     {"dir/", {0, true, 1407920220, nullptr}},
43     {"dir/file3", {56, false, 1407920220, "bffbca4992b32db8ed72bfc2c88e7f11"}},
44 };
45 
46 const FileInfoMap kPkEncryptedZipFiles = {
47     {"file1", {15, false, 1407920154, "b4d9b82bb1cd97aa6191843149df18e6"}},
48     {"file2", {33, false, 1407920174, "b864e9456deb246b018c49ef831f7ca7"}},
49     {"dir/", {0, true, 1407920220, nullptr}},
50     {"dir/file3", {56, false, 1407920220, "bffbca4992b32db8ed72bfc2c88e7f11"}},
51 };
52 
53 const FileInfoMap kAesEncryptedZipFiles = {
54     {"file1", {15, false, 1528178134, "b4d9b82bb1cd97aa6191843149df18e6"}},
55     {"file2", {33, false, 1528178134, "b864e9456deb246b018c49ef831f7ca7"}},
56     {"dir/", {0, true, 1528178134, nullptr}},
57     {"dir/file3", {56, false, 1528178134, "bffbca4992b32db8ed72bfc2c88e7f11"}},
58 };
59 
60 class TestVolumeReader : public VolumeReader {
61  public:
TestVolumeReader(base::FilePath path)62   explicit TestVolumeReader(base::FilePath path)
63       : file_(path, base::File::FLAG_OPEN | base::File::FLAG_READ) {}
64 
Read(int64_t bytes_to_read,const void ** destination_buffer)65   int64_t Read(int64_t bytes_to_read,
66                const void** destination_buffer) override {
67     buffer_.resize(bytes_to_read);
68     *destination_buffer = buffer_.data();
69     return file_.ReadAtCurrentPos(buffer_.data(), bytes_to_read);
70   }
71 
Seek(int64_t offset,base::File::Whence whence)72   int64_t Seek(int64_t offset, base::File::Whence whence) override {
73     return file_.Seek(whence, offset);
74   }
75 
Passphrase()76   base::Optional<std::string> Passphrase() override { return passphrase_; }
77 
offset()78   int64_t offset() override { return Seek(0, base::File::FROM_CURRENT); }
79 
archive_size()80   int64_t archive_size() override { return file_.GetLength(); }
81 
set_passphrase(base::Optional<std::string> passphrase)82   void set_passphrase(base::Optional<std::string> passphrase) {
83     passphrase_ = std::move(passphrase);
84   }
85 
86  private:
87   base::File file_;
88   std::vector<char> buffer_;
89   base::Optional<std::string> passphrase_;
90 };
91 
92 class VolumeArchiveMinizipTest : public testing::Test {
93  public:
94   VolumeArchiveMinizipTest() = default;
95 
GetTestZipPath(const std::string & name)96   base::FilePath GetTestZipPath(const std::string& name) {
97     base::FilePath root_path;
98     CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &root_path));
99 
100     base::FilePath full_path =
101         root_path
102             .Append("chrome/browser/resources/chromeos/zip_archiver/test/data")
103             .Append(name);
104     CHECK(base::PathExists(full_path)) << full_path.value();
105     return full_path;
106   }
107 
SetUp()108   void SetUp() override {
109     // Zip files are stored as local time, but minizip will do some timezone
110     // conversion internally. Fix the time zone (to Perth, Australia) so make
111     // this test deterministic wherever it is run.
112     // NOTE: Perth is +8, but is set as -8 because of how POSIX defines the TZ
113     // variable.
114     setenv("TZ", "UTC-8", 1);
115     tzset();
116   }
117 
CheckFileInfo(VolumeArchiveMinizip * archive,const FileInfo & file_info)118   void CheckFileInfo(VolumeArchiveMinizip* archive, const FileInfo& file_info) {
119     std::string file_path;
120     bool is_utf8 = false;
121     int64_t size = -1;
122     bool is_directory = false;
123     time_t mod_time = 0;
124     auto result = archive->GetCurrentFileInfo(&file_path, &is_utf8, &size,
125                                               &is_directory, &mod_time);
126     EXPECT_EQ(result, VolumeArchive::RESULT_SUCCESS);
127     EXPECT_EQ(size, file_info.size);
128     EXPECT_EQ(is_directory, file_info.is_directory);
129     EXPECT_EQ(mod_time, file_info.mod_time);
130   }
131 
CheckFileContents(VolumeArchiveMinizip * archive,const FileInfo & file_info)132   void CheckFileContents(VolumeArchiveMinizip* archive,
133                          const FileInfo& file_info) {
134     base::MD5Context ctx;
135     base::MD5Init(&ctx);
136     const char* buffer = nullptr;
137     int64_t offset = 0;
138     while (offset < file_info.size) {
139       int64_t read =
140           archive->ReadData(offset, file_info.size - offset, &buffer);
141       ASSERT_GT(read, 0);
142       EXPECT_LE(read, file_info.size - offset);
143       base::MD5Update(&ctx, base::StringPiece(buffer, read));
144       offset += read;
145     }
146     EXPECT_EQ(file_info.size, offset);
147     EXPECT_EQ(0, archive->ReadData(offset, 1, &buffer));
148 
149     base::MD5Digest digest;
150     base::MD5Final(&digest, &ctx);
151     std::string md5_sum = base::MD5DigestToBase16(digest);
152     EXPECT_EQ(md5_sum, file_info.md5_sum);
153   }
154 
155  private:
156   std::unique_ptr<VolumeArchiveMinizip> archive_;
157 };
158 
TEST_F(VolumeArchiveMinizipTest,Basic)159 TEST_F(VolumeArchiveMinizipTest, Basic) {
160   std::unique_ptr<TestVolumeReader> reader =
161       std::make_unique<TestVolumeReader>(GetTestZipPath("small_zip.zip"));
162   VolumeArchiveMinizip archive(std::move(reader));
163   ASSERT_TRUE(archive.Init(""));
164 
165   auto file_infos = kSmallZipFiles;
166   while (true) {
167     std::string file_path;
168     bool is_utf8 = false;
169     int64_t size = -1;
170     bool is_directory = false;
171     time_t mod_time = 0;
172     auto result = archive.GetCurrentFileInfo(&file_path, &is_utf8, &size,
173                                              &is_directory, &mod_time);
174     EXPECT_EQ(result, VolumeArchive::RESULT_SUCCESS);
175     FileInfo fi = file_infos[file_path];
176     EXPECT_EQ(size, fi.size);
177     EXPECT_EQ(is_directory, fi.is_directory);
178     EXPECT_EQ(mod_time, fi.mod_time);
179     file_infos.erase(file_path);
180 
181     result = archive.GoToNextFile();
182     if (result == VolumeArchive::RESULT_EOF)
183       break;
184     ASSERT_EQ(result, VolumeArchive::RESULT_SUCCESS);
185   }
186   EXPECT_TRUE(file_infos.empty());
187 }
188 
TEST_F(VolumeArchiveMinizipTest,SeekHeader)189 TEST_F(VolumeArchiveMinizipTest, SeekHeader) {
190   std::unique_ptr<TestVolumeReader> reader =
191       std::make_unique<TestVolumeReader>(GetTestZipPath("small_zip.zip"));
192   VolumeArchiveMinizip archive(std::move(reader));
193   ASSERT_TRUE(archive.Init(""));
194 
195   for (auto it : kSmallZipFiles) {
196     EXPECT_TRUE(archive.SeekHeader(it.first));
197     CheckFileInfo(&archive, it.second);
198   }
199 }
200 
TEST_F(VolumeArchiveMinizipTest,SeekHeader_NonExistant)201 TEST_F(VolumeArchiveMinizipTest, SeekHeader_NonExistant) {
202   std::unique_ptr<TestVolumeReader> reader =
203       std::make_unique<TestVolumeReader>(GetTestZipPath("small_zip.zip"));
204   VolumeArchiveMinizip archive(std::move(reader));
205   ASSERT_TRUE(archive.Init(""));
206 
207   EXPECT_FALSE(archive.SeekHeader("file4"));
208   EXPECT_FALSE(archive.SeekHeader("dir/file4"));
209   EXPECT_FALSE(archive.SeekHeader("dir2/"));
210 }
211 
TEST_F(VolumeArchiveMinizipTest,Read)212 TEST_F(VolumeArchiveMinizipTest, Read) {
213   std::unique_ptr<TestVolumeReader> reader =
214       std::make_unique<TestVolumeReader>(GetTestZipPath("small_zip.zip"));
215   VolumeArchiveMinizip archive(std::move(reader));
216   ASSERT_TRUE(archive.Init(""));
217 
218   for (auto it : kSmallZipFiles) {
219     EXPECT_TRUE(archive.SeekHeader(it.first));
220     if (it.second.is_directory)
221       continue;
222 
223     CheckFileContents(&archive, it.second);
224   }
225 }
226 
227 // Regression test for https://crbug.com/915960
TEST_F(VolumeArchiveMinizipTest,ReadPartial)228 TEST_F(VolumeArchiveMinizipTest, ReadPartial) {
229   std::unique_ptr<TestVolumeReader> reader =
230       std::make_unique<TestVolumeReader>(GetTestZipPath("small_zip.zip"));
231   VolumeArchiveMinizip archive(std::move(reader));
232   ASSERT_TRUE(archive.Init(""));
233 
234   for (auto it : kSmallZipFiles) {
235     EXPECT_TRUE(archive.SeekHeader(it.first));
236     if (it.second.is_directory)
237       continue;
238 
239     // Read 1 byte.
240     const int32_t read_length = 1;
241     const char* buffer = nullptr;
242     int64_t read = archive.ReadData(0, read_length, &buffer);
243     EXPECT_EQ(read, read_length);
244   }
245 }
246 
TEST_F(VolumeArchiveMinizipTest,Encrypted_PkCrypt)247 TEST_F(VolumeArchiveMinizipTest, Encrypted_PkCrypt) {
248   std::unique_ptr<TestVolumeReader> reader =
249       std::make_unique<TestVolumeReader>(GetTestZipPath("encrypted.zip"));
250   reader->set_passphrase(
251       base::make_optional<std::string>(kEncryptedZipPassphrase));
252   VolumeArchiveMinizip archive(std::move(reader));
253   ASSERT_TRUE(archive.Init(""));
254 
255   for (auto it : kPkEncryptedZipFiles) {
256     EXPECT_TRUE(archive.SeekHeader(it.first));
257     CheckFileInfo(&archive, it.second);
258 
259     if (it.second.is_directory)
260       continue;
261 
262     CheckFileContents(&archive, it.second);
263   }
264 }
265 
TEST_F(VolumeArchiveMinizipTest,Encrypted_AES)266 TEST_F(VolumeArchiveMinizipTest, Encrypted_AES) {
267   std::unique_ptr<TestVolumeReader> reader =
268       std::make_unique<TestVolumeReader>(GetTestZipPath("encrypted_aes.zip"));
269   reader->set_passphrase(
270       base::make_optional<std::string>(kEncryptedZipPassphrase));
271   VolumeArchiveMinizip archive(std::move(reader));
272   ASSERT_TRUE(archive.Init(""));
273 
274   for (auto it : kAesEncryptedZipFiles) {
275     EXPECT_TRUE(archive.SeekHeader(it.first));
276     CheckFileInfo(&archive, it.second);
277 
278     if (it.second.is_directory)
279       continue;
280 
281     CheckFileContents(&archive, it.second);
282   }
283 }
284 
285 }  // namespace
286