httpserver.cpp 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. #include "httpserver.h"
  2. #include "ircclient.h"
  3. #include "connectiondata.h"
  4. #include "global.h"
  5. #include "version.h"
  6. #include <QRegularExpression>
  7. #include <QDirIterator>
  8. #include <QDateTime>
  9. #include <QHostAddress>
  10. #include <QTcpSocket>
  11. #include <QDebug>
  12. #include <QFile>
  13. #include <QDir>
  14. constexpr int MAX_MESSAGE_LENGTH_WITHOUT_WBR = 30;
  15. constexpr int MAX_NICKNAME_LENGTH_WITHOUT_WBR = 20;
  16. constexpr int BUFFER_SIZE = 2048;
  17. HttpServer::HttpServer(const QString &address, quint16 port, const QString& logFolder,
  18. const QString& mainChannel, QObject *parent) :
  19. QObject(parent),
  20. m_TcpServer(new QTcpServer),
  21. m_mainChannel(mainChannel),
  22. m_logFolder(logFolder)
  23. {
  24. if (not m_TcpServer->listen(QHostAddress(address), port)) {
  25. throw std::runtime_error("HttpServer not binded at " +
  26. address.toStdString() + " : " + QString::number(port).toStdString());
  27. }
  28. else {
  29. consoleLog(address + " : " + QString::number(port));
  30. }
  31. connect (m_TcpServer, &QTcpServer::newConnection, this, &HttpServer::acceptor);
  32. }
  33. HttpServer::~HttpServer()
  34. {
  35. m_TcpServer->close();
  36. m_TcpServer->deleteLater();
  37. }
  38. QString HttpServer::convertToClickableLink(const QString &httpLine)
  39. {
  40. QString result;
  41. if (not httpLine.contains(QRegularExpression("http.?://"))) return result;
  42. QString displayedName {httpLine};
  43. displayedName.remove(QRegularExpression("http.?://(www\\.)?"));
  44. displayedName.remove(QRegularExpression("/$"));
  45. result = "<a href=\"" + httpLine + "\"> " + displayedName + " </a>";
  46. return result;
  47. }
  48. std::pair<QString, QString> HttpServer::splitUserNameAndMessage(const QString &rawLine)
  49. {
  50. std::pair<QString, QString> result;
  51. QString nick {rawLine};
  52. nick.remove(QRegularExpression("\\]\\s.*$"));
  53. nick.remove(QRegularExpression("^\\["));
  54. if (nick.isEmpty()) {
  55. return result;
  56. }
  57. QString text {rawLine};
  58. text.remove(QRegularExpression("^\\[[^\\s]*\\]\\s"));
  59. if (text.isEmpty()) {
  60. return result;
  61. }
  62. nick = nick.toHtmlEscaped();
  63. // long nicks
  64. if (nick.size() > MAX_NICKNAME_LENGTH_WITHOUT_WBR) {
  65. int lastWbr = 0;
  66. for (int i = 0; i < nick.size(); i++) {
  67. if (i-lastWbr > MAX_NICKNAME_LENGTH_WITHOUT_WBR) {
  68. nick.insert(i, "<wbr>");
  69. lastWbr = i;
  70. }
  71. }
  72. }
  73. text = text.toHtmlEscaped();
  74. // http links
  75. while (QRegularExpression("(^|\\s)http.?://").match(text).hasMatch()) {
  76. int pos = text.indexOf(QRegularExpression("(^|\\s)http.?://"));
  77. if (pos == -1) {
  78. consoleLog("Bug! HttpServer.cpp while (QRegularExpression(\"(^|\\s)http.?://\").match(text).hasMatch())");
  79. break;
  80. }
  81. QString rawLink {text};
  82. rawLink.remove(0, pos);
  83. if (rawLink.startsWith(' ')) {
  84. rawLink.remove(0,1);
  85. }
  86. int space = rawLink.indexOf(' ');
  87. if (space > 0) {
  88. rawLink.remove(space, rawLink.size()-space);
  89. }
  90. text.replace(rawLink, convertToClickableLink(rawLink));
  91. }
  92. // long lines
  93. int space = 0;
  94. bool nbTag = false; // For safe HTML tags like a &it; via <wbr>!
  95. bool isHref = false;
  96. for (int i = 0; i < text.size(); i++) {
  97. if (text[i] == ' ') {
  98. space = i;
  99. if (isHref) {
  100. isHref = false;
  101. }
  102. else {
  103. if (text.indexOf("href=\"http", i+1) == i+1) {
  104. isHref = true;
  105. }
  106. }
  107. }
  108. if (nbTag and text[i-1] == ';') {
  109. nbTag = false;
  110. }
  111. if (text.indexOf(QRegularExpression("(\\&amp;|\\&lt;|\\&gt;|\\&quot;).*"), i) == i) {
  112. nbTag = true;
  113. }
  114. if (not isHref and i-space > MAX_MESSAGE_LENGTH_WITHOUT_WBR and not nbTag) {
  115. text.insert(i, "<wbr>");
  116. space = i;
  117. }
  118. }
  119. result.first = nick;
  120. result.second = text;
  121. return result;
  122. }
  123. void HttpServer::consoleLog(const QString &message)
  124. {
  125. qInfo().noquote() << "[WEBINTERFACE]" << message;
  126. }
  127. void HttpServer::debugLog(const QString &req)
  128. {
  129. QFile log(m_logFolder + "httprequests.log");
  130. if (log.open(QIODevice::WriteOnly | QIODevice::Append)) {
  131. log.write(QDateTime::currentDateTime().toString().toUtf8() + ":\n" + req.toUtf8() + "\n");
  132. log.close();
  133. }
  134. }
  135. void HttpServer::acceptor()
  136. {
  137. QTcpSocket* socket = m_TcpServer->nextPendingConnection();
  138. connect(socket, &QTcpSocket::readyRead, this, &HttpServer::reader);
  139. connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
  140. }
  141. void HttpServer::reader()
  142. {
  143. QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
  144. QString request = socket->read(BUFFER_SIZE);
  145. if (not request.startsWith("GET") and not request.startsWith("HEAD")) {
  146. if (socket->isOpen()) {
  147. socket->write("Your request has been rejected!\n");
  148. socket->disconnectFromHost();
  149. }
  150. return;
  151. }
  152. bool isHeadRequest = false;
  153. if (request.startsWith("HEAD ")) {
  154. isHeadRequest = true;
  155. }
  156. QString urlPath = getRequestPath(request);
  157. // static files
  158. if (urlPath == "/favicon.ico") {
  159. QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
  160. if (eTag == HTTP_ACTUAL_ETAG) {
  161. if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
  162. }
  163. else {
  164. QFile icon("://html/favicon.ico");
  165. if (icon.open(QIODevice::ReadOnly)) {
  166. QByteArray file = icon.readAll();
  167. icon.close();
  168. QString header = HEADER_ICO;
  169. replaceTag(header, "SIZE", QString::number(file.size()));
  170. if (socket->isOpen()) {
  171. socket->write(header.toUtf8());
  172. if (not isHeadRequest) socket->write(file);
  173. }
  174. }
  175. }
  176. }
  177. else if (urlPath == "/style.css") {
  178. QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
  179. if (eTag == HTTP_ACTUAL_ETAG) {
  180. if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
  181. }
  182. else {
  183. QFile css("://html/style.css");
  184. if (css.open(QIODevice::ReadOnly)) {
  185. QByteArray file = css.readAll();
  186. css.close();
  187. QString header = HEADER_CSS;
  188. replaceTag(header, "SIZE", QString::number(file.size()));
  189. if (socket->isOpen()) {
  190. socket->write(header.toUtf8());
  191. if (not isHeadRequest) socket->write(file);
  192. }
  193. }
  194. }
  195. }
  196. else if (urlPath.endsWith(".svg")) {
  197. QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
  198. if (eTag == HTTP_ACTUAL_ETAG) {
  199. if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
  200. }
  201. else {
  202. QFile svg("://html"+urlPath);
  203. if (svg.open(QIODevice::ReadOnly)) {
  204. QByteArray file = svg.readAll();
  205. svg.close();
  206. QString header = HEADER_SVG;
  207. replaceTag(header, "SIZE", QString::number(file.size()));
  208. if (socket->isOpen()) {
  209. socket->write(header.toUtf8());
  210. if (not isHeadRequest) socket->write(file);
  211. }
  212. }
  213. else {
  214. if (socket->isOpen()) {
  215. socket->write(HEADER_404.toUtf8());
  216. if (not isHeadRequest) socket->write("<center><h1>NOT FOUND</H1></center>");
  217. }
  218. }
  219. }
  220. }
  221. // dynamic page
  222. else {
  223. writeMainPage(socket, urlPath, isHeadRequest);
  224. }
  225. socket->disconnectFromHost();
  226. }
  227. void HttpServer::ircBotFirstInfo(QString server, QStringList channels)
  228. {
  229. for (const auto &c: channels) {
  230. m_onlineUsers[server][c] = QStringList();
  231. }
  232. }
  233. void HttpServer::ircUsersOnline(QString server, QString channel, QStringList users)
  234. {
  235. if (server.isEmpty()) return;
  236. QStringList sortedNicknames;
  237. QStringList ownersNicks;
  238. QStringList operNicks;
  239. QStringList halfopNicks;
  240. QStringList adminNicks;
  241. QStringList voicedNicks;
  242. QStringList plainNicks;
  243. for (const auto& rawOneNick: users) {
  244. if (rawOneNick.startsWith('~')) {
  245. ownersNicks.push_back(rawOneNick);
  246. }
  247. else if (rawOneNick.startsWith('@')) {
  248. operNicks.push_back(rawOneNick);
  249. }
  250. else if (rawOneNick.startsWith('%')) {
  251. halfopNicks.push_back(rawOneNick);
  252. }
  253. else if (rawOneNick.startsWith('&')) {
  254. adminNicks.push_back(rawOneNick);
  255. }
  256. else if (rawOneNick.startsWith('+')) {
  257. voicedNicks.push_back(rawOneNick);
  258. }
  259. else {
  260. plainNicks.push_back(rawOneNick);
  261. }
  262. }
  263. if (not ownersNicks.isEmpty()) {
  264. std::sort(ownersNicks.begin(), ownersNicks.end());
  265. sortedNicknames += ownersNicks;
  266. }
  267. if (not operNicks.isEmpty()) {
  268. std::sort(operNicks.begin(), operNicks.end());
  269. sortedNicknames += operNicks;
  270. }
  271. if (not halfopNicks.isEmpty()) {
  272. std::sort(halfopNicks.begin(), halfopNicks.end());
  273. sortedNicknames += halfopNicks;
  274. }
  275. if (not adminNicks.isEmpty()) {
  276. std::sort(adminNicks.begin(), adminNicks.end());
  277. sortedNicknames += adminNicks;
  278. }
  279. if (not voicedNicks.isEmpty()) {
  280. std::sort(voicedNicks.begin(), voicedNicks.end());
  281. sortedNicknames += voicedNicks;
  282. }
  283. if (not plainNicks.isEmpty()) {
  284. std::sort(plainNicks.begin(), plainNicks.end());
  285. sortedNicknames += plainNicks;
  286. }
  287. sortedNicknames.removeAll("");
  288. m_onlineUsers[server][channel] = sortedNicknames;
  289. }
  290. void HttpServer::ircChannelTopic(QString server, QString channel, QString topic)
  291. {
  292. m_channelsTopic[server][channel] = topic;
  293. }
  294. void HttpServer::ircServerOnline(QString server, quint8 status)
  295. {
  296. if (server.isEmpty()) return;
  297. bool online = status;
  298. m_serversOnline[server] = online;
  299. }
  300. void HttpServer::ircBotNick(QString server, QString nickname)
  301. {
  302. m_botsNick[server] = nickname;
  303. }
  304. QString HttpServer::getRequestPath(const QString &req)
  305. {
  306. if (req.isEmpty()) return QString();
  307. QString result(req);
  308. int begin = result.indexOf(' ');
  309. if (begin == -1) return QString();
  310. result.remove(0, begin+1);
  311. int space = result.indexOf(' ');
  312. int size = result.size();
  313. result.remove(space, size-space);
  314. result = QByteArray::fromPercentEncoding(result.toUtf8());
  315. return result;
  316. }
  317. QString HttpServer::getWordFromPath(const QString &path)
  318. {
  319. QString result {path};
  320. result.remove(QRegularExpression("\\?.*$")); // any actions like a ?toSearch=
  321. if (result.startsWith('/')) {
  322. result.remove(QRegularExpression("^/"));
  323. }
  324. result.remove(QRegularExpression("/.*$"));
  325. return result;
  326. }
  327. void HttpServer::writeErrorPage(QTcpSocket *socket)
  328. {
  329. if (socket->isOpen()) {
  330. socket->write(HEADER_404.toUtf8());
  331. socket->write("<title>404</title><center><h1>NOT FOUND</H1></center>");
  332. }
  333. }
  334. void HttpServer::removeBrakelineSymbols(QString &line)
  335. {
  336. line.remove('\r');
  337. line.remove('\n');
  338. line.remove('\t');
  339. }
  340. void HttpServer::replaceTag(QString &page, const QString &tag, const QString &payload)
  341. {
  342. page.replace("{{"+tag+"}}", payload);
  343. }
  344. void HttpServer::writeMainPage(QTcpSocket *socket, QString &urlPath, bool isHeadRequest)
  345. {
  346. QString searchRequest;
  347. bool isRegexp {false};
  348. int specSymbol = urlPath.indexOf('?'); // any actions like a ?toSearch=
  349. if (specSymbol != -1) {
  350. searchRequest = global::getValue(urlPath, "toSearch", global::eForWeb);
  351. isRegexp = global::getValue(urlPath, "isRegexp", global::eForWeb) == "on";
  352. urlPath.remove(specSymbol, urlPath.size()-specSymbol);
  353. }
  354. if (urlPath == "/") {
  355. urlPath += m_mainChannel;
  356. }
  357. QFile main("://html/main.html");
  358. QString page;
  359. if (main.open(QIODevice::ReadOnly)) {
  360. page = main.readAll();
  361. main.close();
  362. }
  363. else {
  364. if (socket->isOpen()) {
  365. socket->write(HEADER_404.toUtf8());
  366. if (not isHeadRequest) socket->write("<title>Critical error</title><center><h1>NOT FOUND</H1><p>Maybe it's compile time error</p></center>");
  367. }
  368. }
  369. if (isRegexp) {
  370. page.replace("<input id=\"main_header__search_checkbox__button\" type=\"checkbox\" name=\"isRegexp\">",
  371. "<input id=\"main_header__search_checkbox__button\" type=\"checkbox\" name=\"isRegexp\" checked>");
  372. }
  373. QString server = getWordFromPath(urlPath);
  374. QDir fsPath(m_logFolder+server);
  375. if (not fsPath.exists()) {
  376. if (isHeadRequest) {
  377. if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
  378. } else {
  379. writeErrorPage(socket);
  380. }
  381. return;
  382. }
  383. urlPath.remove(QRegularExpression("^.*/"+server));
  384. QString channel = getWordFromPath(urlPath);
  385. channel.remove(QRegularExpression("\\?.*$"));
  386. if (channel.isEmpty()){
  387. // First channel is main if not passed directly
  388. QDirIterator it(fsPath.path());
  389. while (it.hasNext()) {
  390. channel = it.next();
  391. if (channel.endsWith(".") or channel.endsWith("..")) continue; // QDir::NoDotAndNoDotDot not works!
  392. while(channel.contains('/')) {
  393. channel.remove(QRegularExpression("^.*/"));
  394. }
  395. break;
  396. }
  397. }
  398. if (not fsPath.cd(channel)) {
  399. if (isHeadRequest) {
  400. if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
  401. } else {
  402. writeErrorPage(socket);
  403. }
  404. return;
  405. }
  406. QString originalServerName;
  407. for (const auto &s: m_onlineUsers) {
  408. if (global::toLowerAndNoSpaces(s.first) == server) {
  409. originalServerName = s.first;
  410. }
  411. }
  412. QString originalChannelName;
  413. for (const auto &server: m_onlineUsers) {
  414. for (const auto &channel_users: server.second) {
  415. if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) {
  416. originalChannelName = global::toLowerAndNoSpaces(channel_users.first);
  417. }
  418. }
  419. }
  420. urlPath.remove(QRegularExpression("^.*/"+channel));
  421. QString year = getWordFromPath(urlPath);
  422. year.remove(QRegularExpression("\\?.*$"));
  423. QString month;
  424. QString day;
  425. if (not year.isEmpty() and fsPath.cd(year)) {
  426. urlPath.remove(QRegularExpression("^.*/"+year));
  427. month = getWordFromPath(urlPath);
  428. month.remove(QRegularExpression("\\?.*$"));
  429. if (not month.isEmpty() and fsPath.cd(month)) {
  430. if (urlPath.startsWith("/"+month+"/")) {
  431. urlPath.remove(0,1);
  432. int pos = urlPath.indexOf('/');
  433. if (pos != -1) {
  434. urlPath.remove(0,pos);
  435. day = getWordFromPath(urlPath);
  436. if (urlPath.endsWith(".txt")) {
  437. QFile plain(fsPath.path()+global::slash+day);
  438. if (plain.open(QIODevice::ReadOnly)) {
  439. QString header = HEADER_TEXT;
  440. QByteArray file = plain.readAll();
  441. plain.close();
  442. replaceTag(header, "SIZE", QString::number(file.size()));
  443. if (socket->isOpen()) {
  444. socket->write(header.toUtf8());
  445. if (not isHeadRequest) socket->write(file);
  446. }
  447. }
  448. else {
  449. if (isHeadRequest) {
  450. if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
  451. } else {
  452. writeErrorPage(socket);
  453. }
  454. }
  455. return;
  456. }
  457. else {
  458. if (not QFile::exists(fsPath.path()+global::slash+day+".txt")) {
  459. day.clear();
  460. }
  461. }
  462. }
  463. }
  464. }
  465. else { month.clear(); }
  466. }
  467. else { year.clear(); }
  468. //// Left menu compilation
  469. QString htmlServersSectionS;
  470. for (const auto &s: m_onlineUsers) {
  471. if (s.first.isEmpty()) continue; // empty server name?
  472. QString htmlServersSection = HTML_SERVER_SECTION;
  473. replaceTag(htmlServersSection, "SERVER_NAME", s.first);
  474. QString htmlChannelLineS;
  475. for (const auto &c: s.second) {
  476. QString htmlChannelLine;
  477. if (originalServerName == s.first and originalChannelName == c.first) {
  478. htmlChannelLine = HTML_SERVER_SECTION_CHANNEL_SELECTED;
  479. } else {
  480. htmlChannelLine = HTML_SERVER_SECTION_CHANNEL;
  481. }
  482. replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first);
  483. QString channelNameForUrl {c.first};
  484. channelNameForUrl.remove('#');
  485. QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl;
  486. if (not year.isEmpty()) {
  487. channelLink += "/" + year;
  488. if (not month.isEmpty()) {
  489. channelLink += "/" + month;
  490. if (not day.isEmpty()) {
  491. channelLink += "/" + day;
  492. }
  493. }
  494. }
  495. replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink);
  496. htmlChannelLineS += htmlChannelLine;
  497. }
  498. replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS);
  499. bool online {false};
  500. for (const auto &srv: m_serversOnline) {
  501. if (srv.first == s.first) {
  502. online = srv.second;
  503. break;
  504. }
  505. }
  506. if (online) {
  507. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER);
  508. } else {
  509. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER);
  510. }
  511. htmlServersSectionS += htmlServersSection;
  512. }
  513. replaceTag(page, "SERVERS_SECTION", htmlServersSectionS);
  514. //// Main section header compilation
  515. if (m_onlineUsers.size() > 1) {
  516. replaceTag(page, "PAGE_TITLE", originalChannelName + " ("+originalServerName+") | IRCaBot");
  517. } else {
  518. replaceTag(page, "PAGE_TITLE", originalChannelName + " | IRCaBot");
  519. }
  520. replaceTag(page, "MAIN_HEADER", originalChannelName);
  521. if (not m_channelsTopic[originalServerName][originalChannelName].isEmpty()) {
  522. m_channelsTopic[originalServerName][originalChannelName].replace('\"', "&quot;");
  523. page.replace("<div class=\"main_header__title\">",
  524. "<div class=\"main_header__title\" title=\"" + m_channelsTopic[originalServerName][originalChannelName] + "\">");
  525. }
  526. QString middlePath = "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"\">" + "/" + "</a>";
  527. if (not year.isEmpty()) {
  528. middlePath += "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"/"+year+"\">" + year + "</a>";
  529. if (not month.isEmpty()) {
  530. middlePath += "/<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"/"+year+"/"+month+"\">" + month + "</a>";
  531. if (not day.isEmpty()) {
  532. middlePath += "/" + day;
  533. }
  534. }
  535. }
  536. int currentOnline = 0;
  537. QString onlineUserS;
  538. for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
  539. if (QRegularExpression("^(@|\\&|\\+|~)?"+m_botsNick[originalServerName]+"$").match(user).hasMatch()) {
  540. continue;
  541. }
  542. QString onlineUser = HTML_ONLINE_POINT;
  543. replaceTag(onlineUser, "NICKNAME", user);
  544. onlineUserS += onlineUser;
  545. currentOnline++;
  546. }
  547. replaceTag(page, "ONLINE", QString::number(currentOnline));
  548. replaceTag(page, "ONLINE_LIST", onlineUserS);
  549. if (not searchRequest.isEmpty()) {
  550. page.replace("<input class=\"main_header__search_input\" type=\"search\" name=\"toSearch\" placeholder=\"{{SEARCH_PLACEHOLDER}}\">",
  551. "<input class=\"main_header__search_input\" type=\"search\" name=\"toSearch\" value=\"" + searchRequest + "\">");
  552. } else if (middlePath == "/") {
  553. replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName);
  554. } else if (month.isEmpty()) {
  555. replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName + "/" + year);
  556. } else {
  557. replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName + "/" + year + "/" + month);
  558. }
  559. //// Main section body compilation
  560. QString payloadBlock;
  561. // Search request
  562. if (not searchRequest.isEmpty()) {
  563. uint counter = 0;
  564. QRegularExpression userRgx(searchRequest, QRegularExpression::CaseInsensitiveOption);
  565. bool rgxIsValid = false;
  566. if (isRegexp and userRgx.isValid()) {
  567. rgxIsValid = true;
  568. }
  569. consoleLog("Search request (" + server + "): " + searchRequest);
  570. if (not day.isEmpty()) {
  571. middlePath.remove(QRegularExpression("/[0-9]{2}$"));
  572. }
  573. QStringList paths;
  574. QDirIterator it(fsPath.path());
  575. while (it.hasNext()) {
  576. QString currentPath = it.next();
  577. if (currentPath.endsWith(".") or currentPath.endsWith("..")) continue;
  578. QString logFolder = m_logFolder;
  579. #ifdef WIN32
  580. logFolder.replace('\\', '/');
  581. #endif
  582. QString server {currentPath}; // Folder wich is not server folder is ignored
  583. server.remove(QRegularExpression("^"+logFolder));
  584. server.remove(QRegularExpression("/.*$"));
  585. bool serverIsOk = false;
  586. for (const auto &srv: m_onlineUsers) {
  587. if (global::toLowerAndNoSpaces(srv.first) == server) {
  588. serverIsOk = true;
  589. break;
  590. }
  591. }
  592. if (not serverIsOk) continue;
  593. QString currentChannel {currentPath}; // Folder wich is not channel folder is ignored
  594. currentChannel.remove(QRegularExpression("^"+logFolder+"[^/]*/"));
  595. currentChannel.remove(QRegularExpression("/.*$"));
  596. bool channelIsOk = false; // Канал явно указан в конфиге
  597. for (const auto &ch: m_onlineUsers[originalServerName]) {
  598. QString searchChan {ch.first};
  599. searchChan.remove('#');
  600. if (searchChan == currentChannel) {
  601. channelIsOk = true;
  602. break;
  603. }
  604. }
  605. if (not channelIsOk) continue;
  606. paths.push_back(currentPath);
  607. }
  608. if (paths.isEmpty()) {
  609. payloadBlock = HTML_PAYLOAD_ERROR;
  610. replaceTag(payloadBlock, "ERROR_TITLE", "Not found");
  611. replaceTag(payloadBlock, "ERROR_TEXT", "");
  612. }
  613. else {
  614. std::map<QString, QStringList> matchedPathsAndMessages;
  615. if (not month.isEmpty()) {
  616. for (const auto& path: paths) {
  617. if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(path).hasMatch()) continue;
  618. QFile file(path);
  619. if (not file.open(QIODevice::ReadOnly)) {
  620. consoleLog("Error! I can't open log file " + fsPath.path());
  621. continue;
  622. }
  623. QString buffer {file.readLine()};
  624. while (not buffer.isEmpty()) {
  625. removeBrakelineSymbols(buffer);
  626. bool finded = false;
  627. if (rgxIsValid) {
  628. if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) {
  629. finded = true;
  630. }
  631. } else {
  632. if (buffer.contains(searchRequest, Qt::CaseInsensitive)) {
  633. finded = true;
  634. }
  635. }
  636. if (finded) {
  637. std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
  638. if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
  639. buffer = file.readLine();
  640. continue;
  641. }
  642. counter++;
  643. QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  644. for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
  645. if (QRegularExpression("^.?"+rawMessage.first+"$").match(user).hasMatch()) {
  646. message.replace("<div class=\"main_payload__chat_username\">",
  647. "<div class=\"main_payload__chat_username\" style=\"color: green\">");
  648. break;
  649. }
  650. }
  651. replaceTag(message, "USERNAME", rawMessage.first);
  652. replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
  653. matchedPathsAndMessages[path].push_back(message);
  654. }
  655. buffer = file.readLine();
  656. }
  657. file.close();
  658. }
  659. }
  660. else if (month.isEmpty() and not year.isEmpty()){
  661. for (const auto &p: paths) {
  662. QStringList slavePaths;
  663. QDirIterator it(p);
  664. while (it.hasNext()) {
  665. QString fileName = it.next();
  666. if (fileName.endsWith(".") or fileName.endsWith("..")) continue;
  667. if (not QRegularExpression("\\.txt$").match(fileName).hasMatch()) continue;
  668. slavePaths.push_back(fileName);
  669. }
  670. for (const auto &path: slavePaths) {
  671. if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(path).hasMatch()) continue;
  672. QFile file(path);
  673. if (not file.open(QIODevice::ReadOnly)) {
  674. consoleLog("Error! I can't open log file " + fsPath.path());
  675. continue;
  676. }
  677. QString buffer {file.readLine()};
  678. while (not buffer.isEmpty()) {
  679. removeBrakelineSymbols(buffer);
  680. bool finded = false;
  681. if (rgxIsValid) {
  682. if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) {
  683. finded = true;
  684. }
  685. } else {
  686. if (buffer.contains(searchRequest, Qt::CaseInsensitive)) {
  687. finded = true;
  688. }
  689. }
  690. if (finded) {
  691. std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
  692. if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
  693. buffer = file.readLine();
  694. continue;
  695. }
  696. counter++;
  697. QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  698. for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
  699. if (QRegularExpression("^.?"+rawMessage.first+"$").match(user).hasMatch()) {
  700. message.replace("<div class=\"main_payload__chat_username\">",
  701. "<div class=\"main_payload__chat_username\" style=\"color: green\">");
  702. break;
  703. }
  704. }
  705. replaceTag(message, "USERNAME", rawMessage.first);
  706. replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
  707. matchedPathsAndMessages[path].push_back(message);
  708. }
  709. buffer = file.readLine();
  710. }
  711. file.close();
  712. }
  713. }
  714. }
  715. else { // root directory
  716. QStringList yearPaths;
  717. for (const auto& p: paths) {
  718. if (not QRegularExpression("/2[0-9]{3}$").match(p).hasMatch()) continue;
  719. yearPaths.push_back(p);
  720. }
  721. /* If you are reading this code, maybe you are crying now.
  722. * Sorry me, man (woman/fagot/etc). Maybe I'll refactor this hell in the future.
  723. * acetone.
  724. */
  725. QStringList fileNameS;
  726. for (const auto& p: yearPaths) {
  727. QStringList slavePaths;
  728. QDirIterator it(p);
  729. while (it.hasNext()) {
  730. QString folderName = it.next();
  731. if (folderName.endsWith(".") or folderName.endsWith("..")) continue;
  732. if (not QRegularExpression("/[0-9]{2}$").match(folderName).hasMatch()) continue;
  733. slavePaths.push_back(folderName);
  734. }
  735. for (const auto &path: slavePaths) {
  736. QDirIterator itMonth(path);
  737. while (itMonth.hasNext()) {
  738. QString fileName = itMonth.next();
  739. if (fileName.endsWith(".") or fileName.endsWith("..")) continue;
  740. if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(fileName).hasMatch()) continue;
  741. fileNameS.push_back(fileName);
  742. }
  743. }
  744. }
  745. for (const auto& path: fileNameS) {
  746. QFile file(path);
  747. if (not file.open(QIODevice::ReadOnly)) {
  748. consoleLog("Error! I can't open log file " + fsPath.path());
  749. continue;
  750. }
  751. QString buffer {file.readLine()};
  752. while (not buffer.isEmpty()) {
  753. removeBrakelineSymbols(buffer);
  754. bool finded = false;
  755. if (rgxIsValid) {
  756. if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) {
  757. finded = true;
  758. }
  759. } else {
  760. if (buffer.contains(searchRequest, Qt::CaseInsensitive)) {
  761. finded = true;
  762. }
  763. }
  764. if (finded) {
  765. std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
  766. if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
  767. buffer = file.readLine();
  768. continue;
  769. }
  770. counter++;
  771. QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  772. for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
  773. if (QRegularExpression("^.?"+rawMessage.first+"$").match(user).hasMatch()) {
  774. message.replace("<div class=\"main_payload__chat_username\">",
  775. "<div class=\"main_payload__chat_username\" style=\"color: green\">");
  776. break;
  777. }
  778. }
  779. replaceTag(message, "USERNAME", rawMessage.first);
  780. replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
  781. matchedPathsAndMessages[path].push_back(message);
  782. }
  783. buffer = file.readLine();
  784. }
  785. file.close();
  786. }
  787. }
  788. if (matchedPathsAndMessages.empty()) {
  789. payloadBlock = HTML_PAYLOAD_ERROR;
  790. replaceTag(payloadBlock, "ERROR_TITLE", "Not found");
  791. replaceTag(payloadBlock, "ERROR_TEXT", "");
  792. }
  793. else {
  794. QStringList findedPaths;
  795. for (const auto& fp: matchedPathsAndMessages) {
  796. findedPaths << fp.first;
  797. }
  798. std::sort(findedPaths.begin(), findedPaths.end());
  799. for (auto& link: findedPaths) {
  800. QString logFolder {m_logFolder};
  801. logFolder.remove(QRegularExpression(".$"));
  802. QStringList& messages = matchedPathsAndMessages[link];
  803. link.remove(logFolder);
  804. link.remove(QRegularExpression("\\.txt$"));
  805. QString finded = HTML_PAYLOAD_LIST_POINT_MESSAGE;
  806. finded.replace("class=\"main_payload__block\"", "class=\"main_payload__block\" style=\"background: #b6c7d6\"");
  807. replaceTag(finded, "POINT_LINK", link);
  808. link.remove(QRegularExpression("^.*"+channel));
  809. replaceTag(finded, "POINT_CONTENT", link);
  810. payloadBlock += finded;
  811. for(const auto& m: messages) {
  812. payloadBlock += m;
  813. }
  814. payloadBlock += "&nbsp;\n";
  815. }
  816. }
  817. }
  818. middlePath += " " + searchRequest + " ";
  819. if (rgxIsValid) middlePath += "rgx";
  820. middlePath += "(" + QString::number(counter) + ")";
  821. }
  822. // Plain log explorer
  823. else {
  824. if (year.isEmpty()) { // /
  825. QStringList folderNameS;
  826. QDirIterator it(fsPath.path());
  827. while (it.hasNext()) {
  828. QString folderName = it.next();
  829. if (folderName.endsWith(".") or folderName.endsWith("..")) continue;
  830. while(folderName.contains('/')) {
  831. folderName.remove(QRegularExpression("^.*/"));
  832. }
  833. folderName.remove(QRegularExpression("\\.txt$"));
  834. folderNameS << folderName;
  835. }
  836. if (not folderNameS.isEmpty()) {
  837. std::sort(folderNameS.begin(), folderNameS.end());
  838. for (const auto &f: folderNameS) {
  839. QString onePoint = HTML_PAYLOAD_LIST_POINT_FOLDER;
  840. replaceTag(onePoint, "POINT_CONTENT", f);
  841. replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+f);
  842. payloadBlock += onePoint;
  843. }
  844. }
  845. }
  846. else if (not year.isEmpty() and month.isEmpty()) { // /YYYY
  847. QStringList folderNameS;
  848. QDirIterator it(fsPath.path());
  849. while (it.hasNext()) {
  850. QString folderName = it.next();
  851. if (folderName.endsWith(".") or folderName.endsWith("..")) continue;
  852. while(folderName.contains('/')) {
  853. folderName.remove(QRegularExpression("^.*/"));
  854. }
  855. folderName.remove(QRegularExpression("\\.txt$"));
  856. folderNameS << folderName;
  857. }
  858. if (not folderNameS.isEmpty()) {
  859. std::sort(folderNameS.begin(), folderNameS.end());
  860. for (const auto &f: folderNameS) {
  861. QString onePoint = HTML_PAYLOAD_LIST_POINT_FOLDER;
  862. replaceTag(onePoint, "POINT_CONTENT", f);
  863. replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+year+"/"+f);
  864. payloadBlock += onePoint;
  865. }
  866. }
  867. }
  868. else if (not month.isEmpty() and day.isEmpty()) { // /YYYY/MM
  869. QStringList fileNameS;
  870. QDirIterator it(fsPath.path());
  871. while (it.hasNext()) {
  872. QString fileName = it.next();
  873. if (fileName.endsWith(".") or fileName.endsWith("..")) continue; // QDir::NoDotAndNoDotDot not works!
  874. while(fileName.contains('/')) {
  875. fileName.remove(QRegularExpression("^.*/"));
  876. }
  877. fileName.remove(QRegularExpression("\\.txt$"));
  878. fileNameS << fileName;
  879. }
  880. if (not fileNameS.isEmpty()) {
  881. std::sort(fileNameS.begin(), fileNameS.end());
  882. for (const auto &a: fileNameS) {
  883. QString onePoint = HTML_PAYLOAD_LIST_POINT_MESSAGE;
  884. replaceTag(onePoint, "POINT_CONTENT", a);
  885. replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+year+"/"+month+"/"+a);
  886. payloadBlock += onePoint;
  887. }
  888. }
  889. }
  890. else if (not day.isEmpty()) { // /YYYY/MM/dd
  891. QFile file(fsPath.path()+global::slash+day+".txt");
  892. if (not file.open(QIODevice::ReadOnly)) {
  893. consoleLog("Error! I can't open log file " + fsPath.path());
  894. payloadBlock = HTML_PAYLOAD_ERROR;
  895. replaceTag(payloadBlock, "ERROR_TITLE", "Internal error");
  896. replaceTag(payloadBlock, "ERROR_TEXT", "Requested log file openning failed");
  897. }
  898. else {
  899. QString buffer = file.readLine();
  900. while (not buffer.isEmpty()) {
  901. removeBrakelineSymbols(buffer);
  902. std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
  903. if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
  904. buffer = file.readLine();
  905. continue;
  906. }
  907. QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  908. for (const auto &user: m_onlineUsers[originalServerName][originalChannelName]) {
  909. if (QRegularExpression("^.?"+rawMessage.first+"$").match(user).hasMatch()) {
  910. message.replace("<div class=\"main_payload__chat_username\">",
  911. "<div class=\"main_payload__chat_username\" style=\"color: green\">");
  912. break;
  913. }
  914. }
  915. replaceTag(message, "USERNAME", rawMessage.first);
  916. replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
  917. payloadBlock += message;
  918. buffer = file.readLine();
  919. }
  920. file.close();
  921. }
  922. }
  923. if (payloadBlock.isEmpty()) {
  924. payloadBlock = HTML_PAYLOAD_ERROR;
  925. replaceTag(payloadBlock, "ERROR_TITLE", "Empty");
  926. replaceTag(payloadBlock, "ERROR_TEXT", "No logs found for this channel");
  927. }
  928. }
  929. replaceTag(page, "MIDDLE_PATH", middlePath);
  930. replaceTag(page, "PAYLOAD_BLOCK", payloadBlock);
  931. //// Footer
  932. replaceTag(page, "VERSION", IRCABOT_VERSION);
  933. replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR);
  934. QString mainHeader = HEADER_HTML;
  935. replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size()));
  936. if (socket->isOpen()) {
  937. socket->write(mainHeader.toUtf8());
  938. if (not isHeadRequest) socket->write(page.toUtf8());
  939. }
  940. }