/**************************************************************************** * 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 "./httprequest_p.h" #include #include #include 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 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); static int _unescapeUrlEncoded(const char *src, unsigned int srclen, char *buffer, unsigned int maxsize); /* ------------------------------------------------------------------------------------------------ * 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_StringList_free(rq->urlPathMembers); 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; } } const char *AQH_HttpRequest_GetUrlPath(const AQH_HTTP_REQUEST *rq) { return rq?(rq->urlPath):NULL; } GWEN_STRINGLIST *AQH_HttpRequest_GetUrlPathMembers(const AQH_HTTP_REQUEST *rq) { return rq?(rq->urlPathMembers):NULL; } void AQH_HttpRequest_SetUrlPathMembers(AQH_HTTP_REQUEST *rq, GWEN_STRINGLIST *sl) { if (rq) { GWEN_StringList_free(rq->urlPathMembers); rq->urlPathMembers=sl; } } 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; } GWEN_DB_NODE *AQH_HttpRequest_GetDbPostBody(const AQH_HTTP_REQUEST *rq) { return rq?(rq->dbPostBody):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; } } void AQH_HttpRequest_SetupUrlPathMembers(AQH_HTTP_REQUEST *rq) { const char *path; path=AQH_HttpRequest_GetUrlPath(rq); if (path && *path) { GWEN_STRINGLIST *sl; if (*path=='/') /* skip leading slash */ path++; sl=GWEN_StringList_fromString(path, "/", 0); if (sl==NULL) { DBG_ERROR(NULL, "Empty url path member list"); } else AQH_HttpRequest_SetUrlPathMembers(rq, sl); } } 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; } s=GWEN_Url_GetPath(rq->url); if (!(s && *s)) { DBG_ERROR(AQH_LOGDOMAIN, "Empty url in request, assuming \"/\""); s="/"; } rq->urlPath=s; 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) { DBG_INFO(AQH_LOGDOMAIN, "Sessionid: %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->dbHeader, "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); } } 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++; contentLength--; 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 (_unescapeUrlEncoded(sNameStart, nameLen, sNameBuf, sizeof(sNameBuf))>=0 && _unescapeUrlEncoded(sValueStart, valueLen, sValueBuf, sizeof(sValueBuf))>=0) 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=='&')) { contentLength--; s++; } } } /* while */ return 0; } int _unescapeUrlEncoded(const char *src, unsigned int srclen, char *buffer, unsigned int maxsize) { unsigned int size; size=0; while (srclen>0 && *src) { unsigned char x; x=(unsigned char)*src; if ( (x>='A' && x<='Z') || (x>='a' && x<='z') || (x>='0' && x<='9') || x==' ' || x=='.' || x==',' || x=='.' || x=='*' || x=='?' || x=='+' || x=='-' || x=='_' ) { if (size<(maxsize-1)) { buffer[size++]=(x=='+')?' ':x; } else { DBG_ERROR(GWEN_LOGDOMAIN, "Buffer too small"); return GWEN_ERROR_BUFFER_OVERFLOW; } } else { if (*src=='%') { unsigned char d1, d2; unsigned char c; if (srclen<3) { DBG_ERROR(GWEN_LOGDOMAIN, "Incomplete escape sequence (EOLN met)"); return GWEN_ERROR_BAD_DATA; } /* skip '%' */ src++; if (!(*src) || !isxdigit((int)*src)) { DBG_ERROR(GWEN_LOGDOMAIN, "Incomplete escape sequence (no digits)"); return GWEN_ERROR_BAD_DATA; } /* read first digit */ d1=(unsigned char)(toupper(*src)); /* get second digit */ src++; if (!(*src) || !isxdigit((int)*src)) { DBG_ERROR(GWEN_LOGDOMAIN, "Incomplete escape sequence (only 1 digit)"); return GWEN_ERROR_BAD_DATA; } d2=(unsigned char)(toupper(*src)); /* compute character */ d1-='0'; if (d1>9) d1-=7; c=(d1<<4)&0xf0; d2-='0'; if (d2>9) d2-=7; c+=(d2&0xf); /* store character */ if (size<(maxsize-1)) buffer[size++]=(char)c; else { DBG_ERROR(GWEN_LOGDOMAIN, "Buffer too small"); return GWEN_ERROR_BUFFER_OVERFLOW; } srclen-=2; } else { DBG_ERROR(GWEN_LOGDOMAIN, "Found non-alphanum characters in escaped string (\"%s\")", src); return GWEN_ERROR_BAD_DATA; } } srclen--; src++; } /* while */ buffer[size]=0; return 0; }