Merge pull request #646 from nghttp2/invalid-header-cb
Add nghttp2_on_invalid_header_callback
This commit is contained in:
commit
8579b8a968
|
@ -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 \
|
||||
|
|
|
@ -1721,6 +1721,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
|
||||
*
|
||||
|
@ -2079,6 +2138,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
|
||||
*
|
||||
|
@ -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,
|
||||
* :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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3345,6 +3345,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) {
|
||||
|
@ -3608,6 +3633,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",
|
||||
|
|
|
@ -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) {
|
||||
|
@ -10613,6 +10652,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);
|
||||
|
@ -10630,6 +10670,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) {
|
||||
|
@ -10650,7 +10692,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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue