1 /*
2     This file is part of the KDE project
3 
4     SPDX-FileCopyrightText: 2002 David Faure <faure@kde.org>
5     SPDX-License-Identifier: LGPL-2.0-only
6 */
7 
8 #include "browserrun.h"
9 
10 #include "browseropenorsavequestion.h"
11 
12 #include "kparts_logging.h"
13 
14 #include <KConfigGroup>
15 #include <KIO/CommandLauncherJob>
16 #include <KIO/FileCopyJob>
17 #include <KIO/JobUiDelegate>
18 #include <KIO/OpenUrlJob>
19 #include <KIO/Scheduler>
20 #include <KIO/TransferJob>
21 #include <KJobWidgets>
22 #include <KLocalizedString>
23 #include <KMessageBox>
24 #include <KProtocolManager>
25 #include <KSharedConfig>
26 #include <KShell>
27 
28 #include <QFileDialog>
29 #include <QMimeDatabase>
30 #include <QStandardPaths>
31 #include <QTemporaryFile>
32 
33 #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 71)
34 
35 using namespace KParts;
36 
37 class KParts::BrowserRunPrivate
38 {
39 public:
40     bool m_bHideErrorDialog;
41     bool m_bRemoveReferrer;
42     bool m_bTrustedSource;
43     KParts::OpenUrlArguments m_args;
44     KParts::BrowserArguments m_browserArgs;
45 
46     KParts::ReadOnlyPart *m_part; // QGuardedPtr?
47     QPointer<QWidget> m_window;
48     QString m_mimeType;
49     QString m_contentDisposition;
50 };
51 
BrowserRun(const QUrl & url,const KParts::OpenUrlArguments & args,const KParts::BrowserArguments & browserArgs,KParts::ReadOnlyPart * part,QWidget * window,bool removeReferrer,bool trustedSource,bool hideErrorDialog)52 BrowserRun::BrowserRun(const QUrl &url,
53                        const KParts::OpenUrlArguments &args,
54                        const KParts::BrowserArguments &browserArgs,
55                        KParts::ReadOnlyPart *part,
56                        QWidget *window,
57                        bool removeReferrer,
58                        bool trustedSource,
59                        bool hideErrorDialog)
60     : KRun(url, window, false /* no GUI */)
61     , d(new BrowserRunPrivate)
62 {
63     d->m_bHideErrorDialog = hideErrorDialog;
64     d->m_bRemoveReferrer = removeReferrer;
65     d->m_bTrustedSource = trustedSource;
66     d->m_args = args;
67     d->m_browserArgs = browserArgs;
68     d->m_part = part;
69     d->m_window = window;
70 }
71 
72 BrowserRun::~BrowserRun() = default;
73 
part() const74 KParts::ReadOnlyPart *BrowserRun::part() const
75 {
76     return d->m_part;
77 }
78 
url() const79 QUrl BrowserRun::url() const
80 {
81     return KRun::url();
82 }
83 
init()84 void BrowserRun::init()
85 {
86     if (d->m_bHideErrorDialog) {
87         // ### KRun doesn't call a virtual method when it finds out that the URL
88         // is either malformed, or points to a non-existing local file...
89         // So we need to reimplement some of the checks, to handle d->m_bHideErrorDialog
90         if (!KRun::url().isValid()) {
91             redirectToError(KIO::ERR_MALFORMED_URL, KRun::url().toString());
92             return;
93         }
94 
95         if (isLocalFile()) {
96             const QString localPath = KRun::url().toLocalFile();
97             if (!QFile::exists(localPath)) {
98                 // qDebug() << localPath << "doesn't exist.";
99                 redirectToError(KIO::ERR_DOES_NOT_EXIST, localPath);
100                 return;
101             }
102         }
103     }
104     KRun::init();
105 }
106 
scanFile()107 void BrowserRun::scanFile()
108 {
109     const QUrl url = KRun::url();
110     // qDebug() << url;
111 
112     // Let's check for well-known extensions
113     // Not when there is a query in the URL, in any case.
114     // Optimization for http/https, findByURL doesn't trust extensions over http.
115     QString protocol = url.scheme();
116 
117     if (!KProtocolInfo::proxiedBy(protocol).isEmpty()) {
118         QString dummy;
119         protocol = KProtocolManager::slaveProtocol(url, dummy);
120     }
121 
122     if (!url.hasQuery() && !protocol.startsWith(QLatin1String("http")) && (!url.path().endsWith(QLatin1Char('/')) || KProtocolManager::supportsListing(url))) {
123         QMimeDatabase db;
124         QMimeType mime = db.mimeTypeForUrl(url);
125         if (!mime.isDefault() || isLocalFile()) {
126             // qDebug() << "MIME TYPE is" << mime.name();
127             mimeTypeDetermined(mime.name());
128             return;
129         }
130     }
131 
132     QMap<QString, QString> &metaData = d->m_args.metaData();
133     if (d->m_part) {
134         const QString proto = d->m_part->url().scheme();
135 
136         if (proto == QLatin1String("https") || proto == QLatin1String("webdavs")) {
137             metaData.insert(QStringLiteral("main_frame_request"), QStringLiteral("TRUE"));
138             metaData.insert(QStringLiteral("ssl_was_in_use"), QStringLiteral("TRUE"));
139             // metaData.insert(QStringLiteral("ssl_activate_warnings"), QStringLiteral("TRUE"));
140         } else if (proto == QLatin1String("http") || proto == QLatin1String("webdav")) {
141             // metaData.insert(QStringLiteral("ssl_activate_warnings"), QStringLiteral("TRUE"));
142             metaData.insert(QStringLiteral("ssl_was_in_use"), QStringLiteral("FALSE"));
143         }
144 
145         // Set the PropagateHttpHeader meta-data if it has not already been set...
146         if (!metaData.contains(QStringLiteral("PropagateHttpHeader"))) {
147             metaData.insert(QStringLiteral("PropagateHttpHeader"), QStringLiteral("TRUE"));
148         }
149     }
150 
151     KIO::TransferJob *job;
152     if (d->m_browserArgs.doPost() && url.scheme().startsWith(QLatin1String("http"))) {
153         job = KIO::http_post(url, d->m_browserArgs.postData, KIO::HideProgressInfo);
154         job->addMetaData(QStringLiteral("content-type"), d->m_browserArgs.contentType());
155     } else {
156         job = KIO::get(url, d->m_args.reload() ? KIO::Reload : KIO::NoReload, KIO::HideProgressInfo);
157     }
158 
159     if (d->m_bRemoveReferrer) {
160         metaData.remove(QStringLiteral("referrer"));
161     }
162 
163     job->addMetaData(metaData);
164     KJobWidgets::setWindow(job, d->m_window);
165     connect(job, &KIO::TransferJob::result, this, &BrowserRun::slotBrowserScanFinished);
166     connect(job, &KIO::TransferJob::mimeTypeFound, this, &BrowserRun::slotBrowserMimetype);
167     setJob(job);
168 }
169 
slotBrowserScanFinished(KJob * job)170 void BrowserRun::slotBrowserScanFinished(KJob *job)
171 {
172     // qDebug() << job->error();
173     if (job->error() == KIO::ERR_IS_DIRECTORY) {
174         // It is in fact a directory. This happens when HTTP redirects to FTP.
175         // Due to the "protocol doesn't support listing" code in BrowserRun, we
176         // assumed it was a file.
177         // qDebug() << "It is in fact a directory!";
178         // Update our URL in case of a redirection
179         KRun::setUrl(static_cast<KIO::TransferJob *>(job)->url());
180         setJob(nullptr);
181         mimeTypeDetermined(QStringLiteral("inode/directory"));
182     } else {
183         KRun::slotScanFinished(job);
184     }
185 }
186 
fixupMimeType(const QString & mimeType,const QString & fileName)187 static QMimeType fixupMimeType(const QString &mimeType, const QString &fileName)
188 {
189     QMimeDatabase db;
190     QMimeType mime = db.mimeTypeForName(mimeType);
191     if ((!mime.isValid() || mime.isDefault()) && !fileName.isEmpty()) {
192         mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension);
193     }
194     return mime;
195 }
196 
slotBrowserMimetype(KIO::Job * _job,const QString & type)197 void BrowserRun::slotBrowserMimetype(KIO::Job *_job, const QString &type)
198 {
199     Q_ASSERT(_job == KRun::job());
200     Q_UNUSED(_job)
201     KIO::TransferJob *job = static_cast<KIO::TransferJob *>(KRun::job());
202     // Update our URL in case of a redirection
203     // qDebug() << "old URL=" << KRun::url();
204     // qDebug() << "new URL=" << job->url();
205     setUrl(job->url());
206 
207     if (job->isErrorPage()) {
208         d->m_mimeType = type;
209         handleError(job);
210         setJob(nullptr);
211     } else {
212         // qDebug() << "found" << type << "for" << KRun::url();
213 
214         // Suggested filename given by the server (e.g. HTTP content-disposition)
215         // When set, we should really be saving instead of embedding
216         const QString suggestedFileName = job->queryMetaData(QStringLiteral("content-disposition-filename"));
217         setSuggestedFileName(suggestedFileName); // store it (in KRun)
218         // qDebug() << "suggestedFileName=" << suggestedFileName;
219         d->m_contentDisposition = job->queryMetaData(QStringLiteral("content-disposition-type"));
220 
221         const QString modificationTime = job->queryMetaData(QStringLiteral("content-disposition-modification-date"));
222         if (!modificationTime.isEmpty()) {
223             d->m_args.metaData().insert(QStringLiteral("content-disposition-modification-date"), modificationTime);
224         }
225 
226         QMapIterator<QString, QString> it(job->metaData());
227         while (it.hasNext()) {
228             it.next();
229             if (it.key().startsWith(QLatin1String("ssl_"), Qt::CaseInsensitive)) {
230                 d->m_args.metaData().insert(it.key(), it.value());
231             }
232         }
233 
234         // Make a copy to avoid a dead reference
235         QString _type = type;
236         job->putOnHold();
237         setJob(nullptr);
238 
239         // If the current mime-type is the default mime-type, then attempt to
240         // determine the "real" mimetype from the file name.
241         QMimeType mime = fixupMimeType(_type, suggestedFileName.isEmpty() ? url().fileName() : suggestedFileName);
242         if (mime.isValid() && mime.name() != _type) {
243             _type = mime.name();
244         }
245 
246         mimeTypeDetermined(_type);
247     }
248 }
249 
handleNonEmbeddable(const QString & mimeType)250 BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString &mimeType)
251 {
252     return handleNonEmbeddable(mimeType, nullptr);
253 }
254 
handleNonEmbeddable(const QString & _mimeType,KService::Ptr * selectedService)255 BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString &_mimeType, KService::Ptr *selectedService)
256 {
257     QString mimeType(_mimeType);
258     Q_ASSERT(!hasFinished()); // only come here if the mimetype couldn't be embedded
259     // Support for saving remote files.
260     if (mimeType != QLatin1String("inode/directory") && // dirs can't be saved
261         !KRun::url().isLocalFile()) {
262         if (isTextExecutable(mimeType)) {
263             mimeType = QStringLiteral("text/plain"); // view, don't execute
264         }
265         // ... -> ask whether to save
266         BrowserOpenOrSaveQuestion question(d->m_window, KRun::url(), mimeType);
267         question.setSuggestedFileName(suggestedFileName());
268         if (selectedService) {
269             question.setFeatures(BrowserOpenOrSaveQuestion::ServiceSelection);
270         }
271         BrowserOpenOrSaveQuestion::Result res = question.askOpenOrSave();
272         if (res == BrowserOpenOrSaveQuestion::Save) {
273             save(KRun::url(), suggestedFileName());
274             // qDebug() << "Save: returning Handled";
275             setFinished(true);
276             return Handled;
277         } else if (res == BrowserOpenOrSaveQuestion::Cancel) {
278             // saving done or canceled
279             // qDebug() << "Cancel: returning Handled";
280             setFinished(true);
281             return Handled;
282         } else { // "Open" chosen (done by KRun::foundMimeType, called when returning NotHandled)
283             // If we were in a POST, we can't just pass a URL to an external application.
284             // We must save the data to a tempfile first.
285             if (d->m_browserArgs.doPost()) {
286                 // qDebug() << "request comes from a POST, can't pass a URL to another app, need to save";
287                 d->m_mimeType = mimeType;
288                 QString extension;
289                 QString fileName = suggestedFileName().isEmpty() ? KRun::url().fileName() : suggestedFileName();
290                 int extensionPos = fileName.lastIndexOf(QLatin1Char('.'));
291                 if (extensionPos != -1) {
292                     extension = fileName.mid(extensionPos); // keep the '.'
293                 }
294                 QTemporaryFile tempFile(QDir::tempPath() + QLatin1Char('/') + QCoreApplication::applicationName() + QLatin1String("XXXXXX") + extension);
295                 tempFile.setAutoRemove(false);
296                 tempFile.open();
297                 QUrl destURL = QUrl::fromLocalFile(tempFile.fileName());
298                 KIO::Job *job = KIO::file_copy(KRun::url(), destURL, 0600, KIO::Overwrite);
299                 KJobWidgets::setWindow(job, d->m_window);
300                 connect(job, &KIO::Job::result, this, &BrowserRun::slotCopyToTempFileResult);
301                 return Delayed; // We'll continue after the job has finished
302             }
303             if (selectedService && question.selectedService()) {
304                 *selectedService = question.selectedService();
305                 // KRun will use this when starting an app
306                 KRun::setPreferredService(question.selectedService()->desktopEntryName());
307             }
308         }
309     }
310 
311     // Check if running is allowed
312     if (!d->m_bTrustedSource && // ... and untrusted source...
313         !allowExecution(mimeType, KRun::url())) { // ...and the user said no (for executables etc.)
314         setFinished(true);
315         return Handled;
316     }
317 
318     return NotHandled;
319 }
320 
321 // static
allowExecution(const QString & mimeType,const QUrl & url)322 bool BrowserRun::allowExecution(const QString &mimeType, const QUrl &url)
323 {
324     if (!KRun::isExecutable(mimeType)) {
325         return true;
326     }
327 
328     if (!url.isLocalFile()) { // Don't permit to execute remote files
329         return false;
330     }
331 
332     return (KMessageBox::warningContinueCancel(nullptr,
333                                                i18n("Do you really want to execute '%1'?", url.toDisplayString()),
334                                                i18n("Execute File?"),
335                                                KGuiItem(i18n("Execute")))
336             == KMessageBox::Continue);
337 }
338 
339 // static, deprecated
340 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 0)
askSave(const QUrl & url,KService::Ptr offer,const QString & mimeType,const QString & suggestedFileName)341 BrowserRun::AskSaveResult BrowserRun::askSave(const QUrl &url, KService::Ptr offer, const QString &mimeType, const QString &suggestedFileName)
342 {
343     Q_UNUSED(offer);
344     BrowserOpenOrSaveQuestion question(nullptr, url, mimeType);
345     question.setSuggestedFileName(suggestedFileName);
346     const BrowserOpenOrSaveQuestion::Result result = question.askOpenOrSave();
347     // clang-format off
348     return result == BrowserOpenOrSaveQuestion::Save ? Save
349            : BrowserOpenOrSaveQuestion::Open ? Open
350            : Cancel;
351     // clang-format on
352 }
353 #endif
354 
355 // static, deprecated
356 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 0)
askEmbedOrSave(const QUrl & url,const QString & mimeType,const QString & suggestedFileName,int flags)357 BrowserRun::AskSaveResult BrowserRun::askEmbedOrSave(const QUrl &url, const QString &mimeType, const QString &suggestedFileName, int flags)
358 {
359     BrowserOpenOrSaveQuestion question(nullptr, url, mimeType);
360     question.setSuggestedFileName(suggestedFileName);
361     const BrowserOpenOrSaveQuestion::Result result = question.askEmbedOrSave(flags);
362     // clang-format off
363     return result == BrowserOpenOrSaveQuestion::Save ? Save
364            : result == BrowserOpenOrSaveQuestion::Embed ? Open
365            : Cancel;
366     // clang-format on
367 }
368 #endif
369 
370 // Default implementation, overridden in KHTMLRun
save(const QUrl & url,const QString & suggestedFileName)371 void BrowserRun::save(const QUrl &url, const QString &suggestedFileName)
372 {
373     saveUrl(url, suggestedFileName, d->m_window, d->m_args);
374 }
375 
376 #if KPARTS_BUILD_DEPRECATED_SINCE(4, 4)
377 // static
simpleSave(const QUrl & url,const QString & suggestedFileName,QWidget * window)378 void BrowserRun::simpleSave(const QUrl &url, const QString &suggestedFileName, QWidget *window)
379 {
380     saveUrl(url, suggestedFileName, window, KParts::OpenUrlArguments());
381 }
382 #endif
383 
saveUrl(const QUrl & url,const QString & suggestedFileName,QWidget * window,const KParts::OpenUrlArguments & args)384 void KParts::BrowserRun::saveUrl(const QUrl &url, const QString &suggestedFileName, QWidget *window, const KParts::OpenUrlArguments &args)
385 {
386     // DownloadManager <-> konqueror integration
387     // find if the integration is enabled
388     // the empty key  means no integration
389     // only use the downloadmanager for non-local urls
390     if (!url.isLocalFile()) {
391         KConfigGroup cfg = KSharedConfig::openConfig(QStringLiteral("konquerorrc"), KConfig::NoGlobals)->group("HTML Settings");
392         QString downloadManager = cfg.readPathEntry("DownloadManager", QString());
393         if (!downloadManager.isEmpty()) {
394             // then find the download manager location
395             // qDebug() << "Using: "<<downloadManager <<" as Download Manager";
396             if (QStandardPaths::findExecutable(downloadManager).isEmpty()) {
397                 QString errMsg = i18n("The Download Manager (%1) could not be found in your $PATH ", downloadManager);
398                 QString errMsgEx = i18n("Try to reinstall it  \n\nThe integration with Konqueror will be disabled.");
399                 KMessageBox::detailedSorry(nullptr, errMsg, errMsgEx);
400                 cfg.writePathEntry("DownloadManager", QString());
401                 cfg.sync();
402             } else {
403                 QStringList args;
404                 args << url.toString();
405                 if (!suggestedFileName.isEmpty()) {
406                     args << suggestedFileName;
407                 }
408 
409                 // qDebug() << "Calling command" << downloadManager << args;
410 
411                 auto *job = new KIO::CommandLauncherJob(downloadManager, args);
412                 job->setExecutable(downloadManager);
413                 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, window));
414                 job->start();
415                 return;
416             }
417         }
418     }
419 
420     // no download manager available, let's do it ourself
421     QFileDialog *dlg = new QFileDialog(window);
422     dlg->setAcceptMode(QFileDialog::AcceptSave);
423     dlg->setWindowTitle(i18n("Save As"));
424     dlg->setOption(QFileDialog::DontConfirmOverwrite, false);
425     dlg->setAttribute(Qt::WA_DeleteOnClose);
426 
427     QString name;
428     if (!suggestedFileName.isEmpty()) {
429         name = suggestedFileName;
430     } else {
431         name = url.fileName(); // can be empty, e.g. in case http://www.kde.org/
432     }
433 
434     dlg->selectFile(name);
435     connect(dlg, &QDialog::accepted, dlg, [dlg, url, window, args]() {
436         const QUrl destURL = dlg->selectedUrls().value(0);
437         if (destURL.isValid()) {
438             saveUrlUsingKIO(url, destURL, window, args.metaData());
439         }
440     });
441 
442     dlg->show();
443 }
444 
saveUrlUsingKIO(const QUrl & srcUrl,const QUrl & destUrl,QWidget * window,const QMap<QString,QString> & metaData)445 void BrowserRun::saveUrlUsingKIO(const QUrl &srcUrl, const QUrl &destUrl, QWidget *window, const QMap<QString, QString> &metaData)
446 {
447     KIO::FileCopyJob *job = KIO::file_copy(srcUrl, destUrl, -1, KIO::Overwrite);
448 
449     const QString modificationTime = metaData[QStringLiteral("content-disposition-modification-date")];
450     if (!modificationTime.isEmpty()) {
451         job->setModificationTime(QDateTime::fromString(modificationTime, Qt::RFC2822Date));
452     }
453     job->setMetaData(metaData);
454     job->addMetaData(QStringLiteral("MaxCacheSize"), QStringLiteral("0")); // Don't store in http cache.
455     job->addMetaData(QStringLiteral("cache"), QStringLiteral("cache")); // Use entry from cache if available.
456     KJobWidgets::setWindow(job, window);
457     job->uiDelegate()->setAutoErrorHandlingEnabled(true);
458 }
459 
handleError(KJob * job)460 void BrowserRun::handleError(KJob *job)
461 {
462     if (!job) { // Shouldn't happen
463         qCWarning(KPARTSLOG) << "handleError called with job=0! hideErrorDialog=" << d->m_bHideErrorDialog;
464         return;
465     }
466 
467     KIO::TransferJob *tjob = qobject_cast<KIO::TransferJob *>(job);
468     if (tjob && tjob->isErrorPage() && !job->error()) {
469         // The default handling of error pages is to show them like normal pages
470         // But this is done here in handleError so that KHTMLRun can reimplement it
471         tjob->putOnHold();
472         setJob(nullptr);
473         if (!d->m_mimeType.isEmpty()) {
474             mimeTypeDetermined(d->m_mimeType);
475         }
476         return;
477     }
478 
479     if (d->m_bHideErrorDialog && job->error() != KIO::ERR_NO_CONTENT) {
480         redirectToError(job->error(), job->errorText());
481         return;
482     }
483 
484     // Reuse code in KRun, to benefit from d->m_showingError etc.
485     KRun::handleError(job);
486 }
487 
488 // static
makeErrorUrl(int error,const QString & errorText,const QUrl & initialUrl)489 QUrl BrowserRun::makeErrorUrl(int error, const QString &errorText, const QUrl &initialUrl)
490 {
491     /*
492      * The format of the error:/ URL is error:/?query#url,
493      * where two variables are passed in the query:
494      * error = int kio error code, errText = QString error text from kio
495      * The sub-url is the URL that we were trying to open.
496      */
497     QUrl newURL(QStringLiteral("error:/?error=%1&errText=%2").arg(error).arg(QString::fromUtf8(QUrl::toPercentEncoding(errorText))));
498 
499     QString cleanedOrigUrl = initialUrl.toString();
500     QUrl runURL(cleanedOrigUrl);
501     if (runURL.isValid()) {
502         runURL.setPassword(QString()); // don't put the password in the error URL
503         cleanedOrigUrl = runURL.toString();
504     }
505 
506     newURL.setFragment(cleanedOrigUrl);
507     return newURL;
508 }
509 
redirectToError(int error,const QString & errorText)510 void BrowserRun::redirectToError(int error, const QString &errorText)
511 {
512     /**
513      * To display this error in KHTMLPart instead of inside a dialog box,
514      * we tell konq that the mimetype is text/html, and we redirect to
515      * an error:/ URL that sends the info to khtml.
516      */
517     KRun::setUrl(makeErrorUrl(error, errorText, url()));
518     setJob(nullptr);
519     mimeTypeDetermined(QStringLiteral("text/html"));
520 }
521 
slotCopyToTempFileResult(KJob * job)522 void BrowserRun::slotCopyToTempFileResult(KJob *job)
523 {
524     if (job->error()) {
525         job->uiDelegate()->showErrorMessage();
526     } else {
527         // Same as KRun::foundMimeType but with a different URL
528         const QUrl destUrl = static_cast<KIO::FileCopyJob *>(job)->destUrl();
529         KIO::OpenUrlJob *job = new KIO::OpenUrlJob(destUrl, d->m_mimeType);
530         job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, d->m_window));
531         job->setRunExecutables(true);
532         job->start();
533     }
534     setError(true); // see above
535     setFinished(true);
536 }
537 
isTextExecutable(const QString & mimeType)538 bool BrowserRun::isTextExecutable(const QString &mimeType)
539 {
540     return (mimeType == QLatin1String("application/x-desktop") || mimeType == QLatin1String("application/x-shellscript"));
541 }
542 
hideErrorDialog() const543 bool BrowserRun::hideErrorDialog() const
544 {
545     return d->m_bHideErrorDialog;
546 }
547 
contentDisposition() const548 QString BrowserRun::contentDisposition() const
549 {
550     return d->m_contentDisposition;
551 }
552 
serverSuggestsSave() const553 bool BrowserRun::serverSuggestsSave() const
554 {
555     // RfC 2183, section 2.8:
556     // Unrecognized disposition types should be treated as `attachment'.
557     return !contentDisposition().isEmpty() && (contentDisposition() != QLatin1String("inline"));
558 }
559 
arguments()560 KParts::OpenUrlArguments &KParts::BrowserRun::arguments()
561 {
562     return d->m_args;
563 }
564 
browserArguments()565 KParts::BrowserArguments &KParts::BrowserRun::browserArguments()
566 {
567     return d->m_browserArgs;
568 }
569 
570 #include "moc_browserrun.cpp"
571 
572 #endif
573