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