Merge branch 'nghttpx-block-allocator'

This commit is contained in:
Tatsuhiro Tsujikawa 2016-03-12 21:21:34 +09:00
commit 0720671e0a
55 changed files with 2093 additions and 1878 deletions

View File

@ -65,6 +65,9 @@ APIDOCS= \
nghttp2_priority_spec_check_default.rst \ nghttp2_priority_spec_check_default.rst \
nghttp2_priority_spec_default_init.rst \ nghttp2_priority_spec_default_init.rst \
nghttp2_priority_spec_init.rst \ nghttp2_priority_spec_init.rst \
nghttp2_rcbuf_decref.rst \
nghttp2_rcbuf_get_buf.rst \
nghttp2_rcbuf_incref.rst \
nghttp2_select_next_protocol.rst \ nghttp2_select_next_protocol.rst \
nghttp2_session_callbacks_del.rst \ nghttp2_session_callbacks_del.rst \
nghttp2_session_callbacks_new.rst \ nghttp2_session_callbacks_new.rst \
@ -78,6 +81,7 @@ APIDOCS= \
nghttp2_session_callbacks_set_on_frame_recv_callback.rst \ nghttp2_session_callbacks_set_on_frame_recv_callback.rst \
nghttp2_session_callbacks_set_on_frame_send_callback.rst \ nghttp2_session_callbacks_set_on_frame_send_callback.rst \
nghttp2_session_callbacks_set_on_header_callback.rst \ nghttp2_session_callbacks_set_on_header_callback.rst \
nghttp2_session_callbacks_set_on_header_callback2.rst \
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst \ nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst \
nghttp2_session_callbacks_set_on_stream_close_callback.rst \ nghttp2_session_callbacks_set_on_stream_close_callback.rst \
nghttp2_session_callbacks_set_pack_extension_callback.rst \ nghttp2_session_callbacks_set_pack_extension_callback.rst \

View File

@ -62,67 +62,11 @@ HEADERS = [
('vary', 58), ('vary', 58),
('via', 59), ('via', 59),
('www-authenticate', 60), ('www-authenticate', 60),
('accept-ch', None),
('accept-datetime', None),
('accept-features', None),
('accept-patch', None),
('access-control-allow-credentials', None),
('access-control-allow-headers', None),
('access-control-allow-methods', None),
('access-control-expose-headers', None),
('access-control-max-age', None),
('access-control-request-headers', None),
('access-control-request-method', None),
('alt-svc', None),
('alternates', None),
('connection', None),
('content-md5', None),
('content-security-policy', None),
('content-security-policy-report-only', None),
('dnt', None),
('forwarded', None),
('front-end-https', None),
('keep-alive', None),
('last-event-id', None),
('negotiate', None),
('origin', None),
('p3p', None),
('pragma', None),
('proxy-connection', None),
('public-key-pins', None),
('sec-websocket-extensions', None),
('sec-websocket-key', None),
('sec-websocket-origin', None),
('sec-websocket-protocol', None),
('sec-websocket-version', None),
('set-cookie2', None),
('status', None),
('tcn', None),
('te', None), ('te', None),
('trailer', None), ('connection', None),
('tsv', None), ('keep-alive',None),
('proxy-connection', None),
('upgrade', None), ('upgrade', None),
('upgrade-insecure-requests', None),
('variant-vary', None),
('warning', None),
('x-api-version', None),
('x-att-deviceid', None),
('x-cache', None),
('x-cache-lookup', None),
('x-content-duration', None),
('x-content-security-policy', None),
('x-content-type-options', None),
('x-dnsprefetch-control', None),
('x-forwarded-for', None),
('x-forwarded-host', None),
('x-forwarded-proto', None),
('x-frame-options', None),
('x-powered-by', None),
('x-requested-with', None),
('x-ua-compatible', None),
('x-wap-profile', None),
('x-webkit-csp', None),
('x-xss-protection', None),
] ]
def to_enum_hd(k): def to_enum_hd(k):
@ -162,7 +106,7 @@ def gen_enum():
def gen_index_header(): def gen_index_header():
print '''\ print '''\
static inline int lookup_token(const uint8_t *name, size_t namelen) { static inline int32_t lookup_token(const uint8_t *name, size_t namelen) {
switch (namelen) {''' switch (namelen) {'''
b = build_header(HEADERS) b = build_header(HEADERS)
for size in sorted(b.keys()): for size in sorted(b.keys()):

View File

@ -47,7 +47,8 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \
nghttp2_option.c \ nghttp2_option.c \
nghttp2_callbacks.c \ nghttp2_callbacks.c \
nghttp2_mem.c \ nghttp2_mem.c \
nghttp2_http.c nghttp2_http.c \
nghttp2_rcbuf.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 \
@ -61,7 +62,8 @@ HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \
nghttp2_option.h \ nghttp2_option.h \
nghttp2_callbacks.h \ nghttp2_callbacks.h \
nghttp2_mem.h \ nghttp2_mem.h \
nghttp2_http.h nghttp2_http.h \
nghttp2_rcbuf.h
libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS) libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS)
libnghttp2_la_LDFLAGS = -no-undefined \ libnghttp2_la_LDFLAGS = -no-undefined \

View File

@ -419,6 +419,55 @@ typedef enum {
NGHTTP2_ERR_FLOODED = -904 NGHTTP2_ERR_FLOODED = -904
} nghttp2_error; } nghttp2_error;
/**
* @struct
*
* The object representing single contagious buffer.
*/
typedef struct {
/**
* The pointer to the buffer.
*/
uint8_t *base;
/**
* The length of the buffer.
*/
size_t len;
} nghttp2_vec;
struct nghttp2_rcbuf;
/**
* @struct
*
* The object representing reference counted buffer. The details of
* this structure are intentionally hidden from the public API.
*/
typedef struct nghttp2_rcbuf nghttp2_rcbuf;
/**
* @function
*
* Increments the reference count of |rcbuf| by 1.
*/
NGHTTP2_EXTERN void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf);
/**
* @function
*
* Decrements the reference count of |rcbuf| by 1. If the reference
* count becomes zero, the object pointed by |rcbuf| will be freed.
* In this case, application must not use |rcbuf| again.
*/
NGHTTP2_EXTERN void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf);
/**
* @function
*
* Returns the underlying buffer managed by |rcbuf|.
*/
NGHTTP2_EXTERN nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf);
/** /**
* @enum * @enum
* *
@ -1627,6 +1676,32 @@ typedef int (*nghttp2_on_header_callback)(nghttp2_session *session,
const uint8_t *value, size_t valuelen, const uint8_t *value, size_t valuelen,
uint8_t flags, void *user_data); uint8_t flags, void *user_data);
/**
* @functypedef
*
* Callback function invoked when a header name/value pair is received
* for the |frame|. The |name| is header name. The |value| is header
* value. The |flags| is bitwise OR of one or more of
* :type:`nghttp2_nv_flag`.
*
* This callback behaves like :type:`nghttp2_on_header_callback`,
* except that |name| and |value| are stored in reference counted
* buffer. If application wishes to keep these references without
* copying them, use `nghttp2_rcbuf_incref()` to increment their
* reference count. It is the application's responsibility to call
* `nghttp2_rcbuf_decref()` if they called `nghttp2_rcbuf_incref()` so
* as not to leak memory. If the |session| is created by
* `nghttp2_session_server_new3()` or `nghttp2_session_client_new3()`,
* the function to free memory is the one belongs to the mem
* parameter. As long as this free function alives, |name| and
* |value| can live after |session| was destroyed.
*/
typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session,
const nghttp2_frame *frame,
nghttp2_rcbuf *name,
nghttp2_rcbuf *value, uint8_t flags,
void *user_data);
/** /**
* @functypedef * @functypedef
* *
@ -1941,12 +2016,25 @@ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_headers_callback(
* @function * @function
* *
* Sets callback function invoked when a header name/value pair is * Sets callback function invoked when a header name/value pair is
* received. * received. If both
* `nghttp2_session_callbacks_set_on_header_callback()` and
* `nghttp2_session_callbacks_set_on_header_callback2()` are used to
* set callbacks, the latter has the precedence.
*/ */
NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback( NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback(
nghttp2_session_callbacks *cbs, nghttp2_session_callbacks *cbs,
nghttp2_on_header_callback on_header_callback); nghttp2_on_header_callback on_header_callback);
/**
* @function
*
* Sets callback function invoked when a header name/value pair is
* received.
*/
NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback2(
nghttp2_session_callbacks *cbs,
nghttp2_on_header_callback2 on_header_callback2);
/** /**
* @function * @function
* *

View File

@ -73,7 +73,7 @@ typedef struct {
/* /*
* Initializes the |buf|. No memory is allocated in this function. Use * Initializes the |buf|. No memory is allocated in this function. Use
* nghttp2_buf_reserve() or nghttp2_buf_reserve2() to allocate memory. * nghttp2_buf_reserve() to allocate memory.
*/ */
void nghttp2_buf_init(nghttp2_buf *buf); void nghttp2_buf_init(nghttp2_buf *buf);

View File

@ -104,6 +104,12 @@ void nghttp2_session_callbacks_set_on_header_callback(
cbs->on_header_callback = on_header_callback; cbs->on_header_callback = on_header_callback;
} }
void nghttp2_session_callbacks_set_on_header_callback2(
nghttp2_session_callbacks *cbs,
nghttp2_on_header_callback2 on_header_callback2) {
cbs->on_header_callback2 = on_header_callback2;
}
void nghttp2_session_callbacks_set_select_padding_callback( void nghttp2_session_callbacks_set_select_padding_callback(
nghttp2_session_callbacks *cbs, nghttp2_session_callbacks *cbs,
nghttp2_select_padding_callback select_padding_callback) { nghttp2_select_padding_callback select_padding_callback) {

View File

@ -91,6 +91,7 @@ struct nghttp2_session_callbacks {
* received. * received.
*/ */
nghttp2_on_header_callback on_header_callback; nghttp2_on_header_callback on_header_callback;
nghttp2_on_header_callback2 on_header_callback2;
/** /**
* Callback function invoked when the library asks application how * Callback function invoked when the library asks application how
* many padding bytes are required for the transmission of the given * many padding bytes are required for the transmission of the given

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@
#include "nghttp2_hd_huffman.h" #include "nghttp2_hd_huffman.h"
#include "nghttp2_buf.h" #include "nghttp2_buf.h"
#include "nghttp2_mem.h" #include "nghttp2_mem.h"
#include "nghttp2_rcbuf.h"
#define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE NGHTTP2_DEFAULT_HEADER_TABLE_SIZE #define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE NGHTTP2_DEFAULT_HEADER_TABLE_SIZE
#define NGHTTP2_HD_ENTRY_OVERHEAD 32 #define NGHTTP2_HD_ENTRY_OVERHEAD 32
@ -105,88 +106,36 @@ typedef enum {
NGHTTP2_TOKEN_VARY = 58, NGHTTP2_TOKEN_VARY = 58,
NGHTTP2_TOKEN_VIA = 59, NGHTTP2_TOKEN_VIA = 59,
NGHTTP2_TOKEN_WWW_AUTHENTICATE = 60, NGHTTP2_TOKEN_WWW_AUTHENTICATE = 60,
NGHTTP2_TOKEN_ACCEPT_CH,
NGHTTP2_TOKEN_ACCEPT_DATETIME,
NGHTTP2_TOKEN_ACCEPT_FEATURES,
NGHTTP2_TOKEN_ACCEPT_PATCH,
NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_CREDENTIALS,
NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS,
NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_METHODS,
NGHTTP2_TOKEN_ACCESS_CONTROL_EXPOSE_HEADERS,
NGHTTP2_TOKEN_ACCESS_CONTROL_MAX_AGE,
NGHTTP2_TOKEN_ACCESS_CONTROL_REQUEST_HEADERS,
NGHTTP2_TOKEN_ACCESS_CONTROL_REQUEST_METHOD,
NGHTTP2_TOKEN_ALT_SVC,
NGHTTP2_TOKEN_ALTERNATES,
NGHTTP2_TOKEN_CONNECTION,
NGHTTP2_TOKEN_CONTENT_MD5,
NGHTTP2_TOKEN_CONTENT_SECURITY_POLICY,
NGHTTP2_TOKEN_CONTENT_SECURITY_POLICY_REPORT_ONLY,
NGHTTP2_TOKEN_DNT,
NGHTTP2_TOKEN_FORWARDED,
NGHTTP2_TOKEN_FRONT_END_HTTPS,
NGHTTP2_TOKEN_KEEP_ALIVE,
NGHTTP2_TOKEN_LAST_EVENT_ID,
NGHTTP2_TOKEN_NEGOTIATE,
NGHTTP2_TOKEN_ORIGIN,
NGHTTP2_TOKEN_P3P,
NGHTTP2_TOKEN_PRAGMA,
NGHTTP2_TOKEN_PROXY_CONNECTION,
NGHTTP2_TOKEN_PUBLIC_KEY_PINS,
NGHTTP2_TOKEN_SEC_WEBSOCKET_EXTENSIONS,
NGHTTP2_TOKEN_SEC_WEBSOCKET_KEY,
NGHTTP2_TOKEN_SEC_WEBSOCKET_ORIGIN,
NGHTTP2_TOKEN_SEC_WEBSOCKET_PROTOCOL,
NGHTTP2_TOKEN_SEC_WEBSOCKET_VERSION,
NGHTTP2_TOKEN_SET_COOKIE2,
NGHTTP2_TOKEN_STATUS,
NGHTTP2_TOKEN_TCN,
NGHTTP2_TOKEN_TE, NGHTTP2_TOKEN_TE,
NGHTTP2_TOKEN_TRAILER, NGHTTP2_TOKEN_CONNECTION,
NGHTTP2_TOKEN_TSV, NGHTTP2_TOKEN_KEEP_ALIVE,
NGHTTP2_TOKEN_PROXY_CONNECTION,
NGHTTP2_TOKEN_UPGRADE, NGHTTP2_TOKEN_UPGRADE,
NGHTTP2_TOKEN_UPGRADE_INSECURE_REQUESTS,
NGHTTP2_TOKEN_VARIANT_VARY,
NGHTTP2_TOKEN_WARNING,
NGHTTP2_TOKEN_X_API_VERSION,
NGHTTP2_TOKEN_X_ATT_DEVICEID,
NGHTTP2_TOKEN_X_CACHE,
NGHTTP2_TOKEN_X_CACHE_LOOKUP,
NGHTTP2_TOKEN_X_CONTENT_DURATION,
NGHTTP2_TOKEN_X_CONTENT_SECURITY_POLICY,
NGHTTP2_TOKEN_X_CONTENT_TYPE_OPTIONS,
NGHTTP2_TOKEN_X_DNSPREFETCH_CONTROL,
NGHTTP2_TOKEN_X_FORWARDED_FOR,
NGHTTP2_TOKEN_X_FORWARDED_HOST,
NGHTTP2_TOKEN_X_FORWARDED_PROTO,
NGHTTP2_TOKEN_X_FRAME_OPTIONS,
NGHTTP2_TOKEN_X_POWERED_BY,
NGHTTP2_TOKEN_X_REQUESTED_WITH,
NGHTTP2_TOKEN_X_UA_COMPATIBLE,
NGHTTP2_TOKEN_X_WAP_PROFILE,
NGHTTP2_TOKEN_X_WEBKIT_CSP,
NGHTTP2_TOKEN_X_XSS_PROTECTION,
} nghttp2_token; } nghttp2_token;
typedef enum {
NGHTTP2_HD_FLAG_NONE = 0,
/* Indicates name was dynamically allocated and must be freed */
NGHTTP2_HD_FLAG_NAME_ALLOC = 1,
/* Indicates value was dynamically allocated and must be freed */
NGHTTP2_HD_FLAG_VALUE_ALLOC = 1 << 1,
/* Indicates that the name was gifted to the entry and no copying
necessary. */
NGHTTP2_HD_FLAG_NAME_GIFT = 1 << 2,
/* Indicates that the value was gifted to the entry and no copying
necessary. */
NGHTTP2_HD_FLAG_VALUE_GIFT = 1 << 3
} nghttp2_hd_flags;
struct nghttp2_hd_entry; struct nghttp2_hd_entry;
typedef struct nghttp2_hd_entry nghttp2_hd_entry; typedef struct nghttp2_hd_entry nghttp2_hd_entry;
typedef struct {
/* The buffer containing header field name. NULL-termination is
guaranteed. */
nghttp2_rcbuf *name;
/* The buffer containing header field value. NULL-termination is
guaranteed. */
nghttp2_rcbuf *value;
/* nghttp2_token value for name. It could be -1 if we have no token
for that header field name. */
int32_t token;
/* Bitwise OR of one or more of nghttp2_nv_flag. */
uint8_t flags;
} nghttp2_hd_nv;
struct nghttp2_hd_entry { struct nghttp2_hd_entry {
nghttp2_nv nv; /* The header field name/value pair */
nghttp2_hd_nv nv;
/* This is solely for nghttp2_hd_{deflate,inflate}_get_table_entry
APIs to keep backward compatibility. */
nghttp2_nv cnv;
/* The next entry which shares same bucket in hash table. */ /* The next entry which shares same bucket in hash table. */
nghttp2_hd_entry *next; nghttp2_hd_entry *next;
/* The sequence number. We will increment it by one whenever we /* The sequence number. We will increment it by one whenever we
@ -194,14 +143,17 @@ struct nghttp2_hd_entry {
uint32_t seq; uint32_t seq;
/* The hash value for header name (nv.name). */ /* The hash value for header name (nv.name). */
uint32_t hash; uint32_t hash;
/* nghttp2_token value for nv.name. It could be -1 if we have no
token for that header field name. */
int token;
/* Reference count */
uint8_t ref;
uint8_t flags;
}; };
/* The entry used for static header table. */
typedef struct {
nghttp2_rcbuf name;
nghttp2_rcbuf value;
nghttp2_nv cnv;
int32_t token;
uint32_t hash;
} nghttp2_hd_static_entry;
typedef struct { typedef struct {
nghttp2_hd_entry **buffer; nghttp2_hd_entry **buffer;
size_t mask; size_t mask;
@ -275,24 +227,18 @@ struct nghttp2_hd_deflater {
struct nghttp2_hd_inflater { struct nghttp2_hd_inflater {
nghttp2_hd_context ctx; nghttp2_hd_context ctx;
/* header buffer */
nghttp2_bufs nvbufs;
/* Stores current state of huffman decoding */ /* Stores current state of huffman decoding */
nghttp2_hd_huff_decode_context huff_decode_ctx; nghttp2_hd_huff_decode_context huff_decode_ctx;
/* Pointer to the nghttp2_hd_entry which is used current header /* header buffer */
emission. This is required because in some cases the nghttp2_buf namebuf, valuebuf;
ent_keep->ref == 0 and we have to keep track of it. */ nghttp2_rcbuf *namercbuf, *valuercbuf;
nghttp2_hd_entry *ent_keep; /* Pointer to the name/value pair which are used in the current
/* Pointer to the name/value pair buffer which is used in the header emission. */
current header emission. */ nghttp2_rcbuf *nv_name_keep, *nv_value_keep;
uint8_t *nv_keep;
/* The number of bytes to read */ /* The number of bytes to read */
size_t left; size_t left;
/* The index in indexed repr or indexed name */ /* The index in indexed repr or indexed name */
size_t index; size_t index;
/* The length of new name encoded in literal. For huffman encoded
string, this is the length after it is decoded. */
size_t newnamelen;
/* The maximum header table size the inflater supports. This is the /* The maximum header table size the inflater supports. This is the
same value transmitted in SETTINGS_HEADER_TABLE_SIZE */ same value transmitted in SETTINGS_HEADER_TABLE_SIZE */
size_t settings_hd_table_bufsize_max; size_t settings_hd_table_bufsize_max;
@ -312,24 +258,16 @@ struct nghttp2_hd_inflater {
}; };
/* /*
* Initializes the |ent| members. If NGHTTP2_HD_FLAG_NAME_ALLOC bit * Initializes the |ent| members. The reference counts of nv->name
* set in the |flags|, the content pointed by the |name| with length * and nv->value are increased by one for each.
* |namelen| is copied. Likewise, if NGHTTP2_HD_FLAG_VALUE_ALLOC bit
* set in the |flags|, the content pointed by the |value| with length
* |valuelen| is copied. The |token| is enum number looked up by
* |name|. It could be -1 if we don't have that enum value.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM
* Out of memory.
*/ */
int nghttp2_hd_entry_init(nghttp2_hd_entry *ent, uint8_t flags, uint8_t *name, void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv);
size_t namelen, uint8_t *value, size_t valuelen,
int token, nghttp2_mem *mem);
void nghttp2_hd_entry_free(nghttp2_hd_entry *ent, nghttp2_mem *mem); /*
* This function decreases the reference counts of nv->name and
* nv->value.
*/
void nghttp2_hd_entry_free(nghttp2_hd_entry *ent);
/* /*
* Initializes |deflater| for deflating name/values pairs. * Initializes |deflater| for deflating name/values pairs.
@ -410,16 +348,14 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem);
void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater); void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater);
/* /*
* Similar to nghttp2_hd_inflate_hd(), but this takes additional * Similar to nghttp2_hd_inflate_hd(), but this takes nghttp2_hd_nv
* output parameter |token|. On successful header emission, it * instead of nghttp2_nv as output parameter |nv_out|. Other than
* contains nghttp2_token value for nv_out->name. It could be -1 if * that return values and semantics are the same as
* we don't have enum value for the name. Other than that return * nghttp2_hd_inflate_hd().
* values and semantics are the same as nghttp2_hd_inflate_hd().
*/ */
ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater,
nghttp2_nv *nv_out, int *inflate_flags, nghttp2_hd_nv *nv_out, int *inflate_flags,
int *token, uint8_t *in, size_t inlen, uint8_t *in, size_t inlen, int in_final);
int in_final);
/* For unittesting purpose */ /* For unittesting purpose */
int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index, int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index,
@ -433,8 +369,7 @@ int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv,
int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size); int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size);
/* For unittesting purpose */ /* For unittesting purpose */
nghttp2_hd_entry *nghttp2_hd_table_get(nghttp2_hd_context *context, nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t index);
size_t index);
/* For unittesting purpose */ /* For unittesting purpose */
ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *final, ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *final,
@ -470,11 +405,10 @@ int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src,
void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx); void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx);
/* /*
* Decodes the given data |src| with length |srclen|. The |ctx| must * Decodes the given data |src| with length |srclen|. The |ctx| must
* be initialized by nghttp2_hd_huff_decode_context_init(). The result * be initialized by nghttp2_hd_huff_decode_context_init(). The result
* will be added to |dest|. This function may expand |dest| as * will be written to |buf|. This function assumes that |buf| has the
* needed. The caller is responsible to release the memory of |dest| * enough room to store the decoded byte string.
* by calling nghttp2_bufs_free().
* *
* The caller must set the |final| to nonzero if the given input is * The caller must set the |final| to nonzero if the given input is
* the final block. * the final block.
@ -486,13 +420,11 @@ void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx);
* *
* NGHTTP2_ERR_NOMEM * NGHTTP2_ERR_NOMEM
* Out of memory. * Out of memory.
* NGHTTP2_ERR_BUFFER_ERROR
* Maximum buffer capacity size exceeded.
* NGHTTP2_ERR_HEADER_COMP * NGHTTP2_ERR_HEADER_COMP
* Decoding process has failed. * Decoding process has failed.
*/ */
ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
nghttp2_bufs *bufs, const uint8_t *src, nghttp2_buf *buf, const uint8_t *src,
size_t srclen, int final); size_t srclen, int final);
#endif /* NGHTTP2_HD_H */ #endif /* NGHTTP2_HD_H */

View File

@ -166,31 +166,10 @@ void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx) {
ctx->accept = 1; ctx->accept = 1;
} }
/* Use macro to make the code simpler..., but error case is tricky.
We spent most of the CPU in decoding, so we are doing this
thing. */
#define hd_huff_decode_sym_emit(bufs, sym, avail) \
do { \
if ((avail)) { \
nghttp2_bufs_fast_addb((bufs), (sym)); \
--(avail); \
} else { \
rv = nghttp2_bufs_addb((bufs), (sym)); \
if (rv != 0) { \
return rv; \
} \
(avail) = nghttp2_bufs_cur_avail((bufs)); \
} \
} while (0)
ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
nghttp2_bufs *bufs, const uint8_t *src, nghttp2_buf *buf, const uint8_t *src,
size_t srclen, int final) { size_t srclen, int final) {
size_t i; size_t i;
int rv;
size_t avail;
avail = nghttp2_bufs_cur_avail(bufs);
/* We use the decoding algorithm described in /* We use the decoding algorithm described in
http://graphics.ics.uci.edu/pub/Prefix.pdf */ http://graphics.ics.uci.edu/pub/Prefix.pdf */
@ -202,8 +181,7 @@ ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
return NGHTTP2_ERR_HEADER_COMP; return NGHTTP2_ERR_HEADER_COMP;
} }
if (t->flags & NGHTTP2_HUFF_SYM) { if (t->flags & NGHTTP2_HUFF_SYM) {
/* this is macro, and may return from this function on error */ *buf->last++ = t->sym;
hd_huff_decode_sym_emit(bufs, t->sym, avail);
} }
t = &huff_decode_table[t->state][src[i] & 0xf]; t = &huff_decode_table[t->state][src[i] & 0xf];
@ -211,8 +189,7 @@ ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
return NGHTTP2_ERR_HEADER_COMP; return NGHTTP2_ERR_HEADER_COMP;
} }
if (t->flags & NGHTTP2_HUFF_SYM) { if (t->flags & NGHTTP2_HUFF_SYM) {
/* this is macro, and may return from this function on error */ *buf->last++ = t->sym;
hd_huff_decode_sym_emit(bufs, t->sym, avail);
} }
ctx->state = t->state; ctx->state = t->state;

View File

@ -82,12 +82,12 @@ static int lws(const uint8_t *s, size_t n) {
return 1; return 1;
} }
static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_nv *nv, static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_hd_nv *nv,
int flag) { int flag) {
if (stream->http_flags & flag) { if (stream->http_flags & flag) {
return 0; return 0;
} }
if (lws(nv->value, nv->valuelen)) { if (lws(nv->value->base, nv->value->len)) {
return 0; return 0;
} }
stream->http_flags = (uint16_t)(stream->http_flags | flag); stream->http_flags = (uint16_t)(stream->http_flags | flag);
@ -112,16 +112,16 @@ static int check_path(nghttp2_stream *stream) {
(stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_ASTERISK))); (stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_ASTERISK)));
} }
static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv, static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
int token, int trailer) { int trailer) {
if (nv->name[0] == ':') { if (nv->name->base[0] == ':') {
if (trailer || if (trailer ||
(stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
switch (token) { switch (nv->token) {
case NGHTTP2_TOKEN__AUTHORITY: case NGHTTP2_TOKEN__AUTHORITY:
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
@ -131,16 +131,16 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
switch (nv->valuelen) { switch (nv->value->len) {
case 4: case 4:
if (lstreq("HEAD", nv->value, nv->valuelen)) { if (lstreq("HEAD", nv->value->base, nv->value->len)) {
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
} }
break; break;
case 7: case 7:
switch (nv->value[6]) { switch (nv->value->base[6]) {
case 'T': case 'T':
if (lstreq("CONNECT", nv->value, nv->valuelen)) { if (lstreq("CONNECT", nv->value->base, nv->value->len)) {
if (stream->stream_id % 2 == 0) { if (stream->stream_id % 2 == 0) {
/* we won't allow CONNECT for push */ /* we won't allow CONNECT for push */
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
@ -153,7 +153,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
} }
break; break;
case 'S': case 'S':
if (lstreq("OPTIONS", nv->value, nv->valuelen)) { if (lstreq("OPTIONS", nv->value->base, nv->value->len)) {
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS; stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS;
} }
break; break;
@ -168,9 +168,9 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
if (nv->value[0] == '/') { if (nv->value->base[0] == '/') {
stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_REGULAR; stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_REGULAR;
} else if (nv->valuelen == 1 && nv->value[0] == '*') { } else if (nv->value->len == 1 && nv->value->base[0] == '*') {
stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_ASTERISK; stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_ASTERISK;
} }
break; break;
@ -181,8 +181,8 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
if ((nv->valuelen == 4 && memieq("http", nv->value, 4)) || if ((nv->value->len == 4 && memieq("http", nv->value->base, 4)) ||
(nv->valuelen == 5 && memieq("https", nv->value, 5))) { (nv->value->len == 5 && memieq("https", nv->value->base, 5))) {
stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP; stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP;
} }
break; break;
@ -195,7 +195,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (stream->content_length != -1) { if (stream->content_length != -1) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
stream->content_length = parse_uint(nv->value, nv->valuelen); stream->content_length = parse_uint(nv->value->base, nv->value->len);
if (stream->content_length == -1) { if (stream->content_length == -1) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
@ -209,41 +209,41 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
case NGHTTP2_TOKEN_UPGRADE: case NGHTTP2_TOKEN_UPGRADE:
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
case NGHTTP2_TOKEN_TE: case NGHTTP2_TOKEN_TE:
if (!lstrieq("trailers", nv->value, nv->valuelen)) { if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
default: default:
if (nv->name[0] == ':') { if (nv->name->base[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
if (nv->name[0] != ':') { if (nv->name->base[0] != ':') {
stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
} }
return 0; return 0;
} }
static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv, static int http_response_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
int token, int trailer) { int trailer) {
if (nv->name[0] == ':') { if (nv->name->base[0] == ':') {
if (trailer || if (trailer ||
(stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
switch (token) { switch (nv->token) {
case NGHTTP2_TOKEN__STATUS: { case NGHTTP2_TOKEN__STATUS: {
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) { if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
if (nv->valuelen != 3) { if (nv->value->len != 3) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
stream->status_code = (int16_t)parse_uint(nv->value, nv->valuelen); stream->status_code = (int16_t)parse_uint(nv->value->base, nv->value->len);
if (stream->status_code == -1) { if (stream->status_code == -1) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
@ -253,7 +253,7 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (stream->content_length != -1) { if (stream->content_length != -1) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
stream->content_length = parse_uint(nv->value, nv->valuelen); stream->content_length = parse_uint(nv->value->base, nv->value->len);
if (stream->content_length == -1) { if (stream->content_length == -1) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
@ -267,17 +267,17 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
case NGHTTP2_TOKEN_UPGRADE: case NGHTTP2_TOKEN_UPGRADE:
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
case NGHTTP2_TOKEN_TE: case NGHTTP2_TOKEN_TE:
if (!lstrieq("trailers", nv->value, nv->valuelen)) { if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
break; break;
default: default:
if (nv->name[0] == ':') { if (nv->name->base[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
} }
if (nv->name[0] != ':') { if (nv->name->base[0] != ':') {
stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
} }
@ -375,7 +375,7 @@ static int check_scheme(const uint8_t *value, size_t len) {
} }
int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
nghttp2_frame *frame, nghttp2_nv *nv, int token, nghttp2_frame *frame, nghttp2_hd_nv *nv,
int trailer) { int trailer) {
int rv; int rv;
@ -386,14 +386,14 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
this, we may disrupt many web sites and/or libraries. So we this, we may disrupt many web sites and/or libraries. So we
become conservative here, and just ignore those illegal regular become conservative here, and just ignore those illegal regular
headers. */ headers. */
if (!nghttp2_check_header_name(nv->name, nv->namelen)) { if (!nghttp2_check_header_name(nv->name->base, nv->name->len)) {
size_t i; size_t i;
if (nv->namelen > 0 && nv->name[0] == ':') { if (nv->name->len > 0 && nv->name->base[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
/* header field name must be lower-cased without exception */ /* header field name must be lower-cased without exception */
for (i = 0; i < nv->namelen; ++i) { for (i = 0; i < nv->name->len; ++i) {
uint8_t c = nv->name[i]; uint8_t c = nv->name->base[i];
if ('A' <= c && c <= 'Z') { if ('A' <= c && c <= 'Z') {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
@ -405,17 +405,18 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
return NGHTTP2_ERR_IGN_HTTP_HEADER; return NGHTTP2_ERR_IGN_HTTP_HEADER;
} }
if (token == NGHTTP2_TOKEN__AUTHORITY || token == NGHTTP2_TOKEN_HOST) { if (nv->token == NGHTTP2_TOKEN__AUTHORITY ||
rv = check_authority(nv->value, nv->valuelen); nv->token == NGHTTP2_TOKEN_HOST) {
} else if (token == NGHTTP2_TOKEN__SCHEME) { rv = check_authority(nv->value->base, nv->value->len);
rv = check_scheme(nv->value, nv->valuelen); } else if (nv->token == NGHTTP2_TOKEN__SCHEME) {
rv = check_scheme(nv->value->base, nv->value->len);
} else { } else {
rv = nghttp2_check_header_value(nv->value, nv->valuelen); rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
} }
if (rv == 0) { if (rv == 0) {
assert(nv->namelen > 0); assert(nv->name->len > 0);
if (nv->name[0] == ':') { if (nv->name->base[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER; return NGHTTP2_ERR_HTTP_HEADER;
} }
/* When ignoring regular headers, we set this flag so that we /* When ignoring regular headers, we set this flag so that we
@ -426,10 +427,10 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
} }
if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) { if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
return http_request_on_header(stream, nv, token, trailer); return http_request_on_header(stream, nv, trailer);
} }
return http_response_on_header(stream, nv, token, trailer); return http_response_on_header(stream, nv, trailer);
} }
int nghttp2_http_on_request_headers(nghttp2_stream *stream, int nghttp2_http_on_request_headers(nghttp2_stream *stream,

View File

@ -36,8 +36,7 @@
/* /*
* This function is called when HTTP header field |nv| in |frame| is * This function is called when HTTP header field |nv| in |frame| is
* received for |stream|. This function will validate |nv| against * received for |stream|. This function will validate |nv| against
* the current state of stream. The |token| is nghttp2_token value * the current state of stream.
* for nv->name, or -1 if we don't have enum value for the name.
* *
* This function returns 0 if it succeeds, or one of the following * This function returns 0 if it succeeds, or one of the following
* negative error codes: * negative error codes:
@ -49,7 +48,7 @@
* if it was not received because of compatibility reasons. * if it was not received because of compatibility reasons.
*/ */
int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
nghttp2_frame *frame, nghttp2_nv *nv, int token, nghttp2_frame *frame, nghttp2_hd_nv *nv,
int trailer); int trailer);
/* /*

View File

@ -52,6 +52,10 @@ void nghttp2_mem_free(nghttp2_mem *mem, void *ptr) {
mem->free(ptr, mem->mem_user_data); mem->free(ptr, mem->mem_user_data);
} }
void nghttp2_mem_free2(nghttp2_free free, void *ptr, void *mem_user_data) {
free(ptr, mem_user_data);
}
void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size) { void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size) {
return mem->calloc(nmemb, size, mem->mem_user_data); return mem->calloc(nmemb, size, mem->mem_user_data);
} }

View File

@ -38,6 +38,7 @@ nghttp2_mem *nghttp2_mem_default(void);
|mem|. */ |mem|. */
void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size); void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size);
void nghttp2_mem_free(nghttp2_mem *mem, void *ptr); void nghttp2_mem_free(nghttp2_mem *mem, void *ptr);
void nghttp2_mem_free2(nghttp2_free free, void *ptr, void *mem_user_data);
void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size); void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size);
void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size); void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size);

98
lib/nghttp2_rcbuf.c Normal file
View File

@ -0,0 +1,98 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2016 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_rcbuf.h"
#include <string.h>
#include <assert.h>
#include "nghttp2_mem.h"
int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size,
nghttp2_mem *mem) {
uint8_t *p;
p = nghttp2_mem_malloc(mem, sizeof(nghttp2_rcbuf) + size);
if (p == NULL) {
return NGHTTP2_ERR_NOMEM;
}
*rcbuf_ptr = (void *)p;
(*rcbuf_ptr)->mem_user_data = mem->mem_user_data;
(*rcbuf_ptr)->free = mem->free;
(*rcbuf_ptr)->base = p + sizeof(nghttp2_rcbuf);
(*rcbuf_ptr)->len = size;
(*rcbuf_ptr)->ref = 1;
return 0;
}
int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src,
size_t srclen, nghttp2_mem *mem) {
int rv;
rv = nghttp2_rcbuf_new(rcbuf_ptr, srclen + 1, mem);
if (rv != 0) {
return rv;
}
memcpy((*rcbuf_ptr)->base, src, srclen);
(*rcbuf_ptr)->len = srclen;
(*rcbuf_ptr)->base[srclen] = '\0';
return 0;
}
/*
* Frees |rcbuf| itself, regardless of its reference cout.
*/
void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf) {
nghttp2_mem_free2(rcbuf->free, rcbuf, rcbuf->mem_user_data);
}
void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf) {
if (rcbuf->ref == -1) {
return;
}
++rcbuf->ref;
}
void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf) {
if (rcbuf == NULL || rcbuf->ref == -1) {
return;
}
assert(rcbuf->ref > 0);
if (--rcbuf->ref == 0) {
nghttp2_rcbuf_del(rcbuf);
}
}
nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf) {
return (nghttp2_vec){rcbuf->base, rcbuf->len};
}

80
lib/nghttp2_rcbuf.h Normal file
View File

@ -0,0 +1,80 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2016 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_RCBUF_H
#define NGHTTP2_RCBUF_H
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <nghttp2/nghttp2.h>
struct nghttp2_rcbuf {
/* custom memory allocator belongs to the mem parameter when
creating this object. */
void *mem_user_data;
nghttp2_free free;
/* The pointer to the underlying buffer */
uint8_t *base;
/* Size of buffer pointed by |base|. */
size_t len;
/* Reference count */
int32_t ref;
};
/*
* Allocates nghttp2_rcbuf object with |size| as initial buffer size.
* When the function succeeds, the reference count becomes 1.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM:
* Out of memory.
*/
int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size, nghttp2_mem *mem);
/*
* Like nghttp2_rcbuf_new(), but initializes the buffer with |src| of
* length |srclen|. This function allocates additional byte at the
* end and puts '\0' into it, so that the resulting buffer could be
* used as NULL-terminated string. Still (*rcbuf_ptr)->len equals to
* |srclen|.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM:
* Out of memory.
*/
int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src,
size_t srclen, nghttp2_mem *mem);
/*
* Frees |rcbuf| itself, regardless of its reference cout.
*/
void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf);
#endif /* NGHTTP2_RCBUF_H */

View File

@ -3122,20 +3122,24 @@ static int session_call_on_begin_headers(nghttp2_session *session,
static int session_call_on_header(nghttp2_session *session, static int session_call_on_header(nghttp2_session *session,
const nghttp2_frame *frame, const nghttp2_frame *frame,
const nghttp2_nv *nv) { const nghttp2_hd_nv *nv) {
int rv; int rv = 0;
if (session->callbacks.on_header_callback) { if (session->callbacks.on_header_callback2) {
rv = session->callbacks.on_header_callback2(
session, frame, nv->name, nv->value, nv->flags, session->user_data);
} else if (session->callbacks.on_header_callback) {
rv = session->callbacks.on_header_callback( rv = session->callbacks.on_header_callback(
session, frame, nv->name, nv->namelen, nv->value, nv->valuelen, session, frame, nv->name->base, nv->name->len, nv->value->base,
nv->flags, session->user_data); nv->value->len, nv->flags, session->user_data);
if (rv == NGHTTP2_ERR_PAUSE ||
rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
return rv;
}
if (rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
} }
if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
return rv;
}
if (rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
return 0; return 0;
} }
@ -3317,11 +3321,10 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
ssize_t proclen; ssize_t proclen;
int rv; int rv;
int inflate_flags; int inflate_flags;
nghttp2_nv nv; nghttp2_hd_nv nv;
nghttp2_stream *stream; nghttp2_stream *stream;
nghttp2_stream *subject_stream; nghttp2_stream *subject_stream;
int trailer = 0; int trailer = 0;
int token;
*readlen_ptr = 0; *readlen_ptr = 0;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id); stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
@ -3338,7 +3341,7 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
for (;;) { for (;;) {
inflate_flags = 0; inflate_flags = 0;
proclen = nghttp2_hd_inflate_hd2(&session->hd_inflater, &nv, &inflate_flags, proclen = nghttp2_hd_inflate_hd2(&session->hd_inflater, &nv, &inflate_flags,
&token, in, inlen, final); in, inlen, final);
if (nghttp2_is_fatal((int)proclen)) { if (nghttp2_is_fatal((int)proclen)) {
return (int)proclen; return (int)proclen;
} }
@ -3373,13 +3376,13 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) { if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) {
rv = 0; rv = 0;
if (subject_stream && session_enforce_http_messaging(session)) { if (subject_stream && session_enforce_http_messaging(session)) {
rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, token, rv = nghttp2_http_on_header(session, subject_stream, frame, &nv,
trailer); trailer);
if (rv == NGHTTP2_ERR_HTTP_HEADER) { if (rv == NGHTTP2_ERR_HTTP_HEADER) {
DEBUGF(fprintf( DEBUGF(fprintf(
stderr, "recv: HTTP error: type=%d, id=%d, header %.*s: %.*s\n", stderr, "recv: HTTP error: type=%d, id=%d, header %.*s: %.*s\n",
frame->hd.type, subject_stream->stream_id, (int)nv.namelen, frame->hd.type, subject_stream->stream_id, (int)nv.name->len,
nv.name, (int)nv.valuelen, nv.value)); nv.name->base, (int)nv.value->len, nv.value->base));
rv = rv =
session_handle_invalid_stream2(session, subject_stream->stream_id, session_handle_invalid_stream2(session, subject_stream->stream_id,
@ -3394,8 +3397,8 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
/* header is ignored */ /* header is ignored */
DEBUGF(fprintf( DEBUGF(fprintf(
stderr, "recv: HTTP ignored: type=%d, id=%d, header %.*s: %.*s\n", stderr, "recv: HTTP ignored: type=%d, id=%d, header %.*s: %.*s\n",
frame->hd.type, subject_stream->stream_id, (int)nv.namelen, frame->hd.type, subject_stream->stream_id, (int)nv.name->len,
nv.name, (int)nv.valuelen, nv.value)); nv.name->base, (int)nv.value->len, nv.value->base));
} }
} }
if (rv == 0) { if (rv == 0) {

View File

@ -152,7 +152,7 @@ static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
stream_next_cycle(stream, dep_stream->descendant_last_cycle); stream_next_cycle(stream, dep_stream->descendant_last_cycle);
stream->seq = dep_stream->descendant_next_seq++; stream->seq = dep_stream->descendant_next_seq++;
DEBUGF(fprintf(stderr, "stream: stream=%d obq push cycle=%ld\n", DEBUGF(fprintf(stderr, "stream: stream=%d obq push cycle=%d\n",
stream->stream_id, stream->cycle)); stream->stream_id, stream->cycle));
DEBUGF(fprintf(stderr, "stream: push stream %d to stream %d\n", DEBUGF(fprintf(stderr, "stream: push stream %d to stream %d\n",
@ -238,7 +238,7 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry); nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n", DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%d\n",
stream->stream_id, stream->cycle)); stream->stream_id, stream->cycle));
dep_stream->last_writelen = stream->last_writelen; dep_stream->last_writelen = stream->last_writelen;
@ -298,7 +298,7 @@ void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight) {
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry); nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n", DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%d\n",
stream->stream_id, stream->cycle)); stream->stream_id, stream->cycle));
} }

View File

@ -1235,7 +1235,7 @@ void prepare_response(Stream *stream, Http2Handler *hd,
bool last_mod_found = false; bool last_mod_found = false;
if (ims) { if (ims) {
last_mod_found = true; last_mod_found = true;
last_mod = util::parse_http_date(ims->value); last_mod = util::parse_http_date(StringRef{ims->value});
} }
auto query_pos = reqpath.find("?"); auto query_pos = reqpath.find("?");
std::string url; std::string url;

163
src/allocator.h Normal file
View File

@ -0,0 +1,163 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2016 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 ALLOCATOR_H
#define ALLOCATOR_H
#include "nghttp2_config.h"
#include <sys/uio.h>
#include "template.h"
namespace nghttp2 {
struct MemBlock {
// The next MemBlock to chain them. This is for book keeping
// purpose to free them later.
MemBlock *next;
// begin is the pointer to the beginning of buffer. last is the
// location of next write. end is the one beyond of the end of the
// buffer.
uint8_t *begin, *last, *end;
};
// BlockAllocator allocates memory block with given size at once, and
// cuts the region from it when allocation is requested. If the
// requested size is larger than given threshold, it will be allocated
// in a distinct buffer on demand.
struct BlockAllocator {
BlockAllocator(size_t block_size, size_t isolation_threshold)
: retain(nullptr),
head(nullptr),
block_size(block_size),
isolation_threshold(std::min(block_size, isolation_threshold)) {}
~BlockAllocator() {
for (auto mb = retain; mb;) {
auto next = mb->next;
delete[] reinterpret_cast<uint8_t *>(mb);
mb = next;
}
}
MemBlock *alloc_mem_block(size_t size) {
auto block = new uint8_t[sizeof(MemBlock) + size];
auto mb = reinterpret_cast<MemBlock *>(block);
mb->next = retain;
mb->begin = mb->last = block + sizeof(MemBlock);
mb->end = mb->begin + size;
retain = mb;
return mb;
}
void *alloc(size_t size) {
if (size >= isolation_threshold) {
auto mb = alloc_mem_block(size);
mb->last = mb->end;
return mb->begin;
}
if (!head || head->end - head->last < static_cast<ssize_t>(size)) {
head = alloc_mem_block(block_size);
}
auto res = head->last;
head->last = reinterpret_cast<uint8_t *>(
(reinterpret_cast<intptr_t>(head->last + size) + 0xf) & ~0xf);
return res;
}
// This holds live memory block to free them in dtor.
MemBlock *retain;
// Current memory block to use.
MemBlock *head;
// size of single memory block
size_t block_size;
// if allocation greater or equal to isolation_threshold bytes is
// requested, allocate dedicated block.
size_t isolation_threshold;
};
// Makes a copy of |src|. The resulting string will be
// NULL-terminated.
template <typename BlockAllocator>
StringRef make_string_ref(BlockAllocator &alloc, const StringRef &src) {
auto dst = static_cast<uint8_t *>(alloc.alloc(src.size() + 1));
auto p = dst;
p = std::copy(std::begin(src), std::end(src), p);
*p = '\0';
return StringRef{dst, src.size()};
}
// Returns the string which is the concatenation of |a| and |b| in
// this order. The resulting string will be NULL-terminated.
template <typename BlockAllocator>
StringRef concat_string_ref(BlockAllocator &alloc, const StringRef &a,
const StringRef &b) {
auto len = a.size() + b.size();
auto dst = static_cast<uint8_t *>(alloc.alloc(len + 1));
auto p = dst;
p = std::copy(std::begin(a), std::end(a), p);
p = std::copy(std::begin(b), std::end(b), p);
*p = '\0';
return StringRef{dst, len};
}
// Returns the string which is the concatenation of |a|, |b| and |c|
// in this order. The resulting string will be NULL-terminated.
template <typename BlockAllocator>
StringRef concat_string_ref(BlockAllocator &alloc, const StringRef &a,
const StringRef &b, const StringRef &c) {
auto len = a.size() + b.size() + c.size();
auto dst = static_cast<uint8_t *>(alloc.alloc(len + 1));
auto p = dst;
p = std::copy(std::begin(a), std::end(a), p);
p = std::copy(std::begin(b), std::end(b), p);
p = std::copy(std::begin(c), std::end(c), p);
*p = '\0';
return StringRef{dst, len};
}
struct ByteRef {
// The pointer to the beginning of the buffer.
uint8_t *base;
// The length of the buffer.
size_t len;
};
// Makes a buffer with given size. The resulting byte string might
// not be NULL-terminated.
template <typename BlockAllocator>
ByteRef make_byte_ref(BlockAllocator &alloc, size_t size) {
auto dst = static_cast<uint8_t *>(alloc.alloc(size));
return {dst, size};
}
} // namespace aria2
#endif // ALLOCATOR_H

View File

@ -134,111 +134,111 @@ std::string get_status_string(unsigned int status_code) {
} }
} }
const char *stringify_status(unsigned int status_code) { StringRef stringify_status(unsigned int status_code) {
switch (status_code) { switch (status_code) {
case 100: case 100:
return "100"; return StringRef::from_lit("100");
case 101: case 101:
return "101"; return StringRef::from_lit("101");
case 200: case 200:
return "200"; return StringRef::from_lit("200");
case 201: case 201:
return "201"; return StringRef::from_lit("201");
case 202: case 202:
return "202"; return StringRef::from_lit("202");
case 203: case 203:
return "203"; return StringRef::from_lit("203");
case 204: case 204:
return "204"; return StringRef::from_lit("204");
case 205: case 205:
return "205"; return StringRef::from_lit("205");
case 206: case 206:
return "206"; return StringRef::from_lit("206");
case 300: case 300:
return "300"; return StringRef::from_lit("300");
case 301: case 301:
return "301"; return StringRef::from_lit("301");
case 302: case 302:
return "302"; return StringRef::from_lit("302");
case 303: case 303:
return "303"; return StringRef::from_lit("303");
case 304: case 304:
return "304"; return StringRef::from_lit("304");
case 305: case 305:
return "305"; return StringRef::from_lit("305");
// case 306: return "306"; // case 306: return StringRef::from_lit("306");
case 307: case 307:
return "307"; return StringRef::from_lit("307");
case 308: case 308:
return "308"; return StringRef::from_lit("308");
case 400: case 400:
return "400"; return StringRef::from_lit("400");
case 401: case 401:
return "401"; return StringRef::from_lit("401");
case 402: case 402:
return "402"; return StringRef::from_lit("402");
case 403: case 403:
return "403"; return StringRef::from_lit("403");
case 404: case 404:
return "404"; return StringRef::from_lit("404");
case 405: case 405:
return "405"; return StringRef::from_lit("405");
case 406: case 406:
return "406"; return StringRef::from_lit("406");
case 407: case 407:
return "407"; return StringRef::from_lit("407");
case 408: case 408:
return "408"; return StringRef::from_lit("408");
case 409: case 409:
return "409"; return StringRef::from_lit("409");
case 410: case 410:
return "410"; return StringRef::from_lit("410");
case 411: case 411:
return "411"; return StringRef::from_lit("411");
case 412: case 412:
return "412"; return StringRef::from_lit("412");
case 413: case 413:
return "413"; return StringRef::from_lit("413");
case 414: case 414:
return "414"; return StringRef::from_lit("414");
case 415: case 415:
return "415"; return StringRef::from_lit("415");
case 416: case 416:
return "416"; return StringRef::from_lit("416");
case 417: case 417:
return "417"; return StringRef::from_lit("417");
case 421: case 421:
return "421"; return StringRef::from_lit("421");
case 426: case 426:
return "426"; return StringRef::from_lit("426");
case 428: case 428:
return "428"; return StringRef::from_lit("428");
case 429: case 429:
return "429"; return StringRef::from_lit("429");
case 431: case 431:
return "431"; return StringRef::from_lit("431");
case 451: case 451:
return "451"; return StringRef::from_lit("451");
case 500: case 500:
return "500"; return StringRef::from_lit("500");
case 501: case 501:
return "501"; return StringRef::from_lit("501");
case 502: case 502:
return "502"; return StringRef::from_lit("502");
case 503: case 503:
return "503"; return StringRef::from_lit("503");
case 504: case 504:
return "504"; return StringRef::from_lit("504");
case 505: case 505:
return "505"; return StringRef::from_lit("505");
case 511: case 511:
return "511"; return StringRef::from_lit("511");
default: default:
return nullptr; return StringRef{};
} }
} }
void capitalize(DefaultMemchunks *buf, const std::string &s) { void capitalize(DefaultMemchunks *buf, const StringRef &s) {
buf->append(util::upcase(s[0])); buf->append(util::upcase(s[0]));
for (size_t i = 1; i < s.size(); ++i) { for (size_t i = 1; i < s.size(); ++i) {
if (s[i - 1] == '-') { if (s[i - 1] == '-') {
@ -302,14 +302,7 @@ const Headers::value_type *get_header(const Headers &nva, const char *name) {
return res; return res;
} }
std::string value_to_str(const Headers::value_type *nv) { bool non_empty_value(const HeaderRefs::value_type *nv) {
if (nv) {
return nv->value;
}
return "";
}
bool non_empty_value(const Headers::value_type *nv) {
return nv && !nv->value.empty(); return nv && !nv->value.empty();
} }
@ -326,11 +319,29 @@ nghttp2_nv make_nv_internal(const std::string &name, const std::string &value,
} }
} // namespace } // namespace
namespace {
nghttp2_nv make_nv_internal(const StringRef &name, const StringRef &value,
bool no_index, uint8_t nv_flags) {
uint8_t flags;
flags =
nv_flags | (no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE);
return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
value.size(), flags};
}
} // namespace
nghttp2_nv make_nv(const std::string &name, const std::string &value, nghttp2_nv make_nv(const std::string &name, const std::string &value,
bool no_index) { bool no_index) {
return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE); return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE);
} }
nghttp2_nv make_nv(const StringRef &name, const StringRef &value,
bool no_index) {
return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE);
}
nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value, nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
bool no_index) { bool no_index) {
return make_nv_internal(name, value, no_index, return make_nv_internal(name, value, no_index,
@ -338,9 +349,16 @@ nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
NGHTTP2_NV_FLAG_NO_COPY_VALUE); NGHTTP2_NV_FLAG_NO_COPY_VALUE);
} }
nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
bool no_index) {
return make_nv_internal(name, value, no_index,
NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE);
}
namespace { namespace {
void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva, void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
const Headers &headers, uint8_t nv_flags) { const HeaderRefs &headers, uint8_t nv_flags) {
for (auto &kv : headers) { for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') { if (kv.name.empty() || kv.name[0] == ':') {
continue; continue;
@ -367,18 +385,19 @@ void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
} }
} // namespace } // namespace
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) { void copy_headers_to_nva(std::vector<nghttp2_nv> &nva,
const HeaderRefs &headers) {
copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE); copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE);
} }
void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva, void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
const Headers &headers) { const HeaderRefs &headers) {
copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NO_COPY_NAME | copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE); NGHTTP2_NV_FLAG_NO_COPY_VALUE);
} }
void build_http1_headers_from_headers(DefaultMemchunks *buf, void build_http1_headers_from_headers(DefaultMemchunks *buf,
const Headers &headers) { const HeaderRefs &headers) {
for (auto &kv : headers) { for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') { if (kv.name.empty() || kv.name[0] == ':') {
continue; continue;
@ -450,42 +469,77 @@ void dump_nv(FILE *out, const Headers &nva) {
fflush(out); fflush(out);
} }
std::string rewrite_location_uri(const std::string &uri, void dump_nv(FILE *out, const HeaderRefs &nva) {
const http_parser_url &u, for (auto &nv : nva) {
const std::string &match_host, fprintf(out, "%s: %s\n", nv.name.c_str(), nv.value.c_str());
const std::string &request_authority, }
const std::string &upstream_scheme) { fputc('\n', out);
fflush(out);
}
StringRef rewrite_location_uri(BlockAllocator &balloc, const StringRef &uri,
const http_parser_url &u,
const StringRef &match_host,
const StringRef &request_authority,
const StringRef &upstream_scheme) {
// We just rewrite scheme and authority. // We just rewrite scheme and authority.
if ((u.field_set & (1 << UF_HOST)) == 0) { if ((u.field_set & (1 << UF_HOST)) == 0) {
return ""; return StringRef{};
} }
auto field = &u.field_data[UF_HOST]; auto field = &u.field_data[UF_HOST];
if (!util::starts_with(std::begin(match_host), std::end(match_host), if (!util::starts_with(std::begin(match_host), std::end(match_host),
&uri[field->off], &uri[field->off] + field->len) || &uri[field->off], &uri[field->off] + field->len) ||
(match_host.size() != field->len && match_host[field->len] != ':')) { (match_host.size() != field->len && match_host[field->len] != ':')) {
return ""; return StringRef{};
} }
std::string res;
auto len = 0;
if (!request_authority.empty()) { if (!request_authority.empty()) {
res += upstream_scheme; len += upstream_scheme.size() + str_size("://") + request_authority.size();
res += "://"; }
res += request_authority;
if (u.field_set & (1 << UF_PATH)) {
field = &u.field_data[UF_PATH];
len += field->len;
}
if (u.field_set & (1 << UF_QUERY)) {
field = &u.field_data[UF_QUERY];
len += 1 + field->len;
}
if (u.field_set & (1 << UF_FRAGMENT)) {
field = &u.field_data[UF_FRAGMENT];
len += 1 + field->len;
}
auto iov = make_byte_ref(balloc, len + 1);
auto p = iov.base;
if (!request_authority.empty()) {
p = std::copy(std::begin(upstream_scheme), std::end(upstream_scheme), p);
p = util::copy_lit(p, "://");
p = std::copy(std::begin(request_authority), std::end(request_authority),
p);
} }
if (u.field_set & (1 << UF_PATH)) { if (u.field_set & (1 << UF_PATH)) {
field = &u.field_data[UF_PATH]; field = &u.field_data[UF_PATH];
res.append(&uri[field->off], field->len); p = std::copy_n(&uri[field->off], field->len, p);
} }
if (u.field_set & (1 << UF_QUERY)) { if (u.field_set & (1 << UF_QUERY)) {
field = &u.field_data[UF_QUERY]; field = &u.field_data[UF_QUERY];
res += '?'; *p++ = '?';
res.append(&uri[field->off], field->len); p = std::copy_n(&uri[field->off], field->len, p);
} }
if (u.field_set & (1 << UF_FRAGMENT)) { if (u.field_set & (1 << UF_FRAGMENT)) {
field = &u.field_data[UF_FRAGMENT]; field = &u.field_data[UF_FRAGMENT];
res += '#'; *p++ = '#';
res.append(&uri[field->off], field->len); p = std::copy_n(&uri[field->off], field->len, p);
} }
return res;
*p = '\0';
return StringRef{iov.base, p};
} }
int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value, int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
@ -499,7 +553,7 @@ int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
return 1; return 1;
} }
int parse_http_status_code(const std::string &src) { int parse_http_status_code(const StringRef &src) {
if (src.size() != 3) { if (src.size() != 3) {
return -1; return -1;
} }
@ -525,6 +579,10 @@ int lookup_token(const std::string &name) {
name.size()); name.size());
} }
int lookup_token(const StringRef &name) {
return lookup_token(name.byte(), name.size());
}
// This function was generated by genheaderfunc.py. Inspired by h2o // This function was generated by genheaderfunc.py. Inspired by h2o
// header lookup. https://github.com/h2o/h2o // header lookup. https://github.com/h2o/h2o
int lookup_token(const uint8_t *name, size_t namelen) { int lookup_token(const uint8_t *name, size_t namelen) {
@ -1133,132 +1191,11 @@ std::vector<LinkHeader> parse_link_header(const char *src, size_t len) {
return res; return res;
} }
namespace {
void eat_file(std::string &path) {
if (path.empty()) {
path = "/";
return;
}
auto p = path.size() - 1;
if (path[p] == '/') {
return;
}
p = path.rfind('/', p);
if (p == std::string::npos) {
// this should not happend in normal case, where we expect path
// starts with '/'
path = "/";
return;
}
path.erase(std::begin(path) + p + 1, std::end(path));
}
} // namespace
namespace {
void eat_dir(std::string &path) {
if (path.empty()) {
path = "/";
return;
}
auto p = path.size() - 1;
if (path[p] != '/') {
p = path.rfind('/', p);
if (p == std::string::npos) {
// this should not happend in normal case, where we expect path
// starts with '/'
path = "/";
return;
}
}
if (path[p] == '/') {
if (p == 0) {
return;
}
--p;
}
p = path.rfind('/', p);
if (p == std::string::npos) {
// this should not happend in normal case, where we expect path
// starts with '/'
path = "/";
return;
}
path.erase(std::begin(path) + p + 1, std::end(path));
}
} // namespace
std::string path_join(const StringRef &base_path, const StringRef &base_query, std::string path_join(const StringRef &base_path, const StringRef &base_query,
const StringRef &rel_path, const StringRef &rel_query) { const StringRef &rel_path, const StringRef &rel_query) {
std::string res; BlockAllocator balloc(1024, 1024);
if (rel_path.empty()) {
if (base_path.empty()) {
res = "/";
} else {
res.assign(std::begin(base_path), std::end(base_path));
}
if (rel_query.empty()) {
if (!base_query.empty()) {
res += '?';
res.append(std::begin(base_query), std::end(base_query));
}
return res;
}
res += '?';
res.append(std::begin(rel_query), std::end(rel_query));
return res;
}
auto first = std::begin(rel_path); return path_join(balloc, base_path, base_query, rel_path, rel_query).str();
auto last = std::end(rel_path);
if (rel_path[0] == '/') {
res = "/";
++first;
} else if (base_path.empty()) {
res = "/";
} else {
res.assign(std::begin(base_path), std::end(base_path));
}
for (; first != last;) {
if (*first == '.') {
if (first + 1 == last) {
break;
}
if (*(first + 1) == '/') {
first += 2;
continue;
}
if (*(first + 1) == '.') {
if (first + 2 == last) {
eat_dir(res);
break;
}
if (*(first + 2) == '/') {
eat_dir(res);
first += 3;
continue;
}
}
}
if (res.back() != '/') {
eat_file(res);
}
auto slash = std::find(first, last, '/');
if (slash == last) {
res.append(first, last);
break;
}
res.append(first, slash + 1);
first = slash + 1;
for (; first != last && *first == '/'; ++first)
;
}
if (!rel_query.empty()) {
res += '?';
res.append(std::begin(rel_query), std::end(rel_query));
}
return res;
} }
bool expect_response_body(int status_code) { bool expect_response_body(int status_code) {
@ -1278,6 +1215,10 @@ int lookup_method_token(const std::string &name) {
name.size()); name.size());
} }
int lookup_method_token(const StringRef &name) {
return lookup_method_token(name.byte(), name.size());
}
// This function was generated by genmethodfunc.py. // This function was generated by genmethodfunc.py.
int lookup_method_token(const uint8_t *name, size_t namelen) { int lookup_method_token(const uint8_t *name, size_t namelen) {
switch (namelen) { switch (namelen) {
@ -1455,7 +1396,7 @@ StringRef to_method_string(int method_token) {
return StringRef{http_method_str(static_cast<http_method>(method_token))}; return StringRef{http_method_str(static_cast<http_method>(method_token))};
} }
StringRef get_pure_path_component(const std::string &uri) { StringRef get_pure_path_component(const StringRef &uri) {
int rv; int rv;
http_parser_url u{}; http_parser_url u{};
@ -1472,9 +1413,9 @@ StringRef get_pure_path_component(const std::string &uri) {
return StringRef::from_lit("/"); return StringRef::from_lit("/");
} }
int construct_push_component(std::string &scheme, std::string &authority, int construct_push_component(BlockAllocator &balloc, StringRef &scheme,
std::string &path, const StringRef &base, StringRef &authority, StringRef &path,
const StringRef &uri) { const StringRef &base, const StringRef &uri) {
int rv; int rv;
StringRef rel, relq; StringRef rel, relq;
@ -1497,15 +1438,26 @@ int construct_push_component(std::string &scheme, std::string &authority,
} }
} else { } else {
if (u.field_set & (1 << UF_SCHEMA)) { if (u.field_set & (1 << UF_SCHEMA)) {
http2::copy_url_component(scheme, &u, UF_SCHEMA, uri.c_str()); scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
} }
if (u.field_set & (1 << UF_HOST)) { if (u.field_set & (1 << UF_HOST)) {
http2::copy_url_component(authority, &u, UF_HOST, uri.c_str()); auto auth = util::get_uri_field(uri.c_str(), u, UF_HOST);
if (u.field_set & (1 << UF_PORT)) { auto len = auth.size();
authority += ':'; auto port_exists = u.field_set & (1 << UF_PORT);
authority += util::utos(u.port); if (port_exists) {
len += 1 + str_size("65535");
} }
auto iov = make_byte_ref(balloc, len + 1);
auto p = iov.base;
p = std::copy(std::begin(auth), std::end(auth), p);
if (port_exists) {
*p++ = ':';
p = util::utos(p, u.port);
}
*p = '\0';
authority = StringRef{iov.base, p};
} }
if (u.field_set & (1 << UF_PATH)) { if (u.field_set & (1 << UF_PATH)) {
@ -1521,11 +1473,207 @@ int construct_push_component(std::string &scheme, std::string &authority,
} }
} }
path = http2::path_join(base, StringRef{}, rel, relq); path = http2::path_join(balloc, base, StringRef{}, rel, relq);
return 0; return 0;
} }
namespace {
template <typename InputIt> InputIt eat_file(InputIt first, InputIt last) {
if (first == last) {
*first++ = '/';
return first;
}
if (*(last - 1) == '/') {
return last;
}
auto p = last;
for (; p != first && *(p - 1) != '/'; --p)
;
if (p == first) {
// this should not happend in normal case, where we expect path
// starts with '/'
*first++ = '/';
return first;
}
return p;
}
} // namespace
namespace {
template <typename InputIt> InputIt eat_dir(InputIt first, InputIt last) {
auto p = eat_file(first, last);
--p;
assert(*p == '/');
return eat_file(first, p);
}
} // namespace
StringRef path_join(BlockAllocator &balloc, const StringRef &base_path,
const StringRef &base_query, const StringRef &rel_path,
const StringRef &rel_query) {
auto res = make_byte_ref(
balloc, std::max(static_cast<size_t>(1), base_path.size()) +
rel_path.size() + 1 +
std::max(base_query.size(), rel_query.size()) + 1);
auto p = res.base;
if (rel_path.empty()) {
if (base_path.empty()) {
*p++ = '/';
} else {
p = std::copy(std::begin(base_path), std::end(base_path), p);
}
if (rel_query.empty()) {
if (!base_query.empty()) {
*p++ = '?';
p = std::copy(std::begin(base_query), std::end(base_query), p);
}
*p = '\0';
return StringRef{res.base, p};
}
*p++ = '?';
p = std::copy(std::begin(rel_query), std::end(rel_query), p);
*p = '\0';
return StringRef{res.base, p};
}
auto first = std::begin(rel_path);
auto last = std::end(rel_path);
if (rel_path[0] == '/') {
*p++ = '/';
++first;
} else if (base_path.empty()) {
*p++ = '/';
} else {
p = std::copy(std::begin(base_path), std::end(base_path), p);
}
for (; first != last;) {
if (*first == '.') {
if (first + 1 == last) {
break;
}
if (*(first + 1) == '/') {
first += 2;
continue;
}
if (*(first + 1) == '.') {
if (first + 2 == last) {
p = eat_dir(res.base, p);
break;
}
if (*(first + 2) == '/') {
p = eat_dir(res.base, p);
first += 3;
continue;
}
}
}
if (*(p - 1) != '/') {
p = eat_file(res.base, p);
}
auto slash = std::find(first, last, '/');
if (slash == last) {
p = std::copy(first, last, p);
break;
}
p = std::copy(first, slash + 1, p);
first = slash + 1;
for (; first != last && *first == '/'; ++first)
;
}
if (!rel_query.empty()) {
*p++ = '?';
p = std::copy(std::begin(rel_query), std::end(rel_query), p);
}
*p = '\0';
return StringRef{res.base, p};
}
StringRef normalize_path(BlockAllocator &balloc, const StringRef &path,
const StringRef &query) {
// First, decode %XX for unreserved characters, then do
// http2::path_join
// We won't find %XX if length is less than 3.
if (path.size() < 3 ||
std::find(std::begin(path), std::end(path), '%') == std::end(path)) {
return path_join(balloc, StringRef{}, StringRef{}, path, query);
}
// includes last terminal NULL.
auto result = make_byte_ref(balloc, path.size() + 1);
auto p = result.base;
auto it = std::begin(path);
for (; it + 2 < std::end(path);) {
if (*it == '%') {
if (util::is_hex_digit(*(it + 1)) && util::is_hex_digit(*(it + 2))) {
auto c =
(util::hex_to_uint(*(it + 1)) << 4) + util::hex_to_uint(*(it + 2));
if (util::in_rfc3986_unreserved_chars(c)) {
*p++ = c;
it += 3;
continue;
}
*p++ = '%';
*p++ = util::upcase(*(it + 1));
*p++ = util::upcase(*(it + 2));
it += 3;
continue;
}
}
*p++ = *it++;
}
p = std::copy(it, std::end(path), p);
*p = '\0';
return path_join(balloc, StringRef{}, StringRef{}, StringRef{result.base, p},
query);
}
std::string normalize_path(const StringRef &path, const StringRef &query) {
BlockAllocator balloc(1024, 1024);
return normalize_path(balloc, path, query).str();
}
StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src) {
if (src.empty() || src[0] != '/') {
return src;
}
// probably, not necessary most of the case, but just in case.
auto fragment = std::find(std::begin(src), std::end(src), '#');
auto query = std::find(std::begin(src), fragment, '?');
if (query != fragment) {
++query;
}
return normalize_path(balloc, StringRef{std::begin(src), query},
StringRef{query, fragment});
}
StringRef copy_lower(BlockAllocator &balloc, const StringRef &src) {
auto iov = make_byte_ref(balloc, src.size() + 1);
auto p = iov.base;
p = std::copy(std::begin(src), std::end(src), p);
*p = '\0';
util::inp_strlower(iov.base, p);
return StringRef{iov.base, p};
}
} // namespace http2 } // namespace http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -39,6 +39,8 @@
#include "util.h" #include "util.h"
#include "memchunk.h" #include "memchunk.h"
#include "template.h"
#include "allocator.h"
namespace nghttp2 { namespace nghttp2 {
@ -66,7 +68,29 @@ struct Header {
bool no_index; bool no_index;
}; };
struct HeaderRef {
HeaderRef(const StringRef &name, const StringRef &value,
bool no_index = false, int32_t token = -1)
: name(name), value(value), token(token), no_index(no_index) {}
HeaderRef() : token(-1), no_index(false) {}
bool operator==(const HeaderRef &other) const {
return name == other.name && value == other.value;
}
bool operator<(const HeaderRef &rhs) const {
return name < rhs.name || (name == rhs.name && value < rhs.value);
}
StringRef name;
StringRef value;
int32_t token;
bool no_index;
};
using Headers = std::vector<Header>; using Headers = std::vector<Header>;
using HeaderRefs = std::vector<HeaderRef>;
namespace http2 { namespace http2 {
@ -74,9 +98,9 @@ std::string get_status_string(unsigned int status_code);
// Returns string version of |status_code|. This function can handle // Returns string version of |status_code|. This function can handle
// only predefined status code. Otherwise, returns nullptr. // only predefined status code. Otherwise, returns nullptr.
const char *stringify_status(unsigned int status_code); StringRef stringify_status(unsigned int status_code);
void capitalize(DefaultMemchunks *buf, const std::string &s); void capitalize(DefaultMemchunks *buf, const StringRef &s);
// Returns true if |value| is LWS // Returns true if |value| is LWS
bool lws(const char *value); bool lws(const char *value);
@ -103,11 +127,8 @@ void add_header(Headers &nva, const uint8_t *name, size_t namelen,
// in |nva| is returned. If no such entry exist, returns nullptr. // in |nva| is returned. If no such entry exist, returns nullptr.
const Headers::value_type *get_header(const Headers &nva, const char *name); const Headers::value_type *get_header(const Headers &nva, const char *name);
// Returns nv->second if nv is not nullptr. Otherwise, returns "".
std::string value_to_str(const Headers::value_type *nv);
// Returns true if the value of |nv| is not empty. // Returns true if the value of |nv| is not empty.
bool non_empty_value(const Headers::value_type *nv); bool non_empty_value(const HeaderRefs::value_type *nv);
// Creates nghttp2_nv using |name| and |value| and returns it. The // Creates nghttp2_nv using |name| and |value| and returns it. The
// returned value only references the data pointer to name.c_str() and // returned value only references the data pointer to name.c_str() and
@ -116,9 +137,15 @@ bool non_empty_value(const Headers::value_type *nv);
nghttp2_nv make_nv(const std::string &name, const std::string &value, nghttp2_nv make_nv(const std::string &name, const std::string &value,
bool no_index = false); bool no_index = false);
nghttp2_nv make_nv(const StringRef &name, const StringRef &value,
bool no_index = false);
nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value, nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
bool no_index = false); bool no_index = false);
nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
bool no_index = false);
// Create nghttp2_nv from string literal |name| and |value|. // Create nghttp2_nv from string literal |name| and |value|.
template <size_t N, size_t M> template <size_t N, size_t M>
constexpr nghttp2_nv make_nv_ll(const char(&name)[N], const char(&value)[M]) { constexpr nghttp2_nv make_nv_ll(const char(&name)[N], const char(&value)[M]) {
@ -163,19 +190,20 @@ nghttp2_nv make_nv_ls_nocopy(const char(&name)[N], const StringRef &value) {
// before this call (its element's token field is assigned). Certain // before this call (its element's token field is assigned). Certain
// headers, including disallowed headers in HTTP/2 spec and headers // headers, including disallowed headers in HTTP/2 spec and headers
// which require special handling (i.e. via), are not copied. // which require special handling (i.e. via), are not copied.
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers); void copy_headers_to_nva(std::vector<nghttp2_nv> &nva,
const HeaderRefs &headers);
// Just like copy_headers_to_nva(), but this adds // Just like copy_headers_to_nva(), but this adds
// NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE. // NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE.
void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva, void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
const Headers &headers); const HeaderRefs &headers);
// Appends HTTP/1.1 style header lines to |buf| from headers in // Appends HTTP/1.1 style header lines to |buf| from headers in
// |headers|. |headers| must be indexed before this call (its // |headers|. |headers| must be indexed before this call (its
// element's token field is assigned). Certain headers, which // element's token field is assigned). Certain headers, which
// requires special handling (i.e. via and cookie), are not appended. // requires special handling (i.e. via and cookie), are not appended.
void build_http1_headers_from_headers(DefaultMemchunks *buf, void build_http1_headers_from_headers(DefaultMemchunks *buf,
const Headers &headers); const HeaderRefs &headers);
// Return positive window_size_increment if WINDOW_UPDATE should be // Return positive window_size_increment if WINDOW_UPDATE should be
// sent for the stream |stream_id|. If |stream_id| == 0, this function // sent for the stream |stream_id|. If |stream_id| == 0, this function
@ -196,6 +224,8 @@ void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen);
// Dumps name/value pairs in |nva| to |out|. // Dumps name/value pairs in |nva| to |out|.
void dump_nv(FILE *out, const Headers &nva); void dump_nv(FILE *out, const Headers &nva);
void dump_nv(FILE *out, const HeaderRefs &nva);
// Rewrites redirection URI which usually appears in location header // Rewrites redirection URI which usually appears in location header
// field. The |uri| is the URI in the location header field. The |u| // field. The |uri| is the URI in the location header field. The |u|
// stores the result of parsed |uri|. The |request_authority| is the // stores the result of parsed |uri|. The |request_authority| is the
@ -209,11 +239,11 @@ void dump_nv(FILE *out, const Headers &nva);
// This function returns the new rewritten URI on success. If the // This function returns the new rewritten URI on success. If the
// location URI is not subject to the rewrite, this function returns // location URI is not subject to the rewrite, this function returns
// emtpy string. // emtpy string.
std::string rewrite_location_uri(const std::string &uri, StringRef rewrite_location_uri(BlockAllocator &balloc, const StringRef &uri,
const http_parser_url &u, const http_parser_url &u,
const std::string &match_host, const StringRef &match_host,
const std::string &request_authority, const StringRef &request_authority,
const std::string &upstream_scheme); const StringRef &upstream_scheme);
// Checks the header name/value pair using nghttp2_check_header_name() // Checks the header name/value pair using nghttp2_check_header_name()
// and nghttp2_check_header_value(). If both function returns nonzero, // and nghttp2_check_header_value(). If both function returns nonzero,
@ -222,7 +252,7 @@ int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen); size_t valuelen);
// Returns parsed HTTP status code. Returns -1 on failure. // Returns parsed HTTP status code. Returns -1 on failure.
int parse_http_status_code(const std::string &src); int parse_http_status_code(const StringRef &src);
// Header fields to be indexed, except HD_MAXIDX which is convenient // Header fields to be indexed, except HD_MAXIDX which is convenient
// member to get maximum value. // member to get maximum value.
@ -272,6 +302,7 @@ using HeaderIndex = std::array<int16_t, HD_MAXIDX>;
// cannot be tokenized, returns -1. // cannot be tokenized, returns -1.
int lookup_token(const uint8_t *name, size_t namelen); int lookup_token(const uint8_t *name, size_t namelen);
int lookup_token(const std::string &name); int lookup_token(const std::string &name);
int lookup_token(const StringRef &name);
// Initializes |hdidx|, header index. The |hdidx| must point to the // Initializes |hdidx|, header index. The |hdidx| must point to the
// array containing at least HD_MAXIDX elements. // array containing at least HD_MAXIDX elements.
@ -305,6 +336,10 @@ std::vector<LinkHeader> parse_link_header(const char *src, size_t len);
std::string path_join(const StringRef &base, const StringRef &base_query, std::string path_join(const StringRef &base, const StringRef &base_query,
const StringRef &rel_path, const StringRef &rel_query); const StringRef &rel_path, const StringRef &rel_query);
StringRef path_join(BlockAllocator &balloc, const StringRef &base_path,
const StringRef &base_query, const StringRef &rel_path,
const StringRef &rel_query);
// true if response has body, taking into account the request method // true if response has body, taking into account the request method
// and status code. // and status code.
bool expect_response_body(const std::string &method, int status_code); bool expect_response_body(const std::string &method, int status_code);
@ -318,6 +353,7 @@ bool expect_response_body(int status_code);
// tokenized. If method name cannot be tokenized, returns -1. // tokenized. If method name cannot be tokenized, returns -1.
int lookup_method_token(const uint8_t *name, size_t namelen); int lookup_method_token(const uint8_t *name, size_t namelen);
int lookup_method_token(const std::string &name); int lookup_method_token(const std::string &name);
int lookup_method_token(const StringRef &name);
// Returns string representation of |method_token|. This is wrapper // Returns string representation of |method_token|. This is wrapper
// function over http_method_str from http-parser. If |method_token| // function over http_method_str from http-parser. If |method_token|
@ -325,68 +361,29 @@ int lookup_method_token(const std::string &name);
// StringRef is guaranteed to be NULL-terminated. // StringRef is guaranteed to be NULL-terminated.
StringRef to_method_string(int method_token); StringRef to_method_string(int method_token);
template <typename InputIt> StringRef normalize_path(BlockAllocator &balloc, const StringRef &path,
std::string normalize_path(InputIt first, InputIt last) { const StringRef &query);
// First, decode %XX for unreserved characters, then do
// http2::join_path
std::string result;
// We won't find %XX if length is less than 3.
if (last - first < 3) {
result.assign(first, last);
} else {
for (; first < last - 2;) {
if (*first == '%') {
if (util::is_hex_digit(*(first + 1)) &&
util::is_hex_digit(*(first + 2))) {
auto c = (util::hex_to_uint(*(first + 1)) << 4) +
util::hex_to_uint(*(first + 2));
if (util::in_rfc3986_unreserved_chars(c)) {
result += c;
first += 3;
continue;
}
result += '%';
result += util::upcase(*(first + 1));
result += util::upcase(*(first + 2));
first += 3;
continue;
}
}
result += *first++;
}
result.append(first, last);
}
return path_join(StringRef{}, StringRef{}, StringRef{result}, StringRef{});
}
template <typename InputIt> std::string normalize_path(const StringRef &path, const StringRef &query);
std::string rewrite_clean_path(InputIt first, InputIt last) {
if (first == last || *first != '/') { StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src);
return std::string(first, last);
}
// probably, not necessary most of the case, but just in case.
auto fragment = std::find(first, last, '#');
auto query = std::find(first, fragment, '?');
auto path = normalize_path(first, query);
if (query != fragment) {
path.append(query, fragment);
}
return path;
}
// Returns path component of |uri|. The returned path does not // Returns path component of |uri|. The returned path does not
// include query component. This function returns empty string if it // include query component. This function returns empty string if it
// fails. // fails.
StringRef get_pure_path_component(const std::string &uri); StringRef get_pure_path_component(const StringRef &uri);
// Deduces scheme, authority and path from given |uri|, and stores // Deduces scheme, authority and path from given |uri|, and stores
// them in |scheme|, |authority|, and |path| respectively. If |uri| // them in |scheme|, |authority|, and |path| respectively. If |uri|
// is relative path, path resolution takes place using path given in // is relative path, path resolution takes place using path given in
// |base| of length |baselen|. This function returns 0 if it // |base| of length |baselen|. This function returns 0 if it
// succeeds, or -1. // succeeds, or -1.
int construct_push_component(std::string &scheme, std::string &authority, int construct_push_component(BlockAllocator &balloc, StringRef &scheme,
std::string &path, const StringRef &base, StringRef &authority, StringRef &path,
const StringRef &uri); const StringRef &base, const StringRef &uri);
// Copies |src| and return its lower-cased version.
StringRef copy_lower(BlockAllocator &balloc, const StringRef &src);
} // namespace http2 } // namespace http2

View File

@ -46,7 +46,7 @@ using namespace nghttp2;
namespace shrpx { namespace shrpx {
namespace { namespace {
void check_nv(const Header &a, const nghttp2_nv *b) { void check_nv(const HeaderRef &a, const nghttp2_nv *b) {
CU_ASSERT(a.name.size() == b->namelen); CU_ASSERT(a.name.size() == b->namelen);
CU_ASSERT(a.value.size() == b->valuelen); CU_ASSERT(a.value.size() == b->valuelen);
CU_ASSERT(memcmp(a.name.c_str(), b->name, b->namelen) == 0); CU_ASSERT(memcmp(a.name.c_str(), b->name, b->namelen) == 0);
@ -134,20 +134,24 @@ void test_http2_get_header(void) {
} }
namespace { namespace {
auto headers = auto headers = HeaderRefs{
Headers{{"alpha", "0", true}, {StringRef::from_lit("alpha"), StringRef::from_lit("0"), true},
{"bravo", "1"}, {StringRef::from_lit("bravo"), StringRef::from_lit("1")},
{"connection", "2", false, http2::HD_CONNECTION}, {StringRef::from_lit("connection"), StringRef::from_lit("2"), false,
{"connection", "3", false, http2::HD_CONNECTION}, http2::HD_CONNECTION},
{"delta", "4"}, {StringRef::from_lit("connection"), StringRef::from_lit("3"), false,
{"expect", "5"}, http2::HD_CONNECTION},
{"foxtrot", "6"}, {StringRef::from_lit("delta"), StringRef::from_lit("4")},
{"tango", "7"}, {StringRef::from_lit("expect"), StringRef::from_lit("5")},
{"te", "8", false, http2::HD_TE}, {StringRef::from_lit("foxtrot"), StringRef::from_lit("6")},
{"te", "9", false, http2::HD_TE}, {StringRef::from_lit("tango"), StringRef::from_lit("7")},
{"x-forwarded-proto", "10", false, http2::HD_X_FORWARDED_FOR}, {StringRef::from_lit("te"), StringRef::from_lit("8"), false, http2::HD_TE},
{"x-forwarded-proto", "11", false, http2::HD_X_FORWARDED_FOR}, {StringRef::from_lit("te"), StringRef::from_lit("9"), false, http2::HD_TE},
{"zulu", "12"}}; {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("10"), false,
http2::HD_X_FORWARDED_FOR},
{StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("11"), false,
http2::HD_X_FORWARDED_FOR},
{StringRef::from_lit("zulu"), StringRef::from_lit("12")}};
} // namespace } // namespace
void test_http2_copy_headers_to_nva(void) { void test_http2_copy_headers_to_nva(void) {
@ -209,10 +213,12 @@ void check_rewrite_location_uri(const std::string &want, const std::string &uri,
const std::string &match_host, const std::string &match_host,
const std::string &req_authority, const std::string &req_authority,
const std::string &upstream_scheme) { const std::string &upstream_scheme) {
BlockAllocator balloc(4096, 4096);
http_parser_url u{}; http_parser_url u{};
CU_ASSERT(0 == http_parser_parse_url(uri.c_str(), uri.size(), 0, &u)); CU_ASSERT(0 == http_parser_parse_url(uri.c_str(), uri.size(), 0, &u));
auto got = http2::rewrite_location_uri(uri, u, match_host, req_authority, auto got = http2::rewrite_location_uri(
upstream_scheme); balloc, StringRef{uri}, u, StringRef{match_host},
StringRef{req_authority}, StringRef{upstream_scheme});
CU_ASSERT(want == got); CU_ASSERT(want == got);
} }
} // namespace } // namespace
@ -245,13 +251,13 @@ void test_http2_rewrite_location_uri(void) {
} }
void test_http2_parse_http_status_code(void) { void test_http2_parse_http_status_code(void) {
CU_ASSERT(200 == http2::parse_http_status_code("200")); CU_ASSERT(200 == http2::parse_http_status_code(StringRef::from_lit("200")));
CU_ASSERT(102 == http2::parse_http_status_code("102")); CU_ASSERT(102 == http2::parse_http_status_code(StringRef::from_lit("102")));
CU_ASSERT(-1 == http2::parse_http_status_code("099")); CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("099")));
CU_ASSERT(-1 == http2::parse_http_status_code("99")); CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("99")));
CU_ASSERT(-1 == http2::parse_http_status_code("-1")); CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("-1")));
CU_ASSERT(-1 == http2::parse_http_status_code("20a")); CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("20a")));
CU_ASSERT(-1 == http2::parse_http_status_code("")); CU_ASSERT(-1 == http2::parse_http_status_code(StringRef{}));
} }
void test_http2_index_header(void) { void test_http2_index_header(void) {
@ -814,137 +820,135 @@ void test_http2_path_join(void) {
} }
void test_http2_normalize_path(void) { void test_http2_normalize_path(void) {
std::string src;
src = "/alpha/bravo/../charlie";
CU_ASSERT("/alpha/charlie" == CU_ASSERT("/alpha/charlie" ==
http2::normalize_path(std::begin(src), std::end(src))); http2::normalize_path(
StringRef::from_lit("/alpha/bravo/../charlie"), StringRef{}));
src = "/a%6c%70%68%61"; CU_ASSERT("/alpha" ==
CU_ASSERT("/alpha" == http2::normalize_path(std::begin(src), std::end(src))); http2::normalize_path(StringRef::from_lit("/a%6c%70%68%61"),
StringRef{}));
src = "/alpha%2f%3a"; CU_ASSERT(
CU_ASSERT("/alpha%2F%3A" == "/alpha%2F%3A" ==
http2::normalize_path(std::begin(src), std::end(src))); http2::normalize_path(StringRef::from_lit("/alpha%2f%3a"), StringRef{}));
src = "%2f"; CU_ASSERT("/%2F" ==
CU_ASSERT("/%2F" == http2::normalize_path(std::begin(src), std::end(src))); http2::normalize_path(StringRef::from_lit("%2f"), StringRef{}));
src = "%f"; CU_ASSERT("/%f" ==
CU_ASSERT("/%f" == http2::normalize_path(std::begin(src), std::end(src))); http2::normalize_path(StringRef::from_lit("%f"), StringRef{}));
src = "%"; CU_ASSERT("/%" ==
CU_ASSERT("/%" == http2::normalize_path(std::begin(src), std::end(src))); http2::normalize_path(StringRef::from_lit("%"), StringRef{}));
src = ""; CU_ASSERT("/" == http2::normalize_path(StringRef{}, StringRef{}));
CU_ASSERT("/" == http2::normalize_path(std::begin(src), std::end(src)));
CU_ASSERT("/alpha?bravo" ==
http2::normalize_path(StringRef::from_lit("/alpha"),
StringRef::from_lit("bravo")));
} }
void test_http2_rewrite_clean_path(void) { void test_http2_rewrite_clean_path(void) {
std::string src; BlockAllocator balloc(4096, 4096);
// unreserved characters // unreserved characters
src = "/alpha/%62ravo/";
CU_ASSERT("/alpha/bravo/" == CU_ASSERT("/alpha/bravo/" ==
http2::rewrite_clean_path(std::begin(src), std::end(src))); http2::rewrite_clean_path(balloc,
StringRef::from_lit("/alpha/%62ravo/")));
// percent-encoding is converted to upper case. // percent-encoding is converted to upper case.
src = "/delta%3a"; CU_ASSERT("/delta%3A" == http2::rewrite_clean_path(
CU_ASSERT("/delta%3A" == balloc, StringRef::from_lit("/delta%3a")));
http2::rewrite_clean_path(std::begin(src), std::end(src)));
// path component is normalized before mathcing // path component is normalized before mathcing
src = "/alpha/charlie/%2e././bravo/delta/.."; CU_ASSERT(
CU_ASSERT("/alpha/bravo/" == "/alpha/bravo/" ==
http2::rewrite_clean_path(std::begin(src), std::end(src))); http2::rewrite_clean_path(
balloc, StringRef::from_lit("/alpha/charlie/%2e././bravo/delta/..")));
src = "alpha%3a"; CU_ASSERT("alpha%3a" ==
CU_ASSERT(src == http2::rewrite_clean_path(std::begin(src), std::end(src))); http2::rewrite_clean_path(balloc, StringRef::from_lit("alpha%3a")));
src = ""; CU_ASSERT("" == http2::rewrite_clean_path(balloc, StringRef{}));
CU_ASSERT(src == http2::rewrite_clean_path(std::begin(src), std::end(src)));
} }
void test_http2_get_pure_path_component(void) { void test_http2_get_pure_path_component(void) {
std::string path; CU_ASSERT("/" == http2::get_pure_path_component(StringRef::from_lit("/")));
path = "/"; CU_ASSERT("/foo" ==
CU_ASSERT("/" == http2::get_pure_path_component(path)); http2::get_pure_path_component(StringRef::from_lit("/foo")));
path = "/foo"; CU_ASSERT("/bar" == http2::get_pure_path_component(
CU_ASSERT("/foo" == http2::get_pure_path_component(path)); StringRef::from_lit("https://example.org/bar")));
path = "https://example.org/bar"; CU_ASSERT("/alpha" == http2::get_pure_path_component(StringRef::from_lit(
CU_ASSERT("/bar" == http2::get_pure_path_component(path)); "https://example.org/alpha?q=a")));
path = "https://example.org/alpha?q=a"; CU_ASSERT("/bravo" == http2::get_pure_path_component(StringRef::from_lit(
CU_ASSERT("/alpha" == http2::get_pure_path_component(path)); "https://example.org/bravo?q=a#fragment")));
path = "https://example.org/bravo?q=a#fragment"; CU_ASSERT("" ==
CU_ASSERT("/bravo" == http2::get_pure_path_component(path)); http2::get_pure_path_component(StringRef::from_lit("\x01\x02")));
path = "\x01\x02";
CU_ASSERT("" == http2::get_pure_path_component(path));
} }
void test_http2_construct_push_component(void) { void test_http2_construct_push_component(void) {
BlockAllocator balloc(4096, 4096);
StringRef base, uri; StringRef base, uri;
std::string scheme, authority, path; StringRef scheme, authority, path;
base = StringRef::from_lit("/b/"); base = StringRef::from_lit("/b/");
uri = StringRef::from_lit("https://example.org/foo"); uri = StringRef::from_lit("https://example.org/foo");
CU_ASSERT( CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
0 == http2::construct_push_component(scheme, authority, path, base, uri)); path, base, uri));
CU_ASSERT("https" == scheme); CU_ASSERT("https" == scheme);
CU_ASSERT("example.org" == authority); CU_ASSERT("example.org" == authority);
CU_ASSERT("/foo" == path); CU_ASSERT("/foo" == path);
scheme.clear(); scheme = StringRef{};
authority.clear(); authority = StringRef{};
path.clear(); path = StringRef{};
uri = StringRef::from_lit("/foo/bar?q=a"); uri = StringRef::from_lit("/foo/bar?q=a");
CU_ASSERT( CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
0 == http2::construct_push_component(scheme, authority, path, base, uri)); path, base, uri));
CU_ASSERT("" == scheme); CU_ASSERT("" == scheme);
CU_ASSERT("" == authority); CU_ASSERT("" == authority);
CU_ASSERT("/foo/bar?q=a" == path); CU_ASSERT("/foo/bar?q=a" == path);
scheme.clear(); scheme = StringRef{};
authority.clear(); authority = StringRef{};
path.clear(); path = StringRef{};
uri = StringRef::from_lit("foo/../bar?q=a"); uri = StringRef::from_lit("foo/../bar?q=a");
CU_ASSERT( CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
0 == http2::construct_push_component(scheme, authority, path, base, uri)); path, base, uri));
CU_ASSERT("" == scheme); CU_ASSERT("" == scheme);
CU_ASSERT("" == authority); CU_ASSERT("" == authority);
CU_ASSERT("/b/bar?q=a" == path); CU_ASSERT("/b/bar?q=a" == path);
scheme.clear(); scheme = StringRef{};
authority.clear(); authority = StringRef{};
path.clear(); path = StringRef{};
uri = StringRef{}; uri = StringRef{};
CU_ASSERT( CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
0 == http2::construct_push_component(scheme, authority, path, base, uri)); path, base, uri));
CU_ASSERT("" == scheme); CU_ASSERT("" == scheme);
CU_ASSERT("" == authority); CU_ASSERT("" == authority);
CU_ASSERT("/" == path); CU_ASSERT("/" == path);
scheme.clear(); scheme = StringRef{};
authority.clear(); authority = StringRef{};
path.clear(); path = StringRef{};
uri = StringRef::from_lit("?q=a"); uri = StringRef::from_lit("?q=a");
CU_ASSERT( CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority,
0 == http2::construct_push_component(scheme, authority, path, base, uri)); path, base, uri));
CU_ASSERT("" == scheme); CU_ASSERT("" == scheme);
CU_ASSERT("" == authority); CU_ASSERT("" == authority);
CU_ASSERT("/b/?q=a" == path); CU_ASSERT("/b/?q=a" == path);

View File

@ -1600,7 +1600,7 @@ void check_response_header(nghttp2_session *session, Request *req) {
return; return;
} }
auto status = http2::parse_http_status_code(status_hd->value); auto status = http2::parse_http_status_code(StringRef{status_hd->value});
if (status == -1) { if (status == -1) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);

View File

@ -101,8 +101,8 @@ int main(int argc, char *argv[]) {
shrpx::test_http2_get_pure_path_component) || shrpx::test_http2_get_pure_path_component) ||
!CU_add_test(pSuite, "http2_construct_push_component", !CU_add_test(pSuite, "http2_construct_push_component",
shrpx::test_http2_construct_push_component) || shrpx::test_http2_construct_push_component) ||
!CU_add_test(pSuite, "downstream_field_store_add_header_lower", !CU_add_test(pSuite, "downstream_field_store_append_last_header",
shrpx::test_downstream_field_store_add_header_lower) || shrpx::test_downstream_field_store_append_last_header) ||
!CU_add_test(pSuite, "downstream_field_store_header", !CU_add_test(pSuite, "downstream_field_store_header",
shrpx::test_downstream_field_store_header) || shrpx::test_downstream_field_store_header) ||
!CU_add_test(pSuite, "downstream_crumble_request_cookie", !CU_add_test(pSuite, "downstream_crumble_request_cookie",
@ -123,6 +123,8 @@ int main(int argc, char *argv[]) {
shrpx::test_shrpx_worker_match_downstream_addr_group) || shrpx::test_shrpx_worker_match_downstream_addr_group) ||
!CU_add_test(pSuite, "http_create_forwarded", !CU_add_test(pSuite, "http_create_forwarded",
shrpx::test_shrpx_http_create_forwarded) || shrpx::test_shrpx_http_create_forwarded) ||
!CU_add_test(pSuite, "http_create_via_header_value",
shrpx::test_shrpx_http_create_via_header_value) ||
!CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) || !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) ||
!CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) || !CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) ||
!CU_add_test(pSuite, "util_inp_strlower", !CU_add_test(pSuite, "util_inp_strlower",
@ -142,6 +144,9 @@ int main(int argc, char *argv[]) {
!CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) || !CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) ||
!CU_add_test(pSuite, "util_ipv6_numeric_addr", !CU_add_test(pSuite, "util_ipv6_numeric_addr",
shrpx::test_util_ipv6_numeric_addr) || shrpx::test_util_ipv6_numeric_addr) ||
!CU_add_test(pSuite, "util_utos", shrpx::test_util_utos) ||
!CU_add_test(pSuite, "util_make_string_ref_uint",
shrpx::test_util_make_string_ref_uint) ||
!CU_add_test(pSuite, "util_utos_unit", shrpx::test_util_utos_unit) || !CU_add_test(pSuite, "util_utos_unit", shrpx::test_util_utos_unit) ||
!CU_add_test(pSuite, "util_utos_funit", shrpx::test_util_utos_funit) || !CU_add_test(pSuite, "util_utos_funit", shrpx::test_util_utos_funit) ||
!CU_add_test(pSuite, "util_parse_uint_with_unit", !CU_add_test(pSuite, "util_parse_uint_with_unit",

View File

@ -825,11 +825,11 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
bool ClientHandler::get_http2_upgrade_allowed() const { return !conn_.tls.ssl; } bool ClientHandler::get_http2_upgrade_allowed() const { return !conn_.tls.ssl; }
std::string ClientHandler::get_upstream_scheme() const { StringRef ClientHandler::get_upstream_scheme() const {
if (conn_.tls.ssl) { if (conn_.tls.ssl) {
return "https"; return StringRef::from_lit("https");
} else { } else {
return "http"; return StringRef::from_lit("http");
} }
} }
@ -843,24 +843,35 @@ namespace {
// is mostly same routine found in // is mostly same routine found in
// HttpDownstreamConnection::push_request_headers(), but vastly // HttpDownstreamConnection::push_request_headers(), but vastly
// simplified since we only care about absolute URI. // simplified since we only care about absolute URI.
std::string construct_absolute_request_uri(const Request &req) { StringRef construct_absolute_request_uri(BlockAllocator &balloc,
const Request &req) {
if (req.authority.empty()) { if (req.authority.empty()) {
return req.path; return req.path;
} }
std::string uri; auto len = req.authority.size() + req.path.size();
if (req.scheme.empty()) {
len += str_size("http://");
} else {
len += req.scheme.size() + str_size("://");
}
auto iov = make_byte_ref(balloc, len + 1);
auto p = iov.base;
if (req.scheme.empty()) { if (req.scheme.empty()) {
// We may have to log the request which lacks scheme (e.g., // We may have to log the request which lacks scheme (e.g.,
// http/1.1 with origin form). // http/1.1 with origin form).
uri += "http://"; p = util::copy_lit(p, "http://");
} else { } else {
uri += req.scheme; p = std::copy(std::begin(req.scheme), std::end(req.scheme), p);
uri += "://"; p = util::copy_lit(p, "://");
} }
uri += req.authority; p = std::copy(std::begin(req.authority), std::end(req.authority), p);
uri += req.path; p = std::copy(std::begin(req.path), std::end(req.path), p);
*p = '\0';
return uri; return StringRef{iov.base, p};
} }
} // namespace } // namespace
@ -869,6 +880,8 @@ void ClientHandler::write_accesslog(Downstream *downstream) {
const auto &req = downstream->request(); const auto &req = downstream->request();
const auto &resp = downstream->response(); const auto &resp = downstream->response();
auto &balloc = downstream->get_block_allocator();
upstream_accesslog( upstream_accesslog(
get_config()->logging.access.format, get_config()->logging.access.format,
LogSpec{ LogSpec{
@ -877,7 +890,7 @@ void ClientHandler::write_accesslog(Downstream *downstream) {
req.method == HTTP_CONNECT req.method == HTTP_CONNECT
? StringRef(req.authority) ? StringRef(req.authority)
: get_config()->http2_proxy : get_config()->http2_proxy
? StringRef(construct_absolute_request_uri(req)) ? StringRef(construct_absolute_request_uri(balloc, req))
: req.path.empty() : req.path.empty()
? req.method == HTTP_OPTIONS ? req.method == HTTP_OPTIONS
? StringRef::from_lit("*") ? StringRef::from_lit("*")

View File

@ -109,7 +109,7 @@ public:
int perform_http2_upgrade(HttpsUpstream *http); int perform_http2_upgrade(HttpsUpstream *http);
bool get_http2_upgrade_allowed() const; bool get_http2_upgrade_allowed() const;
// Returns upstream scheme, either "http" or "https" // Returns upstream scheme, either "http" or "https"
std::string get_upstream_scheme() const; StringRef get_upstream_scheme() const;
void start_immediate_shutdown(); void start_immediate_shutdown();
// Writes upstream accesslog using |downstream|. The |downstream| // Writes upstream accesslog using |downstream|. The |downstream|

View File

@ -624,7 +624,8 @@ int parse_mapping(const DownstreamAddrConfig &addr,
} else { } else {
pattern.assign(std::begin(raw_pattern), slash); pattern.assign(std::begin(raw_pattern), slash);
util::inp_strlower(pattern); util::inp_strlower(pattern);
pattern += http2::normalize_path(slash, std::end(raw_pattern)); pattern += http2::normalize_path(StringRef{slash, std::end(raw_pattern)},
StringRef{});
} }
for (auto &g : addr_groups) { for (auto &g : addr_groups) {
if (g.pattern == pattern) { if (g.pattern == pattern) {

View File

@ -116,6 +116,9 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
: dlnext(nullptr), : dlnext(nullptr),
dlprev(nullptr), dlprev(nullptr),
response_sent_body_length(0), response_sent_body_length(0),
balloc_(1024, 1024),
req_(balloc_),
resp_(balloc_),
request_start_time_(std::chrono::high_resolution_clock::now()), request_start_time_(std::chrono::high_resolution_clock::now()),
request_buf_(mcpool), request_buf_(mcpool),
response_buf_(mcpool), response_buf_(mcpool),
@ -179,6 +182,10 @@ Downstream::~Downstream() {
// explicitly. // explicitly.
dconn_.reset(); dconn_.reset();
for (auto rcbuf : rcbufs_) {
nghttp2_rcbuf_decref(rcbuf);
}
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
DLOG(INFO, this) << "Deleted"; DLOG(INFO, this) << "Deleted";
} }
@ -237,8 +244,9 @@ void Downstream::force_resume_read() {
} }
namespace { namespace {
const Headers::value_type * const HeaderRefs::value_type *
search_header_linear_backwards(const Headers &headers, const StringRef &name) { search_header_linear_backwards(const HeaderRefs &headers,
const StringRef &name) {
for (auto it = headers.rbegin(); it != headers.rend(); ++it) { for (auto it = headers.rbegin(); it != headers.rend(); ++it) {
auto &kv = *it; auto &kv = *it;
if (kv.name == name) { if (kv.name == name) {
@ -253,16 +261,29 @@ std::string Downstream::assemble_request_cookie() const {
std::string cookie; std::string cookie;
cookie = ""; cookie = "";
for (auto &kv : req_.fs.headers()) { for (auto &kv : req_.fs.headers()) {
if (kv.name.size() != 6 || kv.name[5] != 'e' || if (kv.token != http2::HD_COOKIE) {
!util::streq_l("cooki", kv.name.c_str(), 5)) {
continue; continue;
} }
auto end = kv.value.find_last_not_of(" ;"); if (kv.value.empty()) {
if (end == std::string::npos) { continue;
}
auto end = std::end(kv.value);
for (auto it = std::begin(kv.value) + kv.value.size();
it != std::begin(kv.value); --it) {
auto c = *(it - 1);
if (c == ' ' || c == ';') {
continue;
}
end = it;
break;
}
if (end == std::end(kv.value)) {
cookie += kv.value; cookie += kv.value;
} else { } else {
cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1); cookie.append(std::begin(kv.value), end);
} }
cookie += "; "; cookie += "; ";
} }
@ -280,18 +301,14 @@ size_t Downstream::count_crumble_request_cookie() {
!util::streq_l("cooki", kv.name.c_str(), 5)) { !util::streq_l("cooki", kv.name.c_str(), 5)) {
continue; continue;
} }
size_t last = kv.value.size();
for (size_t j = 0; j < last;) { for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
j = kv.value.find_first_not_of("\t ;", j); if (*it == '\t' || *it == ' ' || *it == ';') {
if (j == std::string::npos) { ++it;
break; continue;
} }
j = kv.value.find(';', j); it = std::find(it, std::end(kv.value), ';');
if (j == std::string::npos) {
j = last;
}
++n; ++n;
} }
@ -305,22 +322,19 @@ void Downstream::crumble_request_cookie(std::vector<nghttp2_nv> &nva) {
!util::streq_l("cooki", kv.name.c_str(), 5)) { !util::streq_l("cooki", kv.name.c_str(), 5)) {
continue; continue;
} }
size_t last = kv.value.size();
for (size_t j = 0; j < last;) { for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
j = kv.value.find_first_not_of("\t ;", j); if (*it == '\t' || *it == ' ' || *it == ';') {
if (j == std::string::npos) { ++it;
break; continue;
}
auto first = j;
j = kv.value.find(';', j);
if (j == std::string::npos) {
j = last;
} }
nva.push_back({(uint8_t *)"cookie", (uint8_t *)kv.value.c_str() + first, auto first = it;
str_size("cookie"), j - first,
it = std::find(it, std::end(kv.value), ';');
nva.push_back({(uint8_t *)"cookie", (uint8_t *)first, str_size("cookie"),
(size_t)(it - first),
(uint8_t)(NGHTTP2_NV_FLAG_NO_COPY_NAME | (uint8_t)(NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE | NGHTTP2_NV_FLAG_NO_COPY_VALUE |
(kv.no_index ? NGHTTP2_NV_FLAG_NO_INDEX : 0))}); (kv.no_index ? NGHTTP2_NV_FLAG_NO_INDEX : 0))});
@ -329,42 +343,42 @@ void Downstream::crumble_request_cookie(std::vector<nghttp2_nv> &nva) {
} }
namespace { namespace {
void add_header(bool &key_prev, size_t &sum, Headers &headers, void add_header(bool &key_prev, size_t &sum, HeaderRefs &headers,
const StringRef &name, const StringRef &value, bool no_index, const StringRef &name, const StringRef &value, bool no_index,
int32_t token) { int32_t token) {
key_prev = true; key_prev = true;
sum += name.size() + value.size(); sum += name.size() + value.size();
headers.emplace_back(name.str(), value.str(), no_index, token); headers.emplace_back(name, value, no_index, token);
} }
} // namespace } // namespace
namespace { namespace {
void add_header(size_t &sum, Headers &headers, const StringRef &name, void append_last_header_key(BlockAllocator &balloc, bool &key_prev, size_t &sum,
const StringRef &value, bool no_index, int32_t token) { HeaderRefs &headers, const char *data, size_t len) {
sum += name.size() + value.size();
headers.emplace_back(name.str(), value.str(), no_index, token);
}
} // namespace
namespace {
void append_last_header_key(bool &key_prev, size_t &sum, Headers &headers,
const char *data, size_t len) {
assert(key_prev); assert(key_prev);
sum += len; sum += len;
auto &item = headers.back(); auto &item = headers.back();
item.name.append(data, len); auto iov = make_byte_ref(balloc, item.name.size() + len + 1);
util::inp_strlower(item.name); auto p = iov.base;
p = std::copy(std::begin(item.name), std::end(item.name), p);
p = std::copy_n(data, len, p);
util::inp_strlower(p - len, p);
*p = '\0';
item.name = StringRef{iov.base, p};
item.token = http2::lookup_token(item.name); item.token = http2::lookup_token(item.name);
} }
} // namespace } // namespace
namespace { namespace {
void append_last_header_value(bool &key_prev, size_t &sum, Headers &headers, void append_last_header_value(BlockAllocator &balloc, bool &key_prev,
size_t &sum, HeaderRefs &headers,
const char *data, size_t len) { const char *data, size_t len) {
key_prev = false; key_prev = false;
sum += len; sum += len;
auto &item = headers.back(); auto &item = headers.back();
item.value.append(data, len); item.value = concat_string_ref(balloc, item.value, StringRef{data, len});
} }
} // namespace } // namespace
@ -388,7 +402,7 @@ int FieldStore::parse_content_length() {
return 0; return 0;
} }
const Headers::value_type *FieldStore::header(int32_t token) const { const HeaderRefs::value_type *FieldStore::header(int32_t token) const {
for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) { for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) {
auto &kv = *it; auto &kv = *it;
if (kv.token == token) { if (kv.token == token) {
@ -398,7 +412,7 @@ const Headers::value_type *FieldStore::header(int32_t token) const {
return nullptr; return nullptr;
} }
Headers::value_type *FieldStore::header(int32_t token) { HeaderRefs::value_type *FieldStore::header(int32_t token) {
for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) { for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) {
auto &kv = *it; auto &kv = *it;
if (kv.token == token) { if (kv.token == token) {
@ -408,61 +422,45 @@ Headers::value_type *FieldStore::header(int32_t token) {
return nullptr; return nullptr;
} }
const Headers::value_type *FieldStore::header(const StringRef &name) const { const HeaderRefs::value_type *FieldStore::header(const StringRef &name) const {
return search_header_linear_backwards(headers_, name); return search_header_linear_backwards(headers_, name);
} }
void FieldStore::add_header_lower(const StringRef &name, const StringRef &value,
bool no_index) {
auto low_name = name.str();
util::inp_strlower(low_name);
auto token = http2::lookup_token(low_name);
shrpx::add_header(header_key_prev_, buffer_size_, headers_,
StringRef{low_name}, value, no_index, token);
}
void FieldStore::add_header_token(const StringRef &name, const StringRef &value, void FieldStore::add_header_token(const StringRef &name, const StringRef &value,
bool no_index, int32_t token) { bool no_index, int32_t token) {
shrpx::add_header(buffer_size_, headers_, name, value, no_index, token); shrpx::add_header(header_key_prev_, buffer_size_, headers_, name, value,
no_index, token);
} }
void FieldStore::append_last_header_key(const char *data, size_t len) { void FieldStore::append_last_header_key(const char *data, size_t len) {
shrpx::append_last_header_key(header_key_prev_, buffer_size_, headers_, data, shrpx::append_last_header_key(balloc_, header_key_prev_, buffer_size_,
len); headers_, data, len);
} }
void FieldStore::append_last_header_value(const char *data, size_t len) { void FieldStore::append_last_header_value(const char *data, size_t len) {
shrpx::append_last_header_value(header_key_prev_, buffer_size_, headers_, shrpx::append_last_header_value(balloc_, header_key_prev_, buffer_size_,
data, len); headers_, data, len);
} }
void FieldStore::clear_headers() { headers_.clear(); } void FieldStore::clear_headers() { headers_.clear(); }
void FieldStore::add_trailer_lower(const StringRef &name,
const StringRef &value, bool no_index) {
auto low_name = name.str();
util::inp_strlower(low_name);
auto token = http2::lookup_token(low_name);
shrpx::add_header(trailer_key_prev_, buffer_size_, trailers_,
StringRef{low_name}, value, no_index, token);
}
void FieldStore::add_trailer_token(const StringRef &name, void FieldStore::add_trailer_token(const StringRef &name,
const StringRef &value, bool no_index, const StringRef &value, bool no_index,
int32_t token) { int32_t token) {
// Header size limit should be applied to all header and trailer // Header size limit should be applied to all header and trailer
// fields combined. // fields combined.
shrpx::add_header(buffer_size_, trailers_, name, value, no_index, token); shrpx::add_header(trailer_key_prev_, buffer_size_, trailers_, name, value,
no_index, token);
} }
void FieldStore::append_last_trailer_key(const char *data, size_t len) { void FieldStore::append_last_trailer_key(const char *data, size_t len) {
shrpx::append_last_header_key(trailer_key_prev_, buffer_size_, trailers_, shrpx::append_last_header_key(balloc_, trailer_key_prev_, buffer_size_,
data, len); trailers_, data, len);
} }
void FieldStore::append_last_trailer_value(const char *data, size_t len) { void FieldStore::append_last_trailer_value(const char *data, size_t len) {
shrpx::append_last_header_value(trailer_key_prev_, buffer_size_, trailers_, shrpx::append_last_header_value(balloc_, trailer_key_prev_, buffer_size_,
data, len); trailers_, data, len);
} }
void Downstream::set_request_start_time( void Downstream::set_request_start_time(
@ -543,7 +541,7 @@ int Downstream::end_upload_data() {
} }
void Downstream::rewrite_location_response_header( void Downstream::rewrite_location_response_header(
const std::string &upstream_scheme) { const StringRef &upstream_scheme) {
auto hd = resp_.fs.header(http2::HD_LOCATION); auto hd = resp_.fs.header(http2::HD_LOCATION);
if (!hd) { if (!hd) {
return; return;
@ -559,14 +557,15 @@ void Downstream::rewrite_location_response_header(
return; return;
} }
auto new_uri = http2::rewrite_location_uri( auto new_uri = http2::rewrite_location_uri(balloc_, hd->value, u,
hd->value, u, request_downstream_host_, req_.authority, upstream_scheme); request_downstream_host_,
req_.authority, upstream_scheme);
if (new_uri.empty()) { if (new_uri.empty()) {
return; return;
} }
hd->value = std::move(new_uri); hd->value = new_uri;
} }
bool Downstream::get_chunked_response() const { return chunked_response_; } bool Downstream::get_chunked_response() const { return chunked_response_; }
@ -703,10 +702,10 @@ bool Downstream::get_http2_upgrade_request() const {
response_state_ == INITIAL; response_state_ == INITIAL;
} }
const std::string &Downstream::get_http2_settings() const { StringRef Downstream::get_http2_settings() const {
auto http2_settings = req_.fs.header(http2::HD_HTTP2_SETTINGS); auto http2_settings = req_.fs.header(http2::HD_HTTP2_SETTINGS);
if (!http2_settings) { if (!http2_settings) {
return EMPTY_STRING; return StringRef{};
} }
return http2_settings->value; return http2_settings->value;
} }
@ -861,8 +860,8 @@ void Downstream::add_retry() { ++num_retry_; }
bool Downstream::no_more_retry() const { return num_retry_ > 5; } bool Downstream::no_more_retry() const { return num_retry_ > 5; }
void Downstream::set_request_downstream_host(std::string host) { void Downstream::set_request_downstream_host(const StringRef &host) {
request_downstream_host_ = std::move(host); request_downstream_host_ = host;
} }
void Downstream::set_request_pending(bool f) { request_pending_ = f; } void Downstream::set_request_pending(bool f) { request_pending_ = f; }
@ -908,4 +907,11 @@ void Downstream::set_assoc_stream_id(int32_t stream_id) {
int32_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; } int32_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; }
BlockAllocator &Downstream::get_block_allocator() { return balloc_; }
void Downstream::add_rcbuf(nghttp2_rcbuf *rcbuf) {
nghttp2_rcbuf_incref(rcbuf);
rcbufs_.push_back(rcbuf);
}
} // namespace shrpx } // namespace shrpx

View File

@ -40,6 +40,7 @@
#include "shrpx_io_control.h" #include "shrpx_io_control.h"
#include "http2.h" #include "http2.h"
#include "memchunk.h" #include "memchunk.h"
#include "allocator.h"
using namespace nghttp2; using namespace nghttp2;
@ -51,18 +52,19 @@ struct BlockedLink;
class FieldStore { class FieldStore {
public: public:
FieldStore(size_t headers_initial_capacity) FieldStore(BlockAllocator &balloc, size_t headers_initial_capacity)
: content_length(-1), : content_length(-1),
balloc_(balloc),
buffer_size_(0), buffer_size_(0),
header_key_prev_(false), header_key_prev_(false),
trailer_key_prev_(false) { trailer_key_prev_(false) {
headers_.reserve(headers_initial_capacity); headers_.reserve(headers_initial_capacity);
} }
const Headers &headers() const { return headers_; } const HeaderRefs &headers() const { return headers_; }
const Headers &trailers() const { return trailers_; } const HeaderRefs &trailers() const { return trailers_; }
Headers &headers() { return headers_; } HeaderRefs &headers() { return headers_; }
const void add_extra_buffer_size(size_t n) { buffer_size_ += n; } const void add_extra_buffer_size(size_t n) { buffer_size_ += n; }
size_t buffer_size() const { return buffer_size_; } size_t buffer_size() const { return buffer_size_; }
@ -73,14 +75,12 @@ public:
// multiple header have |name| as name, return last occurrence from // multiple header have |name| as name, return last occurrence from
// the beginning. If no such header is found, returns nullptr. // the beginning. If no such header is found, returns nullptr.
// This function must be called after headers are indexed // This function must be called after headers are indexed
const Headers::value_type *header(int32_t token) const; const HeaderRefs::value_type *header(int32_t token) const;
Headers::value_type *header(int32_t token); HeaderRefs::value_type *header(int32_t token);
// Returns pointer to the header field with the name |name|. If no // Returns pointer to the header field with the name |name|. If no
// such header is found, returns nullptr. // such header is found, returns nullptr.
const Headers::value_type *header(const StringRef &name) const; const HeaderRefs::value_type *header(const StringRef &name) const;
void add_header_lower(const StringRef &name, const StringRef &value,
bool no_index);
void add_header_token(const StringRef &name, const StringRef &value, void add_header_token(const StringRef &name, const StringRef &value,
bool no_index, int32_t token); bool no_index, int32_t token);
@ -96,8 +96,6 @@ public:
// Empties headers. // Empties headers.
void clear_headers(); void clear_headers();
void add_trailer_lower(const StringRef &name, const StringRef &value,
bool no_index);
void add_trailer_token(const StringRef &name, const StringRef &value, void add_trailer_token(const StringRef &name, const StringRef &value,
bool no_index, int32_t token); bool no_index, int32_t token);
@ -110,10 +108,11 @@ public:
int64_t content_length; int64_t content_length;
private: private:
Headers headers_; BlockAllocator &balloc_;
HeaderRefs headers_;
// trailer fields. For HTTP/1.1, trailer fields are only included // trailer fields. For HTTP/1.1, trailer fields are only included
// with chunked encoding. For HTTP/2, there is no such limit. // with chunked encoding. For HTTP/2, there is no such limit.
Headers trailers_; HeaderRefs trailers_;
// Sum of the length of name and value in headers_ and trailers_. // Sum of the length of name and value in headers_ and trailers_.
// This could also be increased by add_extra_buffer_size() to take // This could also be increased by add_extra_buffer_size() to take
// into account for request URI in case of HTTP/1.x request. // into account for request URI in case of HTTP/1.x request.
@ -123,8 +122,8 @@ private:
}; };
struct Request { struct Request {
Request() Request(BlockAllocator &balloc)
: fs(16), : fs(balloc, 16),
recv_body_length(0), recv_body_length(0),
unconsumed_body_length(0), unconsumed_body_length(0),
method(-1), method(-1),
@ -144,17 +143,17 @@ struct Request {
FieldStore fs; FieldStore fs;
// Request scheme. For HTTP/2, this is :scheme header field value. // Request scheme. For HTTP/2, this is :scheme header field value.
// For HTTP/1.1, this is deduced from URI or connection. // For HTTP/1.1, this is deduced from URI or connection.
std::string scheme; StringRef scheme;
// Request authority. This is HTTP/2 :authority header field value // Request authority. This is HTTP/2 :authority header field value
// or host header field value. We may deduce it from absolute-form // or host header field value. We may deduce it from absolute-form
// HTTP/1 request. We also store authority-form HTTP/1 request. // HTTP/1 request. We also store authority-form HTTP/1 request.
// This could be empty if request comes from HTTP/1.0 without Host // This could be empty if request comes from HTTP/1.0 without Host
// header field and origin-form. // header field and origin-form.
std::string authority; StringRef authority;
// Request path, including query component. For HTTP/1.1, this is // Request path, including query component. For HTTP/1.1, this is
// request-target. For HTTP/2, this is :path header field value. // request-target. For HTTP/2, this is :path header field value.
// For CONNECT request, this is empty. // For CONNECT request, this is empty.
std::string path; StringRef path;
// the length of request body received so far // the length of request body received so far
int64_t recv_body_length; int64_t recv_body_length;
// The number of bytes not consumed by the application yet. // The number of bytes not consumed by the application yet.
@ -179,8 +178,8 @@ struct Request {
}; };
struct Response { struct Response {
Response() Response(BlockAllocator &balloc)
: fs(32), : fs(balloc, 32),
recv_body_length(0), recv_body_length(0),
unconsumed_body_length(0), unconsumed_body_length(0),
http_status(0), http_status(0),
@ -245,7 +244,7 @@ public:
// Returns true if the request is HTTP Upgrade for HTTP/2 // Returns true if the request is HTTP Upgrade for HTTP/2
bool get_http2_upgrade_request() const; bool get_http2_upgrade_request() const;
// Returns the value of HTTP2-Settings request header field. // Returns the value of HTTP2-Settings request header field.
const std::string &get_http2_settings() const; StringRef get_http2_settings() const;
// downstream request API // downstream request API
const Request &request() const { return req_; } const Request &request() const { return req_; }
@ -272,7 +271,7 @@ public:
// Validates that received request body length and content-length // Validates that received request body length and content-length
// matches. // matches.
bool validate_request_recv_body_length() const; bool validate_request_recv_body_length() const;
void set_request_downstream_host(std::string host); void set_request_downstream_host(const StringRef &host);
bool expect_response_body() const; bool expect_response_body() const;
enum { enum {
INITIAL, INITIAL,
@ -303,7 +302,7 @@ public:
Response &response() { return resp_; } Response &response() { return resp_; }
// Rewrites the location response header field. // Rewrites the location response header field.
void rewrite_location_response_header(const std::string &upstream_scheme); void rewrite_location_response_header(const StringRef &upstream_scheme);
bool get_chunked_response() const; bool get_chunked_response() const;
void set_chunked_response(bool f); void set_chunked_response(bool f);
@ -373,6 +372,10 @@ public:
DefaultMemchunks pop_response_buf(); DefaultMemchunks pop_response_buf();
BlockAllocator &get_block_allocator();
void add_rcbuf(nghttp2_rcbuf *rcbuf);
enum { enum {
EVENT_ERROR = 0x1, EVENT_ERROR = 0x1,
EVENT_TIMEOUT = 0x2, EVENT_TIMEOUT = 0x2,
@ -392,6 +395,10 @@ public:
int64_t response_sent_body_length; int64_t response_sent_body_length;
private: private:
BlockAllocator balloc_;
std::vector<nghttp2_rcbuf *> rcbufs_;
Request req_; Request req_;
Response resp_; Response resp_;
@ -400,7 +407,7 @@ private:
// host we requested to downstream. This is used to rewrite // host we requested to downstream. This is used to rewrite
// location header field to decide the location should be rewritten // location header field to decide the location should be rewritten
// or not. // or not.
std::string request_downstream_host_; StringRef request_downstream_host_;
DefaultMemchunks request_buf_; DefaultMemchunks request_buf_;
DefaultMemchunks response_buf_; DefaultMemchunks response_buf_;

View File

@ -71,14 +71,11 @@ DownstreamQueue::find_host_entry(const std::string &host) {
return (*itr).second; return (*itr).second;
} }
const std::string & std::string DownstreamQueue::make_host_key(const StringRef &host) const {
DownstreamQueue::make_host_key(const std::string &host) const { return unified_host_ ? "" : host.str();
static std::string empty_key;
return unified_host_ ? empty_key : host;
} }
const std::string & std::string DownstreamQueue::make_host_key(Downstream *downstream) const {
DownstreamQueue::make_host_key(Downstream *downstream) const {
return make_host_key(downstream->request().authority); return make_host_key(downstream->request().authority);
} }
@ -99,7 +96,7 @@ void DownstreamQueue::mark_blocked(Downstream *downstream) {
ent.blocked.append(link); ent.blocked.append(link);
} }
bool DownstreamQueue::can_activate(const std::string &host) const { bool DownstreamQueue::can_activate(const StringRef &host) const {
auto itr = host_entries_.find(make_host_key(host)); auto itr = host_entries_.find(make_host_key(host));
if (itr == std::end(host_entries_)) { if (itr == std::end(host_entries_)) {
return true; return true;
@ -127,7 +124,7 @@ Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream,
downstreams_.remove(downstream); downstreams_.remove(downstream);
auto &host = make_host_key(downstream); auto host = make_host_key(downstream);
auto &ent = find_host_entry(host); auto &ent = find_host_entry(host);
if (downstream->get_dispatch_state() == Downstream::DISPATCH_ACTIVE) { if (downstream->get_dispatch_state() == Downstream::DISPATCH_ACTIVE) {

View File

@ -77,7 +77,7 @@ public:
void mark_blocked(Downstream *downstream); void mark_blocked(Downstream *downstream);
// Returns true if we can make downstream connection to given // Returns true if we can make downstream connection to given
// |host|. // |host|.
bool can_activate(const std::string &host) const; bool can_activate(const StringRef &host) const;
// Removes and frees |downstream| object. If |downstream| is in // Removes and frees |downstream| object. If |downstream| is in
// Downstream::DISPATCH_ACTIVE, and |next_blocked| is true, this // Downstream::DISPATCH_ACTIVE, and |next_blocked| is true, this
// function may return Downstream object with the same target host // function may return Downstream object with the same target host
@ -87,8 +87,8 @@ public:
bool next_blocked = true); bool next_blocked = true);
Downstream *get_downstreams() const; Downstream *get_downstreams() const;
HostEntry &find_host_entry(const std::string &host); HostEntry &find_host_entry(const std::string &host);
const std::string &make_host_key(const std::string &host) const; std::string make_host_key(const StringRef &host) const;
const std::string &make_host_key(Downstream *downstream) const; std::string make_host_key(Downstream *downstream) const;
private: private:
// Per target host structure to keep track of the number of // Per target host structure to keep track of the number of

View File

@ -32,38 +32,28 @@
namespace shrpx { namespace shrpx {
void test_downstream_field_store_add_header_lower(void) { void test_downstream_field_store_append_last_header(void) {
FieldStore fs(0); BlockAllocator balloc(4096, 4096);
fs.add_header_lower(StringRef::from_lit("1"), StringRef::from_lit("0"), FieldStore fs(balloc, 0);
false); fs.add_header_token(StringRef::from_lit("alpha"), StringRef{}, false, -1);
fs.add_header_lower(StringRef::from_lit("2"), StringRef::from_lit("1"), auto bravo = StringRef::from_lit("BRAVO");
false); fs.append_last_header_key(bravo.c_str(), bravo.size());
fs.add_header_lower(StringRef::from_lit("Charlie"), StringRef::from_lit("2"), auto charlie = StringRef::from_lit("Charlie");
false); fs.append_last_header_value(charlie.c_str(), charlie.size());
fs.add_header_lower(StringRef::from_lit("Alpha"), StringRef::from_lit("3"), auto delta = StringRef::from_lit("deltA");
false); fs.append_last_header_value(delta.c_str(), delta.size());
fs.add_header_lower(StringRef::from_lit("Delta"), StringRef::from_lit("4"), fs.add_header_token(StringRef::from_lit("echo"),
false); StringRef::from_lit("foxtrot"), false, -1);
fs.add_header_lower(StringRef::from_lit("BravO"), StringRef::from_lit("5"),
false);
fs.add_header_lower(StringRef::from_lit(":method"), StringRef::from_lit("6"),
false);
fs.add_header_lower(StringRef::from_lit(":authority"),
StringRef::from_lit("7"), false);
auto ans = Headers{{"1", "0"}, auto ans = HeaderRefs{
{"2", "1"}, {StringRef::from_lit("alphabravo"), StringRef::from_lit("CharliedeltA")},
{"charlie", "2"}, {StringRef::from_lit("echo"), StringRef::from_lit("foxtrot")}};
{"alpha", "3"},
{"delta", "4"},
{"bravo", "5"},
{":method", "6"},
{":authority", "7"}};
CU_ASSERT(ans == fs.headers()); CU_ASSERT(ans == fs.headers());
} }
void test_downstream_field_store_header(void) { void test_downstream_field_store_header(void) {
FieldStore fs(0); BlockAllocator balloc(4096, 4096);
FieldStore fs(balloc, 0);
fs.add_header_token(StringRef::from_lit("alpha"), StringRef::from_lit("0"), fs.add_header_token(StringRef::from_lit("alpha"), StringRef::from_lit("0"),
false, -1); false, -1);
fs.add_header_token(StringRef::from_lit(":authority"), fs.add_header_token(StringRef::from_lit(":authority"),
@ -73,11 +63,13 @@ void test_downstream_field_store_header(void) {
http2::HD_CONTENT_LENGTH); http2::HD_CONTENT_LENGTH);
// By token // By token
CU_ASSERT(Header(":authority", "1") == *fs.header(http2::HD__AUTHORITY)); CU_ASSERT(HeaderRef(StringRef{":authority"}, StringRef{"1"}) ==
*fs.header(http2::HD__AUTHORITY));
CU_ASSERT(nullptr == fs.header(http2::HD__METHOD)); CU_ASSERT(nullptr == fs.header(http2::HD__METHOD));
// By name // By name
CU_ASSERT(Header("alpha", "0") == *fs.header(StringRef::from_lit("alpha"))); CU_ASSERT(HeaderRef(StringRef{"alpha"}, StringRef{"0"}) ==
*fs.header(StringRef::from_lit("alpha")));
CU_ASSERT(nullptr == fs.header(StringRef::from_lit("bravo"))); CU_ASSERT(nullptr == fs.header(StringRef::from_lit("bravo")));
} }
@ -105,19 +97,20 @@ void test_downstream_crumble_request_cookie(void) {
CU_ASSERT(5 == nva.size()); CU_ASSERT(5 == nva.size());
CU_ASSERT(5 == num_cookies); CU_ASSERT(5 == num_cookies);
Headers cookies; HeaderRefs cookies;
std::transform(std::begin(nva), std::end(nva), std::back_inserter(cookies), std::transform(std::begin(nva), std::end(nva), std::back_inserter(cookies),
[](const nghttp2_nv &nv) { [](const nghttp2_nv &nv) {
return Header(std::string(nv.name, nv.name + nv.namelen), return HeaderRef(StringRef{nv.name, nv.namelen},
std::string(nv.value, nv.value + nv.valuelen), StringRef{nv.value, nv.valuelen},
nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
}); });
Headers ans = {{"cookie", "alpha"}, HeaderRefs ans = {
{"cookie", "bravo"}, {StringRef::from_lit("cookie"), StringRef::from_lit("alpha")},
{"cookie", "charlie"}, {StringRef::from_lit("cookie"), StringRef::from_lit("bravo")},
{"cookie", "delta"}, {StringRef::from_lit("cookie"), StringRef::from_lit("charlie")},
{"cookie", "echo"}}; {StringRef::from_lit("cookie"), StringRef::from_lit("delta")},
{StringRef::from_lit("cookie"), StringRef::from_lit("echo")}};
CU_ASSERT(ans == cookies); CU_ASSERT(ans == cookies);
CU_ASSERT(cookies[0].no_index); CU_ASSERT(cookies[0].no_index);
@ -128,6 +121,7 @@ void test_downstream_crumble_request_cookie(void) {
void test_downstream_assemble_request_cookie(void) { void test_downstream_assemble_request_cookie(void) {
Downstream d(nullptr, nullptr, 0); Downstream d(nullptr, nullptr, 0);
auto &req = d.request(); auto &req = d.request();
req.fs.add_header_token(StringRef::from_lit(":method"), req.fs.add_header_token(StringRef::from_lit(":method"),
StringRef::from_lit("get"), false, -1); StringRef::from_lit("get"), false, -1);
req.fs.add_header_token(StringRef::from_lit(":path"), req.fs.add_header_token(StringRef::from_lit(":path"),
@ -151,12 +145,12 @@ void test_downstream_rewrite_location_response_header(void) {
Downstream d(nullptr, nullptr, 0); Downstream d(nullptr, nullptr, 0);
auto &req = d.request(); auto &req = d.request();
auto &resp = d.response(); auto &resp = d.response();
d.set_request_downstream_host("localhost2"); d.set_request_downstream_host(StringRef::from_lit("localhost2"));
req.authority = "localhost:8443"; req.authority = StringRef::from_lit("localhost:8443");
resp.fs.add_header_token(StringRef::from_lit("location"), resp.fs.add_header_token(StringRef::from_lit("location"),
StringRef::from_lit("http://localhost2:3000/"), StringRef::from_lit("http://localhost2:3000/"),
false, http2::HD_LOCATION); false, http2::HD_LOCATION);
d.rewrite_location_response_header("https"); d.rewrite_location_response_header(StringRef::from_lit("https"));
auto location = resp.fs.header(http2::HD_LOCATION); auto location = resp.fs.header(http2::HD_LOCATION);
CU_ASSERT("https://localhost:8443/" == (*location).value); CU_ASSERT("https://localhost:8443/" == (*location).value);
} }

View File

@ -31,7 +31,7 @@
namespace shrpx { namespace shrpx {
void test_downstream_field_store_add_header_lower(void); void test_downstream_field_store_append_last_header(void);
void test_downstream_field_store_header(void); void test_downstream_field_store_header(void);
void test_downstream_crumble_request_cookie(void); void test_downstream_crumble_request_cookie(void);
void test_downstream_assemble_request_cookie(void); void test_downstream_assemble_request_cookie(void);

View File

@ -50,69 +50,75 @@ std::string create_error_html(unsigned int status_code) {
return res; return res;
} }
std::string create_via_header_value(int major, int minor) { StringRef create_forwarded(BlockAllocator &balloc, int params,
std::string hdrs; const StringRef &node_by, const StringRef &node_for,
hdrs += static_cast<char>(major + '0'); const StringRef &host, const StringRef &proto) {
if (major < 2) { size_t len = 0;
hdrs += '.'; if ((params & FORWARDED_BY) && !node_by.empty()) {
hdrs += static_cast<char>(minor + '0'); len += str_size("by=\"") + node_by.size() + str_size("\";");
}
if ((params & FORWARDED_FOR) && !node_for.empty()) {
len += str_size("for=\"") + node_for.size() + str_size("\";");
}
if ((params & FORWARDED_HOST) && !host.empty()) {
len += str_size("host=\"") + host.size() + str_size("\";");
}
if ((params & FORWARDED_PROTO) && !proto.empty()) {
len += str_size("proto=") + proto.size() + str_size(";");
} }
hdrs += " nghttpx";
return hdrs;
}
std::string create_forwarded(int params, const StringRef &node_by, auto iov = make_byte_ref(balloc, len + 1);
const StringRef &node_for, const StringRef &host, auto p = iov.base;
const StringRef &proto) {
std::string res;
if ((params & FORWARDED_BY) && !node_by.empty()) { if ((params & FORWARDED_BY) && !node_by.empty()) {
// This must be quoted-string unless it is obfuscated version // This must be quoted-string unless it is obfuscated version
// (which starts with "_") or some special value (e.g., // (which starts with "_") or some special value (e.g.,
// "localhost" for UNIX domain socket), since ':' is not allowed // "localhost" for UNIX domain socket), since ':' is not allowed
// in token. ':' is used to separate host and port. // in token. ':' is used to separate host and port.
if (node_by[0] == '_' || node_by[0] == 'l') { if (node_by[0] == '_' || node_by[0] == 'l') {
res += "by="; p = util::copy_lit(p, "by=");
res += node_by; p = std::copy(std::begin(node_by), std::end(node_by), p);
res += ";"; p = util::copy_lit(p, ";");
} else { } else {
res += "by=\""; p = util::copy_lit(p, "by=\"");
res += node_by; p = std::copy(std::begin(node_by), std::end(node_by), p);
res += "\";"; p = util::copy_lit(p, "\";");
} }
} }
if ((params & FORWARDED_FOR) && !node_for.empty()) { if ((params & FORWARDED_FOR) && !node_for.empty()) {
// We only quote IPv6 literal address only, which starts with '['. // We only quote IPv6 literal address only, which starts with '['.
if (node_for[0] == '[') { if (node_for[0] == '[') {
res += "for=\""; p = util::copy_lit(p, "for=\"");
res += node_for; p = std::copy(std::begin(node_for), std::end(node_for), p);
res += "\";"; p = util::copy_lit(p, "\";");
} else { } else {
res += "for="; p = util::copy_lit(p, "for=");
res += node_for; p = std::copy(std::begin(node_for), std::end(node_for), p);
res += ";"; p = util::copy_lit(p, ";");
} }
} }
if ((params & FORWARDED_HOST) && !host.empty()) { if ((params & FORWARDED_HOST) && !host.empty()) {
// Just be quoted to skip checking characters. // Just be quoted to skip checking characters.
res += "host=\""; p = util::copy_lit(p, "host=\"");
res += host; p = std::copy(std::begin(host), std::end(host), p);
res += "\";"; p = util::copy_lit(p, "\";");
} }
if ((params & FORWARDED_PROTO) && !proto.empty()) { if ((params & FORWARDED_PROTO) && !proto.empty()) {
// Scheme production rule only allow characters which are all in // Scheme production rule only allow characters which are all in
// token. // token.
res += "proto="; p = util::copy_lit(p, "proto=");
res += proto; p = std::copy(std::begin(proto), std::end(proto), p);
res += ";"; *p++ = ';';
} }
if (res.empty()) { if (iov.base == p) {
return res; return StringRef{};
} }
res.erase(res.size() - 1); --p;
*p = '\0';
return res; return StringRef{iov.base, p};
} }
std::string colorizeHeaders(const char *hdrs) { std::string colorizeHeaders(const char *hdrs) {

View File

@ -31,20 +31,31 @@
#include <nghttp2/nghttp2.h> #include <nghttp2/nghttp2.h>
#include "util.h"
#include "allocator.h"
namespace shrpx { namespace shrpx {
namespace http { namespace http {
std::string create_error_html(unsigned int status_code); std::string create_error_html(unsigned int status_code);
std::string create_via_header_value(int major, int minor); template <typename OutputIt>
OutputIt create_via_header_value(OutputIt dst, int major, int minor) {
*dst++ = static_cast<char>(major + '0');
if (major < 2) {
*dst++ = '.';
*dst++ = static_cast<char>(minor + '0');
}
return util::copy_lit(dst, " nghttpx");
}
// Returns generated RFC 7239 Forwarded header field value. The // Returns generated RFC 7239 Forwarded header field value. The
// |params| is bitwise-OR of zero or more of shrpx_forwarded_param // |params| is bitwise-OR of zero or more of shrpx_forwarded_param
// defined in shrpx_config.h. // defined in shrpx_config.h.
std::string create_forwarded(int params, const StringRef &node_by, StringRef create_forwarded(BlockAllocator &balloc, int params,
const StringRef &node_for, const StringRef &host, const StringRef &node_by, const StringRef &node_for,
const StringRef &proto); const StringRef &host, const StringRef &proto);
// Adds ANSI color codes to HTTP headers |hdrs|. // Adds ANSI color codes to HTTP headers |hdrs|.
std::string colorizeHeaders(const char *hdrs); std::string colorizeHeaders(const char *hdrs);

View File

@ -266,6 +266,8 @@ int Http2DownstreamConnection::push_request_headers() {
const auto &req = downstream_->request(); const auto &req = downstream_->request();
auto &balloc = downstream_->get_block_allocator();
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
auto &http2conf = get_config()->http2; auto &http2conf = get_config()->http2;
@ -282,10 +284,10 @@ int Http2DownstreamConnection::push_request_headers() {
auto authority = StringRef(downstream_hostport); auto authority = StringRef(downstream_hostport);
if (no_host_rewrite && !req.authority.empty()) { if (no_host_rewrite && !req.authority.empty()) {
authority = StringRef(req.authority); authority = req.authority;
} }
downstream_->set_request_downstream_host(authority.str()); downstream_->set_request_downstream_host(authority);
size_t num_cookies = 0; size_t num_cookies = 0;
if (!http2conf.no_cookie_crumbling) { if (!http2conf.no_cookie_crumbling) {
@ -345,8 +347,6 @@ int Http2DownstreamConnection::push_request_headers() {
auto upstream = downstream_->get_upstream(); auto upstream = downstream_->get_upstream();
auto handler = upstream->get_client_handler(); auto handler = upstream->get_client_handler();
std::string forwarded_value;
auto &fwdconf = httpconf.forwarded; auto &fwdconf = httpconf.forwarded;
auto fwd = auto fwd =
@ -360,24 +360,23 @@ int Http2DownstreamConnection::push_request_headers() {
} }
auto value = http::create_forwarded( auto value = http::create_forwarded(
params, handler->get_forwarded_by(), handler->get_forwarded_for(), balloc, params, handler->get_forwarded_by(),
StringRef{req.authority}, StringRef{req.scheme}); handler->get_forwarded_for(), req.authority, req.scheme);
if (fwd || !value.empty()) { if (fwd || !value.empty()) {
if (fwd) { if (fwd) {
forwarded_value = fwd->value; if (value.empty()) {
value = fwd->value;
if (!value.empty()) { } else {
forwarded_value += ", "; value = concat_string_ref(balloc, fwd->value,
StringRef::from_lit(", "), value);
} }
} }
forwarded_value += value; nva.push_back(http2::make_nv_ls_nocopy("forwarded", value));
nva.push_back(http2::make_nv_ls("forwarded", forwarded_value));
} }
} else if (fwd) { } else if (fwd) {
nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value)); nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value));
forwarded_value = fwd->value;
} }
auto &xffconf = httpconf.xff; auto &xffconf = httpconf.xff;
@ -385,17 +384,18 @@ int Http2DownstreamConnection::push_request_headers() {
auto xff = xffconf.strip_incoming ? nullptr auto xff = xffconf.strip_incoming ? nullptr
: req.fs.header(http2::HD_X_FORWARDED_FOR); : req.fs.header(http2::HD_X_FORWARDED_FOR);
std::string xff_value;
if (xffconf.add) { if (xffconf.add) {
StringRef xff_value;
auto addr = StringRef{upstream->get_client_handler()->get_ipaddr()};
if (xff) { if (xff) {
xff_value = (*xff).value; xff_value = concat_string_ref(balloc, xff->value,
xff_value += ", "; StringRef::from_lit(", "), addr);
} else {
xff_value = addr;
} }
xff_value += upstream->get_client_handler()->get_ipaddr(); nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff_value));
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
} else if (xff) { } else if (xff) {
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", (*xff).value)); nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value));
} }
if (!get_config()->http2_proxy && req.method != HTTP_CONNECT) { if (!get_config()->http2_proxy && req.method != HTTP_CONNECT) {
@ -403,19 +403,28 @@ int Http2DownstreamConnection::push_request_headers() {
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", req.scheme)); nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", req.scheme));
} }
std::string via_value;
auto via = req.fs.header(http2::HD_VIA); auto via = req.fs.header(http2::HD_VIA);
if (httpconf.no_via) { if (httpconf.no_via) {
if (via) { if (via) {
nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value)); nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
} }
} else { } else {
size_t vialen = 16;
if (via) { if (via) {
via_value = (*via).value; vialen += via->value.size() + 2;
via_value += ", ";
} }
via_value += http::create_via_header_value(req.http_major, req.http_minor);
nva.push_back(http2::make_nv_ls("via", via_value)); auto iov = make_byte_ref(balloc, vialen + 1);
auto p = iov.base;
if (via) {
p = std::copy(std::begin(via->value), std::end(via->value), p);
p = util::copy_lit(p, ", ");
}
p = http::create_via_header_value(p, req.http_major, req.http_minor);
*p = '\0';
nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
} }
auto te = req.fs.header(http2::HD_TE); auto te = req.fs.header(http2::HD_TE);

View File

@ -801,10 +801,9 @@ void Http2Session::stop_settings_timer() {
} }
namespace { namespace {
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen, nghttp2_rcbuf *name, nghttp2_rcbuf *value,
const uint8_t *value, size_t valuelen, uint8_t flags, uint8_t flags, void *user_data) {
void *user_data) {
auto http2session = static_cast<Http2Session *>(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));
@ -816,6 +815,9 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0; return 0;
} }
auto namebuf = nghttp2_rcbuf_get_buf(name);
auto valuebuf = nghttp2_rcbuf_get_buf(value);
auto &resp = downstream->response(); auto &resp = downstream->response();
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
@ -824,13 +826,14 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS && auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
!downstream->get_expect_final_response(); !downstream->get_expect_final_response();
if (resp.fs.buffer_size() + namelen + valuelen > if (resp.fs.buffer_size() + namebuf.len + valuebuf.len >
httpconf.response_header_field_buffer || httpconf.response_header_field_buffer ||
resp.fs.num_fields() >= httpconf.max_response_header_fields) { resp.fs.num_fields() >= httpconf.max_response_header_fields) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large or many header field size=" DLOG(INFO, downstream)
<< resp.fs.buffer_size() + namelen + valuelen << "Too large or many header field size="
<< ", num=" << resp.fs.num_fields() + 1; << resp.fs.buffer_size() + namebuf.len + valuebuf.len
<< ", num=" << resp.fs.num_fields() + 1;
} }
if (trailer) { if (trailer) {
@ -842,18 +845,23 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
} }
auto token = http2::lookup_token(name, namelen); auto token = http2::lookup_token(namebuf.base, namebuf.len);
auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX; auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX;
downstream->add_rcbuf(name);
downstream->add_rcbuf(value);
if (trailer) { if (trailer) {
// just store header fields for trailer part // just store header fields for trailer part
resp.fs.add_trailer_token(StringRef{name, namelen}, resp.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
StringRef{value, valuelen}, no_index, token); StringRef{valuebuf.base, valuebuf.len},
no_index, token);
return 0; return 0;
} }
resp.fs.add_header_token(StringRef{name, namelen}, resp.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
StringRef{value, valuelen}, no_index, token); StringRef{valuebuf.base, valuebuf.len}, no_index,
token);
return 0; return 0;
} }
case NGHTTP2_PUSH_PROMISE: { case NGHTTP2_PUSH_PROMISE: {
@ -867,28 +875,35 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
auto promised_downstream = promised_sd->dconn->get_downstream(); auto promised_downstream = promised_sd->dconn->get_downstream();
auto namebuf = nghttp2_rcbuf_get_buf(name);
auto valuebuf = nghttp2_rcbuf_get_buf(value);
assert(promised_downstream); assert(promised_downstream);
auto &promised_req = promised_downstream->request(); auto &promised_req = promised_downstream->request();
// We use request header limit for PUSH_PROMISE // We use request header limit for PUSH_PROMISE
if (promised_req.fs.buffer_size() + namelen + valuelen > if (promised_req.fs.buffer_size() + namebuf.len + valuebuf.len >
httpconf.request_header_field_buffer || httpconf.request_header_field_buffer ||
promised_req.fs.num_fields() >= httpconf.max_request_header_fields) { promised_req.fs.num_fields() >= httpconf.max_request_header_fields) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) DLOG(INFO, downstream)
<< "Too large or many header field size=" << "Too large or many header field size="
<< promised_req.fs.buffer_size() + namelen + valuelen << promised_req.fs.buffer_size() + namebuf.len + valuebuf.len
<< ", num=" << promised_req.fs.num_fields() + 1; << ", num=" << promised_req.fs.num_fields() + 1;
} }
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
} }
auto token = http2::lookup_token(name, namelen); promised_downstream->add_rcbuf(name);
promised_req.fs.add_header_token(StringRef{name, namelen}, promised_downstream->add_rcbuf(value);
StringRef{value, valuelen},
auto token = http2::lookup_token(namebuf.base, namebuf.len);
promised_req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
StringRef{valuebuf.base, valuebuf.len},
flags & NGHTTP2_NV_FLAG_NO_INDEX, token); flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0; return 0;
} }
} }
@ -1396,8 +1411,8 @@ nghttp2_session_callbacks *create_http2_downstream_callbacks() {
nghttp2_session_callbacks_set_on_frame_not_send_callback( nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, on_frame_not_send_callback); callbacks, on_frame_not_send_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks, nghttp2_session_callbacks_set_on_header_callback2(callbacks,
on_header_callback); on_header_callback2);
nghttp2_session_callbacks_set_on_begin_headers_callback( nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback); callbacks, on_begin_headers_callback);
@ -1986,6 +2001,8 @@ int Http2Session::handle_downstream_push_promise_complete(
Downstream *downstream, Downstream *promised_downstream) { Downstream *downstream, Downstream *promised_downstream) {
auto &promised_req = promised_downstream->request(); auto &promised_req = promised_downstream->request();
auto &promised_balloc = promised_downstream->get_block_allocator();
auto authority = promised_req.fs.header(http2::HD__AUTHORITY); auto authority = promised_req.fs.header(http2::HD__AUTHORITY);
auto path = promised_req.fs.header(http2::HD__PATH); auto path = promised_req.fs.header(http2::HD__PATH);
auto method = promised_req.fs.header(http2::HD__METHOD); auto method = promised_req.fs.header(http2::HD__METHOD);
@ -2007,16 +2024,19 @@ int Http2Session::handle_downstream_push_promise_complete(
// TODO Rewrite authority if we enabled rewrite host. But we // TODO Rewrite authority if we enabled rewrite host. But we
// really don't know how to rewrite host. Should we use the same // really don't know how to rewrite host. Should we use the same
// host in associated stream? // host in associated stream?
promised_req.authority = http2::value_to_str(authority); if (authority) {
promised_req.authority = authority->value;
}
promised_req.method = method_token; promised_req.method = method_token;
// libnghttp2 ensures that we don't have CONNECT method in // libnghttp2 ensures that we don't have CONNECT method in
// PUSH_PROMISE, and guarantees that :scheme exists. // PUSH_PROMISE, and guarantees that :scheme exists.
promised_req.scheme = http2::value_to_str(scheme); if (scheme) {
promised_req.scheme = scheme->value;
}
// For server-wide OPTIONS request, path is empty. // For server-wide OPTIONS request, path is empty.
if (method_token != HTTP_OPTIONS || path->value != "*") { if (method_token != HTTP_OPTIONS || path->value != "*") {
promised_req.path = http2::rewrite_clean_path(std::begin(path->value), promised_req.path = http2::rewrite_clean_path(promised_balloc, path->value);
std::end(path->value));
} }
promised_downstream->inspect_http2_request(); promised_downstream->inspect_http2_request();

View File

@ -108,7 +108,7 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
int Http2Upstream::upgrade_upstream(HttpsUpstream *http) { int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
int rv; int rv;
auto http2_settings = http->get_downstream()->get_http2_settings(); auto http2_settings = http->get_downstream()->get_http2_settings().str();
util::to_base64(http2_settings); util::to_base64(http2_settings);
auto settings_payload = auto settings_payload =
@ -154,13 +154,15 @@ void Http2Upstream::stop_settings_timer() {
} }
namespace { namespace {
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen, nghttp2_rcbuf *name, nghttp2_rcbuf *value,
const uint8_t *value, size_t valuelen, uint8_t flags, uint8_t flags, void *user_data) {
void *user_data) { auto namebuf = nghttp2_rcbuf_get_buf(name);
auto valuebuf = nghttp2_rcbuf_get_buf(value);
if (get_config()->http2.upstream.debug.frame_debug) { if (get_config()->http2.upstream.debug.frame_debug) {
verbose_on_header_callback(session, frame, name, namelen, value, valuelen, verbose_on_header_callback(session, frame, namebuf.base, namebuf.len,
flags, user_data); valuebuf.base, valuebuf.len, flags, user_data);
} }
if (frame->hd.type != NGHTTP2_HEADERS) { if (frame->hd.type != NGHTTP2_HEADERS) {
return 0; return 0;
@ -176,7 +178,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
if (req.fs.buffer_size() + namelen + valuelen > if (req.fs.buffer_size() + namebuf.len + valuebuf.len >
httpconf.request_header_field_buffer || httpconf.request_header_field_buffer ||
req.fs.num_fields() >= httpconf.max_request_header_fields) { req.fs.num_fields() >= httpconf.max_request_header_fields) {
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
@ -185,7 +187,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large or many header field size=" ULOG(INFO, upstream) << "Too large or many header field size="
<< req.fs.buffer_size() + namelen + valuelen << req.fs.buffer_size() + namebuf.len + valuebuf.len
<< ", num=" << req.fs.num_fields() + 1; << ", num=" << req.fs.num_fields() + 1;
} }
@ -201,18 +203,23 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0; return 0;
} }
auto token = http2::lookup_token(name, namelen); auto token = http2::lookup_token(namebuf.base, namebuf.len);
auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX; auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX;
downstream->add_rcbuf(name);
downstream->add_rcbuf(value);
if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
// just store header fields for trailer part // just store header fields for trailer part
req.fs.add_trailer_token(StringRef{name, namelen}, req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
StringRef{value, valuelen}, no_index, token); StringRef{valuebuf.base, valuebuf.len}, no_index,
token);
return 0; return 0;
} }
req.fs.add_header_token(StringRef{name, namelen}, StringRef{value, valuelen}, req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
no_index, token); StringRef{valuebuf.base, valuebuf.len}, no_index,
token);
return 0; return 0;
} }
} // namespace } // namespace
@ -303,7 +310,9 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
} }
req.method = method_token; req.method = method_token;
req.scheme = http2::value_to_str(scheme); if (scheme) {
req.scheme = scheme->value;
}
// nghttp2 library guarantees either :authority or host exist // nghttp2 library guarantees either :authority or host exist
if (!authority) { if (!authority) {
@ -311,16 +320,18 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
authority = req.fs.header(http2::HD_HOST); authority = req.fs.header(http2::HD_HOST);
} }
req.authority = http2::value_to_str(authority); if (authority) {
req.authority = authority->value;
}
if (path) { if (path) {
if (method_token == HTTP_OPTIONS && path->value == "*") { if (method_token == HTTP_OPTIONS && path->value == "*") {
// Server-wide OPTIONS request. Path is empty. // Server-wide OPTIONS request. Path is empty.
} else if (get_config()->http2_proxy) { } else if (get_config()->http2_proxy) {
req.path = http2::value_to_str(path); req.path = path->value;
} else { } else {
const auto &value = path->value; req.path = http2::rewrite_clean_path(downstream->get_block_allocator(),
req.path = http2::rewrite_clean_path(std::begin(value), std::end(value)); path->value);
} }
} }
@ -580,26 +591,33 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
req.http_major = 2; req.http_major = 2;
req.http_minor = 0; req.http_minor = 0;
auto &promised_balloc = promised_downstream->get_block_allocator();
for (size_t i = 0; i < frame->push_promise.nvlen; ++i) { for (size_t i = 0; i < frame->push_promise.nvlen; ++i) {
auto &nv = frame->push_promise.nva[i]; auto &nv = frame->push_promise.nva[i];
auto name =
make_string_ref(promised_balloc, StringRef{nv.name, nv.namelen});
auto value =
make_string_ref(promised_balloc, StringRef{nv.value, nv.valuelen});
auto token = http2::lookup_token(nv.name, nv.namelen); auto token = http2::lookup_token(nv.name, nv.namelen);
switch (token) { switch (token) {
case http2::HD__METHOD: case http2::HD__METHOD:
req.method = http2::lookup_method_token(nv.value, nv.valuelen); req.method = http2::lookup_method_token(value);
break; break;
case http2::HD__SCHEME: case http2::HD__SCHEME:
req.scheme.assign(nv.value, nv.value + nv.valuelen); req.scheme = value;
break; break;
case http2::HD__AUTHORITY: case http2::HD__AUTHORITY:
req.authority.assign(nv.value, nv.value + nv.valuelen); req.authority = value;
break; break;
case http2::HD__PATH: case http2::HD__PATH:
req.path = http2::rewrite_clean_path(nv.value, nv.value + nv.valuelen); req.path = http2::rewrite_clean_path(promised_balloc, value);
break; break;
} }
req.fs.add_header_token(StringRef{nv.name, nv.namelen}, req.fs.add_header_token(name, value, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX,
StringRef{nv.value, nv.valuelen}, token);
nv.flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
} }
promised_downstream->inspect_http2_request(); promised_downstream->inspect_http2_request();
@ -805,8 +823,8 @@ nghttp2_session_callbacks *create_http2_upstream_callbacks() {
nghttp2_session_callbacks_set_on_frame_not_send_callback( nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, on_frame_not_send_callback); callbacks, on_frame_not_send_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks, nghttp2_session_callbacks_set_on_header_callback2(callbacks,
on_header_callback); on_header_callback2);
nghttp2_session_callbacks_set_on_begin_headers_callback( nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback); callbacks, on_begin_headers_callback);
@ -1211,20 +1229,20 @@ int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
const auto &resp = downstream->response(); const auto &resp = downstream->response();
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
auto &balloc = downstream->get_block_allocator();
const auto &headers = resp.fs.headers(); const auto &headers = resp.fs.headers();
auto nva = std::vector<nghttp2_nv>(); auto nva = std::vector<nghttp2_nv>();
// 2 for :status and server // 2 for :status and server
nva.reserve(2 + headers.size() + httpconf.add_response_headers.size()); nva.reserve(2 + headers.size() + httpconf.add_response_headers.size());
std::string status_code_str; auto response_status = http2::stringify_status(resp.http_status);
auto response_status_const = http2::stringify_status(resp.http_status); if (response_status.empty()) {
if (response_status_const) { response_status = util::make_string_ref_uint(balloc, resp.http_status);
nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const));
} else {
status_code_str = util::utos(resp.http_status);
nva.push_back(http2::make_nv_ls(":status", status_code_str));
} }
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
for (auto &kv : headers) { for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') { if (kv.name.empty() || kv.name[0] == ':') {
continue; continue;
@ -1272,6 +1290,8 @@ int Http2Upstream::error_reply(Downstream *downstream,
int rv; int rv;
auto &resp = downstream->response(); auto &resp = downstream->response();
auto &balloc = downstream->get_block_allocator();
auto html = http::create_error_html(status_code); auto html = http::create_error_html(status_code);
resp.http_status = status_code; resp.http_status = status_code;
auto body = downstream->get_response_buf(); auto body = downstream->get_response_buf();
@ -1285,20 +1305,20 @@ int Http2Upstream::error_reply(Downstream *downstream,
auto lgconf = log_config(); auto lgconf = log_config();
lgconf->update_tstamp(std::chrono::system_clock::now()); lgconf->update_tstamp(std::chrono::system_clock::now());
auto response_status_const = http2::stringify_status(status_code); auto response_status = http2::stringify_status(status_code);
auto content_length = util::utos(html.size()); if (response_status.empty()) {
response_status = util::make_string_ref_uint(balloc, status_code);
}
std::string status_code_str; auto content_length = util::make_string_ref_uint(balloc, html.size());
auto date = make_string_ref(balloc, StringRef{lgconf->time_http_str});
auto nva = make_array( auto nva = std::array<nghttp2_nv, 5>{
response_status_const {http2::make_nv_ls_nocopy(":status", response_status),
? http2::make_nv_lc_nocopy(":status", response_status_const) http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
: http2::make_nv_ls(":status", http2::make_nv_ls_nocopy("server", get_config()->http.server_name),
(status_code_str = util::utos(status_code))), http2::make_nv_ls_nocopy("content-length", content_length),
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"), http2::make_nv_ls_nocopy("date", date)}};
http2::make_nv_ls_nocopy("server", get_config()->http.server_name),
http2::make_nv_ls("content-length", content_length),
http2::make_nv_ls("date", lgconf->time_http_str));
rv = nghttp2_submit_response(session_, downstream->get_stream_id(), rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
nva.data(), nva.size(), &data_prd); nva.data(), nva.size(), &data_prd);
@ -1339,6 +1359,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
const auto &req = downstream->request(); const auto &req = downstream->request();
auto &resp = downstream->response(); auto &resp = downstream->response();
auto &balloc = downstream->get_block_allocator();
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
if (downstream->get_non_final_response()) { if (downstream->get_non_final_response()) {
DLOG(INFO, downstream) << "HTTP non-final response header"; DLOG(INFO, downstream) << "HTTP non-final response header";
@ -1377,17 +1399,14 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
// field. // field.
nva.reserve(resp.fs.headers().size() + 4 + nva.reserve(resp.fs.headers().size() + 4 +
httpconf.add_response_headers.size()); httpconf.add_response_headers.size());
std::string via_value;
std::string response_status;
auto response_status_const = http2::stringify_status(resp.http_status); auto response_status = http2::stringify_status(resp.http_status);
if (response_status_const) { if (response_status.empty()) {
nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const)); response_status = util::make_string_ref_uint(balloc, resp.http_status);
} else {
response_status = util::utos(resp.http_status);
nva.push_back(http2::make_nv_ls(":status", response_status));
} }
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
if (downstream->get_non_final_response()) { if (downstream->get_non_final_response()) {
http2::copy_headers_to_nva(nva, resp.fs.headers()); http2::copy_headers_to_nva(nva, resp.fs.headers());
@ -1433,13 +1452,23 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value)); nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
} }
} else { } else {
// we don't create more than 16 bytes in
// http::create_via_header_value.
size_t len = 16;
if (via) { if (via) {
via_value = (*via).value; len += via->value.size() + 2;
via_value += ", ";
} }
via_value +=
http::create_via_header_value(resp.http_major, resp.http_minor); auto iov = make_byte_ref(balloc, len + 1);
nva.push_back(http2::make_nv_ls("via", via_value)); auto p = iov.base;
if (via) {
p = std::copy(std::begin(via->value), std::end(via->value), p);
p = util::copy_lit(p, ", ");
}
p = http::create_via_header_value(p, resp.http_major, resp.http_minor);
*p = '\0';
nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
} }
for (auto &p : httpconf.add_response_headers) { for (auto &p : httpconf.add_response_headers) {
@ -1746,6 +1775,8 @@ int Http2Upstream::prepare_push_promise(Downstream *downstream) {
return 0; return 0;
} }
auto &balloc = downstream->get_block_allocator();
for (auto &kv : resp.fs.headers()) { for (auto &kv : resp.fs.headers()) {
if (kv.token != http2::HD_LINK) { if (kv.token != http2::HD_LINK) {
continue; continue;
@ -1753,28 +1784,23 @@ int Http2Upstream::prepare_push_promise(Downstream *downstream) {
for (auto &link : for (auto &link :
http2::parse_link_header(kv.value.c_str(), kv.value.size())) { http2::parse_link_header(kv.value.c_str(), kv.value.size())) {
const std::string *scheme_ptr, *authority_ptr; StringRef scheme, authority, path;
std::string scheme, authority, path;
rv = http2::construct_push_component(scheme, authority, path, base, rv = http2::construct_push_component(balloc, scheme, authority, path,
link.uri); base, link.uri);
if (rv != 0) { if (rv != 0) {
continue; continue;
} }
if (scheme.empty()) { if (scheme.empty()) {
scheme_ptr = &req.scheme; scheme = req.scheme;
} else {
scheme_ptr = &scheme;
} }
if (authority.empty()) { if (authority.empty()) {
authority_ptr = &req.authority; authority = req.authority;
} else {
authority_ptr = &authority;
} }
rv = submit_push_promise(*scheme_ptr, *authority_ptr, path, downstream); rv = submit_push_promise(scheme, authority, path, downstream);
if (rv != 0) { if (rv != 0) {
return -1; return -1;
} }
@ -1783,9 +1809,9 @@ int Http2Upstream::prepare_push_promise(Downstream *downstream) {
return 0; return 0;
} }
int Http2Upstream::submit_push_promise(const std::string &scheme, int Http2Upstream::submit_push_promise(const StringRef &scheme,
const std::string &authority, const StringRef &authority,
const std::string &path, const StringRef &path,
Downstream *downstream) { Downstream *downstream) {
const auto &req = downstream->request(); const auto &req = downstream->request();
@ -1795,9 +1821,9 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
// juse use "GET" for now // juse use "GET" for now
nva.push_back(http2::make_nv_ll(":method", "GET")); nva.push_back(http2::make_nv_ll(":method", "GET"));
nva.push_back(http2::make_nv_ls(":scheme", scheme)); nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
nva.push_back(http2::make_nv_ls(":path", path)); nva.push_back(http2::make_nv_ls_nocopy(":path", path));
nva.push_back(http2::make_nv_ls(":authority", authority)); nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
for (auto &kv : req.fs.headers()) { for (auto &kv : req.fs.headers()) {
switch (kv.token) { switch (kv.token) {
@ -1865,27 +1891,25 @@ int Http2Upstream::initiate_push(Downstream *downstream, const StringRef &uri) {
return -1; return -1;
} }
const std::string *scheme_ptr, *authority_ptr; auto &balloc = downstream->get_block_allocator();
std::string scheme, authority, path;
rv = http2::construct_push_component(scheme, authority, path, base, uri); StringRef scheme, authority, path;
rv = http2::construct_push_component(balloc, scheme, authority, path, base,
uri);
if (rv != 0) { if (rv != 0) {
return -1; return -1;
} }
if (scheme.empty()) { if (scheme.empty()) {
scheme_ptr = &req.scheme; scheme = req.scheme;
} else {
scheme_ptr = &scheme;
} }
if (authority.empty()) { if (authority.empty()) {
authority_ptr = &req.authority; authority = req.authority;
} else {
authority_ptr = &authority;
} }
rv = submit_push_promise(*scheme_ptr, *authority_ptr, path, downstream); rv = submit_push_promise(scheme, authority, path, downstream);
if (rv != 0) { if (rv != 0) {
return -1; return -1;
@ -1943,7 +1967,7 @@ int Http2Upstream::on_downstream_push_promise_complete(
nva.reserve(headers.size()); nva.reserve(headers.size());
for (auto &kv : headers) { for (auto &kv : headers) {
nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index)); nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
} }
auto promised_stream_id = nghttp2_submit_push_promise( auto promised_stream_id = nghttp2_submit_push_promise(

View File

@ -111,9 +111,8 @@ public:
void check_shutdown(); void check_shutdown();
int prepare_push_promise(Downstream *downstream); int prepare_push_promise(Downstream *downstream);
int submit_push_promise(const std::string &scheme, int submit_push_promise(const StringRef &scheme, const StringRef &authority,
const std::string &authority, const std::string &path, const StringRef &path, Downstream *downstream);
Downstream *downstream);
int on_request_headers(Downstream *downstream, const nghttp2_frame *frame); int on_request_headers(Downstream *downstream, const nghttp2_frame *frame);

View File

@ -272,6 +272,8 @@ int HttpDownstreamConnection::push_request_headers() {
const auto &downstream_hostport = addr_->hostport; const auto &downstream_hostport = addr_->hostport;
const auto &req = downstream_->request(); const auto &req = downstream_->request();
auto &balloc = downstream_->get_block_allocator();
auto connect_method = req.method == HTTP_CONNECT; auto connect_method = req.method == HTTP_CONNECT;
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
@ -283,10 +285,10 @@ int HttpDownstreamConnection::push_request_headers() {
httpconf.no_host_rewrite || get_config()->http2_proxy || connect_method; httpconf.no_host_rewrite || get_config()->http2_proxy || connect_method;
if (no_host_rewrite && !req.authority.empty()) { if (no_host_rewrite && !req.authority.empty()) {
authority = StringRef(req.authority); authority = req.authority;
} }
downstream_->set_request_downstream_host(authority.str()); downstream_->set_request_downstream_host(authority);
auto buf = downstream_->get_request_buf(); auto buf = downstream_->get_request_buf();
@ -367,8 +369,9 @@ int HttpDownstreamConnection::push_request_headers() {
} }
auto value = http::create_forwarded( auto value = http::create_forwarded(
params, handler->get_forwarded_by(), handler->get_forwarded_for(), balloc, params, handler->get_forwarded_by(),
StringRef{req.authority}, StringRef{req.scheme}); handler->get_forwarded_for(), req.authority, req.scheme);
if (fwd || !value.empty()) { if (fwd || !value.empty()) {
buf->append("Forwarded: "); buf->append("Forwarded: ");
if (fwd) { if (fwd) {
@ -424,7 +427,10 @@ int HttpDownstreamConnection::push_request_headers() {
buf->append((*via).value); buf->append((*via).value);
buf->append(", "); buf->append(", ");
} }
buf->append(http::create_via_header_value(req.http_major, req.http_minor)); std::array<char, 16> viabuf;
auto end = http::create_via_header_value(viabuf.data(), req.http_major,
req.http_minor);
buf->append(viabuf.data(), end - viabuf.data());
buf->append("\r\n"); buf->append("\r\n");
} }
@ -690,6 +696,7 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto downstream = static_cast<Downstream *>(htp->data); auto downstream = static_cast<Downstream *>(htp->data);
auto &resp = downstream->response(); auto &resp = downstream->response();
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
auto &balloc = downstream->get_block_allocator();
if (ensure_header_field_buffer(downstream, httpconf, len) != 0) { if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
return -1; return -1;
@ -702,7 +709,9 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
if (ensure_max_header_fields(downstream, httpconf) != 0) { if (ensure_max_header_fields(downstream, httpconf) != 0) {
return -1; return -1;
} }
resp.fs.add_header_lower(StringRef{data, len}, StringRef{}, false); auto name = http2::copy_lower(balloc, StringRef{data, len});
auto token = http2::lookup_token(name);
resp.fs.add_header_token(name, StringRef{}, false, token);
} }
} else { } else {
// trailer part // trailer part
@ -715,7 +724,9 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
// wrong place or crash if trailer fields are currently empty. // wrong place or crash if trailer fields are currently empty.
return -1; return -1;
} }
resp.fs.add_trailer_lower(StringRef(data, len), StringRef{}, false); auto name = http2::copy_lower(balloc, StringRef{data, len});
auto token = http2::lookup_token(name);
resp.fs.add_trailer_token(name, StringRef{}, false, token);
} }
} }
return 0; return 0;

View File

@ -38,38 +38,54 @@
namespace shrpx { namespace shrpx {
void test_shrpx_http_create_forwarded(void) { void test_shrpx_http_create_forwarded(void) {
BlockAllocator balloc(1024, 1024);
CU_ASSERT("by=\"example.com:3000\";for=\"[::1]\";host=\"www.example.com\";" CU_ASSERT("by=\"example.com:3000\";for=\"[::1]\";host=\"www.example.com\";"
"proto=https" == "proto=https" ==
http::create_forwarded(FORWARDED_BY | FORWARDED_FOR | http::create_forwarded(balloc, FORWARDED_BY | FORWARDED_FOR |
FORWARDED_HOST | FORWARDED_PROTO, FORWARDED_HOST | FORWARDED_PROTO,
StringRef::from_lit("example.com:3000"), StringRef::from_lit("example.com:3000"),
StringRef::from_lit("[::1]"), StringRef::from_lit("[::1]"),
StringRef::from_lit("www.example.com"), StringRef::from_lit("www.example.com"),
StringRef::from_lit("https"))); StringRef::from_lit("https")));
CU_ASSERT("for=192.168.0.1" == CU_ASSERT("for=192.168.0.1" ==
http::create_forwarded(FORWARDED_FOR, StringRef::from_lit("alpha"), http::create_forwarded(
StringRef::from_lit("192.168.0.1"), balloc, FORWARDED_FOR, StringRef::from_lit("alpha"),
StringRef::from_lit("bravo"), StringRef::from_lit("192.168.0.1"),
StringRef::from_lit("charlie"))); StringRef::from_lit("bravo"), StringRef::from_lit("charlie")));
CU_ASSERT("by=_hidden;for=\"[::1]\"" == CU_ASSERT("by=_hidden;for=\"[::1]\"" ==
http::create_forwarded( http::create_forwarded(
FORWARDED_BY | FORWARDED_FOR, StringRef::from_lit("_hidden"), balloc, FORWARDED_BY | FORWARDED_FOR,
StringRef::from_lit("[::1]"), StringRef::from_lit(""), StringRef::from_lit("_hidden"), StringRef::from_lit("[::1]"),
StringRef::from_lit(""))); StringRef::from_lit(""), StringRef::from_lit("")));
CU_ASSERT("by=\"[::1]\";for=_hidden" == CU_ASSERT("by=\"[::1]\";for=_hidden" ==
http::create_forwarded( http::create_forwarded(
FORWARDED_BY | FORWARDED_FOR, StringRef::from_lit("[::1]"), balloc, FORWARDED_BY | FORWARDED_FOR,
StringRef::from_lit("_hidden"), StringRef::from_lit(""), StringRef::from_lit("[::1]"), StringRef::from_lit("_hidden"),
StringRef::from_lit("")));
CU_ASSERT("" ==
http::create_forwarded(
FORWARDED_BY | FORWARDED_FOR | FORWARDED_HOST | FORWARDED_PROTO,
StringRef::from_lit(""), StringRef::from_lit(""),
StringRef::from_lit(""), StringRef::from_lit(""))); StringRef::from_lit(""), StringRef::from_lit("")));
CU_ASSERT("" == http::create_forwarded(
balloc, FORWARDED_BY | FORWARDED_FOR | FORWARDED_HOST |
FORWARDED_PROTO,
StringRef::from_lit(""), StringRef::from_lit(""),
StringRef::from_lit(""), StringRef::from_lit("")));
}
void test_shrpx_http_create_via_header_value(void) {
std::array<char, 16> buf;
auto end = http::create_via_header_value(std::begin(buf), 1, 1);
CU_ASSERT(("1.1 nghttpx" == StringRef{std::begin(buf), end}));
std::fill(std::begin(buf), std::end(buf), '\0');
end = http::create_via_header_value(std::begin(buf), 2, 0);
CU_ASSERT(("2 nghttpx" == StringRef{std::begin(buf), end}));
} }
} // namespace shrpx } // namespace shrpx

View File

@ -32,6 +32,7 @@
namespace shrpx { namespace shrpx {
void test_shrpx_http_create_forwarded(void); void test_shrpx_http_create_forwarded(void);
void test_shrpx_http_create_via_header_value(void);
} // namespace shrpx } // namespace shrpx

View File

@ -86,6 +86,8 @@ int htp_uricb(http_parser *htp, const char *data, size_t len) {
auto downstream = upstream->get_downstream(); auto downstream = upstream->get_downstream();
auto &req = downstream->request(); auto &req = downstream->request();
auto &balloc = downstream->get_block_allocator();
// We happen to have the same value for method token. // We happen to have the same value for method token.
req.method = htp->method; req.method = htp->method;
@ -103,9 +105,10 @@ int htp_uricb(http_parser *htp, const char *data, size_t len) {
req.fs.add_extra_buffer_size(len); req.fs.add_extra_buffer_size(len);
if (req.method == HTTP_CONNECT) { if (req.method == HTTP_CONNECT) {
req.authority.append(data, len); req.authority =
concat_string_ref(balloc, req.authority, StringRef{data, len});
} else { } else {
req.path.append(data, len); req.path = concat_string_ref(balloc, req.path, StringRef{data, len});
} }
return 0; return 0;
@ -118,6 +121,7 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto downstream = upstream->get_downstream(); auto downstream = upstream->get_downstream();
auto &req = downstream->request(); auto &req = downstream->request();
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
auto &balloc = downstream->get_block_allocator();
if (req.fs.buffer_size() + len > httpconf.request_header_field_buffer) { if (req.fs.buffer_size() + len > httpconf.request_header_field_buffer) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@ -142,7 +146,9 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE); Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
return -1; return -1;
} }
req.fs.add_header_lower(StringRef{data, len}, StringRef{}, false); auto name = http2::copy_lower(balloc, StringRef{data, len});
auto token = http2::lookup_token(name);
req.fs.add_header_token(name, StringRef{}, false, token);
} }
} else { } else {
// trailer part // trailer part
@ -156,7 +162,9 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
} }
return -1; return -1;
} }
req.fs.add_trailer_lower(StringRef{data, len}, StringRef{}, false); auto name = http2::copy_lower(balloc, StringRef{data, len});
auto token = http2::lookup_token(name);
req.fs.add_trailer_token(name, StringRef{}, false, token);
} }
} }
return 0; return 0;
@ -190,30 +198,51 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
} // namespace } // namespace
namespace { namespace {
void rewrite_request_host_path_from_uri(Request &req, const char *uri, void rewrite_request_host_path_from_uri(BlockAllocator &balloc, Request &req,
const StringRef &uri,
http_parser_url &u) { http_parser_url &u) {
assert(u.field_set & (1 << UF_HOST)); assert(u.field_set & (1 << UF_HOST));
auto &authority = req.authority;
authority.clear();
// As per https://tools.ietf.org/html/rfc7230#section-5.4, we // As per https://tools.ietf.org/html/rfc7230#section-5.4, we
// rewrite host header field with authority component. // rewrite host header field with authority component.
http2::copy_url_component(authority, &u, UF_HOST, uri); auto authority = util::get_uri_field(uri.c_str(), u, UF_HOST);
// TODO properly check IPv6 numeric address // TODO properly check IPv6 numeric address
if (authority.find(':') != std::string::npos) { auto ipv6 = std::find(std::begin(authority), std::end(authority), ':') !=
authority = '[' + authority; std::end(authority);
authority += ']'; auto authoritylen = authority.size();
if (ipv6) {
authoritylen += 2;
} }
if (u.field_set & (1 << UF_PORT)) { if (u.field_set & (1 << UF_PORT)) {
authority += ':'; authoritylen += 1 + str_size("65535");
authority += util::utos(u.port); }
if (authoritylen > authority.size()) {
auto iovec = make_byte_ref(balloc, authoritylen + 1);
auto p = iovec.base;
if (ipv6) {
*p++ = '[';
}
p = std::copy(std::begin(authority), std::end(authority), p);
if (ipv6) {
*p++ = ']';
}
if (u.field_set & (1 << UF_PORT)) {
*p++ = ':';
p = util::utos(p, u.port);
}
*p = '\0';
req.authority = StringRef{iovec.base, p};
} else {
req.authority = authority;
} }
http2::copy_url_component(req.scheme, &u, UF_SCHEMA, uri); req.scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
std::string path; StringRef path;
if (u.field_set & (1 << UF_PATH)) { if (u.field_set & (1 << UF_PATH)) {
http2::copy_url_component(path, &u, UF_PATH, uri); path = util::get_uri_field(uri.c_str(), u, UF_PATH);
} else if (req.method == HTTP_OPTIONS) { } else if (req.method == HTTP_OPTIONS) {
// Server-wide OPTIONS takes following form in proxy request: // Server-wide OPTIONS takes following form in proxy request:
// //
@ -221,21 +250,35 @@ void rewrite_request_host_path_from_uri(Request &req, const char *uri,
// //
// Notice that no slash after authority. See // Notice that no slash after authority. See
// http://tools.ietf.org/html/rfc7230#section-5.3.4 // http://tools.ietf.org/html/rfc7230#section-5.3.4
req.path = ""; req.path = StringRef::from_lit("");
// we ignore query component here // we ignore query component here
return; return;
} else { } else {
path = "/"; path = StringRef::from_lit("/");
} }
if (u.field_set & (1 << UF_QUERY)) { if (u.field_set & (1 << UF_QUERY)) {
auto &fdata = u.field_data[UF_QUERY]; auto &fdata = u.field_data[UF_QUERY];
path += '?';
path.append(uri + fdata.off, fdata.len); if (u.field_set & (1 << UF_PATH)) {
auto q = util::get_uri_field(uri.c_str(), u, UF_QUERY);
path = StringRef{std::begin(path), std::end(q)};
} else {
auto iov = make_byte_ref(balloc, path.size() + 1 + fdata.len + 1);
auto p = iov.base;
p = std::copy(std::begin(path), std::end(path), p);
*p++ = '?';
p = std::copy_n(&uri[fdata.off], fdata.len, p);
*p = '\0';
path = StringRef{iov.base, p};
}
} }
if (get_config()->http2_proxy) { if (get_config()->http2_proxy) {
req.path = std::move(path); req.path = path;
} else { } else {
req.path = http2::rewrite_clean_path(std::begin(path), std::end(path)); req.path = http2::rewrite_clean_path(balloc, path);
} }
} }
} // namespace } // namespace
@ -299,12 +342,11 @@ int htp_hdrs_completecb(http_parser *htp) {
downstream->inspect_http1_request(); downstream->inspect_http1_request();
auto &balloc = downstream->get_block_allocator();
if (method != HTTP_CONNECT) { if (method != HTTP_CONNECT) {
http_parser_url u{}; http_parser_url u{};
// make a copy of request path, since we may set request path rv = http_parser_parse_url(req.path.c_str(), req.path.size(), 0, &u);
// while we are refering to original request path.
auto path = req.path;
rv = http_parser_parse_url(path.c_str(), path.size(), 0, &u);
if (rv != 0) { if (rv != 0) {
// Expect to respond with 400 bad request // Expect to respond with 400 bad request
return -1; return -1;
@ -318,10 +360,10 @@ int htp_hdrs_completecb(http_parser *htp) {
req.no_authority = true; req.no_authority = true;
if (method == HTTP_OPTIONS && path == "*") { if (method == HTTP_OPTIONS && req.path == "*") {
req.path = ""; req.path = StringRef{};
} else { } else {
req.path = http2::rewrite_clean_path(std::begin(path), std::end(path)); req.path = http2::rewrite_clean_path(balloc, req.path);
} }
if (host) { if (host) {
@ -329,12 +371,12 @@ int htp_hdrs_completecb(http_parser *htp) {
} }
if (handler->get_ssl()) { if (handler->get_ssl()) {
req.scheme = "https"; req.scheme = StringRef::from_lit("https");
} else { } else {
req.scheme = "http"; req.scheme = StringRef::from_lit("http");
} }
} else { } else {
rewrite_request_host_path_from_uri(req, path.c_str(), u); rewrite_request_host_path_from_uri(balloc, req, req.path, u);
} }
} }
@ -1032,8 +1074,10 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
buf->append((*via).value); buf->append((*via).value);
buf->append(", "); buf->append(", ");
} }
buf->append( std::array<char, 16> viabuf;
http::create_via_header_value(resp.http_major, resp.http_minor)); auto end = http::create_via_header_value(viabuf.data(), resp.http_major,
resp.http_minor);
buf->append(viabuf.data(), end - std::begin(viabuf));
buf->append("\r\n"); buf->append("\r\n");
} }

View File

@ -85,7 +85,7 @@ mrb_value init_module(mrb_state *mrb) {
return create_env(mrb); return create_env(mrb);
} }
mrb_value create_headers_hash(mrb_state *mrb, const Headers &headers) { mrb_value create_headers_hash(mrb_state *mrb, const HeaderRefs &headers) {
auto hash = mrb_hash_new(mrb); auto hash = mrb_hash_new(mrb);
for (auto &hd : headers) { for (auto &hd : headers) {

View File

@ -43,7 +43,7 @@ mrb_value init_module(mrb_state *mrb);
void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream); void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream);
mrb_value create_headers_hash(mrb_state *mrb, const Headers &headers); mrb_value create_headers_hash(mrb_state *mrb, const HeaderRefs &headers);
} // namespace mruby } // namespace mruby

View File

@ -116,6 +116,8 @@ mrb_value request_set_authority(mrb_state *mrb, mrb_value self) {
auto downstream = data->downstream; auto downstream = data->downstream;
auto &req = downstream->request(); auto &req = downstream->request();
auto &balloc = downstream->get_block_allocator();
check_phase(mrb, data->phase, PHASE_REQUEST); check_phase(mrb, data->phase, PHASE_REQUEST);
const char *authority; const char *authority;
@ -125,7 +127,8 @@ mrb_value request_set_authority(mrb_state *mrb, mrb_value self) {
mrb_raise(mrb, E_RUNTIME_ERROR, "authority must not be empty string"); mrb_raise(mrb, E_RUNTIME_ERROR, "authority must not be empty string");
} }
req.authority.assign(authority, n); req.authority =
make_string_ref(balloc, StringRef{authority, static_cast<size_t>(n)});
return self; return self;
} }
@ -147,6 +150,8 @@ mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) {
auto downstream = data->downstream; auto downstream = data->downstream;
auto &req = downstream->request(); auto &req = downstream->request();
auto &balloc = downstream->get_block_allocator();
check_phase(mrb, data->phase, PHASE_REQUEST); check_phase(mrb, data->phase, PHASE_REQUEST);
const char *scheme; const char *scheme;
@ -156,7 +161,8 @@ mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) {
mrb_raise(mrb, E_RUNTIME_ERROR, "scheme must not be empty string"); mrb_raise(mrb, E_RUNTIME_ERROR, "scheme must not be empty string");
} }
req.scheme.assign(scheme, n); req.scheme =
make_string_ref(balloc, StringRef{scheme, static_cast<size_t>(n)});
return self; return self;
} }
@ -178,13 +184,16 @@ mrb_value request_set_path(mrb_state *mrb, mrb_value self) {
auto downstream = data->downstream; auto downstream = data->downstream;
auto &req = downstream->request(); auto &req = downstream->request();
auto &balloc = downstream->get_block_allocator();
check_phase(mrb, data->phase, PHASE_REQUEST); check_phase(mrb, data->phase, PHASE_REQUEST);
const char *path; const char *path;
mrb_int pathlen; mrb_int pathlen;
mrb_get_args(mrb, "s", &path, &pathlen); mrb_get_args(mrb, "s", &path, &pathlen);
req.path.assign(path, pathlen); req.path =
make_string_ref(balloc, StringRef{path, static_cast<size_t>(pathlen)});
return self; return self;
} }
@ -204,6 +213,7 @@ mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
auto data = static_cast<MRubyAssocData *>(mrb->ud); auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream; auto downstream = data->downstream;
auto &req = downstream->request(); auto &req = downstream->request();
auto &balloc = downstream->get_block_allocator();
check_phase(mrb, data->phase, PHASE_REQUEST); check_phase(mrb, data->phase, PHASE_REQUEST);
@ -217,7 +227,8 @@ mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
key = mrb_funcall(mrb, key, "downcase", 0); key = mrb_funcall(mrb, key, "downcase", 0);
auto keyref = auto keyref =
StringRef{RSTRING_PTR(key), static_cast<size_t>(RSTRING_LEN(key))}; make_string_ref(balloc, StringRef{RSTRING_PTR(key),
static_cast<size_t>(RSTRING_LEN(key))});
auto token = http2::lookup_token(keyref.byte(), keyref.size()); auto token = http2::lookup_token(keyref.byte(), keyref.size());
if (repl) { if (repl) {
@ -240,15 +251,19 @@ mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
auto value = mrb_ary_entry(values, i); auto value = mrb_ary_entry(values, i);
req.fs.add_header_token( req.fs.add_header_token(
keyref, StringRef{RSTRING_PTR(value), keyref,
static_cast<size_t>(RSTRING_LEN(value))}, make_string_ref(balloc,
StringRef{RSTRING_PTR(value),
static_cast<size_t>(RSTRING_LEN(value))}),
false, token); false, token);
} }
} else if (!mrb_nil_p(values)) { } else if (!mrb_nil_p(values)) {
req.fs.add_header_token(keyref, req.fs.add_header_token(
StringRef{RSTRING_PTR(values), keyref,
static_cast<size_t>(RSTRING_LEN(values))}, make_string_ref(balloc,
false, token); StringRef{RSTRING_PTR(values),
static_cast<size_t>(RSTRING_LEN(values))}),
false, token);
} }
return mrb_nil_value(); return mrb_nil_value();

View File

@ -107,6 +107,7 @@ mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
auto data = static_cast<MRubyAssocData *>(mrb->ud); auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream; auto downstream = data->downstream;
auto &resp = downstream->response(); auto &resp = downstream->response();
auto &balloc = downstream->get_block_allocator();
mrb_value key, values; mrb_value key, values;
mrb_get_args(mrb, "oo", &key, &values); mrb_get_args(mrb, "oo", &key, &values);
@ -118,7 +119,8 @@ mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
key = mrb_funcall(mrb, key, "downcase", 0); key = mrb_funcall(mrb, key, "downcase", 0);
auto keyref = auto keyref =
StringRef{RSTRING_PTR(key), static_cast<size_t>(RSTRING_LEN(key))}; make_string_ref(balloc, StringRef{RSTRING_PTR(key),
static_cast<size_t>(RSTRING_LEN(key))});
auto token = http2::lookup_token(keyref.byte(), keyref.size()); auto token = http2::lookup_token(keyref.byte(), keyref.size());
if (repl) { if (repl) {
@ -141,14 +143,18 @@ mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
auto value = mrb_ary_entry(values, i); auto value = mrb_ary_entry(values, i);
resp.fs.add_header_token( resp.fs.add_header_token(
keyref, StringRef{RSTRING_PTR(value), keyref,
static_cast<size_t>(RSTRING_LEN(value))}, make_string_ref(balloc,
StringRef{RSTRING_PTR(value),
static_cast<size_t>(RSTRING_LEN(value))}),
false, token); false, token);
} }
} else if (!mrb_nil_p(values)) { } else if (!mrb_nil_p(values)) {
resp.fs.add_header_token( resp.fs.add_header_token(
keyref, StringRef{RSTRING_PTR(values), keyref,
static_cast<size_t>(RSTRING_LEN(values))}, make_string_ref(balloc,
StringRef{RSTRING_PTR(values),
static_cast<size_t>(RSTRING_LEN(values))}),
false, token); false, token);
} }
@ -187,6 +193,8 @@ mrb_value response_return(mrb_state *mrb, mrb_value self) {
auto &resp = downstream->response(); auto &resp = downstream->response();
int rv; int rv;
auto &balloc = downstream->get_block_allocator();
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
mrb_raise(mrb, E_RUNTIME_ERROR, "response has already been committed"); mrb_raise(mrb, E_RUNTIME_ERROR, "response has already been committed");
} }
@ -207,13 +215,14 @@ mrb_value response_return(mrb_state *mrb, mrb_value self) {
bodylen = vallen; bodylen = vallen;
} }
auto content_length = util::make_string_ref_uint(balloc, bodylen);
auto cl = resp.fs.header(http2::HD_CONTENT_LENGTH); auto cl = resp.fs.header(http2::HD_CONTENT_LENGTH);
if (cl) { if (cl) {
cl->value = util::utos(bodylen); cl->value = content_length;
} else { } else {
resp.fs.add_header_token(StringRef::from_lit("content-length"), resp.fs.add_header_token(StringRef::from_lit("content-length"),
StringRef{util::utos(bodylen)}, false, content_length, false, http2::HD_CONTENT_LENGTH);
http2::HD_CONTENT_LENGTH);
} }
resp.fs.content_length = bodylen; resp.fs.content_length = bodylen;
@ -221,9 +230,10 @@ mrb_value response_return(mrb_state *mrb, mrb_value self) {
if (!date) { if (!date) {
auto lgconf = log_config(); auto lgconf = log_config();
lgconf->update_tstamp(std::chrono::system_clock::now()); lgconf->update_tstamp(std::chrono::system_clock::now());
resp.fs.add_header_token(StringRef::from_lit("date"), resp.fs.add_header_token(
StringRef{lgconf->time_http_str}, false, StringRef::from_lit("date"),
http2::HD_DATE); make_string_ref(balloc, StringRef{lgconf->time_http_str}), false,
http2::HD_DATE);
} }
auto upstream = downstream->get_upstream(); auto upstream = downstream->get_upstream();

View File

@ -159,6 +159,8 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
auto &req = downstream->request(); auto &req = downstream->request();
auto &balloc = downstream->get_block_allocator();
downstream->reset_upstream_rtimer(); downstream->reset_upstream_rtimer();
auto nv = frame->syn_stream.nv; auto nv = frame->syn_stream.nv;
@ -195,7 +197,9 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
auto name = StringRef{nv[i]}; auto name = StringRef{nv[i]};
auto value = StringRef{nv[i + 1]}; auto value = StringRef{nv[i + 1]};
auto token = http2::lookup_token(name.byte(), name.size()); auto token = http2::lookup_token(name.byte(), name.size());
req.fs.add_header_token(name, value, false, token); req.fs.add_header_token(make_string_ref(balloc, StringRef{name}),
make_string_ref(balloc, StringRef{value}), false,
token);
} }
if (req.fs.parse_content_length() != 0) { if (req.fs.parse_content_length() != 0) {
@ -271,8 +275,7 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
} else if (method_token == HTTP_OPTIONS && path->value == "*") { } else if (method_token == HTTP_OPTIONS && path->value == "*") {
// Server-wide OPTIONS request. Path is empty. // Server-wide OPTIONS request. Path is empty.
} else { } else {
req.path = http2::rewrite_clean_path(std::begin(path->value), req.path = http2::rewrite_clean_path(balloc, path->value);
std::end(path->value));
} }
} }
@ -1069,11 +1072,13 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
} }
} else { } else {
if (via) { if (via) {
via_value = via->value; via_value = via->value.str();
via_value += ", "; via_value += ", ";
} }
via_value += std::array<char, 16> viabuf;
http::create_via_header_value(resp.http_major, resp.http_minor); auto end = http::create_via_header_value(std::begin(viabuf),
resp.http_major, resp.http_minor);
via_value.append(std::begin(viabuf), end);
nv[hdidx++] = "via"; nv[hdidx++] = "via";
nv[hdidx++] = via_value.c_str(); nv[hdidx++] = via_value.c_str();
} }

View File

@ -401,18 +401,20 @@ public:
explicit StringRef(const ImmutableString &s) explicit StringRef(const ImmutableString &s)
: base(s.c_str()), len(s.size()) {} : base(s.c_str()), len(s.size()) {}
explicit StringRef(const char *s) : base(s), len(strlen(s)) {} explicit StringRef(const char *s) : base(s), len(strlen(s)) {}
constexpr StringRef(const char *s, size_t n) : base(s), len(n) {}
template <typename CharT> template <typename CharT>
constexpr StringRef(const CharT *s, size_t n) StringRef(const CharT *s, size_t n)
: base(reinterpret_cast<const char *>(s)), len(n) {} : base(reinterpret_cast<const char *>(s)), len(n) {}
template <typename InputIt> template <typename InputIt>
StringRef(InputIt first, InputIt last) StringRef(InputIt first, InputIt last)
: base(&*first), len(std::distance(first, last)) {} : base(&*first), len(std::distance(first, last)) {}
template <typename InputIt> template <typename InputIt>
StringRef(InputIt *first, InputIt *last) StringRef(InputIt *first, InputIt *last)
: base(first), len(std::distance(first, last)) {} : base(reinterpret_cast<const char *>(first)),
len(std::distance(first, last)) {}
template <typename CharT, size_t N> template <typename CharT, size_t N>
constexpr static StringRef from_lit(const CharT(&s)[N]) { constexpr static StringRef from_lit(const CharT(&s)[N]) {
return StringRef(s, N - 1); return StringRef{s, N - 1};
} }
static StringRef from_maybe_nullptr(const char *s) { static StringRef from_maybe_nullptr(const char *s) {
if (s == nullptr) { if (s == nullptr) {
@ -443,6 +445,11 @@ private:
size_type len; size_type len;
}; };
inline bool operator==(const StringRef &lhs, const StringRef &rhs) {
return lhs.size() == rhs.size() &&
std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
}
inline bool operator==(const StringRef &lhs, const std::string &rhs) { inline bool operator==(const StringRef &lhs, const std::string &rhs) {
return lhs.size() == rhs.size() && return lhs.size() == rhs.size() &&
std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
@ -477,6 +484,11 @@ inline bool operator!=(const char *lhs, const StringRef &rhs) {
return !(rhs == lhs); return !(rhs == lhs);
} }
inline bool operator<(const StringRef &lhs, const StringRef &rhs) {
return std::lexicographical_compare(std::begin(lhs), std::end(lhs),
std::begin(rhs), std::end(rhs));
}
inline std::ostream &operator<<(std::ostream &o, const StringRef &s) { inline std::ostream &operator<<(std::ostream &o, const StringRef &s) {
return o.write(s.c_str(), s.size()); return o.write(s.c_str(), s.size());
} }

View File

@ -339,7 +339,7 @@ std::string iso8601_date(int64_t ms) {
return res; return res;
} }
time_t parse_http_date(const std::string &s) { time_t parse_http_date(const StringRef &s) {
tm tm{}; tm tm{};
char *r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm); char *r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm);
if (r == 0) { if (r == 0) {
@ -1056,6 +1056,10 @@ int64_t parse_uint(const std::string &s) {
return parse_uint(reinterpret_cast<const uint8_t *>(s.c_str()), s.size()); return parse_uint(reinterpret_cast<const uint8_t *>(s.c_str()), s.size());
} }
int64_t parse_uint(const StringRef &s) {
return parse_uint(s.byte(), s.size());
}
int64_t parse_uint(const uint8_t *s, size_t len) { int64_t parse_uint(const uint8_t *s, size_t len) {
int64_t n; int64_t n;
size_t i; size_t i;

View File

@ -51,6 +51,7 @@
#include "template.h" #include "template.h"
#include "network.h" #include "network.h"
#include "allocator.h"
namespace nghttp2 { namespace nghttp2 {
@ -149,7 +150,7 @@ std::string common_log_date(time_t t);
// 2014-11-15T12:58:24.741Z) // 2014-11-15T12:58:24.741Z)
std::string iso8601_date(int64_t ms); std::string iso8601_date(int64_t ms);
time_t parse_http_date(const std::string &s); time_t parse_http_date(const StringRef &s);
char upcase(char c); char upcase(char c);
@ -261,6 +262,11 @@ bool iends_with_l(const std::string &a, const CharT(&b)[N]) {
return iends_with(std::begin(a), std::end(a), b, b + N - 1); return iends_with(std::begin(a), std::end(a), b, b + N - 1);
} }
template <typename CharT, size_t N>
bool iends_with_l(const StringRef &a, const CharT(&b)[N]) {
return iends_with(std::begin(a), std::end(a), b, b + N - 1);
}
int strcompare(const char *a, const uint8_t *b, size_t n); int strcompare(const char *a, const uint8_t *b, size_t n);
template <typename InputIt> bool strieq(const char *a, InputIt b, size_t bn) { template <typename InputIt> bool strieq(const char *a, InputIt b, size_t bn) {
@ -310,6 +316,11 @@ bool strieq_l(const CharT(&a)[N], const std::string &b) {
return strieq(a, N - 1, std::begin(b), b.size()); return strieq(a, N - 1, std::begin(b), b.size());
} }
template <typename CharT, size_t N>
bool strieq_l(const CharT(&a)[N], const StringRef &b) {
return strieq(a, N - 1, std::begin(b), b.size());
}
template <typename InputIt> bool streq(const char *a, InputIt b, size_t bn) { template <typename InputIt> bool streq(const char *a, InputIt b, size_t bn) {
if (!a) { if (!a) {
return false; return false;
@ -377,6 +388,33 @@ template <typename T> std::string utos(T n) {
return res; return res;
} }
template <typename T, typename OutputIt> OutputIt utos(OutputIt dst, T n) {
if (n == 0) {
*dst++ = '0';
return dst;
}
int i = 0;
T t = n;
for (; t; t /= 10, ++i)
;
--i;
auto p = dst + i;
auto res = p + 1;
for (; n; --i, n /= 10) {
*p-- = (n % 10) + '0';
}
return res;
}
template <typename T>
StringRef make_string_ref_uint(BlockAllocator &balloc, T n) {
auto iov = make_byte_ref(balloc, str_size("18446744073709551615") + 1);
auto p = iov.base;
p = util::utos(p, n);
*p = '\0';
return StringRef{iov.base, p};
}
template <typename T> std::string utos_unit(T n) { template <typename T> std::string utos_unit(T n) {
char u = 0; char u = 0;
if (n >= (1 << 30)) { if (n >= (1 << 30)) {
@ -609,6 +647,7 @@ int64_t parse_uint_with_unit(const char *s);
int64_t parse_uint(const char *s); int64_t parse_uint(const char *s);
int64_t parse_uint(const uint8_t *s, size_t len); int64_t parse_uint(const uint8_t *s, size_t len);
int64_t parse_uint(const std::string &s); int64_t parse_uint(const std::string &s);
int64_t parse_uint(const StringRef &s);
// Parses NULL terminated string |s| as unsigned integer and returns // Parses NULL terminated string |s| as unsigned integer and returns
// the parsed integer casted to double. If |s| ends with "s", the // the parsed integer casted to double. If |s| ends with "s", the
@ -684,6 +723,11 @@ std::string random_alpha_digit(Generator &gen, size_t len) {
return res; return res;
} }
template <typename OutputIterator, typename CharT, size_t N>
OutputIterator copy_lit(OutputIterator it, CharT(&s)[N]) {
return std::copy_n(s, N - 1, it);
}
} // namespace util } // namespace util
} // namespace nghttp2 } // namespace nghttp2

View File

@ -234,6 +234,24 @@ void test_util_ipv6_numeric_addr(void) {
CU_ASSERT(!util::ipv6_numeric_addr("localhost")); CU_ASSERT(!util::ipv6_numeric_addr("localhost"));
} }
void test_util_utos(void) {
uint8_t buf[32];
CU_ASSERT(("0" == StringRef{buf, util::utos(buf, 0)}));
CU_ASSERT(("123" == StringRef{buf, util::utos(buf, 123)}));
CU_ASSERT(("18446744073709551615" ==
StringRef{buf, util::utos(buf, 18446744073709551615ULL)}));
}
void test_util_make_string_ref_uint(void) {
BlockAllocator balloc(1024, 1024);
CU_ASSERT("0" == util::make_string_ref_uint(balloc, 0));
CU_ASSERT("123" == util::make_string_ref_uint(balloc, 123));
CU_ASSERT("18446744073709551615" ==
util::make_string_ref_uint(balloc, 18446744073709551615ULL));
}
void test_util_utos_unit(void) { void test_util_utos_unit(void) {
CU_ASSERT("0" == util::utos_unit(0)); CU_ASSERT("0" == util::utos_unit(0));
CU_ASSERT("1023" == util::utos_unit(1023)); CU_ASSERT("1023" == util::utos_unit(1023));
@ -383,8 +401,8 @@ void test_util_ends_with(void) {
} }
void test_util_parse_http_date(void) { void test_util_parse_http_date(void) {
CU_ASSERT(1001939696 == CU_ASSERT(1001939696 == util::parse_http_date(StringRef::from_lit(
util::parse_http_date("Mon, 1 Oct 2001 12:34:56 GMT")); "Mon, 1 Oct 2001 12:34:56 GMT")));
} }
void test_util_localtime_date(void) { void test_util_localtime_date(void) {

View File

@ -44,6 +44,8 @@ void test_util_utox(void);
void test_util_http_date(void); void test_util_http_date(void);
void test_util_select_h2(void); void test_util_select_h2(void);
void test_util_ipv6_numeric_addr(void); void test_util_ipv6_numeric_addr(void);
void test_util_utos(void);
void test_util_make_string_ref_uint(void);
void test_util_utos_unit(void); void test_util_utos_unit(void);
void test_util_utos_funit(void); void test_util_utos_funit(void);
void test_util_parse_uint_with_unit(void); void test_util_parse_uint_with_unit(void);

View File

@ -295,11 +295,6 @@ void test_nghttp2_hd_inflate_indname_inc(void) {
assert_nv_equal(&nv, out.nva, 1, mem); assert_nv_equal(&nv, out.nva, 1, mem);
CU_ASSERT(1 == inflater.ctx.hd_table.len); CU_ASSERT(1 == inflater.ctx.hd_table.len);
CU_ASSERT(62 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); CU_ASSERT(62 == nghttp2_hd_inflate_get_num_table_entries(&inflater));
assert_nv_equal(&nv,
&nghttp2_hd_table_get(&inflater.ctx,
NGHTTP2_STATIC_TABLE_LENGTH +
inflater.ctx.hd_table.len - 1)->nv,
1, mem);
assert_nv_equal(&nv, nghttp2_hd_inflate_get_table_entry( assert_nv_equal(&nv, nghttp2_hd_inflate_get_table_entry(
&inflater, NGHTTP2_STATIC_TABLE_LENGTH + &inflater, NGHTTP2_STATIC_TABLE_LENGTH +
inflater.ctx.hd_table.len), inflater.ctx.hd_table.len),
@ -429,10 +424,9 @@ void test_nghttp2_hd_inflate_newname_inc(void) {
CU_ASSERT(1 == out.nvlen); CU_ASSERT(1 == out.nvlen);
assert_nv_equal(&nv, out.nva, 1, mem); assert_nv_equal(&nv, out.nva, 1, mem);
CU_ASSERT(1 == inflater.ctx.hd_table.len); CU_ASSERT(1 == inflater.ctx.hd_table.len);
assert_nv_equal(&nv, assert_nv_equal(&nv, nghttp2_hd_inflate_get_table_entry(
&nghttp2_hd_table_get(&inflater.ctx, &inflater, NGHTTP2_STATIC_TABLE_LENGTH +
NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len),
inflater.ctx.hd_table.len - 1)->nv,
1, mem); 1, mem);
nva_out_reset(&out, mem); nva_out_reset(&out, mem);
@ -1352,13 +1346,15 @@ void test_nghttp2_hd_decode_length(void) {
void test_nghttp2_hd_huff_encode(void) { void test_nghttp2_hd_huff_encode(void) {
int rv; int rv;
ssize_t len; ssize_t len;
nghttp2_bufs bufs, outbufs; nghttp2_buf outbuf;
nghttp2_bufs bufs;
nghttp2_hd_huff_decode_context ctx; nghttp2_hd_huff_decode_context ctx;
const uint8_t t1[] = {22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, const uint8_t t1[] = {22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11,
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
uint8_t b[256];
nghttp2_buf_wrap_init(&outbuf, b, sizeof(b));
frame_pack_bufs_init(&bufs); frame_pack_bufs_init(&bufs);
frame_pack_bufs_init(&outbufs);
rv = nghttp2_hd_huff_encode(&bufs, t1, sizeof(t1)); rv = nghttp2_hd_huff_encode(&bufs, t1, sizeof(t1));
@ -1366,14 +1362,13 @@ void test_nghttp2_hd_huff_encode(void) {
nghttp2_hd_huff_decode_context_init(&ctx); nghttp2_hd_huff_decode_context_init(&ctx);
len = nghttp2_hd_huff_decode(&ctx, &outbufs, bufs.cur->buf.pos, len = nghttp2_hd_huff_decode(&ctx, &outbuf, bufs.cur->buf.pos,
nghttp2_bufs_len(&bufs), 1); nghttp2_bufs_len(&bufs), 1);
CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == len); CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == len);
CU_ASSERT((ssize_t)sizeof(t1) == nghttp2_bufs_len(&outbufs)); CU_ASSERT((ssize_t)sizeof(t1) == nghttp2_buf_len(&outbuf));
CU_ASSERT(0 == memcmp(t1, outbufs.cur->buf.pos, sizeof(t1))); CU_ASSERT(0 == memcmp(t1, outbuf.pos, sizeof(t1)));
nghttp2_bufs_free(&bufs); nghttp2_bufs_free(&bufs);
nghttp2_bufs_free(&outbufs);
} }