File indexing completed on 2024-05-12 16:06:33

0001 /*
0002     SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "unrar.h"
0008 
0009 #include <QEventLoop>
0010 #include <QFile>
0011 #include <QFileInfo>
0012 #include <QGlobalStatic>
0013 #include <QTemporaryDir>
0014 
0015 #include <QLoggingCategory>
0016 #if defined(WITH_KPTY)
0017 #include <KPtyDevice>
0018 #include <KPtyProcess>
0019 #endif
0020 
0021 #include "debug_comicbook.h"
0022 
0023 #include <QRegularExpression>
0024 #include <QStandardPaths>
0025 #include <memory>
0026 
0027 struct UnrarHelper {
0028     UnrarHelper();
0029     ~UnrarHelper();
0030 
0031     UnrarHelper(const UnrarHelper &) = delete;
0032     UnrarHelper &operator=(const UnrarHelper &) = delete;
0033 
0034     UnrarFlavour *kind;
0035     QString unrarPath;
0036     QString lsarPath;
0037 };
0038 
0039 Q_GLOBAL_STATIC(UnrarHelper, helper)
0040 
0041 static UnrarFlavour *detectUnrar(const QString &unrarPath, const QString &versionCommand)
0042 {
0043     UnrarFlavour *kind = nullptr;
0044     QProcess proc;
0045     proc.start(unrarPath, QStringList() << versionCommand);
0046     bool ok = proc.waitForFinished(-1);
0047     Q_UNUSED(ok)
0048     static const QRegularExpression regex(QStringLiteral("[\r\n]"));
0049     const QStringList lines = QString::fromLocal8Bit(proc.readAllStandardOutput()).split(regex, Qt::SkipEmptyParts);
0050     if (!lines.isEmpty()) {
0051         if (lines.first().startsWith(QLatin1String("UNRAR "))) {
0052             kind = new NonFreeUnrarFlavour();
0053         } else if (lines.first().startsWith(QLatin1String("RAR "))) {
0054             kind = new NonFreeUnrarFlavour();
0055         } else if (lines.first().startsWith(QLatin1String("unrar "))) {
0056             kind = new FreeUnrarFlavour();
0057         } else if (lines.first().startsWith(QLatin1String("v"))) {
0058             kind = new UnarFlavour();
0059         }
0060     }
0061     return kind;
0062 }
0063 
0064 UnrarHelper::UnrarHelper()
0065     : kind(nullptr)
0066 {
0067     QString path = QStandardPaths::findExecutable(QStringLiteral("lsar"));
0068 
0069     if (!path.isEmpty()) {
0070         lsarPath = path;
0071     }
0072 
0073     path = QStandardPaths::findExecutable(QStringLiteral("unrar-nonfree"));
0074 
0075     if (path.isEmpty()) {
0076         path = QStandardPaths::findExecutable(QStringLiteral("unrar"));
0077     }
0078     if (path.isEmpty()) {
0079         path = QStandardPaths::findExecutable(QStringLiteral("rar"));
0080     }
0081     if (path.isEmpty()) {
0082         path = QStandardPaths::findExecutable(QStringLiteral("unar"));
0083     }
0084 
0085     if (!path.isEmpty()) {
0086         kind = detectUnrar(path, QStringLiteral("--version"));
0087     }
0088 
0089     if (!kind) {
0090         kind = detectUnrar(path, QStringLiteral("-v"));
0091     }
0092 
0093     if (!kind) {
0094         // no luck, print that
0095         qWarning() << "Neither unrar nor unarchiver were found.";
0096     } else {
0097         unrarPath = path;
0098         qCDebug(OkularComicbookDebug) << "detected:" << path << "(" << kind->name() << ")";
0099     }
0100 }
0101 
0102 UnrarHelper::~UnrarHelper()
0103 {
0104     delete kind;
0105 }
0106 
0107 Unrar::Unrar()
0108     : QObject(nullptr)
0109     , mLoop(nullptr)
0110     , mTempDir(nullptr)
0111 {
0112 }
0113 
0114 Unrar::~Unrar()
0115 {
0116     delete mTempDir;
0117 }
0118 
0119 bool Unrar::open(const QString &fileName)
0120 {
0121     if (!isSuitableVersionAvailable()) {
0122         return false;
0123     }
0124 
0125     delete mTempDir;
0126     mTempDir = new QTemporaryDir();
0127 
0128     mFileName = fileName;
0129 
0130     /**
0131      * Extract the archive to a temporary directory
0132      */
0133     mStdOutData.clear();
0134     mStdErrData.clear();
0135 
0136     const int ret = startSyncProcess(helper->kind->processOpenArchiveArgs(mFileName, mTempDir->path()));
0137     bool ok = ret == 0;
0138 
0139     return ok;
0140 }
0141 
0142 QStringList Unrar::list()
0143 {
0144     mStdOutData.clear();
0145     mStdErrData.clear();
0146 
0147     if (!isSuitableVersionAvailable()) {
0148         return QStringList();
0149     }
0150 
0151     startSyncProcess(helper->kind->processListArgs(mFileName));
0152 
0153     static const QRegularExpression regex(QStringLiteral("[\r\n]"));
0154     QStringList listFiles = helper->kind->processListing(QString::fromLocal8Bit(mStdOutData).split(regex, Qt::SkipEmptyParts));
0155 
0156     QString subDir;
0157 
0158     if (listFiles.last().endsWith(QLatin1Char('/')) && helper->kind->name() == QLatin1String("unar")) {
0159         // Subfolder detected. The unarchiver is unable to extract all files into a single folder
0160         subDir = listFiles.last();
0161         listFiles.removeLast();
0162     }
0163 
0164     QStringList newList;
0165     for (const QString &f : std::as_const(listFiles)) {
0166         // Extract all the files to mTempDir regardless of their path inside the archive
0167         // This will break if ever an arvhice with two files with the same name in different subfolders
0168         QFileInfo fi(f);
0169         if (QFile::exists(mTempDir->path() + QLatin1Char('/') + subDir + fi.fileName())) {
0170             newList.append(subDir + fi.fileName());
0171         }
0172     }
0173     return newList;
0174 }
0175 
0176 QByteArray Unrar::contentOf(const QString &fileName) const
0177 {
0178     if (!isSuitableVersionAvailable()) {
0179         return QByteArray();
0180     }
0181 
0182     QFile file(mTempDir->path() + QLatin1Char('/') + fileName);
0183     if (!file.open(QIODevice::ReadOnly)) {
0184         return QByteArray();
0185     }
0186 
0187     return file.readAll();
0188 }
0189 
0190 QIODevice *Unrar::createDevice(const QString &fileName) const
0191 {
0192     if (!isSuitableVersionAvailable()) {
0193         return nullptr;
0194     }
0195 
0196     std::unique_ptr<QFile> file(new QFile(mTempDir->path() + QLatin1Char('/') + fileName));
0197     if (!file->open(QIODevice::ReadOnly)) {
0198         return nullptr;
0199     }
0200 
0201     return file.release();
0202 }
0203 
0204 bool Unrar::isAvailable()
0205 {
0206     return helper->kind;
0207 }
0208 
0209 bool Unrar::isSuitableVersionAvailable()
0210 {
0211     if (!isAvailable()) {
0212         return false;
0213     }
0214 
0215     if (dynamic_cast<NonFreeUnrarFlavour *>(helper->kind) || dynamic_cast<UnarFlavour *>(helper->kind)) {
0216         return true;
0217     } else {
0218         return false;
0219     }
0220 }
0221 
0222 void Unrar::readFromStdout()
0223 {
0224     if (!mProcess) {
0225         return;
0226     }
0227 
0228     mStdOutData += mProcess->readAllStandardOutput();
0229 }
0230 
0231 void Unrar::readFromStderr()
0232 {
0233     if (!mProcess) {
0234         return;
0235     }
0236 
0237     mStdErrData += mProcess->readAllStandardError();
0238     if (!mStdErrData.isEmpty()) {
0239         mProcess->kill();
0240         return;
0241     }
0242 }
0243 
0244 void Unrar::finished(int exitCode, QProcess::ExitStatus exitStatus)
0245 {
0246     Q_UNUSED(exitCode)
0247     if (mLoop) {
0248         mLoop->exit(exitStatus == QProcess::CrashExit ? 1 : 0);
0249     }
0250 }
0251 
0252 int Unrar::startSyncProcess(const ProcessArgs &args)
0253 {
0254     int ret = 0;
0255 
0256 #if !defined(WITH_KPTY)
0257     mProcess = new QProcess(this);
0258     connect(mProcess, &QProcess::readyReadStandardOutput, this, &Unrar::readFromStdout);
0259     connect(mProcess, &QProcess::readyReadStandardError, this, &Unrar::readFromStderr);
0260     connect(mProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &Unrar::finished);
0261 
0262 #else
0263     mProcess = new KPtyProcess(this);
0264     mProcess->setOutputChannelMode(KProcess::SeparateChannels);
0265     connect(mProcess, &KPtyProcess::readyReadStandardOutput, this, &Unrar::readFromStdout);
0266     connect(mProcess, &KPtyProcess::readyReadStandardError, this, &Unrar::readFromStderr);
0267     connect(mProcess, static_cast<void (KPtyProcess::*)(int, QProcess::ExitStatus)>(&KPtyProcess::finished), this, &Unrar::finished);
0268 
0269 #endif
0270 
0271 #if !defined(WITH_KPTY)
0272     if (helper->kind->name() == QLatin1String("unar") && args.useLsar) {
0273         mProcess->start(helper->lsarPath, args.appArgs, QIODevice::ReadWrite | QIODevice::Unbuffered);
0274     } else {
0275         mProcess->start(helper->unrarPath, args.appArgs, QIODevice::ReadWrite | QIODevice::Unbuffered);
0276     }
0277 
0278     ret = mProcess->waitForFinished(-1) ? 0 : 1;
0279 #else
0280     if (helper->kind->name() == QLatin1String("unar") && args.useLsar) {
0281         mProcess->setProgram(helper->lsarPath, args.appArgs);
0282     } else {
0283         mProcess->setProgram(helper->unrarPath, args.appArgs);
0284     }
0285 
0286     mProcess->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered);
0287     mProcess->start();
0288     QEventLoop loop;
0289     mLoop = &loop;
0290     ret = loop.exec(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents);
0291     mLoop = nullptr;
0292 #endif
0293 
0294     delete mProcess;
0295     mProcess = nullptr;
0296 
0297     return ret;
0298 }
0299 
0300 void Unrar::writeToProcess(const QByteArray &data)
0301 {
0302     if (!mProcess || data.isNull()) {
0303         return;
0304     }
0305 
0306 #if !defined(WITH_KPTY)
0307     mProcess->write(data);
0308 #else
0309     mProcess->pty()->write(data);
0310 #endif
0311 }