From: Mike Stepanek (mstepane) Date: Mon, 15 Jul 2019 15:23:09 +0000 (-0400) Subject: Merge pull request #1658 in SNORT/snort3 from ~THOPETER/snort3:nhttp122 to master X-Git-Tag: 3.0.0-258~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6905cc51c8f3c823d6775daca4365353fbe1e667;p=thirdparty%2Fsnort3.git Merge pull request #1658 in SNORT/snort3 from ~THOPETER/snort3:nhttp122 to master Squashed commit of the following: commit db33060f5d83ad0b2a625abd8287df6073469f84 Author: Tom Peters Date: Thu Jul 11 13:35:16 2019 -0400 http_inspect: perf improvements commit 37f170ddc1320c6d3bb3eff11a80cd2c21bff1c0 Author: Tom Peters Date: Fri Jun 7 10:22:43 2019 -0400 http_inspect: send headers to detection separately --- diff --git a/doc/http_inspect.txt b/doc/http_inspect.txt index ea995f710..740934f8f 100644 --- a/doc/http_inspect.txt +++ b/doc/http_inspect.txt @@ -493,40 +493,16 @@ Whenever a new URI is available this rule will be evaluated. Nothing complicated about that, but suppose we use more than one rule option: alert tcp any any -> any any ( msg:"combined example"; flow:established, - to_server; http_uri; content:"chocolate"; file_data; - content:"sinister POST data"; sid:5; rev:1; ) - -This rule requires both the URI and the request message body. That sounds -simple until one considers that the message body may be millions of bytes -long. The headers with the URI may be long gone by that time. - -Is this rule going to work or do we need to do something different? - -It is helpful to understand when things happen. All the message headers and -the first few thousand bytes of the body go through detection at the same -time. Commonly this is about 16K bytes but there are several exceptions and -there is no guaranteed minimum amount. - -That may be all you need. In many cases that will be the entire message. Or -it may be more than your request_depth/response_depth. Or this rule may -simply not care what happens after that in a very long message body. - -Beyond that the message body will continue to be subdivided into roughly -16K-byte sections and inspected. But the previous rule will not be able to -see the URI and hence will not work unless we rewrite it: - - alert tcp any any -> any any ( msg:"URI with_body"; flow:established, to_server; http_uri: with_body; content:"chocolate"; file_data; - content:"sinister POST data"; sid:5; rev:2; ) + content:"sinister POST data"; sid:5; rev:1; ) The with_body option to http_uri causes the URI to be made available with -every body section, not just the first one. These extra inspections have a -performance cost which is why they are not done automatically. with_body is -an option to be used when you actually need it. +the message body. Use with_body for header-related rule options in rules +that also examine the message body. The with_trailer option is analogous and causes an earlier message element -to be made available at the end of the message when the trailers following a -chunked body arrive. +to be made available at the end of the message when the trailers following +a chunked body arrive. alert tcp any any -> any any ( msg:"double content-language"; flow:established, to_client; http_header: with_trailer, field @@ -581,33 +557,29 @@ http_header to be searched is the request header. Let's put all of this together. There are six opportunities to do detection: -1. When the first part of the request message body arrives. The request -line, all of the headers, and the first part of the body all go through -detection at the same time. Of course most requests don't have a body. In -that case the request line and the headers are the whole message and get -done at the same time. +1. When the the request headers arrive. The request line and all of the +headers go through detection at the same time. -2. When subsequent sections of the request message body arrive. If you want -to combine this with something from the request line or headers you must -use the with_body option. +2. When sections of the request message body arrive. If you want to combine +this with something from the request line or headers you must use the +with_body option. 3. When the request trailers arrive. If you want to combine this with something from the request line or headers you must use the with_trailer option. -4. When the first part of the response message body arrives. The status -line, all of the headers, and the first part of the body all go through -detection at the same time. These may be combined with elements from the -request line, request headers, or request trailers. Where ambiguity arises -use the request option. +4. When the response headers arrive. The status line and all of the headers +go through detection at the same time. These may be combined with elements +from the request line, request headers, or request trailers. Where +ambiguity arises use the request option. -5. When subsequent sections of the response message body arrive. These may -be combined with the status line, response headers, request line, request -headers, or request trailers as described above. +5. When sections of the response message body arrive. These may be combined +with the status line, response headers, request line, request headers, or +request trailers as described above. 6. When the response trailers arrive. Again these may be combined as described above. -Message body data can only go through detection at the time it is received. -Headers may be combined with later items but the body cannot. +Message body sections can only go through detection at the time they are +received. Headers may be combined with later items but the body cannot. diff --git a/src/service_inspectors/http_inspect/dev_notes.txt b/src/service_inspectors/http_inspect/dev_notes.txt index 3443b90a1..c51fdd6f1 100644 --- a/src/service_inspectors/http_inspect/dev_notes.txt +++ b/src/service_inspectors/http_inspect/dev_notes.txt @@ -105,7 +105,7 @@ URI normalization is performed during HttpUri construction in four steps. Step 1: Identify the type of URI. -NHI recognizes four types of URI: +HI recognizes four types of URI: 1. Asterisk: a lone ‘*’ character signifying that the request does not refer to any resource in particular. Often used with the OPTIONS method. This is not normalized. @@ -144,7 +144,7 @@ This allows rules to be written against a normalized whole URI as is done in 2.X The procedures for normalizing the individual pieces are mostly identical to 2.X. Some points warrant mention: -1. NHI considers it to be normal for reserved characters to be percent encoded and does not +1. HI considers it to be normal for reserved characters to be percent encoded and does not generate an alert. The 119/1 alert is used only for unreserved characters that are found to be percent encoded. The ignore_unreserved configuration option allows the user to specify a list of unreserved characters that are exempt from this alert. @@ -214,7 +214,7 @@ line). This test tool does not implement the feature of being hardened against bad input. If you write a badly formatted or improper test case the program may assert or crash. The responsibility is on the -developer to get it right. Currently that is the best use of resources. +developer to get it right. Test input is currently designed for single-threaded operation only. diff --git a/src/service_inspectors/http_inspect/http_enum.h b/src/service_inspectors/http_inspect/http_enum.h index 94763c1a6..eab6e0307 100644 --- a/src/service_inspectors/http_inspect/http_enum.h +++ b/src/service_inspectors/http_inspect/http_enum.h @@ -106,7 +106,7 @@ enum UriType { URI__NOT_COMPUTE=-14, URI__PROBLEMATIC=-12, URI_ASTERISK = 2, URI enum CompressId { CMP_NONE=2, CMP_GZIP, CMP_DEFLATE }; // Message section in which an IPS option provides the buffer -enum InspectSection { IS_NONE, IS_DETECTION, IS_BODY, IS_TRAILER }; +enum InspectSection { IS_NONE, IS_HEADER, IS_FLEX_HEADER, IS_FIRST_BODY, IS_BODY, IS_TRAILER }; // Part of the URI to be provided enum UriComponent { UC_SCHEME = 1, UC_HOST, UC_PORT, UC_PATH, UC_QUERY, UC_FRAGMENT }; diff --git a/src/service_inspectors/http_inspect/http_inspect.cc b/src/service_inspectors/http_inspect/http_inspect.cc index 0d1006ec2..14738a977 100644 --- a/src/service_inspectors/http_inspect/http_inspect.cc +++ b/src/service_inspectors/http_inspect/http_inspect.cc @@ -87,6 +87,11 @@ InspectSection HttpInspect::get_latest_is(const Packet* p) if (current_section == nullptr) return HttpEnums::IS_NONE; + // FIXIT-L revisit why we need this check. We should not be getting a current section back + // for a raw packet but one of the test cases did exactly that. + if (!(p->packet_flags & PKT_PSEUDO)) + return HttpEnums::IS_NONE; + return current_section->get_inspection_section(); } @@ -143,19 +148,26 @@ bool HttpInspect::http_get_buf(unsigned id, uint64_t sub_id, uint64_t form, Pack bool HttpInspect::get_fp_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b) { + if (get_latest_is(p) == IS_NONE) + return false; + // Fast pattern buffers only supplied at specific times switch (ibt) { case InspectionBuffer::IBT_KEY: - if ((get_latest_is(p) != IS_DETECTION) || (get_latest_src(p) != SRC_CLIENT)) + // Many rules targeting POST feature http_uri fast pattern with http_client_body. We + // accept the performance hit of rerunning http_uri fast pattern with request body message + // sections + if (get_latest_src(p) != SRC_CLIENT) return false; break; case InspectionBuffer::IBT_HEADER: - if ((get_latest_is(p) != IS_DETECTION) && (get_latest_is(p) != IS_TRAILER)) + // http_header fast patterns for response bodies limited to first section + if ((get_latest_src(p) == SRC_SERVER) && (get_latest_is(p) == IS_BODY)) return false; break; case InspectionBuffer::IBT_BODY: - if ((get_latest_is(p) != IS_DETECTION) && (get_latest_is(p) != IS_BODY)) + if ((get_latest_is(p) != IS_FIRST_BODY) && (get_latest_is(p) != IS_BODY)) return false; break; default: @@ -266,6 +278,14 @@ void HttpInspect::eval(Packet* p) if (session_data->section_type[source_id] == SEC__NOT_COMPUTE) return; + // Don't make pkt_data for headers available to detection + // FIXIT-M One byte to avoid potential problems with zero + if ((session_data->section_type[source_id] == SEC_HEADER) || + (session_data->section_type[source_id] == SEC_TRAILER)) + { + p->set_detect_limit(1); + } + // Limit alt_dsize of message body sections to request/response depth if ((session_data->detect_depth_remaining[source_id] > 0) && (session_data->detect_depth_remaining[source_id] < p->dsize)) diff --git a/src/service_inspectors/http_inspect/http_inspect.h b/src/service_inspectors/http_inspect/http_inspect.h index 343201d44..4cea87b7a 100644 --- a/src/service_inspectors/http_inspect/http_inspect.h +++ b/src/service_inspectors/http_inspect/http_inspect.h @@ -54,6 +54,7 @@ public: return new HttpStreamSplitter(is_client_to_server, this); } static HttpEnums::InspectSection get_latest_is(const snort::Packet* p); + static HttpEnums::SourceId get_latest_src(const snort::Packet* p); // Callbacks that provide "extra data" static int get_xtra_trueip(snort::Flow*, uint8_t**, uint32_t*, uint32_t*); @@ -67,7 +68,6 @@ private: bool process(const uint8_t* data, const uint16_t dsize, snort::Flow* const flow, HttpEnums::SourceId source_id_, bool buf_owner) const; - static HttpEnums::SourceId get_latest_src(const snort::Packet* p); const HttpParaList* const params; diff --git a/src/service_inspectors/http_inspect/http_msg_body.cc b/src/service_inspectors/http_inspect/http_msg_body.cc index f54582461..3ce6126c1 100644 --- a/src/service_inspectors/http_inspect/http_msg_body.cc +++ b/src/service_inspectors/http_inspect/http_msg_body.cc @@ -37,7 +37,7 @@ HttpMsgBody::HttpMsgBody(const uint8_t* buffer, const uint16_t buf_size, const HttpParaList* params_) : HttpMsgSection(buffer, buf_size, session_data_, source_id_, buf_owner, flow_, params_), body_octets(session_data->body_octets[source_id]), - detection_section((body_octets == 0) && (session_data->detect_depth_remaining[source_id] > 0)) + first_body(session_data->body_octets[source_id] == 0) { transaction->set_body(this); get_related_sections(); @@ -70,11 +70,6 @@ void HttpMsgBody::analyze() body_octets += msg_text.length(); } -bool HttpMsgBody::detection_required() const -{ - return (detect_data.length() > 0) || (get_inspection_section() == IS_DETECTION); -} - void HttpMsgBody::do_utf_decoding(const Field& input, Field& output) { if ((source_id == SRC_CLIENT) || (session_data->utf_state == nullptr) || (input.length() == 0)) diff --git a/src/service_inspectors/http_inspect/http_msg_body.h b/src/service_inspectors/http_inspect/http_msg_body.h index d49beb3ec..abcdcd0b5 100644 --- a/src/service_inspectors/http_inspect/http_msg_body.h +++ b/src/service_inspectors/http_inspect/http_msg_body.h @@ -32,8 +32,8 @@ class HttpMsgBody : public HttpMsgSection public: void analyze() override; HttpEnums::InspectSection get_inspection_section() const override - { return detection_section ? HttpEnums::IS_DETECTION : HttpEnums::IS_BODY; } - bool detection_required() const override; + { return first_body ? HttpEnums::IS_FIRST_BODY : HttpEnums::IS_BODY; } + bool detection_required() const override { return (detect_data.length() > 0); } HttpMsgBody* get_body() override { return this; } const Field& get_classic_client_body(); const Field& get_detect_data() { return detect_data; } @@ -45,6 +45,7 @@ protected: const HttpParaList* params_); int64_t body_octets; + bool first_body; #ifdef REG_TEST void print_body_section(FILE* output); @@ -61,7 +62,6 @@ private: Field decoded_body; Field decompressed_file_body; Field js_norm_body; - const bool detection_section; }; #endif diff --git a/src/service_inspectors/http_inspect/http_msg_header.cc b/src/service_inspectors/http_inspect/http_msg_header.cc index 150357cad..5b696b106 100644 --- a/src/service_inspectors/http_inspect/http_msg_header.cc +++ b/src/service_inspectors/http_inspect/http_msg_header.cc @@ -293,12 +293,6 @@ void HttpMsgHeader::prepare_body() const int64_t& depth = (source_id == SRC_CLIENT) ? params->request_depth : params->response_depth; session_data->detect_depth_remaining[source_id] = (depth != -1) ? depth : INT64_MAX; - if (session_data->detect_depth_remaining[source_id] > 0) - { - // Depth must be positive because first body section must actually go to detection in order - // to be the detection section - detection_section = false; - } setup_file_processing(); setup_encoding_decompression(); setup_utf_decoding(); diff --git a/src/service_inspectors/http_inspect/http_msg_header.h b/src/service_inspectors/http_inspect/http_msg_header.h index 2c00c1ab4..64aed16f9 100644 --- a/src/service_inspectors/http_inspect/http_msg_header.h +++ b/src/service_inspectors/http_inspect/http_msg_header.h @@ -37,7 +37,8 @@ public: HttpEnums::SourceId source_id_, bool buf_owner, snort::Flow* flow_, const HttpParaList* params_); HttpEnums::InspectSection get_inspection_section() const override - { return detection_section ? HttpEnums::IS_DETECTION : HttpEnums::IS_NONE; } + { return HttpEnums::IS_HEADER; } + bool detection_required() const override { return true; } void update_flow() override; void gen_events() override; void publish() override; @@ -58,8 +59,6 @@ private: Field true_ip; Field true_ip_addr; - bool detection_section = true; - #ifdef REG_TEST void print_section(FILE* output) override; #endif diff --git a/src/service_inspectors/http_inspect/http_msg_section.cc b/src/service_inspectors/http_inspect/http_msg_section.cc index 0a50d1933..18701ee82 100644 --- a/src/service_inspectors/http_inspect/http_msg_section.cc +++ b/src/service_inspectors/http_inspect/http_msg_section.cc @@ -54,12 +54,6 @@ HttpMsgSection::HttpMsgSection(const uint8_t* buffer, const uint16_t buf_size, HttpContextData::save_snapshot(this); } -bool HttpMsgSection::detection_required() const -{ - return ((msg_text.length() > 0) && (get_inspection_section() != IS_NONE)) || - (get_inspection_section() == IS_DETECTION); -} - void HttpMsgSection::add_infraction(int infraction) { *transaction->get_infractions(source_id) += infraction; diff --git a/src/service_inspectors/http_inspect/http_msg_section.h b/src/service_inspectors/http_inspect/http_msg_section.h index 19d32c105..9ed8e8e96 100644 --- a/src/service_inspectors/http_inspect/http_msg_section.h +++ b/src/service_inspectors/http_inspect/http_msg_section.h @@ -37,7 +37,7 @@ public: virtual ~HttpMsgSection() = default; virtual HttpEnums::InspectSection get_inspection_section() const { return HttpEnums::IS_NONE; } - virtual bool detection_required() const; + virtual bool detection_required() const = 0; HttpEnums::SourceId get_source_id() const { return source_id; } HttpTransaction* get_transaction() const { return transaction; } const HttpParaList* get_params() const { return params; } diff --git a/src/service_inspectors/http_inspect/http_msg_start.h b/src/service_inspectors/http_inspect/http_msg_start.h index ec4843fe5..a32decf6b 100644 --- a/src/service_inspectors/http_inspect/http_msg_start.h +++ b/src/service_inspectors/http_inspect/http_msg_start.h @@ -31,6 +31,7 @@ class HttpMsgStart : public HttpMsgSection { public: void analyze() override; + bool detection_required() const override { return false; } const Field& get_version() const { return version; } protected: diff --git a/src/service_inspectors/http_inspect/http_msg_trailer.h b/src/service_inspectors/http_inspect/http_msg_trailer.h index 849605d03..4b0815246 100644 --- a/src/service_inspectors/http_inspect/http_msg_trailer.h +++ b/src/service_inspectors/http_inspect/http_msg_trailer.h @@ -34,6 +34,7 @@ public: const HttpParaList* params_); HttpEnums::InspectSection get_inspection_section() const override { return HttpEnums::IS_TRAILER; } + bool detection_required() const override { return (msg_text.length() > 0); } void gen_events() override; void update_flow() override; diff --git a/src/service_inspectors/http_inspect/ips_http.cc b/src/service_inspectors/http_inspect/ips_http.cc index 883d06757..80203ab44 100644 --- a/src/service_inspectors/http_inspect/ips_http.cc +++ b/src/service_inspectors/http_inspect/ips_http.cc @@ -44,20 +44,22 @@ bool HttpCursorModule::begin(const char*, int, SnortConfig*) form = 0; switch (buffer_index) { + case HTTP_BUFFER_RAW_STATUS: + case HTTP_BUFFER_STAT_CODE: + case HTTP_BUFFER_STAT_MSG: + inspect_section = IS_HEADER; + break; case HTTP_BUFFER_COOKIE: case HTTP_BUFFER_HEADER: case HTTP_BUFFER_METHOD: case HTTP_BUFFER_RAW_COOKIE: case HTTP_BUFFER_RAW_HEADER: case HTTP_BUFFER_RAW_REQUEST: - case HTTP_BUFFER_RAW_STATUS: case HTTP_BUFFER_RAW_URI: - case HTTP_BUFFER_STAT_CODE: - case HTTP_BUFFER_STAT_MSG: case HTTP_BUFFER_TRUE_IP: case HTTP_BUFFER_URI: case HTTP_BUFFER_VERSION: - inspect_section = IS_DETECTION; + inspect_section = IS_FLEX_HEADER; break; case HTTP_BUFFER_CLIENT_BODY: case HTTP_BUFFER_RAW_BODY: @@ -100,7 +102,7 @@ bool HttpCursorModule::set(const char*, Value& v, SnortConfig*) else if (v.is("with_header")) { para_list.with_header = true; - inspect_section = IS_DETECTION; + inspect_section = IS_HEADER; } else if (v.is("with_body")) { @@ -207,16 +209,16 @@ IpsOption::EvalStatus HttpIpsOption::eval(Cursor& c, Packet* p) { RuleProfile profile(HttpCursorModule::http_ps[psi]); - if (!p->flow || !p->flow->gadget) + if (!p->flow || !p->flow->gadget || (HttpInspect::get_latest_is(p) == IS_NONE)) return NO_MATCH; - if (HttpInspect::get_latest_is(p) != inspect_section) - { - // It is OK to provide a body buffer during the detection section. If there actually is - // a body buffer available then the detection section must also be the first body section. - if (! ((inspect_section == IS_BODY) && (HttpInspect::get_latest_is(p) == IS_DETECTION)) ) - return NO_MATCH; - } + const bool section_match = + (HttpInspect::get_latest_is(p) == inspect_section) || + ((HttpInspect::get_latest_is(p) == IS_HEADER) && (inspect_section == IS_FLEX_HEADER)) || + ((HttpInspect::get_latest_is(p) == IS_FIRST_BODY) && (inspect_section == IS_BODY)) || + ((HttpInspect::get_latest_src(p) == SRC_CLIENT) && (inspect_section == IS_FLEX_HEADER)); + if (!section_match) + return NO_MATCH; InspectionBuffer hb; @@ -277,6 +279,8 @@ static const Parameter http_cookie_params[] = { { "request", Parameter::PT_IMPLIED, nullptr, nullptr, "match against the cookie from the request message even when examining the response" }, + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -336,6 +340,8 @@ static const Parameter http_header_params[] = "restrict to given header. Header name is case insensitive." }, { "request", Parameter::PT_IMPLIED, nullptr, nullptr, "match against the headers from the request message even when examining the response" }, + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -385,6 +391,8 @@ static const IpsApi header_api = static const Parameter http_method_params[] = { + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -476,6 +484,8 @@ static const Parameter http_raw_cookie_params[] = { { "request", Parameter::PT_IMPLIED, nullptr, nullptr, "match against the cookie from the request message even when examining the response" }, + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -527,6 +537,8 @@ static const Parameter http_raw_header_params[] = { { "request", Parameter::PT_IMPLIED, nullptr, nullptr, "match against the headers from the request message even when examining the response" }, + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -576,6 +588,8 @@ static const IpsApi raw_header_api = static const Parameter http_raw_request_params[] = { + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -726,6 +740,8 @@ static const IpsApi raw_trailer_api = static const Parameter http_raw_uri_params[] = { + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -938,6 +954,8 @@ static const IpsApi trailer_api = static const Parameter http_true_ip_params[] = { + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -987,6 +1005,8 @@ static const IpsApi true_ip_api = static const Parameter http_uri_params[] = { + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -1050,6 +1070,8 @@ static const Parameter http_version_params[] = { { "request", Parameter::PT_IMPLIED, nullptr, nullptr, "match against the version from the request message even when examining the response" }, + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, "parts of this rule examine HTTP message body" }, { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,