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 | |
39 | class WebServer : public QTcpServer |
40 | { |
41 | public: |
42 | class HttpRequest { |
43 | friend class WebServer; |
44 | |
45 | quint16 port = 0; |
46 | enum class State { |
47 | ReadingMethod, |
48 | ReadingUrl, |
49 | ReadingStatus, |
50 | , |
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> ; |
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 | |
84 | private: |
85 | Handler handler; |
86 | |
87 | QMap<QTcpSocket *, HttpRequest> clients; |
88 | }; |
89 | |
90 | WebServer::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 | |
147 | QUrl 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 | |
153 | bool 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 | |
187 | bool 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 | |
214 | bool 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 | |
239 | bool WebServer::HttpRequest::(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 | |
264 | bool 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 | |