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 | #include <QtNetwork/qtnetwork-config.h> |
31 | |
32 | #ifndef QT_NO_HTTP |
33 | |
34 | #include <qabstractoauth.h> |
35 | #include <qoauthhttpserverreplyhandler.h> |
36 | #include "qabstractoauthreplyhandler_p.h" |
37 | |
38 | #include <private/qoauthhttpserverreplyhandler_p.h> |
39 | |
40 | #include <QtCore/qurl.h> |
41 | #include <QtCore/qurlquery.h> |
42 | #include <QtCore/qcoreapplication.h> |
43 | #include <QtCore/qloggingcategory.h> |
44 | |
45 | #include <QtNetwork/qtcpsocket.h> |
46 | #include <QtNetwork/qnetworkreply.h> |
47 | |
48 | #include <cctype> |
49 | #include <cstring> |
50 | #include <functional> |
51 | |
52 | QT_BEGIN_NAMESPACE |
53 | |
54 | QOAuthHttpServerReplyHandlerPrivate::QOAuthHttpServerReplyHandlerPrivate( |
55 | QOAuthHttpServerReplyHandler *p) : |
56 | text(QObject::tr(s: "Callback received. Feel free to close this page." )), q_ptr(p) |
57 | { |
58 | QObject::connect(sender: &httpServer, signal: &QTcpServer::newConnection, |
59 | slot: [this]() { _q_clientConnected(); }); |
60 | } |
61 | |
62 | QOAuthHttpServerReplyHandlerPrivate::~QOAuthHttpServerReplyHandlerPrivate() |
63 | { |
64 | if (httpServer.isListening()) |
65 | httpServer.close(); |
66 | } |
67 | |
68 | void QOAuthHttpServerReplyHandlerPrivate::_q_clientConnected() |
69 | { |
70 | QTcpSocket *socket = httpServer.nextPendingConnection(); |
71 | |
72 | QObject::connect(sender: socket, signal: &QTcpSocket::disconnected, receiver: socket, slot: &QTcpSocket::deleteLater); |
73 | QObject::connect(sender: socket, signal: &QTcpSocket::readyRead, |
74 | slot: [this, socket]() { _q_readData(socket); }); |
75 | } |
76 | |
77 | void QOAuthHttpServerReplyHandlerPrivate::_q_readData(QTcpSocket *socket) |
78 | { |
79 | if (!clients.contains(akey: socket)) |
80 | clients[socket].port = httpServer.serverPort(); |
81 | |
82 | QHttpRequest *request = &clients[socket]; |
83 | bool error = false; |
84 | |
85 | if (Q_LIKELY(request->state == QHttpRequest::State::ReadingMethod)) |
86 | if (Q_UNLIKELY(error = !request->readMethod(socket))) |
87 | qCWarning(lcReplyHandler, "Invalid Method" ); |
88 | |
89 | if (Q_LIKELY(!error && request->state == QHttpRequest::State::ReadingUrl)) |
90 | if (Q_UNLIKELY(error = !request->readUrl(socket))) |
91 | qCWarning(lcReplyHandler, "Invalid URL" ); |
92 | |
93 | if (Q_LIKELY(!error && request->state == QHttpRequest::State::ReadingStatus)) |
94 | if (Q_UNLIKELY(error = !request->readStatus(socket))) |
95 | qCWarning(lcReplyHandler, "Invalid Status" ); |
96 | |
97 | if (Q_LIKELY(!error && request->state == QHttpRequest::State::ReadingHeader)) |
98 | if (Q_UNLIKELY(error = !request->readHeader(socket))) |
99 | qCWarning(lcReplyHandler, "Invalid Header" ); |
100 | |
101 | if (error) { |
102 | socket->disconnectFromHost(); |
103 | clients.remove(akey: socket); |
104 | } else if (!request->url.isEmpty()) { |
105 | Q_ASSERT(request->state != QHttpRequest::State::ReadingUrl); |
106 | _q_answerClient(socket, url: request->url); |
107 | clients.remove(akey: socket); |
108 | } |
109 | } |
110 | |
111 | void QOAuthHttpServerReplyHandlerPrivate::_q_answerClient(QTcpSocket *socket, const QUrl &url) |
112 | { |
113 | Q_Q(QOAuthHttpServerReplyHandler); |
114 | if (!url.path().startsWith(s: QLatin1String("/" ) + path)) { |
115 | qCWarning(lcReplyHandler, "Invalid request: %s" , qPrintable(url.toString())); |
116 | } else { |
117 | QVariantMap receivedData; |
118 | const QUrlQuery query(url.query()); |
119 | const auto items = query.queryItems(); |
120 | for (auto it = items.begin(), end = items.end(); it != end; ++it) |
121 | receivedData.insert(akey: it->first, avalue: it->second); |
122 | Q_EMIT q->callbackReceived(values: receivedData); |
123 | |
124 | const QByteArray html = QByteArrayLiteral("<html><head><title>" ) + |
125 | qApp->applicationName().toUtf8() + |
126 | QByteArrayLiteral("</title></head><body>" ) + |
127 | text.toUtf8() + |
128 | QByteArrayLiteral("</body></html>" ); |
129 | |
130 | const QByteArray htmlSize = QString::number(html.size()).toUtf8(); |
131 | const QByteArray replyMessage = QByteArrayLiteral("HTTP/1.0 200 OK \r\n" |
132 | "Content-Type: text/html; " |
133 | "charset=\"utf-8\"\r\n" |
134 | "Content-Length: " ) + htmlSize + |
135 | QByteArrayLiteral("\r\n\r\n" ) + |
136 | html; |
137 | |
138 | socket->write(data: replyMessage); |
139 | } |
140 | socket->disconnectFromHost(); |
141 | } |
142 | |
143 | bool QOAuthHttpServerReplyHandlerPrivate::QHttpRequest::readMethod(QTcpSocket *socket) |
144 | { |
145 | bool finished = false; |
146 | while (socket->bytesAvailable() && !finished) { |
147 | const auto c = socket->read(maxlen: 1).at(i: 0); |
148 | if (std::isupper(c) && fragment.size() < 6) |
149 | fragment += c; |
150 | else |
151 | finished = true; |
152 | } |
153 | if (finished) { |
154 | if (fragment == "HEAD" ) |
155 | method = Method::Head; |
156 | else if (fragment == "GET" ) |
157 | method = Method::Get; |
158 | else if (fragment == "PUT" ) |
159 | method = Method::Put; |
160 | else if (fragment == "POST" ) |
161 | method = Method::Post; |
162 | else if (fragment == "DELETE" ) |
163 | method = Method::Delete; |
164 | else |
165 | qCWarning(lcReplyHandler, "Invalid operation %s" , fragment.data()); |
166 | |
167 | state = State::ReadingUrl; |
168 | fragment.clear(); |
169 | |
170 | return method != Method::Unknown; |
171 | } |
172 | return true; |
173 | } |
174 | |
175 | bool QOAuthHttpServerReplyHandlerPrivate::QHttpRequest::readUrl(QTcpSocket *socket) |
176 | { |
177 | bool finished = false; |
178 | while (socket->bytesAvailable() && !finished) { |
179 | const auto c = socket->read(maxlen: 1).at(i: 0); |
180 | if (std::isspace(c)) |
181 | finished = true; |
182 | else |
183 | fragment += c; |
184 | } |
185 | if (finished) { |
186 | if (!fragment.startsWith(c: "/" )) { |
187 | qCWarning(lcReplyHandler, "Invalid URL path %s" , fragment.constData()); |
188 | return false; |
189 | } |
190 | url.setUrl(QStringLiteral("http://127.0.0.1:" ) + QString::number(port) + |
191 | QString::fromUtf8(str: fragment)); |
192 | state = State::ReadingStatus; |
193 | if (!url.isValid()) { |
194 | qCWarning(lcReplyHandler, "Invalid URL %s" , fragment.constData()); |
195 | return false; |
196 | } |
197 | fragment.clear(); |
198 | return true; |
199 | } |
200 | return true; |
201 | } |
202 | |
203 | bool QOAuthHttpServerReplyHandlerPrivate::QHttpRequest::readStatus(QTcpSocket *socket) |
204 | { |
205 | bool finished = false; |
206 | while (socket->bytesAvailable() && !finished) { |
207 | fragment += socket->read(maxlen: 1); |
208 | if (fragment.endsWith(c: "\r\n" )) { |
209 | finished = true; |
210 | fragment.resize(size: fragment.size() - 2); |
211 | } |
212 | } |
213 | if (finished) { |
214 | if (!std::isdigit(fragment.at(i: fragment.size() - 3)) || |
215 | !std::isdigit(fragment.at(i: fragment.size() - 1))) { |
216 | qCWarning(lcReplyHandler, "Invalid version" ); |
217 | return false; |
218 | } |
219 | version = qMakePair(x: fragment.at(i: fragment.size() - 3) - '0', |
220 | y: fragment.at(i: fragment.size() - 1) - '0'); |
221 | state = State::ReadingHeader; |
222 | fragment.clear(); |
223 | } |
224 | return true; |
225 | } |
226 | |
227 | bool QOAuthHttpServerReplyHandlerPrivate::QHttpRequest::readHeader(QTcpSocket *socket) |
228 | { |
229 | while (socket->bytesAvailable()) { |
230 | fragment += socket->read(maxlen: 1); |
231 | if (fragment.endsWith(c: "\r\n" )) { |
232 | if (fragment == "\r\n" ) { |
233 | state = State::ReadingBody; |
234 | fragment.clear(); |
235 | return true; |
236 | } else { |
237 | fragment.chop(n: 2); |
238 | const int index = fragment.indexOf(c: ':'); |
239 | if (index == -1) |
240 | return false; |
241 | |
242 | const QByteArray key = fragment.mid(index: 0, len: index).trimmed(); |
243 | const QByteArray value = fragment.mid(index: index + 1).trimmed(); |
244 | headers.insert(akey: key, avalue: value); |
245 | fragment.clear(); |
246 | } |
247 | } |
248 | } |
249 | return false; |
250 | } |
251 | |
252 | QOAuthHttpServerReplyHandler::QOAuthHttpServerReplyHandler(QObject *parent) : |
253 | QOAuthHttpServerReplyHandler(QHostAddress::Any, 0, parent) |
254 | {} |
255 | |
256 | QOAuthHttpServerReplyHandler::QOAuthHttpServerReplyHandler(quint16 port, QObject *parent) : |
257 | QOAuthHttpServerReplyHandler(QHostAddress::Any, port, parent) |
258 | {} |
259 | |
260 | QOAuthHttpServerReplyHandler::QOAuthHttpServerReplyHandler(const QHostAddress &address, |
261 | quint16 port, QObject *parent) : |
262 | QOAuthOobReplyHandler(parent), |
263 | d_ptr(new QOAuthHttpServerReplyHandlerPrivate(this)) |
264 | { |
265 | listen(address, port); |
266 | } |
267 | |
268 | QOAuthHttpServerReplyHandler::~QOAuthHttpServerReplyHandler() |
269 | {} |
270 | |
271 | QString QOAuthHttpServerReplyHandler::callback() const |
272 | { |
273 | Q_D(const QOAuthHttpServerReplyHandler); |
274 | |
275 | Q_ASSERT(d->httpServer.isListening()); |
276 | const QUrl url(QString::fromLatin1(str: "http://127.0.0.1:%1/%2" ) |
277 | .arg(a: d->httpServer.serverPort()).arg(a: d->path)); |
278 | return url.toString(options: QUrl::EncodeDelimiters); |
279 | } |
280 | |
281 | QString QOAuthHttpServerReplyHandler::callbackPath() const |
282 | { |
283 | Q_D(const QOAuthHttpServerReplyHandler); |
284 | return d->path; |
285 | } |
286 | |
287 | void QOAuthHttpServerReplyHandler::setCallbackPath(const QString &path) |
288 | { |
289 | Q_D(QOAuthHttpServerReplyHandler); |
290 | |
291 | QString copy = path; |
292 | while (copy.startsWith(c: '/')) |
293 | copy = copy.mid(position: 1); |
294 | |
295 | d->path = copy; |
296 | } |
297 | |
298 | QString QOAuthHttpServerReplyHandler::callbackText() const |
299 | { |
300 | Q_D(const QOAuthHttpServerReplyHandler); |
301 | return d->text; |
302 | } |
303 | |
304 | void QOAuthHttpServerReplyHandler::setCallbackText(const QString &text) |
305 | { |
306 | Q_D(QOAuthHttpServerReplyHandler); |
307 | d->text = text; |
308 | } |
309 | |
310 | quint16 QOAuthHttpServerReplyHandler::port() const |
311 | { |
312 | Q_D(const QOAuthHttpServerReplyHandler); |
313 | return d->httpServer.serverPort(); |
314 | } |
315 | |
316 | bool QOAuthHttpServerReplyHandler::listen(const QHostAddress &address, quint16 port) |
317 | { |
318 | Q_D(QOAuthHttpServerReplyHandler); |
319 | return d->httpServer.listen(address, port); |
320 | } |
321 | |
322 | void QOAuthHttpServerReplyHandler::close() |
323 | { |
324 | Q_D(QOAuthHttpServerReplyHandler); |
325 | return d->httpServer.close(); |
326 | } |
327 | |
328 | bool QOAuthHttpServerReplyHandler::isListening() const |
329 | { |
330 | Q_D(const QOAuthHttpServerReplyHandler); |
331 | return d->httpServer.isListening(); |
332 | } |
333 | |
334 | QT_END_NAMESPACE |
335 | |
336 | #endif // QT_NO_HTTP |
337 | |