mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-09-12 17:45:49 +00:00
371 lines
13 KiB
C++
371 lines
13 KiB
C++
#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<C011_ConfigStruct> 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<const uint8_t *>(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(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) {
|
|
const C011_queue_element& element = static_cast<const C011_queue_element&>(element_base);
|
|
// *INDENT-ON*
|
|
|
|
if (!NetworkConnected()) { return false; }
|
|
|
|
int httpCode = -1;
|
|
|
|
send_via_http(
|
|
cpluginID,
|
|
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<C011_ConfigStruct> customConfig(new (std::nothrow) C011_ConfigStruct);
|
|
|
|
if (!customConfig) {
|
|
return false;
|
|
}
|
|
LoadCustomControllerSettings(ControllerIndex, reinterpret_cast<uint8_t *>(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<C011_queue_element> 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_queue_element&>(*(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
|