Add nghttp2_on_invalid_header_callback

nghttp2_on_invalid_header_callback is similar to
nghttp2_on_header_callback, but the former is only called when the
invalid header field is received which is silently ignored when the
callback is not set.  With this callback, application inspects the
incoming invalid field, and it also can reset stream from this
callback by returning NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE, or using
nghttp2_submit_rst_stream() directly with the error code of choice.

We also added nghttp2_on_invalid_header_callback2, which uses
reference counted header fields.
This commit is contained in:
Tatsuhiro Tsujikawa 2016-08-03 23:49:49 +09:00
parent 318235db33
commit 271f7fbbb6
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_callback2.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_pack_extension_callback.rst \
nghttp2_session_callbacks_set_recv_callback.rst \

View File

@ -1717,6 +1717,65 @@ typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session,
nghttp2_rcbuf *value, 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_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
*
@ -2075,6 +2134,29 @@ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback2(
nghttp2_session_callbacks *cbs,
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
*
@ -2734,12 +2816,13 @@ nghttp2_session_mem_send(nghttp2_session *session, const uint8_t **data_ptr);
* taken. If the frame is either HEADERS or PUSH_PROMISE,
* :type:`nghttp2_on_begin_headers_callback` is invoked. Then
* :type:`nghttp2_on_header_callback` is invoked for each header
* name/value pair. After all name/value pairs are emitted
* successfully, :type:`nghttp2_on_frame_recv_callback` is
* invoked. For other frames,
* :type:`nghttp2_on_frame_recv_callback` is invoked. If the
* reception of the frame triggers the closure of the stream,
* :type:`nghttp2_on_stream_close_callback` is invoked.
* name/value pair. For invalid header field,
* :type:`nghttp2_on_invalid_header_callback` is called. After
* all name/value pairs are emitted successfully,
* :type:`nghttp2_on_frame_recv_callback` is invoked. For other
* frames, :type:`nghttp2_on_frame_recv_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
* 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;
}
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(
nghttp2_session_callbacks *cbs,
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_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
* many padding bytes are required for the transmission of the given

View File

@ -3343,6 +3343,31 @@ static int session_call_on_header(nghttp2_session *session,
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
session_call_on_extension_chunk_recv_callback(nghttp2_session *session,
const uint8_t *data, size_t len) {
@ -3606,6 +3631,14 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
if (rv == NGHTTP2_ERR_IGN_HTTP_HEADER) {
/* Don't overwrite rv here */
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 */
DEBUGF(fprintf(
stderr, "recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n",

View File

@ -70,6 +70,7 @@ typedef struct {
const nghttp2_frame *frame;
size_t fixed_sendlen;
int header_cb_called;
int invalid_header_cb_called;
int begin_headers_cb_called;
nghttp2_nv nv;
size_t data_chunk_len;
@ -395,6 +396,44 @@ static int temporal_failure_on_header_callback(
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_,
const nghttp2_frame *frame _U_,
void *user_data) {
@ -10363,6 +10402,7 @@ void test_nghttp2_http_ignore_regular_header(void) {
MAKE_NV(":path", "/"), MAKE_NV(":method", "GET"), MAKE_NV("bar", "buzz")};
size_t proclen;
size_t i;
nghttp2_outbound_item *item;
mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs);
@ -10380,6 +10420,8 @@ void test_nghttp2_http_ignore_regular_header(void) {
CU_ASSERT_FATAL(0 == rv);
nghttp2_hd_deflate_free(&deflater);
proclen = 0;
for (i = 0; i < 4; ++i) {
@ -10400,7 +10442,61 @@ void test_nghttp2_http_ignore_regular_header(void) {
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_bufs_free(&bufs);
}