123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- #include "cppcodec/base32_rfc4648.hpp"
- #include "resolver.h"
- #include "funcs.h"
- #include "HTTPheaders.h"
- #include <QTcpSocket>
- #include <QRegularExpression>
- #include <QJsonArray>
- #include <QDateTime>
- #include <QHostInfo>
- #include <QDebug>
- #include <QFile>
- #ifdef _WIN32
- #include <ws2tcpip.h>
- #else
- #include <arpa/inet.h>
- #endif
- constexpr int LIMIT_TO_READ = 2048;
- Resolver::Resolver(const QString& addr, quint16 port, QObject* parent) :
- QObject(parent),
- m_tcpServer(new QTcpServer),
- m_address(addr),
- m_port(port),
- m_NoApi(false),
- m_NoHttp(false),
- m_NoResolve(false)
- {
- if (!m_tcpServer->listen(m_address, m_port)) {
- throw "Server not binded";
- }
- qInfo().noquote() << "<-" << m_tcpServer->serverAddress().toString() + " : " +
- QString::number(m_tcpServer->serverPort()) << "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz") + "]";
- connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(slotNewUser()));
- }
- void Resolver::disableAPI()
- {
- m_NoApi = true;
- }
- void Resolver::disableWebPage()
- {
- m_NoHttp = true;
- }
- void Resolver::disableResolv()
- {
- m_NoResolve = true;
- http::HTML_PAGE.replace("IPv6 address or domain", "IPv6 address or meship domain");
- }
- void Resolver::slotNewUser()
- {
- if (!m_tcpServer->isListening()) return;
- QTcpSocket* clientSocket = m_tcpServer->nextPendingConnection();
- connect (clientSocket, SIGNAL(readyRead()), this, SLOT(slotReadyClient()));
- connect (clientSocket, SIGNAL(disconnected()), clientSocket, SLOT(deleteLater()));
- }
- void Resolver::slotReadyClient()
- {
- QTcpSocket* clientSocket = static_cast<QTcpSocket*>(sender());
- QString req = clientSocket->read(LIMIT_TO_READ);
- qInfo().noquote() << "\n->" << clientSocket->peerAddress().toString()
- << "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz") + "]";
- QSharedPointer<QTextStream> ts(new QTextStream(clientSocket));
- if (m_NoHttp and (req.startsWith("HEAD /") or req.startsWith("GET /toConverting") or req.startsWith("GET / ")))
- {
- qInfo().noquote() << " └ HTML (dropped)";
- *ts << http::HEADER_REJECT;
- }
- else if (m_NoApi and req.startsWith("GET /api/"))
- {
- qInfo().noquote() << " └ API (dropped)";
- JsonAnswer a;
- a.setValue("status", false);
- *ts << a.result();
- }
- else if (req.startsWith("GET /api/"))
- {
- qInfo().noquote() << " └ API";
- req.contains("toConverting=") ?
- processPage(req, ts, false) :
- startPage(ts, false);
- }
- else if (req.startsWith("GET /"))
- {
- qInfo().noquote() << " └ HTML";
- req.contains("toConverting=") ?
- processPage(req, ts, true) :
- startPage(ts, true);
- }
- else if (req.startsWith("HEAD /"))
- {
- qInfo().noquote() << " └ HEAD request";
- *ts << http::HEADER_OK;
- }
- clientSocket->close();
- }
- void Resolver::convertStrToRaw(const QString &str, Address &array)
- {
- inet_pton(AF_INET6, str.toUtf8(), (void*)array.data());
- }
- QString Resolver::getBase32(const Address &rawAddr)
- {
- return cppcodec::base32_rfc4648::encode(rawAddr.data(), ADDRIPV6_SIZE).c_str();
- }
- QString Resolver::decodeMeshToIP(const QString &meshname)
- {
- std::string mesh = pickupStringForMeshname(meshname.toStdString()) + "======"; // 6 паддингов - норма для IPv6 адреса
- std::vector<uint8_t> raw;
- try {
- raw = cppcodec::base32_rfc4648::decode(mesh);
- } catch (cppcodec::padding_error const&) {
- return QString();
- } catch (cppcodec::symbol_error const&) {
- return QString();
- }
- Address rawAddr;
- for(int i = 0; i < 16; ++i)
- rawAddr[i] = raw[i];
- return getAddress(rawAddr);
- }
- std::string Resolver::pickupStringForMeshname(std::string str)
- {
- bool dot = false;
- std::string::iterator delend;
- for (auto it = str.begin(); it != str.end(); it++)
- {
- *it = toupper(*it); // делаем все буквы заглавными для обработки
- if(*it == '.') {
- delend = it;
- dot = true;
- }
- }
- if (dot)
- for (auto it = str.end(); it != delend; it--)
- str.pop_back(); // удаляем доменную зону
- return str;
- }
- QString Resolver::getAddress(const Address& rawAddr)
- {
- char ipStrBuf[46];
- inet_ntop(AF_INET6, rawAddr.data(), ipStrBuf, 46);
- return QString(ipStrBuf);
- }
- void Resolver::startPage(QSharedPointer<QTextStream> ts, bool html)
- {
- if (html)
- {
- qInfo().noquote() << " └ Start page";
- QString page = http::HTML_PAGE;
- QString css = http::CSS_DEFAULT;
- QString text;
- if (m_NoApi and not m_NoResolve)
- {
- text = "<p>Input any domain to resolve or IPv6 to convert to <a href=\"https://github.com/zhoreeq/meshname\" style=\"text-decoration: none\">meship</a>.</p>";
- }
- else if (m_NoApi and m_NoResolve)
- {
- text = "<p>Input meship domain to resolve or IPv6 to convert to <a href=\"https://github.com/zhoreeq/meshname\" style=\"text-decoration: none\">meship</a>.</p>";
- }
- else if (not m_NoResolve)
- {
- text = "<p>Input any domain to resolve or IPv6 to convert to <a href=\"https://github.com/zhoreeq/meshname\" style=\"text-decoration: none\">meship</a>.</p>"
- "<p>Also you can use Mario DNS tool via API to get result in JSON (GET request like a /api/toConverting=yourvalue).";
- }
- else
- { // no resolve
- text = "<p>Input meship domain to resolve or IPv6 to convert to <a href=\"https://github.com/zhoreeq/meshname\" style=\"text-decoration: none\">meship</a>.</p>"
- "<p>Also you can use Mario DNS tool via API to get result in JSON (GET request like a /api/toConverting=yourvalue).";
- }
- css.replace("{{COLOR}}", "gray");
- page.replace("{{STYLES}}", css);
- page.replace("{{TEXT}}", text);
- *ts << page;
- }
- else
- {
- QString msg;
- if (m_NoResolve) {
- msg = "Push meship domain to resolve or IPv6 to convert to meship like a /api/toConverting=yourvalue";
- } else {
- msg = "Push any domain to resolve or IPv6 to convert to meship like a /api/toConverting=yourvalue";
- }
- qInfo().noquote() << " └ Incorrect request";
- JsonAnswer a;
- a.setValue("status", false);
- a.setValue("answer", msg);
- *ts << a.result();
- }
- }
- void Resolver::startPageWithMessage(const QString & value, QSharedPointer<QTextStream> ts, const QString & msg, bool html)
- {
- qInfo().noquote() << " └ " + msg;
- if (html)
- {
- QString page = http::HTML_PAGE;
- QString css = http::CSS_DEFAULT;
- css.replace("{{COLOR}}", "red");
- page.replace("{{STYLES}}", css);
- page.replace("{{TEXT}}", msg);
- if (!value.isEmpty()) {
- page.replace(QRegularExpression("placeholder=\".*domain\""), "value=\"" + value + "\"");
- }
- *ts << page;
- }
- else
- {
- JsonAnswer a;
- a.setValue("status", false);
- a.setValue("answer", msg);
- *ts << a.result();
- }
- }
- void Resolver::processPage(const QString& req, QSharedPointer<QTextStream> ts, bool html)
- {
- QString value = funcs::getValue(req, "toConverting");
- value.remove('+');
- value = QByteArray::fromPercentEncoding(value.toUtf8());
- qInfo().noquote() << " └ " + value;
- const uint8_t MAX_LEN = 60;
- if (value.size() > MAX_LEN)
- {
- startPageWithMessage("", ts, "Maximum input value length = " + QString::number(MAX_LEN), html);
- }
- else if (value == "version") {
- toVersion(value, ts, html);
- }
- else if (value.contains(":")) {
- toMeship(value, ts, html);
- }
- else if (value.endsWith(".meship")) {
- toIp(value, ts, html);
- }
- else if (value.contains(".") and not m_NoResolve) {
- toRealResolv(value, ts, html);
- }
- else
- {
- QString text;
- m_NoResolve
- ? text ="Input value must contains meship domain or IPv6"
- : text = "Input value must contains domain or IPv6";
- startPageWithMessage(value, ts, text, html);
- }
- }
- void Resolver::toMeship(const QString & value, QSharedPointer<QTextStream> ts, bool html)
- {
- Address rawAddr;
- convertStrToRaw(value, rawAddr);
- QString base32 = getBase32(rawAddr);
- base32 = base32.toLower();
- base32.remove('=');
- base32 += ".meship";
- if (html)
- {
- QString page = http::HTML_PAGE;
- QString css = http::CSS_DEFAULT;
- css.replace("{{COLOR}}", "green");
- page.replace("{{STYLES}}", css);
- page.replace("{{TEXT}}", base32);
- page.replace(QRegularExpression("placeholder=\".*domain\""), "value=\"" + value + "\"");
- *ts << page;
- }
- else
- {
- JsonAnswer a;
- a.setValue("status", true);
- a.setValue("answer", base32);
- *ts << a.result();
- }
- }
- void Resolver::toIp(const QString & value, QSharedPointer<QTextStream> ts, bool html)
- {
- QString result = decodeMeshToIP(value);
- if (result.isEmpty()) {
- startPageWithMessage(value, ts, "Failed: meship domain must have 26 base32 (RFC4648) characters only", html);
- return;
- }
- if (html)
- {
- QString page = http::HTML_PAGE;
- QString css = http::CSS_DEFAULT;
- css.replace("{{COLOR}}", "green");
- page.replace("{{STYLES}}", css);
- page.replace("{{TEXT}}", result);
- page.replace(QRegularExpression("placeholder=\".*domain\""), "value=\"" + value + "\"");
- *ts << page;
- }
- else
- {
- JsonAnswer a;
- a.setValue("status", true);
- a.setValue("answer", result);
- *ts << a.result();
- }
- }
- void Resolver::toRealResolv(const QString & value, QSharedPointer<QTextStream> ts, bool html)
- {
- QHostInfo host = QHostInfo::fromName(value);
- if (host.error() == QHostInfo::NoError)
- {
- auto addrs = host.addresses();
- if (html)
- {
- QString addresses;
- for (auto a: addrs) {
- addresses += a.toString() + "<br>";
- }
- QString page = http::HTML_PAGE;
- QString css = http::CSS_DEFAULT;
- css.replace("{{COLOR}}", "green");
- page.replace("{{STYLES}}", css);
- page.replace("{{TEXT}}", addresses);
- page.replace(QRegularExpression("placeholder=\".*domain\""), "value=\"" + value + "\"");
- *ts << page;
- }
- else
- {
- QJsonArray array;
- for (const auto& address: addrs)
- {
- array.push_back(address.toString());
- }
- JsonAnswer a;
- a.setValue("status", true);
- a.setValue("answer", array);
- *ts << a.result();
- }
- }
- else
- {
- QString msg;
- if (value.endsWith(".meshname")) {
- msg = "Domain not resolved. Try \".meship\" instead \".meshname\" for direct translation to address";
- } else {
- msg = "Failed: " + host.errorString();
- }
- startPageWithMessage(value, ts, msg, html);
- }
- }
- void Resolver::toVersion(const QString &value , QSharedPointer<QTextStream> ts, bool html)
- {
- if (html)
- {
- QString page = http::HTML_PAGE;
- QString css = http::CSS_DEFAULT;
- css.replace("{{COLOR}}", "gray");
- page.replace("{{STYLES}}", css);
- page.replace("{{TEXT}}", VERSION);
- page.replace(QRegularExpression("placeholder=\".*domain\""), "value=\"" + value + "\"");
- *ts << page;
- }
- else
- {
- JsonAnswer a;
- a.setValue("status", true);
- a.setValue("answer", VERSION);
- *ts << a.result();
- }
- }
|