Treat incoming invalid regular header field as stream error
Previously, the incoming invalid regular header field was ignored by default. With this commit, they are now treated as stream error, and the stream is reset by default. The error code used is now PROTOCOL_ERROR, instead of INTERNAL_ERROR.
This commit is contained in:
parent
196673bbce
commit
78d7160a99
|
@ -149,11 +149,11 @@ header fields must not appear: "Connection", "Keep-Alive",
|
||||||
Each header field name and value must obey the field-name and
|
Each header field name and value must obey the field-name and
|
||||||
field-value production rules described in `RFC 7230, section
|
field-value production rules described in `RFC 7230, section
|
||||||
3.2. <https://tools.ietf.org/html/rfc7230#section-3.2>`_.
|
3.2. <https://tools.ietf.org/html/rfc7230#section-3.2>`_.
|
||||||
Additionally, all field name must be lower cased. While the pseudo
|
Additionally, all field name must be lower cased. The invalid header
|
||||||
header fields must satisfy these rules, we just ignore illegal regular
|
fields are treated as stream error, and that stream is reset. If
|
||||||
headers (this means that these header fields are not passed to
|
application wants to treat these headers in their own way, use
|
||||||
application callback). If application wants to treat these headers as
|
`nghttp2_on_invalid_header_callback
|
||||||
error, use `nghttp2_on_invalid_header_callback <https://nghttp2.org/documentation/types.html#c.nghttp2_on_invalid_header_callback>`_.
|
<https://nghttp2.org/documentation/types.html#c.nghttp2_on_invalid_header_callback>`_.
|
||||||
|
|
||||||
For "http" or "https" URIs, ":path" pseudo header fields must start
|
For "http" or "https" URIs, ":path" pseudo header fields must start
|
||||||
with "/". The only exception is OPTIONS request, in that case, "*" is
|
with "/". The only exception is OPTIONS request, in that case, "*" is
|
||||||
|
|
|
@ -1741,11 +1741,12 @@ typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session,
|
||||||
* The parameter and behaviour are similar to
|
* The parameter and behaviour are similar to
|
||||||
* :type:`nghttp2_on_header_callback`. The difference is that this
|
* :type:`nghttp2_on_header_callback`. The difference is that this
|
||||||
* callback is only invoked when a invalid header name/value pair is
|
* callback is only invoked when a invalid header name/value pair is
|
||||||
* received which is silently ignored if this callback is not set.
|
* received which is treated as stream error if this callback is not
|
||||||
* Only invalid regular header field are passed to this callback. In
|
* set. Only invalid regular header field are passed to this
|
||||||
* other words, invalid pseudo header field is not passed to this
|
* callback. In other words, invalid pseudo header field is not
|
||||||
* callback. Also header fields which includes upper cased latter are
|
* passed to this callback. Also header fields which includes upper
|
||||||
* also treated as error without passing them to this callback.
|
* cased latter are also treated as error without passing them to this
|
||||||
|
* callback.
|
||||||
*
|
*
|
||||||
* This callback is only considered if HTTP messaging validation is
|
* This callback is only considered if HTTP messaging validation is
|
||||||
* turned on (which is on by default, see
|
* turned on (which is on by default, see
|
||||||
|
@ -1754,10 +1755,13 @@ typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session,
|
||||||
* With this callback, application inspects the incoming invalid
|
* With this callback, application inspects the incoming invalid
|
||||||
* field, and it also can reset stream from this callback by returning
|
* field, and it also can reset stream from this callback by returning
|
||||||
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By default, the
|
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By default, the
|
||||||
* error code is :enum:`NGHTTP2_INTERNAL_ERROR`. To change the error
|
* error code is :enum:`NGHTTP2_PROTOCOL_ERROR`. To change the error
|
||||||
* code, call `nghttp2_submit_rst_stream()` with the error code of
|
* code, call `nghttp2_submit_rst_stream()` with the error code of
|
||||||
* choice in addition to returning
|
* choice in addition to returning
|
||||||
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
|
* :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
|
||||||
|
*
|
||||||
|
* If 0 is returned, the header field is ignored, and the stream is
|
||||||
|
* not reset.
|
||||||
*/
|
*/
|
||||||
typedef int (*nghttp2_on_invalid_header_callback)(
|
typedef int (*nghttp2_on_invalid_header_callback)(
|
||||||
nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name,
|
nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name,
|
||||||
|
|
|
@ -3321,7 +3321,7 @@ static int session_call_on_invalid_header(nghttp2_session *session,
|
||||||
session, frame, nv->name->base, nv->name->len, nv->value->base,
|
session, frame, nv->name->base, nv->name->len, nv->value->base,
|
||||||
nv->value->len, nv->flags, session->user_data);
|
nv->value->len, nv->flags, session->user_data);
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
|
if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
|
||||||
|
@ -3589,6 +3589,37 @@ 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 == NGHTTP2_ERR_IGN_HTTP_HEADER) {
|
||||||
|
/* Don't overwrite rv here */
|
||||||
|
int rv2;
|
||||||
|
|
||||||
|
rv2 = session_call_on_invalid_header(session, frame, &nv);
|
||||||
|
if (rv2 == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
|
||||||
|
rv = NGHTTP2_ERR_HTTP_HEADER;
|
||||||
|
} else {
|
||||||
|
if (rv2 != 0) {
|
||||||
|
return rv2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* header is ignored */
|
||||||
|
DEBUGF("recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n",
|
||||||
|
frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
|
||||||
|
nv.name->base, (int)nv.value->len, nv.value->base);
|
||||||
|
|
||||||
|
rv2 = session_call_error_callback(
|
||||||
|
session,
|
||||||
|
"Ignoring received invalid HTTP header field: frame type: "
|
||||||
|
"%u, stream: %d, name: [%.*s], value: [%.*s]",
|
||||||
|
frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
|
||||||
|
nv.name->base, (int)nv.value->len, nv.value->base);
|
||||||
|
|
||||||
|
if (nghttp2_is_fatal(rv2)) {
|
||||||
|
return rv2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (rv == NGHTTP2_ERR_HTTP_HEADER) {
|
if (rv == NGHTTP2_ERR_HTTP_HEADER) {
|
||||||
DEBUGF("recv: HTTP error: type=%u, id=%d, header %.*s: %.*s\n",
|
DEBUGF("recv: HTTP error: type=%u, id=%d, header %.*s: %.*s\n",
|
||||||
frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
|
frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
|
||||||
|
@ -3612,34 +3643,6 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
|
||||||
}
|
}
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
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("recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n",
|
|
||||||
frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
|
|
||||||
nv.name->base, (int)nv.value->len, nv.value->base);
|
|
||||||
|
|
||||||
rv2 = session_call_error_callback(
|
|
||||||
session,
|
|
||||||
"Ignoring received invalid HTTP header field: frame type: "
|
|
||||||
"%u, stream: %d, name: [%.*s], value: [%.*s]",
|
|
||||||
frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
|
|
||||||
nv.name->base, (int)nv.value->len, nv.value->base);
|
|
||||||
|
|
||||||
if (nghttp2_is_fatal(rv2)) {
|
|
||||||
return rv2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (rv == 0) {
|
if (rv == 0) {
|
||||||
rv = session_call_on_header(session, frame, &nv);
|
rv = session_call_on_header(session, frame, &nv);
|
||||||
|
|
|
@ -11104,9 +11104,12 @@ void test_nghttp2_http_ignore_regular_header(void) {
|
||||||
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
|
rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen,
|
||||||
nghttp2_buf_len(&bufs.head->buf) - proclen);
|
nghttp2_buf_len(&bufs.head->buf) - proclen);
|
||||||
CU_ASSERT_FATAL(rv > 0);
|
CU_ASSERT_FATAL(rv > 0);
|
||||||
/* header field "foo" must be ignored because it has illegal value.
|
/* Without on_invalid_frame_recv_callback, bad header causes stream
|
||||||
So we have "bar" header field for 5th header. */
|
reset */
|
||||||
CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[4], &ud.nv));
|
item = nghttp2_session_get_next_ob_item(session);
|
||||||
|
|
||||||
|
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
|
||||||
|
|
||||||
proclen += (size_t)rv;
|
proclen += (size_t)rv;
|
||||||
|
|
||||||
CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == proclen);
|
CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == proclen);
|
||||||
|
|
Loading…
Reference in New Issue