From 198aadf24970d2c90961e24e4b434483a4e673fd Mon Sep 17 00:00:00 2001 From: Aleksey Mikhaylov Date: Thu, 24 Feb 2022 16:37:02 +0300 Subject: [PATCH] Websocket upgrade directive implementation Contributes to #755 Signed-off-by: Aleksey Mikhaylov --- fw/http.h | 14 ++- fw/http_limits.c | 50 ++++++++ fw/http_msg.c | 4 + fw/http_parser.c | 226 ++++++++++++++++++++++++++++++++--- fw/t/unit/test_http_parser.c | 42 +++++++ 5 files changed, 315 insertions(+), 21 deletions(-) diff --git a/fw/http.h b/fw/http.h index 6816aff4b..ae9058cc0 100644 --- a/fw/http.h +++ b/fw/http.h @@ -170,11 +170,11 @@ typedef struct { * Http headers table. * * Singular headers (in terms of RFC 7230 3.2.2) go first to protect header - * repetition attacks. See __hdr_is_singular() and don't forget to - * update the static headers array when add a new singular header here. - * If the new header is hop-by-hop (must not be forwarded and cached by Tempesta) - * it must be listed in tfw_http_init_parser_req()/tfw_http_init_parser_resp() - * for unconditionally hop-by-hop header or in __parse_connection() otherwize. + * repetition attacks. See __hdr_is_singular() and don't forget to update the + * static headers array when add a new singular header here. If the new header + * is hop-by-hop (must not be forwarded and cached by Tempesta) it must be + * listed in tfw_http_init_parser_req()/tfw_http_init_parser_resp() + * for unconditionally hop-by-hop header or in __parse_connection() otherwise. * If the header is end-to-end it must be listed in __hbh_parser_add_data(). * * Note: don't forget to update __http_msg_hdr_val() and @@ -212,6 +212,7 @@ typedef enum { TFW_HTTP_HDR_X_FORWARDED_FOR, TFW_HTTP_HDR_KEEP_ALIVE, TFW_HTTP_HDR_TRANSFER_ENCODING, + TFW_HTTP_HDR_UPGRADE, /* Start of list of generic (raw) headers. */ TFW_HTTP_HDR_RAW, @@ -239,7 +240,10 @@ enum { */ TFW_HTTP_B_CONN_CLOSE = TFW_HTTP_FLAGS_COMMON, TFW_HTTP_B_CONN_KA, + TFW_HTTP_B_CONN_UPGRADE, TFW_HTTP_B_CONN_EXTRA, + /* Request is a websocket upgrade request */ + TFW_HTTP_B_UPGRADE_WEBSOCKET, /* Chunked is last transfer encoding. */ TFW_HTTP_B_CHUNKED, /* Chunked in the middle of applied transfer encodings. */ diff --git a/fw/http_limits.c b/fw/http_limits.c index 674836f41..edc0b366b 100644 --- a/fw/http_limits.c +++ b/fw/http_limits.c @@ -564,6 +564,49 @@ frang_http_methods_override(const TfwHttpReq *req, FrangAcc *ra, return TFW_PASS; } +static int +frang_http_upgrade_websocket(const TfwHttpReq *req, FrangAcc *ra, + FrangVhostCfg *f_cfg) +{ + BUG_ON(!req); + + switch (req->version) { + /* + * TODO upgrade websocket checks for h2 as described in RFC8441 + */ + case TFW_HTTP_VER_20: + break; + /* + * Tempesta FW MUST block requests with Upgrade header but without + * upgrade option in Connection header. Tempesta FW MUST ignore + * Upgrade header for HTTP version less then HTTP/1.1. + * See RFC7230#section-6.1. + */ + case TFW_HTTP_VER_11: + case TFW_HTTP_VER_10: + case TFW_HTTP_VER_09: + if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, req->flags) + && !test_bit(TFW_HTTP_B_CONN_UPGRADE, req->flags)) + { + frang_msg("upgrade request without connection option", + &FRANG_ACC2CLI(ra)->addr, ": protocol: %s\n", + "websocket"); + return TFW_BLOCK; + } + if (req->version == TFW_HTTP_VER_10 + || req->version == TFW_HTTP_VER_09) + { + clear_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, + ((TfwHttpReq *)req)->flags); + } + break; + default: + return TFW_BLOCK; + } + + return TFW_PASS; +} + static int frang_http_ct_check(const TfwHttpReq *req, FrangAcc *ra, FrangCtVals *ct_vals) { @@ -1189,6 +1232,13 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data, if (f_cfg->http_ct_required || f_cfg->http_ct_vals) r = frang_http_ct_check(req, ra, f_cfg->http_ct_vals); + /* Do checks for websocket upgrade */ + if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, req->flags) + && (r = frang_http_upgrade_websocket(req, ra, f_cfg))) + { + T_FSM_EXIT(); + } + __FRANG_FSM_MOVE(Frang_Req_Body_Start); } diff --git a/fw/http_msg.c b/fw/http_msg.c index d89fe9bb1..82f3d84fd 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -155,6 +155,7 @@ tfw_http_msg_resp_spec_hid(const TfwStr *hdr) TfwStrDefV("transfer-encoding:",TFW_HTTP_HDR_TRANSFER_ENCODING), TfwStrDefV("x-forwarded-for:", TFW_HTTP_HDR_X_FORWARDED_FOR), TfwStrDefV("x-tempesta-cache:", TFW_HTTP_HDR_X_TEMPESTA_CACHE), + TfwStrDefV("upgrade:", TFW_HTTP_HDR_UPGRADE), }; BUILD_BUG_ON(ARRAY_SIZE(resp_hdrs) != @@ -182,6 +183,7 @@ tfw_http_msg_req_spec_hid(const TfwStr *hdr) TfwStrDefV("user-agent:", TFW_HTTP_HDR_USER_AGENT), TfwStrDefV("x-forwarded-for:", TFW_HTTP_HDR_X_FORWARDED_FOR), TfwStrDefV("x-tempesta-cache:", TFW_HTTP_HDR_X_TEMPESTA_CACHE), + TfwStrDefV("upgrade:", TFW_HTTP_HDR_UPGRADE), }; BUILD_BUG_ON(ARRAY_SIZE(req_hdrs) != @@ -215,6 +217,7 @@ __http_msg_hdr_val(TfwStr *hdr, unsigned id, TfwStr *val, bool client) [TFW_HTTP_HDR_SET_COOKIE] = SLEN("Set-Cookie:"), [TFW_HTTP_HDR_ETAG] = SLEN("ETag:"), [TFW_HTTP_HDR_REFERER] = SLEN("Referer:"), + [TFW_HTTP_HDR_UPGRADE] = SLEN("Upgrade:"), }, (unsigned char []) { [TFW_HTTP_HDR_HOST] = SLEN("Host:"), @@ -229,6 +232,7 @@ __http_msg_hdr_val(TfwStr *hdr, unsigned id, TfwStr *val, bool client) [TFW_HTTP_HDR_COOKIE] = SLEN("Cookie:"), [TFW_HTTP_HDR_IF_NONE_MATCH] = SLEN("If-None-Match:"), [TFW_HTTP_HDR_REFERER] = SLEN("Referer:"), + [TFW_HTTP_HDR_UPGRADE] = SLEN("Upgrade:"), }, }; TfwStr *c, *end; diff --git a/fw/http_parser.c b/fw/http_parser.c index 8980734b4..a31e93efb 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -713,7 +713,7 @@ mark_raw_hbh(TfwHttpMsg *hm, TfwStr *hdr) * * Unset TFW_STR_HBH_HDR flag for header name to indicate that * corresponding hop-by-hop header was found. - */ + */ for (i = 0; i < hbh->off; ++i) { TfwStr *hbh_name = &hbh->raw[i]; if ((hbh_name->flags & TFW_STR_HBH_HDR) @@ -752,10 +752,10 @@ __mark_hbh_hdr(TfwHttpMsg *hm, TfwStr *hdr) } /** - * Add header name listed in Connection header to hop-by-hop table of raw headers. - * If @last is true then (@data, @len) represents last chunk of header name and - * chunk with ':' will be added to the end. Otherwize last header in table stays - * open to add more data. + * Add header name listed in Connection header to hop-by-hop table of raw + * headers. If @last is true then (@data, @len) represents last chunk of header + * name and chunk with ':' will be added to the end. Otherwise last header in + * table stays open to add more data. * * After name of hop-by-hop header was completed, will search for headers * with that name and mark them as hop-by-hop. @@ -766,7 +766,8 @@ __mark_hbh_hdr(TfwHttpMsg *hm, TfwStr *hdr) * TFW_HTTP_PARSE_RAWHDR_VAL macro. */ static int -__hbh_parser_add_data(TfwHttpMsg *hm, char *data, unsigned long len, bool finalize_item) +__hbh_parser_add_data(TfwHttpMsg *hm, char *data, unsigned long len, + bool finalize_item) { TfwStr *hbh_hdr, *append; TfwHttpHbhHdrs *hbh = &hm->stream->parser.hbh_parser; @@ -928,7 +929,8 @@ process_trailer_hdr(TfwHttpMsg *hm, TfwStr *hdr, unsigned int id) * header field); besides, @finish parameter is not used in this macro. * xxx_fixup() family of functions is used to explicit chunking of strings. */ -#define TRY_STR_LAMBDA_fixup(str, field, lambda, curr_st, next_st) \ +#define TRY_STR_LAMBDA_fixup_flag(str, field, lambda, curr_st, next_st, \ + flag) \ BUG_ON(!TFW_STR_PLAIN(str)); \ if (!chunk->data) \ chunk->data = p; \ @@ -938,16 +940,26 @@ process_trailer_hdr(TfwHttpMsg *hm, TfwStr *hdr, unsigned int id) if (chunk->len == (str)->len) { \ lambda; \ TRY_STR_INIT(); \ - __FSM_I_MOVE_fixup_f(next_st, __fsm_n, field, 0);\ + __FSM_I_MOVE_fixup_f(next_st, __fsm_n, field, \ + flag); \ } \ __msg_field_fixup_pos(field, p, __fsm_n); \ + __FSM_I_field_chunk_flags(field, flag); \ parser->_i_st = &&curr_st; \ return CSTR_POSTPONE; \ } +#define TRY_STR_LAMBDA_fixup(str, field, lambda, curr_st, next_st) \ + TRY_STR_LAMBDA_fixup_flag(str, field, lambda, curr_st, \ + next_st, 0) + #define TRY_STR_fixup(str, curr_st, next_st) \ TRY_STR_LAMBDA_fixup(str, &parser->hdr, { }, curr_st, next_st) +#define TRY_STR_fixup_flag(str, curr_st, next_st, flag) \ + TRY_STR_LAMBDA_fixup_flag(str, &parser->hdr, { }, curr_st, \ + next_st, flag) + /* * Headers EOL processing. Allow only LF and CRLF as a newline delimiters. * @@ -1451,10 +1463,9 @@ __parse_connection(TfwHttpMsg *hm, unsigned char *data, size_t len) * Other headers listed in the header will be compared with names of * end-to-end headers during saving in __hbh_parser_add_data(). * - * TODO: RFC 6455 WebSocket Protocol - * During handshake client sets "Connection: update" and "Update" header. - * This headers should be passed to server unchanged to allow - * WebSocket protocol. + * For WebSocket Protocol during handshake client sets + * "Connection: upgrade" and "Upgrade" header. This headers should be + * recreated before pass to backend. */ __FSM_STATE(I_Conn) { WARN_ON_ONCE(parser->_acc); @@ -1466,6 +1477,9 @@ __parse_connection(TfwHttpMsg *hm, unsigned char *data, size_t len) TRY_CONN_TOKEN("keep-alive", { __set_bit(TFW_HTTP_B_CONN_KA, &parser->_acc); }); + TRY_CONN_TOKEN("upgrade", { + __set_bit(TFW_HTTP_B_CONN_UPGRADE, &parser->_acc); + }); TRY_STR_INIT(); __FSM_I_JMP(I_ConnOther); } @@ -1493,6 +1507,15 @@ __parse_connection(TfwHttpMsg *hm, unsigned char *data, size_t len) return CSTR_NEQ; __set_bit(TFW_HTTP_B_CONN_CLOSE, msg->flags); } + else if (test_bit(TFW_HTTP_B_CONN_UPGRADE, &parser->_acc)) { + register unsigned int hid = TFW_HTTP_HDR_UPGRADE; + + __set_bit(TFW_HTTP_B_CONN_UPGRADE, msg->flags); + + parser->hbh_parser.spec |= 0x1 << hid; + if (!TFW_STR_EMPTY(&msg->h_tbl->tbl[hid])) + msg->h_tbl->tbl[hid].flags |= TFW_STR_HBH_HDR; + } __FSM_I_JMP(I_EoT); } @@ -2503,8 +2526,9 @@ STACK_FRAME_NON_STANDARD(__req_parse_cache_control); /* * Nested FSM with explicit fine-grained fixups, should employ - __FSM_I_MOVE_fixup()/__FSM_I_MATCH_fixup()/TRY_STR_fixup() everywhere. -*/ + * __FSM_I_MOVE_fixup()/__FSM_I_MATCH_fixup()/TRY_STR_fixup() + * everywhere. + */ static int __req_parse_cookie(TfwHttpMsg *hm, unsigned char *data, size_t len) { @@ -3320,6 +3344,145 @@ __parse_pragma(TfwHttpMsg *hm, unsigned char *data, size_t len) } STACK_FRAME_NON_STANDARD(__parse_pragma); +/** + * Parse Upgrade header field. Its semantics is described in RFC 7230 6.1. + * For now only websocket protocol supported. + * + * Nested FSM with explicit fine-grained fixups, should employ + * __FSM_I_MOVE_fixup()/__FSM_I_MATCH_fixup()/TRY_STR_fixup() everywhere. + */ +static int +__req_parse_upgrade(TfwHttpMsg *hm, unsigned char *data, size_t len) +{ + int r = CSTR_NEQ; + __FSM_DECLARE_VARS(hm); + + __FSM_START(parser->_i_st); + + /* + * Here we build a header value string manually to split it in chunks: + * next chunk starts after ',' or ' ' list delimiter and '/' delimiter. + * Optional protocol version chunk separate from protocol name chunk. + */ + __FSM_STATE(I_UpgradeProtocolStart) { + static const TfwStr s_websocket = TFW_STR_STRING("websocket"); + TRY_STR_LAMBDA_fixup_flag(&s_websocket, &parser->hdr, { + __set_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, &parser->_acc); + }, I_UpgradeProtocolStart, I_UpgradeProtocol, TFW_STR_NAME); + + __FSM_I_MATCH_MOVE_fixup(token, I_UpgradeProtocol, + TFW_STR_NAME); + if (__fsm_sz == 0) { + if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, + &parser->_acc)) + { + __set_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, + msg->flags); + __FSM_I_JMP(I_UpgradeProtocolEnd); + } + + /* + * Protocol name should contain at least 1 character. + */ + return CSTR_NEQ; + } + __FSM_I_JMP(I_UpgradeProtocolEnd); + } + + /* + * At this state we know that we saw at least one character in + * protocol name and now we can pass zero length token. + */ + __FSM_STATE(I_UpgradeProtocol) { + __FSM_I_MATCH_MOVE_fixup(token, I_UpgradeProtocol, TFW_STR_NAME); + if (__fsm_sz == 0) { + if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, + &parser->_acc)) + { + __set_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, + msg->flags); + } + } + + __FSM_I_JMP(I_UpgradeProtocolEnd); + } + + __FSM_STATE(I_UpgradeProtocolEnd) { + if (__fsm_sz) { + /* Save protocol name */ + __msg_hdr_chunk_fixup(p, __fsm_sz); + __FSM_I_chunk_flags(TFW_STR_NAME); + } + + p += __fsm_sz; + if (likely(IS_CRLF(*(p)))) { + __FSM_EXIT(__data_processed(p)); + } + if (IS_WS(*p) || *p == ',') + __FSM_I_MOVE_fixup(I_EoLE, 1, 0); + if (*p == '/') + __FSM_I_MOVE_fixup(I_UpgradeVersionStart, 1, 0); + return CSTR_NEQ; + } + + /* + * Protocol version stored in a separate value TfwStr chunk. + * May not be empty. '/' already matched. + */ + __FSM_STATE(I_UpgradeVersionStart) { + __FSM_I_MATCH_MOVE_fixup(token, I_UpgradeVersion, + TFW_STR_VALUE); + if (likely(__fsm_sz)) + __FSM_I_JMP(I_UpgradeVersionEnd); + return CSTR_NEQ; + } + + /* + * At this state we know that we saw at least one character in + * protocol version and now we can pass zero length token. + */ + + __FSM_STATE(I_UpgradeVersion) { + __FSM_I_MATCH_MOVE_fixup(token, I_UpgradeVersion, + TFW_STR_VALUE); + __FSM_I_JMP(I_UpgradeVersionEnd); + } + + __FSM_STATE(I_UpgradeVersionEnd) { + if (likely(__fsm_sz)) { + /* Save protocol version */ + __msg_hdr_chunk_fixup(p, __fsm_sz); + __FSM_I_chunk_flags(TFW_STR_VALUE); + } + + p += __fsm_sz; + if (likely(IS_CRLF(*(p)))) { + __FSM_EXIT(__data_processed(p)); + } + if (IS_WS(*p) || *p == ',') + __FSM_I_MOVE_fixup(I_EoLE, 1, 0); + return CSTR_NEQ; + } + + /* End of list entry */ + __FSM_STATE(I_EoLE) { + if (IS_WS(*p) || *p == ',') + __FSM_I_MOVE_fixup(I_EoLE, 1, 0); + + if (IS_TOKEN(*p)) { + parser->_acc = 0; /* reinit for next list entry */ + __FSM_I_JMP(I_UpgradeProtocolStart); + } + if (IS_CRLF(*p)) + __FSM_EXIT(__data_processed(p)); + return CSTR_NEQ; + } + +done: + return r; +} +STACK_FRAME_NON_STANDARD(__req_parse_upgrade); + static int __req_parse_user_agent(TfwHttpMsg *hm, unsigned char *data, size_t len) { @@ -4323,6 +4486,15 @@ tfw_http_parse_req(void *req_data, unsigned char *data, size_t len, p += 10; __FSM_MOVE_hdr_fixup(RGen_LWS, 1); } + if (likely(__data_available(p, 8) + && C4_INT_LCM(p, 'u', 'p', 'g', 'r') + && C4_INT3_LCM(p + 4, 'a', 'd', 'e', ':'))) + { + __msg_hdr_chunk_fixup(data, __data_off(p + 7)); + parser->_i_st = &&Req_HdrUpgradeV; + p += 7; + __FSM_MOVE_hdr_fixup(RGen_LWS, 1); + } __FSM_MOVE(Req_HdrU); default: __FSM_JMP(RGen_HdrOtherN); @@ -4422,6 +4594,10 @@ tfw_http_parse_req(void *req_data, unsigned char *data, size_t len, __req_parse_user_agent, TFW_HTTP_HDR_USER_AGENT); + /* 'Upgrade:*OWS' is read, process field-value. */ + __TFW_HTTP_PARSE_SPECHDR_VAL(Req_HdrUpgradeV, msg,__req_parse_upgrade, + TFW_HTTP_HDR_UPGRADE, 0); + /* 'Cookie:*OWS' is read, process field-value. */ __TFW_HTTP_PARSE_SPECHDR_VAL(Req_HdrCookieV, msg, __req_parse_cookie, TFW_HTTP_HDR_COOKIE, 0); @@ -5068,8 +5244,18 @@ Req_Method_1CharStep: __attribute__((cold)) __FSM_TX_AF(Req_HdrX_Http_Method_Overrid, 'e', Req_HdrX_Http_Method_Override); __FSM_TX_AF_OWS(Req_HdrX_Http_Method_Override, Req_HdrX_Method_OverrideV); + __FSM_STATE(Req_HdrU, cold) { + switch (TFW_LC(c)) { + case 's': + __FSM_MOVE(Req_HdrUs); + case 'p': + __FSM_MOVE(Req_HdrUp); + default: + __FSM_JMP(RGen_HdrOtherN); + } + } + /* User-Agent header processing. */ - __FSM_TX_AF(Req_HdrU, 's', Req_HdrUs); __FSM_TX_AF(Req_HdrUs, 'e', Req_HdrUse); __FSM_TX_AF(Req_HdrUse, 'r', Req_HdrUser); __FSM_TX_AF(Req_HdrUser, '-', Req_HdrUser_); @@ -5080,6 +5266,14 @@ Req_Method_1CharStep: __attribute__((cold)) __FSM_TX_AF(Req_HdrUser_Agen, 't', Req_HdrUser_Agent); __FSM_TX_AF_OWS(Req_HdrUser_Agent, Req_HdrUser_AgentV); + /* Upgrade header processing. */ + __FSM_TX_AF(Req_HdrUp, 'g', Req_HdrUpg); + __FSM_TX_AF(Req_HdrUpg, 'r', Req_HdrUpgr); + __FSM_TX_AF(Req_HdrUpgr, 'a', Req_HdrUpgra); + __FSM_TX_AF(Req_HdrUpgra, 'd', Req_HdrUpgrad); + __FSM_TX_AF(Req_HdrUpgrad, 'e', Req_HdrUpgrade); + __FSM_TX_AF_OWS(Req_HdrUpgrade, Req_HdrUpgradeV); + /* Cookie header processing. */ __FSM_TX_AF(Req_HdrCoo, 'k', Req_HdrCook); __FSM_TX_AF(Req_HdrCook, 'i', Req_HdrCooki); @@ -5731,7 +5925,7 @@ do { \ __FSM_I_field_chunk_flags(fld, TFW_STR_HDR_VALUE); \ __FSM_EXIT(CSTR_POSTPONE); \ } - + #define H2_TRY_STR_LAMBDA_fixup(str, fld, lambda, curr_st, next_st) \ H2_TRY_STR_2LAMBDA_fixup(str, fld, {}, lambda, curr_st, next_st) diff --git a/fw/t/unit/test_http_parser.c b/fw/t/unit/test_http_parser.c index 2e6515a77..4f6a6e447 100644 --- a/fw/t/unit/test_http_parser.c +++ b/fw/t/unit/test_http_parser.c @@ -1636,6 +1636,47 @@ TEST(http_parser, parses_connection_value) EXPECT_FALSE(test_bit(TFW_HTTP_B_CONN_KA, req->flags)); EXPECT_TRUE(test_bit(TFW_HTTP_B_CONN_EXTRA, req->flags)); } + + FOR_REQ_SIMPLE("Connection: upgrade") + { + EXPECT_FALSE(test_bit(TFW_HTTP_B_CONN_CLOSE, req->flags)); + EXPECT_FALSE(test_bit(TFW_HTTP_B_CONN_KA, req->flags)); + EXPECT_FALSE(test_bit(TFW_HTTP_B_CONN_EXTRA, req->flags)); + EXPECT_TRUE(test_bit(TFW_HTTP_B_CONN_UPGRADE, req->flags)); + } +} + +TEST(http_parser, upgrade) +{ + FOR_REQ_SIMPLE("Upgrade: websocket") + { + EXPECT_TFWSTR_EQ(&req->h_tbl->tbl[TFW_HTTP_HDR_UPGRADE], + "Upgrade: websocket"); + EXPECT_TRUE(test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, req->flags)); + } + + FOR_REQ_SIMPLE("Upgrade: h2c, quic") + { + EXPECT_TFWSTR_EQ(&req->h_tbl->tbl[TFW_HTTP_HDR_UPGRADE], + "Upgrade: h2c, quic"); + EXPECT_FALSE(test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, + req->flags)); + } + + FOR_REQ_SIMPLE("Upgrade: websocket, h2c") + { + EXPECT_TFWSTR_EQ(&req->h_tbl->tbl[TFW_HTTP_HDR_UPGRADE], + "Upgrade: websocket, h2c"); + EXPECT_TRUE(test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, req->flags)); + } + + FOR_REQ_SIMPLE("Upgrade: h2c , websocket"); + FOR_REQ_SIMPLE("Upgrade: h2c, websocket"); + EXPECT_BLOCK_REQ_SIMPLE("Upgrade: /websocket"); + EXPECT_BLOCK_REQ_SIMPLE("Upgrade: , websocket"); + EXPECT_BLOCK_REQ_SIMPLE("Upgrade: websocket/"); + EXPECT_BLOCK_REQ_SIMPLE("Upgrade: websocket/, h2c"); + } TEST(http_parser, content_type_in_bodyless_requests) @@ -4326,6 +4367,7 @@ TEST_SUITE(http_parser) TEST_RUN(http_parser, pragma); TEST_RUN(http_parser, suspicious_x_forwarded_for); TEST_RUN(http_parser, parses_connection_value); + TEST_RUN(http_parser, upgrade); TEST_RUN(http_parser, content_type_in_bodyless_requests); TEST_RUN(http_parser, content_length); TEST_RUN(http_parser, eol_crlf);