#include "src/Helpers/_CPlugin_Helper.h" #ifdef USES_C011 // ####################################################################################################### // ########################### Controller Plugin 011: Generic HTTP Advanced ############################## // ####################################################################################################### # define CPLUGIN_011 # define CPLUGIN_ID_011 11 # define CPLUGIN_NAME_011 "Generic HTTP Advanced" # define C011_HTTP_METHOD_MAX_LEN 16 # define C011_HTTP_URI_MAX_LEN 240 # define C011_HTTP_HEADER_MAX_LEN 256 # define C011_HTTP_BODY_MAX_LEN 512 bool C011_sendBinary = false; struct C011_ConfigStruct { void zero_last() { HttpMethod[C011_HTTP_METHOD_MAX_LEN - 1] = 0; HttpUri[C011_HTTP_URI_MAX_LEN - 1] = 0; HttpHeader[C011_HTTP_HEADER_MAX_LEN - 1] = 0; HttpBody[C011_HTTP_BODY_MAX_LEN - 1] = 0; } char HttpMethod[C011_HTTP_METHOD_MAX_LEN] = { 0 }; char HttpUri[C011_HTTP_URI_MAX_LEN] = { 0 }; char HttpHeader[C011_HTTP_HEADER_MAX_LEN] = { 0 }; char HttpBody[C011_HTTP_BODY_MAX_LEN] = { 0 }; }; // Forward declarations bool load_C011_ConfigStruct(controllerIndex_t ControllerIndex, String& HttpMethod, String& HttpUri, String& HttpHeader, String& HttpBody); boolean Create_schedule_HTTP_C011(struct EventStruct *event); void DeleteNotNeededValues(String& s, uint8_t numberOfValuesWanted); void ReplaceTokenByValue(String& s, struct EventStruct *event, bool sendBinary); bool CPlugin_011(CPlugin::Function function, struct EventStruct *event, String& string) { bool success = false; switch (function) { case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: { ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_011; proto.usesMQTT = false; proto.usesAccount = true; proto.usesPassword = true; proto.usesExtCreds = true; proto.defaultPort = 80; proto.usesID = false; break; } case CPlugin::Function::CPLUGIN_GET_DEVICENAME: { string = F(CPLUGIN_NAME_011); break; } case CPlugin::Function::CPLUGIN_INIT: { { MakeControllerSettings(ControllerSettings); //-V522 if (AllocatedControllerSettings()) { LoadControllerSettings(event->ControllerIndex, *ControllerSettings); C011_sendBinary = ControllerSettings->sendBinary(); } } success = init_c011_delay_queue(event->ControllerIndex); break; } case CPlugin::Function::CPLUGIN_EXIT: { exit_c011_delay_queue(); break; } case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: { { String HttpMethod; String HttpUri; String HttpHeader; String HttpBody; if (!load_C011_ConfigStruct(event->ControllerIndex, HttpMethod, HttpUri, HttpHeader, HttpBody)) { return false; } addTableSeparator(F("HTTP Config"), 2, 3); { uint8_t choice = 0; const __FlashStringHelper * methods[] = { F("GET"), F("POST"), F("PUT"), F("HEAD"), F("PATCH") }; for (uint8_t i = 0; i < 5; i++) { if (HttpMethod.equals(methods[i])) { choice = i; } } addFormSelector(F("Method"), F("P011httpmethod"), 5, methods, nullptr, choice); } addFormTextBox(F("URI"), F("P011httpuri"), HttpUri, C011_HTTP_URI_MAX_LEN - 1); { htmlEscape(HttpHeader); addFormTextArea(F("Header"), F("P011httpheader"), HttpHeader, C011_HTTP_HEADER_MAX_LEN - 1, 4, 50); } { htmlEscape(HttpBody); addFormTextArea(F("Body"), F("P011httpbody"), HttpBody, C011_HTTP_BODY_MAX_LEN - 1, 8, 50); } } { // Place in scope to delete ControllerSettings as soon as it is no longer needed MakeControllerSettings(ControllerSettings); //-V522 if (!AllocatedControllerSettings()) { addHtmlError(F("Out of memory, cannot load page")); } else { LoadControllerSettings(event->ControllerIndex, *ControllerSettings); addControllerParameterForm(*ControllerSettings, event->ControllerIndex, ControllerSettingsStruct::CONTROLLER_SEND_BINARY); addFormNote(F("Do not 'percent escape' body when send binary checked")); } } break; } case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: { std::shared_ptr customConfig(new (std::nothrow) C011_ConfigStruct); if (customConfig) { uint8_t choice = 0; String methods[] = { F("GET"), F("POST"), F("PUT"), F("HEAD"), F("PATCH") }; for (uint8_t i = 0; i < 5; i++) { if (methods[i].equals(customConfig->HttpMethod)) { choice = i; } } int httpmethod = getFormItemInt(F("P011httpmethod"), choice); String httpuri = webArg(F("P011httpuri")); String httpheader = webArg(F("P011httpheader")); String httpbody = webArg(F("P011httpbody")); strlcpy(customConfig->HttpMethod, methods[httpmethod].c_str(), sizeof(customConfig->HttpMethod)); strlcpy(customConfig->HttpUri, httpuri.c_str(), sizeof(customConfig->HttpUri)); strlcpy(customConfig->HttpHeader, httpheader.c_str(), sizeof(customConfig->HttpHeader)); strlcpy(customConfig->HttpBody, httpbody.c_str(), sizeof(customConfig->HttpBody)); customConfig->zero_last(); SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C011_ConfigStruct)); } break; } case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { if (C011_DelayHandler->queueFull(event->ControllerIndex)) { break; } success = Create_schedule_HTTP_C011(event); break; } case CPlugin::Function::CPLUGIN_FLUSH: { process_c011_delay_queue(); delay(0); break; } default: break; } return success; } // ******************************************************************************** // Generic HTTP request // ******************************************************************************** // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* bool do_process_c011_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C011_queue_element& element = static_cast(element_base); // *INDENT-ON* if (!NetworkConnected()) { return false; } int httpCode = -1; send_via_http( controller_number, ControllerSettings, element._controller_idx, element.uri, element.HttpMethod, element.header, element.postStr, httpCode); // HTTP codes: // 1xx Informational response // 2xx Success return httpCode >= 100 && httpCode < 300; } bool load_C011_ConfigStruct(controllerIndex_t ControllerIndex, String& HttpMethod, String& HttpUri, String& HttpHeader, String& HttpBody) { // Just copy the needed strings and destruct the C011_ConfigStruct as soon as possible std::shared_ptr customConfig(new (std::nothrow) C011_ConfigStruct); if (!customConfig) { return false; } LoadCustomControllerSettings(ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C011_ConfigStruct)); customConfig->zero_last(); move_special(HttpMethod, String(customConfig->HttpMethod)); move_special(HttpUri , String(customConfig->HttpUri)); move_special(HttpHeader, String(customConfig->HttpHeader)); move_special(HttpBody , String(customConfig->HttpBody)); return true; } // ******************************************************************************** // Create request // ******************************************************************************** boolean Create_schedule_HTTP_C011(struct EventStruct *event) { if (C011_DelayHandler == nullptr) { addLog(LOG_LEVEL_ERROR, F("No C011_DelayHandler")); return false; } //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed // Add a new element to the queue with the minimal payload std::unique_ptr element(new (std::nothrow) C011_queue_element(event)); bool success = C011_DelayHandler->addToQueue(std::move(element)); if (success) { // Element was added. // Now we try to append to the existing element // and thus preventing the need to create a long string only to copy it to a queue element. C011_queue_element& element = static_cast(*(C011_DelayHandler->sendQueue.back())); if (!load_C011_ConfigStruct(event->ControllerIndex, element.HttpMethod, element.uri, element.header, element.postStr)) { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { addLogMove(LOG_LEVEL_ERROR, strformat( F("C011 : %s %s %s %s"), element.HttpMethod.c_str(), element.uri.c_str(), element.header.c_str(), element.postStr.c_str())); } C011_DelayHandler->sendQueue.pop_back(); return false; } ReplaceTokenByValue(element.uri, event, false); ReplaceTokenByValue(element.header, event, false); if (element.postStr.length() > 0) { ReplaceTokenByValue(element.postStr, event, C011_sendBinary); } } else { addLog(LOG_LEVEL_ERROR, F("C011 : Could not add to delay handler")); } Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE, C011_DelayHandler->getNextScheduleTime()); return success; } // parses the string and returns only the the number of name/values we want // according to the parameter numberOfValuesWanted void DeleteNotNeededValues(String& s, uint8_t numberOfValuesWanted) { numberOfValuesWanted++; for (uint8_t i = 1; i < 5; i++) { const String startToken(strformat(F("%%%d%%"), i)); const String endToken(strformat(F("%%/%d%%"), i)); // do we want to keep this one? if (i < numberOfValuesWanted) { // yes, so just remove the tokens s.replace(startToken, EMPTY_STRING); s.replace(endToken, EMPTY_STRING); } else { // remove all the whole strings including tokes int startIndex = s.indexOf(startToken); int endIndex = s.indexOf(endToken); while (startIndex != -1 && endIndex != -1 && endIndex > startIndex) { String p = s.substring(startIndex, endIndex + 4); // remove the whole string including tokens s.replace(p, EMPTY_STRING); // find next ones startIndex = s.indexOf(startToken); endIndex = s.indexOf(endToken); } } } } // ******************************************************************************** // Replace the token in a string by real value. // // Example: // %1%%vname1%____%tskname%____%val1%%/1%%2%%__%%vname2%____%tskname%____%val2%%/2% // will become in case of a sensor with 1 value: // SENSORVALUENAME1____TASKNAME1____VALUE1 <- everything not between %1% and %/1% will be discarded // in case of a sensor with 2 values: // SENSORVALUENAME1____TASKNAME1____VALUE1__SENSORVALUENAME2____TASKNAME2____VALUE2 // ******************************************************************************** void ReplaceTokenByValue(String& s, struct EventStruct *event, bool sendBinary) { // example string: // write?db=testdb&type=%1%%vname1%%/1%%2%;%vname2%%/2%%3%;%vname3%%/3%%4%;%vname4%%/4%&value=%1%%val1%%/1%%2%;%val2%%/2%%3%;%val3%%/3%%4%;%val4%%/4% // %1%%vname1%,Standort=%tskname% Wert=%val1%%/1%%2%%LF%%vname2%,Standort=%tskname% Wert=%val2%%/2%%3%%LF%%vname3%,Standort=%tskname% // Wert=%val3%%/3%%4%%LF%%vname4%,Standort=%tskname% Wert=%val4%%/4% #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { addLog(LOG_LEVEL_DEBUG_MORE, F("HTTP before parsing: ")); addLog(LOG_LEVEL_DEBUG_MORE, s); } #endif const uint8_t valueCount = getValueCountForTask(event->TaskIndex); DeleteNotNeededValues(s, valueCount); #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { addLog(LOG_LEVEL_DEBUG_MORE, F("HTTP after parsing: ")); addLog(LOG_LEVEL_DEBUG_MORE, s); } #endif parseControllerVariables(s, event, !sendBinary); #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { addLog(LOG_LEVEL_DEBUG_MORE, F("HTTP after replacements: ")); addLog(LOG_LEVEL_DEBUG_MORE, s); } #endif } #endif // ifdef USES_C011