1 /* This file is part of the KDE project
3 * Copyright (C) 2002 David Faure <faure@kde.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Library General Public License for more details.
13 * You should have received a copy of the GNU Library General Public License
14 * along with this library; see the file COPYING.LIB. If not, write to
15 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
19 #include "browserrun.h"
20 #include <kmessagebox.h>
21 #include <kfiledialog.h>
23 #include <kio/jobuidelegate.h>
24 #include <kio/scheduler.h>
25 #include <kio/copyjob.h>
28 #include <kstringhandler.h>
29 #include <kmimetypetrader.h>
30 #include <ktemporaryfile.h>
33 #include <kstandarddirs.h>
36 using namespace KParts
;
38 class BrowserRun::BrowserRunPrivate
41 bool m_bHideErrorDialog
;
42 bool m_bRemoveReferrer
;
43 bool m_bTrustedSource
;
44 KParts::OpenUrlArguments m_args
;
45 KParts::BrowserArguments m_browserArgs
;
47 KParts::ReadOnlyPart
*m_part
; // QGuardedPtr?
48 QPointer
<QWidget
> m_window
;
50 QString m_contentDisposition
;
53 BrowserRun::BrowserRun( const KUrl
& url
, const KParts::OpenUrlArguments
& args
,
54 const KParts::BrowserArguments
& browserArgs
,
55 KParts::ReadOnlyPart
*part
, QWidget
* window
,
56 bool removeReferrer
, bool trustedSource
, bool hideErrorDialog
)
57 : KRun( url
, window
, 0 /*mode*/, false /*is_local_file known*/, false /* no GUI */ ),
58 d(new BrowserRunPrivate
)
60 d
->m_bHideErrorDialog
= hideErrorDialog
;
61 d
->m_bRemoveReferrer
= removeReferrer
;
62 d
->m_bTrustedSource
= trustedSource
;
64 d
->m_browserArgs
= browserArgs
;
69 BrowserRun::~BrowserRun()
74 KParts::ReadOnlyPart
* BrowserRun::part() const
79 KUrl
BrowserRun::url() const
84 void BrowserRun::init()
86 if ( d
->m_bHideErrorDialog
)
88 // ### KRun doesn't call a virtual method when it finds out that the URL
89 // is either malformed, or points to a non-existing local file...
90 // So we need to reimplement some of the checks, to handle d->m_bHideErrorDialog
91 if ( !KRun::url().isValid() ) {
92 redirectToError( KIO::ERR_MALFORMED_URL
, KRun::url().url() );
95 if ( !isLocalFile() && !hasError() && KRun::url().isLocalFile() )
96 setIsLocalFile( true );
98 if ( isLocalFile() ) {
100 if ( KDE::stat( KRun::url().toLocalFile(), &buff
) == -1 )
102 kDebug(1000) << KRun::url().toLocalFile() << "doesn't exist.";
103 redirectToError( KIO::ERR_DOES_NOT_EXIST
, KRun::url().toLocalFile() );
106 setMode( buff
.st_mode
); // while we're at it, save it for KRun::init() to use it
112 void BrowserRun::scanFile()
114 kDebug(1000) << KRun::url();
116 // Let's check for well-known extensions
117 // Not when there is a query in the URL, in any case.
118 // Optimization for http/https, findByURL doesn't trust extensions over http.
119 if ( KRun::url().query().isEmpty() && !KRun::url().protocol().startsWith("http") )
121 KMimeType::Ptr mime
= KMimeType::findByUrl( KRun::url() );
123 if ( mime
->name() != "application/octet-stream" || isLocalFile() )
125 kDebug(1000) << "MIME TYPE is" << mime
->name();
126 mimeTypeDetermined( mime
->name() );
131 QMap
<QString
, QString
>& metaData
= d
->m_args
.metaData();
133 const QString proto
= d
->m_part
->url().protocol().toLower();
135 if (proto
== "https" || proto
== "webdavs") {
136 metaData
.insert("main_frame_request", "TRUE" );
137 metaData
.insert("ssl_was_in_use", "TRUE" );
138 metaData
.insert("ssl_activate_warnings", "TRUE" );
139 } else if (proto
== "http" || proto
== "webdav") {
140 metaData
.insert("ssl_activate_warnings", "TRUE" );
141 metaData
.insert("ssl_was_in_use", "FALSE" );
144 // Set the PropagateHttpHeader meta-data if it has not already been set...
145 if (!metaData
.contains("PropagateHttpHeader"))
146 metaData
.insert("PropagateHttpHeader", "TRUE");
149 KIO::TransferJob
*job
;
150 if ( d
->m_browserArgs
.doPost() && KRun::url().protocol().startsWith("http")) {
151 job
= KIO::http_post( KRun::url(), d
->m_browserArgs
.postData
, KIO::HideProgressInfo
);
152 job
->addMetaData( "content-type", d
->m_browserArgs
.contentType() );
154 job
= KIO::get(KRun::url(),
155 d
->m_args
.reload() ? KIO::Reload
: KIO::NoReload
,
156 KIO::HideProgressInfo
);
159 if ( d
->m_bRemoveReferrer
)
160 metaData
.remove("referrer");
162 job
->addMetaData( metaData
);
163 job
->ui()->setWindow( d
->m_window
);
164 connect( job
, SIGNAL( result( KJob
*)),
165 this, SLOT( slotBrowserScanFinished(KJob
*)));
166 connect( job
, SIGNAL( mimetype( KIO::Job
*, const QString
&)),
167 this, SLOT( slotBrowserMimetype(KIO::Job
*, const QString
&)));
171 void BrowserRun::slotBrowserScanFinished(KJob
*job
)
173 kDebug(1000) << job
->error();
174 if ( job
->error() == KIO::ERR_IS_DIRECTORY
)
176 // It is in fact a directory. This happens when HTTP redirects to FTP.
177 // Due to the "protocol doesn't support listing" code in BrowserRun, we
178 // assumed it was a file.
179 kDebug(1000) << "It is in fact a directory!";
180 // Update our URL in case of a redirection
181 KRun::setUrl( static_cast<KIO::TransferJob
*>(job
)->url() );
183 mimeTypeDetermined( "inode/directory" );
190 KRun::slotScanFinished(job
);
194 void BrowserRun::slotBrowserMimetype( KIO::Job
*_job
, const QString
&type
)
196 Q_ASSERT( _job
== KRun::job() );
197 KIO::TransferJob
*job
= static_cast<KIO::TransferJob
*>(KRun::job());
198 // Update our URL in case of a redirection
199 //kDebug(1000) << "old URL=" << KRun::url();
200 //kDebug(1000) << "new URL=" << job->url();
201 setUrl( job
->url() );
203 if (job
->isErrorPage()) {
204 d
->m_mimeType
= type
;
208 kDebug(1000) << "found" << type
<< "for" << KRun::url();
210 // Suggested filename given by the server (e.g. HTTP content-disposition)
211 // When set, we should really be saving instead of embedding
212 const QString suggestedFileName
= job
->queryMetaData("content-disposition-filename");
213 setSuggestedFileName(suggestedFileName
); // store it (in KRun)
214 //kDebug(1000) << "suggestedFileName=" << suggestedFileName;
215 d
->m_contentDisposition
= job
->queryMetaData("content-disposition-type");
217 // Make a copy to avoid a dead reference
218 QString _type
= type
;
222 mimeTypeDetermined( _type
);
226 BrowserRun::NonEmbeddableResult
BrowserRun::handleNonEmbeddable( const QString
& _mimeType
)
228 QString
mimeType( _mimeType
);
229 Q_ASSERT( !hasFinished() ); // only come here if the mimetype couldn't be embedded
230 // Support for saving remote files.
231 if ( mimeType
!= "inode/directory" && // dirs can't be saved
232 !KRun::url().isLocalFile() )
234 if ( isTextExecutable(mimeType
) )
235 mimeType
= QLatin1String("text/plain"); // view, don't execute
236 kDebug(1000) << "ask for saving";
237 KService::Ptr offer
= KMimeTypeTrader::self()->preferredService(mimeType
, "Application");
238 // ... -> ask whether to save
239 KParts::BrowserRun::AskSaveResult res
= askSave( KRun::url(), offer
, mimeType
, suggestedFileName() );
240 if ( res
== KParts::BrowserRun::Save
) {
241 save( KRun::url(), suggestedFileName() );
242 kDebug(1000) << "Save: returning Handled";
246 else if ( res
== KParts::BrowserRun::Cancel
) {
247 // saving done or canceled
248 kDebug(1000) << "Cancel: returning Handled";
252 else // "Open" chosen (done by KRun::foundMimeType, called when returning NotHandled)
254 // If we were in a POST, we can't just pass a URL to an external application.
255 // We must save the data to a tempfile first.
256 if ( d
->m_browserArgs
.doPost() )
258 kDebug(1000) << "request comes from a POST, can't pass a URL to another app, need to save";
259 d
->m_mimeType
= mimeType
;
261 QString fileName
= suggestedFileName().isEmpty() ? KRun::url().fileName() : suggestedFileName();
262 int extensionPos
= fileName
.lastIndexOf( '.' );
263 if ( extensionPos
!= -1 )
264 extension
= fileName
.mid( extensionPos
); // keep the '.'
265 KTemporaryFile tempFile
;
266 tempFile
.setSuffix(extension
);
267 tempFile
.setAutoRemove(false);
270 destURL
.setPath( tempFile
.fileName() );
271 KIO::Job
*job
= KIO::file_copy( KRun::url(), destURL
, 0600, KIO::Overwrite
);
272 job
->ui()->setWindow(d
->m_window
);
273 connect( job
, SIGNAL(result(KJob
*)),
274 this, SLOT(slotCopyToTempFileResult(KJob
*)) );
275 return Delayed
; // We'll continue after the job has finished
280 // Check if running is allowed
281 if ( !d
->m_bTrustedSource
&& // ... and untrusted source...
282 !allowExecution( mimeType
, KRun::url() ) ) // ...and the user said no (for executables etc.)
288 KIO::SimpleJob::removeOnHold(); // Kill any slave that was put on hold.
293 bool BrowserRun::allowExecution( const QString
&mimeType
, const KUrl
&url
)
295 if ( !KRun::isExecutable( mimeType
) )
298 if ( !url
.isLocalFile() ) // Don't permit to execute remote files
301 return ( KMessageBox::warningContinueCancel( 0,
302 i18n( "Do you really want to execute '%1'?", url
.prettyUrl() ),
303 i18n("Execute File?"), KGuiItem(i18n("Execute")) ) == KMessageBox::Continue
);
306 static QString
makeQuestion( const KUrl
& url
, const QString
& mimeType
, const QString
& suggestedFileName
)
308 QString surl
= url
.prettyUrl();
309 KMimeType::Ptr mime
= KMimeType::mimeType(mimeType
, KMimeType::ResolveAliases
);
310 QString comment
= mimeType
;
312 // Test if the mimeType is not recognize as octet-stream.
313 // If so then keep mime-type as comment
314 if (mime
&& mime
->name() != KMimeType::defaultMimeType()) {
315 // The mime-type is known so display the comment instead of mime-type
316 comment
= mime
->comment();
318 // The strange order in the i18n() calls below is due to the possibility
319 // of surl containing a '%'
320 if ( suggestedFileName
.isEmpty() )
321 return i18n("Open '%2'?\nType: %1", comment
, surl
);
323 return i18n("Open '%3'?\nName: %2\nType: %1", comment
, suggestedFileName
, surl
);
327 // TODO should take a QWidget* parent argument
328 BrowserRun::AskSaveResult
BrowserRun::askSave( const KUrl
& url
, KService::Ptr offer
, const QString
& mimeType
, const QString
& suggestedFileName
)
330 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC
331 // NOTE: Keep this function in sync with kdebase/kcontrol/filetypes/filetypedetails.cpp
332 // FileTypeDetails::updateAskSave()
334 QString question
= makeQuestion( url
, mimeType
, suggestedFileName
);
336 // Text used for the open button
337 QString openText
= (offer
&& !offer
->name().isEmpty())
338 ? i18n("&Open with '%1'", offer
->name())
339 : i18n("&Open With...");
341 int choice
= KMessageBox::questionYesNoCancel(
342 0, question
, url
.host(),
343 KStandardGuiItem::saveAs(), KGuiItem(openText
), KStandardGuiItem::cancel(),
344 QLatin1String("askSave")+ mimeType
); // dontAskAgainName, KEEP IN SYNC!!!
346 return choice
== KMessageBox::Yes
? Save
: ( choice
== KMessageBox::No
? Open
: Cancel
);
347 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC
351 // TODO should take a QWidget* parent argument
352 BrowserRun::AskSaveResult
BrowserRun::askEmbedOrSave( const KUrl
& url
, const QString
& mimeType
, const QString
& suggestedFileName
, int flags
)
354 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC
355 // NOTE: Keep this function in sync with
356 // kdebase/apps/konqueror/settings/filetypes/filetypedetails.cpp
357 // FileTypeDetails::updateAskSave()
359 KMimeType::Ptr mime
= KMimeType::mimeType(mimeType
, KMimeType::ResolveAliases
);
361 // - html (even new tabs would ask, due to about:blank!)
362 // - dirs obviously (though not common over HTTP :),
363 // - images (reasoning: no need to save, most of the time, because fast to see)
364 // e.g. postscript is different, because takes longer to read, so
365 // it's more likely that the user might want to save it.
366 // - multipart/* ("server push", see kmultipart)
367 // - other strange 'internal' mimetypes like print/manager...
369 if (flags
!= (int)AttachmentDisposition
&& mime
&& (
370 mime
->is( "text/html" ) ||
371 mime
->is( "application/xml" ) ||
372 mime
->is( "inode/directory" ) ||
373 mimeType
.startsWith( "image" ) ||
374 mime
->is( "multipart/x-mixed-replace" ) ||
375 mime
->is( "multipart/replace" ) ||
376 mimeType
.startsWith( "print" ) ) )
379 QString question
= makeQuestion( url
, mimeType
, suggestedFileName
);
381 // don't use KStandardGuiItem::open() here which has trailing ellipsis!
382 int choice
= KMessageBox::questionYesNoCancel(
383 0, question
, url
.host(),
384 KStandardGuiItem::saveAs(), KGuiItem( i18n( "&Open" ), "document-open"), KStandardGuiItem::cancel(),
385 QLatin1String("askEmbedOrSave")+ mimeType
); // dontAskAgainName, KEEP IN SYNC!!!
386 return choice
== KMessageBox::Yes
? Save
: ( choice
== KMessageBox::No
? Open
: Cancel
);
387 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC
390 // Default implementation, overridden in KHTMLRun
391 void BrowserRun::save( const KUrl
& url
, const QString
& suggestedFileName
)
393 simpleSave( url
, suggestedFileName
, d
->m_window
);
397 void BrowserRun::simpleSave( const KUrl
& url
, const QString
& suggestedFileName
,
400 // DownloadManager <-> konqueror integration
401 // find if the integration is enabled
402 // the empty key means no integration
403 // only use the downloadmanager for non-local urls
404 if ( !url
.isLocalFile() )
406 KConfigGroup cfg
= KSharedConfig::openConfig("konquerorrc", KConfig::NoGlobals
)->group("HTML Settings");
407 QString downloadManger
= cfg
.readPathEntry("DownloadManager", QString());
408 if (!downloadManger
.isEmpty())
410 // then find the download manager location
411 kDebug(1000) << "Using: "<<downloadManger
<<" as Download Manager";
412 QString cmd
=KStandardDirs::findExe(downloadManger
);
415 QString errMsg
=i18n("The Download Manager (%1) could not be found in your $PATH ", downloadManger
);
416 QString errMsgEx
= i18n("Try to reinstall it \n\nThe integration with Konqueror will be disabled.");
417 KMessageBox::detailedSorry(0,errMsg
,errMsgEx
);
418 cfg
.writePathEntry("DownloadManager",QString());
423 // ### suggestedFileName not taken into account. Fix this (and
424 // the duplicated code) with shiny new KDownload class for 3.2 (pfeiffer)
425 // Until the shiny new class comes about, send the suggestedFileName
426 // along with the actual URL to download. (DA)
427 cmd
+= " " + KShell::quoteArg(url
.url());
428 if ( !suggestedFileName
.isEmpty() )
429 cmd
+=" " + KShell::quoteArg(suggestedFileName
);
431 kDebug(1000) << "Calling command" << cmd
;
432 // slave is already on hold (slotBrowserMimetype())
433 KIO::Scheduler::publishSlaveOnHold();
434 KRun::runCommand(cmd
, window
);
440 // no download manager available, let's do it ourself
441 KFileDialog
*dlg
= new KFileDialog( QString(), QString() /*all files*/,
443 dlg
->setOperationMode( KFileDialog::Saving
);
444 dlg
->setCaption(i18n("Save As"));
446 dlg
->setSelection( suggestedFileName
.isEmpty() ? url
.fileName() : suggestedFileName
);
449 KUrl
destURL( dlg
->selectedUrl() );
450 if ( destURL
.isValid() )
452 KIO::Job
*job
= KIO::copy( url
, destURL
);
453 job
->ui()->setWindow (window
);
454 job
->ui()->setAutoErrorHandlingEnabled( true );
460 void BrowserRun::slotStatResult( KJob
*job
)
462 if ( job
->error() ) {
463 kDebug(1000) << job
->errorString();
466 KRun::slotStatResult( job
);
469 void BrowserRun::handleError( KJob
* job
)
471 if ( !job
) { // Shouldn't happen, see docu.
472 kWarning(1000) << "handleError called with job=0! hideErrorDialog=" << d
->m_bHideErrorDialog
;
476 KIO::TransferJob
*tjob
= qobject_cast
<KIO::TransferJob
*>(job
);
477 if (tjob
&& tjob
->isErrorPage() && !job
->error()) {
478 // The default handling of error pages is to show them like normal pages
479 // But this is done here in handleError so that KHTMLRun can reimplement it
482 if (!d
->m_mimeType
.isEmpty())
483 mimeTypeDetermined(d
->m_mimeType
);
487 if (d
->m_bHideErrorDialog
&& job
->error() != KIO::ERR_NO_CONTENT
)
489 redirectToError( job
->error(), job
->errorText() );
493 // Reuse code in KRun, to benefit from d->m_showingError etc.
494 KRun::slotStatResult( job
);
497 void BrowserRun::redirectToError( int error
, const QString
& errorText
)
500 * To display this error in KHTMLPart instead of inside a dialog box,
501 * we tell konq that the mimetype is text/html, and we redirect to
502 * an error:/ URL that sends the info to khtml.
504 * The format of the error:/ URL is error:/?query#url,
505 * where two variables are passed in the query:
506 * error = int kio error code, errText = QString error text from kio
507 * The sub-url is the URL that we were trying to open.
509 KUrl
newURL(QString("error:/?error=%1&errText=%2")
511 .arg( QString::fromUtf8( QUrl::toPercentEncoding( errorText
) ) ) );
512 KUrl runURL
= KRun::url();
513 runURL
.setPass( QString() ); // don't put the password in the error URL
516 lst
<< newURL
<< runURL
;
517 KRun::setUrl( KUrl::join( lst
) );
520 mimeTypeDetermined( "text/html" );
523 void BrowserRun::slotCopyToTempFileResult(KJob
*job
)
525 if ( job
->error() ) {
526 job
->uiDelegate()->showErrorMessage();
528 // Same as KRun::foundMimeType but with a different URL
529 (void) (KRun::runUrl( static_cast<KIO::FileCopyJob
*>(job
)->destUrl(), d
->m_mimeType
, d
->m_window
));
531 setError( true ); // see above
536 bool BrowserRun::isTextExecutable( const QString
&mimeType
)
538 return ( mimeType
== "application/x-desktop" ||
539 mimeType
== "application/x-shellscript" );
542 bool BrowserRun::hideErrorDialog() const
544 return d
->m_bHideErrorDialog
;
547 QString
BrowserRun::contentDisposition() const {
548 return d
->m_contentDisposition
;
551 KParts::OpenUrlArguments
& KParts::BrowserRun::arguments()
556 KParts::BrowserArguments
& KParts::BrowserRun::browserArguments()
558 return d
->m_browserArgs
;
561 #include "browserrun.moc"