\r\n"
+ "";
+}
+
+QByteArray HttpDocument::document() const
+{
+ static constexpr const char HTML_NEWLINE[] {"\r\n"};
+
+ QByteArray result = "HTTP/1.0 " + code().toUtf8() + HTML_NEWLINE;
+
+ auto h = headers();
+ QMapIterator iter(h);
+ while (iter.hasNext())
+ {
+ iter.next();
+ result += (iter.key()+": "+iter.value()+HTML_NEWLINE).toUtf8();
+ }
+
+ result += QString( CommonHeader::ContentLength + ": " +
+ QString::number( body().size()).toUtf8() ).toUtf8() + HTML_NEWLINE;
+ result += QString( CommonHeader::ProcessingDuration + ": " +
+ QString::number( QDateTime::currentMSecsSinceEpoch()-m_processingDurationStartMarker) ).toUtf8() + HTML_NEWLINE;
+ result += HTML_NEWLINE;
+
+ result += body();
+
+ return result;
+}
diff --git a/src/httpdocument.h b/src/httpdocument.h
new file mode 100644
index 0000000..c9b5317
--- /dev/null
+++ b/src/httpdocument.h
@@ -0,0 +1,87 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class HttpDocument
+{
+public:
+ struct CommonHeader
+ {
+ static const QString ContentType;
+ static const QString ContentLength;
+ static const QString ETag;
+ static const QString ProcessingDuration;
+ };
+
+ struct ContentType
+ {
+ static const QString HTML;
+ static const QString TEXT;
+ static const QString CSS;
+ static const QString SVG;
+ static const QString ICO;
+ static const QString PNG;
+ static const QString GIF;
+ static const QString JS;
+ static const QString WOFF;
+ static const QString WOFF2;
+ static const QString JSON;
+ static const QString MP3;
+ static const QString BIN;
+ };
+
+ struct Code {
+ static const QString _200;
+ static const QString _303;
+ static const QString _304;
+ static const QString _400;
+ static const QString _403;
+ static const QString _404;
+ static const QString _413;
+ static const QString _500;
+ };
+
+ HttpDocument();
+ void setCode(const QString& code = Code::_200);
+ QString code() const { return m_code; }
+
+ void setHeader(const QString& name, const QString& value);
+ QMap headers() const { return m_headers; }
+
+ void setBody(const QByteArray& body);
+ void setBody(const QJsonObject& body);
+ void setBody(QFile payload, const QString& eTag = QString());
+ void setBodySingleHTMLLabel(const QString& text, const QString& title = "Notice");
+ QByteArray body() const { return m_body; }
+
+ QByteArray document() const;
+
+private:
+ QString m_code = Code::_200;
+ QMap m_headers;
+ QByteArray m_body;
+
+ const qint64 m_processingDurationStartMarker;
+};
diff --git a/src/httpserver.cpp b/src/httpserver.cpp
new file mode 100644
index 0000000..6d1f148
--- /dev/null
+++ b/src/httpserver.cpp
@@ -0,0 +1,43 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#include "httpserver.h"
+#include "socketrunnable.h"
+
+HttpServer::HttpServer(QObject *parent) :
+ WebServerBase(parent)
+{
+
+}
+
+void HttpServer::killTheCapitalism()
+{
+ qWarning() << "Try to kill capitalism...";
+ QThread::sleep(1);
+ qWarning() << "Pick up black flag...";
+ QThread::sleep(1);
+ qWarning() << "Hmmm. Timed out. Try later with friends!";
+}
+
+void HttpServer::incomingConnection(qintptr handle)
+{
+ SocketRunnable* socket = new SocketRunnable(handle);
+ socket->setAutoDelete(true);
+ pool()->start(socket);
+}
diff --git a/src/httpserver.h b/src/httpserver.h
new file mode 100644
index 0000000..17630e8
--- /dev/null
+++ b/src/httpserver.h
@@ -0,0 +1,33 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#pragma once
+
+#include "webserverbase.h"
+
+class HttpServer : public WebServerBase
+{
+public:
+ HttpServer(QObject *parent = nullptr);
+ void killTheCapitalism();
+
+ // QTcpServer interface
+protected:
+ void incomingConnection(qintptr handle);
+};
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..4b06ded
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,209 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#include "proxyinstanse.h"
+#include "httpserver.h"
+#include "g.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+void terminate(int)
+{
+ std::cout << "Terminating..." << std::endl;
+ for (const auto& instanse: g::instanses)
+ {
+ instanse.first->terminate();
+ }
+}
+
+void usage()
+{
+ std::cout << g::c::SOFTWARE_NAME.toStdString() << " " << g::c::SOFTWARE_VERSION.toStdString() << std::endl
+ << "Accumulate ethical 3proxy statistics with web interface\n\n"
+
+ "U S A G E:\n"
+ " -i --instanse <3proxy>,<3proxy.cfg>\n"
+ " -w --working-directory \n"
+ " -t --service-title <3proxy-eagle>\n"
+ " -s --last-and-top-list-size <5>\n"
+ " -I --ignored-destinations <[0.0.0.0],127.0.0.1>\n"
+ " -a --bind-to-address <127.0.0.1>\n"
+ " -p --bind-to-port <8161>\n"
+ " -l --log-level (off, error, warn, info, debug)\n\n"
+
+ "N O T E S:\n"
+ "* Multi instanses supported. Just pass new one --instanse value!\n"
+ "* Main 3proxy cfg must contain log to stdout with strict format:\n"
+ " log\n"
+ " logformat \" type=%N destination=%n to=%I from=%O\"\n"
+ "* 3proxy cfg can contain blocked domains in format:\n"
+ " {{vk.com,mail.ru,google.com}}\n"
+ " This domains will be resolved automatically and replaced by:\n"
+ " deny * * original.domain\n"
+ " deny * * 8.8.8.8,1.1.1.1 # resolved addresses"
+ "* If the working directory contains a information.html, the Information\n"
+ " box will be added to the web page. The block is full html-formatted.\n"
+ "* The html folder can contain any files, they will be available\n"
+ " for downloading through the web browser.\n"
+ "* html folder can contain styles.css file for overloading default styles.\n"
+ " See page source via web browser to customize CSS classes.\n"
+
+ "\n" << g::c::COPYRIGHT.toStdString() << std::endl;
+}
+
+int main(int argc, char *argv[])
+{
+ signal(SIGINT, terminate);
+ signal(SIGTERM, terminate);
+
+ qInstallMessageHandler(g::customMessageOutput);
+
+ QCoreApplication a(argc, argv);
+
+ QList> instanses;
+
+ for (int i = 1; i < argc; i++)
+ {
+ QString key(argv[i]);
+ QString value;
+ if (i+1 < argc)
+ {
+ value = argv[i+1];
+ }
+
+ if (key == "--log-level" and not value.isEmpty())
+ {
+ if (value.contains("off", Qt::CaseInsensitive))
+ {
+ g::logLevel = g::LogLevel::Off;
+ }
+ else if (value.contains("error", Qt::CaseInsensitive))
+ {
+ g::logLevel = g::LogLevel::Error;
+ }
+ else if (value.contains("warn", Qt::CaseInsensitive))
+ {
+ g::logLevel = g::LogLevel::Warning;
+ }
+ else if (value.contains("info", Qt::CaseInsensitive))
+ {
+ g::logLevel = g::LogLevel::Info;
+ }
+ else if (value.contains("debug", Qt::CaseInsensitive))
+ {
+ g::logLevel = g::LogLevel::Debug;
+ }
+ else
+ {
+ qWarning() << "Invalid log level flag:" << value << "(maybe you should read the --help)";
+ }
+ }
+
+ else if ((key == "-i" or key == "--instanse") and not value.isEmpty())
+ {
+ QStringList splitted = value.split(',');
+ if (splitted.size() != 2)
+ {
+ qWarning() << "--instanse parsing failed:" << value;
+ continue;
+ }
+ instanses.push_back( {splitted.front(), splitted.back()} );
+ }
+
+ else if ((key == "-w" or key == "--working-directory") and not value.isEmpty())
+ {
+ g::p::WORKING_DIR = value;
+ }
+
+ else if ((key == "-s" or key == "--last-and-top-list-size") and not value.isEmpty())
+ {
+ bool ok = false;
+ value.toUInt(&ok);
+ if (not ok)
+ {
+ qWarning() << "--last-and-top-list-size parsing failed, not a positive number:" << value;
+ continue;
+ }
+ g::p::LAST_AND_TOP_LIST_SIZE = value.toUInt();
+ }
+
+ else if ((key == "-I" or key == "--ignored-destinations") and not value.isEmpty())
+ {
+ g::p::IGNORED_DESTINATIONS = value.split(',');
+ }
+
+ else if ((key == "-b" or key == "--bind-to-address") and not value.isEmpty())
+ {
+ g::p::BIND_TO_ADDRESS = value;
+ }
+
+ else if ((key == "-p" or key == "--bind-to-port") and not value.isEmpty())
+ {
+ bool ok = false;
+ value.toUShort(&ok);
+ if (not ok)
+ {
+ qWarning() << "--bind-to-port parsing failed, incorrect port value:" << value;
+ continue;
+ }
+ g::p::BIND_TO_PORT = value.toUShort();
+ }
+
+ else if ((key == "-t" or key == "--service-title") and not value.isEmpty())
+ {
+ g::p::SERVICE_TITLE = value;
+ }
+
+ else if (key == "-h" or key == "--help")
+ {
+ usage();
+ return 0;
+ }
+ }
+
+ qInfo().noquote() << "Instanses count:" << instanses.size();
+ qInfo().noquote() << "Working directory:" << g::p::WORKING_DIR;
+ qInfo().noquote() << "Last and top dest list size:" << g::p::LAST_AND_TOP_LIST_SIZE;
+ qInfo().noquote() << "Ignored destinations:" << g::p::IGNORED_DESTINATIONS;
+ qInfo().noquote() << "Bind to address:" << g::p::BIND_TO_ADDRESS;
+ qInfo().noquote() << "Bind to port:" << g::p::BIND_TO_PORT;
+ qInfo() << "";
+
+ for (const auto& pair: instanses)
+ {
+ QThread* thread = new QThread;
+ ProxyInstanse* pi = new ProxyInstanse;
+ pi->set3proxyBinaryFile(pair.first);
+ pi->set3proxyConfigFile(pair.second);
+ pi->moveToThread(thread);
+ QObject::connect(thread, &QThread::started, pi, &ProxyInstanse::start);
+ QObject::connect(thread, &QThread::finished, pi, &ProxyInstanse::deleteLater);
+ thread->start();
+ g::instanses.push_back( {thread, pi} );
+ }
+
+ (new HttpServer)/*->killTheCapitalism()*/;
+
+ return a.exec();
+}
diff --git a/src/proxyinstanse.cpp b/src/proxyinstanse.cpp
new file mode 100644
index 0000000..2a27dac
--- /dev/null
+++ b/src/proxyinstanse.cpp
@@ -0,0 +1,237 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#include "proxyinstanse.h"
+#include "statistics.h"
+#include "g.h"
+
+#include
+#include
+#include
+#include
+
+ProxyInstanse::ProxyInstanse(QObject* parent)
+ : QObject(parent)
+{
+
+}
+
+ProxyInstanse::~ProxyInstanse()
+{
+ qInfo() << "Proxy instanse" << g::hash(confPath().toUtf8()) << "terminated";
+
+ if (m_process)
+ {
+ m_process->terminate();
+ connect(m_process, QOverload::of(&QProcess::finished),
+ m_process, &QProcess::deleteLater);
+ }
+}
+
+void ProxyInstanse::set3proxyBinaryFile(const QString &pathTo3proxyBinFile)
+{
+ if (m_process)
+ {
+ qWarning() << "ProxyInstanse::set3proxyBinaryFile() process already started";
+ }
+ if (not QFile::exists(pathTo3proxyBinFile))
+ {
+ qCritical() << "ProxyInstanse::set3proxyBinaryFile()" << pathTo3proxyBinFile << "not exists";
+ }
+ m_binPath = pathTo3proxyBinFile;
+}
+
+void ProxyInstanse::set3proxyConfigFile(const QString &pathTo3proxyConfFile)
+{
+ if (m_process)
+ {
+ qWarning() << "ProxyInstanse::set3proxyConfigFile() process already started";
+ }
+
+ if (not QFile::exists(pathTo3proxyConfFile))
+ {
+ qCritical() << "ProxyInstanse::set3proxyConfigFile()" << pathTo3proxyConfFile << "not exists";
+ }
+
+ m_confPath = pathTo3proxyConfFile;
+
+ createConfigurationFile();
+}
+
+void ProxyInstanse::generateBlackList(const QStringList &domainsOrAddresses)
+{
+ qDebug() << "ProxyInstanse::generateBlackList() raw:" << domainsOrAddresses;
+
+ QStringListIterator iterRaw(domainsOrAddresses);
+ while (iterRaw.hasNext())
+ {
+ QString name = iterRaw.next();
+ m_blackList.insert(name, QStringList());
+ static QRegularExpression rgx_hiddenNetworks("^.*\\.(i2p|onion|loki)$");
+ if (name.contains(rgx_hiddenNetworks))
+ {
+ qDebug() << name << "is hidden network name, no attempt to resolve";
+ continue;
+ }
+
+ QHostInfo host = QHostInfo::fromName(name);
+ if (host.error() != QHostInfo::NoError)
+ {
+ qDebug() << "Can not resolve" << name << ":" << host.errorString();
+ continue;
+ }
+ else if (host.addresses().first().toString() == name)
+ {
+ qDebug() << name << "not a domain name (it's normal, just debug info)";
+ continue;
+ }
+
+ auto resolved = host.addresses();
+ for (auto it = resolved.begin(); it != resolved.end(); ++it)
+ {
+ QString address = it->toString();
+ qDebug() << "Domain" << name << "resolved to" << address;
+ m_blackList[name].push_back(address);
+ }
+ }
+}
+
+QString ProxyInstanse::workingConfigPath() const
+{
+ return g::p::WORKING_DIR + "/" + g::hash(confPath()) + ".cfg";
+}
+
+void ProxyInstanse::start()
+{
+ m_process = new QProcess;
+ m_process->setProgram(binPath());
+ m_process->setArguments(QStringList() << workingConfigPath());
+
+ connect (m_process, &QProcess::readyReadStandardOutput, this, &ProxyInstanse::reader);
+ connect (m_process, QOverload::of(&QProcess::finished),
+ this, &ProxyInstanse::restart);
+
+ m_process->start();
+}
+
+void ProxyInstanse::reader()
+{
+ QMutexLocker lock (&m_readerMtx);
+
+ m_readBuffer = m_process->readAll();
+
+ bool lastStringIsUncomplete = not m_readBuffer.endsWith('\n');
+ QStringList completeLines = m_readBuffer.split('\n');
+
+ if (lastStringIsUncomplete)
+ {
+ m_readBuffer = completeLines.last();
+ completeLines.pop_back();
+ }
+
+ for (const auto& str: completeLines)
+ {
+ if (str.isEmpty()) continue;
+ QString type = g::getValue(str, "type");
+ QString dest = g::getValue(str, "destination");
+ quint64 to = g::getValue(str, "to").toULongLong();
+ quint64 from = g::getValue(str, "from").toULongLong();
+
+ if (type.isEmpty() or dest.isEmpty())
+ {
+ qWarning() << "Receive incorrect log format. Please read --help for usage information";
+ continue;
+ }
+
+ qDebug() << "Type:" << type
+ << "dest:" << dest
+ << "to:" << to
+ << "from:" << from;
+
+ Statistics::report( {type, dest, to, from} );
+ }
+}
+
+void ProxyInstanse::restart()
+{
+ qCritical() << "Called because process unexpected finished";
+ m_process->deleteLater();
+ start();
+}
+
+void ProxyInstanse::createConfigurationFile()
+{
+ QFile original (confPath());
+ if (not original.open(QIODevice::ReadOnly))
+ {
+ qFatal("Can not read original cfg");
+ return;
+ }
+ QString originalFile = original.readAll();
+ original.close();
+
+ static QRegularExpression rgx_domainsPattern ("^.*\\{\\{.*\\}\\}.*", QRegularExpression::DotMatchesEverythingOption);
+ if (originalFile.contains(rgx_domainsPattern))
+ {
+ QString rawDomainsList = originalFile;
+ static QRegularExpression rgx_listBegin ("^.*\\{\\{", QRegularExpression::DotMatchesEverythingOption);
+ rawDomainsList.remove(rgx_listBegin);
+ static QRegularExpression rgx_listEnd ("\\}\\}.*$", QRegularExpression::DotMatchesEverythingOption);
+ rawDomainsList.remove(rgx_listEnd);
+ if (rawDomainsList.isEmpty())
+ {
+ qWarning() << "Blocked domains pattern detected, but list is empty in" << confPath();
+ }
+ else
+ {
+ generateBlackList(rawDomainsList.split(','));
+
+ QString blockedDomainsConfigPart;
+ QMapIterator mapIterator (m_blackList);
+ while (mapIterator.hasNext())
+ {
+ auto currentNode = mapIterator.next();
+ blockedDomainsConfigPart += "deny * * " + currentNode.key() + '\n';
+ for (const auto& addr: currentNode.value())
+ {
+ blockedDomainsConfigPart += "deny * * " + addr + '\n';
+ }
+ }
+
+ static QRegularExpression rgx_domains ("\\{\\{.*\\}\\}", QRegularExpression::DotMatchesEverythingOption);
+ originalFile.replace(rgx_domains, blockedDomainsConfigPart);
+ }
+ }
+
+ QFile tmpConfig (workingConfigPath());
+ if (not tmpConfig.open(QIODevice::WriteOnly))
+ {
+ qFatal("Can not write working config");
+ return;
+ }
+
+ auto cfgByteArray = originalFile.toUtf8();
+ int cfgSize = cfgByteArray.size();
+ int writed = tmpConfig.write(cfgByteArray);
+ if (cfgSize != writed)
+ {
+ qCritical() << "Write error:" << writed << "/" << cfgSize;
+ }
+ tmpConfig.close();
+}
diff --git a/src/proxyinstanse.h b/src/proxyinstanse.h
new file mode 100644
index 0000000..c3711c2
--- /dev/null
+++ b/src/proxyinstanse.h
@@ -0,0 +1,59 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class ProxyInstanse : public QObject
+{
+ Q_OBJECT
+public:
+ ProxyInstanse(QObject* parent = nullptr);
+ ~ProxyInstanse();
+ void set3proxyBinaryFile(const QString& pathTo3proxyBinFile);
+ void set3proxyConfigFile(const QString& pathTo3proxyConfFile);
+
+ QString binPath() const { return m_binPath; }
+ QString confPath() const { return m_confPath; }
+ QMap blackList() const { return m_blackList; }
+
+public slots:
+ void start();
+
+private slots:
+ void reader();
+ void restart();
+
+private:
+ void createConfigurationFile();
+ void generateBlackList(const QStringList& domainsOrAddresses);
+ QString workingConfigPath() const;
+
+ QString m_binPath;
+ QString m_confPath;
+ QProcess* m_process = nullptr;
+ QMap m_blackList; // domain, addresses
+
+ QMutex m_readerMtx;
+ QString m_readBuffer;
+};
diff --git a/src/socketrunnable.cpp b/src/socketrunnable.cpp
new file mode 100644
index 0000000..7ebb1d0
--- /dev/null
+++ b/src/socketrunnable.cpp
@@ -0,0 +1,118 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#include "socketrunnable.h"
+#include "g.h"
+#include "webpage.h"
+
+#include
+
+constexpr const int getValueBeforeSpaceLIMIT = 200;
+constexpr const int getValueBeforeBodyLIMIT = 1000;
+
+SocketRunnable::SocketRunnable(qintptr socketDescriptor, QObject *parent) :
+ SocketRunnableBase(socketDescriptor, parent)
+{
+}
+
+void SocketRunnable::get()
+{
+ if (urlPath() == "/index.html")
+ {
+ httpDocument().setBody(WebPage::document());
+ return;
+ }
+
+ QString eTag = g::getValue(m_headers, "If-None-Match", g::GetValueType::HttpHeader);
+
+ if (urlPath() == "/main.css")
+ {
+ httpDocument().setBody(QFile(":/html/main.css"), eTag);
+ }
+ else
+ {
+ httpDocument().setBody(QFile(g::p::WORKING_DIR + "/html" + urlPath()), eTag);
+ }
+}
+
+void SocketRunnable::reader()
+{
+ QString reqType = getValueBeforeSpace();
+ m_urlPath = QByteArray::fromPercentEncoding( getValueBeforeSpace().toUtf8() );
+ if (m_urlPath.contains(".."))
+ {
+ // Possible attempt to explore server directories
+ httpDocument().setCode(HttpDocument::Code::_404);
+ httpDocument().setBodySingleHTMLLabel("What you where looking for is not here", "Not found");
+ return;
+ }
+
+ if (m_urlPath == "/")
+ {
+ m_urlPath = "/index.html";
+ }
+ m_headers = getValueBeforeBody();
+
+ if (reqType == "GET")
+ {
+ get();
+ }
+ else if (reqType != "HEAD")
+ {
+ httpDocument().setCode(HttpDocument::Code::_400); // default code is 200
+ }
+
+ setDataToWrite(httpDocument().document());
+}
+
+QString SocketRunnable::getValueBeforeSpace()
+{
+ QString result;
+ char symbol {0};
+ qint64 tail = socket()->read(&symbol, 1);
+ while (symbol != ' ' and tail > 0)
+ {
+ if (result.size() > getValueBeforeSpaceLIMIT)
+ {
+ qDebug() << "Parsing error: TYPE or URL length >" << getValueBeforeSpaceLIMIT;
+ break;
+ }
+ result += symbol;
+ tail = socket()->read(&symbol, 1);
+ }
+ return result;
+}
+
+QString SocketRunnable::getValueBeforeBody()
+{
+ QString result;
+ char symbol {0};
+ qint64 tail = socket()->read(&symbol, 1);
+ while (not result.endsWith("\r\n\r") and tail > 0)
+ {
+ if (result.size() > getValueBeforeBodyLIMIT)
+ {
+ qDebug() << "Parsing error: headers length >" << getValueBeforeBodyLIMIT;
+ break;
+ }
+ result += symbol;
+ tail = socket()->read(&symbol, 1);
+ }
+ return result;
+}
diff --git a/src/socketrunnable.h b/src/socketrunnable.h
new file mode 100644
index 0000000..bf038e6
--- /dev/null
+++ b/src/socketrunnable.h
@@ -0,0 +1,49 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#pragma once
+
+#include "socketrunnablebase.h"
+#include "httpdocument.h"
+
+class SocketRunnable : public SocketRunnableBase
+{
+ Q_OBJECT
+public:
+ SocketRunnable(qintptr, QObject* parent = nullptr);
+
+ // SocketRunnableBase interface
+public:
+ void reader() override;
+
+private:
+ HttpDocument& httpDocument() { return m_httpDocument; }
+ HttpDocument m_httpDocument;
+
+ QString m_urlPath;
+ const QString& urlPath() const { return m_urlPath; }
+
+ QString m_headers;
+ const QString& headers() const { return m_headers; }
+
+ void get();
+
+ QString getValueBeforeSpace();
+ QString getValueBeforeBody();
+};
diff --git a/src/socketrunnablebase.cpp b/src/socketrunnablebase.cpp
new file mode 100644
index 0000000..7b61866
--- /dev/null
+++ b/src/socketrunnablebase.cpp
@@ -0,0 +1,82 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#include "socketrunnablebase.h"
+
+#include
+
+SocketRunnableBase::SocketRunnableBase(qintptr socketDescriptor, QObject *parent) :
+ QObject(parent),
+ m_socketDescriptor(socketDescriptor)
+{
+}
+
+SocketRunnableBase::~SocketRunnableBase()
+{
+ if (m_socket) delete m_socket;
+}
+
+void SocketRunnableBase::setDataToWrite(const QByteArray &data)
+{
+ m_data = data;
+}
+
+QTcpSocket *SocketRunnableBase::socket()
+{
+ return m_socket;
+}
+
+void SocketRunnableBase::run()
+{
+ if (not m_socketDescriptor)
+ {
+ return;
+ }
+
+ m_socket = new QTcpSocket;
+ m_socket->setSocketDescriptor(m_socketDescriptor);
+
+ if (m_socket->waitForReadyRead())
+ {
+ reader();
+
+ if (not m_data.isEmpty())
+ {
+ m_socket->write(m_data);
+ while (m_socket->bytesToWrite() > 0)
+ {
+ if (m_socket->state() == QTcpSocket::UnconnectedState)
+ {
+ qWarning() << "Socket turned to unconnected state at writing moment";
+ break;
+ }
+ m_socket->waitForBytesWritten(100);
+ }
+ m_socket->close();
+ }
+ else
+ {
+ qWarning() << "Data to write is empty";
+ }
+ }
+ else
+ {
+ qWarning() << "Socket reading timed out";
+ }
+}
diff --git a/src/socketrunnablebase.h b/src/socketrunnablebase.h
new file mode 100644
index 0000000..d4105b5
--- /dev/null
+++ b/src/socketrunnablebase.h
@@ -0,0 +1,45 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#pragma once
+
+#include "httpdocument.h"
+
+#include
+#include
+#include
+
+/* 1) override reader(); 2) call setDataToWrite() at and of reader() */
+
+class SocketRunnableBase : public QObject, public QRunnable
+{
+ Q_OBJECT
+public:
+ SocketRunnableBase(qintptr, QObject* parent = nullptr);
+ ~SocketRunnableBase();
+ void run() override;
+ virtual void reader() = 0;
+ void setDataToWrite(const QByteArray& data);
+ QTcpSocket *socket();
+
+private:
+ qintptr m_socketDescriptor;
+ QTcpSocket* m_socket = nullptr;
+ QByteArray m_data;
+};
diff --git a/src/statistics.cpp b/src/statistics.cpp
new file mode 100644
index 0000000..21c06a1
--- /dev/null
+++ b/src/statistics.cpp
@@ -0,0 +1,92 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#include "statistics.h"
+#include "g.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+QStringList Statistics::m_lastDestinations;
+qint64 Statistics::m_lastSerializeTimestamp = 0;
+bool Statistics::m_deserialized = false;
+DailyCounter Statistics::m_dailyUpload;
+DailyCounter Statistics::m_dailyDownload;
+quint64 Statistics::m_totalUpload = 0;
+quint64 Statistics::m_totalDownload = 0;
+
+void Statistics::report(const LogEvent& event)
+{
+ // mutex enabled in ProxyInstanse::reader()
+
+ deserialize();
+
+ if (g::p::IGNORED_DESTINATIONS.contains(event.dest))
+ {
+ qDebug() << "Destination" << event.dest << "ignored";
+ return;
+ }
+
+ addToDestinationsList(event.dest);
+ incrementUpload(event.to);
+ incrementDownload(event.from);
+ // type not handled
+
+ serialize();
+}
+
+void Statistics::incrementUpload(quint64 value)
+{
+ m_dailyUpload.increment(value);
+ m_totalUpload += value;
+}
+
+void Statistics::incrementDownload(quint64 value)
+{
+ m_dailyDownload.increment(value);
+ m_totalDownload += value;
+}
+
+void Statistics::addToDestinationsList(const QString &dest)
+{
+ if (static_cast(lastDestinations().size()) >= g::p::LAST_AND_TOP_LIST_SIZE)
+ {
+ m_lastDestinations.pop_back();
+ }
+ m_lastDestinations.push_front(dest);
+
+
+}
+
+void Statistics::serialize()
+{
+ QFile file(g::p::WORKING_DIR + "/statistics.json");
+}
+
+void Statistics::deserialize()
+{
+ if (m_deserialized) return;
+
+ QFile file(g::p::WORKING_DIR + "/statistics.json");
+
+}
diff --git a/src/statistics.h b/src/statistics.h
new file mode 100644
index 0000000..a2e0695
--- /dev/null
+++ b/src/statistics.h
@@ -0,0 +1,64 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#pragma once
+
+#include "dailycounter.h"
+
+#include
+
+struct LogEvent
+{
+ QString type;
+ QString dest;
+ quint64 to = 0;
+ quint64 from = 0;
+};
+
+class Statistics
+{
+public:
+ Statistics() = delete;
+ static void report (const LogEvent& event);
+
+ static const QStringList lastDestinations() { return QStringList{"google.com"}; /*m_lastDestinations;*/ }
+ static const QStringList dailyTopDestinations() { return QStringList{"cloudflare.com", "yahoo.com"}; }
+ static const QStringList totalTopDestinations() { return QStringList{"domain.com", "top.org"}; }
+ static quint64 dailyUpload() { return 124; }
+ static quint64 totalUpload() { return 241298412; }
+ static quint64 dailyDownload() { return 21312312312412; }
+ static quint64 totalDownload() { return 23112; }
+
+private:
+ static void incrementUpload(quint64 value);
+ static void incrementDownload(quint64 value);
+ static void addToDestinationsList(const QString& dest);
+ static void serialize();
+ static void deserialize();
+
+ static QStringList m_lastDestinations;
+
+ static qint64 m_lastSerializeTimestamp;
+ static bool m_deserialized;
+ static DailyCounter m_dailyUpload;
+ static DailyCounter m_dailyDownload;
+ static quint64 m_totalUpload;
+ static quint64 m_totalDownload;
+};
+
diff --git a/src/webpage.h b/src/webpage.h
new file mode 100644
index 0000000..f317010
--- /dev/null
+++ b/src/webpage.h
@@ -0,0 +1,55 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#pragma once
+
+#include
+#include
+
+class WebPage
+{
+public:
+ WebPage() = delete;
+ static QByteArray document();
+
+private:
+ static void setTitle(QString& document);
+ static void customStyles(QString& document);
+ static void lastDestinations(QString& document);
+ static void trafficBlock(QString& document);
+ static void topDestinations(QString& document);
+ static void blockedDestinations(QString& document);
+ static void informationBlock(QString& document);
+ static void copyright(QString& document);
+
+ static QString lastDestinationItem(const QString& destination);
+ static QString dailyTopDestinationItem(const QString& destination);
+ static QString totalTopDestinationItem(const QString& destination);
+ static QString blockedDestinationItem(const QString& destination, const QStringList& addresses);
+ static QPair bytesToHumanReadableString(quint64 bytes); // count, measure
+
+ static const QString m_lastDestinationItem;
+ static const QString m_dailyTopDestinationItem;
+ static const QString m_totalTopDestinationItem;
+ static const QString m_blockedDestinationsBlock;
+ static const QString m_blockedDestinationItemLvl1;
+ static const QString m_blockedDestinationItemLvl2;
+ static const QString m_informationBlock;
+};
+
diff --git a/src/webserverbase.cpp b/src/webserverbase.cpp
new file mode 100644
index 0000000..23247d3
--- /dev/null
+++ b/src/webserverbase.cpp
@@ -0,0 +1,57 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#include "webserverbase.h"
+#include "g.h"
+
+#include
+
+WebServerBase::WebServerBase(QObject *parent) :
+ QTcpServer(parent)
+{
+ m_pool = new QThreadPool(this);
+ auto threadCount = std::thread::hardware_concurrency() * 2;
+ m_pool->setMaxThreadCount(threadCount);
+
+ if (not listen(QHostAddress(g::p::BIND_TO_ADDRESS), g::p::BIND_TO_PORT))
+ {
+ throw std::runtime_error("Web server not binded at " +
+ g::p::BIND_TO_ADDRESS.toStdString() + " / " +
+ QString::number(g::p::BIND_TO_PORT).toStdString());
+ }
+ qInfo() << "Web server started at" << g::p::BIND_TO_ADDRESS << "/" << g::p::BIND_TO_PORT;
+}
+
+WebServerBase::~WebServerBase()
+{
+ if (m_pool)
+ {
+ m_pool->clear();
+ if (not m_pool->waitForDone(100))
+ {
+ qWarning() << "Thread pool of web server not finished at 100ms";
+ }
+ m_pool->deleteLater();
+ }
+}
+
+QThreadPool *WebServerBase::pool()
+{
+ return m_pool;
+}
diff --git a/src/webserverbase.h b/src/webserverbase.h
new file mode 100644
index 0000000..1f457c8
--- /dev/null
+++ b/src/webserverbase.h
@@ -0,0 +1,36 @@
+/*
+3proxy-eagle: Accumulate ethical 3proxy statistics with web interface.
+Source code: https://notabug.org/acetone/3proxy-eagle.
+Copyright (C) 2022, acetone
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+#pragma once
+
+#include
+#include
+#include
+
+class WebServerBase : public QTcpServer
+{
+ Q_OBJECT
+public:
+ WebServerBase (QObject *parent = nullptr);
+ virtual ~WebServerBase();
+ QThreadPool *pool();
+
+private:
+ QThreadPool *m_pool;
+};