1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Network Auth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#ifndef WEBSERVER_H
31#define WEBSERVER_H
32
33#include <functional>
34#include <cctype>
35#include <QtCore/qcoreapplication.h>
36#include <QtNetwork/qtcpserver.h>
37#include <QTcpSocket>
38
39class WebServer : public QTcpServer
40{
41public:
42 class HttpRequest {
43 friend class WebServer;
44
45 quint16 port = 0;
46 enum class State {
47 ReadingMethod,
48 ReadingUrl,
49 ReadingStatus,
50 ReadingHeader,
51 ReadingBody,
52 AllDone
53 } state = State::ReadingMethod;
54 QByteArray fragment;
55 int bytesLeft = 0;
56
57 bool readMethod(QTcpSocket *socket);
58 bool readUrl(QTcpSocket *socket);
59 bool readStatus(QTcpSocket *socket);
60 bool readHeaders(QTcpSocket *socket);
61 bool readBody(QTcpSocket *socket);
62
63 public:
64 enum class Method {
65 Unknown,
66 Head,
67 Get,
68 Put,
69 Post,
70 Delete,
71 } method = Method::Unknown;
72 QUrl url;
73 QPair<quint8, quint8> version;
74 QMap<QByteArray, QByteArray> headers;
75 QByteArray body;
76 };
77
78 typedef std::function<void(const HttpRequest &request, QTcpSocket *socket)> Handler;
79
80 WebServer(Handler handler, QObject *parent = nullptr);
81
82 QUrl url(const QString &path);
83
84private:
85 Handler handler;
86
87 QMap<QTcpSocket *, HttpRequest> clients;
88};
89
90WebServer::WebServer(Handler handler, QObject *parent) :
91 QTcpServer(parent),
92 handler(handler)
93{
94 connect(sender: this, signal: &QTcpServer::newConnection, slot: [=]() {
95 auto socket = nextPendingConnection();
96 connect(sender: socket, signal: &QTcpSocket::disconnected, receiver: socket, slot: &QTcpSocket::deleteLater);
97 connect(sender: socket, signal: &QTcpSocket::readyRead, slot: [=]() {
98 if (!clients.contains(key: socket))
99 clients[socket].port = serverPort();
100
101 auto *request = &clients[socket];
102 auto ok = true;
103
104 while (socket->bytesAvailable()) {
105 if (Q_LIKELY(request->state == HttpRequest::State::ReadingMethod))
106 if (Q_UNLIKELY(!(ok = request->readMethod(socket))))
107 qWarning(msg: "Invalid Method");
108
109 if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingUrl))
110 if (Q_UNLIKELY(!(ok = request->readUrl(socket))))
111 qWarning(msg: "Invalid URL");
112
113 if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingStatus))
114 if (Q_UNLIKELY(!(ok = request->readStatus(socket))))
115 qWarning(msg: "Invalid Status");
116
117 if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingHeader))
118 if (Q_UNLIKELY(!(ok = request->readHeaders(socket))))
119 qWarning(msg: "Invalid Header");
120
121 if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingBody))
122 if (Q_UNLIKELY(!(ok = request->readBody(socket))))
123 qWarning(msg: "Invalid Body");
124 }
125 if (Q_UNLIKELY(!ok)) {
126 socket->disconnectFromHost();
127 clients.remove(key: socket);
128 } else if (Q_LIKELY(request->state == HttpRequest::State::AllDone)) {
129 Q_ASSERT(handler);
130 if (request->headers.contains(key: "Host")) {
131 const auto parts = request->headers["Host"].split(sep: ':');
132 request->url.setHost(host: parts.at(i: 0));
133 if (parts.size() == 2)
134 request->url.setPort(parts.at(i: 1).toUInt());
135 }
136 handler(*request, socket);
137 socket->disconnectFromHost();
138 clients.remove(key: socket);
139 }
140 });
141 });
142
143 const auto ok = listen(address: QHostAddress::LocalHost);
144 Q_ASSERT(ok);
145}
146
147QUrl WebServer::url(const QString &path)
148{
149 const QString format("http://127.0.0.1:%1%2");
150 return QUrl(format.arg(a: serverPort()).arg(a: path.startsWith(c: '/') ? path : "/" + path));
151}
152
153bool WebServer::HttpRequest::readMethod(QTcpSocket *socket)
154{
155 bool finished = false;
156 while (socket->bytesAvailable() && !finished) {
157 const auto c = socket->read(maxlen: 1).at(i: 0);
158 if (std::isspace(c))
159 finished = true;
160 else if (std::isupper(c) && fragment.size() < 8)
161 fragment += c;
162 else
163 return false;
164 }
165 if (finished) {
166 if (fragment == "HEAD")
167 method = Method::Head;
168 else if (fragment == "GET")
169 method = Method::Get;
170 else if (fragment == "PUT")
171 method = Method::Put;
172 else if (fragment == "POST")
173 method = Method::Post;
174 else if (fragment == "DELETE")
175 method = Method::Delete;
176 else
177 qWarning(msg: "Invalid operation %s", fragment.data());
178
179 state = State::ReadingUrl;
180 fragment.clear();
181
182 return method != Method::Unknown;
183 }
184 return true;
185}
186
187bool WebServer::HttpRequest::readUrl(QTcpSocket *socket)
188{
189 bool finished = false;
190 while (socket->bytesAvailable() && !finished) {
191 const auto c = socket->read(maxlen: 1).at(i: 0);
192 if (std::isspace(c))
193 finished = true;
194 else
195 fragment += c;
196 }
197 if (finished) {
198 if (!fragment.startsWith(c: "/")) {
199 qWarning(msg: "Invalid URL path %s", fragment.constData());
200 return false;
201 }
202 url.setUrl(QStringLiteral("http://127.0.0.1:") + QString::number(port) +
203 QString::fromUtf8(str: fragment));
204 state = State::ReadingStatus;
205 if (!url.isValid()) {
206 qWarning(msg: "Invalid URL %s", fragment.constData());
207 return false;
208 }
209 fragment.clear();
210 }
211 return true;
212}
213
214bool WebServer::HttpRequest::readStatus(QTcpSocket *socket)
215{
216 bool finished = false;
217 while (socket->bytesAvailable() && !finished) {
218 fragment += socket->read(maxlen: 1);
219 if (fragment.endsWith(c: "\r\n")) {
220 finished = true;
221 fragment.resize(size: fragment.size() - 2);
222 }
223 }
224 if (finished) {
225 if (!std::isdigit(fragment.at(i: fragment.size() - 3)) ||
226 fragment.at(i: fragment.size() - 2) != '.' ||
227 !std::isdigit(fragment.at(i: fragment.size() - 1))) {
228 qWarning(msg: "Invalid version");
229 return false;
230 }
231 version = qMakePair(x: fragment.at(i: fragment.size() - 3) - '0',
232 y: fragment.at(i: fragment.size() - 1) - '0');
233 state = State::ReadingHeader;
234 fragment.clear();
235 }
236 return true;
237}
238
239bool WebServer::HttpRequest::readHeaders(QTcpSocket *socket)
240{
241 while (socket->bytesAvailable()) {
242 fragment += socket->read(maxlen: 1);
243 if (fragment.endsWith(c: "\r\n")) {
244 if (fragment == "\r\n") {
245 state = State::ReadingBody;
246 fragment.clear();
247 return true;
248 } else {
249 fragment.chop(n: 2);
250 const int index = fragment.indexOf(c: ':');
251 if (index == -1)
252 return false;
253
254 const QByteArray key = fragment.mid(index: 0, len: index).trimmed();
255 const QByteArray value = fragment.mid(index: index + 1).trimmed();
256 headers.insert(key, value);
257 fragment.clear();
258 }
259 }
260 }
261 return true;
262}
263
264bool WebServer::HttpRequest::readBody(QTcpSocket *socket)
265{
266 if (headers.contains(key: "Content-Length")) {
267 bool conversionResult;
268 bytesLeft = headers["Content-Length"].toInt(ok: &conversionResult);
269 if (Q_UNLIKELY(!conversionResult))
270 return false;
271 fragment.resize(size: bytesLeft);
272 }
273 while (bytesLeft) {
274 int got = socket->read(data: &fragment.data()[fragment.size() - bytesLeft], maxlen: bytesLeft);
275 if (got < 0)
276 return false; // error
277 bytesLeft -= got;
278 if (bytesLeft)
279 qApp->processEvents();
280 }
281 fragment.swap(other&: body);
282 state = State::AllDone;
283 return true;
284}
285
286#endif // WEBSERVER_H
287

source code of qtnetworkauth/tests/auto/shared/webserver.h