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;
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)
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);
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;
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;
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);
u32 plen;
/* Remove the trailing space */
- _vec_len (path) -= 1;
+ vec_dec_len (path, 1);
plen = vec_len (path);
/* Append "index.html" */
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);
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);
if (hs->data && hs->free_data)
vec_free (hs->data);
- hs->path = path;
hs->data_offset = 0;
ce_index =
{
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;
}
}
}
+ 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);
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);
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;
}
{
error = clib_error_return (0, "unknown input `%U'",
format_unformat_error, line_input);
+ break;
}
}
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;
}