/**************************************************************************** * 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 //#define DISABLE_DEBUGLOG #include "aqhome/http/httpservice_http.h" #include "aqhome/http/httpservice_conf.h" #include "aqhome/http/httpservice_p.h" #include "aqhome/http/urlhandler.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 AQH_HTTP_URLHANDLER *_findMatchingUrlHandler(AQH_SERVICE *sv, const char *path); /* ------------------------------------------------------------------------------------------------ * 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; const char *cmd; const char *protocol; AQH_SESSION *session=NULL; GWEN_MSG *msgResponse; int rv; AQH_HTTP_URLHANDLER *uh; xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); if (xsv==NULL) { DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object"); return NULL; } 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", NULL); } cmd=AQH_HttpRequest_GetCommand(rq); protocol=AQH_HttpRequest_GetProtocol(rq); #if 0 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); } #endif 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 { GWEN_TIMESTAMP *ts; DBG_INFO(AQH_LOGDOMAIN, "Found session \"%s\"", s); ts=GWEN_Timestamp_NowInLocalTime(); AQH_Session_SetTimestampLastAccess(session, ts); GWEN_Timestamp_free(ts); AQH_Session_AddRuntimeFlags(session, AQH_SESSION_RTFLAGS_MODIFIED); AQH_HttpRequest_SetSession(rq, session); } } s=AQH_HttpRequest_GetUrlPath(rq); DBG_INFO(AQH_LOGDOMAIN, "Received \"%s\" request for url \"%s\"", cmd?cmd:"", s); uh=_findMatchingUrlHandler(sv, s); if (uh==NULL) { DBG_INFO(AQH_LOGDOMAIN, "No matching handler found for url \"%s\"", s); AQH_HttpRequest_free(rq); return AQH_HttpService_CreateResponseMsg(sv, 404, "Not found", protocol?protocol:"http/1.1", NULL); } rv=AQH_HttpUrlHandler_HandleMessage(uh, rq); if (rv<0) { DBG_INFO(AQH_LOGDOMAIN, "Error handling request"); AQH_HttpRequest_free(rq); return AQH_HttpService_CreateResponseMsg(sv, 500, "Internal server error", protocol?protocol:"http/1.1", NULL); } msgResponse=AQH_HttpRequest_TakeResponseMsg(rq); AQH_HttpRequest_free(rq); return msgResponse; } void AQH_HttpService_AddUrlHandler(AQH_SERVICE *sv, AQH_HTTP_URLHANDLER *urlHandler) { AQH_HTTP_SERVICE *xsv; xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); if (xsv==NULL) { DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object"); } else { AQH_HttpUrlHandler_List_Add(urlHandler, xsv->urlHandlerList); } } void AQH_HttpService_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) { DBG_INFO(AQH_LOGDOMAIN, "Presetting module's guest perms"); AQH_HttpRequest_SetModulePerms(rq, AQH_Module_GetGuestPerms(m)); if (session) { AQH_USER * user; user=AQH_Session_GetUser(session); if (user) { AQH_MODULE_PERMS_LIST *permsList; permsList=AQH_User_GetModulePermList(user); if (permsList) { AQH_MODULE_PERMS *userPerms; userPerms=AQH_ModulePerms_List_GetByModuleId(AQH_User_GetModulePermList(user), AQH_Module_GetId(m)); if (userPerms) { DBG_INFO(AQH_LOGDOMAIN, "Using user perms for module"); AQH_HttpRequest_SetModulePerms(rq, AQH_ModulePerms_GetPerms(userPerms)); } } else { DBG_INFO(AQH_LOGDOMAIN, "No module perms list in user, using module's guest perms"); } } else { DBG_INFO(AQH_LOGDOMAIN, "Session but no user, SNH!"); } } else { DBG_INFO(AQH_LOGDOMAIN, "No session, using module's guest perms"); } } else { DBG_INFO(AQH_LOGDOMAIN, "No module, no perms"); } } 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, const char *page) { GWEN_BUFFER *buf; GWEN_MSG *msg; buf=GWEN_Buffer_new(0, 256, 0, 1); AQH_HttpService_AddStatusLine(sv, code, text, protocol, buf); _writeDefaultResponseHeaderToBuffer(sv, (page && *page)?strlen(page):0, buf); if (page && *page) GWEN_Buffer_AppendString(buf, page); msg=GWEN_Msg_fromBytes((const uint8_t*)GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf)); GWEN_Buffer_free(buf); return msg; } GWEN_MSG *AQH_HttpService_CreateRedirectingResponseMsg(AQH_SERVICE *sv, const char *protocol, const char *newPage) { GWEN_BUFFER *buf; GWEN_MSG *msg; GWEN_DB_NODE *db; buf=GWEN_Buffer_new(0, 256, 0, 1); AQH_HttpService_AddStatusLine(sv, 303, "OK, redirecting to content", protocol, buf); 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_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Location", newPage); GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Length", 0); AQH_HttpService_AddHeader(sv, db, buf); GWEN_DB_Group_free(db); 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; } void AQH_HttpService_SetupModuleAndPerms(AQH_SERVICE *sv, AQH_HTTP_REQUEST *rq, const char *modulName) { AQH_SESSION *session; AQH_MODULE *m; AQH_HttpRequest_SetModuleName(rq, modulName); m=AQH_HttpService_GetModule(sv, modulName); if (m==NULL) { DBG_ERROR(NULL, "Module \"%s\" not found", modulName); AQH_HttpRequest_SetModulePerms(rq, 0); } else { AQH_HttpRequest_SetModule(rq, m); } session=AQH_HttpRequest_GetSession(rq); if (session) { AQH_USER *u; u=AQH_Session_GetUser(session); if (u) { if (strcasecmp(AQH_User_GetAlias(u), AQH_HTTP_SERVICE_ADMINUSER)==0) { DBG_ERROR(NULL, "special admin user, full access granted"); AQH_HttpRequest_SetModulePerms(rq, 0xffffffff); return; } } } AQH_HttpService_SetRequestPerms(sv, rq); } 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_EscapeToBufferTolerant(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); } AQH_HTTP_URLHANDLER *_findMatchingUrlHandler(AQH_SERVICE *sv, const char *path) { AQH_HTTP_SERVICE *xsv; AQH_HTTP_URLHANDLER *uh; xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv); if (xsv==NULL) { DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object"); return NULL; } uh=AQH_HttpUrlHandler_List_First(xsv->urlHandlerList); while(uh) { if (AQH_HttpUrlHandler_UrlMatches(uh, path)) { DBG_INFO(AQH_LOGDOMAIN, "Found matching url handler for \"%s\"", path); return uh; } uh=AQH_HttpUrlHandler_List_Next(uh); } DBG_INFO(AQH_LOGDOMAIN, "No matching url handler for \"%s\"", path); return NULL; }