diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index d365bb4f..82e1d4dd 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -721,17 +721,13 @@ ssize_t nghttp2_frame_nv_offset(const uint8_t *head) int nghttp2_frame_nv_check_null(const char **nv) { - size_t i, j; + size_t i; for(i = 0; nv[i]; i += 2) { - if(nv[i][0] == '\0' || nv[i+1] == NULL) { + if(nv[i+1] == NULL || + !nghttp2_check_header_name_nocase((const uint8_t*)nv[i], + strlen(nv[i]))) { return 0; } - for(j = 0; nv[i][j]; ++j) { - unsigned char c = nv[i][j]; - if(c < 0x20 || c > 0x7e) { - return 0; - } - } } return 1; } diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index 15366f4c..e29ae578 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -623,7 +623,8 @@ void nghttp2_nv_array_del(nghttp2_nv *nva); /* * Checks names are not empty string and do not contain control - * characters and values are not NULL. + * characters and values are not NULL. This function allows captital + * alphabet letters in name. * * This function returns nonzero if it succeeds, or 0. */ diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index 0f75ded2..4c993d3b 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -914,6 +914,10 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, rv = NGHTTP2_ERR_HEADER_COMP; goto fail; } + if(!nghttp2_check_header_name(in, namelen)) { + rv = NGHTTP2_ERR_HEADER_COMP; + goto fail; + } nv.name = in; in += namelen; in = decode_length(&valuelen, in, last, 8); @@ -1005,6 +1009,10 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater, rv = NGHTTP2_ERR_HEADER_COMP; goto fail; } + if(!nghttp2_check_header_name(in, namelen)) { + rv = NGHTTP2_ERR_HEADER_COMP; + goto fail; + } nv.name = in; in += namelen; in = decode_length(&subindex, in, last, 8); diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c index 6dae649e..90ac00f3 100644 --- a/lib/nghttp2_helper.c +++ b/lib/nghttp2_helper.c @@ -24,6 +24,7 @@ */ #include "nghttp2_helper.h" +#include #include #include "nghttp2_net.h" @@ -122,6 +123,71 @@ int nghttp2_should_send_window_update(int32_t local_window_size, return recv_window_size >= local_window_size / 2; } +static int VALID_HD_NAME_CHARS[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1 /* ! */, + -1, + 1 /* # */, 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */, + -1, -1, + 1 /* * */, 1 /* + */, + -1, + 1 /* - */, 1 /* . */, + -1, + 1 /* 0 */, 1 /* 1 */, 1 /* 2 */, 1 /* 3 */, 1 /* 4 */, 1 /* 5 */, + 1 /* 6 */, 1 /* 7 */, 1 /* 8 */, 1 /* 9 */, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1 /* ^ */, 1 /* _ */, 1 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, 1 /* d */, + 1 /* e */, 1 /* f */, 1 /* g */, 1 /* h */, 1 /* i */, 1 /* j */, 1 /* k */, + 1 /* l */, 1 /* m */, 1 /* n */, 1 /* o */, 1 /* p */, 1 /* q */, 1 /* r */, + 1 /* s */, 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, 1 /* x */, 1 /* y */, + 1 /* z */, + -1, + 1 /* | */, + -1, + 1 /* ~ */, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static int check_header_name(const uint8_t *name, size_t len, int nocase) +{ + const uint8_t *last; + if(len == 0) { + return 0; + } + if(*name == ':') { + if(len == 1) { + return 0; + } + ++name; + --len; + } + for(last = name + len; name != last; ++name) { + if(nocase && 'A' <= *name && *name <= 'Z') continue; + if(VALID_HD_NAME_CHARS[*name] == -1) { + return 0; + } + } + return 1; +} + +int nghttp2_check_header_name(const uint8_t *name, size_t len) +{ + return check_header_name(name, len, 0); +} + +int nghttp2_check_header_name_nocase(const uint8_t *name, size_t len) +{ + return check_header_name(name, len, 1); +} + const char* nghttp2_strerror(int error_code) { switch(error_code) { diff --git a/lib/nghttp2_helper.h b/lib/nghttp2_helper.h index 3439dcf5..40a5d176 100644 --- a/lib/nghttp2_helper.h +++ b/lib/nghttp2_helper.h @@ -116,4 +116,19 @@ int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr, int nghttp2_should_send_window_update(int32_t local_window_size, int32_t recv_window_size); +/* + * Checks the header name in |name| with |len| bytes is valid. + * + * This function returns nonzero if it succeeds, or 0. + */ +int nghttp2_check_header_name(const uint8_t *name, size_t len); + +/* + * Checks the header name in |name| with |len| bytes is valid. This + * function accepts also characters in [A-Z]. + * + * This function returns nonzero if it succeeds, or 0. + */ +int nghttp2_check_header_name_nocase(const uint8_t *name, size_t len); + #endif /* NGHTTP2_HELPER_H */ diff --git a/tests/main.c b/tests/main.c index 78383991..23a11110 100644 --- a/tests/main.c +++ b/tests/main.c @@ -240,7 +240,9 @@ int main(int argc, char* argv[]) test_nghttp2_hd_deflate_inflate) || !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || !CU_add_test(pSuite, "adjust_local_window_size", - test_nghttp2_adjust_local_window_size) + test_nghttp2_adjust_local_window_size) || + !CU_add_test(pSuite, "check_header_name", + test_nghttp2_check_header_name) ) { CU_cleanup_registry(); return CU_get_error(); diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c index beb1ec1b..b01e6dfd 100644 --- a/tests/nghttp2_frame_test.c +++ b/tests/nghttp2_frame_test.c @@ -85,7 +85,7 @@ void test_nghttp2_frame_nv_check_null(void) const char *headers1[] = { "path", "/", "host", "a", NULL }; const char *headers2[] = { "", "/", "host", "a", NULL }; const char *headers3[] = { "path", "/", "host\x01", "a", NULL }; - const char *headers4[] = { "path", "/", "host", NULL, NULL }; + const char *headers4[] = { "PATH", "/", "host", NULL, NULL }; CU_ASSERT(nghttp2_frame_nv_check_null(headers1)); CU_ASSERT(0 == nghttp2_frame_nv_check_null(headers2)); diff --git a/tests/nghttp2_helper_test.c b/tests/nghttp2_helper_test.c index 83ecfb71..f038809b 100644 --- a/tests/nghttp2_helper_test.c +++ b/tests/nghttp2_helper_test.c @@ -73,3 +73,32 @@ void test_nghttp2_adjust_local_window_size(void) CU_ASSERT(100 == local_window_size); CU_ASSERT(50 == recv_window_size); } + +static int check_header_name(const char *s) +{ + return nghttp2_check_header_name((const uint8_t*)s, strlen(s)); +} + +static int check_header_name_nocase(const char *s) +{ + return nghttp2_check_header_name_nocase((const uint8_t*)s, strlen(s)); +} + +void test_nghttp2_check_header_name(void) +{ + CU_ASSERT(check_header_name(":path")); + CU_ASSERT(check_header_name("path")); + CU_ASSERT(check_header_name("!#$%&'*+-.^_`|~")); + CU_ASSERT(!check_header_name(":PATH")); + CU_ASSERT(!check_header_name("path:")); + CU_ASSERT(!check_header_name("")); + CU_ASSERT(!check_header_name(":")); + + CU_ASSERT(check_header_name_nocase(":path")); + CU_ASSERT(check_header_name_nocase("path")); + CU_ASSERT(check_header_name_nocase("!#$%&'*+-.^_`|~")); + CU_ASSERT(check_header_name_nocase(":PATH")); + CU_ASSERT(!check_header_name_nocase("path:")); + CU_ASSERT(!check_header_name_nocase("")); + CU_ASSERT(!check_header_name_nocase(":")); +} diff --git a/tests/nghttp2_helper_test.h b/tests/nghttp2_helper_test.h index c4940d2c..a7699cf9 100644 --- a/tests/nghttp2_helper_test.h +++ b/tests/nghttp2_helper_test.h @@ -26,5 +26,6 @@ #define NGHTTP2_HELPER_TEST_H void test_nghttp2_adjust_local_window_size(void); +void test_nghttp2_check_header_name(void); #endif /* NGHTTP2_HELPER_TEST_H */ diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 87e2e5de..59130416 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -1606,7 +1606,7 @@ void test_nghttp2_submit_response(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const char *nv[] = { "Content-Length", "1024", NULL }; + const char *nv[] = { "content-length", "1024", NULL }; nghttp2_data_provider data_prd; my_user_data ud; nghttp2_outbound_item *item; @@ -1632,7 +1632,7 @@ void test_nghttp2_submit_response_without_data(void) nghttp2_session *session; nghttp2_session_callbacks callbacks; accumulator acc; - const char *nv[] = { ":Version", "HTTP/1.1", NULL }; + const char *nv[] = { ":version", "HTTP/1.1", NULL }; nghttp2_data_provider data_prd = {{-1}, NULL}; nghttp2_outbound_item *item; my_user_data ud; @@ -1665,7 +1665,7 @@ void test_nghttp2_submit_request_with_data(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const char *nv[] = { ":Version", "HTTP/1.1", NULL }; + const char *nv[] = { ":version", "HTTP/1.1", NULL }; nghttp2_data_provider data_prd; my_user_data ud; nghttp2_outbound_item *item; @@ -1691,7 +1691,7 @@ void test_nghttp2_submit_request_without_data(void) nghttp2_session *session; nghttp2_session_callbacks callbacks; accumulator acc; - const char *nv[] = { ":Version", "HTTP/1.1", NULL }; + const char *nv[] = { ":version", "HTTP/1.1", NULL }; nghttp2_data_provider data_prd = {{-1}, NULL}; nghttp2_outbound_item *item; my_user_data ud; @@ -1842,7 +1842,7 @@ void test_nghttp2_submit_headers(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const char *nv[] = { ":Version", "HTTP/1.1", NULL }; + const char *nv[] = { ":version", "HTTP/1.1", NULL }; my_user_data ud; nghttp2_outbound_item *item; nghttp2_stream *stream;