File indexing completed on 2024-05-12 05:50:22

0001 /*
0002     SPDX-FileCopyrightText: 2007 Henrique Pinto <henrique.pinto@kdemail.net>
0003     SPDX-FileCopyrightText: 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
0004     SPDX-FileCopyrightText: 2010 Raphael Kubo da Costa <rakuco@FreeBSD.org>
0005     SPDX-FileCopyrightText: 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
0006 
0007     SPDX-License-Identifier: BSD-2-Clause
0008 */
0009 
0010 #include "libarchiveplugin.h"
0011 #include "ark_debug.h"
0012 #include "queries.h"
0013 #include "windows_stat.h"
0014 
0015 #include <KLocalizedString>
0016 
0017 #include <QDir>
0018 #include <QFileInfo>
0019 #include <QMimeDatabase>
0020 #include <QThread>
0021 
0022 #include <archive_entry.h>
0023 
0024 LibarchivePlugin::LibarchivePlugin(QObject *parent, const QVariantList &args)
0025     : ReadWriteArchiveInterface(parent, args)
0026     , m_archiveReadDisk(archive_read_disk_new())
0027     , m_cachedArchiveEntryCount(0)
0028     , m_emitNoEntries(false)
0029     , m_extractedFilesSize(0)
0030 {
0031     qCDebug(ARK) << "Initializing libarchive plugin";
0032     archive_read_disk_set_standard_lookup(m_archiveReadDisk.data());
0033 
0034     connect(this, &ReadOnlyArchiveInterface::error, this, &LibarchivePlugin::slotRestoreWorkingDir);
0035     connect(this, &ReadOnlyArchiveInterface::cancelled, this, &LibarchivePlugin::slotRestoreWorkingDir);
0036 
0037 #ifdef LIBARCHIVE_RAW_MIMETYPES
0038     m_rawMimetypes = QStringLiteral(LIBARCHIVE_RAW_MIMETYPES).split(QLatin1Char(':'), Qt::SkipEmptyParts);
0039     // shared-mime-info 2.3 explicitly separated application/x-bzip2 from application/x-bzip
0040     // since bzip2 is not compatible with the old (and deprecated) bzip format.
0041     // See https://gitlab.freedesktop.org/xdg/shared-mime-info/-/merge_requests/239
0042     // With shared-mime-info 2.3 (or newer) we can't have both mimetypes at the same time, since libarchive does not support
0043     // the old deprecated bzip format. Also we can't know which version of shared-mime-info the system is actually using.
0044     // For these reasons, just take the mimetype from QMimeDatabase to keep the compatibility with any shared-mime-info version.
0045     if (m_rawMimetypes.contains(QLatin1String("application/x-bzip")) && m_rawMimetypes.contains(QLatin1String("application/x-bzip2"))) {
0046         m_rawMimetypes.removeAll(QLatin1String("application/x-bzip"));
0047         m_rawMimetypes.removeAll(QLatin1String("application/x-bzip2"));
0048         m_rawMimetypes.append(QMimeDatabase().mimeTypeForFile(QStringLiteral("dummy.bz2"), QMimeDatabase::MatchExtension).name());
0049     }
0050     qCDebug(ARK) << "# available raw mimetypes:" << m_rawMimetypes.count();
0051 #endif
0052 }
0053 
0054 LibarchivePlugin::~LibarchivePlugin()
0055 {
0056     for (const auto e : std::as_const(m_emittedEntries)) {
0057         // Entries might be passed to pending slots, so we just schedule their deletion.
0058         e->deleteLater();
0059     }
0060 }
0061 
0062 bool LibarchivePlugin::list()
0063 {
0064     qCDebug(ARK) << "Listing archive contents";
0065 
0066     if (!initializeReader()) {
0067         return false;
0068     }
0069 
0070     qCDebug(ARK) << "Detected compression filter:" << archive_filter_name(m_archiveReader.data(), 0);
0071     QString compMethod = convertCompressionName(QString::fromUtf8(archive_filter_name(m_archiveReader.data(), 0)));
0072     if (!compMethod.isEmpty()) {
0073         Q_EMIT compressionMethodFound(compMethod);
0074     }
0075 
0076     m_cachedArchiveEntryCount = 0;
0077     m_extractedFilesSize = 0;
0078     m_numberOfEntries = 0;
0079     auto compressedArchiveSize = QFileInfo(filename()).size();
0080 
0081     struct archive_entry *aentry;
0082     int result = ARCHIVE_RETRY;
0083 
0084     bool firstEntry = true;
0085     while (!QThread::currentThread()->isInterruptionRequested() && (result = archive_read_next_header(m_archiveReader.data(), &aentry)) == ARCHIVE_OK) {
0086         if (firstEntry) {
0087             qCDebug(ARK) << "Detected format for first entry:" << archive_format_name(m_archiveReader.data());
0088             firstEntry = false;
0089         }
0090 
0091         if (!m_emitNoEntries) {
0092             const bool isRawFormat = (archive_format(m_archiveReader.data()) == ARCHIVE_FORMAT_RAW);
0093             emitEntryFromArchiveEntry(aentry, isRawFormat);
0094         }
0095 
0096         m_extractedFilesSize += (qlonglong)archive_entry_size(aentry);
0097 
0098         Q_EMIT progress(float(archive_filter_bytes(m_archiveReader.data(), -1)) / float(compressedArchiveSize));
0099 
0100         m_cachedArchiveEntryCount++;
0101 
0102         // Skip the entry data.
0103         int readSkipResult = archive_read_data_skip(m_archiveReader.data());
0104         if (readSkipResult != ARCHIVE_OK) {
0105             qCCritical(ARK) << "Error while skipping data for entry:" << QString::fromWCharArray(archive_entry_pathname_w(aentry)) << readSkipResult
0106                             << QLatin1String(archive_error_string(m_archiveReader.data()));
0107             if (!emitCorruptArchive()) {
0108                 return false;
0109             }
0110         }
0111     }
0112 
0113     if (QThread::currentThread()->isInterruptionRequested()) {
0114         return false;
0115     }
0116 
0117     if (result != ARCHIVE_EOF) {
0118         qCCritical(ARK) << "Error while reading archive:" << result << QLatin1String(archive_error_string(m_archiveReader.data()));
0119         if (!emitCorruptArchive()) {
0120             return false;
0121         }
0122     }
0123 
0124     return archive_read_close(m_archiveReader.data()) == ARCHIVE_OK;
0125 }
0126 
0127 bool LibarchivePlugin::emitCorruptArchive()
0128 {
0129     Kerfuffle::LoadCorruptQuery query(filename());
0130     Q_EMIT userQuery(&query);
0131     query.waitForResponse();
0132     if (!query.responseYes()) {
0133         Q_EMIT cancelled();
0134         archive_read_close(m_archiveReader.data());
0135         return false;
0136     } else {
0137         Q_EMIT progress(1.0);
0138         return true;
0139     }
0140 }
0141 
0142 const QString LibarchivePlugin::uncompressedFileName() const
0143 {
0144     QFileInfo fileInfo(filename());
0145     QString uncompressedName(fileInfo.fileName());
0146 
0147     // Bug 252701: For .svgz just remove the terminal "z".
0148     if (uncompressedName.endsWith(QLatin1String(".svgz"), Qt::CaseInsensitive)) {
0149         uncompressedName.chop(1);
0150         return uncompressedName;
0151     }
0152 
0153     if (!fileInfo.suffix().isEmpty()) {
0154         return fileInfo.completeBaseName();
0155     }
0156 
0157     return uncompressedName + QLatin1String(".uncompressed");
0158 }
0159 
0160 void LibarchivePlugin::copyDataBlock(const QString &filename, archive *source, archive *dest, bool partialprogress)
0161 {
0162     while (!QThread::currentThread()->isInterruptionRequested()) {
0163         const void *buff;
0164         size_t size;
0165         la_int64_t offset;
0166         int returnCode = archive_read_data_block(source, &buff, &size, &offset);
0167         if (returnCode == ARCHIVE_EOF) {
0168             return;
0169         }
0170         if (returnCode < ARCHIVE_OK) {
0171             qCCritical(ARK) << "Error while extracting" << filename << ":" << archive_error_string(source) << "(error no =" << archive_errno(source) << ')';
0172             return;
0173         }
0174         returnCode = archive_write_data_block(dest, buff, size, offset);
0175         if (returnCode < ARCHIVE_OK) {
0176             qCCritical(ARK) << "Error while writing" << filename << ":" << archive_error_string(dest) << "(error no =" << archive_errno(dest) << ')';
0177             return;
0178         }
0179         if (partialprogress) {
0180             m_currentExtractedFilesSize += size;
0181             Q_EMIT progress(float(m_currentExtractedFilesSize) / m_extractedFilesSize);
0182         }
0183     }
0184 }
0185 
0186 bool LibarchivePlugin::addFiles(const QVector<Archive::Entry *> &files,
0187                                 const Archive::Entry *destination,
0188                                 const CompressionOptions &options,
0189                                 uint numberOfEntriesToAdd)
0190 {
0191     Q_UNUSED(files)
0192     Q_UNUSED(destination)
0193     Q_UNUSED(options)
0194     Q_UNUSED(numberOfEntriesToAdd)
0195     return false;
0196 }
0197 
0198 bool LibarchivePlugin::moveFiles(const QVector<Archive::Entry *> &files, Archive::Entry *destination, const CompressionOptions &options)
0199 {
0200     Q_UNUSED(files)
0201     Q_UNUSED(destination)
0202     Q_UNUSED(options)
0203     return false;
0204 }
0205 
0206 bool LibarchivePlugin::copyFiles(const QVector<Archive::Entry *> &files, Archive::Entry *destination, const CompressionOptions &options)
0207 {
0208     Q_UNUSED(files)
0209     Q_UNUSED(destination)
0210     Q_UNUSED(options)
0211     return false;
0212 }
0213 
0214 bool LibarchivePlugin::deleteFiles(const QVector<Archive::Entry *> &files)
0215 {
0216     Q_UNUSED(files)
0217     return false;
0218 }
0219 
0220 bool LibarchivePlugin::addComment(const QString &comment)
0221 {
0222     Q_UNUSED(comment)
0223     return false;
0224 }
0225 
0226 bool LibarchivePlugin::testArchive()
0227 {
0228     return false;
0229 }
0230 
0231 bool LibarchivePlugin::hasBatchExtractionProgress() const
0232 {
0233     return true;
0234 }
0235 
0236 bool LibarchivePlugin::doKill()
0237 {
0238     return false;
0239 }
0240 
0241 bool LibarchivePlugin::extractFiles(const QVector<Archive::Entry *> &files, const QString &destinationDirectory, const ExtractionOptions &options)
0242 {
0243     if (!initializeReader()) {
0244         return false;
0245     }
0246 
0247     ArchiveWrite writer(archive_write_disk_new());
0248     if (!writer.data()) {
0249         return false;
0250     }
0251 
0252     int totalEntriesCount = 0;
0253     const bool extractAll = files.isEmpty();
0254     if (extractAll) {
0255         if (!m_cachedArchiveEntryCount) {
0256             Q_EMIT progress(0);
0257             // TODO: once information progress has been implemented, send
0258             // feedback here that the archive is being read
0259             qCDebug(ARK) << "For getting progress information, the archive will be listed once";
0260             m_emitNoEntries = true;
0261             list();
0262             m_emitNoEntries = false;
0263         }
0264         totalEntriesCount = m_cachedArchiveEntryCount;
0265     } else {
0266         totalEntriesCount = files.size();
0267     }
0268 
0269     qCDebug(ARK) << "Going to extract" << totalEntriesCount << "entries";
0270 
0271     qCDebug(ARK) << "Changing current directory to " << destinationDirectory;
0272     m_oldWorkingDir = QDir::currentPath();
0273     QDir::setCurrent(destinationDirectory);
0274 
0275     // Initialize variables.
0276     const bool preservePaths = options.preservePaths();
0277     const bool removeRootNode = options.isDragAndDropEnabled();
0278     bool overwriteAll = false; // Whether to overwrite all files
0279     bool skipAll = false; // Whether to skip all files
0280     bool dontPromptErrors = false; // Whether to prompt for errors
0281     bool isSingleFile = false;
0282     m_currentExtractedFilesSize = 0;
0283     int extractedEntriesCount = 0;
0284     int progressEntryCount = 0;
0285     struct archive_entry *entry;
0286     QString fileBeingRenamed;
0287     // To avoid traversing the entire archive when extracting a limited set of
0288     // entries, we maintain a list of remaining entries and stop when it's empty.
0289     const QStringList fullPaths = entryFullPaths(files);
0290     QStringList remainingFiles = entryFullPaths(files);
0291 
0292     // Iterate through all entries in archive.
0293     while (!QThread::currentThread()->isInterruptionRequested() && (archive_read_next_header(m_archiveReader.data(), &entry) == ARCHIVE_OK)) {
0294         if (!extractAll && remainingFiles.isEmpty()) {
0295             break;
0296         }
0297 
0298         fileBeingRenamed.clear();
0299         int index = -1;
0300 
0301         // Retry with renamed entry, fire an overwrite query again
0302         // if the new entry also exists.
0303     retry:
0304         const bool entryIsDir = S_ISDIR(archive_entry_mode(entry));
0305         // Skip directories if not preserving paths.
0306         if (!preservePaths && entryIsDir) {
0307             archive_read_data_skip(m_archiveReader.data());
0308             continue;
0309         }
0310 
0311         // entryName is the name inside the archive, full path
0312         QString entryName = QDir::fromNativeSeparators(QFile::decodeName(archive_entry_pathname(entry)));
0313         if (archive_format(m_archiveReader.data()) == ARCHIVE_FORMAT_RAW) {
0314             isSingleFile = true;
0315             qCDebug(ARK) << "Detected single file archive, entry path: " << entryName;
0316         }
0317         // Some archive types e.g. AppImage prepend all entries with "./" so remove this part.
0318         if (entryName.startsWith(QLatin1String("./"))) {
0319             entryName.remove(0, 2);
0320         }
0321 
0322         // Static libraries (*.a) contain the two entries "/" and "//".
0323         // We just skip these to allow extracting this archive type.
0324         if (entryName == QLatin1String("/") || entryName == QLatin1String("//")) {
0325             archive_read_data_skip(m_archiveReader.data());
0326             continue;
0327         }
0328 
0329         // Don't allow absolute paths, instead, treat them like relative to the root of the archive.
0330         while (entryName.startsWith(QLatin1Char('/'))) {
0331             entryName.remove(0, 1);
0332         }
0333 
0334         // Should the entry be extracted?
0335         if (extractAll || remainingFiles.contains(entryName) || entryName == fileBeingRenamed) {
0336             // Find the index of entry.
0337             if (entryName != fileBeingRenamed) {
0338                 index = fullPaths.indexOf(entryName);
0339             }
0340             if (!extractAll && index == -1) {
0341                 // If entry is not found in files, skip entry.
0342                 continue;
0343             }
0344 
0345             // entryFI is the fileinfo pointing to where the file will be
0346             // written from the archive.
0347             QFileInfo entryFI(entryName);
0348             // qCDebug(ARK) << "setting path to " << archive_entry_pathname( entry );
0349 
0350             if (isSingleFile && fileBeingRenamed.isEmpty()) {
0351                 // Rename extracted file from libarchive-internal "data" name to the archive uncompressed name.
0352                 const QString uncompressedName = uncompressedFileName();
0353                 qCDebug(ARK) << "going to rename libarchive-internal 'data' filename to:" << uncompressedName;
0354                 archive_entry_copy_pathname(entry, QFile::encodeName(uncompressedName).constData());
0355                 entryFI = QFileInfo(uncompressedName);
0356             }
0357 
0358             const QString fileWithoutPath(entryFI.fileName());
0359             // If we DON'T preserve paths, we cut the path and set the entryFI
0360             // fileinfo to the one without the path.
0361             if (!preservePaths) {
0362                 // Empty filenames (ie dirs) should have been skipped already,
0363                 // so asserting.
0364                 Q_ASSERT(!fileWithoutPath.isEmpty());
0365                 archive_entry_copy_pathname(entry, QFile::encodeName(fileWithoutPath).constData());
0366                 entryFI = QFileInfo(fileWithoutPath);
0367 
0368                 // OR, if the file has a rootNode attached, remove it from file path.
0369             } else if (!extractAll && removeRootNode && entryName != fileBeingRenamed) {
0370                 const QString &rootNode = files.at(index)->rootNode;
0371                 if (!rootNode.isEmpty()) {
0372                     const QString truncatedFilename(entryName.remove(entryName.indexOf(rootNode), rootNode.size()));
0373                     archive_entry_copy_pathname(entry, QFile::encodeName(truncatedFilename).constData());
0374                     entryFI = QFileInfo(truncatedFilename);
0375                 }
0376             }
0377 
0378             // Check if the file about to be written already exists.
0379             if (!entryIsDir && entryFI.exists()) {
0380                 if (skipAll) {
0381                     archive_read_data_skip(m_archiveReader.data());
0382                     archive_entry_clear(entry);
0383                     continue;
0384                 } else if (!overwriteAll && !skipAll) {
0385                     Kerfuffle::OverwriteQuery query(entryName);
0386                     Q_EMIT userQuery(&query);
0387                     query.waitForResponse();
0388 
0389                     if (query.responseCancelled()) {
0390                         Q_EMIT cancelled();
0391                         archive_read_data_skip(m_archiveReader.data());
0392                         archive_entry_clear(entry);
0393                         break;
0394                     } else if (query.responseSkip()) {
0395                         archive_read_data_skip(m_archiveReader.data());
0396                         archive_entry_clear(entry);
0397                         continue;
0398                     } else if (query.responseAutoSkip()) {
0399                         archive_read_data_skip(m_archiveReader.data());
0400                         archive_entry_clear(entry);
0401                         skipAll = true;
0402                         continue;
0403                     } else if (query.responseRename()) {
0404                         const QString newName(query.newFilename());
0405                         fileBeingRenamed = newName;
0406                         archive_entry_copy_pathname(entry, QFile::encodeName(newName).constData());
0407                         goto retry;
0408                     } else if (query.responseOverwriteAll()) {
0409                         overwriteAll = true;
0410                     }
0411                 }
0412             }
0413 
0414             // If there is an already existing directory.
0415             if (entryIsDir && entryFI.exists()) {
0416                 if (entryFI.isWritable()) {
0417                     qCWarning(ARK) << "Warning, existing, but writable dir";
0418                 } else {
0419                     qCWarning(ARK) << "Warning, existing, but non-writable dir. skipping";
0420                     archive_entry_clear(entry);
0421                     archive_read_data_skip(m_archiveReader.data());
0422                     continue;
0423                 }
0424             }
0425 
0426             int flags = extractionFlags();
0427             if (archive_entry_sparse_count(entry) > 0) {
0428                 flags |= ARCHIVE_EXTRACT_SPARSE;
0429             }
0430             archive_write_disk_set_options(writer.data(), flags);
0431 
0432             // Write the entry header and check return value.
0433             const int returnCode = archive_write_header(writer.data(), entry);
0434             switch (returnCode) {
0435             case ARCHIVE_OK:
0436                 // If the whole archive is extracted and the total filesize is
0437                 // available, we use partial progress.
0438                 copyDataBlock(entryName, m_archiveReader.data(), writer.data(), (extractAll && m_extractedFilesSize));
0439                 break;
0440 
0441             case ARCHIVE_FAILED:
0442                 qCCritical(ARK) << "archive_write_header() has returned" << returnCode << "with errno" << archive_errno(writer.data());
0443 
0444                 // If they user previously decided to ignore future errors,
0445                 // don't bother prompting again.
0446                 if (!dontPromptErrors) {
0447                     // Ask the user if he wants to continue extraction despite an error for this entry.
0448                     Kerfuffle::ContinueExtractionQuery query(QLatin1String(archive_error_string(writer.data())), entryName);
0449                     Q_EMIT userQuery(&query);
0450                     query.waitForResponse();
0451 
0452                     if (query.responseCancelled()) {
0453                         Q_EMIT cancelled();
0454                         return false;
0455                     }
0456                     dontPromptErrors = query.dontAskAgain();
0457                 }
0458                 break;
0459 
0460             case ARCHIVE_FATAL:
0461                 qCCritical(ARK) << "archive_write_header() has returned" << returnCode << "with errno" << archive_errno(writer.data());
0462                 Q_EMIT error(i18nc("@info", "Fatal error, extraction aborted."));
0463                 return false;
0464             default:
0465                 qCDebug(ARK) << "archive_write_header() returned" << returnCode << "which will be ignored.";
0466                 break;
0467             }
0468 
0469             // If we only partially extract the archive and the number of
0470             // archive entries is available we use a simple progress based on
0471             // number of items extracted.
0472             if (!extractAll && m_cachedArchiveEntryCount) {
0473                 ++progressEntryCount;
0474                 Q_EMIT progress(float(progressEntryCount) / totalEntriesCount);
0475             }
0476 
0477             extractedEntriesCount++;
0478             remainingFiles.removeOne(entryName);
0479         } else {
0480             // Archive entry not among selected files, skip it.
0481             archive_read_data_skip(m_archiveReader.data());
0482         }
0483     }
0484 
0485     qCDebug(ARK) << "Extracted" << extractedEntriesCount << "entries";
0486     slotRestoreWorkingDir();
0487     return archive_read_close(m_archiveReader.data()) == ARCHIVE_OK;
0488 }
0489 
0490 bool LibarchivePlugin::initializeReader()
0491 {
0492     m_archiveReader.reset(archive_read_new());
0493 
0494     if (!(m_archiveReader.data())) {
0495         Q_EMIT error(i18n("The archive reader could not be initialized."));
0496         return false;
0497     }
0498 
0499     if (archive_read_support_filter_all(m_archiveReader.data()) != ARCHIVE_OK) {
0500         return false;
0501     }
0502 
0503     if (m_rawMimetypes.contains(mimetype().name())) {
0504         qCDebug(ARK) << "Enabling RAW filter for mimetype: " << mimetype().name();
0505         // Enable "raw" format only if we have a "raw mimetype", i.e. a single-file archive, as to not affect normal tar archives.
0506         if (archive_read_support_format_raw(m_archiveReader.data()) != ARCHIVE_OK) {
0507             return false;
0508         }
0509     } else {
0510         if (archive_read_support_format_all(m_archiveReader.data()) != ARCHIVE_OK) {
0511             return false;
0512         }
0513     }
0514 
0515     if (archive_read_open_filename(m_archiveReader.data(), QFile::encodeName(filename()).constData(), 10240) != ARCHIVE_OK) {
0516         qCWarning(ARK) << "Could not open the archive:" << archive_error_string(m_archiveReader.data());
0517         Q_EMIT error(i18nc("@info", "Archive corrupted or insufficient permissions."));
0518         return false;
0519     }
0520 
0521     return true;
0522 }
0523 
0524 void LibarchivePlugin::emitEntryFromArchiveEntry(struct archive_entry *aentry, bool isRawFormat)
0525 {
0526     auto e = new Archive::Entry();
0527     e->setProperty("fullPath", QDir::fromNativeSeparators(QString::fromWCharArray(archive_entry_pathname_w(aentry))));
0528 
0529     if (isRawFormat) {
0530         e->setProperty("displayName",
0531                        uncompressedFileName()); // libarchive reports a fake 'data' entry if raw format, ignore it and use the uncompressed filename.
0532         e->setProperty("compressedSize", QFileInfo(filename()).size());
0533         e->compressedSizeIsSet = true;
0534     } else {
0535         const QString owner = QString::fromLatin1(archive_entry_uname(aentry));
0536         if (!owner.isEmpty()) {
0537             e->setProperty("owner", owner);
0538         } else {
0539             e->setProperty("owner", static_cast<qlonglong>(archive_entry_uid(aentry)));
0540         }
0541 
0542         const QString group = QString::fromLatin1(archive_entry_gname(aentry));
0543         if (!group.isEmpty()) {
0544             e->setProperty("group", group);
0545         } else {
0546             e->setProperty("group", static_cast<qlonglong>(archive_entry_gid(aentry)));
0547         }
0548 
0549         const mode_t mode = archive_entry_mode(aentry);
0550         if (mode != 0) {
0551             e->setProperty("permissions", permissionsToString(mode));
0552         }
0553         e->setProperty("isExecutable", mode & (S_IXUSR | S_IXGRP | S_IXOTH));
0554 
0555         e->compressedSizeIsSet = false;
0556         e->setProperty("size", (qlonglong)archive_entry_size(aentry));
0557         e->setProperty("isDirectory", S_ISDIR(archive_entry_mode(aentry)));
0558 
0559         if (archive_entry_symlink(aentry)) {
0560             e->setProperty("link", QLatin1String(archive_entry_symlink(aentry)));
0561         }
0562 
0563         auto time = static_cast<uint>(archive_entry_mtime(aentry));
0564         e->setProperty("timestamp", QDateTime::fromSecsSinceEpoch(time));
0565     }
0566 
0567     if (archive_entry_sparse_reset(aentry)) {
0568         qulonglong sparseSize = 0;
0569         la_int64_t offset, len;
0570         while (archive_entry_sparse_next(aentry, &offset, &len) == ARCHIVE_OK) {
0571             sparseSize += static_cast<qulonglong>(len);
0572         }
0573         e->setProperty("sparseSize", sparseSize);
0574         e->setProperty("isSparse", true);
0575     }
0576 
0577     Q_EMIT entry(e);
0578     m_emittedEntries << e;
0579 }
0580 
0581 int LibarchivePlugin::extractionFlags() const
0582 {
0583     return ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_SECURE_SYMLINKS;
0584 }
0585 
0586 void LibarchivePlugin::copyData(const QString &filename, struct archive *dest, bool partialprogress)
0587 {
0588     char buff[10240];
0589     QFile file(filename);
0590 
0591     if (!file.open(QIODevice::ReadOnly)) {
0592         return;
0593     }
0594 
0595     auto readBytes = file.read(buff, sizeof(buff));
0596     while (readBytes > 0 && !QThread::currentThread()->isInterruptionRequested()) {
0597         archive_write_data(dest, buff, static_cast<size_t>(readBytes));
0598         if (archive_errno(dest) != ARCHIVE_OK) {
0599             qCCritical(ARK) << "Error while writing" << filename << ":" << archive_error_string(dest) << "(error no =" << archive_errno(dest) << ')';
0600             return;
0601         }
0602 
0603         if (partialprogress) {
0604             m_currentExtractedFilesSize += readBytes;
0605             Q_EMIT progress(float(m_currentExtractedFilesSize) / m_extractedFilesSize);
0606         }
0607 
0608         readBytes = file.read(buff, sizeof(buff));
0609     }
0610 
0611     file.close();
0612 }
0613 
0614 void LibarchivePlugin::copyData(const QString &filename, struct archive *source, struct archive *dest, bool partialprogress)
0615 {
0616     char buff[10240];
0617 
0618     auto readBytes = archive_read_data(source, buff, sizeof(buff));
0619     while (readBytes > 0 && !QThread::currentThread()->isInterruptionRequested()) {
0620         archive_write_data(dest, buff, static_cast<size_t>(readBytes));
0621         if (archive_errno(dest) != ARCHIVE_OK) {
0622             qCCritical(ARK) << "Error while extracting" << filename << ":" << archive_error_string(dest) << "(error no =" << archive_errno(dest) << ')';
0623             return;
0624         }
0625 
0626         if (partialprogress) {
0627             m_currentExtractedFilesSize += readBytes;
0628             Q_EMIT progress(float(m_currentExtractedFilesSize) / m_extractedFilesSize);
0629         }
0630 
0631         readBytes = archive_read_data(source, buff, sizeof(buff));
0632     }
0633 }
0634 
0635 void LibarchivePlugin::slotRestoreWorkingDir()
0636 {
0637     if (m_oldWorkingDir.isEmpty()) {
0638         return;
0639     }
0640 
0641     if (!QDir::setCurrent(m_oldWorkingDir)) {
0642         qCWarning(ARK) << "Failed to restore old working directory:" << m_oldWorkingDir;
0643     } else {
0644         m_oldWorkingDir.clear();
0645     }
0646 }
0647 
0648 QString LibarchivePlugin::convertCompressionName(const QString &method)
0649 {
0650     if (method == QLatin1String("gzip")) {
0651         return QStringLiteral("GZip");
0652     } else if (method == QLatin1String("bzip2")) {
0653         return QStringLiteral("BZip2");
0654     } else if (method == QLatin1String("xz")) {
0655         return QStringLiteral("XZ");
0656     } else if (method == QLatin1String("compress (.Z)")) {
0657         return QStringLiteral("Compress");
0658     } else if (method == QLatin1String("lrzip")) {
0659         return QStringLiteral("LRZip");
0660     } else if (method == QLatin1String("lzip")) {
0661         return QStringLiteral("LZip");
0662     } else if (method == QLatin1String("lz4")) {
0663         return QStringLiteral("LZ4");
0664     } else if (method == QLatin1String("lzop")) {
0665         return QStringLiteral("lzop");
0666     } else if (method == QLatin1String("lzma")) {
0667         return QStringLiteral("LZMA");
0668     } else if (method == QLatin1String("zstd")) {
0669         return QStringLiteral("Zstandard");
0670     }
0671     return QString();
0672 }
0673 
0674 #include "moc_libarchiveplugin.cpp"