1/*
2 * Copyright (C) 2013 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
19#include <errno.h>
20#include <getopt.h>
21#include <stdio.h>
22#include <unistd.h>
23#include <vector>
24
25#include <gtest/gtest.h>
26
27static std::string test_data_dir;
28
29static const std::string kMissingZip = "missing.zip";
30static const std::string kValidZip = "valid.zip";
31
32static const uint8_t kATxtContents[] = {
33  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
34  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
35  '\n'
36};
37
38static const uint8_t kBTxtContents[] = {
39  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
40  '\n'
41};
42
43static int32_t OpenArchiveWrapper(const std::string& name,
44                                  ZipArchiveHandle* handle) {
45  const std::string abs_path = test_data_dir + "/" + name;
46  return OpenArchive(abs_path.c_str(), handle);
47}
48
49static void AssertNameEquals(const std::string& name_str,
50                             const ZipEntryName& name) {
51  ASSERT_EQ(name_str.size(), name.name_length);
52  ASSERT_EQ(0, memcmp(name_str.c_str(), name.name, name.name_length));
53}
54
55TEST(ziparchive, Open) {
56  ZipArchiveHandle handle;
57  ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
58
59  CloseArchive(handle);
60}
61
62TEST(ziparchive, OpenMissing) {
63  ZipArchiveHandle handle;
64  ASSERT_NE(0, OpenArchiveWrapper(kMissingZip, &handle));
65
66  // Confirm the file descriptor is not going to be mistaken for a valid one.
67  ASSERT_EQ(-1, GetFileDescriptor(handle));
68}
69
70TEST(ziparchive, Iteration) {
71  ZipArchiveHandle handle;
72  ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
73
74  void* iteration_cookie;
75  ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL));
76
77  ZipEntry data;
78  ZipEntryName name;
79
80  // b/c.txt
81  ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
82  AssertNameEquals("b/c.txt", name);
83
84  // b/d.txt
85  ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
86  AssertNameEquals("b/d.txt", name);
87
88  // a.txt
89  ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
90  AssertNameEquals("a.txt", name);
91
92  // b.txt
93  ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
94  AssertNameEquals("b.txt", name);
95
96  // b/
97  ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
98  AssertNameEquals("b/", name);
99
100  // End of iteration.
101  ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
102
103  CloseArchive(handle);
104}
105
106TEST(ziparchive, FindEntry) {
107  ZipArchiveHandle handle;
108  ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
109
110  ZipEntry data;
111  ASSERT_EQ(0, FindEntry(handle, "a.txt", &data));
112
113  // Known facts about a.txt, from zipinfo -v.
114  ASSERT_EQ(63, data.offset);
115  ASSERT_EQ(kCompressDeflated, data.method);
116  ASSERT_EQ(static_cast<uint32_t>(17), data.uncompressed_length);
117  ASSERT_EQ(static_cast<uint32_t>(13), data.compressed_length);
118  ASSERT_EQ(0x950821c5, data.crc32);
119
120  // An entry that doesn't exist. Should be a negative return code.
121  ASSERT_LT(FindEntry(handle, "nonexistent.txt", &data), 0);
122
123  CloseArchive(handle);
124}
125
126TEST(ziparchive, ExtractToMemory) {
127  ZipArchiveHandle handle;
128  ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
129
130  // An entry that's deflated.
131  ZipEntry data;
132  ASSERT_EQ(0, FindEntry(handle, "a.txt", &data));
133  const uint32_t a_size = data.uncompressed_length;
134  ASSERT_EQ(a_size, sizeof(kATxtContents));
135  uint8_t* buffer = new uint8_t[a_size];
136  ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, a_size));
137  ASSERT_EQ(0, memcmp(buffer, kATxtContents, a_size));
138  delete[] buffer;
139
140  // An entry that's stored.
141  ASSERT_EQ(0, FindEntry(handle, "b.txt", &data));
142  const uint32_t b_size = data.uncompressed_length;
143  ASSERT_EQ(b_size, sizeof(kBTxtContents));
144  buffer = new uint8_t[b_size];
145  ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, b_size));
146  ASSERT_EQ(0, memcmp(buffer, kBTxtContents, b_size));
147  delete[] buffer;
148
149  CloseArchive(handle);
150}
151
152static const uint32_t kEmptyEntriesZip[] = {
153      0x04034b50, 0x0000000a, 0x63600000, 0x00004438, 0x00000000, 0x00000000,
154      0x00090000, 0x6d65001c, 0x2e797470, 0x55747874, 0x03000954, 0x52e25c13,
155      0x52e25c24, 0x000b7875, 0x42890401, 0x88040000, 0x50000013, 0x1e02014b,
156      0x00000a03, 0x60000000, 0x00443863, 0x00000000, 0x00000000, 0x09000000,
157      0x00001800, 0x00000000, 0xa0000000, 0x00000081, 0x706d6500, 0x742e7974,
158      0x54557478, 0x13030005, 0x7552e25c, 0x01000b78, 0x00428904, 0x13880400,
159      0x4b500000, 0x00000605, 0x00010000, 0x004f0001, 0x00430000, 0x00000000 };
160
161static int make_temporary_file(const char* file_name_pattern) {
162  char full_path[1024];
163  // Account for differences between the host and the target.
164  //
165  // TODO: Maybe reuse bionic/tests/TemporaryFile.h.
166  snprintf(full_path, sizeof(full_path), "/data/local/tmp/%s", file_name_pattern);
167  int fd = mkstemp(full_path);
168  if (fd == -1) {
169    snprintf(full_path, sizeof(full_path), "/tmp/%s", file_name_pattern);
170    fd = mkstemp(full_path);
171  }
172
173  return fd;
174}
175
176TEST(ziparchive, EmptyEntries) {
177  char temp_file_pattern[] = "empty_entries_test_XXXXXX";
178  int fd = make_temporary_file(temp_file_pattern);
179  ASSERT_NE(-1, fd);
180  const ssize_t file_size = sizeof(kEmptyEntriesZip);
181  ASSERT_EQ(file_size, TEMP_FAILURE_RETRY(write(fd, kEmptyEntriesZip, file_size)));
182
183  ZipArchiveHandle handle;
184  ASSERT_EQ(0, OpenArchiveFd(fd, "EmptyEntriesTest", &handle));
185
186  ZipEntry entry;
187  ASSERT_EQ(0, FindEntry(handle, "empty.txt", &entry));
188  ASSERT_EQ(static_cast<uint32_t>(0), entry.uncompressed_length);
189  uint8_t buffer[1];
190  ASSERT_EQ(0, ExtractToMemory(handle, &entry, buffer, 1));
191
192  char output_file_pattern[] = "empty_entries_output_XXXXXX";
193  int output_fd = make_temporary_file(output_file_pattern);
194  ASSERT_NE(-1, output_fd);
195  ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, output_fd));
196
197  struct stat stat_buf;
198  ASSERT_EQ(0, fstat(output_fd, &stat_buf));
199  ASSERT_EQ(0, stat_buf.st_size);
200
201  close(fd);
202  close(output_fd);
203}
204
205TEST(ziparchive, TrailerAfterEOCD) {
206  char temp_file_pattern[] = "trailer_after_eocd_test_XXXXXX";
207  int fd = make_temporary_file(temp_file_pattern);
208  ASSERT_NE(-1, fd);
209
210  // Create a file with 8 bytes of random garbage.
211  static const uint8_t trailer[] = { 'A' ,'n', 'd', 'r', 'o', 'i', 'd', 'z' };
212  const ssize_t file_size = sizeof(kEmptyEntriesZip);
213  const ssize_t trailer_size = sizeof(trailer);
214  ASSERT_EQ(file_size, TEMP_FAILURE_RETRY(write(fd, kEmptyEntriesZip, file_size)));
215  ASSERT_EQ(trailer_size, TEMP_FAILURE_RETRY(write(fd, trailer, trailer_size)));
216
217  ZipArchiveHandle handle;
218  ASSERT_GT(0, OpenArchiveFd(fd, "EmptyEntriesTest", &handle));
219}
220
221TEST(ziparchive, ExtractToFile) {
222  char kTempFilePattern[] = "zip_archive_input_XXXXXX";
223  int fd = make_temporary_file(kTempFilePattern);
224  ASSERT_NE(-1, fd);
225  const uint8_t data[8] = { '1', '2', '3', '4', '5', '6', '7', '8' };
226  const ssize_t data_size = sizeof(data);
227
228  ASSERT_EQ(data_size, TEMP_FAILURE_RETRY(write(fd, data, data_size)));
229
230  ZipArchiveHandle handle;
231  ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
232
233  ZipEntry entry;
234  ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry));
235  ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, fd));
236
237
238  // Assert that the first 8 bytes of the file haven't been clobbered.
239  uint8_t read_buffer[data_size];
240  ASSERT_EQ(0, lseek64(fd, 0, SEEK_SET));
241  ASSERT_EQ(data_size, TEMP_FAILURE_RETRY(read(fd, read_buffer, data_size)));
242  ASSERT_EQ(0, memcmp(read_buffer, data, data_size));
243
244  // Assert that the remainder of the file contains the incompressed data.
245  std::vector<uint8_t> uncompressed_data(entry.uncompressed_length);
246  ASSERT_EQ(static_cast<ssize_t>(entry.uncompressed_length),
247            TEMP_FAILURE_RETRY(
248                read(fd, &uncompressed_data[0], entry.uncompressed_length)));
249  ASSERT_EQ(0, memcmp(&uncompressed_data[0], kATxtContents,
250                      sizeof(kATxtContents)));
251
252  // Assert that the total length of the file is sane
253  ASSERT_EQ(data_size + static_cast<ssize_t>(sizeof(kATxtContents)),
254            lseek64(fd, 0, SEEK_END));
255
256  close(fd);
257}
258
259int main(int argc, char** argv) {
260  ::testing::InitGoogleTest(&argc, argv);
261
262  static struct option options[] = {
263    { "test_data_dir", required_argument, NULL, 't' },
264    { NULL, 0, NULL, 0 }
265  };
266
267  while (true) {
268    int option_index;
269    const int c = getopt_long_only(argc, argv, "", options, &option_index);
270    if (c == -1) {
271      break;
272    }
273
274    if (c == 't') {
275      test_data_dir = optarg;
276    }
277  }
278
279  if (test_data_dir.size() == 0) {
280    printf("Test data flag (--test_data_dir) required\n\n");
281    return -1;
282  }
283
284  if (test_data_dir[0] != '/') {
285    printf("Test data must be an absolute path, was %s\n\n",
286           test_data_dir.c_str());
287    return -2;
288  }
289
290  return RUN_ALL_TESTS();
291}
292