File indexing completed on 2024-05-19 05:47:14

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