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