68c0b075b0d34f16ebc495f9480fb6c98c947e55
[WebKit-https.git] / WebCore / platform / network / qt / ResourceHandleManagerQt.cpp
1 /*
2  * Copyright (C) 2006 Enrico Ros <enrico.ros@m31engineering.it>
3  * Copyright (C) 2006 Trolltech ASA
4  *
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30
31 #include "CString.h"
32 #include "FrameQt.h"
33 #include "ResourceHandle.h"
34 #include "ResourceHandleClient.h"
35 #include "ResourceResponse.h"
36 #include "ResourceHandleManagerQt.h"
37 #include "ResourceHandleInternal.h"
38 #include "ResourceError.h"
39 #include "MimeTypeRegistry.h"
40
41 #include <QCoreApplication>
42 #include <QHttpRequestHeader>
43 #include <QFile>
44 #include <QMap>
45 #include <QByteArray>
46 #include <QUrl>
47 #include <qdebug.h>
48
49 #define notImplemented() qDebug("FIXME: UNIMPLEMENTED: %s:%d (%s)", __FILE__, __LINE__, __FUNCTION__)
50
51 #if 0
52 #define DEBUG qDebug
53 #else
54 #define DEBUG if (1) {} else qDebug
55 #endif
56
57 namespace WebCore {
58
59 static ResourceHandleManager* s_self = 0;
60
61 ResourceHandleManager::ResourceHandleManager()
62 {
63     m_fileLoader = new LoaderThread(this, LoaderThread::File);
64     m_fileLoader->start();
65     m_networkLoader = new LoaderThread(this, LoaderThread::Network);
66     m_networkLoader->start();
67
68     m_fileLoader->waitForSetup();
69     m_networkLoader->waitForSetup();
70 }
71
72 ResourceHandleManager::~ResourceHandleManager()
73 {
74     m_networkLoader->quit();
75     m_fileLoader->quit();
76 }
77
78 ResourceHandleManager* ResourceHandleManager::self()
79 {
80     if (!s_self)
81         s_self = new ResourceHandleManager();
82
83     return s_self;
84 }
85
86 RequestQt::RequestQt(ResourceHandle* res)
87     : resource(res), redirected(false), cancelled(false)
88 {
89     setURL(res->url());
90     request = QHttpRequestHeader(resource->method(), url.path() + url.query());
91     request.setValue(QLatin1String("User-Agent"),
92                            QLatin1String("Mozilla/5.0 (PC; U; Intel; Linux; en) AppleWebKit/420+ (KHTML, like Gecko)"));
93     request.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive"));
94
95     const HTTPHeaderMap& loaderHeaders = resource->requestHeaders();
96     HTTPHeaderMap::const_iterator end = loaderHeaders.end();
97     for (HTTPHeaderMap::const_iterator it = loaderHeaders.begin(); it != end; ++it)
98         request.setValue(it->first, it->second);
99
100     int port = url.port();
101     if (port && port != 80)
102         request.setValue(QLatin1String("Host"), url.host() + QLatin1Char(':') + QString::number(port));
103     else
104         request.setValue(QLatin1String("Host"), url.host());
105
106     int id;
107     // handle and perform a 'POST' request
108     if (resource->method() == "POST") {
109         request.setValue(QLatin1String("PropagateHttpHeader"), QLatin1String("true"));
110         request.setValue(QLatin1String("content-type"), QLatin1String("Content-Type: application/x-www-form-urlencoded"));
111
112         DeprecatedString pd = resource->postData()->flattenToString().deprecatedString();
113         postData = QByteArray(pd.ascii(), pd.length());
114         request.setValue(QLatin1String("content-length"), QString::number(postData.size()));
115     } else if (resource->method() != "GET") {
116         // or.. don't know what to do! (probably a request error!!)
117         // but treat it like a 'GET' request
118         notImplemented();
119         qWarning("REQUEST: [%s]\n", qPrintable(request.toString()));
120     }
121 //     DEBUG() << "RequestQt::RequestQt: http header:";
122 //     DEBUG() << request.toString();
123 }
124
125
126 void RequestQt::setURL(const KURL &u)
127 {
128     url = u;
129     int port = url.port();
130     if (port && port != 80)
131         request.setValue(QLatin1String("Host"), url.host() + QLatin1Char(':') + QString::number(port));
132     else
133         request.setValue(QLatin1String("Host"), url.host());
134     hostInfo = HostInfo(u);
135     qurl = url.url();
136 }
137
138 void ResourceHandleManager::add(ResourceHandle* resource)
139 {
140     ASSERT(resource);
141
142     // check for (probably) broken requests
143     if (resource->method() != "GET" && resource->method() != "POST") {
144         notImplemented();
145         return;
146     }
147
148     RequestQt* request = new RequestQt(resource);
149     add(request);
150 }
151
152 void ResourceHandleManager::add(RequestQt* request)
153 {
154     Q_ASSERT(!pendingRequests.value(request->resource));
155
156     pendingRequests[request->resource] = request;
157
158     //DEBUG() << "ResourceHandleManager::add" << request->hostInfo.protocol << request->hostInfo.host;
159     // check for not implemented protocols
160     String protocol = request->hostInfo.protocol;
161     if (protocol == "http") {
162         //DEBUG() << "networkRequest";
163         emit networkRequest(request);
164         return;
165     }
166
167     // "file", "data" and all unhandled stuff go through here
168     //DEBUG() << "fileRequest";
169     emit fileRequest(request);
170     return;
171 }
172
173
174 void ResourceHandleManager::cancel(ResourceHandle* resource)
175 {
176     ResourceHandleClient* client = resource->client();
177     if (!client)
178         return;
179     RequestQt *req = pendingRequests.value(resource);
180     if (!req)
181         return;
182
183     DEBUG() << "ResourceHandleManager::cancel" << resource->url().path();
184     
185     RequestQt* request = pendingRequests.take(resource);
186     if (!request)
187         return;
188     request->cancelled = true;
189
190     String protocol = request->hostInfo.protocol;
191     if (protocol == "http") 
192         emit networkCancel(request);
193 }
194
195
196 void ResourceHandleManager::receivedResponse(RequestQt* request)
197 {
198     if (request->cancelled)
199         return;
200     Q_ASSERT(pendingRequests.value(request->resource) == request);
201     DEBUG() << "ResourceHandleManager::receivedResponse:";
202     DEBUG() << request->response.toString();
203
204     ResourceHandleClient* client = request->resource->client();
205     if (!client)
206         return;
207
208     QString contentType = request->response.value("Content-Type");
209     QString encoding;
210     int idx = contentType.indexOf(QLatin1Char(';'));
211     if (idx > 0) {
212         QString remainder = contentType.mid(idx + 1).toLower();
213         contentType = contentType.left(idx).trimmed();
214
215         idx = remainder.indexOf("charset");
216         if (idx >= 0) {
217             idx = remainder.indexOf(QLatin1Char('='), idx);
218             if (idx >= 0)
219                 encoding = remainder.mid(idx + 1).trimmed();
220         }
221     }
222     if (contentType.isEmpty()) {
223         // let's try to guess from the extension
224         QString extension = request->qurl.path();
225         int index = extension.lastIndexOf(QLatin1Char('.'));
226         if (index > 0) {
227             extension = extension.mid(index + 1);
228             contentType = MimeTypeRegistry::getMIMETypeForExtension(extension);
229         }
230     }
231 //     qDebug() << "Content-Type=" << contentType;
232 //     qDebug() << "Encoding=" << encoding;
233
234
235     ResourceResponse response(request->url, contentType,
236                               0 /* FIXME */,
237                               encoding,
238                               String() /* FIXME */);
239
240     int statusCode = request->response.statusCode();
241     response.setHTTPStatusCode(statusCode);
242     /* Fill in the other fields */
243
244     if (statusCode >= 300 && statusCode < 400) {
245         // we're on a redirect page! if the 'Location:' field is valid, we redirect
246         QString location = request->response.value("location");
247         DEBUG() << "Redirection";
248         if (!location.isEmpty()) {
249             ResourceRequest newRequest = request->resource->request();
250             newRequest.setURL(DeprecatedString(location));
251             client->willSendRequest(request->resource, newRequest, response);
252             request->request.setRequest(request->request.method(), newRequest.url().path() + newRequest.url().query());
253             request->setURL(newRequest.url());
254             request->redirected = true;
255             return;
256         }
257     }
258
259
260     client->didReceiveResponse(request->resource, response);
261 }
262
263 void ResourceHandleManager::receivedData(RequestQt* request, const QByteArray& data)
264 {
265     if (request->cancelled || request->redirected)
266         return;
267     Q_ASSERT(pendingRequests.value(request->resource) == request);
268
269     ResourceHandleClient* client = request->resource->client();
270     if (!client)
271         return;
272
273     DEBUG() << "receivedData" << request->url.path();
274     client->didReceiveData(request->resource, data.constData(), data.length(), data.length() /*FixMe*/);
275 }
276
277 void ResourceHandleManager::receivedFinished(RequestQt* request, int errorCode)
278 {
279     if (request->cancelled) {
280         delete request;
281         return;
282     }
283     DEBUG() << "receivedFinished" << errorCode << request->url.path();
284     Q_ASSERT(pendingRequests.value(request->resource) == request);
285
286     pendingRequests.remove(request->resource);
287
288     if (request->redirected) {
289         request->redirected = false;
290         add(request);
291         return;
292     }
293
294     ResourceHandleClient* client = request->resource->client();
295     if (!client)
296         return;
297
298     if (errorCode) {
299         //FIXME: error setting error was removed from ResourceHandle
300         client->didFail(request->resource, ResourceError());
301     } else {
302         client->didFinishLoading(request->resource);
303     }
304     DEBUG() << "receivedFinished done" << request->url.path();
305     delete request;
306 }
307
308 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
309 LoaderThread::LoaderThread(ResourceHandleManager *manager, Type type)
310     : QThread(manager), m_type(type), m_loader(0), m_manager(manager), m_setup(false)
311 {
312 }
313
314 void LoaderThread::run()
315 {
316     switch (m_type) {
317     case Network:
318         m_loader = new NetworkLoader;
319         connect(m_manager, SIGNAL(networkRequest(RequestQt*)),
320                 m_loader, SLOT(request(RequestQt*)));
321         connect(m_manager, SIGNAL(networkCancel(RequestQt*)),
322                 m_loader, SLOT(cancel(RequestQt*)));
323         break;
324     case File:
325         m_loader = new FileLoader;
326         connect(m_manager, SIGNAL(fileRequest(RequestQt*)),
327                 m_loader, SLOT(request(RequestQt*)));
328         break;
329     }
330     connect(m_loader, SIGNAL(receivedResponse(RequestQt*)),
331             m_manager, SLOT(receivedResponse(RequestQt*)));
332     connect(m_loader, SIGNAL(receivedData(RequestQt*, QByteArray)),
333             m_manager, SLOT(receivedData(RequestQt*, QByteArray)));
334     connect(m_loader, SIGNAL(receivedFinished(RequestQt*, int)),
335             m_manager, SLOT(receivedFinished(RequestQt*, int)));
336     DEBUG() << "calling exec";
337     m_setup = true;
338     exec();
339     DEBUG() << "done exec";
340     delete m_loader;
341 }
342
343 /////////////////////////////////////////////////////////////////////////////
344 FileLoader::FileLoader()
345     : QObject(0)
346 {
347     DEBUG() << "FileLoader::FileLoader";
348 }
349
350
351 void FileLoader::request(RequestQt* request)
352 {
353     DEBUG() << "FileLoader::request" << request->request.path();
354
355     if (request->cancelled) {
356         sendData(request, 400, QByteArray());
357         return;
358     }
359     
360     if (request->hostInfo.protocol == QLatin1String("data")) {
361         parseDataUrl(request);
362         return;
363     }
364
365     int statusCode = 200;
366     QByteArray data;
367     if (!request->hostInfo.isLocalFile()) {
368         statusCode = 404;
369     } else if (request->postData.isEmpty()) {
370         QFile f(QString(request->qurl.path()));
371         DEBUG() << "opening" << QString(request->qurl.path());
372
373         if (f.open(QIODevice::ReadOnly)) {
374             request->response.setStatusLine(200);        
375             data = f.readAll();
376         } else {
377             statusCode = 404;
378         }
379     } else {
380         statusCode = 404;
381     }
382     sendData(request, statusCode, data);
383 }
384
385 void FileLoader::sendData(RequestQt* request, int statusCode, const QByteArray &data)
386 {
387     int error = statusCode >= 400 ? 1 : 0;
388     if (!request->cancelled) {
389         request->response.setStatusLine(statusCode);
390         emit receivedResponse(request);
391         if (!data.isEmpty())
392             emit receivedData(request, data);
393     }
394     emit receivedFinished(request, error);
395 }
396
397 void FileLoader::parseDataUrl(RequestQt* request)
398 {
399     QByteArray data = request->qurl.toString().toLatin1();
400     //qDebug() << "handling data url:" << data; 
401
402     ASSERT(data.startsWith("data:"));
403
404     // Here's the syntax of data URLs:
405     // dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
406     // mediatype  := [ type "/" subtype ] *( ";" parameter )
407     // data       := *urlchar
408     // parameter  := attribute "=" value
409     QByteArray header;
410     bool base64 = false;
411
412     int index = data.indexOf(',');
413     if (index != -1) {
414         header = data.mid(5, index - 5);
415         header = header.toLower();
416         //qDebug() << "header=" << header;
417         data = data.mid(index+1);
418         //qDebug() << "data=" << data;
419
420         if (header.endsWith(";base64")) {
421             //qDebug() << "base64";
422             base64 = true;
423             header = header.left(header.length() - 7);
424             //qDebug() << "mime=" << header;
425         }        
426     } else {
427         data = QByteArray();
428     }
429     if (base64) {
430         data = QByteArray::fromBase64(data);
431     } else {
432         data = QUrl::fromPercentEncoding(data).toLatin1();
433     }
434
435     if (header.isEmpty()) 
436         header = "text/plain;charset=US-ASCII";
437     int statusCode = data.isEmpty() ? 404 : 200;
438     request->response.setContentType(header);
439     request->response.setContentLength(data.size());
440
441     sendData(request, statusCode, data);
442 }
443
444
445 /////////////////////////////////////////////////////////////////////////////
446 WebCoreHttp::WebCoreHttp(NetworkLoader* parent, const HostInfo &hi)
447     : info(hi),
448       m_loader(parent),
449       m_inCancel(false)
450 {
451     for (int i = 0; i < 2; ++i) {
452         connection[i].http = new QHttp(info.host, info.port);
453         connection[i].current = 0;
454         connect(connection[i].http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader&)),
455                 this, SLOT(onResponseHeaderReceived(const QHttpResponseHeader&)));
456         connect(connection[i].http, SIGNAL(readyRead(const QHttpResponseHeader&)),
457                 this, SLOT(onReadyRead()));
458         connect(connection[i].http, SIGNAL(requestFinished(int, bool)),
459                 this, SLOT(onRequestFinished(int, bool)));
460         connect(connection[i].http, SIGNAL(stateChanged(int)),
461                 this, SLOT(onStateChanged(int)));
462     }
463 }
464
465 WebCoreHttp::~WebCoreHttp()
466 {
467 }
468
469 void WebCoreHttp::request(RequestQt *req)
470 {
471     DEBUG() << ">>>>>>>>>>>>>> WebCoreHttp::request";
472     DEBUG() << req->request.toString() << "\n";
473     m_pendingRequests.append(req);
474
475     scheduleNextRequest();
476 }
477
478 void WebCoreHttp::scheduleNextRequest()
479 {
480     int c = 0;
481     for (; c < 2; ++c) {
482         if (!connection[c].current)
483             break;
484     }
485     if (c >= 2)
486         return;
487
488     RequestQt *req = 0;
489     while (!req && !m_pendingRequests.isEmpty()) {
490         req = m_pendingRequests.takeFirst();
491         if (req->cancelled) {
492             emit m_loader->receivedFinished(req, 1);
493             req = 0;
494         }
495     }
496     if (!req)
497         return;
498     
499     QHttp *http = connection[c].http;
500     if (!req->postData.isEmpty())
501         http->request(req->request, req->postData);
502     else
503         http->request(req->request);
504     connection[c].current = req;
505
506     DEBUG() << "WebCoreHttp::scheduleNextRequest: using connection" << c;
507 //     DEBUG() << req->request.toString();
508 }
509
510 int WebCoreHttp::getConnection()
511 {
512     QObject *o = sender();
513     int c;
514     if (o == connection[0].http) {
515         c = 0;
516     } else {
517         Q_ASSERT(o == connection[1].http);
518         c = 1;
519     }
520     //Q_ASSERT(connection[c].current);
521     return c;
522 }
523
524 void WebCoreHttp::onResponseHeaderReceived(const QHttpResponseHeader &resp)
525 {
526     int c = getConnection();
527     RequestQt *req = connection[c].current;
528     DEBUG() << "WebCoreHttp::slotResponseHeaderReceived connection=" << c;
529
530     req->response = resp;
531
532     emit m_loader->receivedResponse(req);
533 }
534
535 void WebCoreHttp::onReadyRead()
536 {
537     int c = getConnection();
538     RequestQt *req = connection[c].current;
539     QHttp *http = connection[c].http;
540     DEBUG() << "WebCoreHttp::slotReadyRead connection=" << c;
541
542     QByteArray data;
543     data.resize(http->bytesAvailable());
544     http->read(data.data(), data.length());
545     emit m_loader->receivedData(req, data);
546 }
547
548 void WebCoreHttp::onRequestFinished(int, bool error)
549 {
550     int c = getConnection();
551     RequestQt *req = connection[c].current;
552     if (!req) {
553         scheduleNextRequest();
554         return;
555     }
556     QHttp *http = connection[c].http;
557     DEBUG() << "WebCoreHttp::slotFinished connection=" << c << error << req;
558
559     if (error)
560         DEBUG() << "   error: " << http->errorString();
561
562     if (!error && http->bytesAvailable()) {
563         QByteArray data;
564         data.resize(http->bytesAvailable());
565         http->read(data.data(), data.length());
566         emit m_loader->receivedData(req, data);
567     }
568     emit m_loader->receivedFinished(req, error ? 1 : 0);
569
570     connection[c].current = 0;
571     scheduleNextRequest();
572 }
573
574 void WebCoreHttp::onStateChanged(int state)
575 {
576     if (state == QHttp::Closing || state == QHttp::Unconnected) {
577         if (!m_inCancel && m_pendingRequests.isEmpty()
578             && !connection[0].current && !connection[1].current)
579             emit connectionClosed(info);
580     }
581 }
582
583 void WebCoreHttp::cancel(RequestQt* request)
584 {
585     bool doEmit = true;
586     m_inCancel = true;
587     for (int i = 0; i < 2; ++i) {
588         if (request == connection[i].current) {
589             connection[i].http->abort();
590             doEmit = false;
591         }
592     }
593     m_pendingRequests.removeAll(request);
594     m_inCancel = false;
595
596     if (doEmit)
597         emit m_loader->receivedFinished(request, 1);
598
599     if (m_pendingRequests.isEmpty()
600         && !connection[0].current && !connection[1].current)
601         emit connectionClosed(info);
602 }
603
604
605 static uint qHash(const HostInfo &info)
606 {
607     return qHash(info.host) + info.port;
608 }
609
610 static bool operator==(const HostInfo &i1, const HostInfo &i2)
611 {
612     return i1.port == i2.port && i1.host == i2.host;
613 }
614
615 HostInfo::HostInfo(const KURL& url)
616     : protocol(url.protocol())
617     , host(url.host())
618     , port(url.port())
619 {
620     if (!port)
621         port = 80;
622 }
623
624 NetworkLoader::NetworkLoader()
625     : QObject(0)
626 {
627 }
628
629 NetworkLoader::~NetworkLoader()
630 {
631 }
632
633 void NetworkLoader::request(RequestQt* request)
634 {
635     DEBUG() << "NetworkLoader::request";
636     WebCoreHttp *httpConnection = m_hostMapping.value(request->hostInfo);
637     if (!httpConnection) {
638         // #### fix custom ports
639         DEBUG() << "   new connection to" << request->hostInfo.host << request->hostInfo.port;
640         httpConnection = new WebCoreHttp(this, request->hostInfo);
641         connect(httpConnection, SIGNAL(connectionClosed(const HostInfo&)),
642                 this, SLOT(connectionClosed(const HostInfo&)));
643
644         m_hostMapping[request->hostInfo] = httpConnection;
645     }
646     httpConnection->request(request);
647 }
648 void NetworkLoader::connectionClosed(const HostInfo& info)
649 {
650     DEBUG() << "Disconnected";
651     WebCoreHttp *connection = m_hostMapping.take(info);
652     delete connection;
653 }
654
655 void NetworkLoader::cancel(RequestQt* request)
656 {
657     DEBUG() << "NetworkLoader::cancel";
658     WebCoreHttp *httpConnection = m_hostMapping.value(request->hostInfo);
659     if (httpConnection)
660         httpConnection->cancel(request);
661 }
662
663 } // namespace WebCore
664
665 #include "ResourceHandleManagerQt.moc"