#include "../new_common.h" #include "../logging/logging.h" #include "ctype.h" #include "new_http.h" #include "http_fns.h" #include "../new_pins.h" #include "../new_cfg.h" #include "../ota/ota.h" #include "../hal/hal_wifi.h" #include "../base64/base64.h" #include "http_basic_auth.h" // define the feature ADDLOGF_XXX will use #define LOG_FEATURE LOG_FEATURE_HTTP const char httpHeader[] = "HTTP/1.1 %d OK\nContent-type: %s"; // HTTP header const char httpMimeTypeHTML[] = "text/html"; // HTML MIME type const char httpMimeTypeText[] = "text/plain"; // TEXT MIME type const char httpMimeTypeXML[] = "text/xml"; // TEXT MIME type const char httpMimeTypeCSS[] = "text/css"; // CSS MIME type const char httpMimeTypeJavascript[] = "application/javascript"; // NOTE: According to RFC 4329 text/javascript became obsolete see: https://www.rfc-editor.org/rfc/rfc4329.html#section-7.2 const char httpMimeTypeJson[] = "application/json"; // TEXT MIME type const char httpMimeTypeBinary[] = "application/octet-stream"; // binary/file MIME type const char htmlShortcutIcon[] = ""; const char htmlDoctype[] = ""; const char htmlHeadMeta[] = "" "" ""; const char htmlBodyStart[] = "" "" "
" "

" ""; const char htmlBodyStart2[] = "

"; const char htmlBodyEnd[] = "
"; const char htmlFooterReturnToMainPage[] = "Home"; const char htmlFooterRefreshLink[] = "Refresh"; const char htmlFooterReturnToCfgOrMainPage[] = "Return to Config | " "Home"; const char htmlFooterInfo[] = "Forum | " "YT | " "Devices List | " "Docs | " "Support Project
"; const char* g_build_str = "Built on " __DATE__ " " __TIME__ " version " USER_SW_VER; // Show GIT version at Build line; const char httpCorsHeaders[] = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept"; // TEXT MIME type int g_indexAutoRefreshInterval = 1000; // 1s const char* methodNames[] = { "GET", "PUT", "POST", "OPTIONS" }; #if WINDOWS #define os_free free #define os_malloc malloc #endif void misc_formatUpTimeString(int totalSeconds, char* o); typedef struct http_callback_tag { char* url; int method; http_callback_fn callback; int auth_required; } http_callback_t; #define MAX_HTTP_CALLBACKS 32 static http_callback_t* callbacks[MAX_HTTP_CALLBACKS]; static int numCallbacks = 0; int HTTP_RegisterCallback(const char* url, int method, http_callback_fn callback, int auth_required) { int i; if (!url || !callback) { return -1; } if (numCallbacks >= MAX_HTTP_CALLBACKS) { return -4; } for (i = 0; i < MAX_HTTP_CALLBACKS; i++) { if (callbacks[i]) { if (callbacks[i]->callback == callback && !strcmp(callbacks[i]->url, url) && callbacks[i]->method == method) { return i; } } } callbacks[numCallbacks] = (http_callback_t*)os_malloc(sizeof(http_callback_t)); if (!callbacks[numCallbacks]) { return -2; } callbacks[numCallbacks]->url = (char*)os_malloc(strlen(url) + 1); if (!callbacks[numCallbacks]->url) { os_free(callbacks[numCallbacks]); return -3; } strcpy(callbacks[numCallbacks]->url, url); callbacks[numCallbacks]->callback = callback; callbacks[numCallbacks]->method = method; callbacks[numCallbacks]->auth_required = auth_required > 0 ? 1 : 0; numCallbacks++; // success return 0; } int my_strnicmp(const char* a, const char* b, int len) { int i; for (i = 0; i < len; i++) { char x = *a; char y = *b; if (!x || !y) return 1; if ((x | 0x20) != (y | 0x20)) return 1; a++; b++; } return 0; } /// @brief Write escaped data to the response. /// @param request /// @param str void poststr_escaped(http_request_t* request, char* str) { if (str == NULL) { postany(request, NULL, 0); return; } int i; bool foundChar = false; int len = strlen(str); // Do a quick check if escaping is necessary for (i = 0; (foundChar == false) && (i < len); i++) { switch (str[i]) { case '<': foundChar = true; break; case '>': foundChar = true; break; case '&': foundChar = true; break; case '"': foundChar = true; break; } } if (foundChar) { for (i = 0; i < len; i++) { switch (str[i]) { case '<': postany(request, "<", 4); break; case '>': postany(request, ">", 4); break; case '&': postany(request, "&", 5); break; case '"': postany(request, """, 6); break; default: postany(request, str + i, 1); break; } } } else { postany(request, str, strlen(str)); } } void poststr_escapedForJSON(http_request_t* request, char* str) { if (str == NULL) { postany(request, NULL, 0); return; } int i; bool foundChar = false; int len = strlen(str); // Do a quick check if escaping is necessary for (i = 0; (foundChar == false) && (i < len); i++) { switch (str[i]) { case '\n': foundChar = true; break; case '\r': foundChar = true; break; case '\"': foundChar = true; break; } } if (foundChar) { for (i = 0; i < len; i++) { switch (str[i]) { case '\n': postany(request, "\\n", 2); break; case '\r': postany(request, "\\r", 2); break; case '\"': postany(request, "\\\"", 2); break; default: postany(request, str + i, 1); break; } } } else { postany(request, str, strlen(str)); } } bool http_startsWith(const char* base, const char* substr) { while (*substr != 0) { if (*base != *substr) return false; if (*base == 0) return false; base++; substr++; } return true; } bool http_checkUrlBase(const char* base, const char* fileName) { while (*base != 0 && *base != '?' && *base != ' ') { if (*base != *fileName) return false; if (*base == 0) return false; base++; fileName++; } if (*fileName != 0) return false; return true; } void http_setup(http_request_t* request, const char* type) { hprintf255(request, httpHeader, request->responseCode, type); poststr(request, "\r\n"); // next header poststr(request, httpCorsHeaders); #if 0 poststr(request, "Server: Tasmota/10.1.0 (ESP8266EX)"); poststr(request, "\r\n"); poststr(request, "Cache-Control: no-cache, no-store, must-revalidate"); poststr(request, "\r\n"); poststr(request, "Pragma: no-cache"); poststr(request, "\r\n"); poststr(request, "Expires: -1"); poststr(request, "\r\n"); poststr(request, "Accept-Ranges: none"); poststr(request, "\r\n"); poststr(request, "Transfer-Encoding: chunked"); #endif poststr(request, "\r\n"); poststr(request, "Connection: close"); poststr(request, "\r\n"); // end headers with double CRLF poststr(request, "\r\n"); } void http_setup_gz(http_request_t* request, const char* type) { hprintf255(request, httpHeader, request->responseCode, type); poststr(request, "\r\n"); // next header poststr(request, httpCorsHeaders); poststr(request, "\r\n"); poststr(request, "Content-Encoding: gzip"); poststr(request, "\r\n"); poststr(request, "Connection: close"); poststr(request, "\r\n"); // end headers with double CRLF poststr(request, "\r\n"); } void http_html_start(http_request_t* request, const char* pagename) { poststr(request, htmlDoctype); poststr(request, ""); poststr(request, CFG_GetDeviceName()); if (pagename) { hprintf255(request, " - %s", pagename); } poststr(request, ""); poststr(request, htmlShortcutIcon); poststr(request, htmlHeadMeta); poststr(request, htmlHeadStyle); poststr(request, ""); poststr(request, htmlBodyStart); poststr(request, CFG_GetDeviceName()); poststr(request, htmlBodyStart2); } const char pageScriptPart1[] = ""; void http_html_end(http_request_t* request) { char upTimeStr[128]; unsigned char mac[32]; poststr(request, " | "); poststr(request, htmlFooterInfo); poststr(request, "
"); poststr(request, g_build_str); hprintf255(request, "
Online for -", g_secondsElapsed); WiFI_GetMacAddress((char*)mac); snprintf(upTimeStr, sizeof(upTimeStr), "
Device MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); poststr(request, upTimeStr); snprintf(upTimeStr, sizeof(upTimeStr), "
Short name: %s, Chipset %s", CFG_GetShortDeviceName(), PLATFORM_MCU_NAME); poststr(request, upTimeStr); #ifdef PLATFORM_ESPIDF snprintf(upTimeStr, sizeof(upTimeStr), " ESP-IDF %s", esp_get_idf_version()); poststr(request, upTimeStr); #endif poststr(request, htmlBodyEnd); poststr(request, pageScriptPart1); hprintf255(request, "%i", g_indexAutoRefreshInterval); poststr(request, pageScriptPart2); hprintf255(request, "%i", g_indexAutoRefreshInterval); poststr(request, pageScriptPart3); } const char* http_checkArg(const char* p, const char* n) { while (1) { if (*n == 0 && (*p == 0 || *p == '=')) return p; if (*p != *n) return 0; p++; n++; } return p; } int http_copyCarg(const char* atin, char* to, int maxSize) { int a, b; int realSize; const unsigned char* at = (unsigned char*)atin; realSize = 0; while (*at != 0 && *at != '&' && *at != ' ') { #if 0 * to = *at; to++; at++; maxSize--; #else if ((*at == '%') && ((a = at[1]) && (b = at[2])) && (isxdigit(a) && isxdigit(b))) { if (a >= 'a') a -= 'a' - 'A'; if (a >= 'A') a -= ('A' - 10); else a -= '0'; if (b >= 'a') b -= 'a' - 'A'; if (b >= 'A') b -= ('A' - 10); else b -= '0'; // can we afford to place this char in the target? if (maxSize > 1) { maxSize--; *to++ = 16 * a + b; } realSize++; at += 3; } else if (*at == '+') { // can we afford to place this char in the target? if (maxSize > 1) { maxSize--; *to++ = ' '; } realSize++; at++; } else { // can we afford to place this char in the target? if (maxSize > 1) { maxSize--; *to++ = *at; } realSize++; at++; } #endif } *to = 0; return realSize; } int http_getRawArg(const char* base, const char* name, char* o, int maxSize) { *o = '\0'; while (*base) { const char* at = http_checkArg(base, name); if (at) { at++; return http_copyCarg(at, o, maxSize); } while (*base != '&') { if (*base == 0) { return 0; } base++; } base++; } return 0; } int http_getArg(const char* base, const char* name, char* o, int maxSize) { *o = '\0'; while (*base != '?') { if (*base == '\0') return 0; base++; } base++; return http_getRawArg(base, name, o, maxSize); } int http_getArgInteger(const char* base, const char* name) { char tmp[16]; if (http_getArg(base, name, tmp, sizeof(tmp)) == 0) return 0; return atoi(tmp); } const char* htmlPinRoleNames[] = { " ", "Rel", "Rel_n", "Btn", "Btn_n", "LED", "LED_n", "PWM", "WifiLED", "WifiLED_n", "Btn_Tgl_All", "Btn_Tgl_All_n", "dInput", "dInput_n", "TglChanOnTgl", "dInput_NoPullUp", "dInput_NoPullUp_n", "BL0937SEL", "BL0937CF", "BL0937CF1", "ADC", "SM2135DAT", "SM2135CLK", "BP5758D_DAT", "BP5758D_CLK", "BP1658CJ_DAT", "BP1658CJ_CLK", "PWM_n", "IRRecv", "IRSend", "Btn_NextColor", "Btn_NextColor_n", "Btn_NextDimmer", "Btn_NextDimmer_n", "AlwaysHigh", "AlwaysLow", "UCS1912_DIN", "SM16703P_DIN", "Btn_NextTemperature", "Btn_NextTemperature_n", "Btn_ScriptOnly", "Btn_ScriptOnly_n", "DHT11", "DHT12", "DHT21", "DHT22", "CHT83XX_SDA", "CHT83XX_SCK", "SHT3X_SDA", "SHT3X_SCK", "SoftSDA", "SoftSCL", "SM2235DAT", "SM2235CLK", "BridgeFWD", "BridgeREV", "Btn_SmartLED", "Btn_SmartLED_n", "DoorSnsrWSleep", "DoorSnsrWSleep_nPup", "BAT_ADC", "BAT_Relay", "TM1637_DIO", "TM1637_CLK", "BL0937SEL_n", "DoorSnsrWSleep_pd", "SGP_CLK", "SGP_DAT", "ADC_Button", "GN6932_CLK", "GN6932_DAT", "GN6932_STB", "TM1638_CLK", "TM1638_DAT", "TM1638_STB", "BAT_Relay_n", "KP18058_CLK", "KP18058_DAT", "DS1820_IO", "PWM_ScriptOnly", "PWM_ScriptOnly_n", "error", "error", "error", "error", }; const char* PIN_RoleToString(int role) { return htmlPinRoleNames[role]; } int PIN_ParsePinRoleName(const char* name) { int i; if (!stricmp(name, "None")) { return IOR_None; } for (i = 0; i < IOR_Total_Options; i++) { if (!stricmp(name, htmlPinRoleNames[i])) return i; } return IOR_Total_Options; } void setupAllWB2SPinsAsButtons() { PIN_SetPinRoleForPinIndex(6, IOR_Button); PIN_SetPinChannelForPinIndex(6, 1); PIN_SetPinRoleForPinIndex(7, IOR_Button); PIN_SetPinChannelForPinIndex(7, 1); PIN_SetPinRoleForPinIndex(8, IOR_Button); PIN_SetPinChannelForPinIndex(8, 1); PIN_SetPinRoleForPinIndex(23, IOR_Button); PIN_SetPinChannelForPinIndex(23, 1); PIN_SetPinRoleForPinIndex(24, IOR_Button); PIN_SetPinChannelForPinIndex(24, 1); PIN_SetPinRoleForPinIndex(26, IOR_Button); PIN_SetPinChannelForPinIndex(26, 1); PIN_SetPinRoleForPinIndex(27, IOR_Button); PIN_SetPinChannelForPinIndex(27, 1); } // add some more output safely, sending if necessary. // call with str == NULL to force send. - can be binary. // supply length int postany(http_request_t* request, const char* str, int len) { #if PLATFORM_BL602 || PLATFORM_BEKEN_NEW || PLATFORM_RTL8720D send(request->fd, str, len, 0); return 0; #else int currentlen; int addlen = len; //ADDLOG_ERROR(LOG_FEATURE_HTTP, "postany: got %i", len); if (NULL == str) { // fd will be NULL for unit tests where HTTP packet is faked locally if (request->fd == 0) { return request->replylen; } if (request->replylen > 0) { //ADDLOG_ERROR(LOG_FEATURE_HTTP, "postany: send %i", request->replylen); send(request->fd, request->reply, request->replylen, 0); } request->reply[0] = 0; request->replylen = 0; return 0; } currentlen = request->replylen; if (currentlen + addlen >= request->replymaxlen) { //ADDLOG_ERROR(LOG_FEATURE_HTTP, "postany: send %i", request->replylen); send(request->fd, request->reply, request->replylen, 0); request->reply[0] = 0; request->replylen = 0; currentlen = 0; } while (addlen >= request->replymaxlen) { if (request->replylen > 0) { //ADDLOG_ERROR(LOG_FEATURE_HTTP, "postany: send %i", request->replylen); send(request->fd, request->reply, request->replylen, 0); request->replylen = 0; } //ADDLOG_ERROR(LOG_FEATURE_HTTP, "postany: send %i", (request->replymaxlen - 1)); send(request->fd, str, (request->replymaxlen - 1), 0); addlen -= (request->replymaxlen - 1); str += (request->replymaxlen - 1); rtos_delay_milliseconds(1); } memcpy(request->reply + request->replylen, str, addlen); request->replylen += addlen; return (currentlen + addlen); #endif } // add some more output safely, sending if necessary. // call with str == NULL to force send. int poststr(http_request_t* request, const char* str) { if (str == NULL) { return postany(request, NULL, 0); } return postany(request, str, strlen(str)); } int hprintf255(http_request_t* request, const char* fmt, ...) { va_list argList; //BaseType_t taken; char tmp[256]; memset(tmp, 0, sizeof(tmp)); va_start(argList, fmt); vsnprintf(tmp, 255, fmt, argList); va_end(argList); return postany(request, tmp, strlen(tmp)); } int HUE_APICall(http_request_t* request); int HTTP_ProcessPacket(http_request_t* request) { int i; char* p; char* headers; char* protocol; //int bChanged = 0; char* urlStr = ""; char* recvbuf; if (request->received == 0) { ADDLOGF_ERROR("You gave request with NULL input"); return 0; } request->method = -1; recvbuf = request->received; for (i = 0; i < sizeof(methodNames) / sizeof(*methodNames); i++) { if (http_startsWith(recvbuf, methodNames[i])) { urlStr = recvbuf + strlen(methodNames[i]) + 2; // skip method name plus space, plus slash request->method = i; break; } } if (request->method == -1) { ADDLOGF_ERROR("unsupported method %7s", recvbuf); return 0; } if (request->reply == 0) { ADDLOGF_ERROR("You gave request with NULL buffer"); return 0; } if (request->method == HTTP_GET) { //ADDLOG_INFO(LOG_FEATURE_HTTP, "HTTP request\n"); } else { //ADDLOG_INFO(LOG_FEATURE_HTTP, "Other request\n"); } // if OPTIONS, return now - for CORS if (request->method == HTTP_OPTIONS) { http_setup(request, httpMimeTypeHTML); i = strlen(request->reply); return i; } // chop URL at space p = strchr(urlStr, ' '); if (p != 0) { if (*p) { *p = '\0'; p++; // past space } else { ADDLOGF_ERROR("invalid request\n"); return 0; } } else { // if p is 0, then strchr below would crash ADDLOGF_ERROR("invalid request\n"); return 0; } request->url = urlStr; // protocol is next, termed by \r\n protocol = p; p = strchr(protocol, '\r'); if (p != 0) { if (*p) { *p = '\0'; p++; // past \r p++; // past \n } else { ADDLOGF_ERROR("invalid request\n"); return 0; } } // i.e. not received request->contentLength = -1; headers = p; if (headers != 0) { do { p = strchr(headers, '\r'); if (p != headers) { if (p) { if (request->numheaders < MAX_HEADERS) { request->headers[request->numheaders] = headers; request->numheaders++; } // pick out contentLength if (!my_strnicmp(headers, "Content-Length:", 15)) { request->contentLength = atoi(headers + 15); } *p = 0; p++; // past \r p++; // past \n headers = p; } else { break; } } if (*p == '\r') { // end of headers *p = 0; p++; p++; break; } } while (1); } if (p == 0) { request->bodystart = 0; request->bodylen = 0; } else { request->bodystart = p; request->bodylen = request->receivedLen - (p - request->received); } #if 0 postany(request, "test", 4); return 0; #elif 0 return http_fn_empty_url(request); #endif #if ENABLE_DRIVER_HUE if (HUE_APICall(request)) { return 0; } #endif // look for a callback with this URL and method, or HTTP_ANY for (i = 0; i < numCallbacks; i++) { char* url = callbacks[i]->url; if (http_startsWith(urlStr, &url[1])) { int method = callbacks[i]->method; if (method == HTTP_ANY || method == request->method) { if (callbacks[i]->auth_required > 0 && http_basic_auth_run(request) == HTTP_BASIC_AUTH_FAIL) { return 0; } return callbacks[i]->callback(request); } } } if (http_basic_auth_run(request) == HTTP_BASIC_AUTH_FAIL) { ADDLOG_ERROR(LOG_FEATURE_HTTP, "HTTP packet with auth fail\n"); return 0; } #if ENABLE_HTTP_OVERRIDE bool HTTP_checkLFSOverride(http_request_t* request, const char *ext); if (HTTP_checkLFSOverride(request,".html")) { return 1; } if (HTTP_checkLFSOverride(request, "")) { return 1; } #endif if (http_checkUrlBase(urlStr, "")) return http_fn_empty_url(request); if (http_checkUrlBase(urlStr, "testmsg")) return http_fn_testmsg(request); if (http_checkUrlBase(urlStr, "index")) return http_fn_index(request); if (http_checkUrlBase(urlStr, "about")) return http_fn_about(request); #if ENABLE_HTTP_MQTT if (http_checkUrlBase(urlStr, "cfg_mqtt")) return http_fn_cfg_mqtt(request); if (http_checkUrlBase(urlStr, "cfg_mqtt_set")) return http_fn_cfg_mqtt_set(request); #endif #if ENABLE_HTTP_IP if (http_checkUrlBase(urlStr, "cfg_ip")) return http_fn_cfg_ip(request); #endif #if (ENABLE_DRIVER_DS1820_FULL) // including "../driver/drv_ds1820_simple.h" will complain about typedefs not used here // so lets declare it "extern" extern int http_fn_cfg_ds18b20(http_request_t* request); if (http_checkUrlBase(urlStr, "cfg_ds18b20")) return http_fn_cfg_ds18b20(request); #endif #if ENABLE_HTTP_WEBAPP if (http_checkUrlBase(urlStr, "cfg_webapp")) return http_fn_cfg_webapp(request); if (http_checkUrlBase(urlStr, "cfg_webapp_set")) return http_fn_cfg_webapp_set(request); #endif if (http_checkUrlBase(urlStr, "cfg_wifi")) return http_fn_cfg_wifi(request); #if ENABLE_HTTP_NAMES if (http_checkUrlBase(urlStr, "cfg_name")) return http_fn_cfg_name(request); #endif if (http_checkUrlBase(urlStr, "cfg_wifi_set")) return http_fn_cfg_wifi_set(request); if (http_checkUrlBase(urlStr, "cfg_loglevel_set")) return http_fn_cfg_loglevel_set(request); #if ENABLE_HTTP_MAC if (http_checkUrlBase(urlStr, "cfg_mac")) return http_fn_cfg_mac(request); #endif if (http_checkUrlBase(urlStr, "cmd_tool")) return http_fn_cmd_tool(request); #if ENABLE_HTTP_STARTUP if (http_checkUrlBase(urlStr, "startup_command")) return http_fn_startup_command(request); #endif #if ENABLE_HTTP_FLAGS if (http_checkUrlBase(urlStr, "cfg_generic")) return http_fn_cfg_generic(request); #endif #if ENABLE_HTTP_STARTUP if (http_checkUrlBase(urlStr, "cfg_startup")) return http_fn_cfg_startup(request); #endif #if ENABLE_HTTP_DGR if (http_checkUrlBase(urlStr, "cfg_dgr")) return http_fn_cfg_dgr(request); #endif #if ENABLE_HA_DISCOVERY if (http_checkUrlBase(urlStr, "ha_cfg")) return http_fn_ha_cfg(request); if (http_checkUrlBase(urlStr, "ha_discovery")) return http_fn_ha_discovery(request); #endif if (http_checkUrlBase(urlStr, "cfg")) return http_fn_cfg(request); if (http_checkUrlBase(urlStr, "cfg_pins")) return http_fn_cfg_pins(request); #if ENABLE_HTTP_PING if (http_checkUrlBase(urlStr, "cfg_ping")) return http_fn_cfg_ping(request); #endif if (http_checkUrlBase(urlStr, "ota")) return http_fn_ota(request); if (http_checkUrlBase(urlStr, "ota_exec")) return http_fn_ota_exec(request); if (http_checkUrlBase(urlStr, "cm")) return http_fn_cm(request); return http_fn_other(request); } /* NOTE: The following fields should not be manually edited. Instead, edit the script/css files in this folder and then re-generate the fields by running gulp. Gulp tasks should automatically appear in Visual Code or can be invoked from console. See https://github.com/openshwprojects/OpenBK7231T_App/blob/main/BUILDING.md for gulp setup. */ //region_start htmlHeadStyle const char htmlHeadStyle[] = ""; //region_end htmlHeadStyle //region_start ha_discovery_script const char ha_discovery_script[] = ""; //region_end ha_discovery_script