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