LCOV - code coverage report
Current view: top level - tests - catch_archiver.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 99.8 % 479 478
Test Date: 2026-11-09 21:32:01 Functions: 100.0 % 5 5
Legend: Lines: hit not hit

            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
        

Generated by: LCOV version 2.0-1

Snap C++ | List of projects | List of versions