Skip to content

Commit

Permalink
Add nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation
Browse files Browse the repository at this point in the history
  • Loading branch information
tatsuhiro-t committed Sep 7, 2022
1 parent a94d2de commit eb06e33
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 3 deletions.
1 change: 1 addition & 0 deletions doc/Makefile.am
Expand Up @@ -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 \
Expand Down
12 changes: 12 additions & 0 deletions lib/includes/nghttp2/nghttp2.h
Expand Up @@ -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
*
Expand Down
15 changes: 13 additions & 2 deletions lib/nghttp2_http.c
Expand Up @@ -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);
}
Expand All @@ -399,13 +403,20 @@ 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:
rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len);
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) {
Expand Down
7 changes: 7 additions & 0 deletions lib/nghttp2_option.c
Expand Up @@ -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;
}
5 changes: 5 additions & 0 deletions lib/nghttp2_option.h
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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
*/
Expand Down
12 changes: 12 additions & 0 deletions lib/nghttp2_session.c
Expand Up @@ -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,
Expand Down Expand Up @@ -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) ||
Expand Down
3 changes: 2 additions & 1 deletion lib/nghttp2_session.h
Expand Up @@ -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;

/*
Expand Down
3 changes: 3 additions & 0 deletions lib/nghttp2_stream.h
Expand Up @@ -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 */
Expand Down
3 changes: 3 additions & 0 deletions tests/main.c
Expand Up @@ -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",
Expand Down
80 changes: 80 additions & 0 deletions tests/nghttp2_session_test.c
Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions tests/nghttp2_session_test.h
Expand Up @@ -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 */

0 comments on commit eb06e33

Please sign in to comment.