File indexing completed on 2024-04-28 15:29:20

0001 /*
0002     This file is part of the KDE project
0003 
0004     SPDX-FileCopyrightText: 2002 David Faure <faure@kde.org>
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "browserrun.h"
0009 
0010 #include "browseropenorsavequestion.h"
0011 
0012 #include "kparts_logging.h"
0013 
0014 #include <KConfigGroup>
0015 #include <KIO/CommandLauncherJob>
0016 #include <KIO/FileCopyJob>
0017 #include <KIO/JobUiDelegate>
0018 #include <KIO/OpenUrlJob>
0019 #include <KIO/Scheduler>
0020 #include <KIO/TransferJob>
0021 #include <KJobWidgets>
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 #include <KProtocolManager>
0025 #include <KSharedConfig>
0026 #include <KShell>
0027 
0028 #include <QFileDialog>
0029 #include <QMimeDatabase>
0030 #include <QStandardPaths>
0031 #include <QTemporaryFile>
0032 
0033 #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 71)
0034 
0035 using namespace KParts;
0036 
0037 class KParts::BrowserRunPrivate
0038 {
0039 public:
0040     bool m_bHideErrorDialog;
0041     bool m_bRemoveReferrer;
0042     bool m_bTrustedSource;
0043     KParts::OpenUrlArguments m_args;
0044     KParts::BrowserArguments m_browserArgs;
0045 
0046     KParts::ReadOnlyPart *m_part; // QGuardedPtr?
0047     QPointer<QWidget> m_window;
0048     QString m_mimeType;
0049     QString m_contentDisposition;
0050 };
0051 
0052 BrowserRun::BrowserRun(const QUrl &url,
0053                        const KParts::OpenUrlArguments &args,
0054                        const KParts::BrowserArguments &browserArgs,
0055                        KParts::ReadOnlyPart *part,
0056                        QWidget *window,
0057                        bool removeReferrer,
0058                        bool trustedSource,
0059                        bool hideErrorDialog)
0060     : KRun(url, window, false /* no GUI */)
0061     , d(new BrowserRunPrivate)
0062 {
0063     d->m_bHideErrorDialog = hideErrorDialog;
0064     d->m_bRemoveReferrer = removeReferrer;
0065     d->m_bTrustedSource = trustedSource;
0066     d->m_args = args;
0067     d->m_browserArgs = browserArgs;
0068     d->m_part = part;
0069     d->m_window = window;
0070 }
0071 
0072 BrowserRun::~BrowserRun() = default;
0073 
0074 KParts::ReadOnlyPart *BrowserRun::part() const
0075 {
0076     return d->m_part;
0077 }
0078 
0079 QUrl BrowserRun::url() const
0080 {
0081     return KRun::url();
0082 }
0083 
0084 void BrowserRun::init()
0085 {
0086     if (d->m_bHideErrorDialog) {
0087         // ### KRun doesn't call a virtual method when it finds out that the URL
0088         // is either malformed, or points to a non-existing local file...
0089         // So we need to reimplement some of the checks, to handle d->m_bHideErrorDialog
0090         if (!KRun::url().isValid()) {
0091             redirectToError(KIO::ERR_MALFORMED_URL, KRun::url().toString());
0092             return;
0093         }
0094 
0095         if (isLocalFile()) {
0096             const QString localPath = KRun::url().toLocalFile();
0097             if (!QFile::exists(localPath)) {
0098                 // qDebug() << localPath << "doesn't exist.";
0099                 redirectToError(KIO::ERR_DOES_NOT_EXIST, localPath);
0100                 return;
0101             }
0102         }
0103     }
0104     KRun::init();
0105 }
0106 
0107 void BrowserRun::scanFile()
0108 {
0109     const QUrl url = KRun::url();
0110     // qDebug() << url;
0111 
0112     // Let's check for well-known extensions
0113     // Not when there is a query in the URL, in any case.
0114     // Optimization for http/https, findByURL doesn't trust extensions over http.
0115     QString protocol = url.scheme();
0116 
0117     if (!KProtocolInfo::proxiedBy(protocol).isEmpty()) {
0118         QString dummy;
0119         protocol = KProtocolManager::workerProtocol(url, dummy);
0120     }
0121 
0122     if (!url.hasQuery() && !protocol.startsWith(QLatin1String("http")) && (!url.path().endsWith(QLatin1Char('/')) || KProtocolManager::supportsListing(url))) {
0123         QMimeDatabase db;
0124         QMimeType mime = db.mimeTypeForUrl(url);
0125         if (!mime.isDefault() || isLocalFile()) {
0126             // qDebug() << "MIME TYPE is" << mime.name();
0127             mimeTypeDetermined(mime.name());
0128             return;
0129         }
0130     }
0131 
0132     QMap<QString, QString> &metaData = d->m_args.metaData();
0133     if (d->m_part) {
0134         const QString proto = d->m_part->url().scheme();
0135 
0136         if (proto == QLatin1String("https") || proto == QLatin1String("webdavs")) {
0137             metaData.insert(QStringLiteral("main_frame_request"), QStringLiteral("TRUE"));
0138             metaData.insert(QStringLiteral("ssl_was_in_use"), QStringLiteral("TRUE"));
0139             // metaData.insert(QStringLiteral("ssl_activate_warnings"), QStringLiteral("TRUE"));
0140         } else if (proto == QLatin1String("http") || proto == QLatin1String("webdav")) {
0141             // metaData.insert(QStringLiteral("ssl_activate_warnings"), QStringLiteral("TRUE"));
0142             metaData.insert(QStringLiteral("ssl_was_in_use"), QStringLiteral("FALSE"));
0143         }
0144 
0145         // Set the PropagateHttpHeader meta-data if it has not already been set...
0146         if (!metaData.contains(QStringLiteral("PropagateHttpHeader"))) {
0147             metaData.insert(QStringLiteral("PropagateHttpHeader"), QStringLiteral("TRUE"));
0148         }
0149     }
0150 
0151     KIO::TransferJob *job;
0152     if (d->m_browserArgs.doPost() && url.scheme().startsWith(QLatin1String("http"))) {
0153         job = KIO::http_post(url, d->m_browserArgs.postData, KIO::HideProgressInfo);
0154         job->addMetaData(QStringLiteral("content-type"), d->m_browserArgs.contentType());
0155     } else {
0156         job = KIO::get(url, d->m_args.reload() ? KIO::Reload : KIO::NoReload, KIO::HideProgressInfo);
0157     }
0158 
0159     if (d->m_bRemoveReferrer) {
0160         metaData.remove(QStringLiteral("referrer"));
0161     }
0162 
0163     job->addMetaData(metaData);
0164     KJobWidgets::setWindow(job, d->m_window);
0165     connect(job, &KIO::TransferJob::result, this, &BrowserRun::slotBrowserScanFinished);
0166     connect(job, &KIO::TransferJob::mimeTypeFound, this, &BrowserRun::slotBrowserMimetype);
0167     setJob(job);
0168 }
0169 
0170 void BrowserRun::slotBrowserScanFinished(KJob *job)
0171 {
0172     // qDebug() << job->error();
0173     if (job->error() == KIO::ERR_IS_DIRECTORY) {
0174         // It is in fact a directory. This happens when HTTP redirects to FTP.
0175         // Due to the "protocol doesn't support listing" code in BrowserRun, we
0176         // assumed it was a file.
0177         // qDebug() << "It is in fact a directory!";
0178         // Update our URL in case of a redirection
0179         KRun::setUrl(static_cast<KIO::TransferJob *>(job)->url());
0180         setJob(nullptr);
0181         mimeTypeDetermined(QStringLiteral("inode/directory"));
0182     } else {
0183         KRun::slotScanFinished(job);
0184     }
0185 }
0186 
0187 static QMimeType fixupMimeType(const QString &mimeType, const QString &fileName)
0188 {
0189     QMimeDatabase db;
0190     QMimeType mime = db.mimeTypeForName(mimeType);
0191     if ((!mime.isValid() || mime.isDefault()) && !fileName.isEmpty()) {
0192         mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension);
0193     }
0194     return mime;
0195 }
0196 
0197 void BrowserRun::slotBrowserMimetype(KIO::Job *_job, const QString &type)
0198 {
0199     Q_ASSERT(_job == KRun::job());
0200     Q_UNUSED(_job)
0201     KIO::TransferJob *job = static_cast<KIO::TransferJob *>(KRun::job());
0202     // Update our URL in case of a redirection
0203     // qDebug() << "old URL=" << KRun::url();
0204     // qDebug() << "new URL=" << job->url();
0205     setUrl(job->url());
0206 
0207     if (job->isErrorPage()) {
0208         d->m_mimeType = type;
0209         handleError(job);
0210         setJob(nullptr);
0211     } else {
0212         // qDebug() << "found" << type << "for" << KRun::url();
0213 
0214         // Suggested filename given by the server (e.g. HTTP content-disposition)
0215         // When set, we should really be saving instead of embedding
0216         const QString suggestedFileName = job->queryMetaData(QStringLiteral("content-disposition-filename"));
0217         setSuggestedFileName(suggestedFileName); // store it (in KRun)
0218         // qDebug() << "suggestedFileName=" << suggestedFileName;
0219         d->m_contentDisposition = job->queryMetaData(QStringLiteral("content-disposition-type"));
0220 
0221         const QString modificationTime = job->queryMetaData(QStringLiteral("content-disposition-modification-date"));
0222         if (!modificationTime.isEmpty()) {
0223             d->m_args.metaData().insert(QStringLiteral("content-disposition-modification-date"), modificationTime);
0224         }
0225 
0226         QMapIterator<QString, QString> it(job->metaData());
0227         while (it.hasNext()) {
0228             it.next();
0229             if (it.key().startsWith(QLatin1String("ssl_"), Qt::CaseInsensitive)) {
0230                 d->m_args.metaData().insert(it.key(), it.value());
0231             }
0232         }
0233 
0234         // Make a copy to avoid a dead reference
0235         QString _type = type;
0236         job->putOnHold();
0237         setJob(nullptr);
0238 
0239         // If the current mime-type is the default mime-type, then attempt to
0240         // determine the "real" mimetype from the file name.
0241         QMimeType mime = fixupMimeType(_type, suggestedFileName.isEmpty() ? url().fileName() : suggestedFileName);
0242         if (mime.isValid() && mime.name() != _type) {
0243             _type = mime.name();
0244         }
0245 
0246         mimeTypeDetermined(_type);
0247     }
0248 }
0249 
0250 BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString &mimeType)
0251 {
0252     return handleNonEmbeddable(mimeType, nullptr);
0253 }
0254 
0255 BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString &_mimeType, KService::Ptr *selectedService)
0256 {
0257     QString mimeType(_mimeType);
0258     Q_ASSERT(!hasFinished()); // only come here if the mimetype couldn't be embedded
0259     // Support for saving remote files.
0260     if (mimeType != QLatin1String("inode/directory") && // dirs can't be saved
0261         !KRun::url().isLocalFile()) {
0262         if (isTextExecutable(mimeType)) {
0263             mimeType = QStringLiteral("text/plain"); // view, don't execute
0264         }
0265         // ... -> ask whether to save
0266         BrowserOpenOrSaveQuestion question(d->m_window, KRun::url(), mimeType);
0267         question.setSuggestedFileName(suggestedFileName());
0268         if (selectedService) {
0269             question.setFeatures(BrowserOpenOrSaveQuestion::ServiceSelection);
0270         }
0271         BrowserOpenOrSaveQuestion::Result res = question.askOpenOrSave();
0272         if (res == BrowserOpenOrSaveQuestion::Save) {
0273             save(KRun::url(), suggestedFileName());
0274             // qDebug() << "Save: returning Handled";
0275             setFinished(true);
0276             return Handled;
0277         } else if (res == BrowserOpenOrSaveQuestion::Cancel) {
0278             // saving done or canceled
0279             // qDebug() << "Cancel: returning Handled";
0280             setFinished(true);
0281             return Handled;
0282         } else { // "Open" chosen (done by KRun::foundMimeType, called when returning NotHandled)
0283             // If we were in a POST, we can't just pass a URL to an external application.
0284             // We must save the data to a tempfile first.
0285             if (d->m_browserArgs.doPost()) {
0286                 // qDebug() << "request comes from a POST, can't pass a URL to another app, need to save";
0287                 d->m_mimeType = mimeType;
0288                 QString extension;
0289                 QString fileName = suggestedFileName().isEmpty() ? KRun::url().fileName() : suggestedFileName();
0290                 int extensionPos = fileName.lastIndexOf(QLatin1Char('.'));
0291                 if (extensionPos != -1) {
0292                     extension = fileName.mid(extensionPos); // keep the '.'
0293                 }
0294                 QTemporaryFile tempFile(QDir::tempPath() + QLatin1Char('/') + QCoreApplication::applicationName() + QLatin1String("XXXXXX") + extension);
0295                 tempFile.setAutoRemove(false);
0296                 tempFile.open();
0297                 QUrl destURL = QUrl::fromLocalFile(tempFile.fileName());
0298                 KIO::Job *job = KIO::file_copy(KRun::url(), destURL, 0600, KIO::Overwrite);
0299                 KJobWidgets::setWindow(job, d->m_window);
0300                 connect(job, &KIO::Job::result, this, &BrowserRun::slotCopyToTempFileResult);
0301                 return Delayed; // We'll continue after the job has finished
0302             }
0303             if (selectedService && question.selectedService()) {
0304                 *selectedService = question.selectedService();
0305                 // KRun will use this when starting an app
0306                 KRun::setPreferredService(question.selectedService()->desktopEntryName());
0307             }
0308         }
0309     }
0310 
0311     // Check if running is allowed
0312     if (!d->m_bTrustedSource && // ... and untrusted source...
0313         !allowExecution(mimeType, KRun::url())) { // ...and the user said no (for executables etc.)
0314         setFinished(true);
0315         return Handled;
0316     }
0317 
0318     return NotHandled;
0319 }
0320 
0321 // static
0322 bool BrowserRun::allowExecution(const QString &mimeType, const QUrl &url)
0323 {
0324     if (!KRun::isExecutable(mimeType)) {
0325         return true;
0326     }
0327 
0328     if (!url.isLocalFile()) { // Don't permit to execute remote files
0329         return false;
0330     }
0331 
0332     return (KMessageBox::warningContinueCancel(nullptr,
0333                                                i18n("Do you really want to execute '%1'?", url.toDisplayString()),
0334                                                i18n("Execute File?"),
0335                                                KGuiItem(i18n("Execute")))
0336             == KMessageBox::Continue);
0337 }
0338 
0339 // static, deprecated
0340 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 0)
0341 BrowserRun::AskSaveResult BrowserRun::askSave(const QUrl &url, KService::Ptr offer, const QString &mimeType, const QString &suggestedFileName)
0342 {
0343     Q_UNUSED(offer);
0344     BrowserOpenOrSaveQuestion question(nullptr, url, mimeType);
0345     question.setSuggestedFileName(suggestedFileName);
0346     const BrowserOpenOrSaveQuestion::Result result = question.askOpenOrSave();
0347     // clang-format off
0348     return result == BrowserOpenOrSaveQuestion::Save ? Save
0349            : (result == BrowserOpenOrSaveQuestion::Open ? Open
0350            : Cancel);
0351     // clang-format on
0352 }
0353 #endif
0354 
0355 // static, deprecated
0356 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 0)
0357 BrowserRun::AskSaveResult BrowserRun::askEmbedOrSave(const QUrl &url, const QString &mimeType, const QString &suggestedFileName, int flags)
0358 {
0359     BrowserOpenOrSaveQuestion question(nullptr, url, mimeType);
0360     question.setSuggestedFileName(suggestedFileName);
0361     const BrowserOpenOrSaveQuestion::Result result = question.askEmbedOrSave(flags);
0362     // clang-format off
0363     return result == BrowserOpenOrSaveQuestion::Save ? Save
0364            : result == BrowserOpenOrSaveQuestion::Embed ? Open
0365            : Cancel;
0366     // clang-format on
0367 }
0368 #endif
0369 
0370 // Default implementation, overridden in KHTMLRun
0371 void BrowserRun::save(const QUrl &url, const QString &suggestedFileName)
0372 {
0373     saveUrl(url, suggestedFileName, d->m_window, d->m_args);
0374 }
0375 
0376 #if KPARTS_BUILD_DEPRECATED_SINCE(4, 4)
0377 // static
0378 void BrowserRun::simpleSave(const QUrl &url, const QString &suggestedFileName, QWidget *window)
0379 {
0380     saveUrl(url, suggestedFileName, window, KParts::OpenUrlArguments());
0381 }
0382 #endif
0383 
0384 void KParts::BrowserRun::saveUrl(const QUrl &url, const QString &suggestedFileName, QWidget *window, const KParts::OpenUrlArguments &args)
0385 {
0386     // DownloadManager <-> konqueror integration
0387     // find if the integration is enabled
0388     // the empty key  means no integration
0389     // only use the downloadmanager for non-local urls
0390     if (!url.isLocalFile()) {
0391         KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("konquerorrc"), KConfig::NoGlobals)->group("HTML Settings");
0392         QString downloadManager = cfg.readPathEntry("DownloadManager", QString());
0393         if (!downloadManager.isEmpty()) {
0394             // then find the download manager location
0395             // qDebug() << "Using: "<<downloadManager <<" as Download Manager";
0396             if (QStandardPaths::findExecutable(downloadManager).isEmpty()) {
0397                 QString errMsg = i18n("The Download Manager (%1) could not be found in your $PATH ", downloadManager);
0398                 QString errMsgEx = i18n("Try to reinstall it  \n\nThe integration with Konqueror will be disabled.");
0399                 KMessageBox::detailedError(nullptr, errMsg, errMsgEx);
0400                 cfg.writePathEntry("DownloadManager", QString());
0401                 cfg.sync();
0402             } else {
0403                 QStringList args;
0404                 args << url.toString();
0405                 if (!suggestedFileName.isEmpty()) {
0406                     args << suggestedFileName;
0407                 }
0408 
0409                 // qDebug() << "Calling command" << downloadManager << args;
0410 
0411                 auto *job = new KIO::CommandLauncherJob(downloadManager, args);
0412                 job->setExecutable(downloadManager);
0413                 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, window));
0414                 job->start();
0415                 return;
0416             }
0417         }
0418     }
0419 
0420     // no download manager available, let's do it ourself
0421     QFileDialog *dlg = new QFileDialog(window);
0422     dlg->setAcceptMode(QFileDialog::AcceptSave);
0423     dlg->setWindowTitle(i18n("Save As"));
0424     dlg->setOption(QFileDialog::DontConfirmOverwrite, false);
0425     dlg->setAttribute(Qt::WA_DeleteOnClose);
0426 
0427     QString name;
0428     if (!suggestedFileName.isEmpty()) {
0429         name = suggestedFileName;
0430     } else {
0431         name = url.fileName(); // can be empty, e.g. in case http://www.kde.org/
0432     }
0433 
0434     dlg->selectFile(name);
0435     connect(dlg, &QDialog::accepted, dlg, [dlg, url, window, args]() {
0436         const QUrl destURL = dlg->selectedUrls().value(0);
0437         if (destURL.isValid()) {
0438             saveUrlUsingKIO(url, destURL, window, args.metaData());
0439         }
0440     });
0441 
0442     dlg->show();
0443 }
0444 
0445 void BrowserRun::saveUrlUsingKIO(const QUrl &srcUrl, const QUrl &destUrl, QWidget *window, const QMap<QString, QString> &metaData)
0446 {
0447     KIO::FileCopyJob *job = KIO::file_copy(srcUrl, destUrl, -1, KIO::Overwrite);
0448 
0449     const QString modificationTime = metaData[QStringLiteral("content-disposition-modification-date")];
0450     if (!modificationTime.isEmpty()) {
0451         job->setModificationTime(QDateTime::fromString(modificationTime, Qt::RFC2822Date));
0452     }
0453     job->setMetaData(metaData);
0454     job->addMetaData(QStringLiteral("MaxCacheSize"), QStringLiteral("0")); // Don't store in http cache.
0455     job->addMetaData(QStringLiteral("cache"), QStringLiteral("cache")); // Use entry from cache if available.
0456     KJobWidgets::setWindow(job, window);
0457     job->uiDelegate()->setAutoErrorHandlingEnabled(true);
0458 }
0459 
0460 void BrowserRun::handleError(KJob *job)
0461 {
0462     if (!job) { // Shouldn't happen
0463         qCWarning(KPARTSLOG) << "handleError called with job=0! hideErrorDialog=" << d->m_bHideErrorDialog;
0464         return;
0465     }
0466 
0467     KIO::TransferJob *tjob = qobject_cast<KIO::TransferJob *>(job);
0468     if (tjob && tjob->isErrorPage() && !job->error()) {
0469         // The default handling of error pages is to show them like normal pages
0470         // But this is done here in handleError so that KHTMLRun can reimplement it
0471         tjob->putOnHold();
0472         setJob(nullptr);
0473         if (!d->m_mimeType.isEmpty()) {
0474             mimeTypeDetermined(d->m_mimeType);
0475         }
0476         return;
0477     }
0478 
0479     if (d->m_bHideErrorDialog && job->error() != KIO::ERR_NO_CONTENT) {
0480         redirectToError(job->error(), job->errorText());
0481         return;
0482     }
0483 
0484     // Reuse code in KRun, to benefit from d->m_showingError etc.
0485     KRun::handleError(job);
0486 }
0487 
0488 // static
0489 QUrl BrowserRun::makeErrorUrl(int error, const QString &errorText, const QUrl &initialUrl)
0490 {
0491     /*
0492      * The format of the error:/ URL is error:/?query#url,
0493      * where two variables are passed in the query:
0494      * error = int kio error code, errText = QString error text from kio
0495      * The sub-url is the URL that we were trying to open.
0496      */
0497     QUrl newURL(QStringLiteral("error:/?error=%1&errText=%2").arg(error).arg(QString::fromUtf8(QUrl::toPercentEncoding(errorText))));
0498 
0499     QString cleanedOrigUrl = initialUrl.toString();
0500     QUrl runURL(cleanedOrigUrl);
0501     if (runURL.isValid()) {
0502         runURL.setPassword(QString()); // don't put the password in the error URL
0503         cleanedOrigUrl = runURL.toString();
0504     }
0505 
0506     newURL.setFragment(cleanedOrigUrl);
0507     return newURL;
0508 }
0509 
0510 void BrowserRun::redirectToError(int error, const QString &errorText)
0511 {
0512     /**
0513      * To display this error in KHTMLPart instead of inside a dialog box,
0514      * we tell konq that the mimetype is text/html, and we redirect to
0515      * an error:/ URL that sends the info to khtml.
0516      */
0517     KRun::setUrl(makeErrorUrl(error, errorText, url()));
0518     setJob(nullptr);
0519     mimeTypeDetermined(QStringLiteral("text/html"));
0520 }
0521 
0522 void BrowserRun::slotCopyToTempFileResult(KJob *job)
0523 {
0524     if (job->error()) {
0525         job->uiDelegate()->showErrorMessage();
0526     } else {
0527         // Same as KRun::foundMimeType but with a different URL
0528         const QUrl destUrl = static_cast<KIO::FileCopyJob *>(job)->destUrl();
0529         KIO::OpenUrlJob *job = new KIO::OpenUrlJob(destUrl, d->m_mimeType);
0530         job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, d->m_window));
0531         job->setRunExecutables(true);
0532         job->start();
0533     }
0534     setError(true); // see above
0535     setFinished(true);
0536 }
0537 
0538 bool BrowserRun::isTextExecutable(const QString &mimeType)
0539 {
0540     return (mimeType == QLatin1String("application/x-desktop") || mimeType == QLatin1String("application/x-shellscript"));
0541 }
0542 
0543 bool BrowserRun::hideErrorDialog() const
0544 {
0545     return d->m_bHideErrorDialog;
0546 }
0547 
0548 QString BrowserRun::contentDisposition() const
0549 {
0550     return d->m_contentDisposition;
0551 }
0552 
0553 bool BrowserRun::serverSuggestsSave() const
0554 {
0555     // RfC 2183, section 2.8:
0556     // Unrecognized disposition types should be treated as `attachment'.
0557     return !contentDisposition().isEmpty() && (contentDisposition() != QLatin1String("inline"));
0558 }
0559 
0560 KParts::OpenUrlArguments &KParts::BrowserRun::arguments()
0561 {
0562     return d->m_args;
0563 }
0564 
0565 KParts::BrowserArguments &KParts::BrowserRun::browserArguments()
0566 {
0567     return d->m_browserArgs;
0568 }
0569 
0570 #include "moc_browserrun.cpp"
0571 
0572 #endif