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:
parent
318235db33
commit
271f7fbbb6
|
@ -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 \
|
||||||
|
|
|
@ -1717,6 +1717,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
|
||||||
*
|
*
|
||||||
|
@ -2075,6 +2134,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
|
||||||
*
|
*
|
||||||
|
@ -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,
|
* 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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -3343,6 +3343,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) {
|
||||||
|
@ -3606,6 +3631,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",
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -10363,6 +10402,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);
|
||||||
|
@ -10380,6 +10420,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) {
|
||||||
|
@ -10400,7 +10442,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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue