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
52QT_BEGIN_NAMESPACE
53
54QOAuthHttpServerReplyHandlerPrivate::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
62QOAuthHttpServerReplyHandlerPrivate::~QOAuthHttpServerReplyHandlerPrivate()
63{
64 if (httpServer.isListening())
65 httpServer.close();
66}
67
68void 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
77void 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
111void 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
143bool 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
175bool 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
203bool 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
227bool 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
252QOAuthHttpServerReplyHandler::QOAuthHttpServerReplyHandler(QObject *parent) :
253 QOAuthHttpServerReplyHandler(QHostAddress::Any, 0, parent)
254{}
255
256QOAuthHttpServerReplyHandler::QOAuthHttpServerReplyHandler(quint16 port, QObject *parent) :
257 QOAuthHttpServerReplyHandler(QHostAddress::Any, port, parent)
258{}
259
260QOAuthHttpServerReplyHandler::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
268QOAuthHttpServerReplyHandler::~QOAuthHttpServerReplyHandler()
269{}
270
271QString 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
281QString QOAuthHttpServerReplyHandler::callbackPath() const
282{
283 Q_D(const QOAuthHttpServerReplyHandler);
284 return d->path;
285}
286
287void 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
298QString QOAuthHttpServerReplyHandler::callbackText() const
299{
300 Q_D(const QOAuthHttpServerReplyHandler);
301 return d->text;
302}
303
304void QOAuthHttpServerReplyHandler::setCallbackText(const QString &text)
305{
306 Q_D(QOAuthHttpServerReplyHandler);
307 d->text = text;
308}
309
310quint16 QOAuthHttpServerReplyHandler::port() const
311{
312 Q_D(const QOAuthHttpServerReplyHandler);
313 return d->httpServer.serverPort();
314}
315
316bool QOAuthHttpServerReplyHandler::listen(const QHostAddress &address, quint16 port)
317{
318 Q_D(QOAuthHttpServerReplyHandler);
319 return d->httpServer.listen(address, port);
320}
321
322void QOAuthHttpServerReplyHandler::close()
323{
324 Q_D(QOAuthHttpServerReplyHandler);
325 return d->httpServer.close();
326}
327
328bool QOAuthHttpServerReplyHandler::isListening() const
329{
330 Q_D(const QOAuthHttpServerReplyHandler);
331 return d->httpServer.isListening();
332}
333
334QT_END_NAMESPACE
335
336#endif // QT_NO_HTTP
337

source code of qtnetworkauth/src/oauth/qoauthhttpserverreplyhandler.cpp