/**************************************************************************** * 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/endpoint_http_p.h" #include #include #include #include #include #define AQH_ENDPOINT_HTTP_NAME "http" #define AQH_ENDPOINT_HTTP_BUFFERSIZE 1024 #define AQH_ENDPOINT_HTTP_BOOKMARK_HEADER 0 #define AQH_ENDPOINT_HTTP_BOOKMARK_BODY 1 GWEN_INHERIT(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP) /* ------------------------------------------------------------------------------------------------ * forward declarations * ------------------------------------------------------------------------------------------------ */ static void GWENHYWFAR_CB _freeData(void *bp, void *p); static void _addSockets(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKETSET *readSet, GWEN_SOCKETSET *writeSet, GWEN_UNUSED GWEN_SOCKETSET *xSet); static void _checkSockets(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKETSET *readSet, GWEN_SOCKETSET *writeSet, GWEN_SOCKETSET *xSet); static int _writeCurrentMessage(GWEN_MSG_ENDPOINT *ep); static int _readCurrentMessage(GWEN_MSG_ENDPOINT *ep); static int _distributeBufferContent(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen); static int _distributeBufferInCommandMode(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen); static int _distributeBufferInStatusMode(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen); static int _distributeBufferInHeaderMode(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen); static int _distributeBufferInBodyMode(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen); static int _distributeBufferAsLine(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen); static void _finishMessageAndStartNext(GWEN_MSG_ENDPOINT *ep); static void _abortMessage(GWEN_MSG_ENDPOINT *ep); static int _parseHeader(char *bufferPtr, GWEN_DB_NODE *db); static int _parseCommand(const char *buffer, GWEN_DB_NODE *db); /* ------------------------------------------------------------------------------------------------ * implementations * ------------------------------------------------------------------------------------------------ */ void AQH_HttpEndpoint_Extend(GWEN_MSG_ENDPOINT *ep, uint32_t flags) { if (ep) { AQH_ENDPOINT_HTTP *xep; GWEN_NEW_OBJECT(AQH_ENDPOINT_HTTP, xep); GWEN_INHERIT_SETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep, xep, _freeData); xep->readMode=AQH_EndpointHttpd_ReadMode_Command; xep->addSocketsFn=GWEN_MsgEndpoint_SetAddSocketsFn(ep, _addSockets); xep->checkSocketsFn=GWEN_MsgEndpoint_SetCheckSocketsFn(ep, _checkSockets); } } void _freeData(void *bp, void *p) { AQH_ENDPOINT_HTTP *xep; xep=(AQH_ENDPOINT_HTTP*) p; GWEN_DB_Group_free(xep->dbCurrentReadHeader); GWEN_DB_Group_free(xep->dbCurrentReadCommand); GWEN_Buffer_free(xep->currentReadBuffer); GWEN_FREE_OBJECT(xep); } void _addSockets(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKETSET *readSet, GWEN_SOCKETSET *writeSet, GWEN_UNUSED GWEN_SOCKETSET *xSet) { if (ep) { AQH_ENDPOINT_HTTP *xep; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); if (xep) { if (GWEN_MsgEndpoint_GetState(ep)==GWEN_MSG_ENDPOINT_STATE_CONNECTED) { GWEN_SOCKET *sk; sk=GWEN_MsgEndpoint_GetSocket(ep); if (sk) { DBG_DEBUG(AQH_LOGDOMAIN, "Endpoint %s: Adding socket %d to read set", GWEN_MsgEndpoint_GetName(ep), GWEN_Socket_GetSocketInt(sk)); GWEN_SocketSet_AddSocket(readSet, sk); if (GWEN_MsgEndpoint_HaveMessageToSend(ep)) { DBG_DEBUG(AQH_LOGDOMAIN, "Endpoint %s: Adding socket %d to write set", GWEN_MsgEndpoint_GetName(ep), GWEN_Socket_GetSocketInt(sk)); GWEN_SocketSet_AddSocket(writeSet, sk); } } /* if socket */ } else if (xep->addSocketsFn) { DBG_INFO(AQH_LOGDOMAIN, "Endpoint %s: Not connected, calling base function", GWEN_MsgEndpoint_GetName(ep)); xep->addSocketsFn(ep, readSet, writeSet, xSet); } } /* if (xep) */ } /* if (ep) */ } void _checkSockets(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKETSET *readSet, GWEN_SOCKETSET *writeSet, GWEN_SOCKETSET *xSet) { if (ep) { AQH_ENDPOINT_HTTP *xep; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); if (xep) { int rv; if (GWEN_MsgEndpoint_GetState(ep)==GWEN_MSG_ENDPOINT_STATE_CONNECTED) { GWEN_SOCKET *sk; sk=GWEN_MsgEndpoint_GetSocket(ep); if (sk) { if (GWEN_SocketSet_HasSocket(writeSet, sk)) { DBG_DEBUG(AQH_LOGDOMAIN, "Endpoint %s: Has socket in write set", GWEN_MsgEndpoint_GetName(ep)); rv=_writeCurrentMessage(ep); if (rv<0 && rv!=GWEN_ERROR_TIMEOUT) { DBG_INFO(AQH_LOGDOMAIN, "Endpoint %s: Error writing current message (%d), disconnecting", GWEN_MsgEndpoint_GetName(ep), rv); _abortMessage(ep); GWEN_MsgEndpoint_Disconnect(ep); return; } } if (GWEN_SocketSet_HasSocket(readSet, sk)) { DBG_DEBUG(AQH_LOGDOMAIN, "Endpoint %s: Has socket in read set", GWEN_MsgEndpoint_GetName(ep)); rv=_readCurrentMessage(ep); if (rv<0 && rv!=GWEN_ERROR_TIMEOUT) { DBG_INFO(AQH_LOGDOMAIN, "Endpoint %s: Error reading current message (%d), disconnecting", GWEN_MsgEndpoint_GetName(ep), rv); _abortMessage(ep); GWEN_MsgEndpoint_Disconnect(ep); return; } } } } /* if connected */ else if (xep->checkSocketsFn) { DBG_INFO(AQH_LOGDOMAIN, "Endpoint %s: Not connected, calling base function", GWEN_MsgEndpoint_GetName(ep)); xep->checkSocketsFn(ep, readSet, writeSet, xSet); } } } } int _writeCurrentMessage(GWEN_MSG_ENDPOINT *ep) { GWEN_MSG *msg; DBG_DEBUG(AQH_LOGDOMAIN, "Writing to endpoint %s", GWEN_MsgEndpoint_GetName(ep)); msg=GWEN_MsgEndpoint_GetFirstSendMessage(ep); if (msg) { uint8_t pos; int remaining; int rv; pos=GWEN_Msg_GetCurrentPos(msg); remaining=GWEN_Msg_GetRemainingBytes(msg); if (remaining>0) { const uint8_t *buf; /* start new message */ buf=GWEN_Msg_GetBuffer(msg)+pos; rv=GWEN_MsgEndpoint_WriteToSocket(ep, buf, remaining); if (rv<0) { if (rv==GWEN_ERROR_TIMEOUT) return rv; DBG_ERROR(AQH_LOGDOMAIN, "Error on write() (%d)", rv); return rv; } GWEN_Msg_IncCurrentPos(msg, rv); if (rv==remaining) { DBG_INFO(AQH_LOGDOMAIN, "Message completely sent"); /* end current message */ GWEN_Msg_List_Del(msg); GWEN_Msg_free(msg); } } } else { DBG_INFO(AQH_LOGDOMAIN, "Nothing to send"); } return 0; } int _readCurrentMessage(GWEN_MSG_ENDPOINT *ep) { int rv; uint8_t buffer[AQH_ENDPOINT_HTTP_BUFFERSIZE]; DBG_DEBUG(AQH_LOGDOMAIN, "Reading from endpoint %s", GWEN_MsgEndpoint_GetName(ep)); rv=GWEN_MsgEndpoint_ReadFromSocket(ep, buffer, sizeof(buffer)); if (rv<0 && rv!=GWEN_ERROR_TIMEOUT) { DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); return rv; } else if (rv==0) { DBG_INFO(AQH_LOGDOMAIN, "EOF met on read()"); return GWEN_ERROR_IO; } rv=_distributeBufferContent(ep, buffer, rv); if (rv<0) { DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); return rv; } return 0; } int _distributeBufferContent(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen) { if (ep) { AQH_ENDPOINT_HTTP *xep; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); if (xep) { int rv; if (xep->currentReadBuffer==NULL) xep->currentReadBuffer=GWEN_Buffer_new(0, 256, 0, 1); while(bufferLen) { switch(xep->readMode) { case AQH_EndpointHttpd_ReadMode_Command: rv=_distributeBufferInCommandMode(ep, bufferPtr, bufferLen); break; case AQH_EndpointHttpd_ReadMode_Status: rv=_distributeBufferInStatusMode(ep, bufferPtr, bufferLen); break; case AQH_EndpointHttpd_ReadMode_Headers: rv=_distributeBufferInHeaderMode(ep, bufferPtr, bufferLen); break; case AQH_EndpointHttpd_ReadMode_Body: rv=_distributeBufferInBodyMode(ep, bufferPtr, bufferLen); break; default: DBG_ERROR(AQH_LOGDOMAIN, "Invalid read mode %d", xep->readMode); return GWEN_ERROR_GENERIC; } if (rv<0) { DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); return rv; } else if (rv==0) { DBG_INFO(AQH_LOGDOMAIN, "No bytes used? SNH!"); return GWEN_ERROR_INTERNAL; } bufferPtr+=rv; bufferLen-=rv; } /* while */ return 0; } /* if (xep) */ } /* if (ep) */ return GWEN_ERROR_GENERIC; } int _distributeBufferInCommandMode(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen) { int rv; rv=_distributeBufferAsLine(ep, bufferPtr, bufferLen); if (rv<0) { AQH_ENDPOINT_HTTP *xep; const char *s; /* line complete */ DBG_INFO(AQH_LOGDOMAIN, "Command line complete"); rv=-rv; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); xep->dbCurrentReadCommand=GWEN_DB_Group_new("command"); if (_parseCommand(GWEN_Buffer_GetStart(xep->currentReadBuffer), xep->dbCurrentReadCommand)<0) { DBG_INFO(AQH_LOGDOMAIN, "Error parsing command line [%s]", GWEN_Buffer_GetStart(xep->currentReadBuffer)); return GWEN_ERROR_BAD_DATA; } DBG_ERROR(AQH_LOGDOMAIN, "Command line received: %s", GWEN_Buffer_GetStart(xep->currentReadBuffer)); s=GWEN_DB_GetCharValue(xep->dbCurrentReadCommand, "protocol", 0, "HTTP/0.9"); if (s && *s && strcasecmp(s, "HTTP/0.9")==0) { DBG_INFO(AQH_LOGDOMAIN, "HTTP 0.9, no header, message finished"); _finishMessageAndStartNext(ep); return rv; } DBG_INFO(AQH_LOGDOMAIN, "Command line complete, advancing to header read mode"); xep->readMode=AQH_EndpointHttpd_ReadMode_Headers; xep->currentHeaderPos=GWEN_Buffer_GetPos(xep->currentReadBuffer); } else { DBG_INFO(AQH_LOGDOMAIN, "Line not yet finished (%d)", rv); } return rv; } int _distributeBufferInStatusMode(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen) { int rv; rv=_distributeBufferAsLine(ep, bufferPtr, bufferLen); if (rv<0) { AQH_ENDPOINT_HTTP *xep; /* line complete, TODO: parse status/command line */ rv=-rv; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); DBG_INFO(AQH_LOGDOMAIN, "Line complete, advancing to header read mode"); xep->readMode=AQH_EndpointHttpd_ReadMode_Headers; xep->currentBodyPos=GWEN_Buffer_GetPos(xep->currentReadBuffer); } return rv; } int _distributeBufferInHeaderMode(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen) { int rv; rv=_distributeBufferAsLine(ep, bufferPtr, bufferLen); if (rv<0) { AQH_ENDPOINT_HTTP *xep; int lineLength; /* line complete, TODO: parse status/command line */ rv=-rv; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); lineLength=GWEN_Buffer_GetPos(xep->currentReadBuffer)-xep->lastLineStartPos; xep->lastLineStartPos=GWEN_Buffer_GetPos(xep->currentReadBuffer); if (lineLength==2) { char *copyOfHeader; int contentLength; /* Empty line received, TODO: parse header */ DBG_INFO(AQH_LOGDOMAIN, "Empty header line received, end of header reached."); copyOfHeader=strdup(GWEN_Buffer_GetStart(xep->currentReadBuffer)+xep->currentHeaderPos); xep->dbCurrentReadHeader=GWEN_DB_Group_new("header"); if (_parseHeader(copyOfHeader, xep->dbCurrentReadHeader)<0) { DBG_INFO(AQH_LOGDOMAIN, "Error parsing HTTP header"); free(copyOfHeader); return GWEN_ERROR_BAD_DATA; } free(copyOfHeader); contentLength=GWEN_DB_GetIntValue(xep->dbCurrentReadHeader, "Content-Length", 0, -1); if (contentLength==0 || contentLength==-1) { DBG_INFO(AQH_LOGDOMAIN, "Message has no body, done"); _finishMessageAndStartNext(ep); } else { xep->currentBodyPos=GWEN_Buffer_GetPos(xep->currentReadBuffer); xep->currentBodySize=contentLength; xep->remainingBodySize=contentLength; xep->readMode=AQH_EndpointHttpd_ReadMode_Body; } } } return rv; } int _distributeBufferInBodyMode(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen) { AQH_ENDPOINT_HTTP *xep; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); if (xep->remainingBodySize>0) { int len; len=bufferLen; if (len>xep->remainingBodySize) len=xep->remainingBodySize; if (len) { GWEN_Buffer_AppendBytes(xep->currentReadBuffer, (const char*) bufferPtr, len); xep->remainingBodySize-=len; if (xep->remainingBodySize==0) { DBG_INFO(AQH_LOGDOMAIN, "Body completely received"); _finishMessageAndStartNext(ep); } return len; } else { DBG_ERROR(AQH_LOGDOMAIN, "No bytes left to read, SNH!"); return GWEN_ERROR_INTERNAL; } } else { DBG_ERROR(AQH_LOGDOMAIN, "No body size, aborting"); return GWEN_ERROR_GENERIC; } } /* return negative number of bytes handled when LF encountered (i.e. line done), positive value otherwise */ int _distributeBufferAsLine(GWEN_MSG_ENDPOINT *ep, const uint8_t *bufferPtr, int bufferLen) { AQH_ENDPOINT_HTTP *xep; const char *s; int i; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); s=(const char*) bufferPtr; i=0; while(icurrentReadBuffer, (const char*) bufferPtr, i); if (*s==10) { DBG_DEBUG(AQH_LOGDOMAIN, "Received full line (added %d bytes from %d)", i, bufferLen); i=-i; } return i; } void _finishMessageAndStartNext(GWEN_MSG_ENDPOINT *ep) { AQH_ENDPOINT_HTTP *xep; GWEN_MSG *msg; GWEN_DB_NODE *dbParsedData; DBG_INFO(AQH_LOGDOMAIN, "Message completely received."); xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); msg=GWEN_Msg_fromBytes((const uint8_t*)GWEN_Buffer_GetStart(xep->currentReadBuffer), GWEN_Buffer_GetUsedBytes(xep->currentReadBuffer)); if (xep->currentBodySize) { GWEN_Msg_SetParsedPayloadOffset(msg, xep->currentBodyPos); GWEN_Msg_SetParsedPayloadSize(msg, xep->currentBodySize); } dbParsedData=GWEN_DB_Group_new("parsedData"); if (xep->dbCurrentReadCommand) GWEN_DB_AddGroup(dbParsedData, xep->dbCurrentReadCommand); if (xep->dbCurrentReadHeader) GWEN_DB_AddGroup(dbParsedData, xep->dbCurrentReadHeader); GWEN_Msg_SetDbParsedInfo(msg, dbParsedData); GWEN_MsgEndpoint_AddReceivedMessage(ep, msg); GWEN_Buffer_Reset(xep->currentReadBuffer); xep->currentHeaderPos=0; xep->currentBodyPos=0; xep->currentBodySize=0; xep->remainingBodySize=0; xep->lastLineStartPos=0; xep->dbCurrentReadCommand=NULL; xep->dbCurrentReadHeader=NULL; if (xep->flags & AQH_ENDPOINT_HTTP_FLAGS_PASSIVE) xep->readMode=AQH_EndpointHttpd_ReadMode_Command; else xep->readMode=AQH_EndpointHttpd_ReadMode_Status; } void _abortMessage(GWEN_MSG_ENDPOINT *ep) { AQH_ENDPOINT_HTTP *xep; DBG_INFO(AQH_LOGDOMAIN, "Message completely received."); xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_ENDPOINT_HTTP, ep); GWEN_Buffer_Reset(xep->currentReadBuffer); xep->currentHeaderPos=0; xep->currentBodyPos=0; xep->currentBodySize=0; xep->remainingBodySize=0; xep->lastLineStartPos=0; if (xep->dbCurrentReadCommand) GWEN_DB_Group_free(xep->dbCurrentReadCommand); xep->dbCurrentReadCommand=NULL; if (xep->dbCurrentReadHeader) GWEN_DB_Group_free(xep->dbCurrentReadHeader); xep->dbCurrentReadHeader=NULL; xep->readMode=AQH_EndpointHttpd_ReadMode_Aborted; } int _parseHeader(char *bufferPtr, GWEN_DB_NODE *db) { char *p; /* resolve line continuations */ p=bufferPtr; while (*p) { p=strchr(p, 10); if (p) { if (p[1]==32 || p[1]==9) /* found a continuation */ *p=32; p++; } } /* parse every line */ p=bufferPtr; while (p && *p) { char *pNext; char *pVarBegin; char *pVarEnd; /* find end of line */ pNext=strchr(p, 13); if (pNext) { *pNext=0; pNext++; } /* skip LF */ if (*p==10) p++; while (*p && (*p==32 || *p==9)) p++; if (*p) { pVarBegin=p; while (*p && *p!=':' && *p>32 && *p<127) p++; pVarEnd=p; if (*p!=':') { DBG_INFO(AQH_LOGDOMAIN, "No separator after variable name in received header"); return GWEN_ERROR_BAD_DATA; } *pVarEnd=0; p++; while (*p && (*p==32 || *p==9)) p++; if (*p) { DBG_ERROR(AQH_LOGDOMAIN, "Setting header variable: [%s] = [%s]", pVarBegin, p); GWEN_DB_SetCharValue(db, GWEN_PATH_FLAGS_CREATE_VAR, pVarBegin, p); } } p=pNext; } return 0; } int _parseCommand(const char *buffer, GWEN_DB_NODE *db) { char *tmp; char *p; char *s; tmp=strdup(buffer); /* get end of line (marked by CR/LF) */ s=strchr(tmp, 13); if (s) *s=0; s=tmp; /* read command */ p=strchr(s, ' '); if (!p) { DBG_ERROR(AQH_LOGDOMAIN, "Bad format of HTTP request (%s)", buffer); free(tmp); return GWEN_ERROR_BAD_DATA; } *p=0; p++; GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "command", s); s=p; /* read URL */ p=strchr(s, ' '); if (!p) { DBG_ERROR(AQH_LOGDOMAIN, "Bad format of HTTP request (%s)", buffer); free(tmp); return GWEN_ERROR_BAD_DATA; } *p=0; p++; GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "url", s); s=p; if (*s==0) { /* no protocol information follows, so we assume HTTP/0.9 */ DBG_ERROR(AQH_LOGDOMAIN, "Bad request (not in HTTP>=1.0)"); free(tmp); return GWEN_ERROR_BAD_DATA; } else { GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "protocol", s); } free(tmp); return 0; }