Remove "All rights reserved" line from license headers.
[profile/ivi/qtdeclarative.git] / tests / auto / shared / testhttpserver.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "testhttpserver.h"
43 #include <QTcpSocket>
44 #include <QDebug>
45 #include <QFile>
46 #include <QTimer>
47
48 /*!
49 \internal
50 \class TestHTTPServer
51 \brief provides a very, very basic HTTP server for testing.
52
53 Inside the test case, an instance of TestHTTPServer should be created, with the
54 appropriate port to listen on.  The server will listen on the localhost interface.
55
56 Directories to serve can then be added to server, which will be added as "roots".
57 Each root can be added as a Normal, Delay or Disconnect root.  Requests for files
58 within a Normal root are returned immediately.  Request for files within a Delay
59 root are delayed for 500ms, and then served.  Requests for files within a Disconnect
60 directory cause the server to disconnect immediately.  A request for a file that isn't
61 found in any root will return a 404 error.
62
63 If you have the following directory structure:
64
65 \code
66 disconnect/disconnectTest.qml
67 files/main.qml
68 files/Button.qml
69 files/content/WebView.qml
70 slowFiles/slowMain.qml
71 \endcode
72 it can be added like this:
73 \code
74 TestHTTPServer server(14445);
75 server.serveDirectory("disconnect", TestHTTPServer::Disconnect);
76 server.serveDirectory("files");
77 server.serveDirectory("slowFiles", TestHTTPServer::Delay);
78 \endcode
79
80 The following request urls will then result in the appropriate action:
81 \table
82 \header \o URL \o Action
83 \row \o http://localhost:14445/disconnectTest.qml \o Disconnection
84 \row \o http://localhost:14445/main.qml \o main.qml returned immediately
85 \row \o http://localhost:14445/Button.qml \o Button.qml returned immediately
86 \row \o http://localhost:14445/content/WebView.qml \o content/WebView.qml returned immediately
87 \row \o http://localhost:14445/slowMain.qml \o slowMain.qml returned after 500ms
88 \endtable
89 */
90 TestHTTPServer::TestHTTPServer(quint16 port)
91 : m_hasFailed(false)
92 {
93     QObject::connect(&server, SIGNAL(newConnection()), this, SLOT(newConnection()));
94
95     server.listen(QHostAddress::LocalHost, port);
96 }
97
98 bool TestHTTPServer::isValid() const
99 {
100     return server.isListening();
101 }
102
103 bool TestHTTPServer::serveDirectory(const QString &dir, Mode mode)
104 {
105     dirs.append(qMakePair(dir, mode));
106     return true;
107 }
108
109 /*
110    Add an alias, so that if filename is requested and does not exist,
111    alias may be returned.
112 */
113 void TestHTTPServer::addAlias(const QString &filename, const QString &alias)
114 {
115     aliases.insert(filename, alias);
116 }
117
118 void TestHTTPServer::addRedirect(const QString &filename, const QString &redirectName)
119 {
120     redirects.insert(filename, redirectName);
121 }
122
123 bool TestHTTPServer::wait(const QUrl &expect, const QUrl &reply, const QUrl &body)
124 {
125     m_hasFailed = false;
126
127     QFile expectFile(expect.toLocalFile());
128     if (!expectFile.open(QIODevice::ReadOnly)) return false;
129     
130     QFile replyFile(reply.toLocalFile());
131     if (!replyFile.open(QIODevice::ReadOnly)) return false;
132
133     bodyData = QByteArray();
134     if (body.isValid()) {
135         QFile bodyFile(body.toLocalFile());
136         if (!bodyFile.open(QIODevice::ReadOnly)) return false;
137         bodyData = bodyFile.readAll();
138     }
139
140     waitData = expectFile.readAll();
141     /*
142     while (waitData.endsWith('\n'))
143         waitData = waitData.left(waitData.count() - 1);
144         */
145
146     replyData = replyFile.readAll();
147
148     if (!replyData.endsWith('\n'))
149         replyData.append("\n");
150     replyData.append("Content-length: " + QByteArray::number(bodyData.length()));
151     replyData .append("\n\n");
152
153     for (int ii = 0; ii < replyData.count(); ++ii) {
154         if (replyData.at(ii) == '\n' && (!ii || replyData.at(ii - 1) != '\r')) {
155             replyData.insert(ii, '\r');
156             ++ii;
157         }
158     }
159     replyData.append(bodyData);
160
161     return true;
162 }
163
164 bool TestHTTPServer::hasFailed() const
165 {
166     return m_hasFailed;
167 }
168
169 void TestHTTPServer::newConnection()
170 {
171     QTcpSocket *socket = server.nextPendingConnection();
172     if (!socket) return;
173
174     if (!dirs.isEmpty())
175         dataCache.insert(socket, QByteArray());
176
177     QObject::connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
178     QObject::connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
179 }
180
181 void TestHTTPServer::disconnected()
182 {
183     QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
184     if (!socket) return;
185
186     dataCache.remove(socket);
187     for (int ii = 0; ii < toSend.count(); ++ii) {
188         if (toSend.at(ii).first == socket) {
189             toSend.removeAt(ii);
190             --ii;
191         }
192     }
193     socket->disconnect();
194     socket->deleteLater();
195 }
196
197 void TestHTTPServer::readyRead()
198 {
199     QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
200     if (!socket || socket->state() == QTcpSocket::ClosingState) return;
201
202     QByteArray ba = socket->readAll();
203
204     if (!dirs.isEmpty()) {
205         serveGET(socket, ba);
206         return;
207     }
208
209     if (m_hasFailed || waitData.isEmpty()) {
210         qWarning() << "TestHTTPServer: Unexpected data" << ba;
211         return;
212     }
213
214     for (int ii = 0; ii < ba.count(); ++ii) {
215         const char c = ba.at(ii);
216         if (c == '\r' && waitData.isEmpty())
217            continue;
218         else if (!waitData.isEmpty() && c == waitData.at(0))
219             waitData = waitData.mid(1);
220         else if (c == '\r')
221             continue;
222         else {
223             QByteArray data = ba.mid(ii);
224             qWarning() << "TestHTTPServer: Unexpected data" << data << "\nExpected: " << waitData;
225             m_hasFailed = true;
226             socket->disconnectFromHost();
227             return;
228         }
229     }
230
231     if (waitData.isEmpty()) {
232         socket->write(replyData);
233         socket->disconnectFromHost();
234     }
235 }
236
237 bool TestHTTPServer::reply(QTcpSocket *socket, const QByteArray &fileName)
238 {
239     if (redirects.contains(fileName)) {
240         QByteArray response = "HTTP/1.1 302 Found\r\nContent-length: 0\r\nContent-type: text/html; charset=UTF-8\r\nLocation: " + redirects[fileName].toUtf8() + "\r\n\r\n";
241         socket->write(response);
242         return true;
243     }
244
245     for (int ii = 0; ii < dirs.count(); ++ii) {
246         QString dir = dirs.at(ii).first;
247         Mode mode = dirs.at(ii).second;
248
249         QString dirFile = dir + QLatin1String("/") + QLatin1String(fileName);
250
251         if (!QFile::exists(dirFile)) {
252             if (aliases.contains(fileName))
253                 dirFile = dir + QLatin1String("/") + aliases.value(fileName);
254         }
255
256         QFile file(dirFile);
257         if (file.open(QIODevice::ReadOnly)) {
258
259             if (mode == Disconnect)
260                 return true;
261
262             QByteArray data = file.readAll();
263
264             QByteArray response = "HTTP/1.0 200 OK\r\nContent-type: text/html; charset=UTF-8\r\nContent-length: ";
265             response += QByteArray::number(data.count());
266             response += "\r\n\r\n";
267             response += data;
268
269             if (mode == Delay) {
270                 toSend.append(qMakePair(socket, response));
271                 QTimer::singleShot(500, this, SLOT(sendOne()));
272                 return false;
273             } else {
274                 socket->write(response);
275                 return true;
276             }
277         }
278     }
279
280
281     QByteArray response = "HTTP/1.0 404 Not found\r\nContent-type: text/html; charset=UTF-8\r\n\r\n";
282     socket->write(response);
283
284     return true;
285 }
286
287 void TestHTTPServer::sendOne()
288 {
289     if (!toSend.isEmpty()) {
290         toSend.first().first->write(toSend.first().second);
291         toSend.first().first->close();
292         toSend.removeFirst();
293     }
294 }
295
296 void TestHTTPServer::serveGET(QTcpSocket *socket, const QByteArray &data)
297 {
298     if (!dataCache.contains(socket))
299         return;
300
301     QByteArray total = dataCache[socket] + data;
302     dataCache[socket] = total;
303     
304     if (total.contains("\n\r\n")) {
305
306         bool close = true;
307
308         if (total.startsWith("GET /")) {
309
310             int space = total.indexOf(' ', 4);
311             if (space != -1) {
312
313                 QByteArray req = total.mid(5, space - 5);
314                 close = reply(socket, req);
315
316             }
317         }
318         dataCache.remove(socket);
319
320         if (close) 
321             socket->disconnectFromHost();
322     }
323 }
324