mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 08:28:12 +01:00
web_server_idf: support x-www-form-urlencoded POST requests (#6037)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
725b0c81e8
commit
c899a33d1a
4 changed files with 181 additions and 85 deletions
93
esphome/components/web_server_idf/utils.cpp
Normal file
93
esphome/components/web_server_idf/utils.cpp
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
|
#include <memory>
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "http_parser.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace web_server_idf {
|
||||||
|
|
||||||
|
static const char *const TAG = "web_server_idf_utils";
|
||||||
|
|
||||||
|
void url_decode(char *str) {
|
||||||
|
char *ptr = str, buf;
|
||||||
|
for (; *str; str++, ptr++) {
|
||||||
|
if (*str == '%') {
|
||||||
|
str++;
|
||||||
|
if (parse_hex(str, 2, reinterpret_cast<uint8_t *>(&buf), 1) == 2) {
|
||||||
|
*ptr = buf;
|
||||||
|
str++;
|
||||||
|
} else {
|
||||||
|
str--;
|
||||||
|
*ptr = *str;
|
||||||
|
}
|
||||||
|
} else if (*str == '+') {
|
||||||
|
*ptr = ' ';
|
||||||
|
} else {
|
||||||
|
*ptr = *str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*ptr = *str;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool request_has_header(httpd_req_t *req, const char *name) { return httpd_req_get_hdr_value_len(req, name); }
|
||||||
|
|
||||||
|
optional<std::string> request_get_header(httpd_req_t *req, const char *name) {
|
||||||
|
size_t len = httpd_req_get_hdr_value_len(req, name);
|
||||||
|
if (len == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string str;
|
||||||
|
str.resize(len);
|
||||||
|
|
||||||
|
auto res = httpd_req_get_hdr_value_str(req, name, &str[0], len + 1);
|
||||||
|
if (res != ESP_OK) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {str};
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<std::string> request_get_url_query(httpd_req_t *req) {
|
||||||
|
auto len = httpd_req_get_url_query_len(req);
|
||||||
|
if (len == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string str;
|
||||||
|
str.resize(len);
|
||||||
|
|
||||||
|
auto res = httpd_req_get_url_query_str(req, &str[0], len + 1);
|
||||||
|
if (res != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Can't get query for request: %s", esp_err_to_name(res));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {str};
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<std::string> query_key_value(const std::string &query_url, const std::string &key) {
|
||||||
|
if (query_url.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto val = std::unique_ptr<char[]>(new char[query_url.size()]);
|
||||||
|
if (!val) {
|
||||||
|
ESP_LOGE(TAG, "Not enough memory to the query key value");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpd_query_key_value(query_url.c_str(), key.c_str(), val.get(), query_url.size()) != ESP_OK) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
url_decode(val.get());
|
||||||
|
return {val.get()};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace web_server_idf
|
||||||
|
} // namespace esphome
|
||||||
|
#endif // USE_ESP_IDF
|
17
esphome/components/web_server_idf/utils.h
Normal file
17
esphome/components/web_server_idf/utils.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace web_server_idf {
|
||||||
|
|
||||||
|
bool request_has_header(httpd_req_t *req, const char *name);
|
||||||
|
optional<std::string> request_get_header(httpd_req_t *req, const char *name);
|
||||||
|
optional<std::string> request_get_url_query(httpd_req_t *req);
|
||||||
|
optional<std::string> query_key_value(const std::string &query_url, const std::string &key);
|
||||||
|
|
||||||
|
} // namespace web_server_idf
|
||||||
|
} // namespace esphome
|
||||||
|
#endif // USE_ESP_IDF
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "esp_tls_crypto.h"
|
#include "esp_tls_crypto.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
#include "web_server_idf.h"
|
#include "web_server_idf.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -47,7 +48,7 @@ void AsyncWebServer::begin() {
|
||||||
const httpd_uri_t handler_post = {
|
const httpd_uri_t handler_post = {
|
||||||
.uri = "",
|
.uri = "",
|
||||||
.method = HTTP_POST,
|
.method = HTTP_POST,
|
||||||
.handler = AsyncWebServer::request_handler,
|
.handler = AsyncWebServer::request_post_handler,
|
||||||
.user_ctx = this,
|
.user_ctx = this,
|
||||||
};
|
};
|
||||||
httpd_register_uri_handler(this->server_, &handler_post);
|
httpd_register_uri_handler(this->server_, &handler_post);
|
||||||
|
@ -62,20 +63,62 @@ void AsyncWebServer::begin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) {
|
||||||
|
ESP_LOGVV(TAG, "Enter AsyncWebServer::request_post_handler. uri=%s", r->uri);
|
||||||
|
auto content_type = request_get_header(r, "Content-Type");
|
||||||
|
if (content_type.has_value() && *content_type != "application/x-www-form-urlencoded") {
|
||||||
|
ESP_LOGW(TAG, "Only application/x-www-form-urlencoded supported for POST request");
|
||||||
|
// fallback to get handler to support backward compatibility
|
||||||
|
return AsyncWebServer::request_handler(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request_has_header(r, "Content-Length")) {
|
||||||
|
ESP_LOGW(TAG, "Content length is requred for post: %s", r->uri);
|
||||||
|
httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED, nullptr);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r->content_len > HTTPD_MAX_REQ_HDR_LEN) {
|
||||||
|
ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len);
|
||||||
|
httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string post_query;
|
||||||
|
if (r->content_len > 0) {
|
||||||
|
post_query.resize(r->content_len);
|
||||||
|
const int ret = httpd_req_recv(r, &post_query[0], r->content_len + 1);
|
||||||
|
if (ret <= 0) { // 0 return value indicates connection closed
|
||||||
|
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
|
httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT, nullptr);
|
||||||
|
return ESP_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerRequest req(r, std::move(post_query));
|
||||||
|
return static_cast<AsyncWebServer *>(r->user_ctx)->request_handler_(&req);
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t AsyncWebServer::request_handler(httpd_req_t *r) {
|
esp_err_t AsyncWebServer::request_handler(httpd_req_t *r) {
|
||||||
ESP_LOGV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri);
|
ESP_LOGVV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri);
|
||||||
AsyncWebServerRequest req(r);
|
AsyncWebServerRequest req(r);
|
||||||
auto *server = static_cast<AsyncWebServer *>(r->user_ctx);
|
return static_cast<AsyncWebServer *>(r->user_ctx)->request_handler_(&req);
|
||||||
for (auto *handler : server->handlers_) {
|
}
|
||||||
if (handler->canHandle(&req)) {
|
|
||||||
|
esp_err_t AsyncWebServer::request_handler_(AsyncWebServerRequest *request) const {
|
||||||
|
for (auto *handler : this->handlers_) {
|
||||||
|
if (handler->canHandle(request)) {
|
||||||
// At now process only basic requests.
|
// At now process only basic requests.
|
||||||
// OTA requires multipart request support and handleUpload for it
|
// OTA requires multipart request support and handleUpload for it
|
||||||
handler->handleRequest(&req);
|
handler->handleRequest(request);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (server->on_not_found_) {
|
if (this->on_not_found_) {
|
||||||
server->on_not_found_(&req);
|
this->on_not_found_(request);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
return ESP_ERR_NOT_FOUND;
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
@ -88,22 +131,10 @@ AsyncWebServerRequest::~AsyncWebServerRequest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncWebServerRequest::hasHeader(const char *name) const { return httpd_req_get_hdr_value_len(*this, name); }
|
bool AsyncWebServerRequest::hasHeader(const char *name) const { return request_has_header(*this, name); }
|
||||||
|
|
||||||
optional<std::string> AsyncWebServerRequest::get_header(const char *name) const {
|
optional<std::string> AsyncWebServerRequest::get_header(const char *name) const {
|
||||||
size_t buf_len = httpd_req_get_hdr_value_len(*this, name);
|
return request_get_header(*this, name);
|
||||||
if (buf_len == 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
auto buf = std::unique_ptr<char[]>(new char[++buf_len]);
|
|
||||||
if (!buf) {
|
|
||||||
ESP_LOGE(TAG, "No enough memory for get header %s", name);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (httpd_req_get_hdr_value_str(*this, name, buf.get(), buf_len) != ESP_OK) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return {buf.get()};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string AsyncWebServerRequest::url() const {
|
std::string AsyncWebServerRequest::url() const {
|
||||||
|
@ -193,74 +224,25 @@ void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
|
||||||
httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr);
|
httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string url_decode(const std::string &in) {
|
|
||||||
std::string out;
|
|
||||||
out.reserve(in.size());
|
|
||||||
for (std::size_t i = 0; i < in.size(); ++i) {
|
|
||||||
if (in[i] == '%') {
|
|
||||||
++i;
|
|
||||||
if (i + 1 < in.size()) {
|
|
||||||
auto c = parse_hex<uint8_t>(&in[i], 2);
|
|
||||||
if (c.has_value()) {
|
|
||||||
out += static_cast<char>(*c);
|
|
||||||
++i;
|
|
||||||
} else {
|
|
||||||
out += '%';
|
|
||||||
out += in[i++];
|
|
||||||
out += in[i];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out += '%';
|
|
||||||
out += in[i];
|
|
||||||
}
|
|
||||||
} else if (in[i] == '+') {
|
|
||||||
out += ' ';
|
|
||||||
} else {
|
|
||||||
out += in[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) {
|
AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) {
|
||||||
auto find = this->params_.find(name);
|
auto find = this->params_.find(name);
|
||||||
if (find != this->params_.end()) {
|
if (find != this->params_.end()) {
|
||||||
return find->second;
|
return find->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto query_len = httpd_req_get_url_query_len(this->req_);
|
optional<std::string> val = query_key_value(this->post_query_, name);
|
||||||
if (query_len == 0) {
|
if (!val.has_value()) {
|
||||||
return nullptr;
|
auto url_query = request_get_url_query(*this);
|
||||||
|
if (url_query.has_value()) {
|
||||||
|
val = query_key_value(url_query.value(), name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto query_str = std::unique_ptr<char[]>(new char[++query_len]);
|
AsyncWebParameter *param = nullptr;
|
||||||
if (!query_str) {
|
if (val.has_value()) {
|
||||||
ESP_LOGE(TAG, "No enough memory for get query param");
|
param = new AsyncWebParameter(val.value()); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
this->params_.insert({name, param});
|
||||||
auto res = httpd_req_get_url_query_str(*this, query_str.get(), query_len);
|
|
||||||
if (res != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "Can't get query for request: %s", esp_err_to_name(res));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto query_val = std::unique_ptr<char[]>(new char[query_len]);
|
|
||||||
if (!query_val) {
|
|
||||||
ESP_LOGE(TAG, "No enough memory for get query param value");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = httpd_query_key_value(query_str.get(), name.c_str(), query_val.get(), query_len);
|
|
||||||
if (res != ESP_OK) {
|
|
||||||
this->params_.insert({name, nullptr});
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
query_str.release();
|
|
||||||
auto decoded = url_decode(query_val.get());
|
|
||||||
query_val.release();
|
|
||||||
auto *param = new AsyncWebParameter(decoded); // NOLINT(cppcoreguidelines-owning-memory)
|
|
||||||
this->params_.insert(std::make_pair(name, param));
|
|
||||||
return param;
|
return param;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,14 +253,15 @@ void AsyncWebServerResponse::addHeader(const char *name, const char *value) {
|
||||||
void AsyncResponseStream::print(float value) { this->print(to_string(value)); }
|
void AsyncResponseStream::print(float value) { this->print(to_string(value)); }
|
||||||
|
|
||||||
void AsyncResponseStream::printf(const char *fmt, ...) {
|
void AsyncResponseStream::printf(const char *fmt, ...) {
|
||||||
std::string str;
|
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
size_t length = vsnprintf(nullptr, 0, fmt, args);
|
const int length = vsnprintf(nullptr, 0, fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
|
std::string str;
|
||||||
str.resize(length);
|
str.resize(length);
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(&str[0], length + 1, fmt, args);
|
vsnprintf(&str[0], length + 1, fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
|
@ -90,11 +90,10 @@ class AsyncWebServerResponseProgmem : public AsyncWebServerResponse {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const uint8_t *data_;
|
const uint8_t *data_;
|
||||||
const size_t size_;
|
size_t size_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncWebServerRequest {
|
class AsyncWebServerRequest {
|
||||||
// FIXME friend class AsyncWebServerResponse;
|
|
||||||
friend class AsyncWebServer;
|
friend class AsyncWebServer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -164,7 +163,9 @@ class AsyncWebServerRequest {
|
||||||
httpd_req_t *req_;
|
httpd_req_t *req_;
|
||||||
AsyncWebServerResponse *rsp_{};
|
AsyncWebServerResponse *rsp_{};
|
||||||
std::map<std::string, AsyncWebParameter *> params_;
|
std::map<std::string, AsyncWebParameter *> params_;
|
||||||
|
std::string post_query_;
|
||||||
AsyncWebServerRequest(httpd_req_t *req) : req_(req) {}
|
AsyncWebServerRequest(httpd_req_t *req) : req_(req) {}
|
||||||
|
AsyncWebServerRequest(httpd_req_t *req, std::string post_query) : req_(req), post_query_(std::move(post_query)) {}
|
||||||
void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type);
|
void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -191,6 +192,8 @@ class AsyncWebServer {
|
||||||
uint16_t port_{};
|
uint16_t port_{};
|
||||||
httpd_handle_t server_{};
|
httpd_handle_t server_{};
|
||||||
static esp_err_t request_handler(httpd_req_t *r);
|
static esp_err_t request_handler(httpd_req_t *r);
|
||||||
|
static esp_err_t request_post_handler(httpd_req_t *r);
|
||||||
|
esp_err_t request_handler_(AsyncWebServerRequest *request) const;
|
||||||
std::vector<AsyncWebHandler *> handlers_;
|
std::vector<AsyncWebHandler *> handlers_;
|
||||||
std::function<void(AsyncWebServerRequest *request)> on_not_found_{};
|
std::function<void(AsyncWebServerRequest *request)> on_not_found_{};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue