diff --git a/aqhome/http/0BUILD b/aqhome/http/0BUILD index a185b6f..e998d7c 100644 --- a/aqhome/http/0BUILD +++ b/aqhome/http/0BUILD @@ -47,12 +47,16 @@ endpoint_http.h httpservice.h + httpservice_conf.h + httpservice_http.h + httprequest.h endpoint_http_p.h httpservice_p.h + httprequest_p.h @@ -61,6 +65,9 @@ endpoint_http.c httpservice.c + httpservice_conf.c + httpservice_http.c + httprequest.c diff --git a/aqhome/http/httprequest.c b/aqhome/http/httprequest.c new file mode 100644 index 0000000..bedec70 --- /dev/null +++ b/aqhome/http/httprequest.c @@ -0,0 +1,605 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 Martin Preuss, all rights reserved. + * + * The license for this file can be found in the file COPYING which you + * should have received along with this file. + ****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif + + +#include "./httprequest_p.h" + +#include +#include + + +#define AQH_HTTP_REQUEST_SESSIONCOOKIE "sessionid" + + +GWEN_TREE2_FUNCTIONS(AQH_HTTP_REQUEST, AQH_HttpRequest); + + + +/* ------------------------------------------------------------------------------------------------ + * forward declarations + * ------------------------------------------------------------------------------------------------ + */ + +static int _getParsedDbInfo(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg); +static int _inspectReceivedMessage(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg); +static int _inspectMsgCommand(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg); +static void _inspectMsgHeader(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg); +static int _inspectMsgBody(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg); +static void _setModuleNameFromUrl(AQH_HTTP_REQUEST *rq); +static GWEN_DB_NODE *_extractCookies(GWEN_DB_NODE *dbHeader); +static void _setCookieValue(GWEN_DB_NODE *dbCookies, + const char *nameStart, int nameLen, + const char *valueStart, int valueLen, + uint32_t dbFlags); +static int _parsePostBody(const char *s, int contentLength, GWEN_DB_NODE *dbBody); + + + +/* ------------------------------------------------------------------------------------------------ + * implementations + * ------------------------------------------------------------------------------------------------ + */ + +AQH_HTTP_REQUEST *AQH_HttpRequest_new(GWEN_MSG_ENDPOINT *endpoint, const GWEN_MSG *receivedMsg) +{ + AQH_HTTP_REQUEST *rq; + int rv; + + GWEN_NEW_OBJECT(AQH_HTTP_REQUEST, rq); + GWEN_TREE2_INIT(AQH_HTTP_REQUEST, rq, AQH_HttpRequest); + + rq->endpoint=endpoint; + rq->receivedMsg=receivedMsg; + + rv=_inspectReceivedMessage(rq, receivedMsg); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + AQH_HttpRequest_free(rq); + return NULL; + } + return rq; +} + + + +void AQH_HttpRequest_free(AQH_HTTP_REQUEST *rq) +{ + if (rq) { + GWEN_TREE2_FINI(AQH_HTTP_REQUEST, rq, AQH_HttpRequest); + free(rq->sessionId); + free(rq->moduleName); + free(rq->responseText); + GWEN_Msg_free(rq->responseMsg); + GWEN_Url_free(rq->url); + GWEN_DB_Group_free(rq->dbPostBody); + + GWEN_FREE_OBJECT(rq); + } +} + + + +GWEN_MSG_ENDPOINT *AQH_HttpRequest_GetEndpoint(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->endpoint):NULL; +} + + + +const GWEN_MSG *AQH_HttpRequest_GetReceivedMsg(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->receivedMsg):NULL; +} + + + +const char *AQH_HttpRequest_GetCommand(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->command):NULL; +} + + + +const char *AQH_HttpRequest_GetProtocol(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->protocol):NULL; +} + + + +GWEN_URL *AQH_HttpRequest_GetUrl(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->url):NULL; +} + + + +void AQH_HttpRequest_SetUrl(AQH_HTTP_REQUEST *rq, GWEN_URL *url) +{ + if (rq) { + GWEN_Url_free(rq->url); + rq->url=url; + } +} + + + +GWEN_DB_NODE *AQH_HttpRequest_GetDbCommand(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->dbCommand):NULL; +} + + + +GWEN_DB_NODE *AQH_HttpRequest_GetDbHeader(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->dbHeader):NULL; +} + + + +GWEN_DB_NODE *AQH_HttpRequest_GetDbCookies(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->dbCookies):NULL; +} + + + +const char *AQH_HttpRequest_GetSessionId(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->sessionId):NULL; +} + + + +void AQH_HttpRequest_SetSessionId(AQH_HTTP_REQUEST *rq, const char *s) +{ + if (rq) { + free(rq->sessionId); + rq->sessionId=s?strdup(s):NULL; + } +} + + + +const char *AQH_HttpRequest_GetModuleName(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->moduleName):NULL; +} + + + +void AQH_HttpRequest_SetModuleName(AQH_HTTP_REQUEST *rq, const char *s) +{ + if (rq) { + free(rq->moduleName); + rq->moduleName=s?strdup(s):NULL; + } +} + + + +AQH_SESSION *AQH_HttpRequest_GetSession(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->session):NULL; +} + + + +void AQH_HttpRequest_SetSession(AQH_HTTP_REQUEST *rq, AQH_SESSION *session) +{ + if (rq) + rq->session=session; +} + + + +AQH_MODULE *AQH_HttpRequest_GetModule(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->module):NULL; +} + + + +void AQH_HttpRequest_SetModule(AQH_HTTP_REQUEST *rq, AQH_MODULE *m) +{ + if (rq) + rq->module=m; +} + + + +uint32_t AQH_HttpRequest_GetModulePerms(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->modulePerms):0; +} + + + +void AQH_HttpRequest_SetModulePerms(AQH_HTTP_REQUEST *rq, uint32_t i) +{ + if (rq) + rq->modulePerms=i; +} + + + +int AQH_HttpRequest_GetResponseCode(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->responseCode):0; +} + + + +void AQH_HttpRequest_SetResponseCode(AQH_HTTP_REQUEST *rq, int i) +{ + if (rq) + rq->responseCode=i; +} + + + +const char *AQH_HttpRequest_GetResponseText(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->responseText):NULL; +} + + + +void AQH_HttpRequest_SetResponseText(AQH_HTTP_REQUEST *rq, const char *s) +{ + if (rq) { + free(rq->responseText); + rq->responseText=s?strdup(s):NULL; + } +} + + + +GWEN_MSG *AQH_HttpRequest_GetResponseMsg(const AQH_HTTP_REQUEST *rq) +{ + return rq?(rq->responseMsg):NULL; +} + + + +GWEN_MSG *AQH_HttpRequest_TakeResponseMsg(AQH_HTTP_REQUEST *rq) +{ + if (rq) { + GWEN_MSG *msg; + + msg=rq->responseMsg; + rq->responseMsg=NULL; + return msg; + } + return NULL; +} + + + +void AQH_HttpRequest_SetResponseMsg(AQH_HTTP_REQUEST *rq, GWEN_MSG *msg) +{ + if (rq && msg) { + GWEN_Msg_free(rq->responseMsg); + rq->responseMsg=msg; + } +} + + + +int _inspectReceivedMessage(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg) +{ + int rv; + + rv=_getParsedDbInfo(rq, msg); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + rv=_inspectMsgCommand(rq, msg); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + _inspectMsgHeader(rq, msg); + + rv=_inspectMsgBody(rq, msg); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + return 0; +} + + + +int _getParsedDbInfo(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg) +{ + GWEN_DB_NODE *dbParsedInfo; + GWEN_DB_NODE *db; + + dbParsedInfo=GWEN_Msg_GetDbParsedInfo(msg); + if (dbParsedInfo==NULL) { + DBG_INFO(AQH_LOGDOMAIN, "No parsed info db, msg probably not generated by HTTP endpoint module"); + return GWEN_ERROR_GENERIC; + } + + db=GWEN_DB_GetGroup(dbParsedInfo, GWEN_PATH_FLAGS_PATHMUSTEXIST, "command"); + if (db==NULL) { + DBG_INFO(AQH_LOGDOMAIN, "No parsed command db, msg probably not generated by HTTP endpoint module"); + return GWEN_ERROR_GENERIC; + } + rq->dbCommand=db; + + db=GWEN_DB_GetGroup(dbParsedInfo, GWEN_PATH_FLAGS_PATHMUSTEXIST, "header"); + if (db==NULL) { + DBG_INFO(AQH_LOGDOMAIN, "No parsed header db, msg probably not generated by HTTP endpoint module or no header recvd, ignoring"); + } + rq->dbHeader=db; + + return 0; +} + + + +int _inspectMsgCommand(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg) +{ + const char *s; + + s=GWEN_DB_GetCharValue(rq->dbCommand, "command", 0, NULL); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "No command in request"); + return GWEN_ERROR_GENERIC; + } + rq->command=s; + + s=GWEN_DB_GetCharValue(rq->dbCommand, "protocol", 0, NULL); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "No protocol in request"); + return GWEN_ERROR_GENERIC; + } + rq->protocol=s; + + s=GWEN_DB_GetCharValue(rq->dbCommand, "url", 0, NULL); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "No url in request"); + return GWEN_ERROR_GENERIC; + } + + rq->url=GWEN_Url_fromCommandString(s); + if (rq->url==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid URL [%s]", s); + return GWEN_ERROR_GENERIC; + } + + _setModuleNameFromUrl(rq); + + return 0; +} + + + +void _inspectMsgHeader(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg) +{ + if (rq->dbHeader) + rq->dbCookies=_extractCookies(rq->dbHeader); + if (rq->dbCookies) { + const char *s; + + s=GWEN_DB_GetCharValue(rq->dbCookies, AQH_HTTP_REQUEST_SESSIONCOOKIE, 0, NULL); + if (s && *s) + AQH_HttpRequest_SetSessionId(rq, s); + } + +} + + + +int _inspectMsgBody(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg) +{ + int rv; + + rq->recvdBodySize=GWEN_Msg_GetParsedPayloadSize(msg); + if (rq->recvdBodySize>0) + rq->recvdBodyPtr=GWEN_Msg_GetConstBuffer(msg)+GWEN_Msg_GetParsedPayloadOffset(msg); + + if (rq->dbHeader && rq->recvdBodySize>0 && strcasecmp(rq->command, "POST")==0) { + const char *s; + + /* check whether we need to parse POST body */ + s=GWEN_DB_GetCharValue(rq->dbCommand, "content-type", 0, NULL); + if (s && strcasecmp(s, "application/x-www-form-urlencoded")==0) { + rq->dbPostBody=GWEN_DB_Group_new("post"); + rv=_parsePostBody((const char*) (rq->recvdBodyPtr), rq->recvdBodySize, rq->dbPostBody); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + } + } + + return 0; +} + + + +GWEN_DB_NODE *_extractCookies(GWEN_DB_NODE *dbHeader) +{ + int i; + GWEN_DB_NODE *dbCookies; + + dbCookies=GWEN_DB_GetGroup(dbHeader, 0, "cookies"); + for (i=0; ; i++) { + const char *sCookie; + + sCookie=GWEN_DB_GetCharValue(dbHeader, "Cookie", i, NULL); + if (sCookie && *sCookie) { + const char *s; + + s=sCookie; + while(*s) { + while(*s && *s<33) + s++; + + if (*s) { + const char *nameStart; + int nameLen; + + nameStart=s++; + while(*s && *s!='=') + s++; + if (*s=='=') { + nameLen=s-nameStart; + s++; + while(*s && *s<33) + s++; + if (*s) { + const char *valueStart; + int valueLen; + + valueStart=s; + while(*s && *s!=';') + s++; + valueLen=s-valueStart; + + _setCookieValue(dbCookies, nameStart, nameLen, valueStart, valueLen, 0); + + if (*s) + s++; + } + } + } + } /* while(*s) */ + } /* if (sCookie && *sCookie) */ + else + break; + } /* for */ + return dbCookies; +} + + + +void _setCookieValue(GWEN_DB_NODE *dbCookies, const char *nameStart, int nameLen, const char *valueStart, int valueLen, uint32_t dbFlags) +{ + if (nameLen && valueLen) { + int i; + char *sName; + char *sValue; + + for (i=nameLen-1; i>0; i--) { + if (nameStart[i]>32) + break; + } + sName=GWEN_Text_strndup(nameStart, i+1); + + for (i=valueLen-1; i>0; i--) { + if (valueStart[i]>32) + break; + } + sValue=GWEN_Text_strndup(valueStart, i+1); + + DBG_INFO(AQH_LOGDOMAIN, "Received Cookie: [%s]=[%s]", sName, sValue); + GWEN_DB_SetCharValue(dbCookies, dbFlags, sName, sValue); + free(sValue); + free(sName); + } +} + + + +void _setModuleNameFromUrl(AQH_HTTP_REQUEST *rq) +{ + if (rq->url) { + const char *sPath; + + sPath=GWEN_Url_GetPath(rq->url); + if (sPath && *sPath) { + if (*sPath=='/') + sPath++; + while(*sPath && *sPath<33) + sPath++; + if (*sPath) { + const char *s; + + s=sPath; + while(*s && *s!='/') + s++; + if (s>sPath) { + free(rq->moduleName); + rq->moduleName=GWEN_Text_strndup(sPath, s-sPath); + } + } + } + } +} + + + +int _parsePostBody(const char *s, int contentLength, GWEN_DB_NODE *dbBody) +{ + while(contentLength>0) { + const char *sNameStart; + int nameLen; + + while((contentLength>0) && (*s<33)) { + contentLength--; + s++; + } + sNameStart=s; + while((contentLength>0) && (*s!='=') && (*s!='&')) { + contentLength--; + s++; + } + nameLen=s-sNameStart; + + if ((contentLength>0) && (*s=='=')) { + const char *sValueStart; + int valueLen; + + s++; + while((contentLength>0) && (*s<33)) { + s++; + contentLength--; + } + sValueStart=s; + while((contentLength>0) && (*s!='&')) { + contentLength--; + s++; + } + valueLen=s-sValueStart; + + if (nameLen && valueLen) { + char sNameBuf[32]; + char sValueBuf[64]; + + if (GWEN_Text_UnescapeN(sNameStart, nameLen, sNameBuf, sizeof(sNameBuf))!=NULL && + GWEN_Text_UnescapeN(sValueStart, valueLen, sValueBuf, sizeof(sValueBuf))!=NULL) + GWEN_DB_SetCharValue(dbBody, 0, sNameBuf, sValueBuf); + else { + DBG_ERROR(NULL, "Either name or value invalid in body, aborting"); + return GWEN_ERROR_BAD_DATA; + } + } + if ((contentLength>0) && (*s=='&')) + s++; + } + } /* while */ + return 0; +} + + + + + diff --git a/aqhome/http/httprequest.h b/aqhome/http/httprequest.h new file mode 100644 index 0000000..208f564 --- /dev/null +++ b/aqhome/http/httprequest.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 Martin Preuss, all rights reserved. + * + * The license for this file can be found in the file COPYING which you + * should have received along with this file. + ****************************************************************************/ + +#ifndef AQH_HTTP_REQUEST_H +#define AQH_HTTP_REQUEST_H + + +#include "aqhome/service/session.h" +#include "aqhome/service/module.h" + +#include +#include +#include +#include + + + +typedef struct AQH_HTTP_REQUEST AQH_HTTP_REQUEST; +GWEN_TREE2_FUNCTION_LIB_DEFS(AQH_HTTP_REQUEST, AQH_HttpRequest, AQHOME_API); + +AQHOME_API AQH_HTTP_REQUEST *AQH_HttpRequest_new(GWEN_MSG_ENDPOINT *endpoint, const GWEN_MSG *receivedMsg); +AQHOME_API void AQH_HttpRequest_free(AQH_HTTP_REQUEST *rq); + +AQHOME_API GWEN_MSG_ENDPOINT *AQH_HttpRequest_GetEndpoint(const AQH_HTTP_REQUEST *rq); + +AQHOME_API const GWEN_MSG *AQH_HttpRequest_GetReceivedMsg(const AQH_HTTP_REQUEST *rq); + +AQHOME_API const char *AQH_HttpRequest_GetCommand(const AQH_HTTP_REQUEST *rq); + +AQHOME_API const char *AQH_HttpRequest_GetProtocol(const AQH_HTTP_REQUEST *rq); + +AQHOME_API GWEN_URL *AQH_HttpRequest_GetUrl(const AQH_HTTP_REQUEST *rq); +AQHOME_API void AQH_HttpRequest_SetUrl(AQH_HTTP_REQUEST *rq, GWEN_URL *url); + +AQHOME_API GWEN_DB_NODE *AQH_HttpRequest_GetDbCommand(const AQH_HTTP_REQUEST *rq); +AQHOME_API GWEN_DB_NODE *AQH_HttpRequest_GetDbHeader(const AQH_HTTP_REQUEST *rq); +AQHOME_API GWEN_DB_NODE *AQH_HttpRequest_GetDbCookies(const AQH_HTTP_REQUEST *rq); + + +AQHOME_API const char *AQH_HttpRequest_GetSessionId(const AQH_HTTP_REQUEST *rq); +AQHOME_API void AQH_HttpRequest_SetSessionId(AQH_HTTP_REQUEST *rq, const char *s); + +AQHOME_API const char *AQH_HttpRequest_GetModuleName(const AQH_HTTP_REQUEST *rq); +AQHOME_API void AQH_HttpRequest_SetModuleName(AQH_HTTP_REQUEST *rq, const char *s); + +AQHOME_API AQH_SESSION *AQH_HttpRequest_GetSession(const AQH_HTTP_REQUEST *rq); +AQHOME_API void AQH_HttpRequest_SetSession(AQH_HTTP_REQUEST *rq, AQH_SESSION *session); + +AQHOME_API AQH_MODULE *AQH_HttpRequest_GetModule(const AQH_HTTP_REQUEST *rq); +AQHOME_API void AQH_HttpRequest_SetModule(AQH_HTTP_REQUEST *rq, AQH_MODULE *m); + +AQHOME_API uint32_t AQH_HttpRequest_GetModulePerms(const AQH_HTTP_REQUEST *rq); +AQHOME_API void AQH_HttpRequest_SetModulePerms(AQH_HTTP_REQUEST *rq, uint32_t i); + + +AQHOME_API int AQH_HttpRequest_GetResponseCode(const AQH_HTTP_REQUEST *rq); +AQHOME_API void AQH_HttpRequest_SetResponseCode(AQH_HTTP_REQUEST *rq, int i); + +AQHOME_API const char *AQH_HttpRequest_GetResponseText(const AQH_HTTP_REQUEST *rq); +AQHOME_API void AQH_HttpRequest_SetResponseText(AQH_HTTP_REQUEST *rq, const char *s); + +AQHOME_API GWEN_MSG *AQH_HttpRequest_GetResponseMsg(const AQH_HTTP_REQUEST *rq); +AQHOME_API GWEN_MSG *AQH_HttpRequest_TakeResponseMsg(AQH_HTTP_REQUEST *rq); +AQHOME_API void AQH_HttpRequest_SetResponseMsg(AQH_HTTP_REQUEST *rq, GWEN_MSG *msg); + + + +#endif + + + diff --git a/aqhome/http/httprequest_p.h b/aqhome/http/httprequest_p.h new file mode 100644 index 0000000..6d73f59 --- /dev/null +++ b/aqhome/http/httprequest_p.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 Martin Preuss, all rights reserved. + * + * The license for this file can be found in the file COPYING which you + * should have received along with this file. + ****************************************************************************/ + +#ifndef AQH_HTTP_REQUEST_P_H +#define AQH_HTTP_REQUEST_P_H + + +#include "aqhome/http/httprequest.h" + + + +typedef struct AQH_HTTP_REQUEST AQH_HTTP_REQUEST; +struct AQH_HTTP_REQUEST { + GWEN_TREE2_ELEMENT(AQH_HTTP_REQUEST) + + GWEN_MSG_ENDPOINT *endpoint; + + /* received stuff */ + const GWEN_MSG *receivedMsg; /* don't free */ + + const char *command; /* don't free */ + const char *protocol; /* don't free */ + GWEN_URL *url; + + GWEN_DB_NODE *dbCommand; /* don't free */ + GWEN_DB_NODE *dbHeader; /* don't free */ + GWEN_DB_NODE *dbCookies; /* don't free */ + + uint32_t recvdBodySize; + const uint8_t *recvdBodyPtr; + + /* derived stuff */ + char *sessionId; + char *moduleName; + + AQH_SESSION *session; /* don't free */ + AQH_MODULE *module; /* don't free */ + uint32_t modulePerms; + + GWEN_DB_NODE *dbPostBody; + + + /* response stuff */ + int responseCode; + char *responseText; + GWEN_MSG *responseMsg; +}; + + + +#endif + + + diff --git a/aqhome/http/httpservice.c b/aqhome/http/httpservice.c index 30e9c9a..9c7d738 100644 --- a/aqhome/http/httpservice.c +++ b/aqhome/http/httpservice.c @@ -25,9 +25,6 @@ GWEN_INHERIT(AQH_SERVICE, AQH_HTTP_SERVICE) static void GWENHYWFAR_CB _freeData(void *bp, void *p); -static int _parsePostBody(const char *s, int contentLength, GWEN_DB_NODE *dbBody); - - @@ -37,6 +34,10 @@ void AQH_HttpService_Extend(AQH_SERVICE *sv) GWEN_NEW_OBJECT(AQH_HTTP_SERVICE, xsv); GWEN_INHERIT_SETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv, xsv, _freeData); + + xsv->moduleMutex=GWEN_Mutex_new(); + xsv->userMutex=GWEN_Mutex_new(); + xsv->sessionMutex=GWEN_Mutex_new(); } @@ -46,6 +47,12 @@ void _freeData(void *bp, void *p) AQH_HTTP_SERVICE *xsv; xsv=(AQH_HTTP_SERVICE*) p; + + GWEN_Mutex_free(xsv->moduleMutex); + GWEN_Mutex_free(xsv->userMutex); + GWEN_Mutex_free(xsv->sessionMutex); + + free(xsv->configFolder); free(xsv->sourceFolder); free(xsv->siteHeader); free(xsv->siteFooter); @@ -54,6 +61,54 @@ void _freeData(void *bp, void *p) +AQH_HTTP_SERVICE_HANDLEREQUEST_FN AQH_HttpService_SetHandleRequestFn(AQH_SERVICE *sv, AQH_HTTP_SERVICE_HANDLEREQUEST_FN fn) +{ + if (sv) { + AQH_HTTP_SERVICE *xsv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv) { + AQH_HTTP_SERVICE_HANDLEREQUEST_FN oldFn; + + oldFn=xsv->handleRequestFn; + xsv->handleRequestFn=fn; + return oldFn; + } + } + return NULL; +} + + + +const char *AQH_HttpService_GetConfigFolder(const AQH_SERVICE *sv) +{ + if (sv) { + AQH_HTTP_SERVICE *xsv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv) + return xsv->configFolder; + } + return NULL; +} + + + +void AQH_HttpService_SetConfigFolder(AQH_SERVICE *sv, const char *s) +{ + if (sv) { + AQH_HTTP_SERVICE *xsv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv) { + free(xsv->configFolder); + xsv->configFolder=s?strdup(s):NULL; + } + } +} + + + const char *AQH_HttpService_GetSourceFolder(const AQH_SERVICE *sv) { if (sv) { @@ -83,6 +138,64 @@ void AQH_HttpService_SetSourceFolder(AQH_SERVICE *sv, const char *s) +uint32_t AQH_HttpService_GetNextModuleId(AQH_SERVICE *sv) +{ + if (sv) { + AQH_HTTP_SERVICE *xsv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv) { + return ++(xsv->lastModuleId); + } + } + return 0; +} + + + +uint32_t AQH_HttpService_GetNextUserId(AQH_SERVICE *sv) +{ + if (sv) { + AQH_HTTP_SERVICE *xsv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv) { + return ++(xsv->lastUserId); + } + } + return 0; +} + + + +int AQH_HttpService_GetMaxSessionAgeInSecs(const AQH_SERVICE *sv) +{ + if (sv) { + AQH_HTTP_SERVICE *xsv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv) { + return xsv->maxSessionAgeInSecs; + } + } + return 0; +} + + + +void AQH_HttpService_SetMaxSessionAgeInSecs(AQH_SERVICE *sv, int i) +{ + if (sv) { + AQH_HTTP_SERVICE *xsv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv) + xsv->maxSessionAgeInSecs=i; + } +} + + + const char *AQH_HttpService_GetSiteHeader(const AQH_SERVICE *sv) { if (sv) { @@ -141,183 +254,3 @@ void AQH_HttpService_SetSiteFooter(AQH_SERVICE *sv, const char *s) -int AQH_HttpService_AddFile(AQH_SERVICE *sv, const char *fname, GWEN_BUFFER *buf) -{ - if (fname && *fname) { - AQH_HTTP_SERVICE *xsv; - - xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); - if (xsv) { - int rv; - GWEN_BUFFER *fnbuf; - - fnbuf=GWEN_Buffer_new(0, 256, 0, 1); - if (xsv->sourceFolder) { - GWEN_Buffer_AppendString(fnbuf, xsv->sourceFolder); - GWEN_Buffer_AppendString(fnbuf, GWEN_DIR_SEPARATOR_S); - } - GWEN_Buffer_AppendString(fnbuf, fname); - - rv=GWEN_SyncIo_Helper_ReadFile(GWEN_Buffer_GetStart(fnbuf), buf); - if (rv<0) { - DBG_ERROR(AQH_LOGDOMAIN, "Error reading file \"%s\": %d", GWEN_Buffer_GetStart(fnbuf), rv); - GWEN_Buffer_free(fnbuf); - return rv; - } - GWEN_Buffer_free(fnbuf); - } - else { - DBG_ERROR(AQH_LOGDOMAIN, "No http service object"); - } - } - return 0; -} - - - -void AQH_HttpService_AddStatusLine(AQH_SERVICE *sv, int code, const char *msg, const char *proto, GWEN_BUFFER *buf) -{ - GWEN_Buffer_AppendArgs(buf, "%s %03d %s \r\n", proto?proto:"HTTP/1.1", code, msg?msg:""); -} - - - -void AQH_HttpService_AddHeader(AQH_SERVICE *sv, GWEN_DB_NODE *dbHeader, GWEN_BUFFER *buf) -{ - GWEN_DB_NODE *dbVar; - - dbVar=GWEN_DB_GetFirstVar(dbHeader); - while (dbVar) { - GWEN_DB_NODE *dbVal; - - /* only handle first value */ - dbVal=GWEN_DB_GetFirstValue(dbVar); - if (dbVal) { - const char *sVar; - - sVar=GWEN_DB_VariableName(dbVar); - if (sVar && *sVar) { - GWEN_DB_NODE_TYPE vtype; - - vtype=GWEN_DB_GetValueType(dbVal); - if (vtype==GWEN_DB_NodeType_ValueChar) { - const char *sValue; - - sValue=GWEN_DB_GetCharValueFromNode(dbVal); - if (sValue) - GWEN_Buffer_AppendArgs(buf, "%s:%s\r\n", sVar, sValue); - } /* if char */ - else if (vtype==GWEN_DB_NodeType_ValueInt) { - int i; - - i=GWEN_DB_GetIntValueFromNode(dbVal); - if (i!=-1 || strcasecmp(sVar, "Content-Length")==0) - GWEN_Buffer_AppendArgs(buf, "%s:%d\r\n", sVar, i); - } /* if int */ - else { - DBG_INFO(AQH_LOGDOMAIN, "Variable type %d of var [%s] not supported, ignoring", vtype, sVar); - } - } /* if sVar */ - } - dbVar=GWEN_DB_GetNextVar(dbVar); - } - - /* finalize header */ - GWEN_Buffer_AppendString(buf, "\r\n"); -} - - - -int AQH_HttpService_ParsePostBody(AQH_SERVICE *sv, const GWEN_MSG *msgReceived, GWEN_DB_NODE *dbBody) -{ - GWEN_DB_NODE *dbParsedInfo; - GWEN_DB_NODE *dbHeader; - const char *contentType; - int contentLength; - const char *s; - - dbParsedInfo=GWEN_Msg_GetDbParsedInfo(msgReceived); - if (dbParsedInfo==NULL) { - DBG_ERROR(AQH_LOGDOMAIN, "No parsed info in received message"); - return GWEN_ERROR_BAD_DATA; - } - dbHeader=GWEN_DB_GetGroup(dbParsedInfo, GWEN_PATH_FLAGS_PATHMUSTEXIST, "header"); - if (dbHeader==NULL) { - DBG_ERROR(AQH_LOGDOMAIN, "No http header group in received message"); - return GWEN_ERROR_BAD_DATA; - } - - contentType=GWEN_DB_GetCharValue(dbHeader, "content-type", 0, NULL); - if (!(contentType && *contentType && strcasecmp(contentType, "application/x-www-form-urlencoded")==0)) { - DBG_ERROR(NULL, "Invalid or missing content type [%s]", contentType?contentType:""); - return GWEN_ERROR_BAD_DATA; - } - - contentLength=GWEN_Msg_GetParsedPayloadSize(msgReceived); - if (contentLength<1) { - DBG_ERROR(NULL, "Empty message body"); - return 0; - } - - if (contentLength<1) { - DBG_ERROR(NULL, "Empty message body"); - return 0; - } - s=(const char*)(GWEN_Msg_GetConstBuffer(msgReceived)+GWEN_Msg_GetParsedPayloadOffset(msgReceived)); - return _parsePostBody(s, contentLength, dbBody); -} - - - -int _parsePostBody(const char *s, int contentLength, GWEN_DB_NODE *dbBody) -{ - while(contentLength>0) { - const char *sNameStart; - int nameLen; - - while((contentLength>0) && (*s<33)) { - contentLength--; - s++; - } - sNameStart=s; - while((contentLength>0) && (*s!='=') && (*s!='&')) { - contentLength--; - s++; - } - nameLen=s-sNameStart; - if ((contentLength>0) && (*s=='=')) { - const char *sValueStart; - int valueLen; - - s++; - while((contentLength>0) && (*s<33)) { - s++; - contentLength--; - } - sValueStart=s; - while((contentLength>0) && (*s!='&')) { - contentLength--; - s++; - } - valueLen=s-sValueStart; - if (nameLen && valueLen) { - char sNameBuf[32]; - char sValueBuf[64]; - - if (GWEN_Text_UnescapeN(sNameStart, nameLen, sNameBuf, sizeof(sNameBuf))!=NULL && - GWEN_Text_UnescapeN(sValueStart, valueLen, sValueBuf, sizeof(sValueBuf))!=NULL) - GWEN_DB_SetCharValue(dbBody, 0, sNameBuf, sValueBuf); - else { - DBG_ERROR(NULL, "Either name or value invalid in body, aborting"); - return GWEN_ERROR_BAD_DATA; - } - } - if ((contentLength>0) && (*s=='&')) - s++; - } - } /* while */ - return 0; -} - - - diff --git a/aqhome/http/httpservice.h b/aqhome/http/httpservice.h index 54730ba..5400161 100644 --- a/aqhome/http/httpservice.h +++ b/aqhome/http/httpservice.h @@ -13,33 +13,45 @@ #include "aqhome/service/service.h" +#include "aqhome/http/httprequest.h" + +#include #include #include -#include +#include +#include -void AQH_HttpService_Extend(AQH_SERVICE *sv); +typedef int (*AQH_HTTP_SERVICE_HANDLEREQUEST_FN)(AQH_SERVICE *sv, AQH_HTTP_REQUEST *request); -const char *AQH_HttpService_GetSourceFolder(const AQH_SERVICE *sv); -void AQH_HttpService_SetSourceFolder(AQH_SERVICE *sv, const char *s); - -const char *AQH_HttpService_GetSiteHeader(const AQH_SERVICE *sv); -void AQH_HttpService_SetSiteHeader(AQH_SERVICE *sv, const char *s); +AQHOME_API void AQH_HttpService_Extend(AQH_SERVICE *sv); -const char *AQH_HttpService_GetSiteFooter(const AQH_SERVICE *sv); -void AQH_HttpService_SetSiteFooter(AQH_SERVICE *sv, const char *s); +AQHOME_API const char *AQH_HttpService_GetConfigFolder(const AQH_SERVICE *sv); +AQHOME_API void AQH_HttpService_SetConfigFolder(AQH_SERVICE *sv, const char *s); -int AQH_HttpService_AddFile(AQH_SERVICE *sv, const char *fname, GWEN_BUFFER *buf); -void AQH_HttpService_AddStatusLine(AQH_SERVICE *sv, int code, const char *msg, const char *proto, GWEN_BUFFER *buf); -void AQH_HttpService_AddHeader(AQH_SERVICE *sv, GWEN_DB_NODE *dbHeader, GWEN_BUFFER *buf); +AQHOME_API const char *AQH_HttpService_GetSourceFolder(const AQH_SERVICE *sv); +AQHOME_API void AQH_HttpService_SetSourceFolder(AQH_SERVICE *sv, const char *s); -int AQH_HttpService_ParsePostBody(AQH_SERVICE *sv, const GWEN_MSG *msgReceived, GWEN_DB_NODE *dbBody); +AQHOME_API const char *AQH_HttpService_GetSiteHeader(const AQH_SERVICE *sv); +AQHOME_API void AQH_HttpService_SetSiteHeader(AQH_SERVICE *sv, const char *s); +AQHOME_API const char *AQH_HttpService_GetSiteFooter(const AQH_SERVICE *sv); +AQHOME_API void AQH_HttpService_SetSiteFooter(AQH_SERVICE *sv, const char *s); + + +AQHOME_API uint32_t AQH_HttpService_GetNextModuleId(AQH_SERVICE *sv); +AQHOME_API uint32_t AQH_HttpService_GetNextUserId(AQH_SERVICE *sv); + +AQHOME_API int AQH_HttpService_GetMaxSessionAgeInSecs(const AQH_SERVICE *sv); +AQHOME_API void AQH_HttpService_SetMaxSessionAgeInSecs(AQH_SERVICE *sv, int i); + + +AQHOME_API AQH_HTTP_SERVICE_HANDLEREQUEST_FN AQH_HttpService_SetHandleRequestFn(AQH_SERVICE *sv, AQH_HTTP_SERVICE_HANDLEREQUEST_FN fn); #endif diff --git a/aqhome/http/httpservice_conf.c b/aqhome/http/httpservice_conf.c new file mode 100644 index 0000000..0f0f70f --- /dev/null +++ b/aqhome/http/httpservice_conf.c @@ -0,0 +1,982 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 Martin Preuss, all rights reserved. + * + * The license for this file can be found in the file COPYING which you + * should have received along with this file. + ****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif + + +#include "aqhome/http/httpservice_conf.h" +#include "aqhome/http/httpservice_p.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + + + +#define AQH_HTTP_SERVICE_FILE_CONFIG "service.conf" +#define AQH_HTTP_SERVICE_DIR_MODS "modules" +#define AQH_HTTP_SERVICE_DIR_USERS "users" +#define AQH_HTTP_SERVICE_DIR_SESSIONS "sessions" + + +#ifdef GWEN_INHERIT_REF + GWEN_INHERIT_REF(AQH_SERVICE, AQH_HTTP_SERVICE) +#else + extern uint32_t AQH_HTTP_SERVICE_ID; +#endif + + + +/* ------------------------------------------------------------------------------------------------ + * forward declarations + * ------------------------------------------------------------------------------------------------ + */ + +static void _calculateUserPerms(AQH_SERVICE *sv, AQH_USER *user); +static void _calculateModuleUserPerms(AQH_SERVICE *sv, AQH_MODULE_PERMS *p); +static AQH_MODULE *_ensureModule(AQH_SERVICE *sv, const char *modName); +static AQH_USER *_ensureUser(AQH_SERVICE *sv, const char *alias); +static AQH_SESSION *_ensureSession(AQH_SERVICE *sv, const char *sessionUid); +static GWEN_BUFFER *_getConfigFilePath(const AQH_SERVICE *sv, const char *fileName); +static GWEN_BUFFER *_getModuleFilePath(const AQH_SERVICE *sv, const char *modName); +static GWEN_BUFFER *_getUserFilePath(const AQH_SERVICE *sv, const char *userAlias); +static GWEN_BUFFER *_getSessionFilePath(const AQH_SERVICE *sv, const char *sessionUid); +static int _checkUser(const AQH_USER *user, int ignoreMissingId); +static int _checkSession(const AQH_SESSION *session); +static int _checkModule(const AQH_MODULE *m, int ignoreMissingId); +static int _checkRoleList(const AQH_ROLE_LIST *roleList, int ignoreMissingId); +static int _writeDbFile(const char *fname, GWEN_DB_NODE *db); + + + +/* ------------------------------------------------------------------------------------------------ + * implementations + * ------------------------------------------------------------------------------------------------ + */ + +int AQH_HttpService_LoadConfig(AQH_SERVICE *sv) +{ + AQH_HTTP_SERVICE *xsv; + GWEN_BUFFER *nameBuf; + int rv; + GWEN_DB_NODE *db; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object"); + return GWEN_ERROR_INVALID; + } + + db=GWEN_DB_Group_new("service"); + + nameBuf=_getConfigFilePath(sv, AQH_HTTP_SERVICE_FILE_CONFIG); + rv=GWEN_DB_ReadFile(db, GWEN_Buffer_GetStart(nameBuf), GWEN_DB_FLAGS_DEFAULT | GWEN_DB_FLAGS_LOCKFILE); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + GWEN_Buffer_free(nameBuf); + GWEN_DB_Group_free(db); + return rv; + } + GWEN_Buffer_free(nameBuf); + + xsv->lastModuleId=GWEN_DB_GetIntValue(db, "lastModuleId", 0, 0); + xsv->lastUserId=GWEN_DB_GetIntValue(db, "lastUserId", 0, 0); + xsv->maxSessionAgeInSecs=GWEN_DB_GetIntValue(db, "maxSessionAgeInSecs", 0, AQH_HTTP_SERVICE_DEFAULT_MAXSESSIONAGE); + + GWEN_DB_Group_free(db); + + return 0; +} + + + +int AQH_HttpService_SaveConfig(const AQH_SERVICE *sv) +{ + AQH_HTTP_SERVICE *xsv; + int rv; + GWEN_BUFFER *nameBuf; + GWEN_DB_NODE *db; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object"); + return GWEN_ERROR_INVALID; + } + + db=GWEN_DB_Group_new("service"); + GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "lastModuleId", xsv->lastModuleId); + GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "lastUserId", xsv->lastUserId); + GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "maxSessionAgeInSecs", xsv->maxSessionAgeInSecs); + + nameBuf=_getConfigFilePath(sv, AQH_HTTP_SERVICE_FILE_CONFIG); + rv=_writeDbFile(GWEN_Buffer_GetStart(nameBuf), db); + GWEN_Buffer_free(nameBuf); + GWEN_DB_Group_free(db); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + return 0; +} + + + +AQH_MODULE *AQH_HttpService_GetModule(AQH_SERVICE *sv, const char *modName) +{ + AQH_HTTP_SERVICE *xsv; + AQH_MODULE *m; + int rv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object"); + return NULL; + } + + rv=GWEN_Mutex_Lock(xsv->moduleMutex); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error obtaining lock on module mutex"); + return NULL; + } + + m=_ensureModule(sv, modName); + + rv=GWEN_Mutex_Unlock(xsv->moduleMutex); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error releasing lock on module mutex"); + return NULL; + } + + if (m==NULL) { + DBG_INFO(AQH_LOGDOMAIN, "here"); + return NULL; + } + + return m; +} + + + +AQH_USER *AQH_HttpService_GetUser(AQH_SERVICE *sv, const char *alias) +{ + AQH_HTTP_SERVICE *xsv; + AQH_USER *user; + int rv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object"); + return NULL; + } + + rv=GWEN_Mutex_Lock(xsv->userMutex); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error obtaining lock on user mutex"); + return NULL; + } + + user=_ensureUser(sv, alias); + + rv=GWEN_Mutex_Unlock(xsv->userMutex); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error releasing lock on user mutex"); + return NULL; + } + + if (user==NULL) { + DBG_INFO(AQH_LOGDOMAIN, "here"); + return NULL; + } + + return user; +} + + + +AQH_SESSION *AQH_HttpService_GetSession(AQH_SERVICE *sv, const char *sessionUid) +{ + AQH_HTTP_SERVICE *xsv; + AQH_SESSION *session; + int rv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object"); + return NULL; + } + + rv=GWEN_Mutex_Lock(xsv->sessionMutex); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error obtaining lock on session mutex"); + return NULL; + } + + session=_ensureSession(sv, sessionUid); + + rv=GWEN_Mutex_Unlock(xsv->sessionMutex); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error releasing lock on session mutex"); + return NULL; + } + + if (session==NULL) { + DBG_INFO(AQH_LOGDOMAIN, "here"); + return NULL; + } + + return session; +} + + + + + + +AQH_MODULE *AQH_HttpService_LoadModule(const AQH_SERVICE *sv, const char *modName) +{ + GWEN_BUFFER *nameBuf; + int rv; + GWEN_DB_NODE *db; + AQH_MODULE *m; + + db=GWEN_DB_Group_new("user"); + + nameBuf=_getModuleFilePath(sv, modName); + rv=GWEN_DB_ReadFile(db, GWEN_Buffer_GetStart(nameBuf), GWEN_DB_FLAGS_DEFAULT | GWEN_DB_FLAGS_LOCKFILE); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + GWEN_Buffer_free(nameBuf); + GWEN_DB_Group_free(db); + return NULL; + } + GWEN_Buffer_free(nameBuf); + + m=AQH_Module_fromDb(db); + if (m==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Error loading module \"%s\" from config group", modName); + GWEN_DB_Group_free(db); + return NULL; + } + GWEN_DB_Group_free(db); + + rv=_checkModule(m, 0); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "Invalid data for module \"%s\"", modName); + AQH_Module_free(m); + return NULL; + } + + return m; +} + + + +int AQH_HttpService_SaveModule(const AQH_SERVICE *sv, const AQH_MODULE *m) +{ + int rv; + const char *modName; + GWEN_BUFFER *nameBuf; + GWEN_DB_NODE *db; + + rv=_checkModule(m, 0); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + db=GWEN_DB_Group_new("module"); + rv=AQH_Module_toDb(m, db); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + GWEN_DB_Group_free(db); + return rv; + } + + modName=AQH_Module_GetName(m); + nameBuf=_getModuleFilePath(sv, modName); + rv=_writeDbFile(GWEN_Buffer_GetStart(nameBuf), db); + GWEN_Buffer_free(nameBuf); + GWEN_DB_Group_free(db); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + return 0; +} + + + +int AQH_HttpService_AddModule(AQH_SERVICE *sv, AQH_MODULE *m) +{ + int rv; + uint32_t id; + AQH_ROLE_LIST *roleList; + AQH_ROLE *r; + + rv=_checkModule(m, 1); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Not adding invalid module (%d)", rv); + return rv; + } + + /* assign module id */ + id=AQH_HttpService_GetNextModuleId(sv); + AQH_Module_SetId(m, id); + + /* assign role ids */ + roleList=AQH_Module_GetRoleList(m); + id=1; + r=AQH_Role_List_First(roleList); + while(r) { + AQH_Role_SetId(r, id++); + r=AQH_Role_List_Next(r); + } + + rv=AQH_HttpService_SaveModule(sv, m); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error saving module module \"%s\" (%d)", AQH_Module_GetName(m), rv); + return rv; + } + + AQH_Service_AddModule(sv, m); + return 0; +} + + + +AQH_USER *AQH_HttpService_LoadUser(const AQH_SERVICE *sv, const char *userAlias) +{ + GWEN_BUFFER *nameBuf; + int rv; + GWEN_DB_NODE *db; + AQH_USER *user; + + db=GWEN_DB_Group_new("user"); + + nameBuf=_getUserFilePath(sv, userAlias); + rv=GWEN_DB_ReadFile(db, GWEN_Buffer_GetStart(nameBuf), GWEN_DB_FLAGS_DEFAULT | GWEN_DB_FLAGS_LOCKFILE); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + GWEN_Buffer_free(nameBuf); + GWEN_DB_Group_free(db); + return NULL; + } + GWEN_Buffer_free(nameBuf); + + user=AQH_User_fromDb(db); + if (user==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Error loading user \"%s\" from config group", userAlias); + GWEN_DB_Group_free(db); + return NULL; + } + GWEN_DB_Group_free(db); + + rv=_checkUser(user, 0); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "Invalid data for user \"%s\"", userAlias); + AQH_User_free(user); + return NULL; + } + + return user; +} + + + +int AQH_HttpService_SaveUser(const AQH_SERVICE *sv, const AQH_USER *user) +{ + int rv; + const char *sAlias; + GWEN_BUFFER *nameBuf; + GWEN_DB_NODE *db; + + rv=_checkUser(user, 0); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + db=GWEN_DB_Group_new("user"); + rv=AQH_User_toDb(user, db); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + GWEN_DB_Group_free(db); + return rv; + } + + sAlias=AQH_User_GetAlias(user); + nameBuf=_getUserFilePath(sv, sAlias); + rv=_writeDbFile(GWEN_Buffer_GetStart(nameBuf), db); + GWEN_Buffer_free(nameBuf); + GWEN_DB_Group_free(db); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + return 0; +} + + + +int AQH_HttpService_AddUser(AQH_SERVICE *sv, AQH_USER *user) +{ + int rv; + uint32_t id; + + rv=_checkUser(user, 0); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + /* assign user id */ + id=AQH_HttpService_GetNextUserId(sv); + AQH_User_SetId(user, id); + + rv=AQH_HttpService_SaveUser(sv, user); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + AQH_Service_AddUser(sv, user); + + return 0; +} + + + +int AQH_HttpService_DelUser(AQH_SERVICE *sv, AQH_USER *user) +{ + const char *sAlias; + GWEN_BUFFER *nameBuf; + uint32_t userId; + + sAlias=AQH_User_GetAlias(user); + userId=AQH_User_GetId(user); + nameBuf=_getUserFilePath(sv, sAlias); + unlink(GWEN_Buffer_GetStart(nameBuf)); + GWEN_Buffer_free(nameBuf); + AQH_Service_DelUser(sv, userId); + return 0; +} + + + +AQH_SESSION *AQH_HttpService_LoadSession(const AQH_SERVICE *sv, const char *sessionUid) +{ + GWEN_BUFFER *nameBuf; + int rv; + GWEN_DB_NODE *db; + AQH_SESSION *session; + + db=GWEN_DB_Group_new("user"); + + nameBuf=_getSessionFilePath(sv, sessionUid); + rv=GWEN_DB_ReadFile(db, GWEN_Buffer_GetStart(nameBuf), GWEN_DB_FLAGS_DEFAULT | GWEN_DB_FLAGS_LOCKFILE); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + GWEN_Buffer_free(nameBuf); + GWEN_DB_Group_free(db); + return NULL; + } + GWEN_Buffer_free(nameBuf); + + session=AQH_Session_fromDb(db); + if (session==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Error loading session \"%s\" from config group", sessionUid); + GWEN_DB_Group_free(db); + return NULL; + } + GWEN_DB_Group_free(db); + + rv=_checkSession(session); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "Invalid data for session \"%s\"", sessionUid); + AQH_Session_free(session); + return NULL; + } + + return session; +} + + + +int AQH_HttpService_SaveSession(const AQH_SERVICE *sv, const AQH_SESSION *session) +{ + int rv; + const char *sUid; + GWEN_BUFFER *nameBuf; + GWEN_DB_NODE *db; + + rv=_checkSession(session); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + db=GWEN_DB_Group_new("user"); + rv=AQH_Session_toDb(session, db); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + GWEN_DB_Group_free(db); + return rv; + } + + sUid=AQH_Session_GetUid(session); + nameBuf=_getSessionFilePath(sv, sUid); + rv=_writeDbFile(GWEN_Buffer_GetStart(nameBuf), db); + GWEN_Buffer_free(nameBuf); + GWEN_DB_Group_free(db); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + return 0; +} + + + +int AQH_HttpService_AddSession(AQH_SERVICE *sv, AQH_SESSION *session) +{ + int rv; + + rv=_checkSession(session); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + rv=AQH_HttpService_SaveSession(sv, session); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + + AQH_Service_AddSession(sv, session); + + return 0; +} + + + +int AQH_HttpService_DelSession(AQH_SERVICE *sv, AQH_SESSION *session) +{ + const char *uid; + GWEN_BUFFER *nameBuf; + + uid=AQH_Session_GetUid(session); + nameBuf=_getSessionFilePath(sv, uid); + unlink(GWEN_Buffer_GetStart(nameBuf)); + GWEN_Buffer_free(nameBuf); + AQH_Service_DelSession(sv, uid); + return 0; +} + + + +AQH_MODULE *_ensureModule(AQH_SERVICE *sv, const char *modName) +{ + AQH_MODULE *module; + + module=AQH_Service_GetModuleByName(sv, modName); + if (module) + return module; + + module=AQH_HttpService_LoadModule(sv, modName); + if (module==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Could not load module \"%s\"", modName); + return NULL; + } + AQH_Service_AddModule(sv, module); + + return module; +} + + + +AQH_USER *_ensureUser(AQH_SERVICE *sv, const char *alias) +{ + AQH_USER *user; + + user=AQH_Service_GetUserByAlias(sv, alias); + if (user) + return user; + + user=AQH_HttpService_LoadUser(sv, alias); + if (user==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Could not load user \"%s\"", alias); + return NULL; + } + _calculateUserPerms(sv, user); + + AQH_Service_AddUser(sv, user); + return user; +} + + + +AQH_SESSION *_ensureSession(AQH_SERVICE *sv, const char *sessionUid) +{ + AQH_SESSION *session; + + session=AQH_Service_GetSessionByUid(sv, sessionUid); + if (session) + return session; + + session=AQH_HttpService_LoadSession(sv, sessionUid); + if (session) { + AQH_USER *user; + + user=AQH_HttpService_GetUser(sv, AQH_Session_GetUserAlias(session)); + if (user==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "User \"%s\" for session \"%s\" not available", AQH_Session_GetUserAlias(session), sessionUid); + AQH_Session_free(session); + return NULL; + } + AQH_User_Attach(user); + AQH_Session_SetUser(session, user); + AQH_Service_AddSession(sv, session); + } + + if (session==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Could not load session \"%s\"", sessionUid); + return NULL; + } + + return session; +} + + + +GWEN_BUFFER *_getConfigFilePath(const AQH_SERVICE *sv, const char *fileName) +{ + const char *configFolder; + + configFolder=AQH_HttpService_GetConfigFolder(sv); + if (!(configFolder && *configFolder)) { + DBG_ERROR(AQH_LOGDOMAIN, "No config folder given"); + return NULL; + } + else { + GWEN_BUFFER *nameBuf; + + nameBuf=GWEN_Buffer_new(0, 256, 0, 1); + GWEN_Buffer_AppendString(nameBuf, configFolder); + GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S AQH_HTTP_SERVICE_DIR_SESSIONS GWEN_DIR_SEPARATOR_S); + GWEN_Text_EscapeToBuffer(fileName, nameBuf); + return nameBuf; + } +} + + + + +GWEN_BUFFER *_getModuleFilePath(const AQH_SERVICE *sv, const char *modName) +{ + const char *configFolder; + + configFolder=AQH_HttpService_GetConfigFolder(sv); + if (!(configFolder && *configFolder)) { + DBG_ERROR(AQH_LOGDOMAIN, "No config folder given"); + return NULL; + } + else { + GWEN_BUFFER *nameBuf; + + nameBuf=GWEN_Buffer_new(0, 256, 0, 1); + GWEN_Buffer_AppendString(nameBuf, configFolder); + GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S AQH_HTTP_SERVICE_DIR_MODS GWEN_DIR_SEPARATOR_S); + GWEN_Text_EscapeToBuffer(modName, nameBuf); + GWEN_Buffer_AppendString(nameBuf, ".conf"); + return nameBuf; + } +} + + + + +GWEN_BUFFER *_getUserFilePath(const AQH_SERVICE *sv, const char *userAlias) +{ + const char *configFolder; + + configFolder=AQH_HttpService_GetConfigFolder(sv); + if (!(configFolder && *configFolder)) { + DBG_ERROR(AQH_LOGDOMAIN, "No config folder given"); + return NULL; + } + else { + GWEN_BUFFER *nameBuf; + + nameBuf=GWEN_Buffer_new(0, 256, 0, 1); + GWEN_Buffer_AppendString(nameBuf, configFolder); + GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S AQH_HTTP_SERVICE_DIR_USERS GWEN_DIR_SEPARATOR_S); + GWEN_Text_EscapeToBuffer(userAlias, nameBuf); + GWEN_Buffer_AppendString(nameBuf, ".conf"); + return nameBuf; + } +} + + + +GWEN_BUFFER *_getSessionFilePath(const AQH_SERVICE *sv, const char *sessionUid) +{ + const char *configFolder; + + configFolder=AQH_HttpService_GetConfigFolder(sv); + if (!(configFolder && *configFolder)) { + DBG_ERROR(AQH_LOGDOMAIN, "No config folder given"); + return NULL; + } + else { + GWEN_BUFFER *nameBuf; + + nameBuf=GWEN_Buffer_new(0, 256, 0, 1); + GWEN_Buffer_AppendString(nameBuf, configFolder); + GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S AQH_HTTP_SERVICE_DIR_SESSIONS GWEN_DIR_SEPARATOR_S); + GWEN_Text_EscapeToBuffer(sessionUid, nameBuf); + GWEN_Buffer_AppendString(nameBuf, ".conf"); + return nameBuf; + } +} + + + +int _checkUser(const AQH_USER *user, int ignoreMissingId) +{ + const char *s; + + if (!ignoreMissingId) { + uint32_t id; + + id=AQH_User_GetId(user); + if (id==0) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid user: no id"); + return GWEN_ERROR_BAD_DATA; + } + } + + s=AQH_User_GetAlias(user); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid user: no alias"); + return GWEN_ERROR_BAD_DATA; + } + s=AQH_User_GetHashedPassword(user); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid user: no password"); + return GWEN_ERROR_BAD_DATA; + } + s=AQH_User_GetEmail(user); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid user: no email"); + return GWEN_ERROR_BAD_DATA; + } + + return 0; +} + + + +int _checkSession(const AQH_SESSION *session) +{ + const char *s; + + s=AQH_Session_GetUid(session); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid session: no uid"); + return GWEN_ERROR_BAD_DATA; + } + + s=AQH_Session_GetUserAlias(session); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid session: no user alias"); + return GWEN_ERROR_BAD_DATA; + } + + if (AQH_Session_GetTimestampCreation(session)==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid session: no creation time"); + return GWEN_ERROR_BAD_DATA; + } + + return 0; +} + + + +int _checkModule(const AQH_MODULE *m, int ignoreMissingId) +{ + const char *s; + const AQH_ROLE_LIST *roleList; + int rv; + + if (!ignoreMissingId) { + if (AQH_Module_GetId(m)==0) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid module: missing id"); + return GWEN_ERROR_BAD_DATA; + } + } + + s=AQH_Module_GetName(m); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid module: missing name"); + return GWEN_ERROR_BAD_DATA; + } + + roleList=AQH_Module_GetRoleList(m); + if (roleList==NULL || AQH_Role_List_GetCount(roleList)<1) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid module: empty or missing role list"); + return GWEN_ERROR_BAD_DATA; + } + rv=_checkRoleList(roleList, ignoreMissingId); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid module %s: invalid role definition", AQH_Module_GetName(m)); + return rv; + } + + return 0; +} + + + +int _checkRoleList(const AQH_ROLE_LIST *roleList, int ignoreMissingId) +{ + const AQH_ROLE *r; + + r=AQH_Role_List_First(roleList); + while(r) { + const char *s; + + if (!ignoreMissingId) { + if (AQH_Role_GetId(r)==0) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid role: missing id"); + return GWEN_ERROR_BAD_DATA; + } + } + + s=AQH_Role_GetName(r); + if (!(s && *s)) { + DBG_ERROR(AQH_LOGDOMAIN, "Invalid role: missing name"); + return GWEN_ERROR_BAD_DATA; + } + + r=AQH_Role_List_Next(r); + } + + return 0; +} + + + +void _calculateUserPerms(AQH_SERVICE *sv, AQH_USER *user) +{ + AQH_MODULE_PERMS_LIST *permList; + + permList=AQH_User_GetModulePermList(user); + if (permList) { + AQH_MODULE_PERMS *p; + + p=AQH_ModulePerms_List_First(permList); + while(p) { + _calculateModuleUserPerms(sv, p); + p=AQH_ModulePerms_List_Next(p); + } + } + AQH_User_AddRuntimeFlags(user, AQH_USER_RTFLAGS_PERMSCALC); +} + + + +void _calculateModuleUserPerms(AQH_SERVICE *sv, AQH_MODULE_PERMS *p) +{ + uint32_t perms=0; + uint32_t addPerms=0; + uint32_t delPerms=0; + const AQH_MODULE *m; + + m=AQH_Service_GetModuleById(sv, AQH_ModulePerms_GetModuleId(p)); + if (m==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Module \"%d\" not found, ignoring", AQH_ModulePerms_GetModuleId(p)); + } + else { + const AQH_ROLE_LIST *roleList; + + roleList=AQH_Module_GetRoleList(m); + if (roleList) { + int i, j; + + j=AQH_ModulePerms_GetRoleArrayArraySize(); + for(i=0; i0) { + AQH_ROLE *r; + + r=AQH_Role_List_GetById(roleList, roleId); + if (r==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Role %d not found in module \"%s\", ignoring", roleId, AQH_Module_GetName(m)); + } + else { + DBG_INFO(AQH_LOGDOMAIN, "Module \"%s\": adding perms for role \"%s\"", AQH_Module_GetName(m), AQH_Role_GetName(r)); + perms|=AQH_Role_GetPerms(r); + addPerms|=AQH_Role_GetExplAddPerms(r); + delPerms|=AQH_Role_GetExplDelPerms(r); + } + } + } + } + } + perms|=addPerms; + perms&=~delPerms; + AQH_ModulePerms_SetPerms(p, perms); +} + + + +int _writeDbFile(const char *fname, GWEN_DB_NODE *db) +{ + GWEN_BUFFER *tmpFilenameBuf; + int rv; + + tmpFilenameBuf=GWEN_Buffer_new(0, 256, 0, 1); + GWEN_Buffer_AppendString(tmpFilenameBuf, fname); + GWEN_Buffer_AppendString(tmpFilenameBuf, ".tmp"); + unlink(GWEN_Buffer_GetStart(tmpFilenameBuf)); + rv=GWEN_DB_WriteFile(db, GWEN_Buffer_GetStart(tmpFilenameBuf), GWEN_DB_FLAGS_DEFAULT | GWEN_DB_FLAGS_LOCKFILE); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error writing db file \"%s\": %d", GWEN_Buffer_GetStart(tmpFilenameBuf), rv); + GWEN_Buffer_free(tmpFilenameBuf); + return rv; + } + if (rename(GWEN_Buffer_GetStart(tmpFilenameBuf), fname)<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error renaming tmp file to \"%s\": %d (%s)", fname, errno, strerror(errno)); + GWEN_Buffer_free(tmpFilenameBuf); + return GWEN_ERROR_IO; + } + unlink(GWEN_Buffer_GetStart(tmpFilenameBuf)); + GWEN_Buffer_free(tmpFilenameBuf); + return 0; +} + + + + + + + diff --git a/aqhome/http/httpservice_conf.h b/aqhome/http/httpservice_conf.h new file mode 100644 index 0000000..ecb9590 --- /dev/null +++ b/aqhome/http/httpservice_conf.h @@ -0,0 +1,48 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 Martin Preuss, all rights reserved. + * + * The license for this file can be found in the file COPYING which you + * should have received along with this file. + ****************************************************************************/ + +#ifndef AQH_HTTP_SERVICE_CONF_H +#define AQH_HTTP_SERVICE_CONF_H + + + +#include "aqhome/service/service.h" + +#include +#include +#include +#include + + +int AQH_HttpService_LoadConfig(AQH_SERVICE *sv); +int AQH_HttpService_SaveConfig(const AQH_SERVICE *sv); + +AQH_MODULE *AQH_HttpService_GetModule(AQH_SERVICE *sv, const char *modName); +AQH_USER *AQH_HttpService_GetUser(AQH_SERVICE *sv, const char *alias); +AQH_SESSION *AQH_HttpService_GetSession(AQH_SERVICE *sv, const char *sessionUid); + + +AQH_MODULE *AQH_HttpService_LoadModule(const AQH_SERVICE *sv, const char *modName); +int AQH_HttpService_SaveModule(const AQH_SERVICE *sv, const AQH_MODULE *m); +int AQH_HttpService_AddModule(AQH_SERVICE *sv, AQH_MODULE *m); + +AQH_USER *AQH_HttpService_LoadUser(const AQH_SERVICE *sv, const char *userAlias); +int AQH_HttpService_SaveUser(const AQH_SERVICE *sv, const AQH_USER *user); +int AQH_HttpService_AddUser(AQH_SERVICE *sv, AQH_USER *user); +int AQH_HttpService_DelUser(AQH_SERVICE *sv, AQH_USER *user); + +AQH_SESSION *AQH_HttpService_LoadSession(const AQH_SERVICE *sv, const char *sessionUid); +int AQH_HttpService_SaveSession(const AQH_SERVICE *sv, const AQH_SESSION *session); +int AQH_HttpService_AddSession(AQH_SERVICE *sv, AQH_SESSION *session); +int AQH_HttpService_DelSession(AQH_SERVICE *sv, AQH_SESSION *session); + + +#endif + + + diff --git a/aqhome/http/httpservice_http.c b/aqhome/http/httpservice_http.c new file mode 100644 index 0000000..45de3b3 --- /dev/null +++ b/aqhome/http/httpservice_http.c @@ -0,0 +1,281 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 Martin Preuss, all rights reserved. + * + * The license for this file can be found in the file COPYING which you + * should have received along with this file. + ****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif + + +#include "aqhome/http/httpservice_http.h" +#include "aqhome/http/httpservice_conf.h" +#include "aqhome/http/httpservice_p.h" + +#include +#include +#include +#include + + + +#ifdef GWEN_INHERIT_REF + GWEN_INHERIT_REF(AQH_SERVICE, AQH_HTTP_SERVICE) +#else + extern uint32_t AQH_HTTP_SERVICE_ID; +#endif + + + +/* ------------------------------------------------------------------------------------------------ + * forward declarations + * ------------------------------------------------------------------------------------------------ + */ + +static void _writeDefaultResponseHeaderToBuffer(AQH_SERVICE *sv, int contentLength, GWEN_BUFFER *buf); +static GWEN_BUFFER *_getPageFilePath(const AQH_SERVICE *sv, const char *langFolder, const char *fileName); +static void _setRequestPerms(AQH_SERVICE *sv, AQH_HTTP_REQUEST *rq); + + + +/* ------------------------------------------------------------------------------------------------ + * implementations + * ------------------------------------------------------------------------------------------------ + */ + + +GWEN_MSG *AQH_HttpService_HandleHttpRequest(AQH_SERVICE *sv, GWEN_MSG_ENDPOINT *endpoint, const GWEN_MSG *msgReceived) +{ + AQH_HTTP_SERVICE *xsv; + AQH_HTTP_REQUEST *rq; + const char *s; + AQH_MODULE *m=NULL; + AQH_SESSION *session=NULL; + GWEN_MSG *msgResponse; + int rv; + + xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); + if (xsv==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object"); + return NULL; + } + + if (xsv->handleRequestFn==NULL) { + DBG_ERROR(AQH_LOGDOMAIN, "No request handler set"); + return AQH_HttpService_CreateResponseMsg(sv, 500, "Internal server error", "HTTP/1.1"); + } + + rq=AQH_HttpRequest_new(endpoint, msgReceived); + if (rq==NULL) { + DBG_INFO(AQH_LOGDOMAIN, "Could not create valie request from incoming message"); + return AQH_HttpService_CreateResponseMsg(sv, 500, "Internal server error", "HTTP/1.1"); + } + + s=AQH_HttpRequest_GetModuleName(rq); + if (s && *s) { + m=AQH_HttpService_GetModule(sv, s); + if (m==NULL) { + DBG_INFO(AQH_LOGDOMAIN, "Module \"%s\" not found", s); + } + else + AQH_HttpRequest_SetModule(rq, m); + } + + s=AQH_HttpRequest_GetSessionId(rq); + if (s && *s) { + session=AQH_HttpService_GetSession(sv, s); + if (session==NULL) { + DBG_INFO(AQH_LOGDOMAIN, "Session \"%s\" not found", s); + } + else { + AQH_HttpRequest_SetSession(rq, session); + } + } + + _setRequestPerms(sv, rq); + + rv=xsv->handleRequestFn(sv, rq); + if (rv<0) { + DBG_INFO(AQH_LOGDOMAIN, "Error handling request"); + AQH_HttpRequest_free(rq); + return AQH_HttpService_CreateResponseMsg(sv, 500, "Internal server error", "http/1.1"); + } + msgResponse=AQH_HttpRequest_TakeResponseMsg(rq); + AQH_HttpRequest_free(rq); + return msgResponse; +} + + + +void AQH_HttpService_AddStatusLine(AQH_SERVICE *sv, int code, const char *msg, const char *proto, GWEN_BUFFER *buf) +{ + GWEN_Buffer_AppendArgs(buf, "%s %03d %s \r\n", proto?proto:"HTTP/1.1", code, msg?msg:""); +} + + + +void AQH_HttpService_AddHeader(AQH_SERVICE *sv, GWEN_DB_NODE *dbHeader, GWEN_BUFFER *buf) +{ + GWEN_DB_NODE *dbVar; + + dbVar=GWEN_DB_GetFirstVar(dbHeader); + while (dbVar) { + GWEN_DB_NODE *dbVal; + + /* only handle first value */ + dbVal=GWEN_DB_GetFirstValue(dbVar); + if (dbVal) { + const char *sVar; + + sVar=GWEN_DB_VariableName(dbVar); + if (sVar && *sVar) { + GWEN_DB_NODE_TYPE vtype; + + vtype=GWEN_DB_GetValueType(dbVal); + if (vtype==GWEN_DB_NodeType_ValueChar) { + const char *sValue; + + sValue=GWEN_DB_GetCharValueFromNode(dbVal); + if (sValue) + GWEN_Buffer_AppendArgs(buf, "%s: %s\r\n", sVar, sValue); + } /* if char */ + else if (vtype==GWEN_DB_NodeType_ValueInt) { + int i; + + i=GWEN_DB_GetIntValueFromNode(dbVal); + if (i!=-1 || strcasecmp(sVar, "Content-Length")==0) + GWEN_Buffer_AppendArgs(buf, "%s: %d\r\n", sVar, i); + } /* if int */ + else { + DBG_INFO(AQH_LOGDOMAIN, "Variable type %d of var [%s] not supported, ignoring", vtype, sVar); + } + } /* if sVar */ + } + dbVar=GWEN_DB_GetNextVar(dbVar); + } + + /* finalize header */ + GWEN_Buffer_AppendString(buf, "\r\n"); +} + + + +GWEN_MSG *AQH_HttpService_CreateResponseMsg(AQH_SERVICE *sv, int code, const char *text, const char *protocol) +{ + GWEN_BUFFER *buf; + GWEN_MSG *msg; + + buf=GWEN_Buffer_new(0, 256, 0, 1); + AQH_HttpService_AddStatusLine(sv, code, text, protocol, buf); + _writeDefaultResponseHeaderToBuffer(sv, 0, buf); + + msg=GWEN_Msg_fromBytes((const uint8_t*)GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf)); + GWEN_Buffer_free(buf); + return msg; +} + + + +int AQH_HttpService_AddFile(AQH_SERVICE *sv, GWEN_UNUSED AQH_SESSION *session, const char *fname, GWEN_BUFFER *buf) +{ + if (fname && *fname) { + int rv; + GWEN_BUFFER *nameBuf; + + nameBuf=_getPageFilePath(sv, NULL, fname); + + rv=GWEN_SyncIo_Helper_ReadFile(GWEN_Buffer_GetStart(nameBuf), buf); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error reading file \"%s\": %d", fname, rv); + GWEN_Buffer_free(nameBuf); + return rv; + } + GWEN_Buffer_free(nameBuf); + } + + return 0; +} + + + +GWEN_BUFFER *_getPageFilePath(const AQH_SERVICE *sv, const char *langFolder, const char *fileName) +{ + const char *sourceFolder; + + sourceFolder=AQH_HttpService_GetSourceFolder(sv); + if (!(sourceFolder && *sourceFolder)) { + DBG_ERROR(AQH_LOGDOMAIN, "No source folder given"); + return NULL; + } + else { + GWEN_BUFFER *nameBuf; + + nameBuf=GWEN_Buffer_new(0, 256, 0, 1); + GWEN_Buffer_AppendString(nameBuf, sourceFolder); + GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S); + if (langFolder && *langFolder) { + GWEN_Buffer_AppendString(nameBuf, langFolder); + GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S); + } + GWEN_Text_EscapeToBuffer(fileName, nameBuf); + return nameBuf; + } +} + + + + + + +void _writeDefaultResponseHeaderToBuffer(AQH_SERVICE *sv, int contentLength, GWEN_BUFFER *buf) +{ + GWEN_DB_NODE *db; + + db=GWEN_DB_Group_new("header"); + GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Connection", "Keep-Alive"); + GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Type", "text/html; charset=utf-8"); + GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Length", contentLength); + + AQH_HttpService_AddHeader(sv, db, buf); + GWEN_DB_Group_free(db); +} + + + +void _setRequestPerms(AQH_SERVICE *sv, AQH_HTTP_REQUEST *rq) +{ + AQH_MODULE *m; + AQH_SESSION *session; + + AQH_HttpRequest_SetModulePerms(rq, 0); + m=AQH_HttpRequest_GetModule(rq); + session=AQH_HttpRequest_GetSession(rq); + if (m) { + if (session) { + AQH_USER * user; + + user=AQH_Session_GetUser(session); + if (user) { + AQH_MODULE_PERMS *userPerms; + + userPerms=AQH_ModulePerms_List_GetByModuleId(AQH_User_GetModulePermList(user), AQH_Module_GetId(m)); + if (userPerms) + AQH_HttpRequest_SetModulePerms(rq, AQH_ModulePerms_GetPerms(userPerms)); + } + else { + DBG_INFO(AQH_LOGDOMAIN, "Session but no user, SNH!"); + } + } + else { + DBG_INFO(AQH_LOGDOMAIN, "No session"); + } + } + else { + DBG_INFO(AQH_LOGDOMAIN, "No module"); + } +} + + diff --git a/aqhome/http/httpservice_http.h b/aqhome/http/httpservice_http.h new file mode 100644 index 0000000..1ae2448 --- /dev/null +++ b/aqhome/http/httpservice_http.h @@ -0,0 +1,40 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 Martin Preuss, all rights reserved. + * + * The license for this file can be found in the file COPYING which you + * should have received along with this file. + ****************************************************************************/ + +#ifndef AQH_HTTP_SERVICE_HTTP_H +#define AQH_HTTP_SERVICE_HTTP_H + + +#include "aqhome/service/service.h" + +#include +#include +#include + + +#define AQH_HTTP_SERVICE_SESSIONCOOKIE "sessionid" + + +AQHOME_API GWEN_MSG *AQH_HttpService_HandleHttpRequest(AQH_SERVICE *sv, GWEN_MSG_ENDPOINT *endpoint, const GWEN_MSG *msgReceived); + + +AQHOME_API void AQH_HttpService_AddStatusLine(AQH_SERVICE *sv, int code, const char *msg, const char *proto, GWEN_BUFFER *buf); +AQHOME_API void AQH_HttpService_AddHeader(AQH_SERVICE *sv, GWEN_DB_NODE *dbHeader, GWEN_BUFFER *buf); + +AQHOME_API int AQH_HttpService_ParsePostBody(AQH_SERVICE *sv, const GWEN_MSG *msgReceived, GWEN_DB_NODE *dbBody); + + +AQHOME_API GWEN_MSG *AQH_HttpService_CreateResponseMsg(AQH_SERVICE *sv, int code, const char *text, const char *protocol); + +AQHOME_API int AQH_HttpService_AddFile(AQH_SERVICE *sv, AQH_SESSION *session, const char *fname, GWEN_BUFFER *buf); + + +#endif + + + diff --git a/aqhome/http/httpservice_login.c b/aqhome/http/httpservice_login.c new file mode 100644 index 0000000..59e848e --- /dev/null +++ b/aqhome/http/httpservice_login.c @@ -0,0 +1,45 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 Martin Preuss, all rights reserved. + * + * The license for this file can be found in the file COPYING which you + * should have received along with this file. + ****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif + + +#include "aqhome/http/httpservice_login.h" +#include "aqhome/http/httpservice_p.h" + +#include +#include +#include +#include + + + +/* + Login process: + - GET: send a login page + - POST: + - get USER and PASS + - look for user (maybe add virtual function to load a user?) + - check password + - if all okay: + - create session + - add header "set-cookie" + - redirect to main page + */ + +GWEN_MSG *AQH_HttpService_HandleLoginUrl(AQH_SERVICE *sv, const GWEN_MSG *msgReceived, const GWEN_URL *url) +{ + +} + + + + + diff --git a/aqhome/http/httpservice_login.h b/aqhome/http/httpservice_login.h new file mode 100644 index 0000000..5c378f1 --- /dev/null +++ b/aqhome/http/httpservice_login.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 Martin Preuss, all rights reserved. + * + * The license for this file can be found in the file COPYING which you + * should have received along with this file. + ****************************************************************************/ + +#ifndef AQH_HTTP_SERVICE_LOGIN_H +#define AQH_HTTP_SERVICE_LOGIN_H + + + +#include "aqhome/http/httpservice.h" + +#include +#include +#include +#include + + + +GWEN_MSG *AQH_HttpService_HandleLoginUrl(AQH_SERVICE *sv, const GWEN_MSG *msgReceived, const GWEN_URL *url); + + + +#endif + + + diff --git a/aqhome/http/httpservice_p.h b/aqhome/http/httpservice_p.h index 5b73a21..1d69e8a 100644 --- a/aqhome/http/httpservice_p.h +++ b/aqhome/http/httpservice_p.h @@ -13,14 +13,27 @@ #include "aqhome/http/httpservice.h" +#define AQH_HTTP_SERVICE_DEFAULT_MAXSESSIONAGE 600 /* 10m */ + typedef struct AQH_HTTP_SERVICE AQH_HTTP_SERVICE; struct AQH_HTTP_SERVICE { char *sourceFolder; + char *configFolder; char *siteHeader; char *siteFooter; + uint32_t lastModuleId; + uint32_t lastUserId; + + int maxSessionAgeInSecs; + + GWEN_MUTEX *moduleMutex; + GWEN_MUTEX *userMutex; + GWEN_MUTEX *sessionMutex; + + AQH_HTTP_SERVICE_HANDLEREQUEST_FN handleRequestFn; }; diff --git a/aqhome/service/module.t2d b/aqhome/service/module.t2d index ff50d36..e78c385 100644 --- a/aqhome/service/module.t2d +++ b/aqhome/service/module.t2d @@ -64,6 +64,8 @@ NULL public own + none + none diff --git a/aqhome/service/moduleperms.t2d b/aqhome/service/moduleperms.t2d index 015d862..ed8d35b 100644 --- a/aqhome/service/moduleperms.t2d +++ b/aqhome/service/moduleperms.t2d @@ -38,13 +38,6 @@ with_getbymember - - 0 - 0 - public - - - 0 0 @@ -59,13 +52,21 @@ - + 0 0 public + + + 0 + 0 + public + volatile + + diff --git a/aqhome/service/role.t2d b/aqhome/service/role.t2d index 311916b..3e1d536 100644 --- a/aqhome/service/role.t2d +++ b/aqhome/service/role.t2d @@ -29,7 +29,7 @@ - + 0 0 public @@ -50,14 +50,14 @@ - + 0 0 public - + 0 0 public diff --git a/aqhome/service/service.c b/aqhome/service/service.c index 0381ce0..116a1fe 100644 --- a/aqhome/service/service.c +++ b/aqhome/service/service.c @@ -149,9 +149,9 @@ AQH_SESSION_LIST *AQH_Service_GetSessionList(const AQH_SERVICE *sv) -AQH_SESSION *AQH_Service_GetSessionById(const AQH_SERVICE *sv, uint32_t sessionId) +AQH_SESSION *AQH_Service_GetSessionByUid(const AQH_SERVICE *sv, const char *sessionUid) { - return sv?AQH_Session_List_GetById(sv->sessionList, sessionId):NULL; + return sv?AQH_Session_List_GetByUid(sv->sessionList, sessionUid):NULL; } @@ -164,12 +164,12 @@ void AQH_Service_AddSession(AQH_SERVICE *sv, AQH_SESSION *session) -void AQH_Service_DelSession(AQH_SERVICE *sv, uint32_t id) +void AQH_Service_DelSession(AQH_SERVICE *sv, const char *sid) { - if (sv && id) { + if (sv && sid && *sid) { AQH_SESSION *session; - session=AQH_Session_List_GetById(sv->sessionList, id); + session=AQH_Session_List_GetByUid(sv->sessionList, sid); if (session) { AQH_Session_List_Del(session); AQH_Session_free(session); diff --git a/aqhome/service/service.h b/aqhome/service/service.h index 1358825..e5363b8 100644 --- a/aqhome/service/service.h +++ b/aqhome/service/service.h @@ -44,9 +44,9 @@ AQHOME_API void AQH_Service_AddModule(AQH_SERVICE *sv, AQH_MODULE *m); AQHOME_API void AQH_Service_DelModule(AQH_SERVICE *sv, uint32_t moduleId); AQHOME_API AQH_SESSION_LIST *AQH_Service_GetSessionList(const AQH_SERVICE *sv); -AQHOME_API AQH_SESSION *AQH_Service_GetSessionById(const AQH_SERVICE *sv, uint32_t sessionId); +AQHOME_API AQH_SESSION *AQH_Service_GetSessionByUid(const AQH_SERVICE *sv, const char *sessionUid); AQHOME_API void AQH_Service_AddSession(AQH_SERVICE *sv, AQH_SESSION *session); -AQHOME_API void AQH_Service_DelSession(AQH_SERVICE *sv, uint32_t id); +AQHOME_API void AQH_Service_DelSession(AQH_SERVICE *sv, const char *sid); diff --git a/aqhome/service/session.t2d b/aqhome/service/session.t2d index d0ccca1..002e7bb 100644 --- a/aqhome/service/session.t2d +++ b/aqhome/service/session.t2d @@ -14,6 +14,8 @@ with_db with_list1 with_list2 + nodup + nocopy @@ -31,18 +33,11 @@ - - 0 - 0 - public - with_getbymember - - 0 0 public - + with_getbymember @@ -52,9 +47,9 @@ with_flags - - 0 - 0 + + NULL + NULL public @@ -87,8 +82,8 @@ NULL public assign - assign - volatile + none + volatile nodup nocopy diff --git a/aqhome/service/user.t2d b/aqhome/service/user.t2d index c7b9f5d..45c583c 100644 --- a/aqhome/service/user.t2d +++ b/aqhome/service/user.t2d @@ -14,6 +14,9 @@ with_db with_list1 with_list2 + with_refcount + nodup + nocopy @@ -28,6 +31,14 @@ + + + + + + + + @@ -103,6 +114,14 @@ own + + + 0 + 0 + public + volatile with_flags + +