SensESP 3.5.1-alpha
Universal Signal K sensor toolkit ESP32
Loading...
Searching...
No Matches
base_command_handler.cpp
Go to the documentation of this file.
2
3#include <freertos/FreeRTOS.h>
4#include <freertos/task.h>
5
6#include <cerrno>
7#include <cstdint>
8#include <cstdlib>
9#include <memory>
10#include <new>
11
15#include "sensesp_app.h"
16
17namespace sensesp {
18
19// An Origin or Host that does not fit the buffer is rejected rather than
20// treated as absent: a legitimate same-origin request to this device is
21// always short, so an over-long header can only be a forgery attempt and
22// must fail closed.
23bool check_origin(httpd_req_t* req) {
24 if (httpd_req_get_hdr_value_len(req, "Origin") == 0) {
25 return true;
26 }
27
28 char origin[128] = {0};
29 char host[128] = {0};
30 if (httpd_req_get_hdr_value_str(req, "Origin", origin, sizeof(origin)) !=
31 ESP_OK ||
32 httpd_req_get_hdr_value_str(req, "Host", host, sizeof(host)) != ESP_OK) {
33 httpd_resp_send_err(req, HTTPD_403_FORBIDDEN,
34 "Cross-origin request rejected");
35 return false;
36 }
37
38 String origin_str(origin);
39 int scheme_end = origin_str.indexOf("://");
40 String origin_authority =
41 scheme_end < 0 ? origin_str : origin_str.substring(scheme_end + 3);
42
43 if (origin_authority == host) {
44 return true;
45 }
46
47 httpd_resp_send_err(req, HTTPD_403_FORBIDDEN,
48 "Cross-origin request rejected");
49 return false;
50}
51
52void add_http_reset_handler(std::shared_ptr<HTTPServer>& server) {
53 auto reset_handler = std::make_shared<HTTPRequestHandler>(
54 1 << HTTP_POST, "/api/device/reset", [](httpd_req_t* req) {
55 if (!check_origin(req)) {
56 return ESP_FAIL;
57 }
58 httpd_resp_sendstr(req,
59 "Resetting device back to factory defaults. "
60 "You may have to reconfigure the WiFi settings.");
61 event_loop()->onDelay(500, []() { SensESPBaseApp::get()->reset(); });
62 return ESP_OK;
63 });
64 server->add_handler(reset_handler);
65}
66
67void add_http_restart_handler(std::shared_ptr<HTTPServer>& server) {
68 auto restart_handler = std::make_shared<HTTPRequestHandler>(
69 1 << HTTP_POST, "/api/device/restart", [](httpd_req_t* req) {
70 if (!check_origin(req)) {
71 return ESP_FAIL;
72 }
73 httpd_resp_sendstr(req, "Restarting device");
74 event_loop()->onDelay(500, []() { ESP.restart(); });
75 return ESP_OK;
76 });
77 server->add_handler(restart_handler);
78}
79
80void add_http_info_handler(std::shared_ptr<HTTPServer>& server) {
81 auto info_handler = std::make_shared<HTTPRequestHandler>(
82 1 << HTTP_GET, "/api/info", [](httpd_req_t* req) {
83 auto status_page_items = StatusPageItemBase::get_status_page_items();
84
85 JsonDocument json_doc;
86 JsonArray info_items = json_doc.to<JsonArray>();
87
88 for (auto info_item = status_page_items->begin();
89 info_item != status_page_items->end(); ++info_item) {
90 info_items.add(info_item->second->as_json());
91 }
92
93 // Per-task minimum-ever free stack, grouped under "Task stack free
94 // (bytes)", for spotting over- or under-provisioned task stacks.
95 // Computed live per request; needs the FreeRTOS trace facility, which
96 // the Arduino/ESP-IDF default config enables.
97#if defined(CONFIG_FREERTOS_USE_TRACE_FACILITY) && \
98 CONFIG_FREERTOS_USE_TRACE_FACILITY
99 // Over-allocate a few slots so a task created between the count and the
100 // snapshot can't undersize the array: uxTaskGetSystemState returns 0 on
101 // an undersized buffer, which would drop the whole section that request.
102 UBaseType_t task_slots = uxTaskGetNumberOfTasks() + 4;
103 std::unique_ptr<TaskStatus_t[]> tasks(
104 new (std::nothrow) TaskStatus_t[task_slots]);
105 if (tasks) {
106 UBaseType_t n = uxTaskGetSystemState(tasks.get(), task_slots, nullptr);
107 for (UBaseType_t i = 0; i < n; i++) {
108 JsonDocument item;
109 item["name"] = tasks[i].pcTaskName;
110 item["value"] = static_cast<uint32_t>(
111 tasks[i].usStackHighWaterMark * sizeof(StackType_t));
112 item["group"] = "Task stack free (bytes)";
113 item["order"] = kUIOutputDefaultOrder;
114 info_items.add(item);
115 }
116 }
117#endif
118
119 String response;
120 serializeJson(json_doc, response);
121 httpd_resp_set_type(req, "application/json");
122 httpd_resp_sendstr(req, response.c_str());
123 return ESP_OK;
124 });
125 server->add_handler(info_handler);
126}
127
128void add_http_log_handler(std::shared_ptr<HTTPServer>& server) {
129 auto log_handler = std::make_shared<HTTPRequestHandler>(
130 1 << HTTP_GET, "/api/log", [](httpd_req_t* req) {
131 LogBuffer* log_buffer = LogBuffer::instance();
132 httpd_resp_set_type(req, "application/json; charset=utf-8");
133 if (log_buffer == nullptr) {
134 httpd_resp_sendstr(
135 req, "{\"session\":0,\"next\":0,\"gap\":false,\"lines\":[]}");
136 return ESP_OK;
137 }
138
139 // Access control: none of its own, like /api/info. The only gate is the
140 // dispatcher's HTTP auth, which is off by default — so /api/log mirrors
141 // the serial log to anyone who can reach the web server unless web auth
142 // is enabled. check_origin() is anti-CSRF for destructive POSTs and
143 // gives no read protection, so it is not used here.
144 uint32_t since = 0;
145 bool has_since = false;
146 size_t query_len = httpd_req_get_url_query_len(req) + 1;
147 if (query_len > 1 && query_len <= 64) {
148 char query[64];
149 if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) {
150 char value[16];
151 if (httpd_query_key_value(query, "since", value, sizeof(value)) ==
152 ESP_OK) {
153 // Accept only a clean, in-range unsigned integer; ignore garbage
154 // so a stale or crafted cursor cannot corrupt the client's view.
155 char* end = nullptr;
156 errno = 0;
157 unsigned long parsed = strtoul(value, &end, 10);
158 if (end != value && *end == '\0' && errno == 0 &&
159 parsed <= UINT32_MAX) {
160 since = static_cast<uint32_t>(parsed);
161 has_since = true;
162 }
163 }
164 }
165 }
166
167 LogSnapshot snapshot = log_buffer->snapshot_since(since, has_since);
168
169 JsonDocument json_doc;
170 json_doc["session"] = snapshot.session_id;
171 json_doc["next"] = snapshot.next;
172 json_doc["gap"] = snapshot.gap;
173 JsonArray lines = json_doc["lines"].to<JsonArray>();
174 for (const auto& line : snapshot.lines) {
175 lines.add(line.c_str());
176 }
177
178 String response;
179 serializeJson(json_doc, response);
180 httpd_resp_sendstr(req, response.c_str());
181 return ESP_OK;
182 });
183 server->add_handler(log_handler);
184}
185
186void add_routes_handlers(std::shared_ptr<HTTPServer>& server) {
187 std::vector<RouteDefinition> routes;
188
189 routes.push_back(RouteDefinition("Status", "/status", "StatusPage"));
190 routes.push_back(RouteDefinition("System", "/system", "SystemPage"));
191 routes.push_back(RouteDefinition("Log", "/log", "LogPage"));
192 routes.push_back(RouteDefinition("WiFi", "/wifi", "WiFiConfigPage"));
193 routes.push_back(RouteDefinition("Signal K", "/signalk", "SignalKPage"));
194 routes.push_back(
195 RouteDefinition("Configuration", "/configuration", "ConfigurationPage"));
196 routes.push_back(RouteDefinition("Control", "/control", "ControlPage"));
197
198 // Pre-render the response
199 JsonDocument json_doc;
200 JsonArray routes_json = json_doc.to<JsonArray>();
201
202 for (auto it = routes.begin(); it != routes.end(); ++it) {
203 routes_json.add(it->as_json());
204 }
205
206 String response;
207
208 serializeJson(routes_json, response);
209
210 auto routes_handler = std::make_shared<HTTPRequestHandler>(
211 1 << HTTP_GET, "/api/routes", [response](httpd_req_t* req) {
212 httpd_resp_set_type(req, "application/json");
213 httpd_resp_sendstr(req, response.c_str());
214 return ESP_OK;
215 });
216 server->add_handler(routes_handler);
217
218 // Find the root page
219
220 StaticFileData* root_page = nullptr;
221 for (int i = 0; i < sizeof(kFrontendFiles) / sizeof(StaticFileData); i++) {
222 if (strcmp(kFrontendFiles[i].url, "/") == 0) {
223 root_page = (StaticFileData*)&kFrontendFiles[i];
224 break;
225 }
226 }
227 if (root_page == nullptr) {
228 ESP_LOGE(__FILENAME__, "Root page not found in kWebUIFiles");
229 return;
230 }
231
232 // Add a handler for each route that returns the root page
233
234 for (auto it = routes.begin(); it != routes.end(); ++it) {
235 String path = it->get_path();
236 auto route_handler = std::make_shared<HTTPRequestHandler>(
237 1 << HTTP_GET, path.c_str(), [root_page](httpd_req_t* req) {
238 httpd_resp_set_type(req, root_page->content_type);
239 if (root_page->content_encoding != nullptr) {
240 httpd_resp_set_hdr(req, kContentEncoding,
241 root_page->content_encoding);
242 }
243 httpd_resp_send(req, root_page->content, root_page->content_length);
244 return ESP_OK;
245 });
246 server->add_handler(route_handler);
247 }
248}
249
250void add_base_app_http_command_handlers(std::shared_ptr<HTTPServer>& server) {
253 add_http_info_handler(server);
254 add_http_log_handler(server);
255 add_routes_handlers(server);
256}
257
258} // namespace sensesp
Captures ESP_LOGx output into a bounded RAM buffer for the web UI.
Definition log_buffer.h:97
LogSnapshot snapshot_since(uint32_t since, bool has_since, uint32_t now_ms)
Return retained lines newer than since.
static LogBuffer * instance()
Accessor used by the static vprintf trampoline.
Definition log_buffer.h:137
static const std::shared_ptr< SensESPBaseApp > & get()
Get the singleton instance of the SensESPBaseApp.
static const std::map< String, StatusPageItemBase * > * get_status_page_items()
std::shared_ptr< reactesp::EventLoop > event_loop()
Definition sensesp.cpp:9
void add_http_info_handler(std::shared_ptr< HTTPServer > &server)
bool check_origin(httpd_req_t *req)
constexpr int kUIOutputDefaultOrder
const StaticFileData kFrontendFiles[]
void add_http_restart_handler(std::shared_ptr< HTTPServer > &server)
void add_http_reset_handler(std::shared_ptr< HTTPServer > &server)
void add_http_log_handler(std::shared_ptr< HTTPServer > &server)
void add_base_app_http_command_handlers(std::shared_ptr< HTTPServer > &server)
void add_routes_handlers(std::shared_ptr< HTTPServer > &server)
Result of a snapshot_since() query, ready to serialize for the web UI.
Definition log_buffer.h:66
std::vector< std::string > lines
Definition log_buffer.h:70
const unsigned int content_length