updated to new generation of lib and simplify code base

main
acetone 2022-08-14 11:17:35 +03:00
parent fb6cbc5cdb
commit 27b7131450
9 changed files with 61 additions and 108 deletions

@ -1 +1 @@
Subproject commit 7e870d45ee01e92f4219221b109fdd97ebde629c
Subproject commit 6df0c4188bdc9a71b62b76dcc9968e1194af257f

View File

@ -1,4 +1,4 @@
// 2022 (c) GPLv3, acetone at i2pmail.org
// GPLv3 (c) acetone, 2022
// Zero Storage Captcha
#include "httpserver.h"

View File

@ -1,4 +1,4 @@
// 2022 (c) GPLv3, acetone at i2pmail.org
// GPLv3 (c) acetone, 2022
// Zero Storage Captcha
#ifndef HTTPSERVER_H

View File

@ -1,3 +1,6 @@
// GPLv3 (c) acetone, 2022
// Zero Storage Captcha
#include "jsonanswer.h"
#include <QJsonDocument>

View File

@ -1,3 +1,6 @@
// GPLv3 (c) acetone, 2022
// Zero Storage Captcha
#ifndef JSONANSWER_H
#define JSONANSWER_H

View File

@ -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")

View File

@ -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);

View File

@ -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

View File

@ -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