From 39413862a2d2355d3765d986073b29c1c2a1b7d7 Mon Sep 17 00:00:00 2001 From: Andrea Diamantini Date: Fri, 7 Jan 2011 18:53:20 +0100 Subject: Put ioslaves on hold. This patch couples those merged in KIO & KDEWebKit in SC 4.6. I have to say it is probably NOT perfect and ready to merge, but I'd like to because it is anyway a nice step over actual behavior. It basically holds and (re)publish slave for apps needing it, letting us fixing a long standing bug against the infamous TWO connections to do ONE thing (download, move, etc...) It also gave me the possibility to and/or improve these things: - better handling filenames, studying content disposition header for "attachment" and "inline" values - better history handling, when working with kparts (also if this part needs a proper fix, IMHO reimplementing QWebHistory class...) - a (proper?) fix for POST operations returning content. I admit I am dubious with this part and I need more testing. The nice thing is that this patch "circumscribes" this problem, letting it easy eventually fix it again I also copied from kdewebkit a check for exec text files mimetypes (converted to text/plain) and cleaned-up some kDebugs (and as usual, added a lot..) Last, sorry for the big commit message and the big/not so big patch here. It is a special case. commit 29f3021d94e26b39f3e8172a980a6cafcebe5095 Author: Andrea Diamantini Date: Wed Jan 5 12:17:45 2011 +0100 cleanup comments commit fc88a37322810fd25c5bfcd16880fc617e7de9f5 Author: Andrea Diamantini Date: Wed Jan 5 12:06:55 2011 +0100 content-disposition: inline commit 7c2789a1733e1089b9c66cd6c553185792ec3a72 Author: Andrea Diamantini Date: Tue Jan 4 12:04:03 2011 +0100 Fix history handling. For a proper fix here, I think we need to reimplement the tab history, otherwise we cannot manage tab history browsing on the so called "rekonq" pages... commit 892c4d8dc78fda43f67566a3e359bd9805ee3282 Author: Andrea Diamantini Date: Tue Jan 4 11:57:29 2011 +0100 Two cases here: 1) POST operations returning content 2) right file names commit 3b1d5873b7eb743406f3b8c5abb79ce6728d0103 Author: Andrea Diamantini Date: Tue Jan 4 03:24:21 2011 +0100 Stupid kDebugs... commit 73d038a757185816f40795d4dd4a68e11659c971 Author: Andrea Diamantini Date: Tue Jan 4 02:32:45 2011 +0100 It needs testing from people (just) having last KDE SC 4.6 code on... --- src/mainwindow.cpp | 30 +++---- src/webpage.cpp | 244 +++++++++++++++++++++++++++++++++-------------------- src/webpage.h | 9 +- 3 files changed, 175 insertions(+), 108 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 11c00c28..599295f7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -619,26 +619,28 @@ void MainWindow::openLocation() void MainWindow::fileSaveAs() { - KUrl srcUrl; WebTab *w = currentTab(); - if (w->page()->isOnRekonqPage()) - { - QWebElement el = w->page()->mainFrame()->documentElement(); - srcUrl = KUrl( el.findFirst("object").attribute("data") ); - } - else + KUrl srcUrl = w->url(); + + // First, try with suggested file name... + QString name = w->page()->suggestedFileName(); + + // Second, with KUrl fileName... + if (name.isEmpty()) { - srcUrl = w->url(); + name = srcUrl.fileName(); } - kDebug() << "URL to save: " << srcUrl; - - QString name = srcUrl.fileName(); - if (name.isNull()) + + // Last chance... + if(name.isEmpty()) { name = srcUrl.host() + QString(".html"); } + const QString destUrl = KFileDialog::getSaveFileName(name, QString(), this); - if (destUrl.isEmpty()) return; + if (destUrl.isEmpty()) + return; + KIO::Job *job = KIO::file_copy(srcUrl, KUrl(destUrl), -1, KIO::Overwrite); job->addMetaData("MaxCacheSize", "0"); // Don't store in http cache. job->addMetaData("cache", "cache"); // Use entry from cache if available. @@ -984,7 +986,6 @@ void MainWindow::openPrevious(Qt::MouseButtons mouseButtons, Qt::KeyboardModifie if (currentTab()->page()->isOnRekonqPage()) { item = new QWebHistoryItem(history->currentItem()); - currentTab()->view()->page()->setIsOnRekonqPage(false); } else { @@ -1018,7 +1019,6 @@ void MainWindow::openNext(Qt::MouseButtons mouseButtons, Qt::KeyboardModifiers k if (currentTab()->view()->page()->isOnRekonqPage()) { item = new QWebHistoryItem(history->currentItem()); - currentTab()->view()->page()->setIsOnRekonqPage(false); } else { diff --git a/src/webpage.cpp b/src/webpage.cpp index b04c5e89..c7ddfd5b 100644 --- a/src/webpage.cpp +++ b/src/webpage.cpp @@ -54,6 +54,9 @@ // KDE Includes #include #include +#include + +#include #include #include #include @@ -66,6 +69,8 @@ #include +#include + // Qt Includes #include @@ -99,10 +104,67 @@ static bool domainSchemeMatch(const QUrl& u1, const QUrl& u2) // NOTE -// This is heavily based on the one from KdeWebKit and -// extended to provide the extra functionality we need: +// These 2 functions have been copied from the KWebPage class to implement a local version of the downloadResponse method. +// In this way, we can easily provide the extra functionality we need: // 1. KGet Integration // 2. Save downloads history +static void extractSuggestedFileName(const QNetworkReply* reply, QString& fileName) +{ + fileName.clear(); + const KIO::MetaData& metaData = reply->attribute(static_cast(KIO::AccessManager::MetaData)).toMap(); + if (metaData.value(QL1S("content-disposition-type")).compare(QL1S("attachment"), Qt::CaseInsensitive) == 0) + fileName = metaData.value(QL1S("content-disposition-filename")); + + if (!fileName.isEmpty()) + return; + + if (!reply->hasRawHeader("Content-Disposition")) + return; + + const QString value (QL1S(reply->rawHeader("Content-Disposition").simplified().constData())); + if (value.startsWith(QL1S("attachment"), Qt::CaseInsensitive) || value.startsWith(QL1S("inline"), Qt::CaseInsensitive)) + { + const int length = value.size(); + int pos = value.indexOf(QL1S("filename"), 0, Qt::CaseInsensitive); + if (pos > -1) + { + pos += 9; + while (pos < length && (value.at(pos) == QL1C(' ') || value.at(pos) == QL1C('=') || value.at(pos) == QL1C('"'))) + pos++; + + int endPos = pos; + while (endPos < length && value.at(endPos) != QL1C('"') && value.at(endPos) != QL1C(';')) + endPos++; + + if (endPos > pos) + fileName = value.mid(pos, (endPos-pos)).trimmed(); + } + } +} + + +static void extractMimeType(const QNetworkReply* reply, QString& mimeType) +{ + mimeType.clear(); + const KIO::MetaData& metaData = reply->attribute(static_cast(KIO::AccessManager::MetaData)).toMap(); + if (metaData.contains(QL1S("content-type"))) + mimeType = metaData.value(QL1S("content-type")); + + if (!mimeType.isEmpty()) + return; + + if (!reply->hasRawHeader("Content-Type")) + return; + + const QString value (QL1S(reply->rawHeader("Content-Type").simplified().constData())); + const int index = value.indexOf(QL1C(';')); + if (index == -1) + mimeType = value; + else + mimeType = value.left(index); +} + + static bool downloadResource (const KUrl& srcUrl, const KIO::MetaData& metaData = KIO::MetaData(), QWidget* parent = 0, const QString& suggestedName = QString()) { @@ -237,6 +299,9 @@ bool WebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &r _isOnRekonqPage = false; tab->setPart(0, KUrl()); // re-enable the view page } + + // reset webpage values + _suggestedFileName.clear(); _loadingUrl = request.url(); KIO::AccessManager *manager = qobject_cast(networkAccessManager()); @@ -289,15 +354,6 @@ bool WebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &r default: break; } - - if (frame == mainFrame()) - { - setRequestMetaData("main_frame_request", "TRUE"); - } - else - { - setRequestMetaData("main_frame_request", "FALSE"); - } } return KWebPage::acceptNavigationRequest(frame, request, type); } @@ -325,11 +381,10 @@ WebPage *WebPage::createWindow(QWebPage::WebWindowType type) void WebPage::handleUnsupportedContent(QNetworkReply *reply) { Q_ASSERT (reply); - // NOTE: - // Until kio implements a way to resume/continue a network - // request. We must abort the reply to prevent a zombie process - // from continuing to download the unsupported content! - reply->abort(); + + // Put the job on hold... + kDebug() << "PUT REPLY ON HOLD..."; + KIO::Integration::AccessManager::putReplyOnHold(reply); // This is probably needed just in ONE stupid case.. if (_protHandler.postHandling(reply->request(), mainFrame())) @@ -338,78 +393,24 @@ void WebPage::handleUnsupportedContent(QNetworkReply *reply) if (reply->error() != QNetworkReply::NoError) return; + // get reply url... KUrl replyUrl = reply->url(); - // HACK ------------------------------------------- - // This is done to fix #231204 && #212808 - - QString mimeType; - QString suggestedFileName; + // Get suggested file name... + extractSuggestedFileName(reply, _suggestedFileName); - QString app = reply->header(QNetworkRequest::ContentTypeHeader).toString(); - QStringList headerList = app.split( ';' ); - - if(headerList.count() > 0) - { - mimeType = headerList.takeFirst().trimmed(); - Q_FOREACH(const QString &head, headerList) - { - if( head.contains( QL1S("name") ) ) - { - // this is not so sure.. :) - suggestedFileName = head; - suggestedFileName = suggestedFileName.remove( QL1S("name=") ); - suggestedFileName = suggestedFileName.remove( '"' ); - suggestedFileName = suggestedFileName.trimmed(); - break; - } - } - } - else - { - mimeType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); - } - - // NOTE - // This part has been copied from KWebPage::downloadResponse code - if (reply->hasRawHeader("Content-Disposition")) - { - KIO::MetaData metaData = reply->attribute(static_cast(KIO::AccessManager::MetaData)).toMap(); - if (metaData.value(QL1S("content-disposition-type")).compare(QL1S("attachment"), Qt::CaseInsensitive) == 0) - { - suggestedFileName = metaData.value(QL1S("content-disposition-filename")); - } - else - { - const QString value = QL1S(reply->rawHeader("Content-Disposition").simplified()); - if (value.startsWith(QL1S("attachment"), Qt::CaseInsensitive)) - { - const int length = value.size(); - int pos = value.indexOf(QL1S("filename"), 0, Qt::CaseInsensitive); - if (pos > -1) - { - pos += 9; - while (pos < length && (value.at(pos) == QL1C(' ') || value.at(pos) == QL1C('=') || value.at(pos) == QL1C('"'))) - pos++; - - int endPos = pos; - while (endPos < length && value.at(endPos) != QL1C('"') && value.at(endPos) != QL1C(';')) - endPos++; - - if (endPos > pos) - { - suggestedFileName = value.mid(pos, (endPos-pos)).trimmed(); - } - } - } - } - } - - kDebug() << "Detected MimeType = " << mimeType; - kDebug() << "Suggested File Name = " << suggestedFileName; + // Get mimeType... + extractMimeType(reply, _mimeType); + + // Convert executable text files to plain text... + if (KParts::BrowserRun::isTextExecutable(_mimeType)) + _mimeType = QL1S("text/plain"); + + kDebug() << "Detected MimeType = " << _mimeType; + kDebug() << "Suggested File Name = " << _suggestedFileName; // ------------------------------------------------ - KService::Ptr appService = KMimeTypeTrader::self()->preferredService(mimeType); + KService::Ptr appService = KMimeTypeTrader::self()->preferredService(_mimeType); bool isLocal = replyUrl.isLocalFile(); @@ -419,22 +420,22 @@ void WebPage::handleUnsupportedContent(QNetworkReply *reply) isLocal ? KMessageBox::sorry(view(), i18n("No service can handle this file.")) - : downloadReply(reply, suggestedFileName); + : downloadReply(reply, _suggestedFileName); return; } if (!isLocal) { - KParts::BrowserOpenOrSaveQuestion dlg(Application::instance()->mainWindow(), replyUrl, mimeType); - if(!suggestedFileName.isEmpty()) - dlg.setSuggestedFileName(suggestedFileName); + KParts::BrowserOpenOrSaveQuestion dlg(Application::instance()->mainWindow(), replyUrl, _mimeType); + if(!_suggestedFileName.isEmpty()) + dlg.setSuggestedFileName(_suggestedFileName); switch (dlg.askEmbedOrSave()) { case KParts::BrowserOpenOrSaveQuestion::Save: kDebug() << "user choice: no services, just download!"; - downloadReply(reply, suggestedFileName); + downloadReply(reply, _suggestedFileName); return; case KParts::BrowserOpenOrSaveQuestion::Cancel: @@ -445,10 +446,46 @@ void WebPage::handleUnsupportedContent(QNetworkReply *reply) } } + // Handle Post operations that return content... + if (reply->operation() == QNetworkAccessManager::PostOperation) + { + kDebug() << "POST OPERATION: downloading file..."; + QFileInfo finfo (_suggestedFileName.isEmpty() ? _loadingUrl.fileName() : _suggestedFileName); + KTemporaryFile tempFile; + tempFile.setSuffix(QL1C('.') + finfo.suffix()); + tempFile.setAutoRemove(false); + tempFile.open(); + KUrl destUrl; + destUrl.setPath(tempFile.fileName()); + kDebug() << "First save content to" << destUrl; + KIO::Job *job = KIO::file_copy(_loadingUrl, destUrl, 0600, KIO::Overwrite); + job->ui()->setWindow(Application::instance()->mainWindow()); + connect(job, SIGNAL(result(KJob *)), this, SLOT(copyToTempFileResult(KJob*))); + return; + } + + // HACK: The check below is necessary to break an infinite + // recursion that occurs whenever this function is called as a result + // of receiving content that can be rendered by the app using this engine. + // For example a text/html header that containing a content-disposition + // header is received by the app using this class. + const QString& appName = QCoreApplication::applicationName(); + + if (appName == appService->desktopEntryName() || appService->exec().trimmed().startsWith(appName)) + { + kDebug() << "SELF..."; + QNetworkRequest req (reply->request()); + req.setRawHeader("x-kdewebkit-ignore-disposition", "true"); + currentFrame()->load(req); + return; + } + // case KParts::BrowserRun::Embed - KParts::ReadOnlyPart *pa = KMimeTypeTrader::createPartInstanceFromQuery(mimeType, view(), this, QString()); + KParts::ReadOnlyPart *pa = KMimeTypeTrader::createPartInstanceFromQuery(_mimeType, view(), this, QString()); if (pa) { + kDebug() << "EMBEDDING CONTENT..."; + _isOnRekonqPage = true; WebView *view = qobject_cast(parent()); @@ -463,9 +500,21 @@ void WebPage::handleUnsupportedContent(QNetworkReply *reply) else { // No parts, just app services. Load it! - KRun::run(*appService, replyUrl, 0); + // If the app is a KDE one, publish the slave on hold to let it use it. + // Otherwise, run the app and remove it (the io slave...) + if (appService->categories().contains(QL1S("KDE"), Qt::CaseInsensitive)) + { + KIO::Scheduler::publishSlaveOnHold(); + KRun::run(*appService, replyUrl, 0, false, _suggestedFileName); + return; + } + KRun::run(*appService, replyUrl, 0, false, _suggestedFileName); } + // Remove any ioslave that was put on hold... + kDebug() << "REMOVE SLAVES ON HOLD..."; + KIO::Scheduler::removeSlaveOnHold(); + return; } @@ -499,8 +548,6 @@ void WebPage::loadFinished(bool ok) { wallet()->fillFormData(mainFrame()); } - kDebug() << "rekonq page: " << _isOnRekonqPage; - kDebug() << "loading url: " << _loadingUrl; } @@ -508,6 +555,10 @@ void WebPage::manageNetworkErrors(QNetworkReply *reply) { Q_ASSERT(reply); + // check suggested file name + if(_suggestedFileName.isEmpty()) + extractSuggestedFileName(reply, _suggestedFileName); + QWebFrame* frame = qobject_cast(reply->request().originatingObject()); const bool isMainFrameRequest = (frame == mainFrame()); @@ -725,3 +776,12 @@ void WebPage::updateImage(bool ok) p.snapFinished(); } } + + +void WebPage::copyToTempFileResult(KJob* job) +{ + if ( job->error() ) + job->uiDelegate()->showErrorMessage(); + else + (void)KRun::runUrl(static_cast(job)->destUrl(), _mimeType, Application::instance()->mainWindow()); +} diff --git a/src/webpage.h b/src/webpage.h index 41ddd877..7633b26d 100644 --- a/src/webpage.h +++ b/src/webpage.h @@ -59,6 +59,7 @@ public: inline void setIsOnRekonqPage(bool b) { _isOnRekonqPage = b; }; inline KUrl loadingUrl() { return _loadingUrl; }; + inline QString suggestedFileName() { return _suggestedFileName; }; public Q_SLOTS: void downloadAllContentsWithKGet(QPoint); @@ -81,15 +82,21 @@ private Q_SLOTS: void showSSLInfo(QPoint); void updateImage(bool ok); + void copyToTempFileResult(KJob*); + +private: void downloadReply(const QNetworkReply *reply, const QString &suggestedFileName = QString()); private: QString errorPage(QNetworkReply *reply); - QUrl _loadingUrl; + KUrl _loadingUrl; ProtocolHandler _protHandler; WebSslInfo _sslInfo; + QString _mimeType; + QString _suggestedFileName; + bool _networkAnalyzer; bool _isOnRekonqPage; }; -- cgit v1.2.1