1 /* 2 Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 3 Copyright (C) 2007 Staikos Computing Services Inc. <info (at) 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) 182 { 183 m_deferSignals = defer; 184 flush(); 185 } 186 187 void QNetworkReplyHandlerCallQueue::flush() 188 { 189 if (m_flushing) 190 return; 191 192 m_flushing = true; 193 194 while (!m_deferSignals && !m_locks && !m_enqueuedCalls.isEmpty()) 195 (m_replyHandler->*(m_enqueuedCalls.takeFirst()))(); 196 197 m_flushing = false; 198 } 199 200 class QueueLocker { 201 public: 202 QueueLocker(QNetworkReplyHandlerCallQueue* queue) : m_queue(queue) { m_queue->lock(); } 203 ~QueueLocker() { m_queue->unlock(); } 204 private: 205 QNetworkReplyHandlerCallQueue* m_queue; 206 }; 207 208 QNetworkReplyWrapper::QNetworkReplyWrapper(QNetworkReplyHandlerCallQueue* queue, QNetworkReply* reply, bool sniffMIMETypes, QObject* parent) 209 : QObject(parent) 210 , m_reply(reply) 211 , m_queue(queue) 212 , m_responseContainsData(false) 213 , m_sniffer(0) 214 , m_sniffMIMETypes(sniffMIMETypes) 215 { 216 Q_ASSERT(m_reply); 217 218 connect(m_reply, SIGNAL(readyRead()), this, SLOT(receiveMetaData())); 219 connect(m_reply, SIGNAL(finished()), this, SLOT(receiveMetaData())); 220 } 221 222 QNetworkReplyWrapper::~QNetworkReplyWrapper() 223 { 224 if (m_reply) 225 m_reply->deleteLater(); 226 m_queue->clear(); 227 } 228 229 QNetworkReply* QNetworkReplyWrapper::release() 230 { 231 if (!m_reply) 232 return 0; 233 234 resetConnections(); 235 QNetworkReply* reply = m_reply; 236 m_reply = 0; 237 m_sniffer = 0; 238 239 reply->setParent(0); 240 return reply; 241 } 242 243 void QNetworkReplyWrapper::resetConnections() 244 { 245 if (m_reply) 246 m_reply->disconnect(this); 247 QCoreApplication::removePostedEvents(this, QEvent::MetaCall); 248 } 249 250 void QNetworkReplyWrapper::receiveMetaData() 251 { 252 // This slot is only used to receive the first signal from the QNetworkReply object. 253 resetConnections(); 254 255 256 WTF::String contentType = m_reply->header(QNetworkRequest::ContentTypeHeader).toString(); 257 m_encoding = extractCharsetFromMediaType(contentType); 258 m_advertisedMIMEType = extractMIMETypeFromMediaType(contentType); 259 260 m_redirectionTargetUrl = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 261 if (m_redirectionTargetUrl.isValid()) { 262 QueueLocker lock(m_queue); 263 m_queue->push(&QNetworkReplyHandler::sendResponseIfNeeded); 264 m_queue->push(&QNetworkReplyHandler::finish); 265 return; 266 } 267 268 if (!m_sniffMIMETypes) { 269 emitMetaDataChanged(); 270 return; 271 } 272 273 bool isSupportedImageType = MIMETypeRegistry::isSupportedImageMIMEType(m_advertisedMIMEType); 274 275 Q_ASSERT(!m_sniffer); 276 277 m_sniffer = new QtMIMETypeSniffer(m_reply, m_advertisedMIMEType, isSupportedImageType); 278 279 if (m_sniffer->isFinished()) { 280 receiveSniffedMIMEType(); 281 return; 282 } 283 284 connect(m_sniffer.get(), SIGNAL(finished()), this, SLOT(receiveSniffedMIMEType())); 285 } 286 287 void QNetworkReplyWrapper::receiveSniffedMIMEType() 288 { 289 Q_ASSERT(m_sniffer); 290 291 m_sniffedMIMEType = m_sniffer->mimeType(); 292 m_sniffer = 0; 293 294 emitMetaDataChanged(); 295 } 296 297 void QNetworkReplyWrapper::emitMetaDataChanged() 298 { 299 QueueLocker lock(m_queue); 300 m_queue->push(&QNetworkReplyHandler::sendResponseIfNeeded); 301 302 if (m_reply->bytesAvailable()) { 303 m_responseContainsData = true; 304 m_queue->push(&QNetworkReplyHandler::forwardData); 305 } 306 307 if (m_reply->isFinished()) { 308 m_queue->push(&QNetworkReplyHandler::finish); 309 return; 310 } 311 312 // If not finished, connect to the slots that will be used from this point on. 313 connect(m_reply, SIGNAL(readyRead()), this, SLOT(didReceiveReadyRead())); 314 connect(m_reply, SIGNAL(finished()), this, SLOT(didReceiveFinished())); 315 } 316 317 void QNetworkReplyWrapper::didReceiveReadyRead() 318 { 319 if (m_reply->bytesAvailable()) 320 m_responseContainsData = true; 321 m_queue->push(&QNetworkReplyHandler::forwardData); 322 } 323 324 void QNetworkReplyWrapper::didReceiveFinished() 325 { 326 // Disconnecting will make sure that nothing will happen after emitting the finished signal. 327 resetConnections(); 328 m_queue->push(&QNetworkReplyHandler::finish); 329 } 330 331 String QNetworkReplyHandler::httpMethod() const 332 { 333 switch (m_method) { 334 case QNetworkAccessManager::GetOperation: 335 return "GET"; 336 case QNetworkAccessManager::HeadOperation: 337 return "HEAD"; 338 case QNetworkAccessManager::PostOperation: 339 return "POST"; 340 case QNetworkAccessManager::PutOperation: 341 return "PUT"; 342 case QNetworkAccessManager::DeleteOperation: 343 return "DELETE"; 344 case QNetworkAccessManager::CustomOperation: 345 return m_resourceHandle->firstRequest().httpMethod(); 346 default: 347 ASSERT_NOT_REACHED(); 348 return "GET"; 349 } 350 } 351 352 QNetworkReplyHandler::QNetworkReplyHandler(ResourceHandle* handle, LoadType loadType, bool deferred) 353 : QObject(0) 354 , m_replyWrapper(0) 355 , m_resourceHandle(handle) 356 , m_loadType(loadType) 357 , m_redirectionTries(gMaxRedirections) 358 , m_queue(this, deferred) 359 { 360 const ResourceRequest &r = m_resourceHandle->firstRequest(); 361 362 if (r.httpMethod() == "GET") 363 m_method = QNetworkAccessManager::GetOperation; 364 else if (r.httpMethod() == "HEAD") 365 m_method = QNetworkAccessManager::HeadOperation; 366 else if (r.httpMethod() == "POST") 367 m_method = QNetworkAccessManager::PostOperation; 368 else if (r.httpMethod() == "PUT") 369 m_method = QNetworkAccessManager::PutOperation; 370 else if (r.httpMethod() == "DELETE") 371 m_method = QNetworkAccessManager::DeleteOperation; 372 else 373 m_method = QNetworkAccessManager::CustomOperation; 374 375 QObject* originatingObject = 0; 376 if (m_resourceHandle->getInternal()->m_context) 377 originatingObject = m_resourceHandle->getInternal()->m_context->originatingObject(); 378 379 m_request = r.toNetworkRequest(originatingObject); 380 381 m_queue.push(&QNetworkReplyHandler::start); 382 } 383 384 void QNetworkReplyHandler::abort() 385 { 386 m_resourceHandle = 0; 387 if (QNetworkReply* reply = release()) { 388 reply->abort(); 389 reply->deleteLater(); 390 } 391 deleteLater(); 392 } 393 394 QNetworkReply* QNetworkReplyHandler::release() 395 { 396 if (!m_replyWrapper) 397 return 0; 398 399 QNetworkReply* reply = m_replyWrapper->release(); 400 m_replyWrapper = 0; 401 return reply; 402 } 403 404 static bool shouldIgnoreHttpError(QNetworkReply* reply, bool receivedData) 405 { 406 int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 407 408 if (httpStatusCode == 401 || httpStatusCode == 407) 409 return true; 410 411 if (receivedData && (httpStatusCode >= 400 && httpStatusCode < 600)) 412 return true; 413 414 return false; 415 } 416 417 void QNetworkReplyHandler::finish() 418 { 419 ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted()); 420 421 ResourceHandleClient* client = m_resourceHandle->client(); 422 if (!client) { 423 m_replyWrapper = 0; 424 return; 425 } 426 427 if (m_replyWrapper->wasRedirected()) { 428 m_replyWrapper = 0; 429 m_queue.push(&QNetworkReplyHandler::start); 430 return; 431 } 432 433 if (!m_replyWrapper->reply()->error() || shouldIgnoreHttpError(m_replyWrapper->reply(), m_replyWrapper->responseContainsData())) 434 client->didFinishLoading(m_resourceHandle, 0); 435 else { 436 QUrl url = m_replyWrapper->reply()->url(); 437 int httpStatusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 438 439 if (httpStatusCode) { 440 ResourceError error("HTTP", httpStatusCode, url.toString(), m_replyWrapper->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()); 441 client->didFail(m_resourceHandle, error); 442 } else { 443 ResourceError error("QtNetwork", m_replyWrapper->reply()->error(), url.toString(), m_replyWrapper->reply()->errorString()); 444 client->didFail(m_resourceHandle, error); 445 } 446 } 447 448 m_replyWrapper = 0; 449 } 450 451 void QNetworkReplyHandler::sendResponseIfNeeded() 452 { 453 ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted()); 454 455 if (m_replyWrapper->reply()->error() && m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).isNull()) 456 return; 457 458 ResourceHandleClient* client = m_resourceHandle->client(); 459 if (!client) 460 return; 461 462 WTF::String mimeType = m_replyWrapper->mimeType(); 463 464 if (mimeType.isEmpty()) { 465 // let's try to guess from the extension 466 mimeType = MIMETypeRegistry::getMIMETypeForPath(m_replyWrapper->reply()->url().path()); 467 } 468 469 KURL url(m_replyWrapper->reply()->url()); 470 ResourceResponse response(url, mimeType.lower(), 471 m_replyWrapper->reply()->header(QNetworkRequest::ContentLengthHeader).toLongLong(), 472 m_replyWrapper->encoding(), String()); 473 474 if (url.isLocalFile()) { 475 client->didReceiveResponse(m_resourceHandle, response); 476 return; 477 } 478 479 // The status code is equal to 0 for protocols not in the HTTP family. 480 int statusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 481 482 if (url.protocolInHTTPFamily()) { 483 String suggestedFilename = filenameFromHTTPContentDisposition(QString::fromLatin1(m_replyWrapper->reply()->rawHeader("Content-Disposition"))); 484 485 if (!suggestedFilename.isEmpty()) 486 response.setSuggestedFilename(suggestedFilename); 487 else 488 response.setSuggestedFilename(url.lastPathComponent()); 489 490 response.setHTTPStatusCode(statusCode); 491 response.setHTTPStatusText(m_replyWrapper->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toByteArray().constData()); 492 493 // Add remaining headers. 494 foreach (const QNetworkReply::RawHeaderPair& pair, m_replyWrapper->reply()->rawHeaderPairs()) 495 response.setHTTPHeaderField(QString::fromLatin1(pair.first), QString::fromLatin1(pair.second)); 496 } 497 498 QUrl redirection = m_replyWrapper->reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 499 if (redirection.isValid()) { 500 redirect(response, redirection); 501 return; 502 } 503 504 client->didReceiveResponse(m_resourceHandle, response); 505 } 506 507 void QNetworkReplyHandler::redirect(ResourceResponse& response, const QUrl& redirection) 508 { 509 QUrl newUrl = m_replyWrapper->reply()->url().resolved(redirection); 510 511 ResourceHandleClient* client = m_resourceHandle->client(); 512 ASSERT(client); 513 514 int statusCode = m_replyWrapper->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 515 516 m_redirectionTries--; 517 if (!m_redirectionTries) { 518 ResourceError error(newUrl.host(), 400 /*bad request*/, 519 newUrl.toString(), 520 QCoreApplication::translate("QWebPage", "Redirection limit reached")); 521 client->didFail(m_resourceHandle, error); 522 m_replyWrapper = 0; 523 return; 524 } 525 526 // Status Code 301 (Moved Permanently), 302 (Moved Temporarily), 303 (See Other): 527 // - If original request is POST convert to GET and redirect automatically 528 // Status Code 307 (Temporary Redirect) and all other redirect status codes: 529 // - Use the HTTP method from the previous request 530 if ((statusCode >= 301 && statusCode <= 303) && m_resourceHandle->firstRequest().httpMethod() == "POST") 531 m_method = QNetworkAccessManager::GetOperation; 532 533 ResourceRequest newRequest = m_resourceHandle->firstRequest(); 534 newRequest.setHTTPMethod(httpMethod()); 535 newRequest.setURL(newUrl); 536 537 // Should not set Referer after a redirect from a secure resource to non-secure one. 538 if (!newRequest.url().protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https")) 539 newRequest.clearHTTPReferrer(); 540 541 client->willSendRequest(m_resourceHandle, newRequest, response); 542 if (wasAborted()) // Network error cancelled the request. 543 return; 544 545 QObject* originatingObject = 0; 546 if (m_resourceHandle->getInternal()->m_context) 547 originatingObject = m_resourceHandle->getInternal()->m_context->originatingObject(); 548 549 m_request = newRequest.toNetworkRequest(originatingObject); 550 } 551 552 void QNetworkReplyHandler::forwardData() 553 { 554 ASSERT(m_replyWrapper && m_replyWrapper->reply() && !wasAborted() && !m_replyWrapper->wasRedirected()); 555 556 QByteArray data = m_replyWrapper->reply()->read(m_replyWrapper->reply()->bytesAvailable()); 557 558 ResourceHandleClient* client = m_resourceHandle->client(); 559 if (!client) 560 return; 561 562 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793 563 // -1 means we do not provide any data about transfer size to inspector so it would use 564 // Content-Length headers or content size to show transfer size. 565 if (!data.isEmpty()) 566 client->didReceiveData(m_resourceHandle, data.constData(), data.length(), -1); 567 } 568 569 void QNetworkReplyHandler::uploadProgress(qint64 bytesSent, qint64 bytesTotal) 570 { 571 if (wasAborted()) 572 return; 573 574 ResourceHandleClient* client = m_resourceHandle->client(); 575 if (!client) 576 return; 577 578 client->didSendData(m_resourceHandle, bytesSent, bytesTotal); 579 } 580 581 QNetworkReply* QNetworkReplyHandler::sendNetworkRequest(QNetworkAccessManager* manager, const ResourceRequest& request) 582 { 583 if (m_loadType == SynchronousLoad) 584 m_request.setAttribute(gSynchronousNetworkRequestAttribute, true); 585 586 if (!manager) 587 return 0; 588 589 const QUrl url = m_request.url(); 590 const QString scheme = url.scheme(); 591 // Post requests on files and data don't really make sense, but for 592 // fast/forms/form-post-urlencoded.html and for fast/forms/button-state-restore.html 593 // we still need to retrieve the file/data, which means we map it to a Get instead. 594 if (m_method == QNetworkAccessManager::PostOperation 595 && (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data"))) 596 m_method = QNetworkAccessManager::GetOperation; 597 598 switch (m_method) { 599 case QNetworkAccessManager::GetOperation: 600 return manager->get(m_request); 601 case QNetworkAccessManager::PostOperation: { 602 FormDataIODevice* postDevice = new FormDataIODevice(request.httpBody()); 603 // We may be uploading files so prevent QNR from buffering data 604 m_request.setHeader(QNetworkRequest::ContentLengthHeader, postDevice->getFormDataSize()); 605 m_request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, QVariant(true)); 606 QNetworkReply* result = manager->post(m_request, postDevice); 607 postDevice->setParent(result); 608 return result; 609 } 610 case QNetworkAccessManager::HeadOperation: 611 return manager->head(m_request); 612 case QNetworkAccessManager::PutOperation: { 613 FormDataIODevice* putDevice = new FormDataIODevice(request.httpBody()); 614 // We may be uploading files so prevent QNR from buffering data 615 m_request.setHeader(QNetworkRequest::ContentLengthHeader, putDevice->getFormDataSize()); 616 m_request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, QVariant(true)); 617 QNetworkReply* result = manager->put(m_request, putDevice); 618 putDevice->setParent(result); 619 return result; 620 } 621 case QNetworkAccessManager::DeleteOperation: { 622 return manager->deleteResource(m_request); 623 } 624 case QNetworkAccessManager::CustomOperation: 625 return manager->sendCustomRequest(m_request, m_resourceHandle->firstRequest().httpMethod().latin1().data()); 626 case QNetworkAccessManager::UnknownOperation: 627 ASSERT_NOT_REACHED(); 628 return 0; 629 } 630 return 0; 631 } 632 633 void QNetworkReplyHandler::start() 634 { 635 ResourceHandleInternal* d = m_resourceHandle->getInternal(); 636 if (!d || !d->m_context) 637 return; 638 639 QNetworkReply* reply = sendNetworkRequest(d->m_context->networkAccessManager(), d->m_firstRequest); 640 if (!reply) 641 return; 642 643 m_replyWrapper = new QNetworkReplyWrapper(&m_queue, reply, m_resourceHandle->shouldContentSniff() && d->m_context->mimeSniffingEnabled(), this); 644 645 if (m_loadType == SynchronousLoad && m_replyWrapper->reply()->isFinished()) { 646 m_replyWrapper->synchronousLoad(); 647 // If supported, a synchronous request will be finished at this point, no need to hook up the signals. 648 return; 649 } 650 651 if (m_resourceHandle->firstRequest().reportUploadProgress()) 652 connect(m_replyWrapper->reply(), SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64))); 653 } 654 655 } 656 657 #include "moc_QNetworkReplyHandler.cpp" 658