335 lines
9.9 KiB
C++
335 lines
9.9 KiB
C++
// For license of this file, see <project-root-folder>/LICENSE.md.
|
|
|
|
#include "network-web/oauthhttphandler.h"
|
|
|
|
#include "definitions/definitions.h"
|
|
#include "miscellaneous/application.h"
|
|
|
|
#include <cctype>
|
|
|
|
#include <QTcpSocket>
|
|
#include <QUrlQuery>
|
|
|
|
OAuthHttpHandler::OAuthHttpHandler(const QString& success_text, QObject* parent)
|
|
: QObject(parent), m_successText(success_text) {
|
|
connect(&m_httpServer, &QTcpServer::newConnection, this, &OAuthHttpHandler::clientConnected);
|
|
setListenAddressPort(QString(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(OAUTH_REDIRECT_URI_PORT));
|
|
}
|
|
|
|
OAuthHttpHandler::~OAuthHttpHandler() {
|
|
if (m_httpServer.isListening()) {
|
|
qWarningNN << LOGSEC_OAUTH << "Redirection OAuth handler is listening. Stopping it now.";
|
|
m_httpServer.close();
|
|
}
|
|
}
|
|
|
|
bool OAuthHttpHandler::isListening() const {
|
|
return m_httpServer.isListening();
|
|
}
|
|
|
|
void OAuthHttpHandler::setListenAddressPort(const QString& full_uri) {
|
|
QUrl url = QUrl::fromUserInput(full_uri);
|
|
QHostAddress listen_address;
|
|
quint16 listen_port = quint16(url.port(80));
|
|
|
|
if (url.host() == QL1S("localhost")) {
|
|
listen_address = QHostAddress(QHostAddress::SpecialAddress::LocalHost);
|
|
}
|
|
else {
|
|
listen_address = QHostAddress(url.host());
|
|
}
|
|
|
|
if (listen_address == m_listenAddress && m_listenPort == url.port()) {
|
|
return;
|
|
}
|
|
|
|
m_listenAddress = listen_address;
|
|
m_listenPort = listen_port;
|
|
m_listenAddressPort = full_uri;
|
|
|
|
if (m_httpServer.isListening()) {
|
|
qWarningNN << LOGSEC_OAUTH << "Redirection OAuth handler is listening. Stopping it now.";
|
|
m_httpServer.close();
|
|
}
|
|
|
|
if (!m_httpServer.listen(m_listenAddress, m_listenPort)) {
|
|
qCriticalNN << LOGSEC_OAUTH
|
|
<< "OAuth redirect handler FAILED TO START TO LISTEN on address"
|
|
<< QUOTE_W_SPACE(m_listenAddress.toString())
|
|
<< "and port"
|
|
<< QUOTE_W_SPACE_DOT(m_listenPort);
|
|
}
|
|
else {
|
|
qDebugNN << LOGSEC_OAUTH
|
|
<< "OAuth redirect handler IS LISTENING on address"
|
|
<< QUOTE_W_SPACE(m_listenAddress.toString())
|
|
<< "and port"
|
|
<< QUOTE_W_SPACE_DOT(m_listenPort);
|
|
}
|
|
}
|
|
|
|
void OAuthHttpHandler::clientConnected() {
|
|
QTcpSocket* socket = m_httpServer.nextPendingConnection();
|
|
|
|
QObject::connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
|
|
QObject::connect(socket, &QTcpSocket::readyRead, [this, socket]() {
|
|
readReceivedData(socket);
|
|
});
|
|
}
|
|
|
|
void OAuthHttpHandler::handleRedirection(const QVariantMap& data) {
|
|
if (data.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
const QString error = data.value(QSL("error")).toString();
|
|
const QString code = data.value(QSL("code")).toString();
|
|
const QString received_state = data.value(QSL("state")).toString();
|
|
|
|
if (error.size() != 0) {
|
|
const QString uri = data.value(QSL("error_uri")).toString();
|
|
const QString description = data.value(QSL("error_description")).toString();
|
|
|
|
qCriticalNN << LOGSEC_OAUTH
|
|
<< "AuthenticationError: " << error << "(" << uri << "): " << description;
|
|
emit authRejected(description, received_state);
|
|
}
|
|
else if (code.isEmpty()) {
|
|
qCriticalNN << LOGSEC_OAUTH
|
|
<< "We did not receive authentication code.";
|
|
emit authRejected(QSL("Code not received"), received_state);
|
|
}
|
|
else if (received_state.isEmpty()) {
|
|
qCriticalNN << LOGSEC_OAUTH
|
|
<< "State not received.";
|
|
emit authRejected(QSL("State not received"), received_state);
|
|
}
|
|
else {
|
|
emit authGranted(code, received_state);
|
|
}
|
|
}
|
|
|
|
void OAuthHttpHandler::answerClient(QTcpSocket* socket, const QUrl& url) {
|
|
if (!url.path().remove(QL1C('/')).isEmpty()) {
|
|
qCriticalNN << LOGSEC_OAUTH << "Invalid request:" << QUOTE_W_SPACE_DOT(url.toString());
|
|
}
|
|
else {
|
|
QVariantMap received_data;
|
|
const QUrlQuery query(url.query());
|
|
const auto items = query.queryItems();
|
|
|
|
for (const auto& item : items) {
|
|
received_data.insert(item.first, item.second);
|
|
}
|
|
|
|
handleRedirection(received_data);
|
|
|
|
const QString html = QSL("<html><head><title>") +
|
|
qApp->applicationName() +
|
|
QSL("</title></head><body>") +
|
|
m_successText +
|
|
QSL("</body></html>");
|
|
const QByteArray html_utf = html.toUtf8();
|
|
const QByteArray html_size = QString::number(html_utf.size()).toLocal8Bit();
|
|
const QByteArray reply_message = QByteArrayLiteral("HTTP/1.0 200 OK \r\n"
|
|
"Content-Type: text/html; charset=\"utf-8\"\r\n"
|
|
"Content-Length: ") + html_size +
|
|
QByteArrayLiteral("\r\n\r\n") + html_utf;
|
|
|
|
socket->write(reply_message);
|
|
}
|
|
|
|
socket->disconnectFromHost();
|
|
}
|
|
|
|
void OAuthHttpHandler::readReceivedData(QTcpSocket* socket) {
|
|
if (!m_connectedClients.contains(socket)) {
|
|
m_connectedClients[socket].m_address = QSL("http://") + m_httpServer.serverAddress().toString();
|
|
m_connectedClients[socket].m_port = m_httpServer.serverPort();
|
|
}
|
|
|
|
QHttpRequest* request = &m_connectedClients[socket];
|
|
bool error = false;
|
|
|
|
if (Q_LIKELY(request->m_state == QHttpRequest::State::ReadingMethod)) {
|
|
if (Q_UNLIKELY(error = !request->readMethod(socket))) {
|
|
qWarningNN << LOGSEC_OAUTH << "Invalid method.";
|
|
}
|
|
}
|
|
|
|
if (Q_LIKELY(!error && request->m_state == QHttpRequest::State::ReadingUrl)) {
|
|
if (Q_UNLIKELY(error = !request->readUrl(socket))) {
|
|
qWarningNN << LOGSEC_OAUTH << "Invalid URL.";
|
|
}
|
|
}
|
|
|
|
if (Q_LIKELY(!error && request->m_state == QHttpRequest::State::ReadingStatus)) {
|
|
if (Q_UNLIKELY(error = !request->readStatus(socket))) {
|
|
qWarningNN << LOGSEC_OAUTH << "Invalid status.";
|
|
}
|
|
}
|
|
|
|
if (Q_LIKELY(!error && request->m_state == QHttpRequest::State::ReadingHeader)) {
|
|
if (Q_UNLIKELY(error = !request->readHeader(socket))) {
|
|
qWarningNN << LOGSEC_OAUTH << "Invalid header.";
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
socket->disconnectFromHost();
|
|
m_connectedClients.remove(socket);
|
|
}
|
|
else if (!request->m_url.isEmpty()) {
|
|
Q_ASSERT(request->m_state != QHttpRequest::State::ReadingUrl);
|
|
|
|
answerClient(socket, request->m_url);
|
|
m_connectedClients.remove(socket);
|
|
}
|
|
}
|
|
|
|
QHostAddress OAuthHttpHandler::listenAddress() const {
|
|
return m_listenAddress;
|
|
}
|
|
|
|
QString OAuthHttpHandler::listenAddressPort() const {
|
|
return m_listenAddressPort;
|
|
}
|
|
|
|
quint16 OAuthHttpHandler::listenPort() const {
|
|
return m_listenPort;
|
|
}
|
|
|
|
bool OAuthHttpHandler::QHttpRequest::readMethod(QTcpSocket* socket) {
|
|
bool finished = false;
|
|
|
|
while ((socket->bytesAvailable() != 0) && !finished) {
|
|
const auto c = socket->read(1).at(0);
|
|
|
|
if ((std::isupper(c) != 0) && m_fragment.size() < 6) {
|
|
m_fragment += c;
|
|
}
|
|
else {
|
|
finished = true;
|
|
}
|
|
}
|
|
|
|
if (finished) {
|
|
if (m_fragment == "HEAD") {
|
|
m_method = Method::Head;
|
|
}
|
|
else if (m_fragment == "GET") {
|
|
m_method = Method::Get;
|
|
}
|
|
else if (m_fragment == "PUT") {
|
|
m_method = Method::Put;
|
|
}
|
|
else if (m_fragment == "POST") {
|
|
m_method = Method::Post;
|
|
}
|
|
else if (m_fragment == "DELETE") {
|
|
m_method = Method::Delete;
|
|
}
|
|
else {
|
|
qWarningNN << LOGSEC_OAUTH << "Invalid operation:" << QUOTE_W_SPACE_DOT(m_fragment.data());
|
|
}
|
|
|
|
m_state = State::ReadingUrl;
|
|
m_fragment.clear();
|
|
|
|
return m_method != Method::Unknown;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OAuthHttpHandler::QHttpRequest::readUrl(QTcpSocket* socket) {
|
|
bool finished = false;
|
|
|
|
while ((socket->bytesAvailable() != 0) && !finished) {
|
|
const auto c = socket->read(1).at(0);
|
|
|
|
if (std::isspace(c) != 0) {
|
|
finished = true;
|
|
}
|
|
else {
|
|
m_fragment += c;
|
|
}
|
|
}
|
|
|
|
if (finished) {
|
|
if (!m_fragment.startsWith("/")) {
|
|
qWarningNN << LOGSEC_OAUTH << "Invalid URL path" << QUOTE_W_SPACE_DOT(m_fragment);
|
|
return false;
|
|
}
|
|
|
|
m_url.setUrl(m_address + QString::number(m_port) + QString::fromUtf8(m_fragment));
|
|
m_state = State::ReadingStatus;
|
|
|
|
if (!m_url.isValid()) {
|
|
qWarningNN << LOGSEC_OAUTH << "Invalid URL" << QUOTE_W_SPACE_DOT(m_fragment);
|
|
return false;
|
|
}
|
|
|
|
m_fragment.clear();
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OAuthHttpHandler::QHttpRequest::readStatus(QTcpSocket* socket) {
|
|
bool finished = false;
|
|
|
|
while ((socket->bytesAvailable() != 0) && !finished) {
|
|
m_fragment += socket->read(1);
|
|
|
|
if (m_fragment.endsWith("\r\n")) {
|
|
finished = true;
|
|
m_fragment.resize(m_fragment.size() - 2);
|
|
}
|
|
}
|
|
|
|
if (finished) {
|
|
if ((std::isdigit(m_fragment.at(m_fragment.size() - 3)) == 0) || (std::isdigit(m_fragment.at(m_fragment.size() - 1)) == 0)) {
|
|
qWarningNN << LOGSEC_OAUTH << "Invalid version";
|
|
return false;
|
|
}
|
|
|
|
m_version = qMakePair(m_fragment.at(m_fragment.size() - 3) - '0', m_fragment.at(m_fragment.size() - 1) - '0');
|
|
m_state = State::ReadingHeader;
|
|
m_fragment.clear();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OAuthHttpHandler::QHttpRequest::readHeader(QTcpSocket* socket) {
|
|
while (socket->bytesAvailable() != 0) {
|
|
m_fragment += socket->read(1);
|
|
|
|
if (m_fragment.endsWith("\r\n")) {
|
|
if (m_fragment == "\r\n") {
|
|
m_state = State::ReadingBody;
|
|
m_fragment.clear();
|
|
return true;
|
|
}
|
|
else {
|
|
m_fragment.chop(2);
|
|
const int index = m_fragment.indexOf(':');
|
|
|
|
if (index == -1) {
|
|
return false;
|
|
}
|
|
|
|
const QByteArray key = m_fragment.mid(0, index).trimmed();
|
|
const QByteArray value = m_fragment.mid(index + 1).trimmed();
|
|
|
|
m_headers.insert(key, value);
|
|
m_fragment.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|