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