diff options
Diffstat (limited to 'src/plugins/http/http.c')
-rw-r--r-- | src/plugins/http/http.c | 204 |
1 files changed, 133 insertions, 71 deletions
diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 7c0b55e26d2..c5cd894cb59 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -17,6 +17,7 @@ #include <vnet/session/session.h> #include <http/http_timer.h> #include <http/http_status_codes.h> +#include <http/http_header_names.h> static http_main_t http_main; @@ -35,6 +36,12 @@ const http_buffer_type_t msg_to_buf_type[] = { [HTTP_MSG_DATA_PTR] = HTTP_BUFFER_PTR, }; +const char *http_upgrade_proto_str[] = { "", +#define _(sym, str) str, + foreach_http_upgrade_proto +#undef _ +}; + static u8 * format_http_req_state (u8 *s, va_list *va) { @@ -446,6 +453,9 @@ static const char *http_response_template = "HTTP/1.1 %s\r\n" static const char *content_len_template = "Content-Length: %llu\r\n"; +static const char *connection_upgrade_template = "Connection: upgrade\r\n" + "Upgrade: %s\r\n"; + /** * http request boilerplate */ @@ -706,6 +716,7 @@ http_parse_request_line (http_req_t *req, http_status_code_t *ec) { HTTP_DBG (0, "CONNECT method"); req->method = HTTP_REQ_CONNECT; + req->upgrade_proto = HTTP_UPGRADE_PROTO_NA; req->target_path_offset = method_offset + 8; req->is_tunnel = 1; } @@ -892,7 +903,17 @@ http_parse_status_line (http_req_t *req) static int http_identify_headers (http_req_t *req, http_status_code_t *ec) { - int i; + int rv; + u8 *p, *end, *name_start, *value_start; + u32 name_len, value_len; + http_field_line_t *field_line; + uword header_index; + + vec_reset_length (req->headers); + req->content_len_header_index = ~0; + req->connection_header_index = ~0; + req->upgrade_header_index = ~0; + req->headers_offset = req->rx_buf_offset; /* check if we have any header */ if ((req->rx_buf[req->rx_buf_offset] == '\r') && @@ -905,16 +926,53 @@ http_identify_headers (http_req_t *req, http_status_code_t *ec) return 0; } - /* find empty line indicating end of header section */ - i = v_find_index (req->rx_buf, req->rx_buf_offset, 0, "\r\n\r\n"); - if (i < 0) + end = req->rx_buf + vec_len (req->rx_buf); + p = req->rx_buf + req->rx_buf_offset; + + while (1) { - clib_warning ("cannot find header section end"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - req->headers_offset = req->rx_buf_offset; - req->headers_len = i - req->rx_buf_offset + 2; + rv = _parse_field_name (&p, end, &name_start, &name_len); + if (rv != 0) + { + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + rv = _parse_field_value (&p, end, &value_start, &value_len); + if (rv != 0 || (end - p) < 2) + { + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + vec_add2 (req->headers, field_line, 1); + field_line->name_offset = + (name_start - req->rx_buf) - req->headers_offset; + field_line->name_len = name_len; + field_line->value_offset = + (value_start - req->rx_buf) - req->headers_offset; + field_line->value_len = value_len; + header_index = field_line - req->headers; + + /* find headers that will be used later in preprocessing */ + if (req->content_len_header_index == ~0 && + http_token_is ((const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_CONTENT_LENGTH))) + req->content_len_header_index = header_index; + else if (req->connection_header_index == ~0 && + http_token_is ((const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_CONNECTION))) + req->connection_header_index = header_index; + else if (req->upgrade_header_index == ~0 && + http_token_is ((const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_UPGRADE))) + req->upgrade_header_index = header_index; + + /* are we done? */ + if (*p == '\r' && *(p + 1) == '\n') + break; + } + + req->headers_len = p - (req->rx_buf + req->headers_offset); req->control_data_len += (req->headers_len + 2); HTTP_DBG (2, "headers length: %u", req->headers_len); HTTP_DBG (2, "headers offset: %u", req->headers_offset); @@ -925,9 +983,10 @@ http_identify_headers (http_req_t *req, http_status_code_t *ec) static int http_identify_message_body (http_req_t *req, http_status_code_t *ec) { - int i, value_len; - u8 *end, *p, *value_start; + int i; + u8 *p; u64 body_len = 0, digit; + http_field_line_t *field_line; req->body_len = 0; @@ -944,67 +1003,15 @@ http_identify_message_body (http_req_t *req, http_status_code_t *ec) /* TODO check for chunked transfer coding */ - /* try to find Content-Length header */ - i = v_find_index (req->rx_buf, req->headers_offset, req->headers_len, - "Content-Length:"); - if (i < 0) + if (req->content_len_header_index == ~0) { HTTP_DBG (2, "Content-Length header not present, no message-body"); return 0; } - req->rx_buf_offset = i + 15; + field_line = vec_elt_at_index (req->headers, req->content_len_header_index); - i = v_find_index (req->rx_buf, req->rx_buf_offset, req->headers_len, "\r\n"); - if (i < 0) - { - clib_warning ("end of line missing"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - value_len = i - req->rx_buf_offset; - if (value_len < 1) - { - clib_warning ("invalid header, content length value missing"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - - end = req->rx_buf + req->rx_buf_offset + value_len; - p = req->rx_buf + req->rx_buf_offset; - /* skip leading whitespace */ - while (1) - { - if (p == end) - { - clib_warning ("value not found"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - else if (*p != ' ' && *p != '\t') - { - break; - } - p++; - value_len--; - } - value_start = p; - /* skip trailing whitespace */ - p = value_start + value_len - 1; - while (*p == ' ' || *p == '\t') - { - p--; - value_len--; - } - - if (value_len < 1) - { - clib_warning ("value not found"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - - p = value_start; - for (i = 0; i < value_len; i++) + p = req->rx_buf + req->headers_offset + field_line->value_offset; + for (i = 0; i < field_line->value_len; i++) { /* check for digit */ if (!isdigit (*p)) @@ -1095,6 +1102,7 @@ http_req_state_wait_transport_reply (http_conn_t *hc, msg.data.body_len = hc->req.body_len; msg.data.type = HTTP_MSG_DATA_INLINE; msg.data.len = len; + msg.data.headers_ctx = pointer_to_uword (hc->req.headers); svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { hc->req.rx_buf, len } }; @@ -1130,6 +1138,49 @@ error: return HTTP_SM_ERROR; } +#define http_field_line_value_token(_fl, _req) \ + (const char *) ((_req)->rx_buf + (_req)->headers_offset + \ + (_fl)->value_offset), \ + (_fl)->value_len + +static void +http_check_connection_upgrade (http_req_t *req) +{ + http_field_line_t *connection, *upgrade; + u8 skip; + + skip = (req->method != HTTP_REQ_GET) + (req->connection_header_index == ~0) + + (req->upgrade_header_index == ~0); + if (skip) + return; + + connection = vec_elt_at_index (req->headers, req->connection_header_index); + /* connection options are case-insensitive (RFC9110 7.6.1) */ + if (http_token_is_case (http_field_line_value_token (connection, req), + http_token_lit ("upgrade"))) + { + upgrade = vec_elt_at_index (req->headers, req->upgrade_header_index); + + /* check upgrade protocol, we want to ignore something like upgrade to + * newer HTTP version, only tunnels are supported */ + if (0) + ; +#define _(sym, str) \ + else if (http_token_is (http_field_line_value_token (upgrade, req), \ + http_token_lit (str))) req->upgrade_proto = \ + HTTP_UPGRADE_PROTO_##sym; + foreach_http_upgrade_proto +#undef _ + else return; + + HTTP_DBG (1, "connection upgrade: %U", format_http_bytes, + req->rx_buf + req->headers_offset + upgrade->value_offset, + upgrade->value_len); + req->is_tunnel = 1; + req->method = HTTP_REQ_CONNECT; + } +} + static http_sm_result_t http_req_state_wait_transport_method (http_conn_t *hc, transport_send_params_t *sp) @@ -1164,6 +1215,8 @@ http_req_state_wait_transport_method (http_conn_t *hc, if (rv) goto error; + http_check_connection_upgrade (&hc->req); + rv = http_identify_message_body (&hc->req, &ec); if (rv) goto error; @@ -1196,6 +1249,8 @@ http_req_state_wait_transport_method (http_conn_t *hc, msg.data.headers_len = hc->req.headers_len; msg.data.body_offset = hc->req.body_offset; msg.data.body_len = hc->req.body_len; + msg.data.headers_ctx = pointer_to_uword (hc->req.headers); + msg.data.upgrade_proto = hc->req.upgrade_proto; svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { hc->req.rx_buf, len } }; @@ -1286,15 +1341,21 @@ http_req_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp) /* Server */ hc->app_name); - /* RFC9110 9.3.6: A server MUST NOT send Content-Length header field in a - * 2xx (Successful) response to CONNECT. */ - if (hc->req.is_tunnel && http_status_code_str[msg.code][0] == '2') + /* RFC9110 8.6: A server MUST NOT send Content-Length header field in a + * 2xx (Successful) response to CONNECT or with a status code of 101 + * (Switching Protocols). */ + if (hc->req.is_tunnel && (http_status_code_str[msg.code][0] == '2' || + msg.code == HTTP_STATUS_SWITCHING_PROTOCOLS)) { ASSERT (msg.data.body_len == 0); next_state = HTTP_REQ_STATE_TUNNEL; + if (hc->req.upgrade_proto > HTTP_UPGRADE_PROTO_NA) + response = format (response, connection_upgrade_template, + http_upgrade_proto_str[hc->req.upgrade_proto]); /* cleanup some stuff we don't need anymore in tunnel mode */ http_conn_timer_stop (hc); vec_free (hc->req.rx_buf); + vec_free (hc->req.headers); http_buffer_free (&hc->req.tx_buf); } else @@ -1860,6 +1921,7 @@ http_ts_cleanup_callback (session_t *ts, session_cleanup_ntf_t ntf) HTTP_DBG (1, "going to free hc [%u]%x", ts->thread_index, ts->opaque); vec_free (hc->req.rx_buf); + vec_free (hc->req.headers); http_buffer_free (&hc->req.tx_buf); |