diff --git a/src/brpc/details/http_message.cpp b/src/brpc/details/http_message.cpp index 45f77bab5e..0cb4f78378 100644 --- a/src/brpc/details/http_message.cpp +++ b/src/brpc/details/http_message.cpp @@ -33,6 +33,13 @@ namespace brpc { DEFINE_bool(allow_chunked_length, false, "Allow both Transfer-Encoding and Content-Length headers are present."); +DEFINE_bool(http_allow_obs_fold, false, + "Allow obsolete line folding (RFC 7230 3.2.4) of Content-Length " + "and Transfer-Encoding, which front proxies may parse differently."); +DEFINE_bool(http_strict_header_token, false, + "Reject a space inside any header field name (RFC 7230 3.2.6), " + "not just inside the names that decide framing. Names carrying no " + "framing semantics are accepted by default for compatibility."); DEFINE_bool(allow_http_1_1_request_without_host, true, "Allow HTTP/1.1 request without host which violates the HTTP/1.1 specification."); DEFINE_bool(http_verbose, false, @@ -83,6 +90,15 @@ int HttpMessage::on_status(http_parser *parser, const char *, const size_t) { // change the order of these field values when a message is forwarded. int HttpMessage::on_header_field(http_parser *parser, const char *at, const size_t length) { + if (parser->flags & F_TRAILING) { + // Drop chunked trailers instead of merging them into the header map. + // A front-end that strips or rewrites headers (Authorization, + // X-Forwarded-For, ...) only looks at the header section, so anything + // accepted here would be a header smuggled past it. HTTP/1 trailers + // have no reader API in bRPC anyway. Supporting them later should add + // a separate map rather than reuse this one. + return 0; + } HttpMessage *http_message = (HttpMessage *)parser->data; if (http_message->_stage != HTTP_ON_HEADER_FIELD) { http_message->_stage = HTTP_ON_HEADER_FIELD; @@ -94,6 +110,10 @@ int HttpMessage::on_header_field(http_parser *parser, int HttpMessage::on_header_value(http_parser *parser, const char *at, const size_t length) { + if (parser->flags & F_TRAILING) { + // See the comment in on_header_field(). + return 0; + } HttpMessage *http_message = (HttpMessage *)parser->data; bool first_entry = false; if (http_message->_stage != HTTP_ON_HEADER_VALUE) { @@ -454,6 +474,8 @@ HttpMessage::HttpMessage(bool read_body_progressively, , _read_body_progressively(read_body_progressively) { http_parser_init(&_parser, HTTP_BOTH); _parser.allow_chunked_length = 1; + _parser.allow_obs_fold = FLAGS_http_allow_obs_fold; + _parser.strict_header_token = FLAGS_http_strict_header_token; _parser.data = this; } diff --git a/src/brpc/details/http_parser.cpp b/src/brpc/details/http_parser.cpp index 2c68cffca3..e983683d34 100644 --- a/src/brpc/details/http_parser.cpp +++ b/src/brpc/details/http_parser.cpp @@ -407,6 +407,8 @@ enum header_states , h_connection , h_content_length + , h_content_length_num + , h_content_length_ws , h_transfer_encoding , h_upgrade @@ -435,9 +437,13 @@ const char* http_parser_header_state_name(unsigned int header_state) { case h_matching_upgrade: return "h_matching_upgrade"; case h_connection: return "h_connection"; case h_content_length: return "h_content_length"; + case h_content_length_num: return "h_content_length_num"; + case h_content_length_ws: return "h_content_length_ws"; case h_transfer_encoding: return "h_transfer_encoding"; case h_upgrade: return "h_upgrade"; + case h_matching_transfer_encoding_token_start: return "h_matching_transfer_encoding_token_start"; case h_matching_transfer_encoding_chunked: return "h_matching_transfer_encoding_chunked"; + case h_matching_transfer_encoding_token: return "h_matching_transfer_encoding_token"; case h_matching_connection_keep_alive: return "h_matching_connection_keep_alive"; case h_matching_connection_close: return "h_matching_connection_close"; case h_transfer_encoding_chunked: return "h_transfer_encoding_chunked"; @@ -447,6 +453,26 @@ const char* http_parser_header_state_name(unsigned int header_state) { return "h_unknown"; } +/* States in which the value being parsed decides where the body ends. Folding + * such a value (RFC 7230 3.2.4 obs-fold) is never legitimate and makes us + * disagree with front proxies on the framing, which is a smuggling primitive. + */ +static int is_framing_header_state(unsigned int header_state) { + switch (header_state) { + case h_content_length: + case h_content_length_num: + case h_content_length_ws: + case h_transfer_encoding: + case h_matching_transfer_encoding_token_start: + case h_matching_transfer_encoding_chunked: + case h_matching_transfer_encoding_token: + case h_transfer_encoding_chunked: + return 1; + default: + return 0; + } +} + const char* http_parser_type_name(enum http_parser_type type) { switch (type) { case HTTP_REQUEST: return "HTTP_REQUEST"; @@ -477,6 +503,10 @@ enum http_host_state #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') #define IS_NUM(c) ((c) >= '0' && (c) <= '9') #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +/* RFC 7230 3.2.3: OWS = *( SP / HTAB ). Pass the raw byte, never the LOWER()ed + * one: LOWER('\t') is ')', so a tab never compares equal to '\t' afterwards. + */ +#define IS_OWS(ch) ((ch) == ' ' || (ch) == '\t') #define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) #define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ @@ -489,6 +519,12 @@ enum http_host_state * characeters seem to be OK. */ #define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +/* tokens[] already leaves SP out, as RFC 7230 3.2.6 does, so the strict form is + * the plain lookup and TOKEN() is the leniency layered on top of it. The name + * matches upstream, where the table keeps SP and STRICT_TOKEN() is what drops + * it, so both forks spell the same two behaviours the same way. + */ +#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) #define IS_HOST_CHAR(c) \ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') /* NOTE(gejun): Enable utf8 which is compatible with ASCII. Services in baidu @@ -702,6 +738,8 @@ size_t http_parser_execute (http_parser *parser, const char *status_mark = 0; const unsigned int lenient = parser->lenient_http_headers; const unsigned int allow_chunked_length = parser->allow_chunked_length; + const unsigned int allow_obs_fold = parser->allow_obs_fold; + const unsigned int strict_header_token = parser->strict_header_token; /* We're in an error state. Don't bother doing anything. */ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { @@ -1369,7 +1407,7 @@ size_t http_parser_execute (http_parser *parser, goto reexecute_byte; } - c = TOKEN(ch); + c = strict_header_token ? STRICT_TOKEN(ch) : TOKEN(ch); if (!c) { SET_ERRNO(HPE_INVALID_HEADER_TOKEN); @@ -1407,7 +1445,7 @@ size_t http_parser_execute (http_parser *parser, case s_header_field: { - c = TOKEN(ch); + c = strict_header_token ? STRICT_TOKEN(ch) : TOKEN(ch); if (c) { switch (parser->header_state) { @@ -1504,7 +1542,19 @@ size_t http_parser_execute (http_parser *parser, case h_content_length: case h_transfer_encoding: case h_upgrade: - if (ch != ' ') parser->header_state = h_general; + /* TOKEN() accepts ' ' (see the NOTE above it), so without this + * `Content-Length : 5` would keep header_state and be used for + * framing while the field name we report is "Content-Length ". + * RFC 7230 3.2.4 forbids whitespace before the colon; front + * proxies that follow it disagree with us, which is a smuggling + * primitive. Only these four names are strict, lenient names + * elsewhere stay accepted. + */ + if (ch == ' ') { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + parser->header_state = h_general; break; default: @@ -1580,6 +1630,12 @@ size_t http_parser_execute (http_parser *parser, parser->flags |= F_CONTENTLENGTH; parser->content_length = ch - '0'; + parser->header_state = h_content_length_num; + break; + + /* when obsolete line folding is encountered for content length + * continue to the s_header_value state */ + case h_content_length_ws: break; case h_connection: @@ -1633,10 +1689,18 @@ size_t http_parser_execute (http_parser *parser, break; case h_content_length: + if (IS_OWS(ch)) break; + parser->header_state = h_content_length_num; + /* FALLTHROUGH */ + + case h_content_length_num: { uint64_t t; - if (ch == ' ') break; + if (IS_OWS(ch)) { + parser->header_state = h_content_length_ws; + break; + } if (!IS_NUM(ch)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); @@ -1657,14 +1721,25 @@ size_t http_parser_execute (http_parser *parser, break; } + /* Trailing OWS ends the number. A digit after it means the value was + * something like "1 3", which we used to read as 13 while proxies may + * read 1 or reject the message. cf. CVE-2022-32213. + */ + case h_content_length_ws: + if (IS_OWS(ch)) break; + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + /* Transfer-Encoding: chunked */ case h_matching_transfer_encoding_token_start: /* looking for 'Transfer-Encoding: chunked' */ if ('c' == c) { parser->header_state = h_matching_transfer_encoding_chunked; - } else if (TOKEN(c)) { - /* NOTE(gejun): Not use strict mode for these macros since the additional - * characeters seem to be OK. + } else if (STRICT_TOKEN(c)) { + /* TOKEN() here made the OWS arm below unreachable for SP, so the + * space in `Transfer-Encoding: gzip, chunked` started a token + * instead of being skipped and chunked was never matched. Use the + * strict form unconditionally, as upstream does. */ /* TODO(indutny): similar code below does this, but why? @@ -1673,7 +1748,7 @@ size_t http_parser_execute (http_parser *parser, * `STRICT_TOKEN` */ parser->header_state = h_matching_transfer_encoding_token; - } else if (c == ' ' || c == '\t') { + } else if (IS_OWS(ch)) { /* Skip lws */ } else { parser->header_state = h_general; @@ -1720,12 +1795,12 @@ size_t http_parser_execute (http_parser *parser, break; case h_transfer_encoding_chunked: - if (ch != ' ') parser->header_state = h_matching_transfer_encoding_token; + if (!IS_OWS(ch)) parser->header_state = h_matching_transfer_encoding_token; break; case h_connection_keep_alive: case h_connection_close: - if (ch != ' ') parser->header_state = h_general; + if (!IS_OWS(ch)) parser->header_state = h_general; break; default: @@ -1747,6 +1822,14 @@ size_t http_parser_execute (http_parser *parser, case s_header_value_lws: { if (ch == ' ' || ch == '\t') { + if (!allow_obs_fold && is_framing_header_state(parser->header_state)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + if (parser->header_state == h_content_length_num) { + /* treat obsolete line folding as space */ + parser->header_state = h_content_length_ws; + } parser->state = s_header_value_start; goto reexecute_byte; } @@ -1780,6 +1863,15 @@ size_t http_parser_execute (http_parser *parser, case s_header_value_discard_lws: { if (ch == ' ' || ch == '\t') { + /* The value is still empty, so `Transfer-Encoding:\r\n chunked` and + * `Content-Length:\r\n 5` come through here rather than through + * s_header_value_lws. Both states keep header_state across the fold, + * so both have to refuse it. + */ + if (!allow_obs_fold && is_framing_header_state(parser->header_state)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } parser->state = s_header_value_discard_ws; break; } else { diff --git a/src/brpc/details/http_parser.h b/src/brpc/details/http_parser.h index 0ea4efd051..b09c912bcd 100644 --- a/src/brpc/details/http_parser.h +++ b/src/brpc/details/http_parser.h @@ -216,6 +216,12 @@ struct http_parser { * `Content-Length` and * `Transfer-Encoding: chunked` set */ unsigned int lenient_http_headers : 1; + unsigned int allow_obs_fold : 1; /* Allow obsolete line folding (RFC 7230 + * 3.2.4) of `Content-Length` and + * `Transfer-Encoding` */ + unsigned int strict_header_token : 1; /* Reject SP inside any header field + * name, not just the ones that decide + * framing (RFC 7230 3.2.6) */ uint32_t nread; /* # bytes read in various scenarios */ uint64_t content_length; /* # bytes in body. `(uint64_t) -1` (all bits one) diff --git a/test/brpc_http_message_unittest.cpp b/test/brpc_http_message_unittest.cpp index 951c910876..57e98ccab0 100644 --- a/test/brpc_http_message_unittest.cpp +++ b/test/brpc_http_message_unittest.cpp @@ -18,6 +18,7 @@ // Date 2014/10/24 16:44:30 #include +#include #include #include "brpc/server.h" @@ -29,6 +30,8 @@ namespace brpc { DECLARE_bool(allow_chunked_length); DECLARE_bool(allow_http_1_1_request_without_host); +DECLARE_bool(http_allow_obs_fold); +DECLARE_bool(http_strict_header_token); int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv); @@ -340,6 +343,8 @@ TEST(HttpMessageTest, parse_http_set_cookie) { } TEST(HttpMessageTest, cl_and_te) { + GFLAGS_NAMESPACE::FlagSaver flag_saver; + // https://datatracker.ietf.org/doc/html/rfc2616#section-14.41 // If multiple encodings have been applied to an entity, the transfer- // codings MUST be listed in the order in which they were applied. @@ -426,6 +431,218 @@ TEST(HttpMessageTest, cl_and_te) { } } +brpc::http_errno ParseHttpErrno(const char* buf) { + butil::IOBuf data; + data.append(buf); + brpc::HttpMessage http_message; + if (http_message.ParseFromIOBuf(data) == (ssize_t)data.size() && + http_message.Completed()) { + return brpc::HPE_OK; + } + brpc::http_errno err = (brpc::http_errno)http_message._parser.http_errno; + return err != brpc::HPE_OK ? err : brpc::HPE_UNKNOWN; +} + +TEST(HttpMessageTest, space_before_colon_of_framing_headers) { + GFLAGS_NAMESPACE::FlagSaver flag_saver; + brpc::FLAGS_http_strict_header_token = false; + + const char* rejected[] = { + "POST / HTTP/1.1\r\nHost: a.com\r\nContent-Length : 5\r\n\r\nhello", + "POST / HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding : chunked\r\n\r\n0\r\n\r\n", + "GET / HTTP/1.1\r\nHost: a.com\r\nConnection : close\r\n\r\n", + "GET / HTTP/1.1\r\nHost: a.com\r\nUpgrade : h2c\r\n\r\n", + }; + for (size_t i = 0; i < arraysize(rejected); ++i) { + ASSERT_EQ(brpc::HPE_INVALID_HEADER_TOKEN, ParseHttpErrno(rejected[i])) + << rejected[i]; + } + + // Names without framing semantics keep the historical leniency. + ASSERT_EQ(brpc::HPE_OK, ParseHttpErrno( + "POST / HTTP/1.1\r\nHost: a.com\r\nX-Foo : bar\r\n" + "Content-Length: 5\r\n\r\nhello")); +} + +TEST(HttpMessageTest, strict_header_token) { + GFLAGS_NAMESPACE::FlagSaver flag_saver; + brpc::FLAGS_http_strict_header_token = false; + + const char* lenient_only[] = { + "POST / HTTP/1.1\r\nHost: a.com\r\nX-Foo : bar\r\n" + "Content-Length: 5\r\n\r\nhello", + // A space anywhere in the name, not just before the colon. + "GET / HTTP/1.1\r\nHost: a.com\r\nX Foo: bar\r\n\r\n", + // Names that only look like a framing one until they diverge. + "GET / HTTP/1.1\r\nHost: a.com\r\nContent-Type : text/plain\r\n\r\n", + }; + for (size_t i = 0; i < arraysize(lenient_only); ++i) { + ASSERT_EQ(brpc::HPE_OK, ParseHttpErrno(lenient_only[i])) + << lenient_only[i]; + } + + brpc::FLAGS_http_strict_header_token = true; + for (size_t i = 0; i < arraysize(lenient_only); ++i) { + ASSERT_EQ(brpc::HPE_INVALID_HEADER_TOKEN, + ParseHttpErrno(lenient_only[i])) << lenient_only[i]; + } + // Well-formed names are unaffected. + ASSERT_EQ(brpc::HPE_OK, ParseHttpErrno("POST / HTTP/1.1\r\n" + "Host: a.com\r\nX-Foo: bar\r\n" + "Content-Length: 5\r\n\r\n" + "hello")); +} + +TEST(HttpMessageTest, transfer_encoding_list_with_space_after_comma) { + butil::IOBuf response; + response.append("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: gzip, chunked\r\n\r\n" + "5\r\nhello\r\n0\r\n\r\n"); + brpc::HttpMessage http_message; + ASSERT_EQ((ssize_t)response.size(), http_message.ParseFromIOBuf(response)) + << http_message._parser; + ASSERT_TRUE(http_message.Completed()); + ASSERT_TRUE(http_message._parser.flags & brpc::F_CHUNKED) + << http_message._parser; + ASSERT_EQ("hello", http_message.body().to_string()); + + // A request would have been rejected outright by RFC 7230 3.3.3 before, + // because uses_transfer_encoding was set while F_CHUNKED was not. + ASSERT_EQ(brpc::HPE_OK, ParseHttpErrno("POST / HTTP/1.1\r\n" + "Host: a.com\r\n" + "Transfer-Encoding: gzip, chunked\r\n\r\n" + "0\r\n\r\n")); +} + +TEST(HttpMessageTest, content_length_with_interior_space) { + ASSERT_EQ(brpc::HPE_INVALID_CONTENT_LENGTH, ParseHttpErrno("POST / HTTP/1.1\r\n" + "Host: a.com\r\n" + "Content-Length: 1 3\r\n\r\n" + "0123456789abc")); + + // Trailing OWS is legal and does not change the value. + butil::IOBuf data; + data.append("POST / HTTP/1.1\r\nHost: a.com\r\nContent-Length: 13 \r\n\r\n0123456789abc"); + brpc::HttpMessage http_message; + ASSERT_EQ((ssize_t)data.size(), http_message.ParseFromIOBuf(data)) + << http_message._parser; + ASSERT_TRUE(http_message.Completed()); + ASSERT_EQ("0123456789abc", http_message.body().to_string()); +} + +TEST(HttpMessageTest, obs_fold_of_framing_headers) { + GFLAGS_NAMESPACE::FlagSaver flag_saver; + brpc::FLAGS_http_allow_obs_fold = false; + + const char* folded_te_value = "POST / HTTP/1.1\r\n" + "Host: a.com\r\n" + "Transfer-Encoding:\r\n chunked\r\n\r\n" + "0\r\n\r\n"; + // Folding inside the value restarts the chunked matcher, so this one never + // reached F_CHUNKED, but we still report "chun ked" where a proxy that + // unfolds reports "chunked". + const char* folded_te_token = "GET / HTTP/1.1\r\n" + "Host: a.com\r\n" + "Transfer-Encoding: chun\r\n ked\r\n\r\n"; + const char* folded_cl_value = "POST / HTTP/1.1\r\n" + "Host: a.com\r\n" + "Content-Length:\r\n 13\r\n\r\n" + "0123456789abc"; + const char* folded_cl_digits = "POST / HTTP/1.1\r\n" + "Host: a.com\r\n" + "Content-Length: 1\r\n 3\r\n\r\n" + "0123456789abc"; + + ASSERT_EQ(brpc::HPE_INVALID_HEADER_TOKEN, ParseHttpErrno(folded_te_value)); + ASSERT_EQ(brpc::HPE_INVALID_HEADER_TOKEN, ParseHttpErrno(folded_te_token)); + ASSERT_EQ(brpc::HPE_INVALID_HEADER_TOKEN, ParseHttpErrno(folded_cl_value)); + ASSERT_EQ(brpc::HPE_INVALID_HEADER_TOKEN, ParseHttpErrno(folded_cl_digits)); + + // Folding a header that decides nothing about framing is still accepted. + ASSERT_EQ(brpc::HPE_OK, ParseHttpErrno( + "POST / HTTP/1.1\r\nHost: a.com\r\nX-Foo: a\r\n b\r\n" + "Content-Length: 5\r\n\r\nhello")); + + brpc::FLAGS_http_allow_obs_fold = true; + ASSERT_EQ(brpc::HPE_OK, ParseHttpErrno(folded_te_value)); + ASSERT_EQ(brpc::HPE_OK, ParseHttpErrno(folded_cl_value)); + // Unfolding turns this into `Content-Length: 1 3`, which stays invalid. + ASSERT_EQ(brpc::HPE_INVALID_CONTENT_LENGTH, + ParseHttpErrno(folded_cl_digits)); + // Unfolding leaves a Transfer-Encoding that is not chunked, which RFC 7230 + // 3.3.3 makes unparseable in a request whatever the fold policy is. + ASSERT_EQ(brpc::HPE_INVALID_TRANSFER_ENCODING, + ParseHttpErrno(folded_te_token)); +} + +TEST(HttpMessageTest, chunked_trailer_is_not_a_header) { + butil::IOBuf request; + request.append("POST / HTTP/1.1\r\n" + "Host: a.com\r\n" + "X-Forwarded-For: 10.0.0.1\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + "5\r\nhello\r\n" + "0\r\n" + "X-Injected: evil\r\n" + "Authorization: Bearer stolen\r\n" + "X-Forwarded-For: 6.6.6.6\r\n" + "\r\n"); + brpc::HttpMessage http_message; + ASSERT_EQ((ssize_t)request.size(), http_message.ParseFromIOBuf(request)) + << http_message._parser; + ASSERT_TRUE(http_message.Completed()); + // The body is unaffected by dropping the trailer. + ASSERT_EQ("hello", http_message.body().to_string()); + + brpc::HttpHeader& header = http_message.header(); + ASSERT_EQ(nullptr, header.GetHeader("X-Injected")); + ASSERT_EQ(nullptr, header.GetHeader("Authorization")); + // A trailer repeating a real header must not be appended to it either, + // which is where a comma-folded name like X-Forwarded-For would land. + const std::string* xff = header.GetHeader("X-Forwarded-For"); + ASSERT_TRUE(xff != nullptr); + ASSERT_EQ("10.0.0.1", *xff); +} + +TEST(HttpMessageTest, htab_is_ows_in_header_values) { + // Trailing OWS after the number, alone and mixed with SP. + ASSERT_EQ(brpc::HPE_OK, ParseHttpErrno("POST / HTTP/1.1\r\n" + "Host: a.com\r\n" + "Content-Length: 13\t\r\n\r\n" + "0123456789abc")); + ASSERT_EQ(brpc::HPE_OK, ParseHttpErrno("POST / HTTP/1.1\r\n" + "Host: a.com\r\n" + "Content-Length: 13 \t\r\n\r\n" + "0123456789abc")); + // A tab between digits ends the number just like a space, so the value + // stays invalid rather than reading as 13. + ASSERT_EQ(brpc::HPE_INVALID_CONTENT_LENGTH, + ParseHttpErrno("POST / HTTP/1.1\r\n" + "Host: a.com\r\n" + "Content-Length: 1\t3\r\n\r\n" + "0123456789abc")); + + // Tabs around a transfer-coding and after `Connection: close` used to leave + // the matcher in a state that never set the flag. The Connection case + // parsed without an error, so only the flag catches it. + struct { const char* buf; unsigned int flag; } flagged[] = { + {"POST / HTTP/1.1\r\nHost: a.com\r\n" + "Transfer-Encoding: gzip,\tchunked\r\n\r\n0\r\n\r\n", brpc::F_CHUNKED}, + {"POST / HTTP/1.1\r\nHost: a.com\r\n" + "Transfer-Encoding: chunked\t\r\n\r\n0\r\n\r\n", brpc::F_CHUNKED}, + {"GET / HTTP/1.1\r\nHost: a.com\r\nConnection: close\t\r\n\r\n", brpc::F_CONNECTION_CLOSE}, + }; + for (size_t i = 0; i < arraysize(flagged); ++i) { + butil::IOBuf data; + data.append(flagged[i].buf); + brpc::HttpMessage http_message; + ASSERT_EQ((ssize_t)data.size(), http_message.ParseFromIOBuf(data)) + << http_message._parser; + ASSERT_TRUE(http_message._parser.flags & flagged[i].flag) + << http_message._parser; + } +} + TEST(HttpMessageTest, find_method_property_by_uri) { brpc::Server server; ASSERT_EQ(0, server.AddService(new test::EchoService(), @@ -799,6 +1016,7 @@ TEST(HttpMessageTest, serialize_content_type_with_crlf_is_not_injected) { } TEST(HttpMessageTest, http_1_1_request_without_host) { + GFLAGS_NAMESPACE::FlagSaver flag_saver; brpc::FLAGS_allow_http_1_1_request_without_host = false; { butil::IOBuf request; @@ -830,7 +1048,6 @@ TEST(HttpMessageTest, http_1_1_request_without_host) { ASSERT_TRUE(http_message.Completed()); ASSERT_EQ("text/plain", http_message.header().content_type()); } - brpc::FLAGS_allow_http_1_1_request_without_host = true; } } //namespace