mirror of https://notabug.org/acetone/ircabot.git
1078 lines
42 KiB
C++
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 ⁢ 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("(\\&|\\<|\\>|\\").*"), 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('\"', """);
|
|
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 += " \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> (" + 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> (" + 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());
|
|
}
|
|
}
|