browserrun.cpp
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 <kmessagebox.h> 00021 #include <kfiledialog.h> 00022 #include <kio/job.h> 00023 #include <kio/scheduler.h> 00024 #include <klocale.h> 00025 #include <kprocess.h> 00026 #include <kstringhandler.h> 00027 #include <kuserprofile.h> 00028 #include <ktempfile.h> 00029 #include <kdebug.h> 00030 #include <kstandarddirs.h> 00031 #include <assert.h> 00032 00033 using namespace KParts; 00034 00035 class BrowserRun::BrowserRunPrivate 00036 { 00037 public: 00038 bool m_bHideErrorDialog; 00039 QString contentDisposition; 00040 }; 00041 00042 BrowserRun::BrowserRun( const KURL& url, const KParts::URLArgs& args, 00043 KParts::ReadOnlyPart *part, QWidget* window, 00044 bool removeReferrer, bool trustedSource ) 00045 : KRun( url, window, 0 /*mode*/, false /*is_local_file known*/, false /* no GUI */ ), 00046 m_args( args ), m_part( part ), m_window( window ), 00047 m_bRemoveReferrer( removeReferrer ), m_bTrustedSource( trustedSource ) 00048 { 00049 d = new BrowserRunPrivate; 00050 d->m_bHideErrorDialog = false; 00051 } 00052 00053 // BIC: merge with above ctor 00054 BrowserRun::BrowserRun( const KURL& url, const KParts::URLArgs& args, 00055 KParts::ReadOnlyPart *part, QWidget* window, 00056 bool removeReferrer, bool trustedSource, bool hideErrorDialog ) 00057 : KRun( url, window, 0 /*mode*/, false /*is_local_file known*/, false /* no GUI */ ), 00058 m_args( args ), m_part( part ), m_window( window ), 00059 m_bRemoveReferrer( removeReferrer ), m_bTrustedSource( trustedSource ) 00060 { 00061 d = new BrowserRunPrivate; 00062 d->m_bHideErrorDialog = hideErrorDialog; 00063 } 00064 00065 BrowserRun::~BrowserRun() 00066 { 00067 delete d; 00068 } 00069 00070 void BrowserRun::init() 00071 { 00072 if ( d->m_bHideErrorDialog ) 00073 { 00074 // ### KRun doesn't call a virtual method when it finds out that the URL 00075 // is either malformed, or points to a non-existing local file... 00076 // So we need to reimplement some of the checks, to handle m_bHideErrorDialog 00077 if ( !m_strURL.isValid() ) { 00078 redirectToError( KIO::ERR_MALFORMED_URL, m_strURL.url() ); 00079 return; 00080 } 00081 if ( !m_bIsLocalFile && !m_bFault && m_strURL.isLocalFile() ) 00082 m_bIsLocalFile = true; 00083 00084 if ( m_bIsLocalFile ) { 00085 struct stat buff; 00086 if ( stat( QFile::encodeName(m_strURL.path()), &buff ) == -1 ) 00087 { 00088 kdDebug(1000) << "BrowserRun::init : " << m_strURL.prettyURL() << " doesn't exist." << endl; 00089 redirectToError( KIO::ERR_DOES_NOT_EXIST, m_strURL.path() ); 00090 return; 00091 } 00092 m_mode = buff.st_mode; // while we're at it, save it for KRun::init() to use it 00093 } 00094 } 00095 KRun::init(); 00096 } 00097 00098 void BrowserRun::scanFile() 00099 { 00100 kdDebug(1000) << "BrowserRun::scanfile " << m_strURL.prettyURL() << endl; 00101 00102 // Let's check for well-known extensions 00103 // Not when there is a query in the URL, in any case. 00104 // Optimization for http/https, findByURL doesn't trust extensions over http. 00105 if ( m_strURL.query().isEmpty() && !m_strURL.protocol().startsWith("http") ) 00106 { 00107 KMimeType::Ptr mime = KMimeType::findByURL( m_strURL ); 00108 assert( mime != 0L ); 00109 if ( mime->name() != "application/octet-stream" || m_bIsLocalFile ) 00110 { 00111 kdDebug(1000) << "Scanfile: MIME TYPE is " << mime->name() << endl; 00112 foundMimeType( mime->name() ); 00113 return; 00114 } 00115 } 00116 00117 if ( m_part ) 00118 { 00119 QString proto = m_part->url().protocol().lower(); 00120 00121 if (proto == "https" || proto == "webdavs") { 00122 m_args.metaData().insert("main_frame_request", "TRUE" ); 00123 m_args.metaData().insert("ssl_was_in_use", "TRUE" ); 00124 m_args.metaData().insert("ssl_activate_warnings", "TRUE" ); 00125 } else if (proto == "http" || proto == "webdav") { 00126 m_args.metaData().insert("ssl_activate_warnings", "TRUE" ); 00127 m_args.metaData().insert("ssl_was_in_use", "FALSE" ); 00128 } 00129 00130 // Set the PropagateHttpHeader meta-data if it has not already been set... 00131 if (!m_args.metaData().contains("PropagateHttpHeader")) 00132 m_args.metaData().insert("PropagateHttpHeader", "TRUE"); 00133 } 00134 00135 KIO::TransferJob *job; 00136 if ( m_args.doPost() && m_strURL.protocol().startsWith("http")) 00137 { 00138 job = KIO::http_post( m_strURL, m_args.postData, false ); 00139 job->addMetaData( "content-type", m_args.contentType() ); 00140 } 00141 else 00142 job = KIO::get(m_strURL, m_args.reload, false); 00143 00144 if ( m_bRemoveReferrer ) 00145 m_args.metaData().remove("referrer"); 00146 00147 job->addMetaData( m_args.metaData() ); 00148 job->setWindow( m_window ); 00149 connect( job, SIGNAL( result( KIO::Job *)), 00150 this, SLOT( slotBrowserScanFinished(KIO::Job *))); 00151 connect( job, SIGNAL( mimetype( KIO::Job *, const QString &)), 00152 this, SLOT( slotBrowserMimetype(KIO::Job *, const QString &))); 00153 m_job = job; 00154 } 00155 00156 void BrowserRun::slotBrowserScanFinished(KIO::Job *job) 00157 { 00158 kdDebug(1000) << "BrowserRun::slotBrowserScanFinished" << endl; 00159 if ( job->error() == KIO::ERR_IS_DIRECTORY ) 00160 { 00161 // It is in fact a directory. This happens when HTTP redirects to FTP. 00162 // Due to the "protocol doesn't support listing" code in BrowserRun, we 00163 // assumed it was a file. 00164 kdDebug(1000) << "It is in fact a directory!" << endl; 00165 // Update our URL in case of a redirection 00166 m_strURL = static_cast<KIO::TransferJob *>(job)->url(); 00167 m_job = 0; 00168 foundMimeType( "inode/directory" ); 00169 } 00170 else 00171 { 00172 if ( job->error() ) 00173 handleError( job ); 00174 else 00175 KRun::slotScanFinished(job); 00176 } 00177 } 00178 00179 void BrowserRun::slotBrowserMimetype( KIO::Job *_job, const QString &type ) 00180 { 00181 Q_ASSERT( _job == m_job ); 00182 KIO::TransferJob *job = static_cast<KIO::TransferJob *>(m_job); 00183 // Update our URL in case of a redirection 00184 //kdDebug(1000) << "old URL=" << m_strURL.url() << endl; 00185 //kdDebug(1000) << "new URL=" << job->url().url() << endl; 00186 m_strURL = job->url(); 00187 kdDebug(1000) << "slotBrowserMimetype: found " << type << " for " << m_strURL.prettyURL() << endl; 00188 00189 m_suggestedFilename = job->queryMetaData("content-disposition-filename"); 00190 d->contentDisposition = job->queryMetaData("content-disposition-type"); 00191 //kdDebug(1000) << "m_suggestedFilename=" << m_suggestedFilename << endl; 00192 00193 // Make a copy to avoid a dead reference 00194 QString _type = type; 00195 job->putOnHold(); 00196 m_job = 0; 00197 00198 KRun::setSuggestedFileName(m_suggestedFilename); 00199 00200 foundMimeType( _type ); 00201 } 00202 00203 BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable( const QString& _mimeType ) 00204 { 00205 QString mimeType( _mimeType ); 00206 Q_ASSERT( !m_bFinished ); // only come here if the mimetype couldn't be embedded 00207 // Support for saving remote files. 00208 if ( mimeType != "inode/directory" && // dirs can't be saved 00209 !m_strURL.isLocalFile() ) 00210 { 00211 if ( isTextExecutable(mimeType) ) 00212 mimeType = QString::fromLatin1("text/plain"); // view, don't execute 00213 kdDebug(1000) << "BrowserRun: ask for saving" << endl; 00214 KService::Ptr offer = KServiceTypeProfile::preferredService(mimeType, "Application"); 00215 // ... -> ask whether to save 00216 KParts::BrowserRun::AskSaveResult res = askSave( m_strURL, offer, mimeType, m_suggestedFilename ); 00217 if ( res == KParts::BrowserRun::Save ) { 00218 save( m_strURL, m_suggestedFilename ); 00219 kdDebug(1000) << "BrowserRun::handleNonEmbeddable: Save: returning Handled" << endl; 00220 m_bFinished = true; 00221 return Handled; 00222 } 00223 else if ( res == KParts::BrowserRun::Cancel ) { 00224 // saving done or canceled 00225 kdDebug(1000) << "BrowserRun::handleNonEmbeddable: Cancel: returning Handled" << endl; 00226 m_bFinished = true; 00227 return Handled; 00228 } 00229 else // "Open" chosen (done by KRun::foundMimeType, called when returning NotHandled) 00230 { 00231 // If we were in a POST, we can't just pass a URL to an external application. 00232 // We must save the data to a tempfile first. 00233 if ( m_args.doPost() ) 00234 { 00235 kdDebug(1000) << "BrowserRun: request comes from a POST, can't pass a URL to another app, need to save" << endl; 00236 m_sMimeType = mimeType; 00237 QString extension; 00238 QString fileName = m_suggestedFilename.isEmpty() ? m_strURL.fileName() : m_suggestedFilename; 00239 int extensionPos = fileName.findRev( '.' ); 00240 if ( extensionPos != -1 ) 00241 extension = fileName.mid( extensionPos ); // keep the '.' 00242 KTempFile tempFile( QString::null, extension ); 00243 KURL destURL; 00244 destURL.setPath( tempFile.name() ); 00245 KIO::Job *job = KIO::file_copy( m_strURL, destURL, 0600, true /*overwrite*/, false /*no resume*/, true /*progress info*/ ); 00246 job->setWindow (m_window); 00247 connect( job, SIGNAL( result( KIO::Job *)), 00248 this, SLOT( slotCopyToTempFileResult(KIO::Job *)) ); 00249 return Delayed; // We'll continue after the job has finished 00250 } 00251 } 00252 } 00253 00254 // Check if running is allowed 00255 if ( !m_bTrustedSource && // ... and untrusted source... 00256 !allowExecution( mimeType, m_strURL ) ) // ...and the user said no (for executables etc.) 00257 { 00258 m_bFinished = true; 00259 return Handled; 00260 } 00261 00262 KIO::SimpleJob::removeOnHold(); // Kill any slave that was put on hold. 00263 return NotHandled; 00264 } 00265 00266 //static 00267 bool BrowserRun::allowExecution( const QString &serviceType, const KURL &url ) 00268 { 00269 if ( !isExecutable( serviceType ) ) 00270 return true; 00271 00272 if ( !url.isLocalFile() ) // Don't permit to execute remote files 00273 return false; 00274 00275 return ( KMessageBox::warningContinueCancel( 0, i18n( "Do you really want to execute '%1'? " ).arg( url.prettyURL() ), 00276 i18n("Execute File?"), i18n("Execute") ) == KMessageBox::Continue ); 00277 } 00278 00279 static QString makeQuestion( const KURL& url, const QString& mimeType, const QString& suggestedFilename ) 00280 { 00281 QString surl = KStringHandler::csqueeze( url.prettyURL() ); 00282 KMimeType::Ptr mime = KMimeType::mimeType( mimeType ); 00283 QString comment = mimeType; 00284 00285 // Test if the mimeType is not recognize as octet-stream. 00286 // If so then keep mime-type as comment 00287 if (mime->name() != KMimeType::defaultMimeType()) { 00288 // The mime-type is known so display the comment instead of mime-type 00289 comment = mime->comment(); 00290 } 00291 // The strange order in the i18n() calls below is due to the possibility 00292 // of surl containing a '%' 00293 if ( suggestedFilename.isEmpty() ) 00294 return i18n("Open '%2'?\nType: %1").arg(comment, surl); 00295 else 00296 return i18n("Open '%3'?\nName: %2\nType: %1").arg(comment, suggestedFilename, surl); 00297 } 00298 00299 //static 00300 BrowserRun::AskSaveResult BrowserRun::askSave( const KURL & url, KService::Ptr offer, const QString& mimeType, const QString & suggestedFilename ) 00301 { 00302 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC 00303 // NOTE: Keep this function in sync with kdebase/kcontrol/filetypes/filetypedetails.cpp 00304 // FileTypeDetails::updateAskSave() 00305 00306 QString question = makeQuestion( url, mimeType, suggestedFilename ); 00307 00308 // Text used for the open button 00309 QString openText = (offer && !offer->name().isEmpty()) 00310 ? i18n("&Open with '%1'").arg(offer->name()) 00311 : i18n("&Open With..."); 00312 00313 int choice = KMessageBox::questionYesNoCancel( 00314 0L, question, url.host(), 00315 KStdGuiItem::saveAs(), openText, 00316 QString::fromLatin1("askSave")+ mimeType ); // dontAskAgainName, KEEP IN SYNC!!! 00317 00318 return choice == KMessageBox::Yes ? Save : ( choice == KMessageBox::No ? Open : Cancel ); 00319 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC 00320 } 00321 00322 //static 00323 BrowserRun::AskSaveResult BrowserRun::askEmbedOrSave( const KURL & url, const QString& mimeType, const QString & suggestedFilename, int flags ) 00324 { 00325 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC 00326 // NOTE: Keep this funcion in sync with kdebase/kcontrol/filetypes/filetypedetails.cpp 00327 // FileTypeDetails::updateAskSave() 00328 00329 KMimeType::Ptr mime = KMimeType::mimeType( mimeType ); 00330 // Don't ask for: 00331 // - html (even new tabs would ask, due to about:blank!) 00332 // - dirs obviously (though not common over HTTP :), 00333 // - images (reasoning: no need to save, most of the time, because fast to see) 00334 // e.g. postscript is different, because takes longer to read, so 00335 // it's more likely that the user might want to save it. 00336 // - multipart/* ("server push", see kmultipart) 00337 // - other strange 'internal' mimetypes like print/manager... 00338 // KEEP IN SYNC!!! 00339 if (flags != (int)AttachmentDisposition && ( 00340 mime->is( "text/html" ) || 00341 mime->is( "text/xml" ) || 00342 mime->is( "inode/directory" ) || 00343 mimeType.startsWith( "image" ) || 00344 mime->is( "multipart/x-mixed-replace" ) || 00345 mime->is( "multipart/replace" ) || 00346 mimeType.startsWith( "print" ) ) ) 00347 return Open; 00348 00349 QString question = makeQuestion( url, mimeType, suggestedFilename ); 00350 00351 int choice = KMessageBox::questionYesNoCancel( 00352 0L, question, url.host(), 00353 KStdGuiItem::saveAs(), KGuiItem( i18n( "&Open" ), "fileopen"), 00354 QString::fromLatin1("askEmbedOrSave")+ mimeType ); // dontAskAgainName, KEEP IN SYNC!!! 00355 return choice == KMessageBox::Yes ? Save : ( choice == KMessageBox::No ? Open : Cancel ); 00356 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC 00357 } 00358 00359 // Default implementation, overridden in KHTMLRun 00360 void BrowserRun::save( const KURL & url, const QString & suggestedFilename ) 00361 { 00362 simpleSave( url, suggestedFilename, m_window ); 00363 } 00364 00365 // static 00366 void BrowserRun::simpleSave( const KURL & url, const QString & suggestedFilename ) 00367 { 00368 simpleSave (url, suggestedFilename, 0); 00369 } 00370 00371 void BrowserRun::simpleSave( const KURL & url, const QString & suggestedFilename, 00372 QWidget* window ) 00373 { 00374 // DownloadManager <-> konqueror integration 00375 // find if the integration is enabled 00376 // the empty key means no integration 00377 // only use the downloadmanager for non-local urls 00378 if ( !url.isLocalFile() ) 00379 { 00380 KConfig cfg("konquerorrc", false, false); 00381 cfg.setGroup("HTML Settings"); 00382 QString downloadManger = cfg.readPathEntry("DownloadManager"); 00383 if (!downloadManger.isEmpty()) 00384 { 00385 // then find the download manager location 00386 kdDebug(1000) << "Using: "<<downloadManger <<" as Download Manager" <<endl; 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 ").arg(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::null); 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 += " " + KProcess::quote(url.url()); 00403 if ( !suggestedFilename.isEmpty() ) 00404 cmd +=" " + KProcess::quote(suggestedFilename); 00405 00406 kdDebug(1000) << "Calling command " << cmd << endl; 00407 // slave is already on hold (slotBrowserMimetype()) 00408 KIO::Scheduler::publishSlaveOnHold(); 00409 KRun::runCommand(cmd); 00410 return; 00411 } 00412 } 00413 } 00414 00415 // no download manager available, let's do it ourself 00416 KFileDialog *dlg = new KFileDialog( QString::null, QString::null /*all files*/, 00417 window , "filedialog", true ); 00418 dlg->setOperationMode( KFileDialog::Saving ); 00419 dlg->setCaption(i18n("Save As")); 00420 00421 dlg->setSelection( suggestedFilename.isEmpty() ? url.fileName() : suggestedFilename ); 00422 if ( dlg->exec() ) 00423 { 00424 KURL destURL( dlg->selectedURL() ); 00425 if ( destURL.isValid() ) 00426 { 00427 KIO::Job *job = KIO::copy( url, destURL ); 00428 job->setWindow (window); 00429 job->setAutoErrorHandlingEnabled( true ); 00430 } 00431 } 00432 delete dlg; 00433 } 00434 00435 void BrowserRun::slotStatResult( KIO::Job *job ) 00436 { 00437 if ( job->error() ) { 00438 kdDebug(1000) << "BrowserRun::slotStatResult : " << job->errorString() << endl; 00439 handleError( job ); 00440 } else 00441 KRun::slotStatResult( job ); 00442 } 00443 00444 void BrowserRun::handleError( KIO::Job * job ) 00445 { 00446 if ( !job ) { // Shouldn't happen, see docu. 00447 kdWarning(1000) << "BrowserRun::handleError called with job=0! hideErrorDialog=" << d->m_bHideErrorDialog << endl; 00448 return; 00449 } 00450 00451 if (d->m_bHideErrorDialog && job->error() != KIO::ERR_NO_CONTENT) 00452 { 00453 redirectToError( job->error(), job->errorText() ); 00454 return; 00455 } 00456 00457 // Reuse code in KRun, to benefit from d->m_showingError etc. 00458 KRun::slotStatResult( job ); 00459 } 00460 00461 void BrowserRun::redirectToError( int error, const QString& errorText ) 00462 { 00473 KURL newURL(QString("error:/?error=%1&errText=%2") 00474 .arg( error ).arg( KURL::encode_string(errorText) ), 106 ); 00475 m_strURL.setPass( QString::null ); // don't put the password in the error URL 00476 00477 KURL::List lst; 00478 lst << newURL << m_strURL; 00479 m_strURL = KURL::join( lst ); 00480 //kdDebug(1202) << "BrowserRun::handleError m_strURL=" << m_strURL.prettyURL() << endl; 00481 00482 m_job = 0; 00483 foundMimeType( "text/html" ); 00484 } 00485 00486 void BrowserRun::slotCopyToTempFileResult(KIO::Job *job) 00487 { 00488 if ( job->error() ) { 00489 job->showErrorDialog( m_window ); 00490 } else { 00491 // Same as KRun::foundMimeType but with a different URL 00492 (void) (KRun::runURL( static_cast<KIO::FileCopyJob *>(job)->destURL(), m_sMimeType )); 00493 } 00494 m_bFault = true; // see above 00495 m_bFinished = true; 00496 m_timer.start( 0, true ); 00497 } 00498 00499 bool BrowserRun::isTextExecutable( const QString &serviceType ) 00500 { 00501 return ( serviceType == "application/x-desktop" || 00502 serviceType == "application/x-shellscript" ); 00503 } 00504 00505 bool BrowserRun::isExecutable( const QString &serviceType ) 00506 { 00507 return KRun::isExecutable( serviceType ); 00508 } 00509 00510 bool BrowserRun::hideErrorDialog() const 00511 { 00512 return d->m_bHideErrorDialog; 00513 } 00514 00515 QString BrowserRun::contentDisposition() const { 00516 return d->contentDisposition; 00517 } 00518 00519 #include "browserrun.moc"