#include "httpserver.h" #include "ircclient.h" #include "connectiondata.h" #include "global.h" #include "version.h" #include #include #include #include #include #include #include HttpServer::HttpServer(const QString &address, quint16 port, const QString& logFolder, const QString& mainChannel, QObject *parent) : QObject(parent), m_TcpServer(new QTcpServer), m_mainChannel(mainChannel), m_logFolder(logFolder) { if (not m_TcpServer->listen(QHostAddress(address), port)) { throw std::runtime_error("HttpServer not binded at " + address.toStdString() + " : " + QString::number(port).toStdString()); } else { consoleLog(address + " : " + QString::number(port)); } connect (m_TcpServer, &QTcpServer::newConnection, this, &HttpServer::acceptor); } HttpServer::~HttpServer() { m_TcpServer->close(); m_TcpServer->deleteLater(); } void HttpServer::consoleLog(const QString &message) { qInfo().noquote() << "[WEBINTERFACE]" << message; } void HttpServer::acceptor() { QTcpSocket* socket = m_TcpServer->nextPendingConnection(); connect(socket, &QTcpSocket::readyRead, this, &HttpServer::reader); connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); } void HttpServer::reader() { QTcpSocket* socket = static_cast(sender()); QString request = socket->readAll(); QString urlPath = getRequestPath(request); // static files if (urlPath == "/favicon.ico") { QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader); if (eTag == HTTP_ACTUAL_ETAG) { socket->write(HEADER_304.toUtf8()); return; } QFile icon("://html/favicon.ico"); if (icon.open(QIODevice::ReadOnly)) { QByteArray file = icon.readAll(); icon.close(); QString header = HEADER_ICO; replaceTag(header, "SIZE", QString::number(file.size())); socket->write(header.toUtf8()); socket->write(file); } } else if (urlPath == "/style.css") { QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader); if (eTag == HTTP_ACTUAL_ETAG) { socket->write(HEADER_304.toUtf8()); return; } QFile css("://html/style.css"); if (css.open(QIODevice::ReadOnly)) { QByteArray file = css.readAll(); css.close(); QString header = HEADER_CSS; replaceTag(header, "SIZE", QString::number(file.size())); socket->write(header.toUtf8()); socket->write(file); } } else if (urlPath.endsWith(".svg")) { QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader); if (eTag == HTTP_ACTUAL_ETAG) { socket->write(HEADER_304.toUtf8()); return; } QFile svg("://html"+urlPath); if (svg.open(QIODevice::ReadOnly)) { QByteArray file = svg.readAll(); svg.close(); QString header = HEADER_SVG; replaceTag(header, "SIZE", QString::number(file.size())); socket->write(header.toUtf8()); socket->write(file); } else { socket->write(HEADER_404.toUtf8()); socket->write("

NOT FOUND

"); } } // dynamic page else { writeMainPage(socket, urlPath); } socket->disconnectFromHost(); } void HttpServer::ircBotFirstInfo(QString server, QStringList channels) { for (const auto &c: channels) { m_backendInfo[server][c] = QStringList(); } } void HttpServer::ircClientAction(QString server, QString channel, QStringList users) { if (server.isEmpty()) return; m_backendInfo[server][channel] = users; } void HttpServer::ircServerOnline(QString server, quint8 status) { if (server.isEmpty()) return; bool online = status; m_serversOnline[server] = online; } void HttpServer::ircBotNic(QString server, QString nickname) { m_botsNick[server] = nickname; } QString HttpServer::getRequestPath(const QString &req) { if (req.isEmpty()) return QString(); QString result(req); int begin = result.indexOf(' '); if (begin == -1) return QString(); result.remove(0, begin+1); int space = result.indexOf(' '); int size = result.size(); result.remove(space, size-space); result = QByteArray::fromPercentEncoding(result.toUtf8()); return result; } QString HttpServer::getWordFromPath(const QString &path) { QString result {path}; result.remove(QRegularExpression("\\?.*$")); // any actions like a ?toSearch= if (result.startsWith('/')) { result.remove(QRegularExpression("^/")); } result.remove(QRegularExpression("/.*$")); return result; } void HttpServer::writeErrorPage(QTcpSocket *socket) { socket->write(HEADER_404.toUtf8()); socket->write("404

NOT FOUND

"); } void HttpServer::replaceTag(QString &page, const QString &tag, const QString &payload) { page.replace("{{"+tag+"}}", payload); } void HttpServer::writeMainPage(QTcpSocket *socket, QString &urlPath) { QString searchRequest; int specSymbol = urlPath.indexOf('?'); // any actions like a ?toSearch= if (specSymbol != -1) { searchRequest = global::getValue(urlPath, "toSearch", global::eForWeb); urlPath.remove(specSymbol, urlPath.size()-specSymbol); } if (urlPath == "/") { urlPath += m_mainChannel; } QFile main("://html/main.html"); QString page; if (main.open(QIODevice::ReadOnly)) { page = main.readAll(); main.close(); } else { socket->write(HEADER_404.toUtf8()); socket->write("Critical error

NOT FOUND

Maybe it is compilation error

"); } QString server = getWordFromPath(urlPath); QDir fsPath(m_logFolder+server); if (not fsPath.exists()) { writeErrorPage(socket); return; } urlPath.remove(QRegularExpression("^.*/"+server)); QString channel = getWordFromPath(urlPath); channel.remove(QRegularExpression("\\?.*$")); if (channel.isEmpty()){ // First channel if not passed directly QDirIterator it(fsPath.path()); while (it.hasNext()) { channel = it.next(); if (channel.endsWith(".") or channel.endsWith("..")) continue; // QDir::NoDotAndNoDotDot not works! while(channel.contains('/')) { channel.remove(QRegularExpression("^.*/")); } break; } } if (not fsPath.cd(channel)) { writeErrorPage(socket); return; } QString originalServerName; for (const auto &s: m_backendInfo) { if (global::toLowerAndNoSpaces(s.first) == server) { originalServerName = s.first; } } QString originalChannelName; for (const auto &server: m_backendInfo) { for (const auto &channel_users: server.second) { if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) { originalChannelName = global::toLowerAndNoSpaces(channel_users.first); } } } urlPath.remove(QRegularExpression("^.*/"+channel)); QString year = getWordFromPath(urlPath); year.remove(QRegularExpression("\\?.*$")); QString month; QString day; if (not year.isEmpty() and fsPath.cd(year)) { urlPath.remove(QRegularExpression("^.*/"+year)); month = getWordFromPath(urlPath); month.remove(QRegularExpression("\\?.*$")); if (not month.isEmpty() and fsPath.cd(month)) { if (urlPath.startsWith("/"+month+"/")) { urlPath.remove(0,1); int pos = urlPath.indexOf('/'); if (pos != -1) { urlPath.remove(0,pos); day = getWordFromPath(urlPath); if (urlPath.endsWith(".txt")) { QFile plain(fsPath.path()+global::slash+day); if (plain.open(QIODevice::ReadOnly)) { QString header = HEADER_TEXT; QByteArray file = plain.readAll(); plain.close(); replaceTag(header, "SIZE", QString::number(file.size())); socket->write(header.toUtf8()); socket->write(file); } else { writeErrorPage(socket); } return; } else { if (not QFile::exists(fsPath.path()+global::slash+day+".txt")) { day.clear(); } } } } } else { month.clear(); } } else { year.clear(); } //// Left menu compilation QString htmlServersSectionS; for (const auto &s: m_backendInfo) { if (s.first.isEmpty()) continue; // empty server name? QString htmlServersSection = HTML_SERVER_SECTION; replaceTag(htmlServersSection, "SERVER_NAME", s.first); QString htmlChannelLineS; for (const auto &c: s.second) { QString htmlChannelLine; if (originalServerName == s.first and originalChannelName == c.first) { htmlChannelLine = HTML_SERVER_SECTION_CHANNEL_SELECTED; } else { htmlChannelLine = HTML_SERVER_SECTION_CHANNEL; } replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first); QString channelNameForUrl {c.first}; channelNameForUrl.remove('#'); QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl; if (not year.isEmpty()) { channelLink += "/" + year; if (not month.isEmpty()) { channelLink += "/" + month; if (not day.isEmpty()) { channelLink += "/" + day; } } } replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink); htmlChannelLineS += htmlChannelLine; } replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS); bool online {false}; for (const auto &srv: m_serversOnline) { if (srv.first == s.first) { online = srv.second; break; } } if (online) { replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER); } else { replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER); } htmlServersSectionS += htmlServersSection; } replaceTag(page, "SERVERS_SECTION", htmlServersSectionS); //// Main section header compilation if (m_backendInfo.size() > 1) { replaceTag(page, "PAGE_TITLE", originalChannelName + " ("+originalServerName+") | IRCaBot"); } else { replaceTag(page, "PAGE_TITLE", originalChannelName + " | IRCaBot"); } replaceTag(page, "MAIN_HEADER", originalChannelName); QString middlePath = "" + "/" + ""; if (not year.isEmpty()) { middlePath += "" + year + ""; if (not month.isEmpty()) { middlePath += "/" + month + ""; if (not day.isEmpty()) { middlePath += "/" + day; } } } int currentOnline = m_backendInfo[originalServerName][originalChannelName].size(); if (currentOnline > 0) currentOnline -= 1; replaceTag(page, "ONLINE", QString::number(currentOnline)); QString onlineUserS; for (const auto &user: m_backendInfo[originalServerName][originalChannelName]) { if (QRegularExpression("^.?"+m_botsNick[originalServerName]+"$").match(user).hasMatch()) { continue; } QString onlineUser = HTML_ONLINE_POINT; replaceTag(onlineUser, "NICKNAME", user); onlineUserS += onlineUser; } replaceTag(page, "ONLINE_LIST", onlineUserS); if (middlePath == "/") { replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName); } else if (month.isEmpty()) { replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName + "/" + year); } else { replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName + "/" + year + "/" + month); } //// Main section body compilation QString payloadBlock; // Search request if (not searchRequest.isEmpty()) { QRegularExpression userRgx(searchRequest, QRegularExpression::CaseInsensitiveOption); bool rgxIsValid = false; if (userRgx.isValid()) rgxIsValid = true; consoleLog("Search request (" + server + "): " + searchRequest); if (not day.isEmpty()) { middlePath.remove(QRegularExpression("/[0-9]{2}$")); } middlePath += " ("+searchRequest+")"; QStringList paths; QDirIterator it(fsPath.path()); while (it.hasNext()) { QString currentPath = it.next(); if (currentPath.endsWith(".") or currentPath.endsWith("..")) continue; QString logFolder = m_logFolder; #ifdef WIN32 logFolder.replace('\\', '/'); #endif QString server {currentPath}; // Folder wich is not server folder is ignored server.remove(QRegularExpression("^"+logFolder)); server.remove(QRegularExpression("/.*$")); bool serverIsOk = false; for (const auto &srv: m_backendInfo) { if (global::toLowerAndNoSpaces(srv.first) == server) { serverIsOk = true; break; } } if (not serverIsOk) continue; QString currentChannel {currentPath}; // Folder wich is not channel folder is ignored currentChannel.remove(QRegularExpression("^"+logFolder+"[^/]*/")); currentChannel.remove(QRegularExpression("/.*$")); bool channelIsOk = false; for (const auto &ch: m_backendInfo[originalServerName]) { QString searchChan {ch.first}; searchChan.remove('#'); if (searchChan == currentChannel) { channelIsOk = true; break; } } if (not channelIsOk) continue; paths.push_back(currentPath); } if (paths.isEmpty()) { payloadBlock = HTML_PAYLOAD_ERROR; replaceTag(payloadBlock, "ERROR_TITLE", "Not found"); replaceTag(payloadBlock, "ERROR_TEXT", ""); } else { QStringList matchedPaths; if (not month.isEmpty()) { for (const auto& path: paths) { if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(path).hasMatch()) continue; QFile file(path); if (not file.open(QIODevice::ReadOnly)) { consoleLog("Error! I can't open log file " + fsPath.path()); continue; } QString buffer {file.readAll()}; file.close(); if (rgxIsValid) { if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) { matchedPaths.push_back(path); } } else { if (buffer.contains(searchRequest, Qt::CaseInsensitive)) { matchedPaths.push_back(path); } } } } else if (month.isEmpty() and not year.isEmpty()){ for (const auto &p: paths) { QStringList slavePaths; QDirIterator it(p); while (it.hasNext()) { QString fileName = it.next(); if (fileName.endsWith(".") or fileName.endsWith("..")) continue; if (not QRegularExpression("\\.txt$").match(fileName).hasMatch()) continue; slavePaths.push_back(fileName); } for (const auto &path: slavePaths) { if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(path).hasMatch()) continue; QFile file(path); if (not file.open(QIODevice::ReadOnly)) { consoleLog("Error! I can't open log file " + fsPath.path()); continue; } QString buffer {file.readAll()}; file.close(); if (rgxIsValid) { if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) { matchedPaths.push_back(path); } } else { if (buffer.contains(searchRequest, Qt::CaseInsensitive)) { matchedPaths.push_back(path); } } } } } else { // root directory QStringList yearPaths; for (const auto& p: paths) { if (not QRegularExpression("/2[0-9]{3}$").match(p).hasMatch()) continue; yearPaths.push_back(p); } /* If you are reading this code, maybe you are crying now. * Sorry me, man (woman/fagot/etc). Maybe I'll refactor this hell in the future. * acetone. */ QStringList fileNameS; for (const auto& p: yearPaths) { QStringList slavePaths; QDirIterator it(p); while (it.hasNext()) { QString folderName = it.next(); if (folderName.endsWith(".") or folderName.endsWith("..")) continue; if (not QRegularExpression("/[0-9]{2}$").match(folderName).hasMatch()) continue; slavePaths.push_back(folderName); } for (const auto &path: slavePaths) { QDirIterator itMonth(path); while (itMonth.hasNext()) { QString fileName = itMonth.next(); if (fileName.endsWith(".") or fileName.endsWith("..")) continue; if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(fileName).hasMatch()) continue; fileNameS.push_back(fileName); } } } for (const auto& path: fileNameS) { QFile file(path); if (not file.open(QIODevice::ReadOnly)) { consoleLog("Error! I can't open log file " + fsPath.path()); continue; } QString buffer {file.readAll()}; file.close(); if (rgxIsValid) { if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) { matchedPaths.push_back(path); } } else { if (buffer.contains(searchRequest, Qt::CaseInsensitive)) { matchedPaths.push_back(path); } } } } if (matchedPaths.isEmpty()) { payloadBlock = HTML_PAYLOAD_ERROR; replaceTag(payloadBlock, "ERROR_TITLE", "Not found"); replaceTag(payloadBlock, "ERROR_TEXT", ""); } else { std::sort(matchedPaths.begin(), matchedPaths.end()); for (auto& point: matchedPaths) { QString logFolder {m_logFolder}; logFolder.remove(QRegularExpression(".$")); point.remove(logFolder); point.remove(QRegularExpression("\\.txt$")); QString finded = HTML_PAYLOAD_LIST_POINT_MESSAGE; replaceTag(finded, "POINT_LINK", point); point.remove(QRegularExpression("^.*"+channel)); replaceTag(finded, "POINT_CONTENT", point); payloadBlock += finded; } } } } // Simple log explorer else { if (year.isEmpty()) { // / QStringList folderNameS; QDirIterator it(fsPath.path()); while (it.hasNext()) { QString folderName = it.next(); if (folderName.endsWith(".") or folderName.endsWith("..")) continue; while(folderName.contains('/')) { folderName.remove(QRegularExpression("^.*/")); } folderName.remove(QRegularExpression("\\.txt$")); folderNameS << folderName; } if (not folderNameS.isEmpty()) { std::sort(folderNameS.begin(), folderNameS.end()); for (const auto &f: folderNameS) { QString onePoint = HTML_PAYLOAD_LIST_POINT_FOLDER; replaceTag(onePoint, "POINT_CONTENT", f); replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+f); payloadBlock += onePoint; } } } else if (not year.isEmpty() and month.isEmpty()) { // /YYYY QStringList folderNameS; QDirIterator it(fsPath.path()); while (it.hasNext()) { QString folderName = it.next(); if (folderName.endsWith(".") or folderName.endsWith("..")) continue; while(folderName.contains('/')) { folderName.remove(QRegularExpression("^.*/")); } folderName.remove(QRegularExpression("\\.txt$")); folderNameS << folderName; } if (not folderNameS.isEmpty()) { std::sort(folderNameS.begin(), folderNameS.end()); for (const auto &f: folderNameS) { QString onePoint = HTML_PAYLOAD_LIST_POINT_FOLDER; replaceTag(onePoint, "POINT_CONTENT", f); replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+year+"/"+f); payloadBlock += onePoint; } } } else if (not month.isEmpty() and day.isEmpty()) { // /YYYY/MM QStringList fileNameS; QDirIterator it(fsPath.path()); while (it.hasNext()) { QString fileName = it.next(); if (fileName.endsWith(".") or fileName.endsWith("..")) continue; // QDir::NoDotAndNoDotDot not works! while(fileName.contains('/')) { fileName.remove(QRegularExpression("^.*/")); } fileName.remove(QRegularExpression("\\.txt$")); fileNameS << fileName; } if (not fileNameS.isEmpty()) { std::sort(fileNameS.begin(), fileNameS.end()); for (const auto &a: fileNameS) { QString onePoint = HTML_PAYLOAD_LIST_POINT_MESSAGE; replaceTag(onePoint, "POINT_CONTENT", a); replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+year+"/"+month+"/"+a); payloadBlock += onePoint; } } } else if (not day.isEmpty()) { // /YYYY/MM/dd QFile file(fsPath.path()+global::slash+day+".txt"); if (not file.open(QIODevice::ReadOnly)) { consoleLog("Error! I can't open log file " + fsPath.path()); payloadBlock = HTML_PAYLOAD_ERROR; replaceTag(payloadBlock, "ERROR_TITLE", "Internal error"); replaceTag(payloadBlock, "ERROR_TEXT", "Requested log file openning failed"); } else { QString buffer = file.readLine(); while (not buffer.isEmpty()) { buffer.remove('\n'); buffer.remove('\r'); // perebzdel buffer.remove('\t'); // QString nick {buffer}; nick.remove(QRegularExpression("\\]\\s.*$")); nick.remove(QRegularExpression("^\\[")); if (nick.isEmpty()) continue; QString text {buffer}; text.remove(QRegularExpression("^\\[.*\\]\\s")); while (QRegularExpression("[^\\s]{100,10000}.*").match(text).hasMatch()) { int pos = text.indexOf(QRegularExpression("[^\\s]{50,10000}.*")); if (pos == -1) { consoleLog("Bug! HttpServer.cpp while (QRegularExpression(\"[^\\s]{50,10000}.*\").match(text).hasMatch())"); break; } text.insert(pos+25, ' '); } if (text.isEmpty()) continue; QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE; for (const auto &user: m_backendInfo[originalServerName][originalChannelName]) { if (QRegularExpression("^.?"+nick+"$").match(user).hasMatch()) { message.replace("
", "
"); break; } } replaceTag(message, "USERNAME", nick); replaceTag(message, "MESSAGE_TEXT", text); payloadBlock += message; buffer = file.readLine(); } file.close(); } } } replaceTag(page, "MIDDLE_PATH", middlePath); replaceTag(page, "PAYLOAD_BLOCK", payloadBlock); //// Footer replaceTag(page, "VERSION", IRCABOT_VERSION); replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR); QString mainHeader = HEADER_HTML; replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size())); socket->write(mainHeader.toUtf8()); socket->write(page.toUtf8()); }