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