1/*
2    Copyright (C) 2008, 2012 Digia Plc. and/or its subsidiary(-ies)
3    Copyright (C) 2007 Staikos Computing Services Inc.  <info@staikos.net>
4    Copyright (C) 2008 Holger Hans Peter Freyther
5
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.
10
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.
15
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.
20*/
21#include "config.h"
22#include "QNetworkReplyHandler.h"
23
24#include "BlobData.h"
25#include "HTTPParsers.h"
26#include "MIMETypeRegistry.h"
27#include "NetworkingContext.h"
28#include "ResourceHandle.h"
29#include "ResourceHandleClient.h"
30#include "ResourceHandleInternal.h"
31#include "ResourceRequest.h"
32#include "ResourceResponse.h"
33#include <QDateTime>
34#include <QFile>
35#include <QFileInfo>
36#include <QMimeDatabase>
37#include <QNetworkCookie>
38#include <QNetworkReply>
39
40#include <wtf/text/CString.h>
41
42#include <QCoreApplication>
43
44static const int gMaxRedirections = 10;
45
46namespace WebCore {
47
48FormDataIODevice::FormDataIODevice(FormData* data)
49    : m_currentFile(0)
50    , m_currentDelta(0)
51    , m_fileSize(0)
52    , m_dataSize(0)
53{
54    setOpenMode(FormDataIODevice::ReadOnly);
55
56    prepareFormElements(data);
57    prepareCurrentElement();
58    computeSize();
59}
60
61FormDataIODevice::~FormDataIODevice()
62{
63    delete m_currentFile;
64}
65
66void FormDataIODevice::prepareFormElements(FormData* formData)
67{
68    if (!formData)
69        return;
70
71    RefPtr<FormData> formDataRef(formData);
72
73#if ENABLE(BLOB)
74    formDataRef = formDataRef->resolveBlobReferences();
75#endif
76
77    // Take a deep copy of the FormDataElements
78    m_formElements = formDataRef->elements();
79}
80
81
82qint64 FormDataIODevice::computeSize()
83{
84    for (int i = 0; i < m_formElements.size(); ++i) {
85        const FormDataElement& element = m_formElements[i];
86        if (element.m_type == FormDataElement::data)
87            m_dataSize += element.m_data.size();
88        else {
89            QFileInfo fi(element.m_filename);
90#if ENABLE(BLOB)
91            qint64 fileEnd = fi.size();
92            if (element.m_fileLength != BlobDataItem::toEndOfFile)
93                fileEnd = qMin<qint64>(fi.size(), element.m_fileStart + element.m_fileLength);
94            m_fileSize += qMax<qint64>(0, fileEnd - element.m_fileStart);
95#else
96            m_fileSize += fi.size();
97#endif
98        }
99    }
100    return m_dataSize + m_fileSize;
101}
102
103void FormDataIODevice::moveToNextElement()
104{
105    if (m_currentFile)
106        m_currentFile->close();
107    m_currentDelta = 0;
108
109    m_formElements.remove(0);
110
111    prepareCurrentElement();
112}
113
114void FormDataIODevice::prepareCurrentElement()
115{
116    if (m_formElements.isEmpty())
117        return;
118
119    switch (m_formElements[0].m_type) {
120    case FormDataElement::data:
121        return;
122    case FormDataElement::encodedFile:
123        openFileForCurrentElement();
124        break;
125    default:
126        // At this point encodedBlob should already have been handled.
127        ASSERT_NOT_REACHED();
128    }
129}
130
131void FormDataIODevice::openFileForCurrentElement()
132{
133    if (!m_currentFile)
134        m_currentFile = new QFile;
135
136    m_currentFile->setFileName(m_formElements[0].m_filename);
137    m_currentFile->open(QFile::ReadOnly);
138#if ENABLE(BLOB)
139    if (isValidFileTime(m_formElements[0].m_expectedFileModificationTime)) {
140        QFileInfo info(*m_currentFile);
141        if (!info.exists() || static_cast<time_t>(m_formElements[0].m_expectedFileModificationTime) < info.lastModified().toTime_t()) {
142            moveToNextElement();
143            return;
144        }
145    }
146    if (m_formElements[0].m_fileStart)
147        m_currentFile->seek(m_formElements[0].m_fileStart);
148#endif
149}
150
151// m_formElements[0] is the current item. If the destination buffer is
152// big enough we are going to read from more than one FormDataElement
153qint64 FormDataIODevice::readData(char* destination, qint64 size)
154{
155    if (m_formElements.isEmpty())
156        return -1;
157
158    qint64 copied = 0;
159    while (copied < size && !m_formElements.isEmpty()) {
160        const FormDataElement& element = m_formElements[0];
161        const qint64 available = size-copied;
162
163        if (element.m_type == FormDataElement::data) {
164            const qint64 toCopy = qMin<qint64>(available, element.m_data.size() - m_currentDelta);
165            memcpy(destination+copied, element.m_data.data()+m_currentDelta, toCopy);
166            m_currentDelta += toCopy;
167            copied += toCopy;
168
169            if (m_currentDelta == element.m_data.size())
170                moveToNextElement();
171        } else if (element.m_type == FormDataElement::encodedFile) {
172            quint64 toCopy = available;
173#if ENABLE(BLOB)
174            if (element.m_fileLength != BlobDataItem::toEndOfFile)
175                toCopy = qMin<qint64>(toCopy, element.m_fileLength - m_currentDelta);
176#endif
177            const QByteArray data = m_currentFile->read(toCopy);
178            memcpy(destination+copied, data.constData(), data.size());
179            m_currentDelta += data.size();
180            copied += data.size();
181
182            if (m_currentFile->atEnd() || !m_currentFile->isOpen())
183                moveToNextElement();
184#if ENABLE(BLOB)
185            else if (element.m_fileLength != BlobDataItem::toEndOfFile && m_currentDelta == element.m_fileLength)
186                moveToNextElement();
187#endif
188        }
189    }
190
191    return copied;
192}
193
194qint64 FormDataIODevice::writeData(const char*, qint64)
195{
196    return -1;
197}
198
199bool FormDataIODevice::isSequential() const
200{
201    return true;
202}
203
204QNetworkReplyHandlerCallQueue::QNetworkReplyHandlerCallQueue(QNetworkReplyHandler* handler, bool deferSignals)
205    : m_replyHandler(handler)
206    , m_locks(0)
207    , m_deferSignals(deferSignals)
208    , m_flushing(false)
209{
210    Q_ASSERT(handler);
211}
212
213void QNetworkReplyHandlerCallQueue::push(EnqueuedCall method)
214{
215    m_enqueuedCalls.append(method);
216    flush();
217}
218
219void QNetworkReplyHandlerCallQueue::lock()
220{
221    ++m_locks;
222}
223
224void QNetworkReplyHandlerCallQueue::unlock()
225{
226    if (!m_locks)
227        return;
228
229    --m_locks;
230    flush();
231}
232
233void QNetworkReplyHandlerCallQueue::setDeferSignals(bool defer, bool sync)
234{
235    m_deferSignals = defer;
236    if (sync)
237        flush();
238    else
239        QMetaObject::invokeMethod(this, "flush",  Qt::QueuedConnection);
240}
241
242void QNetworkReplyHandlerCallQueue::flush()
243{
244    if (m_flushing)
245        return;
246
247    m_flushing = true;
248
249    while (!m_deferSignals && !m_locks && !m_enqueuedCalls.isEmpty())
250        (m_replyHandler->*(m_enqueuedCalls.takeFirst()))();
251
252    m_flushing = false;
253}
254
255class QueueLocker {
256public:
257    QueueLocker(QNetworkReplyHandlerCallQueue* queue) : m_queue(queue) { m_queue->lock(); }
258    ~QueueLocker() { m_queue->unlock(); }
259private:
260    QNetworkReplyHandlerCallQueue* m_queue;
261};
262
263QNetworkReplyWrapper::QNetworkReplyWrapper(QNetworkReplyHandlerCallQueue* queue, QNetworkReply* reply, bool sniffMIMETypes, QObject* parent)
264    : QObject(parent)
265    , m_reply(reply)
266    , m_queue(queue)
267    , m_responseContainsData(false)
268    , m_sniffMIMETypes(sniffMIMETypes)
269{
270    Q_ASSERT(m_reply);
271
272    // setFinished() must be the first that we connect, so isFinished() is updated when running other slots.
273    connect(m_reply, SIGNAL(finished()), this, SLOT(setFinished()));
274    connect(m_reply, SIGNAL(finished()), this, SLOT(receiveMetaData()));
275    connect(m_reply, SIGNAL(readyRead()), this, SLOT(receiveMetaData()));
276    connect(m_reply, SIGNAL(destroyed()), this, SLOT(replyDestroyed()));
277}
278
279QNetworkReplyWrapper::~QNetworkReplyWrapper()
280{
281    if (m_reply)
282        m_reply->deleteLater();
283    m_queue->clear();
284}
285
286QNetworkReply* QNetworkReplyWrapper::release()
287{
288    if (!m_reply)
289        return 0;
290
291    m_reply->disconnect(this);
292    QNetworkReply* reply = m_reply;
293    m_reply = 0;
294    m_sniffer = nullptr;
295
296    return reply;
297}
298
299void QNetworkReplyWrapper::synchronousLoad()
300{
301    setFinished();
302    receiveMetaData();
303}
304
305void QNetworkReplyWrapper::stopForwarding()
306{
307    if (m_reply) {
308        // Disconnect all connections that might affect the ResourceHandleClient.
309        m_reply->disconnect(this, SLOT(receiveMetaData()));
310        m_reply->disconnect(this, SLOT(didReceiveFinished()));
311        m_reply->disconnect(this, SLOT(didReceiveReadyRead()));
312    }
313    QCoreApplication::removePostedEvents(this, QEvent::MetaCall);
314}
315
316void QNetworkReplyWrapper::receiveMetaData()
317{
318    // This slot is only used to receive the first signal from the QNetworkReply object.
319    stopForwarding();
320
321    WTF::String contentType = m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
322    m_encoding = extractCharsetFromMediaType(contentType);
323    m_advertisedMIMEType = extractMIMETypeFromMediaType(contentType);
324
325    m_redirectionTargetUrl = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
326    if (m_redirectionTargetUrl.isValid()) {
327        QueueLocker lock(m_queue);
328        m_queue->push(&QNetworkReplyHandler::sendResponseIfNeeded);
329        m_queue->push(&QNetworkReplyHandler::finish);
330        return;
331    }
332
333    if (!m_sniffMIMETypes) {
334        emitMetaDataChanged();
335        return;
336    }
337
338    bool isSupportedImageType = MIMETypeRegistry::isSupportedImageMIMEType(m_advertisedMIMEType);
339
340    Q_ASSERT(!m_sniffer);
341
342    m_sniffer = adoptPtr(new QtMIMETypeSniffer(m_reply, m_advertisedMIMEType, isSupportedImageType));
343
344    if (m_sniffer->isFinished()) {
345        receiveSniffedMIMEType();
346        return;
347    }
348
349    connect(m_sniffer.get(), SIGNAL(finished()), this, SLOT(receiveSniffedMIMEType()));
350}
351
352void QNetworkReplyWrapper::receiveSniffedMIMEType()
353{
354    Q_ASSERT(m_sniffer);
355
356    m_sniffedMIMEType = m_sniffer->mimeType();
357    m_sniffer = nullptr;
358
359    emitMetaDataChanged();
360}
361
362void QNetworkReplyWrapper::setFinished()
363{
364    // Due to a limitation of QNetworkReply public API, its subclasses never get the chance to
365    // change the result of QNetworkReply::isFinished() method. So we need to keep track of the
366    // finished state ourselves. This limitation is fixed in 4.8, but we'll still have applications
367    // that don't use the solution. See http://bugreports.qt.nokia.com/browse/QTBUG-11737.
368    Q_ASSERT(!isFinished());
369    m_reply->setProperty("_q_isFinished", true);
370}
371
372void QNetworkReplyWrapper::replyDestroyed()
373{
374    m_reply = 0;
375    m_sniffer = nullptr;
376}
377
378void QNetworkReplyWrapper::emitMetaDataChanged()
379{
380    QueueLocker lock(m_queue);
381    m_queue->push(&QNetworkReplyHandler::sendResponseIfNeeded);
382
383    if (m_reply->bytesAvailable()) {
384        m_responseContainsData = true;
385        m_queue->push(&QNetworkReplyHandler::forwardData);
386    }
387
388    if (isFinished()) {
389        m_queue->push(&QNetworkReplyHandler::finish);
390        return;
391    }
392
393    // If not finished, connect to the slots that will be used from this point on.
394    connect(m_reply, SIGNAL(readyRead()), this, SLOT(didReceiveReadyRead()));
395    connect(m_reply, SIGNAL(finished()), this, SLOT(didReceiveFinished()));
396}
397
398void QNetworkReplyWrapper::didReceiveReadyRead()
399{
400    if (m_reply->bytesAvailable())
401        m_responseContainsData = true;
402    m_queue->push(&QNetworkReplyHandler::forwardData);
403}
404
405void QNetworkReplyWrapper::didReceiveFinished()
406{
407    // Disconnecting will make sure that nothing will happen after emitting the finished signal.
408    stopForwarding();
409    m_queue->push(&QNetworkReplyHandler::finish);
410}
411
412String QNetworkReplyHandler::httpMethod() const
413{
414    switch (m_method) {
415    case QNetworkAccessManager::GetOperation:
416        return "GET";
417    case QNetworkAccessManager::HeadOperation:
418        return "HEAD";
419    case QNetworkAccessManager::PostOperation:
420        return "POST";
421    case QNetworkAccessManager::PutOperation:
422        return "PUT";
423    case QNetworkAccessManager::DeleteOperation:
424        return "DELETE";
425    case QNetworkAccessManager::CustomOperation:
426        return m_resourceHandle->firstRequest().httpMethod();
427    default:
428        ASSERT_NOT_REACHED();
429        return "GET";
430    }
431}
432
433QNetworkReplyHandler::QNetworkReplyHandler(ResourceHandle* handle, LoadType loadType, bool deferred)
434    : QObject(0)
435    , m_resourceHandle(handle)
436    , m_loadType(loadType)
437    , m_redirectionTries(gMaxRedirections)
438    , m_queue(this, deferred)
439{
440    const ResourceRequest &r = m_resourceHandle->firstRequest();
441
442    if (r.httpMethod() == "GET")
443        m_method = QNetworkAccessManager::GetOperation;
444    else if (r.httpMethod() == "HEAD")
445        m_method = QNetworkAccessManager::HeadOperation;
446    else if (r.httpMethod() == "POST")
447        m_method = QNetworkAccessManager::PostOperation;
448    else if (r.httpMethod() == "PUT")
449        m_method = QNetworkAccessManager::PutOperation;
450    else if (r.httpMethod() == "DELETE")
451        m_method = QNetworkAccessManager::DeleteOperation;
452    else
453        m_method = QNetworkAccessManager::CustomOperation;
454
455    m_request = r.toNetworkRequest(m_resourceHandle->getInternal()->m_context.get());
456
457    m_queue.push(&QNetworkReplyHandler::start);
458}
459
460void QNetworkReplyHandler::abort()
461{
462    m_resourceHandle = 0;
463    if (QNetworkReply* reply = release()) {
464        reply->abort();
465        reply->deleteLater();
466    }
467    deleteLater();
468}
469
470QNetworkReply* QNetworkReplyHandler::release()
471{
472    if (!m_replyWrapper)
473        return 0;
474
475    m_timeoutTimer.stop();
476    QNetworkReply* reply = m_replyWrapper->release();
477    m_replyWrapper = nullptr;
478    return reply;
479}
480
481static bool shouldIgnoreHttpError(QNetworkReply* reply, bool receivedData)
482{
483    // An HEAD XmlHTTPRequest shouldn't be marked as failure for HTTP errors.
484    if (reply->operation() == QNetworkAccessManager::HeadOperation)
485        return true;
486
487    int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
488
489    if (httpStatusCode == 401 || httpStatusCode == 407)
490        return true;
491
492    if (receivedData && (httpStatusCode >= 400 && httpStatusCode < 600))
493        return true;
494
495    return false;
496}
497
498void QNetworkReplyHandler::finish()
499{
500    ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted());
501    m_timeoutTimer.stop();
502
503    ResourceHandleClient* client = m_resourceHandle->client();
504    if (!client) {
505        m_replyWrapper = nullptr;
506        return;
507    }
508
509    if (m_replyWrapper->wasRedirected()) {
510        m_replyWrapper = nullptr;
511        m_queue.push(&QNetworkReplyHandler::start);
512        return;
513    }
514
515    if (!m_replyWrapper->reply()->error() || shouldIgnoreHttpError(m_replyWrapper->reply(), m_replyWrapper->responseContainsData()))
516        client->didFinishLoading(m_resourceHandle, 0);
517    else
518        client->didFail(m_resourceHandle, errorForReply(m_replyWrapper->reply()));
519
520    m_replyWrapper = nullptr;
521}
522
523void QNetworkReplyHandler::timeout()
524{
525    if (!m_replyWrapper || wasAborted())
526        return;
527
528    // The request is already finished, but is probably just waiting in the queue to get processed.
529    // In this case we ignore the timeout and proceed as normal.
530    if (m_replyWrapper->isFinished())
531        return;
532
533    ResourceHandleClient* client = m_resourceHandle->client();
534    if (!client) {
535        m_replyWrapper.clear();
536        return;
537    }
538
539    ASSERT(m_replyWrapper->reply());
540
541    ResourceError timeoutError("QtNetwork", QNetworkReply::TimeoutError, m_replyWrapper->reply()->url().toString(), "Request timed out");
542    timeoutError.setIsTimeout(true);
543    client->didFail(m_resourceHandle, timeoutError);
544
545    m_replyWrapper.clear();
546}
547
548void QNetworkReplyHandler::timerEvent(QTimerEvent* timerEvent)
549{
550    ASSERT_UNUSED(timerEvent, timerEvent->timerId()== m_timeoutTimer.timerId());
551    m_timeoutTimer.stop();
552    timeout();
553}
554
555void QNetworkReplyHandler::sendResponseIfNeeded()
556{
557    ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted());
558
559    if (m_replyWrapper->reply()->error() && m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).isNull())
560        return;
561
562    ResourceHandleClient* client = m_resourceHandle->client();
563    if (!client)
564        return;
565
566    WTF::String mimeType = m_replyWrapper->mimeType();
567
568    if (mimeType.isEmpty()) {
569        // let's try to guess from the extension
570        mimeType = MIMETypeRegistry::getMIMETypeForPath(m_replyWrapper->reply()->url().path());
571    }
572
573    KURL url(m_replyWrapper->reply()->url());
574    ResourceResponse response(url, mimeType.lower(),
575                              m_replyWrapper->reply()->header(QNetworkRequest::ContentLengthHeader).toLongLong(),
576                              m_replyWrapper->encoding(), String());
577
578    if (url.isLocalFile()) {
579        client->didReceiveResponse(m_resourceHandle, response);
580        return;
581    }
582
583    // The status code is equal to 0 for protocols not in the HTTP family.
584    int statusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
585
586    if (url.protocolIsInHTTPFamily()) {
587        String suggestedFilename = filenameFromHTTPContentDisposition(QString::fromLatin1(m_replyWrapper->reply()->rawHeader("Content-Disposition")));
588
589        if (!suggestedFilename.isEmpty())
590            response.setSuggestedFilename(suggestedFilename);
591        else {
592            Vector<String> extensions = MIMETypeRegistry::getExtensionsForMIMEType(mimeType);
593            if (extensions.isEmpty())
594                response.setSuggestedFilename(url.lastPathComponent());
595            else {
596                // If the suffix doesn't match the MIME type, correct the suffix.
597                QString filename = url.lastPathComponent();
598                const String suffix = QMimeDatabase().suffixForFileName(filename);
599                if (!extensions.contains(suffix)) {
600                    filename.chop(suffix.length());
601                    filename += MIMETypeRegistry::getPreferredExtensionForMIMEType(mimeType);
602                }
603                response.setSuggestedFilename(filename);
604            }
605        }
606
607        response.setHTTPStatusCode(statusCode);
608        response.setHTTPStatusText(m_replyWrapper->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toByteArray().constData());
609
610        // Add remaining headers.
611        foreach (const QNetworkReply::RawHeaderPair& pair, m_replyWrapper->reply()->rawHeaderPairs())
612            response.setHTTPHeaderField(QString::fromLatin1(pair.first), QString::fromLatin1(pair.second));
613    }
614
615    QUrl redirection = m_replyWrapper->reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
616    if (redirection.isValid()) {
617        redirect(response, redirection);
618        return;
619    }
620
621    client->didReceiveResponse(m_resourceHandle, response);
622}
623
624void QNetworkReplyHandler::redirect(ResourceResponse& response, const QUrl& redirection)
625{
626    QUrl newUrl = m_replyWrapper->reply()->url().resolved(redirection);
627
628    ResourceHandleClient* client = m_resourceHandle->client();
629    ASSERT(client);
630
631    int statusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
632
633    m_redirectionTries--;
634    if (!m_redirectionTries) {
635        ResourceError error("HTTP", 400 /*bad request*/,
636                            newUrl.toString(),
637                            QCoreApplication::translate("QWebPage", "Redirection limit reached"));
638        client->didFail(m_resourceHandle, error);
639        m_replyWrapper = nullptr;
640        return;
641    }
642
643    //  Status Code 301 (Moved Permanently), 302 (Moved Temporarily), 303 (See Other):
644    //    - If original request is POST convert to GET and redirect automatically
645    //  Status Code 307 (Temporary Redirect) and all other redirect status codes:
646    //    - Use the HTTP method from the previous request
647    if ((statusCode >= 301 && statusCode <= 303) && m_resourceHandle->firstRequest().httpMethod() == "POST")
648        m_method = QNetworkAccessManager::GetOperation;
649
650    ResourceRequest newRequest = m_resourceHandle->firstRequest();
651    newRequest.setHTTPMethod(httpMethod());
652    newRequest.setURL(newUrl);
653
654    // Should not set Referer after a redirect from a secure resource to non-secure one.
655    if (!newRequest.url().protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https") && m_resourceHandle->context()->shouldClearReferrerOnHTTPSToHTTPRedirect())
656        newRequest.clearHTTPReferrer();
657
658    client->willSendRequest(m_resourceHandle, newRequest, response);
659    if (wasAborted()) // Network error cancelled the request.
660        return;
661
662    m_request = newRequest.toNetworkRequest(m_resourceHandle->getInternal()->m_context.get());
663}
664
665void QNetworkReplyHandler::forwardData()
666{
667    ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted() && !m_replyWrapper->wasRedirected());
668
669    QByteArray data = m_replyWrapper->reply()->read(m_replyWrapper->reply()->bytesAvailable());
670
671    ResourceHandleClient* client = m_resourceHandle->client();
672    if (!client)
673        return;
674
675    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793
676    // -1 means we do not provide any data about transfer size to inspector so it would use
677    // Content-Length headers or content size to show transfer size.
678    if (!data.isEmpty())
679        client->didReceiveData(m_resourceHandle, data.constData(), data.length(), -1);
680}
681
682void QNetworkReplyHandler::uploadProgress(qint64 bytesSent, qint64 bytesTotal)
683{
684    if (wasAborted())
685        return;
686
687    ResourceHandleClient* client = m_resourceHandle->client();
688    if (!client)
689        return;
690
691    if (!bytesTotal) {
692        // When finished QNetworkReply emits a progress of 0 bytes.
693        // Ignore that, to avoid firing twice.
694        return;
695    }
696
697    client->didSendData(m_resourceHandle, bytesSent, bytesTotal);
698}
699
700void QNetworkReplyHandler::clearContentHeaders()
701{
702    // Clearing Content-length and Content-type of the requests that do not have contents.
703    // This is necessary to ensure POST requests redirected to GETs do not leak metadata
704    // about the POST content to the site they've been redirected to.
705    m_request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant());
706    m_request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant());
707}
708
709FormDataIODevice* QNetworkReplyHandler::getIODevice(const ResourceRequest& request)
710{
711    FormDataIODevice* device = new FormDataIODevice(request.httpBody());
712    // We may be uploading files so prevent QNR from buffering data.
713    m_request.setHeader(QNetworkRequest::ContentLengthHeader, device->getFormDataSize());
714    m_request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, QVariant(true));
715    return device;
716}
717
718QNetworkReply* QNetworkReplyHandler::sendNetworkRequest(QNetworkAccessManager* manager, const ResourceRequest& request)
719{
720    if (m_loadType == SynchronousLoad)
721        m_request.setAttribute(QNetworkRequest::SynchronousRequestAttribute, true);
722
723    if (!manager)
724        return 0;
725
726    const QUrl url = m_request.url();
727
728    // Post requests on files and data don't really make sense, but for
729    // fast/forms/form-post-urlencoded.html and for fast/forms/button-state-restore.html
730    // we still need to retrieve the file/data, which means we map it to a Get instead.
731    if (m_method == QNetworkAccessManager::PostOperation
732        && (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data")))
733        m_method = QNetworkAccessManager::GetOperation;
734
735    switch (m_method) {
736        case QNetworkAccessManager::GetOperation:
737            clearContentHeaders();
738            return manager->get(m_request);
739        case QNetworkAccessManager::PostOperation: {
740            FormDataIODevice* postDevice = getIODevice(request);
741            QNetworkReply* result = manager->post(m_request, postDevice);
742            postDevice->setParent(result);
743            return result;
744        }
745        case QNetworkAccessManager::HeadOperation:
746            clearContentHeaders();
747            return manager->head(m_request);
748        case QNetworkAccessManager::PutOperation: {
749            FormDataIODevice* putDevice = getIODevice(request);
750            QNetworkReply* result = manager->put(m_request, putDevice);
751            putDevice->setParent(result);
752            return result;
753        }
754        case QNetworkAccessManager::DeleteOperation: {
755            clearContentHeaders();
756            return manager->deleteResource(m_request);
757        }
758        case QNetworkAccessManager::CustomOperation: {
759            FormDataIODevice* customDevice = getIODevice(request);
760            QNetworkReply* result = manager->sendCustomRequest(m_request, m_resourceHandle->firstRequest().httpMethod().latin1().data(), customDevice);
761            customDevice->setParent(result);
762            return result;
763        }
764        case QNetworkAccessManager::UnknownOperation:
765            ASSERT_NOT_REACHED();
766            return 0;
767    }
768    return 0;
769}
770
771void QNetworkReplyHandler::start()
772{
773    ResourceHandleInternal* d = m_resourceHandle->getInternal();
774    if (!d || !d->m_context)
775        return;
776
777    QNetworkReply* reply = sendNetworkRequest(d->m_context->networkAccessManager(), d->m_firstRequest);
778    if (!reply)
779        return;
780
781    m_replyWrapper = adoptPtr(new QNetworkReplyWrapper(&m_queue, reply, m_resourceHandle->shouldContentSniff() && d->m_context->mimeSniffingEnabled(), this));
782
783    if (m_loadType == SynchronousLoad) {
784        m_replyWrapper->synchronousLoad();
785        // If supported, a synchronous request will be finished at this point, no need to hook up the signals.
786        return;
787    }
788
789    double timeoutInSeconds = d->m_firstRequest.timeoutInterval();
790    if (timeoutInSeconds > 0 && timeoutInSeconds < (INT_MAX / 1000))
791        m_timeoutTimer.start(timeoutInSeconds * 1000, this);
792
793    if (m_resourceHandle->firstRequest().reportUploadProgress())
794        connect(m_replyWrapper->reply(), SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64)));
795}
796
797ResourceError QNetworkReplyHandler::errorForReply(QNetworkReply* reply)
798{
799    QUrl url = reply->url();
800    int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
801
802    if (httpStatusCode)
803        return ResourceError("HTTP", httpStatusCode, url.toString(), reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString());
804
805    return ResourceError("QtNetwork", reply->error(), url.toString(), reply->errorString());
806}
807
808}
809
810#include "moc_QNetworkReplyHandler.cpp"
811