QNetworkReplyHandler.cpp [plain text]
#include "config.h"
#include "QNetworkReplyHandler.h"
#include "HTTPParsers.h"
#include "MIMETypeRegistry.h"
#include "ResourceHandle.h"
#include "ResourceHandleClient.h"
#include "ResourceHandleInternal.h"
#include "ResourceResponse.h"
#include "ResourceRequest.h"
#include <QDateTime>
#include <QFile>
#include <QNetworkReply>
#include <QNetworkCookie>
#include <qwebframe.h>
#include <qwebpage.h>
#include <wtf/text/CString.h>
#include <QDebug>
#include <QCoreApplication>
#if QT_VERSION > QT_VERSION_CHECK(4, 6, 2)
#define SIGNAL_CONN Qt::DirectConnection
#else
#define SIGNAL_CONN Qt::QueuedConnection
#endif
static const int gMaxRecursionLimit = 10;
namespace WebCore {
FormDataIODevice::FormDataIODevice(FormData* data)
: m_formElements(data ? data->elements() : Vector<FormDataElement>())
, m_currentFile(0)
, m_currentDelta(0)
{
setOpenMode(FormDataIODevice::ReadOnly);
}
FormDataIODevice::~FormDataIODevice()
{
delete m_currentFile;
}
void FormDataIODevice::moveToNextElement()
{
if (m_currentFile)
m_currentFile->close();
m_currentDelta = 0;
m_formElements.remove(0);
if (m_formElements.isEmpty() || m_formElements[0].m_type == FormDataElement::data)
return;
if (!m_currentFile)
m_currentFile = new QFile;
m_currentFile->setFileName(m_formElements[0].m_filename);
m_currentFile->open(QFile::ReadOnly);
}
qint64 FormDataIODevice::readData(char* destination, qint64 size)
{
if (m_formElements.isEmpty())
return -1;
qint64 copied = 0;
while (copied < size && !m_formElements.isEmpty()) {
const FormDataElement& element = m_formElements[0];
const qint64 available = size-copied;
if (element.m_type == FormDataElement::data) {
const qint64 toCopy = qMin<qint64>(available, element.m_data.size() - m_currentDelta);
memcpy(destination+copied, element.m_data.data()+m_currentDelta, toCopy);
m_currentDelta += toCopy;
copied += toCopy;
if (m_currentDelta == element.m_data.size())
moveToNextElement();
} else {
const QByteArray data = m_currentFile->read(available);
memcpy(destination+copied, data.constData(), data.size());
copied += data.size();
if (m_currentFile->atEnd() || !m_currentFile->isOpen())
moveToNextElement();
}
}
return copied;
}
qint64 FormDataIODevice::writeData(const char*, qint64)
{
return -1;
}
bool FormDataIODevice::isSequential() const
{
return true;
}
QNetworkReplyHandler::QNetworkReplyHandler(ResourceHandle* handle, LoadMode loadMode)
: QObject(0)
, m_reply(0)
, m_resourceHandle(handle)
, m_redirected(false)
, m_responseSent(false)
, m_responseDataSent(false)
, m_loadMode(loadMode)
, m_shouldStart(true)
, m_shouldFinish(false)
, m_shouldSendResponse(false)
, m_shouldForwardData(false)
, m_redirectionTries(gMaxRecursionLimit)
{
const ResourceRequest &r = m_resourceHandle->request();
if (r.httpMethod() == "GET")
m_method = QNetworkAccessManager::GetOperation;
else if (r.httpMethod() == "HEAD")
m_method = QNetworkAccessManager::HeadOperation;
else if (r.httpMethod() == "POST")
m_method = QNetworkAccessManager::PostOperation;
else if (r.httpMethod() == "PUT")
m_method = QNetworkAccessManager::PutOperation;
#if QT_VERSION >= 0x040600
else if (r.httpMethod() == "DELETE")
m_method = QNetworkAccessManager::DeleteOperation;
#endif
#if QT_VERSION >= 0x040700
else if (r.httpMethod() == "OPTIONS")
m_method = QNetworkAccessManager::CustomOperation;
#endif
else
m_method = QNetworkAccessManager::UnknownOperation;
m_request = r.toNetworkRequest(m_resourceHandle->getInternal()->m_frame);
if (m_loadMode == LoadNormal)
start();
}
void QNetworkReplyHandler::setLoadMode(LoadMode mode)
{
switch (mode) {
case LoadNormal:
m_loadMode = LoadResuming;
emit processQueuedItems();
break;
case LoadDeferred:
m_loadMode = LoadDeferred;
break;
case LoadResuming:
Q_ASSERT(0); break;
};
}
void QNetworkReplyHandler::abort()
{
m_resourceHandle = 0;
if (m_reply) {
QNetworkReply* reply = release();
reply->abort();
reply->deleteLater();
}
deleteLater();
}
QNetworkReply* QNetworkReplyHandler::release()
{
QNetworkReply* reply = m_reply;
if (m_reply) {
disconnect(m_reply, 0, this, 0);
QCoreApplication::removePostedEvents(this, QEvent::MetaCall);
m_reply->setParent(0);
m_reply = 0;
}
return reply;
}
static bool ignoreHttpError(QNetworkReply* reply, bool receivedData)
{
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (httpStatusCode == 401 || httpStatusCode == 407)
return true;
if (receivedData && (httpStatusCode >= 400 && httpStatusCode < 600))
return true;
return false;
}
void QNetworkReplyHandler::finish()
{
m_shouldFinish = (m_loadMode != LoadNormal);
if (m_shouldFinish)
return;
sendResponseIfNeeded();
if (!m_resourceHandle)
return;
ResourceHandleClient* client = m_resourceHandle->client();
if (!client) {
m_reply->deleteLater();
m_reply = 0;
return;
}
QNetworkReply* oldReply = m_reply;
if (m_redirected) {
resetState();
start();
} else if (!m_reply->error() || ignoreHttpError(m_reply, m_responseDataSent)) {
client->didFinishLoading(m_resourceHandle);
} else {
QUrl url = m_reply->url();
int httpStatusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (httpStatusCode) {
ResourceError error("HTTP", httpStatusCode, url.toString(), m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString());
client->didFail(m_resourceHandle, error);
} else {
ResourceError error("QtNetwork", m_reply->error(), url.toString(), m_reply->errorString());
client->didFail(m_resourceHandle, error);
}
}
oldReply->deleteLater();
if (oldReply == m_reply)
m_reply = 0;
}
void QNetworkReplyHandler::sendResponseIfNeeded()
{
m_shouldSendResponse = (m_loadMode != LoadNormal);
if (m_shouldSendResponse)
return;
if (m_reply->error() && !ignoreHttpError(m_reply, m_responseDataSent))
return;
if (m_responseSent || !m_resourceHandle)
return;
m_responseSent = true;
ResourceHandleClient* client = m_resourceHandle->client();
if (!client)
return;
WebCore::String contentType = m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
WebCore::String encoding = extractCharsetFromMediaType(contentType);
WebCore::String mimeType = extractMIMETypeFromMediaType(contentType);
if (mimeType.isEmpty()) {
QString extension = m_reply->url().path();
int index = extension.lastIndexOf(QLatin1Char('.'));
if (index > 0) {
extension = extension.mid(index + 1);
mimeType = MIMETypeRegistry::getMIMETypeForExtension(extension);
}
}
KURL url(m_reply->url());
ResourceResponse response(url, mimeType,
m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(),
encoding, String());
if (url.isLocalFile()) {
client->didReceiveResponse(m_resourceHandle, response);
return;
}
int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (url.protocolInHTTPFamily()) {
String suggestedFilename = filenameFromHTTPContentDisposition(QString::fromAscii(m_reply->rawHeader("Content-Disposition")));
if (!suggestedFilename.isEmpty())
response.setSuggestedFilename(suggestedFilename);
else
response.setSuggestedFilename(url.lastPathComponent());
response.setHTTPStatusCode(statusCode);
response.setHTTPStatusText(m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toByteArray().constData());
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
foreach (const QNetworkReply::RawHeaderPair& pair, m_reply->rawHeaderPairs()) {
response.setHTTPHeaderField(QString::fromAscii(pair.first), QString::fromAscii(pair.second));
}
#else
foreach (const QByteArray& headerName, m_reply->rawHeaderList()) {
response.setHTTPHeaderField(QString::fromAscii(headerName), QString::fromAscii(m_reply->rawHeader(headerName)));
}
#endif
}
QUrl redirection = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (redirection.isValid()) {
QUrl newUrl = m_reply->url().resolved(redirection);
m_redirectionTries--;
if (m_redirectionTries == 0) { ResourceError error(newUrl.host(), 400 ,
newUrl.toString(),
QCoreApplication::translate("QWebPage", "Redirection limit reached"));
client->didFail(m_resourceHandle, error);
return;
}
m_redirected = true;
ResourceRequest newRequest = m_resourceHandle->request();
newRequest.setURL(newUrl);
if (((statusCode >= 301 && statusCode <= 303) || statusCode == 307) && m_method == QNetworkAccessManager::PostOperation) {
m_method = QNetworkAccessManager::GetOperation;
newRequest.setHTTPMethod("GET");
}
if (!newRequest.url().protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https"))
newRequest.clearHTTPReferrer();
client->willSendRequest(m_resourceHandle, newRequest, response);
if (!m_resourceHandle) return;
m_request = newRequest.toNetworkRequest(m_resourceHandle->getInternal()->m_frame);
return;
}
client->didReceiveResponse(m_resourceHandle, response);
}
void QNetworkReplyHandler::forwardData()
{
m_shouldForwardData = (m_loadMode != LoadNormal);
if (m_shouldForwardData)
return;
sendResponseIfNeeded();
if (m_redirected)
return;
if (!m_resourceHandle)
return;
QByteArray data = m_reply->read(m_reply->bytesAvailable());
ResourceHandleClient* client = m_resourceHandle->client();
if (!client)
return;
if (!data.isEmpty()) {
m_responseDataSent = true;
client->didReceiveData(m_resourceHandle, data.constData(), data.length(), data.length() );
}
}
void QNetworkReplyHandler::uploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
if (!m_resourceHandle)
return;
ResourceHandleClient* client = m_resourceHandle->client();
if (!client)
return;
client->didSendData(m_resourceHandle, bytesSent, bytesTotal);
}
void QNetworkReplyHandler::start()
{
m_shouldStart = false;
ResourceHandleInternal* d = m_resourceHandle->getInternal();
QNetworkAccessManager* manager = d->m_frame->page()->networkAccessManager();
const QUrl url = m_request.url();
const QString scheme = url.scheme();
if (m_method == QNetworkAccessManager::PostOperation
&& (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data")))
m_method = QNetworkAccessManager::GetOperation;
switch (m_method) {
case QNetworkAccessManager::GetOperation:
m_reply = manager->get(m_request);
break;
case QNetworkAccessManager::PostOperation: {
FormDataIODevice* postDevice = new FormDataIODevice(d->m_request.httpBody());
m_reply = manager->post(m_request, postDevice);
postDevice->setParent(m_reply);
break;
}
case QNetworkAccessManager::HeadOperation:
m_reply = manager->head(m_request);
break;
case QNetworkAccessManager::PutOperation: {
FormDataIODevice* putDevice = new FormDataIODevice(d->m_request.httpBody());
m_reply = manager->put(m_request, putDevice);
putDevice->setParent(m_reply);
break;
}
#if QT_VERSION >= 0x040600
case QNetworkAccessManager::DeleteOperation: {
m_reply = manager->deleteResource(m_request);
break;
}
#endif
#if QT_VERSION >= 0x040700
case QNetworkAccessManager::CustomOperation:
m_reply = manager->sendCustomRequest(m_request, m_resourceHandle->request().httpMethod().latin1().data());
break;
#endif
case QNetworkAccessManager::UnknownOperation: {
m_reply = 0;
ResourceHandleClient* client = m_resourceHandle->client();
if (client) {
ResourceError error(url.host(), 400 ,
url.toString(),
QCoreApplication::translate("QWebPage", "Bad HTTP request"));
client->didFail(m_resourceHandle, error);
}
return;
}
}
m_reply->setParent(this);
connect(m_reply, SIGNAL(finished()),
this, SLOT(finish()), SIGNAL_CONN);
if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
connect(m_reply, SIGNAL(metaDataChanged()),
this, SLOT(sendResponseIfNeeded()), SIGNAL_CONN);
connect(m_reply, SIGNAL(readyRead()),
this, SLOT(forwardData()), SIGNAL_CONN);
if (m_resourceHandle->request().reportUploadProgress()) {
connect(m_reply, SIGNAL(uploadProgress(qint64, qint64)),
this, SLOT(uploadProgress(qint64, qint64)), SIGNAL_CONN);
}
connect(this, SIGNAL(processQueuedItems()),
this, SLOT(sendQueuedItems()), SIGNAL_CONN);
}
void QNetworkReplyHandler::resetState()
{
m_redirected = false;
m_responseSent = false;
m_responseDataSent = false;
m_shouldStart = true;
m_shouldFinish = false;
m_shouldSendResponse = false;
m_shouldForwardData = false;
}
void QNetworkReplyHandler::sendQueuedItems()
{
if (m_loadMode != LoadResuming)
return;
m_loadMode = LoadNormal;
if (m_shouldStart)
start();
if (m_shouldSendResponse)
sendResponseIfNeeded();
if (m_shouldForwardData)
forwardData();
if (m_shouldFinish)
finish();
}
}
#include "moc_QNetworkReplyHandler.cpp"