/ components / esp_http_server / src / httpd_uri.c
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  }