#include "httpserver.h" #include "ircclient.h" #include "connectiondata.h" #include "global.h" #include "version.h" #include #include #include #include #include #include #include #include #include #include #include const char CRITICAL_ERROR[] {"Critical error

NOT FOUND

Maybe it's compile time error

"}; HttpServer::HttpServer(const QString &address, quint16 port, const QString& dataFolder, const QString& serviceName, const QString& serviceEmoji, bool ajaxIsDisabled, QObject *parent) : QObject(parent), m_TcpServer(new QTcpServer), m_serviceName(serviceName), m_dataFolder(dataFolder), m_ajaxIsDisabled(ajaxIsDisabled) { if (not m_TcpServer->listen(QHostAddress(address), port)) { throw std::runtime_error("HttpServer not binded at " + address.toStdString() + " : " + QString::number(port).toStdString()); } consoleLog(address + " : " + QString::number(port)); if (m_ajaxIsDisabled) { consoleLog("JavaScript on webpages removed and AJAX disabled!"); } if (not QFile::exists(m_dataFolder+"main_page.txt")) { QFile mp(m_dataFolder+"main_page.txt"); if (mp.open(QIODevice::WriteOnly)) { mp.write("# Main page file.\n" "# HTML is supported. For line breaks, use
or

.\n\n" "
\n" "# Your images from \"" + m_dataFolder.toUtf8() + "custom_images\" must have URL \"/~images/\"\n" " \n" "
\n"); mp.close(); } else { throw std::runtime_error("main_page.txt not exist and creating failed"); } } QDir dir {m_dataFolder}; if (not dir.cd("custom_images")) { if (dir.mkdir("custom_images")) { dir.cd("custom_images"); QFile examplePng(dir.path()+global::slash+"example.png"); if (examplePng.open(QIODevice::WriteOnly)) { QFile nativePng("://html/custom_img_example.png"); if (nativePng.open(QIODevice::ReadOnly)) { examplePng.write(nativePng.readAll()); nativePng.close(); examplePng.close(); } } } else { consoleLog("Creating folder \"custom_images\" failed"); } } m_serviceButton = HTML_LEFT_MENU_MAIN_POINT; replaceTag(m_serviceButton, "EMOJI", serviceEmoji); replaceTag(m_serviceButton, "SERVICE_NAME", serviceName); connect (m_TcpServer, &QTcpServer::newConnection, this, &HttpServer::acceptor); } HttpServer::~HttpServer() { m_TcpServer->close(); m_TcpServer->deleteLater(); } QString HttpServer::convertToClickableLink(const QString &httpLine) { QString result; if (not httpLine.contains(QRegularExpression("http.?://"))) return result; QString displayedName {httpLine}; displayedName.remove(QRegularExpression("http.?://(www\\.)?")); displayedName.remove(QRegularExpression("/$")); result = " " + displayedName + " "; return result; } std::pair HttpServer::splitUserNameAndMessage(const QString &rawLine) { std::pair result; QString nick {rawLine}; nick.remove(QRegularExpression("\\]\\s.*$")); nick.remove(QRegularExpression("^\\[")); if (nick.isEmpty()) { return result; } nick = nick.toHtmlEscaped(); // long nicks if (nick.size() > MAX_NICKNAME_LENGTH_WITHOUT_WBR) { int lastWbr = 0; for (int i = 0; i < nick.size(); i++) { if (i-lastWbr > MAX_NICKNAME_LENGTH_WITHOUT_WBR) { nick.insert(i, ""); lastWbr = i; } } } QString text {rawLine}; text.remove(QRegularExpression("^\\[[^\\s]*\\]\\s")); if (text.isEmpty()) { return result; } text = text.toHtmlEscaped(); // http links bool linksFound {false}; while (QRegularExpression("(^|\\s)http.?://").match(text).hasMatch()) { if (not linksFound) linksFound = true; int pos = text.indexOf(QRegularExpression("(^|\\s)http.?://")); if (pos == -1) { consoleLog("Bug! HttpServer.cpp while (QRegularExpression(\"(^|\\s)http.?://\").match(text).hasMatch())"); break; } QString rawLink {text}; rawLink.remove(0, pos); if (rawLink.startsWith(' ')) { rawLink.remove(0,1); } int space = rawLink.indexOf(' '); if (space > 0) { rawLink.remove(space, rawLink.size()-space); } text.replace(rawLink, convertToClickableLink(rawLink)); } // long lines int space = 0; bool nbTag = false; // For safe HTML tags like a ⁢ via ! bool isHref = false; for (int i = 0; i < text.size(); i++) { if (text[i] == ' ') { space = i; if (isHref) { isHref = false; } else { if (text.indexOf("href=\"http", i+1) == i+1) { isHref = true; } } } if (nbTag and text[i-1] == ';') { nbTag = false; } if (text.indexOf(QRegularExpression("(\\&|\\<|\\>|\\").*"), i) == i) { nbTag = true; } if (not isHref and i-space > MAX_MESSAGE_LENGTH_WITHOUT_WBR and not nbTag) { text.insert(i, ""); space = i; } } if (linksFound) text.replace(" ", ""); // delete whitespace in links end result.first = nick; result.second = text; return result; } void HttpServer::consoleLog(const QString &message) { qInfo().noquote() << "[WEB_UI]" << message; } void HttpServer::debugLog(const QString &req) { QFile log(m_dataFolder + "debug.log"); if (log.open(QIODevice::WriteOnly | QIODevice::Append)) { log.write(QDateTime::currentDateTime().toString().toUtf8() + ":\n" + req.toUtf8() + "\n\n"); log.close(); } } 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->read(BUFFER_SIZE); if (not request.startsWith("GET") and not request.startsWith("HEAD")) { if (socket->isOpen()) { socket->write("Your request has been rejected!\n"); socket->disconnectFromHost(); } return; } bool isHeadRequest = false; if (request.startsWith("HEAD ")) { isHeadRequest = true; } QString urlPath = getRequestPath(request); if (urlPath == "/favicon.ico") { QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader); if (eTag == HTTP_ACTUAL_ETAG) { if (socket->isOpen()) socket->write(HEADER_304.toUtf8()); } else { 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())); if (socket->isOpen()) { socket->write(header.toUtf8()); if (not isHeadRequest) socket->write(file); } } } } else if (urlPath == "/") { writeMainPage(socket, isHeadRequest); } else if (urlPath == "/newmessage.mp3" and not m_ajaxIsDisabled) { QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader); if (eTag == HTTP_ACTUAL_ETAG) { if (socket->isOpen()) socket->write(HEADER_304.toUtf8()); } else { QFile mp3("://html/newmessage.mp3"); if (mp3.open(QIODevice::ReadOnly)) { QByteArray file = mp3.readAll(); mp3.close(); QString header = HEADER_MP3; replaceTag(header, "SIZE", QString::number(file.size())); if (socket->isOpen()) { socket->write(header.toUtf8()); if (not isHeadRequest) socket->write(file); } } } } else if (urlPath == "/style.css") { QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader); if (eTag == HTTP_ACTUAL_ETAG) { if (socket->isOpen()) socket->write(HEADER_304.toUtf8()); } else { 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())); if (socket->isOpen()) { socket->write(header.toUtf8()); if (not isHeadRequest) socket->write(file); } } } } else if (urlPath.endsWith(".svg")) { QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader); if (eTag == HTTP_ACTUAL_ETAG) { if (socket->isOpen()) socket->write(HEADER_304.toUtf8()); } else { 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())); if (socket->isOpen()) { socket->write(header.toUtf8()); if (not isHeadRequest) socket->write(file); } } else { if (socket->isOpen()) { socket->write(HEADER_404.toUtf8()); if (not isHeadRequest) writeErrorPage(socket); } } } } else if (urlPath == "/realtimechat.js" and not m_ajaxIsDisabled) { QFile js("://html/realtimechat.js"); if (js.open(QIODevice::ReadOnly)) { QByteArray file = js.readAll(); js.close(); QString header = HEADER_JS; replaceTag(header, "SIZE", QString::number(file.size())); if (socket->isOpen()) { socket->write(header.toUtf8()); if (not isHeadRequest) socket->write(file); } } } else if (urlPath.startsWith("/ajax/")) { if (m_ajaxIsDisabled) { writeErrorJson(socket, "AJAX disabled"); } else { writeAjaxAnswer(socket, urlPath, isHeadRequest); } } else if (urlPath.startsWith("/realtimereadingchat/")) { if (m_ajaxIsDisabled) { writeErrorPage(socket, "DISABLED" "

No JS, no AJAX, no real time reading!

"); } else { writeRealTimeChatPage(socket, urlPath, isHeadRequest); } } else if (urlPath.startsWith("/~images/")) { writeCustomPicture(socket, urlPath, isHeadRequest); } else { writeRegularPage(socket, urlPath, isHeadRequest); } socket->disconnectFromHost(); } void HttpServer::ircBotFirstInfo(QString server, QStringList channels) { for (const auto &c: channels) { if (c.isEmpty()) continue; m_servers[server][c] = QStringList(); } } void HttpServer::ircUsersOnline(QString server, QString channel, QStringList users) { if (server.isEmpty()) return; if (channel.isEmpty()) return; QStringList sortedNicknames; QStringList ownersNicks; QStringList operNicks; QStringList halfopNicks; QStringList adminNicks; QStringList voicedNicks; QStringList plainNicks; for (auto rawOneNick: users) { rawOneNick = rawOneNick.toHtmlEscaped(); if (rawOneNick.startsWith('~')) { ownersNicks.push_back(rawOneNick); } else if (rawOneNick.startsWith('@')) { operNicks.push_back(rawOneNick); } else if (rawOneNick.startsWith('%')) { halfopNicks.push_back(rawOneNick); } else if (rawOneNick.startsWith('&')) { adminNicks.push_back(rawOneNick); } else if (rawOneNick.startsWith('+')) { voicedNicks.push_back(rawOneNick); } else { plainNicks.push_back(rawOneNick); } } if (not ownersNicks.isEmpty()) { std::sort(ownersNicks.begin(), ownersNicks.end()); sortedNicknames += ownersNicks; } if (not operNicks.isEmpty()) { std::sort(operNicks.begin(), operNicks.end()); sortedNicknames += operNicks; } if (not halfopNicks.isEmpty()) { std::sort(halfopNicks.begin(), halfopNicks.end()); sortedNicknames += halfopNicks; } if (not adminNicks.isEmpty()) { std::sort(adminNicks.begin(), adminNicks.end()); sortedNicknames += adminNicks; } if (not voicedNicks.isEmpty()) { std::sort(voicedNicks.begin(), voicedNicks.end()); sortedNicknames += voicedNicks; } if (not plainNicks.isEmpty()) { std::sort(plainNicks.begin(), plainNicks.end()); sortedNicknames += plainNicks; } sortedNicknames.removeAll(""); m_servers[server][channel] = sortedNicknames; } void HttpServer::ircChannelTopic(QString server, QString channel, QString topic) { m_channelsTopic[server][channel] = topic; } void HttpServer::ircServerOnline(QString server, quint8 status) { if (server.isEmpty()) return; bool online = status; m_serversOnline[server] = online; } void HttpServer::ircBotNick(QString server, QString nickname) { m_botNick[server] = nickname; } void HttpServer::ircMessageCache(QString server, QString channel, QString nick, QString text) { QString channelId {server+channel}; if (not m_messageCache.contains(channelId)) return; // remove timed out session if (QDateTime::currentMSecsSinceEpoch() - MSECS_TO_AUTOREMOVE_MESSAGES_FROM_BUFFER > m_messageCache[channelId]->getLastPing()) { consoleLog("Message caching disabled for "+server+"/#"+channel+". No active reader."); m_messageCache.remove(channelId); return; } else { auto processedMsg {splitUserNameAndMessage("["+nick+"] " + text)}; m_messageCache[channelId]->saveNewMessage(processedMsg.first, processedMsg.second); } } 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::writeMainPage(QTcpSocket *socket, bool isHeadRequest) { auto renderStart = QDateTime::currentMSecsSinceEpoch(); QFile main("://html/main.html"); QString page; if (main.open(QIODevice::ReadOnly)) { page = main.readAll(); main.close(); } else { if (socket->isOpen()) { socket->write(HEADER_404.toUtf8()); if (not isHeadRequest) socket->write(CRITICAL_ERROR); } } replaceTag(page, "PAGE_TITLE", m_serviceName + " | IRC logger"); //// Left menu compilation QString htmlServersSectionS; for (const auto &s: m_servers) { if (s.first.isEmpty()) continue; // empty server name? QString htmlServersSection = HTML_SERVER_SECTION; replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+s.first); replaceTag(htmlServersSection, "SERVER_NAME", s.first); QString htmlChannelLineS; for (const auto &c: s.second) { QString htmlChannelLine; htmlChannelLine = HTML_SERVER_SECTION_CHANNEL; replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first); QString channelNameForUrl {c.first}; channelNameForUrl.remove('#'); QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl; 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; } QString serviceButtonSelected {m_serviceButton}; serviceButtonSelected.replace("class=\"left_menu__mainitem\"", "class=\"left_menu__mainitem\" style=\"opacity: 1\""); htmlServersSectionS.push_front(serviceButtonSelected); replaceTag(page, "SERVERS_SECTION", htmlServersSectionS); page.remove(QRegularExpression("
.*", QRegularExpression::DotMatchesEverythingOption)); QString payloadBlock = HTML_PAYLOAD_ABOUT; payloadBlock.remove("{{ABOUT_TITLE}}
\n"); QString aboutBlock; QFile mp(m_dataFolder+global::slash+"main_page.txt"); if (mp.open(QIODevice::ReadOnly)) { QString rbuffer = mp.readLine(); while (not rbuffer.isEmpty()) { if (rbuffer.startsWith('#')) { rbuffer = mp.readLine(); continue; } removeBrakelineSymbols(rbuffer); if (not rbuffer.isEmpty()) { aboutBlock += rbuffer; } rbuffer = mp.readLine(); } } else { aboutBlock = "No information provided"; } replaceTag(payloadBlock, "ABOUT_TEXT", aboutBlock); replaceTag(page, "PAYLOAD_BLOCK", payloadBlock); //// Footer replaceTag(page, "VERSION", IRCABOT_VERSION); replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR); //// Finish replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart)); QString mainHeader = HEADER_HTML; replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size())); if (socket->isOpen()) { socket->write(mainHeader.toUtf8()); if (not isHeadRequest) socket->write(page.toUtf8()); } } void HttpServer::writeCustomPicture(QTcpSocket *socket, QString &urlPath, bool isHeadRequest) { QString fileName {urlPath}; fileName.remove(QRegularExpression("^/~images/")); QFile f {m_dataFolder+global::slash+"custom_images"+global::slash+fileName}; if (not f.exists()) { writeErrorPage(socket, fileName + "

IMAGE NOT FOUND

"); return; } QByteArray file; if (f.open(QIODevice::ReadOnly)) { file = f.readAll(); f.close(); } else { writeErrorPage(socket, "FORBIDDEN"); return; } QString picHeader = HEADER_IMG; QString fileType {fileName}; fileType.remove(QRegularExpression(".*\\.")); replaceTag(picHeader, "TYPE", fileType); replaceTag(picHeader, "SIZE", QString::number(file.size())); if (socket->isOpen()) { socket->write(picHeader.toUtf8()); if (not isHeadRequest) socket->write(file); } } void HttpServer::writeErrorPage(QTcpSocket *socket, const QString& text) { if (socket->isOpen()) { socket->write(HEADER_404.toUtf8()); socket->write("404

"+text.toUtf8()+"

"); } } void HttpServer::writeErrorJson(QTcpSocket * socket, const QString &text) { QString header = HEADER_JSON; QByteArray body { "{\"status\": false, \"message\": \"" + text.toUtf8() + "\"}" }; replaceTag(header, "SIZE", QString::number(body.size())); socket->write(header.toUtf8()); socket->write(body); } void HttpServer::removeBrakelineSymbols(QString &line) { line.remove('\r'); line.remove('\n'); line.remove('\t'); } inline void HttpServer::replaceTag(QString &page, const QString &tag, const QString &payload) { page.replace("{{"+tag+"}}", payload); } void HttpServer::writeRegularPage(QTcpSocket *socket, QString &urlPath, bool isHeadRequest) { auto renderStart = QDateTime::currentMSecsSinceEpoch(); QString searchRequest; bool isRegexp {false}; int specSymbol = urlPath.indexOf('?'); // any actions like a ?toSearch= if (specSymbol != -1) { searchRequest = global::getValue(urlPath, "toSearch", global::eForWeb); isRegexp = global::getValue(urlPath, "isRegexp", global::eForWeb) == "on"; urlPath.remove(specSymbol, urlPath.size()-specSymbol); } QString server = getWordFromPath(urlPath); QDir fsPath(m_dataFolder+server); if (not fsPath.exists()) { if (isHeadRequest) { if (socket->isOpen()) socket->write(HEADER_404.toUtf8()); } else { writeErrorPage(socket, "REQUESTED SERVER LOG NOT EXIST"); } return; } urlPath.remove(QRegularExpression("^.*/"+server)); QString channel = getWordFromPath(urlPath); channel.remove(QRegularExpression("\\?.*$")); if (channel.isEmpty()) { writeAboutServerPage(socket, server, isHeadRequest); return; } if (not fsPath.cd(channel)) { if (isHeadRequest) { if (socket->isOpen()) socket->write(HEADER_404.toUtf8()); } else { writeErrorPage(socket, "REQUESTED CHANNEL LOG NOT EXIST"); } return; } QString originalServerName; for (const auto &s: m_servers) { if (global::toLowerAndNoSpaces(s.first) == server) { originalServerName = s.first; } } QString originalChannelName; for (const auto &server: m_servers) { 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())); if (socket->isOpen()) { socket->write(header.toUtf8()); if (not isHeadRequest) socket->write(file); } } else { if (isHeadRequest) { if (socket->isOpen()) socket->write(HEADER_404.toUtf8()); } else { writeErrorPage(socket, "FILE OPENING FAILED"); } } return; } else { if (not QFile::exists(fsPath.path()+global::slash+day+".txt")) { day.clear(); } } } } } else { month.clear(); } } else { year.clear(); } QFile main("://html/main.html"); QString page; if (main.open(QIODevice::ReadOnly)) { page = main.readAll(); main.close(); } else { if (socket->isOpen()) { socket->write(HEADER_404.toUtf8()); if (not isHeadRequest) socket->write(CRITICAL_ERROR); } } if (isRegexp) { page.replace("", ""); } //// Left menu compilation QString htmlServersSectionS; for (const auto &s: m_servers) { if (s.first.isEmpty()) continue; // empty server name? QString htmlServersSection = HTML_SERVER_SECTION; replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+s.first); 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); } if (s.first == originalServerName) { htmlServersSectionS.push_front(htmlServersSection); } else { htmlServersSectionS += htmlServersSection; } } htmlServersSectionS.push_front(m_serviceButton); replaceTag(page, "SERVERS_SECTION", htmlServersSectionS); //// Main section header compilation QString& topic = m_channelsTopic[originalServerName][originalChannelName]; topic = topic.replace('\"', """); QString titlePostfix = " | " + m_serviceName; if (not topic.isEmpty()) { titlePostfix.push_front(" | " + topic); } if (m_servers.size() > 1) { replaceTag(page, "PAGE_TITLE", originalChannelName + " ("+originalServerName+")" + titlePostfix); } else { replaceTag(page, "PAGE_TITLE", originalChannelName + titlePostfix); } replaceTag(page, "CHANNEL_TOPIC", topic); replaceTag(page, "MAIN_HEADER", originalChannelName); if (m_ajaxIsDisabled) { page.remove(""); } else { replaceTag(page, "REALTIME_LINK", "/realtimereadingchat/"+server+"/"+channel); replaceTag(page, "AIRPLAIN_TITLE", "Read in real time"); } QString middlePath = "" + "/" + ""; if (not year.isEmpty()) { middlePath += "" + year + ""; if (not month.isEmpty()) { middlePath += "/" + month + ""; } } QString arrows = HTML_PAYLOAD_ADDITIONAL_ARROWS; QString currentYear {QDateTime::currentDateTime().toString("yyyy")}; QString currentMonth {QDateTime::currentDateTime().toString("MM")}; QString currentDay {QDateTime::currentDateTime().toString("dd")}; replaceTag(arrows, "CURRENT_DATA_LOG", "/"+server+"/"+channel+"/"+ currentYear+"/"+currentMonth+"/"+currentDay); replaceTag(page, "ADDITIONAL_BUTTON", arrows); int currentOnline = 0; QString onlineUserS; for (const auto &user: m_servers[originalServerName][originalChannelName]) { if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+m_botNick[originalServerName]+"$").match(user).hasMatch()) { continue; } QString onlineUser = HTML_ONLINE_POINT; replaceTag(onlineUser, "NICKNAME", user); onlineUserS += onlineUser; currentOnline++; } replaceTag(page, "ONLINE", QString::number(currentOnline)); replaceTag(page, "ONLINE_LIST", onlineUserS); if (not searchRequest.isEmpty()) { page.replace("", ""); } else 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()) { uint counter = 0; QRegularExpression userRgx(searchRequest, QRegularExpression::CaseInsensitiveOption); bool rgxIsValid = false; if (isRegexp and userRgx.isValid()) { rgxIsValid = true; } consoleLog("Search request (" + server + "): " + searchRequest); QStringList paths; QDirIterator it(fsPath.path()); while (it.hasNext()) { QString currentPath = it.next(); if (currentPath.endsWith(".") or currentPath.endsWith("..")) continue; QString logFolder = m_dataFolder; #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_servers) { 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_servers[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 { std::map matchedPathsAndMessages; 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.readLine()}; while (not buffer.isEmpty()) { removeBrakelineSymbols(buffer); bool finded = false; if (rgxIsValid) { if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) { finded = true; } } else { if (buffer.contains(searchRequest, Qt::CaseInsensitive)) { finded = true; } } if (finded) { std::pair rawMessage = splitUserNameAndMessage(buffer); if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) { buffer = file.readLine(); continue; } counter++; QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE; if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) { message.replace("class=\"main_payload__chat\"", "class=\"main_payload__chat\" style=\"opacity: .5\""); } for (const auto &user: m_servers[originalServerName][originalChannelName]) { if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) { message.replace("class=\"main_payload__chat_username\"", "class=\"main_payload__chat_username\" style=\"color: green\""); break; } } replaceTag(message, "USERNAME", rawMessage.first); replaceTag(message, "MESSAGE_TEXT", rawMessage.second); matchedPathsAndMessages[path].push_back(message); } buffer = file.readLine(); } file.close(); } } 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.readLine()}; while (not buffer.isEmpty()) { removeBrakelineSymbols(buffer); bool finded = false; if (rgxIsValid) { if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) { finded = true; } } else { if (buffer.contains(searchRequest, Qt::CaseInsensitive)) { finded = true; } } if (finded) { std::pair rawMessage = splitUserNameAndMessage(buffer); if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) { buffer = file.readLine(); continue; } counter++; QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE; if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) { message.replace("class=\"main_payload__chat\"", "class=\"main_payload__chat\" style=\"opacity: .5\""); } for (const auto &user: m_servers[originalServerName][originalChannelName]) { if (QRegularExpression("^^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) { message.replace("class=\"main_payload__chat_username\"", "class=\"main_payload__chat_username\" style=\"color: green\""); break; } } replaceTag(message, "USERNAME", rawMessage.first); replaceTag(message, "MESSAGE_TEXT", rawMessage.second); matchedPathsAndMessages[path].push_back(message); } buffer = file.readLine(); } file.close(); } } } 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); } 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.readLine()}; while (not buffer.isEmpty()) { removeBrakelineSymbols(buffer); bool finded = false; if (rgxIsValid) { if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) { finded = true; } } else { if (buffer.contains(searchRequest, Qt::CaseInsensitive)) { finded = true; } } if (finded) { std::pair rawMessage = splitUserNameAndMessage(buffer); if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) { buffer = file.readLine(); continue; } counter++; QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE; if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) { message.replace("class=\"main_payload__chat\"", "class=\"main_payload__chat\" style=\"opacity: .5\""); } for (const auto &user: m_servers[originalServerName][originalChannelName]) { if (QRegularExpression("^^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) { message.replace("class=\"main_payload__chat_username\"", "class=\"main_payload__chat_username\" style=\"color: green\""); break; } } replaceTag(message, "USERNAME", rawMessage.first); replaceTag(message, "MESSAGE_TEXT", rawMessage.second); matchedPathsAndMessages[path].push_back(message); } buffer = file.readLine(); } file.close(); } } if (matchedPathsAndMessages.empty()) { payloadBlock = HTML_PAYLOAD_ERROR; replaceTag(payloadBlock, "ERROR_TITLE", "Not found"); replaceTag(payloadBlock, "ERROR_TEXT", ""); } else { QStringList findedPaths; for (const auto& fp: matchedPathsAndMessages) { findedPaths << fp.first; } std::sort(findedPaths.begin(), findedPaths.end()); for (auto& link: findedPaths) { QString logFolder {m_dataFolder}; logFolder.remove(QRegularExpression(".$")); QStringList& messages = matchedPathsAndMessages[link]; link.remove(logFolder); link.remove(QRegularExpression("\\.txt$")); QString finded = HTML_PAYLOAD_LIST_POINT_MESSAGE; finded.replace("class=\"main_payload__block\"", "class=\"main_payload__block\" style=\"background: #b6c7d6\""); replaceTag(finded, "POINT_LINK", link); link.remove(QRegularExpression("^.*"+channel)); replaceTag(finded, "POINT_CONTENT", link); payloadBlock += finded; for(const auto& m: messages) { payloadBlock += m; } payloadBlock += " \n"; } } } int lastWbr = 0; for (int i = 0; i < searchRequest.size(); i++) { if (i-lastWbr > MAX_MESSAGE_LENGTH_WITHOUT_WBR) { searchRequest.insert(i, ""); lastWbr = i; } } middlePath += " " + searchRequest.toHtmlEscaped() + " "; if (rgxIsValid) middlePath += "rgx"; middlePath += "(" + QString::number(counter) + ")"; } // Plain 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("^.*/")); } folderNameS << folderName; } if (not folderNameS.isEmpty()) { std::sort(folderNameS.begin(), folderNameS.end()); for (const auto &f: folderNameS) { QString onePoint = HTML_PAYLOAD_LIST_POINT_FOLDER; bool yearIsOk {false}; bool monthIsOk {false}; QDate dateOfMonth (year.toInt(&yearIsOk), f.toInt(&monthIsOk), 1); QString nameOfMonth; if (yearIsOk and monthIsOk) { nameOfMonth = QLocale().standaloneMonthName(dateOfMonth.month(), QLocale::ShortFormat); } QDirIterator dayLogs(fsPath.path() + global::slash + f); int8_t dayLogsCounter {0}; while (dayLogs.hasNext()) { QString dayLogFile = dayLogs.next(); if (dayLogFile.endsWith(".") or dayLogFile.endsWith("..")) continue; dayLogsCounter++; } QString filesLabel; dayLogsCounter == 1 ? filesLabel = "day" : filesLabel = "days"; replaceTag(onePoint, "POINT_CONTENT", "" + f + " (" + nameOfMonth + ") " + QString::number(dayLogsCounter) + " " + filesLabel); 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; bool yearIsOk {false}; bool monthIsOk {false}; bool dayIsOk {false}; QDate dateOfDay (year.toInt(&yearIsOk), month.toInt(&monthIsOk), a.toInt(&dayIsOk)); QString nameOfDay; if (yearIsOk and monthIsOk and dayIsOk) { nameOfDay = QLocale().standaloneDayName(dateOfDay.dayOfWeek(), QLocale::ShortFormat); } auto logFileSizeBytes = QFile(fsPath.path() + global::slash + a +".txt").size(); auto logFileSizeinKb = logFileSizeBytes/1000; QString logFileSizeString; if (logFileSizeinKb != 0) { logFileSizeString = QString::number(logFileSizeinKb)+"."+ QString::number((logFileSizeBytes - logFileSizeinKb*1000)/10)+ " KB"; } else { logFileSizeString = QString::number(logFileSizeBytes)+" B"; } replaceTag(onePoint, "POINT_CONTENT", "" + a + " (" + nameOfDay + ") " + logFileSizeString); 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 { middlePath += "/" + day; QString buffer = file.readLine(); while (not buffer.isEmpty()) { removeBrakelineSymbols(buffer); std::pair rawMessage = splitUserNameAndMessage(buffer); if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) { buffer = file.readLine(); continue; } QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE; if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) { message.replace("class=\"main_payload__chat\"", "class=\"main_payload__chat\" style=\"opacity: .5\""); } for (const auto &user: m_servers[originalServerName][originalChannelName]) { if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) { message.replace("class=\"main_payload__chat_username\"", "class=\"main_payload__chat_username\" style=\"color: green\""); break; } } replaceTag(message, "USERNAME", rawMessage.first); replaceTag(message, "MESSAGE_TEXT", rawMessage.second); payloadBlock += message; buffer = file.readLine(); } file.close(); // arrows to prev & next days bool prevDayFound {false}; uint prevDayUInt {day.toUShort()}; uint prevMonthUInt {month.toUShort()}; uint prevYearUInt {year.toUShort()}; QDir fsPathForPrev {fsPath}; for(uint i = prevDayUInt-1; i >= 1; i--) { // current month QString d {fsPathForPrev.path()+global::slash}; if (i < 10) d += "0"; d += QString::number(i)+".txt"; if (QFile::exists(d)) { prevDayUInt = i; prevDayFound = true; break; } } if (not prevDayFound) { // other month fsPathForPrev.cdUp(); bool mf {false}; for (uint i = prevMonthUInt-1; i >= 1; i--) { QString m; if (i < 10) m += "0"; m += QString::number(i); if (fsPathForPrev.cd(m)) { prevMonthUInt = i; mf = true; break; } } if (mf) { for(uint i = 31; i >= 1; i--) { QString d {fsPathForPrev.path()+global::slash}; if (i < 10) d += "0"; d += QString::number(i)+".txt"; if (QFile::exists(d)) { prevDayUInt = i; prevDayFound = true; break; } } } } if (not prevDayFound) { // other year fsPathForPrev.cdUp(); bool yf {false}; for (uint i = prevYearUInt-1; i > prevYearUInt-21; i--) { if (fsPathForPrev.cd(QString::number(i))) { prevYearUInt = i; yf = true; break; } } if (yf) { bool mf {false}; for (uint i = 12; i >= 1; i--) { QString m; if (i < 10) m += "0"; m += QString::number(i); if (fsPathForPrev.cd(m)) { prevMonthUInt = i; mf = true; break; } } if (mf) { for(uint i = 31; i >= 1; i--) { QString d {fsPathForPrev.path()+global::slash}; if (i < 10) d += "0"; d += QString::number(i)+".txt"; if (QFile::exists(d)) { prevDayUInt = i; prevDayFound = true; break; } } } } } QString leftArrow; if (prevDayFound) { leftArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_PREV; QString link = "/"+server+"/"+channel+"/"+QString::number(prevYearUInt)+"/"; QString m; if (prevMonthUInt < 10) m += "0"; m += QString::number(prevMonthUInt); link += m+"/"; QString d; if (prevDayUInt < 10) d += "0"; d += QString::number(prevDayUInt); link += d; replaceTag(leftArrow, "LINK", link); } else { leftArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_PREV_FALSE; } middlePath += leftArrow; bool nextDayFound {false}; uint nextDayUInt {day.toUShort()}; uint nextMonthUInt {month.toUShort()}; uint nextYearUInt {year.toUShort()}; QDir fsPathForNext {fsPath}; for(uint i = nextDayUInt+1; i <= 31; i++) { // current month QString d {fsPathForNext.path()+global::slash}; if (i < 10) d += "0"; d += QString::number(i)+".txt"; if (QFile::exists(d)) { nextDayUInt = i; nextDayFound = true; break; } } if (not nextDayFound) { // other month fsPathForNext.cdUp(); bool mf {false}; for (uint i = nextMonthUInt+1; i <= 12; i++) { QString m; if (i < 10) m += "0"; m += QString::number(i); if (fsPathForNext.cd(m)) { nextMonthUInt = i; mf = true; break; } } if (mf) { for(uint i = 1; i <= 31; i++) { QString d {fsPathForNext.path()+global::slash}; if (i < 10) d += "0"; d += QString::number(i)+".txt"; if (QFile::exists(d)) { nextDayUInt = i; nextDayFound = true; break; } } } } if (not nextDayFound) { // other year fsPathForNext.cdUp(); bool yf {false}; for (uint i = nextYearUInt+1; i < nextYearUInt+20; i++) { if (fsPathForNext.cd(QString::number(i))) { nextYearUInt = i; yf = true; break; } } if (yf) { bool mf {false}; for (uint i = 1; i <= 12; i++) { QString m; if (i < 10) m += "0"; m += QString::number(i); if (fsPathForNext.cd(m)) { nextMonthUInt = i; mf = true; break; } } if (mf) { for(uint i = 1; i <= 31; i++) { QString d {fsPathForNext.path()+global::slash}; if (i < 10) d += "0"; d += QString::number(i)+".txt"; if (QFile::exists(d)) { nextDayUInt = i; nextDayFound = true; break; } } } } } QString rightArrow; if (nextDayFound) { rightArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_NEXT; QString link = "/"+server+"/"+channel+"/"+QString::number(nextYearUInt)+"/"; QString m; if (nextMonthUInt < 10) m += "0"; m += QString::number(nextMonthUInt); link += m+"/"; QString d; if (nextDayUInt < 10) d += "0"; d += QString::number(nextDayUInt); link += d; replaceTag(rightArrow, "LINK", link); } else { rightArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_NEXT_FALSE; } middlePath += rightArrow; } } if (payloadBlock.isEmpty()) { payloadBlock = HTML_PAYLOAD_ERROR; replaceTag(payloadBlock, "ERROR_TITLE", "Empty"); replaceTag(payloadBlock, "ERROR_TEXT", "No logs found for this channel"); } } replaceTag(page, "MIDDLE_PATH", middlePath); replaceTag(page, "PAYLOAD_BLOCK", payloadBlock); //// Footer replaceTag(page, "VERSION", IRCABOT_VERSION); replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR); replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart)); QString mainHeader = HEADER_HTML; replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size())); if (socket->isOpen()) { socket->write(mainHeader.toUtf8()); if (not isHeadRequest) socket->write(page.toUtf8()); } } void HttpServer::writeRealTimeChatPage(QTcpSocket *socket, QString &urlPath, bool isHeadRequest) { if (isHeadRequest) { QString header = HEADER_HTML; replaceTag(header, "SIZE", "0"); socket->write(header.toUtf8()); return; } auto renderStart = QDateTime::currentMSecsSinceEpoch(); urlPath.remove(QRegularExpression("^/realtimereadingchat/")); QString server = getWordFromPath(urlPath); if (server.isEmpty()) { writeErrorPage(socket, "SERVER VALUE IS MISSING"); return; } urlPath.remove(QRegularExpression("^"+server)); QString channel = getWordFromPath(urlPath); if (channel.isEmpty()) { writeErrorPage(socket, "CHANNEL VALUE IS MISSING"); return; } QString originalServerName; for (const auto &s: m_servers) { if (global::toLowerAndNoSpaces(s.first) == server) { originalServerName = s.first; } } if (originalServerName.isEmpty()) { writeErrorPage(socket, "REQUESTED SERVER NOT EXIST"); return; } QString originalChannelName; for (const auto &server: m_servers) { for (const auto &channel_users: server.second) { if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) { originalChannelName = global::toLowerAndNoSpaces(channel_users.first); } } } if (originalChannelName.isEmpty()) { writeErrorPage(socket, "REQUESTED CHANNEL NOT EXIST"); return; } QFile main("://html/main.html"); QString page; if (main.open(QIODevice::ReadOnly)) { page = main.readAll(); main.close(); } else { if (socket->isOpen()) { socket->write(HEADER_404.toUtf8()); if (not isHeadRequest) socket->write(CRITICAL_ERROR); } } QString year {QDateTime::currentDateTime().toString("yyyy")}; QString month {QDateTime::currentDateTime().toString("MM")}; QString day {QDateTime::currentDateTime().toString("dd")}; //// Left menu compilation QString htmlServersSectionS; for (const auto &s: m_servers) { if (s.first.isEmpty()) continue; // empty server name? QString htmlServersSection = HTML_SERVER_SECTION; replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+server); replaceTag(htmlServersSection, "SERVER_NAME", s.first); if (s.first == originalServerName) { htmlServersSection.replace("{{ONLINE_STATUS}}", "{{ONLINE_STATUS}}"); } 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 = "/realtimereadingchat/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl; 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); } if (s.first == originalServerName) { htmlServersSectionS.push_front(htmlServersSection); } else { htmlServersSectionS += htmlServersSection; } } htmlServersSectionS.push_front(m_serviceButton); replaceTag(page, "SERVERS_SECTION", htmlServersSectionS); //// Main section header compilation QString& topic = m_channelsTopic[originalServerName][originalChannelName]; topic = topic.replace('\"', """); QString titlePostfix = " | " + m_serviceName; if (not topic.isEmpty()) { titlePostfix.push_front(" | " + topic); } if (m_servers.size() > 1) { replaceTag(page, "PAGE_TITLE", originalChannelName + " ("+originalServerName+") [real time]" + titlePostfix); } else { replaceTag(page, "PAGE_TITLE", originalChannelName + " [real time]" + titlePostfix); } replaceTag(page, "CHANNEL_TOPIC", topic); replaceTag(page, "MAIN_HEADER", originalChannelName); replaceTag(page, "ADDITIONAL_BUTTON", HTML_PAYLOAD_ADDITIONAL_NOTIFY); replaceTag(page, "REALTIME_LINK", "/"+server+"/"+channel+"/"+year+"/"+month+"/"+day); replaceTag(page, "AIRPLAIN_TITLE", "Back to plain text log"); page.replace("class=\"main_header__title_airplaine\"", "class=\"main_header__title_airplaine\" style=\"transform: scale(-1,1)\""); int currentOnline = 0; QString onlineUserS; for (const auto &user: m_servers[originalServerName][originalChannelName]) { if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+m_botNick[originalServerName]+"$").match(user).hasMatch()) { continue; } QString onlineUser = HTML_ONLINE_POINT; replaceTag(onlineUser, "NICKNAME", user); onlineUserS += onlineUser; currentOnline++; } page.replace("{{ONLINE}}", "{{ONLINE}}"); replaceTag(page, "ONLINE", QString::number(currentOnline)); page.replace("
", "
"); replaceTag(page, "ONLINE_LIST", onlineUserS); page.replace("class=\"main_header__search_form\"", "action=\"/"+server+"/"+channel+"\" class=\"main_header__search_form\""); replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName); page.replace("
", "
"); replaceTag(page, "MIDDLE_PATH", ""); //// Payload page.replace("
", "
"); bool logsExisted {false}; QFile file; QDir fsPath(m_dataFolder+server+global::slash+channel); if (fsPath.cd(year)) { if (fsPath.cd(month)) { file.setFileName(fsPath.path()+global::slash+day+".txt"); if (file.open(QIODevice::ReadOnly)) { logsExisted = true; } } } QString payloadBlock; if (logsExisted) { QString buffer = file.readLine(); while (not buffer.isEmpty()) { removeBrakelineSymbols(buffer); std::pair rawMessage = splitUserNameAndMessage(buffer); if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) { buffer = file.readLine(); continue; } QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE; if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) { message.replace("class=\"main_payload__chat\"", "class=\"main_payload__chat\" style=\"opacity: .5\""); } message.replace("class=\"main_payload__chat_username\"", "class=\"main_payload__chat_username\" style=\"color: #e34f10\""); replaceTag(message, "USERNAME", rawMessage.first); replaceTag(message, "MESSAGE_TEXT", rawMessage.second); payloadBlock += message; buffer = file.readLine(); } file.close(); } QString hr = HTML_PAYLOAD_LIST_CHAT_MESSAGE; hr.replace("class=\"main_payload__chat_mail\"", "id=\"hr\" class=\"main_payload__chat_mail\""); hr.replace("class=\"main_payload__chat_username\"", "class=\"main_payload__chat_username\" style=\"color: white; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;\""); replaceTag(hr, "USERNAME", "IRCaBot"); replaceTag(hr, "MESSAGE_TEXT", "New messages won't show without JavaScript.
" "My JS code is small and simple. Check it at " "/realtimechat.js and come back " "with enabled!"); payloadBlock += hr; replaceTag(page, "PAYLOAD_BLOCK", payloadBlock); //// Footer replaceTag(page, "VERSION", IRCABOT_VERSION); replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR); //// Finish page.replace("", "
" + QString::number(QDateTime::currentMSecsSinceEpoch()) + "
\n" "
" + server + "/" + channel + "
\n" " \n"); replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart)); QString mainHeader = HEADER_HTML; replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size())); if (socket->isOpen()) { socket->write(mainHeader.toUtf8()); if (not isHeadRequest) socket->write(page.toUtf8()); } } void HttpServer::writeAboutServerPage(QTcpSocket *socket, QString &server, bool isHeadRequest) { auto renderStart = QDateTime::currentMSecsSinceEpoch(); QString originalServerName; for (const auto &s: m_servers) { if (global::toLowerAndNoSpaces(s.first) == server) { originalServerName = s.first; } } if (originalServerName.isEmpty()) { writeErrorPage(socket, "SERVER NOT EXIST"); return; } QFile main("://html/main.html"); QString page; if (main.open(QIODevice::ReadOnly)) { page = main.readAll(); main.close(); } else { if (socket->isOpen()) { socket->write(HEADER_404.toUtf8()); if (not isHeadRequest) socket->write(CRITICAL_ERROR); } } replaceTag(page, "PAGE_TITLE", "About " + originalServerName + " | " + m_serviceName); //// Left menu compilation QString htmlServersSectionS; for (const auto &s: m_servers) { if (s.first.isEmpty()) continue; // empty server name? QString htmlServersSection = HTML_SERVER_SECTION; if (s.first == originalServerName) { htmlServersSection.replace("
", "
"); } replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+s.first); replaceTag(htmlServersSection, "SERVER_NAME", s.first); QString htmlChannelLineS; for (const auto &c: s.second) { QString htmlChannelLine; htmlChannelLine = HTML_SERVER_SECTION_CHANNEL; replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first); QString channelNameForUrl {c.first}; channelNameForUrl.remove('#'); QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl; 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); } if (s.first == originalServerName) { htmlServersSectionS.push_front(htmlServersSection); } else { htmlServersSectionS += htmlServersSection; } } htmlServersSectionS.push_front(m_serviceButton); replaceTag(page, "SERVERS_SECTION", htmlServersSectionS); page.remove(QRegularExpression("
.*", QRegularExpression::DotMatchesEverythingOption)); QString payloadBlock = HTML_PAYLOAD_ABOUT; replaceTag(payloadBlock, "ABOUT_TITLE", originalServerName); QString aboutBlock; QFile about(m_dataFolder+server+global::slash+"about_server.txt"); if (about.open(QIODevice::ReadOnly)) { QString rbuffer = about.readLine(); while (not rbuffer.isEmpty()) { if (rbuffer.startsWith('#')) { rbuffer = about.readLine(); continue; } removeBrakelineSymbols(rbuffer); if (not rbuffer.isEmpty()) { aboutBlock += rbuffer; } rbuffer = about.readLine(); } } else { aboutBlock = "No information provided"; } replaceTag(payloadBlock, "ABOUT_TEXT", aboutBlock); replaceTag(page, "PAYLOAD_BLOCK", payloadBlock); //// Footer replaceTag(page, "VERSION", IRCABOT_VERSION); replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR); //// Finish replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart)); QString mainHeader = HEADER_HTML; replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size())); if (socket->isOpen()) { socket->write(mainHeader.toUtf8()); if (not isHeadRequest) socket->write(page.toUtf8()); } } void HttpServer::writeAjaxAnswer(QTcpSocket *socket, QString &urlPath, bool isHeadRequest) { if (isHeadRequest) { QString header = HEADER_JSON; replaceTag(header, "SIZE", "0"); socket->write(header.toUtf8()); return; } //// Validate urlPath.remove(QRegularExpression("^/ajax/")); QString server = getWordFromPath(urlPath); if (server.isEmpty()) { writeErrorJson(socket, "Invalid request. Server value is missing!"); return; } urlPath.remove(QRegularExpression("^"+server)); QString channel = getWordFromPath(urlPath); if (channel.isEmpty()) { writeErrorJson(socket, "Invalid request. Channel value is missing!"); return; } QString originalServerName; for (const auto &s: m_servers) { if (global::toLowerAndNoSpaces(s.first) == server) { originalServerName = s.first; } } if (originalServerName.isEmpty()) { writeErrorJson(socket, "Invalid request. Server not exist!"); return; } QString originalChannelName; for (const auto &server: m_servers) { for (const auto &channel_users: server.second) { if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) { originalChannelName = global::toLowerAndNoSpaces(channel_users.first); } } } if (originalChannelName.isEmpty()) { writeErrorJson(socket, "Invalid request. Channel not exist!"); return; } //// Parse bool userOnlineCounterIsOk {false}; auto userOnlineCounter = global::getValue(urlPath, "onlineCounter", global::eForWeb).toInt(&userOnlineCounterIsOk); if (not userOnlineCounterIsOk) { writeErrorJson(socket, "Invalid request: 'onlineCounter' (int) value is missing!"); return; } bool userMessageLastIdIsOk {false}; qint64 userMessageLastId = global::getValue(urlPath, "messageId", global::eForWeb).toLongLong(&userMessageLastIdIsOk); if (not userMessageLastIdIsOk) { writeErrorJson(socket, "Invalid request: 'messageId' value is missing!"); return; } bool userServerStatus = global::getValue(urlPath, "serverStatus", global::eForWeb) == "true"; //// Building QJsonObject jResponse; jResponse.insert("status", QJsonValue(true)); // online server if (userServerStatus != m_serversOnline[originalServerName]) { jResponse.insert("serverStatusChanged", QJsonValue(true)); jResponse.insert("serverStatus", QJsonValue(m_serversOnline[originalServerName])); } else { jResponse.insert("serverStatusChanged", QJsonValue(false)); } // online users if (m_servers[originalServerName][originalChannelName].size()-1/*self*/ != userOnlineCounter) { jResponse.insert("onlineUsersChanged", QJsonValue(true)); QJsonObject jOnline; int currentOnline = m_servers[originalServerName][originalChannelName].size(); if (currentOnline > 0) currentOnline -= 1; jOnline.insert("count", QJsonValue(currentOnline)); QJsonArray jOnlineList; for (const auto& user: m_servers[originalServerName][originalChannelName]) { if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+m_botNick[originalServerName]+"$").match(user).hasMatch()) { continue; } jOnlineList.push_back(QJsonValue(user)); } jOnline.insert("list", jOnlineList); jResponse.insert("online", jOnline); } else { jResponse.insert("onlineUsersChanged", QJsonValue(false)); } // new messages bool newMessagesIsExisted {false}; QString channelId {server+channel}; QJsonArray jNewMessages; qint64 newLastMessageId {userMessageLastId}; if (not m_messageCache.contains(channelId)) { m_messageCache.insert(channelId, new MessagePool); consoleLog("Message caching enabled for "+server+"/#"+channel+". Real time reading started."); } else { auto messages = *(m_messageCache[channelId]->getMessages()); for (const auto& msg: messages) { if (userMessageLastId < msg.first) { if (not newMessagesIsExisted) newMessagesIsExisted = true; QJsonObject jOneNewMessage; jOneNewMessage.insert("user", msg.second->getSender()); jOneNewMessage.insert("text", msg.second->getText()); jNewMessages.push_back(jOneNewMessage); newLastMessageId = msg.first; } } } if (newMessagesIsExisted) { jResponse.insert("LMIdChanged" /* last message id == LMId */, QJsonValue(true)); jResponse.insert("newMessages", jNewMessages); jResponse.insert("LMId", QJsonValue(QString::number(newLastMessageId))); } else { jResponse.insert("LMIdChanged", QJsonValue(false)); } //// Finish QByteArray response {QJsonDocument(jResponse).toJson()}; QString header = HEADER_JSON; replaceTag(header, "SIZE", QString::number(response.size())); socket->write(header.toUtf8()); socket->write(response); } /*\ |*| //////////'''''''''''''\\\\\\\\\\ |*| HAPPY NEW YEAR IN RUSSIAN PRISON! |*| |||||||||| 2022 |||||||||| |*| \\\\\\\\\\_____________////////// \*/ Message::Message(const QString& s, const QString& t, qint64 timestamp, QObject *parent) : QObject(parent), m_sender(s), m_text(t), m_timestamp(timestamp) { connect (&m_selfKiller, &QTimer::timeout, [&](){emit outDated(m_timestamp);}); m_selfKiller.setSingleShot(true); m_selfKiller.start(MSECS_TO_AUTOREMOVE_MESSAGES_FROM_BUFFER); } const QString Message::getSender() { return m_sender; } const QString Message::getText() { return m_text; } MessagePool::MessagePool(QObject *parent) : QObject(parent), m_lastPing(QDateTime::currentMSecsSinceEpoch()) {} void MessagePool::saveNewMessage(const QString &nick, const QString &text) { qint64 timestamp = QDateTime::currentMSecsSinceEpoch(); Message* newMessage = new Message (nick, text, timestamp); connect (newMessage, SIGNAL(outDated(qint64)), this, SLOT (messageToDelete(qint64))); m_messages.insert({timestamp, newMessage}); } const std::multimap* MessagePool::getMessages() { m_lastPing = QDateTime::currentMSecsSinceEpoch(); return &m_messages; } qint64 MessagePool::getLastPing() { return m_lastPing; } void MessagePool::messageToDelete(qint64 timestamp) { while (m_messages.find(timestamp) != m_messages.end()) { auto it = m_messages.find(timestamp); it->second->deleteLater(); m_messages.erase(it); } }