Call on_invalid_frame_recv_callback on bad HTTP messaging

This commit is contained in:
Tatsuhiro Tsujikawa 2015-02-26 22:59:07 +09:00
parent 1c0d617742
commit 05b8901d69
6 changed files with 84 additions and 47 deletions

View File

@ -348,6 +348,11 @@ typedef enum {
* `nghttp2_session_terminate_session()` is called. * `nghttp2_session_terminate_session()` is called.
*/ */
NGHTTP2_ERR_SESSION_CLOSING = -530, NGHTTP2_ERR_SESSION_CLOSING = -530,
/**
* Invalid HTTP header field was received and stream is going to be
* closed.
*/
NGHTTP2_ERR_HTTP_HEADER = -531,
/** /**
* The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
* under unexpected condition and processing was terminated (e.g., * under unexpected condition and processing was terminated (e.g.,

View File

@ -232,7 +232,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (nv->name[0] == ':') { if (nv->name[0] == ':') {
if (trailer || if (trailer ||
(stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
@ -241,55 +241,55 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
switch (token) { switch (token) {
case NGHTTP2_TOKEN__AUTHORITY: case NGHTTP2_TOKEN__AUTHORITY:
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
case NGHTTP2_TOKEN__METHOD: case NGHTTP2_TOKEN__METHOD:
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
if (streq("HEAD", nv->value, nv->valuelen)) { if (streq("HEAD", nv->value, nv->valuelen)) {
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
} else if (streq("CONNECT", nv->value, nv->valuelen)) { } else if (streq("CONNECT", nv->value, nv->valuelen)) {
if (stream->stream_id % 2 == 0) { if (stream->stream_id % 2 == 0) {
/* we won't allow CONNECT for push */ /* we won't allow CONNECT for push */
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
if (stream->http_flags & if (stream->http_flags &
(NGHTTP2_HTTP_FLAG__PATH | NGHTTP2_HTTP_FLAG__SCHEME)) { (NGHTTP2_HTTP_FLAG__PATH | NGHTTP2_HTTP_FLAG__SCHEME)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
break; break;
case NGHTTP2_TOKEN__PATH: case NGHTTP2_TOKEN__PATH:
if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
case NGHTTP2_TOKEN__SCHEME: case NGHTTP2_TOKEN__SCHEME:
if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
case NGHTTP2_TOKEN_HOST: case NGHTTP2_TOKEN_HOST:
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
case NGHTTP2_TOKEN_CONTENT_LENGTH: { case NGHTTP2_TOKEN_CONTENT_LENGTH: {
if (stream->content_length != -1) { if (stream->content_length != -1) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
stream->content_length = parse_uint(nv->value, nv->valuelen); stream->content_length = parse_uint(nv->value, nv->valuelen);
if (stream->content_length == -1) { if (stream->content_length == -1) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
} }
@ -299,15 +299,15 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
case NGHTTP2_TOKEN_PROXY_CONNECTION: case NGHTTP2_TOKEN_PROXY_CONNECTION:
case NGHTTP2_TOKEN_TRANSFER_ENCODING: case NGHTTP2_TOKEN_TRANSFER_ENCODING:
case NGHTTP2_TOKEN_UPGRADE: case NGHTTP2_TOKEN_UPGRADE:
return -1; return NGHTTP2_ERR_HTTP_HEADER;
case NGHTTP2_TOKEN_TE: case NGHTTP2_TOKEN_TE:
if (!strieq("trailers", nv->value, nv->valuelen)) { if (!strieq("trailers", nv->value, nv->valuelen)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
default: default:
if (nv->name[0] == ':') { if (nv->name[0] == ':') {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
@ -325,7 +325,7 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (nv->name[0] == ':') { if (nv->name[0] == ':') {
if (trailer || if (trailer ||
(stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
@ -334,24 +334,24 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
switch (token) { switch (token) {
case NGHTTP2_TOKEN__STATUS: { case NGHTTP2_TOKEN__STATUS: {
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
if (nv->valuelen != 3) { if (nv->valuelen != 3) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
stream->status_code = parse_uint(nv->value, nv->valuelen); stream->status_code = parse_uint(nv->value, nv->valuelen);
if (stream->status_code == -1) { if (stream->status_code == -1) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
} }
case NGHTTP2_TOKEN_CONTENT_LENGTH: { case NGHTTP2_TOKEN_CONTENT_LENGTH: {
if (stream->content_length != -1) { if (stream->content_length != -1) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
stream->content_length = parse_uint(nv->value, nv->valuelen); stream->content_length = parse_uint(nv->value, nv->valuelen);
if (stream->content_length == -1) { if (stream->content_length == -1) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
} }
@ -361,15 +361,15 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
case NGHTTP2_TOKEN_PROXY_CONNECTION: case NGHTTP2_TOKEN_PROXY_CONNECTION:
case NGHTTP2_TOKEN_TRANSFER_ENCODING: case NGHTTP2_TOKEN_TRANSFER_ENCODING:
case NGHTTP2_TOKEN_UPGRADE: case NGHTTP2_TOKEN_UPGRADE:
return -1; return NGHTTP2_ERR_HTTP_HEADER;
case NGHTTP2_TOKEN_TE: case NGHTTP2_TOKEN_TE:
if (!strieq("trailers", nv->value, nv->valuelen)) { if (!strieq("trailers", nv->value, nv->valuelen)) {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
default: default:
if (nv->name[0] == ':') { if (nv->name[0] == ':') {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
@ -392,31 +392,32 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
if (!nghttp2_check_header_name(nv->name, nv->namelen)) { if (!nghttp2_check_header_name(nv->name, nv->namelen)) {
size_t i; size_t i;
if (nv->namelen > 0 && nv->name[0] == ':') { if (nv->namelen > 0 && nv->name[0] == ':') {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
/* header field name must be lower-cased without exception */ /* header field name must be lower-cased without exception */
for (i = 0; i < nv->namelen; ++i) { for (i = 0; i < nv->namelen; ++i) {
char c = nv->name[i]; char c = nv->name[i];
if ('A' <= c && c <= 'Z') { if ('A' <= c && c <= 'Z') {
return -1; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
/* When ignoring regular headers, we set this flag so that we /* When ignoring regular headers, we set this flag so that we
still enforce header field ordering rule for pseudo header still enforce header field ordering rule for pseudo header
fields. */ fields. */
stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
return 1; return NGHTTP2_ERR_IGN_HTTP_HEADER;
} }
if (!nghttp2_check_header_value(nv->value, nv->valuelen)) { if (!nghttp2_check_header_value(nv->value, nv->valuelen)) {
if (nv->namelen > 0 && nv->name[0] == ':') { assert(nv->namelen > 0);
return -1; if (nv->name[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER;
} }
/* When ignoring regular headers, we set this flag so that we /* When ignoring regular headers, we set this flag so that we
still enforce header field ordering rule for pseudo header still enforce header field ordering rule for pseudo header
fields. */ fields. */
stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
return 1; return NGHTTP2_ERR_IGN_HTTP_HEADER;
} }
if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) { if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {

View File

@ -36,10 +36,16 @@
/* /*
* This function is called when HTTP header field |nv| in |frame| is * This function is called when HTTP header field |nv| in |frame| is
* received for |stream|. This function will validate |nv| against * received for |stream|. This function will validate |nv| against
* the current state of stream. This function returns 0 if it * the current state of stream.
* succeeds, or 1 if the header field should be ignored, or -1 if the *
* header field contains totally unforgivable piece of junk and stream * This function returns 0 if it succeeds, or one of the following
* should be killed. * negative error codes:
*
* NGHTTP2_ERR_HTTP_HEADER
* Invalid HTTP header field was received.
* NGHTTP2_ERR_IGN_HTTP_HEADER
* Invalid HTTP header field was received but it can be treated as
* if it was not received because of compatibility reasons.
*/ */
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_nv *nv, int trailer); nghttp2_frame *frame, nghttp2_nv *nv, int trailer);

View File

@ -46,7 +46,12 @@ typedef int (*nghttp2_compar)(const void *lhs, const void *rhs);
typedef enum { typedef enum {
NGHTTP2_ERR_CREDENTIAL_PENDING = -101, NGHTTP2_ERR_CREDENTIAL_PENDING = -101,
NGHTTP2_ERR_IGN_HEADER_BLOCK = -103, NGHTTP2_ERR_IGN_HEADER_BLOCK = -103,
NGHTTP2_ERR_IGN_PAYLOAD = -104 NGHTTP2_ERR_IGN_PAYLOAD = -104,
/*
* Invalid HTTP header field was received but it can be treated as
* if it was not received because of compatibility reasons.
*/
NGHTTP2_ERR_IGN_HTTP_HEADER = -105,
} nghttp2_internal_error; } nghttp2_internal_error;
#endif /* NGHTTP2_INT_H */ #endif /* NGHTTP2_INT_H */

View File

@ -3023,11 +3023,12 @@ static int session_handle_frame_size_error(nghttp2_session *session,
return nghttp2_session_terminate_session(session, NGHTTP2_FRAME_SIZE_ERROR); return nghttp2_session_terminate_session(session, NGHTTP2_FRAME_SIZE_ERROR);
} }
static int session_handle_invalid_stream(nghttp2_session *session, static int session_handle_invalid_stream2(nghttp2_session *session,
int32_t stream_id,
nghttp2_frame *frame, nghttp2_frame *frame,
uint32_t error_code) { uint32_t error_code) {
int rv; int rv;
rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id, error_code); rv = nghttp2_session_add_rst_stream(session, stream_id, error_code);
if (rv != 0) { if (rv != 0) {
return rv; return rv;
} }
@ -3040,6 +3041,13 @@ static int session_handle_invalid_stream(nghttp2_session *session,
return 0; return 0;
} }
static int session_handle_invalid_stream(nghttp2_session *session,
nghttp2_frame *frame,
uint32_t error_code) {
return session_handle_invalid_stream2(session, frame->hd.stream_id, frame,
error_code);
}
static int session_inflate_handle_invalid_stream(nghttp2_session *session, static int session_inflate_handle_invalid_stream(nghttp2_session *session,
nghttp2_frame *frame, nghttp2_frame *frame,
uint32_t error_code) { uint32_t error_code) {
@ -3170,18 +3178,22 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
if (subject_stream && session_enforce_http_messaging(session)) { if (subject_stream && session_enforce_http_messaging(session)) {
rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, rv = nghttp2_http_on_header(session, subject_stream, frame, &nv,
trailer); trailer);
if (rv == -1) { if (rv == NGHTTP2_ERR_HTTP_HEADER) {
DEBUGF(fprintf( DEBUGF(fprintf(
stderr, "recv: HTTP error: type=%d, id=%d, header %.*s: %.*s\n", stderr, "recv: HTTP error: type=%d, id=%d, header %.*s: %.*s\n",
frame->hd.type, subject_stream->stream_id, (int)nv.namelen, frame->hd.type, subject_stream->stream_id, (int)nv.namelen,
nv.name, (int)nv.valuelen, nv.value)); nv.name, (int)nv.valuelen, nv.value));
rv = nghttp2_session_add_rst_stream(
session, subject_stream->stream_id, NGHTTP2_PROTOCOL_ERROR); rv =
session_handle_invalid_stream2(session, subject_stream->stream_id,
frame, NGHTTP2_PROTOCOL_ERROR);
if (nghttp2_is_fatal(rv)) { if (nghttp2_is_fatal(rv)) {
return rv; return rv;
} }
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
} else if (rv == 1) { }
if (rv == NGHTTP2_ERR_IGN_HTTP_HEADER) {
/* header is ignored */ /* header is ignored */
DEBUGF(fprintf( DEBUGF(fprintf(
stderr, "recv: HTTP ignored: type=%d, id=%d, header %.*s: %.*s\n", stderr, "recv: HTTP ignored: type=%d, id=%d, header %.*s: %.*s\n",
@ -3189,7 +3201,7 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
nv.name, (int)nv.valuelen, nv.value)); nv.name, (int)nv.valuelen, nv.value));
} }
} }
if (rv == 0 && call_header_cb) { if (rv == 0) {
rv = session_call_on_header(session, frame, &nv); rv = session_call_on_header(session, frame, &nv);
/* This handles NGHTTP2_ERR_PAUSE and /* This handles NGHTTP2_ERR_PAUSE and
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */ NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */
@ -3348,7 +3360,7 @@ static int session_after_header_block_received(nghttp2_session *session) {
call_cb = 0; call_cb = 0;
rv = nghttp2_session_add_rst_stream(session, stream_id, rv = session_handle_invalid_stream2(session, stream_id, frame,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
if (nghttp2_is_fatal(rv)) { if (nghttp2_is_fatal(rv)) {
return rv; return rv;

View File

@ -6820,10 +6820,13 @@ static void check_nghttp2_http_recv_headers_fail(
ssize_t rv; ssize_t rv;
nghttp2_outbound_item *item; nghttp2_outbound_item *item;
nghttp2_bufs bufs; nghttp2_bufs bufs;
my_user_data *ud;
mem = nghttp2_mem_default(); mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs); frame_pack_bufs_init(&bufs);
ud = session->user_data;
if (stream_state != -1) { if (stream_state != -1) {
nghttp2_session_open_stream(session, stream_id, NGHTTP2_STREAM_FLAG_NONE, nghttp2_session_open_stream(session, stream_id, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default, stream_state, NULL); &pri_spec_default, stream_state, NULL);
@ -6833,6 +6836,8 @@ static void check_nghttp2_http_recv_headers_fail(
nvlen, mem); nvlen, mem);
CU_ASSERT(0 == rv); CU_ASSERT(0 == rv);
ud->invalid_frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
nghttp2_buf_len(&bufs.head->buf)); nghttp2_buf_len(&bufs.head->buf));
@ -6841,6 +6846,7 @@ static void check_nghttp2_http_recv_headers_fail(
item = nghttp2_session_get_next_ob_item(session); item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
CU_ASSERT(1 == ud->invalid_frame_recv_cb_called);
CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == nghttp2_session_send(session));
@ -6852,6 +6858,7 @@ void test_nghttp2_http_mandatory_headers(void) {
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
nghttp2_hd_deflater deflater; nghttp2_hd_deflater deflater;
nghttp2_mem *mem; nghttp2_mem *mem;
my_user_data ud;
/* test case for response */ /* test case for response */
const nghttp2_nv nostatus_resnv[] = {MAKE_NV("server", "foo")}; const nghttp2_nv nostatus_resnv[] = {MAKE_NV("server", "foo")};
const nghttp2_nv dupstatus_resnv[] = {MAKE_NV(":status", "200"), const nghttp2_nv dupstatus_resnv[] = {MAKE_NV(":status", "200"),
@ -6907,8 +6914,9 @@ void test_nghttp2_http_mandatory_headers(void) {
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback; callbacks.send_callback = null_send_callback;
callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
nghttp2_session_client_new(&session, &callbacks, NULL); nghttp2_session_client_new(&session, &callbacks, &ud);
nghttp2_hd_deflate_init(&deflater, mem); nghttp2_hd_deflate_init(&deflater, mem);
@ -6957,7 +6965,7 @@ void test_nghttp2_http_mandatory_headers(void) {
nghttp2_session_del(session); nghttp2_session_del(session);
/* check server side */ /* check server side */
nghttp2_session_server_new(&session, &callbacks, NULL); nghttp2_session_server_new(&session, &callbacks, &ud);
nghttp2_hd_deflate_init(&deflater, mem); nghttp2_hd_deflate_init(&deflater, mem);