Line data Source code
1 : // Copyright (c) 2011-2026 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/edhttp
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : /** \file
20 : * \brief Verify the archiver classes.
21 : *
22 : * This file implements tests to verify that the archiver classes.
23 : */
24 :
25 : // edhttp
26 : //
27 : #include <edhttp/compression/archiver.h>
28 :
29 : #include <edhttp/exception.h>
30 :
31 :
32 : // self
33 : //
34 : #include "catch_main.h"
35 :
36 :
37 : // snapdev
38 : //
39 : #include <snapdev/string_replace_many.h>
40 :
41 :
42 : // snaplogger
43 : //
44 : #include <snaplogger/message.h>
45 :
46 :
47 : // C
48 : //
49 : #include <tar.h>
50 :
51 :
52 : // last include
53 : //
54 : #include <snapdev/poison.h>
55 :
56 :
57 :
58 : namespace
59 : {
60 :
61 :
62 :
63 11 : std::uint32_t check_sum(unsigned char const * s)
64 : {
65 11 : std::uint32_t result = 8 * ' '; // the checksum field
66 :
67 : // name + mode + uid + gid + size + mtime = 148 bytes
68 : //
69 1639 : for(int n(148); n > 0; --n, ++s)
70 : {
71 1628 : result += *s;
72 : }
73 :
74 11 : s += 8; // skip the checksum field
75 :
76 : // everything after the checksum is another 356 bytes
77 : //
78 3927 : for(int n(356); n > 0; --n, ++s)
79 : {
80 3916 : result += *s;
81 : }
82 :
83 11 : return result;
84 : }
85 :
86 :
87 :
88 : } // no name namespace
89 :
90 :
91 :
92 2 : CATCH_TEST_CASE("archiver_file", "[archiver]")
93 : {
94 2 : CATCH_START_SECTION("archiver_file: verify archiver file defaults")
95 : {
96 1 : edhttp::archiver_file file;
97 :
98 1 : CATCH_REQUIRE(file.get_type() == edhttp::file_type_t::FILE_TYPE_REGULAR);
99 1 : CATCH_REQUIRE(file.get_data() == edhttp::buffer_t());
100 1 : CATCH_REQUIRE(file.get_filename() == std::string());
101 1 : CATCH_REQUIRE(file.get_user() == std::string());
102 1 : CATCH_REQUIRE(file.get_group() == std::string());
103 1 : CATCH_REQUIRE(file.get_uid() == 0);
104 1 : CATCH_REQUIRE(file.get_gid() == 0);
105 1 : CATCH_REQUIRE(file.get_mode() == 0);
106 1 : CATCH_REQUIRE(file.get_mtime() == snapdev::timespec_ex());
107 1 : }
108 3 : CATCH_END_SECTION()
109 :
110 2 : CATCH_START_SECTION("archiver_file: verify set/get file metadata")
111 : {
112 1 : edhttp::archiver_file file;
113 :
114 1 : CATCH_REQUIRE(file.get_type() == edhttp::file_type_t::FILE_TYPE_REGULAR);
115 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_DIRECTORY);
116 1 : CATCH_REQUIRE(file.get_type() == edhttp::file_type_t::FILE_TYPE_DIRECTORY);
117 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
118 1 : CATCH_REQUIRE(file.get_type() == edhttp::file_type_t::FILE_TYPE_REGULAR);
119 :
120 1 : CATCH_REQUIRE(file.get_data() == edhttp::buffer_t());
121 1 : auto const data(SNAP_CATCH2_NAMESPACE::random_buffer(1, 1024 * 16));
122 1 : file.set_data(data);
123 1 : CATCH_REQUIRE(file.get_data() == data);
124 :
125 1 : CATCH_REQUIRE(file.get_filename() == std::string());
126 3 : file.set_filename("/this/file/here");
127 1 : CATCH_REQUIRE(file.get_filename() == "/this/file/here");
128 :
129 1 : CATCH_REQUIRE(file.get_user() == std::string());
130 1 : CATCH_REQUIRE(file.get_uid() == 0);
131 3 : file.set_user("edhttp", 1'000);
132 1 : CATCH_REQUIRE(file.get_user() == "edhttp");
133 1 : CATCH_REQUIRE(file.get_uid() == 1'000);
134 :
135 1 : CATCH_REQUIRE(file.get_group() == std::string());
136 1 : CATCH_REQUIRE(file.get_gid() == 0);
137 3 : file.set_group("edhttp", 1'230);
138 1 : CATCH_REQUIRE(file.get_group() == "edhttp");
139 1 : CATCH_REQUIRE(file.get_gid() == 1'230);
140 :
141 1 : CATCH_REQUIRE(file.get_mode() == 0);
142 1 : file.set_mode(0750);
143 1 : CATCH_REQUIRE(file.get_mode() == 0750);
144 :
145 1 : snapdev::timespec_ex const now(snapdev::now());
146 1 : CATCH_REQUIRE(file.get_mtime() == snapdev::timespec_ex());
147 1 : file.set_mtime(now);
148 1 : CATCH_REQUIRE(file.get_mtime() == now);
149 1 : }
150 3 : CATCH_END_SECTION()
151 2 : }
152 :
153 :
154 3 : CATCH_TEST_CASE("archiver_tar", "[archiver]")
155 : {
156 3 : CATCH_START_SECTION("archiver_tar: verify tar archiver")
157 : {
158 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
159 1 : CATCH_REQUIRE(tar != nullptr);
160 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
161 :
162 1 : edhttp::archiver_archive archive;
163 :
164 : // add files to the tarball using randomly generated buffers
165 : // and metadata
166 : //
167 1 : constexpr std::size_t const FILE_COUNT = 15;
168 3 : std::vector<edhttp::buffer_t> file_data(FILE_COUNT);
169 3 : std::vector<snapdev::timespec_ex> file_mtime(FILE_COUNT);
170 16 : for(std::size_t i(0); i < FILE_COUNT; ++i)
171 : {
172 15 : file_data[i] = SNAP_CATCH2_NAMESPACE::random_buffer(1, 1024 * 64);
173 15 : file_mtime[i] = snapdev::now();
174 :
175 15 : edhttp::archiver_file file;
176 15 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
177 15 : file.set_data(file_data[i]);
178 15 : file.set_filename("this/file-" + std::to_string(i + 1));
179 15 : file.set_user("user-" + std::to_string(i + 1), 1000 + i);
180 15 : file.set_group("group-" + std::to_string(i + 1), 2000 + i);
181 15 : file.set_mode(0640);
182 15 : file.set_mtime(file_mtime[i]);
183 :
184 15 : tar->append_file(archive, file);
185 15 : }
186 :
187 16 : for(std::size_t i(0); i < FILE_COUNT; ++i)
188 : {
189 15 : edhttp::archiver_file file;
190 15 : CATCH_REQUIRE(tar->next_file(archive, file));
191 :
192 15 : CATCH_REQUIRE(file.get_type() == edhttp::file_type_t::FILE_TYPE_REGULAR);
193 15 : CATCH_REQUIRE(file.get_data() == file_data[i]);
194 15 : CATCH_REQUIRE(file.get_filename() == "this/file-" + std::to_string(i + 1));
195 15 : CATCH_REQUIRE(file.get_user() == "user-" + std::to_string(i + 1));
196 15 : CATCH_REQUIRE(file.get_uid() == 1000 + i);
197 15 : CATCH_REQUIRE(file.get_group() == "group-" + std::to_string(i + 1));
198 15 : CATCH_REQUIRE(file.get_gid() == 2000 + i);
199 15 : CATCH_REQUIRE(file.get_mode() == 0640);
200 15 : CATCH_REQUIRE(file.get_mtime().tv_sec == file_mtime[i].tv_sec);
201 15 : CATCH_REQUIRE(file.get_mtime().tv_nsec == 0);
202 15 : }
203 :
204 : {
205 1 : edhttp::archiver_file file;
206 1 : CATCH_REQUIRE_FALSE(tar->next_file(archive, file));
207 1 : CATCH_REQUIRE_FALSE(tar->next_file(archive, file));
208 1 : CATCH_REQUIRE_FALSE(tar->next_file(archive, file));
209 1 : CATCH_REQUIRE_FALSE(tar->next_file(archive, file));
210 1 : }
211 1 : }
212 4 : CATCH_END_SECTION()
213 :
214 3 : CATCH_START_SECTION("archiver_tar: verify tar archiver with long filenames")
215 : {
216 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
217 1 : CATCH_REQUIRE(tar != nullptr);
218 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
219 :
220 1 : edhttp::archiver_archive archive;
221 :
222 : // add files to the tarball using randomly generated buffers
223 : // and metadata
224 : //
225 1 : constexpr std::size_t const FILE_COUNT = 15;
226 1 : std::size_t file_count(0);
227 1 : std::vector<edhttp::buffer_t> file_data;
228 1 : std::vector<snapdev::timespec_ex> file_mtime;
229 1 : std::vector<std::string> file_filename;
230 16 : for(std::size_t i(0); i < FILE_COUNT; ++i)
231 : {
232 15 : std::string filename;
233 15 : std::size_t max_segments(rand() % 10 + 5);
234 45 : std::vector<std::string> segments(max_segments);
235 : for(;;)
236 : {
237 15 : segments.clear();
238 180 : for(std::size_t j(0); j < max_segments; ++j)
239 : {
240 660 : segments.push_back(snapdev::string_replace_many(SNAP_CATCH2_NAMESPACE::random_string(
241 : 1
242 : , 24
243 : , SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_ASCII)
244 : , {{"/", "-"}}));
245 : }
246 15 : segments[max_segments - 1] += '_';
247 15 : segments[max_segments - 1] += std::to_string(file_count + 1);
248 :
249 15 : std::size_t filename_len(0);
250 15 : std::size_t prefix_len(0);
251 180 : for(std::size_t j(max_segments); j > 0; )
252 : {
253 165 : --j;
254 165 : if(prefix_len == 0
255 165 : && filename_len + segments[j].length() <= 100)
256 : {
257 109 : if(filename_len != 0)
258 : {
259 94 : ++filename_len; // for the '/'
260 : }
261 109 : filename_len += segments[j].length();
262 : }
263 : else
264 : {
265 56 : if(prefix_len != 0)
266 : {
267 45 : ++prefix_len; // for the '/'
268 : }
269 56 : prefix_len += segments[j].length();
270 : }
271 : }
272 :
273 15 : if(prefix_len <= 155)
274 : {
275 45 : filename = snapdev::join_strings(segments, "/");
276 15 : break;
277 : }
278 :
279 : // prefix too long, try again
280 0 : }
281 :
282 : // do we have folders?
283 : //
284 15 : if(max_segments > 1)
285 : {
286 165 : for(std::size_t j(1); j < max_segments; ++j)
287 : {
288 750 : std::string const dir_name(snapdev::join_strings(std::vector<std::string>(segments.begin(), segments.begin() + j), "/"));
289 :
290 : // make the data buffer very small because we don't
291 : // need it for directories (it gets ignored)
292 : //
293 150 : file_data.push_back(SNAP_CATCH2_NAMESPACE::random_buffer(0, 1024));
294 150 : file_mtime.push_back(snapdev::now());
295 150 : file_filename.push_back(dir_name);
296 :
297 150 : edhttp::archiver_file dir;
298 150 : dir.set_type(edhttp::file_type_t::FILE_TYPE_DIRECTORY);
299 150 : dir.set_data(file_data[file_count]); // this must be ignored for directories
300 150 : dir.set_filename(dir_name);
301 150 : dir.set_user("user-" + std::to_string(file_count + 1), 1000 + file_count);
302 150 : dir.set_group("group-" + std::to_string(file_count + 1), 2000 + file_count);
303 150 : dir.set_mode(0750);
304 150 : dir.set_mtime(file_mtime[file_count]);
305 :
306 150 : tar->append_file(archive, dir);
307 150 : ++file_count;
308 150 : }
309 : }
310 :
311 : {
312 15 : file_data.push_back(SNAP_CATCH2_NAMESPACE::random_buffer(1, 1024 * 64));
313 15 : file_mtime.push_back(snapdev::now());
314 15 : file_filename.push_back(filename);
315 :
316 15 : edhttp::archiver_file file;
317 15 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
318 15 : file.set_data(file_data[file_count]);
319 15 : file.set_filename(filename);
320 15 : file.set_user("user-" + std::to_string(file_count + 1), 1000 + file_count);
321 15 : file.set_group("group-" + std::to_string(file_count + 1), 2000 + file_count);
322 15 : file.set_mode(0640);
323 15 : file.set_mtime(file_mtime[file_count]);
324 :
325 15 : std::size_t const header_pos(archive.get().size());
326 :
327 15 : tar->append_file(archive, file);
328 15 : ++file_count;
329 :
330 : // once in a while, add a '/' at the end of the prefix
331 : //
332 15 : if(filename.length() > 100 && (rand() & 1) != 0)
333 : {
334 6 : unsigned char * header(archive.get().data() + header_pos);
335 6 : std::size_t const len(strlen(reinterpret_cast<char const *>(header) + 345));
336 6 : if(len < 155)
337 : {
338 6 : header[345 + len] = '/';
339 :
340 : // if this happens with a regular file, also test
341 : // with CONTTYPE instead
342 : //
343 6 : if(header[156] == REGTYPE)
344 : {
345 : header[156] == CONTTYPE;
346 : }
347 :
348 : // adjust the checksum
349 : //
350 6 : std::uint32_t const checksum(check_sum(header));
351 6 : std::stringstream ss;
352 6 : ss << std::oct
353 : << std::setw(6)
354 6 : << std::setfill('0')
355 6 : << checksum;
356 6 : strncpy(reinterpret_cast<char *>(header) + 148, ss.str().c_str(), 6);
357 6 : }
358 : }
359 15 : }
360 15 : }
361 :
362 : #if 0
363 : edhttp::buffer_t data(archive.get());
364 : std::ofstream out("data.tar");
365 : out.write(reinterpret_cast<char const *>(data.data()), data.size());
366 : #endif
367 :
368 3 : for(int a(0); a < 2; ++a)
369 : {
370 332 : for(std::size_t i(0); i < file_count; ++i)
371 : {
372 330 : edhttp::archiver_file file;
373 330 : CATCH_REQUIRE(tar->next_file(archive, file));
374 :
375 330 : if(file.get_type() == edhttp::file_type_t::FILE_TYPE_DIRECTORY)
376 : {
377 300 : CATCH_REQUIRE(file.get_data().size() == 0);
378 300 : CATCH_REQUIRE(file.get_data() == edhttp::buffer_t());
379 300 : CATCH_REQUIRE(file.get_filename() == file_filename[i]);
380 300 : CATCH_REQUIRE(file.get_user() == "user-" + std::to_string(i + 1));
381 300 : CATCH_REQUIRE(file.get_uid() == 1000 + i);
382 300 : CATCH_REQUIRE(file.get_group() == "group-" + std::to_string(i + 1));
383 300 : CATCH_REQUIRE(file.get_gid() == 2000 + i);
384 300 : CATCH_REQUIRE(file.get_mode() == 0750);
385 300 : CATCH_REQUIRE(file.get_mtime().tv_sec == file_mtime[i].tv_sec);
386 300 : CATCH_REQUIRE(file.get_mtime().tv_nsec == 0);
387 : }
388 : else
389 : {
390 30 : CATCH_REQUIRE(file.get_type() == edhttp::file_type_t::FILE_TYPE_REGULAR);
391 30 : CATCH_REQUIRE(file.get_data().size() == file_data[i].size());
392 30 : CATCH_REQUIRE(file.get_data() == file_data[i]);
393 30 : CATCH_REQUIRE(file.get_filename() == file_filename[i]);
394 30 : CATCH_REQUIRE(file.get_user() == "user-" + std::to_string(i + 1));
395 30 : CATCH_REQUIRE(file.get_uid() == 1000 + i);
396 30 : CATCH_REQUIRE(file.get_group() == "group-" + std::to_string(i + 1));
397 30 : CATCH_REQUIRE(file.get_gid() == 2000 + i);
398 30 : CATCH_REQUIRE(file.get_mode() == 0640);
399 30 : CATCH_REQUIRE(file.get_mtime().tv_sec == file_mtime[i].tv_sec);
400 30 : CATCH_REQUIRE(file.get_mtime().tv_nsec == 0);
401 : }
402 330 : }
403 :
404 : // once we reached the end, we get `false`
405 : {
406 2 : edhttp::archiver_file file;
407 2 : CATCH_REQUIRE_FALSE(tar->next_file(archive, file));
408 2 : }
409 :
410 : // append an empty header, which is legal at the end of tar files
411 6 : edhttp::buffer_t zeroes(512);
412 2 : archive.get().insert(archive.get().end(), zeroes.begin(), zeroes.end());
413 :
414 : // we can repeat the verification by rewinding
415 : //
416 2 : tar->rewind(archive);
417 2 : }
418 1 : }
419 4 : CATCH_END_SECTION()
420 :
421 3 : CATCH_START_SECTION("archiver_tar: verify large checksum")
422 : {
423 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
424 1 : CATCH_REQUIRE(tar != nullptr);
425 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
426 :
427 1 : edhttp::archiver_archive archive;
428 :
429 : // create a filename that will make the checksum go over 32,767
430 : //
431 : // TODO: see whether a defined UTF-8 character can be used instead
432 : // of 0x10FFFD which is considered valid but is not (yet)
433 : // defined
434 : //
435 1 : std::string filename;
436 1 : std::string const last_utf8{ // 0x10FFFD
437 : static_cast<char>(0xF4),
438 : static_cast<char>(0x8F),
439 : static_cast<char>(0xBF),
440 : static_cast<char>(0xBD),
441 3 : };
442 39 : for(int i(0); i < 155 / 4; ++i)
443 : {
444 38 : filename += last_utf8;
445 : }
446 : //filename += std::string(155, '\xFF'); // prefix
447 1 : filename += '/';
448 : //filename += std::string(100, '\xFF'); // filename
449 26 : for(int i(0); i < 100 / 4; ++i)
450 : {
451 25 : filename += last_utf8;
452 : }
453 :
454 1 : edhttp::buffer_t const data(SNAP_CATCH2_NAMESPACE::random_buffer(1, 25));
455 1 : snapdev::timespec_ex const now(snapdev::now());
456 :
457 : {
458 1 : edhttp::archiver_file file;
459 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
460 1 : file.set_data(data);
461 1 : file.set_filename(filename);
462 3 : file.set_user("edhttp", 1000);
463 3 : file.set_group("edhttp", 1000);
464 1 : file.set_mode(0444);
465 1 : file.set_mtime(now);
466 1 : tar->append_file(archive, file);
467 1 : }
468 :
469 : {
470 1 : edhttp::archiver_file file;
471 1 : CATCH_REQUIRE(tar->next_file(archive, file));
472 :
473 1 : CATCH_REQUIRE(file.get_type() == edhttp::file_type_t::FILE_TYPE_REGULAR);
474 1 : CATCH_REQUIRE(file.get_data().size() == data.size());
475 1 : CATCH_REQUIRE(file.get_data() == data);
476 1 : CATCH_REQUIRE(file.get_filename() == filename);
477 1 : CATCH_REQUIRE(file.get_user() == "edhttp");
478 1 : CATCH_REQUIRE(file.get_uid() == 1000);
479 1 : CATCH_REQUIRE(file.get_group() == "edhttp");
480 1 : CATCH_REQUIRE(file.get_gid() == 1000);
481 1 : CATCH_REQUIRE(file.get_mode() == 0444);
482 1 : CATCH_REQUIRE(file.get_mtime().tv_sec == now.tv_sec);
483 1 : CATCH_REQUIRE(file.get_mtime().tv_nsec == 0);
484 1 : }
485 :
486 : {
487 1 : edhttp::archiver_file file;
488 1 : CATCH_REQUIRE_FALSE(tar->next_file(archive, file));
489 1 : }
490 1 : }
491 4 : CATCH_END_SECTION()
492 168 : }
493 :
494 :
495 2 : CATCH_TEST_CASE("archiver", "[archiver]")
496 : {
497 2 : CATCH_START_SECTION("archiver: verify list of archivers in our library")
498 : {
499 1 : advgetopt::string_list_t const list(edhttp::archiver_list());
500 :
501 1 : CATCH_REQUIRE(list.size() == 1);
502 :
503 : // internally it's in a map so it remains sorted
504 : //
505 1 : CATCH_REQUIRE(list[0] == "tar");
506 :
507 1 : auto const input(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024 * 16));
508 2 : for(auto const & name : list)
509 : {
510 1 : edhttp::archiver * a(edhttp::get_archiver(name));
511 1 : CATCH_REQUIRE(a != nullptr);
512 : }
513 1 : }
514 3 : CATCH_END_SECTION()
515 :
516 2 : CATCH_START_SECTION("archiver: verify the \"unknown\" archiver does not exist")
517 : {
518 3 : edhttp::archiver * unknown(edhttp::get_archiver("unknown"));
519 1 : CATCH_REQUIRE(unknown == nullptr);
520 : }
521 3 : CATCH_END_SECTION()
522 2 : }
523 :
524 :
525 8 : CATCH_TEST_CASE("archiver_error", "[archiver][error]")
526 : {
527 8 : CATCH_START_SECTION("archiver_error: tar filename missing")
528 : {
529 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
530 1 : CATCH_REQUIRE(tar != nullptr);
531 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
532 :
533 1 : edhttp::buffer_t file_data(SNAP_CATCH2_NAMESPACE::random_buffer(1, 1024));
534 :
535 1 : edhttp::archiver_file file;
536 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
537 1 : file.set_data(file_data);
538 : // file.set_filename(...); -- this parameter is mandatory
539 3 : file.set_user("edhttp", 1000);
540 3 : file.set_group("www-data", 128);
541 1 : file.set_mode(0644);
542 1 : file.set_mtime(snapdev::now());
543 :
544 1 : edhttp::archiver_archive archive;
545 :
546 3 : CATCH_REQUIRE_THROWS_MATCHES(
547 : tar->append_file(archive, file)
548 : , edhttp::missing_name
549 : , Catch::Matchers::ExceptionMessage(
550 : "edhttp_exception: a filename is required for an archive file."));
551 1 : }
552 9 : CATCH_END_SECTION()
553 :
554 8 : CATCH_START_SECTION("archiver_error: tar filename too long")
555 : {
556 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
557 1 : CATCH_REQUIRE(tar != nullptr);
558 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
559 :
560 : // generate a random filename (string)
561 : //
562 1 : std::string filename(SNAP_CATCH2_NAMESPACE::random_string(
563 : 101
564 : , 1024
565 1 : , SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_ASCII));
566 5 : filename = snapdev::string_replace_many(
567 : filename
568 1 : , {{"/", "-"}});
569 :
570 1 : edhttp::buffer_t file_data(SNAP_CATCH2_NAMESPACE::random_buffer(1, 1024));
571 :
572 1 : edhttp::archiver_file file;
573 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
574 1 : file.set_data(file_data);
575 1 : file.set_filename(filename);
576 3 : file.set_user("edhttp", 1000);
577 3 : file.set_group("www-data", 128);
578 1 : file.set_mode(0644);
579 1 : file.set_mtime(snapdev::now());
580 :
581 1 : edhttp::archiver_archive archive;
582 :
583 3 : CATCH_REQUIRE_THROWS_MATCHES(
584 : tar->append_file(archive, file)
585 : , edhttp::name_too_large
586 : , Catch::Matchers::ExceptionMessage(
587 : "edhttp_exception: this file cannot be added to a tar archive at this point (filename too long)."));
588 1 : }
589 9 : CATCH_END_SECTION()
590 :
591 8 : CATCH_START_SECTION("archiver_error: tar prefix too long")
592 : {
593 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
594 1 : CATCH_REQUIRE(tar != nullptr);
595 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
596 :
597 : // generate a random filename of the perfect size +1
598 : //
599 1 : std::string filename(SNAP_CATCH2_NAMESPACE::random_string(
600 : 100 + 1 + 155 + 1
601 : , 100 + 1 + 155 + 1
602 1 : , SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_ASCII));
603 :
604 : // remove any slashes
605 : //
606 5 : filename = snapdev::string_replace_many(
607 : filename
608 1 : , {{"/", "-"}});
609 :
610 : // place the slash at the exact right position so the filename
611 : // is exactly 100 characters
612 : //
613 1 : filename[156] = '/';
614 :
615 1 : edhttp::buffer_t file_data(SNAP_CATCH2_NAMESPACE::random_buffer(1, 1024));
616 :
617 1 : edhttp::archiver_file file;
618 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
619 1 : file.set_data(file_data);
620 1 : file.set_filename(filename);
621 3 : file.set_user("edhttp", 1000);
622 3 : file.set_group("www-data", 128);
623 1 : file.set_mode(0644);
624 1 : file.set_mtime(snapdev::now());
625 :
626 1 : edhttp::archiver_archive archive;
627 :
628 3 : CATCH_REQUIRE_THROWS_MATCHES(
629 : tar->append_file(archive, file)
630 : , edhttp::name_too_large
631 : , Catch::Matchers::ExceptionMessage(
632 : "edhttp_exception: this prefix + file names cannot be added to a tar archive at this point (filename too long)."));
633 1 : }
634 9 : CATCH_END_SECTION()
635 :
636 8 : CATCH_START_SECTION("archiver_error: tar user name too long")
637 : {
638 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
639 1 : CATCH_REQUIRE(tar != nullptr);
640 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
641 :
642 : // generate a random user_name (string)
643 : //
644 1 : std::string const user_name(SNAP_CATCH2_NAMESPACE::random_string(
645 : 33
646 : , 100
647 1 : , SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_ASCII));
648 :
649 1 : edhttp::buffer_t file_data(SNAP_CATCH2_NAMESPACE::random_buffer(1, 1024));
650 :
651 1 : edhttp::archiver_file file;
652 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
653 1 : file.set_data(file_data);
654 3 : file.set_filename("long-user-name.pdf");
655 1 : file.set_user(user_name, 1000);
656 3 : file.set_group("www-data", 128);
657 1 : file.set_mode(0644);
658 1 : file.set_mtime(snapdev::now());
659 :
660 1 : edhttp::archiver_archive archive;
661 :
662 3 : CATCH_REQUIRE_THROWS_MATCHES(
663 : tar->append_file(archive, file)
664 : , edhttp::name_too_large
665 : , Catch::Matchers::ExceptionMessage(
666 : "edhttp_exception: this file cannot be added to a tar archive at this point (user name too long)."));
667 1 : }
668 9 : CATCH_END_SECTION()
669 :
670 8 : CATCH_START_SECTION("archiver_error: tar group name too long")
671 : {
672 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
673 1 : CATCH_REQUIRE(tar != nullptr);
674 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
675 :
676 : // generate a random group_name (string)
677 : //
678 1 : std::string const group_name(SNAP_CATCH2_NAMESPACE::random_string(
679 : 33
680 : , 100
681 1 : , SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_ASCII));
682 :
683 1 : edhttp::buffer_t file_data(SNAP_CATCH2_NAMESPACE::random_buffer(1, 1024));
684 :
685 1 : edhttp::archiver_file file;
686 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
687 1 : file.set_data(file_data);
688 3 : file.set_filename("long-user-name.pdf");
689 3 : file.set_user("edhttp", 1000);
690 1 : file.set_group(group_name, 128);
691 1 : file.set_mode(0644);
692 1 : file.set_mtime(snapdev::now());
693 :
694 1 : edhttp::archiver_archive archive;
695 :
696 3 : CATCH_REQUIRE_THROWS_MATCHES(
697 : tar->append_file(archive, file)
698 : , edhttp::name_too_large
699 : , Catch::Matchers::ExceptionMessage(
700 : "edhttp_exception: this file cannot be added to a tar archive at this point (group name too long)."));
701 1 : }
702 9 : CATCH_END_SECTION()
703 :
704 8 : CATCH_START_SECTION("archiver_error: tar file data missing")
705 : {
706 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
707 1 : CATCH_REQUIRE(tar != nullptr);
708 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
709 :
710 1 : edhttp::archiver_archive archive;
711 :
712 : // generate a random file
713 : //
714 1 : edhttp::buffer_t file_data(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024));
715 :
716 1 : snapdev::timespec_ex const file_mtime(snapdev::now());
717 :
718 : {
719 1 : edhttp::archiver_file file;
720 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
721 1 : file.set_data(file_data);
722 3 : file.set_filename("document.pdf");
723 3 : file.set_user("edhttp", 1000);
724 3 : file.set_group("edhttp", 1001);
725 1 : file.set_mode(0664);
726 1 : file.set_mtime(file_mtime);
727 :
728 1 : tar->append_file(archive, file);
729 1 : }
730 :
731 : // re-reading this one works as expected at first
732 : {
733 1 : edhttp::archiver_file file;
734 1 : CATCH_REQUIRE(tar->next_file(archive, file));
735 :
736 1 : CATCH_REQUIRE(file.get_type() == edhttp::file_type_t::FILE_TYPE_REGULAR);
737 1 : CATCH_REQUIRE(file.get_data() == file_data);
738 1 : CATCH_REQUIRE(file.get_filename() == "document.pdf");
739 1 : CATCH_REQUIRE(file.get_user() == "edhttp");
740 1 : CATCH_REQUIRE(file.get_uid() == 1000);
741 1 : CATCH_REQUIRE(file.get_group() == "edhttp");
742 1 : CATCH_REQUIRE(file.get_gid() == 1001);
743 1 : CATCH_REQUIRE(file.get_mode() == 0664);
744 1 : CATCH_REQUIRE(file.get_mtime().tv_sec == file_mtime.tv_sec);
745 1 : CATCH_REQUIRE(file.get_mtime().tv_nsec == 0);
746 1 : }
747 :
748 : // now try again, but first let's destroy half the file
749 : {
750 1 : tar->rewind(archive);
751 :
752 1 : archive.get().resize(1024); // block is 512, file was 1024, now block is 512 and file is 512
753 :
754 1 : edhttp::archiver_file file;
755 3 : CATCH_REQUIRE_THROWS_MATCHES(
756 : tar->next_file(archive, file)
757 : , edhttp::out_of_range
758 : , Catch::Matchers::ExceptionMessage(
759 : "out_of_range: file data not available (archive too small)."));
760 1 : }
761 1 : }
762 9 : CATCH_END_SECTION()
763 :
764 8 : CATCH_START_SECTION("archiver_error: tar file invalid checksum & file type")
765 : {
766 1 : char const unsupported_types[] =
767 : {
768 : //REGTYPE,
769 : //AREGTYPE,
770 : LNKTYPE,
771 : SYMTYPE,
772 : CHRTYPE,
773 : BLKTYPE,
774 : //DIRTYPE,
775 : FIFOTYPE,
776 : //CONTTYPE,
777 : };
778 :
779 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
780 1 : CATCH_REQUIRE(tar != nullptr);
781 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
782 :
783 1 : edhttp::archiver_archive archive;
784 :
785 : // generate a random file
786 : //
787 1 : edhttp::buffer_t file_data(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024));
788 :
789 1 : snapdev::timespec_ex const file_mtime(snapdev::now());
790 :
791 : {
792 1 : edhttp::archiver_file file;
793 1 : file.set_type(edhttp::file_type_t::FILE_TYPE_REGULAR);
794 1 : file.set_data(file_data);
795 3 : file.set_filename("document.pdf");
796 3 : file.set_user("edhttp", 1000);
797 3 : file.set_group("edhttp", 1001);
798 1 : file.set_mode(0664);
799 1 : file.set_mtime(file_mtime);
800 :
801 1 : tar->append_file(archive, file);
802 1 : }
803 :
804 : // re-reading this one works as expected at first
805 : {
806 1 : edhttp::archiver_file file;
807 1 : CATCH_REQUIRE(tar->next_file(archive, file));
808 :
809 1 : CATCH_REQUIRE(file.get_type() == edhttp::file_type_t::FILE_TYPE_REGULAR);
810 1 : CATCH_REQUIRE(file.get_data() == file_data);
811 1 : CATCH_REQUIRE(file.get_filename() == "document.pdf");
812 1 : CATCH_REQUIRE(file.get_user() == "edhttp");
813 1 : CATCH_REQUIRE(file.get_uid() == 1000);
814 1 : CATCH_REQUIRE(file.get_group() == "edhttp");
815 1 : CATCH_REQUIRE(file.get_gid() == 1001);
816 1 : CATCH_REQUIRE(file.get_mode() == 0664);
817 1 : CATCH_REQUIRE(file.get_mtime().tv_sec == file_mtime.tv_sec);
818 1 : CATCH_REQUIRE(file.get_mtime().tv_nsec == 0);
819 1 : }
820 :
821 : // now try again, but first let's destroy half the file
822 6 : for(auto const c : unsupported_types)
823 : {
824 5 : tar->rewind(archive);
825 :
826 5 : archive.get()[156] = c;
827 :
828 5 : std::uint32_t const new_checksum(check_sum(archive.get().data()));
829 :
830 5 : char buf[8];
831 5 : memcpy(buf, reinterpret_cast<char *>(archive.get().data()) + 148, 8);
832 5 : long const file_checksum(strtol(buf, nullptr, 8));
833 :
834 : // try "too soon" and the checksum breaks
835 : //
836 5 : edhttp::archiver_file file;
837 5 : CATCH_REQUIRE_THROWS_MATCHES(
838 : tar->next_file(archive, file)
839 : , edhttp::invalid_checksum
840 : , Catch::Matchers::ExceptionMessage(
841 : "edhttp_exception: ustar checksum code ("
842 : + std::to_string(new_checksum)
843 : + ") does not match what was expected ("
844 : + std::to_string(file_checksum)
845 : + ")."));
846 :
847 : // fix the checksum
848 : //
849 5 : std::stringstream ss;
850 5 : ss << std::oct
851 : << std::setw(6)
852 5 : << std::setfill('0')
853 5 : << new_checksum;
854 5 : strncpy(reinterpret_cast<char *>(archive.get().data()) + 148, ss.str().c_str(), 6);
855 :
856 : // next attempt bypasses the checksum check, but now fails on
857 : // the file type which we do not support
858 : //
859 15 : CATCH_REQUIRE_THROWS_MATCHES(
860 : tar->next_file(archive, file)
861 : , edhttp::incompatible
862 : , Catch::Matchers::ExceptionMessage(
863 : "edhttp_exception: file type in tarball not supported (we accept regular and directory files only)."));
864 5 : }
865 1 : }
866 9 : CATCH_END_SECTION()
867 :
868 8 : CATCH_START_SECTION("archiver_error: tar file invalid checksum & file type")
869 : {
870 3 : edhttp::archiver * tar(edhttp::get_archiver("tar"));
871 1 : CATCH_REQUIRE(tar != nullptr);
872 1 : CATCH_REQUIRE(strcmp(tar->get_name(), "tar") == 0);
873 :
874 1 : edhttp::archiver_archive archive;
875 :
876 : // generate a random header block
877 : //
878 1 : edhttp::buffer_t header;
879 : for(;;)
880 : {
881 1 : header = SNAP_CATCH2_NAMESPACE::random_buffer(512, 512);
882 1 : if(header[257] != 'u' || header[258] != 's' || header[259] != 't' || header[260] != 'a'
883 1 : || header[261] != 'r' || (header[262] != ' ' && header[262] != '\0'))
884 : {
885 1 : break;
886 : }
887 : // try again in the extremely remote chance the randomizer
888 : // generated the "ustar( |\0)" file magic
889 : }
890 :
891 1 : archive.set(header);
892 1 : CATCH_REQUIRE(const_cast<edhttp::archiver_archive const &>(archive).get() == header);
893 :
894 1 : edhttp::archiver_file file;
895 3 : CATCH_REQUIRE_THROWS_MATCHES(
896 : tar->next_file(archive, file)
897 : , edhttp::incompatible
898 : , Catch::Matchers::ExceptionMessage(
899 : "edhttp_exception: ustar magic code missing at position 0."));
900 1 : }
901 9 : CATCH_END_SECTION()
902 10 : }
903 :
904 :
905 : // vim: ts=4 sw=4 et
|