krarchandler.cpp 31.3 KB
Newer Older
1 2 3
/*****************************************************************************
 * Copyright (C) 2001 Shie Erlich <krusader@users.sourceforge.net>           *
 * Copyright (C) 2001 Rafi Yanai <krusader@users.sourceforge.net>            *
Davide Gianforte's avatar
Davide Gianforte committed
4
 * Copyright (C) 2004-2020 Krusader Krew [https://krusader.org]              *
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *                                                                           *
 * This file is part of Krusader [https://krusader.org].                     *
 *                                                                           *
 * Krusader is free software: you can redistribute it and/or modify          *
 * it under the terms of the GNU General Public License as published by      *
 * the Free Software Foundation, either version 2 of the License, or         *
 * (at your option) any later version.                                       *
 *                                                                           *
 * Krusader is distributed in the hope that it will be useful,               *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 * GNU General Public License for more details.                              *
 *                                                                           *
 * You should have received a copy of the GNU General Public License         *
 * along with Krusader.  If not, see [http://www.gnu.org/licenses/].         *
 *****************************************************************************/
Fathi Boudra's avatar
Fathi Boudra committed
21 22 23

#include "krarchandler.h"

24 25 26 27 28 29
// QtCore
#include <QDebug>
#include <QDir>
#include <QFile>
// QtWidgets
#include <QApplication>
Fathi Boudra's avatar
Fathi Boudra committed
30

31
#include <KArchive/KTar>
32
#include <KConfigCore/KSharedConfig>
Simon Persson's avatar
Simon Persson committed
33
#include <KI18n/KLocalizedString>
34
#include <KIO/Global>
35
#include <KIOCore/KProtocolManager>
36
#include <KWallet/KWallet>
37
#include <KWidgetsAddons/KMessageBox>
38
#include <KWidgetsAddons/KPasswordDialog>
39
#include <utility>
Fathi Boudra's avatar
Fathi Boudra committed
40

41
#include "kr7zencryptionchecker.h"
42
#include "../krglobal.h"
Rafi Yanai's avatar
Rafi Yanai committed
43
#include "../defaults.h"
44
#include "../krservices.h"
45
#include "../Dialogs/krpleasewait.h"
46
#include "../../krArc/krlinecountingprocess.h"
Rafi Yanai's avatar
Rafi Yanai committed
47

48
#if 0
49
class DefaultKrArcObserver : public KrArcObserver
50 51
{
public:
52 53
    DefaultKrArcObserver() {}
    virtual ~DefaultKrArcObserver() {}
Fathi Boudra's avatar
Fathi Boudra committed
54

55
    virtual void processEvents() override {
Fathi Boudra's avatar
Fathi Boudra committed
56 57 58 59
        usleep(1000);
        qApp->processEvents();
    }

60
    virtual void subJobStarted(const QString & jobTitle, qulonglong count) override {
Fathi Boudra's avatar
Fathi Boudra committed
61 62 63
        krApp->startWaiting(jobTitle, count, true);
    }

64
    virtual void subJobStopped() override {
Fathi Boudra's avatar
Fathi Boudra committed
65 66 67
        krApp->stopWait();
    }

68
    virtual bool wasCancelled() override {
Fathi Boudra's avatar
Fathi Boudra committed
69 70 71
        return krApp->wasWaitingCancelled();
    }

72
    virtual void error(const QString & error) override {
Fathi Boudra's avatar
Fathi Boudra committed
73 74 75
        KMessageBox::error(krApp, error, i18n("Error"));
    }

76
    virtual void detailedError(const QString & error, const QString & details) override {
Fathi Boudra's avatar
Fathi Boudra committed
77 78 79
        KMessageBox::detailedError(krApp, error, details, i18n("Error"));
    }

80
    virtual void incrementProgress(int c) override {
Fathi Boudra's avatar
Fathi Boudra committed
81 82
        krApp->plzWait->incProgress(c);
    }
83
};
84
#endif
85

86
static QStringList arcProtocols = QString("tar;bzip;bzip2;lzma;xz;gzip;krarc;zip").split(';');
87

88 89
QMap<QString, QString>* KrArcHandler::slaveMap = nullptr;
KWallet::Wallet * KrArcHandler::wallet = nullptr;
90

91
KrArcHandler::KrArcHandler(QObject *parent) : QObject(parent)
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
{
    // Reminder: If a mime type is added/removed/modified in that
    // member function, it's important to research if the type has to
    // be added/removed/modified in the `krarc.protocol` file, or
    // in `KrArcBaseManager::getShortTypeFromMime(const QString &mime)`

    // Hard-code these proven mimetypes openable by krarc protocol.
    // They cannot be listed in krarc.protocol itself
    // because it would baffle other file managers (like Dolphin).
    krarcArchiveMimetypes = { QStringLiteral("application/x-deb"),
                              QStringLiteral("application/x-debian-package"),
                              QStringLiteral("application/vnd.debian.binary-package"),
                              QStringLiteral("application/x-java-archive"),
                              QStringLiteral("application/x-rpm"),
                              QStringLiteral("application/x-source-rpm"),
                              QStringLiteral("application/vnd.oasis.opendocument.chart"),
                              QStringLiteral("application/vnd.oasis.opendocument.database"),
                              QStringLiteral("application/vnd.oasis.opendocument.formula"),
                              QStringLiteral("application/vnd.oasis.opendocument.graphics"),
                              QStringLiteral("application/vnd.oasis.opendocument.presentation"),
                              QStringLiteral("application/vnd.oasis.opendocument.spreadsheet"),
                              QStringLiteral("application/vnd.oasis.opendocument.text"),
                              QStringLiteral("application/vnd.openxmlformats-officedocument.presentationml.presentation"),
                              QStringLiteral("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
                              QStringLiteral("application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
                              QStringLiteral("application/x-cbz"),
                              QStringLiteral("application/vnd.comicbook+zip"),
                              QStringLiteral("application/x-cbr"),
                              QStringLiteral("application/vnd.comicbook-rar"),
                              QStringLiteral("application/epub+zip"),
                              QStringLiteral("application/x-webarchive"),
                              QStringLiteral("application/x-plasma"),
                              QStringLiteral("application/vnd.rar") };

    #ifdef KRARC_QUERY_ENABLED
127 128 129
    auto mimetypes = KProtocolInfo::archiveMimetypes("krarc");
    for (const auto &mimetype : mimetypes)
        krarcArchiveMimetypes.insert(mimetype);
130 131 132
    #endif
}

133
QStringList KrArcHandler::supportedPackers()
Fathi Boudra's avatar
Fathi Boudra committed
134 135 136 137 138 139 140 141
{
    QStringList packers;

    // we will simply try to find the packers here..
    if (KrServices::cmdExist("tar")) packers.append("tar");
    if (KrServices::cmdExist("gzip")) packers.append("gzip");
    if (KrServices::cmdExist("bzip2")) packers.append("bzip2");
    if (KrServices::cmdExist("lzma")) packers.append("lzma");
142
    if (KrServices::cmdExist("xz")) packers.append("xz");
Fathi Boudra's avatar
Fathi Boudra committed
143 144
    if (KrServices::cmdExist("unzip")) packers.append("unzip");
    if (KrServices::cmdExist("zip")) packers.append("zip");
Davide Gianforte's avatar
Davide Gianforte committed
145
    if (KrServices::cmdExist("zip")) packers.append("cbz");
Fathi Boudra's avatar
Fathi Boudra committed
146 147 148 149
    if (KrServices::cmdExist("lha")) packers.append("lha");
    if (KrServices::cmdExist("cpio")) packers.append("cpio");
    if (KrServices::cmdExist("unrar")) packers.append("unrar");
    if (KrServices::cmdExist("rar")) packers.append("rar");
Davide Gianforte's avatar
Davide Gianforte committed
150
    if (KrServices::cmdExist("rar")) packers.append("cbr");
Fathi Boudra's avatar
Fathi Boudra committed
151 152 153 154 155 156
    if (KrServices::cmdExist("arj")) packers.append("arj");
    if (KrServices::cmdExist("unarj")) packers.append("unarj");
    if (KrServices::cmdExist("unace")) packers.append("unace");
    if (KrServices::cmdExist("dpkg")) packers.append("dpkg");
    if (KrServices::cmdExist("7z") || KrServices::cmdExist("7za")) packers.append("7z");
    if (KrServices::cmdExist("rpm") && KrServices::cmdExist("rpm2cpio")) packers.append("rpm");
157
    // qDebug() << "Supported Packers:";
Fathi Boudra's avatar
Fathi Boudra committed
158 159
    //QStringList::Iterator it;
    //for( it = packers.begin(); it != packers.end(); ++it )
160
    // qDebug() << *it;
Fathi Boudra's avatar
Fathi Boudra committed
161 162 163 164

    return packers;
}

165
bool KrArcHandler::arcSupported(QString type)
Fathi Boudra's avatar
Fathi Boudra committed
166 167
{
    // lst will contain the supported unpacker list...
168 169
    const KConfigGroup group(krConfig, "Archives");
    const QStringList lst = group.readEntry("Supported Packers", QStringList());
Fathi Boudra's avatar
Fathi Boudra committed
170

171 172 173 174 175 176
    // Let's notice that in some cases the QString `type` that arrives here
    // represents a mimetype, and in some other cases it represents
    // a short identifier.
    // If `type` is not a short identifier then it's supposed that `type` is a mime type
    if (type.length() > maxLenType) {
        type = getShortTypeFromMime(type);
177 178
    }

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    return (type == "zip" && lst.contains("unzip"))
           || (type == "tar" && lst.contains("tar"))
           || (type == "tbz" && lst.contains("tar"))
           || (type == "tgz" && lst.contains("tar"))
           || (type == "tlz" && lst.contains("tar"))
           || (type == "txz" && lst.contains("tar"))
           || (type == "tarz" && lst.contains("tar"))
           || (type == "gzip" && lst.contains("gzip"))
           || (type == "bzip2" && lst.contains("bzip2"))
           || (type == "lzma" && lst.contains("lzma"))
           || (type == "xz" && lst.contains("xz"))
           || (type == "lha" && lst.contains("lha"))
           || (type == "ace" && lst.contains("unace"))
           || (type == "rpm" && lst.contains("cpio"))
           || (type == "cpio" && lst.contains("cpio"))
           || (type == "rar" && (lst.contains("unrar") || lst.contains("rar")))
           || (type == "arj" && (lst.contains("unarj") || lst.contains("arj")))
           || (type == "deb" && (lst.contains("dpkg") && lst.contains("tar")))
           || (type == "7z" && lst.contains("7z"));
Fathi Boudra's avatar
Fathi Boudra committed
198 199
}

200
qulonglong KrArcHandler::arcFileCount(const QString& archive, const QString& type, const QString& password, KrArcObserver *observer)
Fathi Boudra's avatar
Fathi Boudra committed
201 202 203 204
{
    int divideWith = 1;

    // first check if supported
205 206
    if (!arcSupported(type))
        return 0;
Fathi Boudra's avatar
Fathi Boudra committed
207

208 209 210
    // bzip2, gzip, etc. archives contain only one file
    if (type == "bzip2" || type == "gzip" || type == "lzma" || type == "xz")
        return 1L;
Fathi Boudra's avatar
Fathi Boudra committed
211 212 213 214

    // set the right lister to do the job
    QStringList lister;

215 216 217
    if (type == "zip") lister << KrServices::fullPathName("unzip") << "-ZTs";
    else if (type == "tar") lister << KrServices::fullPathName("tar") << "-tvf";
    else if (type == "tgz") lister << KrServices::fullPathName("tar") << "-tvzf";
Fathi Boudra's avatar
Fathi Boudra committed
218
    else if (type == "tarz") lister << KrServices::fullPathName("tar") << "-tvzf";
219 220 221 222 223 224 225
    else if (type == "tbz") lister << KrServices::fullPathName("tar") << "-tjvf";
    else if (type == "tlz") lister << KrServices::fullPathName("tar") << "--lzma" << "-tvf";
    else if (type == "txz") lister << KrServices::fullPathName("tar") << "--xz" << "-tvf";
    else if (type == "lha") lister << KrServices::fullPathName("lha") << "l";
    else if (type == "rar") lister << KrServices::fullPathName(KrServices::cmdExist("rar") ? "rar" : "unrar") << "l" << "-v";
    else if (type == "ace") lister << KrServices::fullPathName("unace") << "l";
    else if (type == "arj") {
226 227
        if (KrServices::cmdExist("arj")) {
            lister << KrServices::fullPathName("arj") << "v" << "-y" << "-v";
Fathi Boudra's avatar
Fathi Boudra committed
228
            divideWith = 4;
229
        } else {
Fathi Boudra's avatar
Fathi Boudra committed
230
            lister << KrServices::fullPathName("unarj") << "l";
231
        }
232 233
    } else if (type == "rpm") lister << KrServices::fullPathName("rpm") << "--dump" << "-lpq";
    else if (type == "deb") lister << KrServices::fullPathName("dpkg") << "-c";
234
    else if (type == "7z")  lister << find7zExecutable() << "-y" << "l";
Fathi Boudra's avatar
Fathi Boudra committed
235 236 237
    else return 0L;

    if (!password.isNull()) {
238
        if (type == "arj")
Fathi Boudra's avatar
Fathi Boudra committed
239
            lister << QString("-g%1").arg(password);
240
        if (type == "ace" || type == "rar" || type == "7z")
Fathi Boudra's avatar
Fathi Boudra committed
241
            lister << QString("-p%1").arg(password);
242
    }
Fathi Boudra's avatar
Fathi Boudra committed
243 244 245 246 247

    // tell the user to wait
    observer->subJobStarted(i18n("Counting files in archive"), 0);

    // count the number of files in the archive
248
    qulonglong count = 1;
Fathi Boudra's avatar
Fathi Boudra committed
249 250
    KProcess list;
    list << lister << archive;
251
    if (type == "ace" && QFile("/dev/ptmx").exists())     // Don't remove, unace crashes if missing!!!
Fathi Boudra's avatar
Fathi Boudra committed
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
        list.setStandardInputFile("/dev/ptmx");
    list.setOutputChannelMode(KProcess::SeparateChannels); // without this output redirection has no effect
    list.start();
    // TODO make use of asynchronous process starting. waitForStarted(int msec = 30000) is blocking
    // it would be better to connect to started(), error() and finished()
    if (list.waitForStarted()) while (list.state() == QProcess::Running) {
            observer->processEvents();
            if (observer->wasCancelled())
                list.kill();
        }
    ; // busy wait - need to find something better...

    observer->subJobStopped();

    if (list.exitStatus() != QProcess::NormalExit || !checkStatus(type, list.exitCode())) {
Pino Toscano's avatar
Pino Toscano committed
267
        observer->detailedError(i18n("Failed to list the content of the archive (%1).", archive),
Fathi Boudra's avatar
Fathi Boudra committed
268 269
                                QString::fromLocal8Bit(list.readAllStandardError()));
        return 0;
270 271
    }

Fathi Boudra's avatar
Fathi Boudra committed
272 273 274 275 276 277 278 279
    count = list.readAllStandardOutput().count('\n');

    //make sure you call stopWait after this function return...
    //  observer->subJobStopped();

    return count / divideWith;
}

280
bool KrArcHandler::unpack(QString archive, const QString& type, const QString& password, const QString& dest, KrArcObserver *observer)
Fathi Boudra's avatar
Fathi Boudra committed
281 282 283 284
{
    KConfigGroup group(krConfig, "Archives");
    if (group.readEntry("Test Before Unpack", _TestBeforeUnpack)) {
        // test first - or be sorry later...
285
        if (type != "rpm" && type != "deb" && !test(archive, type, password, observer, 0)) {
Pino Toscano's avatar
Pino Toscano committed
286
            observer->error(i18n("Failed to unpack %1.", archive));
Fathi Boudra's avatar
Fathi Boudra committed
287 288
            return false;
        }
289
    }
Fathi Boudra's avatar
Fathi Boudra committed
290 291

    // count the files in the archive
292
    qulonglong count = arcFileCount(archive, type, password, observer);
293 294 295 296
    if (count == 0)
        return false;   // not supported
    if (count == 1)
        count = 0;
Fathi Boudra's avatar
Fathi Boudra committed
297 298 299 300 301 302

    // choose the right packer for the job
    QString cpioName;
    QStringList packer;

    // set the right packer to do the job
303
    if (type == "zip") packer << KrServices::fullPathName("unzip") << "-o";
304 305
    else if (type == "tar") packer << KrServices::fullPathName("tar") << "-xvf";
    else if (type == "tgz") packer << KrServices::fullPathName("tar") << "-xvzf";
Fathi Boudra's avatar
Fathi Boudra committed
306
    else if (type == "tarz") packer << KrServices::fullPathName("tar") << "-xvzf";
307 308 309
    else if (type == "tbz") packer << KrServices::fullPathName("tar") << "-xjvf";
    else if (type == "tlz") packer << KrServices::fullPathName("tar") << "--lzma" << "-xvf";
    else if (type == "txz") packer << KrServices::fullPathName("tar") << "--xz" << "-xvf";
Fathi Boudra's avatar
Fathi Boudra committed
310
    else if (type == "gzip") packer << KrServices::fullPathName("gzip") << "-cd";
311
    else if (type == "bzip2") packer << KrServices::fullPathName("bzip2") << "-cdk";
Fathi Boudra's avatar
Fathi Boudra committed
312
    else if (type == "lzma") packer << KrServices::fullPathName("lzma") << "-cdk";
313 314 315 316 317
    else if (type == "xz")  packer << KrServices::fullPathName("xz") << "-cdk";
    else if (type == "lha") packer << KrServices::fullPathName("lha") << "xf";
    else if (type == "rar") packer << KrServices::fullPathName(KrServices::cmdExist("rar") ? "rar" : "unrar") << "-y" << "x";
    else if (type == "ace") packer << KrServices::fullPathName("unace") << "x";
    else if (type == "arj") {
Fathi Boudra's avatar
Fathi Boudra committed
318 319 320 321
        if (KrServices::cmdExist("arj"))
            packer << KrServices::fullPathName("arj") << "-y" << "-v" << "x";
        else
            packer << KrServices::fullPathName("unarj") << "x";
322
    } else if (type == "7z")  packer << find7zExecutable() << "-y" << "x";
323
    else if (type == "rpm") {
324 325
        // TODO use QTemporaryFile (setAutoRemove(false) when asynchrone)
        cpioName = QDir::tempPath() + QStringLiteral("/contents.cpio");
Fathi Boudra's avatar
Fathi Boudra committed
326 327 328 329 330 331

        KrLinecountingProcess cpio;
        cpio << KrServices::fullPathName("rpm2cpio") << archive;
        cpio.setStandardOutputFile(cpioName); // TODO maybe no tmpfile but a pipe (setStandardOutputProcess(packer))
        cpio.start();
        if (!cpio.waitForFinished() || cpio.exitStatus() != QProcess::NormalExit || !checkStatus("cpio", cpio.exitCode())) {
Pino Toscano's avatar
Pino Toscano committed
332
            observer->detailedError(i18n("Failed to convert rpm (%1) to cpio.", archive), cpio.getErrorMsg());
Fathi Boudra's avatar
Fathi Boudra committed
333 334 335 336 337
            return 0;
        }

        archive = cpioName;
        packer << KrServices::fullPathName("cpio") << "--force-local" << "--no-absolute-filenames" <<  "-iuvdF";
338
    } else if (type == "deb") {
339 340
        // TODO use QTemporaryFile (setAutoRemove(false) when asynchrone)
        cpioName = QDir::tempPath() + QStringLiteral("/contents.tar");
Fathi Boudra's avatar
Fathi Boudra committed
341 342 343 344 345

        KrLinecountingProcess dpkg;
        dpkg << KrServices::fullPathName("dpkg") << "--fsys-tarfile" << archive;
        dpkg.setStandardOutputFile(cpioName); // TODO maybe no tmpfile but a pipe (setStandardOutputProcess(packer))
        dpkg.start();
346
        if (!dpkg.waitForFinished() || dpkg.exitStatus() != QProcess::NormalExit || !checkStatus("deb", dpkg.exitCode())) {
Pino Toscano's avatar
Pino Toscano committed
347
            observer->detailedError(i18n("Failed to convert deb (%1) to tar.", archive), dpkg.getErrorMsg());
Fathi Boudra's avatar
Fathi Boudra committed
348 349 350 351 352 353 354 355
            return 0;
        }

        archive = cpioName;
        packer << KrServices::fullPathName("tar") << "xvf";
    } else return false;

    if (!password.isNull()) {
356
        if (type == "zip")
Fathi Boudra's avatar
Fathi Boudra committed
357
            packer << "-P" << password;
358
        if (type == "arj")
Fathi Boudra's avatar
Fathi Boudra committed
359
            packer << QString("-g%1").arg(password);
360
        if (type == "ace" || type == "rar" || type == "7z")
Fathi Boudra's avatar
Fathi Boudra committed
361
            packer << QString("-p%1").arg(password);
362
    }
Fathi Boudra's avatar
Fathi Boudra committed
363 364 365 366

    // unpack the files
    KrLinecountingProcess proc;
    proc << packer << archive;
367
    if (type == "bzip2" || type == "gzip" || type == "lzma" || type == "xz") {
Fathi Boudra's avatar
Fathi Boudra committed
368 369
        QString arcname = archive.mid(archive.lastIndexOf("/") + 1);
        if (arcname.contains(".")) arcname = arcname.left(arcname.lastIndexOf("."));
370
            proc.setStandardOutputFile(dest + '/' + arcname);
371
    }
372
    if (type == "ace" && QFile("/dev/ptmx").exists())    // Don't remove, unace crashes if missing!!!
Fathi Boudra's avatar
Fathi Boudra committed
373 374 375 376 377 378 379
        proc.setStandardInputFile("/dev/ptmx");

    proc.setWorkingDirectory(dest);

    // tell the user to wait
    observer->subJobStarted(i18n("Unpacking File(s)"), count);
    if (count != 0) {
380
        connect(&proc, &KrLinecountingProcess::newOutputLines, observer, &KrArcObserver::incrementProgress);
381
        if (type == "rpm")
382
            connect(&proc, &KrLinecountingProcess::newErrorLines, observer, &KrArcObserver::incrementProgress);
383
    }
Fathi Boudra's avatar
Fathi Boudra committed
384 385 386 387 388

    // start the unpacking process
    proc.start();
    // TODO make use of asynchronous process starting. waitForStarted(int msec = 30000) is blocking
    // it would be better to connect to started(), error() and finished()
389 390
    if (proc.waitForStarted())
        while (proc.state() == QProcess::Running) {
Fathi Boudra's avatar
Fathi Boudra committed
391 392 393 394 395 396 397 398 399 400 401 402
            observer->processEvents();
            if (observer->wasCancelled())
                proc.kill();
        }
    ; // busy wait - need to find something better...
    observer->subJobStopped();

    if (!cpioName.isEmpty())
        QFile(cpioName).remove();      /* remove the cpio file */

    // check the return value
    if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(type, proc.exitCode())) {
Pino Toscano's avatar
Pino Toscano committed
403
        observer->detailedError(i18n("Failed to unpack %1.", archive),
Fathi Boudra's avatar
Fathi Boudra committed
404 405
                                observer->wasCancelled() ? i18n("User cancelled.") : proc.getErrorMsg());
        return false;
406
    }
Fathi Boudra's avatar
Fathi Boudra committed
407 408
    return true; // SUCCESS
}
409

410
bool KrArcHandler::test(const QString& archive, const QString& type, const QString& password, KrArcObserver *observer, qulonglong count)
Fathi Boudra's avatar
Fathi Boudra committed
411 412 413 414 415
{
    // choose the right packer for the job
    QStringList packer;

    // set the right packer to do the job
416 417 418
    if (type == "zip") packer << KrServices::fullPathName("unzip") << "-t";
    else if (type == "tar") packer << KrServices::fullPathName("tar") << "-tvf";
    else if (type == "tgz") packer << KrServices::fullPathName("tar") << "-tvzf";
Fathi Boudra's avatar
Fathi Boudra committed
419
    else if (type == "tarz") packer << KrServices::fullPathName("tar") << "-tvzf";
420 421 422
    else if (type == "tbz") packer << KrServices::fullPathName("tar") << "-tjvf";
    else if (type == "tlz") packer << KrServices::fullPathName("tar") << "--lzma" << "-tvf";
    else if (type == "txz") packer << KrServices::fullPathName("tar") << "--xz" << "-tvf";
Fathi Boudra's avatar
Fathi Boudra committed
423
    else if (type == "gzip") packer << KrServices::fullPathName("gzip") << "-tv";
424
    else if (type == "bzip2") packer << KrServices::fullPathName("bzip2") << "-tv";
Fathi Boudra's avatar
Fathi Boudra committed
425
    else if (type == "lzma") packer << KrServices::fullPathName("lzma") << "-tv";
426 427 428 429 430
    else if (type == "xz")  packer << KrServices::fullPathName("xz") << "-tv";
    else if (type == "rar") packer << KrServices::fullPathName(KrServices::cmdExist("rar") ? "rar" : "unrar") << "t";
    else if (type == "ace") packer << KrServices::fullPathName("unace") << "t";
    else if (type == "lha") packer << KrServices::fullPathName("lha") << "t";
    else if (type == "arj") packer << KrServices::fullPathName(KrServices::cmdExist("arj") ? "arj" : "unarj") << "t";
Fathi Boudra's avatar
Fathi Boudra committed
431
    else if (type == "cpio") packer << KrServices::fullPathName("cpio") << "--only-verify-crc" << "-tvF";
432
    else if (type == "7z")  packer << find7zExecutable() << "-y" << "t";
Fathi Boudra's avatar
Fathi Boudra committed
433 434 435
    else return false;

    if (!password.isNull()) {
436
        if (type == "zip")
Fathi Boudra's avatar
Fathi Boudra committed
437
            packer << "-P" << password;
438
        if (type == "arj")
Fathi Boudra's avatar
Fathi Boudra committed
439
            packer << QString("-g%1").arg(password);
440
        if (type == "ace" || type == "rar" || type == "7z")
Fathi Boudra's avatar
Fathi Boudra committed
441 442
            packer << QString("-p%1").arg(password);
    }
443

Fathi Boudra's avatar
Fathi Boudra committed
444 445 446 447
    // unpack the files
    KrLinecountingProcess proc;
    proc << packer << archive;

448
    if (type == "ace" && QFile("/dev/ptmx").exists())    // Don't remove, unace crashes if missing!!!
Fathi Boudra's avatar
Fathi Boudra committed
449 450 451 452 453
        proc.setStandardInputFile("/dev/ptmx");

    // tell the user to wait
    observer->subJobStarted(i18n("Testing Archive"), count);
    if (count != 0)
454
        connect(&proc, &KrLinecountingProcess::newOutputLines, observer, &KrArcObserver::incrementProgress);
Fathi Boudra's avatar
Fathi Boudra committed
455 456 457 458 459

    // start the unpacking process
    proc.start();
    // TODO make use of asynchronous process starting. waitForStarted(int msec = 30000) is blocking
    // it would be better to connect to started(), error() and finished()
460 461
    if (proc.waitForStarted())
        while (proc.state() == QProcess::Running) {
Fathi Boudra's avatar
Fathi Boudra committed
462 463 464 465 466 467 468 469 470 471 472 473 474 475
            observer->processEvents();
            if (observer->wasCancelled())
                proc.kill();
        }
    ; // busy wait - need to find something better...
    observer->subJobStopped();

    // check the return value
    if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(type, proc.exitCode()))
        return false;

    return true; // SUCCESS
}

476
bool KrArcHandler::pack(QStringList fileNames, QString type, const QString& dest, qulonglong count, QMap<QString, QString> extraProps, KrArcObserver *observer)
Fathi Boudra's avatar
Fathi Boudra committed
477 478 479 480 481
{
    // set the right packer to do the job
    QStringList packer;

    if (type == "zip") {
482
        packer << KrServices::fullPathName("zip") << "-ry";
Davide Gianforte's avatar
Davide Gianforte committed
483
    } else if (type == "cbz") {
484 485
        packer << KrServices::fullPathName("zip") << "-ry";
        type = "zip";
Fathi Boudra's avatar
Fathi Boudra committed
486
    } else if (type == "tar") {
487
        packer << KrServices::fullPathName("tar") << "-cvf";
Fathi Boudra's avatar
Fathi Boudra committed
488
    } else if (type == "tar.gz") {
489 490
        packer << KrServices::fullPathName("tar") << "-cvzf";
        type = "tgz";
Fathi Boudra's avatar
Fathi Boudra committed
491
    } else if (type == "tar.bz2") {
492 493
        packer << KrServices::fullPathName("tar") << "-cvjf";
        type = "tbz";
Fathi Boudra's avatar
Fathi Boudra committed
494
    } else if (type == "tar.lzma") {
495 496
        packer << KrServices::fullPathName("tar") << "--lzma" << "-cvf";
        type = "tlz";
497
    } else if (type == "tar.xz") {
498 499
        packer << KrServices::fullPathName("tar") << "--xz" << "-cvf";
        type = "txz";
Fathi Boudra's avatar
Fathi Boudra committed
500
    } else if (type == "rar") {
501
        packer << KrServices::fullPathName("rar") << "-r" << "a";
Davide Gianforte's avatar
Davide Gianforte committed
502
    } else if (type == "cbr") {
503 504
        packer << KrServices::fullPathName("rar") << "-r" << "a";
        type = "rar";
Fathi Boudra's avatar
Fathi Boudra committed
505
    } else if (type == "lha") {
506
        packer << KrServices::fullPathName("lha") << "a";
Fathi Boudra's avatar
Fathi Boudra committed
507
    } else if (type == "arj") {
508
        packer << KrServices::fullPathName("arj") << "-r" << "-y" << "a";
Fathi Boudra's avatar
Fathi Boudra committed
509
    } else if (type == "7z") {
510
        packer << find7zExecutable() << "-y" << "a";
Fathi Boudra's avatar
Fathi Boudra committed
511 512
    } else return false;

Fathi Boudra's avatar
Fathi Boudra committed
513
    QString password;
Fathi Boudra's avatar
Fathi Boudra committed
514 515 516 517 518

    if (extraProps.count("Password") > 0) {
        password = extraProps[ "Password" ];

        if (!password.isNull()) {
519
            if (type == "zip")
Fathi Boudra's avatar
Fathi Boudra committed
520
                packer << "-P" << password;
521
            else if (type == "arj")
Fathi Boudra's avatar
Fathi Boudra committed
522
                packer << QString("-g%1").arg(password);
523
            else if (type == "ace" || type == "7z")
Fathi Boudra's avatar
Fathi Boudra committed
524
                packer << QString("-p%1").arg(password);
525
            else if (type == "rar") {
Fathi Boudra's avatar
Fathi Boudra committed
526 527 528 529 530
                if (extraProps.count("EncryptHeaders") > 0)
                    packer << QString("-hp%1").arg(password);
                else
                    packer << QString("-p%1").arg(password);
            } else
Fathi Boudra's avatar
Fathi Boudra committed
531
                password.clear();
Fathi Boudra's avatar
Fathi Boudra committed
532
        }
Csaba Karai's avatar
Csaba Karai committed
533
    }
Fathi Boudra's avatar
Fathi Boudra committed
534 535 536 537 538 539

    if (extraProps.count("VolumeSize") > 0) {
        QString sizeStr = extraProps[ "VolumeSize" ];
        KIO::filesize_t size = sizeStr.toLongLong();

        if (size >= 10000) {
540
            if (type == "arj" || type == "rar")
Fathi Boudra's avatar
Fathi Boudra committed
541 542
                packer << QString("-v%1b").arg(sizeStr);
        }
543 544
    }

Fathi Boudra's avatar
Fathi Boudra committed
545 546 547 548 549 550 551
    if (extraProps.count("CompressionLevel") > 0) {
        int level = extraProps[ "CompressionLevel" ].toInt() - 1;
        if (level < 0)
            level = 0;
        if (level > 8)
            level = 8;

552
        if (type == "rar") {
Fathi Boudra's avatar
Fathi Boudra committed
553 554
            static const int rarLevels[] = { 0, 1, 2, 2, 3, 3, 4, 4, 5 };
            packer << QString("-m%1").arg(rarLevels[ level ]);
555
        } else if (type == "arj") {
Fathi Boudra's avatar
Fathi Boudra committed
556 557
            static const int arjLevels[] = { 0, 4, 4, 3, 3, 2, 2, 1, 1 };
            packer << QString("-m%1").arg(arjLevels[ level ]);
558
        } else if (type == "zip") {
Fathi Boudra's avatar
Fathi Boudra committed
559 560
            static const int zipLevels[] = { 0, 1, 2, 4, 5, 6, 7, 8, 9 };
            packer << QString("-%1").arg(zipLevels[ level ]);
561
        } else if (type == "7z") {
Fathi Boudra's avatar
Fathi Boudra committed
562 563 564
            static const int sevenZipLevels[] = { 0, 1, 2, 4, 5, 6, 7, 8, 9 };
            packer << QString("-mx%1").arg(sevenZipLevels[ level ]);
        }
565 566
    }

Fathi Boudra's avatar
Fathi Boudra committed
567 568 569 570 571 572 573
    if (extraProps.count("CommandLineSwitches") > 0)
        packer << QString("%1").arg(extraProps[ "CommandLineSwitches" ]);

    // prepare to pack
    KrLinecountingProcess proc;
    proc << packer << dest;

574 575
    for (auto & fileName : fileNames) {
        proc << fileName;
576 577
    }

Fathi Boudra's avatar
Fathi Boudra committed
578 579 580
    // tell the user to wait
    observer->subJobStarted(i18n("Packing File(s)"), count);
    if (count != 0)
581
        connect(&proc, &KrLinecountingProcess::newOutputLines, observer, &KrArcObserver::incrementProgress);
Fathi Boudra's avatar
Fathi Boudra committed
582 583 584 585 586

    // start the packing process
    proc.start();
    // TODO make use of asynchronous process starting. waitForStarted(int msec = 30000) is blocking
    // it would be better to connect to started(), error() and finished()
587 588
    if (proc.waitForStarted())
        while (proc.state() == QProcess::Running) {
Fathi Boudra's avatar
Fathi Boudra committed
589 590 591 592 593 594 595 596 597
            observer->processEvents();
            if (observer->wasCancelled())
                proc.kill();
        }
    ; // busy wait - need to find something better...
    observer->subJobStopped();

    // check the return value
    if (proc.exitStatus() != QProcess::NormalExit || !checkStatus(type, proc.exitCode())) {
Pino Toscano's avatar
Pino Toscano committed
598
        observer->detailedError(i18n("Failed to pack %1.", dest),
Fathi Boudra's avatar
Fathi Boudra committed
599 600 601 602 603 604
                                observer->wasCancelled() ? i18n("User cancelled.") : proc.getErrorMsg());
        return false;
    }

    KConfigGroup group(krConfig, "Archives");
    if (group.readEntry("Test Archives", _TestArchives) &&
605
            !test(dest, type, password, observer, count)) {
Pino Toscano's avatar
Pino Toscano committed
606
        observer->error(i18n("Failed to pack %1.", dest));
Fathi Boudra's avatar
Fathi Boudra committed
607
        return false;
608
    }
Fathi Boudra's avatar
Fathi Boudra committed
609
    return true; // SUCCESS
Rafi Yanai's avatar
Rafi Yanai committed
610
}
611

612
bool KrArcHandler::openWallet()
613 614 615 616 617 618 619 620 621
{
    if (!wallet) {
        // find a suitable parent window
        QWidget *actWindow = QApplication::activeWindow();
        if (!actWindow)
            actWindow = (QWidget*) QApplication::desktop();

        wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), actWindow->effectiveWinId());
    }
622
    return (wallet != nullptr);
623 624
}

625
QString KrArcHandler::getPassword(const QString& path)
Fathi Boudra's avatar
Fathi Boudra committed
626 627 628 629 630 631
{
    QString password;

    QString key = "krarc-" + path;

    if (!KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), key)) {
632
        if (!KWallet::Wallet::isOpen(KWallet::Wallet::NetworkWallet())  && wallet != nullptr) {
Fathi Boudra's avatar
Fathi Boudra committed
633
            delete wallet;
634
            wallet = nullptr;
Fathi Boudra's avatar
Fathi Boudra committed
635
        }
636
        if (openWallet() && wallet->hasFolder(KWallet::Wallet::PasswordFolder())) {
Fathi Boudra's avatar
Fathi Boudra committed
637 638 639 640 641 642 643 644 645 646 647 648
            wallet->setFolder(KWallet::Wallet::PasswordFolder());
            QMap<QString, QString> map;
            if (wallet->readMap(key, map) == 0) {
                QMap<QString, QString>::const_iterator it = map.constFind("password");
                if (it != map.constEnd())
                    password = it.value();
            }
        }
    }

    bool keep = true;
    QString user = "archive";
649
    QPointer<KPasswordDialog> passDlg = new KPasswordDialog(nullptr, KPasswordDialog::ShowKeepPassword);
650 651
            passDlg->setPrompt(i18n("This archive is encrypted, please supply the password:") ),
            passDlg->setUsername(user);
652
    passDlg->setPassword(password);
653
    if (passDlg->exec() == KPasswordDialog::Accepted) {
654
        password = passDlg->password();
Fathi Boudra's avatar
Fathi Boudra committed
655
        if (keep) {
656
            if (!KWallet::Wallet::isOpen(KWallet::Wallet::NetworkWallet()) && wallet != nullptr) {
Fathi Boudra's avatar
Fathi Boudra committed
657
                delete wallet;
658
                wallet = nullptr;
Fathi Boudra's avatar
Fathi Boudra committed
659
            }
660
            if (openWallet()) {
Fathi Boudra's avatar
Fathi Boudra committed
661 662 663 664 665 666 667 668 669 670 671 672
                bool ok = true;
                if (!wallet->hasFolder(KWallet::Wallet::PasswordFolder()))
                    ok = wallet->createFolder(KWallet::Wallet::PasswordFolder());
                if (ok) {
                    wallet->setFolder(KWallet::Wallet::PasswordFolder());
                    QMap<QString, QString> map;
                    map.insert("login", "archive");
                    map.insert("password", password);
                    wallet->writeMap(key, map);
                }
            }
        }
Csaba Karai's avatar
Csaba Karai committed
673
        delete passDlg;
Fathi Boudra's avatar
Fathi Boudra committed
674 675
        return password;
    }
676
    delete passDlg;
Fathi Boudra's avatar
Fathi Boudra committed
677 678

    return "";
Rafi Yanai's avatar
Rafi Yanai committed
679
}
680

681
bool KrArcHandler::isArchive(const QUrl &url)
Fathi Boudra's avatar
Fathi Boudra committed
682
{
683
    QString protocol = url.scheme();
Fathi Boudra's avatar
Fathi Boudra committed
684 685 686
    if (arcProtocols.indexOf(protocol) != -1)
        return true;
    else return false;
Rafi Yanai's avatar
Rafi Yanai committed
687
}
688

689
QString KrArcHandler::getType(bool &encrypted, QString fileName, const QString& mime, bool checkEncrypted, bool fast)
Fathi Boudra's avatar
Fathi Boudra committed
690
{
691
    QString result = detectArchive(encrypted, std::move(fileName), checkEncrypted, fast);
692
    if (result.isNull()) {
693 694
        // Then the type is based on the mime type
        return getShortTypeFromMime(mime);
695
    }
696
    return result;
Fathi Boudra's avatar
Fathi Boudra committed
697 698
}

699
bool KrArcHandler::checkStatus(const QString& type, int exitCode)
Fathi Boudra's avatar
Fathi Boudra committed
700
{
701
    return KrArcBaseManager::checkStatus(type, exitCode);
Rafi Yanai's avatar
Rafi Yanai committed
702
}
703

704
void KrArcHandler::checkIf7zIsEncrypted(bool &encrypted, QString fileName)
Fathi Boudra's avatar
Fathi Boudra committed
705
{
706 707 708
    // Reminder: If that function is modified, it's important to research if the
    // changes must also be applied to `kio_krarcProtocol::checkIf7zIsEncrypted()`

709 710
    Kr7zEncryptionChecker proc;
    // TODO incorporate all this in Kr7zEncryptionChecker
711
    proc << find7zExecutable() << "-y" << "t";
712 713 714 715
    proc << fileName;
    proc.start();
    proc.waitForFinished();
    encrypted = proc.isEncrypted();
Rafi Yanai's avatar
Rafi Yanai committed
716 717
}

718
QString KrArcHandler::registeredProtocol(const QString& mimetype)
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740
{
    if (slaveMap == nullptr) {
        slaveMap = new QMap<QString, QString>();

        KConfigGroup group(krConfig, "Protocols");
        QStringList protList = group.readEntry("Handled Protocols", QStringList());
        for (auto & it : protList) {
            QStringList mimes = group.readEntry(QString("Mimes For %1").arg(it), QStringList());
            for (auto & mime : mimes)
                (*slaveMap)[mime] = it;
        }
    }
    QString protocol = (*slaveMap)[mimetype];
    if (protocol.isEmpty()) {
        if (krarcArchiveMimetypes.contains(mimetype)) {
            return QStringLiteral("krarc");
        }
        protocol = KProtocolManager::protocolForArchiveMimetype(mimetype);
    }
    return protocol;
}

741
void KrArcHandler::clearProtocolCache()
742 743 744 745 746 747
{
    if (slaveMap)
        delete slaveMap;
    slaveMap = nullptr;
}