ircabot/httpserver.cpp

1078 lines
42 KiB
C++

#include "httpserver.h"
#include "ircclient.h"
#include "connectiondata.h"
#include "global.h"
#include "version.h"
#include <QRegularExpression>
#include <QDirIterator>
#include <QDateTime>
#include <QHostAddress>
#include <QTcpSocket>
#include <QDebug>
#include <QFile>
#include <QDir>
constexpr int MAX_MESSAGE_LENGTH_WITHOUT_WBR = 30;
constexpr int MAX_NICKNAME_LENGTH_WITHOUT_WBR = 20;
constexpr int BUFFER_SIZE = 2048;
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();
}
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 = "<a href=\"" + httpLine + "\"> " + displayedName + " </a>";
return result;
}
std::pair<QString, QString> HttpServer::splitUserNameAndMessage(const QString &rawLine)
{
std::pair<QString, QString> result;
QString nick {rawLine};
nick.remove(QRegularExpression("\\]\\s.*$"));
nick.remove(QRegularExpression("^\\["));
if (nick.isEmpty()) {
return result;
}
QString text {rawLine};
text.remove(QRegularExpression("^\\[[^\\s]*\\]\\s"));
if (text.isEmpty()) {
return result;
}
// 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, "<wbr>");
lastWbr = i;
}
}
}
text = text.toHtmlEscaped();
// http links
while (QRegularExpression("(^|\\s)http.?://").match(text).hasMatch()) {
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 &it; via <wbr>!
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("(\\&amp;|\\&lt;|\\&gt;|\\&quot;).*"), i) == i) {
nbTag = true;
}
if (not isHref and i-space > MAX_MESSAGE_LENGTH_WITHOUT_WBR and not nbTag) {
text.insert(i, "<wbr>");
space = i;
}
}
result.first = nick;
result.second = text;
return result;
}
void HttpServer::consoleLog(const QString &message)
{
qInfo().noquote() << "[WEBINTERFACE]" << message;
}
void HttpServer::debugLog(const QString &req)
{
QFile log(m_logFolder + "httprequests.log");
if (log.open(QIODevice::WriteOnly | QIODevice::Append)) {
log.write(QDateTime::currentDateTime().toString().toUtf8() + ":\n" + req.toUtf8() + "\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<QTcpSocket*>(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);
// static files
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 == "/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) socket->write("<center><h1>NOT FOUND</H1></center>");
}
}
}
}
// dynamic page
else {
writeMainPage(socket, urlPath, isHeadRequest);
}
socket->disconnectFromHost();
}
void HttpServer::ircBotFirstInfo(QString server, QStringList channels)
{
for (const auto &c: channels) {
m_onlineUsers[server][c] = QStringList();
}
}
void HttpServer::ircUsersOnline(QString server, QString channel, QStringList users)
{
if (server.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_onlineUsers[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_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)
{
if (socket->isOpen()) {
socket->write(HEADER_404.toUtf8());
socket->write("<title>404</title><center><h1>NOT FOUND</H1></center>");
}
}
void HttpServer::removeBrakelineSymbols(QString &line)
{
line.remove('\r');
line.remove('\n');
line.remove('\t');
}
void HttpServer::replaceTag(QString &page, const QString &tag, const QString &payload)
{
page.replace("{{"+tag+"}}", payload);
}
void HttpServer::writeMainPage(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);
}
if (urlPath == "/") {
urlPath += m_mainChannel;
}
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("<title>Critical error</title><center><h1>NOT FOUND</H1><p>Maybe it's compile time error</p></center>");
}
}
if (isRegexp) {
page.replace("<input id=\"main_header__search_checkbox__button\" type=\"checkbox\" name=\"isRegexp\">",
"<input id=\"main_header__search_checkbox__button\" type=\"checkbox\" name=\"isRegexp\" checked>");
}
QString server = getWordFromPath(urlPath);
QDir fsPath(m_logFolder+server);
if (not fsPath.exists()) {
if (isHeadRequest) {
if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
} else {
writeErrorPage(socket);
}
return;
}
urlPath.remove(QRegularExpression("^.*/"+server));
QString channel = getWordFromPath(urlPath);
channel.remove(QRegularExpression("\\?.*$"));
if (channel.isEmpty()){
// First channel is main 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)) {
if (isHeadRequest) {
if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
} else {
writeErrorPage(socket);
}
return;
}
QString originalServerName;
for (const auto &s: m_onlineUsers) {
if (global::toLowerAndNoSpaces(s.first) == server) {
originalServerName = s.first;
}
}
QString originalChannelName;
for (const auto &server: m_onlineUsers) {
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);
}
}
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_onlineUsers) {
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_onlineUsers.size() > 1) {
replaceTag(page, "PAGE_TITLE", originalChannelName + " ("+originalServerName+") | IRCaBot");
} else {
replaceTag(page, "PAGE_TITLE", originalChannelName + " | IRCaBot");
}
replaceTag(page, "MAIN_HEADER", originalChannelName);
if (not m_channelsTopic[originalServerName][originalChannelName].isEmpty()) {
m_channelsTopic[originalServerName][originalChannelName].replace('\"', "&quot;");
page.replace("<div class=\"main_header__title\">",
"<div class=\"main_header__title\" title=\"" + m_channelsTopic[originalServerName][originalChannelName] + "\">");
}
QString middlePath = "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"\">" + "/" + "</a>";
if (not year.isEmpty()) {
middlePath += "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"/"+year+"\">" + year + "</a>";
if (not month.isEmpty()) {
middlePath += "/<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"/"+year+"/"+month+"\">" + month + "</a>";
if (not day.isEmpty()) {
middlePath += "/" + day;
}
}
}
int currentOnline = 0;
QString onlineUserS;
for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
if (QRegularExpression("^(@|\\&|\\+|~)?"+m_botsNick[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("<input class=\"main_header__search_input\" type=\"search\" name=\"toSearch\" placeholder=\"{{SEARCH_PLACEHOLDER}}\">",
"<input class=\"main_header__search_input\" type=\"search\" name=\"toSearch\" value=\"" + searchRequest + "\">");
} 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);
if (not day.isEmpty()) {
middlePath.remove(QRegularExpression("/[0-9]{2}$"));
}
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_onlineUsers) {
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_onlineUsers[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<QString, QStringList> 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<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
buffer = file.readLine();
continue;
}
counter++;
QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
if (QRegularExpression("^.?"+rawMessage.first+"$").match(user).hasMatch()) {
message.replace("<div class=\"main_payload__chat_username\">",
"<div class=\"main_payload__chat_username\" style=\"color: green\">");
break;
}
}
rawMessage.first = rawMessage.first.toHtmlEscaped();
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<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
buffer = file.readLine();
continue;
}
counter++;
QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
if (QRegularExpression("^.?"+rawMessage.first+"$").match(user).hasMatch()) {
message.replace("<div class=\"main_payload__chat_username\">",
"<div class=\"main_payload__chat_username\" style=\"color: green\">");
break;
}
}
rawMessage.first = rawMessage.first.toHtmlEscaped();
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);
}
/* 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.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<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
buffer = file.readLine();
continue;
}
counter++;
QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
if (QRegularExpression("^.?"+rawMessage.first+"$").match(user).hasMatch()) {
message.replace("<div class=\"main_payload__chat_username\">",
"<div class=\"main_payload__chat_username\" style=\"color: green\">");
break;
}
}
rawMessage.first = rawMessage.first.toHtmlEscaped();
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_logFolder};
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 += "&nbsp;\n";
}
}
}
middlePath += " " + searchRequest + " ";
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", "<b>" + f + "</b>&nbsp;(" + 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", "<b>" + a + "</b>&nbsp;(" + 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 {
QString buffer = file.readLine();
while (not buffer.isEmpty()) {
removeBrakelineSymbols(buffer);
std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
buffer = file.readLine();
continue;
}
QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
if (QRegularExpression("^(@|\\&|\\+|~)?"+rawMessage.first+"$").match(user).hasMatch()) {
message.replace("<div class=\"main_payload__chat_username\">",
"<div class=\"main_payload__chat_username\" style=\"color: green\">");
break;
}
}
rawMessage.first = rawMessage.first.toHtmlEscaped();
replaceTag(message, "USERNAME", rawMessage.first);
replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
payloadBlock += message;
buffer = file.readLine();
}
file.close();
}
}
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());
}
}