Merge pull request #646 from nghttp2/invalid-header-cb

Add nghttp2_on_invalid_header_callback
This commit is contained in:
Tatsuhiro Tsujikawa 2016-08-15 11:13:20 +09:00 committed by GitHub
commit 8579b8a968
6 changed files with 240 additions and 7 deletions

View File

@ -87,6 +87,8 @@ APIDOCS= \
nghttp2_session_callbacks_set_on_header_callback.rst \ nghttp2_session_callbacks_set_on_header_callback.rst \
nghttp2_session_callbacks_set_on_header_callback2.rst \ nghttp2_session_callbacks_set_on_header_callback2.rst \
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst \ nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst \
nghttp2_session_callbacks_set_on_invalid_header_callback.rst \
nghttp2_session_callbacks_set_on_invalid_header_callback2.rst \
nghttp2_session_callbacks_set_on_stream_close_callback.rst \ nghttp2_session_callbacks_set_on_stream_close_callback.rst \
nghttp2_session_callbacks_set_pack_extension_callback.rst \ nghttp2_session_callbacks_set_pack_extension_callback.rst \
nghttp2_session_callbacks_set_recv_callback.rst \ nghttp2_session_callbacks_set_recv_callback.rst \

View File

@ -1721,6 +1721,65 @@ typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session,
nghttp2_rcbuf *value, uint8_t flags, nghttp2_rcbuf *value, uint8_t flags,
void *user_data); void *user_data);
/**
* @functypedef
*
* Callback function invoked when a invalid header name/value pair is
* received for the |frame|.
*
* The parameter and behaviour are similar to
* :type:`nghttp2_on_header_callback`. The difference is that this
* callback is only invoked when a invalid header name/value pair is
* received which is silently ignored if this callback is not set.
* Only invalid regular header field are passed to this callback. In
* other words, invalid pseudo header field is not passed to this
* callback. Also header fields which includes upper cased latter are
* also treated as error without passing them to this callback.
*
* This callback is only considered if HTTP messaging validation is
* turned on (which is on by default, see
* `nghttp2_option_set_no_http_messaging()`).
*
* With this callback, application inspects the incoming invalid
* field, and it also can reset stream from this callback by returning
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`, or using
* `nghttp2_submit_rst_stream()` directly with the error code of
* choice.
*/
typedef int (*nghttp2_on_invalid_header_callback)(
nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name,
size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags,
void *user_data);
/**
* @functypedef
*
* Callback function invoked when a invalid header name/value pair is
* received for the |frame|.
*
* The parameter and behaviour are similar to
* :type:`nghttp2_on_header_callback2`. The difference is that this
* callback is only invoked when a invalid header name/value pair is
* received which is silently ignored if this callback is not set.
* Only invalid regular header field are passed to this callback. In
* other words, invalid pseudo header field is not passed to this
* callback. Also header fields which includes upper cased latter are
* also treated as error without passing them to this callback.
*
* This callback is only considered if HTTP messaging validation is
* turned on (which is on by default, see
* `nghttp2_option_set_no_http_messaging()`).
*
* With this callback, application inspects the incoming invalid
* field, and it also can reset stream from this callback by returning
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`, or using
* `nghttp2_submit_rst_stream()` directly with the error code of
* choice.
*/
typedef int (*nghttp2_on_invalid_header_callback2)(
nghttp2_session *session, const nghttp2_frame *frame, nghttp2_rcbuf *name,
nghttp2_rcbuf *value, uint8_t flags, void *user_data);
/** /**
* @functypedef * @functypedef
* *
@ -2079,6 +2138,29 @@ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback2(
nghttp2_session_callbacks *cbs, nghttp2_session_callbacks *cbs,
nghttp2_on_header_callback2 on_header_callback2); nghttp2_on_header_callback2 on_header_callback2);
/**
* @function
*
* Sets callback function invoked when a invalid header name/value
* pair is received. If both
* `nghttp2_session_callbacks_set_on_invalid_header_callback()` and
* `nghttp2_session_callbacks_set_on_invalid_header_callback2()` are
* used to set callbacks, the latter takes the precedence.
*/
NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback(
nghttp2_session_callbacks *cbs,
nghttp2_on_invalid_header_callback on_invalid_header_callback);
/**
* @function
*
* Sets callback function invoked when a invalid header name/value
* pair is received.
*/
NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback2(
nghttp2_session_callbacks *cbs,
nghttp2_on_invalid_header_callback2 on_invalid_header_callback2);
/** /**
* @function * @function
* *
@ -2738,12 +2820,13 @@ nghttp2_session_mem_send(nghttp2_session *session, const uint8_t **data_ptr);
* taken. If the frame is either HEADERS or PUSH_PROMISE, * taken. If the frame is either HEADERS or PUSH_PROMISE,
* :type:`nghttp2_on_begin_headers_callback` is invoked. Then * :type:`nghttp2_on_begin_headers_callback` is invoked. Then
* :type:`nghttp2_on_header_callback` is invoked for each header * :type:`nghttp2_on_header_callback` is invoked for each header
* name/value pair. After all name/value pairs are emitted * name/value pair. For invalid header field,
* successfully, :type:`nghttp2_on_frame_recv_callback` is * :type:`nghttp2_on_invalid_header_callback` is called. After
* invoked. For other frames, * all name/value pairs are emitted successfully,
* :type:`nghttp2_on_frame_recv_callback` is invoked. If the * :type:`nghttp2_on_frame_recv_callback` is invoked. For other
* reception of the frame triggers the closure of the stream, * frames, :type:`nghttp2_on_frame_recv_callback` is invoked.
* :type:`nghttp2_on_stream_close_callback` is invoked. * If the reception of the frame triggers the closure of the
* stream, :type:`nghttp2_on_stream_close_callback` is invoked.
* *
* 3. If the received frame is unpacked but is interpreted as * 3. If the received frame is unpacked but is interpreted as
* invalid, :type:`nghttp2_on_invalid_frame_recv_callback` is * invalid, :type:`nghttp2_on_invalid_frame_recv_callback` is

View File

@ -110,6 +110,18 @@ void nghttp2_session_callbacks_set_on_header_callback2(
cbs->on_header_callback2 = on_header_callback2; cbs->on_header_callback2 = on_header_callback2;
} }
void nghttp2_session_callbacks_set_on_invalid_header_callback(
nghttp2_session_callbacks *cbs,
nghttp2_on_invalid_header_callback on_invalid_header_callback) {
cbs->on_invalid_header_callback = on_invalid_header_callback;
}
void nghttp2_session_callbacks_set_on_invalid_header_callback2(
nghttp2_session_callbacks *cbs,
nghttp2_on_invalid_header_callback2 on_invalid_header_callback2) {
cbs->on_invalid_header_callback2 = on_invalid_header_callback2;
}
void nghttp2_session_callbacks_set_select_padding_callback( void nghttp2_session_callbacks_set_select_padding_callback(
nghttp2_session_callbacks *cbs, nghttp2_session_callbacks *cbs,
nghttp2_select_padding_callback select_padding_callback) { nghttp2_select_padding_callback select_padding_callback) {

View File

@ -92,6 +92,13 @@ struct nghttp2_session_callbacks {
*/ */
nghttp2_on_header_callback on_header_callback; nghttp2_on_header_callback on_header_callback;
nghttp2_on_header_callback2 on_header_callback2; nghttp2_on_header_callback2 on_header_callback2;
/**
* Callback function invoked when a invalid header name/value pair
* is received which is silently ignored if these callbacks are not
* set.
*/
nghttp2_on_invalid_header_callback on_invalid_header_callback;
nghttp2_on_invalid_header_callback2 on_invalid_header_callback2;
/** /**
* Callback function invoked when the library asks application how * Callback function invoked when the library asks application how
* many padding bytes are required for the transmission of the given * many padding bytes are required for the transmission of the given

View File

@ -3345,6 +3345,31 @@ static int session_call_on_header(nghttp2_session *session,
return 0; return 0;
} }
static int session_call_on_invalid_header(nghttp2_session *session,
const nghttp2_frame *frame,
const nghttp2_hd_nv *nv) {
int rv;
if (session->callbacks.on_invalid_header_callback2) {
rv = session->callbacks.on_invalid_header_callback2(
session, frame, nv->name, nv->value, nv->flags, session->user_data);
} else if (session->callbacks.on_invalid_header_callback) {
rv = session->callbacks.on_invalid_header_callback(
session, frame, nv->name->base, nv->name->len, nv->value->base,
nv->value->len, nv->flags, session->user_data);
} else {
return 0;
}
if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
return rv;
}
if (rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
static int static int
session_call_on_extension_chunk_recv_callback(nghttp2_session *session, session_call_on_extension_chunk_recv_callback(nghttp2_session *session,
const uint8_t *data, size_t len) { const uint8_t *data, size_t len) {
@ -3608,6 +3633,14 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
if (rv == NGHTTP2_ERR_IGN_HTTP_HEADER) { if (rv == NGHTTP2_ERR_IGN_HTTP_HEADER) {
/* Don't overwrite rv here */ /* Don't overwrite rv here */
int rv2; int rv2;
rv2 = session_call_on_invalid_header(session, frame, &nv);
/* This handles NGHTTP2_ERR_PAUSE and
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */
if (rv2 != 0) {
return rv2;
}
/* header is ignored */ /* header is ignored */
DEBUGF(fprintf( DEBUGF(fprintf(
stderr, "recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n", stderr, "recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n",

View File

@ -70,6 +70,7 @@ typedef struct {
const nghttp2_frame *frame; const nghttp2_frame *frame;
size_t fixed_sendlen; size_t fixed_sendlen;
int header_cb_called; int header_cb_called;
int invalid_header_cb_called;
int begin_headers_cb_called; int begin_headers_cb_called;
nghttp2_nv nv; nghttp2_nv nv;
size_t data_chunk_len; size_t data_chunk_len;
@ -395,6 +396,44 @@ static int temporal_failure_on_header_callback(
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
} }
static int on_invalid_header_callback(nghttp2_session *session _U_,
const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
uint8_t flags _U_, void *user_data) {
my_user_data *ud = (my_user_data *)user_data;
++ud->invalid_header_cb_called;
ud->nv.name = (uint8_t *)name;
ud->nv.namelen = namelen;
ud->nv.value = (uint8_t *)value;
ud->nv.valuelen = valuelen;
ud->frame = frame;
return 0;
}
static int pause_on_invalid_header_callback(nghttp2_session *session,
const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value,
size_t valuelen, uint8_t flags,
void *user_data) {
on_invalid_header_callback(session, frame, name, namelen, value, valuelen,
flags, user_data);
return NGHTTP2_ERR_PAUSE;
}
static int reset_on_invalid_header_callback(nghttp2_session *session,
const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value,
size_t valuelen, uint8_t flags,
void *user_data) {
on_invalid_header_callback(session, frame, name, namelen, value, valuelen,
flags, user_data);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
static int on_begin_headers_callback(nghttp2_session *session _U_, static int on_begin_headers_callback(nghttp2_session *session _U_,
const nghttp2_frame *frame _U_, const nghttp2_frame *frame _U_,
void *user_data) { void *user_data) {
@ -10613,6 +10652,7 @@ void test_nghttp2_http_ignore_regular_header(void) {
MAKE_NV(":path", "/"), MAKE_NV(":method", "GET"), MAKE_NV("bar", "buzz")}; MAKE_NV(":path", "/"), MAKE_NV(":method", "GET"), MAKE_NV("bar", "buzz")};
size_t proclen; size_t proclen;
size_t i; size_t i;
nghttp2_outbound_item *item;
mem = nghttp2_mem_default(); mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs); frame_pack_bufs_init(&bufs);
@ -10630,6 +10670,8 @@ void test_nghttp2_http_ignore_regular_header(void) {
CU_ASSERT_FATAL(0 == rv); CU_ASSERT_FATAL(0 == rv);
nghttp2_hd_deflate_free(&deflater);
proclen = 0; proclen = 0;
for (i = 0; i < 4; ++i) { for (i = 0; i < 4; ++i) {
@ -10650,7 +10692,61 @@ void test_nghttp2_http_ignore_regular_header(void) {
CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == proclen); CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == proclen);
nghttp2_hd_deflate_free(&deflater); nghttp2_session_del(session);
/* use on_invalid_header_callback */
callbacks.on_invalid_header_callback = pause_on_invalid_header_callback;
nghttp2_session_server_new(&session, &callbacks, &ud);
proclen = 0;
ud.invalid_header_cb_called = 0;
for (i = 0; i < 4; ++i) {
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
nghttp2_buf_len(&bufs.head->buf) - proclen);
CU_ASSERT_FATAL(rv > 0);
proclen += (size_t)rv;
CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[i], &ud.nv));
}
CU_ASSERT(0 == ud.invalid_header_cb_called);
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
nghttp2_buf_len(&bufs.head->buf) - proclen);
CU_ASSERT_FATAL(rv > 0);
CU_ASSERT(1 == ud.invalid_header_cb_called);
CU_ASSERT(nghttp2_nv_equal(&bad_reqnv[4], &ud.nv));
proclen += (size_t)rv;
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
nghttp2_buf_len(&bufs.head->buf) - proclen);
CU_ASSERT(rv > 0);
CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[4], &ud.nv));
nghttp2_session_del(session);
/* make sure that we can reset stream from
on_invalid_header_callback */
callbacks.on_header_callback = on_header_callback;
callbacks.on_invalid_header_callback = reset_on_invalid_header_callback;
nghttp2_session_server_new(&session, &callbacks, &ud);
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
nghttp2_buf_len(&bufs.head->buf));
CU_ASSERT(rv == (ssize_t)nghttp2_buf_len(&bufs.head->buf));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
CU_ASSERT(1 == item->frame.hd.stream_id);
nghttp2_session_del(session); nghttp2_session_del(session);
nghttp2_bufs_free(&bufs); nghttp2_bufs_free(&bufs);
} }