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