hs-test: cache docker build in local filesystem
[vpp.git] / src / plugins / http_static / static_server.c
index cd54670..2622498 100644 (file)
@@ -41,7 +41,7 @@ hss_session_alloc (u32 thread_index)
   return hs;
 }
 
-static hss_session_t *
+__clib_export hss_session_t *
 hss_session_get (u32 thread_index, u32 hs_index)
 {
   hss_main_t *hsm = &hss_main;
@@ -89,7 +89,7 @@ start_send_data (hss_session_t *hs, http_status_code_t status)
 
   msg.type = HTTP_MSG_REPLY;
   msg.code = status;
-  msg.content_type = HTTP_CONTENT_TEXT_HTML;
+  msg.content_type = hs->content_type;
   msg.data.len = hs->data_len;
 
   if (hs->data_len > hss_main.use_ptr_thresh)
@@ -133,6 +133,8 @@ hss_session_send_data (hss_url_handler_args_t *args)
   hss_session_t *hs;
 
   hs = hss_session_get (args->sh.thread_index, args->sh.session_index);
+  if (!hs)
+    return;
 
   if (hs->data && hs->free_data)
     vec_free (hs->data);
@@ -143,23 +145,97 @@ hss_session_send_data (hss_url_handler_args_t *args)
   start_send_data (hs, args->sc);
 }
 
+/*
+ * path_has_known_suffix()
+ * Returns 1 if the request ends with a known suffix, like .htm or .ico
+ * Used to avoid looking for "/favicon.ico/index.html" or similar.
+ */
+
+static int
+path_has_known_suffix (u8 *request)
+{
+  u8 *ext;
+  uword *p;
+
+  if (vec_len (request) == 0)
+    {
+      return 0;
+    }
+
+  ext = request + vec_len (request) - 1;
+
+  while (ext > request && ext[0] != '.')
+    ext--;
+
+  if (ext == request)
+    return 0;
+
+  p = hash_get_mem (hss_main.mime_type_indices_by_file_extensions, ext);
+  if (p)
+    return 1;
+
+  return 0;
+}
+
+/*
+ * content_type_from_request
+ * Returns the index of the request's suffix in the
+ * http-layer http_content_type_str[] array.
+ */
+
+static http_content_type_t
+content_type_from_request (u8 *request)
+{
+  u8 *ext;
+  uword *p;
+  /* default to text/html */
+  http_content_type_t rv = HTTP_CONTENT_TEXT_HTML;
+
+  ASSERT (vec_len (request) > 0);
+
+  ext = request + vec_len (request) - 1;
+
+  while (ext > request && ext[0] != '.')
+    ext--;
+
+  if (ext == request)
+    return rv;
+
+  p = hash_get_mem (hss_main.mime_type_indices_by_file_extensions, ext);
+
+  if (p == 0)
+    return rv;
+
+  rv = p[0];
+  return rv;
+}
+
 static int
 try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
-                u8 *request)
+                u8 *target_path, u8 *target_query, u8 *data)
 {
   http_status_code_t sc = HTTP_STATUS_OK;
   hss_url_handler_args_t args = {};
   uword *p, *url_table;
+  http_content_type_t type;
   int rv;
 
-  if (!hsm->enable_url_handlers || !request)
+  if (!hsm->enable_url_handlers || !target_path)
     return -1;
 
+  /* zero-length? try "index.html" */
+  if (vec_len (target_path) == 0)
+    {
+      target_path = format (target_path, "index.html");
+    }
+
+  type = content_type_from_request (target_path);
+
   /* Look for built-in GET / POST handlers */
   url_table =
     (rt == HTTP_REQ_GET) ? hsm->get_url_handlers : hsm->post_url_handlers;
 
-  p = hash_get_mem (url_table, request);
+  p = hash_get_mem (url_table, target_path);
   if (!p)
     return -1;
 
@@ -168,10 +244,12 @@ try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
   hs->cache_pool_index = ~0;
 
   if (hsm->debug_level > 0)
-    clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
+    clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST",
+                 target_path);
 
-  args.reqtype = rt;
-  args.request = request;
+  args.req_type = rt;
+  args.query = target_query;
+  args.req_data = data;
   args.sh.thread_index = hs->thread_index;
   args.sh.session_index = hs->session_index;
 
@@ -184,13 +262,14 @@ try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
   if (rv == HSS_URL_HANDLER_ERROR)
     {
       clib_warning ("builtin handler %llx hit on %s '%s' but failed!", p[0],
-                   (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
+                   (rt == HTTP_REQ_GET) ? "GET" : "POST", target_path);
       sc = HTTP_STATUS_NOT_FOUND;
     }
 
   hs->data = args.data;
   hs->data_len = args.data_len;
   hs->free_data = args.free_vec_data;
+  hs->content_type = type;
 
   start_send_data (hs, sc);
 
@@ -224,7 +303,7 @@ try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path)
   u32 plen;
 
   /* Remove the trailing space */
-  _vec_len (path) -= 1;
+  vec_dec_len (path, 1);
   plen = vec_len (path);
 
   /* Append "index.html" */
@@ -259,7 +338,6 @@ try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path)
 
   redirect =
     format (0,
-           "HTTP/1.1 301 Moved Permanently\r\n"
            "Location: http%s://%U%s%s\r\n\r\n",
            proto == TRANSPORT_PROTO_TLS ? "s" : "", format_ip46_address,
            &endpt.ip, endpt.is_ip4, print_port ? port_str : (u8 *) "", path);
@@ -273,31 +351,34 @@ try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path)
   hs->data_len = vec_len (redirect);
   hs->free_data = 1;
 
-  return HTTP_STATUS_OK;
+  return HTTP_STATUS_MOVED;
 }
 
 static int
 try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
-                 u8 *request)
+                 u8 *target)
 {
   http_status_code_t sc = HTTP_STATUS_OK;
-  u8 *path;
+  u8 *path, *sanitized_path;
   u32 ce_index;
+  http_content_type_t type;
 
   /* Feature not enabled */
   if (!hsm->www_root)
     return -1;
 
+  type = content_type_from_request (target);
+
+  /* Remove dot segments to prevent path traversal */
+  sanitized_path = http_path_remove_dot_segments (target);
+
   /*
    * Construct the file to open
-   * Browsers are capable of sporadically including a leading '/'
    */
-  if (!request)
+  if (!target)
     path = format (0, "%s%c", hsm->www_root, 0);
-  else if (request[0] == '/')
-    path = format (0, "%s%s%c", hsm->www_root, request, 0);
   else
-    path = format (0, "%s/%s%c", hsm->www_root, request, 0);
+    path = format (0, "%s/%s%c", hsm->www_root, sanitized_path, 0);
 
   if (hsm->debug_level > 0)
     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", path);
@@ -305,7 +386,6 @@ try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
   if (hs->data && hs->free_data)
     vec_free (hs->data);
 
-  hs->path = path;
   hs->data_offset = 0;
 
   ce_index =
@@ -314,6 +394,17 @@ try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
     {
       if (!file_path_is_valid (path))
        {
+         /*
+          * Generate error 404 right now if we can't find a path with
+          * a known file extension. It's silly to look for
+          * "favicon.ico/index.html" if you can't find
+          * "favicon.ico"; realistic example which used to happen.
+          */
+         if (path_has_known_suffix (path))
+           {
+             sc = HTTP_STATUS_NOT_FOUND;
+             goto done;
+           }
          sc = try_index_file (hsm, hs, path);
          goto done;
        }
@@ -326,10 +417,12 @@ try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
        }
     }
 
+  hs->path = path;
   hs->cache_pool_index = ce_index;
 
 done:
-
+  vec_free (sanitized_path);
+  hs->content_type = type;
   start_send_data (hs, sc);
   if (!hs->data)
     hss_session_disconnect_transport (hs);
@@ -337,33 +430,35 @@ done:
   return 0;
 }
 
-static int
-handle_request (hss_session_t *hs, http_req_method_t rt, u8 *request)
+static void
+handle_request (hss_session_t *hs, http_req_method_t rt, u8 *target_path,
+               u8 *target_query, u8 *data)
 {
   hss_main_t *hsm = &hss_main;
 
-  if (!try_url_handler (hsm, hs, rt, request))
-    return 0;
+  if (!try_url_handler (hsm, hs, rt, target_path, target_query, data))
+    return;
 
-  if (!try_file_handler (hsm, hs, rt, request))
-    return 0;
+  if (!try_file_handler (hsm, hs, rt, target_path))
+    return;
 
   /* Handler did not find anything return 404 */
   start_send_data (hs, HTTP_STATUS_NOT_FOUND);
   hss_session_disconnect_transport (hs);
-
-  return 0;
 }
 
 static int
 hss_ts_rx_callback (session_t *ts)
 {
   hss_session_t *hs;
-  u8 *request = 0;
+  u8 *target_path = 0, *target_query = 0, *data = 0;
   http_msg_t msg;
   int rv;
 
   hs = hss_session_get (ts->thread_index, ts->opaque);
+  if (hs->free_data)
+    vec_free (hs->data);
+  hs->data = 0;
 
   /* Read the http message header */
   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
@@ -372,24 +467,63 @@ hss_ts_rx_callback (session_t *ts)
   if (msg.type != HTTP_MSG_REQUEST ||
       (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST))
     {
-      hs->data = 0;
       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
-      return 0;
+      goto done;
     }
 
-  /* Read request */
-  if (msg.data.len)
+  if (msg.data.target_form != HTTP_TARGET_ORIGIN_FORM)
     {
-      vec_validate (request, msg.data.len - 1);
-      rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, request);
-      ASSERT (rv == msg.data.len);
+      start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
+      goto done;
     }
 
-  /* Find and send data */
-  handle_request (hs, msg.method_type, request);
+  /* Read target path */
+  if (msg.data.target_path_len)
+    {
+      vec_validate (target_path, msg.data.target_path_len - 1);
+      rv = svm_fifo_peek (ts->rx_fifo, msg.data.target_path_offset,
+                         msg.data.target_path_len, target_path);
+      ASSERT (rv == msg.data.target_path_len);
+      if (http_validate_abs_path_syntax (target_path, 0))
+       {
+         start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
+         goto done;
+       }
+      /* Target path must be a proper C-string in addition to a vector */
+      vec_add1 (target_path, 0);
+    }
 
-  vec_free (request);
+  /* Read target query */
+  if (msg.data.target_query_len)
+    {
+      vec_validate (target_query, msg.data.target_query_len - 1);
+      rv = svm_fifo_peek (ts->rx_fifo, msg.data.target_query_offset,
+                         msg.data.target_query_len, target_query);
+      ASSERT (rv == msg.data.target_query_len);
+      if (http_validate_query_syntax (target_query, 0))
+       {
+         start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
+         goto done;
+       }
+    }
 
+  /* Read body */
+  if (msg.data.body_len)
+    {
+      vec_validate (data, msg.data.body_len - 1);
+      rv = svm_fifo_peek (ts->rx_fifo, msg.data.body_offset, msg.data.body_len,
+                         data);
+      ASSERT (rv == msg.data.body_len);
+    }
+
+  /* Find and send data */
+  handle_request (hs, msg.method_type, target_path, target_query, data);
+
+done:
+  vec_free (target_path);
+  vec_free (target_query);
+  vec_free (data);
+  svm_fifo_dequeue_drop (ts->rx_fifo, msg.data.len);
   return 0;
 }
 
@@ -719,6 +853,7 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
        {
          error = clib_error_return (0, "unknown input `%U'",
                                     format_unformat_error, line_input);
+         break;
        }
     }
 
@@ -904,6 +1039,16 @@ hss_main_init (vlib_main_t *vm)
   hsm->app_index = ~0;
   hsm->vlib_main = vm;
 
+  /* Set up file extension to mime type index map */
+  hsm->mime_type_indices_by_file_extensions =
+    hash_create_string (0, sizeof (uword));
+
+#define _(def, ext, str)                                                      \
+  hash_set_mem (hsm->mime_type_indices_by_file_extensions, ext,               \
+               HTTP_CONTENT_##def);
+  foreach_http_content_type;
+#undef _
+
   return 0;
 }