move the invoking JS to the server side
[qt-labs:milianws-qwebchannel.git] / src / qwebchannel.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QWebChannel module on Qt labs.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qwebchannel.h"
43
44 #include <QtDeclarative/qdeclarative.h>
45 #include <QFile>
46 #include <QPointer>
47 #include <QStringList>
48 #include <QTcpServer>
49 #include <QTcpSocket>
50 #include <QTimer>
51 #include <QUuid>
52
53 QWebChannelResponder::QWebChannelResponder(QTcpSocket* s)
54     : QObject(s)
55     , socket(s)
56 {
57     connect(socket.data(), SIGNAL(disconnected()), socket.data(), SLOT(deleteLater()));
58 }
59
60 void QWebChannelResponder::open()
61 {
62     if (!socket || !socket->isOpen())
63         return;
64
65     socket->write("HTTP/1.1 200 OK\r\n"
66                   "Content-Type: text/json\r\n"
67                   "\r\n");
68 }
69
70 void QWebChannelResponder::write(const QString& data)
71 {
72     if (!socket || !socket->isOpen())
73         return;
74     socket->write(data.toUtf8());
75 }
76
77 void QWebChannelResponder::close()
78 {
79     if (!socket)
80         return;
81     deleteLater();
82     socket->close();
83 }
84
85 class QWebChannelPrivate : public QObject {
86     Q_OBJECT
87 public:
88     bool useSecret;
89     int port;
90     int minPort;
91     int maxPort;
92     QUrl baseUrl;
93     QTcpServer* server;
94     QString secret;
95     QStringList allowedOrigins;
96     typedef QMultiMap<QString, QPointer<QTcpSocket> > SubscriberMap;
97     SubscriberMap subscribers;
98     bool starting;
99
100     QWebChannelPrivate(QObject* parent)
101         : QObject(0)
102         , useSecret(true)
103         , port(-1)
104         , minPort(49158)
105         , maxPort(65535)
106         , server(new QTcpServer(this))
107         , secret("42")
108         , starting(false)
109     {
110         connect(server, SIGNAL(newConnection()), this, SLOT(service()));
111     }
112
113     void initLater()
114     {
115         if (starting)
116             return;
117         metaObject()->invokeMethod(this, "init", Qt::QueuedConnection   );
118         starting = true;
119     }
120
121 public slots:
122     void init();
123     void broadcast(const QString&, const QString&);
124     void service();
125     void onSocketDelete(QObject*);
126
127 signals:
128     void execute(const QString&, QObject*);
129     void initialized();
130     void noPortAvailable();
131 };
132
133 void QWebChannelPrivate::onSocketDelete(QObject * o)
134 {
135     for (SubscriberMap::iterator it = subscribers.begin(); it != subscribers.end(); ++it) {
136         if (it.value() != o)
137             continue;
138         subscribers.erase(it);
139         return;
140     }
141 }
142
143 void QWebChannelPrivate::broadcast(const QString& id, const QString& message)
144 {
145     QList<QPointer<QTcpSocket> > sockets = subscribers.values(id);
146
147     foreach(QPointer<QTcpSocket> socket, sockets) {
148         if (!socket)
149             continue;
150         socket->write("HTTP/1.1 200 OK\r\n"
151                       "Content-Type: text/json\r\n"
152                       "\r\n");
153         socket->write(message.toUtf8());
154         socket->close();
155     }
156 }
157
158 void QWebChannelPrivate::service()
159 {
160     if (!server->hasPendingConnections())
161         return;
162
163     QTcpSocket* socket = server->nextPendingConnection();
164     if (!socket->canReadLine())
165         if (!socket->waitForReadyRead(1000))
166             return;
167
168
169     QString firstLine = socket->readLine();
170     QStringList firstLineValues = firstLine.split(' ');
171     QString method = firstLineValues[0];
172     QString path = firstLineValues[1];
173     QString query;
174     QString hash;
175     int indexOfQM = path.indexOf('?');
176     if (indexOfQM > 0) {
177         query = path.mid(indexOfQM + 1);
178         path = path.left(indexOfQM);
179         int indexOfHash = query.indexOf('#');
180         if (indexOfHash > 0) {
181             hash = query.mid(indexOfHash + 1);
182             query = query.left(indexOfHash);
183         }
184     } else {
185         int indexOfHash = path.indexOf('#');
186         if (indexOfHash > 0) {
187             hash = path.mid(indexOfHash + 1);
188             path = path.left(indexOfHash);
189         }
190     }
191
192     QStringList queryVars = query.split('&');
193     QMap<QString, QString> queryMap;
194     foreach (QString q, queryVars) {
195         int idx = q.indexOf("=");
196         queryMap[q.left(idx)] = q.mid(idx + 1);
197     }
198
199     while (!socket->canReadLine())
200         socket->waitForReadyRead();
201     QString requestString = socket->readAll();
202     QStringList headersAndContent = requestString.split("\r\n\r\n");
203     QStringList headerList = headersAndContent[0].split("\r\n");
204     QMap<QString, QString> headerMap;
205     foreach (QString h, headerList) {
206         int idx = h.indexOf(": ");
207         headerMap[h.left(idx)] = h.mid(idx + 2);
208     }
209
210     QStringList pathElements = path.split('/');
211     pathElements.removeFirst();
212
213     if ((useSecret && pathElements[0] != secret)) {
214         socket->write(
215                             "HTTP/1.1 401 Wrong Path\r\n"
216                             "Content-Type: text/json\r\n"
217                             "\r\n"
218                             "<html><body><h1>Wrong Path</h1></body></html>"
219                     );
220         socket->close();
221         return;
222     }
223
224     if (method == "GET") {
225         QString type = pathElements.size() == 1 ? "j" : pathElements[1];
226         if (type == "e") {
227             QString message = QStringList(pathElements.mid(2)).join("/");
228             QWebChannelResponder* responder = new QWebChannelResponder(socket);
229             QString msg = QUrl::fromPercentEncoding(message.toUtf8());
230
231             emit execute(msg, responder);
232         } else if (type == "j") {
233             QFile file(":/webchannel.js");
234             file.open(QIODevice::ReadOnly);
235             socket->write("HTTP/1.1 200 OK\r\n"
236                           "Content-Type: text/javascript\r\n"
237                           "\r\n");
238             socket->write("(function() {");
239             socket->write(QString("baseUrl = '%1';").arg(baseUrl.toString()).toUtf8());
240             socket->write(file.readAll());
241             socket->write("})();");
242             socket->close();
243             file.close();
244         } else if (type == "s") {
245             QString id = pathElements[2];
246             connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
247             connect(socket, SIGNAL(destroyed(QObject*)), this, SLOT(onSocketDelete(QObject*)));
248             subscribers.insert(id, socket);
249         }
250     }
251 }
252
253 void QWebChannelPrivate::init()
254 {
255     starting = false;
256     if (useSecret) {
257         secret = QUuid::createUuid().toString();
258         secret = secret.mid(1, secret.size() - 2);
259     }
260
261     bool found = false;
262     for (port = minPort; port <= maxPort; ++port) {
263         if (!server->listen(QHostAddress::LocalHost, port))
264             continue;
265         found = true;
266         break;
267     }
268
269     if (!found) {
270         port = -1;
271         emit noPortAvailable();
272         return;
273     }
274
275     baseUrl = QString("http://localhost:%1/%2").arg(port).arg(secret);
276     emit initialized();
277 }
278
279 QStringList QWebChannel::allowedOrigins() const
280 {
281     return d->allowedOrigins;
282 }
283 void QWebChannel::setAllowedOrigins(const QStringList& s)
284 {
285     d->allowedOrigins = s;
286 }
287
288 QWebChannel::QWebChannel(QDeclarativeItem *parent):
289         QDeclarativeItem(parent)
290 {
291     d = new QWebChannelPrivate(this);
292     connect(d, SIGNAL(execute(QString,QObject*)), this, SIGNAL(execute(QString, QObject*)));
293     connect(d, SIGNAL(initialized()), this, SLOT(onInitialized()));
294     connect(d, SIGNAL(noPortAvailable()), this, SIGNAL(noPortAvailable()));
295     d->initLater();
296 }
297
298 QWebChannel::~QWebChannel()
299 {
300 }
301
302 QUrl QWebChannel::baseUrl() const
303 {
304     return d->baseUrl;
305 }
306 void QWebChannel::setUseSecret(bool s)
307 {
308     if (d->useSecret == s)
309         return;
310     d->useSecret = s;
311     d->initLater();
312 }
313 bool QWebChannel::useSecret() const
314 {
315     return d->useSecret;
316 }
317
318 int QWebChannel::port() const
319 {
320     return d->port;
321 }
322
323 int QWebChannel::minPort() const
324 {
325     return d->minPort;
326 }
327
328 int QWebChannel::maxPort() const
329 {
330     return d->maxPort;
331 }
332
333 void QWebChannel::setMinPort(int p)
334 {
335     if (d->minPort == p)
336         return;
337     d->minPort = p;
338     d->initLater();
339 }
340
341 void QWebChannel::setMaxPort(int p)
342 {
343     if (d->maxPort == p)
344         return;
345     d->maxPort = p;
346     d->initLater();
347 }
348
349 void QWebChannel::onInitialized()
350 {
351     emit baseUrlChanged(d->baseUrl);
352 }
353
354 void QWebChannel::broadcast(const QString& id, const QString& data)
355 {
356     d->broadcast(id, data);
357 }
358
359
360 #include <qwebchannel.moc>