Merge pull request #1792 from nghttp2/option-no-rfc9113-ltws-validation

Option no rfc9113 ltws validation
This commit is contained in:
Tatsuhiro Tsujikawa 2022-09-07 17:08:04 +09:00 committed by GitHub
commit 9f5bf5c7dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 157 additions and 2 deletions

View File

@ -69,6 +69,7 @@ APIDOCS= \
nghttp2_option_set_no_closed_streams.rst \ nghttp2_option_set_no_closed_streams.rst \
nghttp2_option_set_no_http_messaging.rst \ nghttp2_option_set_no_http_messaging.rst \
nghttp2_option_set_no_recv_client_magic.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_peer_max_concurrent_streams.rst \
nghttp2_option_set_server_fallback_rfc7540_priorities.rst \ nghttp2_option_set_server_fallback_rfc7540_priorities.rst \
nghttp2_option_set_user_recv_extension_type.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, nghttp2_option_set_server_fallback_rfc7540_priorities(nghttp2_option *option,
int val); 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 * @function
* *

View File

@ -336,6 +336,16 @@ static int check_scheme(const uint8_t *value, size_t len) {
return 1; return 1;
} }
static int lws(const uint8_t *s, size_t n) {
size_t i;
for (i = 0; i < n; ++i) {
if (s[i] != ' ' && s[i] != '\t') {
return 0;
}
}
return 1;
}
int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
nghttp2_frame *frame, nghttp2_hd_nv *nv, nghttp2_frame *frame, nghttp2_hd_nv *nv,
int trailer) { int trailer) {
@ -378,6 +388,10 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
case NGHTTP2_TOKEN_HOST: case NGHTTP2_TOKEN_HOST:
if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) { if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
rv = nghttp2_check_authority(nv->value->base, nv->value->len); 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 { } else {
rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len); rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len);
} }
@ -385,8 +399,24 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
case NGHTTP2_TOKEN__SCHEME: case NGHTTP2_TOKEN__SCHEME:
rv = check_scheme(nv->value->base, nv->value->len); rv = check_scheme(nv->value->base, nv->value->len);
break; break;
case NGHTTP2_TOKEN__PROTOCOL:
/* 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 ((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: 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) { if (rv == 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->opt_set_mask |= NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES;
option->server_fallback_rfc7540_priorities = val; 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_OUTBOUND_ACK = 1 << 11,
NGHTTP2_OPT_MAX_SETTINGS = 1 << 12, NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13, NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13,
NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 14,
} nghttp2_option_flag; } nghttp2_option_flag;
/** /**
@ -132,6 +133,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES * NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES
*/ */
int 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 * NGHTTP2_OPT_USER_RECV_EXT_TYPES
*/ */

View File

@ -566,6 +566,13 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->opt_flags |= (*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES; 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, 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; mem = &session->mem;
stream = nghttp2_session_get_stream_raw(session, stream_id); 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) { if (stream) {
assert(stream->state == NGHTTP2_STREAM_IDLE); assert(stream->state == NGHTTP2_STREAM_IDLE);
assert((stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) || 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_HTTP_MESSAGING = 1 << 2,
NGHTTP2_OPTMASK_NO_AUTO_PING_ACK = 1 << 3, NGHTTP2_OPTMASK_NO_AUTO_PING_ACK = 1 << 3,
NGHTTP2_OPTMASK_NO_CLOSED_STREAMS = 1 << 4, 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; } nghttp2_optmask;
/* /*

View File

@ -96,6 +96,9 @@ typedef enum {
NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES = 0x10, NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES = 0x10,
/* Ignore client RFC 9218 priority signal. */ /* Ignore client RFC 9218 priority signal. */
NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES = 0x20, 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; } nghttp2_stream_flag;
/* HTTP related flags to enforce HTTP semantics */ /* HTTP related flags to enforce HTTP semantics */

View File

@ -363,6 +363,9 @@ int main(void) {
test_nghttp2_http_push_promise) || test_nghttp2_http_push_promise) ||
!CU_add_test(pSuite, "http_head_method_upgrade_workaround", !CU_add_test(pSuite, "http_head_method_upgrade_workaround",
test_nghttp2_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", !CU_add_test(pSuite, "frame_pack_headers",
test_nghttp2_frame_pack_headers) || test_nghttp2_frame_pack_headers) ||
!CU_add_test(pSuite, "frame_pack_headers_frame_too_large", !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_session_del(session);
nghttp2_bufs_free(&bufs); 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_record_request_method(void);
void test_nghttp2_http_push_promise(void); void test_nghttp2_http_push_promise(void);
void test_nghttp2_http_head_method_upgrade_workaround(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 */ #endif /* NGHTTP2_SESSION_TEST_H */