5e3d9432f418837b7bc276738dd05e436bcb148f
[WebKit-https.git] / WebCore / platform / network / qt / QNetworkReplyHandler.cpp
1 /*
2     Copyright (C) 2007-2008 Trolltech ASA
3     Copyright (C) 2007 Staikos Computing Services Inc.  <info@staikos.net>
4
5     This library is free software; you can redistribute it and/or
6     modify it under the terms of the GNU Library General Public
7     License as published by the Free Software Foundation; either
8     version 2 of the License, or (at your option) any later version.
9
10     This library is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13     Library General Public License for more details.
14
15     You should have received a copy of the GNU Library General Public License
16     along with this library; see the file COPYING.LIB.  If not, write to
17     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18     Boston, MA 02110-1301, USA.
19 */
20 #include "config.h"
21 #include "QNetworkReplyHandler.h"
22
23 #if QT_VERSION >= 0x040400
24
25 #include "HTTPParsers.h"
26 #include "MIMETypeRegistry.h"
27 #include "ResourceHandle.h"
28 #include "ResourceHandleClient.h"
29 #include "ResourceHandleInternal.h"
30 #include "ResourceResponse.h"
31 #include "ResourceRequest.h"
32 #include <QDateTime>
33 #include <QFile>
34 #include <QNetworkReply>
35 #include <QNetworkCookie>
36 #include <qwebframe.h>
37 #include <qwebpage.h>
38
39 #include <QDebug>
40
41 namespace WebCore {
42
43 // Take a deep copy of the FormDataElement
44 FormDataIODevice::FormDataIODevice(FormData* data)
45     : m_formElements(data ? data->elements() : Vector<FormDataElement>())
46     , m_currentFile(0)
47     , m_currentDelta(0)
48 {
49     setOpenMode(FormDataIODevice::ReadOnly);
50 }
51
52 FormDataIODevice::~FormDataIODevice()
53 {
54     delete m_currentFile;
55 }
56
57 void FormDataIODevice::moveToNextElement()
58 {
59     if (m_currentFile)
60         m_currentFile->close();
61     m_currentDelta = 0;
62
63     m_formElements.remove(0);
64
65     if (m_formElements.isEmpty() || m_formElements[0].m_type == FormDataElement::data)
66         return;
67
68     if (!m_currentFile)
69         m_currentFile = new QFile;
70
71     m_currentFile->setFileName(m_formElements[0].m_filename);
72     m_currentFile->open(QFile::ReadOnly);
73 }
74
75 // m_formElements[0] is the current item. If the destination buffer is
76 // big enough we are going to read from more than one FormDataElement
77 qint64 FormDataIODevice::readData(char* destination, qint64 size)
78 {
79     if (m_formElements.isEmpty())
80         return -1;
81
82     qint64 copied = 0;
83     while (copied < size && !m_formElements.isEmpty()) {
84         const FormDataElement& element = m_formElements[0];
85         const qint64 available = size-copied;
86
87         if (element.m_type == FormDataElement::data) {
88             const qint64 toCopy = qMin<qint64>(available, element.m_data.size() - m_currentDelta);
89             memcpy(destination+copied, element.m_data.data()+m_currentDelta, toCopy); 
90             m_currentDelta += toCopy;
91             copied += toCopy;
92
93             if (m_currentDelta == element.m_data.size())
94                 moveToNextElement();
95         } else {
96             const QByteArray data = m_currentFile->read(available);
97             memcpy(destination+copied, data.constData(), data.size());
98             copied += data.size();
99
100             if (m_currentFile->atEnd() || !m_currentFile->isOpen())
101                 moveToNextElement();
102         }
103     }
104
105     return copied;
106 }
107
108 qint64 FormDataIODevice::writeData(const char*, qint64)
109 {
110     return -1;
111 }
112
113 void FormDataIODevice::setParent(QNetworkReply* reply)
114 {
115     QIODevice::setParent(reply);
116
117     connect(reply, SIGNAL(finished()), SLOT(slotFinished()));
118 }
119
120 bool FormDataIODevice::isSequential() const
121 {
122     return true;
123 }
124
125 void FormDataIODevice::slotFinished()
126 {
127     deleteLater();
128 }
129
130 QNetworkReplyHandler::QNetworkReplyHandler(ResourceHandle *handle)
131     : QObject(0)
132     , m_resourceHandle(handle)
133     , m_reply(0)
134     , m_redirected(false)
135     , m_responseSent(false)
136     , m_startTime(0)
137 {
138     const ResourceRequest &r = m_resourceHandle->request();
139
140     if (r.httpMethod() == "GET")
141         m_method = QNetworkAccessManager::GetOperation;
142     else if (r.httpMethod() == "HEAD")
143         m_method = QNetworkAccessManager::HeadOperation;
144     else if (r.httpMethod() == "POST")
145         m_method = QNetworkAccessManager::PostOperation;
146     else if (r.httpMethod() == "PUT")
147         m_method = QNetworkAccessManager::PutOperation;
148     else
149         m_method = QNetworkAccessManager::UnknownOperation;
150
151     m_request = r.toNetworkRequest();
152
153     start();
154 }
155
156 void QNetworkReplyHandler::abort()
157 {
158     m_resourceHandle = 0;
159     if (m_reply) {
160         disconnect(m_reply, 0, this, 0);
161         m_reply->abort();
162         deleteLater();
163         m_reply = 0;
164     }
165 }
166
167 QNetworkReply *QNetworkReplyHandler::release()
168 {
169     QNetworkReply *reply = m_reply;
170     if (m_reply) {
171         disconnect(m_reply, 0, this, 0);
172         m_reply = 0;
173     }
174     return reply;
175 }
176
177 void QNetworkReplyHandler::finish()
178 {
179     sendResponseIfNeeded();
180
181     if (!m_resourceHandle)
182         return;
183     ResourceHandleClient* client = m_resourceHandle->client();
184     m_reply->deleteLater();
185     if (!client)
186         return;
187     if (m_redirected) {
188         m_redirected = false;
189         m_responseSent = false;
190         start();
191     } else if (m_reply->error() != QNetworkReply::NoError) {
192         QUrl url = m_reply->url();
193         ResourceError error(url.host(), m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(),
194                             url.toString(), m_reply->errorString());
195         client->didFail(m_resourceHandle, error);
196     } else {
197         client->didFinishLoading(m_resourceHandle);
198     }
199 }
200
201 void QNetworkReplyHandler::sendResponseIfNeeded()
202 {
203     if (m_responseSent || !m_resourceHandle)
204         return;
205     m_responseSent = true;
206
207     ResourceHandleClient* client = m_resourceHandle->client();
208     if (!client)
209         return;
210
211     WebCore::String contentType = m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
212     WebCore::String encoding = extractCharsetFromMediaType(contentType);
213     WebCore::String mimeType = extractMIMETypeFromMediaType(contentType);
214
215     if (mimeType.isEmpty()) {
216         // let's try to guess from the extension
217         QString extension = m_reply->url().path();
218         int index = extension.lastIndexOf(QLatin1Char('.'));
219         if (index > 0) {
220             extension = extension.mid(index + 1);
221             mimeType = MIMETypeRegistry::getMIMETypeForExtension(extension);
222         }
223     }
224
225     KURL url(m_reply->url());
226     String suggestedFilename = filenameFromHTTPContentDisposition(QString::fromAscii(m_reply->rawHeader("Content-Disposition")));
227
228     if (suggestedFilename.isEmpty())
229         suggestedFilename = url.lastPathComponent();
230
231     ResourceResponse response(url, mimeType,
232                               m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(),
233                               encoding,
234                               suggestedFilename);
235
236     const bool isLocalFileReply = (m_reply->url().scheme() == QLatin1String("file"));
237     int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
238     if (!isLocalFileReply)
239         response.setHTTPStatusCode(statusCode);
240     else if (m_reply->error() == QNetworkReply::ContentNotFoundError)
241         response.setHTTPStatusCode(404);
242
243
244     /* Fill in the other fields
245      * For local file requests remove the content length and the last-modified
246      * headers as required by fast/dom/xmlhttprequest-get.xhtml
247      */
248     foreach (QByteArray headerName, m_reply->rawHeaderList()) {
249
250         if (isLocalFileReply
251             && (headerName == "Content-Length" || headerName == "Last-Modified"))
252             continue;
253
254         response.setHTTPHeaderField(QString::fromAscii(headerName), QString::fromAscii(m_reply->rawHeader(headerName)));
255     }
256
257     if (isLocalFileReply)
258         response.setExpirationDate(m_startTime);
259
260     QUrl redirection = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
261     if (redirection.isValid()) {
262         QUrl newUrl = m_reply->url().resolved(redirection);
263         ResourceRequest newRequest = m_resourceHandle->request();
264         newRequest.setURL(newUrl);
265
266         if (((statusCode >= 301 && statusCode <= 303) || statusCode == 307) && m_method == QNetworkAccessManager::PostOperation) {
267             m_method = QNetworkAccessManager::GetOperation;
268             newRequest.setHTTPMethod("GET");
269         }
270
271         client->willSendRequest(m_resourceHandle, newRequest, response);
272         m_redirected = true;
273         m_request = newRequest.toNetworkRequest();
274     } else {
275         client->didReceiveResponse(m_resourceHandle, response);
276     }
277 }
278
279 void QNetworkReplyHandler::forwardData()
280 {
281     sendResponseIfNeeded();
282
283     // don't emit the "Document has moved here" type of HTML
284     if (m_redirected)
285         return;
286
287     if (!m_resourceHandle)
288         return;
289
290     QByteArray data = m_reply->read(m_reply->bytesAvailable());
291
292     ResourceHandleClient* client = m_resourceHandle->client();
293     if (!client)
294         return;
295
296     if (!data.isEmpty())
297         client->didReceiveData(m_resourceHandle, data.constData(), data.length(), data.length() /*FixMe*/);
298 }
299
300 void QNetworkReplyHandler::start()
301 {
302     ResourceHandleInternal* d = m_resourceHandle->getInternal();
303
304     QNetworkAccessManager* manager = d->m_frame->page()->networkAccessManager();
305
306     const QUrl url = m_request.url();
307     const QString scheme = url.scheme();
308     // Post requests on files and data don't really make sense, but for
309     // fast/forms/form-post-urlencoded.html and for fast/forms/button-state-restore.html
310     // we still need to retrieve the file/data, which means we map it to a Get instead.
311     if (m_method == QNetworkAccessManager::PostOperation
312         && (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data")))
313         m_method = QNetworkAccessManager::GetOperation;
314
315     m_startTime = QDateTime::currentDateTime().toTime_t();
316
317     switch (m_method) {
318         case QNetworkAccessManager::GetOperation:
319             m_reply = manager->get(m_request);
320             break;
321         case QNetworkAccessManager::PostOperation: {
322             FormDataIODevice* postDevice = new FormDataIODevice(d->m_request.httpBody()); 
323             m_reply = manager->post(m_request, postDevice);
324             postDevice->setParent(m_reply);
325             break;
326         }
327         case QNetworkAccessManager::HeadOperation:
328             m_reply = manager->head(m_request);
329             break;
330         case QNetworkAccessManager::PutOperation: {
331             FormDataIODevice* putDevice = new FormDataIODevice(d->m_request.httpBody()); 
332             m_reply = manager->put(m_request, putDevice);
333             putDevice->setParent(m_reply);
334             break;
335         }
336         case QNetworkAccessManager::UnknownOperation:
337             break; // eh?
338     }
339
340     m_reply->setParent(this);
341
342     connect(m_reply, SIGNAL(finished()),
343             this, SLOT(finish()));
344
345     // For http(s) we know that the headers are complete upon metaDataChanged() emission, so we
346     // can send the response as early as possible
347     if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
348         connect(m_reply, SIGNAL(metaDataChanged()),
349                 this, SLOT(sendResponseIfNeeded()));
350
351     connect(m_reply, SIGNAL(readyRead()),
352             this, SLOT(forwardData()));
353 }
354
355 }
356
357 #include "moc_QNetworkReplyHandler.cpp"
358
359 #endif