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