From a26eb08a895c2cb29a185df1c21124a97d16c772 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 11 Mar 2018 10:11:07 +0900 Subject: [PATCH] Deal with :protocol pseudo header --- lib/nghttp2_http.c | 40 ++++++++++++++++++++------------ lib/nghttp2_http.h | 6 +++-- lib/nghttp2_session.c | 8 +++++-- lib/nghttp2_stream.h | 3 ++- tests/nghttp2_session_test.c | 44 ++++++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 20 deletions(-) diff --git a/lib/nghttp2_http.c b/lib/nghttp2_http.c index b08f8863..5ecd2d67 100644 --- a/lib/nghttp2_http.c +++ b/lib/nghttp2_http.c @@ -113,7 +113,7 @@ static int check_path(nghttp2_stream *stream) { } static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, - int trailer) { + int trailer, int connect_protocol) { if (nv->name->base[0] == ':') { if (trailer || (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { @@ -146,10 +146,6 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, return NGHTTP2_ERR_HTTP_HEADER; } stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; - if (stream->http_flags & - (NGHTTP2_HTTP_FLAG__PATH | NGHTTP2_HTTP_FLAG__SCHEME)) { - return NGHTTP2_ERR_HTTP_HEADER; - } } break; case 'S': @@ -162,9 +158,6 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, } break; case NGHTTP2_TOKEN__PATH: - if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { - return NGHTTP2_ERR_HTTP_HEADER; - } if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) { return NGHTTP2_ERR_HTTP_HEADER; } @@ -175,9 +168,6 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, } break; case NGHTTP2_TOKEN__SCHEME: - if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { - return NGHTTP2_ERR_HTTP_HEADER; - } if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) { return NGHTTP2_ERR_HTTP_HEADER; } @@ -186,6 +176,15 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP; } break; + case NGHTTP2_TOKEN__PROTOCOL: + if (!connect_protocol) { + return NGHTTP2_ERR_HTTP_HEADER; + } + + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PROTOCOL)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + break; case NGHTTP2_TOKEN_HOST: if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) { return NGHTTP2_ERR_HTTP_HEADER; @@ -458,16 +457,22 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, } if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) { - return http_request_on_header(stream, nv, trailer); + return http_request_on_header( + stream, nv, trailer, + session->server && session->local_settings.enable_connect_protocol); } return http_response_on_header(stream, nv, trailer); } int nghttp2_http_on_request_headers(nghttp2_stream *stream, - nghttp2_frame *frame) { - if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { - if ((stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) { + nghttp2_frame *frame, + int connect_protocol) { + if (!connect_protocol && + (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) { + if ((stream->http_flags & + (NGHTTP2_HTTP_FLAG__SCHEME | NGHTTP2_HTTP_FLAG__PATH)) || + (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) { return -1; } stream->content_length = -1; @@ -478,6 +483,11 @@ int nghttp2_http_on_request_headers(nghttp2_stream *stream, (NGHTTP2_HTTP_FLAG__AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) { return -1; } + if ((stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) && + (!connect_protocol || + (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) == 0)) { + return -1; + } if (!check_path(stream)) { return -1; } diff --git a/lib/nghttp2_http.h b/lib/nghttp2_http.h index dd057cdb..c747675f 100644 --- a/lib/nghttp2_http.h +++ b/lib/nghttp2_http.h @@ -52,11 +52,13 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, int trailer); /* - * This function is called when request header is received. This + * This function is called when request header is received. + * |connect_protocol| is nonzero if SETTINGS_ENABLE_CONNECT_PROTOCOL + * is enabled by the local endpoint (which must be server). This * function performs validation and returns 0 if it succeeds, or -1. */ int nghttp2_http_on_request_headers(nghttp2_stream *stream, - nghttp2_frame *frame); + nghttp2_frame *frame, int connect_protocol); /* * This function is called when response header is received. This diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 6152cfa4..548ff896 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -3746,13 +3746,17 @@ static int session_after_header_block_received(nghttp2_session *session) { subject_stream = nghttp2_session_get_stream( session, frame->push_promise.promised_stream_id); if (subject_stream) { - rv = nghttp2_http_on_request_headers(subject_stream, frame); + rv = nghttp2_http_on_request_headers( + subject_stream, frame, + session->server && session->local_settings.enable_connect_protocol); } } else { assert(frame->hd.type == NGHTTP2_HEADERS); switch (frame->headers.cat) { case NGHTTP2_HCAT_REQUEST: - rv = nghttp2_http_on_request_headers(stream, frame); + rv = nghttp2_http_on_request_headers( + stream, frame, + session->server && session->local_settings.enable_connect_protocol); break; case NGHTTP2_HCAT_RESPONSE: case NGHTTP2_HCAT_PUSH_RESPONSE: diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index d1d5856d..fb8dc14d 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -130,7 +130,8 @@ typedef enum { /* "http" or "https" scheme */ NGHTTP2_HTTP_FLAG_SCHEME_HTTP = 1 << 13, /* set if final response is expected */ - NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 14 + NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 14, + NGHTTP2_HTTP_FLAG__PROTOCOL = 1 << 15, } nghttp2_http_flag; struct nghttp2_stream { diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 042678b6..87647d79 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -10874,6 +10874,17 @@ void test_nghttp2_http_mandatory_headers(void) { const nghttp2_nv asteriskoptions2_reqnv[] = { MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), MAKE_NV(":method", "OPTIONS"), MAKE_NV(":path", "*")}; + const nghttp2_nv connectproto_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv connectprotoget_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv connectprotonopath_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":protocol", "websocket")}; mem = nghttp2_mem_default(); @@ -11021,6 +11032,39 @@ void test_nghttp2_http_mandatory_headers(void) { asteriskoptions2_reqnv, ARRLEN(asteriskoptions2_reqnv)); + /* :protocol is not allowed unless it is enabled by the local + endpoint. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 27, -1, + connectproto_reqnv, + ARRLEN(connectproto_reqnv)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + /* enable SETTINGS_CONNECT_PROTOCOL */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + session->local_settings.enable_connect_protocol = 1; + + nghttp2_hd_deflate_init(&deflater, mem); + + /* :protocol is allowed if SETTINGS_CONNECT_PROTOCOL is enabled by + the local endpoint. */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 1, -1, + connectproto_reqnv, + ARRLEN(connectproto_reqnv)); + + /* :protocol is only allowed with CONNECT method. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 3, -1, + connectprotoget_reqnv, + ARRLEN(connectprotoget_reqnv)); + + /* CONNECT method with :protocol requires :path. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 5, -1, + connectprotonopath_reqnv, + ARRLEN(connectprotonopath_reqnv)); + nghttp2_hd_deflate_free(&deflater); nghttp2_session_del(session);