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