393 lines
13 KiB
C++
393 lines
13 KiB
C++
#include "tauth.h"
|
||
|
||
#include <QTcpServer>
|
||
#include <QTcpSocket>
|
||
#include <QUrl>
|
||
#include <QUrlQuery>
|
||
#include <QDesktopServices>
|
||
#include <QHostAddress>
|
||
#include <QByteArray>
|
||
#include <QDebug>
|
||
#include <QStringList>
|
||
#include <QTimer>
|
||
|
||
TAuth::TAuth(QObject *parent)
|
||
: QObject(parent)
|
||
, m_server(nullptr)
|
||
, m_clientSocket(nullptr)
|
||
, m_serverPort(0)
|
||
, m_autoOpenBrowser(true)
|
||
{
|
||
}
|
||
|
||
TAuth::~TAuth()
|
||
{
|
||
stopServer();
|
||
}
|
||
|
||
void TAuth::startServer(const QString &authUrl, bool openBrowser)
|
||
{
|
||
stopServer();
|
||
|
||
m_authUrl = authUrl;
|
||
m_autoOpenBrowser = openBrowser;
|
||
m_server = new QTcpServer(this);
|
||
|
||
// Пытаемся слушать на портах, начиная с 8080
|
||
QVector<int> portsToTry = {8089};
|
||
|
||
for (int port : portsToTry) {
|
||
if (m_server->listen(QHostAddress::LocalHost, port)) {
|
||
m_serverPort = port;
|
||
emit serverStarted(port);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!m_server->isListening()) {
|
||
emit errorOccurred("Не удалось запустить сервер на доступных портах");
|
||
delete m_server;
|
||
m_server = nullptr;
|
||
return;
|
||
}
|
||
|
||
connect(m_server, &QTcpServer::newConnection,
|
||
this, &TAuth::handleNewConnection);
|
||
|
||
// Открываем браузер только если указано
|
||
if (openBrowser && !authUrl.isEmpty()) {
|
||
QDesktopServices::openUrl(QUrl(authUrl));
|
||
}
|
||
}
|
||
|
||
void TAuth::stopServer()
|
||
{
|
||
if (m_server) {
|
||
m_server->close();
|
||
m_server->deleteLater();
|
||
m_server = nullptr;
|
||
}
|
||
|
||
if (m_clientSocket) {
|
||
m_clientSocket->disconnect();
|
||
m_clientSocket->close();
|
||
m_clientSocket->deleteLater();
|
||
m_clientSocket = nullptr;
|
||
}
|
||
|
||
m_serverPort = 0;
|
||
}
|
||
|
||
void TAuth::handleNewConnection()
|
||
{
|
||
if (!m_server) return;
|
||
|
||
m_clientSocket = m_server->nextPendingConnection();
|
||
if (m_clientSocket) {
|
||
connect(m_clientSocket, &QTcpSocket::readyRead,
|
||
this, &TAuth::readClientData);
|
||
connect(m_clientSocket, &QTcpSocket::disconnected,
|
||
this, [this]() {
|
||
// Удаляем сокет после отключения
|
||
if (m_clientSocket) {
|
||
m_clientSocket->deleteLater();
|
||
m_clientSocket = nullptr;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
void TAuth::readClientData()
|
||
{
|
||
if (!m_clientSocket || !m_clientSocket->bytesAvailable()) return;
|
||
|
||
QByteArray requestData = m_clientSocket->readAll();
|
||
QString request = QString::fromUtf8(requestData);
|
||
|
||
QStringList lines = request.split("\r\n");
|
||
if (lines.isEmpty()) return;
|
||
|
||
QString requestLine = lines[0];
|
||
QStringList parts = requestLine.split(" ");
|
||
|
||
if (parts.size() >= 2 && parts[0].toUpper() == "GET") {
|
||
QString document = parts[1];
|
||
|
||
// Проверяем, есть ли в запросе параметры (они могут быть в теле запроса)
|
||
// или в реферере
|
||
QString referer;
|
||
for (const QString &line : lines) {
|
||
if (line.startsWith("Referer:", Qt::CaseInsensitive)) {
|
||
referer = line.mid(9).trimmed();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Обрабатываем обычный redirect
|
||
if (document.startsWith("/redirect")) {
|
||
handleRedirectRequest(document, m_clientSocket);
|
||
}
|
||
// Обрабатываем корневой запрос
|
||
else if (document == "/" || document == "/?") {
|
||
// Проверяем, есть ли хэш в URL
|
||
QString fullUrl = document;
|
||
if (!referer.isEmpty() && referer.contains("#")) {
|
||
// Извлекаем хэш из реферера
|
||
QString hash = referer.mid(referer.indexOf("#") + 1);
|
||
fullUrl = "/redirect?" + hash;
|
||
handleRedirectRequest(fullUrl, m_clientSocket);
|
||
} else {
|
||
// Или проверяем, есть ли параметры в самом запросе
|
||
// (хэш не передается, но могут быть другие параметры)
|
||
QString params;
|
||
if (document.contains("#")) {
|
||
params = document.mid(document.indexOf("#") + 1);
|
||
fullUrl = "/redirect?" + params;
|
||
handleRedirectRequest(fullUrl, m_clientSocket);
|
||
} else {
|
||
handleRootRequest(m_clientSocket);
|
||
}
|
||
}
|
||
}
|
||
else if (document.startsWith("/da")) {
|
||
QString html =
|
||
"<!DOCTYPE html>\n<html>\n<head>\n"
|
||
" <title>Redirecting...</title>\n</head>\n<body>\n"
|
||
" <p>получаю код</p>\n<script>\n"
|
||
"var paragraph = window.location.href;\n"
|
||
"var urrl = paragraph.replace('localhost:8089/da','localhost:8089/redirect');\n"
|
||
"urrl = urrl.replace('#','?');\n"
|
||
"console.log(urrl);\n"
|
||
"window.location.href = urrl;\n"
|
||
"</script>\n</body>\n</html>";
|
||
sendResponse(m_clientSocket, html);
|
||
}
|
||
else {
|
||
// Показываем страницу с JavaScript для извлечения хэша
|
||
QString html =
|
||
"<!DOCTYPE html>\n<html>\n<head>\n"
|
||
" <title>Processing...</title>\n</head>\n<body>\n"
|
||
" <p>Обработка авторизации...</p>\n<script>\n"
|
||
"try {\n"
|
||
" // Проверяем, есть ли хэш в URL\n"
|
||
" var hash = window.location.hash;\n"
|
||
" if (hash) {\n"
|
||
" // Преобразуем # в ? для передачи на сервер\n"
|
||
" var params = hash.substring(1); // убираем #\n"
|
||
" // Перенаправляем на /redirect с параметрами\n"
|
||
" window.location.href = '/redirect?' + params;\n"
|
||
" } else {\n"
|
||
" document.body.innerHTML = '<h2>Нет данных авторизации</h2>';\n"
|
||
" }\n"
|
||
"} catch(e) {\n"
|
||
" document.body.innerHTML = '<h2>Ошибка: ' + e.message + '</h2>';\n"
|
||
"}\n"
|
||
"</script>\n</body>\n</html>";
|
||
sendResponse(m_clientSocket, html);
|
||
}
|
||
} else {
|
||
sendErrorResponse(m_clientSocket, "Invalid request method");
|
||
}
|
||
}
|
||
|
||
void TAuth::handleRootRequest(QTcpSocket *socket)
|
||
{
|
||
QString html =
|
||
"<!DOCTYPE html>\n<html>\n<head>\n"
|
||
" <title>Processing...</title>\n</head>\n<body>\n"
|
||
" <p>Обработка авторизации...</p>\n<script>\n"
|
||
"try {\n"
|
||
" var hash = window.location.hash;\n"
|
||
" if (hash) {\n"
|
||
" var params = hash.substring(1);\n"
|
||
" window.location.href = '/redirect?' + params;\n"
|
||
" } else {\n"
|
||
" document.body.innerHTML = '<h2>Нет данных авторизации в URL</h2>';\n"
|
||
" }\n"
|
||
"} catch(e) {\n"
|
||
" document.body.innerHTML = '<h2>Ошибка: ' + e.message + '</h2>';\n"
|
||
"}\n"
|
||
"</script>\n</body>\n</html>";
|
||
|
||
sendResponse(socket, html);
|
||
}
|
||
|
||
void TAuth::handleRedirectRequest(const QString &request, QTcpSocket *socket)
|
||
{
|
||
QString params;
|
||
if (request.contains('?')) {
|
||
params = request.mid(request.indexOf('?') + 1);
|
||
}
|
||
|
||
QString html;
|
||
QString token, code, expiresIn;
|
||
|
||
// Проверяем наличие access_token
|
||
if (params.contains("access_token=")) {
|
||
token = extractParam(params, "access_token");
|
||
expiresIn = extractExpiresIn(params);
|
||
|
||
// Формируем полный токен с временем жизни (если есть)
|
||
QString fullToken = token;
|
||
if (!expiresIn.isEmpty()) {
|
||
fullToken += "|" + expiresIn;
|
||
}
|
||
|
||
emit tokenReceived(fullToken);
|
||
|
||
html =
|
||
"<!DOCTYPE html>\n<html>\n<head>\n"
|
||
" <title>Success</title>\n</head>\n<body>\n"
|
||
"<h2>Authorization Successful!</h2>\n"
|
||
"<p>Token received. You can close this window.</p>\n"
|
||
"</body>\n</html>";
|
||
|
||
// Отправляем ответ клиенту
|
||
sendResponse(socket, html);
|
||
|
||
// Останавливаем сервер СРАЗУ
|
||
QTimer::singleShot(100, this, &TAuth::stopServer);
|
||
return;
|
||
}
|
||
// Проверяем наличие error_description
|
||
else if (params.contains("error_description=")) {
|
||
QString error = extractErrorDescription(params);
|
||
emit errorOccurred(error);
|
||
|
||
html =
|
||
QString("<!DOCTYPE html>\n<html>\n<head>\n"
|
||
" <title>Error</title>\n</head>\n<body>\n"
|
||
"<h2>Authorization Failed</h2>\n"
|
||
"<p>Error: %1</p>\n</body>\n</html>").arg(error);
|
||
}
|
||
// Проверяем наличие code (для OAuth code flow)
|
||
else if (params.contains("code=")) {
|
||
code = extractParam(params, "code");
|
||
emit codeReceived(code);
|
||
|
||
html =
|
||
"<!DOCTYPE html>\n<html>\n<head>\n"
|
||
" <title>Success</title>\n</head>\n<body>\n"
|
||
"<h2>Authorization Successful!</h2>\n"
|
||
"<p>Code received. You can close this window.</p>\n"
|
||
"</body>\n</html>";
|
||
|
||
// Отправляем ответ клиенту
|
||
sendResponse(socket, html);
|
||
|
||
// Останавливаем сервер СРАЗУ
|
||
QTimer::singleShot(100, this, &TAuth::stopServer);
|
||
return;
|
||
}
|
||
else {
|
||
html =
|
||
"<!DOCTYPE html>\n<html>\n<head>\n"
|
||
" <title>No Data</title>\n</head>\n<body>\n"
|
||
"<h2>No authorization data received</h2>\n"
|
||
"<p>Try again or check your authorization URL.</p>\n"
|
||
"</body>\n</html>";
|
||
sendResponse(socket, html);
|
||
|
||
// Останавливаем сервер через 5 секунд если нет данных
|
||
QTimer::singleShot(5000, this, &TAuth::stopServer);
|
||
return;
|
||
}
|
||
|
||
sendResponse(socket, html);
|
||
|
||
// Останавливаем сервер через 5 секунд для других случаев
|
||
QTimer::singleShot(5000, this, &TAuth::stopServer);
|
||
}
|
||
|
||
QString TAuth::extractParam(const QString ¶ms, const QString ¶mName)
|
||
{
|
||
QStringList paramList = params.split('&');
|
||
for (const QString ¶m : paramList) {
|
||
if (param.startsWith(paramName + "=")) {
|
||
QString value = param.mid(paramName.length() + 1);
|
||
// Декодируем URL-encoded значения
|
||
value = QUrl::fromPercentEncoding(value.toUtf8());
|
||
return value;
|
||
}
|
||
}
|
||
return QString();
|
||
}
|
||
|
||
QString TAuth::extractExpiresIn(const QString ¶ms)
|
||
{
|
||
return extractParam(params, "expires_in");
|
||
}
|
||
|
||
void TAuth::extractAndEmitToken(const QString ¶ms)
|
||
{
|
||
QString token = extractParam(params, "access_token");
|
||
QString expiresIn = extractExpiresIn(params);
|
||
|
||
if (!token.isEmpty()) {
|
||
QString fullToken = token;
|
||
if (!expiresIn.isEmpty()) {
|
||
fullToken += "|" + expiresIn;
|
||
}
|
||
emit tokenReceived(fullToken);
|
||
}
|
||
}
|
||
|
||
void TAuth::extractAndEmitError(const QString ¶ms)
|
||
{
|
||
QString error = extractErrorDescription(params);
|
||
if (!error.isEmpty()) {
|
||
emit errorOccurred(error);
|
||
}
|
||
}
|
||
|
||
void TAuth::extractAndEmitCode(const QString ¶ms)
|
||
{
|
||
QString code = extractParam(params, "code");
|
||
if (!code.isEmpty()) {
|
||
emit codeReceived(code);
|
||
}
|
||
}
|
||
|
||
QString TAuth::extractErrorDescription(const QString ¶ms)
|
||
{
|
||
QString error = extractParam(params, "error_description");
|
||
if (error.isEmpty()) {
|
||
error = extractParam(params, "error");
|
||
}
|
||
if (error.isEmpty()) {
|
||
error = "Unknown error";
|
||
}
|
||
return error;
|
||
}
|
||
|
||
void TAuth::sendResponse(QTcpSocket *socket, const QString &content)
|
||
{
|
||
if (!socket) return;
|
||
|
||
QString response =
|
||
"HTTP/1.1 200 OK\r\n"
|
||
"Content-Type: text/html; charset=utf-8\r\n"
|
||
"Content-Length: " + QString::number(content.toUtf8().size()) + "\r\n"
|
||
"Connection: close\r\n"
|
||
"\r\n" + content;
|
||
|
||
socket->write(response.toUtf8());
|
||
socket->flush();
|
||
}
|
||
|
||
void TAuth::sendErrorResponse(QTcpSocket *socket, const QString &error)
|
||
{
|
||
if (!socket) return;
|
||
|
||
QString content = "<html><body><h1>" + error + "</h1></body></html>";
|
||
QString response =
|
||
"HTTP/1.1 404 Not Found\r\n"
|
||
"Content-Type: text/html; charset=utf-8\r\n"
|
||
"Content-Length: " + QString::number(content.toUtf8().size()) + "\r\n"
|
||
"Connection: close\r\n"
|
||
"\r\n" + content;
|
||
|
||
socket->write(response.toUtf8());
|
||
socket->flush();
|
||
}
|