• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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