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 <QNetworkReply> 34 #include <QNetworkCookie> 35 #include <qwebframe.h> 36 #include <qwebpage.h> 37 38 #include <QDebug> 39 #include <QCoreApplication> 40 41 // What type of connection should be used for the signals of the 42 // QNetworkReply? This depends on if Qt has a bugfix for this or not. 43 // It is fixed in Qt 4.6.1. See https://bugs.webkit.org/show_bug.cgi?id=32113 44 #if QT_VERSION > QT_VERSION_CHECK(4, 6, 0) 45 #define SIGNAL_CONN Qt::DirectConnection 46 #else 47 #define SIGNAL_CONN Qt::QueuedConnection 48 #endif 49 50 51 namespace WebCore { 52 53 // Take a deep copy of the FormDataElement 54 FormDataIODevice::FormDataIODevice(FormData* data) 55 : m_formElements(data ? data->elements() : Vector<FormDataElement>()) 56 , m_currentFile(0) 57 , m_currentDelta(0) 58 { 59 setOpenMode(FormDataIODevice::ReadOnly); 60 } 61 62 FormDataIODevice::~FormDataIODevice() 63 { 64 delete m_currentFile; 65 } 66 67 void FormDataIODevice::moveToNextElement() 68 { 69 if (m_currentFile) 70 m_currentFile->close(); 71 m_currentDelta = 0; 72 73 m_formElements.remove(0); 74 75 if (m_formElements.isEmpty() || m_formElements[0].m_type == FormDataElement::data) 76 return; 77 78 if (!m_currentFile) 79 m_currentFile = new QFile; 80 81 m_currentFile->setFileName(m_formElements[0].m_filename); 82 m_currentFile->open(QFile::ReadOnly); 83 } 84 85 // m_formElements[0] is the current item. If the destination buffer is 86 // big enough we are going to read from more than one FormDataElement 87 qint64 FormDataIODevice::readData(char* destination, qint64 size) 88 { 89 if (m_formElements.isEmpty()) 90 return -1; 91 92 qint64 copied = 0; 93 while (copied < size && !m_formElements.isEmpty()) { 94 const FormDataElement& element = m_formElements[0]; 95 const qint64 available = size-copied; 96 97 if (element.m_type == FormDataElement::data) { 98 const qint64 toCopy = qMin<qint64>(available, element.m_data.size() - m_currentDelta); 99 memcpy(destination+copied, element.m_data.data()+m_currentDelta, toCopy); 100 m_currentDelta += toCopy; 101 copied += toCopy; 102 103 if (m_currentDelta == element.m_data.size()) 104 moveToNextElement(); 105 } else { 106 const QByteArray data = m_currentFile->read(available); 107 memcpy(destination+copied, data.constData(), data.size()); 108 copied += data.size(); 109 110 if (m_currentFile->atEnd() || !m_currentFile->isOpen()) 111 moveToNextElement(); 112 } 113 } 114 115 return copied; 116 } 117 118 qint64 FormDataIODevice::writeData(const char*, qint64) 119 { 120 return -1; 121 } 122 123 bool FormDataIODevice::isSequential() const 124 { 125 return true; 126 } 127 128 QNetworkReplyHandler::QNetworkReplyHandler(ResourceHandle* handle, LoadMode loadMode) 129 : QObject(0) 130 , m_reply(0) 131 , m_resourceHandle(handle) 132 , m_redirected(false) 133 , m_responseSent(false) 134 , m_responseDataSent(false) 135 , m_loadMode(loadMode) 136 , m_shouldStart(true) 137 , m_shouldFinish(false) 138 , m_shouldSendResponse(false) 139 , m_shouldForwardData(false) 140 { 141 const ResourceRequest &r = m_resourceHandle->request(); 142 143 if (r.httpMethod() == "GET") 144 m_method = QNetworkAccessManager::GetOperation; 145 else if (r.httpMethod() == "HEAD") 146 m_method = QNetworkAccessManager::HeadOperation; 147 else if (r.httpMethod() == "POST") 148 m_method = QNetworkAccessManager::PostOperation; 149 else if (r.httpMethod() == "PUT") 150 m_method = QNetworkAccessManager::PutOperation; 151 #if QT_VERSION >= 0x040600 152 else if (r.httpMethod() == "DELETE") 153 m_method = QNetworkAccessManager::DeleteOperation; 154 #endif 155 else 156 m_method = QNetworkAccessManager::UnknownOperation; 157 158 m_request = r.toNetworkRequest(m_resourceHandle->getInternal()->m_frame); 159 160 if (m_loadMode == LoadNormal) 161 start(); 162 } 163 164 void QNetworkReplyHandler::setLoadMode(LoadMode mode) 165 { 166 // https://bugs.webkit.org/show_bug.cgi?id=26556 167 // We cannot call sendQueuedItems() from here, because the signal that 168 // caused us to get into deferred mode, might not be processed yet. 169 switch (mode) { 170 case LoadNormal: 171 m_loadMode = LoadResuming; 172 emit processQueuedItems(); 173 break; 174 case LoadDeferred: 175 m_loadMode = LoadDeferred; 176 break; 177 case LoadResuming: 178 Q_ASSERT(0); // should never happen 179 break; 180 }; 181 } 182 183 void QNetworkReplyHandler::abort() 184 { 185 m_resourceHandle = 0; 186 if (m_reply) { 187 QNetworkReply* reply = release(); 188 reply->abort(); 189 reply->deleteLater(); 190 } 191 deleteLater(); 192 } 193 194 QNetworkReply* QNetworkReplyHandler::release() 195 { 196 QNetworkReply* reply = m_reply; 197 if (m_reply) { 198 disconnect(m_reply, 0, this, 0); 199 // We have queued connections to the QNetworkReply. Make sure any 200 // posted meta call events that were the result of a signal emission 201 // don't reach the slots in our instance. 202 QCoreApplication::removePostedEvents(this, QEvent::MetaCall); 203 m_reply->setParent(0); 204 m_reply = 0; 205 } 206 return reply; 207 } 208 209 static bool ignoreHttpError(QNetworkReply* reply, bool receivedData) 210 { 211 int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 212 213 if (httpStatusCode == 401 || httpStatusCode == 407) 214 return true; 215 216 if (receivedData && (httpStatusCode >= 400 && httpStatusCode < 600)) 217 return true; 218 219 return false; 220 } 221 222 void QNetworkReplyHandler::finish() 223 { 224 m_shouldFinish = (m_loadMode != LoadNormal); 225 if (m_shouldFinish) 226 return; 227 228 sendResponseIfNeeded(); 229 230 if (!m_resourceHandle) 231 return; 232 ResourceHandleClient* client = m_resourceHandle->client(); 233 if (!client) { 234 m_reply->deleteLater(); 235 m_reply = 0; 236 return; 237 } 238 239 QNetworkReply* oldReply = m_reply; 240 241 if (m_redirected) { 242 resetState(); 243 start(); 244 } else if (!m_reply->error() || ignoreHttpError(m_reply, m_responseDataSent)) { 245 client->didFinishLoading(m_resourceHandle); 246 } else { 247 QUrl url = m_reply->url(); 248 int httpStatusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 249 250 if (httpStatusCode) { 251 ResourceError error("HTTP", httpStatusCode, url.toString(), m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()); 252 client->didFail(m_resourceHandle, error); 253 } else { 254 ResourceError error("QtNetwork", m_reply->error(), url.toString(), m_reply->errorString()); 255 client->didFail(m_resourceHandle, error); 256 } 257 } 258 259 oldReply->deleteLater(); 260 if (oldReply == m_reply) 261 m_reply = 0; 262 } 263 264 void QNetworkReplyHandler::sendResponseIfNeeded() 265 { 266 m_shouldSendResponse = (m_loadMode != LoadNormal); 267 if (m_shouldSendResponse) 268 return; 269 270 if (m_reply->error() && !ignoreHttpError(m_reply, m_responseDataSent)) 271 return; 272 273 if (m_responseSent || !m_resourceHandle) 274 return; 275 m_responseSent = true; 276 277 ResourceHandleClient* client = m_resourceHandle->client(); 278 if (!client) 279 return; 280 281 WebCore::String contentType = m_reply->header(QNetworkRequest::ContentTypeHeader).toString(); 282 WebCore::String encoding = extractCharsetFromMediaType(contentType); 283 WebCore::String mimeType = extractMIMETypeFromMediaType(contentType); 284 285 if (mimeType.isEmpty()) { 286 // let's try to guess from the extension 287 QString extension = m_reply->url().path(); 288 int index = extension.lastIndexOf(QLatin1Char('.')); 289 if (index > 0) { 290 extension = extension.mid(index + 1); 291 mimeType = MIMETypeRegistry::getMIMETypeForExtension(extension); 292 } 293 } 294 295 KURL url(m_reply->url()); 296 ResourceResponse response(url, mimeType, 297 m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), 298 encoding, String()); 299 300 if (url.isLocalFile()) { 301 client->didReceiveResponse(m_resourceHandle, response); 302 return; 303 } 304 305 // The status code is equal to 0 for protocols not in the HTTP family. 306 int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 307 308 if (url.protocolInHTTPFamily()) { 309 String suggestedFilename = filenameFromHTTPContentDisposition(QString::fromAscii(m_reply->rawHeader("Content-Disposition"))); 310 311 if (!suggestedFilename.isEmpty()) 312 response.setSuggestedFilename(suggestedFilename); 313 else 314 response.setSuggestedFilename(url.lastPathComponent()); 315 316 response.setHTTPStatusCode(statusCode); 317 response.setHTTPStatusText(m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toByteArray().constData()); 318 319 // Add remaining headers. 320 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0) 321 foreach (const QNetworkReply::RawHeaderPair& pair, m_reply->rawHeaderPairs()) { 322 response.setHTTPHeaderField(QString::fromAscii(pair.first), QString::fromAscii(pair.second)); 323 } 324 #else 325 foreach (const QByteArray& headerName, m_reply->rawHeaderList()) { 326 response.setHTTPHeaderField(QString::fromAscii(headerName), QString::fromAscii(m_reply->rawHeader(headerName))); 327 } 328 #endif 329 } 330 331 QUrl redirection = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 332 if (redirection.isValid()) { 333 m_redirected = true; 334 335 QUrl newUrl = m_reply->url().resolved(redirection); 336 ResourceRequest newRequest = m_resourceHandle->request(); 337 newRequest.setURL(newUrl); 338 339 if (((statusCode >= 301 && statusCode <= 303) || statusCode == 307) && m_method == QNetworkAccessManager::PostOperation) { 340 m_method = QNetworkAccessManager::GetOperation; 341 newRequest.setHTTPMethod("GET"); 342 } 343 344 // Should not set Referer after a redirect from a secure resource to non-secure one. 345 if (!newRequest.url().protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https")) 346 newRequest.clearHTTPReferrer(); 347 348 client->willSendRequest(m_resourceHandle, newRequest, response); 349 if (!m_resourceHandle) // network error did cancel the request 350 return; 351 352 m_request = newRequest.toNetworkRequest(m_resourceHandle->getInternal()->m_frame); 353 return; 354 } 355 356 client->didReceiveResponse(m_resourceHandle, response); 357 } 358 359 void QNetworkReplyHandler::forwardData() 360 { 361 m_shouldForwardData = (m_loadMode != LoadNormal); 362 if (m_shouldForwardData) 363 return; 364 365 sendResponseIfNeeded(); 366 367 // don't emit the "Document has moved here" type of HTML 368 if (m_redirected) 369 return; 370 371 if (!m_resourceHandle) 372 return; 373 374 QByteArray data = m_reply->read(m_reply->bytesAvailable()); 375 376 ResourceHandleClient* client = m_resourceHandle->client(); 377 if (!client) 378 return; 379 380 if (!data.isEmpty()) { 381 m_responseDataSent = true; 382 client->didReceiveData(m_resourceHandle, data.constData(), data.length(), data.length() /*FixMe*/); 383 } 384 } 385 386 void QNetworkReplyHandler::uploadProgress(qint64 bytesSent, qint64 bytesTotal) 387 { 388 if (!m_resourceHandle) 389 return; 390 391 ResourceHandleClient* client = m_resourceHandle->client(); 392 if (!client) 393 return; 394 395 client->didSendData(m_resourceHandle, bytesSent, bytesTotal); 396 } 397 398 void QNetworkReplyHandler::start() 399 { 400 m_shouldStart = false; 401 402 ResourceHandleInternal* d = m_resourceHandle->getInternal(); 403 404 QNetworkAccessManager* manager = d->m_frame->page()->networkAccessManager(); 405 406 const QUrl url = m_request.url(); 407 const QString scheme = url.scheme(); 408 // Post requests on files and data don't really make sense, but for 409 // fast/forms/form-post-urlencoded.html and for fast/forms/button-state-restore.html 410 // we still need to retrieve the file/data, which means we map it to a Get instead. 411 if (m_method == QNetworkAccessManager::PostOperation 412 && (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data"))) 413 m_method = QNetworkAccessManager::GetOperation; 414 415 switch (m_method) { 416 case QNetworkAccessManager::GetOperation: 417 m_reply = manager->get(m_request); 418 break; 419 case QNetworkAccessManager::PostOperation: { 420 FormDataIODevice* postDevice = new FormDataIODevice(d->m_request.httpBody()); 421 m_reply = manager->post(m_request, postDevice); 422 postDevice->setParent(m_reply); 423 break; 424 } 425 case QNetworkAccessManager::HeadOperation: 426 m_reply = manager->head(m_request); 427 break; 428 case QNetworkAccessManager::PutOperation: { 429 FormDataIODevice* putDevice = new FormDataIODevice(d->m_request.httpBody()); 430 m_reply = manager->put(m_request, putDevice); 431 putDevice->setParent(m_reply); 432 break; 433 } 434 #if QT_VERSION >= 0x040600 435 case QNetworkAccessManager::DeleteOperation: { 436 m_reply = manager->deleteResource(m_request); 437 break; 438 } 439 #endif 440 case QNetworkAccessManager::UnknownOperation: { 441 m_reply = 0; 442 ResourceHandleClient* client = m_resourceHandle->client(); 443 if (client) { 444 ResourceError error(url.host(), 400 /*bad request*/, 445 url.toString(), 446 QCoreApplication::translate("QWebPage", "Bad HTTP request")); 447 client->didFail(m_resourceHandle, error); 448 } 449 return; 450 } 451 } 452 453 m_reply->setParent(this); 454 455 connect(m_reply, SIGNAL(finished()), 456 this, SLOT(finish()), SIGNAL_CONN); 457 458 // For http(s) we know that the headers are complete upon metaDataChanged() emission, so we 459 // can send the response as early as possible 460 if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) 461 connect(m_reply, SIGNAL(metaDataChanged()), 462 this, SLOT(sendResponseIfNeeded()), SIGNAL_CONN); 463 464 connect(m_reply, SIGNAL(readyRead()), 465 this, SLOT(forwardData()), SIGNAL_CONN); 466 467 if (m_resourceHandle->request().reportUploadProgress()) { 468 connect(m_reply, SIGNAL(uploadProgress(qint64, qint64)), 469 this, SLOT(uploadProgress(qint64, qint64)), SIGNAL_CONN); 470 } 471 472 // Make this a direct function call once we require 4.6.1+. 473 connect(this, SIGNAL(processQueuedItems()), 474 this, SLOT(sendQueuedItems()), SIGNAL_CONN); 475 } 476 477 void QNetworkReplyHandler::resetState() 478 { 479 m_redirected = false; 480 m_responseSent = false; 481 m_responseDataSent = false; 482 m_shouldStart = true; 483 m_shouldFinish = false; 484 m_shouldSendResponse = false; 485 m_shouldForwardData = false; 486 } 487 488 void QNetworkReplyHandler::sendQueuedItems() 489 { 490 if (m_loadMode != LoadResuming) 491 return; 492 m_loadMode = LoadNormal; 493 494 if (m_shouldStart) 495 start(); 496 497 if (m_shouldSendResponse) 498 sendResponseIfNeeded(); 499 500 if (m_shouldForwardData) 501 forwardData(); 502 503 if (m_shouldFinish) 504 finish(); 505 } 506 507 } 508 509 #include "moc_QNetworkReplyHandler.cpp" 510