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

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