From f38babe30f2ad30cd4e4e856944b2a5a2317fa65 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 28 May 2016 16:08:20 +0900 Subject: [PATCH] nghttpx: Support unknown method --- integration-tests/nghttpx_http1_test.go | 13 +- integration-tests/nghttpx_http2_test.go | 13 +- integration-tests/nghttpx_spdy_test.go | 13 +- src/shrpx_client_handler.cc | 8 +- src/shrpx_downstream.cc | 8 +- src/shrpx_downstream.h | 5 +- src/shrpx_http2_downstream_connection.cc | 17 ++- src/shrpx_http2_session.cc | 10 +- src/shrpx_http2_upstream.cc | 19 ++- src/shrpx_http_downstream_connection.cc | 9 +- src/shrpx_https_upstream.cc | 58 +++++++-- src/shrpx_mruby_module_request.cc | 13 +- src/shrpx_spdy_upstream.cc | 9 +- third-party/Makefile.am | 1 + third-party/http-parser/http_parser.c | 149 +++++++++++++++++++++-- third-party/http-parser/http_parser.h | 16 ++- third-party/http-parser/test.c | 119 +++++++++++++++++- 17 files changed, 377 insertions(+), 103 deletions(-) diff --git a/integration-tests/nghttpx_http1_test.go b/integration-tests/nghttpx_http1_test.go index 199eadcb..ee0d12d0 100644 --- a/integration-tests/nghttpx_http1_test.go +++ b/integration-tests/nghttpx_http1_test.go @@ -53,23 +53,20 @@ func TestH1H1PlainGETClose(t *testing.T) { } } -// TestH1H1InvalidMethod tests that server rejects invalid method with -// 501 status code -func TestH1H1InvalidMethod(t *testing.T) { - st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) { - t.Errorf("server should not forward this request") - }) +// TestH1H1UnknownMethod tests that server can forward unknown method +func TestH1H1UnknownMethod(t *testing.T) { + st := newServerTester(nil, t, noopHandler) defer st.Close() res, err := st.http1(requestParam{ - name: "TestH1H1InvalidMethod", + name: "TestH1H1UnknownMethod", method: "get", }) if err != nil { t.Fatalf("Error st.http1() = %v", err) } - if got, want := res.status, 501; got != want { + if got, want := res.status, 200; got != want { t.Errorf("status = %v; want %v", got, want) } } diff --git a/integration-tests/nghttpx_http2_test.go b/integration-tests/nghttpx_http2_test.go index bb9d8b0e..3e9f24b1 100644 --- a/integration-tests/nghttpx_http2_test.go +++ b/integration-tests/nghttpx_http2_test.go @@ -589,22 +589,19 @@ func TestH2H1InvalidRequestCL(t *testing.T) { // } // } -// TestH2H1InvalidMethod tests that server rejects invalid method with -// 501. -func TestH2H1InvalidMethod(t *testing.T) { - st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) { - t.Errorf("server should not forward this request") - }) +// TestH2H1UnknownMethod tests that server can forward unknown method. +func TestH2H1UnknownMethod(t *testing.T) { + st := newServerTester(nil, t, noopHandler) defer st.Close() res, err := st.http2(requestParam{ - name: "TestH2H1InvalidMethod", + name: "TestH2H1UnknownMethod", method: "get", }) if err != nil { t.Fatalf("Error st.http2() = %v", err) } - if got, want := res.status, 501; got != want { + if got, want := res.status, 200; got != want { t.Errorf("status: %v; want %v", got, want) } } diff --git a/integration-tests/nghttpx_spdy_test.go b/integration-tests/nghttpx_spdy_test.go index e6723af0..cf2e5456 100644 --- a/integration-tests/nghttpx_spdy_test.go +++ b/integration-tests/nghttpx_spdy_test.go @@ -210,22 +210,19 @@ func TestS3H1HeaderFields(t *testing.T) { } } -// TestS3H1InvalidMethod tests that server rejects invalid method with -// 501. -func TestS3H1InvalidMethod(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Errorf("server should not forward this request") - }) +// TestS3H1UnknownMethod tests that server can forward unknown method. +func TestS3H1UnknownMethod(t *testing.T) { + st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, noopHandler) defer st.Close() res, err := st.spdy(requestParam{ - name: "TestS3H1InvalidMethod", + name: "TestS3H1UnknownMethod", method: "get", }) if err != nil { t.Fatalf("Error st.spdy() = %v", err) } - if got, want := res.status, 501; got != want { + if got, want := res.status, 200; got != want { t.Errorf("status: %v; want %v", got, want) } } diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index a2083b38..77d0290c 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -824,7 +824,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) { // proxy mode falls in this case. if (groups.size() == 1) { group_idx = 0; - } else if (req.method == HTTP_CONNECT) { + } else if (req.method_token == HTTP_CONNECT) { // We don't know how to treat CONNECT request in host-path // mapping. It most likely appears in proxy scenario. Since we // have dealt with proxy case already, just use catch-all group. @@ -1040,14 +1040,14 @@ void ClientHandler::write_accesslog(Downstream *downstream) { upstream_accesslog( get_config()->logging.access.format, LogSpec{ - downstream, StringRef{ipaddr_}, http2::to_method_string(req.method), + downstream, StringRef{ipaddr_}, req.method, - req.method == HTTP_CONNECT + req.method_token == HTTP_CONNECT ? StringRef(req.authority) : get_config()->http2_proxy ? StringRef(construct_absolute_request_uri(balloc, req)) : req.path.empty() - ? req.method == HTTP_OPTIONS + ? req.method_token == HTTP_OPTIONS ? StringRef::from_lit("*") : StringRef::from_lit("-") : StringRef(req.path), diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index b8d16066..86862835 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -635,7 +635,7 @@ bool Downstream::validate_response_recv_body_length() const { } void Downstream::check_upgrade_fulfilled() { - if (req_.method == HTTP_CONNECT) { + if (req_.method_token == HTTP_CONNECT) { upgraded_ = 200 <= resp_.http_status && resp_.http_status < 300; return; @@ -650,13 +650,13 @@ void Downstream::check_upgrade_fulfilled() { } void Downstream::inspect_http2_request() { - if (req_.method == HTTP_CONNECT) { + if (req_.method_token == HTTP_CONNECT) { req_.upgrade_request = true; } } void Downstream::inspect_http1_request() { - if (req_.method == HTTP_CONNECT) { + if (req_.method_token == HTTP_CONNECT) { req_.upgrade_request = true; } else { auto upgrade = req_.fs.header(http2::HD_UPGRADE); @@ -741,7 +741,7 @@ bool Downstream::get_expect_final_response() const { bool Downstream::expect_response_body() const { return !resp_.headers_only && - http2::expect_response_body(req_.method, resp_.http_status); + http2::expect_response_body(req_.method_token, resp_.http_status); } bool Downstream::expect_response_trailer() const { diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 9f5c7f67..3f213127 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -126,7 +126,7 @@ struct Request { : fs(balloc, 16), recv_body_length(0), unconsumed_body_length(0), - method(-1), + method_token(-1), http_major(1), http_minor(1), upgrade_request(false), @@ -158,7 +158,8 @@ struct Request { int64_t recv_body_length; // The number of bytes not consumed by the application yet. size_t unconsumed_body_length; - int method; + StringRef method; + int method_token; // HTTP major and minor version int http_major, http_minor; // Returns true if the request is HTTP upgrade (HTTP Upgrade or diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc index ab5c20d2..f4b87921 100644 --- a/src/shrpx_http2_downstream_connection.cc +++ b/src/shrpx_http2_downstream_connection.cc @@ -103,7 +103,7 @@ int Http2DownstreamConnection::attach_downstream(Downstream *downstream) { auto &req = downstream_->request(); // HTTP/2 disables HTTP Upgrade. - if (req.method != HTTP_CONNECT) { + if (req.method_token != HTTP_CONNECT) { req.upgrade_request = false; } @@ -251,7 +251,7 @@ int Http2DownstreamConnection::push_request_headers() { auto no_host_rewrite = httpconf.no_host_rewrite || get_config()->http2_proxy || - req.method == HTTP_CONNECT; + req.method_token == HTTP_CONNECT; // http2session_ has already in CONNECTED state, so we can get // addr_idx here. @@ -286,15 +286,14 @@ int Http2DownstreamConnection::push_request_headers() { nva.reserve(req.fs.headers().size() + 9 + num_cookies + httpconf.add_request_headers.size()); - nva.push_back( - http2::make_nv_ls_nocopy(":method", http2::to_method_string(req.method))); + nva.push_back(http2::make_nv_ls_nocopy(":method", req.method)); - if (req.method != HTTP_CONNECT) { + if (req.method_token != HTTP_CONNECT) { assert(!req.scheme.empty()); nva.push_back(http2::make_nv_ls_nocopy(":scheme", req.scheme)); - if (req.method == HTTP_OPTIONS && req.path.empty()) { + if (req.method_token == HTTP_OPTIONS && req.path.empty()) { nva.push_back(http2::make_nv_ll(":path", "*")); } else { nva.push_back(http2::make_nv_ls_nocopy(":path", req.path)); @@ -333,7 +332,7 @@ int Http2DownstreamConnection::push_request_headers() { if (fwdconf.params) { auto params = fwdconf.params; - if (get_config()->http2_proxy || req.method == HTTP_CONNECT) { + if (get_config()->http2_proxy || req.method_token == HTTP_CONNECT) { params &= ~FORWARDED_PROTO; } @@ -376,7 +375,7 @@ int Http2DownstreamConnection::push_request_headers() { nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value)); } - if (!get_config()->http2_proxy && req.method != HTTP_CONNECT) { + if (!get_config()->http2_proxy && req.method_token != HTTP_CONNECT) { // We use same protocol with :scheme header field nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", req.scheme)); } @@ -428,7 +427,7 @@ int Http2DownstreamConnection::push_request_headers() { // Add body as long as transfer-encoding is given even if // req.fs.content_length == 0 to forward trailer fields. - if (req.method == HTTP_CONNECT || transfer_encoding || + if (req.method_token == HTTP_CONNECT || transfer_encoding || req.fs.content_length > 0 || req.http2_expect_body) { // Request-body is expected. nghttp2_data_provider data_prd{{}, http2_data_read_callback}; diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index a0361be9..f42e6243 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -2055,13 +2055,6 @@ int Http2Session::handle_downstream_push_promise_complete( } auto method_token = http2::lookup_method_token(method->value); - if (method_token == -1) { - if (LOG_ENABLED(INFO)) { - SSLOG(INFO, this) << "Unrecognized method: " << method->value; - } - - return -1; - } // TODO Rewrite authority if we enabled rewrite host. But we // really don't know how to rewrite host. Should we use the same @@ -2069,7 +2062,8 @@ int Http2Session::handle_downstream_push_promise_complete( if (authority) { promised_req.authority = authority->value; } - promised_req.method = method_token; + promised_req.method = method->value; + promised_req.method_token = method_token; // libnghttp2 ensures that we don't have CONNECT method in // PUSH_PROMISE, and guarantees that :scheme exists. if (scheme) { diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index f603bd04..1387b828 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -117,7 +117,7 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) { rv = nghttp2_session_upgrade2( session_, reinterpret_cast(settings_payload.c_str()), settings_payload.size(), - http->get_downstream()->request().method == HTTP_HEAD, nullptr); + http->get_downstream()->request().method_token == HTTP_HEAD, nullptr); if (rv != 0) { if (LOG_ENABLED(INFO)) { ULOG(INFO, this) << "nghttp2_session_upgrade() returned error: " @@ -296,12 +296,6 @@ int Http2Upstream::on_request_headers(Downstream *downstream, auto scheme = req.fs.header(http2::HD__SCHEME); auto method_token = http2::lookup_method_token(method->value); - if (method_token == -1) { - if (error_reply(downstream, 501) != 0) { - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - return 0; - } // For HTTP/2 proxy, we request :authority. if (method_token != HTTP_CONNECT && get_config()->http2_proxy && !authority) { @@ -309,7 +303,8 @@ int Http2Upstream::on_request_headers(Downstream *downstream, return 0; } - req.method = method_token; + req.method = method->value; + req.method_token = method_token; if (scheme) { req.scheme = scheme->value; } @@ -604,9 +599,11 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, auto token = http2::lookup_token(nv.name, nv.namelen); switch (token) { - case http2::HD__METHOD: - req.method = http2::lookup_method_token(value); + case http2::HD__METHOD: { + req.method = value; + req.method_token = http2::lookup_method_token(value); break; + } case http2::HD__SCHEME: req.scheme = value; break; @@ -1415,7 +1412,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { !get_config()->http2_proxy && (downstream->get_stream_id() % 2) && resp.fs.header(http2::HD_LINK) && (downstream->get_non_final_response() || resp.http_status == 200) && - (req.method == HTTP_GET || req.method == HTTP_POST)) { + (req.method_token == HTTP_GET || req.method_token == HTTP_POST)) { if (prepare_push_promise(downstream) != 0) { // Continue to send response even if push was failed. diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index d2251789..6dd55595 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -321,7 +321,7 @@ int HttpDownstreamConnection::push_request_headers() { auto &balloc = downstream_->get_block_allocator(); - auto connect_method = req.method == HTTP_CONNECT; + auto connect_method = req.method_token == HTTP_CONNECT; auto &httpconf = get_config()->http; @@ -340,8 +340,7 @@ int HttpDownstreamConnection::push_request_headers() { auto buf = downstream_->get_request_buf(); // Assume that method and request path do not contain \r\n. - auto meth = http2::to_method_string(req.method); - buf->append(meth); + buf->append(req.method); buf->append(" "); if (connect_method) { @@ -354,7 +353,7 @@ int HttpDownstreamConnection::push_request_headers() { buf->append("://"); buf->append(authority); buf->append(req.path); - } else if (req.method == HTTP_OPTIONS && req.path.empty()) { + } else if (req.method_token == HTTP_OPTIONS && req.path.empty()) { // Server-wide OPTIONS buf->append("*"); } else { @@ -708,7 +707,7 @@ int htp_hdrs_completecb(http_parser *htp) { // TODO It seems that the cases other than HEAD are handled by // http-parser. Need test. - return !http2::expect_response_body(req.method, resp.http_status); + return !http2::expect_response_body(req.method_token, resp.http_status); } } // namespace diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 1dff8cd6..15489d7a 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -80,6 +80,33 @@ int htp_msg_begin(http_parser *htp) { } } // namespace +namespace { +int htp_methodcb(http_parser *htp, const char *data, size_t len) { + auto upstream = static_cast(htp->data); + auto downstream = upstream->get_downstream(); + auto &req = downstream->request(); + + auto &balloc = downstream->get_block_allocator(); + + if (req.fs.buffer_size() + len > + get_config()->http.request_header_field_buffer) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large request size=" + << req.fs.buffer_size() + len; + } + assert(downstream->get_request_state() == Downstream::INITIAL); + downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE); + return -1; + } + + req.fs.add_extra_buffer_size(len); + + req.method = concat_string_ref(balloc, req.method, StringRef{data, len}); + + return 0; +} +} // namespace + namespace { int htp_uricb(http_parser *htp, const char *data, size_t len) { auto upstream = static_cast(htp->data); @@ -88,8 +115,11 @@ int htp_uricb(http_parser *htp, const char *data, size_t len) { auto &balloc = downstream->get_block_allocator(); - // We happen to have the same value for method token. - req.method = htp->method; + // This could be executed more than once, but no harm here. + if (htp->method != HTTP_METHOD_UNKNOWN) { + // We happen to have the same value for method token. + req.method_token = htp->method; + } if (req.fs.buffer_size() + len > get_config()->http.request_header_field_buffer) { @@ -104,7 +134,7 @@ int htp_uricb(http_parser *htp, const char *data, size_t len) { req.fs.add_extra_buffer_size(len); - if (req.method == HTTP_CONNECT) { + if (req.method_token == HTTP_CONNECT) { req.authority = concat_string_ref(balloc, req.authority, StringRef{data, len}); } else { @@ -243,7 +273,7 @@ void rewrite_request_host_path_from_uri(BlockAllocator &balloc, Request &req, StringRef path; if (u.field_set & (1 << UF_PATH)) { path = util::get_uri_field(uri.c_str(), u, UF_PATH); - } else if (req.method == HTTP_OPTIONS) { + } else if (req.method_token == HTTP_OPTIONS) { // Server-wide OPTIONS takes following form in proxy request: // // OPTIONS http://example.org HTTP/1.1 @@ -297,13 +327,11 @@ int htp_hdrs_completecb(http_parser *htp) { req.connection_close = !http_should_keep_alive(htp); - auto method = req.method; - if (LOG_ENABLED(INFO)) { std::stringstream ss; - ss << http2::to_method_string(method) << " " - << (method == HTTP_CONNECT ? req.authority : req.path) << " " - << "HTTP/" << req.http_major << "." << req.http_minor << "\n"; + ss << req.method << " " + << (req.method_token == HTTP_CONNECT ? req.authority : req.path) + << " HTTP/" << req.http_major << "." << req.http_minor << "\n"; for (const auto &kv : req.fs.headers()) { ss << TTY_HTTP_HD << kv.name << TTY_RST << ": " << kv.value << "\n"; @@ -340,7 +368,7 @@ int htp_hdrs_completecb(http_parser *htp) { auto &balloc = downstream->get_block_allocator(); - if (method != HTTP_CONNECT) { + if (req.method_token != HTTP_CONNECT) { http_parser_url u{}; rv = http_parser_parse_url(req.path.c_str(), req.path.size(), 0, &u); if (rv != 0) { @@ -356,7 +384,8 @@ int htp_hdrs_completecb(http_parser *htp) { req.no_authority = true; - if (method == HTTP_OPTIONS && req.path == StringRef::from_lit("*")) { + if (req.method_token == HTTP_OPTIONS && + req.path == StringRef::from_lit("*")) { req.path = StringRef{}; } else { req.path = http2::rewrite_clean_path(balloc, req.path); @@ -482,7 +511,10 @@ http_parser_settings htp_hooks = { htp_hdr_valcb, // http_data_cb on_header_value; htp_hdrs_completecb, // http_cb on_headers_complete; htp_bodycb, // http_data_cb on_body; - htp_msg_completecb // http_cb on_message_complete; + htp_msg_completecb, // http_cb on_message_complete; + nullptr, // http_cb on_chunk_header; + nullptr, // http_cb on_chunk_complete; + htp_methodcb, // http_data_cb on_method; }; } // namespace @@ -973,7 +1005,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { } #endif // HAVE_MRUBY - auto connect_method = req.method == HTTP_CONNECT; + auto connect_method = req.method_token == HTTP_CONNECT; auto buf = downstream->get_response_buf(); diff --git a/src/shrpx_mruby_module_request.cc b/src/shrpx_mruby_module_request.cc index f98442a4..49726ed6 100644 --- a/src/shrpx_mruby_module_request.cc +++ b/src/shrpx_mruby_module_request.cc @@ -68,9 +68,8 @@ mrb_value request_get_method(mrb_state *mrb, mrb_value self) { auto data = static_cast(mrb->ud); auto downstream = data->downstream; const auto &req = downstream->request(); - auto method = http2::to_method_string(req.method); - return mrb_str_new(mrb, method.c_str(), method.size()); + return mrb_str_new(mrb, req.method.c_str(), req.method.size()); } } // namespace @@ -80,6 +79,8 @@ mrb_value request_set_method(mrb_state *mrb, mrb_value self) { auto downstream = data->downstream; auto &req = downstream->request(); + auto &balloc = downstream->get_block_allocator(); + check_phase(mrb, data->phase, PHASE_REQUEST); const char *method; @@ -88,13 +89,13 @@ mrb_value request_set_method(mrb_state *mrb, mrb_value self) { if (n == 0) { mrb_raise(mrb, E_RUNTIME_ERROR, "method must not be empty string"); } + auto token = http2::lookup_method_token(reinterpret_cast(method), n); - if (token == -1) { - mrb_raise(mrb, E_RUNTIME_ERROR, "method not supported"); - } - req.method = token; + req.method = + make_string_ref(balloc, StringRef{method, static_cast(n)}); + req.method_token = token; return self; } diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index bbb96ba6..c86f5df5 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -220,12 +220,6 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type, } auto method_token = http2::lookup_method_token(method->value); - if (method_token == -1) { - if (upstream->error_reply(downstream, 501) != 0) { - ULOG(FATAL, upstream) << "error_reply failed"; - } - return; - } auto is_connect = method_token == HTTP_CONNECT; if (!path || !host || !http2::non_empty_value(host) || @@ -264,7 +258,8 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type, return; } - req.method = method_token; + req.method = method->value; + req.method_token = method_token; if (is_connect) { req.authority = path->value; } else { diff --git a/third-party/Makefile.am b/third-party/Makefile.am index ebc49458..c3564112 100644 --- a/third-party/Makefile.am +++ b/third-party/Makefile.am @@ -28,6 +28,7 @@ EXTRA_DIST = CMakeLists.txt if ENABLE_THIRD_PARTY noinst_LTLIBRARIES = libhttp-parser.la +libhttp_parser_la_CPPFLAGS = ${AMCPPFLAGS} -DHTTP_PARSER_METHOD_CB=1 libhttp_parser_la_SOURCES = \ http-parser/http_parser.c \ http-parser/http_parser.h diff --git a/third-party/http-parser/http_parser.c b/third-party/http-parser/http_parser.c index 3c896ffa..7a951cb6 100644 --- a/third-party/http-parser/http_parser.c +++ b/third-party/http-parser/http_parser.c @@ -280,6 +280,7 @@ enum state { s_dead = 1 /* important that this is > 0 */ , s_start_req_or_res + , s_res_or_resp_mark_H , s_res_or_resp_H , s_start_res , s_res_H @@ -298,7 +299,11 @@ enum state , s_start_req + , s_req_method_start , s_req_method +#if HTTP_PARSER_METHOD_CB + , s_req_method_unknown +#endif , s_req_spaces_before_url , s_req_schema , s_req_schema_slash @@ -644,6 +649,9 @@ size_t http_parser_execute (http_parser *parser, const char *url_mark = 0; const char *body_mark = 0; const char *status_mark = 0; +#if HTTP_PARSER_METHOD_CB + const char *method_mark = 0; +#endif enum state p_state = (enum state) parser->state; const unsigned int lenient = parser->lenient_http_headers; @@ -692,6 +700,14 @@ size_t http_parser_execute (http_parser *parser, case s_req_fragment: url_mark = data; break; +#if HTTP_PARSER_METHOD_CB + case s_res_or_resp_H: + case s_req_method_start: + case s_req_method: + case s_req_method_unknown: + method_mark = data; + break; +#endif case s_res_status: status_mark = data; break; @@ -726,9 +742,10 @@ reexecute: parser->content_length = ULLONG_MAX; if (ch == 'H') { - UPDATE_STATE(s_res_or_resp_H); + UPDATE_STATE(s_res_or_resp_mark_H); - CALLBACK_NOTIFY(message_begin); + CALLBACK_NOTIFY_NOADVANCE(message_begin); + REEXECUTE(); } else { parser->type = HTTP_REQUEST; UPDATE_STATE(s_start_req); @@ -738,19 +755,46 @@ reexecute: break; } + case s_res_or_resp_mark_H: + assert(ch == 'H'); + UPDATE_STATE(s_res_or_resp_H); +#if HTTP_PARSER_METHOD_CB + /* TODO This will call on_method even if type == HTTP_BOTH is + * used and it later turns out that this is response. + * Automatic handling bites us hard. + */ + MARK(method); +#endif + break; + case s_res_or_resp_H: if (ch == 'T') { parser->type = HTTP_RESPONSE; UPDATE_STATE(s_res_HT); } else { +#if HTTP_PARSER_METHOD_CB + if (UNLIKELY(!TOKEN(ch))) { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + if (UNLIKELY(ch != 'E')) { + parser->method = HTTP_METHOD_UNKNOWN; + } else { + parser->method = HTTP_HEAD; + parser->index = 2; + } +#else if (UNLIKELY(ch != 'E')) { SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } - parser->type = HTTP_REQUEST; parser->method = HTTP_HEAD; parser->index = 2; +#endif + + parser->type = HTTP_REQUEST; UPDATE_STATE(s_req_method); } break; @@ -952,7 +996,6 @@ reexecute: break; case s_start_req: - { if (ch == CR || ch == LF) break; parser->flags = 0; @@ -963,6 +1006,25 @@ reexecute: goto error; } + UPDATE_STATE(s_req_method_start); +#if HTTP_PARSER_METHOD_CB + MARK(method); +#endif + + CALLBACK_NOTIFY_NOADVANCE(message_begin); + REEXECUTE(); + + break; + + case s_req_method_start: + { + enum state next_state = s_req_method; + + if (UNLIKELY(!IS_ALPHA(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + parser->method = (enum http_method) 0; parser->index = 1; switch (ch) { @@ -984,12 +1046,26 @@ reexecute: case 'T': parser->method = HTTP_TRACE; break; case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; default: +#if HTTP_PARSER_METHOD_CB + if (UNLIKELY(!TOKEN(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (unsigned int) HTTP_METHOD_UNKNOWN; + + next_state = s_req_method_unknown; +#else SET_ERRNO(HPE_INVALID_METHOD); goto error; +#endif } - UPDATE_STATE(s_req_method); - CALLBACK_NOTIFY(message_begin); + UPDATE_STATE(next_state); + +#if HTTP_PARSER_METHOD_CB + MARK(method); +#endif break; } @@ -1003,8 +1079,21 @@ reexecute: } matcher = method_strings[parser->method]; - if (ch == ' ' && matcher[parser->index] == '\0') { + + if (ch == ' ') { + if (matcher[parser->index] != '\0') { +#if HTTP_PARSER_METHOD_CB + parser->method = (unsigned int) HTTP_METHOD_UNKNOWN; +#else + SET_ERRNO(HPE_INVALID_METHOD); + goto error; +#endif + } + UPDATE_STATE(s_req_spaces_before_url); +#if HTTP_PARSER_METHOD_CB + CALLBACK_DATA(method); +#endif } else if (ch == matcher[parser->index]) { ; /* nada */ } else if (IS_ALPHA(ch)) { @@ -1034,14 +1123,56 @@ reexecute: #undef XX default: +#if HTTP_PARSER_METHOD_CB + if (UNLIKELY(!TOKEN(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (unsigned int) HTTP_METHOD_UNKNOWN; + + UPDATE_STATE(s_req_method_unknown); +#else SET_ERRNO(HPE_INVALID_METHOD); goto error; +#endif } } else if (ch == '-' && parser->index == 1 && parser->method == HTTP_MKCOL) { parser->method = HTTP_MSEARCH; } else { +#if HTTP_PARSER_METHOD_CB + if (UNLIKELY(!TOKEN(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (unsigned int) HTTP_METHOD_UNKNOWN; + + UPDATE_STATE(s_req_method_unknown); +#else + SET_ERRNO(HPE_INVALID_METHOD); + goto error; +#endif + } + + ++parser->index; + break; + } + +#if HTTP_PARSER_METHOD_CB + case s_req_method_unknown: + { + if (UNLIKELY(ch == '\0')) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + if (ch == ' ') { + UPDATE_STATE(s_req_spaces_before_url); + CALLBACK_DATA(method); + } else if (UNLIKELY(!TOKEN(ch))) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } @@ -1049,6 +1180,7 @@ reexecute: ++parser->index; break; } +#endif case s_req_spaces_before_url: { @@ -2078,6 +2210,9 @@ reexecute: CALLBACK_DATA_NOADVANCE(url); CALLBACK_DATA_NOADVANCE(body); CALLBACK_DATA_NOADVANCE(status); +#if HTTP_PARSER_METHOD_CB + CALLBACK_DATA_NOADVANCE(method); +#endif RETURN(len); diff --git a/third-party/http-parser/http_parser.h b/third-party/http-parser/http_parser.h index 105ae510..37acc231 100644 --- a/third-party/http-parser/http_parser.h +++ b/third-party/http-parser/http_parser.h @@ -53,6 +53,17 @@ typedef unsigned __int64 uint64_t; # define HTTP_PARSER_STRICT 1 #endif +/* Compile with -DHTTP_PARSER_METHOD_CB=1 to enable method + * callback. If it is enabled, method string is notified with + * on_method callback. The unknown method which would be rejeted + * previously is also accepted and notified with the on_method + * callback. The method field of http_parser struct becomes + * HTTP_METHOD_UNKNOWN if method is unknown to http_parser. + */ +#ifndef HTTP_PARSER_METHOD_CB +# define HTTP_PARSER_METHOD_CB 0 +#endif + /* Maximium header size allowed. If the macro is not defined * before including this header then the default is used. To * change the maximum header size, define the macro in the build @@ -140,6 +151,8 @@ enum http_method #undef XX }; +/* Unknown HTTP method */ +#define HTTP_METHOD_UNKNOWN 255 enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; @@ -176,6 +189,7 @@ enum flags XX(CB_status, "the on_status callback failed") \ XX(CB_chunk_header, "the on_chunk_header callback failed") \ XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ + XX(CB_method, "the on_method callback failed") \ \ /* Parsing-related errors */ \ XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ @@ -218,7 +232,6 @@ enum http_errno { /* Get an http_errno value from an http_parser */ #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) - struct http_parser { /** PRIVATE **/ unsigned int type : 2; /* enum http_parser_type */ @@ -264,6 +277,7 @@ struct http_parser_settings { */ http_cb on_chunk_header; http_cb on_chunk_complete; + http_data_cb on_method; }; diff --git a/third-party/http-parser/test.c b/third-party/http-parser/test.c index 456a78ad..8e2e3a8f 100644 --- a/third-party/http-parser/test.c +++ b/third-party/http-parser/test.c @@ -52,6 +52,7 @@ struct message { enum http_method method; int status_code; char response_status[MAX_ELEMENT_SIZE]; + char request_method[MAX_ELEMENT_SIZE]; char request_path[MAX_ELEMENT_SIZE]; char request_url[MAX_ELEMENT_SIZE]; char fragment[MAX_ELEMENT_SIZE]; @@ -103,6 +104,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/test" @@ -134,6 +136,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/favicon.ico" @@ -163,6 +166,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/dumbfuck" @@ -184,6 +188,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "page=1" ,.fragment= "posts-17408" ,.request_path= "/forums/1/topics/2375" @@ -203,6 +208,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/get_no_headers_no_body/world" @@ -222,6 +228,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/get_one_header_no_body" @@ -245,6 +252,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/get_funky_content_length_body_hello" @@ -270,6 +278,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST + ,.request_method = "POST" ,.query_string= "q=search" ,.fragment= "hey" ,.request_path= "/post_identity_body_world" @@ -297,6 +306,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST + ,.request_method = "POST" ,.query_string= "" ,.fragment= "" ,.request_path= "/post_chunked_all_your_base" @@ -325,6 +335,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST + ,.request_method = "POST" ,.query_string= "" ,.fragment= "" ,.request_path= "/two_chunks_mult_zero_end" @@ -355,6 +366,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST + ,.request_method = "POST" ,.query_string= "" ,.fragment= "" ,.request_path= "/chunked_w_trailing_headers" @@ -385,6 +397,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST + ,.request_method = "POST" ,.query_string= "" ,.fragment= "" ,.request_path= "/chunked_w_bullshit_after_length" @@ -407,6 +420,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "foo=\"bar\"" ,.fragment= "" ,.request_path= "/with_\"stupid\"_quotes" @@ -433,6 +447,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/test" @@ -456,6 +471,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "foo=bar?baz" ,.fragment= "" ,.request_path= "/test.cgi" @@ -477,6 +493,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/test" @@ -504,6 +521,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/demo" @@ -535,6 +553,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT + ,.request_method = "CONNECT" ,.query_string= "" ,.fragment= "" ,.request_path= "" @@ -557,6 +576,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_REPORT + ,.request_method = "REPORT" ,.query_string= "" ,.fragment= "" ,.request_path= "/test" @@ -576,6 +596,7 @@ const struct message requests[] = ,.http_major= 0 ,.http_minor= 9 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/" @@ -598,6 +619,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_MSEARCH + ,.request_method = "M-SEARCH" ,.query_string= "" ,.fragment= "" ,.request_path= "*" @@ -633,6 +655,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/" @@ -658,6 +681,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "hail=all" ,.fragment= "" ,.request_path= "" @@ -678,6 +702,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "hail=all" ,.fragment= "" ,.request_path= "" @@ -699,6 +724,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "" @@ -725,6 +751,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_PATCH + ,.request_method = "PATCH" ,.query_string= "" ,.fragment= "" ,.request_path= "/file.txt" @@ -750,6 +777,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT + ,.request_method = "CONNECT" ,.query_string= "" ,.fragment= "" ,.request_path= "" @@ -774,6 +802,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "q=1" ,.fragment= "narf" ,.request_path= "/δ¶/δt/pope" @@ -796,6 +825,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT + ,.request_method = "CONNECT" ,.query_string= "" ,.fragment= "" ,.request_path= "" @@ -823,6 +853,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST + ,.request_method = "POST" ,.query_string= "" ,.fragment= "" ,.request_path= "/" @@ -851,6 +882,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST + ,.request_method = "POST" ,.query_string= "" ,.fragment= "" ,.request_path= "/" @@ -876,6 +908,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_PURGE + ,.request_method = "PURGE" ,.query_string= "" ,.fragment= "" ,.request_path= "/file.txt" @@ -896,6 +929,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_SEARCH + ,.request_method = "SEARCH" ,.query_string= "" ,.fragment= "" ,.request_path= "/" @@ -915,6 +949,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.fragment= "" ,.request_path= "/toto" ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto" @@ -949,6 +984,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/" @@ -982,6 +1018,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/demo" @@ -1012,6 +1049,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/demo" @@ -1037,6 +1075,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET + ,.request_method = "GET" ,.query_string= "" ,.fragment= "" ,.request_path= "/demo" @@ -1065,6 +1104,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST + ,.request_method = "POST" ,.request_path= "/demo" ,.request_url= "/demo" ,.num_headers= 4 @@ -1091,6 +1131,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT + ,.request_method = "CONNECT" ,.request_url= "foo.bar.com:443" ,.num_headers= 3 ,.upgrade="blarfcicle" @@ -1118,6 +1159,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_LINK + ,.request_method = "LINK" ,.request_path= "/images/my_dog.jpg" ,.request_url= "/images/my_dog.jpg" ,.query_string= "" @@ -1142,6 +1184,7 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_UNLINK + ,.request_method = "UNLINK" ,.request_path= "/images/my_dog.jpg" ,.request_url= "/images/my_dog.jpg" ,.query_string= "" @@ -1153,6 +1196,30 @@ const struct message requests[] = ,.body= "" } +#if HTTP_PARSER_METHOD_CB +#define UNKNOWN_METHOD 42 +, {.name="unknown method" + ,.type= HTTP_REQUEST + ,.raw= "NON-STANDARD-METHOD / HTTP/1.1\r\n" + "Host: example.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_METHOD_UNKNOWN + ,.request_method= "NON-STANDARD-METHOD" + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 1 + ,.headers= + { { "Host", "example.com" } } + ,.body= "" + } +#endif + , {.name= NULL } /* sentinel */ }; @@ -1815,6 +1882,19 @@ strlcpy(char *dst, const char *src, size_t len) return strlncpy(dst, len, src, (size_t) -1); } +#if HTTP_PARSER_METHOD_CB +int +request_method_cb(http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + strlncat(messages[num_messages].request_method, + sizeof(messages[num_messages].request_method), + buf, + len); + return 0; +} +#endif + int request_url_cb (http_parser *p, const char *buf, size_t len) { @@ -2117,6 +2197,16 @@ pause_header_value_cb (http_parser *p, const char *buf, size_t len) return header_value_cb(p, buf, len); } +#if HTTP_PARSER_METHOD_CB +int +pause_request_method_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return request_method_cb(p, buf, len); +} +#endif + int pause_request_url_cb (http_parser *p, const char *buf, size_t len) { @@ -2198,6 +2288,9 @@ static http_parser_settings settings_pause = ,.on_message_complete = pause_message_complete_cb ,.on_chunk_header = pause_chunk_header_cb ,.on_chunk_complete = pause_chunk_complete_cb +#if HTTP_PARSER_METHOD_CB + ,.on_method = pause_request_method_cb +#endif }; static http_parser_settings settings = @@ -2211,6 +2304,9 @@ static http_parser_settings settings = ,.on_message_complete = message_complete_cb ,.on_chunk_header = chunk_header_cb ,.on_chunk_complete = chunk_complete_cb +#if HTTP_PARSER_METHOD_CB + ,.on_method = request_method_cb +#endif }; static http_parser_settings settings_count_body = @@ -2224,6 +2320,9 @@ static http_parser_settings settings_count_body = ,.on_message_complete = message_complete_cb ,.on_chunk_header = chunk_header_cb ,.on_chunk_complete = chunk_complete_cb +#if HTTP_PARSER_METHOD_CB + ,.on_method = request_method_cb +#endif }; static http_parser_settings settings_connect = @@ -2237,6 +2336,9 @@ static http_parser_settings settings_connect = ,.on_message_complete = connect_message_complete_cb ,.on_chunk_header = chunk_header_cb ,.on_chunk_complete = chunk_complete_cb +#if HTTP_PARSER_METHOD_CB + ,.on_method = request_method_cb +#endif }; static http_parser_settings settings_null = @@ -2250,6 +2352,9 @@ static http_parser_settings settings_null = ,.on_message_complete = 0 ,.on_chunk_header = 0 ,.on_chunk_complete = 0 +#if HTTP_PARSER_METHOD_CB + ,.on_method = 0 +#endif }; void @@ -2376,6 +2481,9 @@ message_eq (int index, int connect, const struct message *expected) if (expected->type == HTTP_REQUEST) { MESSAGE_CHECK_NUM_EQ(expected, m, method); +#if HTTP_PARSER_METHOD_CB + MESSAGE_CHECK_STR_EQ(expected, m, request_method); +#endif } else { MESSAGE_CHECK_NUM_EQ(expected, m, status_code); MESSAGE_CHECK_STR_EQ(expected, m, response_status); @@ -3850,11 +3958,10 @@ test_message_connect (const struct message *msg) { char *buf = (char*) msg->raw; size_t buflen = strlen(msg->raw); - size_t nread; parser_init(msg->type); - nread = parse_connect(buf, buflen); + parse_connect(buf, buflen); if (num_messages != 1) { printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); @@ -4074,7 +4181,15 @@ main (void) for (this_method = bad_methods; *this_method; this_method++) { char buf[200]; sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); +#if HTTP_PARSER_METHOD_CB + if (strchr(*this_method, ' ')) { + test_simple(buf, HPE_INVALID_URL); + } else { + test_simple(buf, HPE_OK); + } +#else test_simple(buf, HPE_INVALID_METHOD); +#endif } // illegal header field name line folding