1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "ziparchive/zip_archive.h"
18#include "ziparchive/zip_writer.h"
19
20#include <android-base/test_utils.h>
21#include <gtest/gtest.h>
22#include <time.h>
23#include <memory>
24#include <vector>
25
26struct zipwriter : public ::testing::Test {
27  TemporaryFile* temp_file_;
28  int fd_;
29  FILE* file_;
30
31  void SetUp() override {
32    temp_file_ = new TemporaryFile();
33    fd_ = temp_file_->fd;
34    file_ = fdopen(fd_, "w");
35    ASSERT_NE(file_, nullptr);
36  }
37
38  void TearDown() override {
39    fclose(file_);
40    delete temp_file_;
41  }
42};
43
44TEST_F(zipwriter, WriteUncompressedZipWithOneFile) {
45  ZipWriter writer(file_);
46
47  const char* expected = "hello";
48
49  ASSERT_EQ(0, writer.StartEntry("file.txt", 0));
50  ASSERT_EQ(0, writer.WriteBytes("he", 2));
51  ASSERT_EQ(0, writer.WriteBytes("llo", 3));
52  ASSERT_EQ(0, writer.FinishEntry());
53  ASSERT_EQ(0, writer.Finish());
54
55  ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
56
57  ZipArchiveHandle handle;
58  ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
59
60  ZipEntry data;
61  ASSERT_EQ(0, FindEntry(handle, ZipString("file.txt"), &data));
62  EXPECT_EQ(strlen(expected), data.compressed_length);
63  EXPECT_EQ(strlen(expected), data.uncompressed_length);
64  EXPECT_EQ(kCompressStored, data.method);
65
66  char buffer[6];
67  EXPECT_EQ(0,
68            ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(&buffer), sizeof(buffer)));
69  buffer[5] = 0;
70
71  EXPECT_STREQ(expected, buffer);
72
73  CloseArchive(handle);
74}
75
76TEST_F(zipwriter, WriteUncompressedZipWithMultipleFiles) {
77  ZipWriter writer(file_);
78
79  ASSERT_EQ(0, writer.StartEntry("file.txt", 0));
80  ASSERT_EQ(0, writer.WriteBytes("he", 2));
81  ASSERT_EQ(0, writer.FinishEntry());
82
83  ASSERT_EQ(0, writer.StartEntry("file/file.txt", 0));
84  ASSERT_EQ(0, writer.WriteBytes("llo", 3));
85  ASSERT_EQ(0, writer.FinishEntry());
86
87  ASSERT_EQ(0, writer.StartEntry("file/file2.txt", 0));
88  ASSERT_EQ(0, writer.FinishEntry());
89
90  ASSERT_EQ(0, writer.Finish());
91
92  ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
93
94  ZipArchiveHandle handle;
95  ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
96
97  char buffer[4];
98  ZipEntry data;
99
100  ASSERT_EQ(0, FindEntry(handle, ZipString("file.txt"), &data));
101  EXPECT_EQ(kCompressStored, data.method);
102  EXPECT_EQ(2u, data.compressed_length);
103  EXPECT_EQ(2u, data.uncompressed_length);
104  ASSERT_EQ(0,
105            ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(buffer), arraysize(buffer)));
106  buffer[2] = 0;
107  EXPECT_STREQ("he", buffer);
108
109  ASSERT_EQ(0, FindEntry(handle, ZipString("file/file.txt"), &data));
110  EXPECT_EQ(kCompressStored, data.method);
111  EXPECT_EQ(3u, data.compressed_length);
112  EXPECT_EQ(3u, data.uncompressed_length);
113  ASSERT_EQ(0,
114            ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(buffer), arraysize(buffer)));
115  buffer[3] = 0;
116  EXPECT_STREQ("llo", buffer);
117
118  ASSERT_EQ(0, FindEntry(handle, ZipString("file/file2.txt"), &data));
119  EXPECT_EQ(kCompressStored, data.method);
120  EXPECT_EQ(0u, data.compressed_length);
121  EXPECT_EQ(0u, data.uncompressed_length);
122
123  CloseArchive(handle);
124}
125
126TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedFlag) {
127  ZipWriter writer(file_);
128
129  ASSERT_EQ(0, writer.StartEntry("align.txt", ZipWriter::kAlign32));
130  ASSERT_EQ(0, writer.WriteBytes("he", 2));
131  ASSERT_EQ(0, writer.FinishEntry());
132  ASSERT_EQ(0, writer.Finish());
133
134  ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
135
136  ZipArchiveHandle handle;
137  ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
138
139  ZipEntry data;
140  ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data));
141  EXPECT_EQ(0, data.offset & 0x03);
142
143  CloseArchive(handle);
144}
145
146void ConvertZipTimeToTm(uint32_t& zip_time, struct tm* tm) {
147  memset(tm, 0, sizeof(struct tm));
148  tm->tm_hour = (zip_time >> 11) & 0x1f;
149  tm->tm_min = (zip_time >> 5) & 0x3f;
150  tm->tm_sec = (zip_time & 0x1f) << 1;
151
152  tm->tm_year = ((zip_time >> 25) & 0x7f) + 80;
153  tm->tm_mon = ((zip_time >> 21) & 0xf) - 1;
154  tm->tm_mday = (zip_time >> 16) & 0x1f;
155}
156
157static struct tm MakeTm() {
158  struct tm tm;
159  memset(&tm, 0, sizeof(struct tm));
160  tm.tm_year = 2001 - 1900;
161  tm.tm_mon = 1;
162  tm.tm_mday = 12;
163  tm.tm_hour = 18;
164  tm.tm_min = 30;
165  tm.tm_sec = 20;
166  return tm;
167}
168
169TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedFlagAndTime) {
170  ZipWriter writer(file_);
171
172  struct tm tm = MakeTm();
173  time_t time = mktime(&tm);
174  ASSERT_EQ(0, writer.StartEntryWithTime("align.txt", ZipWriter::kAlign32, time));
175  ASSERT_EQ(0, writer.WriteBytes("he", 2));
176  ASSERT_EQ(0, writer.FinishEntry());
177  ASSERT_EQ(0, writer.Finish());
178
179  ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
180
181  ZipArchiveHandle handle;
182  ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
183
184  ZipEntry data;
185  ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data));
186  EXPECT_EQ(0, data.offset & 0x03);
187
188  struct tm mod;
189  ConvertZipTimeToTm(data.mod_time, &mod);
190  EXPECT_EQ(tm.tm_sec, mod.tm_sec);
191  EXPECT_EQ(tm.tm_min, mod.tm_min);
192  EXPECT_EQ(tm.tm_hour, mod.tm_hour);
193  EXPECT_EQ(tm.tm_mday, mod.tm_mday);
194  EXPECT_EQ(tm.tm_mon, mod.tm_mon);
195  EXPECT_EQ(tm.tm_year, mod.tm_year);
196
197  CloseArchive(handle);
198}
199
200TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedValue) {
201  ZipWriter writer(file_);
202
203  ASSERT_EQ(0, writer.StartAlignedEntry("align.txt", 0, 4096));
204  ASSERT_EQ(0, writer.WriteBytes("he", 2));
205  ASSERT_EQ(0, writer.FinishEntry());
206  ASSERT_EQ(0, writer.Finish());
207
208  ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
209
210  ZipArchiveHandle handle;
211  ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
212
213  ZipEntry data;
214  ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data));
215  EXPECT_EQ(0, data.offset & 0xfff);
216
217  CloseArchive(handle);
218}
219
220TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedValueAndTime) {
221  ZipWriter writer(file_);
222
223  struct tm tm = MakeTm();
224  time_t time = mktime(&tm);
225  ASSERT_EQ(0, writer.StartAlignedEntryWithTime("align.txt", 0, time, 4096));
226  ASSERT_EQ(0, writer.WriteBytes("he", 2));
227  ASSERT_EQ(0, writer.FinishEntry());
228  ASSERT_EQ(0, writer.Finish());
229
230  ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
231
232  ZipArchiveHandle handle;
233  ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
234
235  ZipEntry data;
236  ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data));
237  EXPECT_EQ(0, data.offset & 0xfff);
238
239  struct tm mod;
240  ConvertZipTimeToTm(data.mod_time, &mod);
241  EXPECT_EQ(tm.tm_sec, mod.tm_sec);
242  EXPECT_EQ(tm.tm_min, mod.tm_min);
243  EXPECT_EQ(tm.tm_hour, mod.tm_hour);
244  EXPECT_EQ(tm.tm_mday, mod.tm_mday);
245  EXPECT_EQ(tm.tm_mon, mod.tm_mon);
246  EXPECT_EQ(tm.tm_year, mod.tm_year);
247
248  CloseArchive(handle);
249}
250
251TEST_F(zipwriter, WriteCompressedZipWithOneFile) {
252  ZipWriter writer(file_);
253
254  ASSERT_EQ(0, writer.StartEntry("file.txt", ZipWriter::kCompress));
255  ASSERT_EQ(0, writer.WriteBytes("helo", 4));
256  ASSERT_EQ(0, writer.FinishEntry());
257  ASSERT_EQ(0, writer.Finish());
258
259  ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
260
261  ZipArchiveHandle handle;
262  ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
263
264  ZipEntry data;
265  ASSERT_EQ(0, FindEntry(handle, ZipString("file.txt"), &data));
266  EXPECT_EQ(kCompressDeflated, data.method);
267  EXPECT_EQ(4u, data.uncompressed_length);
268
269  char buffer[5];
270  ASSERT_EQ(0,
271            ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(buffer), arraysize(buffer)));
272  buffer[4] = 0;
273
274  EXPECT_STREQ("helo", buffer);
275
276  CloseArchive(handle);
277}
278
279TEST_F(zipwriter, WriteCompressedZipFlushFull) {
280  // This exact data will cause the Finish() to require multiple calls
281  // to deflate() because the ZipWriter buffer isn't big enough to hold
282  // the entire compressed data buffer.
283  constexpr size_t kBufSize = 10000000;
284  std::vector<uint8_t> buffer(kBufSize);
285  size_t prev = 1;
286  for (size_t i = 0; i < kBufSize; i++) {
287    buffer[i] = i + prev;
288    prev = i;
289  }
290
291  ZipWriter writer(file_);
292  ASSERT_EQ(0, writer.StartEntry("file.txt", ZipWriter::kCompress));
293  ASSERT_EQ(0, writer.WriteBytes(buffer.data(), buffer.size()));
294  ASSERT_EQ(0, writer.FinishEntry());
295  ASSERT_EQ(0, writer.Finish());
296
297  ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
298
299  ZipArchiveHandle handle;
300  ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
301
302  ZipEntry data;
303  ASSERT_EQ(0, FindEntry(handle, ZipString("file.txt"), &data));
304  EXPECT_EQ(kCompressDeflated, data.method);
305  EXPECT_EQ(kBufSize, data.uncompressed_length);
306
307  std::vector<uint8_t> decompress(kBufSize);
308  memset(decompress.data(), 0, kBufSize);
309  ASSERT_EQ(0, ExtractToMemory(handle, &data, decompress.data(), decompress.size()));
310  EXPECT_EQ(0, memcmp(decompress.data(), buffer.data(), kBufSize))
311      << "Input buffer and output buffer are different.";
312
313  CloseArchive(handle);
314}
315
316TEST_F(zipwriter, CheckStartEntryErrors) {
317  ZipWriter writer(file_);
318
319  ASSERT_EQ(-5, writer.StartAlignedEntry("align.txt", ZipWriter::kAlign32, 4096));
320  ASSERT_EQ(-6, writer.StartAlignedEntry("align.txt", 0, 3));
321}
322