Add nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation

This commit is contained in:
Tatsuhiro Tsujikawa 2022-09-06 21:55:48 +09:00
parent a94d2de89a
commit eb06e33e38
11 changed files with 139 additions and 3 deletions

View File

@ -69,6 +69,7 @@ APIDOCS= \
nghttp2_option_set_no_closed_streams.rst \
nghttp2_option_set_no_http_messaging.rst \
nghttp2_option_set_no_recv_client_magic.rst \
nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation.rst \
nghttp2_option_set_peer_max_concurrent_streams.rst \
nghttp2_option_set_server_fallback_rfc7540_priorities.rst \
nghttp2_option_set_user_recv_extension_type.rst \

View File

@ -2750,6 +2750,18 @@ NGHTTP2_EXTERN void
nghttp2_option_set_server_fallback_rfc7540_priorities(nghttp2_option *option,
int val);
/**
* @function
*
* This option, if set to nonzero, turns off RFC 9113 leading and
* trailing white spaces validation against HTTP field value. Some
* important fields, such as HTTP/2 pseudo header fields, are
* validated more strictly and this option does not apply to them.
*/
NGHTTP2_EXTERN void
nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(
nghttp2_option *option, int val);
/**
* @function
*

View File

@ -388,6 +388,10 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
case NGHTTP2_TOKEN_HOST:
if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
rv = nghttp2_check_authority(nv->value->base, nv->value->len);
} else if (
stream->flags &
NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) {
rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
} else {
rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len);
}
@ -399,14 +403,21 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
/* Check the value consists of just white spaces, which was done
in check_pseudo_header before
nghttp2_check_header_value_rfc9113 has been introduced. */
if (lws(nv->value->base, nv->value->len)) {
if ((stream->flags &
NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) &&
lws(nv->value->base, nv->value->len)) {
rv = 0;
break;
}
/* fall through */
default:
if (stream->flags &
NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) {
rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
} else {
rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len);
}
}
if (rv == 0) {
assert(nv->name->len > 0);

View File

@ -136,3 +136,10 @@ void nghttp2_option_set_server_fallback_rfc7540_priorities(
option->opt_set_mask |= NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES;
option->server_fallback_rfc7540_priorities = val;
}
void nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(
nghttp2_option *option, int val) {
option->opt_set_mask |=
NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION;
option->no_rfc9113_leading_and_trailing_ws_validation = val;
}

View File

@ -69,6 +69,7 @@ typedef enum {
NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13,
NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 14,
} nghttp2_option_flag;
/**
@ -132,6 +133,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES
*/
int server_fallback_rfc7540_priorities;
/**
* NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION
*/
int no_rfc9113_leading_and_trailing_ws_validation;
/**
* NGHTTP2_OPT_USER_RECV_EXT_TYPES
*/

View File

@ -566,6 +566,13 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES;
}
if ((option->opt_set_mask &
NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) &&
option->no_rfc9113_leading_and_trailing_ws_validation) {
(*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION;
}
}
rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
@ -1296,6 +1303,11 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
mem = &session->mem;
stream = nghttp2_session_get_stream_raw(session, stream_id);
if (session->opt_flags &
NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) {
flags |= NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION;
}
if (stream) {
assert(stream->state == NGHTTP2_STREAM_IDLE);
assert((stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) ||

View File

@ -53,7 +53,8 @@ typedef enum {
NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2,
NGHTTP2_OPTMASK_NO_AUTO_PING_ACK = 1 << 3,
NGHTTP2_OPTMASK_NO_CLOSED_STREAMS = 1 << 4,
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 5
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 5,
NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 6,
} nghttp2_optmask;
/*

View File

@ -96,6 +96,9 @@ typedef enum {
NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES = 0x10,
/* Ignore client RFC 9218 priority signal. */
NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES = 0x20,
/* Indicates that RFC 9113 leading and trailing white spaces
validation against a field value is not performed. */
NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 0x40,
} nghttp2_stream_flag;
/* HTTP related flags to enforce HTTP semantics */

View File

@ -363,6 +363,9 @@ int main(void) {
test_nghttp2_http_push_promise) ||
!CU_add_test(pSuite, "http_head_method_upgrade_workaround",
test_nghttp2_http_head_method_upgrade_workaround) ||
!CU_add_test(
pSuite, "http_no_rfc9113_leading_and_trailing_ws_validation",
test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation) ||
!CU_add_test(pSuite, "frame_pack_headers",
test_nghttp2_frame_pack_headers) ||
!CU_add_test(pSuite, "frame_pack_headers_frame_too_large",

View File

@ -13224,3 +13224,83 @@ void test_nghttp2_http_head_method_upgrade_workaround(void) {
nghttp2_session_del(session);
nghttp2_bufs_free(&bufs);
}
void test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_hd_deflater deflater;
nghttp2_mem *mem;
nghttp2_bufs bufs;
ssize_t rv;
const nghttp2_nv ws_reqnv[] = {
MAKE_NV(":path", "/"),
MAKE_NV(":method", "GET"),
MAKE_NV(":authority", "localhost"),
MAKE_NV(":scheme", "https"),
MAKE_NV("foo", "bar "),
};
nghttp2_outbound_item *item;
nghttp2_option *option;
mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs);
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
/* By default, the leading and trailing white spaces validation is
enabled as per RFC 9113. */
nghttp2_session_server_new(&session, &callbacks, NULL);
nghttp2_hd_deflate_init(&deflater, mem);
rv = pack_headers(&bufs, &deflater, 1,
NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
ws_reqnv, ARRLEN(ws_reqnv), mem);
CU_ASSERT(0 == rv);
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
nghttp2_buf_len(&bufs.head->buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
CU_ASSERT(0 == nghttp2_session_send(session));
nghttp2_bufs_reset(&bufs);
nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session);
/* Turn off the validation */
nghttp2_option_new(&option);
nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(option, 1);
nghttp2_session_server_new2(&session, &callbacks, NULL, option);
nghttp2_hd_deflate_init(&deflater, mem);
rv = pack_headers(&bufs, &deflater, 1,
NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
ws_reqnv, ARRLEN(ws_reqnv), mem);
CU_ASSERT(0 == rv);
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
nghttp2_buf_len(&bufs.head->buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NULL == item);
nghttp2_bufs_reset(&bufs);
nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session);
nghttp2_option_del(option);
nghttp2_bufs_free(&bufs);
}

View File

@ -178,5 +178,6 @@ void test_nghttp2_http_ignore_content_length(void);
void test_nghttp2_http_record_request_method(void);
void test_nghttp2_http_push_promise(void);
void test_nghttp2_http_head_method_upgrade_workaround(void);
void test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation(void);
#endif /* NGHTTP2_SESSION_TEST_H */