mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
- The first part
- Added CodeDocs config file for customization - Fixing LGTM alerts - LGTM bug fixed again - added token option to hyperion-remote - fix DBManager::getDB() - next bugfix - correct broken signal from SettingManager to Hyperion - Token list is created after the schema is fetched Signed-off-by: Paulchen-Panther <Paulchen-Panter@protonmail.com>
This commit is contained in:
@@ -18,5 +18,6 @@ add_subdirectory(utils)
|
||||
add_subdirectory(effectengine)
|
||||
add_subdirectory(grabber)
|
||||
add_subdirectory(webserver)
|
||||
add_subdirectory(db)
|
||||
add_subdirectory(api)
|
||||
add_subdirectory(python)
|
||||
|
44
libsrc/api/JSONRPC_schema/schema-authorize.json
Normal file
44
libsrc/api/JSONRPC_schema/schema-authorize.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"type":"object",
|
||||
"required":true,
|
||||
"properties":{
|
||||
"command": {
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"enum" : ["authorize"]
|
||||
},
|
||||
"subcommand" : {
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"enum" : ["requestToken","createToken","deleteToken","getTokenList","logout","login","required","answerRequest","getPendingRequests"]
|
||||
},
|
||||
"tan" : {
|
||||
"type" : "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"minLength" : 3
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"minLength" : 8
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"minLength" : 36
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string",
|
||||
"minLength" : 5
|
||||
},
|
||||
"id" : {
|
||||
"type" : "string",
|
||||
"minLength" : 5,
|
||||
"maxLength" : 5
|
||||
},
|
||||
"accept" : {
|
||||
"type" : "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
@@ -5,7 +5,7 @@
|
||||
"command": {
|
||||
"type" : "string",
|
||||
"required" : true,
|
||||
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "transform", "correction" , "temperature"]
|
||||
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "transform", "correction" , "temperature"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@
|
||||
<file alias="schema-logging">JSONRPC_schema/schema-logging.json</file>
|
||||
<file alias="schema-processing">JSONRPC_schema/schema-processing.json</file>
|
||||
<file alias="schema-videomode">JSONRPC_schema/schema-videomode.json</file>
|
||||
<file alias="schema-authorize">JSONRPC_schema/schema-authorize.json</file>
|
||||
<!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control-->
|
||||
<file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file>
|
||||
<file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file>
|
||||
|
@@ -35,10 +35,17 @@
|
||||
// api includes
|
||||
#include <api/JsonCB.h>
|
||||
|
||||
// auth manager
|
||||
#include <hyperion/AuthManager.h>
|
||||
|
||||
using namespace hyperion;
|
||||
|
||||
JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListener)
|
||||
JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection, QObject* parent, bool noListener)
|
||||
: QObject(parent)
|
||||
, _authManager(AuthManager::getInstance())
|
||||
, _authorized(false)
|
||||
, _userAuthorized(false)
|
||||
, _apiAuthRequired(_authManager->isAuthRequired())
|
||||
, _noListener(noListener)
|
||||
, _peerAddress(peerAddress)
|
||||
, _log(log)
|
||||
@@ -50,6 +57,14 @@ JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListe
|
||||
{
|
||||
Q_INIT_RESOURCE(JSONRPC_schemas);
|
||||
|
||||
// if this is localConnection and network allows unauth locals, set authorized flag
|
||||
if(_apiAuthRequired && localConnection)
|
||||
_authorized = !_authManager->isLocalAuthRequired();
|
||||
|
||||
// setup auth interface
|
||||
connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest);
|
||||
connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse);
|
||||
|
||||
// the JsonCB creates json messages you can subscribe to e.g. data change events; forward them to the parent client
|
||||
connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
|
||||
|
||||
@@ -57,7 +72,7 @@ JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListe
|
||||
connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage);
|
||||
}
|
||||
|
||||
void JsonAPI::handleMessage(const QString& messageString)
|
||||
void JsonAPI::handleMessage(const QString& messageString, const QString& httpAuthHeader)
|
||||
{
|
||||
const QString ident = "JsonRpc@"+_peerAddress;
|
||||
QJsonObject message;
|
||||
@@ -84,6 +99,29 @@ void JsonAPI::handleMessage(const QString& messageString)
|
||||
}
|
||||
|
||||
int tan = message["tan"].toInt();
|
||||
|
||||
// client auth before everything else but not for http
|
||||
if (!_noListener && command == "authorize")
|
||||
{
|
||||
handleAuthorizeCommand(message, command, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// on the fly auth available for http from http Auth header, on failure we return and auth handler sends a failure
|
||||
if(_noListener && _apiAuthRequired && !_authorized)
|
||||
{
|
||||
// extract token from http header
|
||||
QString cToken = httpAuthHeader.mid(5).trimmed();
|
||||
if(!handleHTTPAuth(command, tan, cToken))
|
||||
return;
|
||||
}
|
||||
|
||||
// on strong api auth you need a auth for all cmds
|
||||
if(_apiAuthRequired && !_authorized)
|
||||
{
|
||||
sendErrorReply("No Authorization", command, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// switch over all possible commands and handle them
|
||||
if (command == "color") handleColorCommand (message, command, tan);
|
||||
@@ -104,13 +142,14 @@ void JsonAPI::handleMessage(const QString& messageString)
|
||||
else if (command == "videomode") handleVideoModeCommand (message, command, tan);
|
||||
|
||||
// BEGIN | The following commands are derecated but used to ensure backward compatibility with hyperion Classic remote control
|
||||
else if (command == "clearall") handleClearallCommand(message, command, tan);
|
||||
else if (command == "clearall")
|
||||
handleClearallCommand(message, command, tan);
|
||||
else if (command == "transform" || command == "correction" || command == "temperature")
|
||||
sendErrorReply("The command " + command + "is deprecated, please use the Hyperion Web Interface to configure");
|
||||
// END
|
||||
|
||||
// handle not implemented commands
|
||||
else handleNotImplemented ();
|
||||
else handleNotImplemented();
|
||||
}
|
||||
|
||||
void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& command, const int tan)
|
||||
@@ -949,6 +988,205 @@ void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString &
|
||||
sendSuccessReply(command, tan);
|
||||
}
|
||||
|
||||
void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString &command, const int tan)
|
||||
{
|
||||
const QString& subc = message["subcommand"].toString().trimmed();
|
||||
// catch test if auth is required
|
||||
if(subc == "required")
|
||||
{
|
||||
QJsonObject req;
|
||||
req["required"] = _apiAuthRequired;
|
||||
sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// catch logout
|
||||
if(subc == "logout")
|
||||
{
|
||||
_authorized = false;
|
||||
_userAuthorized = false;
|
||||
sendSuccessReply(command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// token created from ui
|
||||
if(subc == "createToken")
|
||||
{
|
||||
const QString& c = message["comment"].toString().trimmed();
|
||||
// for user authorized sessions
|
||||
if(_userAuthorized)
|
||||
{
|
||||
AuthManager::AuthDefinition def = _authManager->createToken(c);
|
||||
QJsonObject newTok;
|
||||
newTok["comment"] = def.comment;
|
||||
newTok["id"] = def.id;
|
||||
newTok["token"] = def.token;
|
||||
|
||||
sendSuccessDataReply(QJsonDocument(newTok), command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
sendErrorReply("No Authorization",command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// delete token
|
||||
if(subc == "deleteToken")
|
||||
{
|
||||
const QString& did = message["id"].toString().trimmed();
|
||||
// for user authorized sessions
|
||||
if(_userAuthorized)
|
||||
{
|
||||
_authManager->deleteToken(did);
|
||||
sendSuccessReply(command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
sendErrorReply("No Authorization",command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// catch token request
|
||||
if(subc == "requestToken")
|
||||
{
|
||||
const QString& comment = message["comment"].toString().trimmed();
|
||||
const QString& id = message["id"].toString().trimmed();
|
||||
_authManager->setNewTokenRequest(this, comment, id);
|
||||
// client should wait for answer
|
||||
return;
|
||||
}
|
||||
|
||||
// get pending token requests
|
||||
if(subc == "getPendingRequests")
|
||||
{
|
||||
if(_userAuthorized)
|
||||
{
|
||||
QMap<QString, AuthManager::AuthDefinition> map = _authManager->getPendingRequests();
|
||||
QJsonArray arr;
|
||||
for(const auto& entry : map)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["comment"] = entry.comment;
|
||||
obj["id"] = entry.id;
|
||||
obj["timeout"] = int(entry.timeoutTime - QDateTime::currentMSecsSinceEpoch());
|
||||
arr.append(obj);
|
||||
}
|
||||
sendSuccessDataReply(QJsonDocument(arr),command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("No Authorization", command+"-"+subc, tan);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// accept token request
|
||||
if(subc == "answerRequest")
|
||||
{
|
||||
const QString& id = message["id"].toString().trimmed();
|
||||
const bool& accept = message["accept"].toBool(false);
|
||||
if(_userAuthorized)
|
||||
{
|
||||
if(accept)
|
||||
_authManager->acceptTokenRequest(id);
|
||||
else
|
||||
_authManager->denyTokenRequest(id);
|
||||
}
|
||||
else
|
||||
sendErrorReply("No Authorization", command+"-"+subc, tan);
|
||||
|
||||
return;
|
||||
}
|
||||
// deny token request
|
||||
if(subc == "acceptRequest")
|
||||
{
|
||||
const QString& id = message["id"].toString().trimmed();
|
||||
if(_userAuthorized)
|
||||
{
|
||||
_authManager->acceptTokenRequest(id);
|
||||
}
|
||||
else
|
||||
sendErrorReply("No Authorization", command+"-"+subc, tan);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// cath get token list
|
||||
if(subc == "getTokenList")
|
||||
{
|
||||
if(_userAuthorized)
|
||||
{
|
||||
QVector<AuthManager::AuthDefinition> defVect = _authManager->getTokenList();
|
||||
QJsonArray tArr;
|
||||
for(const auto& entry : defVect)
|
||||
{
|
||||
QJsonObject subO;
|
||||
subO["comment"] = entry.comment;
|
||||
subO["id"] = entry.id;
|
||||
subO["last_use"] = entry.lastUse;
|
||||
|
||||
tArr.append(subO);
|
||||
}
|
||||
|
||||
sendSuccessDataReply(QJsonDocument(tArr),command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
sendErrorReply("No Authorization",command+"-"+subc, tan);
|
||||
return;
|
||||
}
|
||||
|
||||
// login
|
||||
if(subc == "login")
|
||||
{
|
||||
// catch token auth
|
||||
const QString& token = message["token"].toString().trimmed();
|
||||
|
||||
if(!token.isEmpty())
|
||||
{
|
||||
if(token.count() >= 36)
|
||||
{
|
||||
if(_authManager->isTokenAuthorized(token))
|
||||
{
|
||||
_authorized = true;
|
||||
sendSuccessReply(command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("No Authorization", command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("Token is too short", command+"-"+subc, tan);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// user & password
|
||||
const QString& user = message["username"].toString().trimmed();
|
||||
const QString& password = message["password"].toString().trimmed();
|
||||
|
||||
if(user.count() >= 3 && password.count() >= 8)
|
||||
{
|
||||
if(_authManager->isUserAuthorized(user, password))
|
||||
{
|
||||
_authorized = true;
|
||||
_userAuthorized = true;
|
||||
sendSuccessReply(command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("No Authorization", command+"-"+subc, tan);
|
||||
}
|
||||
else
|
||||
sendErrorReply("User or password string too short", command+"-"+subc, tan);
|
||||
}
|
||||
}
|
||||
|
||||
const bool JsonAPI::handleHTTPAuth(const QString& command, const int& tan, const QString& token)
|
||||
{
|
||||
if(_authManager->isTokenAuthorized(token))
|
||||
{
|
||||
_authorized = true;
|
||||
return true;
|
||||
}
|
||||
sendErrorReply("No Authorization", command, tan);
|
||||
return false;
|
||||
}
|
||||
|
||||
void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan)
|
||||
{
|
||||
emit forwardJsonMessage(message);
|
||||
@@ -1093,3 +1331,35 @@ void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg)
|
||||
// send the result
|
||||
emit callbackMessage(_streaming_logging_reply);
|
||||
}
|
||||
|
||||
void JsonAPI::handlePendingTokenRequest(const QString& id, const QString& comment)
|
||||
{
|
||||
// just user sessions are allowed to react on this, to prevent that token authorized instances authorize new tokens on their own
|
||||
if(_userAuthorized)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["command"] = "authorize-event";
|
||||
obj["comment"] = comment;
|
||||
obj["id"] = id;
|
||||
|
||||
emit callbackMessage(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void JsonAPI::handleTokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id)
|
||||
{
|
||||
// if this is the requester, we send the reply
|
||||
if(this == caller)
|
||||
{
|
||||
const QString cmd = "authorize-requestToken";
|
||||
QJsonObject result;
|
||||
result["token"] = token;
|
||||
result["comment"] = comment;
|
||||
result["id"] = id;
|
||||
|
||||
if(success)
|
||||
sendSuccessDataReply(QJsonDocument(result), cmd);
|
||||
else
|
||||
sendErrorReply("Token request timeout or denied", cmd);
|
||||
}
|
||||
}
|
||||
|
18
libsrc/db/CMakeLists.txt
Normal file
18
libsrc/db/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
find_package(Qt5 COMPONENTS Sql REQUIRED)
|
||||
|
||||
# Define the current source locations
|
||||
SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/db)
|
||||
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/db)
|
||||
|
||||
FILE ( GLOB DB_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
|
||||
|
||||
add_library(database
|
||||
${DB_SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(database
|
||||
hyperion
|
||||
hyperion-utils
|
||||
Qt5::Core
|
||||
Qt5::Sql
|
||||
)
|
408
libsrc/db/DBManager.cpp
Normal file
408
libsrc/db/DBManager.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
#include <db/DBManager.h>
|
||||
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlRecord>
|
||||
#include <QDir>
|
||||
|
||||
// not in header because of linking
|
||||
static QString _rootPath;
|
||||
|
||||
DBManager::DBManager(QObject* parent)
|
||||
: QObject(parent)
|
||||
, _log(Logger::getInstance("DB"))
|
||||
{
|
||||
}
|
||||
|
||||
DBManager::~DBManager()
|
||||
{
|
||||
}
|
||||
|
||||
void DBManager::setRootPath(const QString& rootPath)
|
||||
{
|
||||
_rootPath = rootPath;
|
||||
// create directory
|
||||
QDir().mkpath(_rootPath+"/db");
|
||||
}
|
||||
|
||||
void DBManager::setDB(const QString& dbn)
|
||||
{
|
||||
_dbn = dbn;
|
||||
// new database connection if not found
|
||||
if(!QSqlDatabase::contains(dbn))
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",dbn);
|
||||
db.setDatabaseName(_rootPath+"/db/"+dbn+".db");
|
||||
if(!db.open())
|
||||
{
|
||||
Error(_log, QSTRING_CSTR(db.lastError().text()));
|
||||
throw std::runtime_error("Failed to open database connection!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DBManager::setTable(const QString& table)
|
||||
{
|
||||
_table = table;
|
||||
}
|
||||
|
||||
QSqlDatabase DBManager::getDB() const
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::database(_dbn);
|
||||
|
||||
if (db.isOpen() && db.isValid())
|
||||
{
|
||||
return db;
|
||||
}
|
||||
else
|
||||
{
|
||||
db = QSqlDatabase::addDatabase("QSQLITE", _dbn);
|
||||
db.setDatabaseName(_rootPath+"/db/"+_dbn+".db");
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
bool DBManager::createRecord(const VectorPair& conditions, const QVariantMap& columns) const
|
||||
{
|
||||
if(recordExists(conditions))
|
||||
{
|
||||
// if there is no column data, return
|
||||
if(columns.isEmpty())
|
||||
return true;
|
||||
|
||||
if(!updateRecord(conditions, columns))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QVariantList cValues;
|
||||
QStringList prep;
|
||||
QStringList placeh;
|
||||
// prep merge columns & condition
|
||||
QVariantMap::const_iterator i = columns.constBegin();
|
||||
while (i != columns.constEnd()) {
|
||||
prep.append(i.key());
|
||||
cValues += i.value();
|
||||
placeh.append("?");
|
||||
|
||||
++i;
|
||||
}
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
// remove the condition statements
|
||||
QString tmp = pair.first;
|
||||
prep << tmp.remove("AND");
|
||||
cValues << pair.second;
|
||||
placeh.append("?");
|
||||
}
|
||||
query.prepare(QString("INSERT INTO %1 ( %2 ) VALUES ( %3 )").arg(_table,prep.join(", ")).arg(placeh.join(", ")));
|
||||
// add column & condition values
|
||||
doAddBindValue(query, cValues);
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to create record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prep.join(", ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::recordExists(const VectorPair& conditions) const
|
||||
{
|
||||
if(conditions.isEmpty())
|
||||
return false;
|
||||
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QStringList prepCond;
|
||||
QVariantList bindVal;
|
||||
prepCond << "WHERE";
|
||||
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
prepCond << pair.first+"=?";
|
||||
bindVal << pair.second;
|
||||
}
|
||||
query.prepare(QString("SELECT * FROM %1 %2").arg(_table,prepCond.join(" ")));
|
||||
doAddBindValue(query, bindVal);
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed recordExists(): '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
int entry = 0;
|
||||
while (query.next()) {
|
||||
entry++;
|
||||
}
|
||||
|
||||
if(entry)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBManager::updateRecord(const VectorPair& conditions, const QVariantMap& columns) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QVariantList values;
|
||||
QStringList prep;
|
||||
|
||||
// prepare columns valus
|
||||
QVariantMap::const_iterator i = columns.constBegin();
|
||||
while (i != columns.constEnd()) {
|
||||
prep += i.key()+"=?";
|
||||
values += i.value();
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
// prepare condition values
|
||||
QStringList prepCond;
|
||||
QVariantList prepBindVal;
|
||||
if(!conditions.isEmpty())
|
||||
prepCond << "WHERE";
|
||||
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
prepCond << pair.first+"=?";
|
||||
prepBindVal << pair.second;
|
||||
}
|
||||
|
||||
query.prepare(QString("UPDATE %1 SET %2 %3").arg(_table,prep.join(", ")).arg(prepCond.join(" ")));
|
||||
// add column values
|
||||
doAddBindValue(query, values);
|
||||
// add condition values
|
||||
doAddBindValue(query, prepBindVal);
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to update record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::getRecord(const VectorPair& conditions, QVariantMap& results, const QStringList& tColumns) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QString sColumns("*");
|
||||
if(!tColumns.isEmpty())
|
||||
sColumns = tColumns.join(", ");
|
||||
|
||||
// prep conditions
|
||||
QStringList prepCond;
|
||||
QVariantList bindVal;
|
||||
if(!conditions.isEmpty())
|
||||
prepCond << "WHERE";
|
||||
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
prepCond << pair.first+"=?";
|
||||
bindVal << pair.second;
|
||||
}
|
||||
query.prepare(QString("SELECT %1 FROM %2 %3").arg(sColumns,_table).arg(prepCond.join(" ")));
|
||||
doAddBindValue(query, bindVal);
|
||||
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to get record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// go to first row
|
||||
query.next();
|
||||
|
||||
QSqlRecord rec = query.record();
|
||||
for(int i = 0; i<rec.count(); i++)
|
||||
{
|
||||
results[rec.fieldName(i)] = rec.value(i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::getRecords(QVector<QVariantMap>& results, const QStringList& tColumns) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
|
||||
QString sColumns("*");
|
||||
if(!tColumns.isEmpty())
|
||||
sColumns = tColumns.join(", ");
|
||||
|
||||
query.prepare(QString("SELECT %1 FROM %2").arg(sColumns,_table));
|
||||
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to get records: '%s' in table: '%s' Error: %s", QSTRING_CSTR(sColumns), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// iterate through all found records
|
||||
while(query.next())
|
||||
{
|
||||
QVariantMap entry;
|
||||
QSqlRecord rec = query.record();
|
||||
for(int i = 0; i<rec.count(); i++)
|
||||
{
|
||||
entry[rec.fieldName(i)] = rec.value(i);
|
||||
}
|
||||
results.append(entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool DBManager::deleteRecord(const VectorPair& conditions) const
|
||||
{
|
||||
if(conditions.isEmpty())
|
||||
{
|
||||
Error(_log, "Oops, a deleteRecord() call wants to delete the entire table (%s)! Denied it", QSTRING_CSTR(_table));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(recordExists(conditions))
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
|
||||
// prep conditions
|
||||
QStringList prepCond("WHERE");
|
||||
QVariantList bindValues;
|
||||
|
||||
for(const auto& pair : conditions)
|
||||
{
|
||||
prepCond << pair.first+"=?";
|
||||
bindValues << pair.second;
|
||||
}
|
||||
|
||||
query.prepare(QString("DELETE FROM %1 %2").arg(_table,prepCond.join(" ")));
|
||||
doAddBindValue(query, bindValues);
|
||||
if(!query.exec())
|
||||
{
|
||||
Error(_log, "Failed to delete record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBManager::createTable(QStringList& columns) const
|
||||
{
|
||||
if(columns.isEmpty())
|
||||
{
|
||||
Error(_log,"Empty tables aren't supported!");
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlDatabase idb = getDB();
|
||||
// create table if required
|
||||
QSqlQuery query(idb);
|
||||
if(!tableExists(_table))
|
||||
{
|
||||
// empty tables aren't supported by sqlite, add one column
|
||||
QString tcolumn = columns.takeFirst();
|
||||
// default CURRENT_TIMESTAMP is not supported by ALTER TABLE
|
||||
if(!query.exec(QString("CREATE TABLE %1 ( %2 )").arg(_table,tcolumn)))
|
||||
{
|
||||
Error(_log, "Failed to create table: '%s' Error: %s", QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// create columns if required
|
||||
QSqlRecord rec = idb.record(_table);
|
||||
int err = 0;
|
||||
for(const auto& column : columns)
|
||||
{
|
||||
QStringList id = column.split(' ');
|
||||
if(rec.indexOf(id.at(0)) == -1)
|
||||
{
|
||||
if(!createColumn(column))
|
||||
{
|
||||
err++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(err)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::createColumn(const QString& column) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
if(!query.exec(QString("ALTER TABLE %1 ADD COLUMN %2").arg(_table,column)))
|
||||
{
|
||||
Error(_log, "Failed to create column: '%s' in table: '%s' Error: %s", QSTRING_CSTR(column), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBManager::tableExists(const QString& table) const
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QStringList tables = idb.tables();
|
||||
if(tables.contains(table))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBManager::deleteTable(const QString& table) const
|
||||
{
|
||||
if(tableExists(table))
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
if(!query.exec(QString("DROP TABLE %1").arg(table)))
|
||||
{
|
||||
Error(_log, "Failed to delete table: '%s' Error: %s", QSTRING_CSTR(table), QSTRING_CSTR(idb.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DBManager::doAddBindValue(QSqlQuery& query, const QVariantList& variants) const
|
||||
{
|
||||
for(const auto& variant : variants)
|
||||
{
|
||||
QVariant::Type t = variant.type();
|
||||
switch(t)
|
||||
{
|
||||
case QVariant::UInt:
|
||||
case QVariant::Int:
|
||||
case QVariant::Bool:
|
||||
query.addBindValue(variant.toInt());
|
||||
break;
|
||||
case QVariant::Double:
|
||||
query.addBindValue(variant.toFloat());
|
||||
break;
|
||||
case QVariant::ByteArray:
|
||||
query.addBindValue(variant.toByteArray());
|
||||
break;
|
||||
default:
|
||||
query.addBindValue(variant.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,9 @@
|
||||
#include <flatbufserver/FlatBufferServer.h>
|
||||
#include "FlatBufferClient.h"
|
||||
|
||||
// util
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
// qt
|
||||
#include <QJsonObject>
|
||||
#include <QTcpServer>
|
||||
@@ -24,6 +27,7 @@ FlatBufferServer::~FlatBufferServer()
|
||||
|
||||
void FlatBufferServer::initServer()
|
||||
{
|
||||
_netOrigin = NetOrigin::getInstance();
|
||||
connect(_server, &QTcpServer::newConnection, this, &FlatBufferServer::newConnection);
|
||||
|
||||
// apply config
|
||||
@@ -58,11 +62,16 @@ void FlatBufferServer::newConnection()
|
||||
{
|
||||
if(QTcpSocket* socket = _server->nextPendingConnection())
|
||||
{
|
||||
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
|
||||
FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
|
||||
// internal
|
||||
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
|
||||
_openConnections.append(client);
|
||||
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
|
||||
{
|
||||
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
|
||||
FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
|
||||
// internal
|
||||
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
|
||||
_openConnections.append(client);
|
||||
}
|
||||
else
|
||||
socket->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
164
libsrc/hyperion/AuthManager.cpp
Normal file
164
libsrc/hyperion/AuthManager.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <hyperion/AuthManager.h>
|
||||
|
||||
// util
|
||||
#include <db/AuthTable.h>
|
||||
|
||||
// qt
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
|
||||
AuthManager* AuthManager::manager = nullptr;
|
||||
|
||||
AuthManager::AuthManager(const QString& rootPath, QObject* parent)
|
||||
: QObject(parent)
|
||||
, _authTable(new AuthTable(rootPath, this))
|
||||
, _pendingRequests()
|
||||
, _authRequired(true)
|
||||
, _timer(new QTimer(this))
|
||||
{
|
||||
AuthManager::manager = this;
|
||||
|
||||
// setup timer
|
||||
_timer->setInterval(1000);
|
||||
connect(_timer, &QTimer::timeout, this, &AuthManager::checkTimeout);
|
||||
|
||||
// init with default user and password
|
||||
if(!_authTable->userExist("Hyperion"))
|
||||
{
|
||||
_authTable->createUser("Hyperion","hyperion");
|
||||
}
|
||||
}
|
||||
|
||||
const bool & AuthManager::isAuthRequired()
|
||||
{
|
||||
return _authRequired;
|
||||
}
|
||||
|
||||
const bool & AuthManager::isLocalAuthRequired()
|
||||
{
|
||||
return _localAuthRequired;
|
||||
}
|
||||
|
||||
const AuthManager::AuthDefinition AuthManager::createToken(const QString& comment)
|
||||
{
|
||||
const QString token = QUuid::createUuid().toString().mid(1, 36);
|
||||
const QString id = QUuid::createUuid().toString().mid(1, 36).left(5);
|
||||
|
||||
_authTable->createToken(token, comment, id);
|
||||
|
||||
AuthDefinition def;
|
||||
def.comment = comment;
|
||||
def.token = token;
|
||||
def.id = id;
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
const QVector<AuthManager::AuthDefinition> AuthManager::getTokenList()
|
||||
{
|
||||
QVector<QVariantMap> vector = _authTable->getTokenList();
|
||||
QVector<AuthManager::AuthDefinition> finalVec;
|
||||
for(const auto& entry : vector)
|
||||
{
|
||||
AuthDefinition def;
|
||||
def.comment = entry["comment"].toString();
|
||||
def.id = entry["id"].toString();
|
||||
def.lastUse = entry["last_use"].toString();
|
||||
|
||||
// don't add empty ids
|
||||
if(!entry["id"].toString().isEmpty())
|
||||
finalVec.append(def);
|
||||
}
|
||||
return finalVec;
|
||||
}
|
||||
|
||||
const bool AuthManager::isUserAuthorized(const QString& user, const QString& pw)
|
||||
{
|
||||
return _authTable->isUserAuthorized(user, pw);
|
||||
}
|
||||
|
||||
const bool AuthManager::isTokenAuthorized(const QString& token)
|
||||
{
|
||||
return _authTable->tokenExist(token);
|
||||
}
|
||||
|
||||
void AuthManager::setNewTokenRequest(QObject* caller, const QString& comment, const QString& id)
|
||||
{
|
||||
if(!_pendingRequests.contains(id))
|
||||
{
|
||||
AuthDefinition newDef {id, comment, caller, uint64_t(QDateTime::currentMSecsSinceEpoch()+60000)};
|
||||
_pendingRequests[id] = newDef;
|
||||
_timer->start();
|
||||
emit newPendingTokenRequest(id, comment);
|
||||
}
|
||||
}
|
||||
|
||||
const bool AuthManager::acceptTokenRequest(const QString& id)
|
||||
{
|
||||
if(_pendingRequests.contains(id))
|
||||
{
|
||||
const QString token = QUuid::createUuid().toString().remove("{").remove("}");
|
||||
AuthDefinition def = _pendingRequests.take(id);
|
||||
_authTable->createToken(token, def.comment, id);
|
||||
emit tokenResponse(true, def.caller, token, def.comment, id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool AuthManager::denyTokenRequest(const QString& id)
|
||||
{
|
||||
if(_pendingRequests.contains(id))
|
||||
{
|
||||
AuthDefinition def = _pendingRequests.take(id);
|
||||
emit tokenResponse(false, def.caller, QString(), def.comment, id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const QMap<QString, AuthManager::AuthDefinition> AuthManager::getPendingRequests()
|
||||
{
|
||||
return _pendingRequests;
|
||||
}
|
||||
|
||||
const bool AuthManager::deleteToken(const QString& id)
|
||||
{
|
||||
if(_authTable->deleteToken(id))
|
||||
{
|
||||
//emit tokenDeleted(token);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AuthManager::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
|
||||
{
|
||||
if(type == settings::NETWORK)
|
||||
{
|
||||
const QJsonObject& obj = config.object();
|
||||
_authRequired = obj["apiAuth"].toBool(true);
|
||||
_localAuthRequired = obj["localApiAuth"].toBool(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AuthManager::checkTimeout()
|
||||
{
|
||||
const uint64_t now = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
QMapIterator<QString, AuthDefinition> i(_pendingRequests);
|
||||
while (i.hasNext())
|
||||
{
|
||||
i.next();
|
||||
|
||||
const AuthDefinition& def = i.value();
|
||||
if(def.timeoutTime <= now)
|
||||
{
|
||||
emit tokenResponse(false, def.caller, QString(), def.comment, def.id);
|
||||
_pendingRequests.remove(i.key());
|
||||
}
|
||||
}
|
||||
// abort if empty
|
||||
if(_pendingRequests.isEmpty())
|
||||
_timer->stop();
|
||||
}
|
@@ -25,5 +25,6 @@ target_link_libraries(hyperion
|
||||
bonjour
|
||||
boblightserver
|
||||
effectengine
|
||||
database
|
||||
${QT_LIBRARIES}
|
||||
)
|
||||
|
@@ -71,7 +71,7 @@ Hyperion* Hyperion::getInstance()
|
||||
|
||||
Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath)
|
||||
: _daemon(daemon)
|
||||
, _settingsManager(new SettingsManager(this, instance, configFile))
|
||||
, _settingsManager(new SettingsManager(instance, configFile, this))
|
||||
, _componentRegister(this)
|
||||
, _ledString(hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object())))
|
||||
, _ledStringClone(hyperion::createLedStringClone(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object())))
|
||||
@@ -89,6 +89,9 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
|
||||
, _prevCompId(hyperion::COMP_INVALID)
|
||||
, _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK)
|
||||
{
|
||||
// forward settings changed to Hyperion
|
||||
connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::settingsChanged);
|
||||
|
||||
if (!_raw2ledAdjustment->verifyAdjustments())
|
||||
Warning(_log, "At least one led has no color calibration, please add all leds from your led layout to an 'LED index' field!");
|
||||
|
||||
@@ -209,9 +212,6 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
|
||||
|
||||
const QJsonArray leds = config.array();
|
||||
|
||||
// // lock update()
|
||||
// _lockUpdate = true;
|
||||
|
||||
// stop and cache all running effects, as effects depend heavily on ledlayout
|
||||
_effectEngine->cacheRunningEffects();
|
||||
|
||||
@@ -247,14 +247,10 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
|
||||
|
||||
// start cached effects
|
||||
_effectEngine->startCachedEffects();
|
||||
|
||||
// // unlock
|
||||
// _lockUpdate = false;
|
||||
}
|
||||
else if(type == settings::DEVICE)
|
||||
{
|
||||
QMutexLocker lock(&_changes);
|
||||
// _lockUpdate = true;
|
||||
QJsonObject dev = config.object();
|
||||
|
||||
// handle hwLedCount update
|
||||
@@ -278,7 +274,6 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
|
||||
// do always reinit until the led devices can handle dynamic changes
|
||||
dev["currentLedCount"] = int(_hwLedCount); // Inject led count info
|
||||
_ledDeviceWrapper->createLedDevice(dev);
|
||||
// _lockUpdate = false;
|
||||
}
|
||||
// update once to push single color sets / adjustments/ ledlayout resizes and update ledBuffer color
|
||||
update();
|
||||
|
@@ -3,6 +3,7 @@
|
||||
|
||||
// util
|
||||
#include <utils/JsonUtils.h>
|
||||
#include <db/SettingsTable.h>
|
||||
|
||||
// json schema process
|
||||
#include <utils/jsonschema/QJsonFactory.h>
|
||||
@@ -16,11 +17,11 @@
|
||||
|
||||
QJsonObject SettingsManager::schemaJson;
|
||||
|
||||
SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile)
|
||||
SettingsManager::SettingsManager(const quint8& instance, const QString& configFile, Hyperion* hyperion)
|
||||
: _hyperion(hyperion)
|
||||
, _log(Logger::getInstance("SettingsManager"))
|
||||
, _sTable(new SettingsTable(instance, this))
|
||||
{
|
||||
connect(this, &SettingsManager::settingsChanged, _hyperion, &Hyperion::settingsChanged);
|
||||
// get schema
|
||||
if(schemaJson.isEmpty())
|
||||
{
|
||||
@@ -34,11 +35,14 @@ SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, con
|
||||
throw std::runtime_error(error.what());
|
||||
}
|
||||
}
|
||||
|
||||
// get default config
|
||||
QJsonObject defaultConfig;
|
||||
if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log))
|
||||
throw std::runtime_error("Failed to read default config");
|
||||
|
||||
// TODO BEGIN - remove when database migration is done
|
||||
|
||||
Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile));
|
||||
QJsonSchemaChecker schemaCheckerT;
|
||||
schemaCheckerT.setSchema(schemaJson);
|
||||
@@ -70,76 +74,74 @@ SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, con
|
||||
throw std::runtime_error("ERROR: Can't save configuration file, aborting");
|
||||
}
|
||||
|
||||
Debug(_log,"Settings database initialized")
|
||||
}
|
||||
// TODO END - remove when database migration is done
|
||||
|
||||
SettingsManager::SettingsManager(const quint8& instance, const QString& configFile)
|
||||
: _hyperion(nullptr)
|
||||
, _log(Logger::getInstance("SettingsManager"))
|
||||
{
|
||||
Q_INIT_RESOURCE(resource);
|
||||
// get schema
|
||||
if(schemaJson.isEmpty())
|
||||
// transform json to string lists
|
||||
QStringList keyList = defaultConfig.keys();
|
||||
QStringList defValueList;
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
try
|
||||
if(defaultConfig[key].isObject())
|
||||
{
|
||||
schemaJson = QJsonFactory::readSchema(":/hyperion-schema");
|
||||
defValueList << QString(QJsonDocument(defaultConfig[key].toObject()).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
catch(const std::runtime_error& error)
|
||||
else if(defaultConfig[key].isArray())
|
||||
{
|
||||
throw std::runtime_error(error.what());
|
||||
defValueList << QString(QJsonDocument(defaultConfig[key].toArray()).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
}
|
||||
// get default config
|
||||
QJsonObject defaultConfig;
|
||||
if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log))
|
||||
throw std::runtime_error("Failed to read default config");
|
||||
|
||||
Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile));
|
||||
QJsonSchemaChecker schemaCheckerT;
|
||||
schemaCheckerT.setSchema(schemaJson);
|
||||
|
||||
if(!JsonUtils::readFile(configFile, _qconfig, _log))
|
||||
throw std::runtime_error("Failed to load config!");
|
||||
|
||||
// validate config with schema and correct it if required
|
||||
QPair<bool, bool> validate = schemaCheckerT.validate(_qconfig);
|
||||
|
||||
// errors in schema syntax, abort
|
||||
if (!validate.second)
|
||||
// fill database with default data if required
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
foreach (auto & schemaError, schemaCheckerT.getMessages())
|
||||
QString val = defValueList.takeFirst();
|
||||
// prevent overwrite
|
||||
if(!_sTable->recordExist(key))
|
||||
_sTable->createSettingsRecord(key,val);
|
||||
}
|
||||
|
||||
// need to validate all data in database constuct the entire data object
|
||||
// TODO refactor schemaChecker to accept QJsonArray in validate(); QJsonDocument container? To validate them per entry...
|
||||
QJsonObject dbConfig;
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
QJsonDocument doc = _sTable->getSettingsRecord(key);
|
||||
if(doc.isArray())
|
||||
dbConfig[key] = doc.array();
|
||||
else
|
||||
dbConfig[key] = doc.object();
|
||||
}
|
||||
|
||||
// validate full dbconfig against schema, on error we need to rewrite entire table
|
||||
QJsonSchemaChecker schemaChecker;
|
||||
schemaChecker.setSchema(schemaJson);
|
||||
QPair<bool,bool> valid = schemaChecker.validate(dbConfig);
|
||||
// check if our main schema syntax is IO
|
||||
if (!valid.second)
|
||||
{
|
||||
foreach (auto & schemaError, schemaChecker.getMessages())
|
||||
Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError));
|
||||
|
||||
throw std::runtime_error("ERROR: Hyperion schema has syntax errors!");
|
||||
throw std::runtime_error("The config schema has invalid syntax. This should never happen! Go fix it!");
|
||||
}
|
||||
// errors in configuration, correct it!
|
||||
if (!validate.first)
|
||||
if (!valid.first)
|
||||
{
|
||||
Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied");
|
||||
_qconfig = schemaCheckerT.getAutoCorrectedConfig(_qconfig);
|
||||
Info(_log,"Table upgrade required...");
|
||||
dbConfig = schemaChecker.getAutoCorrectedConfig(dbConfig);
|
||||
|
||||
foreach (auto & schemaError, schemaCheckerT.getMessages())
|
||||
foreach (auto & schemaError, schemaChecker.getMessages())
|
||||
Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError));
|
||||
|
||||
if (!JsonUtils::write(configFile, _qconfig, _log))
|
||||
throw std::runtime_error("ERROR: Can't save configuration file, aborting");
|
||||
saveSettings(dbConfig);
|
||||
}
|
||||
else
|
||||
_qconfig = dbConfig;
|
||||
|
||||
Debug(_log,"Settings database initialized")
|
||||
}
|
||||
|
||||
SettingsManager::~SettingsManager()
|
||||
{
|
||||
}
|
||||
|
||||
const QJsonDocument SettingsManager::getSetting(const settings::type& type)
|
||||
{
|
||||
QString key = settings::typeToString(type);
|
||||
if(_qconfig[key].isObject())
|
||||
return QJsonDocument(_qconfig[key].toObject());
|
||||
else
|
||||
return QJsonDocument(_qconfig[key].toArray());
|
||||
return _sTable->getSettingsRecord(settings::typeToString(type));
|
||||
}
|
||||
|
||||
bool SettingsManager::saveSettings(QJsonObject config, const bool& correct)
|
||||
@@ -168,25 +170,34 @@ bool SettingsManager::saveSettings(QJsonObject config, const bool& correct)
|
||||
return false;
|
||||
}
|
||||
|
||||
// compare old data with new data to emit/save changes accordingly
|
||||
for(const auto key : config.keys())
|
||||
{
|
||||
QString newData, oldData;
|
||||
|
||||
_qconfig[key].isObject()
|
||||
? oldData = QString(QJsonDocument(_qconfig[key].toObject()).toJson(QJsonDocument::Compact))
|
||||
: oldData = QString(QJsonDocument(_qconfig[key].toArray()).toJson(QJsonDocument::Compact));
|
||||
|
||||
config[key].isObject()
|
||||
? newData = QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact))
|
||||
: newData = QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact));
|
||||
|
||||
if(oldData != newData)
|
||||
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(newData.toLocal8Bit()));
|
||||
}
|
||||
|
||||
// store the current state
|
||||
// store the new config
|
||||
_qconfig = config;
|
||||
|
||||
// extract keys and data
|
||||
QStringList keyList = config.keys();
|
||||
QStringList newValueList;
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
if(config[key].isObject())
|
||||
{
|
||||
newValueList << QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
else if(config[key].isArray())
|
||||
{
|
||||
newValueList << QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
}
|
||||
|
||||
// compare database data with new data to emit/save changes accordingly
|
||||
for(const auto key : keyList)
|
||||
{
|
||||
QString data = newValueList.takeFirst();
|
||||
if(_sTable->getSettingsRecordString(key) != data)
|
||||
{
|
||||
_sTable->createSettingsRecord(key, data);
|
||||
|
||||
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@@ -79,6 +79,10 @@
|
||||
{
|
||||
"$ref": "schema-instCapture.json"
|
||||
},
|
||||
"network":
|
||||
{
|
||||
"$ref": "schema-network.json"
|
||||
},
|
||||
"ledConfig":
|
||||
{
|
||||
"$ref": "schema-ledConfig.json"
|
||||
|
@@ -23,5 +23,6 @@
|
||||
<file alias="schema-ledConfig.json">schema/schema-ledConfig.json</file>
|
||||
<file alias="schema-leds.json">schema/schema-leds.json</file>
|
||||
<file alias="schema-instCapture.json">schema/schema-instCapture.json</file>
|
||||
<file alias="schema-network.json">schema/schema-network.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
54
libsrc/hyperion/schema/schema-network.json
Normal file
54
libsrc/hyperion/schema/schema-network.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"title" : "edt_conf_net_heading_title",
|
||||
"required" : true,
|
||||
"properties" :
|
||||
{
|
||||
"internetAccessAPI" :
|
||||
{
|
||||
"type" : "boolean",
|
||||
"title" : "edt_conf_net_internetAccessAPI_title",
|
||||
"required" : true,
|
||||
"default" : false,
|
||||
"propertyOrder" : 1
|
||||
},
|
||||
"ipWhitelist" :
|
||||
{
|
||||
"type" : "array",
|
||||
"title" : "edt_conf_net_ipWhitelist_title",
|
||||
"required" : true,
|
||||
"items" : {
|
||||
"type": "string",
|
||||
"title" : "edt_conf_net_ip_itemtitle"
|
||||
},
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"internetAccessAPI": false
|
||||
}
|
||||
},
|
||||
"propertyOrder" : 2
|
||||
},
|
||||
"apiAuth" :
|
||||
{
|
||||
"type" : "boolean",
|
||||
"title" : "edt_conf_net_apiAuth_title",
|
||||
"required" : true,
|
||||
"default" : true,
|
||||
"propertyOrder" : 3
|
||||
},
|
||||
"localApiAuth" :
|
||||
{
|
||||
"type" : "boolean",
|
||||
"title" : "edt_conf_net_localApiAuth_title",
|
||||
"required" : true,
|
||||
"default" : false,
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"apiAuth": true
|
||||
}
|
||||
},
|
||||
"propertyOrder" : 4
|
||||
}
|
||||
},
|
||||
"additionalProperties" : false
|
||||
}
|
@@ -6,20 +6,16 @@
|
||||
#include <QTcpSocket>
|
||||
#include <QHostAddress>
|
||||
|
||||
// websocket includes
|
||||
#include "webserver/WebSocketClient.h"
|
||||
|
||||
JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
|
||||
JsonClientConnection::JsonClientConnection(QTcpSocket *socket, const bool& localConnection)
|
||||
: QObject()
|
||||
, _socket(socket)
|
||||
, _websocketClient(nullptr)
|
||||
, _receiveBuffer()
|
||||
, _log(Logger::getInstance("JSONCLIENTCONNECTION"))
|
||||
{
|
||||
connect(_socket, &QTcpSocket::disconnected, this, &JsonClientConnection::disconnected);
|
||||
connect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest);
|
||||
// create a new instance of JsonAPI
|
||||
_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, this);
|
||||
_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, localConnection, this);
|
||||
// get the callback messages from JsonAPI and send it to the client
|
||||
connect(_jsonAPI,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject)));
|
||||
}
|
||||
@@ -27,37 +23,21 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
|
||||
void JsonClientConnection::readRequest()
|
||||
{
|
||||
_receiveBuffer += _socket->readAll();
|
||||
|
||||
// might be an old hyperion classic handshake request or raw socket data
|
||||
if(_receiveBuffer.contains("Upgrade: websocket"))
|
||||
// raw socket data, handling as usual
|
||||
int bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||
while(bytes > 0)
|
||||
{
|
||||
if(_websocketClient == Q_NULLPTR)
|
||||
{
|
||||
// disconnect this slot from socket for further requests
|
||||
disconnect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest);
|
||||
int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19;
|
||||
QByteArray header(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data());
|
||||
_websocketClient = new WebSocketClient(header, _socket, this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// raw socket data, handling as usual
|
||||
int bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||
while(bytes > 0)
|
||||
{
|
||||
// create message string
|
||||
QString message(QByteArray(_receiveBuffer.data(), bytes));
|
||||
// create message string
|
||||
QString message(QByteArray(_receiveBuffer.data(), bytes));
|
||||
|
||||
// remove message data from buffer
|
||||
_receiveBuffer = _receiveBuffer.mid(bytes);
|
||||
// remove message data from buffer
|
||||
_receiveBuffer = _receiveBuffer.mid(bytes);
|
||||
|
||||
// handle message
|
||||
_jsonAPI->handleMessage(message);
|
||||
// handle message
|
||||
_jsonAPI->handleMessage(message);
|
||||
|
||||
// try too look up '\n' again
|
||||
bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||
}
|
||||
// try too look up '\n' again
|
||||
bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,6 @@
|
||||
|
||||
class JsonAPI;
|
||||
class QTcpSocket;
|
||||
class WebSocketClient;
|
||||
|
||||
///
|
||||
/// The Connection object created by \a JsonServer when a new connection is established
|
||||
@@ -24,7 +23,7 @@ public:
|
||||
/// Constructor
|
||||
/// @param socket The Socket object for this connection
|
||||
///
|
||||
JsonClientConnection(QTcpSocket * socket);
|
||||
JsonClientConnection(QTcpSocket * socket, const bool& localConnection);
|
||||
|
||||
signals:
|
||||
void connectionClosed();
|
||||
@@ -42,7 +41,6 @@ private slots:
|
||||
|
||||
private:
|
||||
QTcpSocket* _socket;
|
||||
WebSocketClient* _websocketClient;
|
||||
/// new instance of JsonAPI
|
||||
JsonAPI * _jsonAPI;
|
||||
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
// bonjour include
|
||||
#include <bonjour/bonjourserviceregister.h>
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
// qt includes
|
||||
#include <QTcpServer>
|
||||
@@ -19,6 +20,7 @@ JsonServer::JsonServer(const QJsonDocument& config)
|
||||
, _server(new QTcpServer(this))
|
||||
, _openConnections()
|
||||
, _log(Logger::getInstance("JSONSERVER"))
|
||||
, _netOrigin(NetOrigin::getInstance())
|
||||
{
|
||||
Debug(_log, "Created instance");
|
||||
|
||||
@@ -95,12 +97,17 @@ void JsonServer::newConnection()
|
||||
{
|
||||
if (QTcpSocket * socket = _server->nextPendingConnection())
|
||||
{
|
||||
Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str());
|
||||
JsonClientConnection * connection = new JsonClientConnection(socket);
|
||||
_openConnections.insert(connection);
|
||||
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
|
||||
{
|
||||
Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str());
|
||||
JsonClientConnection * connection = new JsonClientConnection(socket, _netOrigin->isLocalAddress(socket->peerAddress(), socket->localAddress()));
|
||||
_openConnections.insert(connection);
|
||||
|
||||
// register slot for cleaning up after the connection closed
|
||||
connect(connection, &JsonClientConnection::connectionClosed, this, &JsonServer::closedConnection);
|
||||
// register slot for cleaning up after the connection closed
|
||||
connect(connection, &JsonClientConnection::connectionClosed, this, &JsonServer::closedConnection);
|
||||
}
|
||||
else
|
||||
socket->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ bool ProviderUdp::init(const QJsonObject &deviceConfig)
|
||||
}
|
||||
|
||||
_port = deviceConfig["port"].toInt(_port);
|
||||
if ( (_port <= 0) || (_port > 65535) )
|
||||
if ( (_port <= 0) || (_port > MAX_PORT) )
|
||||
{
|
||||
throw std::runtime_error("invalid target port");
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@
|
||||
|
||||
class QUdpSocket;
|
||||
|
||||
#define MAX_PORT 65535
|
||||
|
||||
///
|
||||
/// The ProviderUdp implements an abstract base-class for LedDevices using UDP packets.
|
||||
///
|
||||
@@ -54,6 +56,6 @@ protected:
|
||||
///
|
||||
QUdpSocket * _udpSocket;
|
||||
QHostAddress _address;
|
||||
quint16 _port;
|
||||
ushort _port;
|
||||
QString _defaultHost;
|
||||
};
|
||||
|
@@ -1,6 +1,9 @@
|
||||
#include <protoserver/ProtoServer.h>
|
||||
#include "ProtoClientConnection.h"
|
||||
|
||||
// util
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
// qt
|
||||
#include <QJsonObject>
|
||||
#include <QTcpServer>
|
||||
@@ -24,6 +27,7 @@ ProtoServer::~ProtoServer()
|
||||
|
||||
void ProtoServer::initServer()
|
||||
{
|
||||
_netOrigin = NetOrigin::getInstance();
|
||||
connect(_server, &QTcpServer::newConnection, this, &ProtoServer::newConnection);
|
||||
|
||||
// apply config
|
||||
@@ -58,11 +62,16 @@ void ProtoServer::newConnection()
|
||||
{
|
||||
if(QTcpSocket * socket = _server->nextPendingConnection())
|
||||
{
|
||||
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
|
||||
ProtoClientConnection * client = new ProtoClientConnection(socket, _timeout, this);
|
||||
// internal
|
||||
connect(client, &ProtoClientConnection::clientDisconnected, this, &ProtoServer::clientDisconnected);
|
||||
_openConnections.append(client);
|
||||
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
|
||||
{
|
||||
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
|
||||
ProtoClientConnection * client = new ProtoClientConnection(socket, _timeout, this);
|
||||
// internal
|
||||
connect(client, &ProtoClientConnection::clientDisconnected, this, &ProtoServer::clientDisconnected);
|
||||
_openConnections.append(client);
|
||||
}
|
||||
else
|
||||
socket->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,20 +8,24 @@
|
||||
#include <hyperion/Hyperion.h>
|
||||
#include "HyperionConfig.h"
|
||||
|
||||
// utils includes
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
// qt includes
|
||||
#include <QUdpSocket>
|
||||
#include <QJsonObject>
|
||||
|
||||
using namespace hyperion;
|
||||
|
||||
UDPListener::UDPListener(const QJsonDocument& config) :
|
||||
QObject(),
|
||||
_server(new QUdpSocket(this)),
|
||||
_priority(0),
|
||||
_timeout(0),
|
||||
_log(Logger::getInstance("UDPLISTENER")),
|
||||
_isActive(false),
|
||||
_listenPort(0)
|
||||
UDPListener::UDPListener(const QJsonDocument& config)
|
||||
: QObject()
|
||||
, _server(new QUdpSocket(this))
|
||||
, _priority(0)
|
||||
, _timeout(0)
|
||||
, _log(Logger::getInstance("UDPLISTENER"))
|
||||
, _isActive(false)
|
||||
, _listenPort(0)
|
||||
, _netOrigin(NetOrigin::getInstance())
|
||||
{
|
||||
// listen for component change
|
||||
connect(Hyperion::getInstance(), &Hyperion::componentStateChanged, this, &UDPListener::componentStateChanged);
|
||||
@@ -116,7 +120,9 @@ void UDPListener::readPendingDatagrams()
|
||||
quint16 senderPort;
|
||||
|
||||
_server->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
|
||||
processTheDatagram(&datagram, &sender);
|
||||
|
||||
if(_netOrigin->accessAllowed(sender, _listenAddress))
|
||||
processTheDatagram(&datagram, &sender);
|
||||
}
|
||||
}
|
||||
|
||||
|
76
libsrc/utils/NetOrigin.cpp
Normal file
76
libsrc/utils/NetOrigin.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
NetOrigin* NetOrigin::instance = nullptr;
|
||||
|
||||
NetOrigin::NetOrigin(QObject* parent, Logger* log)
|
||||
: QObject(parent)
|
||||
, _log(log)
|
||||
, _internetAccessAllowed(false)
|
||||
, _ipWhitelist()
|
||||
{
|
||||
NetOrigin::instance = this;
|
||||
}
|
||||
|
||||
bool NetOrigin::accessAllowed(const QHostAddress& address, const QHostAddress& local)
|
||||
{
|
||||
if(_internetAccessAllowed)
|
||||
return true;
|
||||
|
||||
if(_ipWhitelist.contains(address)) // v4 and v6
|
||||
return true;
|
||||
|
||||
if(!isLocalAddress(address, local))
|
||||
{
|
||||
Warning(_log,"Client connection with IP address '%s' has been rejected! It's not whitelisted, access denied.",QSTRING_CSTR(address.toString()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetOrigin::isLocalAddress(const QHostAddress& address, const QHostAddress& local)
|
||||
{
|
||||
if(address.protocol() == QAbstractSocket::IPv4Protocol)
|
||||
{
|
||||
if(!address.isInSubnet(local, 24)) // 255.255.255.xxx; IPv4 0-32
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(address.protocol() == QAbstractSocket::IPv6Protocol)
|
||||
{
|
||||
if(!address.isInSubnet(local, 64)) // 2001:db8:abcd:0012:XXXX:XXXX:XXXX:XXXX; IPv6 0-128
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetOrigin::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
|
||||
{
|
||||
if(type == settings::NETWORK)
|
||||
{
|
||||
const QJsonObject& obj = config.object();
|
||||
_internetAccessAllowed = obj["internetAccessAPI"].toBool(false);
|
||||
|
||||
const QJsonArray& arr = obj["ipWhitelist"].toArray();
|
||||
_ipWhitelist.clear();
|
||||
|
||||
for(const auto& e : arr)
|
||||
{
|
||||
const QString& entry = e.toString("");
|
||||
if(entry.isEmpty())
|
||||
continue;
|
||||
|
||||
QHostAddress host(entry);
|
||||
if(host.isNull())
|
||||
{
|
||||
Warning(_log,"The whitelisted IP address '%s' isn't valid! Skipped",QSTRING_CSTR(entry));
|
||||
continue;
|
||||
}
|
||||
_ipWhitelist << host;
|
||||
}
|
||||
}
|
||||
}
|
@@ -55,7 +55,6 @@ void CgiHandler::cmd_cfg_jsonserver()
|
||||
if ( _args.at(0) == "cfg_jsonserver" )
|
||||
{
|
||||
quint16 jsonPort = 19444;
|
||||
|
||||
// send result as reply
|
||||
_reply->addHeader ("Content-Type", "text/plain" );
|
||||
_reply->appendRawData (QByteArrayLiteral(":") % QString::number(jsonPort).toUtf8() );
|
||||
|
@@ -4,8 +4,8 @@
|
||||
#include "QtHttpReply.h"
|
||||
#include "QtHttpServer.h"
|
||||
#include "QtHttpHeader.h"
|
||||
#include "WebSocketClient.h"
|
||||
#include "WebJsonRpc.h"
|
||||
#include "webserver/WebSocketClient.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QTcpSocket>
|
||||
@@ -16,15 +16,16 @@
|
||||
|
||||
const QByteArray & QtHttpClientWrapper::CRLF = QByteArrayLiteral ("\r\n");
|
||||
|
||||
QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent)
|
||||
: QObject (parent)
|
||||
, m_guid ("")
|
||||
, m_parsingStatus (AwaitingRequest)
|
||||
, m_sockClient (sock)
|
||||
, m_currentRequest (Q_NULLPTR)
|
||||
, m_serverHandle (parent)
|
||||
, m_websocketClient(nullptr)
|
||||
, m_webJsonRpc (nullptr)
|
||||
QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, const bool& localConnection, QtHttpServer * parent)
|
||||
: QObject (parent)
|
||||
, m_guid ("")
|
||||
, m_parsingStatus (AwaitingRequest)
|
||||
, m_sockClient (sock)
|
||||
, m_currentRequest (Q_NULLPTR)
|
||||
, m_serverHandle (parent)
|
||||
, m_localConnection(localConnection)
|
||||
, m_websocketClient(nullptr)
|
||||
, m_webJsonRpc (nullptr)
|
||||
{
|
||||
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
|
||||
}
|
||||
@@ -120,7 +121,7 @@ void QtHttpClientWrapper::onClientDataReceived (void) {
|
||||
{
|
||||
// disconnect this slot from socket for further requests
|
||||
disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
|
||||
m_websocketClient = new WebSocketClient(m_currentRequest->getHeader(QtHttpHeader::SecWebSocketKey), m_sockClient, this);
|
||||
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, m_localConnection, this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -149,7 +150,7 @@ void QtHttpClientWrapper::onClientDataReceived (void) {
|
||||
{
|
||||
if(m_webJsonRpc == Q_NULLPTR)
|
||||
{
|
||||
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, this);
|
||||
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, m_localConnection, this);
|
||||
}
|
||||
m_webJsonRpc->handleMessage(m_currentRequest);
|
||||
break;
|
||||
|
@@ -13,43 +13,44 @@ class WebSocketClient;
|
||||
class WebJsonRpc;
|
||||
|
||||
class QtHttpClientWrapper : public QObject {
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent);
|
||||
explicit QtHttpClientWrapper (QTcpSocket * sock, const bool& localConnection, QtHttpServer * parent);
|
||||
|
||||
static const char SPACE = ' ';
|
||||
static const char COLON = ':';
|
||||
static const QByteArray & CRLF;
|
||||
static const char SPACE = ' ';
|
||||
static const char COLON = ':';
|
||||
static const QByteArray & CRLF;
|
||||
|
||||
enum ParsingStatus {
|
||||
ParsingError = -1,
|
||||
AwaitingRequest = 0,
|
||||
AwaitingHeaders = 1,
|
||||
AwaitingContent = 2,
|
||||
RequestParsed = 3
|
||||
};
|
||||
enum ParsingStatus {
|
||||
ParsingError = -1,
|
||||
AwaitingRequest = 0,
|
||||
AwaitingHeaders = 1,
|
||||
AwaitingContent = 2,
|
||||
RequestParsed = 3
|
||||
};
|
||||
|
||||
QString getGuid (void);
|
||||
QString getGuid (void);
|
||||
/// @brief Wrapper for sendReplyToClient(), handles m_parsingStatus and signal connect
|
||||
void sendToClientWithReply (QtHttpReply * reply);
|
||||
|
||||
private slots:
|
||||
void onClientDataReceived (void);
|
||||
void onClientDataReceived (void);
|
||||
|
||||
protected:
|
||||
ParsingStatus sendReplyToClient (QtHttpReply * reply);
|
||||
ParsingStatus sendReplyToClient (QtHttpReply * reply);
|
||||
|
||||
protected slots:
|
||||
void onReplySendHeadersRequested (void);
|
||||
void onReplySendDataRequested (void);
|
||||
void onReplySendHeadersRequested (void);
|
||||
void onReplySendDataRequested (void);
|
||||
|
||||
private:
|
||||
QString m_guid;
|
||||
ParsingStatus m_parsingStatus;
|
||||
QTcpSocket * m_sockClient;
|
||||
QtHttpRequest * m_currentRequest;
|
||||
QtHttpServer * m_serverHandle;
|
||||
QString m_guid;
|
||||
ParsingStatus m_parsingStatus;
|
||||
QTcpSocket * m_sockClient;
|
||||
QtHttpRequest * m_currentRequest;
|
||||
QtHttpServer * m_serverHandle;
|
||||
const bool m_localConnection;
|
||||
WebSocketClient * m_websocketClient;
|
||||
WebJsonRpc * m_webJsonRpc;
|
||||
};
|
||||
|
@@ -1,3 +1,4 @@
|
||||
|
||||
#include "QtHttpServer.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpReply.h"
|
||||
@@ -5,158 +6,147 @@
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include <utils/NetOrigin.h>
|
||||
|
||||
const QString & QtHttpServer::HTTP_VERSION = QStringLiteral ("HTTP/1.1");
|
||||
|
||||
QtHttpServerWrapper::QtHttpServerWrapper (QObject * parent)
|
||||
: QTcpServer (parent)
|
||||
, m_useSsl (false)
|
||||
{
|
||||
}
|
||||
: QTcpServer (parent)
|
||||
, m_useSsl (false)
|
||||
{ }
|
||||
|
||||
QtHttpServerWrapper::~QtHttpServerWrapper (void)
|
||||
{
|
||||
}
|
||||
QtHttpServerWrapper::~QtHttpServerWrapper (void) { }
|
||||
|
||||
void QtHttpServerWrapper::setUseSecure (const bool ssl) {
|
||||
m_useSsl = ssl;
|
||||
m_useSsl = ssl;
|
||||
}
|
||||
|
||||
void QtHttpServerWrapper::incomingConnection (qintptr handle)
|
||||
{
|
||||
QTcpSocket * sock = (m_useSsl
|
||||
? new QSslSocket (this)
|
||||
: new QTcpSocket (this));
|
||||
(sock->setSocketDescriptor (handle))
|
||||
? addPendingConnection (sock)
|
||||
: delete sock;
|
||||
void QtHttpServerWrapper::incomingConnection (qintptr handle) {
|
||||
QTcpSocket * sock = (m_useSsl
|
||||
? new QSslSocket (this)
|
||||
: new QTcpSocket (this));
|
||||
if (sock->setSocketDescriptor (handle)) {
|
||||
addPendingConnection (sock);
|
||||
}
|
||||
else {
|
||||
delete sock;
|
||||
}
|
||||
}
|
||||
|
||||
QtHttpServer::QtHttpServer (QObject * parent)
|
||||
: QObject (parent)
|
||||
, m_useSsl (false)
|
||||
, m_serverName (QStringLiteral ("The Qt5 HTTP Server"))
|
||||
: QObject (parent)
|
||||
, m_useSsl (false)
|
||||
, m_serverName (QStringLiteral ("The Qt5 HTTP Server"))
|
||||
, m_netOrigin (NetOrigin::getInstance())
|
||||
{
|
||||
m_sockServer = new QtHttpServerWrapper (this);
|
||||
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
|
||||
m_sockServer = new QtHttpServerWrapper (this);
|
||||
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
|
||||
}
|
||||
|
||||
const QString & QtHttpServer::getServerName (void) const
|
||||
{
|
||||
return m_serverName;
|
||||
const QString & QtHttpServer::getServerName (void) const {
|
||||
return m_serverName;
|
||||
}
|
||||
|
||||
quint16 QtHttpServer::getServerPort (void) const
|
||||
{
|
||||
return m_sockServer->serverPort ();
|
||||
quint16 QtHttpServer::getServerPort (void) const {
|
||||
return m_sockServer->serverPort ();
|
||||
}
|
||||
|
||||
QString QtHttpServer::getErrorString (void) const
|
||||
{
|
||||
return m_sockServer->errorString ();
|
||||
QString QtHttpServer::getErrorString (void) const {
|
||||
return m_sockServer->errorString ();
|
||||
}
|
||||
|
||||
void QtHttpServer::start (quint16 port)
|
||||
{
|
||||
void QtHttpServer::start (quint16 port) {
|
||||
if(!m_sockServer->isListening())
|
||||
(m_sockServer->listen (QHostAddress::Any, port))
|
||||
? emit started (m_sockServer->serverPort ())
|
||||
: emit error (m_sockServer->errorString ());
|
||||
{
|
||||
if (m_sockServer->listen (QHostAddress::Any, port)) {
|
||||
emit started (m_sockServer->serverPort ());
|
||||
}
|
||||
else {
|
||||
emit error (m_sockServer->errorString ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpServer::stop (void)
|
||||
{
|
||||
if (m_sockServer->isListening ())
|
||||
{
|
||||
m_sockServer->close ();
|
||||
|
||||
void QtHttpServer::stop (void) {
|
||||
if (m_sockServer->isListening ()) {
|
||||
m_sockServer->close ();
|
||||
// disconnect clients
|
||||
const QList<QTcpSocket*> socks = m_socksClientsHash.keys();
|
||||
for(auto sock : socks)
|
||||
{
|
||||
sock->close();
|
||||
}
|
||||
|
||||
emit stopped ();
|
||||
}
|
||||
emit stopped ();
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpServer::setServerName (const QString & serverName)
|
||||
{
|
||||
m_serverName = serverName;
|
||||
void QtHttpServer::setServerName (const QString & serverName) {
|
||||
m_serverName = serverName;
|
||||
}
|
||||
|
||||
void QtHttpServer::setUseSecure (const bool ssl)
|
||||
{
|
||||
m_useSsl = ssl;
|
||||
m_sockServer->setUseSecure (m_useSsl);
|
||||
void QtHttpServer::setUseSecure (const bool ssl) {
|
||||
m_useSsl = ssl;
|
||||
m_sockServer->setUseSecure (m_useSsl);
|
||||
}
|
||||
|
||||
void QtHttpServer::setPrivateKey (const QSslKey & key)
|
||||
{
|
||||
m_sslKey = key;
|
||||
void QtHttpServer::setPrivateKey (const QSslKey & key) {
|
||||
m_sslKey = key;
|
||||
}
|
||||
|
||||
void QtHttpServer::setCertificates (const QList<QSslCertificate> & certs)
|
||||
{
|
||||
m_sslCerts = certs;
|
||||
void QtHttpServer::setCertificates (const QList<QSslCertificate> & certs) {
|
||||
m_sslCerts = certs;
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientConnected (void)
|
||||
{
|
||||
while (m_sockServer->hasPendingConnections ())
|
||||
{
|
||||
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ())
|
||||
{
|
||||
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
|
||||
if (m_useSsl)
|
||||
void QtHttpServer::onClientConnected (void) {
|
||||
while (m_sockServer->hasPendingConnections ()) {
|
||||
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ()) {
|
||||
if(m_netOrigin->accessAllowed(sock->peerAddress(), sock->localAddress()))
|
||||
{
|
||||
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock))
|
||||
{
|
||||
connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
|
||||
connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
|
||||
connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
|
||||
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
|
||||
ssl->setLocalCertificateChain (m_sslCerts);
|
||||
ssl->setPrivateKey (m_sslKey);
|
||||
ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
|
||||
ssl->startServerEncryption ();
|
||||
}
|
||||
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
|
||||
if (m_useSsl) {
|
||||
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock)) {
|
||||
connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
|
||||
connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
|
||||
connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
|
||||
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
|
||||
ssl->setLocalCertificateChain (m_sslCerts);
|
||||
ssl->setPrivateKey (m_sslKey);
|
||||
ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
|
||||
ssl->startServerEncryption ();
|
||||
}
|
||||
}
|
||||
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, m_netOrigin->isLocalAddress(sock->peerAddress(), sock->localAddress()), this);
|
||||
m_socksClientsHash.insert (sock, wrapper);
|
||||
emit clientConnected (wrapper->getGuid ());
|
||||
}
|
||||
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, this);
|
||||
m_socksClientsHash.insert (sock, wrapper);
|
||||
emit clientConnected (wrapper->getGuid ());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sock->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslEncrypted (void)
|
||||
{
|
||||
void QtHttpServer::onClientSslEncrypted (void) { }
|
||||
|
||||
void QtHttpServer::onClientSslPeerVerifyError (const QSslError & err) {
|
||||
Q_UNUSED (err)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslPeerVerifyError (const QSslError & err)
|
||||
{
|
||||
Q_UNUSED (err)
|
||||
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors) {
|
||||
Q_UNUSED (errors)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors)
|
||||
{
|
||||
Q_UNUSED (errors)
|
||||
void QtHttpServer::onClientSslModeChanged (QSslSocket::SslMode mode) {
|
||||
Q_UNUSED (mode)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslModeChanged (QSslSocket::SslMode mode)
|
||||
{
|
||||
Q_UNUSED (mode)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientDisconnected (void)
|
||||
{
|
||||
if (QTcpSocket * sockClient = qobject_cast<QTcpSocket *> (sender ()))
|
||||
{
|
||||
if (QtHttpClientWrapper * wrapper = m_socksClientsHash.value (sockClient, Q_NULLPTR))
|
||||
{
|
||||
emit clientDisconnected (wrapper->getGuid ());
|
||||
wrapper->deleteLater ();
|
||||
m_socksClientsHash.remove (sockClient);
|
||||
}
|
||||
}
|
||||
void QtHttpServer::onClientDisconnected (void) {
|
||||
if (QTcpSocket * sockClient = qobject_cast<QTcpSocket *> (sender ())) {
|
||||
if (QtHttpClientWrapper * wrapper = m_socksClientsHash.value (sockClient, Q_NULLPTR)) {
|
||||
emit clientDisconnected (wrapper->getGuid ());
|
||||
wrapper->deleteLater ();
|
||||
m_socksClientsHash.remove (sockClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,72 +17,72 @@ class QTcpServer;
|
||||
class QtHttpRequest;
|
||||
class QtHttpReply;
|
||||
class QtHttpClientWrapper;
|
||||
class NetOrigin;
|
||||
|
||||
class QtHttpServerWrapper : public QTcpServer {
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtHttpServerWrapper (QObject * parent = Q_NULLPTR);
|
||||
virtual ~QtHttpServerWrapper (void);
|
||||
explicit QtHttpServerWrapper (QObject * parent = Q_NULLPTR);
|
||||
virtual ~QtHttpServerWrapper (void);
|
||||
|
||||
void setUseSecure (const bool ssl = true);
|
||||
void setUseSecure (const bool ssl = true);
|
||||
|
||||
protected:
|
||||
void incomingConnection (qintptr handle) Q_DECL_OVERRIDE;
|
||||
void incomingConnection (qintptr handle) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
bool m_useSsl;
|
||||
bool m_useSsl;
|
||||
};
|
||||
|
||||
class QtHttpServer : public QObject {
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtHttpServer (QObject * parent = Q_NULLPTR);
|
||||
explicit QtHttpServer (QObject * parent = Q_NULLPTR);
|
||||
|
||||
static const QString & HTTP_VERSION;
|
||||
static const QString & HTTP_VERSION;
|
||||
|
||||
typedef void (QSslSocket::* SslErrorSignal) (const QList<QSslError> &);
|
||||
typedef void (QSslSocket::* SslErrorSignal) (const QList<QSslError> &);
|
||||
|
||||
const QString & getServerName (void) const;
|
||||
const QString & getServerName (void) const;
|
||||
|
||||
quint16 getServerPort (void) const;
|
||||
QString getErrorString (void) const;
|
||||
|
||||
bool isListening(void) { return m_sockServer->isListening(); };
|
||||
quint16 getServerPort (void) const;
|
||||
QString getErrorString (void) const;
|
||||
const bool isListening() { return m_sockServer->isListening(); };
|
||||
|
||||
public slots:
|
||||
void start (quint16 port = 0);
|
||||
void stop (void);
|
||||
void setServerName (const QString & serverName);
|
||||
void setUseSecure (const bool ssl = true);
|
||||
void setPrivateKey (const QSslKey & key);
|
||||
void setCertificates (const QList<QSslCertificate> & certs);
|
||||
void start (quint16 port = 0);
|
||||
void stop (void);
|
||||
void setServerName (const QString & serverName);
|
||||
void setUseSecure (const bool ssl = true);
|
||||
void setPrivateKey (const QSslKey & key);
|
||||
void setCertificates (const QList<QSslCertificate> & certs);
|
||||
|
||||
signals:
|
||||
void started (quint16 port);
|
||||
void stopped (void);
|
||||
void error (const QString & msg);
|
||||
void clientConnected (const QString & guid);
|
||||
void clientDisconnected (const QString & guid);
|
||||
void requestNeedsReply (QtHttpRequest * request, QtHttpReply * reply);
|
||||
void started (quint16 port);
|
||||
void stopped (void);
|
||||
void error (const QString & msg);
|
||||
void clientConnected (const QString & guid);
|
||||
void clientDisconnected (const QString & guid);
|
||||
void requestNeedsReply (QtHttpRequest * request, QtHttpReply * reply);
|
||||
|
||||
private slots:
|
||||
void onClientConnected (void);
|
||||
void onClientDisconnected (void);
|
||||
void onClientSslEncrypted (void);
|
||||
void onClientSslPeerVerifyError (const QSslError & err);
|
||||
void onClientSslErrors (const QList<QSslError> & errors);
|
||||
void onClientSslModeChanged (QSslSocket::SslMode mode);
|
||||
void onClientConnected (void);
|
||||
void onClientDisconnected (void);
|
||||
void onClientSslEncrypted (void);
|
||||
void onClientSslPeerVerifyError (const QSslError & err);
|
||||
void onClientSslErrors (const QList<QSslError> & errors);
|
||||
void onClientSslModeChanged (QSslSocket::SslMode mode);
|
||||
|
||||
private:
|
||||
bool m_useSsl;
|
||||
QSslKey m_sslKey;
|
||||
QList<QSslCertificate> m_sslCerts;
|
||||
QString m_serverName;
|
||||
QtHttpServerWrapper * m_sockServer;
|
||||
QHash<QTcpSocket *, QtHttpClientWrapper *> m_socksClientsHash;
|
||||
bool m_useSsl;
|
||||
QSslKey m_sslKey;
|
||||
QList<QSslCertificate> m_sslCerts;
|
||||
QString m_serverName;
|
||||
NetOrigin* m_netOrigin;
|
||||
QtHttpServerWrapper * m_sockServer;
|
||||
QHash<QTcpSocket *, QtHttpClientWrapper *> m_socksClientsHash;
|
||||
};
|
||||
|
||||
#endif // QTHTTPSERVER_H
|
||||
|
||||
|
@@ -23,7 +23,7 @@ StaticFileServing::StaticFileServing (QObject * parent)
|
||||
|
||||
StaticFileServing::~StaticFileServing ()
|
||||
{
|
||||
delete _mimeDb;
|
||||
|
||||
}
|
||||
|
||||
void StaticFileServing::setBaseUrl(const QString& url)
|
||||
|
@@ -6,22 +6,23 @@
|
||||
|
||||
#include <api/JsonAPI.h>
|
||||
|
||||
WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent)
|
||||
WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, const bool& localConnection, QtHttpClientWrapper* parent)
|
||||
: QObject(parent)
|
||||
, _server(server)
|
||||
, _wrapper(parent)
|
||||
, _log(Logger::getInstance("HTTPJSONRPC"))
|
||||
{
|
||||
const QString client = request->getClientInfo().clientAddress.toString();
|
||||
_jsonAPI = new JsonAPI(client, _log, this, true);
|
||||
_jsonAPI = new JsonAPI(client, _log, localConnection, this, true);
|
||||
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebJsonRpc::handleCallback);
|
||||
}
|
||||
|
||||
void WebJsonRpc::handleMessage(QtHttpRequest* request)
|
||||
{
|
||||
QByteArray header = request->getHeader("Authorization");
|
||||
QByteArray data = request->getRawData();
|
||||
_unlocked = true;
|
||||
_jsonAPI->handleMessage(data);
|
||||
_jsonAPI->handleMessage(data,header);
|
||||
}
|
||||
|
||||
void WebJsonRpc::handleCallback(QJsonObject obj)
|
||||
|
@@ -1,9 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// utils includes
|
||||
#include <utils/Logger.h>
|
||||
|
||||
// qt includes
|
||||
#include <QJsonObject>
|
||||
|
||||
class QtHttpServer;
|
||||
@@ -14,7 +12,7 @@ class JsonAPI;
|
||||
class WebJsonRpc : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent);
|
||||
WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, const bool& localConnection, QtHttpClientWrapper* parent);
|
||||
|
||||
void handleMessage(QtHttpRequest* request);
|
||||
|
||||
|
@@ -13,11 +13,12 @@
|
||||
|
||||
|
||||
WebServer::WebServer(const QJsonDocument& config, QObject * parent)
|
||||
: QObject(parent)
|
||||
: QObject(parent)
|
||||
, _config(config)
|
||||
, _log(Logger::getInstance("WEBSERVER"))
|
||||
, _server()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WebServer::~WebServer()
|
||||
|
@@ -1,39 +1,37 @@
|
||||
#include "webserver/WebSocketClient.h"
|
||||
#include "WebSocketClient.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpHeader.h"
|
||||
|
||||
// hyperion includes
|
||||
#include <hyperion/Hyperion.h>
|
||||
|
||||
// JsonAPI includes
|
||||
#include <api/JsonAPI.h>
|
||||
|
||||
// qt includes
|
||||
#include <QTcpSocket>
|
||||
#include <QtEndian>
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
|
||||
WebSocketClient::WebSocketClient(QByteArray socketKey, QTcpSocket* sock, QObject* parent)
|
||||
WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, const bool& localConnection, QObject* parent)
|
||||
: QObject(parent)
|
||||
, _socket(sock)
|
||||
, _secWebSocketKey(socketKey)
|
||||
, _log(Logger::getInstance("WEBSOCKET"))
|
||||
// , _hyperion(Hyperion::getInstance())
|
||||
{
|
||||
// connect socket; disconnect handled from QtHttpServer
|
||||
connect(_socket, &QTcpSocket::readyRead , this, &WebSocketClient::handleWebSocketFrame);
|
||||
|
||||
const QString client = sock->peerAddress().toString();
|
||||
// QtHttpRequest contains all headers for handshake
|
||||
QByteArray secWebSocketKey = request->getHeader(QtHttpHeader::SecWebSocketKey);
|
||||
const QString client = request->getClientInfo().clientAddress.toString();
|
||||
|
||||
// Json processor
|
||||
_jsonAPI = new JsonAPI(client, _log, this);
|
||||
_jsonAPI = new JsonAPI(client, _log, localConnection, this);
|
||||
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebSocketClient::sendMessage);
|
||||
|
||||
Debug(_log, "New connection from %s", QSTRING_CSTR(client));
|
||||
|
||||
// do handshake
|
||||
_secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
QByteArray hash = QCryptographicHash::hash(_secWebSocketKey, QCryptographicHash::Sha1).toBase64();
|
||||
secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
QByteArray hash = QCryptographicHash::hash(secWebSocketKey, QCryptographicHash::Sha1).toBase64();
|
||||
|
||||
QString data
|
||||
= QString("HTTP/1.1 101 Switching Protocols\r\n")
|
||||
@@ -113,14 +111,14 @@ void WebSocketClient::handleWebSocketFrame(void)
|
||||
if (_wsh.fin)
|
||||
{
|
||||
_onContinuation = false;
|
||||
// if (_wsh.opCode == OPCODE::TEXT)
|
||||
// {
|
||||
if (_wsh.opCode == OPCODE::TEXT)
|
||||
{
|
||||
_jsonAPI->handleMessage(QString(_wsReceiveBuffer));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// handleBinaryMessage(_wsReceiveBuffer);
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
handleBinaryMessage(_wsReceiveBuffer);
|
||||
}
|
||||
_wsReceiveBuffer.clear();
|
||||
}
|
||||
}
|
||||
@@ -223,7 +221,6 @@ void WebSocketClient::sendClose(int status, QString reason)
|
||||
_socket->close();
|
||||
}
|
||||
|
||||
/*
|
||||
void WebSocketClient::handleBinaryMessage(QByteArray &data)
|
||||
{
|
||||
//uint8_t priority = data.at(0);
|
||||
@@ -242,10 +239,9 @@ void WebSocketClient::handleBinaryMessage(QByteArray &data)
|
||||
image.resize(width, height);
|
||||
|
||||
memcpy(image.memptr(), data.data()+4, imgSize);
|
||||
_hyperion->registerInput();
|
||||
_hyperion->setInputImage(priority, image, duration_s*1000);
|
||||
//_hyperion->registerInput();
|
||||
//_hyperion->setInputImage(priority, image, duration_s*1000);
|
||||
}
|
||||
*/
|
||||
|
||||
qint64 WebSocketClient::sendMessage(QJsonObject obj)
|
||||
{
|
||||
|
72
libsrc/webserver/WebSocketClient.h
Normal file
72
libsrc/webserver/WebSocketClient.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/Logger.h>
|
||||
#include "WebSocketUtils.h"
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class QtHttpRequest;
|
||||
class Hyperion;
|
||||
class JsonAPI;
|
||||
|
||||
class WebSocketClient : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, const bool& localConnection, QObject* parent);
|
||||
|
||||
struct WebSocketHeader
|
||||
{
|
||||
bool fin;
|
||||
quint8 opCode;
|
||||
bool masked;
|
||||
quint64 payloadLength;
|
||||
char key[4];
|
||||
};
|
||||
|
||||
private:
|
||||
QTcpSocket* _socket;
|
||||
Logger* _log;
|
||||
Hyperion* _hyperion;
|
||||
JsonAPI* _jsonAPI;
|
||||
|
||||
void getWsFrameHeader(WebSocketHeader* header);
|
||||
void sendClose(int status, QString reason = "");
|
||||
void handleBinaryMessage(QByteArray &data);
|
||||
qint64 sendMessage_Raw(const char* data, quint64 size);
|
||||
qint64 sendMessage_Raw(QByteArray &data);
|
||||
QByteArray makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame);
|
||||
|
||||
/// The buffer used for reading data from the socket
|
||||
QByteArray _receiveBuffer;
|
||||
|
||||
/// buffer for websockets multi frame receive
|
||||
QByteArray _wsReceiveBuffer;
|
||||
quint8 _maskKey[4];
|
||||
|
||||
bool _onContinuation = false;
|
||||
|
||||
// true when data is missing for parsing
|
||||
bool _notEnoughData = false;
|
||||
|
||||
// websocket header store
|
||||
WebSocketHeader _wsh;
|
||||
|
||||
// masks for fields in the basic header
|
||||
static uint8_t const BHB0_OPCODE = 0x0F;
|
||||
static uint8_t const BHB0_RSV3 = 0x10;
|
||||
static uint8_t const BHB0_RSV2 = 0x20;
|
||||
static uint8_t const BHB0_RSV1 = 0x40;
|
||||
static uint8_t const BHB0_FIN = 0x80;
|
||||
|
||||
static uint8_t const BHB1_PAYLOAD = 0x7F;
|
||||
static uint8_t const BHB1_MASK = 0x80;
|
||||
|
||||
static uint8_t const payload_size_code_16bit = 0x7E; // 126
|
||||
static uint8_t const payload_size_code_64bit = 0x7F; // 127
|
||||
|
||||
static const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message
|
||||
|
||||
private slots:
|
||||
void handleWebSocketFrame(void);
|
||||
qint64 sendMessage(QJsonObject obj);
|
||||
};
|
68
libsrc/webserver/WebSocketUtils.h
Normal file
68
libsrc/webserver/WebSocketUtils.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
/// Constants and utility functions related to WebSocket opcodes
|
||||
/**
|
||||
* WebSocket Opcodes are 4 bits. See RFC6455 section 5.2.
|
||||
*/
|
||||
namespace OPCODE {
|
||||
enum value {
|
||||
CONTINUATION = 0x0,
|
||||
TEXT = 0x1,
|
||||
BINARY = 0x2,
|
||||
RSV3 = 0x3,
|
||||
RSV4 = 0x4,
|
||||
RSV5 = 0x5,
|
||||
RSV6 = 0x6,
|
||||
RSV7 = 0x7,
|
||||
CLOSE = 0x8,
|
||||
PING = 0x9,
|
||||
PONG = 0xA,
|
||||
CONTROL_RSVB = 0xB,
|
||||
CONTROL_RSVC = 0xC,
|
||||
CONTROL_RSVD = 0xD,
|
||||
CONTROL_RSVE = 0xE,
|
||||
CONTROL_RSVF = 0xF
|
||||
};
|
||||
|
||||
/// Check if an opcode is reserved
|
||||
/**
|
||||
* @param v The opcode to test.
|
||||
* @return Whether or not the opcode is reserved.
|
||||
*/
|
||||
inline bool reserved(value v) {
|
||||
return (v >= RSV3 && v <= RSV7) || (v >= CONTROL_RSVB && v <= CONTROL_RSVF);
|
||||
}
|
||||
|
||||
/// Check if an opcode is invalid
|
||||
/**
|
||||
* Invalid opcodes are negative or require greater than 4 bits to store.
|
||||
*
|
||||
* @param v The opcode to test.
|
||||
* @return Whether or not the opcode is invalid.
|
||||
*/
|
||||
inline bool invalid(value v) {
|
||||
return (v > 0xF || v < 0);
|
||||
}
|
||||
|
||||
/// Check if an opcode is for a control frame
|
||||
/**
|
||||
* @param v The opcode to test.
|
||||
* @return Whether or not the opcode is a control opcode.
|
||||
*/
|
||||
inline bool is_control(value v) {
|
||||
return v >= 0x8;
|
||||
}
|
||||
}
|
||||
|
||||
namespace CLOSECODE {
|
||||
enum value {
|
||||
NORMAL = 1000,
|
||||
AWAY = 1001,
|
||||
TERM = 1002,
|
||||
INV_TYPE = 1003,
|
||||
INV_DATA = 1007,
|
||||
VIOLATION = 1008,
|
||||
BIG_MSG = 1009,
|
||||
UNEXPECTED= 1011
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user