#include "./archive.h" #include "../conversion/stringbuilder.h" #include "../io/misc.h" #include #include #include using namespace CppUtilities; namespace CppUtilities { /*! * \brief Destroys the ArchiveException. */ ArchiveException::~ArchiveException() { } /// \cond /// struct AddDirectoryToFileMap { bool operator()(std::string_view path) { fileMap[std::string(path)]; return false; } FileMap &fileMap; }; struct AddFileToFileMap { bool operator()(std::string_view directoryPath, ArchiveFile &&file) { fileMap[std::string(directoryPath)].emplace_back(std::move(file)); return false; } FileMap &fileMap; }; void walkThroughArchiveInternal(struct archive *ar, std::string_view archiveName, const FilePredicate &isFileRelevant, FileHandler &&fileHandler, DirectoryHandler &&directoryHandler) { // iterate through all archive entries struct archive_entry *const entry = archive_entry_new(); auto fileContent = std::string(); while (archive_read_next_header2(ar, entry) == ARCHIVE_OK) { // check entry type (only dirs, files and symlinks relevant here) const auto entryType(archive_entry_filetype(entry)); if (entryType != AE_IFDIR && entryType != AE_IFREG && entryType != AE_IFLNK) { continue; } // get file path const char *filePath = archive_entry_pathname_utf8(entry); if (!filePath) { filePath = archive_entry_pathname(entry); } if (!filePath) { continue; } // get permissions const mode_t perm = archive_entry_perm(entry); // add directories explicitly to get the entire tree though skipping irrelevant files if (entryType == AE_IFDIR) { // remove trailing slashes const char *dirEnd = filePath; for (const char *i = filePath; *i; ++i) { if (*i != '/') { dirEnd = i + 1; } } if (directoryHandler(std::string_view(filePath, static_cast(dirEnd - filePath)))) { goto free; } continue; } // split the path into dir and fileName const char *fileName = filePath, *dirEnd = filePath; for (const char *i = filePath; *i; ++i) { if (*i == '/') { fileName = i + 1; dirEnd = i; } } // prevent looking into irrelevant files if (isFileRelevant && !isFileRelevant(filePath, fileName, perm)) { continue; } // read timestamps const auto creationTime = DateTime::fromTimeStampGmt(archive_entry_ctime(entry)); const auto modificationTime = DateTime::fromTimeStampGmt(archive_entry_mtime(entry)); // read symlink if (entryType == AE_IFLNK) { if (fileHandler(std::string_view(filePath, static_cast(dirEnd - filePath)), ArchiveFile(fileName, std::string(archive_entry_symlink_utf8(entry)), ArchiveFileType::Link, creationTime, modificationTime))) { goto free; } continue; } // determine file size to pre-allocate buffer for file content const la_int64_t fileSize = archive_entry_size(entry); fileContent.clear(); if (fileSize > 0) { fileContent.reserve(static_cast(fileSize)); } // read file content const char *buff; auto size = std::size_t(); auto offset = la_int64_t(); for (;;) { const auto returnCode = archive_read_data_block(ar, reinterpret_cast(&buff), &size, &offset); if (returnCode == ARCHIVE_EOF || returnCode < ARCHIVE_OK) { break; } fileContent.append(buff, size); } // move it to results if (fileHandler(std::string_view(filePath, static_cast(dirEnd - filePath)), ArchiveFile(fileName, std::move(fileContent), ArchiveFileType::Regular, creationTime, modificationTime))) { goto free; } } // free resources used by libarchive free: archive_entry_free(entry); const auto returnCode = archive_read_free(ar); if (returnCode != ARCHIVE_OK) { throw ArchiveException(argsToString("Unable to free archive: ", archiveName)); } } /// \endcond /*! * \brief Invokes callbacks for files and directories in the specified archive. */ void walkThroughArchiveFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant, FileHandler &&fileHandler, DirectoryHandler &&directoryHandler) { // refuse opening empty buffer if (archiveData.empty()) { throw ArchiveException("Unable to open archive \"" % archiveName + "\": archive data is empty"); } // open archive buffer using libarchive struct archive *ar = archive_read_new(); archive_read_support_filter_all(ar); archive_read_support_format_all(ar); const auto returnCode = archive_read_open_memory(ar, archiveData.data(), archiveData.size()); if (returnCode != ARCHIVE_OK) { archive_read_free(ar); if (const char *const error = archive_error_string(ar)) { throw ArchiveException("Unable to open/read archive \"" % archiveName % "\": " + error); } else { throw ArchiveException("Unable to open/read archive \"" % archiveName + "\": unable to open archive from memory"); } } walkThroughArchiveInternal(ar, archiveName, isFileRelevant, std::move(fileHandler), std::move(directoryHandler)); } /*! * \brief Extracts the specified archive. */ FileMap extractFilesFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant) { auto results = FileMap(); walkThroughArchiveFromBuffer(archiveData, archiveName, isFileRelevant, AddFileToFileMap{ results }, AddDirectoryToFileMap{ results }); return results; } /*! * \brief Invokes callbacks for files and directories in the specified archive. */ void walkThroughArchive( std::string_view archivePath, const FilePredicate &isFileRelevant, FileHandler &&fileHandler, DirectoryHandler &&directoryHandler) { // open archive file using libarchive if (archivePath.empty()) { throw ArchiveException("Unable to open archive: no path specified"); } auto ec = std::error_code(); auto size = std::filesystem::file_size(archivePath, ec); if (ec) { throw ArchiveException("Unable to determine size of \"" % archivePath % "\": " + ec.message()); } if (!size) { throw ArchiveException("Unable to open archive \"" % archivePath + "\": file is empty"); } struct archive *ar = archive_read_new(); archive_read_support_filter_all(ar); archive_read_support_format_all(ar); const auto returnCode = archive_read_open_filename(ar, archivePath.data(), 10240); if (returnCode != ARCHIVE_OK) { archive_read_free(ar); if (const char *const error = archive_error_string(ar)) { throw ArchiveException("Unable to open/read archive \"" % archivePath % "\": " + error); } else { throw ArchiveException("Unable to open/read archive \"" % archivePath + "\": unable to open archive from file"); } } walkThroughArchiveInternal(ar, archivePath, isFileRelevant, std::move(fileHandler), std::move(directoryHandler)); } /*! * \brief Extracts the specified archive. */ FileMap extractFiles(std::string_view archivePath, const FilePredicate &isFileRelevant) { auto results = FileMap(); walkThroughArchive(archivePath, isFileRelevant, AddFileToFileMap{ results }, AddDirectoryToFileMap{ results }); return results; } } // namespace CppUtilities