Merge branch 'enforce-http-semantics'

This commit is contained in:
Tatsuhiro Tsujikawa 2015-02-20 01:02:06 +09:00
commit b371331297
19 changed files with 1883 additions and 370 deletions

85
genlibtokenlookup.py Executable file
View File

@ -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()

View File

@ -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)
}
}

View File

@ -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 \

511
lib/nghttp2_http.c Normal file
View File

@ -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 <string.h>
#include <assert.h>
#include <stdio.h>
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;
}
}
}
}

88
lib/nghttp2_http.h Normal file
View File

@ -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 <config.h>
#endif /* HAVE_CONFIG_H */
#include <nghttp2/nghttp2.h>
#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 */

View File

@ -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;
}
}
}
}

View File

@ -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 {

View File

@ -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_) {

View File

@ -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,

View File

@ -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<int64_t>(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;

View File

@ -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;

View File

@ -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<Request *>(
@ -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);

View File

@ -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<Http2Session *>(user_data);
auto sd = static_cast<StreamData *>(
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) {

View File

@ -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();

View File

@ -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",

File diff suppressed because it is too large Load Diff

View File

@ -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 */

View File

@ -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,

View File

@ -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);