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 {
|
if err != nil {
|
||||||
t.Fatalf("Error st.http2() = %v", err)
|
t.Fatalf("Error st.http2() = %v", err)
|
||||||
}
|
}
|
||||||
want := 400
|
if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
|
||||||
if got := res.status; got != want {
|
t.Errorf("res.errCode: %v; want %v", got, want)
|
||||||
t.Errorf("status: %v; want %v", got, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,9 +377,8 @@ func TestH2H1InvalidRequestCL(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error st.http2() = %v", err)
|
t.Fatalf("Error st.http2() = %v", err)
|
||||||
}
|
}
|
||||||
want := 400
|
if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
|
||||||
if got := res.status; got != want {
|
t.Errorf("res.errCode: %v; want %v", got, want)
|
||||||
t.Errorf("status: %v; want %v", got, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,9 +612,8 @@ func TestH2H2MultipleResponseCL(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error st.http2() = %v", err)
|
t.Fatalf("Error st.http2() = %v", err)
|
||||||
}
|
}
|
||||||
want := 502
|
if got, want := res.errCode, http2.ErrCodeInternal; got != want {
|
||||||
if got := res.status; got != want {
|
t.Errorf("res.errCode: %v; want %v", got, want)
|
||||||
t.Errorf("status: %v; want %v", got, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -635,9 +632,8 @@ func TestH2H2InvalidResponseCL(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error st.http2() = %v", err)
|
t.Fatalf("Error st.http2() = %v", err)
|
||||||
}
|
}
|
||||||
want := 502
|
if got, want := res.errCode, http2.ErrCodeInternal; got != want {
|
||||||
if got := res.status; got != want {
|
t.Errorf("res.errCode: %v; want %v", got, want)
|
||||||
t.Errorf("status: %v; want %v", got, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,8 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \
|
||||||
nghttp2_priority_spec.c \
|
nghttp2_priority_spec.c \
|
||||||
nghttp2_option.c \
|
nghttp2_option.c \
|
||||||
nghttp2_callbacks.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 \
|
HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \
|
||||||
nghttp2_frame.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_priority_spec.h \
|
||||||
nghttp2_option.h \
|
nghttp2_option.h \
|
||||||
nghttp2_callbacks.h \
|
nghttp2_callbacks.h \
|
||||||
nghttp2_mem.h
|
nghttp2_mem.h \
|
||||||
|
nghttp2_http.h
|
||||||
|
|
||||||
libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS)
|
libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS)
|
||||||
libnghttp2_la_LDFLAGS = -no-undefined \
|
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_net.h"
|
||||||
#include "nghttp2_priority_spec.h"
|
#include "nghttp2_priority_spec.h"
|
||||||
#include "nghttp2_option.h"
|
#include "nghttp2_option.h"
|
||||||
|
#include "nghttp2_http.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns non-zero if the number of outgoing opened streams is larger
|
* 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; }
|
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 */
|
/* Returns nonzero if the |stream| is in reserved(remote) state */
|
||||||
static int state_reserved_remote(nghttp2_session *session,
|
static int state_reserved_remote(nghttp2_session *session,
|
||||||
nghttp2_stream *stream) {
|
nghttp2_stream *stream) {
|
||||||
|
@ -1738,6 +1760,10 @@ static int session_prep_frame(nghttp2_session *session,
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (session_enforce_http_semantics(session)) {
|
||||||
|
nghttp2_http_record_request_method(stream, frame);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
nghttp2_stream *stream;
|
nghttp2_stream *stream;
|
||||||
|
|
||||||
|
@ -3082,8 +3108,19 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
|
||||||
int inflate_flags;
|
int inflate_flags;
|
||||||
nghttp2_nv nv;
|
nghttp2_nv nv;
|
||||||
nghttp2_stream *stream;
|
nghttp2_stream *stream;
|
||||||
|
nghttp2_stream *subject_stream;
|
||||||
|
int trailer = 0;
|
||||||
|
|
||||||
*readlen_ptr = 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));
|
DEBUGF(fprintf(stderr, "recv: decoding header block %zu bytes\n", inlen));
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
@ -3095,8 +3132,6 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
|
||||||
}
|
}
|
||||||
if (proclen < 0) {
|
if (proclen < 0) {
|
||||||
if (session->iframe.state == NGHTTP2_IB_READ_HEADER_BLOCK) {
|
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) {
|
if (stream && stream->state != NGHTTP2_STREAM_CLOSING) {
|
||||||
/* Adding RST_STREAM here is very important. It prevents
|
/* Adding RST_STREAM here is very important. It prevents
|
||||||
from invoking subsequent callbacks for the same stream
|
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));
|
DEBUGF(fprintf(stderr, "recv: proclen=%zd\n", proclen));
|
||||||
|
|
||||||
if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) {
|
if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) {
|
||||||
rv = session_call_on_header(session, frame, &nv);
|
if (subject_stream && session_enforce_http_semantics(session)) {
|
||||||
/* This handles NGHTTP2_ERR_PAUSE and
|
rv = nghttp2_http_on_header(session, subject_stream, frame, &nv,
|
||||||
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */
|
trailer);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return rv;
|
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) {
|
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) {
|
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_frame *frame = &session->iframe.frame;
|
||||||
nghttp2_stream *stream;
|
nghttp2_stream *stream;
|
||||||
|
|
||||||
|
@ -3235,9 +3289,64 @@ static int session_after_header_block_received(nghttp2_session *session) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = session_call_on_frame_received(session, frame);
|
if (session_enforce_http_semantics(session)) {
|
||||||
if (nghttp2_is_fatal(rv)) {
|
if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
|
||||||
return rv;
|
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) {
|
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,
|
int nghttp2_session_on_data_received(nghttp2_session *session,
|
||||||
nghttp2_frame *frame) {
|
nghttp2_frame *frame) {
|
||||||
int rv = 0;
|
int rv = 0;
|
||||||
|
int call_cb = 1;
|
||||||
nghttp2_stream *stream;
|
nghttp2_stream *stream;
|
||||||
|
|
||||||
/* We don't call on_frame_recv_callback if stream has been closed
|
/* 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = session_call_on_frame_received(session, frame);
|
if (session_enforce_http_semantics(session) &&
|
||||||
if (nghttp2_is_fatal(rv)) {
|
(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
|
||||||
return rv;
|
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) {
|
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));
|
DEBUGF(fprintf(stderr, "recv: data_readlen=%zd\n", data_readlen));
|
||||||
|
|
||||||
if (stream && data_readlen > 0 &&
|
if (stream && data_readlen > 0) {
|
||||||
session->callbacks.on_data_chunk_recv_callback) {
|
if (session_enforce_http_semantics(session)) {
|
||||||
rv = session->callbacks.on_data_chunk_recv_callback(
|
if (nghttp2_http_on_data_chunk(stream, data_readlen) != 0) {
|
||||||
session, iframe->frame.hd.flags, iframe->frame.hd.stream_id,
|
rv = nghttp2_session_add_rst_stream(
|
||||||
in - readlen, data_readlen, session->user_data);
|
session, iframe->frame.hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
|
||||||
if (rv == NGHTTP2_ERR_PAUSE) {
|
if (nghttp2_is_fatal(rv)) {
|
||||||
return in - first;
|
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)) {
|
if (nghttp2_is_fatal(rv)) {
|
||||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
typedef enum {
|
typedef enum {
|
||||||
NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0,
|
NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0,
|
||||||
NGHTTP2_OPTMASK_RECV_CLIENT_PREFACE = 1 << 1,
|
NGHTTP2_OPTMASK_RECV_CLIENT_PREFACE = 1 << 1,
|
||||||
|
NGHTTP2_OPTMASK_NO_HTTP_SEMANTICS = 1 << 2,
|
||||||
} nghttp2_optmask;
|
} nghttp2_optmask;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|
|
@ -68,6 +68,11 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
|
||||||
stream->roots = roots;
|
stream->roots = roots;
|
||||||
stream->root_prev = NULL;
|
stream->root_prev = NULL;
|
||||||
stream->root_next = 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_) {
|
void nghttp2_stream_free(nghttp2_stream *stream _U_) {
|
||||||
|
|
|
@ -99,6 +99,33 @@ typedef enum {
|
||||||
|
|
||||||
} nghttp2_stream_flag;
|
} 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 {
|
typedef enum {
|
||||||
NGHTTP2_STREAM_DPRI_NONE = 0,
|
NGHTTP2_STREAM_DPRI_NONE = 0,
|
||||||
NGHTTP2_STREAM_DPRI_NO_ITEM = 0x01,
|
NGHTTP2_STREAM_DPRI_NO_ITEM = 0x01,
|
||||||
|
@ -184,6 +211,14 @@ struct nghttp2_stream {
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
/* Bitwise OR of zero or more nghttp2_shut_flag values */
|
/* Bitwise OR of zero or more nghttp2_shut_flag values */
|
||||||
uint8_t shut_flags;
|
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,
|
void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
|
||||||
|
|
|
@ -247,8 +247,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
Stream::Stream(Http2Handler *handler, int32_t stream_id)
|
Stream::Stream(Http2Handler *handler, int32_t stream_id)
|
||||||
: handler(handler), body_left(0), upload_left(-1), stream_id(stream_id),
|
: handler(handler), body_left(0), stream_id(stream_id), file(-1) {
|
||||||
file(-1) {
|
|
||||||
auto config = handler->get_config();
|
auto config = handler->get_config();
|
||||||
ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout);
|
ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout);
|
||||||
ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_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;
|
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);
|
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::index_header(stream->hdidx, token, stream->headers.size());
|
||||||
http2::add_header(stream->headers, name, namelen, value, valuelen,
|
http2::add_header(stream->headers, name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
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) {
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||||
remove_stream_read_timeout(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) {
|
if (!hd->get_config()->early_response) {
|
||||||
prepare_response(stream, hd);
|
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 (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 =
|
auto expect100 =
|
||||||
http2::get_header(stream->hdidx, http2::HD_EXPECT, stream->headers);
|
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) {
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||||
remove_stream_read_timeout(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) {
|
if (!hd->get_config()->early_response) {
|
||||||
prepare_response(stream, hd);
|
prepare_response(stream, hd);
|
||||||
}
|
}
|
||||||
|
@ -1244,15 +1194,6 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
|
||||||
|
|
||||||
// TODO Handle POST
|
// 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);
|
add_stream_read_timeout(stream);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -81,7 +81,6 @@ struct Stream {
|
||||||
ev_timer rtimer;
|
ev_timer rtimer;
|
||||||
ev_timer wtimer;
|
ev_timer wtimer;
|
||||||
int64_t body_left;
|
int64_t body_left;
|
||||||
int64_t upload_left;
|
|
||||||
int32_t stream_id;
|
int32_t stream_id;
|
||||||
int file;
|
int file;
|
||||||
http2::HeaderIndex hdidx;
|
http2::HeaderIndex hdidx;
|
||||||
|
|
|
@ -1505,12 +1505,6 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
|
||||||
user_data);
|
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;
|
req->response_len += len;
|
||||||
|
|
||||||
if (req->inflater) {
|
if (req->inflater) {
|
||||||
|
@ -1667,12 +1661,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
flags, user_data);
|
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) {
|
switch (frame->hd.type) {
|
||||||
case NGHTTP2_HEADERS: {
|
case NGHTTP2_HEADERS: {
|
||||||
auto req = static_cast<Request *>(
|
auto req = static_cast<Request *>(
|
||||||
|
@ -1682,23 +1670,14 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
|
/* ignore trailer header */
|
||||||
frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE &&
|
if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
|
||||||
(frame->headers.cat != NGHTTP2_HCAT_HEADERS ||
|
!req->expect_final_response) {
|
||||||
!req->expect_final_response)) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto token = http2::lookup_token(name, namelen);
|
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::index_header(req->res_hdidx, token, req->res_nva.size());
|
||||||
http2::add_header(req->res_nva, name, namelen, value, valuelen,
|
http2::add_header(req->res_nva, name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
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);
|
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::index_header(req->req_hdidx, token, req->req_nva.size());
|
||||||
http2::add_header(req->req_nva, name, namelen, value, valuelen,
|
http2::add_header(req->req_nva, name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
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) {
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||||
req->record_response_end_time();
|
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 (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();
|
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);
|
authority = req->get_req_header(http2::HD_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scheme || !authority || !method || !path || scheme->value.empty() ||
|
if (!scheme || !authority || !method || !path || path->value[0] != '/') {
|
||||||
authority->value.empty() || method->value.empty() ||
|
|
||||||
path->value.empty() || path->value[0] != '/') {
|
|
||||||
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
frame->push_promise.promised_stream_id,
|
frame->push_promise.promised_stream_id,
|
||||||
NGHTTP2_PROTOCOL_ERROR);
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
|
|
@ -664,6 +664,11 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
Downstream::MSG_BAD_HEADER) {
|
Downstream::MSG_BAD_HEADER) {
|
||||||
downstream->set_response_state(Downstream::MSG_RESET);
|
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);
|
call_downstream_readcb(http2session, downstream);
|
||||||
// dconn may be deleted
|
// 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 *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen, uint8_t flags,
|
const uint8_t *value, size_t valuelen, uint8_t flags,
|
||||||
void *user_data) {
|
void *user_data) {
|
||||||
auto http2session = static_cast<Http2Session *>(user_data);
|
|
||||||
auto sd = static_cast<StreamData *>(
|
auto sd = static_cast<StreamData *>(
|
||||||
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
||||||
if (!sd || !sd->dconn) {
|
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;
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
}
|
}
|
||||||
if (!http2::check_nv(name, namelen, value, valuelen)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto token = http2::lookup_token(name, namelen);
|
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) {
|
if (token == http2::HD_CONTENT_LENGTH) {
|
||||||
|
// libnghttp2 guarantees this can be parsed
|
||||||
auto len = util::parse_uint(value, valuelen);
|
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);
|
downstream->set_response_content_length(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -791,18 +767,8 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
||||||
downstream->set_expect_final_response(false);
|
downstream->set_expect_final_response(false);
|
||||||
|
|
||||||
auto status = downstream->get_response_header(http2::HD__STATUS);
|
auto status = downstream->get_response_header(http2::HD__STATUS);
|
||||||
int status_code;
|
// libnghttp2 guarantees this exists and can be parsed
|
||||||
|
auto status_code = http2::parse_http_status_code(status->value);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
downstream->set_response_http_status(status_code);
|
downstream->set_response_http_status(status_code);
|
||||||
downstream->set_response_major(2);
|
downstream->set_response_major(2);
|
||||||
|
@ -907,12 +873,6 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downstream->get_expect_final_response()) {
|
|
||||||
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
||||||
NGHTTP2_PROTOCOL_ERROR);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto upstream = downstream->get_upstream();
|
auto upstream = downstream->get_upstream();
|
||||||
rv = upstream->on_downstream_body(downstream, nullptr, 0, true);
|
rv = upstream->on_downstream_body(downstream, nullptr, 0, true);
|
||||||
if (rv != 0) {
|
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 (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();
|
downstream->disable_downstream_rtimer();
|
||||||
|
|
||||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
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;
|
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);
|
auto token = http2::lookup_token(name, namelen);
|
||||||
|
|
||||||
if (name[0] == ':') {
|
if (token == http2::HD_CONTENT_LENGTH) {
|
||||||
if (!downstream->request_pseudo_header_allowed(token)) {
|
// libnghttp2 guarantees this can be parsed
|
||||||
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: {
|
|
||||||
auto len = util::parse_uint(value, valuelen);
|
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);
|
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,
|
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);
|
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 authority = downstream->get_request_header(http2::HD__AUTHORITY);
|
||||||
auto path = downstream->get_request_header(http2::HD__PATH);
|
auto path = downstream->get_request_header(http2::HD__PATH);
|
||||||
auto method = downstream->get_request_header(http2::HD__METHOD);
|
auto method = downstream->get_request_header(http2::HD__METHOD);
|
||||||
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
|
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
|
||||||
|
|
||||||
bool is_connect = method && "CONNECT" == method->value;
|
bool is_connect = "CONNECT" == method->value;
|
||||||
bool having_host = http2::non_empty_value(host);
|
|
||||||
bool having_authority = http2::non_empty_value(authority);
|
bool having_authority = http2::non_empty_value(authority);
|
||||||
|
|
||||||
if (is_connect) {
|
// presence of mandatory header fields are guaranteed by libnghttp2.
|
||||||
// Here we strictly require :authority header field.
|
if (!is_connect) {
|
||||||
if (scheme || path || !having_authority) {
|
// For HTTP/2 proxy, :authority is required.
|
||||||
|
if (get_config()->http2_proxy && !having_authority) {
|
||||||
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,11 +275,6 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream,
|
||||||
|
|
||||||
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
||||||
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
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->disable_upstream_rtimer();
|
||||||
|
|
||||||
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
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) {
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||||
downstream->disable_upstream_rtimer();
|
downstream->disable_upstream_rtimer();
|
||||||
|
|
||||||
if (!downstream->validate_request_bodylen()) {
|
|
||||||
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
downstream->end_upload_data();
|
downstream->end_upload_data();
|
||||||
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
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 (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->disable_upstream_rtimer();
|
||||||
|
|
||||||
downstream->end_upload_data();
|
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) ||
|
test_nghttp2_session_cancel_reserved_remote) ||
|
||||||
!CU_add_test(pSuite, "session_reset_pending_headers",
|
!CU_add_test(pSuite, "session_reset_pending_headers",
|
||||||
test_nghttp2_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",
|
!CU_add_test(pSuite, "frame_pack_headers",
|
||||||
test_nghttp2_frame_pack_headers) ||
|
test_nghttp2_frame_pack_headers) ||
|
||||||
!CU_add_test(pSuite, "frame_pack_headers_frame_too_large",
|
!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_open_idle_stream(void);
|
||||||
void test_nghttp2_session_cancel_reserved_remote(void);
|
void test_nghttp2_session_cancel_reserved_remote(void);
|
||||||
void test_nghttp2_session_reset_pending_headers(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 */
|
#endif /* NGHTTP2_SESSION_TEST_H */
|
||||||
|
|
|
@ -200,6 +200,42 @@ ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out,
|
||||||
return processed;
|
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) {
|
int frame_pack_bufs_init(nghttp2_bufs *bufs) {
|
||||||
/* 1 for Pad Length */
|
/* 1 for Pad Length */
|
||||||
return nghttp2_bufs_init2(bufs, 4096, 16, NGHTTP2_FRAME_HDLEN + 1,
|
return nghttp2_bufs_init2(bufs, 4096, 16, NGHTTP2_FRAME_HDLEN + 1,
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
#define assert_nv_equal(A, B, len) \
|
#define assert_nv_equal(A, B, len) \
|
||||||
do { \
|
do { \
|
||||||
size_t alloclen = sizeof(nghttp2_nv) * len; \
|
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 *a = malloc(alloclen); \
|
||||||
nghttp2_nv *b = malloc(alloclen); \
|
nghttp2_nv *b = malloc(alloclen); \
|
||||||
ssize_t i_; \
|
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,
|
ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out,
|
||||||
nghttp2_bufs *bufs, size_t offset);
|
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);
|
int frame_pack_bufs_init(nghttp2_bufs *bufs);
|
||||||
|
|
||||||
void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size);
|
void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size);
|
||||||
|
|
Loading…
Reference in New Issue