ESPHome  2024.4.2
web_server_idf.cpp
Go to the documentation of this file.
1 #ifdef USE_ESP_IDF
2 
3 #include <cstdarg>
4 
5 #include "esphome/core/log.h"
6 #include "esphome/core/helpers.h"
7 
8 #include "esp_tls_crypto.h"
9 
10 #include "utils.h"
11 #include "web_server_idf.h"
12 
13 namespace esphome {
14 namespace web_server_idf {
15 
16 #ifndef HTTPD_409
17 #define HTTPD_409 "409 Conflict"
18 #endif
19 
20 #define CRLF_STR "\r\n"
21 #define CRLF_LEN (sizeof(CRLF_STR) - 1)
22 
23 static const char *const TAG = "web_server_idf";
24 
26  if (this->server_) {
27  httpd_stop(this->server_);
28  this->server_ = nullptr;
29  }
30 }
31 
33  if (this->server_) {
34  this->end();
35  }
36  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
37  config.server_port = this->port_;
38  config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; };
39  if (httpd_start(&this->server_, &config) == ESP_OK) {
40  const httpd_uri_t handler_get = {
41  .uri = "",
42  .method = HTTP_GET,
44  .user_ctx = this,
45  };
46  httpd_register_uri_handler(this->server_, &handler_get);
47 
48  const httpd_uri_t handler_post = {
49  .uri = "",
50  .method = HTTP_POST,
52  .user_ctx = this,
53  };
54  httpd_register_uri_handler(this->server_, &handler_post);
55 
56  const httpd_uri_t handler_options = {
57  .uri = "",
58  .method = HTTP_OPTIONS,
60  .user_ctx = this,
61  };
62  httpd_register_uri_handler(this->server_, &handler_options);
63  }
64 }
65 
66 esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) {
67  ESP_LOGVV(TAG, "Enter AsyncWebServer::request_post_handler. uri=%s", r->uri);
68  auto content_type = request_get_header(r, "Content-Type");
69  if (content_type.has_value() && *content_type != "application/x-www-form-urlencoded") {
70  ESP_LOGW(TAG, "Only application/x-www-form-urlencoded supported for POST request");
71  // fallback to get handler to support backward compatibility
73  }
74 
75  if (!request_has_header(r, "Content-Length")) {
76  ESP_LOGW(TAG, "Content length is requred for post: %s", r->uri);
77  httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED, nullptr);
78  return ESP_OK;
79  }
80 
81  if (r->content_len > HTTPD_MAX_REQ_HDR_LEN) {
82  ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len);
83  httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
84  return ESP_FAIL;
85  }
86 
87  std::string post_query;
88  if (r->content_len > 0) {
89  post_query.resize(r->content_len);
90  const int ret = httpd_req_recv(r, &post_query[0], r->content_len + 1);
91  if (ret <= 0) { // 0 return value indicates connection closed
92  if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
93  httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT, nullptr);
94  return ESP_ERR_TIMEOUT;
95  }
96  httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
97  return ESP_FAIL;
98  }
99  }
100 
101  AsyncWebServerRequest req(r, std::move(post_query));
102  return static_cast<AsyncWebServer *>(r->user_ctx)->request_handler_(&req);
103 }
104 
105 esp_err_t AsyncWebServer::request_handler(httpd_req_t *r) {
106  ESP_LOGVV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri);
107  AsyncWebServerRequest req(r);
108  return static_cast<AsyncWebServer *>(r->user_ctx)->request_handler_(&req);
109 }
110 
112  for (auto *handler : this->handlers_) {
113  if (handler->canHandle(request)) {
114  // At now process only basic requests.
115  // OTA requires multipart request support and handleUpload for it
116  handler->handleRequest(request);
117  return ESP_OK;
118  }
119  }
120  if (this->on_not_found_) {
121  this->on_not_found_(request);
122  return ESP_OK;
123  }
124  return ESP_ERR_NOT_FOUND;
125 }
126 
128  delete this->rsp_;
129  for (const auto &pair : this->params_) {
130  delete pair.second; // NOLINT(cppcoreguidelines-owning-memory)
131  }
132 }
133 
134 bool AsyncWebServerRequest::hasHeader(const char *name) const { return request_has_header(*this, name); }
135 
137  return request_get_header(*this, name);
138 }
139 
140 std::string AsyncWebServerRequest::url() const {
141  auto *str = strchr(this->req_->uri, '?');
142  if (str == nullptr) {
143  return this->req_->uri;
144  }
145  return std::string(this->req_->uri, str - this->req_->uri);
146 }
147 
148 std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); }
149 
151  httpd_resp_send(*this, response->get_content_data(), response->get_content_size());
152 }
153 
154 void AsyncWebServerRequest::send(int code, const char *content_type, const char *content) {
155  this->init_response_(nullptr, code, content_type);
156  if (content) {
157  httpd_resp_send(*this, content, HTTPD_RESP_USE_STRLEN);
158  } else {
159  httpd_resp_send(*this, nullptr, 0);
160  }
161 }
162 
163 void AsyncWebServerRequest::redirect(const std::string &url) {
164  httpd_resp_set_status(*this, "302 Found");
165  httpd_resp_set_hdr(*this, "Location", url.c_str());
166  httpd_resp_send(*this, nullptr, 0);
167 }
168 
169 void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type) {
170  httpd_resp_set_status(*this, code == 200 ? HTTPD_200
171  : code == 404 ? HTTPD_404
172  : code == 409 ? HTTPD_409
173  : to_string(code).c_str());
174 
175  if (content_type && *content_type) {
176  httpd_resp_set_type(*this, content_type);
177  }
178  httpd_resp_set_hdr(*this, "Accept-Ranges", "none");
179 
180  for (const auto &pair : DefaultHeaders::Instance().headers_) {
181  httpd_resp_set_hdr(*this, pair.first.c_str(), pair.second.c_str());
182  }
183 
184  delete this->rsp_;
185  this->rsp_ = rsp;
186 }
187 
188 bool AsyncWebServerRequest::authenticate(const char *username, const char *password) const {
189  if (username == nullptr || password == nullptr || *username == 0) {
190  return true;
191  }
192  auto auth = this->get_header("Authorization");
193  if (!auth.has_value()) {
194  return false;
195  }
196 
197  auto *auth_str = auth.value().c_str();
198 
199  const auto auth_prefix_len = sizeof("Basic ") - 1;
200  if (strncmp("Basic ", auth_str, auth_prefix_len) != 0) {
201  ESP_LOGW(TAG, "Only Basic authorization supported yet");
202  return false;
203  }
204 
205  std::string user_info;
206  user_info += username;
207  user_info += ':';
208  user_info += password;
209 
210  size_t n = 0, out;
211  esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size());
212 
213  auto digest = std::unique_ptr<char[]>(new char[n + 1]);
214  esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest.get()), n, &out,
215  reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size());
216 
217  return strncmp(digest.get(), auth_str + auth_prefix_len, auth.value().size() - auth_prefix_len) == 0;
218 }
219 
220 void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
221  httpd_resp_set_hdr(*this, "Connection", "keep-alive");
222  auto auth_val = str_sprintf("Basic realm=\"%s\"", realm ? realm : "Login Required");
223  httpd_resp_set_hdr(*this, "WWW-Authenticate", auth_val.c_str());
224  httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr);
225 }
226 
228  auto find = this->params_.find(name);
229  if (find != this->params_.end()) {
230  return find->second;
231  }
232 
233  optional<std::string> val = query_key_value(this->post_query_, name);
234  if (!val.has_value()) {
235  auto url_query = request_get_url_query(*this);
236  if (url_query.has_value()) {
237  val = query_key_value(url_query.value(), name);
238  }
239  }
240 
241  AsyncWebParameter *param = nullptr;
242  if (val.has_value()) {
243  param = new AsyncWebParameter(val.value()); // NOLINT(cppcoreguidelines-owning-memory)
244  }
245  this->params_.insert({name, param});
246  return param;
247 }
248 
249 void AsyncWebServerResponse::addHeader(const char *name, const char *value) {
250  httpd_resp_set_hdr(*this->req_, name, value);
251 }
252 
253 void AsyncResponseStream::print(float value) { this->print(to_string(value)); }
254 
255 void AsyncResponseStream::printf(const char *fmt, ...) {
256  va_list args;
257 
258  va_start(args, fmt);
259  const int length = vsnprintf(nullptr, 0, fmt, args);
260  va_end(args);
261 
262  std::string str;
263  str.resize(length);
264 
265  va_start(args, fmt);
266  vsnprintf(&str[0], length + 1, fmt, args);
267  va_end(args);
268 
269  this->print(str);
270 }
271 
273  for (auto *ses : this->sessions_) {
274  delete ses; // NOLINT(cppcoreguidelines-owning-memory)
275  }
276 }
277 
279  auto *rsp = new AsyncEventSourceResponse(request, this); // NOLINT(cppcoreguidelines-owning-memory)
280  if (this->on_connect_) {
281  this->on_connect_(rsp);
282  }
283  this->sessions_.insert(rsp);
284 }
285 
286 void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
287  for (auto *ses : this->sessions_) {
288  ses->send(message, event, id, reconnect);
289  }
290 }
291 
293  : server_(server) {
294  httpd_req_t *req = *request;
295 
296  httpd_resp_set_status(req, HTTPD_200);
297  httpd_resp_set_type(req, "text/event-stream");
298  httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
299  httpd_resp_set_hdr(req, "Connection", "keep-alive");
300 
301  for (const auto &pair : DefaultHeaders::Instance().headers_) {
302  httpd_resp_set_hdr(req, pair.first.c_str(), pair.second.c_str());
303  }
304 
305  httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN);
306 
307  req->sess_ctx = this;
308  req->free_ctx = AsyncEventSourceResponse::destroy;
309 
310  this->hd_ = req->handle;
311  this->fd_ = httpd_req_to_sockfd(req);
312 }
313 
315  auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
316  rsp->server_->sessions_.erase(rsp);
317  delete rsp; // NOLINT(cppcoreguidelines-owning-memory)
318 }
319 
320 void AsyncEventSourceResponse::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
321  if (this->fd_ == 0) {
322  return;
323  }
324 
325  std::string ev;
326 
327  if (reconnect) {
328  ev.append("retry: ", sizeof("retry: ") - 1);
329  ev.append(to_string(reconnect));
330  ev.append(CRLF_STR, CRLF_LEN);
331  }
332 
333  if (id) {
334  ev.append("id: ", sizeof("id: ") - 1);
335  ev.append(to_string(id));
336  ev.append(CRLF_STR, CRLF_LEN);
337  }
338 
339  if (event && *event) {
340  ev.append("event: ", sizeof("event: ") - 1);
341  ev.append(event);
342  ev.append(CRLF_STR, CRLF_LEN);
343  }
344 
345  if (message && *message) {
346  ev.append("data: ", sizeof("data: ") - 1);
347  ev.append(message);
348  ev.append(CRLF_STR, CRLF_LEN);
349  }
350 
351  if (ev.empty()) {
352  return;
353  }
354 
355  ev.append(CRLF_STR, CRLF_LEN);
356 
357  // Sending chunked content prelude
358  auto cs = str_snprintf("%x" CRLF_STR, 4 * sizeof(ev.size()) + CRLF_LEN, ev.size());
359  httpd_socket_send(this->hd_, this->fd_, cs.c_str(), cs.size(), 0);
360 
361  // Sendiing content chunk
362  httpd_socket_send(this->hd_, this->fd_, ev.c_str(), ev.size(), 0);
363 
364  // Indicate end of chunk
365  httpd_socket_send(this->hd_, this->fd_, CRLF_STR, CRLF_LEN, 0);
366 }
367 
368 } // namespace web_server_idf
369 } // namespace esphome
370 
371 #endif // !defined(USE_ESP_IDF)
value_type const & value() const
Definition: optional.h:89
const char * name
Definition: stm32flash.h:78
AsyncWebParameter * getParam(const std::string &name)
void send(AsyncWebServerResponse *response)
optional< std::string > get_header(const char *name) const
static esp_err_t request_handler(httpd_req_t *r)
AsyncEventSourceResponse(const AsyncWebServerRequest *request, AsyncEventSource *server)
mopeka_std_values val[4]
bool has_value() const
Definition: optional.h:87
static DefaultHeaders & Instance()
void send(const char *message, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
optional< std::string > request_get_header(httpd_req_t *req, const char *name)
Definition: utils.cpp:37
const char *const TAG
Definition: spi.cpp:8
bool request_has_header(httpd_req_t *req, const char *name)
Definition: utils.cpp:35
void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type)
std::vector< AsyncWebHandler * > handlers_
void send(const char *message, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:312
optional< std::string > query_key_value(const std::string &query_url, const std::string &key)
Definition: utils.cpp:72
void handleRequest(AsyncWebServerRequest *request) override
optional< std::string > request_get_url_query(httpd_req_t *req)
Definition: utils.cpp:54
void addHeader(const char *name, const char *value)
esp_err_t request_handler_(AsyncWebServerRequest *request) const
void requestAuthentication(const char *realm=nullptr) const
bool authenticate(const char *username, const char *password) const
std::string to_string(int value)
Definition: helpers.cpp:82
uint16_t length
Definition: tt21100.cpp:12
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
std::set< AsyncEventSourceResponse * > sessions_
std::string str_snprintf(const char *fmt, size_t len,...)
Definition: helpers.cpp:298
std::function< void(AsyncWebServerRequest *request)> on_not_found_
virtual const char * get_content_data() const =0
static esp_err_t request_post_handler(httpd_req_t *r)
void printf(const char *fmt,...) __attribute__((format(printf