2 Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
3 Copyright (C) 2007 Staikos Computing Services Inc. <info@staikos.net>
4 Copyright (C) 2008 Holger Hans Peter Freyther
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
22 #include "QNetworkReplyHandler.h"
24 #include "HTTPParsers.h"
25 #include "MIMETypeRegistry.h"
26 #include "ResourceHandle.h"
27 #include "ResourceHandleClient.h"
28 #include "ResourceHandleInternal.h"
29 #include "ResourceResponse.h"
30 #include "ResourceRequest.h"
34 #include <QNetworkReply>
35 #include <QNetworkCookie>
36 #include <qwebframe.h>
39 #include <wtf/text/CString.h>
42 #include <QCoreApplication>
44 // In Qt 4.8, the attribute for sending a request synchronously will be made public,
45 // for now, use this hackish solution for setting the internal attribute.
46 const QNetworkRequest::Attribute gSynchronousNetworkRequestAttribute = static_cast<QNetworkRequest::Attribute>(QNetworkRequest::HttpPipeliningWasUsedAttribute + 7);
48 static const int gMaxRedirections = 10;
52 // Take a deep copy of the FormDataElement
53 FormDataIODevice::FormDataIODevice(FormData* data)
54 : m_formElements(data ? data->elements() : Vector<FormDataElement>())
60 setOpenMode(FormDataIODevice::ReadOnly);
62 if (!m_formElements.isEmpty() && m_formElements[0].m_type == FormDataElement::encodedFile)
63 openFileForCurrentElement();
67 FormDataIODevice::~FormDataIODevice()
72 qint64 FormDataIODevice::computeSize()
74 for (int i = 0; i < m_formElements.size(); ++i) {
75 const FormDataElement& element = m_formElements[i];
76 if (element.m_type == FormDataElement::data)
77 m_dataSize += element.m_data.size();
79 QFileInfo fi(element.m_filename);
80 m_fileSize += fi.size();
83 return m_dataSize + m_fileSize;
86 void FormDataIODevice::moveToNextElement()
89 m_currentFile->close();
92 m_formElements.remove(0);
94 if (m_formElements.isEmpty() || m_formElements[0].m_type == FormDataElement::data)
97 openFileForCurrentElement();
100 void FormDataIODevice::openFileForCurrentElement()
103 m_currentFile = new QFile;
105 m_currentFile->setFileName(m_formElements[0].m_filename);
106 m_currentFile->open(QFile::ReadOnly);
109 // m_formElements[0] is the current item. If the destination buffer is
110 // big enough we are going to read from more than one FormDataElement
111 qint64 FormDataIODevice::readData(char* destination, qint64 size)
113 if (m_formElements.isEmpty())
117 while (copied < size && !m_formElements.isEmpty()) {
118 const FormDataElement& element = m_formElements[0];
119 const qint64 available = size-copied;
121 if (element.m_type == FormDataElement::data) {
122 const qint64 toCopy = qMin<qint64>(available, element.m_data.size() - m_currentDelta);
123 memcpy(destination+copied, element.m_data.data()+m_currentDelta, toCopy);
124 m_currentDelta += toCopy;
127 if (m_currentDelta == element.m_data.size())
130 const QByteArray data = m_currentFile->read(available);
131 memcpy(destination+copied, data.constData(), data.size());
132 copied += data.size();
134 if (m_currentFile->atEnd() || !m_currentFile->isOpen())
142 qint64 FormDataIODevice::writeData(const char*, qint64)
147 bool FormDataIODevice::isSequential() const
152 QNetworkReplyHandlerCallQueue::QNetworkReplyHandlerCallQueue(QNetworkReplyHandler* handler, bool deferSignals)
153 : m_replyHandler(handler)
155 , m_deferSignals(deferSignals)
161 void QNetworkReplyHandlerCallQueue::push(EnqueuedCall method)
163 m_enqueuedCalls.append(method);
167 void QNetworkReplyHandlerCallQueue::lock()
172 void QNetworkReplyHandlerCallQueue::unlock()
181 void QNetworkReplyHandlerCallQueue::setDeferSignals(bool defer, bool sync)
183 m_deferSignals = defer;
187 QMetaObject::invokeMethod(this, "flush", Qt::QueuedConnection);
190 void QNetworkReplyHandlerCallQueue::flush()
197 while (!m_deferSignals && !m_locks && !m_enqueuedCalls.isEmpty())
198 (m_replyHandler->*(m_enqueuedCalls.takeFirst()))();
205 QueueLocker(QNetworkReplyHandlerCallQueue* queue) : m_queue(queue) { m_queue->lock(); }
206 ~QueueLocker() { m_queue->unlock(); }
208 QNetworkReplyHandlerCallQueue* m_queue;
211 QNetworkReplyWrapper::QNetworkReplyWrapper(QNetworkReplyHandlerCallQueue* queue, QNetworkReply* reply, bool sniffMIMETypes, QObject* parent)
215 , m_responseContainsData(false)
216 , m_sniffMIMETypes(sniffMIMETypes)
220 // setFinished() must be the first that we connect, so isFinished() is updated when running other slots.
221 connect(m_reply, SIGNAL(finished()), this, SLOT(setFinished()));
222 connect(m_reply, SIGNAL(finished()), this, SLOT(receiveMetaData()));
223 connect(m_reply, SIGNAL(readyRead()), this, SLOT(receiveMetaData()));
226 QNetworkReplyWrapper::~QNetworkReplyWrapper()
229 m_reply->deleteLater();
233 QNetworkReply* QNetworkReplyWrapper::release()
239 QNetworkReply* reply = m_reply;
247 void QNetworkReplyWrapper::synchronousLoad()
253 void QNetworkReplyWrapper::resetConnections()
256 // Disconnect all connections except the one to setFinished() slot.
257 m_reply->disconnect(this, SLOT(receiveMetaData()));
258 m_reply->disconnect(this, SLOT(didReceiveFinished()));
259 m_reply->disconnect(this, SLOT(didReceiveReadyRead()));
261 QCoreApplication::removePostedEvents(this, QEvent::MetaCall);
264 void QNetworkReplyWrapper::receiveMetaData()
266 // This slot is only used to receive the first signal from the QNetworkReply object.
270 WTF::String contentType = m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
271 m_encoding = extractCharsetFromMediaType(contentType);
272 m_advertisedMIMEType = extractMIMETypeFromMediaType(contentType);
274 m_redirectionTargetUrl = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
275 if (m_redirectionTargetUrl.isValid()) {
276 QueueLocker lock(m_queue);
277 m_queue->push(&QNetworkReplyHandler::sendResponseIfNeeded);
278 m_queue->push(&QNetworkReplyHandler::finish);
282 if (!m_sniffMIMETypes) {
283 emitMetaDataChanged();
287 bool isSupportedImageType = MIMETypeRegistry::isSupportedImageMIMEType(m_advertisedMIMEType);
289 Q_ASSERT(!m_sniffer);
291 m_sniffer = adoptPtr(new QtMIMETypeSniffer(m_reply, m_advertisedMIMEType, isSupportedImageType));
293 if (m_sniffer->isFinished()) {
294 receiveSniffedMIMEType();
298 connect(m_sniffer.get(), SIGNAL(finished()), this, SLOT(receiveSniffedMIMEType()));
301 void QNetworkReplyWrapper::receiveSniffedMIMEType()
305 m_sniffedMIMEType = m_sniffer->mimeType();
308 emitMetaDataChanged();
311 void QNetworkReplyWrapper::setFinished()
313 // Due to a limitation of QNetworkReply public API, its subclasses never get the chance to
314 // change the result of QNetworkReply::isFinished() method. So we need to keep track of the
315 // finished state ourselves. This limitation is fixed in 4.8, but we'll still have applications
316 // that don't use the solution. See http://bugreports.qt.nokia.com/browse/QTBUG-11737.
317 Q_ASSERT(!isFinished());
318 m_reply->setProperty("_q_isFinished", true);
321 void QNetworkReplyWrapper::emitMetaDataChanged()
323 QueueLocker lock(m_queue);
324 m_queue->push(&QNetworkReplyHandler::sendResponseIfNeeded);
326 if (m_reply->bytesAvailable()) {
327 m_responseContainsData = true;
328 m_queue->push(&QNetworkReplyHandler::forwardData);
332 m_queue->push(&QNetworkReplyHandler::finish);
336 // If not finished, connect to the slots that will be used from this point on.
337 connect(m_reply, SIGNAL(readyRead()), this, SLOT(didReceiveReadyRead()));
338 connect(m_reply, SIGNAL(finished()), this, SLOT(didReceiveFinished()));
341 void QNetworkReplyWrapper::didReceiveReadyRead()
343 if (m_reply->bytesAvailable())
344 m_responseContainsData = true;
345 m_queue->push(&QNetworkReplyHandler::forwardData);
348 void QNetworkReplyWrapper::didReceiveFinished()
350 // Disconnecting will make sure that nothing will happen after emitting the finished signal.
352 m_queue->push(&QNetworkReplyHandler::finish);
355 String QNetworkReplyHandler::httpMethod() const
358 case QNetworkAccessManager::GetOperation:
360 case QNetworkAccessManager::HeadOperation:
362 case QNetworkAccessManager::PostOperation:
364 case QNetworkAccessManager::PutOperation:
366 case QNetworkAccessManager::DeleteOperation:
368 case QNetworkAccessManager::CustomOperation:
369 return m_resourceHandle->firstRequest().httpMethod();
371 ASSERT_NOT_REACHED();
376 QNetworkReplyHandler::QNetworkReplyHandler(ResourceHandle* handle, LoadType loadType, bool deferred)
378 , m_resourceHandle(handle)
379 , m_loadType(loadType)
380 , m_redirectionTries(gMaxRedirections)
381 , m_queue(this, deferred)
383 const ResourceRequest &r = m_resourceHandle->firstRequest();
385 if (r.httpMethod() == "GET")
386 m_method = QNetworkAccessManager::GetOperation;
387 else if (r.httpMethod() == "HEAD")
388 m_method = QNetworkAccessManager::HeadOperation;
389 else if (r.httpMethod() == "POST")
390 m_method = QNetworkAccessManager::PostOperation;
391 else if (r.httpMethod() == "PUT")
392 m_method = QNetworkAccessManager::PutOperation;
393 else if (r.httpMethod() == "DELETE")
394 m_method = QNetworkAccessManager::DeleteOperation;
396 m_method = QNetworkAccessManager::CustomOperation;
398 QObject* originatingObject = 0;
399 if (m_resourceHandle->getInternal()->m_context)
400 originatingObject = m_resourceHandle->getInternal()->m_context->originatingObject();
402 m_request = r.toNetworkRequest(originatingObject);
404 m_queue.push(&QNetworkReplyHandler::start);
407 void QNetworkReplyHandler::abort()
409 m_resourceHandle = 0;
410 if (QNetworkReply* reply = release()) {
412 reply->deleteLater();
417 QNetworkReply* QNetworkReplyHandler::release()
422 QNetworkReply* reply = m_replyWrapper->release();
423 m_replyWrapper = nullptr;
427 static bool shouldIgnoreHttpError(QNetworkReply* reply, bool receivedData)
429 int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
431 if (httpStatusCode == 401 || httpStatusCode == 407)
434 if (receivedData && (httpStatusCode >= 400 && httpStatusCode < 600))
440 void QNetworkReplyHandler::finish()
442 ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted());
444 ResourceHandleClient* client = m_resourceHandle->client();
446 m_replyWrapper = nullptr;
450 if (m_replyWrapper->wasRedirected()) {
451 m_replyWrapper = nullptr;
452 m_queue.push(&QNetworkReplyHandler::start);
456 if (!m_replyWrapper->reply()->error() || shouldIgnoreHttpError(m_replyWrapper->reply(), m_replyWrapper->responseContainsData()))
457 client->didFinishLoading(m_resourceHandle, 0);
459 QUrl url = m_replyWrapper->reply()->url();
460 int httpStatusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
462 if (httpStatusCode) {
463 ResourceError error("HTTP", httpStatusCode, url.toString(), m_replyWrapper->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString());
464 client->didFail(m_resourceHandle, error);
466 ResourceError error("QtNetwork", m_replyWrapper->reply()->error(), url.toString(), m_replyWrapper->reply()->errorString());
467 client->didFail(m_resourceHandle, error);
471 m_replyWrapper = nullptr;
474 void QNetworkReplyHandler::sendResponseIfNeeded()
476 ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted());
478 if (m_replyWrapper->reply()->error() && m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).isNull())
481 ResourceHandleClient* client = m_resourceHandle->client();
485 WTF::String mimeType = m_replyWrapper->mimeType();
487 if (mimeType.isEmpty()) {
488 // let's try to guess from the extension
489 mimeType = MIMETypeRegistry::getMIMETypeForPath(m_replyWrapper->reply()->url().path());
492 KURL url(m_replyWrapper->reply()->url());
493 ResourceResponse response(url, mimeType.lower(),
494 m_replyWrapper->reply()->header(QNetworkRequest::ContentLengthHeader).toLongLong(),
495 m_replyWrapper->encoding(), String());
497 if (url.isLocalFile()) {
498 client->didReceiveResponse(m_resourceHandle, response);
502 // The status code is equal to 0 for protocols not in the HTTP family.
503 int statusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
505 if (url.protocolInHTTPFamily()) {
506 String suggestedFilename = filenameFromHTTPContentDisposition(QString::fromLatin1(m_replyWrapper->reply()->rawHeader("Content-Disposition")));
508 if (!suggestedFilename.isEmpty())
509 response.setSuggestedFilename(suggestedFilename);
511 response.setSuggestedFilename(url.lastPathComponent());
513 response.setHTTPStatusCode(statusCode);
514 response.setHTTPStatusText(m_replyWrapper->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toByteArray().constData());
516 // Add remaining headers.
517 foreach (const QNetworkReply::RawHeaderPair& pair, m_replyWrapper->reply()->rawHeaderPairs())
518 response.setHTTPHeaderField(QString::fromLatin1(pair.first), QString::fromLatin1(pair.second));
521 QUrl redirection = m_replyWrapper->reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
522 if (redirection.isValid()) {
523 redirect(response, redirection);
527 client->didReceiveResponse(m_resourceHandle, response);
530 void QNetworkReplyHandler::redirect(ResourceResponse& response, const QUrl& redirection)
532 QUrl newUrl = m_replyWrapper->reply()->url().resolved(redirection);
534 ResourceHandleClient* client = m_resourceHandle->client();
537 int statusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
539 m_redirectionTries--;
540 if (!m_redirectionTries) {
541 ResourceError error(newUrl.host(), 400 /*bad request*/,
543 QCoreApplication::translate("QWebPage", "Redirection limit reached"));
544 client->didFail(m_resourceHandle, error);
545 m_replyWrapper = nullptr;
549 // Status Code 301 (Moved Permanently), 302 (Moved Temporarily), 303 (See Other):
550 // - If original request is POST convert to GET and redirect automatically
551 // Status Code 307 (Temporary Redirect) and all other redirect status codes:
552 // - Use the HTTP method from the previous request
553 if ((statusCode >= 301 && statusCode <= 303) && m_resourceHandle->firstRequest().httpMethod() == "POST")
554 m_method = QNetworkAccessManager::GetOperation;
556 ResourceRequest newRequest = m_resourceHandle->firstRequest();
557 newRequest.setHTTPMethod(httpMethod());
558 newRequest.setURL(newUrl);
560 // Should not set Referer after a redirect from a secure resource to non-secure one.
561 if (!newRequest.url().protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https"))
562 newRequest.clearHTTPReferrer();
564 client->willSendRequest(m_resourceHandle, newRequest, response);
565 if (wasAborted()) // Network error cancelled the request.
568 QObject* originatingObject = 0;
569 if (m_resourceHandle->getInternal()->m_context)
570 originatingObject = m_resourceHandle->getInternal()->m_context->originatingObject();
572 m_request = newRequest.toNetworkRequest(originatingObject);
575 void QNetworkReplyHandler::forwardData()
577 ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted() && !m_replyWrapper->wasRedirected());
579 QByteArray data = m_replyWrapper->reply()->read(m_replyWrapper->reply()->bytesAvailable());
581 ResourceHandleClient* client = m_resourceHandle->client();
585 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793
586 // -1 means we do not provide any data about transfer size to inspector so it would use
587 // Content-Length headers or content size to show transfer size.
589 client->didReceiveData(m_resourceHandle, data.constData(), data.length(), -1);
592 void QNetworkReplyHandler::uploadProgress(qint64 bytesSent, qint64 bytesTotal)
597 ResourceHandleClient* client = m_resourceHandle->client();
601 client->didSendData(m_resourceHandle, bytesSent, bytesTotal);
604 void QNetworkReplyHandler::clearContentHeaders()
606 // Clearing Content-length and Content-type of the requests that do not have contents.
607 // This is necessary to ensure POST requests redirected to GETs do not leak metadata
608 // about the POST content to the site they've been redirected to.
609 m_request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant());
610 m_request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant());
613 FormDataIODevice* QNetworkReplyHandler::getIODevice(const ResourceRequest& request)
615 FormDataIODevice* device = new FormDataIODevice(request.httpBody());
616 // We may be uploading files so prevent QNR from buffering data.
617 m_request.setHeader(QNetworkRequest::ContentLengthHeader, device->getFormDataSize());
618 m_request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, QVariant(true));
622 QNetworkReply* QNetworkReplyHandler::sendNetworkRequest(QNetworkAccessManager* manager, const ResourceRequest& request)
624 if (m_loadType == SynchronousLoad)
625 m_request.setAttribute(gSynchronousNetworkRequestAttribute, true);
630 const QUrl url = m_request.url();
631 const QString scheme = url.scheme();
632 // Post requests on files and data don't really make sense, but for
633 // fast/forms/form-post-urlencoded.html and for fast/forms/button-state-restore.html
634 // we still need to retrieve the file/data, which means we map it to a Get instead.
635 if (m_method == QNetworkAccessManager::PostOperation
636 && (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data")))
637 m_method = QNetworkAccessManager::GetOperation;
640 case QNetworkAccessManager::GetOperation:
641 clearContentHeaders();
642 return manager->get(m_request);
643 case QNetworkAccessManager::PostOperation: {
644 FormDataIODevice* postDevice = getIODevice(request);
645 QNetworkReply* result = manager->post(m_request, postDevice);
646 postDevice->setParent(result);
649 case QNetworkAccessManager::HeadOperation:
650 clearContentHeaders();
651 return manager->head(m_request);
652 case QNetworkAccessManager::PutOperation: {
653 FormDataIODevice* putDevice = getIODevice(request);
654 QNetworkReply* result = manager->put(m_request, putDevice);
655 putDevice->setParent(result);
658 case QNetworkAccessManager::DeleteOperation: {
659 clearContentHeaders();
660 return manager->deleteResource(m_request);
662 case QNetworkAccessManager::CustomOperation: {
663 FormDataIODevice* customDevice = getIODevice(request);
664 QNetworkReply* result = manager->sendCustomRequest(m_request, m_resourceHandle->firstRequest().httpMethod().latin1().data(), customDevice);
665 customDevice->setParent(result);
668 case QNetworkAccessManager::UnknownOperation:
669 ASSERT_NOT_REACHED();
675 void QNetworkReplyHandler::start()
677 ResourceHandleInternal* d = m_resourceHandle->getInternal();
678 if (!d || !d->m_context)
681 QNetworkReply* reply = sendNetworkRequest(d->m_context->networkAccessManager(), d->m_firstRequest);
685 m_replyWrapper = adoptPtr(new QNetworkReplyWrapper(&m_queue, reply, m_resourceHandle->shouldContentSniff() && d->m_context->mimeSniffingEnabled(), this));
687 if (m_loadType == SynchronousLoad) {
688 m_replyWrapper->synchronousLoad();
689 // If supported, a synchronous request will be finished at this point, no need to hook up the signals.
693 if (m_resourceHandle->firstRequest().reportUploadProgress())
694 connect(m_replyWrapper->reply(), SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64)));
699 #include "moc_QNetworkReplyHandler.cpp"