Line data Source code
1 : /*
2 : Zipios -- a small C++ library that provides easy access to .zip files.
3 :
4 : Copyright (C) 2000-2007 Thomas Sondergaard
5 : Copyright (c) 2015-2022 Made to Order Software Corp. All Rights Reserved
6 :
7 : This library is free software; you can redistribute it and/or
8 : modify it under the terms of the GNU Lesser General Public
9 : License as published by the Free Software Foundation; either
10 : version 2.1 of the License, or (at your option) any later version.
11 :
12 : This library is distributed in the hope that it will be useful,
13 : but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : Lesser General Public License for more details.
16 :
17 : You should have received a copy of the GNU Lesser General Public
18 : License along with this library; if not, write to the Free Software
19 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 : */
21 :
22 : /** \file
23 : *
24 : * Zipios unit tests for the ZipFile class.
25 : */
26 :
27 : #include "catch_main.hpp"
28 :
29 : #include <zipios/zipfile.hpp>
30 : #include <zipios/directorycollection.hpp>
31 : #include <zipios/zipiosexceptions.hpp>
32 : #include <zipios/dosdatetime.hpp>
33 :
34 : #include <algorithm>
35 : #include <fstream>
36 :
37 : #include <unistd.h>
38 : #include <string.h>
39 : #include <zlib.h>
40 :
41 :
42 :
43 : namespace
44 : {
45 :
46 :
47 : zipios::StorageMethod const g_supported_storage_methods[]
48 : {
49 : zipios::StorageMethod::STORED,
50 : zipios::StorageMethod::DEFLATED
51 : };
52 :
53 :
54 : } // no name namespace
55 :
56 :
57 :
58 :
59 1 : CATCH_TEST_CASE("An Empty ZipFile", "[ZipFile][FileCollection]")
60 : {
61 1 : zipios::ZipFile zf;
62 :
63 1 : CATCH_REQUIRE(zf.isValid());
64 1 : CATCH_REQUIRE(zf.entries().empty());
65 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
66 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
67 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
68 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
69 1 : CATCH_REQUIRE(zf.getName() == "-");
70 1 : CATCH_REQUIRE(zf.size() == 0);
71 1 : zf.mustBeValid();
72 1 : }
73 :
74 :
75 1 : CATCH_TEST_CASE("A ZipFile with an invalid name", "[ZipFile][FileCollection]")
76 : {
77 4 : CATCH_REQUIRE_THROWS_AS([&](){
78 : zipios::ZipFile zf("this/file/does/not/exists/so/the/constructor/throws");
79 : }(), zipios::IOException);
80 1 : }
81 :
82 :
83 1 : CATCH_TEST_CASE("A ZipFile with an invalid file", "[ZipFile][FileCollection]")
84 : {
85 1 : zipios_test::safe_chdir cwd(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
86 :
87 : // create a totally random file which means there is still a very slight
88 : // chance that it represents a valid ZipFile, but frankly... no.
89 2 : zipios_test::auto_unlink_t auto_unlink("invalid.zip", true);
90 : {
91 1 : std::ofstream os("invalid.zip", std::ios::out | std::ios::binary);
92 1 : size_t const max_size(rand() % 1024 + 1024);
93 1192 : for(size_t i(0); i < max_size; ++i)
94 : {
95 1191 : os << static_cast<char>(rand());
96 : }
97 1 : }
98 4 : CATCH_REQUIRE_THROWS_AS([&](){
99 : zipios::ZipFile zf("invalid.zip");
100 : }(), zipios::FileCollectionException);
101 1 : }
102 :
103 :
104 1 : CATCH_TEST_CASE("An empty ZipFile", "[ZipFile][FileCollection]")
105 : {
106 1 : zipios_test::safe_chdir cwd(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
107 :
108 : // this is a special case where the file is composed of one
109 : // End of Central Directory with 0 entries
110 2 : zipios_test::auto_unlink_t auto_unlink("empty.zip", true);
111 : {
112 1 : std::ofstream os("empty.zip", std::ios::out | std::ios::binary);
113 1 : os << static_cast<char>(0x50);
114 1 : os << static_cast<char>(0x4B);
115 1 : os << static_cast<char>(0x05);
116 1 : os << static_cast<char>(0x06);
117 1 : os << static_cast<char>(0x00);
118 1 : os << static_cast<char>(0x00);
119 1 : os << static_cast<char>(0x00);
120 1 : os << static_cast<char>(0x00);
121 1 : os << static_cast<char>(0x00);
122 1 : os << static_cast<char>(0x00);
123 1 : os << static_cast<char>(0x00);
124 1 : os << static_cast<char>(0x00);
125 1 : os << static_cast<char>(0x00);
126 1 : os << static_cast<char>(0x00);
127 1 : os << static_cast<char>(0x00);
128 1 : os << static_cast<char>(0x00);
129 1 : os << static_cast<char>(0x00);
130 1 : os << static_cast<char>(0x00);
131 1 : os << static_cast<char>(0x00);
132 1 : os << static_cast<char>(0x00);
133 1 : os << static_cast<char>(0x00);
134 1 : os << static_cast<char>(0x00);
135 1 : }
136 2 : zipios::ZipFile zf("empty.zip");
137 :
138 1 : CATCH_REQUIRE(zf.isValid());
139 1 : CATCH_REQUIRE(zf.entries().empty());
140 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
141 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
142 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
143 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
144 1 : CATCH_REQUIRE(zf.getName() == "empty.zip");
145 1 : CATCH_REQUIRE(zf.size() == 0);
146 1 : zf.mustBeValid(); // not throwing
147 1 : }
148 :
149 :
150 3 : CATCH_SCENARIO("ZipFile with a valid zip archive", "[ZipFile][FileCollection]")
151 : {
152 3 : zipios_test::safe_chdir cwd(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
153 :
154 3 : CATCH_GIVEN("a tree directory")
155 : {
156 3 : CATCH_REQUIRE(system("rm -rf tree tree.zip") == 0); // clean up, just in case
157 3 : size_t const start_count(rand() % 40 + 80);
158 6 : zipios_test::file_t tree(zipios_test::file_t::type_t::DIRECTORY, start_count, "tree");
159 6 : zipios_test::auto_unlink_t remove_zip("tree.zip", false);
160 3 : CATCH_REQUIRE(system("zip -r tree.zip tree >/dev/null") == 0);
161 :
162 : // first, check that the object is setup as expected
163 3 : CATCH_WHEN("we load the zip file")
164 : {
165 6 : zipios::ZipFile zf("tree.zip");
166 :
167 3 : CATCH_THEN("it is valid and includes all the files in the tree")
168 : {
169 1 : CATCH_REQUIRE(zf.isValid());
170 1 : CATCH_REQUIRE_FALSE(zf.entries().empty());
171 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
172 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
173 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
174 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
175 1 : CATCH_REQUIRE(zf.getName() == "tree.zip");
176 1 : CATCH_REQUIRE(zf.size() == tree.size());
177 1 : zf.mustBeValid(); // not throwing
178 :
179 1 : zipios::FileEntry::vector_t v(zf.entries());
180 1171 : for(auto it(v.begin()); it != v.end(); ++it)
181 : {
182 1170 : zipios::FileEntry::pointer_t entry(*it);
183 :
184 : // verify that our tree knows about this file
185 1170 : zipios_test::file_t::type_t t(tree.find(entry->getName()));
186 1170 : CATCH_REQUIRE(t != zipios_test::file_t::type_t::UNKNOWN);
187 :
188 : struct stat file_stats;
189 1170 : CATCH_REQUIRE(stat(entry->getName().c_str(), &file_stats) == 0);
190 :
191 1170 : CATCH_REQUIRE((*it)->getComment().empty());
192 : //CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize()); -- not too sure how we could verify this size in this case
193 : //CATCH_REQUIRE((*it)->getCrc() == ...); -- not too sure how to compute that right now, but once we have it we'll test it
194 : //CATCH_REQUIRE((*it)->getEntryOffset() == ...); -- that's also difficult to test
195 : //CATCH_REQUIRE((*it)->getExtra().empty());
196 : //CATCH_REQUIRE((*it)->getHeaderSize() == 0); -- the header size varies
197 1170 : if((*it)->getMethod() == zipios::StorageMethod::STORED)
198 : {
199 471 : CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize());
200 : }
201 : else
202 : {
203 : // you would think that the compressed size would
204 : // either be equal to the size or smaller, but never
205 : // larger, that's not the case with zip under Linux...
206 : //
207 : // they probably use a streaming mechanism and thus
208 : // cannot fix the problem later if the compressed
209 : // version ends up being larger than the
210 : // non-compressed version...
211 : //
212 : //CATCH_REQUIRE((*it)->getCompressedSize() < (*it)->getSize());
213 : }
214 : //CATCH_REQUIRE((*it)->getName() == ...);
215 : //CATCH_REQUIRE((*it)->getFileName() == ...);
216 1170 : zipios::DOSDateTime dt;
217 1170 : dt.setUnixTimestamp(file_stats.st_mtime);
218 1170 : CATCH_REQUIRE((*it)->getTime() == dt.getDOSDateTime());
219 1170 : std::time_t ut(dt.getUnixTimestamp());
220 1170 : CATCH_REQUIRE((*it)->getUnixTime() == ut);
221 1170 : CATCH_REQUIRE_FALSE((*it)->hasCrc());
222 1170 : CATCH_REQUIRE((*it)->isValid());
223 : //CATCH_REQUIRE((*it)->toString() == "... (0 bytes)");
224 :
225 1170 : if(t == zipios_test::file_t::type_t::DIRECTORY)
226 : {
227 129 : CATCH_REQUIRE((*it)->isDirectory());
228 129 : CATCH_REQUIRE((*it)->getSize() == 0); // size is zero for directories
229 : }
230 : else
231 : {
232 1041 : CATCH_REQUIRE_FALSE((*it)->isDirectory());
233 1041 : CATCH_REQUIRE((*it)->getSize() == static_cast<std::size_t>(file_stats.st_size));
234 :
235 : // now read both files (if not a directory) and make sure
236 : // they are equal
237 1041 : zipios::FileCollection::stream_pointer_t is(zf.getInputStream(entry->getName()));
238 1041 : CATCH_REQUIRE(is);
239 1041 : std::ifstream in(entry->getName(), std::ios::in | std::ios::binary);
240 :
241 7981 : while(in && *is)
242 : {
243 : char buf1[BUFSIZ], buf2[BUFSIZ];
244 :
245 6940 : in.read(buf1, sizeof(buf1));
246 6940 : std::streamsize sz1(in.gcount());
247 :
248 6940 : is->read(buf2, sizeof(buf2));
249 6940 : std::streamsize sz2(is->gcount());
250 :
251 6940 : CATCH_REQUIRE(sz1 == sz2);
252 6940 : CATCH_REQUIRE(memcmp(buf1, buf2, sz1) == 0);
253 : }
254 :
255 1041 : CATCH_REQUIRE(!in);
256 1041 : CATCH_REQUIRE(!*is);
257 1041 : }
258 :
259 : // I don't think we will test those directly...
260 : //CATCH_REQUIRE_THROWS_AS((*it)->read(std::cin), zipios::IOException);
261 : //CATCH_REQUIRE_THROWS_AS((*it)->write(std::cout), zipios::IOException);
262 1170 : }
263 4 : }
264 :
265 3 : CATCH_THEN("we can create a totally valid clone")
266 : {
267 : // we do not have a specific copy constructor and
268 : // assignment operator so we only try to clone() function
269 1 : zipios::ZipFile::pointer_t clone(zf.clone());
270 :
271 : // zf is unaffected
272 1 : CATCH_REQUIRE(zf.isValid());
273 1 : CATCH_REQUIRE_FALSE(zf.entries().empty());
274 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
275 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
276 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
277 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
278 1 : CATCH_REQUIRE(zf.getName() == "tree.zip");
279 1 : CATCH_REQUIRE(zf.size() == tree.size());
280 1 : zf.mustBeValid(); // not throwing
281 :
282 : // clone is valid
283 1 : CATCH_REQUIRE(clone->isValid());
284 1 : CATCH_REQUIRE_FALSE(clone->entries().empty());
285 1 : CATCH_REQUIRE_FALSE(clone->getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
286 1 : CATCH_REQUIRE_FALSE(clone->getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
287 1 : CATCH_REQUIRE_FALSE(clone->getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
288 1 : CATCH_REQUIRE_FALSE(clone->getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
289 1 : CATCH_REQUIRE(clone->getName() == "tree.zip");
290 1 : CATCH_REQUIRE(clone->size() == tree.size());
291 1 : clone->mustBeValid(); // not throwing
292 :
293 1 : zipios::FileEntry::vector_t v(clone->entries());
294 1077 : for(auto it(v.begin()); it != v.end(); ++it)
295 : {
296 1076 : zipios::FileEntry::pointer_t entry(*it);
297 :
298 : // verify that our tree knows about this file
299 1076 : zipios_test::file_t::type_t t(tree.find(entry->getName()));
300 1076 : CATCH_REQUIRE(t != zipios_test::file_t::type_t::UNKNOWN);
301 :
302 : struct stat file_stats;
303 1076 : CATCH_REQUIRE(stat(entry->getName().c_str(), &file_stats) == 0);
304 :
305 1076 : CATCH_REQUIRE((*it)->getComment().empty());
306 : //CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize()); -- not too sure how we could verify this size in this case
307 : //CATCH_REQUIRE((*it)->getCrc() == ...); -- not too sure how to compute that right now, but once we have it we'll test it
308 : //CATCH_REQUIRE((*it)->getEntryOffset() == ...); -- that's also difficult to test
309 : //CATCH_REQUIRE((*it)->getExtra().empty());
310 : //CATCH_REQUIRE((*it)->getHeaderSize() == 0); -- the header size varies
311 1076 : if((*it)->getMethod() == zipios::StorageMethod::STORED)
312 : {
313 438 : CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize());
314 : }
315 : else
316 : {
317 : // you would think that the compressed size would
318 : // either be equal to the size or smaller, but never
319 : // larger, that's not the case with zip under Linux...
320 : //
321 : // they probably use a streaming mechanism and thus
322 : // cannot fix the problem later if the compressed
323 : // version ends up being larger than the
324 : // non-compressed version...
325 : //
326 : //CATCH_REQUIRE((*it)->getCompressedSize() < (*it)->getSize());
327 : }
328 : //CATCH_REQUIRE((*it)->getName() == ...);
329 : //CATCH_REQUIRE((*it)->getFileName() == ...);
330 1076 : zipios::DOSDateTime dt;
331 1076 : dt.setUnixTimestamp(file_stats.st_mtime);
332 1076 : CATCH_REQUIRE((*it)->getTime() == dt.getDOSDateTime()); // invalid date
333 1076 : std::time_t ut(dt.getUnixTimestamp());
334 1076 : CATCH_REQUIRE((*it)->getUnixTime() == ut);
335 1076 : CATCH_REQUIRE_FALSE((*it)->hasCrc());
336 1076 : CATCH_REQUIRE((*it)->isValid());
337 : //CATCH_REQUIRE((*it)->toString() == "... (0 bytes)");
338 :
339 1076 : if(t == zipios_test::file_t::type_t::DIRECTORY)
340 : {
341 125 : CATCH_REQUIRE((*it)->isDirectory());
342 125 : CATCH_REQUIRE((*it)->getSize() == 0); // size is zero for directories
343 : }
344 : else
345 : {
346 951 : CATCH_REQUIRE_FALSE((*it)->isDirectory());
347 951 : CATCH_REQUIRE((*it)->getSize() == static_cast<std::size_t>(file_stats.st_size));
348 :
349 : // now read both files (if not a directory) and make sure
350 : // they are equal
351 951 : zipios::FileCollection::stream_pointer_t is(clone->getInputStream(entry->getName()));
352 951 : CATCH_REQUIRE(is);
353 951 : std::ifstream in(entry->getName(), std::ios::in | std::ios::binary);
354 :
355 7431 : while(in && *is)
356 : {
357 : char buf1[BUFSIZ], buf2[BUFSIZ];
358 :
359 6480 : in.read(buf1, sizeof(buf1));
360 6480 : std::streamsize sz1(in.gcount());
361 :
362 6480 : is->read(buf2, sizeof(buf2));
363 6480 : std::streamsize sz2(is->gcount());
364 :
365 6480 : CATCH_REQUIRE(sz1 == sz2);
366 6480 : CATCH_REQUIRE(memcmp(buf1, buf2, sz1) == 0);
367 : }
368 :
369 951 : CATCH_REQUIRE(!in);
370 951 : CATCH_REQUIRE(!*is);
371 951 : }
372 :
373 : // I don't think we will test those directly...
374 : //CATCH_REQUIRE_THROWS_AS((*it)->read(std::cin), zipios::IOException);
375 : //CATCH_REQUIRE_THROWS_AS((*it)->write(std::cout), zipios::IOException);
376 1076 : }
377 4 : }
378 :
379 3 : CATCH_THEN("we can compare incompatible entries against each others")
380 : {
381 : // we read the tree as a directory so we have
382 : // incompatible entries
383 2 : zipios::DirectoryCollection dc("tree");
384 :
385 1 : zipios::FileEntry::vector_t e(dc.entries());
386 1 : zipios::FileEntry::vector_t v(zf.entries());
387 1 : CATCH_REQUIRE(e.size() == v.size()); // same tree so same size
388 : //size_t const max_entries(std::min(e.size(), v.size());
389 739 : for(size_t idx(0); idx < e.size(); ++idx)
390 : {
391 738 : CATCH_REQUIRE_FALSE(e[idx]->isEqual(*v[idx]));
392 738 : CATCH_REQUIRE_FALSE(v[idx]->isEqual(*e[idx]));
393 : }
394 4 : }
395 6 : }
396 6 : }
397 3 : }
398 :
399 :
400 2 : CATCH_SCENARIO("use Zipios to create a zip archive", "[ZipFile][FileCollection]")
401 : {
402 2 : zipios_test::safe_chdir cwd(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
403 :
404 2 : CATCH_GIVEN("a tree directory")
405 : {
406 2 : CATCH_REQUIRE(system("rm -rf tree tree.zip") == 0); // clean up, just in case
407 2 : size_t const start_count(rand() % 40 + 80);
408 4 : zipios_test::file_t tree(zipios_test::file_t::type_t::DIRECTORY, start_count, "tree");
409 4 : zipios_test::auto_unlink_t remove_zip("tree.zip", false);
410 4 : zipios::DirectoryCollection dc("tree");
411 :
412 : // first, check that the object is setup as expected
413 2 : CATCH_WHEN("we save the directory tree in a .zip file")
414 : {
415 : {
416 1 : dc.setMethod(1024, zipios::StorageMethod::STORED, zipios::StorageMethod::DEFLATED);
417 1 : dc.setLevel(1024, zipios::FileEntry::COMPRESSION_LEVEL_NONE, zipios::FileEntry::COMPRESSION_LEVEL_MAXIMUM);
418 1 : std::ofstream out("tree.zip", std::ios::out | std::ios::binary);
419 1 : zipios::ZipFile::saveCollectionToArchive(out, dc);
420 1 : }
421 :
422 1 : CATCH_THEN("it is valid and includes all the files in the tree as expected")
423 : {
424 2 : zipios::ZipFile zf("tree.zip");
425 :
426 1 : CATCH_REQUIRE(zf.isValid());
427 1 : CATCH_REQUIRE_FALSE(zf.entries().empty());
428 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
429 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
430 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
431 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
432 1 : CATCH_REQUIRE(zf.getName() == "tree.zip");
433 1 : CATCH_REQUIRE(zf.size() == tree.size());
434 1 : zf.mustBeValid(); // not throwing
435 :
436 1 : zipios::FileEntry::vector_t v(zf.entries());
437 314 : for(auto it(v.begin()); it != v.end(); ++it)
438 : {
439 313 : zipios::FileEntry::pointer_t entry(*it);
440 :
441 : // verify that our tree knows about this file
442 313 : zipios_test::file_t::type_t t(tree.find(entry->getName()));
443 313 : CATCH_REQUIRE(t != zipios_test::file_t::type_t::UNKNOWN);
444 :
445 : struct stat file_stats;
446 313 : CATCH_REQUIRE(stat(entry->getName().c_str(), &file_stats) == 0);
447 :
448 313 : CATCH_REQUIRE((*it)->getComment().empty());
449 : //CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize()); -- not too sure how we could verify this size in this case
450 : //CATCH_REQUIRE((*it)->getCrc() == ...); -- not too sure how to compute that right now, but once we have it we'll test it
451 : //CATCH_REQUIRE((*it)->getEntryOffset() == ...); -- that's also difficult to test
452 : //CATCH_REQUIRE((*it)->getExtra().empty());
453 : //CATCH_REQUIRE((*it)->getHeaderSize() == 0); -- the header size varies
454 313 : if((*it)->getMethod() == zipios::StorageMethod::STORED)
455 : {
456 33 : CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize());
457 : }
458 : else
459 : {
460 : // you would think that the compressed size would
461 : // either be equal to the size or smaller, but never
462 : // larger, that's not the case with zip under Linux...
463 : //
464 : // they probably use a streaming mechanism and thus
465 : // cannot fix the problem later if the compressed
466 : // version ends up being larger than the
467 : // non-compressed version...
468 : //
469 : //CATCH_REQUIRE((*it)->getCompressedSize() < (*it)->getSize());
470 : }
471 : //CATCH_REQUIRE((*it)->getName() == ...);
472 : //CATCH_REQUIRE((*it)->getFileName() == ...);
473 313 : zipios::DOSDateTime dt;
474 313 : dt.setUnixTimestamp(file_stats.st_mtime);
475 313 : CATCH_REQUIRE((*it)->getTime() == dt.getDOSDateTime());
476 313 : CATCH_REQUIRE((*it)->getUnixTime() == dt.getUnixTimestamp());
477 313 : CATCH_REQUIRE_FALSE((*it)->hasCrc());
478 313 : CATCH_REQUIRE((*it)->isValid());
479 : //CATCH_REQUIRE((*it)->toString() == "... (0 bytes)");
480 :
481 313 : if(t == zipios_test::file_t::type_t::DIRECTORY)
482 : {
483 27 : CATCH_REQUIRE((*it)->isDirectory());
484 27 : CATCH_REQUIRE((*it)->getSize() == 0); // size is zero for directories
485 : }
486 : else
487 : {
488 286 : CATCH_REQUIRE_FALSE((*it)->isDirectory());
489 286 : CATCH_REQUIRE((*it)->getSize() == static_cast<std::size_t>(file_stats.st_size));
490 :
491 : // now read both files (if not a directory) and make sure
492 : // they are equal
493 286 : zipios::FileCollection::stream_pointer_t is(zf.getInputStream(entry->getName()));
494 286 : CATCH_REQUIRE(is);
495 286 : std::ifstream in(entry->getName(), std::ios::in | std::ios::binary);
496 :
497 2231 : while(in && *is)
498 : {
499 : char buf1[BUFSIZ], buf2[BUFSIZ];
500 :
501 1945 : in.read(buf1, sizeof(buf1));
502 1945 : std::streamsize sz1(in.gcount());
503 :
504 1945 : is->read(buf2, sizeof(buf2));
505 1945 : std::streamsize sz2(is->gcount());
506 :
507 1945 : CATCH_REQUIRE(sz1 == sz2);
508 1945 : CATCH_REQUIRE(memcmp(buf1, buf2, sz1) == 0);
509 : }
510 :
511 286 : CATCH_REQUIRE(!in);
512 286 : CATCH_REQUIRE(!*is);
513 286 : }
514 :
515 : // I don't think we will test those directly...
516 : //CATCH_REQUIRE_THROWS_AS((*it)->read(std::cin), zipios::IOException);
517 : //CATCH_REQUIRE_THROWS_AS((*it)->write(std::cout), zipios::IOException);
518 313 : }
519 2 : }
520 2 : }
521 :
522 : // test with all the possible levels
523 2 : CATCH_START_SECTION("test creating zip with all available levels")
524 : {
525 105 : for(zipios::FileEntry::CompressionLevel level(-3); level <= 100; ++level)
526 : {
527 : // Note that a level of COMPRESSION_LEVEL_NONE and method of
528 : // DEFLATED is valid, the system will ignore the DEFLATED
529 : // when saving the file and just use STORED instead.
530 104 : dc.setMethod(1024, zipios::StorageMethod::STORED, zipios::StorageMethod::DEFLATED);
531 104 : dc.setLevel(1024, zipios::FileEntry::COMPRESSION_LEVEL_NONE, level);
532 : {
533 104 : std::ofstream out("tree.zip", std::ios::out | std::ios::binary | std::ios::trunc);
534 104 : zipios::ZipFile::saveCollectionToArchive(out, dc);
535 104 : }
536 :
537 208 : zipios::ZipFile zf("tree.zip");
538 :
539 104 : CATCH_REQUIRE(zf.isValid());
540 104 : CATCH_REQUIRE_FALSE(zf.entries().empty());
541 104 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
542 104 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
543 104 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
544 104 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
545 104 : CATCH_REQUIRE(zf.getName() == "tree.zip");
546 104 : CATCH_REQUIRE(zf.size() == tree.size());
547 104 : zf.mustBeValid(); // not throwing
548 :
549 104 : zipios::FileEntry::vector_t v(zf.entries());
550 55640 : for(auto it(v.begin()); it != v.end(); ++it)
551 : {
552 55536 : zipios::FileEntry::pointer_t entry(*it);
553 :
554 : // verify that our tree knows about this file
555 55536 : zipios_test::file_t::type_t t(tree.find(entry->getName()));
556 55536 : CATCH_REQUIRE(t != zipios_test::file_t::type_t::UNKNOWN);
557 :
558 : struct stat file_stats;
559 55536 : CATCH_REQUIRE(stat(entry->getName().c_str(), &file_stats) == 0);
560 :
561 55536 : CATCH_REQUIRE((*it)->getComment().empty());
562 : //CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize()); -- not too sure how we could verify this size in this case
563 : //CATCH_REQUIRE((*it)->getCrc() == ...); -- not too sure how to compute that right now, but once we have it we'll test it
564 : //CATCH_REQUIRE((*it)->getEntryOffset() == ...); -- that's also difficult to test
565 : //CATCH_REQUIRE((*it)->getExtra().empty());
566 : //CATCH_REQUIRE((*it)->getHeaderSize() == 0); -- the header size varies
567 55536 : if((*it)->getMethod() == zipios::StorageMethod::STORED)
568 : {
569 6405 : CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize());
570 : }
571 : else
572 : {
573 : // you would think that the compressed size would
574 : // either be equal to the size or smaller, but never
575 : // larger, that's not the case with zip under Linux...
576 : //
577 : // they probably use a streaming mechanism and thus
578 : // cannot fix the problem later if the compressed
579 : // version ends up being larger than the
580 : // non-compressed version...
581 : //
582 : //CATCH_REQUIRE((*it)->getCompressedSize() < (*it)->getSize());
583 : }
584 : //CATCH_REQUIRE((*it)->getName() == ...);
585 : //CATCH_REQUIRE((*it)->getFileName() == ...);
586 55536 : zipios::DOSDateTime dt;
587 55536 : dt.setUnixTimestamp(file_stats.st_mtime);
588 55536 : CATCH_REQUIRE((*it)->getTime() == dt.getDOSDateTime());
589 55536 : CATCH_REQUIRE((*it)->getUnixTime() == dt.getUnixTimestamp());
590 55536 : CATCH_REQUIRE_FALSE((*it)->hasCrc());
591 55536 : CATCH_REQUIRE((*it)->isValid());
592 : //CATCH_REQUIRE((*it)->toString() == "... (0 bytes)");
593 :
594 55536 : if(t == zipios_test::file_t::type_t::DIRECTORY)
595 : {
596 4784 : CATCH_REQUIRE((*it)->isDirectory());
597 4784 : CATCH_REQUIRE((*it)->getSize() == 0); // size is zero for directories
598 : }
599 : else
600 : {
601 50752 : CATCH_REQUIRE_FALSE((*it)->isDirectory());
602 50752 : CATCH_REQUIRE((*it)->getSize() == static_cast<std::size_t>(file_stats.st_size));
603 :
604 : // now read both files (if not a directory) and make sure
605 : // they are equal
606 50752 : zipios::FileCollection::stream_pointer_t is(zf.getInputStream(entry->getName()));
607 50752 : CATCH_REQUIRE(is);
608 50752 : std::ifstream in(entry->getName(), std::ios::in | std::ios::binary);
609 :
610 390416 : while(in && *is)
611 : {
612 : char buf1[BUFSIZ], buf2[BUFSIZ];
613 :
614 339664 : in.read(buf1, sizeof(buf1));
615 339664 : std::streamsize sz1(in.gcount());
616 :
617 339664 : is->read(buf2, sizeof(buf2));
618 339664 : std::streamsize sz2(is->gcount());
619 :
620 339664 : CATCH_REQUIRE(sz1 == sz2);
621 339664 : CATCH_REQUIRE(memcmp(buf1, buf2, sz1) == 0);
622 : }
623 :
624 50752 : CATCH_REQUIRE_FALSE(in);
625 50752 : CATCH_REQUIRE_FALSE(*is);
626 50752 : }
627 :
628 : // I don't think we will test those directly...
629 : //CATCH_REQUIRE_THROWS_AS((*it)->read(std::cin), zipios::IOException);
630 : //CATCH_REQUIRE_THROWS_AS((*it)->write(std::cout), zipios::IOException);
631 55536 : }
632 104 : }
633 : }
634 2 : CATCH_END_SECTION()
635 4 : }
636 2 : }
637 :
638 :
639 5 : CATCH_SCENARIO("use Zipios to create zip archives with 1 or 3 files each", "[ZipFile][FileCollection]")
640 : {
641 5 : zipios_test::safe_chdir cwd(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
642 :
643 5 : CATCH_GIVEN("a one file zip file")
644 : {
645 8 : zipios_test::auto_unlink_t remove_bin("file.bin", true); // clean up, just in case
646 : {
647 4 : std::ofstream file_bin("file.bin", std::ios::out | std::ios::binary);
648 4 : file_bin << "this zip file contents.\n";
649 4 : }
650 :
651 : // first, check that the object is setup as expected
652 4 : CATCH_WHEN("we save the file in a .zip")
653 : {
654 2 : zipios::DirectoryCollection dc("file.bin");
655 2 : zipios_test::auto_unlink_t remove_zip("file.zip", true);
656 : {
657 1 : std::ofstream out("file.zip", std::ios::out | std::ios::binary);
658 1 : zipios::ZipFile::saveCollectionToArchive(out, dc);
659 1 : }
660 :
661 1 : CATCH_THEN("it is valid and includes the file as expected")
662 : {
663 2 : zipios::ZipFile zf("file.zip");
664 :
665 1 : CATCH_REQUIRE(zf.isValid());
666 1 : CATCH_REQUIRE_FALSE(zf.entries().empty());
667 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
668 1 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
669 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
670 1 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
671 1 : CATCH_REQUIRE(zf.getName() == "file.zip");
672 1 : CATCH_REQUIRE(zf.size() == 1);
673 1 : zf.mustBeValid(); // not throwing
674 :
675 1 : zipios::FileEntry::vector_t v(zf.entries());
676 1 : CATCH_REQUIRE(v.size() == 1);
677 2 : for(auto it(v.begin()); it != v.end(); ++it)
678 : {
679 1 : zipios::FileEntry::pointer_t entry(*it);
680 :
681 : struct stat file_stats;
682 1 : CATCH_REQUIRE(stat(entry->getName().c_str(), &file_stats) == 0);
683 :
684 1 : CATCH_REQUIRE((*it)->getComment().empty());
685 1 : CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize()); // we keep STORED as the method
686 : //CATCH_REQUIRE((*it)->getCrc() == ...); -- not too sure how to compute that right now, but once we have it we'll test it
687 : //CATCH_REQUIRE((*it)->getEntryOffset() == ...); -- that's also difficult to test
688 : //CATCH_REQUIRE((*it)->getExtra().empty());
689 : //CATCH_REQUIRE((*it)->getHeaderSize() == 0); -- the header size varies
690 1 : if((*it)->getMethod() == zipios::StorageMethod::STORED)
691 : {
692 1 : CATCH_REQUIRE((*it)->getCompressedSize() == (*it)->getSize());
693 : }
694 : else
695 : {
696 : // you would think that the compressed size would
697 : // either be equal to the size or smaller, but never
698 : // larger, that is not the case with zip under Linux...
699 : //
700 : // they probably use a streaming mechanism and thus
701 : // cannot fix the problem later if the compressed
702 : // version ends up being larger than the
703 : // non-compressed version...
704 : //
705 : //CATCH_REQUIRE((*it)->getCompressedSize() < (*it)->getSize());
706 : }
707 : //CATCH_REQUIRE((*it)->getName() == ...);
708 : //CATCH_REQUIRE((*it)->getFileName() == ...);
709 1 : zipios::DOSDateTime dt;
710 1 : dt.setUnixTimestamp(file_stats.st_mtime);
711 1 : CATCH_REQUIRE((*it)->getTime() == dt.getDOSDateTime());
712 1 : CATCH_REQUIRE((*it)->getUnixTime() == dt.getUnixTimestamp());
713 1 : CATCH_REQUIRE_FALSE((*it)->hasCrc());
714 1 : CATCH_REQUIRE((*it)->isValid());
715 : //CATCH_REQUIRE((*it)->toString() == "... (0 bytes)");
716 :
717 1 : CATCH_REQUIRE_FALSE((*it)->isDirectory());
718 1 : CATCH_REQUIRE((*it)->getSize() == static_cast<std::size_t>(file_stats.st_size));
719 :
720 : // now read both files (if not a directory) and make sure
721 : // they are equal
722 1 : zipios::FileCollection::stream_pointer_t is(zf.getInputStream(entry->getName()));
723 1 : CATCH_REQUIRE(is);
724 1 : std::ifstream in(entry->getName(), std::ios::in | std::ios::binary);
725 :
726 2 : while(in && *is)
727 : {
728 : char buf1[BUFSIZ], buf2[BUFSIZ];
729 :
730 1 : in.read(buf1, sizeof(buf1));
731 1 : std::streamsize sz1(in.gcount());
732 :
733 1 : is->read(buf2, sizeof(buf2));
734 1 : std::streamsize sz2(is->gcount());
735 :
736 1 : CATCH_REQUIRE(sz1 == sz2);
737 1 : CATCH_REQUIRE(memcmp(buf1, buf2, sz1) == 0);
738 : }
739 :
740 1 : CATCH_REQUIRE_FALSE(in);
741 1 : CATCH_REQUIRE_FALSE(*is);
742 :
743 : // I don't think we will test those directly...
744 : //CATCH_REQUIRE_THROWS_AS((*it)->read(std::cin), zipios::IOException);
745 : //CATCH_REQUIRE_THROWS_AS((*it)->write(std::cout), zipios::IOException);
746 1 : }
747 2 : }
748 5 : }
749 :
750 : /** \todo
751 : * Once clang is fixed, remove those #if/#endif boundaries. clang does not
752 : * clear the std::unchecked_exception() flag when we have a re-throw in a
753 : * catch.
754 : */
755 : #ifndef __clang__
756 : // test with a comment that's too large
757 4 : CATCH_WHEN("we make sure that saving the file fails if the comment is too large")
758 : {
759 2 : zipios::DirectoryCollection dc("file.bin");
760 1 : zipios::FileEntry::vector_t v(dc.entries());
761 1 : CATCH_REQUIRE(v.size() == 1);
762 1 : auto it(v.begin());
763 : // generate a random comment of 65Kb
764 1 : std::string comment;
765 66561 : for(int i(0); i < 65 * 1024; ++i)
766 : {
767 66560 : comment += rand() % 26 + 'A';
768 : }
769 1 : (*it)->setComment(comment);
770 :
771 1 : CATCH_THEN("it is invalid and fails as expected")
772 : {
773 2 : zipios_test::auto_unlink_t remove_zip("file.zip", true);
774 : {
775 1 : std::ofstream out("file.zip", std::ios::out | std::ios::binary);
776 2 : CATCH_REQUIRE_THROWS_AS(zipios::ZipFile::saveCollectionToArchive(out, dc), zipios::InvalidStateException);
777 1 : CATCH_REQUIRE_FALSE(out);
778 1 : }
779 2 : }
780 5 : }
781 : #endif
782 :
783 : #ifndef __clang__
784 : // check that extra buffers that are too large make the save fail
785 4 : CATCH_WHEN("we make sure that saving the file fails if the extra buffer is too large")
786 : {
787 2 : zipios::DirectoryCollection dc("file.bin");
788 1 : zipios::FileEntry::vector_t v(dc.entries());
789 1 : CATCH_REQUIRE(v.size() == 1);
790 1 : auto it(v.begin());
791 : // generate a random extra buffer of 65Kb
792 1 : zipios::FileEntry::buffer_t buffer;
793 66561 : for(int i(0); i < 65 * 1024; ++i)
794 : {
795 66560 : buffer.push_back(static_cast<unsigned char>(rand()));
796 : }
797 1 : (*it)->setExtra(buffer);
798 :
799 1 : CATCH_THEN("it is invalid and fails as expected")
800 : {
801 2 : zipios_test::auto_unlink_t remove_zip("file.zip", true);
802 : {
803 1 : std::ofstream out("file.zip", std::ios::out | std::ios::binary);
804 2 : CATCH_REQUIRE_THROWS_AS(zipios::ZipFile::saveCollectionToArchive(out, dc), zipios::InvalidStateException);
805 1 : CATCH_REQUIRE_FALSE(out);
806 1 : }
807 2 : }
808 5 : }
809 : #endif
810 :
811 : #ifndef __clang__
812 : // check with a global comment which is too large
813 4 : CATCH_WHEN("we make sure that saving the file fails if the Zip (global) comment is too large")
814 : {
815 2 : zipios::DirectoryCollection dc("file.bin");
816 : // generate a random comment of 65Kb
817 1 : std::string comment;
818 66561 : for(int i(0); i < 65 * 1024; ++i)
819 : {
820 66560 : comment += rand() % 26 + 'A';
821 : }
822 :
823 1 : CATCH_THEN("it is invalid and fails as expected")
824 : {
825 2 : zipios_test::auto_unlink_t remove_zip("file.zip", true);
826 : {
827 1 : std::ofstream out("file.zip", std::ios::out | std::ios::binary);
828 1 : CATCH_REQUIRE_THROWS_AS(zipios::ZipFile::saveCollectionToArchive(out, dc, comment), zipios::InvalidStateException);
829 1 : CATCH_REQUIRE_FALSE(out);
830 1 : }
831 2 : }
832 5 : }
833 : #endif
834 9 : }
835 :
836 : #ifndef __clang__
837 5 : CATCH_GIVEN("a very small file")
838 : {
839 2 : zipios_test::auto_unlink_t remove_bin("file.bin", true); // clean up, just in case
840 : {
841 : // one byte file
842 1 : std::ofstream file_bin("file.bin", std::ios::out | std::ios::binary);
843 1 : file_bin << "1";
844 1 : }
845 :
846 : // first, check that the object is setup as expected
847 1 : CATCH_WHEN("we add it more than 64Kb times to the directory")
848 : {
849 2 : zipios::DirectoryCollection dc("file.bin");
850 :
851 : // add another 64Kb file entries! (all the same name, ouch!)
852 1 : int const max(64 * 1024 + rand() % 100);
853 65583 : for(int i(0); i < max; ++i)
854 : {
855 131164 : zipios::DirectoryEntry other_entry(zipios::FilePath("file.bin"));
856 65582 : dc.addEntry(other_entry);
857 65582 : }
858 :
859 1 : CATCH_THEN("the creating of the zip archive fails: too many file entries")
860 : {
861 2 : zipios_test::auto_unlink_t remove_zip("file.zip", true);
862 : {
863 1 : std::ofstream out("file.zip", std::ios::out | std::ios::binary);
864 2 : CATCH_REQUIRE_THROWS_AS(zipios::ZipFile::saveCollectionToArchive(out, dc), zipios::InvalidStateException);
865 1 : }
866 2 : }
867 2 : }
868 6 : }
869 : #endif
870 5 : }
871 :
872 :
873 2 : CATCH_TEST_CASE("Simple Valid and Invalid ZipFile Archives", "[ZipFile][FileCollection]")
874 : {
875 2 : zipios_test::safe_chdir cwd(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
876 :
877 2 : CATCH_START_SECTION("try one uncompressed file of many sizes")
878 : {
879 70 : for(int sz(0); sz <= 128 * 1024; sz += sz < 10 ? 1 : rand() % (1024 * 4))
880 : {
881 138 : zipios_test::auto_unlink_t remove_bin("file.bin", true); // clean up, just in case
882 138 : zipios_test::auto_unlink_t remove_zip("file.zip", true);
883 :
884 : // create a file of various sizes (increasingly big though)
885 : {
886 69 : std::ofstream file_bin("file.bin", std::ios::out | std::ios::binary);
887 3860731 : for(int pos(0); pos < sz; ++pos)
888 : {
889 3860662 : file_bin << static_cast<char>(rand());
890 : }
891 69 : }
892 :
893 138 : zipios::DirectoryCollection dc("file.bin");
894 69 : zipios::FileEntry::vector_t v(dc.entries());
895 69 : (*v.begin())->setLevel(zipios::FileEntry::COMPRESSION_LEVEL_NONE);
896 69 : (*v.begin())->setMethod(zipios::StorageMethod::DEFLATED);
897 : {
898 69 : std::ofstream out("file.zip", std::ios::out | std::ios::binary | std::ios::trunc);
899 69 : zipios::ZipFile::saveCollectionToArchive(out, dc);
900 69 : }
901 :
902 138 : zipios::ZipFile zf("file.zip");
903 :
904 69 : CATCH_REQUIRE(zf.isValid());
905 69 : CATCH_REQUIRE_FALSE(zf.entries().empty());
906 69 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
907 69 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
908 69 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
909 69 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
910 69 : CATCH_REQUIRE(zf.getName() == "file.zip");
911 69 : CATCH_REQUIRE(zf.size() == 1);
912 69 : zf.mustBeValid(); // not throwing
913 69 : }
914 : }
915 2 : CATCH_END_SECTION()
916 :
917 2 : CATCH_START_SECTION("try three uncompressed files of many sizes")
918 : {
919 77 : for(int sz(0); sz <= 128 * 1024; sz += sz < 10 ? 1 : rand() % (1024 * 4))
920 : {
921 152 : zipios_test::auto_unlink_t remove_bin1("file1.bin", true);
922 152 : zipios_test::auto_unlink_t remove_bin2("file2.bin", true);
923 152 : zipios_test::auto_unlink_t remove_bin3("file3.bin", true);
924 152 : zipios_test::auto_unlink_t remove_zip("file.zip", true);
925 :
926 : // create a file of various sizes (increasingly big though)
927 : {
928 76 : std::ofstream file_bin("file1.bin", std::ios::out | std::ios::binary);
929 4081217 : for(int pos(0); pos < sz; ++pos)
930 : {
931 4081141 : file_bin << static_cast<char>(rand());
932 : }
933 76 : }
934 : {
935 76 : std::ofstream file_bin("file2.bin", std::ios::out | std::ios::binary);
936 4081217 : for(int pos(0); pos < sz; ++pos)
937 : {
938 4081141 : file_bin << static_cast<char>(rand());
939 : }
940 76 : }
941 : {
942 76 : std::ofstream file_bin("file3.bin", std::ios::out | std::ios::binary);
943 4081217 : for(int pos(0); pos < sz; ++pos)
944 : {
945 4081141 : file_bin << static_cast<char>(rand());
946 : }
947 76 : }
948 :
949 152 : zipios::DirectoryCollection dc("file1.bin");
950 : {
951 152 : zipios::DirectoryEntry other_entry2(zipios::FilePath("file2.bin"));
952 76 : dc.addEntry(other_entry2);
953 152 : zipios::DirectoryEntry other_entry3(zipios::FilePath("file3.bin"));
954 76 : dc.addEntry(other_entry3);
955 76 : }
956 76 : zipios::FileEntry::vector_t v(dc.entries());
957 76 : (*v.begin())->setLevel(zipios::FileEntry::COMPRESSION_LEVEL_NONE);
958 76 : (*v.begin())->setMethod(zipios::StorageMethod::DEFLATED);
959 : {
960 76 : std::ofstream out("file.zip", std::ios::out | std::ios::binary | std::ios::trunc);
961 76 : zipios::ZipFile::saveCollectionToArchive(out, dc);
962 76 : }
963 :
964 152 : zipios::ZipFile zf("file.zip");
965 :
966 76 : CATCH_REQUIRE(zf.isValid());
967 76 : CATCH_REQUIRE_FALSE(zf.entries().empty());
968 76 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::MATCH));
969 76 : CATCH_REQUIRE_FALSE(zf.getEntry("inexistant", zipios::FileCollection::MatchPath::IGNORE));
970 76 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::MATCH));
971 76 : CATCH_REQUIRE_FALSE(zf.getInputStream("inexistant", zipios::FileCollection::MatchPath::IGNORE));
972 76 : CATCH_REQUIRE(zf.getName() == "file.zip");
973 76 : CATCH_REQUIRE(zf.size() == 3);
974 76 : zf.mustBeValid(); // not throwing
975 76 : }
976 : }
977 2 : CATCH_END_SECTION()
978 2 : }
979 :
980 :
981 : // For this one we have a set of structures and we "manually"
982 : // create Zip archive files that we can thus tweak with totally
983 : // invalid parameters
984 : struct local_header_t
985 : {
986 : typedef std::vector<unsigned char> buffer_t;
987 :
988 : uint32_t m_signature; // "PK 3.4"
989 : uint16_t m_version; // 10 or 20
990 : uint16_t m_flags; // generally zero ("general purpose field")
991 : uint16_t m_compression_method; // Zipios only supports STORED and DEFLATE
992 : uint32_t m_time_and_date; // MS-DOS date and time
993 : uint32_t m_crc32; // CRC-32 of the file data
994 : uint32_t m_compressed_size; // size of data once compressed
995 : uint32_t m_uncompressed_size; // size of data uncompressed
996 : //uint16_t m_filename_length; // length name of this file
997 : //uint16_t m_extra_field_length; // length of extra buffer, Zipios ignore those
998 : //uint8_t m_filename[m_filename_length];
999 : std::string m_filename;
1000 : //uint8_t m_extra_field[m_extra_field_length];
1001 : buffer_t m_extra_field;
1002 :
1003 50 : local_header_t()
1004 50 : : m_signature(0x04034B50)
1005 50 : , m_version(10)
1006 50 : , m_flags(0)
1007 50 : , m_compression_method(0) // 0 == STORED
1008 50 : , m_crc32(0)
1009 50 : , m_compressed_size(0) // undefined is compression method is 0
1010 50 : , m_uncompressed_size(0)
1011 : //, m_filename("") -- auto-init
1012 : //, m_extra_field() -- auto-init
1013 : {
1014 50 : zipios::DOSDateTime dt;
1015 50 : dt.setUnixTimestamp(time(nullptr));
1016 50 : m_time_and_date = dt.getDOSDateTime();
1017 50 : }
1018 :
1019 50 : void write(std::ostream& os)
1020 : {
1021 50 : if(m_filename.empty())
1022 : {
1023 : std::cerr << "bug: local_header_t::write() called without a filename." << std::endl; // LCOV_EXCL_LINE
1024 : exit(1); // LCOV_EXCL_LINE
1025 : }
1026 :
1027 : // IMPORTANT NOTE:
1028 : // We do not verify any field other than the filename because
1029 : // we want to be able to use this class to create anything
1030 : // (i.e. including invalid headers.)
1031 :
1032 50 : os << static_cast<unsigned char>(m_signature >> 0);
1033 50 : os << static_cast<unsigned char>(m_signature >> 8);
1034 50 : os << static_cast<unsigned char>(m_signature >> 16);
1035 50 : os << static_cast<unsigned char>(m_signature >> 24);
1036 50 : os << static_cast<unsigned char>(m_version >> 0);
1037 50 : os << static_cast<unsigned char>(m_version >> 8);
1038 50 : os << static_cast<unsigned char>(m_flags >> 0);
1039 50 : os << static_cast<unsigned char>(m_flags >> 8);
1040 50 : os << static_cast<unsigned char>(m_compression_method >> 0);
1041 50 : os << static_cast<unsigned char>(m_compression_method >> 8);
1042 50 : os << static_cast<unsigned char>(m_time_and_date >> 0);
1043 50 : os << static_cast<unsigned char>(m_time_and_date >> 8);
1044 50 : os << static_cast<unsigned char>(m_time_and_date >> 16);
1045 50 : os << static_cast<unsigned char>(m_time_and_date >> 24);
1046 50 : os << static_cast<unsigned char>(m_crc32 >> 0);
1047 50 : os << static_cast<unsigned char>(m_crc32 >> 8);
1048 50 : os << static_cast<unsigned char>(m_crc32 >> 16);
1049 50 : os << static_cast<unsigned char>(m_crc32 >> 24);
1050 50 : os << static_cast<unsigned char>(m_compressed_size >> 0);
1051 50 : os << static_cast<unsigned char>(m_compressed_size >> 8);
1052 50 : os << static_cast<unsigned char>(m_compressed_size >> 16);
1053 50 : os << static_cast<unsigned char>(m_compressed_size >> 24);
1054 50 : os << static_cast<unsigned char>(m_uncompressed_size >> 0);
1055 50 : os << static_cast<unsigned char>(m_uncompressed_size >> 8);
1056 50 : os << static_cast<unsigned char>(m_uncompressed_size >> 16);
1057 50 : os << static_cast<unsigned char>(m_uncompressed_size >> 24);
1058 50 : uint16_t filename_length(m_filename.length());
1059 50 : os << static_cast<unsigned char>(filename_length >> 0);
1060 50 : os << static_cast<unsigned char>(filename_length >> 8);
1061 50 : uint16_t extra_field_length(m_extra_field.size());
1062 50 : os << static_cast<unsigned char>(extra_field_length >> 0);
1063 50 : os << static_cast<unsigned char>(extra_field_length >> 8);
1064 50 : os << m_filename;
1065 50 : os.write(reinterpret_cast<char const *>(&m_extra_field[0]), m_extra_field.size());
1066 50 : }
1067 : };
1068 :
1069 :
1070 : struct central_directory_header_t
1071 : {
1072 : typedef std::vector<unsigned char> buffer_t;
1073 :
1074 : uint32_t m_signature; // 00 -- "PK 2.1"
1075 : uint16_t m_version; // 04 -- 10 or 20
1076 : uint16_t m_extract_version; // 06 -- generally zero
1077 : uint16_t m_flags; // 08 -- various flags
1078 : uint16_t m_compression_method; // 0A -- method used to compress the data
1079 : uint32_t m_time_and_date; // 0C -- MS-DOS date and time
1080 : uint32_t m_crc32; // 10 -- CRC-32 of the file data
1081 : uint32_t m_compressed_size; // 14 -- size of data once compressed
1082 : uint32_t m_uncompressed_size; // 18 -- size of data uncompressed
1083 : //uint16_t m_filename_length; // 1C -- length name of this file
1084 : //uint16_t m_extra_field_length; // 1E -- length of extra buffer, Zipios ignore those
1085 : //uint16_t m_file_comment_length; // 20 -- length of comment
1086 : uint16_t m_disk_number_start; // 22 -- disk number of split archives
1087 : uint16_t m_internal_file_attributes; // 24 -- file attributes
1088 : uint32_t m_external_file_attributes; // 26 -- file attributes
1089 : uint32_t m_relative_offset_to_local_header; // 2A -- offset to actual file
1090 : //uint8_t m_filename[m_filename_length]; // 2E -- filename (variable size
1091 : std::string m_filename;
1092 : //uint8_t m_extra_field[m_extra_field_length]; // .. -- extra field(s)
1093 : buffer_t m_extra_field;
1094 : //uint8_t m_file_comment[m_file_comment_length]; // .. -- file comment
1095 : std::string m_file_comment;
1096 :
1097 70 : central_directory_header_t()
1098 70 : : m_signature(0x02014B50)
1099 70 : , m_version(10)
1100 70 : , m_extract_version(10)
1101 70 : , m_flags(0)
1102 70 : , m_compression_method(0) // 0 == STORED
1103 70 : , m_crc32(0)
1104 70 : , m_compressed_size(0) // undefined is compression method is 0
1105 70 : , m_uncompressed_size(0)
1106 70 : , m_disk_number_start(0)
1107 70 : , m_internal_file_attributes(0)
1108 70 : , m_external_file_attributes(0)
1109 70 : , m_relative_offset_to_local_header(0)
1110 : //, m_filename("") -- auto-init
1111 : //, m_extra_field() -- auto-init
1112 : //, m_file_comment("") -- auto-init
1113 : {
1114 70 : zipios::DOSDateTime dt;
1115 70 : dt.setUnixTimestamp(time(nullptr));
1116 70 : m_time_and_date = dt.getDOSDateTime();
1117 70 : }
1118 :
1119 70 : void write(std::ostream& os)
1120 : {
1121 70 : if(m_filename.empty())
1122 : {
1123 : std::cerr << "bug: central_directory_header_t::write() called without a filename." << std::endl; // LCOV_EXCL_LINE
1124 : exit(1); // LCOV_EXCL_LINE
1125 : }
1126 :
1127 : // IMPORTANT NOTE:
1128 : // We do not verify any field other than the filename because
1129 : // we want to be able to use this class to create anything
1130 : // (i.e. including invalid headers.)
1131 :
1132 70 : os << static_cast<unsigned char>(m_signature >> 0);
1133 70 : os << static_cast<unsigned char>(m_signature >> 8);
1134 70 : os << static_cast<unsigned char>(m_signature >> 16);
1135 70 : os << static_cast<unsigned char>(m_signature >> 24);
1136 70 : os << static_cast<unsigned char>(m_version >> 0);
1137 70 : os << static_cast<unsigned char>(m_version >> 8);
1138 70 : os << static_cast<unsigned char>(m_extract_version >> 0);
1139 70 : os << static_cast<unsigned char>(m_extract_version >> 8);
1140 70 : os << static_cast<unsigned char>(m_flags >> 0);
1141 70 : os << static_cast<unsigned char>(m_flags >> 8);
1142 70 : os << static_cast<unsigned char>(m_compression_method >> 0);
1143 70 : os << static_cast<unsigned char>(m_compression_method >> 8);
1144 70 : os << static_cast<unsigned char>(m_time_and_date >> 0);
1145 70 : os << static_cast<unsigned char>(m_time_and_date >> 8);
1146 70 : os << static_cast<unsigned char>(m_time_and_date >> 16);
1147 70 : os << static_cast<unsigned char>(m_time_and_date >> 24);
1148 70 : os << static_cast<unsigned char>(m_crc32 >> 0);
1149 70 : os << static_cast<unsigned char>(m_crc32 >> 8);
1150 70 : os << static_cast<unsigned char>(m_crc32 >> 16);
1151 70 : os << static_cast<unsigned char>(m_crc32 >> 24);
1152 70 : os << static_cast<unsigned char>(m_compressed_size >> 0);
1153 70 : os << static_cast<unsigned char>(m_compressed_size >> 8);
1154 70 : os << static_cast<unsigned char>(m_compressed_size >> 16);
1155 70 : os << static_cast<unsigned char>(m_compressed_size >> 24);
1156 70 : os << static_cast<unsigned char>(m_uncompressed_size >> 0);
1157 70 : os << static_cast<unsigned char>(m_uncompressed_size >> 8);
1158 70 : os << static_cast<unsigned char>(m_uncompressed_size >> 16);
1159 70 : os << static_cast<unsigned char>(m_uncompressed_size >> 24);
1160 70 : uint16_t filename_length(m_filename.length());
1161 70 : os << static_cast<unsigned char>(filename_length >> 0);
1162 70 : os << static_cast<unsigned char>(filename_length >> 8);
1163 70 : uint16_t extra_field_length(m_extra_field.size());
1164 70 : os << static_cast<unsigned char>(extra_field_length >> 0);
1165 70 : os << static_cast<unsigned char>(extra_field_length >> 8);
1166 70 : uint16_t file_comment_length(m_file_comment.length());
1167 70 : os << static_cast<unsigned char>(file_comment_length >> 0);
1168 70 : os << static_cast<unsigned char>(file_comment_length >> 8);
1169 70 : os << static_cast<unsigned char>(m_disk_number_start >> 0);
1170 70 : os << static_cast<unsigned char>(m_disk_number_start >> 8);
1171 70 : os << static_cast<unsigned char>(m_internal_file_attributes >> 0);
1172 70 : os << static_cast<unsigned char>(m_internal_file_attributes >> 8);
1173 70 : os << static_cast<unsigned char>(m_external_file_attributes >> 0);
1174 70 : os << static_cast<unsigned char>(m_external_file_attributes >> 8);
1175 70 : os << static_cast<unsigned char>(m_external_file_attributes >> 16);
1176 70 : os << static_cast<unsigned char>(m_external_file_attributes >> 24);
1177 70 : os << static_cast<unsigned char>(m_relative_offset_to_local_header >> 0);
1178 70 : os << static_cast<unsigned char>(m_relative_offset_to_local_header >> 8);
1179 70 : os << static_cast<unsigned char>(m_relative_offset_to_local_header >> 16);
1180 70 : os << static_cast<unsigned char>(m_relative_offset_to_local_header >> 24);
1181 70 : os << m_filename;
1182 70 : os.write(reinterpret_cast<char const *>(&m_extra_field[0]), m_extra_field.size());
1183 70 : os << m_file_comment;
1184 70 : }
1185 : };
1186 :
1187 :
1188 : struct end_of_central_directory_t
1189 : {
1190 : uint32_t m_signature; // "PK 5.6"
1191 : uint16_t m_disk_number;
1192 : uint16_t m_disk_start;
1193 : uint16_t m_file_count; // number of files in this archive
1194 : uint16_t m_total_count; // total number across all split files
1195 : uint32_t m_central_directory_size;
1196 : uint32_t m_central_directory_offset;
1197 : //uint16_t m_comment_length;
1198 : //unsigned char m_comment[m_comment_length];
1199 : std::string m_comment;
1200 :
1201 112 : end_of_central_directory_t()
1202 112 : : m_signature(0x06054B50)
1203 112 : , m_disk_number(0)
1204 112 : , m_disk_start(0)
1205 112 : , m_file_count(0)
1206 112 : , m_total_count(0)
1207 112 : , m_central_directory_size(0)
1208 112 : , m_central_directory_offset(0)
1209 : //, m_comment_length(0)
1210 : //, m_comment("") -- auto-init
1211 : {
1212 112 : }
1213 :
1214 112 : void write(std::ostream& os)
1215 : {
1216 : // IMPORTANT NOTE:
1217 : // We do not verify any of the values on purpose, we want to be
1218 : // able to use this class to create anything (i.e. including invalid
1219 : // headers.)
1220 :
1221 112 : os << static_cast<unsigned char>(m_signature >> 0);
1222 112 : os << static_cast<unsigned char>(m_signature >> 8);
1223 112 : os << static_cast<unsigned char>(m_signature >> 16);
1224 112 : os << static_cast<unsigned char>(m_signature >> 24);
1225 112 : os << static_cast<unsigned char>(m_disk_number >> 0);
1226 112 : os << static_cast<unsigned char>(m_disk_number >> 8);
1227 112 : os << static_cast<unsigned char>(m_disk_start >> 0);
1228 112 : os << static_cast<unsigned char>(m_disk_start >> 8);
1229 112 : os << static_cast<unsigned char>(m_file_count >> 0);
1230 112 : os << static_cast<unsigned char>(m_file_count >> 8);
1231 112 : os << static_cast<unsigned char>(m_total_count >> 0);
1232 112 : os << static_cast<unsigned char>(m_total_count >> 8);
1233 112 : os << static_cast<unsigned char>(m_central_directory_size >> 0);
1234 112 : os << static_cast<unsigned char>(m_central_directory_size >> 8);
1235 112 : os << static_cast<unsigned char>(m_central_directory_size >> 16);
1236 112 : os << static_cast<unsigned char>(m_central_directory_size >> 24);
1237 112 : os << static_cast<unsigned char>(m_central_directory_offset >> 0);
1238 112 : os << static_cast<unsigned char>(m_central_directory_offset >> 8);
1239 112 : os << static_cast<unsigned char>(m_central_directory_offset >> 16);
1240 112 : os << static_cast<unsigned char>(m_central_directory_offset >> 24);
1241 112 : uint16_t comment_length(m_comment.length());
1242 112 : os << static_cast<unsigned char>(comment_length >> 0);
1243 112 : os << static_cast<unsigned char>(comment_length >> 8);
1244 112 : os << m_comment;
1245 112 : }
1246 : };
1247 :
1248 :
1249 10 : CATCH_TEST_CASE("valid_and_invalid_zipfile_archives", "[ZipFile][FileCollection]")
1250 : {
1251 10 : zipios_test::safe_chdir cwd(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
1252 :
1253 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with End of Central Directory that are tool small")
1254 : {
1255 23 : for(ssize_t i(22 - 1); i >= 0; --i)
1256 : {
1257 : // create an empty header in the file
1258 44 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1259 : {
1260 22 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1261 :
1262 22 : end_of_central_directory_t eocd;
1263 22 : eocd.write(os);
1264 22 : }
1265 :
1266 : // truncate the file to 'i' size
1267 22 : CATCH_REQUIRE(truncate("file.zip", i) == 0);
1268 :
1269 88 : CATCH_REQUIRE_THROWS_AS([&](){
1270 : zipios::ZipFile zf("file.zip");
1271 : }(), zipios::FileCollectionException);
1272 22 : }
1273 : }
1274 10 : CATCH_END_SECTION()
1275 :
1276 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with End of Central Directory file except for the comment")
1277 : {
1278 11 : for(int i(0); i < 10; ++i)
1279 : {
1280 : // create an empty header in the file
1281 20 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1282 10 : size_t const comment_len(rand() % 20 + 5);
1283 : {
1284 10 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1285 :
1286 10 : end_of_central_directory_t eocd;
1287 175 : for(size_t j(0); j < comment_len; ++j)
1288 : {
1289 165 : eocd.m_comment += static_cast<char>('A' + rand() % 26);
1290 : }
1291 10 : eocd.write(os);
1292 10 : }
1293 :
1294 : // truncate the file to not include the whole comment
1295 : // (truncate at least one character though)
1296 10 : size_t const five(5);
1297 10 : CATCH_REQUIRE(truncate("file.zip", (22 + comment_len) - (rand() % std::min(five, comment_len) + 1)) == 0);
1298 :
1299 40 : CATCH_REQUIRE_THROWS_AS([&](){
1300 : zipios::ZipFile zf("file.zip");
1301 : }(), zipios::IOException);
1302 10 : }
1303 : }
1304 10 : CATCH_END_SECTION()
1305 :
1306 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with End of Central Directory using counts that differ")
1307 : {
1308 11 : for(int i(0); i < 10; ++i)
1309 : {
1310 : // create an empty header in the file
1311 20 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1312 10 : size_t const size1(rand() & 0xFFFF);
1313 : size_t size2;
1314 : do
1315 : {
1316 10 : size2 = rand() & 0xFFFF;
1317 : }
1318 10 : while(size1 == size2);
1319 : {
1320 10 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1321 :
1322 10 : end_of_central_directory_t eocd;
1323 10 : eocd.m_file_count = size1;
1324 10 : eocd.m_total_count = size2;
1325 10 : eocd.write(os);
1326 10 : }
1327 :
1328 40 : CATCH_REQUIRE_THROWS_AS([&](){
1329 : zipios::ZipFile zf("file.zip");
1330 : }(), zipios::FileCollectionException);
1331 10 : }
1332 : }
1333 10 : CATCH_END_SECTION()
1334 :
1335 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with one Local Entry using an invalid signature")
1336 : {
1337 11 : for(int i(0); i < 10; ++i)
1338 : {
1339 : // create an empty header in the file
1340 20 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1341 : {
1342 10 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1343 :
1344 : // since the signature will be invalid, we can ignore the
1345 : // rest too...
1346 10 : central_directory_header_t cdh;
1347 10 : cdh.m_signature = 0x01020304; // an invalid signature
1348 10 : cdh.m_filename = "invalid";
1349 10 : cdh.write(os);
1350 :
1351 10 : end_of_central_directory_t eocd;
1352 10 : eocd.m_file_count = 1;
1353 10 : eocd.m_total_count = 1;
1354 10 : eocd.m_central_directory_size = 46 + 7; // structure + filename
1355 10 : eocd.write(os);
1356 10 : }
1357 :
1358 40 : CATCH_REQUIRE_THROWS_AS([&](){
1359 : zipios::ZipFile zf("file.zip");
1360 : }(), zipios::IOException);
1361 10 : }
1362 : }
1363 10 : CATCH_END_SECTION()
1364 :
1365 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with a valid central directory but no local entries")
1366 : {
1367 11 : for(int i(0); i < 10; ++i)
1368 : {
1369 : // create an empty header in the file
1370 20 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1371 : {
1372 10 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1373 :
1374 : // since the signature will be invalid, we can ignore the
1375 : // rest too...
1376 10 : central_directory_header_t cdh;
1377 10 : cdh.m_filename = "invalid";
1378 10 : cdh.write(os);
1379 :
1380 10 : end_of_central_directory_t eocd;
1381 10 : eocd.m_file_count = 1;
1382 10 : eocd.m_total_count = 1;
1383 10 : eocd.m_central_directory_size = 46 + 7; // structure + filename
1384 10 : eocd.write(os);
1385 10 : }
1386 :
1387 40 : CATCH_REQUIRE_THROWS_AS([&](){
1388 : zipios::ZipFile zf("file.zip");
1389 : }(), zipios::IOException);
1390 10 : }
1391 : }
1392 10 : CATCH_END_SECTION()
1393 :
1394 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with one an unsupported compression method")
1395 : {
1396 11 : for(int i(0); i < 10; ++i)
1397 : {
1398 : // create an empty header in the file
1399 20 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1400 : {
1401 10 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1402 :
1403 : // create a header (has to be equal to pass to the method check)
1404 10 : local_header_t lh;
1405 10 : central_directory_header_t cdh;
1406 10 : end_of_central_directory_t eocd;
1407 :
1408 : for(;;)
1409 : {
1410 : // this is saved as a 16 bit value so it probably should
1411 : // support 16 bits
1412 10 : lh.m_compression_method = rand() & 0xFFFF;
1413 :
1414 : // make sure it is not a valid method
1415 10 : bool found(false);
1416 30 : for(size_t m(0); m < sizeof(g_supported_storage_methods) / sizeof(g_supported_storage_methods[0]); ++m)
1417 : {
1418 20 : if(static_cast<zipios::StorageMethod>(lh.m_compression_method) == g_supported_storage_methods[m])
1419 : {
1420 : // it is valid, we will try again
1421 : // (it is going to be really rare, so we exclude
1422 : // these lines from the coverage)
1423 : found = true; // LCOV_EXCL_LINE
1424 : break; // LCOV_EXCL_LINE
1425 : }
1426 : }
1427 10 : if(!found)
1428 : {
1429 10 : break;
1430 : }
1431 : } // LCOV_EXCL_LINE
1432 10 : lh.m_filename = "invalid";
1433 10 : lh.write(os);
1434 :
1435 10 : eocd.m_central_directory_offset = os.tellp();
1436 :
1437 10 : cdh.m_compression_method = lh.m_compression_method;
1438 10 : cdh.m_filename = "invalid";
1439 10 : cdh.write(os);
1440 :
1441 10 : eocd.m_file_count = 1;
1442 10 : eocd.m_total_count = 1;
1443 10 : eocd.m_central_directory_size = 46 + 7; // structure + filename
1444 10 : eocd.write(os);
1445 10 : }
1446 :
1447 20 : zipios::ZipFile zf("file.zip");
1448 30 : CATCH_REQUIRE_THROWS_AS(zf.getInputStream("invalid"), zipios::FileCollectionException);
1449 10 : }
1450 : }
1451 10 : CATCH_END_SECTION()
1452 :
1453 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with a trailing data descriptor")
1454 : {
1455 11 : for(int i(0); i < 10; ++i)
1456 : {
1457 : // create an empty header in the file
1458 20 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1459 : {
1460 10 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1461 :
1462 : // create a header (has to be equal to pass to the method check)
1463 10 : local_header_t lh;
1464 10 : central_directory_header_t cdh;
1465 10 : end_of_central_directory_t eocd;
1466 :
1467 : // use a valid compression method
1468 10 : lh.m_flags |= 1 << 3; // <-- testing that trailing data is not supported!
1469 10 : lh.m_compression_method = static_cast<uint16_t>(g_supported_storage_methods[rand() % (sizeof(g_supported_storage_methods) / sizeof(g_supported_storage_methods[0]))]);
1470 10 : lh.m_filename = "invalid";
1471 10 : lh.write(os);
1472 :
1473 10 : eocd.m_central_directory_offset = os.tellp();
1474 :
1475 10 : cdh.m_compression_method = lh.m_compression_method;
1476 10 : cdh.m_flags = lh.m_flags;
1477 10 : cdh.m_filename = "invalid";
1478 10 : cdh.write(os);
1479 :
1480 10 : eocd.m_file_count = 1;
1481 10 : eocd.m_total_count = 1;
1482 10 : eocd.m_central_directory_size = 46 + 7; // structure + filename
1483 10 : eocd.write(os);
1484 10 : }
1485 :
1486 20 : zipios::ZipFile zf("file.zip");
1487 30 : CATCH_REQUIRE_THROWS_AS(zf.getInputStream("invalid"), zipios::FileCollectionException);
1488 10 : }
1489 : }
1490 10 : CATCH_END_SECTION()
1491 :
1492 : /** \todo
1493 : * We need to write a similar test that verifies each and every field
1494 : * that proves there is an error and all the fields that do not prove
1495 : * anything.
1496 : */
1497 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with a mismatched compression method")
1498 : {
1499 11 : for(int i(0); i < 10; ++i)
1500 : {
1501 : // create an empty header in the file
1502 20 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1503 : {
1504 10 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1505 :
1506 : // create a header (has to be equal to pass to the method check)
1507 10 : local_header_t lh;
1508 10 : central_directory_header_t cdh;
1509 10 : end_of_central_directory_t eocd;
1510 :
1511 : // use a valid compression method
1512 10 : lh.m_compression_method = static_cast<uint16_t>(zipios::StorageMethod::STORED);
1513 10 : lh.m_filename = "invalid";
1514 10 : lh.write(os);
1515 :
1516 10 : eocd.m_central_directory_offset = os.tellp();
1517 :
1518 10 : cdh.m_compression_method = static_cast<uint16_t>(zipios::StorageMethod::DEFLATED);
1519 10 : cdh.m_flags = lh.m_flags;
1520 10 : cdh.m_filename = "invalid";
1521 10 : cdh.write(os);
1522 :
1523 10 : eocd.m_file_count = 1;
1524 10 : eocd.m_total_count = 1;
1525 10 : eocd.m_central_directory_size = 46 + 7; // structure + filename
1526 10 : eocd.write(os);
1527 10 : }
1528 :
1529 40 : CATCH_REQUIRE_THROWS_AS([&](){
1530 : zipios::ZipFile zf("file.zip");
1531 : }(), zipios::FileCollectionException);
1532 10 : }
1533 : }
1534 10 : CATCH_END_SECTION()
1535 :
1536 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with a trailing data descriptor")
1537 : {
1538 11 : for(int i(0); i < 10; ++i)
1539 : {
1540 : // create an empty header in the file
1541 20 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1542 : {
1543 10 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1544 :
1545 : // create a header (has to be equal to pass to the method check)
1546 10 : local_header_t lh;
1547 10 : central_directory_header_t cdh;
1548 10 : end_of_central_directory_t eocd;
1549 :
1550 : // use a valid compression method
1551 10 : lh.m_compression_method = static_cast<uint16_t>(g_supported_storage_methods[rand() % (sizeof(g_supported_storage_methods) / sizeof(g_supported_storage_methods[0]))]);
1552 10 : lh.m_filename = "invalid";
1553 10 : lh.write(os);
1554 :
1555 10 : eocd.m_central_directory_offset = os.tellp();
1556 :
1557 10 : cdh.m_compression_method = lh.m_compression_method;
1558 10 : cdh.m_flags = lh.m_flags;
1559 10 : cdh.m_filename = "invalid";
1560 10 : cdh.write(os);
1561 :
1562 10 : eocd.m_file_count = 1;
1563 10 : eocd.m_total_count = 1;
1564 10 : if(i & 1)
1565 : {
1566 5 : eocd.m_central_directory_size = 46 + 7 + rand() % 10 + 1; // structure + filename + erroneous size
1567 : }
1568 : else
1569 : {
1570 5 : eocd.m_central_directory_size = 46 + 7 - rand() % 10 - 1; // structure + filename - erroneous size
1571 : }
1572 10 : eocd.write(os);
1573 10 : }
1574 :
1575 40 : CATCH_REQUIRE_THROWS_AS([&](){
1576 : zipios::ZipFile zf("file.zip");
1577 : }(), zipios::FileCollectionException);
1578 10 : }
1579 : }
1580 10 : CATCH_END_SECTION()
1581 :
1582 : /** \todo
1583 : * Once clang is fixed, remove those tests. clang does not clear the
1584 : * std::unchecked_exception() flag when we have a re-throw in a catch.
1585 : * In this case we have a problem with the exception raised in
1586 : * InflateInputStreambuf::underflow() when gzip finds an invalid
1587 : * input stream.
1588 : */
1589 : #ifndef __clang__
1590 10 : CATCH_START_SECTION("valid_and_invalid_zipfile_archives: create files with a compressed file, save only 50% of the data")
1591 : {
1592 11 : for(int i(0); i < 10; ++i)
1593 : {
1594 : // create an empty header in the file
1595 20 : zipios_test::auto_unlink_t auto_unlink("file.zip", true);
1596 10 : size_t uncompressed_size(0);
1597 : {
1598 10 : std::ofstream os("file.zip", std::ios::out | std::ios::binary);
1599 :
1600 : // create a header (has to be equal to pass to the method check)
1601 10 : local_header_t lh;
1602 10 : central_directory_header_t cdh;
1603 10 : end_of_central_directory_t eocd;
1604 :
1605 : // create a file in a buffer then compress it
1606 : // make sure the file is large enough to ensure that the
1607 : // decompression fails "as expected" by this test
1608 : typedef std::vector<Bytef> buffer_t;
1609 10 : buffer_t file_buffer;
1610 10 : size_t const file_size(rand() % (80 * 1024) + zipios::getBufferSize() * 3);
1611 645898 : for(size_t pos(0); pos < file_size; ++pos)
1612 : {
1613 645888 : file_buffer.push_back(static_cast<unsigned char>(rand()));
1614 : }
1615 10 : buffer_t compressed_buffer;
1616 10 : uLongf compressed_size(file_size * 2);
1617 10 : compressed_buffer.resize(compressed_size);
1618 10 : compress2(&compressed_buffer[0], &compressed_size, &file_buffer[0], file_size, 9);
1619 10 : compressed_buffer.resize(compressed_size); // the new size!
1620 10 : std::fill(compressed_buffer.begin() + compressed_size / 2, compressed_buffer.end(), 0);
1621 :
1622 : // use a valid compression method
1623 10 : lh.m_compression_method = static_cast<uint16_t>(zipios::StorageMethod::DEFLATED);
1624 10 : lh.m_compressed_size = compressed_size - 2;
1625 10 : lh.m_uncompressed_size = file_size;
1626 10 : lh.m_crc32 = crc32(0L, &file_buffer[0], file_size);
1627 10 : lh.m_filename = "invalid";
1628 10 : lh.write(os);
1629 :
1630 : // write the first 50% of the compressed data then zeroes
1631 : // make sure to skip the first 2 bytes which are the zlib
1632 : // marker (0x78 0x9C)
1633 10 : os.write(reinterpret_cast<char *>(&compressed_buffer[0]) + 2, (compressed_size - 2));
1634 :
1635 10 : eocd.m_central_directory_offset = os.tellp();
1636 :
1637 10 : cdh.m_compression_method = lh.m_compression_method;
1638 10 : cdh.m_compressed_size = lh.m_compressed_size;
1639 10 : cdh.m_uncompressed_size = lh.m_uncompressed_size;
1640 10 : cdh.m_crc32 = lh.m_crc32;
1641 10 : cdh.m_flags = lh.m_flags;
1642 10 : cdh.m_filename = "invalid";
1643 10 : cdh.write(os);
1644 :
1645 10 : eocd.m_file_count = 1;
1646 10 : eocd.m_total_count = 1;
1647 10 : eocd.m_central_directory_size = 46 + 7; // structure + filename
1648 10 : eocd.write(os);
1649 :
1650 : // keep a copy of the uncompressed size to test after the
1651 : // read stops
1652 10 : uncompressed_size = file_size;
1653 10 : }
1654 :
1655 20 : zipios::ZipFile zf("file.zip");
1656 : // we cannot really know when exactly
1657 : // we are going to get the throw
1658 20 : zipios::ZipFile::stream_pointer_t in(zf.getInputStream("invalid"));
1659 10 : size_t amount_read(0);
1660 : do
1661 : {
1662 : char buf[BUFSIZ];
1663 58 : in->read(buf, sizeof(buf));
1664 58 : amount_read += in->gcount();
1665 : }
1666 58 : while(*in);
1667 10 : CATCH_REQUIRE(in->bad());
1668 10 : CATCH_REQUIRE(in->fail());
1669 10 : CATCH_REQUIRE(amount_read != uncompressed_size);
1670 10 : }
1671 : }
1672 10 : CATCH_END_SECTION()
1673 : #endif
1674 10 : }
1675 :
1676 :
1677 1 : CATCH_TEST_CASE("saveCollectionToArchive_with_DirectoryCollection", "[ZipFile][DirectoryCollection]")
1678 : {
1679 1 : CATCH_START_SECTION("saveCollectionToArchive_with_DirectoryCollection: create files with a compressed file, save only 50% of the data")
1680 : {
1681 1 : std::string const top_dir(SNAP_CATCH2_NAMESPACE::g_tmp_dir() + "/save-collection");
1682 1 : std::string const test_dir(top_dir + "/test_dir");
1683 :
1684 1 : zipios_test::auto_unlink_t auto_unlink(top_dir, true);
1685 :
1686 1 : CATCH_REQUIRE(system(("mkdir -p " + test_dir).c_str()) == 0);
1687 1 : zipios_test::safe_chdir cwd(top_dir);
1688 :
1689 1 : std::string cache_bin;
1690 1 : std::string cache_text;
1691 : {
1692 1 : std::ofstream file_bin("test_dir/file1.bin", std::ios::out | std::ios::binary);
1693 1 : size_t const size(512 + rand() % 512);
1694 591 : for(size_t pos(0); pos < size; ++pos)
1695 : {
1696 590 : char const c(static_cast<char>(rand()));
1697 590 : file_bin << c;
1698 590 : cache_bin += c;
1699 : }
1700 :
1701 1 : std::ofstream file_empty("test_dir/file2.empty", std::ios::out | std::ios::binary);
1702 :
1703 1 : std::ofstream file_text("test_dir/file3.text", std::ios::out | std::ios::binary);
1704 1 : size_t const length(512 + rand() % 512);
1705 582 : for(size_t pos(0); pos < length; ++pos)
1706 : {
1707 581 : char c(rand() % 26 + 'a');
1708 581 : file_text << c;
1709 581 : cache_text += c;
1710 581 : if(pos % 40 == 39)
1711 : {
1712 14 : file_text << '\n';
1713 14 : cache_text += '\n';
1714 : }
1715 : }
1716 1 : }
1717 :
1718 : {
1719 2 : zipios::DirectoryCollection directoryCollection("test_dir");
1720 1 : std::ofstream tempZipStream("test.zip", std::ios_base::binary | std::ios::out);
1721 1 : zipios::ZipFile::saveCollectionToArchive(tempZipStream, directoryCollection);
1722 1 : tempZipStream.close();
1723 1 : }
1724 :
1725 1 : CATCH_REQUIRE(system("unzip -o test.zip >/dev/null") == 0);
1726 :
1727 : {
1728 1 : std::ifstream extracted_bin("test_dir/file1.bin", std::ios::in | std::ios::binary);
1729 1 : CATCH_REQUIRE(static_cast<bool>(extracted_bin));
1730 1 : CATCH_REQUIRE(extracted_bin.is_open());
1731 : char buf_bin[1024];
1732 1 : extracted_bin.read(buf_bin, sizeof(buf_bin));
1733 1 : CATCH_REQUIRE(extracted_bin.gcount() == static_cast<ssize_t>(cache_bin.length()));
1734 1 : CATCH_REQUIRE(memcmp(buf_bin, cache_bin.c_str(), cache_bin.length()) == 0);
1735 1 : }
1736 :
1737 : {
1738 1 : std::ifstream extracted_text("test_dir/file3.text", std::ios::in | std::ios::binary);
1739 : char buf_text[2048]; // 512 + 511 + '\n' x (512 + 511) / 40 < 2048
1740 1 : extracted_text.read(buf_text, sizeof(buf_text));
1741 1 : CATCH_REQUIRE(extracted_text.gcount() == static_cast<ssize_t>(cache_text.length()));
1742 1 : CATCH_REQUIRE(memcmp(buf_text, cache_text.c_str(), cache_text.length()) == 0);
1743 1 : }
1744 1 : }
1745 1 : CATCH_END_SECTION()
1746 1 : }
1747 :
1748 :
1749 1 : CATCH_TEST_CASE("test_memory_input_stream", "[ZipFile][MemoryStream]")
1750 : {
1751 1 : CATCH_START_SECTION("test_memory_input_stream: create files with a compressed file, save only 50% of the data")
1752 : {
1753 1 : std::string const top_dir(SNAP_CATCH2_NAMESPACE::g_tmp_dir() + "/memory-test");
1754 1 : zipios_test::auto_unlink_t auto_unlink(top_dir, true);
1755 :
1756 1 : CATCH_REQUIRE(system(("mkdir -p " + top_dir).c_str()) == 0);
1757 1 : zipios_test::safe_chdir cwd(top_dir);
1758 :
1759 1 : std::stringstream ss;
1760 1 : ss << "content of the file\n";
1761 1 : CATCH_REQUIRE(ss.tellp() == 20);
1762 :
1763 : // first create files for .zip file
1764 : //
1765 1 : CATCH_REQUIRE(system("pwd") == 0);
1766 1 : CATCH_REQUIRE(system("mkdir -p test_dir/hide test_dir/text") == 0);
1767 :
1768 1 : std::string cache_bin;
1769 1 : std::vector<std::string> cache_text;
1770 : {
1771 1 : std::ofstream file_bin("test_dir/file1.bin", std::ios::out | std::ios::binary);
1772 1 : size_t const size(512 + rand() % 512);
1773 939 : for(size_t pos(0); pos < size; ++pos)
1774 : {
1775 938 : char c(static_cast<char>(rand()));
1776 938 : file_bin << c;
1777 938 : cache_bin += c;
1778 : }
1779 1 : CATCH_REQUIRE(static_cast<bool>(file_bin));
1780 :
1781 1 : std::ofstream file1_empty("test_dir/hide/file1.empty", std::ios::out | std::ios::binary);
1782 1 : CATCH_REQUIRE(static_cast<bool>(file1_empty));
1783 1 : std::ofstream file2_empty("test_dir/hide/file2.empty", std::ios::out | std::ios::binary);
1784 1 : CATCH_REQUIRE(static_cast<bool>(file2_empty));
1785 1 : std::ofstream file3_empty("test_dir/hide/file3.empty", std::ios::out | std::ios::binary);
1786 1 : CATCH_REQUIRE(static_cast<bool>(file3_empty));
1787 :
1788 1 : int const count(rand() % 5 + 3);
1789 8 : for(int i(1); i <= count; ++i)
1790 : {
1791 7 : cache_text.push_back(std::string());
1792 14 : std::ofstream file_text("test_dir/text/file" + std::to_string(i) + ".text", std::ios::out | std::ios::binary);
1793 7 : size_t const length(512 + rand() % 512);
1794 4921 : for(size_t pos(0); pos < length; ++pos)
1795 : {
1796 4914 : char c(rand() % 26 + 'a');
1797 4914 : file_text << c;
1798 4914 : cache_text.back() += c;
1799 4914 : if(pos % 40 == 39)
1800 : {
1801 119 : file_text << '\n';
1802 119 : cache_text.back() += '\n';
1803 : }
1804 : }
1805 7 : CATCH_REQUIRE(static_cast<bool>(file_text));
1806 7 : }
1807 1 : }
1808 :
1809 : // create the .zip file
1810 : //
1811 2 : zipios::DirectoryCollection directoryCollection("test_dir");
1812 1 : std::ofstream tempZipStream("test.zip", std::ios_base::binary | std::ios::out);
1813 1 : zipios::ZipFile::saveCollectionToArchive(tempZipStream, directoryCollection);
1814 1 : tempZipStream.close();
1815 :
1816 : // test with unzip
1817 : //
1818 1 : CATCH_REQUIRE(system("unzip -o test.zip >/dev/null") == 0);
1819 :
1820 : // verify the content is the same as what we kept in memory
1821 : {
1822 1 : std::ifstream extracted_bin("test_dir/file1.bin", std::ios::in | std::ios::binary);
1823 1 : CATCH_REQUIRE(static_cast<bool>(extracted_bin));
1824 1 : CATCH_REQUIRE(extracted_bin.is_open());
1825 : char buf_bin[1024];
1826 1 : extracted_bin.read(buf_bin, sizeof(buf_bin));
1827 1 : CATCH_REQUIRE(extracted_bin.gcount() == static_cast<ssize_t>(cache_bin.length()));
1828 1 : CATCH_REQUIRE(memcmp(buf_bin, cache_bin.c_str(), cache_bin.length()) == 0);
1829 1 : }
1830 :
1831 4 : for(int i(1); i <= 3; ++i)
1832 : {
1833 : struct stat s;
1834 3 : CATCH_REQUIRE(stat(("test_dir/hide/file" + std::to_string(i) + ".empty").c_str(), &s) == 0);
1835 3 : CATCH_REQUIRE(s.st_size == 0);
1836 : }
1837 :
1838 8 : for(std::size_t i(1); i <= cache_text.size(); ++i)
1839 : {
1840 14 : std::ifstream extracted_text("test_dir/text/file" + std::to_string(i) + ".text", std::ios::in | std::ios::binary);
1841 : char buf_text[2048]; // 512 + 511 + '\n' x (512 + 511) / 40 < 2048
1842 7 : extracted_text.read(buf_text, sizeof(buf_text));
1843 7 : CATCH_REQUIRE(extracted_text.gcount() == static_cast<ssize_t>(cache_text[i - 1].length()));
1844 7 : CATCH_REQUIRE(memcmp(buf_text, cache_text[i - 1].c_str(), cache_text[i - 1].length()) == 0);
1845 7 : }
1846 1 : }
1847 1 : CATCH_END_SECTION()
1848 1 : }
1849 :
1850 :
1851 :
1852 : // Local Variables:
1853 : // mode: cpp
1854 : // indent-tabs-mode: nil
1855 : // c-basic-offset: 4
1856 : // tab-width: 4
1857 : // End:
1858 :
1859 : // vim: ts=4 sw=4 et
|