diff --git a/genlibtokenlookup.py b/genlibtokenlookup.py new file mode 100755 index 00000000..c396ec08 --- /dev/null +++ b/genlibtokenlookup.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +HEADERS = [ + ':authority', + ':method', + ':path', + ':scheme', + ':status', + "content-length", + "host", + "te", + 'connection', + 'keep-alive', + 'proxy-connection', + 'transfer-encoding', + 'upgrade' +] + +def to_enum_hd(k): + res = 'NGHTTP2_TOKEN_' + for c in k.upper(): + if c == ':' or c == '-': + res += '_' + continue + res += c + return res + +def build_header(headers): + res = {} + for k in headers: + size = len(k) + if size not in res: + res[size] = {} + ent = res[size] + c = k[-1] + if c not in ent: + ent[c] = [] + ent[c].append(k) + + return res + +def gen_enum(): + print '''\ +typedef enum {''' + for k in sorted(HEADERS): + print '''\ + {},'''.format(to_enum_hd(k)) + print '''\ + NGHTTP2_TOKEN_MAXIDX, +} nghttp2_token;''' + +def gen_index_header(): + print '''\ +static int lookup_token(const uint8_t *name, size_t namelen) { + switch (namelen) {''' + b = build_header(HEADERS) + for size in sorted(b.keys()): + ents = b[size] + print '''\ + case {}:'''.format(size) + print '''\ + switch (name[namelen - 1]) {''' + for c in sorted(ents.keys()): + headers = sorted(ents[c]) + print '''\ + case '{}':'''.format(c) + for k in headers: + print '''\ + if (streq("{}", name, {})) {{ + return {}; + }}'''.format(k[:-1], size - 1, to_enum_hd(k)) + print '''\ + break;''' + print '''\ + } + break;''' + print '''\ + } + return -1; +}''' + +if __name__ == '__main__': + gen_enum() + print '' + gen_index_header() diff --git a/integration-tests/nghttpx_http2_test.go b/integration-tests/nghttpx_http2_test.go index 29e5cc82..4e967d9e 100644 --- a/integration-tests/nghttpx_http2_test.go +++ b/integration-tests/nghttpx_http2_test.go @@ -355,9 +355,8 @@ func TestH2H1MultipleRequestCL(t *testing.T) { if err != nil { t.Fatalf("Error st.http2() = %v", err) } - want := 400 - if got := res.status; got != want { - t.Errorf("status: %v; want %v", got, want) + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) } } @@ -378,9 +377,8 @@ func TestH2H1InvalidRequestCL(t *testing.T) { if err != nil { t.Fatalf("Error st.http2() = %v", err) } - want := 400 - if got := res.status; got != want { - t.Errorf("status: %v; want %v", got, want) + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) } } @@ -614,9 +612,8 @@ func TestH2H2MultipleResponseCL(t *testing.T) { if err != nil { t.Fatalf("Error st.http2() = %v", err) } - want := 502 - if got := res.status; got != want { - t.Errorf("status: %v; want %v", got, want) + if got, want := res.errCode, http2.ErrCodeInternal; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) } } @@ -635,9 +632,8 @@ func TestH2H2InvalidResponseCL(t *testing.T) { if err != nil { t.Fatalf("Error st.http2() = %v", err) } - want := 502 - if got := res.status; got != want { - t.Errorf("status: %v; want %v", got, want) + if got, want := res.errCode, http2.ErrCodeInternal; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) } } diff --git a/lib/Makefile.am b/lib/Makefile.am index 35a05954..6d2393ef 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -45,7 +45,8 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \ nghttp2_priority_spec.c \ nghttp2_option.c \ nghttp2_callbacks.c \ - nghttp2_mem.c + nghttp2_mem.c \ + nghttp2_http.c HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ nghttp2_frame.h \ @@ -58,7 +59,8 @@ HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ nghttp2_priority_spec.h \ nghttp2_option.h \ nghttp2_callbacks.h \ - nghttp2_mem.h + nghttp2_mem.h \ + nghttp2_http.h libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS) libnghttp2_la_LDFLAGS = -no-undefined \ diff --git a/lib/nghttp2_http.c b/lib/nghttp2_http.c new file mode 100644 index 00000000..069b75fc --- /dev/null +++ b/lib/nghttp2_http.c @@ -0,0 +1,511 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_http.h" + +#include +#include +#include + +static int memeq(const void *a, const void *b, size_t n) { + return memcmp(a, b, n) == 0; +} + +#define streq(A, B, N) ((sizeof((A)) - 1) == (N) && memeq((A), (B), (N))) + +static char downcase(char c) { + return 'A' <= c && c <= 'Z' ? (c - 'A' + 'a') : c; +} + +static int memieq(const void *a, const void *b, size_t n) { + size_t i; + const uint8_t *aa = a, *bb = b; + + for (i = 0; i < n; ++i) { + if (downcase(aa[i]) != downcase(bb[i])) { + return 0; + } + } + return 1; +} + +#define strieq(A, B, N) ((sizeof((A)) - 1) == (N) && memieq((A), (B), (N))) + +typedef enum { + NGHTTP2_TOKEN__AUTHORITY, + NGHTTP2_TOKEN__METHOD, + NGHTTP2_TOKEN__PATH, + NGHTTP2_TOKEN__SCHEME, + NGHTTP2_TOKEN__STATUS, + NGHTTP2_TOKEN_CONNECTION, + NGHTTP2_TOKEN_CONTENT_LENGTH, + NGHTTP2_TOKEN_HOST, + NGHTTP2_TOKEN_KEEP_ALIVE, + NGHTTP2_TOKEN_PROXY_CONNECTION, + NGHTTP2_TOKEN_TE, + NGHTTP2_TOKEN_TRANSFER_ENCODING, + NGHTTP2_TOKEN_UPGRADE, + NGHTTP2_TOKEN_MAXIDX, +} nghttp2_token; + +// This function was generated by genlibtokenlookup.py. Inspired by +// h2o header lookup. https://github.com/h2o/h2o +static int lookup_token(const uint8_t *name, size_t namelen) { + switch (namelen) { + case 2: + switch (name[namelen - 1]) { + case 'e': + if (streq("t", name, 1)) { + return NGHTTP2_TOKEN_TE; + } + break; + } + break; + case 4: + switch (name[namelen - 1]) { + case 't': + if (streq("hos", name, 3)) { + return NGHTTP2_TOKEN_HOST; + } + break; + } + break; + case 5: + switch (name[namelen - 1]) { + case 'h': + if (streq(":pat", name, 4)) { + return NGHTTP2_TOKEN__PATH; + } + break; + } + break; + case 7: + switch (name[namelen - 1]) { + case 'd': + if (streq(":metho", name, 6)) { + return NGHTTP2_TOKEN__METHOD; + } + break; + case 'e': + if (streq(":schem", name, 6)) { + return NGHTTP2_TOKEN__SCHEME; + } + if (streq("upgrad", name, 6)) { + return NGHTTP2_TOKEN_UPGRADE; + } + break; + case 's': + if (streq(":statu", name, 6)) { + return NGHTTP2_TOKEN__STATUS; + } + break; + } + break; + case 10: + switch (name[namelen - 1]) { + case 'e': + if (streq("keep-aliv", name, 9)) { + return NGHTTP2_TOKEN_KEEP_ALIVE; + } + break; + case 'n': + if (streq("connectio", name, 9)) { + return NGHTTP2_TOKEN_CONNECTION; + } + break; + case 'y': + if (streq(":authorit", name, 9)) { + return NGHTTP2_TOKEN__AUTHORITY; + } + break; + } + break; + case 14: + switch (name[namelen - 1]) { + case 'h': + if (streq("content-lengt", name, 13)) { + return NGHTTP2_TOKEN_CONTENT_LENGTH; + } + break; + } + break; + case 16: + switch (name[namelen - 1]) { + case 'n': + if (streq("proxy-connectio", name, 15)) { + return NGHTTP2_TOKEN_PROXY_CONNECTION; + } + break; + } + break; + case 17: + switch (name[namelen - 1]) { + case 'g': + if (streq("transfer-encodin", name, 16)) { + return NGHTTP2_TOKEN_TRANSFER_ENCODING; + } + break; + } + break; + } + return -1; +} + +static int64_t parse_uint(const uint8_t *s, size_t len) { + int64_t n = 0; + size_t i; + if (len == 0) { + return -1; + } + for (i = 0; i < len; ++i) { + if ('0' <= s[i] && s[i] <= '9') { + if (n > INT64_MAX / 10) { + return -1; + } + n *= 10; + if (n > INT64_MAX - (s[i] - '0')) { + return -1; + } + n += s[i] - '0'; + continue; + } + return -1; + } + return n; +} + +static int lws(const uint8_t *s, size_t n) { + size_t i; + for (i = 0; i < n; ++i) { + if (s[i] != ' ' && s[i] != '\t') { + return 0; + } + } + return 1; +} + +static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_nv *nv, + int flag) { + if (stream->http_flags & flag) { + return 0; + } + if (lws(nv->value, nv->valuelen)) { + return 0; + } + stream->http_flags |= flag; + return 1; +} + +static int expect_response_body(nghttp2_stream *stream) { + return (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_HEAD) == 0 && + stream->status_code / 100 != 1 && stream->status_code != 304 && + stream->status_code != 204; +} + +static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, + int trailer) { + int token; + + if (nv->name[0] == ':') { + if (trailer || + (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { + return -1; + } + } + + token = lookup_token(nv->name, nv->namelen); + + switch (token) { + case NGHTTP2_TOKEN__AUTHORITY: + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_AUTHORITY)) { + return -1; + } + break; + case NGHTTP2_TOKEN__METHOD: + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_METHOD)) { + return -1; + } + if (streq("HEAD", nv->value, nv->valuelen)) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; + } else if (streq("CONNECT", nv->value, nv->valuelen)) { + if (stream->stream_id % 2 == 0) { + /* we won't allow CONNECT for push */ + return -1; + } + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; + if (stream->http_flags & + (NGHTTP2_HTTP_FLAG_PATH | NGHTTP2_HTTP_FLAG_SCHEME)) { + return -1; + } + } + break; + case NGHTTP2_TOKEN__PATH: + if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { + return -1; + } + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_PATH)) { + return -1; + } + break; + case NGHTTP2_TOKEN__SCHEME: + if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { + return -1; + } + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_SCHEME)) { + return -1; + } + break; + case NGHTTP2_TOKEN_HOST: + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) { + return -1; + } + break; + case NGHTTP2_TOKEN_CONTENT_LENGTH: { + if (stream->content_length != -1) { + return -1; + } + stream->content_length = parse_uint(nv->value, nv->valuelen); + if (stream->content_length == -1) { + return -1; + } + break; + } + /* disallowed header fields */ + case NGHTTP2_TOKEN_CONNECTION: + case NGHTTP2_TOKEN_KEEP_ALIVE: + case NGHTTP2_TOKEN_PROXY_CONNECTION: + case NGHTTP2_TOKEN_TRANSFER_ENCODING: + case NGHTTP2_TOKEN_UPGRADE: + return -1; + case NGHTTP2_TOKEN_TE: + if (!strieq("trailers", nv->value, nv->valuelen)) { + return -1; + } + break; + default: + if (nv->name[0] == ':') { + return -1; + } + } + + if (nv->name[0] != ':') { + stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; + } + + return 0; +} + +static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv, + int trailer) { + int token; + + if (nv->name[0] == ':') { + if (trailer || + (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { + return -1; + } + } + + token = lookup_token(nv->name, nv->namelen); + + switch (token) { + case NGHTTP2_TOKEN__STATUS: { + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_STATUS)) { + return -1; + } + if (nv->valuelen != 3) { + return -1; + } + stream->status_code = parse_uint(nv->value, nv->valuelen); + if (stream->status_code == -1) { + return -1; + } + break; + } + case NGHTTP2_TOKEN_CONTENT_LENGTH: { + if (stream->content_length != -1) { + return -1; + } + stream->content_length = parse_uint(nv->value, nv->valuelen); + if (stream->content_length == -1) { + return -1; + } + break; + } + /* disallowed header fields */ + case NGHTTP2_TOKEN_CONNECTION: + case NGHTTP2_TOKEN_KEEP_ALIVE: + case NGHTTP2_TOKEN_PROXY_CONNECTION: + case NGHTTP2_TOKEN_TRANSFER_ENCODING: + case NGHTTP2_TOKEN_UPGRADE: + return -1; + case NGHTTP2_TOKEN_TE: + if (!strieq("trailers", nv->value, nv->valuelen)) { + return -1; + } + break; + default: + if (nv->name[0] == ':') { + return -1; + } + } + + if (nv->name[0] != ':') { + stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; + } + + return 0; +} + +int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, + nghttp2_frame *frame, nghttp2_nv *nv, int trailer) { + if (!nghttp2_check_header_name(nv->name, nv->namelen) || + !nghttp2_check_header_value(nv->value, nv->valuelen)) { + return -1; + } + + if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) { + return http_request_on_header(stream, nv, trailer); + } + + return http_response_on_header(stream, nv, trailer); +} + +int nghttp2_http_on_request_headers(nghttp2_stream *stream, + nghttp2_frame *frame) { + if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { + if ((stream->http_flags & NGHTTP2_HTTP_FLAG_AUTHORITY) == 0) { + return -1; + } + stream->content_length = -1; + } else if ((stream->http_flags & NGHTTP2_HTTP_FLAG_REQ_HEADERS) != + NGHTTP2_HTTP_FLAG_REQ_HEADERS || + (stream->http_flags & + (NGHTTP2_HTTP_FLAG_AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) { + return -1; + } + + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + /* we are going to reuse data fields for upcoming response. Clear + them now, except for method flags. */ + stream->http_flags &= NGHTTP2_HTTP_FLAG_METH_ALL; + stream->content_length = -1; + } + + return 0; +} + +int nghttp2_http_on_response_headers(nghttp2_stream *stream) { + if ((stream->http_flags & NGHTTP2_HTTP_FLAG_STATUS) == 0) { + return -1; + } + + if (stream->status_code / 100 == 1) { + /* non-final response */ + stream->http_flags = (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_ALL) | + NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE; + stream->content_length = -1; + stream->status_code = -1; + return 0; + } + + stream->http_flags &= ~NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE; + + if (!expect_response_body(stream)) { + stream->content_length = 0; + } else if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) { + stream->content_length = -1; + } + + return 0; +} + +int nghttp2_http_on_trailer_headers(nghttp2_stream *stream _U_, + nghttp2_frame *frame) { + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + return -1; + } + + return 0; +} + +int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream) { + if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) { + return -1; + } + + if (stream->content_length != -1 && + stream->content_length != stream->recv_content_length) { + return -1; + } + + return 0; +} + +int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n) { + stream->recv_content_length += n; + + if ((stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) || + (stream->content_length != -1 && + stream->recv_content_length > stream->content_length)) { + return -1; + } + + return 0; +} + +void nghttp2_http_record_request_method(nghttp2_stream *stream, + nghttp2_frame *frame) { + const nghttp2_nv *nva; + size_t nvlen; + size_t i; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + nva = frame->headers.nva; + nvlen = frame->headers.nvlen; + break; + case NGHTTP2_PUSH_PROMISE: + nva = frame->push_promise.nva; + nvlen = frame->push_promise.nvlen; + break; + default: + return; + } + + /* TODO we should do this in stricter manner. */ + for (i = 0; i < nvlen; ++i) { + const nghttp2_nv *nv = &nva[i]; + if (lookup_token(nv->name, nv->namelen) == NGHTTP2_TOKEN__METHOD) { + if (streq("CONNECT", nv->value, nv->valuelen)) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; + return; + } + if (streq("HEAD", nv->value, nv->valuelen)) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; + return; + } + } + } +} diff --git a/lib/nghttp2_http.h b/lib/nghttp2_http.h new file mode 100644 index 00000000..3f2aa3e3 --- /dev/null +++ b/lib/nghttp2_http.h @@ -0,0 +1,88 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HTTP_H +#define NGHTTP2_HTTP_H + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include +#include "nghttp2_session.h" +#include "nghttp2_stream.h" + +/* + * This function is called when HTTP header field |nv| in |frame| is + * received for |stream|. This function will validate |nv| against + * the current state of stream. This function returns 0 if it + * succeeds, or -1. + */ +int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, + nghttp2_frame *frame, nghttp2_nv *nv, int trailer); + +/* + * This function is called when request header is received. This + * function performs validation and returns 0 if it succeeds, or -1. + */ +int nghttp2_http_on_request_headers(nghttp2_stream *stream, + nghttp2_frame *frame); + +/* + * This function is called when response header is received. This + * function performs validation and returns 0 if it succeeds, or -1. + */ +int nghttp2_http_on_response_headers(nghttp2_stream *stream); + +/* + * This function is called trailer header (for both request and + * response) is received. This function performs validation and + * returns 0 if it succeeds, or -1. + */ +int nghttp2_http_on_trailer_headers(nghttp2_stream *stream, + nghttp2_frame *frame); + +/* + * This function is called when END_STREAM flag is seen in incoming + * frame. This function performs validation and returns 0 if it + * succeeds, or -1. + */ +int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream); + +/* + * This function is called when chunk of data is received. This + * function performs validation and returns 0 if it succeeds, or -1. + */ +int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n); + +/* + * This function inspects header field in |frame| and records its + * method in stream->http_flags. If frame->hd.type is neither + * NGHTTP2_HEADERS nor NGHTTP2_PUSH_PROMISE, this function does + * nothing. + */ +void nghttp2_http_record_request_method(nghttp2_stream *stream, + nghttp2_frame *frame); + +#endif /* NGHTTP2_HTTP_H */ diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index f390621d..8f0be930 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -33,6 +33,7 @@ #include "nghttp2_net.h" #include "nghttp2_priority_spec.h" #include "nghttp2_option.h" +#include "nghttp2_http.h" /* * Returns non-zero if the number of outgoing opened streams is larger @@ -76,6 +77,27 @@ static int is_non_fatal(int lib_error) { int nghttp2_is_fatal(int lib_error) { return lib_error < NGHTTP2_ERR_FATAL; } +static int session_enforce_http_semantics(nghttp2_session *session) { + return (session->opt_flags & NGHTTP2_OPTMASK_NO_HTTP_SEMANTICS) == 0; +} + +/* + * Returns nonzero if |frame| is trailer headers. + */ +static int session_trailer_headers(nghttp2_session *session, + nghttp2_stream *stream, + nghttp2_frame *frame) { + if (!stream || frame->hd.type != NGHTTP2_HEADERS) { + return 0; + } + if (session->server) { + return frame->headers.cat == NGHTTP2_HCAT_HEADERS; + } + + return frame->headers.cat == NGHTTP2_HCAT_HEADERS && + (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) == 0; +} + /* Returns nonzero if the |stream| is in reserved(remote) state */ static int state_reserved_remote(nghttp2_session *session, nghttp2_stream *stream) { @@ -1738,6 +1760,10 @@ static int session_prep_frame(nghttp2_session *session, if (rv != 0) { return rv; } + + if (session_enforce_http_semantics(session)) { + nghttp2_http_record_request_method(stream, frame); + } } else { nghttp2_stream *stream; @@ -3082,8 +3108,19 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, int inflate_flags; nghttp2_nv nv; nghttp2_stream *stream; + nghttp2_stream *subject_stream; + int trailer = 0; *readlen_ptr = 0; + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + subject_stream = nghttp2_session_get_stream( + session, frame->push_promise.promised_stream_id); + } else { + subject_stream = stream; + trailer = session_trailer_headers(session, stream, frame); + } DEBUGF(fprintf(stderr, "recv: decoding header block %zu bytes\n", inlen)); for (;;) { @@ -3095,8 +3132,6 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, } if (proclen < 0) { if (session->iframe.state == NGHTTP2_IB_READ_HEADER_BLOCK) { - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (stream && stream->state != NGHTTP2_STREAM_CLOSING) { /* Adding RST_STREAM here is very important. It prevents from invoking subsequent callbacks for the same stream @@ -3124,11 +3159,29 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, DEBUGF(fprintf(stderr, "recv: proclen=%zd\n", proclen)); if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) { - rv = session_call_on_header(session, frame, &nv); - /* This handles NGHTTP2_ERR_PAUSE and - NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */ - if (rv != 0) { - return rv; + if (subject_stream && session_enforce_http_semantics(session)) { + rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, + trailer); + if (rv != 0) { + DEBUGF(fprintf( + stderr, "recv: HTTP error: type=%d, id=%d, header %.*s: %.*s\n", + frame->hd.type, subject_stream->stream_id, (int)nv.namelen, + nv.name, (int)nv.valuelen, nv.value)); + rv = nghttp2_session_add_rst_stream( + session, subject_stream->stream_id, NGHTTP2_PROTOCOL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + } + if (call_header_cb) { + rv = session_call_on_header(session, frame, &nv); + /* This handles NGHTTP2_ERR_PAUSE and + NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */ + if (rv != 0) { + return rv; + } } } if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { @@ -3224,7 +3277,8 @@ int nghttp2_session_end_headers_received(nghttp2_session *session, } static int session_after_header_block_received(nghttp2_session *session) { - int rv; + int rv = 0; + int call_cb = 1; nghttp2_frame *frame = &session->iframe.frame; nghttp2_stream *stream; @@ -3235,9 +3289,64 @@ static int session_after_header_block_received(nghttp2_session *session) { return 0; } - rv = session_call_on_frame_received(session, frame); - if (nghttp2_is_fatal(rv)) { - return rv; + if (session_enforce_http_semantics(session)) { + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + nghttp2_stream *subject_stream; + + subject_stream = nghttp2_session_get_stream( + session, frame->push_promise.promised_stream_id); + if (subject_stream) { + rv = nghttp2_http_on_request_headers(subject_stream, frame); + } + } else { + assert(frame->hd.type == NGHTTP2_HEADERS); + switch (frame->headers.cat) { + case NGHTTP2_HCAT_REQUEST: + rv = nghttp2_http_on_request_headers(stream, frame); + break; + case NGHTTP2_HCAT_RESPONSE: + case NGHTTP2_HCAT_PUSH_RESPONSE: + rv = nghttp2_http_on_response_headers(stream); + break; + case NGHTTP2_HCAT_HEADERS: + if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) { + assert(!session->server); + rv = nghttp2_http_on_response_headers(stream); + } else { + rv = nghttp2_http_on_trailer_headers(stream, frame); + } + break; + default: + assert(0); + } + if (rv == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + rv = nghttp2_http_on_remote_end_stream(stream); + } + } + if (rv != 0) { + int32_t stream_id; + + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + stream_id = frame->push_promise.promised_stream_id; + } else { + stream_id = frame->hd.stream_id; + } + + call_cb = 0; + + rv = nghttp2_session_add_rst_stream(session, stream_id, + NGHTTP2_PROTOCOL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + } + + if (call_cb) { + rv = session_call_on_frame_received(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } } if (frame->hd.type != NGHTTP2_HEADERS) { @@ -4234,6 +4343,7 @@ static int session_process_window_update_frame(nghttp2_session *session) { int nghttp2_session_on_data_received(nghttp2_session *session, nghttp2_frame *frame) { int rv = 0; + int call_cb = 1; nghttp2_stream *stream; /* We don't call on_frame_recv_callback if stream has been closed @@ -4246,9 +4356,23 @@ int nghttp2_session_on_data_received(nghttp2_session *session, return 0; } - rv = session_call_on_frame_received(session, frame); - if (nghttp2_is_fatal(rv)) { - return rv; + if (session_enforce_http_semantics(session) && + (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + if (nghttp2_http_on_remote_end_stream(stream) != 0) { + call_cb = 0; + rv = nghttp2_session_add_rst_stream(session, stream->stream_id, + NGHTTP2_PROTOCOL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + } + + if (call_cb) { + rv = session_call_on_frame_received(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } } if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { @@ -5582,17 +5706,30 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, DEBUGF(fprintf(stderr, "recv: data_readlen=%zd\n", data_readlen)); - if (stream && data_readlen > 0 && - session->callbacks.on_data_chunk_recv_callback) { - rv = session->callbacks.on_data_chunk_recv_callback( - session, iframe->frame.hd.flags, iframe->frame.hd.stream_id, - in - readlen, data_readlen, session->user_data); - if (rv == NGHTTP2_ERR_PAUSE) { - return in - first; + if (stream && data_readlen > 0) { + if (session_enforce_http_semantics(session)) { + if (nghttp2_http_on_data_chunk(stream, data_readlen) != 0) { + rv = nghttp2_session_add_rst_stream( + session, iframe->frame.hd.stream_id, NGHTTP2_PROTOCOL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + busy = 1; + iframe->state = NGHTTP2_IB_IGN_DATA; + break; + } } + if (session->callbacks.on_data_chunk_recv_callback) { + rv = session->callbacks.on_data_chunk_recv_callback( + session, iframe->frame.hd.flags, iframe->frame.hd.stream_id, + in - readlen, data_readlen, session->user_data); + if (rv == NGHTTP2_ERR_PAUSE) { + return in - first; + } - if (nghttp2_is_fatal(rv)) { - return NGHTTP2_ERR_CALLBACK_FAILURE; + if (nghttp2_is_fatal(rv)) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } } } } diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index c9422a5d..721a7a0a 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -47,6 +47,7 @@ typedef enum { NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0, NGHTTP2_OPTMASK_RECV_CLIENT_PREFACE = 1 << 1, + NGHTTP2_OPTMASK_NO_HTTP_SEMANTICS = 1 << 2, } nghttp2_optmask; typedef enum { diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c index 505a32c6..cdb67f2f 100644 --- a/lib/nghttp2_stream.c +++ b/lib/nghttp2_stream.c @@ -68,6 +68,11 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, stream->roots = roots; stream->root_prev = NULL; stream->root_next = NULL; + + stream->http_flags = NGHTTP2_HTTP_FLAG_NONE; + stream->content_length = -1; + stream->recv_content_length = 0; + stream->status_code = -1; } void nghttp2_stream_free(nghttp2_stream *stream _U_) { diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index eede2dc3..6fd6046d 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -99,6 +99,33 @@ typedef enum { } nghttp2_stream_flag; +/* HTTP related flags to enforce HTTP semantics */ +typedef enum { + NGHTTP2_HTTP_FLAG_NONE = 0, + /* header field seen so far */ + NGHTTP2_HTTP_FLAG_AUTHORITY = 1, + NGHTTP2_HTTP_FLAG_PATH = 1 << 1, + NGHTTP2_HTTP_FLAG_METHOD = 1 << 2, + NGHTTP2_HTTP_FLAG_SCHEME = 1 << 3, + /* host is not pseudo header, but we require either host or + :authority */ + NGHTTP2_HTTP_FLAG_HOST = 1 << 4, + NGHTTP2_HTTP_FLAG_STATUS = 1 << 5, + /* required header fields for HTTP request except for CONNECT + method. */ + NGHTTP2_HTTP_FLAG_REQ_HEADERS = NGHTTP2_HTTP_FLAG_METHOD | + NGHTTP2_HTTP_FLAG_PATH | + NGHTTP2_HTTP_FLAG_SCHEME, + NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED = 1 << 6, + /* HTTP method flags */ + NGHTTP2_HTTP_FLAG_METH_CONNECT = 1 << 7, + NGHTTP2_HTTP_FLAG_METH_HEAD = 1 << 8, + NGHTTP2_HTTP_FLAG_METH_ALL = + NGHTTP2_HTTP_FLAG_METH_CONNECT | NGHTTP2_HTTP_FLAG_METH_HEAD, + /* set if final response is expected */ + NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 9, +} nghttp2_http_flag; + typedef enum { NGHTTP2_STREAM_DPRI_NONE = 0, NGHTTP2_STREAM_DPRI_NO_ITEM = 0x01, @@ -184,6 +211,14 @@ struct nghttp2_stream { uint8_t flags; /* Bitwise OR of zero or more nghttp2_shut_flag values */ uint8_t shut_flags; + /* Content-Length of request/response body. -1 if unknown. */ + int64_t content_length; + /* Received body so far */ + int64_t recv_content_length; + /* status code from remote server */ + int16_t status_code; + /* Bitwise OR of zero or more nghttp2_http_flag values */ + uint16_t http_flags; }; void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, diff --git a/src/HttpServer.cc b/src/HttpServer.cc index d3e5c55c..862de3c8 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -247,8 +247,7 @@ private: }; Stream::Stream(Http2Handler *handler, int32_t stream_id) - : handler(handler), body_left(0), upload_left(-1), stream_id(stream_id), - file(-1) { + : handler(handler), body_left(0), stream_id(stream_id), file(-1) { auto config = handler->get_config(); ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout); ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout); @@ -1021,44 +1020,8 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (!http2::check_nv(name, namelen, value, valuelen)) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - auto token = http2::lookup_token(name, namelen); - if (name[0] == ':') { - if ((!stream->headers.empty() && - stream->headers.back().name.c_str()[0] != ':') || - !http2::check_http2_request_pseudo_header(stream->hdidx, token)) { - - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - } - - if (!http2::http2_header_allowed(token)) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - - switch (token) { - case http2::HD_CONTENT_LENGTH: { - auto upload_left = util::parse_uint(value, valuelen); - if (upload_left != -1) { - stream->upload_left = upload_left; - } - break; - } - case http2::HD_TE: - if (!util::strieq("trailers", value, valuelen)) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - break; - } - http2::index_header(stream->hdidx, token, stream->headers.size()); http2::add_header(stream->headers, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX, token); @@ -1104,10 +1067,6 @@ int hd_on_frame_recv_callback(nghttp2_session *session, if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { remove_stream_read_timeout(stream); - if (stream->upload_left > 0) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } if (!hd->get_config()->early_response) { prepare_response(stream, hd); } @@ -1125,11 +1084,6 @@ int hd_on_frame_recv_callback(nghttp2_session *session, if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { - if (!http2::http2_mandatory_request_headers_presence(stream->hdidx)) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } - auto expect100 = http2::get_header(stream->hdidx, http2::HD_EXPECT, stream->headers); @@ -1144,10 +1098,6 @@ int hd_on_frame_recv_callback(nghttp2_session *session, if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { remove_stream_read_timeout(stream); - if (stream->upload_left > 0) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } if (!hd->get_config()->early_response) { prepare_response(stream, hd); } @@ -1244,15 +1194,6 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, // TODO Handle POST - if (stream->upload_left != -1) { - if (stream->upload_left < static_cast(len)) { - stream->upload_left = -1; - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } - stream->upload_left -= len; - } - add_stream_read_timeout(stream); return 0; diff --git a/src/HttpServer.h b/src/HttpServer.h index 222daeb7..54b7abaf 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -81,7 +81,6 @@ struct Stream { ev_timer rtimer; ev_timer wtimer; int64_t body_left; - int64_t upload_left; int32_t stream_id; int file; http2::HeaderIndex hdidx; diff --git a/src/nghttp.cc b/src/nghttp.cc index 986bf59c..dcc69b52 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -1505,12 +1505,6 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, user_data); } - if (req->expect_final_response) { - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, - NGHTTP2_PROTOCOL_ERROR); - return 0; - } - req->response_len += len; if (req->inflater) { @@ -1667,12 +1661,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, flags, user_data); } - if (!http2::check_nv(name, namelen, value, valuelen)) { - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - switch (frame->hd.type) { case NGHTTP2_HEADERS: { auto req = static_cast( @@ -1682,23 +1670,14 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, break; } - if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE && - frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE && - (frame->headers.cat != NGHTTP2_HCAT_HEADERS || - !req->expect_final_response)) { + /* ignore trailer header */ + if (frame->headers.cat == NGHTTP2_HCAT_HEADERS && + !req->expect_final_response) { break; } auto token = http2::lookup_token(name, namelen); - if (name[0] == ':') { - if (!req->response_pseudo_header_allowed(token)) { - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, - frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - } - http2::index_header(req->res_hdidx, token, req->res_nva.size()); http2::add_header(req->res_nva, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX, token); @@ -1714,15 +1693,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, auto token = http2::lookup_token(name, namelen); - if (name[0] == ':') { - if (!req->push_request_pseudo_header_allowed(token)) { - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, - frame->push_promise.promised_stream_id, - NGHTTP2_PROTOCOL_ERROR); - break; - } - } - http2::index_header(req->req_hdidx, token, req->req_nva.size()); http2::add_header(req->req_nva, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX, token); @@ -1752,12 +1722,6 @@ int on_frame_recv_callback2(nghttp2_session *session, ; } - if (req->expect_final_response) { - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - return 0; - } - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { req->record_response_end_time(); } @@ -1795,11 +1759,6 @@ int on_frame_recv_callback2(nghttp2_session *session, } if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - if (req->expect_final_response) { - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, - frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); - return 0; - } req->record_response_end_time(); } @@ -1826,9 +1785,7 @@ int on_frame_recv_callback2(nghttp2_session *session, authority = req->get_req_header(http2::HD_HOST); } - if (!scheme || !authority || !method || !path || scheme->value.empty() || - authority->value.empty() || method->value.empty() || - path->value.empty() || path->value[0] != '/') { + if (!scheme || !authority || !method || !path || path->value[0] != '/') { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->push_promise.promised_stream_id, NGHTTP2_PROTOCOL_ERROR); diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 0de9e785..2d08e7f6 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -664,6 +664,11 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, Downstream::MSG_BAD_HEADER) { downstream->set_response_state(Downstream::MSG_RESET); } + if (downstream->get_response_state() == Downstream::MSG_RESET && + downstream->get_response_rst_stream_error_code() == + NGHTTP2_NO_ERROR) { + downstream->set_response_rst_stream_error_code(error_code); + } call_downstream_readcb(http2session, downstream); // dconn may be deleted } @@ -687,7 +692,6 @@ int on_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) { - auto http2session = static_cast(user_data); auto sd = static_cast( nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); if (!sd || !sd->dconn) { @@ -711,40 +715,12 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, } return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } - if (!http2::check_nv(name, namelen, value, valuelen)) { - return 0; - } auto token = http2::lookup_token(name, namelen); - if (name[0] == ':') { - if (!downstream->response_pseudo_header_allowed(token)) { - http2session->submit_rst_stream(frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - } - - if (!http2::http2_header_allowed(token)) { - http2session->submit_rst_stream(frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - if (token == http2::HD_CONTENT_LENGTH) { + // libnghttp2 guarantees this can be parsed auto len = util::parse_uint(value, valuelen); - if (len == -1) { - http2session->submit_rst_stream(frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - downstream->set_response_state(Downstream::MSG_BAD_HEADER); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - if (downstream->get_response_content_length() != -1) { - http2session->submit_rst_stream(frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - downstream->set_response_state(Downstream::MSG_BAD_HEADER); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } downstream->set_response_content_length(len); } @@ -791,18 +767,8 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream, downstream->set_expect_final_response(false); auto status = downstream->get_response_header(http2::HD__STATUS); - int status_code; - - if (!http2::non_empty_value(status) || - (status_code = http2::parse_http_status_code(status->value)) == -1) { - - http2session->submit_rst_stream(frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - downstream->set_response_state(Downstream::MSG_RESET); - call_downstream_readcb(http2session, downstream); - - return 0; - } + // libnghttp2 guarantees this exists and can be parsed + auto status_code = http2::parse_http_status_code(status->value); downstream->set_response_http_status(status_code); downstream->set_response_major(2); @@ -907,12 +873,6 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, break; } - if (downstream->get_expect_final_response()) { - http2session->submit_rst_stream(frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - break; - } - auto upstream = downstream->get_upstream(); rv = upstream->on_downstream_body(downstream, nullptr, 0, true); if (rv != 0) { @@ -972,12 +932,6 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, } if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - if (downstream->get_expect_final_response()) { - http2session->submit_rst_stream(frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - return 0; - } - downstream->disable_downstream_rtimer(); if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) { diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index 14e313e3..794beaec 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -180,49 +180,12 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (!http2::check_nv(name, namelen, value, valuelen)) { - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - auto token = http2::lookup_token(name, namelen); - if (name[0] == ':') { - if (!downstream->request_pseudo_header_allowed(token)) { - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - } - - if (!http2::http2_header_allowed(token)) { - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - - switch (token) { - case http2::HD_CONTENT_LENGTH: { + if (token == http2::HD_CONTENT_LENGTH) { + // libnghttp2 guarantees this can be parsed auto len = util::parse_uint(value, valuelen); - if (len == -1) { - if (upstream->error_reply(downstream, 400) != 0) { - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - return 0; - } - if (downstream->get_request_content_length() != -1) { - if (upstream->error_reply(downstream, 400) != 0) { - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - return 0; - } downstream->set_request_content_length(len); - break; - } - case http2::HD_TE: - if (!util::strieq("trailers", value, valuelen)) { - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - break; } downstream->add_request_header(name, namelen, value, valuelen, @@ -282,34 +245,19 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream, http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva); } - auto host = downstream->get_request_header(http2::HD_HOST); auto authority = downstream->get_request_header(http2::HD__AUTHORITY); auto path = downstream->get_request_header(http2::HD__PATH); auto method = downstream->get_request_header(http2::HD__METHOD); auto scheme = downstream->get_request_header(http2::HD__SCHEME); - bool is_connect = method && "CONNECT" == method->value; - bool having_host = http2::non_empty_value(host); + bool is_connect = "CONNECT" == method->value; bool having_authority = http2::non_empty_value(authority); - if (is_connect) { - // Here we strictly require :authority header field. - if (scheme || path || !having_authority) { - + // presence of mandatory header fields are guaranteed by libnghttp2. + if (!is_connect) { + // For HTTP/2 proxy, :authority is required. + if (get_config()->http2_proxy && !having_authority) { upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - - return 0; - } - } else { - // For proxy, :authority is required. Otherwise, we can accept - // :authority or host for methods. - if (!http2::non_empty_value(method) || !http2::non_empty_value(scheme) || - (get_config()->http2_proxy && !having_authority) || - (!get_config()->http2_proxy && !having_authority && !having_host) || - !http2::non_empty_value(path)) { - - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - return 0; } } @@ -327,11 +275,6 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream, downstream->set_request_state(Downstream::HEADER_COMPLETE); if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - if (!downstream->validate_request_bodylen()) { - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } - downstream->disable_upstream_rtimer(); downstream->set_request_state(Downstream::MSG_COMPLETE); @@ -411,11 +354,6 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { downstream->disable_upstream_rtimer(); - if (!downstream->validate_request_bodylen()) { - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } - downstream->end_upload_data(); downstream->set_request_state(Downstream::MSG_COMPLETE); } @@ -435,11 +373,6 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, } if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - if (!downstream->validate_request_bodylen()) { - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } - downstream->disable_upstream_rtimer(); downstream->end_upload_data(); diff --git a/tests/main.c b/tests/main.c index ec7f7eea..9d448fa2 100644 --- a/tests/main.c +++ b/tests/main.c @@ -259,6 +259,22 @@ int main(int argc _U_, char *argv[] _U_) { test_nghttp2_session_cancel_reserved_remote) || !CU_add_test(pSuite, "session_reset_pending_headers", test_nghttp2_session_reset_pending_headers) || + !CU_add_test(pSuite, "http_mandatory_headers", + test_nghttp2_http_mandatory_headers) || + !CU_add_test(pSuite, "http_content_length", + test_nghttp2_http_content_length) || + !CU_add_test(pSuite, "http_content_length_mismatch", + test_nghttp2_http_content_length_mismatch) || + !CU_add_test(pSuite, "http_non_final_response", + test_nghttp2_http_non_final_response) || + !CU_add_test(pSuite, "http_trailer_headers", + test_nghttp2_http_trailer_headers) || + !CU_add_test(pSuite, "http_ignore_content_length", + test_nghttp2_http_ignore_content_length) || + !CU_add_test(pSuite, "http_record_request_method", + test_nghttp2_http_record_request_method) || + !CU_add_test(pSuite, "http_push_promise", + test_nghttp2_http_push_promise) || !CU_add_test(pSuite, "frame_pack_headers", test_nghttp2_frame_pack_headers) || !CU_add_test(pSuite, "frame_pack_headers_frame_too_large", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index be6016d6..f9f9eec5 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -79,6 +79,15 @@ typedef struct { int begin_frame_cb_called; } my_user_data; +static const nghttp2_nv reqnv[] = { + MAKE_NV(":method", "GET"), MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), +}; + +static const nghttp2_nv resnv[] = { + MAKE_NV(":status", "200"), +}; + static void scripted_data_feed_init2(scripted_data_feed *df, nghttp2_bufs *bufs) { nghttp2_buf_chain *ci; @@ -369,7 +378,6 @@ void test_nghttp2_session_recv(void) { nghttp2_session_callbacks callbacks; scripted_data_feed df; my_user_data user_data; - const nghttp2_nv nv[] = {MAKE_NV("url", "/")}; nghttp2_bufs bufs; ssize_t framelen; nghttp2_frame frame; @@ -395,8 +403,8 @@ void test_nghttp2_session_recv(void) { nghttp2_session_server_new(&session, &callbacks, &user_data); nghttp2_hd_deflate_init(&deflater, mem); - nvlen = ARRLEN(nv); - nghttp2_nv_array_copy(&nva, nv, nvlen, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); @@ -425,25 +433,6 @@ void test_nghttp2_session_recv(void) { nghttp2_bufs_reset(&bufs); - /* Received HEADERS without header block, which is valid */ - nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 5, - NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); - rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); - - CU_ASSERT(0 == rv); - - nghttp2_frame_headers_free(&frame.headers, mem); - - scripted_data_feed_init2(&df, &bufs); - user_data.frame_recv_cb_called = 0; - user_data.begin_frame_cb_called = 0; - - CU_ASSERT(0 == nghttp2_session_recv(session)); - CU_ASSERT(1 == user_data.frame_recv_cb_called); - CU_ASSERT(1 == user_data.begin_frame_cb_called); - - nghttp2_bufs_reset(&bufs); - /* Receive PRIORITY */ nghttp2_frame_priority_init(&frame.priority, 5, &pri_spec_default); @@ -515,6 +504,8 @@ void test_nghttp2_session_recv_invalid_stream_id(void) { nghttp2_hd_deflater deflater; int rv; nghttp2_mem *mem; + nghttp2_nv *nva; + size_t nvlen; mem = nghttp2_mem_default(); frame_pack_bufs_init(&bufs); @@ -528,8 +519,10 @@ void test_nghttp2_session_recv_invalid_stream_id(void) { nghttp2_session_server_new(&session, &callbacks, &user_data); nghttp2_hd_deflate_init(&deflater, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, - NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); @@ -551,7 +544,6 @@ void test_nghttp2_session_recv_invalid_frame(void) { nghttp2_session_callbacks callbacks; scripted_data_feed df; my_user_data user_data; - const nghttp2_nv nv[] = {MAKE_NV("url", "/")}; nghttp2_bufs bufs; nghttp2_frame frame; nghttp2_nv *nva; @@ -572,8 +564,8 @@ void test_nghttp2_session_recv_invalid_frame(void) { user_data.frame_send_cb_called = 0; nghttp2_session_server_new(&session, &callbacks, &user_data); nghttp2_hd_deflate_init(&deflater, mem); - nvlen = ARRLEN(nv); - nghttp2_nv_array_copy(&nva, nv, nvlen, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); @@ -587,13 +579,14 @@ void test_nghttp2_session_recv_invalid_frame(void) { CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == user_data.frame_send_cb_called); - /* Receive exactly same bytes of HEADERS is treated as subsequent - HEADERS (e.g., trailers */ + /* Receive exactly same bytes of HEADERS is treated as error, because it has + * pseudo headers and without END_STREAM flag set */ scripted_data_feed_init2(&df, &bufs); CU_ASSERT(0 == nghttp2_session_recv(session)); CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(0 == user_data.frame_send_cb_called); + CU_ASSERT(1 == user_data.frame_send_cb_called); + CU_ASSERT(NGHTTP2_RST_STREAM == user_data.sent_frame_type); nghttp2_bufs_free(&bufs); nghttp2_frame_headers_free(&frame.headers, mem); @@ -743,7 +736,6 @@ void test_nghttp2_session_recv_data(void) { void test_nghttp2_session_recv_continuation(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const nghttp2_nv nv1[] = {MAKE_NV("method", "GET"), MAKE_NV("path", "/")}; nghttp2_nv *nva; size_t nvlen; nghttp2_frame frame; @@ -771,8 +763,8 @@ void test_nghttp2_session_recv_continuation(void) { nghttp2_hd_deflate_init(&deflater, mem); /* Make 1 HEADERS and insert CONTINUATION header */ - nvlen = ARRLEN(nv1); - nghttp2_nv_array_copy(&nva, nv1, nvlen, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); @@ -822,7 +814,7 @@ void test_nghttp2_session_recv_continuation(void) { rv = nghttp2_session_mem_recv(session, data, datalen); CU_ASSERT((ssize_t)datalen == rv); - CU_ASSERT(2 == ud.header_cb_called); + CU_ASSERT(4 == ud.header_cb_called); CU_ASSERT(3 == ud.begin_frame_cb_called); nghttp2_hd_deflate_free(&deflater); @@ -834,8 +826,8 @@ void test_nghttp2_session_recv_continuation(void) { nghttp2_hd_deflate_init(&deflater, mem); /* HEADERS without END_HEADERS flag */ - nvlen = ARRLEN(nv1); - nghttp2_nv_array_copy(&nva, nv1, nvlen, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); nghttp2_bufs_reset(&bufs); @@ -883,7 +875,6 @@ void test_nghttp2_session_recv_continuation(void) { void test_nghttp2_session_recv_headers_with_priority(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const nghttp2_nv nv1[] = {MAKE_NV("method", "GET"), MAKE_NV("path", "/")}; nghttp2_nv *nva; size_t nvlen; nghttp2_frame frame; @@ -910,8 +901,8 @@ void test_nghttp2_session_recv_headers_with_priority(void) { open_stream(session, 1); /* With NGHTTP2_FLAG_PRIORITY without exclusive flag set */ - nvlen = ARRLEN(nv1); - nghttp2_nv_array_copy(&nva, nv1, nvlen, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_priority_spec_init(&pri_spec, 1, 99, 0); @@ -945,8 +936,8 @@ void test_nghttp2_session_recv_headers_with_priority(void) { /* With NGHTTP2_FLAG_PRIORITY, but cut last 1 byte to make it invalid. */ - nvlen = ARRLEN(nv1); - nghttp2_nv_array_copy(&nva, nv1, nvlen, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_priority_spec_init(&pri_spec, 0, 99, 0); @@ -957,12 +948,12 @@ void test_nghttp2_session_recv_headers_with_priority(void) { rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); CU_ASSERT(0 == rv); - CU_ASSERT(NGHTTP2_FRAME_HDLEN + 5 + 2 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(nghttp2_bufs_len(&bufs) > NGHTTP2_FRAME_HDLEN + 5); nghttp2_frame_headers_free(&frame.headers, mem); buf = &bufs.head->buf; - /* Make payload shorter than required length to store priroty + /* Make payload shorter than required length to store priority group */ nghttp2_put_uint32be(buf->pos, (4 << 8) + buf->pos[3]); @@ -992,8 +983,8 @@ void test_nghttp2_session_recv_headers_with_priority(void) { nghttp2_hd_deflate_init(&deflater, mem); - nvlen = ARRLEN(nv1); - nghttp2_nv_array_copy(&nva, nv1, nvlen, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_priority_spec_init(&pri_spec, 1, 0, 0); @@ -1037,7 +1028,6 @@ void test_nghttp2_session_recv_headers_with_priority(void) { void test_nghttp2_session_recv_premature_headers(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const nghttp2_nv nv1[] = {MAKE_NV("method", "GET"), MAKE_NV("path", "/")}; nghttp2_nv *nva; size_t nvlen; nghttp2_frame frame; @@ -1058,8 +1048,8 @@ void test_nghttp2_session_recv_premature_headers(void) { nghttp2_hd_deflate_init(&deflater, mem); - nvlen = ARRLEN(nv1); - nghttp2_nv_array_copy(&nva, nv1, nvlen, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); @@ -1395,6 +1385,8 @@ void test_nghttp2_session_continue(void) { callbacks.on_begin_headers_callback = on_begin_headers_callback; nghttp2_session_server_new(&session, &callbacks, &user_data); + /* disable strict HTTP layering checks */ + session->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_SEMANTICS; nghttp2_hd_deflate_init(&deflater, mem); @@ -1553,8 +1545,6 @@ void test_nghttp2_session_add_frame(void) { nghttp2_session_callbacks callbacks; accumulator acc; my_user_data user_data; - const nghttp2_nv nv[] = {MAKE_NV("method", "GET"), MAKE_NV("scheme", "https"), - MAKE_NV("url", "/"), MAKE_NV("version", "HTTP/1.1")}; nghttp2_outbound_item *item; nghttp2_frame *frame; nghttp2_nv *nva; @@ -1576,8 +1566,8 @@ void test_nghttp2_session_add_frame(void) { frame = &item->frame; - nvlen = ARRLEN(nv); - nghttp2_nv_array_copy(&nva, nv, nvlen, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); nghttp2_frame_headers_init( &frame->headers, NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, @@ -3293,7 +3283,6 @@ void test_nghttp2_submit_data_twice(void) { void test_nghttp2_submit_request_with_data(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - nghttp2_nv nva[] = {MAKE_NV(":version", "HTTP/1.1")}; nghttp2_data_provider data_prd; my_user_data ud; nghttp2_outbound_item *item; @@ -3304,10 +3293,11 @@ void test_nghttp2_submit_request_with_data(void) { data_prd.read_callback = fixed_length_data_source_read_callback; ud.data_source_length = 64 * 1024 - 1; CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); - CU_ASSERT(1 == nghttp2_submit_request(session, NULL, nva, ARRLEN(nva), + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), &data_prd, NULL)); item = nghttp2_session_get_next_ob_item(session); - CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0])); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == ud.data_source_length); @@ -3318,7 +3308,6 @@ void test_nghttp2_submit_request_without_data(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; accumulator acc; - nghttp2_nv nva[] = {MAKE_NV(":version", "HTTP/1.1")}; nghttp2_data_provider data_prd = {{-1}, NULL}; nghttp2_outbound_item *item; my_user_data ud; @@ -3339,10 +3328,11 @@ void test_nghttp2_submit_request_without_data(void) { CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); nghttp2_hd_inflate_init(&inflater, mem); - CU_ASSERT(1 == nghttp2_submit_request(session, NULL, nva, ARRLEN(nva), + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), &data_prd, NULL)); item = nghttp2_session_get_next_ob_item(session); - CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0])); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen); CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -3351,7 +3341,8 @@ void test_nghttp2_submit_request_without_data(void) { nghttp2_bufs_add(&bufs, acc.buf, acc.length); inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN); - CU_ASSERT(nvnameeq(":version", &out.nva[0])); + CU_ASSERT(ARRLEN(reqnv) == out.nvlen); + assert_nv_equal(reqnv, out.nva, out.nvlen); nghttp2_frame_headers_free(&frame.headers, mem); nva_out_reset(&out); @@ -3363,7 +3354,6 @@ void test_nghttp2_submit_request_without_data(void) { void test_nghttp2_submit_response_with_data(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - nghttp2_nv nva[] = {MAKE_NV(":version", "HTTP/1.1")}; nghttp2_data_provider data_prd; my_user_data ud; nghttp2_outbound_item *item; @@ -3376,10 +3366,11 @@ void test_nghttp2_submit_response_with_data(void) { CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_END_STREAM, &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL); - CU_ASSERT(0 == - nghttp2_submit_response(session, 1, nva, ARRLEN(nva), &data_prd)); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); item = nghttp2_session_get_next_ob_item(session); - CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0])); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == ud.data_source_length); @@ -3390,7 +3381,6 @@ void test_nghttp2_submit_response_without_data(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; accumulator acc; - nghttp2_nv nva[] = {MAKE_NV(":version", "HTTP/1.1")}; nghttp2_data_provider data_prd = {{-1}, NULL}; nghttp2_outbound_item *item; my_user_data ud; @@ -3413,10 +3403,11 @@ void test_nghttp2_submit_response_without_data(void) { nghttp2_hd_inflate_init(&inflater, mem); nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_END_STREAM, &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL); - CU_ASSERT(0 == - nghttp2_submit_response(session, 1, nva, ARRLEN(nva), &data_prd)); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); item = nghttp2_session_get_next_ob_item(session); - CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0])); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen); CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM); CU_ASSERT(0 == nghttp2_session_send(session)); @@ -3425,7 +3416,8 @@ void test_nghttp2_submit_response_without_data(void) { nghttp2_bufs_add(&bufs, acc.buf, acc.length); inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN); - CU_ASSERT(nvnameeq(":version", &out.nva[0])); + CU_ASSERT(ARRLEN(resnv) == out.nvlen); + assert_nv_equal(resnv, out.nva, out.nvlen); nva_out_reset(&out); nghttp2_bufs_free(&bufs); @@ -3437,15 +3429,15 @@ void test_nghttp2_submit_response_without_data(void) { void test_nghttp2_submit_headers_start_stream(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")}; nghttp2_outbound_item *item; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, - NULL, nv, ARRLEN(nv), NULL)); + NULL, reqnv, ARRLEN(reqnv), NULL)); item = nghttp2_session_get_next_ob_item(session); - CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0])); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen); CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM) == item->frame.hd.flags); CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY)); @@ -3456,7 +3448,6 @@ void test_nghttp2_submit_headers_start_stream(void) { void test_nghttp2_submit_headers_reply(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")}; my_user_data ud; nghttp2_outbound_item *item; nghttp2_stream *stream; @@ -3467,9 +3458,10 @@ void test_nghttp2_submit_headers_reply(void) { CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, - NULL, nv, ARRLEN(nv), NULL)); + NULL, resnv, ARRLEN(resnv), NULL)); item = nghttp2_session_get_next_ob_item(session); - CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0])); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen); CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == item->frame.hd.flags); @@ -3485,7 +3477,7 @@ void test_nghttp2_submit_headers_reply(void) { NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, - NULL, nv, ARRLEN(nv), NULL)); + NULL, resnv, ARRLEN(resnv), NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == ud.frame_send_cb_called); CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); @@ -3497,7 +3489,6 @@ void test_nghttp2_submit_headers_reply(void) { void test_nghttp2_submit_headers_push_reply(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")}; my_user_data ud; nghttp2_stream *stream; int foo; @@ -3510,8 +3501,8 @@ void test_nghttp2_submit_headers_push_reply(void) { stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default, NGHTTP2_STREAM_RESERVED, NULL); - CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, nv, - ARRLEN(nv), &foo)); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, + resnv, ARRLEN(resnv), &foo)); ud.frame_send_cb_called = 0; ud.sent_frame_type = 0; @@ -3529,8 +3520,8 @@ void test_nghttp2_submit_headers_push_reply(void) { stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default, NGHTTP2_STREAM_RESERVED, NULL); - CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, nv, - ARRLEN(nv), NULL)); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, + reqnv, ARRLEN(reqnv), NULL)); ud.frame_send_cb_called = 0; ud.sent_frame_type = 0; @@ -3543,7 +3534,6 @@ void test_nghttp2_submit_headers_push_reply(void) { void test_nghttp2_submit_headers(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")}; my_user_data ud; nghttp2_outbound_item *item; nghttp2_stream *stream; @@ -3568,9 +3558,10 @@ void test_nghttp2_submit_headers(void) { nghttp2_hd_inflate_init(&inflater, mem); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, - NULL, nv, ARRLEN(nv), NULL)); + NULL, reqnv, ARRLEN(reqnv), NULL)); item = nghttp2_session_get_next_ob_item(session); - CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0])); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen); CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == item->frame.hd.flags); @@ -3586,7 +3577,7 @@ void test_nghttp2_submit_headers(void) { NGHTTP2_STREAM_OPENING, NULL); CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, - NULL, nv, ARRLEN(nv), NULL)); + NULL, reqnv, ARRLEN(reqnv), NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == ud.frame_send_cb_called); CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); @@ -3597,7 +3588,8 @@ void test_nghttp2_submit_headers(void) { nghttp2_bufs_add(&bufs, acc.buf, acc.length); inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN); - CU_ASSERT(nvnameeq(":version", &out.nva[0])); + CU_ASSERT(ARRLEN(reqnv) == out.nvlen); + assert_nv_equal(reqnv, out.nva, out.nvlen); nva_out_reset(&out); nghttp2_bufs_free(&bufs); @@ -3835,7 +3827,6 @@ void test_nghttp2_submit_settings_update_local_window_size(void) { void test_nghttp2_submit_push_promise(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")}; my_user_data ud; nghttp2_stream *stream; @@ -3847,8 +3838,8 @@ void test_nghttp2_submit_push_promise(void) { CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL); - CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, nv, - ARRLEN(nv), &ud)); + CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, + reqnv, ARRLEN(reqnv), &ud)); ud.frame_send_cb_called = 0; ud.sent_frame_type = 0; @@ -3860,8 +3851,8 @@ void test_nghttp2_submit_push_promise(void) { CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2)); /* submit PUSH_PROMISE while associated stream is not opened */ - CU_ASSERT(4 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, nv, - ARRLEN(nv), &ud)); + CU_ASSERT(4 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, + reqnv, ARRLEN(reqnv), &ud)); ud.frame_not_send_cb_called = 0; @@ -5174,7 +5165,6 @@ void test_nghttp2_session_pack_headers_with_padding(void) { accumulator acc; my_user_data ud; nghttp2_session_callbacks callbacks; - nghttp2_nv nv = MAKE_NV(":path", "/"); memset(&callbacks, 0, sizeof(callbacks)); callbacks.send_callback = accumulator_send_callback; @@ -5190,7 +5180,8 @@ void test_nghttp2_session_pack_headers_with_padding(void) { ud.padlen = 163; - CU_ASSERT(1 == nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL)); + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), + NULL, NULL)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(acc.length < NGHTTP2_MAX_PAYLOADLEN); @@ -6517,10 +6508,10 @@ void test_nghttp2_session_on_header_temporal_failure(void) { nghttp2_hd_deflate_init(&deflater, mem); - nghttp2_nv_array_copy(&nva, nv, 1, mem); + nghttp2_nv_array_copy(&nva, reqnv, ARRLEN(reqnv), mem); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1, - NGHTTP2_HCAT_REQUEST, NULL, nva, 1); + NGHTTP2_HCAT_REQUEST, NULL, nva, ARRLEN(reqnv)); nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); nghttp2_frame_headers_free(&frame.headers, mem); @@ -6688,7 +6679,6 @@ void test_nghttp2_session_cancel_reserved_remote(void) { nghttp2_session_callbacks callbacks; nghttp2_stream *stream; nghttp2_frame frame; - const nghttp2_nv nv[] = {MAKE_NV(":status", "200")}; nghttp2_nv *nva; ssize_t nvlen; nghttp2_hd_deflater deflater; @@ -6718,8 +6708,8 @@ void test_nghttp2_session_cancel_reserved_remote(void) { CU_ASSERT(0 == nghttp2_session_send(session)); - nvlen = ARRLEN(nv); - nghttp2_nv_array_copy(&nva, nv, nvlen, mem); + nvlen = ARRLEN(resnv); + nghttp2_nv_array_copy(&nva, resnv, nvlen, mem); nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, NGHTTP2_HCAT_PUSH_RESPONSE, NULL, nva, nvlen); @@ -6822,3 +6812,814 @@ void test_nghttp2_session_reset_pending_headers(void) { nghttp2_session_del(session); } + +static void check_nghttp2_http_recv_headers_fail( + nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id, + int stream_state, const nghttp2_nv *nva, size_t nvlen) { + nghttp2_mem *mem; + ssize_t rv; + nghttp2_outbound_item *item; + nghttp2_bufs bufs; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + if (stream_state != -1) { + nghttp2_session_open_stream(session, stream_id, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, stream_state, NULL); + } + + rv = pack_headers(&bufs, deflater, stream_id, NGHTTP2_FLAG_END_HEADERS, nva, + nvlen, mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_mandatory_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + /* test case for response */ + const nghttp2_nv nostatus_resnv[] = {MAKE_NV("server", "foo")}; + const nghttp2_nv dupstatus_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV(":status", "200")}; + const nghttp2_nv badpseudo_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV(":scheme", "https")}; + const nghttp2_nv latepseudo_resnv[] = {MAKE_NV("server", "foo"), + MAKE_NV(":status", "200")}; + const nghttp2_nv badstatus_resnv[] = {MAKE_NV(":status", "2000")}; + const nghttp2_nv badcl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "-1")}; + const nghttp2_nv dupcl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "0"), + MAKE_NV("content-length", "0")}; + const nghttp2_nv badhd_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("connection", "close")}; + + /* test case for request */ + const nghttp2_nv nopath_reqnv[] = {MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost")}; + const nghttp2_nv earlyconnect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), MAKE_NV(":authority", "localhost")}; + const nghttp2_nv lateconnect_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost")}; + const nghttp2_nv duppath_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV(":path", "/")}; + const nghttp2_nv badcl_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("content-length", "-1")}; + const nghttp2_nv dupcl_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("content-length", "0"), MAKE_NV("content-length", "0")}; + const nghttp2_nv badhd_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("connection", "close")}; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* response header lacks :status */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 1, + NGHTTP2_STREAM_OPENING, nostatus_resnv, + ARRLEN(nostatus_resnv)); + + /* response header has 2 :status */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 3, + NGHTTP2_STREAM_OPENING, dupstatus_resnv, + ARRLEN(dupstatus_resnv)); + + /* response header has bad pseudo header :scheme */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 5, + NGHTTP2_STREAM_OPENING, badpseudo_resnv, + ARRLEN(badpseudo_resnv)); + + /* response header has :status after regular header field */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 7, + NGHTTP2_STREAM_OPENING, latepseudo_resnv, + ARRLEN(latepseudo_resnv)); + + /* response header has bad status code */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 9, + NGHTTP2_STREAM_OPENING, badstatus_resnv, + ARRLEN(badstatus_resnv)); + + /* response header has bad content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 11, + NGHTTP2_STREAM_OPENING, badcl_resnv, + ARRLEN(badcl_resnv)); + + /* response header has multiple content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 13, + NGHTTP2_STREAM_OPENING, dupcl_resnv, + ARRLEN(dupcl_resnv)); + + /* response header has disallowed header field */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 15, + NGHTTP2_STREAM_OPENING, badhd_resnv, + ARRLEN(badhd_resnv)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + /* check server side */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* request header has no :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 1, -1, nopath_reqnv, + ARRLEN(nopath_reqnv)); + + /* request header has CONNECT method, but followed by :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 3, -1, + earlyconnect_reqnv, + ARRLEN(earlyconnect_reqnv)); + + /* request header has CONNECT method following :path */ + check_nghttp2_http_recv_headers_fail( + session, &deflater, 5, -1, lateconnect_reqnv, ARRLEN(lateconnect_reqnv)); + + /* request header has multiple :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 7, -1, duppath_reqnv, + ARRLEN(duppath_reqnv)); + + /* request header has bad content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 9, -1, badcl_reqnv, + ARRLEN(badcl_reqnv)); + + /* request header has multiple content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 11, -1, dupcl_reqnv, + ARRLEN(dupcl_reqnv)); + + /* request header has disallowed header field */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 13, -1, badhd_reqnv, + ARRLEN(badhd_reqnv)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); +} + +void test_nghttp2_http_content_length(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_stream *stream; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("te", "trailers"), + MAKE_NV("content-length", "9000000000")}; + const nghttp2_nv cl_reqnv[] = { + MAKE_NV(":path", "/"), MAKE_NV(":method", "PUT"), + MAKE_NV(":scheme", "https"), MAKE_NV("te", "trailers"), + MAKE_NV("host", "localhost"), MAKE_NV("content-length", "9000000000")}; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_resnv, + ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(9000000000LL == stream->content_length); + CU_ASSERT(200 == stream->status_code); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_reset(&bufs); + + /* check server side */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_reqnv, + ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(9000000000LL == stream->content_length); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_content_length_mismatch(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv cl_reqnv[] = { + MAKE_NV(":path", "/"), MAKE_NV(":method", "PUT"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"), + MAKE_NV("content-length", "20")}; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* header says content-length: 20, but HEADERS has END_STREAM flag set */ + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + cl_reqnv, ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 0 byte */ + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, cl_reqnv, + ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 21 bytes */ + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, cl_reqnv, + ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 21, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 5); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 21; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_non_final_response(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv nonfinal_resnv[] = { + MAKE_NV(":status", "100"), + }; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* non-final HEADERS with END_STREAM is illegal */ + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by non-empty DATA is illegal */ + stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 10, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 10; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by empty DATA (without END_STREAM) is + ok */ + stream = nghttp2_session_open_stream(session, 5, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 5); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by empty DATA (with END_STREAM) is + illegal */ + stream = nghttp2_session_open_stream(session, 7, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_headers(&bufs, &deflater, 7, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 7); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by final HEADERS is OK */ + stream = nghttp2_session_open_stream(session, 9, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_headers(&bufs, &deflater, 9, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 9, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_trailer_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv trailer_reqnv[] = { + MAKE_NV("foo", "bar"), + }; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* good trailer header */ + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + trailer_reqnv, ARRLEN(trailer_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + /* trailer header without END_STREAM is illegal */ + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, + trailer_reqnv, ARRLEN(trailer_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* trailer header including pseudo header field is illegal */ + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_ignore_content_length(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "304"), + MAKE_NV("content-length", "20")}; + const nghttp2_nv conn_reqnv[] = {MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "CONNECT"), + MAKE_NV("content-length", "999999")}; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* If status 304, content-length must be ignored */ + nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + cl_resnv, ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* If request method is CONNECT, content-length must be ignored */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, conn_reqnv, + ARRLEN(conn_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(-1 == stream->content_length); + CU_ASSERT((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) > 0); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_record_request_method(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const nghttp2_nv conn_reqnv[] = {MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost")}; + const nghttp2_nv conn_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "9999")}; + nghttp2_stream *stream; + ssize_t rv; + nghttp2_bufs bufs; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, conn_reqnv, + ARRLEN(conn_reqnv), NULL, NULL)); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_HTTP_FLAG_METH_CONNECT == stream->http_flags); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, conn_resnv, + ARRLEN(conn_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT((NGHTTP2_HTTP_FLAG_METH_CONNECT & stream->http_flags) > 0); + CU_ASSERT(-1 == stream->content_length); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_push_promise(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_stream *stream; + const nghttp2_nv bad_reqnv[] = {MAKE_NV(":method", "GET")}; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* good PUSH_PROMISE case */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, + reqnv, ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NULL != stream); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 2, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + CU_ASSERT(200 == stream->status_code); + + nghttp2_bufs_reset(&bufs); + + /* PUSH_PROMISE lacks mandatory header */ + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 4, + bad_reqnv, ARRLEN(bad_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(4 == item->frame.hd.stream_id); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 3e64c8e7..b0a85e83 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -122,5 +122,13 @@ void test_nghttp2_session_delete_data_item(void); void test_nghttp2_session_open_idle_stream(void); void test_nghttp2_session_cancel_reserved_remote(void); void test_nghttp2_session_reset_pending_headers(void); +void test_nghttp2_http_mandatory_headers(void); +void test_nghttp2_http_content_length(void); +void test_nghttp2_http_content_length_mismatch(void); +void test_nghttp2_http_non_final_response(void); +void test_nghttp2_http_trailer_headers(void); +void test_nghttp2_http_ignore_content_length(void); +void test_nghttp2_http_record_request_method(void); +void test_nghttp2_http_push_promise(void); #endif /* NGHTTP2_SESSION_TEST_H */ diff --git a/tests/nghttp2_test_helper.c b/tests/nghttp2_test_helper.c index ba149015..01872325 100644 --- a/tests/nghttp2_test_helper.c +++ b/tests/nghttp2_test_helper.c @@ -200,6 +200,42 @@ ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out, return processed; } +int pack_headers(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, int flags, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem) { + nghttp2_nv *dnva; + nghttp2_frame frame; + int rv; + + nghttp2_nv_array_copy(&dnva, nva, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, flags, stream_id, + NGHTTP2_HCAT_HEADERS, NULL, dnva, nvlen); + rv = nghttp2_frame_pack_headers(bufs, &frame.headers, deflater); + + nghttp2_frame_headers_free(&frame.headers, mem); + + return rv; +} + +int pack_push_promise(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, int flags, int32_t promised_stream_id, + const nghttp2_nv *nva, size_t nvlen, nghttp2_mem *mem) { + nghttp2_nv *dnva; + nghttp2_frame frame; + int rv; + + nghttp2_nv_array_copy(&dnva, nva, nvlen, mem); + + nghttp2_frame_push_promise_init(&frame.push_promise, flags, stream_id, + promised_stream_id, dnva, nvlen); + rv = nghttp2_frame_pack_push_promise(bufs, &frame.push_promise, deflater); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + + return rv; +} + int frame_pack_bufs_init(nghttp2_bufs *bufs) { /* 1 for Pad Length */ return nghttp2_bufs_init2(bufs, 4096, 16, NGHTTP2_FRAME_HDLEN + 1, diff --git a/tests/nghttp2_test_helper.h b/tests/nghttp2_test_helper.h index 36d44765..1796f937 100644 --- a/tests/nghttp2_test_helper.h +++ b/tests/nghttp2_test_helper.h @@ -43,7 +43,7 @@ #define assert_nv_equal(A, B, len) \ do { \ size_t alloclen = sizeof(nghttp2_nv) * len; \ - nghttp2_nv *sa = A, *sb = B; \ + const nghttp2_nv *sa = A, *sb = B; \ nghttp2_nv *a = malloc(alloclen); \ nghttp2_nv *b = malloc(alloclen); \ ssize_t i_; \ @@ -81,6 +81,14 @@ void add_out(nva_out *out, nghttp2_nv *nv); ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out, nghttp2_bufs *bufs, size_t offset); +int pack_headers(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, int flags, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem); + +int pack_push_promise(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, int flags, int32_t promised_stream_id, + const nghttp2_nv *nva, size_t nvlen, nghttp2_mem *mem); + int frame_pack_bufs_init(nghttp2_bufs *bufs); void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size);