some more win32'fication to fix non-ascii filename handling
[kdelibs.git] / kparts / browserrun.cpp
blobb1e28bf94b6965b44aab9cdf1095282ea005eef3
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>
22 #include <kio/job.h>
23 #include <kio/jobuidelegate.h>
24 #include <kio/scheduler.h>
25 #include <kio/copyjob.h>
26 #include <klocale.h>
27 #include <kshell.h>
28 #include <kstringhandler.h>
29 #include <kmimetypetrader.h>
30 #include <ktemporaryfile.h>
31 #include <kdebug.h>
32 #include <kde_file.h>
33 #include <kstandarddirs.h>
34 #include <assert.h>
36 using namespace KParts;
38 class BrowserRun::BrowserRunPrivate
40 public:
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;
49 QString m_mimeType;
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;
63 d->m_args = args;
64 d->m_browserArgs = browserArgs;
65 d->m_part = part;
66 d->m_window = window;
69 BrowserRun::~BrowserRun()
71 delete d;
74 KParts::ReadOnlyPart* BrowserRun::part() const
76 return d->m_part;
79 KUrl BrowserRun::url() const
81 return KRun::url();
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() );
93 return;
95 if ( !isLocalFile() && !hasError() && KRun::url().isLocalFile() )
96 setIsLocalFile( true );
98 if ( isLocalFile() ) {
99 KDE_struct_stat buff;
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() );
104 return;
106 setMode( buff.st_mode ); // while we're at it, save it for KRun::init() to use it
109 KRun::init();
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() );
122 assert( mime );
123 if ( mime->name() != "application/octet-stream" || isLocalFile() )
125 kDebug(1000) << "MIME TYPE is" << mime->name();
126 mimeTypeDetermined( mime->name() );
127 return;
131 QMap<QString, QString>& metaData = d->m_args.metaData();
132 if ( d->m_part ) {
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() );
153 } else {
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 &)));
168 setJob( job );
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() );
182 setJob( 0 );
183 mimeTypeDetermined( "inode/directory" );
185 else
187 if ( job->error() )
188 handleError( job );
189 else
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;
205 handleError(job);
206 setJob( 0 );
207 } else {
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;
219 job->putOnHold();
220 setJob( 0 );
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";
243 setFinished( true );
244 return Handled;
246 else if ( res == KParts::BrowserRun::Cancel ) {
247 // saving done or canceled
248 kDebug(1000) << "Cancel: returning Handled";
249 setFinished( true );
250 return 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;
260 QString extension;
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);
268 tempFile.open();
269 KUrl destURL;
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.)
284 setFinished( true );
285 return Handled;
288 KIO::SimpleJob::removeOnHold(); // Kill any slave that was put on hold.
289 return NotHandled;
292 //static
293 bool BrowserRun::allowExecution( const QString &mimeType, const KUrl &url )
295 if ( !KRun::isExecutable( mimeType ) )
296 return true;
298 if ( !url.isLocalFile() ) // Don't permit to execute remote files
299 return false;
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);
322 else
323 return i18n("Open '%3'?\nName: %2\nType: %1", comment, suggestedFileName, surl);
326 //static
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
350 //static
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);
360 // Don't ask for:
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...
368 // KEEP IN SYNC!!!
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" ) ) )
377 return Open;
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 );
396 // static
397 void BrowserRun::simpleSave( const KUrl & url, const QString & suggestedFileName,
398 QWidget* window )
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);
413 if (cmd.isEmpty())
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());
419 cfg.sync ();
421 else
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);
435 return;
440 // no download manager available, let's do it ourself
441 KFileDialog *dlg = new KFileDialog( QString(), QString() /*all files*/,
442 window);
443 dlg->setOperationMode( KFileDialog::Saving );
444 dlg->setCaption(i18n("Save As"));
446 dlg->setSelection( suggestedFileName.isEmpty() ? url.fileName() : suggestedFileName );
447 if ( dlg->exec() )
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 );
457 delete dlg;
460 void BrowserRun::slotStatResult( KJob *job )
462 if ( job->error() ) {
463 kDebug(1000) << job->errorString();
464 handleError( job );
465 } else
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;
473 return;
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
480 tjob->putOnHold();
481 setJob(0);
482 if (!d->m_mimeType.isEmpty())
483 mimeTypeDetermined(d->m_mimeType);
484 return;
487 if (d->m_bHideErrorDialog && job->error() != KIO::ERR_NO_CONTENT)
489 redirectToError( job->error(), job->errorText() );
490 return;
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")
510 .arg( error )
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
515 KUrl::List lst;
516 lst << newURL << runURL;
517 KRun::setUrl( KUrl::join( lst ) );
519 setJob( 0 );
520 mimeTypeDetermined( "text/html" );
523 void BrowserRun::slotCopyToTempFileResult(KJob *job)
525 if ( job->error() ) {
526 job->uiDelegate()->showErrorMessage();
527 } else {
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
532 setFinished( true );
533 timer().start( 0 );
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()
553 return d->m_args;
556 KParts::BrowserArguments& KParts::BrowserRun::browserArguments()
558 return d->m_browserArgs;
561 #include "browserrun.moc"