Validate HTTP semantics by default
Previously we did not check HTTP semantics and it is left out for application. Although checking is relatively easy, but they are scattered and error prone. We have implemented these checks in our applications and also feel they are tedious. To make application development a bit easier, this commit adds basic HTTP semantics validation to library code. We do following checks: server: * HEADERS is either request header or trailer header. Other type of header is disallowed. client: * HEADERS is either zero or more non-final response header or final response header or trailer header. Other type of header is disallowed. For both: * Check mandatory pseudo header fields. * Make sure that content-length matches the amount of DATA we received. If validation fails, RST_STREAM of type PROTOCOL_ERROR is issued.
This commit is contained in:
parent
9845729709
commit
b157d4ebb2
|
@ -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,6 +3159,23 @@ 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)) {
|
||||||
|
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);
|
rv = session_call_on_header(session, frame, &nv);
|
||||||
/* This handles NGHTTP2_ERR_PAUSE and
|
/* This handles NGHTTP2_ERR_PAUSE and
|
||||||
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */
|
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */
|
||||||
|
@ -3131,6 +3183,7 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
|
if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
|
||||||
nghttp2_hd_inflate_end_headers(&session->hd_inflater);
|
nghttp2_hd_inflate_end_headers(&session->hd_inflater);
|
||||||
break;
|
break;
|
||||||
|
@ -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,10 +3289,65 @@ static int session_after_header_block_received(nghttp2_session *session) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
rv = session_call_on_frame_received(session, frame);
|
||||||
if (nghttp2_is_fatal(rv)) {
|
if (nghttp2_is_fatal(rv)) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (frame->hd.type != NGHTTP2_HEADERS) {
|
if (frame->hd.type != NGHTTP2_HEADERS) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -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,10 +4356,24 @@ int nghttp2_session_on_data_received(nghttp2_session *session,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
rv = session_call_on_frame_received(session, frame);
|
||||||
if (nghttp2_is_fatal(rv)) {
|
if (nghttp2_is_fatal(rv)) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||||
|
@ -5582,8 +5706,20 @@ 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)) {
|
||||||
|
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(
|
rv = session->callbacks.on_data_chunk_recv_callback(
|
||||||
session, iframe->frame.hd.flags, iframe->frame.hd.stream_id,
|
session, iframe->frame.hd.flags, iframe->frame.hd.stream_id,
|
||||||
in - readlen, data_readlen, session->user_data);
|
in - readlen, data_readlen, session->user_data);
|
||||||
|
@ -5596,6 +5732,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (iframe->payloadleft) {
|
if (iframe->payloadleft) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
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