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