1 /*
2 * SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7
8 #include <errno.h>
9 #include <esp_log.h>
10 #include <esp_err.h>
11 #include <http_parser.h>
12
13 #include <esp_http_server.h>
14 #include "esp_httpd_priv.h"
15
16 static const char *TAG = "httpd_uri";
17
httpd_uri_match_simple(const char * uri1,const char * uri2,size_t len2)18 static bool httpd_uri_match_simple(const char *uri1, const char *uri2, size_t len2)
19 {
20 return strlen(uri1) == len2 && // First match lengths
21 (strncmp(uri1, uri2, len2) == 0); // Then match actual URIs
22 }
23
httpd_uri_match_wildcard(const char * template,const char * uri,size_t len)24 bool httpd_uri_match_wildcard(const char *template, const char *uri, size_t len)
25 {
26 const size_t tpl_len = strlen(template);
27 size_t exact_match_chars = tpl_len;
28
29 /* Check for trailing question mark and asterisk */
30 const char last = (const char) (tpl_len > 0 ? template[tpl_len - 1] : 0);
31 const char prevlast = (const char) (tpl_len > 1 ? template[tpl_len - 2] : 0);
32 const bool asterisk = last == '*' || (prevlast == '*' && last == '?');
33 const bool quest = last == '?' || (prevlast == '?' && last == '*');
34
35 /* Minimum template string length must be:
36 * 0 : if neither of '*' and '?' are present
37 * 1 : if only '*' is present
38 * 2 : if only '?' is present
39 * 3 : if both are present
40 *
41 * The expression (asterisk + quest*2) serves as a
42 * case wise generator of these length values
43 */
44
45 /* abort in cases such as "?" with no preceding character (invalid template) */
46 if (exact_match_chars < asterisk + quest*2) {
47 return false;
48 }
49
50 /* account for special characters and the optional character if "?" is used */
51 exact_match_chars -= asterisk + quest*2;
52
53 if (len < exact_match_chars) {
54 return false;
55 }
56
57 if (!quest) {
58 if (!asterisk && len != exact_match_chars) {
59 /* no special characters and different length - strncmp would return false */
60 return false;
61 }
62 /* asterisk allows arbitrary trailing characters, we ignore these using
63 * exact_match_chars as the length limit */
64 return (strncmp(template, uri, exact_match_chars) == 0);
65 } else {
66 /* question mark present */
67 if (len > exact_match_chars && template[exact_match_chars] != uri[exact_match_chars]) {
68 /* the optional character is present, but different */
69 return false;
70 }
71 if (strncmp(template, uri, exact_match_chars) != 0) {
72 /* the mandatory part differs */
73 return false;
74 }
75 /* Now we know the URI is longer than the required part of template,
76 * the mandatory part matches, and if the optional character is present, it is correct.
77 * Match is OK if we have asterisk, i.e. any trailing characters are OK, or if
78 * there are no characters beyond the optional character. */
79 return asterisk || len <= exact_match_chars + 1;
80 }
81 }
82
83 /* Find handler with matching URI and method, and set
84 * appropriate error code if URI or method not found */
httpd_find_uri_handler(struct httpd_data * hd,const char * uri,size_t uri_len,httpd_method_t method,httpd_err_code_t * err)85 static httpd_uri_t* httpd_find_uri_handler(struct httpd_data *hd,
86 const char *uri, size_t uri_len,
87 httpd_method_t method,
88 httpd_err_code_t *err)
89 {
90 if (err) {
91 *err = HTTPD_404_NOT_FOUND;
92 }
93
94 for (int i = 0; i < hd->config.max_uri_handlers; i++) {
95 if (!hd->hd_calls[i]) {
96 break;
97 }
98 ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri);
99
100 /* Check if custom URI matching function is set,
101 * else use simple string compare */
102 if (hd->config.uri_match_fn ?
103 hd->config.uri_match_fn(hd->hd_calls[i]->uri, uri, uri_len) :
104 httpd_uri_match_simple(hd->hd_calls[i]->uri, uri, uri_len)) {
105 /* URIs match. Now check if method is supported */
106 if (hd->hd_calls[i]->method == method) {
107 /* Match found! */
108 if (err) {
109 /* Unset any error that may
110 * have been set earlier */
111 *err = 0;
112 }
113 return hd->hd_calls[i];
114 }
115 /* URI found but method not allowed.
116 * If URI is found later then this
117 * error must be set to 0 */
118 if (err) {
119 *err = HTTPD_405_METHOD_NOT_ALLOWED;
120 }
121 }
122 }
123 return NULL;
124 }
125
httpd_register_uri_handler(httpd_handle_t handle,const httpd_uri_t * uri_handler)126 esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
127 const httpd_uri_t *uri_handler)
128 {
129 if (handle == NULL || uri_handler == NULL) {
130 return ESP_ERR_INVALID_ARG;
131 }
132
133 struct httpd_data *hd = (struct httpd_data *) handle;
134
135 /* Make sure another handler with matching URI and method
136 * is not already registered. This will also catch cases
137 * when a registered URI wildcard pattern already accounts
138 * for the new URI being registered */
139 if (httpd_find_uri_handler(handle, uri_handler->uri,
140 strlen(uri_handler->uri),
141 uri_handler->method, NULL) != NULL) {
142 ESP_LOGW(TAG, LOG_FMT("handler %s with method %d already registered"),
143 uri_handler->uri, uri_handler->method);
144 return ESP_ERR_HTTPD_HANDLER_EXISTS;
145 }
146
147 for (int i = 0; i < hd->config.max_uri_handlers; i++) {
148 if (hd->hd_calls[i] == NULL) {
149 hd->hd_calls[i] = malloc(sizeof(httpd_uri_t));
150 if (hd->hd_calls[i] == NULL) {
151 /* Failed to allocate memory */
152 return ESP_ERR_HTTPD_ALLOC_MEM;
153 }
154
155 /* Copy URI string */
156 hd->hd_calls[i]->uri = strdup(uri_handler->uri);
157 if (hd->hd_calls[i]->uri == NULL) {
158 /* Failed to allocate memory */
159 free(hd->hd_calls[i]);
160 return ESP_ERR_HTTPD_ALLOC_MEM;
161 }
162
163 /* Copy remaining members */
164 hd->hd_calls[i]->method = uri_handler->method;
165 hd->hd_calls[i]->handler = uri_handler->handler;
166 hd->hd_calls[i]->user_ctx = uri_handler->user_ctx;
167 #ifdef CONFIG_HTTPD_WS_SUPPORT
168 hd->hd_calls[i]->is_websocket = uri_handler->is_websocket;
169 hd->hd_calls[i]->handle_ws_control_frames = uri_handler->handle_ws_control_frames;
170 if (uri_handler->supported_subprotocol) {
171 hd->hd_calls[i]->supported_subprotocol = strdup(uri_handler->supported_subprotocol);
172 } else {
173 hd->hd_calls[i]->supported_subprotocol = NULL;
174 }
175 #endif
176 ESP_LOGD(TAG, LOG_FMT("[%d] installed %s"), i, uri_handler->uri);
177 return ESP_OK;
178 }
179 ESP_LOGD(TAG, LOG_FMT("[%d] exists %s"), i, hd->hd_calls[i]->uri);
180 }
181 ESP_LOGW(TAG, LOG_FMT("no slots left for registering handler"));
182 return ESP_ERR_HTTPD_HANDLERS_FULL;
183 }
184
httpd_unregister_uri_handler(httpd_handle_t handle,const char * uri,httpd_method_t method)185 esp_err_t httpd_unregister_uri_handler(httpd_handle_t handle,
186 const char *uri, httpd_method_t method)
187 {
188 if (handle == NULL || uri == NULL) {
189 return ESP_ERR_INVALID_ARG;
190 }
191
192 struct httpd_data *hd = (struct httpd_data *) handle;
193 for (int i = 0; i < hd->config.max_uri_handlers; i++) {
194 if (!hd->hd_calls[i]) {
195 break;
196 }
197 if ((hd->hd_calls[i]->method == method) && // First match methods
198 (strcmp(hd->hd_calls[i]->uri, uri) == 0)) { // Then match URI string
199 ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
200
201 free((char*)hd->hd_calls[i]->uri);
202 free(hd->hd_calls[i]);
203 hd->hd_calls[i] = NULL;
204
205 /* Shift the remaining non null handlers in the array
206 * forward by 1 so that order of insertion is maintained */
207 for (i += 1; i < hd->config.max_uri_handlers; i++) {
208 if (!hd->hd_calls[i]) {
209 break;
210 }
211 hd->hd_calls[i-1] = hd->hd_calls[i];
212 }
213 /* Nullify the following non null entry */
214 hd->hd_calls[i-1] = NULL;
215 return ESP_OK;
216 }
217 }
218 ESP_LOGW(TAG, LOG_FMT("handler %s with method %d not found"), uri, method);
219 return ESP_ERR_NOT_FOUND;
220 }
221
httpd_unregister_uri(httpd_handle_t handle,const char * uri)222 esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char *uri)
223 {
224 if (handle == NULL || uri == NULL) {
225 return ESP_ERR_INVALID_ARG;
226 }
227
228 struct httpd_data *hd = (struct httpd_data *) handle;
229 bool found = false;
230
231 int i = 0, j = 0; // For keeping count of removed entries
232 for (; i < hd->config.max_uri_handlers; i++) {
233 if (!hd->hd_calls[i]) {
234 break;
235 }
236 if (strcmp(hd->hd_calls[i]->uri, uri) == 0) { // Match URI strings
237 ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, uri);
238
239 free((char*)hd->hd_calls[i]->uri);
240 free(hd->hd_calls[i]);
241 hd->hd_calls[i] = NULL;
242 found = true;
243
244 j++; // Update count of removed entries
245 } else {
246 /* Shift the remaining non null handlers in the array
247 * forward by j so that order of insertion is maintained */
248 hd->hd_calls[i-j] = hd->hd_calls[i];
249 }
250 }
251 /* Nullify the following non null entries */
252 for (int k = (i - j); k < i; k++) {
253 hd->hd_calls[k] = NULL;
254 }
255
256 if (!found) {
257 ESP_LOGW(TAG, LOG_FMT("no handler found for URI %s"), uri);
258 }
259 return (found ? ESP_OK : ESP_ERR_NOT_FOUND);
260 }
261
httpd_unregister_all_uri_handlers(struct httpd_data * hd)262 void httpd_unregister_all_uri_handlers(struct httpd_data *hd)
263 {
264 for (unsigned i = 0; i < hd->config.max_uri_handlers; i++) {
265 if (!hd->hd_calls[i]) {
266 break;
267 }
268 ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri);
269
270 free((char*)hd->hd_calls[i]->uri);
271 free(hd->hd_calls[i]);
272 hd->hd_calls[i] = NULL;
273 }
274 }
275
httpd_uri(struct httpd_data * hd)276 esp_err_t httpd_uri(struct httpd_data *hd)
277 {
278 httpd_uri_t *uri = NULL;
279 httpd_req_t *req = &hd->hd_req;
280 struct http_parser_url *res = &hd->hd_req_aux.url_parse_res;
281
282 /* For conveying URI not found/method not allowed */
283 httpd_err_code_t err = 0;
284
285 ESP_LOGD(TAG, LOG_FMT("request for %s with type %d"), req->uri, req->method);
286
287 /* URL parser result contains offset and length of path string */
288 if (res->field_set & (1 << UF_PATH)) {
289 uri = httpd_find_uri_handler(hd, req->uri + res->field_data[UF_PATH].off,
290 res->field_data[UF_PATH].len, req->method, &err);
291 }
292
293 /* If URI with method not found, respond with error code */
294 if (uri == NULL) {
295 switch (err) {
296 case HTTPD_404_NOT_FOUND:
297 ESP_LOGW(TAG, LOG_FMT("URI '%s' not found"), req->uri);
298 return httpd_req_handle_err(req, HTTPD_404_NOT_FOUND);
299 case HTTPD_405_METHOD_NOT_ALLOWED:
300 ESP_LOGW(TAG, LOG_FMT("Method '%d' not allowed for URI '%s'"),
301 req->method, req->uri);
302 return httpd_req_handle_err(req, HTTPD_405_METHOD_NOT_ALLOWED);
303 default:
304 return ESP_FAIL;
305 }
306 }
307
308 /* Attach user context data (passed during URI registration) into request */
309 req->user_ctx = uri->user_ctx;
310
311 /* Final step for a WebSocket handshake verification */
312 #ifdef CONFIG_HTTPD_WS_SUPPORT
313 struct httpd_req_aux *aux = req->aux;
314 if (uri->is_websocket && aux->ws_handshake_detect && uri->method == HTTP_GET) {
315 ESP_LOGD(TAG, LOG_FMT("Responding WS handshake to sock %d"), aux->sd->fd);
316 esp_err_t ret = httpd_ws_respond_server_handshake(&hd->hd_req, uri->supported_subprotocol);
317 if (ret != ESP_OK) {
318 return ret;
319 }
320
321 aux->sd->ws_handshake_done = true;
322 aux->sd->ws_handler = uri->handler;
323 aux->sd->ws_control_frames = uri->handle_ws_control_frames;
324 aux->sd->ws_user_ctx = uri->user_ctx;
325 }
326 #endif
327
328 /* Invoke handler */
329 if (uri->handler(req) != ESP_OK) {
330 /* Handler returns error, this socket should be closed */
331 ESP_LOGW(TAG, LOG_FMT("uri handler execution failed"));
332 return ESP_FAIL;
333 }
334 return ESP_OK;
335 }
336