Merge branch 'enforce-http-semantics'
This commit is contained in:
commit
b371331297
|
@ -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()
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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_) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
16
tests/main.c
16
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",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue