diff options
author | Matus Fabian <matfabia@cisco.com> | 2025-01-12 22:18:56 +0100 |
---|---|---|
committer | Matus Fabian <matfabia@cisco.com> | 2025-01-21 13:47:28 -0500 |
commit | 98d1264fcdb81c1213e1d0ac02098a3f6673631a (patch) | |
tree | 71fa5fe3da48681c64419bc6ceab24e2f442fb42 | |
parent | 98028dd4c475f8929cf07a87114e857bfc6bda93 (diff) |
http: target parsing improvement
Make it http version neutral, since h2 and h3 use 3 pseudo-headers.
Added scheme, target_authority_offset and target_authority_len
to http_msg_data_t, target_form removed.
Http transport now validate if correct form of request target
is received, so now we are also able to receive requests with
absolute-form target in server apps like http_static.
As bonus, unformat is not longer used to parse IP addresses.
Type: improvement
Change-Id: I369f77e2639c43cc1244d91f883c526eb88af63e
Signed-off-by: Matus Fabian <matfabia@cisco.com>
-rw-r--r-- | extras/hs-test/http_test.go | 68 | ||||
-rw-r--r-- | extras/hs-test/infra/connect_udp_client.go | 1 | ||||
-rw-r--r-- | extras/hs-test/proxy_test.go | 25 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_cli.c | 3 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_client.c | 1 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_client_cli.c | 1 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_tps.c | 3 | ||||
-rw-r--r-- | src/plugins/hs_apps/proxy.c | 81 | ||||
-rw-r--r-- | src/plugins/hs_apps/vcl/vcl_test_protos.c | 8 | ||||
-rw-r--r-- | src/plugins/http/http.c | 121 | ||||
-rw-r--r-- | src/plugins/http/http.h | 408 | ||||
-rw-r--r-- | src/plugins/http/http_plugin.rst | 44 | ||||
-rw-r--r-- | src/plugins/http/test/http_test.c | 397 | ||||
-rw-r--r-- | src/plugins/http_static/static_server.c | 6 |
14 files changed, 666 insertions, 501 deletions
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 4b67d274882..4cbcdeb1b21 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -26,7 +26,7 @@ func init() { RegisterVethTests(HttpCliTest, HttpCliConnectErrorTest) RegisterSoloVethTests(HttpClientGetMemLeakTest) RegisterNoTopoTests(HeaderServerTest, HttpPersistentConnectionTest, HttpPipeliningTest, - HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest, + HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest, HttpAbsoluteFormUriTest, HttpCliBadRequestTest, HttpStaticBuildInUrlGetIfStatsTest, HttpStaticBuildInUrlPostIfStatsTest, HttpInvalidRequestLineTest, HttpMethodNotImplementedTest, HttpInvalidHeadersTest, HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest, @@ -36,7 +36,7 @@ func init() { HttpClientErrRespTest, HttpClientPostFormTest, HttpClientGet128kbResponseTest, HttpClientGetResponseBodyTest, HttpClientGetNoResponseBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest, HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, - HttpClientGetRepeat, HttpClientPostRepeat, HttpIgnoreH2UpgradeTest) + HttpClientGetRepeat, HttpClientPostRepeat, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest) @@ -1204,6 +1204,18 @@ func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) { s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target query") + + resp, err = TcpSendReceive(serverAddress+":80", "GET * HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "asterisk-form is only used for a server-wide OPTIONS request") + + resp, err = TcpSendReceive(serverAddress+":80", "GET www.example.com:80 HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "authority-form is only used for CONNECT requests") + + resp, err = TcpSendReceive(serverAddress+":80", "CONNECT https://www.example.com/tunnel HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "CONNECT requests must use authority-form only") } func HttpInvalidContentLengthTest(s *NoTopoSuite) { @@ -1294,6 +1306,58 @@ func HttpUriDecodeTest(s *NoTopoSuite) { s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html") } +func HttpAbsoluteFormUriTest(s *NoTopoSuite) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.VppAddr() + vpp.Vppctl("http cli server") + + resp, err := TcpSendReceive(serverAddress+":80", "GET http://"+serverAddress+"/show/version HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 200 OK") + + resp, err = TcpSendReceive(serverAddress+":80", "GET http://"+serverAddress+":80/show/version HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 200 OK") +} + +func HttpInvalidAuthorityFormUriTest(s *NoTopoSuite) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.VppAddr() + vpp.Vppctl("test proxy server fifo-size 512k server-uri http://%s/8080", serverAddress) + + resp, err := TcpSendReceive(serverAddress+":8080", "CONNECT 1.2.3.4:80a HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request") + + resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT 1.2.3.4:80000000 HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request") + + resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT 1.2a3.4:80 HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request") + + resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT 1.2.4:80 HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request") + + resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT [dead:beef::1234:443 HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request") + + resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT [zyx:beef::1234]:443 HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request") + + resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT dead:beef::1234:443 HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request") + + resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT example.org:443 HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "name resolution not supported") +} + func HttpHeadersTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() diff --git a/extras/hs-test/infra/connect_udp_client.go b/extras/hs-test/infra/connect_udp_client.go index 595f7e7f2e6..a30ad3a769a 100644 --- a/extras/hs-test/infra/connect_udp_client.go +++ b/extras/hs-test/infra/connect_udp_client.go @@ -81,6 +81,7 @@ func (c *ConnectUdpClient) Dial(proxyAddress, targetUri string) error { return errors.New("request failed: " + resp.Status) } if resp.Header.Get("Connection") != "upgrade" || resp.Header.Get("Upgrade") != "connect-udp" || resp.Header.Get("Capsule-Protocol") != "?1" { + conn.Close() return errors.New("invalid response") } diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index ec90d24ba36..192451fe8f4 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -26,7 +26,7 @@ func init() { RegisterVppProxySoloTests(VppProxyHttpGetTcpMTTest, VppProxyHttpPutTcpMTTest, VppProxyTcpIperfMTTest, VppProxyUdpIperfMTTest, VppConnectProxyStressTest, VppConnectProxyStressMTTest, VppConnectProxyConnectionFailedMTTest) RegisterVppUdpProxyTests(VppProxyUdpTest, VppConnectUdpProxyTest, VppConnectUdpInvalidCapsuleTest, - VppConnectUdpUnknownCapsuleTest, VppConnectUdpClientCloseTest) + VppConnectUdpUnknownCapsuleTest, VppConnectUdpClientCloseTest, VppConnectUdpInvalidTargetTest) RegisterVppUdpProxySoloTests(VppProxyUdpMigrationMTTest, VppConnectUdpStressMTTest, VppConnectUdpStressTest) RegisterEnvoyProxyTests(EnvoyProxyHttpGetTcpTest, EnvoyProxyHttpPutTcpTest) RegisterNginxProxyTests(NginxMirroringTest) @@ -411,6 +411,29 @@ func VppConnectUdpProxyTest(s *VppUdpProxySuite) { s.AssertEqual(data, payload) } +func VppConnectUdpInvalidTargetTest(s *VppUdpProxySuite) { + vppProxy := s.Containers.VppProxy.VppInstance + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + s.Log(vppProxy.Vppctl(cmd)) + + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) + + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/example.com/80/", s.VppProxyAddr(), s.ProxyPort()) + c := s.NewConnectUdpClient(s.MaxTimeout, true) + err := c.Dial(proxyAddress, targetUri) + s.AssertNotNil(err, "name resolution not supported") + + targetUri = fmt.Sprintf("http://%s:%d/.well-known/masque/udp/1.2.3.4/800000000/", s.VppProxyAddr(), s.ProxyPort()) + c = s.NewConnectUdpClient(s.MaxTimeout, true) + err = c.Dial(proxyAddress, targetUri) + s.AssertNotNil(err, "invalid port number") + + targetUri = fmt.Sprintf("http://%s:%d/masque/udp/1.2.3.4/80/", s.VppProxyAddr(), s.ProxyPort()) + c = s.NewConnectUdpClient(s.MaxTimeout, true) + err = c.Dial(proxyAddress, targetUri) + s.AssertNotNil(err, "invalid prefix") +} + func VppConnectUdpInvalidCapsuleTest(s *VppUdpProxySuite) { remoteServerConn := s.StartEchoServer() defer remoteServerConn.Close() diff --git a/src/plugins/hs_apps/http_cli.c b/src/plugins/hs_apps/http_cli.c index 3ca86d24673..89eb49c8ec7 100644 --- a/src/plugins/hs_apps/http_cli.c +++ b/src/plugins/hs_apps/http_cli.c @@ -387,8 +387,7 @@ hcs_ts_rx_callback (session_t *ts) goto done; } - if (msg.data.target_path_len == 0 || - msg.data.target_form != HTTP_TARGET_ORIGIN_FORM) + if (msg.data.target_path_len == 0) { start_send_data (hs, HTTP_STATUS_BAD_REQUEST); goto done; diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index bfecc9a58fb..91ac6cf0032 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -238,7 +238,6 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, wrk->msg.type = HTTP_MSG_REQUEST; /* request target */ - wrk->msg.data.target_form = HTTP_TARGET_ORIGIN_FORM; wrk->msg.data.target_path_len = vec_len (hcm->target); /* custom headers */ wrk->msg.data.headers_len = vec_len (wrk->headers_buf); diff --git a/src/plugins/hs_apps/http_client_cli.c b/src/plugins/hs_apps/http_client_cli.c index b9658ed10d0..3c50e24c9fd 100644 --- a/src/plugins/hs_apps/http_client_cli.c +++ b/src/plugins/hs_apps/http_client_cli.c @@ -166,7 +166,6 @@ hcc_ts_connected_callback (u32 app_index, u32 hc_index, session_t *as, msg.type = HTTP_MSG_REQUEST; msg.method_type = HTTP_REQ_GET; /* request target */ - msg.data.target_form = HTTP_TARGET_ORIGIN_FORM; msg.data.target_path_offset = 0; msg.data.target_path_len = vec_len (hcm->http_query); /* custom headers */ diff --git a/src/plugins/hs_apps/http_tps.c b/src/plugins/hs_apps/http_tps.c index a40a31caf63..f4ef808e410 100644 --- a/src/plugins/hs_apps/http_tps.c +++ b/src/plugins/hs_apps/http_tps.c @@ -401,8 +401,7 @@ hts_ts_rx_callback (session_t *ts) goto done; } - if (msg.data.target_path_len == 0 || - msg.data.target_form != HTTP_TARGET_ORIGIN_FORM) + if (msg.data.target_path_len == 0) { hts_start_send_data (hs, HTTP_STATUS_BAD_REQUEST); goto done; diff --git a/src/plugins/hs_apps/proxy.c b/src/plugins/hs_apps/proxy.c index f96940e13af..f3b1fdce48c 100644 --- a/src/plugins/hs_apps/proxy.c +++ b/src/plugins/hs_apps/proxy.c @@ -507,12 +507,11 @@ proxy_http_connect (session_t *s, vnet_connect_args_t *a) { proxy_main_t *pm = &proxy_main; http_msg_t msg; - http_uri_t target_uri; + http_uri_authority_t target_uri; session_endpoint_cfg_t target_sep = SESSION_ENDPOINT_CFG_NULL; int rv; u8 *rx_buf = pm->rx_buf[s->thread_index]; http_header_table_t req_headers = pm->req_headers[s->thread_index]; - u32 target_offset, target_len; rv = svm_fifo_dequeue (s->rx_fifo, sizeof (msg), (u8 *) &msg); ASSERT (rv == sizeof (msg)); @@ -528,22 +527,27 @@ proxy_http_connect (session_t *s, vnet_connect_args_t *a) { /* TCP tunnel (RFC9110 section 9.3.6) */ PROXY_DBG ("CONNECT"); - if (msg.data.target_form != HTTP_TARGET_AUTHORITY_FORM) + /* get tunnel target */ + if (!msg.data.target_authority_len) { - PROXY_DBG ("CONNECT target not authority form"); + PROXY_DBG ("CONNECT target missing"); goto bad_req; } - - /* get tunnel target */ - ASSERT (msg.data.target_path_len <= pm->rcv_buffer_size); - rv = svm_fifo_peek (s->rx_fifo, msg.data.target_path_offset, - msg.data.target_path_len, rx_buf); - ASSERT (rv == msg.data.target_path_len); - rv = http_parse_authority_form_target (rx_buf, msg.data.target_path_len, - &target_uri); + ASSERT (msg.data.target_authority_len <= pm->rcv_buffer_size); + rv = svm_fifo_peek (s->rx_fifo, msg.data.target_authority_offset, + msg.data.target_authority_len, rx_buf); + ASSERT (rv == msg.data.target_authority_len); + rv = http_parse_authority (rx_buf, msg.data.target_authority_len, + &target_uri); if (rv) { - PROXY_DBG ("target parsing failed"); + PROXY_DBG ("authority parsing failed"); + goto bad_req; + } + /* TODO reg-name resolution */ + if (target_uri.host_type == HTTP_URI_HOST_TYPE_REG_NAME) + { + PROXY_DBG ("reg-name resolution not supported"); goto bad_req; } target_sep.transport_proto = TRANSPORT_PROTO_TCP; @@ -553,50 +557,28 @@ proxy_http_connect (session_t *s, vnet_connect_args_t *a) /* UDP tunnel (RFC9298) */ PROXY_DBG ("CONNECT-UDP"); /* get tunnel target */ - if (msg.data.target_form == HTTP_TARGET_ORIGIN_FORM) - { - if (msg.data.target_path_len < MASQUE_UDP_URI_MIN_LEN) - { - PROXY_DBG ("target too short"); - goto bad_req; - } - rv = svm_fifo_peek (s->rx_fifo, msg.data.target_path_offset, - msg.data.target_path_len, rx_buf); - ASSERT (rv == msg.data.target_path_len); - target_offset = 0; - target_len = msg.data.target_path_len; - } - else if (msg.data.target_form == HTTP_TARGET_ABSOLUTE_FORM) + if (msg.data.target_path_len < MASQUE_UDP_URI_MIN_LEN) { - http_url_t target_url; - ASSERT (msg.data.target_path_len <= pm->rcv_buffer_size); - rv = svm_fifo_peek (s->rx_fifo, msg.data.target_path_offset, - msg.data.target_path_len, rx_buf); - ASSERT (rv == msg.data.target_path_len); - rv = http_parse_absolute_form (rx_buf, msg.data.target_path_len, - &target_url); - if (rv || target_url.path_len < MASQUE_UDP_URI_MIN_LEN) - { - PROXY_DBG ("target parsing failed"); - goto bad_req; - } - target_offset = target_url.path_offset; - target_len = target_url.path_len; + PROXY_DBG ("invalid target"); + goto bad_req; } - else + ASSERT (msg.data.target_path_len <= pm->rcv_buffer_size); + rv = svm_fifo_peek (s->rx_fifo, msg.data.target_path_offset, + msg.data.target_path_len, rx_buf); + ASSERT (rv == msg.data.target_path_len); + if (http_validate_target_syntax (rx_buf, msg.data.target_path_len, 0, 0)) { - PROXY_DBG ("invalid target form"); + PROXY_DBG ("invalid target"); goto bad_req; } - if (memcmp (rx_buf + target_offset, masque_udp_uri_prefix, - MASQUE_UDP_URI_PREFIX_LEN)) + if (memcmp (rx_buf, masque_udp_uri_prefix, MASQUE_UDP_URI_PREFIX_LEN)) { PROXY_DBG ("uri prefix not match"); goto bad_req; } rv = http_parse_masque_host_port ( - rx_buf + target_offset + MASQUE_UDP_URI_PREFIX_LEN, - target_len - MASQUE_UDP_URI_PREFIX_LEN, &target_uri); + rx_buf + MASQUE_UDP_URI_PREFIX_LEN, + msg.data.target_path_len - MASQUE_UDP_URI_PREFIX_LEN, &target_uri); if (rv) { PROXY_DBG ("masque host/port parsing failed"); @@ -633,9 +615,10 @@ proxy_http_connect (session_t *s, vnet_connect_args_t *a) return; } PROXY_DBG ("proxy target %U:%u", format_ip46_address, &target_uri.ip, - target_uri.is_ip4, clib_net_to_host_u16 (target_uri.port)); + target_uri.host_type == HTTP_URI_HOST_TYPE_IP4, + clib_net_to_host_u16 (target_uri.port)); svm_fifo_dequeue_drop (s->rx_fifo, msg.data.len); - target_sep.is_ip4 = target_uri.is_ip4; + target_sep.is_ip4 = target_uri.host_type == HTTP_URI_HOST_TYPE_IP4; target_sep.ip = target_uri.ip; target_sep.port = target_uri.port; clib_memcpy (&a->sep_ext, &target_sep, sizeof (target_sep)); diff --git a/src/plugins/hs_apps/vcl/vcl_test_protos.c b/src/plugins/hs_apps/vcl/vcl_test_protos.c index 9c81c5f17a1..da4b6997ec1 100644 --- a/src/plugins/hs_apps/vcl/vcl_test_protos.c +++ b/src/plugins/hs_apps/vcl/vcl_test_protos.c @@ -1087,13 +1087,6 @@ vt_process_http_server_read_msg (vcl_test_session_t *ts, void *buf, return 0; } - if (msg.data.target_form != HTTP_TARGET_ORIGIN_FORM) - { - vt_http_send_reply_msg (ts, HTTP_STATUS_BAD_REQUEST); - vterr ("error! http target not in origin form", 0); - return 0; - } - /* validate target path syntax */ if (msg.data.target_path_len) { @@ -1225,7 +1218,6 @@ vt_process_http_client_write_msg (vcl_test_session_t *ts, void *buf, msg.method_type = HTTP_REQ_POST; /* target */ - msg.data.target_form = HTTP_TARGET_ORIGIN_FORM; target = (u8 *) "/vcl_test_http\0"; msg.data.target_path_len = strlen ((char *) target); diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index c33c85a303e..666f45c7d8f 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -42,6 +42,13 @@ const char *http_upgrade_proto_str[] = { "", #undef _ }; +#define expect_char(c) \ + if (*p++ != c) \ + { \ + clib_warning ("unexpected character"); \ + return -1; \ + } + static u8 * format_http_req_state (u8 *s, va_list *va) { @@ -619,19 +626,21 @@ http_identify_optional_query (http_req_t *req) } static int -http_get_target_form (http_req_t *req) +http_parse_target (http_req_t *req) { int i; + u8 *p, *end; - /* "*" */ + /* asterisk-form = "*" */ if ((req->rx_buf[req->target_path_offset] == '*') && (req->target_path_len == 1)) { req->target_form = HTTP_TARGET_ASTERISK_FORM; - return 0; + /* we do not support OPTIONS request */ + return -1; } - /* 1*( "/" segment ) [ "?" query ] */ + /* origin-form = 1*( "/" segment ) [ "?" query ] */ if (req->rx_buf[req->target_path_offset] == '/') { /* drop leading slash */ @@ -639,27 +648,66 @@ http_get_target_form (http_req_t *req) req->target_path_offset++; req->target_form = HTTP_TARGET_ORIGIN_FORM; http_identify_optional_query (req); - return 0; + /* can't be CONNECT method */ + return req->method == HTTP_REQ_CONNECT ? -1 : 0; } - /* scheme "://" host [ ":" port ] *( "/" segment ) [ "?" query ] */ - i = v_find_index (req->rx_buf, req->target_path_offset, req->target_path_len, - "://"); - if (i > 0) + /* absolute-form = + * scheme "://" host [ ":" port ] *( "/" segment ) [ "?" query ] */ + if (req->target_path_len > 8 && + !memcmp (req->rx_buf + req->target_path_offset, "http", 4)) { - req->target_form = HTTP_TARGET_ABSOLUTE_FORM; - http_identify_optional_query (req); - return 0; + req->scheme = HTTP_URL_SCHEME_HTTP; + p = req->rx_buf + req->target_path_offset + 4; + if (*p == 's') + { + p++; + req->scheme = HTTP_URL_SCHEME_HTTPS; + } + if (*p++ == ':') + { + expect_char ('/'); + expect_char ('/'); + req->target_form = HTTP_TARGET_ABSOLUTE_FORM; + req->target_authority_offset = p - req->rx_buf; + req->target_authority_len = 0; + end = req->rx_buf + req->target_path_offset + req->target_path_len; + while (p < end) + { + if (*p == '/') + { + p++; /* drop leading slash */ + req->target_path_offset = p - req->rx_buf; + req->target_path_len = end - p; + break; + } + req->target_authority_len++; + p++; + } + if (!req->target_path_len) + { + clib_warning ("zero length host"); + return -1; + } + http_identify_optional_query (req); + /* can't be CONNECT method */ + return req->method == HTTP_REQ_CONNECT ? -1 : 0; + } } - /* host ":" port */ + /* authority-form = host ":" port */ for (i = req->target_path_offset; i < (req->target_path_offset + req->target_path_len); i++) { if ((req->rx_buf[i] == ':') && (isdigit (req->rx_buf[i + 1]))) { + req->target_authority_len = req->target_path_len; + req->target_path_len = 0; + req->target_authority_offset = req->target_path_offset; + req->target_path_offset = 0; req->target_form = HTTP_TARGET_AUTHORITY_FORM; - return 0; + /* "authority-form" is only used for CONNECT requests */ + return req->method == HTTP_REQ_CONNECT ? 0 : -1; } } @@ -776,7 +824,9 @@ http_parse_request_line (http_req_t *req, http_status_code_t *ec) req->target_path_len = target_len; req->target_query_offset = 0; req->target_query_len = 0; - if (http_get_target_form (req)) + req->target_authority_len = 0; + req->target_authority_offset = 0; + if (http_parse_target (req)) { clib_warning ("invalid target"); *ec = HTTP_STATUS_BAD_REQUEST; @@ -793,13 +843,6 @@ http_parse_request_line (http_req_t *req, http_status_code_t *ec) return 0; } -#define expect_char(c) \ - if (*p++ != c) \ - { \ - clib_warning ("unexpected character"); \ - return -1; \ - } - #define parse_int(val, mul) \ do \ { \ @@ -913,6 +956,7 @@ http_identify_headers (http_req_t *req, http_status_code_t *ec) req->content_len_header_index = ~0; req->connection_header_index = ~0; req->upgrade_header_index = ~0; + req->host_header_index = ~0; req->headers_offset = req->rx_buf_offset; /* check if we have any header */ @@ -970,6 +1014,10 @@ http_identify_headers (http_req_t *req, http_status_code_t *ec) (const char *) name_start, name_len, http_header_name_token (HTTP_HEADER_UPGRADE))) req->upgrade_header_index = header_index; + else if (req->host_header_index == ~0 && + http_token_is_case ((const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_HOST))) + req->host_header_index = header_index; /* are we done? */ if (*p == '\r' && *(p + 1) == '\n') @@ -1185,6 +1233,30 @@ http_check_connection_upgrade (http_req_t *req) } } +static void +http_target_fixup (http_conn_t *hc) +{ + http_field_line_t *host; + + if (hc->req.target_form == HTTP_TARGET_ABSOLUTE_FORM) + return; + + /* scheme fixup */ + hc->req.scheme = session_get_transport_proto (session_get_from_handle ( + hc->h_tc_session_handle)) == TRANSPORT_PROTO_TLS ? + HTTP_URL_SCHEME_HTTPS : + HTTP_URL_SCHEME_HTTP; + + if (hc->req.target_form == HTTP_TARGET_AUTHORITY_FORM || + hc->req.connection_header_index == ~0) + return; + + /* authority fixup */ + host = vec_elt_at_index (hc->req.headers, hc->req.connection_header_index); + hc->req.target_authority_offset = host->value_offset; + hc->req.target_authority_len = host->value_len; +} + static http_sm_result_t http_req_state_wait_transport_method (http_conn_t *hc, transport_send_params_t *sp) @@ -1219,6 +1291,7 @@ http_req_state_wait_transport_method (http_conn_t *hc, if (rv) goto error; + http_target_fixup (hc); http_check_connection_upgrade (&hc->req); rv = http_identify_message_body (&hc->req, &ec); @@ -1244,7 +1317,9 @@ http_req_state_wait_transport_method (http_conn_t *hc, msg.method_type = hc->req.method; msg.data.type = HTTP_MSG_DATA_INLINE; msg.data.len = len; - msg.data.target_form = hc->req.target_form; + msg.data.scheme = hc->req.scheme; + msg.data.target_authority_offset = hc->req.target_authority_offset; + msg.data.target_authority_len = hc->req.target_authority_len; msg.data.target_path_offset = hc->req.target_path_offset; msg.data.target_path_len = hc->req.target_path_len; msg.data.target_query_offset = hc->req.target_query_offset; diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 3bdbc6cfdda..008f6634ea9 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -395,11 +395,19 @@ typedef struct http_field_line_ u32 value_len; } http_field_line_t; +typedef enum http_url_scheme_ +{ + HTTP_URL_SCHEME_HTTP, + HTTP_URL_SCHEME_HTTPS, +} http_url_scheme_t; + typedef struct http_msg_data_ { http_msg_data_type_t type; u64 len; - http_target_form_t target_form; + http_url_scheme_t scheme; + u32 target_authority_offset; + u32 target_authority_len; u32 target_path_offset; u32 target_path_len; u32 target_query_offset; @@ -455,6 +463,9 @@ typedef struct http_req_ }; http_target_form_t target_form; + http_url_scheme_t scheme; + u32 target_authority_offset; + u32 target_authority_len; u32 target_path_offset; u32 target_path_len; u32 target_query_offset; @@ -470,6 +481,7 @@ typedef struct http_req_ uword content_len_header_index; uword connection_header_index; uword upgrade_header_index; + uword host_header_index; http_upgrade_proto_t upgrade_proto; } http_req_t; @@ -553,7 +565,8 @@ format_http_bytes (u8 *s, va_list *va) } always_inline int -_validate_target_syntax (u8 *target, u32 len, int is_query, int *is_encoded) +http_validate_target_syntax (u8 *target, u32 len, int is_query, + int *is_encoded) { int encoded = 0; u32 i; @@ -605,13 +618,13 @@ _validate_target_syntax (u8 *target, u32 len, int is_query, int *is_encoded) always_inline int http_validate_abs_path_syntax (u8 *path, int *is_encoded) { - return _validate_target_syntax (path, vec_len (path), 0, is_encoded); + return http_validate_target_syntax (path, vec_len (path), 0, is_encoded); } /** * A "query" rule validation (RFC3986 section 2.1). * - * @param query Vector of target query to validate. + * @param query Target query to validate. * @param is_encoded Return flag that indicates if percent-encoded (optional). * * @return @c 0 on success. @@ -619,7 +632,7 @@ http_validate_abs_path_syntax (u8 *path, int *is_encoded) always_inline int http_validate_query_syntax (u8 *query, int *is_encoded) { - return _validate_target_syntax (query, vec_len (query), 1, is_encoded); + return http_validate_target_syntax (query, vec_len (query), 1, is_encoded); } #define htoi(x) (isdigit (x) ? (x - '0') : (tolower (x) - 'a' + 10)) @@ -1106,90 +1119,166 @@ http_serialize_headers (http_header_t *headers) return headers_buf; } +typedef enum http_uri_host_type_ +{ + HTTP_URI_HOST_TYPE_IP4, + HTTP_URI_HOST_TYPE_IP6, + HTTP_URI_HOST_TYPE_REG_NAME +} http_uri_host_type_t; + typedef struct { - ip46_address_t ip; + http_uri_host_type_t host_type; + union + { + ip46_address_t ip; + http_token_t reg_name; + }; u16 port; - u8 is_ip4; -} http_uri_t; +} http_uri_authority_t; -/** - * An "authority-form" URL parsing. - * - * @param target Target URL to parse. - * @param target_len Length of URL. - * @param authority Parsed URL metadata in case of success. - * - * @return @c 0 on success. - */ always_inline int -http_parse_authority_form_target (u8 *target, u32 target_len, - http_uri_t *authority) +_http_parse_ip4 (u8 **p, u8 *end, ip4_address_t *ip4) { - unformat_input_t input; - u8 *tmp = 0; - u32 port; + u8 n_octets = 0, digit, n_digits = 0; + u16 dec_octet = 0; int rv = 0; - vec_validate (tmp, target_len - 1); - vec_copy (tmp, target); - unformat_init_vector (&input, tmp); - if (unformat (&input, "[%U]:%d", unformat_ip6_address, &authority->ip.ip6, - &port)) - { - authority->port = clib_host_to_net_u16 (port); - authority->is_ip4 = 0; - } - else if (unformat (&input, "%U:%d", unformat_ip4_address, &authority->ip.ip4, - &port)) + while (*p != end) { - authority->port = clib_host_to_net_u16 (port); - authority->is_ip4 = 1; - } - /* TODO reg-name resolution */ - else - { - clib_warning ("unsupported format '%v'", target); - rv = -1; + if (**p >= '0' && **p <= '9') + { + digit = **p - '0'; + dec_octet = dec_octet * 10 + digit; + n_digits++; + /* must fit in 8 bits */ + if (dec_octet > 255) + return -1; + } + else if (**p == '.' && n_digits) + { + ip4->as_u8[n_octets++] = (u8) dec_octet; + dec_octet = 0; + n_digits = 0; + /* too many octets */ + if (n_octets >= ARRAY_LEN (ip4->as_u8)) + return -1; + } + else + { + /* probably more data (delimiter) after IPv4 address */ + rv = **p; + break; + } + + (*p)++; } - unformat_free (&input); + + /* must end with octet */ + if (!n_digits) + return -1; + + ip4->as_u8[n_octets++] = (u8) dec_octet; + + /* too few octets */ + if (n_octets < ARRAY_LEN (ip4->as_u8)) + return -1; + return rv; } -always_inline u8 * -http_serialize_authority_form_target (http_uri_t *authority) +/* modified unformat_ip6_address */ +always_inline int +_http_parse_ip6 (u8 **p, u8 *end, ip6_address_t *ip6) { - u8 *s; + u8 n_hex_digits = 0, n_colon = 0, n_hex_quads = 0; + u8 double_colon_index = ~0, i; + u16 hex_digit; + u32 hex_quad = 0; + int rv = 0; - if (authority->is_ip4) - s = format (0, "%U:%d", format_ip4_address, &authority->ip.ip4, - clib_net_to_host_u16 (authority->port)); - else - s = format (0, "[%U]:%d", format_ip6_address, &authority->ip.ip6, - clib_net_to_host_u16 (authority->port)); + while (*p != end) + { + hex_digit = 16; + if (**p >= '0' && **p <= '9') + hex_digit = **p - '0'; + else if (**p >= 'a' && **p <= 'f') + hex_digit = **p + 10 - 'a'; + else if (**p >= 'A' && **p <= 'F') + hex_digit = **p + 10 - 'A'; + else if (**p == ':' && n_colon < 2) + n_colon++; + else + { + /* probably more data (delimiter) after IPv6 address */ + rv = **p; + break; + } - return s; -} + /* too many hex quads */ + if (n_hex_quads >= ARRAY_LEN (ip6->as_u16)) + return -1; -typedef enum http_url_scheme_ -{ - HTTP_URL_SCHEME_HTTP, - HTTP_URL_SCHEME_HTTPS, -} http_url_scheme_t; + if (hex_digit < 16) + { + hex_quad = (hex_quad << 4) | hex_digit; -typedef struct -{ - http_url_scheme_t scheme; - u16 port; - u32 host_offset; - u32 host_len; - u32 path_offset; - u32 path_len; - u8 host_is_ip6; -} http_url_t; + /* must fit in 16 bits */ + if (n_hex_digits >= 4) + return -1; + + n_colon = 0; + n_hex_digits++; + } + + /* save position of :: */ + if (n_colon == 2) + { + /* more than one :: ? */ + if (double_colon_index < ARRAY_LEN (ip6->as_u16)) + return -1; + double_colon_index = n_hex_quads; + } + + if (n_colon > 0 && n_hex_digits > 0) + { + ip6->as_u16[n_hex_quads++] = clib_host_to_net_u16 ((u16) hex_quad); + hex_quad = 0; + n_hex_digits = 0; + } + + (*p)++; + } + + if (n_hex_digits > 0) + ip6->as_u16[n_hex_quads++] = clib_host_to_net_u16 ((u16) hex_quad); + + /* expand :: to appropriate number of zero hex quads */ + if (double_colon_index < ARRAY_LEN (ip6->as_u16)) + { + u8 n_zero = ARRAY_LEN (ip6->as_u16) - n_hex_quads; + + for (i = n_hex_quads - 1; i >= double_colon_index; i--) + ip6->as_u16[n_zero + i] = ip6->as_u16[i]; + + for (i = 0; i < n_zero; i++) + { + ASSERT ((double_colon_index + i) < ARRAY_LEN (ip6->as_u16)); + ip6->as_u16[double_colon_index + i] = 0; + } + + n_hex_quads = ARRAY_LEN (ip6->as_u16); + } + + /* too few hex quads */ + if (n_hex_quads < ARRAY_LEN (ip6->as_u16)) + return -1; + + return rv; +} always_inline int -_parse_port (u8 **pos, u8 *end, u16 *port) +_http_parse_port (u8 **pos, u8 *end, u16 *port) { u32 value = 0; u8 *p = *pos; @@ -1214,19 +1303,20 @@ _parse_port (u8 **pos, u8 *end, u16 *port) } /** - * An "absolute-form" URL parsing. + * Parse authority to components. * - * @param url Target URL to parse. - * @param url_len Length of URL. - * @param parsed Parsed URL metadata in case of success. + * @param authority Target URL to parse. + * @param authority_len Length of URL. + * @param parsed Parsed authority (port is se to 0 if not present). * * @return @c 0 on success. */ always_inline int -http_parse_absolute_form (u8 *url, u32 url_len, http_url_t *parsed) +http_parse_authority (u8 *authority, u32 authority_len, + http_uri_authority_t *parsed) { - u8 *token_start, *token_end, *end; - int is_encoded = 0; + u8 *token_start, *p, *end; + int rv; static uword valid_chars[4] = { /* -.0123456789 */ @@ -1237,111 +1327,102 @@ http_parse_absolute_form (u8 *url, u32 url_len, http_url_t *parsed) 0x0000000000000000, }; - if (url_len < 9) - { - clib_warning ("uri too short"); - return -1; - } - - clib_memset (parsed, 0, sizeof (*parsed)); + /* reg-name max 255 chars + colon + port max 5 chars */ + if (authority_len > 261) + return -1; - end = url + url_len; + end = authority + authority_len; + token_start = authority; + parsed->port = 0; - /* parse scheme */ - if (!memcmp (url, "http:// ", 7)) - { - parsed->scheme = HTTP_URL_SCHEME_HTTP; - parsed->port = clib_host_to_net_u16 (80); - parsed->host_offset = 7; - } - else if (!memcmp (url, "https:// ", 8)) + /* parse host */ + if (*token_start == '[') { - parsed->scheme = HTTP_URL_SCHEME_HTTPS; - parsed->port = clib_host_to_net_u16 (443); - parsed->host_offset = 8; + /* IPv6 address */ + if (authority_len < 4) + return -1; + + p = ++token_start; + rv = _http_parse_ip6 (&p, end, &parsed->ip.ip6); + if (rv != ']') + return -1; + + parsed->host_type = HTTP_URI_HOST_TYPE_IP6; + token_start = ++p; } - else + else if (isdigit (*token_start)) { - clib_warning ("invalid scheme"); - return -1; - } - token_start = url + parsed->host_offset; + /* maybe IPv4 address */ + p = token_start; - /* parse host */ - if (*token_start == '[') - /* IPv6 address */ - { - parsed->host_is_ip6 = 1; - parsed->host_offset++; - token_end = ++token_start; - while (1) + if (authority_len < 7) + goto reg_name; + + rv = _http_parse_ip4 (&p, end, &parsed->ip.ip4); + if (rv == 0 || rv == ':') { - if (token_end == end) - { - clib_warning ("invalid host, IPv6 addr not terminated with ']'"); - return -1; - } - else if (*token_end == ']') - { - parsed->host_len = token_end - token_start; - token_start = token_end + 1; - break; - } - else if (*token_end != ':' && *token_end != '.' && - !isxdigit (*token_end)) - { - clib_warning ("invalid character '%u'", *token_end); - return -1; - } - token_end++; + parsed->host_type = HTTP_URI_HOST_TYPE_IP4; + token_start = p; } + else + goto reg_name; } else { - token_end = token_start; - while (token_end != end && *token_end != ':' && *token_end != '/') + /* registered name */ + p = token_start; + reg_name: + while (p != end && *p != ':') { - if (!clib_bitmap_get_no_check (valid_chars, *token_end)) + if (!clib_bitmap_get_no_check (valid_chars, *p)) { - clib_warning ("invalid character '%u'", *token_end); + clib_warning ("invalid character '%u'", *p); return -1; } - token_end++; + p++; } - parsed->host_len = token_end - token_start; - token_start = token_end; - } - - if (!parsed->host_len) - { - clib_warning ("zero length host"); - return -1; + parsed->reg_name.len = p - token_start; + if (parsed->reg_name.len > 255) + { + clib_warning ("reg-name too long"); + return -1; + } + parsed->host_type = HTTP_URI_HOST_TYPE_REG_NAME; + parsed->reg_name.base = (char *) token_start; + token_start = p; } /* parse port, if any */ - if (token_start != end && *token_start == ':') + if ((end - token_start) > 1 && *token_start == ':') { - token_end = ++token_start; - if (_parse_port (&token_end, end, &parsed->port)) + token_start++; + if (_http_parse_port (&token_start, end, &parsed->port)) { clib_warning ("invalid port"); return -1; } - token_start = token_end; } - if (token_start == end) - return 0; + return token_start == end ? 0 : -1; +} + +always_inline u8 * +http_serialize_authority (http_uri_authority_t *authority) +{ + u8 *s; - token_start++; /* drop leading slash */ - parsed->path_offset = token_start - url; - parsed->path_len = end - token_start; + if (authority->host_type == HTTP_URI_HOST_TYPE_IP4) + s = format (0, "%U", format_ip4_address, &authority->ip.ip4); + else if (authority->host_type == HTTP_URI_HOST_TYPE_IP6) + s = format (0, "[%U]", format_ip6_address, &authority->ip.ip6); + else + s = format (0, "%U", format_http_bytes, authority->reg_name.base, + authority->reg_name.len); - if (parsed->path_len) - return _validate_target_syntax (token_start, parsed->path_len, 0, - &is_encoded); + if (authority->port) + s = format (s, ":%d", clib_net_to_host_u16 (authority->port)); - return 0; + return s; } /** @@ -1356,11 +1437,11 @@ http_parse_absolute_form (u8 *url, u32 url_len, http_url_t *parsed) * @note Only IPv4 literals and IPv6 literals supported. */ always_inline int -http_parse_masque_host_port (u8 *path, u32 path_len, http_uri_t *parsed) +http_parse_masque_host_port (u8 *path, u32 path_len, + http_uri_authority_t *parsed) { - u8 *p, *end, *decoded_host; + u8 *p, *end, *decoded_host, *p4, *p6; u32 host_len; - unformat_input_t input; p = path; end = path + path_len; @@ -1373,21 +1454,22 @@ http_parse_masque_host_port (u8 *path, u32 path_len, http_uri_t *parsed) if (!host_len || (host_len == path_len) || (host_len + 1 == path_len)) return -1; decoded_host = http_percent_decode (path, host_len); - unformat_init_vector (&input, decoded_host); - if (unformat (&input, "%U", unformat_ip4_address, &parsed->ip.ip4)) - parsed->is_ip4 = 1; - else if (unformat (&input, "%U", unformat_ip6_address, &parsed->ip.ip6)) - parsed->is_ip4 = 0; + p4 = p6 = decoded_host; + if (0 == _http_parse_ip6 (&p6, p6 + vec_len (decoded_host), &parsed->ip.ip6)) + parsed->host_type = HTTP_URI_HOST_TYPE_IP6; + else if (0 == + _http_parse_ip4 (&p4, p4 + vec_len (decoded_host), &parsed->ip.ip4)) + parsed->host_type = HTTP_URI_HOST_TYPE_IP4; else { - unformat_free (&input); + vec_free (decoded_host); clib_warning ("unsupported target_host format"); return -1; } - unformat_free (&input); + vec_free (decoded_host); p++; - if (_parse_port (&p, end, &parsed->port)) + if (_http_parse_port (&p, end, &parsed->port)) { clib_warning ("invalid port"); return -1; diff --git a/src/plugins/http/http_plugin.rst b/src/plugins/http/http_plugin.rst index 61c70503e54..90ffb1919e6 100644 --- a/src/plugins/http/http_plugin.rst +++ b/src/plugins/http/http_plugin.rst @@ -16,10 +16,10 @@ Usage The plugin exposes following inline functions: ``http_validate_abs_path_syntax``, ``http_validate_query_syntax``, ``http_percent_decode``, ``http_path_remove_dot_segments``, ``http_build_header_table``, ``http_get_header``, -``http_reset_header_table``, ``http_free_header_table``, ``http_add_header``, -``http_serialize_headers``, ``http_parse_authority_form_target``, ``http_serialize_authority_form_target``, -``http_parse_absolute_form``, ``http_parse_masque_host_port``, ``http_decap_udp_payload_datagram``, -``http_encap_udp_payload_datagram``. ``http_token_is``, ``http_token_is_case``, ``http_token_contains`` +``http_reset_header_table``, ``http_free_header_table``, ``http_add_header``, ``http_validate_target_syntax``, +``http_serialize_headers``, ``http_parse_authority``, ``http_serialize_authority``, ``http_parse_masque_host_port``, +``http_decap_udp_payload_datagram``, ``http_encap_udp_payload_datagram``. ``http_token_is``, ``http_token_is_case``, +``http_token_contains`` It relies on the hoststack constructs and uses ``http_msg_data_t`` data structure for passing metadata to/from applications. @@ -36,7 +36,8 @@ HTTP plugin sends message header with metadata for parsing, in form of offset an Application will get pre-parsed following items: * HTTP method -* target form +* scheme (HTTP/HTTPS) +* target authority offset and length * target path offset and length * target query offset and length * header section offset and length @@ -65,30 +66,31 @@ Now application can start reading HTTP data. First let's read the target path: .. code-block:: C u8 *target_path; + if (msg.data.target_path_len == 0) + { + /* your error handling */ + } 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); -Application might also want to know target form which is stored in ``msg.data.target_form``, you can read more about target forms in RFC9112 section 3.2. -In case of origin form HTTP plugin always sets ``target_path_offset`` after leading slash character. +Target path might be in some cases empty (e.g. CONNECT method), you can read more about target forms in RFC9112 section 3.2. +In case of origin and absolute form HTTP plugin always sets ``target_path_offset`` after leading slash character. -Example bellow validates "absolute-path" rule, as described in RFC9110 section 4.1, in case of target in origin form, additionally application can get information if percent encoding is used and decode path: +Example bellow validates "absolute-path" rule, as described in RFC9110 section 4.1, additionally application can get information if percent encoding is used and decode path: .. code-block:: C int is_encoded = 0; - if (msg.data.target_form == HTTP_TARGET_ORIGIN_FORM) + if (http_validate_abs_path_syntax (target_path, &is_encoded)) { - if (http_validate_abs_path_syntax (target_path, &is_encoded)) - { - /* your error handling */ - } - if (is_encoded) - { - u8 *decoded = http_percent_decode (target_path, vec_len (target_path)); - vec_free (target_path); - target_path = decoded; - } + /* your error handling */ + } + if (is_encoded) + { + u8 *decoded = http_percent_decode (target_path, vec_len (target_path)); + vec_free (target_path); + target_path = decoded; } More on topic when to decode in RFC3986 section 2.4. @@ -245,7 +247,6 @@ When server application sends response back to HTTP layer it starts with message Application should set following items: * Status code -* target form * header section offset and length * body offset and length @@ -353,7 +354,7 @@ When client application sends message to HTTP layer it starts with message metad Application should set following items: * HTTP method -* target form, offset and length +* target offset and length * header section offset and length * body offset and length @@ -390,7 +391,6 @@ Following example shows how to set message metadata: msg.method_type = HTTP_REQ_GET; msg.data.headers_offset = 0; /* request target */ - msg.data.target_form = HTTP_TARGET_ORIGIN_FORM; msg.data.target_path_offset = 0; msg.data.target_path_len = vec_len (target); /* custom headers */ diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index 089f93ff574..7cf36f82577 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -29,220 +29,175 @@ } static int -http_test_authority_form (vlib_main_t *vm) +http_test_parse_authority (vlib_main_t *vm) { - u8 *target = 0, *formated_target = 0; - http_uri_t authority; + u8 *authority = 0, *formated = 0; + http_uri_authority_t parsed; int rv; - target = format (0, "10.10.2.45:20"); - rv = http_parse_authority_form_target (target, vec_len (target), &authority); - HTTP_TEST ((rv == 0), "'%v' should be valid", target); - formated_target = http_serialize_authority_form_target (&authority); - rv = vec_cmp (target, formated_target); - HTTP_TEST ((rv == 0), "'%v' should match '%v'", target, formated_target); - vec_free (target); - vec_free (formated_target); - - target = format (0, "[dead:beef::1234]:443"); - rv = http_parse_authority_form_target (target, vec_len (target), &authority); - HTTP_TEST ((rv == 0), "'%v' should be valid", target); - formated_target = http_serialize_authority_form_target (&authority); - rv = vec_cmp (target, formated_target); - HTTP_TEST ((rv == 0), "'%v' should match '%v'", target, formated_target); - vec_free (target); - vec_free (formated_target); - - target = format (0, "example.com:80"); - rv = http_parse_authority_form_target (target, vec_len (target), &authority); - HTTP_TEST ((rv != 0), "'%v' reg-name not supported", target); - vec_free (target); - - target = format (0, "10.10.2.45"); - rv = http_parse_authority_form_target (target, vec_len (target), &authority); - HTTP_TEST ((rv != 0), "'%v' should be invalid", target); - vec_free (target); - - target = format (0, "1000.10.2.45:20"); - rv = http_parse_authority_form_target (target, vec_len (target), &authority); - HTTP_TEST ((rv != 0), "'%v' should be invalid", target); - vec_free (target); - - target = format (0, "[xyz0::1234]:443"); - rv = http_parse_authority_form_target (target, vec_len (target), &authority); - HTTP_TEST ((rv != 0), "'%v' should be invalid", target); - vec_free (target); - - return 0; -} - -static int -http_test_absolute_form (vlib_main_t *vm) -{ - u8 *url = 0; - http_url_t parsed_url; - int rv; - - url = format (0, "https://example.org/.well-known/masque/udp/1.2.3.4/123/"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv == 0), "'%v' should be valid", url); - HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTPS), - "scheme should be https"); - HTTP_TEST ((parsed_url.host_is_ip6 == 0), "host_is_ip6=%u should be 0", - parsed_url.host_is_ip6); - HTTP_TEST ((parsed_url.host_offset == strlen ("https://")), - "host_offset=%u should be %u", parsed_url.host_offset, - strlen ("https://")); - HTTP_TEST ((parsed_url.host_len == strlen ("example.org")), - "host_len=%u should be %u", parsed_url.host_len, - strlen ("example.org")); - HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 443), - "port=%u should be 443", clib_net_to_host_u16 (parsed_url.port)); - HTTP_TEST ((parsed_url.path_offset == strlen ("https://example.org/")), - "path_offset=%u should be %u", parsed_url.path_offset, - strlen ("https://example.org/")); - HTTP_TEST ( - (parsed_url.path_len == strlen (".well-known/masque/udp/1.2.3.4/123/")), - "path_len=%u should be %u", parsed_url.path_len, - strlen (".well-known/masque/udp/1.2.3.4/123/")); - vec_free (url); - - url = format (0, "http://vpp-example.org"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv == 0), "'%v' should be valid", url); - HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTP), - "scheme should be http"); - HTTP_TEST ((parsed_url.host_is_ip6 == 0), "host_is_ip6=%u should be 0", - parsed_url.host_is_ip6); - HTTP_TEST ((parsed_url.host_offset == strlen ("http://")), - "host_offset=%u should be %u", parsed_url.host_offset, - strlen ("http://")); - HTTP_TEST ((parsed_url.host_len == strlen ("vpp-example.org")), - "host_len=%u should be %u", parsed_url.host_len, - strlen ("vpp-example.org")); - HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 80), - "port=%u should be 80", clib_net_to_host_u16 (parsed_url.port)); - HTTP_TEST ((parsed_url.path_len == 0), "path_len=%u should be 0", - parsed_url.path_len); - vec_free (url); - - url = format (0, "http://1.2.3.4:8080/abcd"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv == 0), "'%v' should be valid", url); - HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTP), - "scheme should be http"); - HTTP_TEST ((parsed_url.host_is_ip6 == 0), "host_is_ip6=%u should be 0", - parsed_url.host_is_ip6); - HTTP_TEST ((parsed_url.host_offset == strlen ("http://")), - "host_offset=%u should be %u", parsed_url.host_offset, - strlen ("http://")); - HTTP_TEST ((parsed_url.host_len == strlen ("1.2.3.4")), - "host_len=%u should be %u", parsed_url.host_len, - strlen ("1.2.3.4")); - HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 8080), - "port=%u should be 8080", clib_net_to_host_u16 (parsed_url.port)); - HTTP_TEST ((parsed_url.path_offset == strlen ("http://1.2.3.4:8080/")), - "path_offset=%u should be %u", parsed_url.path_offset, - strlen ("http://1.2.3.4:8080/")); - HTTP_TEST ((parsed_url.path_len == strlen ("abcd")), - "path_len=%u should be %u", parsed_url.path_len, strlen ("abcd")); - vec_free (url); - - url = format (0, "https://[dead:beef::1234]/abcd"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv == 0), "'%v' should be valid", url); - HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTPS), - "scheme should be https"); - HTTP_TEST ((parsed_url.host_is_ip6 == 1), "host_is_ip6=%u should be 1", - parsed_url.host_is_ip6); - HTTP_TEST ((parsed_url.host_offset == strlen ("https://[")), - "host_offset=%u should be %u", parsed_url.host_offset, - strlen ("https://[")); - HTTP_TEST ((parsed_url.host_len == strlen ("dead:beef::1234")), - "host_len=%u should be %u", parsed_url.host_len, - strlen ("dead:beef::1234")); - HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 443), - "port=%u should be 443", clib_net_to_host_u16 (parsed_url.port)); - HTTP_TEST ((parsed_url.path_offset == strlen ("https://[dead:beef::1234]/")), - "path_offset=%u should be %u", parsed_url.path_offset, - strlen ("https://[dead:beef::1234]/")); - HTTP_TEST ((parsed_url.path_len == strlen ("abcd")), - "path_len=%u should be %u", parsed_url.path_len, strlen ("abcd")); - vec_free (url); - - url = format (0, "http://[::ffff:192.0.2.128]:8080/"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv == 0), "'%v' should be valid", url); - HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTP), - "scheme should be http"); - HTTP_TEST ((parsed_url.host_is_ip6 == 1), "host_is_ip6=%u should be 1", - parsed_url.host_is_ip6); - HTTP_TEST ((parsed_url.host_offset == strlen ("http://[")), - "host_offset=%u should be %u", parsed_url.host_offset, - strlen ("http://[")); - HTTP_TEST ((parsed_url.host_len == strlen ("::ffff:192.0.2.128")), - "host_len=%u should be %u", parsed_url.host_len, - strlen ("::ffff:192.0.2.128")); - HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 8080), - "port=%u should be 8080", clib_net_to_host_u16 (parsed_url.port)); - HTTP_TEST ((parsed_url.path_len == 0), "path_len=%u should be 0", - parsed_url.path_len); - vec_free (url); - - url = format (0, "http://[dead:beef::1234/abc"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "http://[dead|beef::1234]/abc"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "http:example.org:8080/abcd"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "htt://example.org:8080/abcd"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "http://"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "http:///abcd"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "http://example.org:808080/abcd"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "http://example.org/a%%3Xbcd"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "http://example.org/a%%3"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "http://example.org/a[b]cd"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); - - url = format (0, "http://exa[m]ple.org/abcd"); - rv = http_parse_absolute_form (url, vec_len (url), &parsed_url); - HTTP_TEST ((rv != 0), "'%v' should be invalid", url); - vec_free (url); + /* IPv4 address */ + authority = format (0, "10.10.2.45:20"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_IP4), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_IP4); + HTTP_TEST ((clib_net_to_host_u16 (parsed.port) == 20), + "port=%u should be 20", clib_net_to_host_u16 (parsed.port)); + formated = http_serialize_authority (&parsed); + rv = vec_cmp (authority, formated); + HTTP_TEST ((rv == 0), "'%v' should match '%v'", authority, formated); + vec_free (authority); + vec_free (formated); + + authority = format (0, "10.255.2.1"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_IP4), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_IP4); + HTTP_TEST ((parsed.port == 0), "port=%u should be 0", parsed.port); + formated = http_serialize_authority (&parsed); + rv = vec_cmp (authority, formated); + HTTP_TEST ((rv == 0), "'%v' should match '%v'", authority, formated); + vec_free (authority); + vec_free (formated); + + /* IPv6 address */ + authority = format (0, "[dead:beef::1234]:443"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_IP6), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_IP6); + HTTP_TEST ((clib_net_to_host_u16 (parsed.port) == 443), + "port=%u should be 443", clib_net_to_host_u16 (parsed.port)); + formated = http_serialize_authority (&parsed); + rv = vec_cmp (authority, formated); + HTTP_TEST ((rv == 0), "'%v' should match '%v'", authority, formated); + vec_free (authority); + vec_free (formated); + + /* registered name */ + authority = format (0, "example.com:80"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_REG_NAME), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_REG_NAME); + HTTP_TEST ((clib_net_to_host_u16 (parsed.port) == 80), + "port=%u should be 80", clib_net_to_host_u16 (parsed.port)); + formated = http_serialize_authority (&parsed); + rv = vec_cmp (authority, formated); + HTTP_TEST ((rv == 0), "'%v' should match '%v'", authority, formated); + vec_free (authority); + vec_free (formated); + + authority = format (0, "3xample.com:80"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_REG_NAME), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_REG_NAME); + HTTP_TEST ((clib_net_to_host_u16 (parsed.port) == 80), + "port=%u should be 80", clib_net_to_host_u16 (parsed.port)); + formated = http_serialize_authority (&parsed); + rv = vec_cmp (authority, formated); + HTTP_TEST ((rv == 0), "'%v' should match '%v'", authority, formated); + vec_free (authority); + vec_free (formated); + + /* 'invalid IPv4 address' is recognized as registered name */ + authority = format (0, "1000.10.2.45:80"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_REG_NAME), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_REG_NAME); + HTTP_TEST ((clib_net_to_host_u16 (parsed.port) == 80), + "port=%u should be 80", clib_net_to_host_u16 (parsed.port)); + formated = http_serialize_authority (&parsed); + rv = vec_cmp (authority, formated); + HTTP_TEST ((rv == 0), "'%v' should match '%v'", authority, formated); + vec_free (authority); + vec_free (formated); + + authority = format (0, "10.10.20:80"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_REG_NAME), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_REG_NAME); + HTTP_TEST ((clib_net_to_host_u16 (parsed.port) == 80), + "port=%u should be 80", clib_net_to_host_u16 (parsed.port)); + formated = http_serialize_authority (&parsed); + rv = vec_cmp (authority, formated); + HTTP_TEST ((rv == 0), "'%v' should match '%v'", authority, formated); + vec_free (authority); + vec_free (formated); + + authority = format (0, "10.10.10.10.2"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_REG_NAME), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_REG_NAME); + HTTP_TEST ((parsed.port == 0), "port=%u should be 0", parsed.port); + formated = http_serialize_authority (&parsed); + rv = vec_cmp (authority, formated); + HTTP_TEST ((rv == 0), "'%v' should match '%v'", authority, formated); + vec_free (authority); + vec_free (formated); + + /* invalid port */ + authority = format (0, "example.com:80000000"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + + /* no port after colon */ + authority = format (0, "example.com:"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + + /* invalid character in registered name */ + authority = format (0, "bad#example.com"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + + /* invalid IPv6 address not terminated with ']' */ + authority = format (0, "[dead:beef::1234"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + + /* empty IPv6 address */ + authority = format (0, "[]"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + + /* invalid IPv6 address too few hex quads */ + authority = format (0, "[dead:beef]:80"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + + /* invalid IPv6 address more than one :: */ + authority = format (0, "[dead::beef::1]:80"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + + /* invalid IPv6 address too much hex quads */ + authority = format (0, "[d:e:a:d:b:e:e:f:1:2]:80"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + + /* invalid character in IPv6 address */ + authority = format (0, "[xyz0::1234]:443"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + + /* invalid IPv6 address */ + authority = format (0, "[deadbeef::1234"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); return 0; } @@ -251,13 +206,15 @@ static int http_test_parse_masque_host_port (vlib_main_t *vm) { u8 *path = 0; - http_uri_t target; + http_uri_authority_t target; int rv; path = format (0, "10.10.2.45/443/"); rv = http_parse_masque_host_port (path, vec_len (path), &target); HTTP_TEST ((rv == 0), "'%v' should be valid", path); - HTTP_TEST ((target.is_ip4 == 1), "is_ip4=%d should be 1", target.is_ip4); + HTTP_TEST ((target.host_type == HTTP_URI_HOST_TYPE_IP4), + "host_type=%d should be %d", target.host_type, + HTTP_URI_HOST_TYPE_IP4); HTTP_TEST ((clib_net_to_host_u16 (target.port) == 443), "port=%u should be 443", clib_net_to_host_u16 (target.port)); HTTP_TEST ((target.ip.ip4.data[0] == 10 && target.ip.ip4.data[1] == 10 && @@ -269,7 +226,9 @@ http_test_parse_masque_host_port (vlib_main_t *vm) path = format (0, "dead%%3Abeef%%3A%%3A1234/80/"); rv = http_parse_masque_host_port (path, vec_len (path), &target); HTTP_TEST ((rv == 0), "'%v' should be valid", path); - HTTP_TEST ((target.is_ip4 == 0), "is_ip4=%d should be 0", target.is_ip4); + HTTP_TEST ((target.host_type == HTTP_URI_HOST_TYPE_IP6), + "host_type=%d should be %d", target.host_type, + HTTP_URI_HOST_TYPE_IP6); HTTP_TEST ((clib_net_to_host_u16 (target.port) == 80), "port=%u should be 80", clib_net_to_host_u16 (target.port)); HTTP_TEST ((clib_net_to_host_u16 (target.ip.ip6.as_u16[0]) == 0xdead && @@ -398,19 +357,15 @@ test_http_command_fn (vlib_main_t *vm, unformat_input_t *input, int res = 0; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { - if (unformat (input, "authority-form")) - res = http_test_authority_form (vm); - else if (unformat (input, "absolute-form")) - res = http_test_absolute_form (vm); + if (unformat (input, "parse-authority")) + res = http_test_parse_authority (vm); else if (unformat (input, "parse-masque-host-port")) res = http_test_parse_masque_host_port (vm); else if (unformat (input, "udp-payload-datagram")) res = http_test_udp_payload_datagram (vm); else if (unformat (input, "all")) { - if ((res = http_test_authority_form (vm))) - goto done; - if ((res = http_test_absolute_form (vm))) + if ((res = http_test_parse_authority (vm))) goto done; if ((res = http_test_parse_masque_host_port (vm))) goto done; diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c index 7afac4229c3..fe5718b7e0f 100644 --- a/src/plugins/http_static/static_server.c +++ b/src/plugins/http_static/static_server.c @@ -546,12 +546,6 @@ hss_ts_rx_callback (session_t *ts) goto done; } - if (msg.data.target_form != HTTP_TARGET_ORIGIN_FORM) - { - start_send_data (hs, HTTP_STATUS_BAD_REQUEST); - goto done; - } - /* Read target path */ if (msg.data.target_path_len) { |