KParts
browserrun.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE project 00002 * 00003 * Copyright (C) 2002 David Faure <faure@kde.org> 00004 * This library is free software; you can redistribute it and/or 00005 * modify it under the terms of the GNU Library General Public 00006 * License version 2, as published by the Free Software Foundation. 00007 * 00008 * This library is distributed in the hope that it will be useful, 00009 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00011 * Library General Public License for more details. 00012 * 00013 * You should have received a copy of the GNU Library General Public License 00014 * along with this library; see the file COPYING.LIB. If not, write to 00015 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00016 * Boston, MA 02110-1301, USA. 00017 */ 00018 00019 #include "browserrun.h" 00020 #include "browserrun_p.h" 00021 00022 #include <kmessagebox.h> 00023 #include <kfiledialog.h> 00024 #include <kio/job.h> 00025 #include <kio/jobuidelegate.h> 00026 #include <kio/scheduler.h> 00027 #include <kio/copyjob.h> 00028 #include <klocale.h> 00029 #include <kshell.h> 00030 #include <kstringhandler.h> 00031 #include <kmimetypetrader.h> 00032 #include <ktemporaryfile.h> 00033 #include <kdebug.h> 00034 #include <kde_file.h> 00035 #include <kstandarddirs.h> 00036 #include <kdatetime.h> 00037 #include "browseropenorsavequestion.h" 00038 #include <assert.h> 00039 00040 using namespace KParts; 00041 00042 class BrowserRun::BrowserRunPrivate 00043 { 00044 public: 00045 bool m_bHideErrorDialog; 00046 bool m_bRemoveReferrer; 00047 bool m_bTrustedSource; 00048 KParts::OpenUrlArguments m_args; 00049 KParts::BrowserArguments m_browserArgs; 00050 00051 KParts::ReadOnlyPart *m_part; // QGuardedPtr? 00052 QPointer<QWidget> m_window; 00053 QString m_mimeType; 00054 QString m_contentDisposition; 00055 }; 00056 00057 BrowserRun::BrowserRun( const KUrl& url, const KParts::OpenUrlArguments& args, 00058 const KParts::BrowserArguments& browserArgs, 00059 KParts::ReadOnlyPart *part, QWidget* window, 00060 bool removeReferrer, bool trustedSource, bool hideErrorDialog ) 00061 : KRun( url, window, 0 /*mode*/, false /*is_local_file known*/, false /* no GUI */ ), 00062 d(new BrowserRunPrivate) 00063 { 00064 d->m_bHideErrorDialog = hideErrorDialog; 00065 d->m_bRemoveReferrer = removeReferrer; 00066 d->m_bTrustedSource = trustedSource; 00067 d->m_args = args; 00068 d->m_browserArgs = browserArgs; 00069 d->m_part = part; 00070 d->m_window = window; 00071 } 00072 00073 BrowserRun::~BrowserRun() 00074 { 00075 delete d; 00076 } 00077 00078 KParts::ReadOnlyPart* BrowserRun::part() const 00079 { 00080 return d->m_part; 00081 } 00082 00083 KUrl BrowserRun::url() const 00084 { 00085 return KRun::url(); 00086 } 00087 00088 void BrowserRun::init() 00089 { 00090 if ( d->m_bHideErrorDialog ) 00091 { 00092 // ### KRun doesn't call a virtual method when it finds out that the URL 00093 // is either malformed, or points to a non-existing local file... 00094 // So we need to reimplement some of the checks, to handle d->m_bHideErrorDialog 00095 if ( !KRun::url().isValid() ) { 00096 redirectToError( KIO::ERR_MALFORMED_URL, KRun::url().url() ); 00097 return; 00098 } 00099 if ( !isLocalFile() && !hasError() && KRun::url().isLocalFile() ) 00100 setIsLocalFile( true ); 00101 00102 if ( isLocalFile() ) { 00103 KDE_struct_stat buff; 00104 if ( KDE::stat( KRun::url().toLocalFile(), &buff ) == -1 ) 00105 { 00106 kDebug(1000) << KRun::url().toLocalFile() << "doesn't exist."; 00107 redirectToError( KIO::ERR_DOES_NOT_EXIST, KRun::url().toLocalFile() ); 00108 return; 00109 } 00110 setMode( buff.st_mode ); // while we're at it, save it for KRun::init() to use it 00111 } 00112 } 00113 KRun::init(); 00114 } 00115 00116 void BrowserRun::scanFile() 00117 { 00118 kDebug(1000) << KRun::url(); 00119 00120 // Let's check for well-known extensions 00121 // Not when there is a query in the URL, in any case. 00122 // Optimization for http/https, findByURL doesn't trust extensions over http. 00123 if ( KRun::url().query().isEmpty() && !KRun::url().protocol().startsWith(QLatin1String("http")) ) 00124 { 00125 KMimeType::Ptr mime = KMimeType::findByUrl( KRun::url() ); 00126 assert( mime ); 00127 if ( !mime->isDefault() || isLocalFile() ) 00128 { 00129 kDebug(1000) << "MIME TYPE is" << mime->name(); 00130 mimeTypeDetermined( mime->name() ); 00131 return; 00132 } 00133 } 00134 00135 QMap<QString, QString>& metaData = d->m_args.metaData(); 00136 if ( d->m_part ) { 00137 const QString proto = d->m_part->url().protocol().toLower(); 00138 00139 if (proto == "https" || proto == "webdavs") { 00140 metaData.insert("main_frame_request", "TRUE" ); 00141 metaData.insert("ssl_was_in_use", "TRUE" ); 00142 // metaData.insert("ssl_activate_warnings", "TRUE" ); 00143 } else if (proto == "http" || proto == "webdav") { 00144 // metaData.insert("ssl_activate_warnings", "TRUE" ); 00145 metaData.insert("ssl_was_in_use", "FALSE" ); 00146 } 00147 00148 // Set the PropagateHttpHeader meta-data if it has not already been set... 00149 if (!metaData.contains("PropagateHttpHeader")) 00150 metaData.insert("PropagateHttpHeader", "TRUE"); 00151 } 00152 00153 KIO::TransferJob *job; 00154 if ( d->m_browserArgs.doPost() && KRun::url().protocol().startsWith(QLatin1String("http"))) { 00155 job = KIO::http_post( KRun::url(), d->m_browserArgs.postData, KIO::HideProgressInfo ); 00156 job->addMetaData( "content-type", d->m_browserArgs.contentType() ); 00157 } else { 00158 job = KIO::get(KRun::url(), 00159 d->m_args.reload() ? KIO::Reload : KIO::NoReload, 00160 KIO::HideProgressInfo); 00161 } 00162 00163 if ( d->m_bRemoveReferrer ) 00164 metaData.remove("referrer"); 00165 00166 job->addMetaData( metaData ); 00167 job->ui()->setWindow( d->m_window ); 00168 connect( job, SIGNAL( result( KJob *)), 00169 this, SLOT( slotBrowserScanFinished(KJob *))); 00170 connect( job, SIGNAL( mimetype( KIO::Job *, const QString &)), 00171 this, SLOT( slotBrowserMimetype(KIO::Job *, const QString &))); 00172 setJob( job ); 00173 } 00174 00175 void BrowserRun::slotBrowserScanFinished(KJob *job) 00176 { 00177 kDebug(1000) << job->error(); 00178 if ( job->error() == KIO::ERR_IS_DIRECTORY ) 00179 { 00180 // It is in fact a directory. This happens when HTTP redirects to FTP. 00181 // Due to the "protocol doesn't support listing" code in BrowserRun, we 00182 // assumed it was a file. 00183 kDebug(1000) << "It is in fact a directory!"; 00184 // Update our URL in case of a redirection 00185 KRun::setUrl( static_cast<KIO::TransferJob *>(job)->url() ); 00186 setJob( 0 ); 00187 mimeTypeDetermined( "inode/directory" ); 00188 } 00189 else 00190 { 00191 if ( job->error() ) 00192 handleError( job ); 00193 else 00194 KRun::slotScanFinished(job); 00195 } 00196 } 00197 00198 void BrowserRun::slotBrowserMimetype( KIO::Job *_job, const QString &type ) 00199 { 00200 Q_ASSERT( _job == KRun::job() ); Q_UNUSED(_job) 00201 KIO::TransferJob *job = static_cast<KIO::TransferJob *>(KRun::job()); 00202 // Update our URL in case of a redirection 00203 //kDebug(1000) << "old URL=" << KRun::url(); 00204 //kDebug(1000) << "new URL=" << job->url(); 00205 setUrl( job->url() ); 00206 00207 if (job->isErrorPage()) { 00208 d->m_mimeType = type; 00209 handleError(job); 00210 setJob( 0 ); 00211 } else { 00212 kDebug(1000) << "found" << type << "for" << KRun::url(); 00213 00214 // Suggested filename given by the server (e.g. HTTP content-disposition) 00215 // When set, we should really be saving instead of embedding 00216 const QString suggestedFileName = job->queryMetaData("content-disposition-filename"); 00217 setSuggestedFileName(suggestedFileName); // store it (in KRun) 00218 //kDebug(1000) << "suggestedFileName=" << suggestedFileName; 00219 d->m_contentDisposition = job->queryMetaData("content-disposition-type"); 00220 00221 const QString modificationTime = job->queryMetaData("content-disposition-modification-date"); 00222 if (!modificationTime.isEmpty()) { 00223 d->m_args.metaData().insert(QLatin1String("content-disposition-modification-date"), modificationTime); 00224 } 00225 00226 QMapIterator<QString,QString> it (job->metaData()); 00227 while (it.hasNext()) { 00228 it.next(); 00229 if (it.key().startsWith(QLatin1String("ssl_"), Qt::CaseInsensitive)) 00230 d->m_args.metaData().insert(it.key(), it.value()); 00231 } 00232 00233 // Make a copy to avoid a dead reference 00234 QString _type = type; 00235 job->putOnHold(); 00236 setJob( 0 ); 00237 00238 mimeTypeDetermined( _type ); 00239 } 00240 } 00241 00242 BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString& mimeType) 00243 { 00244 KService::Ptr dummy; 00245 return handleNonEmbeddable(mimeType, &dummy); 00246 } 00247 00248 BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString& _mimeType, KService::Ptr* selectedService) 00249 { 00250 QString mimeType( _mimeType ); 00251 Q_ASSERT( !hasFinished() ); // only come here if the mimetype couldn't be embedded 00252 // Support for saving remote files. 00253 if ( mimeType != "inode/directory" && // dirs can't be saved 00254 !KRun::url().isLocalFile() ) 00255 { 00256 if ( isTextExecutable(mimeType) ) 00257 mimeType = QLatin1String("text/plain"); // view, don't execute 00258 // ... -> ask whether to save 00259 BrowserOpenOrSaveQuestion question(d->m_window, KRun::url(), mimeType); 00260 question.setSuggestedFileName(suggestedFileName()); 00261 if (selectedService) 00262 question.setFeatures(BrowserOpenOrSaveQuestion::ServiceSelection); 00263 BrowserOpenOrSaveQuestion::Result res = question.askOpenOrSave(); 00264 if (res == BrowserOpenOrSaveQuestion::Save) { 00265 save( KRun::url(), suggestedFileName() ); 00266 kDebug(1000) << "Save: returning Handled"; 00267 setFinished( true ); 00268 return Handled; 00269 } 00270 else if (res == BrowserOpenOrSaveQuestion::Cancel) { 00271 // saving done or canceled 00272 kDebug(1000) << "Cancel: returning Handled"; 00273 setFinished( true ); 00274 return Handled; 00275 } 00276 else // "Open" chosen (done by KRun::foundMimeType, called when returning NotHandled) 00277 { 00278 // If we were in a POST, we can't just pass a URL to an external application. 00279 // We must save the data to a tempfile first. 00280 if ( d->m_browserArgs.doPost() ) 00281 { 00282 kDebug(1000) << "request comes from a POST, can't pass a URL to another app, need to save"; 00283 d->m_mimeType = mimeType; 00284 QString extension; 00285 QString fileName = suggestedFileName().isEmpty() ? KRun::url().fileName() : suggestedFileName(); 00286 int extensionPos = fileName.lastIndexOf( '.' ); 00287 if ( extensionPos != -1 ) 00288 extension = fileName.mid( extensionPos ); // keep the '.' 00289 KTemporaryFile tempFile; 00290 tempFile.setSuffix(extension); 00291 tempFile.setAutoRemove(false); 00292 tempFile.open(); 00293 KUrl destURL; 00294 destURL.setPath( tempFile.fileName() ); 00295 KIO::Job *job = KIO::file_copy( KRun::url(), destURL, 0600, KIO::Overwrite ); 00296 job->ui()->setWindow(d->m_window); 00297 connect( job, SIGNAL(result(KJob *)), 00298 this, SLOT(slotCopyToTempFileResult(KJob *)) ); 00299 return Delayed; // We'll continue after the job has finished 00300 } 00301 if (selectedService) 00302 *selectedService = question.selectedService(); 00303 } 00304 } 00305 00306 // Check if running is allowed 00307 if ( !d->m_bTrustedSource && // ... and untrusted source... 00308 !allowExecution( mimeType, KRun::url() ) ) // ...and the user said no (for executables etc.) 00309 { 00310 setFinished( true ); 00311 return Handled; 00312 } 00313 00314 KIO::SimpleJob::removeOnHold(); // Kill any slave that was put on hold. 00315 return NotHandled; 00316 } 00317 00318 //static 00319 bool BrowserRun::allowExecution( const QString &mimeType, const KUrl &url ) 00320 { 00321 if ( !KRun::isExecutable( mimeType ) ) 00322 return true; 00323 00324 if ( !url.isLocalFile() ) // Don't permit to execute remote files 00325 return false; 00326 00327 return ( KMessageBox::warningContinueCancel( 0, 00328 i18n( "Do you really want to execute '%1'?", url.prettyUrl() ), 00329 i18n("Execute File?"), KGuiItem(i18n("Execute")) ) == KMessageBox::Continue ); 00330 } 00331 00332 //static, deprecated 00333 #ifndef KDE_NO_DEPRECATED 00334 BrowserRun::AskSaveResult BrowserRun::askSave( const KUrl & url, KService::Ptr offer, const QString& mimeType, const QString & suggestedFileName ) 00335 { 00336 Q_UNUSED(offer); 00337 BrowserOpenOrSaveQuestion question(0, url, mimeType); 00338 question.setSuggestedFileName(suggestedFileName); 00339 const BrowserOpenOrSaveQuestion::Result result = question.askOpenOrSave(); 00340 return result == BrowserOpenOrSaveQuestion::Save ? Save 00341 : BrowserOpenOrSaveQuestion::Open ? Open 00342 : Cancel; 00343 } 00344 #endif 00345 00346 //static, deprecated 00347 #ifndef KDE_NO_DEPRECATED 00348 BrowserRun::AskSaveResult BrowserRun::askEmbedOrSave( const KUrl & url, const QString& mimeType, const QString & suggestedFileName, int flags ) 00349 { 00350 BrowserOpenOrSaveQuestion question(0, url, mimeType); 00351 question.setSuggestedFileName(suggestedFileName); 00352 const BrowserOpenOrSaveQuestion::Result result = question.askEmbedOrSave(flags); 00353 return result == BrowserOpenOrSaveQuestion::Save ? Save 00354 : BrowserOpenOrSaveQuestion::Embed ? Open 00355 : Cancel; 00356 } 00357 #endif 00358 00359 // Default implementation, overridden in KHTMLRun 00360 void BrowserRun::save( const KUrl & url, const QString & suggestedFileName ) 00361 { 00362 saveUrl(url, suggestedFileName, d->m_window, d->m_args); 00363 } 00364 00365 // static 00366 void BrowserRun::simpleSave( const KUrl & url, const QString & suggestedFileName, 00367 QWidget* window ) 00368 { 00369 saveUrl(url, suggestedFileName, window, KParts::OpenUrlArguments()); 00370 } 00371 00372 void KParts::BrowserRun::saveUrl(const KUrl & url, const QString & suggestedFileName, 00373 QWidget* window, const KParts::OpenUrlArguments& args) 00374 { 00375 // DownloadManager <-> konqueror integration 00376 // find if the integration is enabled 00377 // the empty key means no integration 00378 // only use the downloadmanager for non-local urls 00379 if ( !url.isLocalFile() ) 00380 { 00381 KConfigGroup cfg = KSharedConfig::openConfig("konquerorrc", KConfig::NoGlobals)->group("HTML Settings"); 00382 QString downloadManger = cfg.readPathEntry("DownloadManager", QString()); 00383 if (!downloadManger.isEmpty()) 00384 { 00385 // then find the download manager location 00386 kDebug(1000) << "Using: "<<downloadManger <<" as Download Manager"; 00387 QString cmd=KStandardDirs::findExe(downloadManger); 00388 if (cmd.isEmpty()) 00389 { 00390 QString errMsg=i18n("The Download Manager (%1) could not be found in your $PATH ", downloadManger); 00391 QString errMsgEx= i18n("Try to reinstall it \n\nThe integration with Konqueror will be disabled."); 00392 KMessageBox::detailedSorry(0,errMsg,errMsgEx); 00393 cfg.writePathEntry("DownloadManager",QString()); 00394 cfg.sync (); 00395 } 00396 else 00397 { 00398 // ### suggestedFileName not taken into account. Fix this (and 00399 // the duplicated code) with shiny new KDownload class for 3.2 (pfeiffer) 00400 // Until the shiny new class comes about, send the suggestedFileName 00401 // along with the actual URL to download. (DA) 00402 cmd += ' ' + KShell::quoteArg(url.url()); 00403 if ( !suggestedFileName.isEmpty() ) 00404 cmd += ' ' + KShell::quoteArg(suggestedFileName); 00405 00406 kDebug(1000) << "Calling command" << cmd; 00407 // slave is already on hold (slotBrowserMimetype()) 00408 KIO::Scheduler::publishSlaveOnHold(); 00409 KRun::runCommand(cmd, window); 00410 return; 00411 } 00412 } 00413 } 00414 00415 // no download manager available, let's do it ourself 00416 KFileDialog *dlg = new KFileDialog( QString(), QString() /*all files*/, 00417 window); 00418 dlg->setOperationMode( KFileDialog::Saving ); 00419 dlg->setCaption(i18n("Save As")); 00420 dlg->setConfirmOverwrite(true); 00421 00422 QString name; 00423 if ( !suggestedFileName.isEmpty() ) 00424 name = suggestedFileName; 00425 else 00426 name = url.fileName(KUrl::ObeyTrailingSlash); // can be empty, e.g. in case http://www.kde.org/ 00427 00428 dlg->setSelection(name); 00429 if ( dlg->exec() ) 00430 { 00431 KUrl destURL( dlg->selectedUrl() ); 00432 if ( destURL.isValid() ) 00433 { 00434 saveUrlUsingKIO(url, destURL, window, args.metaData()); 00435 } 00436 } 00437 delete dlg; 00438 } 00439 00440 void BrowserRun::saveUrlUsingKIO(const KUrl & srcUrl, const KUrl& destUrl, 00441 QWidget* window, const QMap<QString, QString> &metaData) 00442 { 00443 KIO::FileCopyJob *job = KIO::file_copy(srcUrl, destUrl, -1, KIO::Overwrite); 00444 00445 const QString modificationTime = metaData[QLatin1String("content-disposition-modification-date")]; 00446 if (!modificationTime.isEmpty()) { 00447 job->setModificationTime(KDateTime::fromString(modificationTime, KDateTime::RFCDate).dateTime()); 00448 } 00449 job->setMetaData(metaData); 00450 job->addMetaData("MaxCacheSize", "0"); // Don't store in http cache. 00451 job->addMetaData("cache", "cache"); // Use entry from cache if available. 00452 job->ui()->setWindow(window); 00453 job->ui()->setAutoErrorHandlingEnabled( true ); 00454 new DownloadJobWatcher(job, metaData); 00455 } 00456 00457 void BrowserRun::slotStatResult( KJob *job ) 00458 { 00459 if ( job->error() ) { 00460 kDebug(1000) << job->errorString(); 00461 handleError( job ); 00462 } else 00463 KRun::slotStatResult( job ); 00464 } 00465 00466 void BrowserRun::handleError( KJob * job ) 00467 { 00468 if ( !job ) { // Shouldn't happen, see docu. 00469 kWarning(1000) << "handleError called with job=0! hideErrorDialog=" << d->m_bHideErrorDialog; 00470 return; 00471 } 00472 00473 KIO::TransferJob *tjob = qobject_cast<KIO::TransferJob *>(job); 00474 if (tjob && tjob->isErrorPage() && !job->error()) { 00475 // The default handling of error pages is to show them like normal pages 00476 // But this is done here in handleError so that KHTMLRun can reimplement it 00477 tjob->putOnHold(); 00478 setJob(0); 00479 if (!d->m_mimeType.isEmpty()) 00480 mimeTypeDetermined(d->m_mimeType); 00481 return; 00482 } 00483 00484 if (d->m_bHideErrorDialog && job->error() != KIO::ERR_NO_CONTENT) 00485 { 00486 redirectToError( job->error(), job->errorText() ); 00487 return; 00488 } 00489 00490 // Reuse code in KRun, to benefit from d->m_showingError etc. 00491 KRun::slotStatResult( job ); 00492 } 00493 00494 // static 00495 KUrl BrowserRun::makeErrorUrl(int error, const QString& errorText, const QString& initialUrl) 00496 { 00497 /* 00498 * The format of the error:/ URL is error:/?query#url, 00499 * where two variables are passed in the query: 00500 * error = int kio error code, errText = QString error text from kio 00501 * The sub-url is the URL that we were trying to open. 00502 */ 00503 KUrl newURL(QString("error:/?error=%1&errText=%2") 00504 .arg( error ) 00505 .arg( QString::fromUtf8( QUrl::toPercentEncoding( errorText ) ) ) ); 00506 00507 QString cleanedOrigUrl = initialUrl; 00508 KUrl runURL = cleanedOrigUrl; 00509 if (runURL.isValid()) { 00510 runURL.setPass( QString() ); // don't put the password in the error URL 00511 cleanedOrigUrl = runURL.url(); 00512 } 00513 00514 newURL.setFragment(cleanedOrigUrl); 00515 return newURL; 00516 00517 // The kde3 approach broke with invalid urls, now that they become empty in qt4. 00518 //KUrl::List lst; 00519 //lst << newURL << runURL; 00520 //return KUrl::join(lst); 00521 } 00522 00523 void BrowserRun::redirectToError( int error, const QString& errorText ) 00524 { 00530 KRun::setUrl(makeErrorUrl(error, errorText, url().url())); 00531 setJob( 0 ); 00532 mimeTypeDetermined( "text/html" ); 00533 } 00534 00535 void BrowserRun::slotCopyToTempFileResult(KJob *job) 00536 { 00537 if ( job->error() ) { 00538 job->uiDelegate()->showErrorMessage(); 00539 } else { 00540 // Same as KRun::foundMimeType but with a different URL 00541 (void) (KRun::runUrl( static_cast<KIO::FileCopyJob *>(job)->destUrl(), d->m_mimeType, d->m_window )); 00542 } 00543 setError( true ); // see above 00544 setFinished( true ); 00545 } 00546 00547 bool BrowserRun::isTextExecutable( const QString &mimeType ) 00548 { 00549 return ( mimeType == "application/x-desktop" || 00550 mimeType == "application/x-shellscript" ); 00551 } 00552 00553 bool BrowserRun::hideErrorDialog() const 00554 { 00555 return d->m_bHideErrorDialog; 00556 } 00557 00558 QString BrowserRun::contentDisposition() const 00559 { 00560 return d->m_contentDisposition; 00561 } 00562 00563 bool BrowserRun::serverSuggestsSave() const 00564 { 00565 // RfC 2183, section 2.8: 00566 // Unrecognized disposition types should be treated as `attachment'. 00567 return !contentDisposition().isEmpty() && (contentDisposition() != "inline"); 00568 } 00569 00570 KParts::OpenUrlArguments& KParts::BrowserRun::arguments() 00571 { 00572 return d->m_args; 00573 } 00574 00575 KParts::BrowserArguments& KParts::BrowserRun::browserArguments() 00576 { 00577 return d->m_browserArgs; 00578 } 00579 00580 #include "browserrun.moc" 00581 #include "browserrun_p.moc"
KDE 4.6 API Reference