/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include "ArchiveReader.hpp" #include "ArchiveConfiguration.hpp" #include "LibArchiveHelper.hpp" #include "Base/Utils.hpp" #include #include #include #include namespace OpenVulkano { namespace { constexpr int BUFFER_SIZE = 16384; const char* ArchivePasswordCallbackHandler(struct archive *, void *_client_data) { ArchiveReader* reader = static_cast(_client_data); return reader->GetPasswordCallback()(); } } ArchiveReader::ArchiveReader(const std::shared_ptr& logger, std::optional archiveType) : ArchiveBase(archive_read_new(), nullptr, logger), m_archiveType(archiveType) {} ArchiveReader::ArchiveReader(const std::string& archiveFile, const std::shared_ptr& logger, std::optional archiveType) : ArchiveReader(archiveFile.c_str(), logger, archiveType) { } ArchiveReader::ArchiveReader(const std::filesystem::path& archiveFile, const std::shared_ptr& logger, std::optional archiveType) : ArchiveReader(logger, archiveType) { Open(archiveFile); } ArchiveReader::ArchiveReader(const char* archiveFile, const std::shared_ptr& logger, std::optional archiveType) : ArchiveReader(logger, archiveType) { Open(archiveFile); } ArchiveReader::ArchiveReader(const void* archiveBuffer, const size_t size, const std::shared_ptr& logger, std::optional archiveType) : ArchiveReader(logger, archiveType) { OpenMemory(archiveBuffer, size); } ArchiveReader::~ArchiveReader() = default; void ArchiveReader::SetPasswordCallback(const decltype(m_passwordCallback)& callback) { archive_read_set_passphrase_callback(m_archive, this, &ArchivePasswordCallbackHandler); } void ArchiveReader::PrepOpen() { if (m_open) { ChkErr(archive_read_close(m_archive)); ChkErr(archive_free(m_archive)); m_archive = archive_read_new(); } ChkErr(archive_read_support_filter_all(m_archive)); ChkErr(GetCurrentFormatReadFunc()(m_archive)); m_open = true; } bool ArchiveReader::Open(const std::filesystem::path& archiveFile) { PrepOpen(); if constexpr (std::is_same_v) ChkErr(archive_read_open_filename(m_archive, (const char*)archiveFile.c_str(), BUFFER_SIZE)); else ChkErr(archive_read_open_filename_w(m_archive, (const wchar_t*)archiveFile.c_str(), BUFFER_SIZE)); ReadNextHeader(); return HasNext(); } bool ArchiveReader::Open(const char* archiveFile) { if (archiveFile[0] == '\0') { if (m_logger) m_logger->error("Unable to open archive file with an empty name!"); return false; } PrepOpen(); ChkErr(archive_read_open_filename(m_archive, archiveFile, BUFFER_SIZE)); ReadNextHeader(); return HasNext(); } bool ArchiveReader::Open(const std::string& archiveFile) { return Open(archiveFile.c_str()); } bool ArchiveReader::OpenMemory(const void* archiveBuffer, size_t size) { PrepOpen(); ChkErr(archive_read_open_memory(m_archive, archiveBuffer, size)); ReadNextHeader(); return HasNext(); } bool ArchiveReader::Open(const std::vector& archiveFiles) { if (archiveFiles.empty()) return false; const ArchiveConfiguration ac = ArchiveConfiguration::FromFileName(archiveFiles.front().c_str()); //TODO if (ac.type == ArchiveType::TAR) // TODO handle all archive types correctly { // Queue based approach for all archive types that do not natively support split archives for(const std::string& file : archiveFiles) { m_archivesToRead.push(file); } const bool state = Open(m_archivesToRead.front()); m_archivesToRead.pop(); return state; } else { // This might only be used for archive types that support split archives (like zip, rar, 7zip, ...) auto files = Utils::toCString(archiveFiles); files.push_back(nullptr); PrepOpen(); ChkErr(archive_read_open_filenames(m_archive, files.data(), BUFFER_SIZE)); ReadNextHeader(); return HasNext(); } } bool ArchiveReader::Open(const std::filesystem::path& dir, const std::string& fileNamePattern, size_t* outSize) { std::vector files; std::regex fileRegex(fileNamePattern); for(const std::filesystem::directory_entry& dirEntry : std::filesystem::directory_iterator(dir)) { std::string entryPath = dirEntry.path().string(); if (!dirEntry.is_directory() && std::regex_match(entryPath, fileRegex)) { files.push_back(std::move(entryPath)); if (outSize) { *outSize += dirEntry.file_size(); } } } std::sort(files.begin(), files.end()); return Open(files); } void ArchiveReader::ReadNextHeader() { if (m_eof || !m_open) { m_archiveEntry = nullptr; return; } int result = archive_read_next_header(m_archive, &m_archiveEntry); if (!ChkErr(result)) { m_archiveEntry = nullptr; } if (result == ARCHIVE_EOF) { m_archiveEntry = nullptr; if (!m_archivesToRead.empty()) { std::string next = std::move(m_archivesToRead.front()); m_archivesToRead.pop(); Open(next); } else m_eof = true; } } std::function ArchiveReader::GetCurrentFormatReadFunc() const { if (!m_archiveType) { return archive_read_support_format_all; } ArchiveType::Type type = *m_archiveType; switch (type) { case ArchiveType::SEVEN_ZIP: return archive_read_support_format_7zip; case ArchiveType::ZIP: return archive_read_support_format_zip; case ArchiveType::CPIO: return archive_read_support_format_cpio; case ArchiveType::ISO: return archive_read_support_format_iso9660; case ArchiveType::TAR: return archive_read_support_format_tar; case ArchiveType::WARC: return archive_read_support_format_warc; case ArchiveType::XAR: return archive_read_support_format_xar; case ArchiveType::SHAR: break; // nothing to do } return archive_read_support_format_all; } size_t ArchiveReader::ExtractRemaining(const std::filesystem::path &targetDir) { size_t count = 0; while (ExtractNext(targetDir)) { count++; } return count; } size_t ArchiveReader::ExtractRemaining(const std::filesystem::path& targetDir, const std::function& extractionCallback) { size_t count = 0; std::optional fd; while ((fd = ExtractNext(targetDir))) { extractionCallback(fd.value()); count++; } return count; } bool ArchiveReader::HasNext() const { return m_archiveEntry != nullptr; } void ArchiveReader::SkipNext() { ReadNextHeader(); } bool ArchiveReader::SkipTill(std::filesystem::file_type type) { mode_t typeId = LibArchiveHelper::GetFileType(type); if (typeId == AE_IFMT) throw std::invalid_argument("Invalid file type"); while(HasNext() && archive_entry_filetype(m_archiveEntry) != typeId) { SkipNext(); } return HasNext(); } FileDescription ArchiveReader::GetDescription() const { return { LibArchiveHelper::GetFileType(archive_entry_filetype(m_archiveEntry)), std::string(archive_entry_pathname_utf8(m_archiveEntry)), archive_entry_size_is_set(m_archiveEntry) ? static_cast(archive_entry_size(m_archiveEntry)) : SIZE_MAX, static_cast(archive_entry_perm(m_archiveEntry)) & std::filesystem::perms::mask, archive_entry_ctime(m_archiveEntry), archive_entry_mtime(m_archiveEntry) }; } std::pair> ArchiveReader::GetNext() { if (!HasNext()) throw std::runtime_error("Reached end of file!"); std::pair> file = {GetDescription(), Array() }; file.second = Array(file.first.size); ChkErr(archive_read_data(m_archive, file.second.Data(), file.second.Size())); ReadNextHeader(); return file; } std::optional ArchiveReader::GetNextDirectory() { if (SkipTill(std::filesystem::file_type::directory)) { return GetDescription(); } return std::nullopt; } std::optional ArchiveReader::ExtractNext(const std::filesystem::path& targetDir) { if (SkipTill(std::filesystem::file_type::regular)) { FileDescription fileDescription = GetDescription(); std::filesystem::path outputFilePath; if (m_filePathRewrite) outputFilePath = targetDir / m_filePathRewrite(fileDescription.path); else outputFilePath = targetDir / static_cast(fileDescription.path); const std::unique_ptr a(archive_write_disk_new(), archive_write_free); const std::unique_ptr entry(archive_entry_clone(m_archiveEntry), archive_entry_free); if constexpr (std::is_same_v) archive_entry_set_pathname(entry.get(), (const char*)outputFilePath.c_str()); else archive_entry_copy_pathname_w(entry.get(), (const wchar_t*)outputFilePath.c_str()); ChkErr(archive_write_disk_set_options(a.get(), ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS)); ChkErr(archive_write_header(a.get(), entry.get())); long result = LibArchiveHelper::CopyArchiveData(m_archive, a.get()); ChkErr(result); LibArchiveHelper::CheckError(result, a.get(), m_logger); ChkErr(archive_write_finish_entry(a.get())); ReadNextHeader(); return fileDescription; } return std::nullopt; } std::optional>> ArchiveReader::GetNextFile() { if (SkipTill(std::filesystem::file_type::regular)) { std::pair> file = {GetDescription(), Array() }; file.second = Array(file.first.size); ChkErr(archive_read_data(m_archive, file.second.Data(), file.second.Size())); ReadNextHeader(); return file; } return std::nullopt; } std::optional>> ArchiveReader::GetNextFileAsVector() { if (SkipTill(std::filesystem::file_type::regular)) { std::pair> file = {GetDescription(), std::vector() }; file.second.resize(file.first.size); ChkErr(archive_read_data(m_archive, file.second.data(), file.second.size())); ReadNextHeader(); return file; } return std::nullopt; } std::optional>> ArchiveReader::GetFile(const std::string& path) { while (HasNext()) { if (path == archive_entry_pathname(m_archiveEntry)) { return GetNextFile(); } if (archive_read_next_header(m_archive, &m_archiveEntry) != ARCHIVE_OK) { return {}; } } return {}; } bool ArchiveReader::GetNextFileAsStream(const std::function& streamReader) { if (SkipTill(std::filesystem::file_type::regular)) { FileDescription fileDescription = GetDescription(); LibArchiveEntryStreamBuffer streamBuffer(m_archive); std::istream stream(&streamBuffer); streamReader(fileDescription, stream); ReadNextHeader(); return true; } return false; } std::optional ArchiveReader::StreamNextFile(std::ostream& stream) { if (SkipTill(std::filesystem::file_type::regular)) { const void *buffer; size_t size; int64_t offset, r; while ((r = archive_read_data_block(m_archive, &buffer, &size, &offset)) != ARCHIVE_EOF) { ChkErr(r); stream.write(static_cast(buffer), size); } FileDescription fileDescription = GetDescription(); ReadNextHeader(); return fileDescription; } return std::nullopt; } }