File indexing completed on 2024-04-28 07:44:16

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
0004     SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
0005     SPDX-FileCopyrightText: 2001 Waldo Bastian <bastian@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "main.h"
0011 #include "filecopyjob.h"
0012 #include "kio_version.h"
0013 #include "kioexecdinterface.h"
0014 #include "statjob.h"
0015 
0016 #include <QDir>
0017 #include <QFile>
0018 
0019 #include <KAboutData>
0020 #include <KDBusService>
0021 #include <KLocalizedString>
0022 #include <KMessageBox>
0023 #include <KService>
0024 #include <QApplication>
0025 #include <QDebug>
0026 #include <copyjob.h>
0027 #include <desktopexecparser.h>
0028 #include <job.h>
0029 
0030 #include <QCommandLineOption>
0031 #include <QCommandLineParser>
0032 #include <QFileInfo>
0033 #include <QStandardPaths>
0034 #include <QThread>
0035 
0036 #include <config-kioexec.h>
0037 
0038 #if HAVE_X11
0039 #include <KStartupInfo>
0040 #include <private/qtx11extras_p.h>
0041 #endif
0042 
0043 KIOExec::KIOExec(const QStringList &args, bool tempFiles, const QString &suggestedFileName)
0044     : mExited(false)
0045     , mTempFiles(tempFiles)
0046     , mUseDaemon(false)
0047     , mSuggestedFileName(suggestedFileName)
0048     , expectedCounter(0)
0049     , command(args.first())
0050     , jobCounter(0)
0051 {
0052     qDebug() << "command=" << command << "args=" << args;
0053 
0054     for (int i = 1; i < args.count(); i++) {
0055         const QUrl urlArg = QUrl::fromUserInput(args.value(i));
0056         if (!urlArg.isValid()) {
0057             KMessageBox::error(nullptr, i18n("Invalid URL: %1", args.value(i)));
0058             exit(1);
0059         }
0060         KIO::StatJob *mostlocal = KIO::mostLocalUrl(urlArg);
0061         bool b = mostlocal->exec();
0062         if (!b) {
0063             KMessageBox::error(nullptr, i18n("File not found: %1", urlArg.toDisplayString()));
0064             exit(1);
0065         }
0066         Q_ASSERT(b);
0067         const QUrl url = mostlocal->mostLocalUrl();
0068 
0069         // kDebug() << "url=" << url.url() << " filename=" << url.fileName();
0070         // A local file, not an URL ?
0071         // => It is not encoded and not shell escaped, too.
0072         if (url.isLocalFile()) {
0073             FileInfo file;
0074             file.path = url.toLocalFile();
0075             file.url = url;
0076             fileList.append(file);
0077         } else {
0078             // It is an URL
0079             if (!url.isValid()) {
0080                 KMessageBox::error(nullptr, i18n("The URL %1\nis malformed", url.url()));
0081             } else if (mTempFiles) {
0082                 KMessageBox::error(nullptr, i18n("Remote URL %1\nnot allowed with --tempfiles switch", url.toDisplayString()));
0083             } else {
0084                 // We must fetch the file
0085                 QString fileName = KIO::encodeFileName(url.fileName());
0086                 if (!suggestedFileName.isEmpty()) {
0087                     fileName = suggestedFileName;
0088                 }
0089                 if (fileName.isEmpty()) {
0090                     fileName = QStringLiteral("unnamed");
0091                 }
0092                 // Build the destination filename, in ~/.cache/kioexec/krun/
0093                 // Unlike KDE-1.1, we put the filename at the end so that the extension is kept
0094                 // (Some programs rely on it)
0095                 QString krun_writable = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
0096                     + QStringLiteral("/krun/%1_%2/").arg(QCoreApplication::applicationPid()).arg(jobCounter++);
0097                 QDir().mkpath(krun_writable); // error handling will be done by the job
0098                 QString tmp = krun_writable + fileName;
0099                 FileInfo file;
0100                 file.path = tmp;
0101                 file.url = url;
0102                 fileList.append(file);
0103 
0104                 expectedCounter++;
0105                 const QUrl dest = QUrl::fromLocalFile(tmp);
0106                 qDebug() << "Copying" << url << " to" << dest;
0107                 KIO::Job *job = KIO::file_copy(url, dest);
0108                 jobList.append(job);
0109 
0110                 connect(job, &KJob::result, this, &KIOExec::slotResult);
0111             }
0112         }
0113     }
0114 
0115     if (mTempFiles) {
0116         // delay call so QApplication::exit passes the exit code to exec()
0117         QTimer::singleShot(0, this, &KIOExec::slotRunApp);
0118         return;
0119     }
0120 
0121     counter = 0;
0122     if (counter == expectedCounter) {
0123         slotResult(nullptr);
0124     }
0125 }
0126 
0127 void KIOExec::slotResult(KJob *job)
0128 {
0129     if (job) {
0130         KIO::FileCopyJob *copyJob = static_cast<KIO::FileCopyJob *>(job);
0131         const QString path = copyJob->destUrl().path();
0132 
0133         if (job->error()) {
0134             // That error dialog would be queued, i.e. not immediate...
0135             // job->showErrorDialog();
0136             if (job->error() != KIO::ERR_USER_CANCELED) {
0137                 KMessageBox::error(nullptr, job->errorString());
0138             }
0139 
0140             auto it = std::find_if(fileList.begin(), fileList.end(), [&path](const FileInfo &i) {
0141                 return i.path == path;
0142             });
0143             if (it != fileList.end()) {
0144                 fileList.erase(it);
0145             } else {
0146                 qDebug() << path << " not found in list";
0147             }
0148         } else {
0149             // Tell kioexecd to watch the file for changes.
0150             const QString dest = copyJob->srcUrl().toString();
0151             qDebug() << "Telling kioexecd to watch path" << path << "dest" << dest;
0152             OrgKdeKIOExecdInterface kioexecd(QStringLiteral("org.kde.kioexecd6"), QStringLiteral("/modules/kioexecd"), QDBusConnection::sessionBus());
0153             kioexecd.watch(path, dest);
0154             mUseDaemon = !kioexecd.lastError().isValid();
0155             if (!mUseDaemon) {
0156                 qDebug() << "Not using kioexecd";
0157             }
0158         }
0159     }
0160 
0161     counter++;
0162 
0163     if (counter < expectedCounter) {
0164         return;
0165     }
0166 
0167     qDebug() << "All files downloaded, will call slotRunApp shortly";
0168     // We know we can run the app now - but let's finish the job properly first.
0169     QTimer::singleShot(0, this, &KIOExec::slotRunApp);
0170 
0171     jobList.clear();
0172 }
0173 
0174 void KIOExec::slotRunApp()
0175 {
0176     if (fileList.isEmpty()) {
0177         qDebug() << "No files downloaded -> exiting";
0178         mExited = true;
0179         QApplication::exit(1);
0180         return;
0181     }
0182 
0183     KService service(QStringLiteral("dummy"), command, QString());
0184 
0185     QList<QUrl> list;
0186     list.reserve(fileList.size());
0187     // Store modification times
0188     QList<FileInfo>::Iterator it = fileList.begin();
0189     for (; it != fileList.end(); ++it) {
0190         QFileInfo info(it->path);
0191         it->time = info.lastModified();
0192         QUrl url = QUrl::fromLocalFile(it->path);
0193         list << url;
0194     }
0195 
0196     KIO::DesktopExecParser execParser(service, list);
0197     QStringList params = execParser.resultingArguments();
0198     if (params.isEmpty()) {
0199         qWarning() << execParser.errorMessage();
0200         QApplication::exit(-1);
0201         return;
0202     }
0203 
0204     qDebug() << "EXEC" << params.join(QLatin1Char(' '));
0205 
0206 #if HAVE_X11
0207     // propagate the startup identification to the started process
0208     KStartupInfoId id;
0209     QByteArray startupId;
0210     if (QX11Info::isPlatformX11()) {
0211         startupId = QX11Info::nextStartupId();
0212     }
0213     id.initId(startupId);
0214     id.setupStartupEnv();
0215 #endif
0216 
0217     QString exe(params.takeFirst());
0218     const int exit_code = QProcess::execute(exe, params);
0219 
0220 #if HAVE_X11
0221     KStartupInfo::resetStartupEnv();
0222 #endif
0223 
0224     qDebug() << "EXEC done";
0225 
0226     // Test whether one of the files changed
0227     for (it = fileList.begin(); it != fileList.end(); ++it) {
0228         QString src = it->path;
0229         const QUrl dest = it->url;
0230         QFileInfo info(src);
0231         const bool uploadChanges = !mUseDaemon && !dest.isLocalFile();
0232         if (info.exists() && (it->time != info.lastModified())) {
0233             if (mTempFiles) {
0234                 const auto result = KMessageBox::questionTwoActions(
0235                     nullptr,
0236                     i18n("The supposedly temporary file\n%1\nhas been modified.\nDo you still want to delete it?", dest.toDisplayString(QUrl::PreferLocalFile)),
0237                     i18n("File Changed"),
0238                     KStandardGuiItem::del(),
0239                     KGuiItem(i18n("Do Not Delete")));
0240                 if (result != KMessageBox::PrimaryAction) {
0241                     continue; // don't delete the temp file
0242                 }
0243             } else if (uploadChanges) { // no upload when it's already a local file or kioexecd already did it.
0244                 const auto result =
0245                     KMessageBox::questionTwoActions(nullptr,
0246                                                     i18n("The file\n%1\nhas been modified.\nDo you want to upload the changes?", dest.toDisplayString()),
0247                                                     i18n("File Changed"),
0248                                                     KGuiItem(i18n("Upload")),
0249                                                     KGuiItem(i18n("Do Not Upload")));
0250                 if (result == KMessageBox::PrimaryAction) {
0251                     qDebug() << "src='" << src << "'  dest='" << dest << "'";
0252                     // Do it the synchronous way.
0253                     KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(src), dest);
0254                     if (!job->exec()) {
0255                         KMessageBox::error(nullptr, job->errorText());
0256                         continue; // don't delete the temp file
0257                     }
0258                 }
0259             }
0260         }
0261 
0262         if ((uploadChanges || mTempFiles) && exit_code == 0) {
0263             // Wait for a reasonable time so that even if the application forks on startup (like OOo or amarok)
0264             // it will have time to start up and read the file before it gets deleted. #130709.
0265             const int sleepSecs = 180;
0266             qDebug() << "sleeping for" << sleepSecs << "seconds before deleting file...";
0267             QThread::sleep(sleepSecs);
0268             const QString parentDir = info.path();
0269             qDebug() << sleepSecs << "seconds have passed, deleting" << info.filePath();
0270             QFile(src).remove();
0271             // NOTE: this is not necessarily a temporary directory.
0272             if (QDir().rmdir(parentDir)) {
0273                 qDebug() << "Removed empty parent directory" << parentDir;
0274             }
0275         }
0276     }
0277 
0278     mExited = true;
0279     QApplication::exit(exit_code);
0280 }
0281 
0282 int main(int argc, char **argv)
0283 {
0284     QApplication app(argc, argv);
0285     KAboutData aboutData(QStringLiteral("kioexec"),
0286                          i18n("KIOExec"),
0287                          QStringLiteral(KIO_VERSION_STRING),
0288                          i18n("KIO Exec - Opens remote files, watches modifications, asks for upload"),
0289                          KAboutLicense::GPL,
0290                          i18n("(c) 1998-2000,2003 The KFM/Konqueror Developers"));
0291     aboutData.addAuthor(i18n("David Faure"), QString(), QStringLiteral("faure@kde.org"));
0292     aboutData.addAuthor(i18n("Stephan Kulow"), QString(), QStringLiteral("coolo@kde.org"));
0293     aboutData.addAuthor(i18n("Bernhard Rosenkraenzer"), QString(), QStringLiteral("bero@arklinux.org"));
0294     aboutData.addAuthor(i18n("Waldo Bastian"), QString(), QStringLiteral("bastian@kde.org"));
0295     aboutData.addAuthor(i18n("Oswald Buddenhagen"), QString(), QStringLiteral("ossi@kde.org"));
0296     KAboutData::setApplicationData(aboutData);
0297     KDBusService service(KDBusService::Multiple);
0298 
0299     QCommandLineParser parser;
0300     parser.addOption(QCommandLineOption(QStringList{QStringLiteral("tempfiles")}, i18n("Treat URLs as local files and delete them afterwards")));
0301     parser.addOption(
0302         QCommandLineOption(QStringList{QStringLiteral("suggestedfilename")}, i18n("Suggested file name for the downloaded file"), QStringLiteral("filename")));
0303     parser.addPositionalArgument(QStringLiteral("command"), i18n("Command to execute"));
0304     parser.addPositionalArgument(QStringLiteral("urls"), i18n("URL(s) or local file(s) used for 'command'"));
0305 
0306     app.setQuitOnLastWindowClosed(false);
0307 
0308     aboutData.setupCommandLine(&parser);
0309     parser.process(app);
0310     aboutData.processCommandLine(&parser);
0311 
0312     if (parser.positionalArguments().count() < 1) {
0313         parser.showHelp(-1);
0314         return -1;
0315     }
0316 
0317     const bool tempfiles = parser.isSet(QStringLiteral("tempfiles"));
0318     const QString suggestedfilename = parser.value(QStringLiteral("suggestedfilename"));
0319     KIOExec exec(parser.positionalArguments(), tempfiles, suggestedfilename);
0320 
0321     // Don't go into the event loop if we already want to exit (#172197)
0322     if (exec.exited()) {
0323         return 0;
0324     }
0325 
0326     return app.exec();
0327 }
0328 
0329 #include "moc_main.cpp"