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