diff options
Diffstat (limited to 'src/plugins/http_static/static_server.c')
-rw-r--r-- | src/plugins/http_static/static_server.c | 275 |
1 files changed, 200 insertions, 75 deletions
diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c index 040cdca9d7a..48e71f51629 100644 --- a/src/plugins/http_static/static_server.c +++ b/src/plugins/http_static/static_server.c @@ -19,6 +19,9 @@ #include <sys/stat.h> #include <unistd.h> +#include <http/http_header_names.h> +#include <http/http_content_types.h> + /** @file static_server.c * Static http server, sufficient to serve .html / .css / .js content. */ @@ -55,8 +58,6 @@ hss_session_free (hss_session_t *hs) { hss_main_t *hsm = &hss_main; - pool_put (hsm->sessions[hs->thread_index], hs); - if (CLIB_DEBUG) { u32 save_thread_index; @@ -65,6 +66,8 @@ hss_session_free (hss_session_t *hs) memset (hs, 0xfa, sizeof (*hs)); hs->thread_index = save_thread_index; } + + pool_put (hsm->sessions[hs->thread_index], hs); } /** \brief Disconnect a session @@ -83,48 +86,84 @@ start_send_data (hss_session_t *hs, http_status_code_t status) { http_msg_t msg; session_t *ts; + u8 *headers_buf = 0; + u32 n_enq; + u64 to_send; int rv; ts = session_get (hs->vpp_session_index, hs->thread_index); + if (vec_len (hs->resp_headers)) + { + headers_buf = http_serialize_headers (hs->resp_headers); + vec_free (hs->resp_headers); + msg.data.headers_offset = 0; + msg.data.headers_len = vec_len (headers_buf); + } + else + { + msg.data.headers_offset = 0; + msg.data.headers_len = 0; + } + msg.type = HTTP_MSG_REPLY; msg.code = status; - msg.content_type = hs->content_type; - msg.data.len = hs->data_len; + msg.data.body_len = hs->data_len; + msg.data.len = msg.data.body_len + msg.data.headers_len; - if (hs->data_len > hss_main.use_ptr_thresh) + if (msg.data.len > hss_main.use_ptr_thresh) { msg.data.type = HTTP_MSG_DATA_PTR; rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg); ASSERT (rv == sizeof (msg)); + if (msg.data.headers_len) + { + hs->headers_buf = headers_buf; + uword headers = pointer_to_uword (hs->headers_buf); + rv = + svm_fifo_enqueue (ts->tx_fifo, sizeof (headers), (u8 *) &headers); + ASSERT (rv == sizeof (headers)); + } + uword data = pointer_to_uword (hs->data); rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (data), (u8 *) &data); - ASSERT (rv == sizeof (sizeof (data))); + ASSERT (rv == sizeof (data)); goto done; } msg.data.type = HTTP_MSG_DATA_INLINE; + msg.data.body_offset = msg.data.headers_len; rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg); ASSERT (rv == sizeof (msg)); - if (!msg.data.len) + if (msg.data.headers_len) + { + rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (headers_buf), headers_buf); + ASSERT (rv == msg.data.headers_len); + vec_free (headers_buf); + } + + if (!msg.data.body_len) goto done; - rv = svm_fifo_enqueue (ts->tx_fifo, hs->data_len, hs->data); + to_send = hs->data_len; + n_enq = clib_min (svm_fifo_size (ts->tx_fifo), to_send); - if (rv != hs->data_len) + rv = svm_fifo_enqueue (ts->tx_fifo, n_enq, hs->data); + + if (rv < to_send) { - hs->data_offset = rv; + hs->data_offset = (rv > 0) ? rv : 0; svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); } done: if (svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); } __clib_export void @@ -142,6 +181,15 @@ hss_session_send_data (hss_url_handler_args_t *args) hs->data = args->data; hs->data_len = args->data_len; hs->free_data = args->free_vec_data; + + /* Set content type only if we have some response data */ + if (hs->data_len) + { + http_add_header (&hs->resp_headers, + http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + http_content_type_token (args->ct)); + } + start_send_data (hs, args->sc); } @@ -212,30 +260,27 @@ content_type_from_request (u8 *request) 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 (request) == 0) + if (vec_len (target_path) == 0) { - request = format (request, "index.html"); + target_path = format (target_path, "index.html"); } - type = content_type_from_request (request); - /* 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; @@ -244,10 +289,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; @@ -260,18 +307,25 @@ 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); - sc = HTTP_STATUS_NOT_FOUND; + (rt == HTTP_REQ_GET) ? "GET" : "POST", target_path); + sc = HTTP_STATUS_BAD_GATEWAY; } hs->data = args.data; hs->data_len = args.data_len; hs->free_data = args.free_vec_data; - hs->content_type = type; + + /* Set content type only if we have some response data */ + if (hs->data_len) + { + http_add_header (&hs->resp_headers, + http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + http_content_type_token (args.ct)); + } start_send_data (hs, sc); - if (!hs->data) + if (!hs->data_len) hss_session_disconnect_transport (hs); return 0; @@ -335,18 +389,20 @@ try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path) } redirect = - format (0, - "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); + format (0, "http%s://%U%s%s", proto == TRANSPORT_PROTO_TLS ? "s" : "", + format_ip46_address, &endpt.ip, endpt.is_ip4, + print_port ? port_str : (u8 *) "", path); if (hsm->debug_level > 0) clib_warning ("redirect: %s", redirect); vec_free (port_str); - hs->data = redirect; - hs->data_len = vec_len (redirect); + http_add_header (&hs->resp_headers, + http_header_name_token (HTTP_HEADER_LOCATION), + (const char *) redirect, vec_len (redirect)); + hs->data = redirect; /* TODO: find better way */ + hs->data_len = 0; hs->free_data = 1; return HTTP_STATUS_MOVED; @@ -354,29 +410,28 @@ try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path) 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; + u8 *last_modified; /* Feature not enabled */ if (!hsm->www_root) return -1; - type = content_type_from_request (request); + /* 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); @@ -386,8 +441,8 @@ try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt, hs->data_offset = 0; - ce_index = - hss_cache_lookup_and_attach (&hsm->cache, path, &hs->data, &hs->data_len); + ce_index = hss_cache_lookup_and_attach (&hsm->cache, path, &hs->data, + &hs->data_len, &last_modified); if (ce_index == ~0) { if (!file_path_is_valid (path)) @@ -406,8 +461,8 @@ try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt, sc = try_index_file (hsm, hs, path); goto done; } - ce_index = - hss_cache_add_and_attach (&hsm->cache, path, &hs->data, &hs->data_len); + ce_index = hss_cache_add_and_attach (&hsm->cache, path, &hs->data, + &hs->data_len, &last_modified); if (ce_index == ~0) { sc = HTTP_STATUS_INTERNAL_ERROR; @@ -418,43 +473,61 @@ 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: + /* Set following headers only for happy path: + * Content-Type + * Cache-Control max-age + */ + type = content_type_from_request (target); + http_add_header (&hs->resp_headers, + http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + http_content_type_token (type)); + http_add_header ( + &hs->resp_headers, http_header_name_token (HTTP_HEADER_CACHE_CONTROL), + (const char *) hsm->max_age_formatted, vec_len (hsm->max_age_formatted)); + http_add_header (&hs->resp_headers, + http_header_name_token (HTTP_HEADER_LAST_MODIFIED), + (const char *) last_modified, vec_len (last_modified)); - hs->content_type = type; +done: + vec_free (sanitized_path); start_send_data (hs, sc); - if (!hs->data) + if (!hs->data_len) 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; + hs->resp_headers = 0; + vec_free (hs->headers_buf); /* Read the http message header */ rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg); @@ -463,26 +536,66 @@ 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; + http_add_header (&hs->resp_headers, + http_header_name_token (HTTP_HEADER_ALLOW), + http_token_lit ("GET, POST")); 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); - /* request must be a proper C-string in addition to a vector */ - vec_add1 (request, 0); + 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); + } + + /* 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; + } + } - vec_free (request); + /* 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; } @@ -490,7 +603,8 @@ static int hss_ts_tx_callback (session_t *ts) { hss_session_t *hs; - u32 to_send; + u32 n_enq; + u64 to_send; int rv; hs = hss_session_get (ts->thread_index, ts->opaque); @@ -498,7 +612,9 @@ hss_ts_tx_callback (session_t *ts) return 0; to_send = hs->data_len - hs->data_offset; - rv = svm_fifo_enqueue (ts->tx_fifo, to_send, hs->data + hs->data_offset); + n_enq = clib_min (svm_fifo_size (ts->tx_fifo), to_send); + + rv = svm_fifo_enqueue (ts->tx_fifo, n_enq, hs->data + hs->data_offset); if (rv <= 0) { @@ -513,7 +629,7 @@ hss_ts_tx_callback (session_t *ts) } if (svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); return 0; } @@ -607,6 +723,7 @@ hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf) hs->data = 0; hs->data_offset = 0; hs->free_data = 0; + vec_free (hs->headers_buf); vec_free (hs->path); hss_session_free (hs); @@ -757,6 +874,8 @@ hss_create (vlib_main_t *vm) if (hsm->enable_url_handlers) hss_url_handlers_init (hsm); + hsm->max_age_formatted = format (0, "max-age=%d", hsm->max_age); + return 0; } @@ -777,6 +896,7 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, hsm->private_segment_size = 0; hsm->fifo_size = 0; hsm->cache_size = 10 << 20; + hsm->max_age = HSS_DEFAULT_MAX_AGE; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) @@ -808,6 +928,8 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, ; else if (unformat (line_input, "url-handlers")) hsm->enable_url_handlers = 1; + else if (unformat (line_input, "max-age %d", &hsm->max_age)) + ; else { error = clib_error_return (0, "unknown input `%U'", @@ -836,7 +958,10 @@ no_input: goto done; } - vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ ); + session_enable_disable_args_t args = { .is_en = 1, + .rt_engine_type = + RT_BACKEND_ENGINE_RULE_TABLE }; + vnet_session_enable_disable (vm, &args); if ((rv = hss_create (vm))) { @@ -865,8 +990,8 @@ VLIB_CLI_COMMAND (hss_create_command, static) = { .path = "http static server", .short_help = "http static server www-root <path> [prealloc-fifos <nn>]\n" - "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n" - "[ptr-thresh <nn>] [url-handlers] [debug [nn]]\n", + "[private-segment-size <nnMG>] [fifo-size <nbytes>] [max-age <nseconds>]\n" + "[uri <uri>] [ptr-thresh <nn>] [url-handlers] [debug [nn]]\n", .function = hss_create_command_fn, }; @@ -876,7 +1001,7 @@ format_hss_session (u8 *s, va_list *args) hss_session_t *hs = va_arg (*args, hss_session_t *); int __clib_unused verbose = va_arg (*args, int); - s = format (s, "\n path %s, data length %u, data_offset %u", + s = format (s, "\n path %s, data length %llu, data_offset %llu", hs->path ? hs->path : (u8 *) "[none]", hs->data_len, hs->data_offset); return s; |