5af0049da49c32dbeed7c1d265b7023793746641
[WebKit.git] / Source / WebCore / platform / network / qt / QNetworkReplyHandler.cpp
1 /*
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
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 "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"
31 #include <QDateTime>
32 #include <QFile>
33 #include <QFileInfo>
34 #include <QNetworkReply>
35 #include <QNetworkCookie>
36 #include <qwebframe.h>
37 #include <qwebpage.h>
38
39 #include <wtf/text/CString.h>
40
41 #include <QDebug>
42 #include <QCoreApplication>
43
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);
47
48 static const int gMaxRedirections = 10;
49
50 namespace WebCore {
51
52 // Take a deep copy of the FormDataElement
53 FormDataIODevice::FormDataIODevice(FormData* data)
54     : m_formElements(data ? data->elements() : Vector<FormDataElement>())
55     , m_currentFile(0)
56     , m_currentDelta(0)
57     , m_fileSize(0)
58     , m_dataSize(0)
59 {
60     setOpenMode(FormDataIODevice::ReadOnly);
61
62     if (!m_formElements.isEmpty() && m_formElements[0].m_type == FormDataElement::encodedFile)
63         openFileForCurrentElement();
64     computeSize();
65 }
66
67 FormDataIODevice::~FormDataIODevice()
68 {
69     delete m_currentFile;
70 }
71
72 qint64 FormDataIODevice::computeSize() 
73 {
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();
78         else {
79             QFileInfo fi(element.m_filename);
80             m_fileSize += fi.size();
81         }
82     }
83     return m_dataSize + m_fileSize;
84 }
85
86 void FormDataIODevice::moveToNextElement()
87 {
88     if (m_currentFile)
89         m_currentFile->close();
90     m_currentDelta = 0;
91
92     m_formElements.remove(0);
93
94     if (m_formElements.isEmpty() || m_formElements[0].m_type == FormDataElement::data)
95         return;
96
97     openFileForCurrentElement();
98 }
99
100 void FormDataIODevice::openFileForCurrentElement()
101 {
102     if (!m_currentFile)
103         m_currentFile = new QFile;
104
105     m_currentFile->setFileName(m_formElements[0].m_filename);
106     m_currentFile->open(QFile::ReadOnly);
107 }
108
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)
112 {
113     if (m_formElements.isEmpty())
114         return -1;
115
116     qint64 copied = 0;
117     while (copied < size && !m_formElements.isEmpty()) {
118         const FormDataElement& element = m_formElements[0];
119         const qint64 available = size-copied;
120
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;
125             copied += toCopy;
126
127             if (m_currentDelta == element.m_data.size())
128                 moveToNextElement();
129         } else {
130             const QByteArray data = m_currentFile->read(available);
131             memcpy(destination+copied, data.constData(), data.size());
132             copied += data.size();
133
134             if (m_currentFile->atEnd() || !m_currentFile->isOpen())
135                 moveToNextElement();
136         }
137     }
138
139     return copied;
140 }
141
142 qint64 FormDataIODevice::writeData(const char*, qint64)
143 {
144     return -1;
145 }
146
147 bool FormDataIODevice::isSequential() const
148 {
149     return true;
150 }
151
152 QNetworkReplyHandlerCallQueue::QNetworkReplyHandlerCallQueue(QNetworkReplyHandler* handler, bool deferSignals)
153     : m_replyHandler(handler)
154     , m_locks(0)
155     , m_deferSignals(deferSignals)
156     , m_flushing(false)
157 {
158     Q_ASSERT(handler);
159 }
160
161 void QNetworkReplyHandlerCallQueue::push(EnqueuedCall method)
162 {
163     m_enqueuedCalls.append(method);
164     flush();
165 }
166
167 void QNetworkReplyHandlerCallQueue::lock()
168 {
169     ++m_locks;
170 }
171
172 void QNetworkReplyHandlerCallQueue::unlock()
173 {
174     if (!m_locks)
175         return;
176
177     --m_locks;
178     flush();
179 }
180
181 void QNetworkReplyHandlerCallQueue::setDeferSignals(bool defer, bool sync)
182 {
183     m_deferSignals = defer;
184     if (sync)
185         flush();
186     else
187         QMetaObject::invokeMethod(this, "flush",  Qt::QueuedConnection);
188 }
189
190 void QNetworkReplyHandlerCallQueue::flush()
191 {
192     if (m_flushing)
193         return;
194
195     m_flushing = true;
196
197     while (!m_deferSignals && !m_locks && !m_enqueuedCalls.isEmpty())
198         (m_replyHandler->*(m_enqueuedCalls.takeFirst()))();
199
200     m_flushing = false;
201 }
202
203 class QueueLocker {
204 public:
205     QueueLocker(QNetworkReplyHandlerCallQueue* queue) : m_queue(queue) { m_queue->lock(); }
206     ~QueueLocker() { m_queue->unlock(); }
207 private:
208     QNetworkReplyHandlerCallQueue* m_queue;
209 };
210
211 QNetworkReplyWrapper::QNetworkReplyWrapper(QNetworkReplyHandlerCallQueue* queue, QNetworkReply* reply, bool sniffMIMETypes, QObject* parent)
212     : QObject(parent)
213     , m_reply(reply)
214     , m_queue(queue)
215     , m_responseContainsData(false)
216     , m_sniffMIMETypes(sniffMIMETypes)
217 {
218     Q_ASSERT(m_reply);
219
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()));
224 }
225
226 QNetworkReplyWrapper::~QNetworkReplyWrapper()
227 {
228     if (m_reply)
229         m_reply->deleteLater();
230     m_queue->clear();
231 }
232
233 QNetworkReply* QNetworkReplyWrapper::release()
234 {
235     if (!m_reply)
236         return 0;
237
238     resetConnections();
239     QNetworkReply* reply = m_reply;
240     m_reply = 0;
241     m_sniffer = nullptr;
242
243     reply->setParent(0);
244     return reply;
245 }
246
247 void QNetworkReplyWrapper::synchronousLoad()
248 {
249     setFinished();
250     receiveMetaData();
251 }
252
253 void QNetworkReplyWrapper::resetConnections()
254 {
255     if (m_reply) {
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()));
260     }
261     QCoreApplication::removePostedEvents(this, QEvent::MetaCall);
262 }
263
264 void QNetworkReplyWrapper::receiveMetaData()
265 {
266     // This slot is only used to receive the first signal from the QNetworkReply object.
267     resetConnections();
268
269
270     WTF::String contentType = m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
271     m_encoding = extractCharsetFromMediaType(contentType);
272     m_advertisedMIMEType = extractMIMETypeFromMediaType(contentType);
273
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);
279         return;
280     }
281
282     if (!m_sniffMIMETypes) {
283         emitMetaDataChanged();
284         return;
285     }
286
287     bool isSupportedImageType = MIMETypeRegistry::isSupportedImageMIMEType(m_advertisedMIMEType);
288
289     Q_ASSERT(!m_sniffer);
290
291     m_sniffer = adoptPtr(new QtMIMETypeSniffer(m_reply, m_advertisedMIMEType, isSupportedImageType));
292
293     if (m_sniffer->isFinished()) {
294         receiveSniffedMIMEType();
295         return;
296     }
297
298     connect(m_sniffer.get(), SIGNAL(finished()), this, SLOT(receiveSniffedMIMEType()));
299 }
300
301 void QNetworkReplyWrapper::receiveSniffedMIMEType()
302 {
303     Q_ASSERT(m_sniffer);
304
305     m_sniffedMIMEType = m_sniffer->mimeType();
306     m_sniffer = nullptr;
307
308     emitMetaDataChanged();
309 }
310
311 void QNetworkReplyWrapper::setFinished()
312 {
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);
319 }
320
321 void QNetworkReplyWrapper::emitMetaDataChanged()
322 {
323     QueueLocker lock(m_queue);
324     m_queue->push(&QNetworkReplyHandler::sendResponseIfNeeded);
325
326     if (m_reply->bytesAvailable()) {
327         m_responseContainsData = true;
328         m_queue->push(&QNetworkReplyHandler::forwardData);
329     }
330
331     if (isFinished()) {
332         m_queue->push(&QNetworkReplyHandler::finish);
333         return;
334     }
335
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()));
339 }
340
341 void QNetworkReplyWrapper::didReceiveReadyRead()
342 {
343     if (m_reply->bytesAvailable())
344         m_responseContainsData = true;
345     m_queue->push(&QNetworkReplyHandler::forwardData);
346 }
347
348 void QNetworkReplyWrapper::didReceiveFinished()
349 {
350     // Disconnecting will make sure that nothing will happen after emitting the finished signal.
351     resetConnections();
352     m_queue->push(&QNetworkReplyHandler::finish);
353 }
354
355 String QNetworkReplyHandler::httpMethod() const
356 {
357     switch (m_method) {
358     case QNetworkAccessManager::GetOperation:
359         return "GET";
360     case QNetworkAccessManager::HeadOperation:
361         return "HEAD";
362     case QNetworkAccessManager::PostOperation:
363         return "POST";
364     case QNetworkAccessManager::PutOperation:
365         return "PUT";
366     case QNetworkAccessManager::DeleteOperation:
367         return "DELETE";
368     case QNetworkAccessManager::CustomOperation:
369         return m_resourceHandle->firstRequest().httpMethod();
370     default:
371         ASSERT_NOT_REACHED();
372         return "GET";
373     }
374 }
375
376 QNetworkReplyHandler::QNetworkReplyHandler(ResourceHandle* handle, LoadType loadType, bool deferred)
377     : QObject(0)
378     , m_resourceHandle(handle)
379     , m_loadType(loadType)
380     , m_redirectionTries(gMaxRedirections)
381     , m_queue(this, deferred)
382 {
383     const ResourceRequest &r = m_resourceHandle->firstRequest();
384
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;
395     else
396         m_method = QNetworkAccessManager::CustomOperation;
397
398     QObject* originatingObject = 0;
399     if (m_resourceHandle->getInternal()->m_context)
400         originatingObject = m_resourceHandle->getInternal()->m_context->originatingObject();
401
402     m_request = r.toNetworkRequest(originatingObject);
403
404     m_queue.push(&QNetworkReplyHandler::start);
405 }
406
407 void QNetworkReplyHandler::abort()
408 {
409     m_resourceHandle = 0;
410     if (QNetworkReply* reply = release()) {
411         reply->abort();
412         reply->deleteLater();
413     }
414     deleteLater();
415 }
416
417 QNetworkReply* QNetworkReplyHandler::release()
418 {
419     if (!m_replyWrapper)
420         return 0;
421
422     QNetworkReply* reply = m_replyWrapper->release();
423     m_replyWrapper = nullptr;
424     return reply;
425 }
426
427 static bool shouldIgnoreHttpError(QNetworkReply* reply, bool receivedData)
428 {
429     int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
430
431     if (httpStatusCode == 401 || httpStatusCode == 407)
432         return true;
433
434     if (receivedData && (httpStatusCode >= 400 && httpStatusCode < 600))
435         return true;
436
437     return false;
438 }
439
440 void QNetworkReplyHandler::finish()
441 {
442     ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted());
443
444     ResourceHandleClient* client = m_resourceHandle->client();
445     if (!client) {
446         m_replyWrapper = nullptr;
447         return;
448     }
449
450     if (m_replyWrapper->wasRedirected()) {
451         m_replyWrapper = nullptr;
452         m_queue.push(&QNetworkReplyHandler::start);
453         return;
454     }
455
456     if (!m_replyWrapper->reply()->error() || shouldIgnoreHttpError(m_replyWrapper->reply(), m_replyWrapper->responseContainsData()))
457         client->didFinishLoading(m_resourceHandle, 0);
458     else
459         client->didFail(m_resourceHandle, errorForReply(m_replyWrapper->reply()));
460
461     m_replyWrapper = nullptr;
462 }
463
464 void QNetworkReplyHandler::sendResponseIfNeeded()
465 {
466     ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted());
467
468     if (m_replyWrapper->reply()->error() && m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).isNull())
469         return;
470
471     ResourceHandleClient* client = m_resourceHandle->client();
472     if (!client)
473         return;
474
475     WTF::String mimeType = m_replyWrapper->mimeType();
476
477     if (mimeType.isEmpty()) {
478         // let's try to guess from the extension
479         mimeType = MIMETypeRegistry::getMIMETypeForPath(m_replyWrapper->reply()->url().path());
480     }
481
482     KURL url(m_replyWrapper->reply()->url());
483     ResourceResponse response(url, mimeType.lower(),
484                               m_replyWrapper->reply()->header(QNetworkRequest::ContentLengthHeader).toLongLong(),
485                               m_replyWrapper->encoding(), String());
486
487     if (url.isLocalFile()) {
488         client->didReceiveResponse(m_resourceHandle, response);
489         return;
490     }
491
492     // The status code is equal to 0 for protocols not in the HTTP family.
493     int statusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
494
495     if (url.protocolInHTTPFamily()) {
496         String suggestedFilename = filenameFromHTTPContentDisposition(QString::fromLatin1(m_replyWrapper->reply()->rawHeader("Content-Disposition")));
497
498         if (!suggestedFilename.isEmpty())
499             response.setSuggestedFilename(suggestedFilename);
500         else
501             response.setSuggestedFilename(url.lastPathComponent());
502
503         response.setHTTPStatusCode(statusCode);
504         response.setHTTPStatusText(m_replyWrapper->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toByteArray().constData());
505
506         // Add remaining headers.
507         foreach (const QNetworkReply::RawHeaderPair& pair, m_replyWrapper->reply()->rawHeaderPairs())
508             response.setHTTPHeaderField(QString::fromLatin1(pair.first), QString::fromLatin1(pair.second));
509     }
510
511     QUrl redirection = m_replyWrapper->reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
512     if (redirection.isValid()) {
513         redirect(response, redirection);
514         return;
515     }
516
517     client->didReceiveResponse(m_resourceHandle, response);
518 }
519
520 void QNetworkReplyHandler::redirect(ResourceResponse& response, const QUrl& redirection)
521 {
522     QUrl newUrl = m_replyWrapper->reply()->url().resolved(redirection);
523
524     ResourceHandleClient* client = m_resourceHandle->client();
525     ASSERT(client);
526
527     int statusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
528
529     m_redirectionTries--;
530     if (!m_redirectionTries) {
531         ResourceError error(newUrl.host(), 400 /*bad request*/,
532                             newUrl.toString(),
533                             QCoreApplication::translate("QWebPage", "Redirection limit reached"));
534         client->didFail(m_resourceHandle, error);
535         m_replyWrapper = nullptr;
536         return;
537     }
538
539     //  Status Code 301 (Moved Permanently), 302 (Moved Temporarily), 303 (See Other):
540     //    - If original request is POST convert to GET and redirect automatically
541     //  Status Code 307 (Temporary Redirect) and all other redirect status codes:
542     //    - Use the HTTP method from the previous request
543     if ((statusCode >= 301 && statusCode <= 303) && m_resourceHandle->firstRequest().httpMethod() == "POST")
544         m_method = QNetworkAccessManager::GetOperation;
545
546     ResourceRequest newRequest = m_resourceHandle->firstRequest();
547     newRequest.setHTTPMethod(httpMethod());
548     newRequest.setURL(newUrl);
549
550     // Should not set Referer after a redirect from a secure resource to non-secure one.
551     if (!newRequest.url().protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https"))
552         newRequest.clearHTTPReferrer();
553
554     client->willSendRequest(m_resourceHandle, newRequest, response);
555     if (wasAborted()) // Network error cancelled the request.
556         return;
557
558     QObject* originatingObject = 0;
559     if (m_resourceHandle->getInternal()->m_context)
560         originatingObject = m_resourceHandle->getInternal()->m_context->originatingObject();
561
562     m_request = newRequest.toNetworkRequest(originatingObject);
563 }
564
565 void QNetworkReplyHandler::forwardData()
566 {
567     ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted() && !m_replyWrapper->wasRedirected());
568
569     QByteArray data = m_replyWrapper->reply()->read(m_replyWrapper->reply()->bytesAvailable());
570
571     ResourceHandleClient* client = m_resourceHandle->client();
572     if (!client)
573         return;
574
575     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793
576     // -1 means we do not provide any data about transfer size to inspector so it would use
577     // Content-Length headers or content size to show transfer size.
578     if (!data.isEmpty())
579         client->didReceiveData(m_resourceHandle, data.constData(), data.length(), -1);
580 }
581
582 void QNetworkReplyHandler::uploadProgress(qint64 bytesSent, qint64 bytesTotal)
583 {
584     if (wasAborted())
585         return;
586
587     ResourceHandleClient* client = m_resourceHandle->client();
588     if (!client)
589         return;
590
591     client->didSendData(m_resourceHandle, bytesSent, bytesTotal);
592 }
593
594 void QNetworkReplyHandler::clearContentHeaders()
595 {
596     // Clearing Content-length and Content-type of the requests that do not have contents.
597     // This is necessary to ensure POST requests redirected to GETs do not leak metadata
598     // about the POST content to the site they've been redirected to.
599     m_request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant());
600     m_request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant());
601 }
602
603 FormDataIODevice* QNetworkReplyHandler::getIODevice(const ResourceRequest& request)
604 {
605     FormDataIODevice* device = new FormDataIODevice(request.httpBody());
606     // We may be uploading files so prevent QNR from buffering data.
607     m_request.setHeader(QNetworkRequest::ContentLengthHeader, device->getFormDataSize());
608     m_request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, QVariant(true));
609     return device;
610 }
611
612 QNetworkReply* QNetworkReplyHandler::sendNetworkRequest(QNetworkAccessManager* manager, const ResourceRequest& request)
613 {
614     if (m_loadType == SynchronousLoad)
615         m_request.setAttribute(gSynchronousNetworkRequestAttribute, true);
616
617     if (!manager)
618         return 0;
619
620     const QUrl url = m_request.url();
621     const QString scheme = url.scheme();
622     // Post requests on files and data don't really make sense, but for
623     // fast/forms/form-post-urlencoded.html and for fast/forms/button-state-restore.html
624     // we still need to retrieve the file/data, which means we map it to a Get instead.
625     if (m_method == QNetworkAccessManager::PostOperation
626         && (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data")))
627         m_method = QNetworkAccessManager::GetOperation;
628
629     switch (m_method) {
630         case QNetworkAccessManager::GetOperation:
631             clearContentHeaders();
632             return manager->get(m_request);
633         case QNetworkAccessManager::PostOperation: {
634             FormDataIODevice* postDevice = getIODevice(request);
635             QNetworkReply* result = manager->post(m_request, postDevice);
636             postDevice->setParent(result);
637             return result;
638         }
639         case QNetworkAccessManager::HeadOperation:
640             clearContentHeaders();
641             return manager->head(m_request);
642         case QNetworkAccessManager::PutOperation: {
643             FormDataIODevice* putDevice = getIODevice(request);
644             QNetworkReply* result = manager->put(m_request, putDevice);
645             putDevice->setParent(result);
646             return result;
647         }
648         case QNetworkAccessManager::DeleteOperation: {
649             clearContentHeaders();
650             return manager->deleteResource(m_request);
651         }
652         case QNetworkAccessManager::CustomOperation: {
653             FormDataIODevice* customDevice = getIODevice(request);
654             QNetworkReply* result = manager->sendCustomRequest(m_request, m_resourceHandle->firstRequest().httpMethod().latin1().data(), customDevice);
655             customDevice->setParent(result);
656             return result;
657         }
658         case QNetworkAccessManager::UnknownOperation:
659             ASSERT_NOT_REACHED();
660             return 0;
661     }
662     return 0;
663 }
664
665 void QNetworkReplyHandler::start()
666 {
667     ResourceHandleInternal* d = m_resourceHandle->getInternal();
668     if (!d || !d->m_context)
669         return;
670
671     QNetworkReply* reply = sendNetworkRequest(d->m_context->networkAccessManager(), d->m_firstRequest);
672     if (!reply)
673         return;
674
675     m_replyWrapper = adoptPtr(new QNetworkReplyWrapper(&m_queue, reply, m_resourceHandle->shouldContentSniff() && d->m_context->mimeSniffingEnabled(), this));
676
677     if (m_loadType == SynchronousLoad) {
678         m_replyWrapper->synchronousLoad();
679         // If supported, a synchronous request will be finished at this point, no need to hook up the signals.
680         return;
681     }
682
683     if (m_resourceHandle->firstRequest().reportUploadProgress())
684         connect(m_replyWrapper->reply(), SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64)));
685 }
686
687 ResourceError QNetworkReplyHandler::errorForReply(QNetworkReply* reply)
688 {
689     QUrl url = reply->url();
690     int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
691
692     if (httpStatusCode)
693         return ResourceError("HTTP", httpStatusCode, url.toString(), reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString());
694
695     return ResourceError("QtNetwork", reply->error(), url.toString(), reply->errorString());
696 }
697
698 }
699
700 #include "moc_QNetworkReplyHandler.cpp"