updated to new generation of lib and simplify code base
parent
fb6cbc5cdb
commit
27b7131450
2
cpp-lib
2
cpp-lib
|
@ -1 +1 @@
|
|||
Subproject commit 7e870d45ee01e92f4219221b109fdd97ebde629c
|
||||
Subproject commit 6df0c4188bdc9a71b62b76dcc9968e1194af257f
|
|
@ -1,4 +1,4 @@
|
|||
// 2022 (c) GPLv3, acetone at i2pmail.org
|
||||
// GPLv3 (c) acetone, 2022
|
||||
// Zero Storage Captcha
|
||||
|
||||
#include "httpserver.h"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// 2022 (c) GPLv3, acetone at i2pmail.org
|
||||
// GPLv3 (c) acetone, 2022
|
||||
// Zero Storage Captcha
|
||||
|
||||
#ifndef HTTPSERVER_H
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// GPLv3 (c) acetone, 2022
|
||||
// Zero Storage Captcha
|
||||
|
||||
#include "jsonanswer.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// GPLv3 (c) acetone, 2022
|
||||
// Zero Storage Captcha
|
||||
|
||||
#ifndef JSONANSWER_H
|
||||
#define JSONANSWER_H
|
||||
|
||||
|
|
34
main.cpp
34
main.cpp
|
@ -1,4 +1,4 @@
|
|||
// 2022 (c) GPLv3, acetone at i2pmail.org
|
||||
// GPLv3 (c) acetone, 2022
|
||||
// Zero Storage Captcha
|
||||
|
||||
#include "httpserver.h"
|
||||
|
@ -6,33 +6,29 @@
|
|||
#include <QApplication>
|
||||
#include <iostream>
|
||||
|
||||
const std::string COPYRIGHT = "2022 (c) GPLv3, acetone";
|
||||
const std::string COPYRIGHT = "GPLv3 (c) acetone, 2022";
|
||||
|
||||
void usage()
|
||||
{
|
||||
std::cout << "Zero Storage Captcha selfhosted REST API service 0.1 usage:\n\n"
|
||||
std::cout << "Zero Storage Captcha selfhosted REST API service 0.2 usage:\n\n"
|
||||
"RUN\n"
|
||||
"# To start QApplication without X-server (non-GUI system) should use:\n"
|
||||
"# \"export QT_QPA_PLATFORM=offscreen\" in plain shell\n"
|
||||
"# or\n"
|
||||
"# \"Environment=QT_QPA_PLATFORM=offscreen\" in systemd service, [Service] section\n"
|
||||
" -a --address Address to bind\n"
|
||||
" -a --address Address to bind (127.0.0.1 by default)\n"
|
||||
" -p --port Port to bind (7697 by default)\n"
|
||||
" -t --threads Working threads (" << std::thread::hardware_concurrency() << " by default)\n" << std::endl;
|
||||
" -t --threads Working threads (hardware count by default)\n" << std::endl;
|
||||
|
||||
std::cout << "API (GET request, JSON response)\n"
|
||||
std::cout << "REST API (GET request)\n"
|
||||
"# Each response contains a boolean \"status\" that indicates the logic success of the operation.\n"
|
||||
"# If !status, read \"message\" field.\n"
|
||||
" 1. [Generate captcha]\n"
|
||||
" -> /generate?length= [>0] &difficulty= [0-5] &output= [base64|file] IF output=file: &filepath= [file system path]\n"
|
||||
" <- \"token\", IF output=base64: \"png\" (base64 encoded picture)\n"
|
||||
" -> /generate?length= [>0] &difficulty= [0-2]\n"
|
||||
" <- { \"token\": \"CAPTCHA_TOKEN\", \"png\": \"base64 encoded picture\" }\n"
|
||||
" 2. [Validate captcha]\n"
|
||||
" -> /validate?answer= [user's answer] &token = [user's token]\n"
|
||||
" <- boolean \"valid\"\n"
|
||||
" 3. [Settings]\n"
|
||||
" 3.1 Tokens case sensitive to captcha answer:\n"
|
||||
" <- { \"valid\": true|false }\n"
|
||||
" 3. [Global settings]\n"
|
||||
" 3.1 Tokens case sensitive to captcha answer (disabled by default):\n"
|
||||
" -> /settings?case_sensitive= [enable|disable]\n"
|
||||
" 3.2 Only numbers mode:\n"
|
||||
" 3.2 Numbers only mode (disabled by default):\n"
|
||||
" -> /settings?number_mode= [enable|disable]\n" << std::endl;
|
||||
|
||||
std::cout << COPYRIGHT << std::endl;
|
||||
|
@ -48,7 +44,7 @@ int main(int argc, char *argv[])
|
|||
|
||||
int threads = static_cast<int>(std::thread::hardware_concurrency());
|
||||
quint16 port = 7697;
|
||||
QString address;
|
||||
QString address = "127.0.0.1";
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
|
@ -63,7 +59,7 @@ int main(int argc, char *argv[])
|
|||
port = QString(argv[i+1]).toUShort(&ok);
|
||||
if (not ok)
|
||||
{
|
||||
throw std::invalid_argument("Port not int");
|
||||
throw std::invalid_argument("Port not a number");
|
||||
}
|
||||
}
|
||||
else if ((param == "--threads" or param == "-t") and i+1 < argc)
|
||||
|
@ -72,7 +68,7 @@ int main(int argc, char *argv[])
|
|||
threads = QString(argv[i+1]).toInt(&ok);
|
||||
if (not ok or threads < 1)
|
||||
{
|
||||
throw std::invalid_argument("Threads not int or less then 1");
|
||||
throw std::invalid_argument("Threads not a number or less then 1");
|
||||
}
|
||||
}
|
||||
else if (param == "--help" or param == "-h")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// 2022 (c) GPLv3, acetone at i2pmail.org
|
||||
// GPLv3 (c) acetone, 2022
|
||||
// Zero Storage Captcha
|
||||
|
||||
#include "socketrunnable.h"
|
||||
|
@ -7,7 +7,6 @@
|
|||
|
||||
#include <QRegularExpression>
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
|
||||
constexpr int URL_PATH_MAX_LENGTH {200};
|
||||
|
||||
|
@ -39,10 +38,10 @@ void SocketRunnable::settings()
|
|||
else if (setCaseSensitive == "disable") enable = false;
|
||||
else
|
||||
{
|
||||
writeJsonError("Invalid case_sensitive argument: expected enable or disable");
|
||||
writeError("Invalid case_sensitive argument: expected enable or disable");
|
||||
return;
|
||||
}
|
||||
ZeroStorageCaptchaCrypto::KeyHolder::setCaseSensitive(enable);
|
||||
ZeroStorageCaptcha::setCaseSensitive(enable);
|
||||
}
|
||||
|
||||
QString numberMode = getValue("number_mode");
|
||||
|
@ -53,15 +52,15 @@ void SocketRunnable::settings()
|
|||
else if (numberMode == "disable") enable = false;
|
||||
else
|
||||
{
|
||||
writeJsonError("Invalid number_mode argument: expected enable or disable");
|
||||
writeError("Invalid number_mode argument: expected enable or disable");
|
||||
return;
|
||||
}
|
||||
ZeroStorageCaptcha::setOnlyNumbersMode(enable);
|
||||
ZeroStorageCaptcha::setNumbersOnlyMode(enable);
|
||||
}
|
||||
|
||||
if (setCaseSensitive.isEmpty() and numberMode.isEmpty())
|
||||
{
|
||||
writeJsonError("Invalid key: expected case_sensitive or number_mode");
|
||||
writeError("Invalid key: expected case_sensitive or number_mode");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -73,69 +72,34 @@ void SocketRunnable::settings()
|
|||
|
||||
void SocketRunnable::generate()
|
||||
{
|
||||
bool success = false;
|
||||
JsonAnswer answer;
|
||||
QString errorMessage;
|
||||
|
||||
int length = getValue("length").toInt(&success);
|
||||
if (not success)
|
||||
int length = getValue("length").toInt();
|
||||
if (length < 0)
|
||||
{
|
||||
writeJsonError("Invalid length: expected int > 0");
|
||||
return;
|
||||
errorMessage += "Length must be greater than 0;";
|
||||
}
|
||||
|
||||
int difficulty = getValue("difficulty").toInt(&success);
|
||||
if (not success)
|
||||
int difficulty = getValue("difficulty").toInt();
|
||||
if (difficulty < 0 or difficulty > 2)
|
||||
{
|
||||
writeJsonError("Invalid difficulty: expected int (0, 5)");
|
||||
return;
|
||||
}
|
||||
if (not success)
|
||||
{
|
||||
writeJsonError("Invalid difficulty: expected int (0, 5)");
|
||||
return;
|
||||
errorMessage += "Difficulty can take values from 0 to 2;";
|
||||
}
|
||||
|
||||
auto res = ZeroStorageCaptcha::getCaptcha(length, difficulty);
|
||||
if (res.token().isEmpty())
|
||||
{
|
||||
writeJsonError("Temporary issue: token cache is full");
|
||||
return;
|
||||
}
|
||||
ZeroStorageCaptcha captcha;
|
||||
captcha.setDifficulty(difficulty);
|
||||
captcha.generateAnswer(length);
|
||||
captcha.render();
|
||||
|
||||
JsonAnswer result;
|
||||
QString output = getValue("output");
|
||||
if (output == "file")
|
||||
answer.setValue("status", errorMessage.isEmpty());
|
||||
if (not errorMessage.isEmpty())
|
||||
{
|
||||
QString filepath = getValue("filepath");
|
||||
filepath = QByteArray::fromPercentEncoding(filepath.toUtf8());
|
||||
filepath.replace('+', ' ');
|
||||
filepath.replace("\\", "/");
|
||||
if (filepath.isEmpty())
|
||||
{
|
||||
writeJsonError("Accepted flag to save picture to disk, but \"filepath\" is empty");
|
||||
return;
|
||||
}
|
||||
QFile f(filepath);
|
||||
if (not f.open(QIODevice::WriteOnly))
|
||||
{
|
||||
writeJsonError("Can not write file " + filepath);
|
||||
return;
|
||||
}
|
||||
f.write(res.picture());
|
||||
f.close();
|
||||
answer.setValue("message", errorMessage);
|
||||
}
|
||||
else if (output == "base64")
|
||||
{
|
||||
result.setValue("png", QString(res.picture().toBase64()));
|
||||
}
|
||||
else
|
||||
{
|
||||
writeJsonError("Invalid output type: expected file or base64");
|
||||
return;
|
||||
}
|
||||
|
||||
result.setValue("status", true);
|
||||
result.setValue("token", res.token());
|
||||
m_socket->write(result.document());
|
||||
answer.setValue("png", QString(captcha.picturePng().toBase64()));
|
||||
answer.setValue("token", captcha.token());
|
||||
m_socket->write(answer.document());
|
||||
}
|
||||
|
||||
void SocketRunnable::validate()
|
||||
|
@ -143,14 +107,14 @@ void SocketRunnable::validate()
|
|||
QString token = getValue("token");
|
||||
if (token.isEmpty())
|
||||
{
|
||||
writeJsonError("Empty token");
|
||||
writeError("Empty token");
|
||||
return;
|
||||
}
|
||||
|
||||
QString answer = getValue("answer");
|
||||
if (answer.isEmpty())
|
||||
{
|
||||
writeJsonError("Empty answer");
|
||||
writeError("Empty answer");
|
||||
}
|
||||
|
||||
bool valid = ZeroStorageCaptcha::validate(answer, token);
|
||||
|
@ -170,7 +134,6 @@ QString SocketRunnable::readBeforeSpace()
|
|||
{
|
||||
if (counter > URL_PATH_MAX_LENGTH)
|
||||
{
|
||||
wariningLog ("SocketRunnable::readBeforeSpace() size > " + QString::number(URL_PATH_MAX_LENGTH) + ": " + result);
|
||||
break;
|
||||
}
|
||||
result += symbol;
|
||||
|
@ -186,34 +149,28 @@ QString SocketRunnable::getValue(const QString &key) const
|
|||
QString pattern = key+"=";
|
||||
if (not result.contains(pattern)) return QString();
|
||||
|
||||
result.remove(QRegularExpression("^.*"+QRegularExpression::escape(pattern)));
|
||||
static QRegularExpression rgx_allToPattern("^.*"+QRegularExpression::escape(pattern));
|
||||
result.remove(rgx_allToPattern);
|
||||
if (result.isEmpty() or result.startsWith("&")) return QString();
|
||||
result.remove(QRegularExpression("&.*$"));
|
||||
static QRegularExpression rgx_allFromAmpersand("&.*$");
|
||||
result.remove(rgx_allFromAmpersand);
|
||||
return result;
|
||||
}
|
||||
|
||||
void SocketRunnable::wariningLog(const QString &str) const
|
||||
{
|
||||
qInfo().noquote() <<
|
||||
"<warning time=\"" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")+ "\">\n"
|
||||
" " + str + "\n"
|
||||
"</warning>";
|
||||
}
|
||||
|
||||
void SocketRunnable::reader()
|
||||
{
|
||||
QString reqType = readBeforeSpace();
|
||||
|
||||
if (reqType != "GET")
|
||||
{
|
||||
writeJsonError("Invalid request: expected GET");
|
||||
m_socket->waitForBytesWritten();
|
||||
writeError("Invalid request: expected GET");
|
||||
return;
|
||||
}
|
||||
|
||||
QString urlPath = readBeforeSpace();
|
||||
m_request = urlPath;
|
||||
m_request.remove(QRegularExpression("^.*\\?"));
|
||||
static QRegularExpression rgx_allToQuestionMark("^.*\\?");
|
||||
m_request.remove(rgx_allToQuestionMark);
|
||||
m_request.remove('\r');
|
||||
m_request.remove('\n');
|
||||
|
||||
|
@ -231,11 +188,11 @@ void SocketRunnable::reader()
|
|||
}
|
||||
else
|
||||
{
|
||||
writeJsonError("Invalid request path: expected /generate, /validate or /settings");
|
||||
writeError("Invalid request path: expected /generate, /validate or /settings");
|
||||
}
|
||||
}
|
||||
|
||||
void SocketRunnable::writeJsonError(const QString &text)
|
||||
void SocketRunnable::writeError(const QString &text)
|
||||
{
|
||||
JsonAnswer answer;
|
||||
answer.setError(text);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// 2022 (c) GPLv3, acetone at i2pmail.org
|
||||
// GPLv3 (c) acetone, 2022
|
||||
// Zero Storage Captcha
|
||||
|
||||
#ifndef SOCKETRUNNABLE_H
|
||||
|
@ -28,9 +28,8 @@ private:
|
|||
inline QString readBeforeSpace();
|
||||
inline QString getValue(const QString& key) const;
|
||||
|
||||
void wariningLog(const QString& str) const;
|
||||
void reader();
|
||||
void writeJsonError(const QString& text);
|
||||
void writeError(const QString &text);
|
||||
};
|
||||
|
||||
#endif // SOCKETRUNNABLE_H
|
||||
|
|
|
@ -11,15 +11,10 @@ SOURCES += \
|
|||
jsonanswer.cpp \
|
||||
main.cpp \
|
||||
socketrunnable.cpp \
|
||||
cpp-lib/zerostoragecaptchacrypto.cpp \
|
||||
cpp-lib/zerostoragecaptcha.cpp
|
||||
|
||||
HEADERS += \
|
||||
httpserver.h \
|
||||
jsonanswer.h \
|
||||
socketrunnable.h \
|
||||
cpp-lib/zerostoragecaptchacrypto.h \
|
||||
cpp-lib/zerostoragecaptcha.h
|
||||
|
||||
LIBS += \
|
||||
-lcrypto
|
||||
|
|
Loading…
Reference in New Issue