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 }