Driver#html returns raw data for non-HTML content

The behavior of QWebFrame::toPlainText is undefined if the content
contains non-Latin1 characters. In Qt 5, additional MIME types are
considered supported, so there's no way to get at the raw data when
unsupported content has been loaded. Instead, proxy network replies
through NetworkReplyProxy which provides repetitive reads.
This commit is contained in:
Matthew Horan 2012-11-18 02:48:12 -05:00
parent 366b6b9a5b
commit 410f4e78c6
10 changed files with 213 additions and 49 deletions

View File

@ -2232,6 +2232,7 @@ describe Capybara::Webkit::Driver do
xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.send('hello');
console.log(xhr.response);
return false;
}
</script>
</body>

View File

@ -6,11 +6,6 @@ Headers::Headers(WebPageManager *manager, QStringList &arguments, QObject *paren
}
void Headers::start() {
QStringList headers;
foreach(QNetworkReply::RawHeaderPair header, page()->pageHeaders())
headers << header.first+": "+header.second;
finish(true, headers.join("\n"));
finish(true, page()->pageHeaders().join("\n"));
}

View File

@ -3,6 +3,7 @@
#include <iostream>
#include <fstream>
#include "NoOpReply.h"
#include "NetworkReplyProxy.h"
NetworkAccessManager::NetworkAccessManager(QObject *parent):QNetworkAccessManager(parent) {
connect(this, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), SLOT(provideAuthentication(QNetworkReply*,QAuthenticator*)));
@ -13,7 +14,7 @@ QNetworkReply* NetworkAccessManager::createRequest(QNetworkAccessManager::Operat
QNetworkRequest new_request(request);
QByteArray url = new_request.url().toEncoded();
if (this->isBlacklisted(new_request.url())) {
return new NoOpReply(new_request, this);
return new NetworkReplyProxy(new NoOpReply(new_request), this);
} else {
if (operation != QNetworkAccessManager::PostOperation && operation != QNetworkAccessManager::PutOperation) {
new_request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant());
@ -23,7 +24,7 @@ QNetworkReply* NetworkAccessManager::createRequest(QNetworkAccessManager::Operat
item.next();
new_request.setRawHeader(item.key().toLatin1(), item.value().toLatin1());
}
QNetworkReply *reply = QNetworkAccessManager::createRequest(operation, new_request, outgoingData);
QNetworkReply *reply = new NetworkReplyProxy(QNetworkAccessManager::createRequest(operation, new_request, outgoingData), this);
emit requestCreated(url, reply);
return reply;
}
@ -37,10 +38,7 @@ void NetworkAccessManager::finished(QNetworkReply *reply) {
QUrl requestedUrl = reply->url();
while (m_redirectMappings.contains(requestedUrl))
requestedUrl = m_redirectMappings.take(requestedUrl);
NetworkResponse response;
response.statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
response.headers = reply->rawHeaderPairs();
m_responses[requestedUrl] = response;
emit finished(requestedUrl, reply);
}
}
@ -68,14 +66,6 @@ void NetworkAccessManager::provideAuthentication(QNetworkReply *reply, QAuthenti
authenticator->setPassword(m_password);
}
int NetworkAccessManager::statusFor(QUrl url) {
return m_responses[url].statusCode;
}
const QList<QNetworkReply::RawHeaderPair> &NetworkAccessManager::headersFor(QUrl url) {
return m_responses[url].headers;
}
void NetworkAccessManager::setUrlBlacklist(QStringList urlBlacklist) {
m_urlBlacklist.clear();

View File

@ -7,20 +7,12 @@ class NetworkAccessManager : public QNetworkAccessManager {
Q_OBJECT
struct NetworkResponse {
int statusCode;
QList<QNetworkReply::RawHeaderPair> headers;
NetworkResponse() : statusCode(0) { }
};
public:
NetworkAccessManager(QObject *parent = 0);
void addHeader(QString key, QString value);
void resetHeaders();
void setUserName(const QString &userName);
void setPassword(const QString &password);
int statusFor(QUrl url);
const QList<QNetworkReply::RawHeaderPair> &headersFor(QUrl url);
void setUrlBlacklist(QStringList urlBlacklist);
protected:
@ -29,10 +21,8 @@ class NetworkAccessManager : public QNetworkAccessManager {
QString m_password;
QList<QUrl> m_urlBlacklist;
private:
QHash<QString, QString> m_headers;
QHash<QUrl, NetworkResponse> m_responses;
bool isBlacklisted(QUrl url);
QHash<QUrl, QUrl> m_redirectMappings;
@ -42,4 +32,5 @@ class NetworkAccessManager : public QNetworkAccessManager {
signals:
void requestCreated(QByteArray &url, QNetworkReply *reply);
void finished(QUrl &, QNetworkReply *);
};

91
src/NetworkReplyProxy.cpp Normal file
View File

@ -0,0 +1,91 @@
#include "NetworkReplyProxy.h"
NetworkReplyProxy::NetworkReplyProxy(QNetworkReply* reply, QObject* parent)
: QNetworkReply(parent)
, m_reply(reply)
{
m_reply->setParent(this);
setOperation(m_reply->operation());
setRequest(m_reply->request());
setUrl(m_reply->url());
if (m_reply->isFinished()) {
readInternal();
setFinished(true);
}
applyMetaData();
connect(m_reply, SIGNAL(metaDataChanged()), SLOT(applyMetaData()));
connect(m_reply, SIGNAL(readyRead()), SLOT(handleReadyRead()));
connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(errorInternal(QNetworkReply::NetworkError)));
connect(m_reply, SIGNAL(finished()), SLOT(handleFinished()));
connect(m_reply, SIGNAL(uploadProgress(qint64,qint64)), SIGNAL(uploadProgress(qint64,qint64)));
connect(m_reply, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(downloadProgress(qint64,qint64)));
connect(m_reply, SIGNAL(sslErrors(const QList<QSslError> &)), SIGNAL(sslErrors(const QList<QSslError> &)));
setOpenMode(ReadOnly);
}
void NetworkReplyProxy::abort() { m_reply->abort(); }
void NetworkReplyProxy::close() { m_reply->close(); }
bool NetworkReplyProxy::isSequential() const { return m_reply->isSequential(); }
void NetworkReplyProxy::handleFinished() {
setFinished(true);
emit finished();
}
qint64 NetworkReplyProxy::bytesAvailable() const
{
return m_buffer.size() + QIODevice::bytesAvailable();
}
qint64 NetworkReplyProxy::readData(char* data, qint64 maxlen)
{
qint64 size = qMin(maxlen, qint64(m_buffer.size()));
memcpy(data, m_buffer.constData(), size);
m_buffer.remove(0, size);
return size;
}
void NetworkReplyProxy::ignoreSslErrors() { m_reply->ignoreSslErrors(); }
void NetworkReplyProxy::applyMetaData() {
foreach(QNetworkReply::RawHeaderPair header, m_reply->rawHeaderPairs())
setRawHeader(header.first, header.second);
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute));
setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute));
setAttribute(QNetworkRequest::RedirectionTargetAttribute, m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute));
setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, m_reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute));
setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_reply->attribute(QNetworkRequest::CacheLoadControlAttribute));
setAttribute(QNetworkRequest::CacheSaveControlAttribute, m_reply->attribute(QNetworkRequest::CacheSaveControlAttribute));
setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, m_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute));
setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, m_reply->attribute(QNetworkRequest::DoNotBufferUploadDataAttribute));
emit metaDataChanged();
}
void NetworkReplyProxy::errorInternal(QNetworkReply::NetworkError _error)
{
setError(_error, errorString());
emit error(_error);
}
void NetworkReplyProxy::readInternal() {
QByteArray data = m_reply->readAll();
m_data += data;
m_buffer += data;
}
void NetworkReplyProxy::handleReadyRead()
{
readInternal();
emit readyRead();
}
QByteArray NetworkReplyProxy::data() {
return m_data;
}

65
src/NetworkReplyProxy.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef _NETWORKREPLYPROXY_H
#define _NETWORKREPLYPROXY_H
/*
* Copyright (C) 2009, 2010 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2009, 2010 Holger Hans Peter Freyther
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <QObject>
#include <QtNetwork/QNetworkReply>
class NetworkReplyProxy : public QNetworkReply {
Q_OBJECT
public:
NetworkReplyProxy(QNetworkReply* reply, QObject* parent);
virtual void abort();
virtual void close();
virtual bool isSequential() const;
virtual qint64 bytesAvailable() const;
virtual qint64 readData(char* data, qint64 maxlen);
QByteArray data();
public slots:
void ignoreSslErrors();
void applyMetaData();
void errorInternal(QNetworkReply::NetworkError _error);
void handleReadyRead();
void handleFinished();
private:
void readInternal();
QNetworkReply* m_reply;
QByteArray m_data;
QByteArray m_buffer;
};
#endif

View File

@ -5,6 +5,7 @@
#include "NetworkCookieJar.h"
#include "UnsupportedContentHandler.h"
#include "InvocationResult.h"
#include "NetworkReplyProxy.h"
#include <QResource>
#include <iostream>
#include <QWebSettings>
@ -15,7 +16,6 @@ WebPage::WebPage(WebPageManager *manager, QObject *parent) : QWebPage(parent) {
m_failed = false;
m_manager = manager;
m_uuid = QUuid::createUuid().toString();
m_unsupportedContentLoaded = false;
setForwardUnsupportedContent(true);
loadJavascript();
@ -50,10 +50,32 @@ void WebPage::setCustomNetworkAccessManager() {
this, SLOT(handleSslErrorsForReply(QNetworkReply *, QList<QSslError>)));
connect(manager, SIGNAL(requestCreated(QByteArray &, QNetworkReply *)),
SIGNAL(requestCreated(QByteArray &, QNetworkReply *)));
connect(manager, SIGNAL(finished(QUrl &, QNetworkReply *)),
SLOT(replyFinished(QUrl &, QNetworkReply *)));
}
void WebPage::replyFinished(QUrl &requestedUrl, QNetworkReply *reply) {
NetworkReplyProxy *proxy = qobject_cast<NetworkReplyProxy *>(reply);
setFrameProperties(mainFrame(), requestedUrl, proxy);
foreach(QWebFrame *frame, mainFrame()->childFrames())
setFrameProperties(frame, requestedUrl, proxy);
}
void WebPage::setFrameProperties(QWebFrame *frame, QUrl &requestedUrl, NetworkReplyProxy *reply) {
if (frame->requestedUrl() == requestedUrl) {
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
frame->setProperty("statusCode", statusCode);
QStringList headers;
foreach(QNetworkReply::RawHeaderPair header, reply->rawHeaderPairs())
headers << header.first+": "+header.second;
frame->setProperty("headers", headers);
frame->setProperty("body", reply->data());
QVariant contentMimeType = reply->header(QNetworkRequest::ContentTypeHeader);
frame->setProperty("contentType", contentMimeType);
}
}
void WebPage::unsupportedContentFinishedReply(QNetworkReply *reply) {
m_unsupportedContentLoaded = true;
m_manager->replyFinished(reply);
}
@ -169,7 +191,6 @@ bool WebPage::javaScriptPrompt(QWebFrame *frame, const QString &message, const Q
void WebPage::loadStarted() {
m_loading = true;
m_errorPageMessage = QString();
m_unsupportedContentLoaded = false;
}
void WebPage::loadFinished(bool success) {
@ -245,8 +266,9 @@ QStringList WebPage::getAttachedFileNames() {
}
void WebPage::handleSslErrorsForReply(QNetworkReply *reply, const QList<QSslError> &errors) {
Q_UNUSED(errors);
if (m_manager->ignoreSslErrors())
reply->ignoreSslErrors(errors);
reply->ignoreSslErrors();
}
void WebPage::setSkipImageLoading(bool skip) {
@ -254,11 +276,19 @@ void WebPage::setSkipImageLoading(bool skip) {
}
int WebPage::getLastStatus() {
return networkAccessManager()->statusFor(currentFrame()->requestedUrl());
return currentFrame()->property("statusCode").toInt();
}
const QList<QNetworkReply::RawHeaderPair> &WebPage::pageHeaders() {
return networkAccessManager()->headersFor(currentFrame()->requestedUrl());
QStringList WebPage::pageHeaders() {
return currentFrame()->property("headers").toStringList();
}
QByteArray WebPage::body() {
return currentFrame()->property("body").toByteArray();
}
QString WebPage::contentType() {
return currentFrame()->property("contentType").toString();
}
NetworkAccessManager *WebPage::networkAccessManager() {
@ -277,10 +307,6 @@ void WebPage::handleUnsupportedContent(QNetworkReply *reply) {
}
}
bool WebPage::unsupportedContentLoaded() {
return m_unsupportedContentLoaded;
}
bool WebPage::supportsExtension(Extension extension) const {
if (extension == ErrorPageExtension)
return true;

View File

@ -10,6 +10,7 @@
class WebPageManager;
class NetworkAccessManager;
class InvocationResult;
class NetworkReplyProxy;
class WebPage : public QWebPage {
Q_OBJECT
@ -40,8 +41,10 @@ class WebPage : public QWebPage {
bool matchesWindowSelector(QString);
void setFocus();
NetworkAccessManager *networkAccessManager();
bool unsupportedContentLoaded();
void unsupportedContentFinishedReply(QNetworkReply *reply);
QStringList pageHeaders();
QByteArray body();
QString contentType();
public slots:
bool shouldInterruptJavaScript();
@ -49,10 +52,10 @@ class WebPage : public QWebPage {
void loadStarted();
void loadFinished(bool);
bool isLoading() const;
const QList<QNetworkReply::RawHeaderPair> &pageHeaders();
void frameCreated(QWebFrame *);
void handleSslErrorsForReply(QNetworkReply *reply, const QList<QSslError> &);
void handleUnsupportedContent(QNetworkReply *reply);
void replyFinished(QUrl &, QNetworkReply *);
signals:
void pageFinished(bool);
@ -85,7 +88,7 @@ class WebPage : public QWebPage {
QString m_uuid;
WebPageManager *m_manager;
QString m_errorPageMessage;
bool m_unsupportedContentLoaded;
void setFrameProperties(QWebFrame *, QUrl &, NetworkReplyProxy *);
};
#endif //_WEBPAGE_H

View File

@ -7,10 +7,10 @@ Body::Body(WebPageManager *manager, QStringList &arguments, QObject *parent) : S
void Body::start() {
QString result;
if (page()->unsupportedContentLoaded())
result = page()->currentFrame()->toPlainText();
else
if (page()->contentType().contains("html"))
result = page()->currentFrame()->toHtml();
else
result = page()->body();
finish(true, result);
}

View File

@ -61,7 +61,8 @@ HEADERS = \
Title.h \
FindCss.h \
JavascriptCommand.h \
FindXpath.h
FindXpath.h \
NetworkReplyProxy.h
SOURCES = \
Version.cpp \
@ -124,7 +125,8 @@ SOURCES = \
Title.cpp \
FindCss.cpp \
JavascriptCommand.cpp \
FindXpath.cpp
FindXpath.cpp \
NetworkReplyProxy.cpp
RESOURCES = webkit_server.qrc
QT += network