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

0001 /*
0002  * SPDX-FileCopyrightText: 2009 Harald Hvaal <haraldhv@stud.ntnu.no>
0003  * SPDX-FileCopyrightText: 2010-2011, 2014 Raphael Kubo da Costa <rakuco@FreeBSD.org>
0004  * SPDX-FileCopyrightText: 2015-2016 Ragnar Thomsen <rthomsen6@gmail.com>
0005  * SPDX-FileCopyrightText: 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
0006  *
0007  * SPDX-License-Identifier: GPL-2.0-or-later
0008  *
0009  */
0010 
0011 #include "cliplugin.h"
0012 #include "archiveentry.h"
0013 #include "ark_debug.h"
0014 
0015 #include <QDateTime>
0016 
0017 #include <KLocalizedString>
0018 #include <KPluginFactory>
0019 
0020 using namespace Kerfuffle;
0021 
0022 K_PLUGIN_CLASS_WITH_JSON(CliPlugin, "kerfuffle_clirar.json")
0023 
0024 CliPlugin::CliPlugin(QObject *parent, const QVariantList &args)
0025     : CliInterface(parent, args)
0026     , m_parseState(ParseStateTitle)
0027     , m_isUnrar5(false)
0028     , m_isPasswordProtected(false)
0029     , m_isSolid(false)
0030     , m_isRAR5(false)
0031     , m_isLocked(false)
0032     , m_remainingIgnoreLines(1) // The first line of UNRAR output is empty.
0033     , m_linesComment(0)
0034 {
0035     qCDebug(ARK) << "Loaded cli_rar plugin";
0036 
0037     // Empty lines are needed for parsing output of unrar.
0038     setListEmptyLines(true);
0039 
0040     setupCliProperties();
0041 }
0042 
0043 CliPlugin::~CliPlugin()
0044 {
0045 }
0046 
0047 void CliPlugin::resetParsing()
0048 {
0049     m_parseState = ParseStateTitle;
0050     m_remainingIgnoreLines = 1;
0051     m_unrarVersion.clear();
0052     m_comment.clear();
0053     m_numberOfVolumes = 0;
0054 }
0055 
0056 void CliPlugin::setupCliProperties()
0057 {
0058     qCDebug(ARK) << "Setting up parameters...";
0059 
0060     m_cliProps->setProperty("captureProgress", true);
0061 
0062     m_cliProps->setProperty("addProgram", QStringLiteral("rar"));
0063     m_cliProps->setProperty("addSwitch", QStringList({QStringLiteral("a")}));
0064 
0065     m_cliProps->setProperty("deleteProgram", QStringLiteral("rar"));
0066     m_cliProps->setProperty("deleteSwitch", QStringLiteral("d"));
0067 
0068     m_cliProps->setProperty("extractProgram", QStringLiteral("unrar"));
0069     m_cliProps->setProperty("extractSwitch", QStringList{QStringLiteral("x"), QStringLiteral("-kb"), QStringLiteral("-p-")});
0070     m_cliProps->setProperty("extractSwitchNoPreserve", QStringList{QStringLiteral("e"), QStringLiteral("-kb"), QStringLiteral("-p-")});
0071 
0072     m_cliProps->setProperty("listProgram", QStringLiteral("unrar"));
0073     m_cliProps->setProperty("listSwitch", QStringList{QStringLiteral("vt"), QStringLiteral("-v")});
0074 
0075     m_cliProps->setProperty("moveProgram", QStringLiteral("rar"));
0076     m_cliProps->setProperty("moveSwitch", QStringLiteral("rn"));
0077 
0078     m_cliProps->setProperty("testProgram", QStringLiteral("unrar"));
0079     m_cliProps->setProperty("testSwitch", QStringLiteral("t"));
0080 
0081     m_cliProps->setProperty("commentSwitch", QStringList{QStringLiteral("c"), QStringLiteral("-z$CommentFile")});
0082 
0083     m_cliProps->setProperty("passwordSwitch", QStringList{QStringLiteral("-p$Password")});
0084     m_cliProps->setProperty("passwordSwitchHeaderEnc", QStringList{QStringLiteral("-hp$Password")});
0085 
0086     m_cliProps->setProperty("compressionLevelSwitch", QStringLiteral("-m$CompressionLevel"));
0087     m_cliProps->setProperty("compressionMethodSwitch",
0088                             QHash<QString, QVariant>{{QStringLiteral("application/vnd.rar"), QStringLiteral("-ma$CompressionMethod")},
0089                                                      {QStringLiteral("application/x-rar"), QStringLiteral("-ma$CompressionMethod")}});
0090     m_cliProps->setProperty("multiVolumeSwitch", QStringLiteral("-v$VolumeSizek"));
0091 
0092     m_cliProps->setProperty("testPassedPatterns", QStringList{QStringLiteral("^All OK$")});
0093     m_cliProps->setProperty("fileExistsFileNameRegExp",
0094                             QStringList{QStringLiteral("^(.+) already exists. Overwrite it"), // unrar 3 & 4
0095                                         QStringLiteral("^Would you like to replace the existing file (.+)$")}); // unrar 5
0096     m_cliProps->setProperty("fileExistsInput",
0097                             QStringList{
0098                                 QStringLiteral("Y"), // Overwrite
0099                                 QStringLiteral("N"), // Skip
0100                                 QStringLiteral("A"), // Overwrite all
0101                                 QStringLiteral("E"), // Autoskip
0102                                 QStringLiteral("Q"), // Cancel
0103                             });
0104 
0105     // rar will sometimes create multi-volume archives where first volume is
0106     // called name.part1.rar and other times name.part01.rar.
0107     m_cliProps->setProperty("multiVolumeSuffix", QStringList{QStringLiteral("part01.$Suffix"), QStringLiteral("part1.$Suffix")});
0108 }
0109 
0110 bool CliPlugin::readListLine(const QString &line)
0111 {
0112     // Ignore number of lines corresponding to m_remainingIgnoreLines.
0113     if (m_remainingIgnoreLines > 0) {
0114         --m_remainingIgnoreLines;
0115         return true;
0116     }
0117 
0118     // Parse the title line, which contains the version of unrar.
0119     if (m_parseState == ParseStateTitle) {
0120         QRegularExpression rxVersionLine(QStringLiteral("^UNRAR (\\d+\\.\\d+)( beta \\d)? .*$"));
0121         QRegularExpressionMatch matchVersion = rxVersionLine.match(line);
0122 
0123         if (matchVersion.hasMatch()) {
0124             m_parseState = ParseStateComment;
0125             m_unrarVersion = matchVersion.captured(1);
0126             qCDebug(ARK) << "UNRAR version" << m_unrarVersion << "detected";
0127             if (m_unrarVersion.toFloat() >= 5) {
0128                 m_isUnrar5 = true;
0129                 qCDebug(ARK) << "Using UNRAR 5 parser";
0130             } else {
0131                 qCDebug(ARK) << "Using UNRAR 4 parser";
0132             }
0133         } else {
0134             // If the second line doesn't contain an UNRAR title, something
0135             // is wrong.
0136             qCCritical(ARK) << "Failed to detect UNRAR output.";
0137             return false;
0138         }
0139 
0140         // Or see what version of unrar we are dealing with and call specific
0141         // handler functions.
0142     } else if (m_isUnrar5) {
0143         return handleUnrar5Line(line);
0144     } else {
0145         return handleUnrar4Line(line);
0146     }
0147     return true;
0148 }
0149 
0150 bool CliPlugin::handleUnrar5Line(const QString &line)
0151 {
0152     if (line.startsWith(QLatin1String("Cannot find volume "))) {
0153         Q_EMIT error(i18n("Failed to find all archive volumes."));
0154         return false;
0155     }
0156 
0157     switch (m_parseState) {
0158     // Parses the comment field.
0159     case ParseStateComment:
0160 
0161         // "Archive: " is printed after the comment.
0162         // FIXME: Comment itself could also contain the searched string.
0163 
0164         if (line.startsWith(QLatin1String("Archive: "))) {
0165             m_parseState = ParseStateHeader;
0166             m_comment = m_comment.trimmed();
0167             m_linesComment = m_comment.count(QLatin1Char('\n')) + 1;
0168             if (!m_comment.isEmpty()) {
0169                 qCDebug(ARK) << "Found a comment with" << m_linesComment << "lines";
0170             }
0171 
0172         } else {
0173             m_comment.append(line + QLatin1Char('\n'));
0174         }
0175 
0176         break;
0177 
0178     // Parses the header, which is whatever is between the comment field
0179     // and the entries.
0180     case ParseStateHeader:
0181 
0182         // "Details: " indicates end of header.
0183         if (line.startsWith(QLatin1String("Details: "))) {
0184             ignoreLines(1, ParseStateEntryDetails);
0185             if (line.contains(QLatin1String("volume"))) {
0186                 m_numberOfVolumes++;
0187                 if (!isMultiVolume()) {
0188                     setMultiVolume(true);
0189                     qCDebug(ARK) << "Multi-volume archive detected";
0190                 }
0191             }
0192             if (line.contains(QLatin1String("solid")) && !m_isSolid) {
0193                 m_isSolid = true;
0194                 qCDebug(ARK) << "Solid archive detected";
0195             }
0196             if (line.contains(QLatin1String("RAR 4"))) {
0197                 Q_EMIT compressionMethodFound(QStringLiteral("RAR4"));
0198             } else if (line.contains(QLatin1String("RAR 5"))) {
0199                 Q_EMIT compressionMethodFound(QStringLiteral("RAR5"));
0200                 m_isRAR5 = true;
0201             }
0202             if (line.contains(QLatin1String("lock"))) {
0203                 m_isLocked = true;
0204             }
0205         }
0206         break;
0207 
0208     // Parses the entry details for each entry.
0209     case ParseStateEntryDetails:
0210 
0211         // For multi-volume archives there is a header between the entries in
0212         // each volume.
0213         if (line.startsWith(QLatin1String("Archive: "))) {
0214             m_parseState = ParseStateHeader;
0215             return true;
0216 
0217             // Empty line indicates end of entry.
0218         } else if (line.trimmed().isEmpty() && !m_unrar5Details.isEmpty()) {
0219             handleUnrar5Entry();
0220 
0221         } else {
0222             // All detail lines should contain a colon.
0223             if (!line.contains(QLatin1Char(':'))) {
0224                 qCWarning(ARK) << "Unrecognized line:" << line;
0225                 return true;
0226             }
0227 
0228             // The details are on separate lines, so we store them in the QHash
0229             // m_unrar5Details.
0230             m_unrar5Details.insert(line.section(QLatin1Char(':'), 0, 0).trimmed().toLower(), line.section(QLatin1Char(':'), 1).trimmed());
0231         }
0232 
0233         break;
0234 
0235     default:
0236         break;
0237     }
0238     return true;
0239 }
0240 
0241 void CliPlugin::handleUnrar5Entry()
0242 {
0243     Archive::Entry *e = new Archive::Entry(this);
0244 
0245     QString compressionRatio = m_unrar5Details.value(QStringLiteral("ratio"));
0246     compressionRatio.chop(1); // Remove the '%'
0247     e->setProperty("ratio", compressionRatio);
0248 
0249     e->setProperty("timestamp", QDateTime::fromString(m_unrar5Details.value(QStringLiteral("mtime")), QStringLiteral("yyyy-MM-dd HH:mm:ss,zzz")));
0250 
0251     bool isDirectory = (m_unrar5Details.value(QStringLiteral("type")) == QLatin1String("Directory"));
0252     e->setProperty("isDirectory", isDirectory);
0253 
0254     if (isDirectory && !m_unrar5Details.value(QStringLiteral("name")).endsWith(QLatin1Char('/'))) {
0255         m_unrar5Details[QStringLiteral("name")] += QLatin1Char('/');
0256     }
0257 
0258     QString compression = m_unrar5Details.value(QStringLiteral("compression"));
0259     int optionPos = compression.indexOf(QLatin1Char('-'));
0260     if (optionPos != -1) {
0261         e->setProperty("method", compression.mid(optionPos));
0262         e->setProperty("version", compression.left(optionPos).trimmed());
0263     } else {
0264         // No method specified.
0265         e->setProperty("method", QString());
0266         e->setProperty("version", compression);
0267     }
0268 
0269     m_isPasswordProtected = m_unrar5Details.value(QStringLiteral("flags")).contains(QLatin1String("encrypted"));
0270     e->setProperty("isPasswordProtected", m_isPasswordProtected);
0271     if (m_isPasswordProtected) {
0272         m_isRAR5 ? Q_EMIT encryptionMethodFound(QStringLiteral("AES256")) : Q_EMIT encryptionMethodFound(QStringLiteral("AES128"));
0273     }
0274 
0275     e->setProperty("fullPath", m_unrar5Details.value(QStringLiteral("name")));
0276     e->setProperty("size", m_unrar5Details.value(QStringLiteral("size")));
0277     e->setProperty("compressedSize", m_unrar5Details.value(QStringLiteral("packed size")));
0278     e->setProperty("permissions", m_unrar5Details.value(QStringLiteral("attributes")));
0279     e->setProperty("CRC", m_unrar5Details.value(QStringLiteral("crc32")));
0280     e->setProperty("BLAKE2", m_unrar5Details.value(QStringLiteral("blake2")));
0281 
0282     if (e->property("permissions").toString().startsWith(QLatin1Char('l'))) {
0283         e->setProperty("link", m_unrar5Details.value(QStringLiteral("target")));
0284     }
0285 
0286     m_unrar5Details.clear();
0287     Q_EMIT entry(e);
0288 }
0289 
0290 bool CliPlugin::handleUnrar4Line(const QString &line)
0291 {
0292     if (line.startsWith(QLatin1String("Cannot find volume "))) {
0293         Q_EMIT error(i18n("Failed to find all archive volumes."));
0294         return false;
0295     }
0296 
0297     // RegExp matching end of comment field.
0298     // FIXME: Comment itself could also contain the Archive path string here.
0299     QRegularExpression rxCommentEnd(QStringLiteral("^(Solid archive|Archive|Volume) .+$"));
0300 
0301     // Three types of subHeaders can be displayed for unrar 3 and 4.
0302     // STM has 4 lines, RR has 3, and CMT has lines corresponding to
0303     // length of comment field +3. We ignore the subheaders.
0304     QRegularExpression rxSubHeader(QStringLiteral("^Data header type: (CMT|STM|RR)$"));
0305     QRegularExpressionMatch matchSubHeader;
0306 
0307     switch (m_parseState) {
0308     // Parses the comment field.
0309     case ParseStateComment:
0310 
0311         // unrar 4 outputs the following string when opening v5 RAR archives.
0312         if (line == QLatin1String("Unsupported archive format. Please update RAR to a newer version.")) {
0313             Q_EMIT error(
0314                 i18n("Your unrar executable is version %1, which is too old to handle this archive. Please update to a more recent version.", m_unrarVersion));
0315             return false;
0316         }
0317 
0318         // unrar 3 reports a non-RAR archive when opening v5 RAR archives.
0319         if (line.endsWith(QLatin1String(" is not RAR archive"))) {
0320             Q_EMIT error(i18n("Unrar reported a non-RAR archive. The installed unrar version (%1) is old. Try updating your unrar.", m_unrarVersion));
0321             return false;
0322         }
0323 
0324         // If we reach this point, then we can be sure that it's not a RAR5
0325         // archive, so assume RAR4.
0326         Q_EMIT compressionMethodFound(QStringLiteral("RAR4"));
0327 
0328         if (rxCommentEnd.match(line).hasMatch()) {
0329             if (line.startsWith(QLatin1String("Volume "))) {
0330                 m_numberOfVolumes++;
0331                 if (!isMultiVolume()) {
0332                     setMultiVolume(true);
0333                     qCDebug(ARK) << "Multi-volume archive detected";
0334                 }
0335             }
0336             if (line.startsWith(QLatin1String("Solid archive")) && !m_isSolid) {
0337                 m_isSolid = true;
0338                 qCDebug(ARK) << "Solid archive detected";
0339             }
0340 
0341             m_parseState = ParseStateHeader;
0342             m_comment = m_comment.trimmed();
0343             m_linesComment = m_comment.count(QLatin1Char('\n')) + 1;
0344             if (!m_comment.isEmpty()) {
0345                 qCDebug(ARK) << "Found a comment with" << m_linesComment << "lines";
0346             }
0347 
0348         } else {
0349             m_comment.append(line + QLatin1Char('\n'));
0350         }
0351 
0352         break;
0353 
0354     // Parses the header, which is whatever is between the comment field
0355     // and the entries.
0356     case ParseStateHeader:
0357 
0358         // Horizontal line indicates end of header.
0359         if (line.startsWith(QLatin1String("--------------------"))) {
0360             m_parseState = ParseStateEntryFileName;
0361         } else if (line.startsWith(QLatin1String("Volume "))) {
0362             m_numberOfVolumes++;
0363         } else if (line == QLatin1String("Lock is present")) {
0364             m_isLocked = true;
0365         }
0366         break;
0367 
0368     // Parses the entry name, which is on the first line of each entry.
0369     case ParseStateEntryFileName:
0370 
0371         // Ignore empty lines.
0372         if (line.trimmed().isEmpty()) {
0373             return true;
0374         }
0375 
0376         matchSubHeader = rxSubHeader.match(line);
0377 
0378         if (matchSubHeader.hasMatch()) {
0379             qCDebug(ARK) << "SubHeader of type" << matchSubHeader.captured(1) << "found";
0380             if (matchSubHeader.captured(1) == QLatin1String("STM")) {
0381                 ignoreLines(4, ParseStateEntryFileName);
0382             } else if (matchSubHeader.captured(1) == QLatin1String("CMT")) {
0383                 ignoreLines(m_linesComment + 3, ParseStateEntryFileName);
0384             } else if (matchSubHeader.captured(1) == QLatin1String("RR")) {
0385                 ignoreLines(3, ParseStateEntryFileName);
0386             }
0387             return true;
0388         }
0389 
0390         // The entries list ends with a horizontal line, followed by a
0391         // single summary line or, for multi-volume archives, another header.
0392         if (line.startsWith(QLatin1String("-----------------"))) {
0393             m_parseState = ParseStateHeader;
0394             return true;
0395 
0396             // Encrypted files are marked with an asterisk.
0397         } else if (line.startsWith(QLatin1Char('*'))) {
0398             m_isPasswordProtected = true;
0399             m_unrar4Details.append(QString(line.trimmed()).remove(0, 1)); // Remove the asterisk
0400             Q_EMIT encryptionMethodFound(QStringLiteral("AES128"));
0401 
0402             // Entry names always start at the second position, so a line not
0403             // starting with a space is not an entry name.
0404         } else if (!line.startsWith(QLatin1Char(' '))) {
0405             qCWarning(ARK) << "Unrecognized line:" << line;
0406             return true;
0407 
0408             // If we reach this, then we can assume the line is an entry name, so
0409             // save it, and move on to the rest of the entry details.
0410         } else {
0411             m_unrar4Details.append(line.trimmed());
0412         }
0413 
0414         m_parseState = ParseStateEntryDetails;
0415 
0416         break;
0417 
0418     // Parses the remainder of the entry details for each entry.
0419     case ParseStateEntryDetails:
0420 
0421         // If the line following an entry name is empty, we did something
0422         // wrong.
0423         Q_ASSERT(!line.trimmed().isEmpty());
0424 
0425         // If we reach a horizontal line, then the previous line was not an
0426         // entry name, so go back to header.
0427         if (line.startsWith(QLatin1String("-----------------"))) {
0428             m_parseState = ParseStateHeader;
0429             return true;
0430         }
0431 
0432         // In unrar 3 and 4 the details are on a single line, so we
0433         // pass a QStringList containing the details. We need to store
0434         // it due to symlinks (see below).
0435         m_unrar4Details.append(line.split(QLatin1Char(' '), Qt::SkipEmptyParts));
0436 
0437         // The details line contains 9 fields, so m_unrar4Details
0438         // should now contain 9 + the filename = 10 strings. If not, this is
0439         // not an archive entry.
0440         if (m_unrar4Details.size() != 10) {
0441             m_parseState = ParseStateHeader;
0442             return true;
0443         }
0444 
0445         // When unrar 3 and 4 list a symlink, they output an extra line
0446         // containing the link target. The extra line is output after
0447         // the line we ignore, so we first need to ignore one line.
0448         if (m_unrar4Details.at(6).startsWith(QLatin1Char('l'))) {
0449             ignoreLines(1, ParseStateLinkTarget);
0450             return true;
0451         } else {
0452             handleUnrar4Entry();
0453         }
0454 
0455         // Unrar 3 & 4 show a third line for each entry, which contains
0456         // three details: Host OS, Solid, and Old. We can ignore this
0457         // line.
0458         ignoreLines(1, ParseStateEntryFileName);
0459 
0460         break;
0461 
0462     // Parses a symlink target.
0463     case ParseStateLinkTarget:
0464 
0465         m_unrar4Details.append(QString(line).remove(QStringLiteral("-->")).trimmed());
0466         handleUnrar4Entry();
0467 
0468         m_parseState = ParseStateEntryFileName;
0469         break;
0470 
0471     default:
0472         break;
0473     }
0474 
0475     return true;
0476 }
0477 
0478 void CliPlugin::handleUnrar4Entry()
0479 {
0480     Archive::Entry *e = new Archive::Entry(this);
0481 
0482     QDateTime ts = QDateTime::fromString(QString(m_unrar4Details.at(4) + QLatin1Char(' ') + m_unrar4Details.at(5)), QStringLiteral("dd-MM-yy hh:mm"));
0483     // Unrar 3 & 4 output dates with a 2-digit year but QDateTime takes it as
0484     // 19??. Let's take 1950 as cut-off; similar to KDateTime.
0485     // Hopefully no one will create rar archives in 2051 with unrar 4...
0486     if (ts.date().year() < 1950) {
0487         // NOTE: only change the date. QDateTime::addYears() might also change the time because of DST changes.
0488         ts.setDate(ts.date().addYears(100));
0489     }
0490     e->setProperty("timestamp", ts);
0491 
0492     bool isDirectory = ((m_unrar4Details.at(6).at(0) == QLatin1Char('d')) || (m_unrar4Details.at(6).at(1) == QLatin1Char('D')));
0493     e->setProperty("isDirectory", isDirectory);
0494 
0495     if (isDirectory && !m_unrar4Details.at(0).endsWith(QLatin1Char('/'))) {
0496         m_unrar4Details[0] += QLatin1Char('/');
0497     }
0498 
0499     // Unrar reports the ratio as ((compressed size * 100) / size);
0500     // we consider ratio as (100 * ((size - compressed size) / size)).
0501     // If the archive is a multivolume archive, a string indicating
0502     // whether the archive's position in the volume is displayed
0503     // instead of the compression ratio.
0504     QString compressionRatio = m_unrar4Details.at(3);
0505     if ((compressionRatio == QLatin1String("<--")) || (compressionRatio == QLatin1String("<->")) || (compressionRatio == QLatin1String("-->"))) {
0506         compressionRatio = QLatin1Char('0');
0507     } else {
0508         compressionRatio.chop(1); // Remove the '%'
0509     }
0510     e->setProperty("ratio", compressionRatio);
0511 
0512     // TODO:
0513     // - Permissions differ depending on the system the entry was added
0514     //   to the archive.
0515     e->setProperty("fullPath", m_unrar4Details.at(0));
0516     e->setProperty("size", m_unrar4Details.at(1));
0517     e->setProperty("compressedSize", m_unrar4Details.at(2));
0518     e->setProperty("permissions", m_unrar4Details.at(6));
0519     e->setProperty("CRC", m_unrar4Details.at(7));
0520     e->setProperty("method", m_unrar4Details.at(8));
0521     e->setProperty("version", m_unrar4Details.at(9));
0522     e->setProperty("isPasswordProtected", m_isPasswordProtected);
0523 
0524     if (e->property("permissions").toString().startsWith(QLatin1Char('l'))) {
0525         e->setProperty("link", m_unrar4Details.at(10));
0526     }
0527 
0528     m_unrar4Details.clear();
0529     Q_EMIT entry(e);
0530 }
0531 
0532 bool CliPlugin::readExtractLine(const QString &line)
0533 {
0534     if (line.contains(QLatin1String("CRC failed"))) {
0535         Q_EMIT error(i18n("One or more wrong checksums"));
0536         return false;
0537     }
0538 
0539     if (line.startsWith(QLatin1String("Cannot find volume "))) {
0540         Q_EMIT error(i18n("Failed to find all archive volumes."));
0541         return false;
0542     }
0543 
0544     return true;
0545 }
0546 
0547 bool CliPlugin::hasBatchExtractionProgress() const
0548 {
0549     return true;
0550 }
0551 
0552 void CliPlugin::ignoreLines(int lines, ParseState nextState)
0553 {
0554     m_remainingIgnoreLines = lines;
0555     m_parseState = nextState;
0556 }
0557 
0558 bool CliPlugin::isPasswordPrompt(const QString &line)
0559 {
0560     return line.startsWith(QLatin1String("Enter password (will not be echoed) for"));
0561 }
0562 
0563 bool CliPlugin::isWrongPasswordMsg(const QString &line)
0564 {
0565     return (line.contains(QLatin1String("password incorrect")) || line.contains(QLatin1String("wrong password")));
0566 }
0567 
0568 bool CliPlugin::isCorruptArchiveMsg(const QString &line)
0569 {
0570     return (line == QLatin1String("Unexpected end of archive") || line.contains(QLatin1String("the file header is corrupt"))
0571             || line.endsWith(QLatin1String("checksum error")));
0572 }
0573 
0574 bool CliPlugin::isDiskFullMsg(const QString &line)
0575 {
0576     return line.contains(QLatin1String("No space left on device"));
0577 }
0578 
0579 bool CliPlugin::isFileExistsMsg(const QString &line)
0580 {
0581     return (line == QLatin1String("[Y]es, [N]o, [A]ll, n[E]ver, [R]ename, [Q]uit "));
0582 }
0583 
0584 bool CliPlugin::isFileExistsFileName(const QString &line)
0585 {
0586     return (line.startsWith(QLatin1String("Would you like to replace the existing file ")) || // unrar 5
0587             line.contains(QLatin1String(" already exists. Overwrite it"))); // unrar 3 & 4
0588 }
0589 
0590 bool CliPlugin::isLocked() const
0591 {
0592     return m_isLocked;
0593 }
0594 
0595 #include "cliplugin.moc"
0596 #include "moc_cliplugin.cpp"