diff options
author | Matus Fabian <matfabia@cisco.com> | 2024-06-04 19:00:00 +0200 |
---|---|---|
committer | Florin Coras <florin.coras@gmail.com> | 2024-06-13 06:35:26 +0000 |
commit | 82ad9660becfcdd93c906d909d7e478733c5fbbe (patch) | |
tree | 9eb2615037a0e49d87ed73dc2ca8447eeeafc32c /src/plugins/http/http.c | |
parent | eaa7d91ad77f9c6691b42b0e9f631166b4bcf44f (diff) |
http: return more than url to server app
Provide all bytes as received from transport as data in the http
message to server. Additionally provide offset and length of target
path, target query, headers and body. Offers apis for parsing of
headers, percent decoding, target path/query syntax verification.
Type: improvement
Change-Id: Idbe6f13afa378650cc5212ea7d3f9319183ebbbe
Signed-off-by: Matus Fabian <matfabia@cisco.com>
Diffstat (limited to 'src/plugins/http/http.c')
-rw-r--r-- | src/plugins/http/http.c | 354 |
1 files changed, 315 insertions, 39 deletions
diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 893dd877c29..72b4812fd42 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -83,6 +83,16 @@ format_http_state (u8 *s, va_list *va) } \ while (0) +static inline int +http_state_is_tx_valid (http_conn_t *hc) +{ + http_state_t state = hc->http_state; + return (state == HTTP_STATE_APP_IO_MORE_DATA || + state == HTTP_STATE_CLIENT_IO_MORE_DATA || + state == HTTP_STATE_WAIT_APP_REPLY || + state == HTTP_STATE_WAIT_APP_METHOD); +} + static inline http_worker_t * http_worker_get (u32 thread_index) { @@ -383,7 +393,7 @@ static const char *http_response_template = "HTTP/1.1 %s\r\n" static const char *http_request_template = "GET %s HTTP/1.1\r\n" "User-Agent: %s\r\n" - "Accept: */*\r\n"; + "Accept: */*\r\n\r\n"; static u32 http_send_data (http_conn_t *hc, u8 *data, u32 length, u32 offset) @@ -449,8 +459,18 @@ http_read_message (http_conn_t *hc) return 0; } -static int -v_find_index (u8 *vec, u32 offset, char *str) +/** + * @brief Find the first occurrence of the string in the vector. + * + * @param vec The vector to be scanned. + * @param offset Search offset in the vector. + * @param num Maximum number of characters to be searched if non-zero. + * @param str The string to be searched. + * + * @return @c -1 if the string is not found within the vector; index otherwise. + */ +static inline int +v_find_index (u8 *vec, u32 offset, u32 num, char *str) { int start_index = offset; u32 slen = (u32) strnlen_s_inline (str, 16); @@ -461,7 +481,15 @@ v_find_index (u8 *vec, u32 offset, char *str) if (vlen <= slen) return -1; - for (; start_index < (vlen - slen); start_index++) + int end_index = vlen - slen; + if (num) + { + if (num < slen) + return -1; + end_index = clib_min (end_index, offset + num - slen); + } + + for (; start_index <= end_index; start_index++) { if (!memcmp (vec + start_index, str, slen)) return start_index; @@ -470,6 +498,259 @@ v_find_index (u8 *vec, u32 offset, char *str) return -1; } +static void +http_identify_optional_query (http_conn_t *hc) +{ + u32 pos = vec_search (hc->rx_buf, '?'); + if (~0 != pos) + { + hc->target_query_offset = pos + 1; + hc->target_query_len = + hc->target_path_offset + hc->target_path_len - hc->target_query_offset; + hc->target_path_len = hc->target_path_len - hc->target_query_len - 1; + } +} + +static int +http_get_target_form (http_conn_t *hc) +{ + int i; + + /* "*" */ + if ((hc->rx_buf[hc->target_path_offset] == '*') && + (hc->target_path_len == 1)) + { + hc->target_form = HTTP_TARGET_ASTERISK_FORM; + return 0; + } + + /* 1*( "/" segment ) [ "?" query ] */ + if (hc->rx_buf[hc->target_path_offset] == '/') + { + /* drop leading slash */ + hc->target_path_len--; + hc->target_path_offset++; + hc->target_form = HTTP_TARGET_ORIGIN_FORM; + http_identify_optional_query (hc); + return 0; + } + + /* scheme "://" host [ ":" port ] *( "/" segment ) [ "?" query ] */ + i = v_find_index (hc->rx_buf, hc->target_path_offset, hc->target_path_len, + "://"); + if (i > 0) + { + hc->target_form = HTTP_TARGET_ABSOLUTE_FORM; + http_identify_optional_query (hc); + return 0; + } + + /* host ":" port */ + for (i = hc->target_path_offset; + i < (hc->target_path_offset + hc->target_path_len); i++) + { + if ((hc->rx_buf[i] == ':') && (isdigit (hc->rx_buf[i + 1]))) + { + hc->target_form = HTTP_TARGET_AUTHORITY_FORM; + return 0; + } + } + + return -1; +} + +static int +http_parse_request_line (http_conn_t *hc, http_status_code_t *ec) +{ + int i, target_len; + u32 next_line_offset; + + /* request-line = method SP request-target SP HTTP-version CRLF */ + i = v_find_index (hc->rx_buf, 0, 0, "\r\n"); + if (i < 0) + { + clib_warning ("request line incomplete"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + HTTP_DBG (0, "request line length: %d", i); + next_line_offset = i + 2; + + /* there should be at least one more CRLF */ + if (vec_len (hc->rx_buf) < (next_line_offset + 2)) + { + clib_warning ("malformed message, too short"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + /* parse method */ + if ((i = v_find_index (hc->rx_buf, 0, next_line_offset, "GET ")) >= 0) + { + HTTP_DBG (0, "GET method"); + hc->method = HTTP_REQ_GET; + hc->target_path_offset = i + 4; + } + else if ((i = v_find_index (hc->rx_buf, 0, next_line_offset, "POST ")) >= 0) + { + HTTP_DBG (0, "POST method"); + hc->method = HTTP_REQ_POST; + hc->target_path_offset = i + 5; + } + else + { + clib_warning ("method not implemented: %8v", hc->rx_buf); + *ec = HTTP_STATUS_NOT_IMPLEMENTED; + return -1; + } + + /* find version */ + i = v_find_index (hc->rx_buf, next_line_offset - 11, 11, " HTTP/"); + if (i < 0) + { + clib_warning ("HTTP version not present"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + /* verify major version */ + if (isdigit (hc->rx_buf[i + 6])) + { + if (hc->rx_buf[i + 6] != '1') + { + clib_warning ("HTTP major version '%c' not supported", + hc->rx_buf[i + 6]); + *ec = HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED; + return -1; + } + } + else + { + clib_warning ("HTTP major version '%c' is not digit", hc->rx_buf[i + 6]); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + /* parse request-target */ + target_len = i - hc->target_path_offset; + if (target_len < 1) + { + clib_warning ("request-target not present"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + hc->target_path_len = target_len; + hc->target_query_offset = 0; + hc->target_query_len = 0; + if (http_get_target_form (hc)) + { + clib_warning ("invalid target"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + HTTP_DBG (0, "request-target path length: %u", hc->target_path_len); + HTTP_DBG (0, "request-target path offset: %u", hc->target_path_offset); + HTTP_DBG (0, "request-target query length: %u", hc->target_query_len); + HTTP_DBG (0, "request-target query offset: %u", hc->target_query_offset); + + /* set buffer offset to nex line start */ + hc->rx_buf_offset = next_line_offset; + + return 0; +} + +static int +http_identify_headers (http_conn_t *hc, http_status_code_t *ec) +{ + int i; + + /* check if we have any header */ + if ((hc->rx_buf[hc->rx_buf_offset] == '\r') && + (hc->rx_buf[hc->rx_buf_offset + 1] == '\n')) + { + /* just another CRLF -> no headers */ + HTTP_DBG (0, "no headers"); + hc->headers_len = 0; + return 0; + } + + /* find empty line indicating end of header section */ + i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, "\r\n\r\n"); + if (i < 0) + { + clib_warning ("cannot find header section end"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + hc->headers_offset = hc->rx_buf_offset; + hc->headers_len = i - hc->rx_buf_offset + 2; + HTTP_DBG (0, "headers length: %u", hc->headers_len); + HTTP_DBG (0, "headers offset: %u", hc->headers_offset); + + return 0; +} + +static int +http_identify_message_body (http_conn_t *hc, http_status_code_t *ec) +{ + unformat_input_t input; + int i, len; + u8 *line; + + hc->body_len = 0; + + if (hc->headers_len == 0) + { + HTTP_DBG (0, "no header, no message-body"); + return 0; + } + + /* TODO check for chunked transfer coding */ + + /* try to find Content-Length header */ + i = v_find_index (hc->rx_buf, hc->headers_offset, hc->headers_len, + "Content-Length:"); + if (i < 0) + { + HTTP_DBG (0, "Content-Length header not present, no message-body"); + return 0; + } + hc->rx_buf_offset = i + 15; + + i = v_find_index (hc->rx_buf, hc->rx_buf_offset, hc->headers_len, "\r\n"); + if (i < 0) + { + clib_warning ("end of line missing"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + len = i - hc->rx_buf_offset; + if (len < 1) + { + clib_warning ("invalid header, content length value missing"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + line = vec_new (u8, len); + clib_memcpy (line, hc->rx_buf + hc->rx_buf_offset, len); + HTTP_DBG (0, "%v", line); + + unformat_init_vector (&input, line); + if (!unformat (&input, "%lu", &hc->body_len)) + { + clib_warning ("failed to unformat content length value"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + unformat_free (&input); + + hc->body_offset = hc->headers_offset + hc->headers_len + 2; + HTTP_DBG (0, "body length: %u", hc->body_len); + HTTP_DBG (0, "body offset: %u", hc->body_offset); + + return 0; +} + static int http_parse_header (http_conn_t *hc, int *content_length) { @@ -477,7 +758,7 @@ http_parse_header (http_conn_t *hc, int *content_length) int i, len; u8 *line; - i = v_find_index (hc->rx_buf, hc->rx_buf_offset, CONTENT_LEN_STR); + i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, CONTENT_LEN_STR); if (i < 0) { clib_warning ("cannot find '%s' in the header!", CONTENT_LEN_STR); @@ -486,7 +767,7 @@ http_parse_header (http_conn_t *hc, int *content_length) hc->rx_buf_offset = i; - i = v_find_index (hc->rx_buf, hc->rx_buf_offset, "\n"); + i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, "\n"); if (i < 0) { clib_warning ("end of line missing; incomplete data"); @@ -507,7 +788,7 @@ http_parse_header (http_conn_t *hc, int *content_length) /* skip rest of the header */ hc->rx_buf_offset += len; - i = v_find_index (hc->rx_buf, hc->rx_buf_offset, "<html>"); + i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, "<html>"); if (i < 0) { clib_warning ("<html> tag not found"); @@ -541,7 +822,7 @@ http_state_wait_server_reply (http_conn_t *hc, transport_send_params_t *sp) goto error; } - if ((i = v_find_index (hc->rx_buf, 0, "200 OK")) >= 0) + if ((i = v_find_index (hc->rx_buf, 0, 0, "200 OK")) >= 0) { msg.type = HTTP_MSG_REPLY; msg.content_type = HTTP_CONTENT_TEXT_HTML; @@ -614,9 +895,8 @@ http_state_wait_client_method (http_conn_t *hc, transport_send_params_t *sp) app_worker_t *app_wrk; http_msg_t msg; session_t *as; - int i, rv; + int rv; u32 len; - u8 *buf; rv = http_read_message (hc); @@ -624,50 +904,45 @@ http_state_wait_client_method (http_conn_t *hc, transport_send_params_t *sp) if (rv) return HTTP_SM_STOP; + HTTP_DBG (0, "%v", hc->rx_buf); + if (vec_len (hc->rx_buf) < 8) { ec = HTTP_STATUS_BAD_REQUEST; goto error; } - if ((i = v_find_index (hc->rx_buf, 0, "GET ")) >= 0) - { - hc->method = HTTP_REQ_GET; - hc->rx_buf_offset = i + 5; + rv = http_parse_request_line (hc, &ec); + if (rv) + goto error; - i = v_find_index (hc->rx_buf, hc->rx_buf_offset, "HTTP"); - if (i < 0) - { - ec = HTTP_STATUS_BAD_REQUEST; - goto error; - } + rv = http_identify_headers (hc, &ec); + if (rv) + goto error; - HTTP_DBG (0, "GET method %v", hc->rx_buf); - len = i - hc->rx_buf_offset - 1; - } - else if ((i = v_find_index (hc->rx_buf, 0, "POST ")) >= 0) - { - hc->method = HTTP_REQ_POST; - hc->rx_buf_offset = i + 6; - len = vec_len (hc->rx_buf) - hc->rx_buf_offset - 1; - HTTP_DBG (0, "POST method %v", hc->rx_buf); - } - else - { - HTTP_DBG (0, "Unknown http method %v", hc->rx_buf); - ec = HTTP_STATUS_METHOD_NOT_ALLOWED; - goto error; - } + rv = http_identify_message_body (hc, &ec); + if (rv) + goto error; - buf = &hc->rx_buf[hc->rx_buf_offset]; + len = vec_len (hc->rx_buf); msg.type = HTTP_MSG_REQUEST; msg.method_type = hc->method; msg.content_type = HTTP_CONTENT_TEXT_HTML; msg.data.type = HTTP_MSG_DATA_INLINE; msg.data.len = len; - - svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { buf, len } }; + msg.data.target_form = hc->target_form; + msg.data.target_path_offset = hc->target_path_offset; + msg.data.target_path_len = hc->target_path_len; + msg.data.target_query_offset = hc->target_query_offset; + msg.data.target_query_len = hc->target_query_len; + msg.data.headers_offset = hc->headers_offset; + msg.data.headers_len = hc->headers_len; + msg.data.body_offset = hc->body_offset; + msg.data.body_len = hc->body_len; + + svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, + { hc->rx_buf, len } }; as = session_get_from_handle (hc->h_pa_session_handle); rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, 0 /* allow partial */); @@ -748,6 +1023,7 @@ http_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp) case HTTP_STATUS_METHOD_NOT_ALLOWED: case HTTP_STATUS_BAD_REQUEST: case HTTP_STATUS_INTERNAL_ERROR: + case HTTP_STATUS_FORBIDDEN: case HTTP_STATUS_OK: header = format (0, http_response_template, http_status_code_str[msg.code], |