diff --git a/web-server/web-server.c b/web-server/web-server.c index c1bfb6f..4689014 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -8,14 +8,31 @@ #include "cmd.h" #include "serbridge.h" +// the file is responsible for handling user defined web-pages +// - collects HTML files from user image, shows them on the left frame +// - handles JSON data coming from the browser +// - handles SLIP messages coming from MCU + #define WEB_CB "webCb" +#define MAX_ARGUMENT_BUFFER_SIZE 1024 -#define MAX_VARS 20 +struct ArgumentBuffer +{ + char argBuffer[MAX_ARGUMENT_BUFFER_SIZE]; + int argBufferPtr; + int numberOfArgs; +}; static char* web_server_reasons[] = { - "load", "refresh", "button", "submit" + "load", // readable name for RequestReason::LOAD + "refresh", // readable name for RequestReason::REFRESH + "button", // readable name for RequestReason::BUTTON + "submit" // readable name for RequestReason::SUBMIT }; +// this variable contains the names of the user defined pages +// this information appears at the left frame below of the built in URL-s +// format: ,"UserPage1", "/UserPage1.html", "UserPage2", "/UserPage2.html", char * webServerPages = NULL; char * ICACHE_FLASH_ATTR WEB_UserPages() @@ -23,6 +40,7 @@ char * ICACHE_FLASH_ATTR WEB_UserPages() return webServerPages; } +// generates the content of webServerPages variable (called at booting/web page uploading) void ICACHE_FLASH_ATTR WEB_BrowseFiles() { char buffer[1024]; @@ -34,37 +52,35 @@ void ICACHE_FLASH_ATTR WEB_BrowseFiles() espFsIteratorInit(userPageCtx, &it); while( espFsIteratorNext(&it) ) { - int nlen = os_strlen(it.name); - if( nlen >= 6 ) + int nameLen = os_strlen(it.name); + if( nameLen >= 6 ) { - if( os_strcmp( it.name + nlen-5, ".html" ) == 0 ) + // fetch HTML files + if( os_strcmp( it.name + nameLen-5, ".html" ) == 0 ) { - char sh_name[17]; + int slashPos = nameLen - 5; - int spos = nlen-5; - - while( spos > 0 ) - { - if( it.name[spos+1] == '/' ) - break; - spos--; - } + // chop path and .html from the name + while( slashPos > 0 && it.name[slashPos-1] != '/' ) + slashPos--; - int ps = nlen-5-spos; - if( ps > 16 ) - ps = 16; - os_memcpy(sh_name, it.name + spos, ps); - sh_name[ps] = 0; + // here we check buffer overrun + int maxLen = 10 + os_strlen( it.name ) + (nameLen - slashPos -5); + if( maxLen >= sizeof(buffer) ) + break; os_strcat(buffer, ", \""); - os_strcat(buffer, sh_name); + + int writePos = os_strlen(buffer); + for( int i=slashPos; i < nameLen-5; i++ ) + buffer[writePos++] = it.name[i]; + buffer[writePos] = 0; // terminating zero + os_strcat(buffer, "\", \"/"); os_strcat(buffer, it.name); os_strcat(buffer, "\""); } } - if( os_strlen(buffer) > 600 ) - break; } } @@ -76,6 +92,7 @@ void ICACHE_FLASH_ATTR WEB_BrowseFiles() os_memcpy( webServerPages, buffer, len ); } +// initializer void ICACHE_FLASH_ATTR WEB_Init() { espFsInit(userPageCtx, (void *)getUserPageSectionStart(), ESPFS_FLASH); @@ -83,288 +100,339 @@ void ICACHE_FLASH_ATTR WEB_Init() os_printf("Valid user file system found!\n"); else os_printf("No user file system found!\n"); - WEB_BrowseFiles(); + WEB_BrowseFiles(); // collect user defined HTML files } -int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) +// initializes the argument buffer +static void WEB_argInit(struct ArgumentBuffer * argBuffer) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + argBuffer->numberOfArgs = 0; + argBuffer->argBufferPtr = 0; +} + +// adds an argument to the argument buffer (returns 0 if successful) +static int WEB_addArg(struct ArgumentBuffer * argBuffer, char * arg, int argLen ) +{ + if( argBuffer->argBufferPtr + argLen + sizeof(int) >= MAX_ARGUMENT_BUFFER_SIZE ) + return -1; // buffer overflow - void * cgiData = connData->cgiData; + os_memcpy(argBuffer->argBuffer + argBuffer->argBufferPtr, &argLen, sizeof(int)); - if( cgiData == NULL ) + if( argLen != 0 ) { - if( !flashConfig.slip_enable ) - { - errorResponse(connData, 400, "Slip processing is disabled!"); - return HTTPD_CGI_DONE; - } - CmdCallback* cb = cmdGetCbByName( WEB_CB ); - if( cb == NULL ) - { - errorResponse(connData, 500, "No MCU callback is registered!"); - return HTTPD_CGI_DONE; - } - if( serbridgeInProgramming() ) - { - errorResponse(connData, 500, "Slip disabled at programming mode!"); - return HTTPD_CGI_DONE; - } - - char reasonBuf[16]; - int i; - int len = httpdFindArg(connData->getArgs, "reason", reasonBuf, sizeof(reasonBuf)); - if( len < 0 ) - { - errorResponse(connData, 400, "No reason specified!"); - return HTTPD_CGI_DONE; - } - - RequestReason reason = INVALID; - for(i=0; i < sizeof(web_server_reasons)/sizeof(char *); i++) - { - if( os_strcmp( web_server_reasons[i], reasonBuf ) == 0 ) - reason = (RequestReason)i; - } + os_memcpy( argBuffer->argBuffer + argBuffer->argBufferPtr + sizeof(int), arg, argLen ); + argBuffer->numberOfArgs++; + } + + argBuffer->argBufferPtr += argLen + sizeof(int); + return 0; +} + +// creates and sends a SLIP message from the argument buffer +static void WEB_sendArgBuffer(struct ArgumentBuffer * argBuffer, HttpdConnData *connData, int id, RequestReason reason) +{ + cmdResponseStart(CMD_WEB_REQ_CB, id, 4 + argBuffer->numberOfArgs); + uint16_t r = (uint16_t)reason; + cmdResponseBody(&r, sizeof(uint16_t)); // 1st argument: reason + cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); // 2nd argument: IP + cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); // 3rd argument: port + cmdResponseBody(connData->url, os_strlen(connData->url)); // 4th argument: URL + + int p = 0; + for( int j=0; j < argBuffer->numberOfArgs; j++ ) + { + int argLen; + os_memcpy( &argLen, argBuffer->argBuffer + p, sizeof(int) ); - if( reason == INVALID ) - { - errorResponse(connData, 400, "Invalid reason!"); + char * arg = argBuffer->argBuffer + p + sizeof(int); + cmdResponseBody(arg, argLen); + p += argLen + sizeof(int); + } + + cmdResponseEnd(); +} + +// this method processes SLIP data from MCU and converts to JSON +// this method receives JSON from the browser, sends SLIP data to MCU +static int ICACHE_FLASH_ATTR WEB_handleJSONRequest(HttpdConnData *connData) +{ + if( !flashConfig.slip_enable ) + { + errorResponse(connData, 400, "Slip processing is disabled!"); + return HTTPD_CGI_DONE; + } + CmdCallback* cb = cmdGetCbByName( WEB_CB ); + if( cb == NULL ) + { + errorResponse(connData, 500, "No MCU callback is registered!"); + return HTTPD_CGI_DONE; + } + if( serbridgeInProgramming() ) + { + errorResponse(connData, 500, "Slip disabled at uploading program onto the MCU!"); + return HTTPD_CGI_DONE; + } + + char reasonBuf[16]; + int i; + int len = httpdFindArg(connData->getArgs, "reason", reasonBuf, sizeof(reasonBuf)); + if( len < 0 ) + { + errorResponse(connData, 400, "No reason specified!"); return HTTPD_CGI_DONE; - } - - char body[1024]; - int bodyPtr = 0; - int argNum = 0; - char *argPos[MAX_VARS]; - int argLen[MAX_VARS]; - - switch(reason) - { - case BUTTON: - argLen[0] = httpdFindArg(connData->getArgs, "id", body, sizeof(body)); - if( argLen[0] <= 0 ) + } + + RequestReason reason = INVALID; + for(i=0; i < sizeof(web_server_reasons)/sizeof(char *); i++) + { + if( os_strcmp( web_server_reasons[i], reasonBuf ) == 0 ) + reason = (RequestReason)i; + } + + if( reason == INVALID ) + { + errorResponse(connData, 400, "Invalid reason!"); + return HTTPD_CGI_DONE; + } + + struct ArgumentBuffer argBuffer; + WEB_argInit( &argBuffer ); + + switch(reason) + { + case BUTTON: + { + char id_buf[40]; + + int id_len = httpdFindArg(connData->getArgs, "id", id_buf, sizeof(id_buf)); + if( id_len <= 0 ) { errorResponse(connData, 400, "No button ID specified!"); return HTTPD_CGI_DONE; } - argPos[0] = body; - argNum++; - break; - case SUBMIT: + if( WEB_addArg(&argBuffer, id_buf, id_len) ) { - if( connData->post->received < connData->post->len ) + errorResponse(connData, 400, "Post too large!"); + return HTTPD_CGI_DONE; + } + } + break; + case SUBMIT: + { + if( connData->post->received < connData->post->len ) + { + errorResponse(connData, 400, "Post too large!"); + return HTTPD_CGI_DONE; + } + + int bptr = 0; + + while( bptr < connData->post->len ) + { + char * line = connData->post->buff + bptr; + + char * eo = os_strchr(line, '&' ); + if( eo != NULL ) { - errorResponse(connData, 400, "Post too large!"); - return HTTPD_CGI_DONE; + *eo = 0; + bptr = eo - connData->post->buff + 1; + } + else + { + eo = line + os_strlen( line ); + bptr = connData->post->len; } - int bptr = 0; + int len = os_strlen(line); + while( len >= 1 && ( line[len-1] == '\r' || line[len-1] == '\n' )) + len--; + line[len] = 0; - while( bptr < connData->post->len ) + char * val = os_strchr(line, '='); + if( val != NULL ) { - if( argNum >= MAX_VARS ) - { - errorResponse(connData, 400, "Too many variables!"); - return HTTPD_CGI_DONE; - } + *val = 0; + char * name = line; + int vblen = os_strlen(val+1) * 2; + char value[vblen]; + httpdUrlDecode(val+1, strlen(val+1), value, vblen); - char * line = connData->post->buff + bptr; + int namLen = os_strlen(name); + int valLen = os_strlen(value); - char * eo = os_strchr(line, '&' ); - if( eo != NULL ) - { - *eo = 0; - bptr = eo - connData->post->buff + 1; - } - else - { - eo = line + os_strlen( line ); - bptr = connData->post->len; - } - - int len = os_strlen(line); - while( len >= 1 && ( line[len-1] == '\r' || line[len-1] == '\n' )) - len--; - line[len] = 0; + char arg[namLen + valLen + 3]; + int argPtr = 0; + arg[argPtr++] = (char)WEB_STRING; + os_strcpy( arg + argPtr, name ); + argPtr += namLen; + arg[argPtr++] = 0; + os_strcpy( arg + argPtr, value ); + argPtr += valLen; - char * val = os_strchr(line, '='); - if( val != NULL ) + if( WEB_addArg(&argBuffer, arg, argPtr) ) { - *val = 0; - char * name = line; - int vblen = os_strlen(val+1) * 2; - char value[vblen]; - httpdUrlDecode(val+1, strlen(val+1), value, vblen); - - int namLen = os_strlen(name); - int valLen = os_strlen(value); - - int totallen = namLen + valLen + 2; - if( bodyPtr + totallen > sizeof(body) - 10 ) - { - errorResponse(connData, 400, "Post too large!"); - return HTTPD_CGI_DONE; - } - - argPos[argNum] = body + bodyPtr; - - body[bodyPtr++] = (char)WEB_STRING; - os_strcpy( body + bodyPtr, name ); - bodyPtr += namLen; - body[bodyPtr++] = 0; - - os_strcpy( body + bodyPtr, value ); - bodyPtr += valLen; - - argLen[argNum++] = totallen; + errorResponse(connData, 400, "Post too large!"); + return HTTPD_CGI_DONE; } } } - break; - case LOAD: - case REFRESH: - default: - break; - } + } + break; + case LOAD: + case REFRESH: + default: + break; + } + + if( WEB_addArg(&argBuffer, NULL, 0) ) + { + errorResponse(connData, 400, "Post too large!"); + return HTTPD_CGI_DONE; + } + + os_printf("Web callback to MCU: %s\n", reasonBuf); + + WEB_sendArgBuffer(&argBuffer, connData, (uint32_t)cb->callback, reason ); + + if( reason == SUBMIT ) + { + httpdStartResponse(connData, 204); + httpdEndHeaders(connData); + return HTTPD_CGI_DONE; + } + + return HTTPD_CGI_MORE; +} + +// this method receives SLIP data from MCU sends JSON to the browser +static int ICACHE_FLASH_ATTR WEB_handleMCUResponse(HttpdConnData *connData, CmdRequest * response) +{ + char jsonBuf[1500]; + int jsonPtr = 0; + + + jsonBuf[jsonPtr++] = '{'; + + int c = 2; + while( c++ < cmdGetArgc(response) ) + { + int len = cmdArgLen(response); + char buf[len+1]; + buf[len] = 0; - os_printf("Web callback to MCU: %s\n", reasonBuf); + cmdPopArg(response, buf, len); - cmdResponseStart(CMD_WEB_REQ_CB, (uint32_t)cb->callback, 4 + argNum); - uint16_t r = (uint16_t)reason; - cmdResponseBody(&r, sizeof(uint16_t)); - cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); - cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); - cmdResponseBody(connData->url, os_strlen(connData->url)); + if(len == 0) + break; // last argument - int j; - for( j=0; j < argNum; j++ ) - cmdResponseBody(argPos[j], argLen[j]); + if( c > 3 ) // skip the first argument + jsonBuf[jsonPtr++] = ','; - cmdResponseEnd(); - - if( reason == SUBMIT ) + if( jsonPtr + 20 + len > sizeof(jsonBuf) ) { - httpdStartResponse(connData, 204); - httpdEndHeaders(connData); + errorResponse(connData, 500, "Response too large!"); return HTTPD_CGI_DONE; } - connData->cgiData = (void *)1; - } - - if( connData->cgiResponse != NULL ) // data from MCU - { - char jsonBuf[1500]; - int jsonPtr = 0; + WebValueType type = (WebValueType)buf[0]; + int nameLen = os_strlen(buf+1); + jsonBuf[jsonPtr++] = '"'; + os_memcpy(jsonBuf + jsonPtr, buf + 1, nameLen); + jsonPtr += nameLen; + jsonBuf[jsonPtr++] = '"'; + jsonBuf[jsonPtr++] = ':'; - jsonBuf[jsonPtr++] = '{'; - CmdRequest * req = (CmdRequest *)(connData->cgiResponse); + char * value = buf + 2 + nameLen; - int c = 2; - while( c++ < cmdGetArgc(req) ) + switch(type) { - int len = cmdArgLen(req); - char buf[len+1]; - buf[len] = 0; - - cmdPopArg(req, buf, len); - - if(len == 0) - break; // last argument - - if( c > 3 ) // skip the first argument - jsonBuf[jsonPtr++] = ','; - - if( jsonPtr + 20 + len > sizeof(jsonBuf) ) - { - errorResponse(connData, 500, "Response too large!"); - return HTTPD_CGI_DONE; - } - - WebValueType type = (WebValueType)buf[0]; - - int nameLen = os_strlen(buf+1); - jsonBuf[jsonPtr++] = '"'; - os_memcpy(jsonBuf + jsonPtr, buf + 1, nameLen); - jsonPtr += nameLen; - jsonBuf[jsonPtr++] = '"'; - jsonBuf[jsonPtr++] = ':'; - - char * value = buf + 2 + nameLen; - - switch(type) - { - case WEB_NULL: - os_memcpy(jsonBuf + jsonPtr, "null", 4); + case WEB_NULL: + os_memcpy(jsonBuf + jsonPtr, "null", 4); + jsonPtr += 4; + break; + case WEB_INTEGER: + { + int v; + os_memcpy( &v, value, 4); + + char intbuf[20]; + os_sprintf(intbuf, "%d", v); + os_strcpy(jsonBuf + jsonPtr, intbuf); + jsonPtr += os_strlen(intbuf); + } + break; + case WEB_BOOLEAN: + if( *value ) { + os_memcpy(jsonBuf + jsonPtr, "true", 4); jsonPtr += 4; - break; - case WEB_INTEGER: - { - int v; - os_memcpy( &v, value, 4); - - char intbuf[20]; - os_sprintf(intbuf, "%d", v); - os_strcpy(jsonBuf + jsonPtr, intbuf); - jsonPtr += os_strlen(intbuf); - } - break; - case WEB_BOOLEAN: - if( *value ) { - os_memcpy(jsonBuf + jsonPtr, "true", 4); - jsonPtr += 4; - } else { - os_memcpy(jsonBuf + jsonPtr, "false", 5); - jsonPtr += 5; - } - break; - case WEB_FLOAT: - { - float f; - os_memcpy( &f, value, 4); - - char intbuf[20]; - os_sprintf(intbuf, "%f", f); - os_strcpy(jsonBuf + jsonPtr, intbuf); - jsonPtr += os_strlen(intbuf); - } - break; - case WEB_STRING: - jsonBuf[jsonPtr++] = '"'; - while(*value) - { - if( *value == '\\' || *value == '"' ) - jsonBuf[jsonPtr++] = '\\'; - jsonBuf[jsonPtr++] = *(value++); - } - jsonBuf[jsonPtr++] = '"'; - break; - case WEB_JSON: - os_memcpy(jsonBuf + jsonPtr, value, len - 2 - nameLen); - jsonPtr += len - 2 - nameLen; - break; - } + } else { + os_memcpy(jsonBuf + jsonPtr, "false", 5); + jsonPtr += 5; + } + break; + case WEB_FLOAT: + { + float f; + os_memcpy( &f, value, 4); + + char intbuf[20]; + os_sprintf(intbuf, "%f", f); + os_strcpy(jsonBuf + jsonPtr, intbuf); + jsonPtr += os_strlen(intbuf); + } + break; + case WEB_STRING: + jsonBuf[jsonPtr++] = '"'; + while(*value) + { + if( *value == '\\' || *value == '"' ) + jsonBuf[jsonPtr++] = '\\'; + jsonBuf[jsonPtr++] = *(value++); + } + jsonBuf[jsonPtr++] = '"'; + break; + case WEB_JSON: + os_memcpy(jsonBuf + jsonPtr, value, len - 2 - nameLen); + jsonPtr += len - 2 - nameLen; + break; } - - jsonBuf[jsonPtr++] = '}'; - - noCacheHeaders(connData, 200); - httpdHeader(connData, "Content-Type", "application/json"); - - char cl[16]; - os_sprintf(cl, "%d", jsonPtr); - httpdHeader(connData, "Content-Length", cl); - httpdEndHeaders(connData); - - httpdSend(connData, jsonBuf, jsonPtr); - return HTTPD_CGI_DONE; } + jsonBuf[jsonPtr++] = '}'; + + noCacheHeaders(connData, 200); + httpdHeader(connData, "Content-Type", "application/json"); + + char cl[16]; + os_sprintf(cl, "%d", jsonPtr); + httpdHeader(connData, "Content-Length", cl); + httpdEndHeaders(connData); + + httpdSend(connData, jsonBuf, jsonPtr); + return HTTPD_CGI_DONE; +} + +// this method is responsible for the MCU <==JSON==> Browser communication +int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) +{ + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + void * cgiData = connData->cgiData; + + if( cgiData == NULL ) + { + connData->cgiData = (void *)1; // indicate, that request was processed + return WEB_handleJSONRequest(connData); + } + + if( connData->cgiResponse != NULL ) // data from MCU + return WEB_handleMCUResponse(connData, (CmdRequest *)(connData->cgiResponse)); + return HTTPD_CGI_MORE; } +// this method is called when MCU transmits WEB_DATA command void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd) { CmdRequest req; @@ -373,14 +441,14 @@ void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd) if (cmdGetArgc(&req) < 2) return; uint8_t ip[4]; - cmdPopArg(&req, ip, 4); + cmdPopArg(&req, ip, 4); // pop the IP address uint16_t port; - cmdPopArg(&req, &port, 2); + cmdPopArg(&req, &port, 2); // pop the HTTP port - HttpdConnData * conn = httpdLookUpConn(ip, port); - if( conn != NULL && conn->cgi == WEB_CgiJsonHook ) // make sure that the right CGI handler will be called + HttpdConnData * conn = httpdLookUpConn(ip, port); // look up connection based on IP/port + if( conn != NULL && conn->cgi == WEB_CgiJsonHook ) // make sure that the right CGI handler is configured httpdSetCGIResponse( conn, &req ); else - os_printf("WEB response ignored as no valid http connection found for the request!\n"); + os_printf("WEB_DATA ignored as no valid HTTP connection found!\n"); } diff --git a/web-server/web-server.h b/web-server/web-server.h index 2e3413a..0827229 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -8,22 +8,22 @@ typedef enum { - LOAD=0, - REFRESH, - BUTTON, - SUBMIT, + LOAD=0, // loading web-page content at the first time + REFRESH, // loading web-page subsequently + BUTTON, // HTML button pressed + SUBMIT, // HTML form is submitted INVALID=-1, } RequestReason; typedef enum { - WEB_STRING=0, - WEB_NULL, - WEB_INTEGER, - WEB_BOOLEAN, - WEB_FLOAT, - WEB_JSON + WEB_STRING=0, // the value is string + WEB_NULL, // the value is NULL + WEB_INTEGER, // the value is integer + WEB_BOOLEAN, // the value is boolean + WEB_FLOAT, // the value is float + WEB_JSON // the value is JSON data } WebValueType; void WEB_Init();