372 lines
12 KiB
C++
372 lines
12 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,
|
|
m_clientSocket, &QTcpSocket::deleteLater);
|
|
}
|
|
}
|
|
|
|
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>";
|
|
}
|
|
// Проверяем наличие 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>";
|
|
}
|
|
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);
|
|
return;
|
|
}
|
|
|
|
sendResponse(socket, html);
|
|
|
|
// Останавливаем сервер после обработки
|
|
QTimer::singleShot(1000, 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();
|
|
}
|