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"