1.13.0: [FEATURE, API Change] HTTP header bypass

Add ability to create custom header set objects via callbacks.
This avoids reading and re-parsing headers from the stream.

See test/http_client.c for example implementation.  (Use -B flag
to turn it on).
This commit is contained in:
Dmitri Tikhonov 2018-08-28 09:59:47 -04:00
parent 6f126d809b
commit 3b55e6ae0a
25 changed files with 1229 additions and 737 deletions

View File

@ -1,3 +1,10 @@
2018-08-27
- 1.13.0
- [FEATURE, API Change] Add ability to create custom header set
objects via callbacks. This avoids reading and re-parsing
headers from the stream.
2018-08-27
- 1.12.4

View File

@ -24,8 +24,8 @@ extern "C" {
#endif
#define LSQUIC_MAJOR_VERSION 1
#define LSQUIC_MINOR_VERSION 12
#define LSQUIC_PATCH_VERSION 4
#define LSQUIC_MINOR_VERSION 13
#define LSQUIC_PATCH_VERSION 0
/**
* Engine flags:
@ -498,6 +498,72 @@ struct lsquic_packout_mem_if
struct stack_st_X509;
/**
* When headers are processed, various errors may occur. They are listed
* in this enum.
*/
enum lsquic_header_status
{
LSQUIC_HDR_OK,
/** Duplicate pseudo-header */
LSQUIC_HDR_ERR_DUPLICATE_PSDO_HDR,
/** Not all request pseudo-headers are present */
LSQUIC_HDR_ERR_INCOMPL_REQ_PSDO_HDR,
/** Unnecessary request pseudo-header present in the response */
LSQUIC_HDR_ERR_UNNEC_REQ_PSDO_HDR,
/** Not all response pseudo-headers are present */
LSQUIC_HDR_ERR_INCOMPL_RESP_PSDO_HDR,
/** Unnecessary response pseudo-header present in the response. */
LSQUIC_HDR_ERR_UNNEC_RESP_PSDO_HDR,
/** Unknown pseudo-header */
LSQUIC_HDR_ERR_UNKNOWN_PSDO_HDR,
/** Uppercase letter in header */
LSQUIC_HDR_ERR_UPPERCASE_HEADER,
/** Misplaced pseudo-header */
LSQUIC_HDR_ERR_MISPLACED_PSDO_HDR,
/** Missing pseudo-header */
LSQUIC_HDR_ERR_MISSING_PSDO_HDR,
/** Header or headers are too large */
LSQUIC_HDR_ERR_HEADERS_TOO_LARGE,
/** Cannot allocate any more memory. */
LSQUIC_HDR_ERR_NOMEM,
};
struct lsquic_hset_if
{
/**
* Create a new header set. This object is (and must be) fetched from a
* stream by calling @ref lsquic_stream_get_hset() before the stream can
* be read.
*/
void * (*hsi_create_header_set)(void *hsi_ctx,
int is_push_promise);
/**
* Process new header. Return 0 on success, -1 if there is a problem with
* the header. -1 is treated as a stream error: the associated stream is
* reset.
*
* `hdr_set' is the header set object returned by
* @ref hsi_create_header_set().
*
* `name_idx' is set to the index in the HPACK static table whose entry's
* name element matches `name'. If there is no such match, `name_idx' is
* set to zero.
*
* If `name' is NULL, this means that no more header are going to be
* added to the set.
*/
enum lsquic_header_status (*hsi_process_header)(void *hdr_set,
unsigned name_idx,
const char *name, unsigned name_len,
const char *value, unsigned value_len);
/**
* Discard header set. This is called for unclaimed header sets and
* header sets that had an error.
*/
void (*hsi_discard_header_set)(void *hdr_set);
};
/* TODO: describe this important data structure */
typedef struct lsquic_engine_api
{
@ -525,6 +591,14 @@ typedef struct lsquic_engine_api
int (*ea_verify_cert)(void *verify_ctx,
struct stack_st_X509 *chain);
void *ea_verify_ctx;
/**
* Optional header set interface. If not specified, the incoming headers
* are converted to HTTP/1.x format and are read from stream and have to
* be parsed again.
*/
const struct lsquic_hset_if *ea_hsi_if;
void *ea_hsi_ctx;
} lsquic_engine_api_t;
/**
@ -694,6 +768,20 @@ struct lsquic_http_headers
int lsquic_stream_send_headers(lsquic_stream_t *s,
const lsquic_http_headers_t *h, int eos);
/**
* Get header set associated with the stream. The header set is created by
* @ref hsi_create_header_set() callback. After this call, the ownership of
* the header set is trasnferred to the caller.
*
* This call must precede calls to @ref lsquic_stream_read() and
* @ref lsquic_stream_readv().
*
* If the optional header set interface (@ref ea_hsi_if) is not specified,
* this function returns NULL.
*/
void *
lsquic_stream_get_hset (lsquic_stream_t *);
int lsquic_conn_is_push_enabled(lsquic_conn_t *c);
/** Possible values for how are 0, 1, and 2. See shutdown(2). */
@ -743,16 +831,15 @@ lsquic_stream_refuse_push (lsquic_stream_t *s);
*
* @param ref_stream_id Stream ID in response to which push promise was
* sent.
* @param headers Uncompressed request headers.
* @param headers_sz Size of uncompressed request headers, not counting
* the NUL byte.
* @param hdr_set Header set. This object was passed to or generated
* by @ref lsquic_conn_push_stream().
*
* @retval 0 Success.
* @retval -1 This is not a pushed stream.
*/
int
lsquic_stream_push_info (const lsquic_stream_t *, uint32_t *ref_stream_id,
const char **headers, size_t *headers_sz);
void **hdr_set);
/** Return current priority of the stream */
unsigned lsquic_stream_priority (const lsquic_stream_t *s);

View File

@ -53,6 +53,7 @@ SET(lsquic_STAT_SRCS
lsquic_min_heap.c
lshpack.c
lsquic_parse_Q044.c
lsquic_http1x_if.c
)

View File

@ -5412,10 +5412,7 @@ lshpack_enc_cleanup (struct lshpack_enc *enc)
//not find return 0, otherwise return the index
#if !LS_HPACK_EMIT_TEST_CODE
static
#endif
unsigned
unsigned
lshpack_enc_get_stx_tab_id (const char *name, lshpack_strlen_t name_len,
const char *val, lshpack_strlen_t val_len, int *val_matched)
{
@ -6197,6 +6194,7 @@ struct dec_table_entry
{
uint16_t dte_name_len;
uint16_t dte_val_len;
uint8_t dte_name_idx;
char dte_buf[0]; /* Contains both name and value */
};
@ -6430,7 +6428,7 @@ hdec_get_table_entry (struct lshpack_dec *dec, uint32_t index)
static
#endif
int
lshpack_dec_push_entry (struct lshpack_dec *dec, const char *name,
lshpack_dec_push_entry (struct lshpack_dec *dec, uint8_t name_idx, const char *name,
uint16_t name_len, const char *val, uint16_t val_len)
{
struct dec_table_entry *entry;
@ -6450,6 +6448,7 @@ lshpack_dec_push_entry (struct lshpack_dec *dec, const char *name,
dec->hpd_cur_capacity += DYNAMIC_ENTRY_OVERHEAD + name_len + val_len;
entry->dte_name_len = name_len;
entry->dte_val_len = val_len;
entry->dte_name_idx = name_idx;
memcpy(DTE_NAME(entry), name, name_len);
memcpy(DTE_VALUE(entry), val, val_len);
return 0;
@ -6459,7 +6458,8 @@ lshpack_dec_push_entry (struct lshpack_dec *dec, const char *name,
int
lshpack_dec_decode (struct lshpack_dec *dec,
const unsigned char **src, const unsigned char *src_end,
char *dst, char *const dst_end, uint16_t *name_len, uint16_t *val_len)
char *dst, char *const dst_end, uint16_t *name_len, uint16_t *val_len,
uint32_t *name_idx)
{
struct dec_table_entry *entry;
uint32_t index, new_capacity;
@ -6532,6 +6532,7 @@ lshpack_dec_decode (struct lshpack_dec *dec,
indexed_type = 1;
}
*name_idx = index;
char *const name = dst;
if (index > 0)
@ -6562,6 +6563,8 @@ lshpack_dec_decode (struct lshpack_dec *dec,
*name_len = entry->dte_name_len;
memcpy(name, DTE_NAME(entry), *name_len);
if (entry->dte_name_idx)
*name_idx = entry->dte_name_idx;
if (indexed_type == 3)
{
if (entry->dte_name_len + entry->dte_val_len > dst_end - dst)
@ -6592,7 +6595,9 @@ lshpack_dec_decode (struct lshpack_dec *dec,
if (indexed_type == 0)
{
if (0 != lshpack_dec_push_entry(dec, name, *name_len,
if (index > HPACK_STATIC_TABLE_SIZE)
index = 0;
if (0 != lshpack_dec_push_entry(dec, index, name, *name_len,
name + *name_len, *val_len))
return -1; //error
}

View File

@ -101,7 +101,7 @@ int
lshpack_dec_decode (struct lshpack_dec *dec,
const unsigned char **src, const unsigned char *src_end,
char *dst, char *const dst_end, lshpack_strlen_t *name_len,
lshpack_strlen_t *val_len);
lshpack_strlen_t *val_len, uint32_t *name_idx);
void
lshpack_dec_set_max_capacity (struct lshpack_dec *, unsigned);
@ -156,6 +156,10 @@ struct lshpack_dec
struct lshpack_arr hpd_dyn_table;
};
unsigned
lshpack_enc_get_stx_tab_id (const char *name, lshpack_strlen_t name_len,
const char *val, lshpack_strlen_t val_len, int *val_matched);
#ifdef __cplusplus
}
#endif

View File

@ -55,6 +55,7 @@
#include "lsquic_hash.h"
#include "lsquic_attq.h"
#include "lsquic_min_heap.h"
#include "lsquic_http1x_if.h"
#define LSQUIC_LOGGER_MODULE LSQLM_ENGINE
#include "lsquic_logger.h"
@ -325,6 +326,16 @@ lsquic_engine_new (unsigned flags,
engine->stream_if_ctx = api->ea_stream_if_ctx;
engine->packets_out = api->ea_packets_out;
engine->packets_out_ctx = api->ea_packets_out_ctx;
if (api->ea_hsi_if)
{
engine->pub.enp_hsi_if = api->ea_hsi_if;
engine->pub.enp_hsi_ctx = api->ea_hsi_ctx;
}
else
{
engine->pub.enp_hsi_if = lsquic_http1x_if;
engine->pub.enp_hsi_ctx = NULL;
}
if (api->ea_pmi)
{
engine->pub.enp_pmi = api->ea_pmi;

View File

@ -14,6 +14,8 @@ struct stack_st_X509;
struct lsquic_engine_public {
struct lsquic_mm enp_mm;
struct lsquic_engine_settings enp_settings;
const struct lsquic_hset_if *enp_hsi_if;
void *enp_hsi_ctx;
int (*enp_verify_cert)(void *verify_ctx,
struct stack_st_X509 *chain);
void *enp_verify_ctx;

View File

@ -18,7 +18,7 @@
#include "lsquic_packet_out.h"
#include "lsquic_parse.h"
#include "lsquic_frame_common.h"
#include "lsquic_frame_reader.h"
#include "lsquic_headers.h"
#include "lsquic_str.h"
#include "lsquic_handshake.h"
#include "lsquic_ev_log.h"
@ -208,6 +208,7 @@ void
lsquic_ev_log_http_headers_in (lsquic_cid_t cid, int is_server,
const struct uncompressed_headers *uh)
{
const struct http1x_headers *h1h;
const char *cr, *p;
if (uh->uh_flags & UH_PP)
@ -220,13 +221,17 @@ lsquic_ev_log_http_headers_in (lsquic_cid_t cid, int is_server,
uh->uh_stream_id, uh->uh_oth_stream_id, uh->uh_weight,
(int) uh->uh_exclusive, !!(uh->uh_flags & UH_FIN));
for (p = uh->uh_headers; p < uh->uh_headers + uh->uh_size; p = cr + 2)
if (uh->uh_flags & UH_H1H)
{
cr = strchr(p, '\r');
if (cr && cr > p)
LCID(" %.*s", (int) (cr - p), p);
else
break;
h1h = uh->uh_hset;
for (p = h1h->h1h_buf; p < h1h->h1h_buf + h1h->h1h_size; p = cr + 2)
{
cr = strchr(p, '\r');
if (cr && cr > p)
LCID(" %.*s", (int) (cr - p), p);
else
break;
}
}
}

View File

@ -19,6 +19,8 @@
#include "lsquic_mm.h"
#include "lsquic_frame_common.h"
#include "lsquic_frame_reader.h"
#include "lsquic_http1x_if.h"
#include "lsquic_headers.h"
#include "lsquic_ev_log.h"
#define LSQUIC_LOGGER_MODULE LSQLM_FRAME_READER
@ -26,27 +28,6 @@
#include "lsquic_logger.h"
enum pseudo_header
{
PSEH_METHOD,
PSEH_SCHEME,
PSEH_AUTHORITY,
PSEH_PATH,
PSEH_STATUS,
N_PSEH
};
#define BIT(x) (1 << (x))
#define ALL_REQUEST_PSEH (BIT(PSEH_METHOD)|BIT(PSEH_SCHEME)|BIT(PSEH_AUTHORITY)|BIT(PSEH_PATH))
#define REQUIRED_REQUEST_PSEH (BIT(PSEH_METHOD)|BIT(PSEH_SCHEME)|BIT(PSEH_PATH))
#define ALL_SERVER_PSEH BIT(PSEH_STATUS)
#define REQUIRED_SERVER_PSEH ALL_SERVER_PSEH
#define PSEH_LEN(h) (sizeof(#h) - 5)
/* headers_state is used by HEADERS, PUSH_PROMISE, and CONTINUATION frames */
struct headers_state
{
@ -128,6 +109,9 @@ struct lsquic_frame_reader
const struct frame_reader_callbacks
*fr_callbacks;
void *fr_cb_ctx;
const struct lsquic_hset_if *fr_hsi_if;
void *fr_hsi_ctx;
struct http1x_ctor_ctx fr_h1x_ctor_ctx;
/* The the header block is shared between HEADERS, PUSH_PROMISE, and
* CONTINUATION frames. It gets added to as block fragments come in.
*/
@ -190,7 +174,8 @@ lsquic_frame_reader_new (enum frame_reader_flags flags,
struct lsquic_stream *stream, fr_stream_read_f read,
struct lshpack_dec *hdec,
const struct frame_reader_callbacks *cb,
void *frame_reader_cb_ctx)
void *frame_reader_cb_ctx,
const struct lsquic_hset_if *hsi_if, void *hsi_ctx)
{
struct lsquic_frame_reader *fr = malloc(sizeof(*fr));
if (!fr)
@ -204,6 +189,18 @@ lsquic_frame_reader_new (enum frame_reader_flags flags,
fr->fr_cb_ctx = frame_reader_cb_ctx;
fr->fr_header_block = NULL;
fr->fr_max_headers_sz = max_headers_sz;
fr->fr_hsi_if = hsi_if;
if (hsi_if == lsquic_http1x_if)
{
fr->fr_h1x_ctor_ctx = (struct http1x_ctor_ctx) {
.cid = LSQUIC_LOG_CONN_ID,
.max_headers_sz = fr->fr_max_headers_sz,
.is_server = fr->fr_flags & FRF_SERVER,
};
fr->fr_hsi_ctx = &fr->fr_h1x_ctor_ctx;
}
else
fr->fr_hsi_ctx = hsi_ctx;
reset_state(fr);
return fr;
}
@ -500,423 +497,6 @@ skip_headers_padding (struct lsquic_frame_reader *fr)
}
struct header_writer_ctx
{
struct uncompressed_headers *uh;
struct lsquic_mm *mm;
char *buf;
char *cookie_val;
unsigned cookie_sz, cookie_nalloc;
unsigned max_headers_sz,
headers_sz,
w_off;
enum {
HWC_EXPECT_COLON = (1 << 0),
HWC_SEEN_HOST = (1 << 1),
} hwc_flags;
enum pseudo_header pseh_mask;
char *pseh_bufs[N_PSEH];
lshpack_strlen_t name_len,
val_len;
};
#define HWC_PSEH_LEN(hwc, ph) ((int) strlen((hwc)->pseh_bufs[ph]))
#define HWC_PSEH_VAL(hwc, ph) ((hwc)->pseh_bufs[ph])
static int
hwc_uh_write (struct header_writer_ctx *hwc, const void *buf, size_t sz)
{
struct uncompressed_headers *uh;
if (hwc->w_off + sz > hwc->headers_sz)
{
if (hwc->headers_sz * 2 >= hwc->w_off + sz)
hwc->headers_sz *= 2;
else
hwc->headers_sz = hwc->w_off + sz;
uh = realloc(hwc->uh, sizeof(*hwc->uh) + hwc->headers_sz);
if (!uh)
return -1;
hwc->uh = uh;
}
memcpy(hwc->uh->uh_headers + hwc->w_off, buf, sz);
hwc->w_off += sz;
return 0;
}
static enum frame_reader_error
init_hwc (struct header_writer_ctx *hwc, struct lsquic_mm *mm,
unsigned max_headers_sz, unsigned headers_block_sz)
{
memset(hwc, 0, sizeof(*hwc));
hwc->hwc_flags = HWC_EXPECT_COLON;
hwc->max_headers_sz = max_headers_sz;
hwc->headers_sz = headers_block_sz * 4; /* A guess */
hwc->uh = malloc(sizeof(*hwc->uh) + hwc->headers_sz);
if (!hwc->uh)
return FR_ERR_NOMEM;
hwc->mm = mm;
hwc->buf = lsquic_mm_get_16k(mm);
if (!hwc->buf)
return FR_ERR_NOMEM;
return 0;
}
static void
deinit_hwc (struct header_writer_ctx *hwc)
{
unsigned i;
for (i = 0; i < sizeof(hwc->pseh_bufs) / sizeof(hwc->pseh_bufs[0]); ++i)
if (hwc->pseh_bufs[i])
free(hwc->pseh_bufs[i]);
if (hwc->cookie_val)
free(hwc->cookie_val);
free(hwc->uh);
if (hwc->buf)
lsquic_mm_put_16k(hwc->mm, hwc->buf);
}
static enum frame_reader_error
save_pseudo_header (const struct lsquic_frame_reader *fr,
struct header_writer_ctx *hwc, enum pseudo_header ph)
{
if (0 == (hwc->pseh_mask & BIT(ph)))
{
assert(!hwc->pseh_bufs[ph]);
hwc->pseh_bufs[ph] = malloc(hwc->val_len + 1);
if (!hwc->pseh_bufs[ph])
return FR_ERR_NOMEM;
hwc->pseh_mask |= BIT(ph);
memcpy(hwc->pseh_bufs[ph], hwc->buf + hwc->name_len, hwc->val_len);
hwc->pseh_bufs[ph][hwc->val_len] = '\0';
return 0;
}
else
{
LSQ_INFO("header %u is already present", ph);
return FR_ERR_DUPLICATE_PSEH;
}
}
static enum frame_reader_error
add_pseudo_header_to_uh (const struct lsquic_frame_reader *fr,
struct header_writer_ctx *hwc)
{
if (!(hwc->hwc_flags & HWC_EXPECT_COLON))
{
LSQ_INFO("unexpected colon");
return FR_ERR_MISPLACED_PSEH;
}
switch (hwc->name_len)
{
case 5:
if (0 == memcmp(hwc->buf, ":path", 5))
return save_pseudo_header(fr, hwc, PSEH_PATH);
break;
case 7:
switch (hwc->buf[2])
{
case 'c':
if (0 == memcmp(hwc->buf, ":scheme", 7))
return save_pseudo_header(fr, hwc, PSEH_SCHEME);
break;
case 'e':
if (0 == memcmp(hwc->buf, ":method", 7))
return save_pseudo_header(fr, hwc, PSEH_METHOD);
break;
case 't':
if (0 == memcmp(hwc->buf, ":status", 7))
return save_pseudo_header(fr, hwc, PSEH_STATUS);
break;
}
break;
case 10:
if (0 == memcmp(hwc->buf, ":authority", 10))
return save_pseudo_header(fr, hwc, PSEH_AUTHORITY);
break;
}
LSQ_INFO("unknown pseudo-header `%.*s'", hwc->name_len, hwc->buf);
return FR_ERR_UNKNOWN_PSEH;
}
#define HTTP_CODE_LEN 3
static const char *
code_str_to_reason (const char code_str[HTTP_CODE_LEN])
{
/* RFC 7231, Section 6: */
static const char *const http_reason_phrases[] =
{
#define HTTP_REASON_CODE(code, reason) [code - 100] = reason
HTTP_REASON_CODE(100, "Continue"),
HTTP_REASON_CODE(101, "Switching Protocols"),
HTTP_REASON_CODE(200, "OK"),
HTTP_REASON_CODE(201, "Created"),
HTTP_REASON_CODE(202, "Accepted"),
HTTP_REASON_CODE(203, "Non-Authoritative Information"),
HTTP_REASON_CODE(204, "No Content"),
HTTP_REASON_CODE(205, "Reset Content"),
HTTP_REASON_CODE(206, "Partial Content"),
HTTP_REASON_CODE(300, "Multiple Choices"),
HTTP_REASON_CODE(301, "Moved Permanently"),
HTTP_REASON_CODE(302, "Found"),
HTTP_REASON_CODE(303, "See Other"),
HTTP_REASON_CODE(304, "Not Modified"),
HTTP_REASON_CODE(305, "Use Proxy"),
HTTP_REASON_CODE(307, "Temporary Redirect"),
HTTP_REASON_CODE(400, "Bad Request"),
HTTP_REASON_CODE(401, "Unauthorized"),
HTTP_REASON_CODE(402, "Payment Required"),
HTTP_REASON_CODE(403, "Forbidden"),
HTTP_REASON_CODE(404, "Not Found"),
HTTP_REASON_CODE(405, "Method Not Allowed"),
HTTP_REASON_CODE(406, "Not Acceptable"),
HTTP_REASON_CODE(407, "Proxy Authentication Required"),
HTTP_REASON_CODE(408, "Request Timeout"),
HTTP_REASON_CODE(409, "Conflict"),
HTTP_REASON_CODE(410, "Gone"),
HTTP_REASON_CODE(411, "Length Required"),
HTTP_REASON_CODE(412, "Precondition Failed"),
HTTP_REASON_CODE(413, "Payload Too Large"),
HTTP_REASON_CODE(414, "URI Too Long"),
HTTP_REASON_CODE(415, "Unsupported Media Type"),
HTTP_REASON_CODE(416, "Range Not Satisfiable"),
HTTP_REASON_CODE(417, "Expectation Failed"),
HTTP_REASON_CODE(426, "Upgrade Required"),
HTTP_REASON_CODE(500, "Internal Server Error"),
HTTP_REASON_CODE(501, "Not Implemented"),
HTTP_REASON_CODE(502, "Bad Gateway"),
HTTP_REASON_CODE(503, "Service Unavailable"),
HTTP_REASON_CODE(504, "Gateway Timeout"),
HTTP_REASON_CODE(505, "HTTP Version Not Supported"),
#undef HTTP_REASON_CODE
};
long code;
char code_buf[HTTP_CODE_LEN + 1];
memcpy(code_buf, code_str, HTTP_CODE_LEN);
code_buf[HTTP_CODE_LEN] = '\0';
code = strtol(code_buf, NULL, 10) - 100;
if (code > 0 && code < (long) (sizeof(http_reason_phrases) /
sizeof(http_reason_phrases[0])))
return http_reason_phrases[code];
else
return NULL;
}
static enum frame_reader_error
convert_response_pseudo_headers (const struct lsquic_frame_reader *fr,
struct header_writer_ctx *hwc)
{
if ((hwc->pseh_mask & REQUIRED_SERVER_PSEH) != REQUIRED_SERVER_PSEH)
{
LSQ_INFO("not all response pseudo-headers are specified");
return FR_ERR_INCOMPL_RESP_PSEH;
}
if (hwc->pseh_mask & ALL_REQUEST_PSEH)
{
LSQ_INFO("response pseudo-headers contain request-only headers");
return FR_ERR_UNNEC_REQ_PSEH;
}
const char *code_str, *reason;
int code_len;
code_str = HWC_PSEH_VAL(hwc, PSEH_STATUS);
code_len = HWC_PSEH_LEN(hwc, PSEH_STATUS);
#define HWC_UH_WRITE(h, buf, sz) do { \
if (0 != hwc_uh_write(h, buf, sz)) \
return FR_ERR_NOMEM; \
} while (0)
HWC_UH_WRITE(hwc, "HTTP/1.1 ", 9);
HWC_UH_WRITE(hwc, code_str, code_len);
if (HTTP_CODE_LEN == code_len && (reason = code_str_to_reason(code_str)))
{
HWC_UH_WRITE(hwc, " ", 1);
HWC_UH_WRITE(hwc, reason, strlen(reason));
HWC_UH_WRITE(hwc, "\r\n", 2);
}
else
HWC_UH_WRITE(hwc, " \r\n", 3);
if (hwc->max_headers_sz && hwc->w_off > hwc->max_headers_sz)
{
LSQ_INFO("headers too large");
return FR_ERR_HEADERS_TOO_LARGE;
}
return 0;
#undef HWC_UH_WRITE
}
static enum frame_reader_error
convert_request_pseudo_headers (const struct lsquic_frame_reader *fr,
struct header_writer_ctx *hwc)
{
if ((hwc->pseh_mask & REQUIRED_REQUEST_PSEH) != REQUIRED_REQUEST_PSEH)
{
LSQ_INFO("not all request pseudo-headers are specified");
return FR_ERR_INCOMPL_REQ_PSEH;
}
if (hwc->pseh_mask & ALL_SERVER_PSEH)
{
LSQ_INFO("request pseudo-headers contain response-only headers");
return FR_ERR_UNNEC_RESP_PSEH;
}
#define HWC_UH_WRITE(h, buf, sz) do { \
if (0 != hwc_uh_write(h, buf, sz)) \
return FR_ERR_NOMEM; \
} while (0)
HWC_UH_WRITE(hwc, HWC_PSEH_VAL(hwc, PSEH_METHOD), HWC_PSEH_LEN(hwc, PSEH_METHOD));
HWC_UH_WRITE(hwc, " ", 1);
HWC_UH_WRITE(hwc, HWC_PSEH_VAL(hwc, PSEH_PATH), HWC_PSEH_LEN(hwc, PSEH_PATH));
HWC_UH_WRITE(hwc, " HTTP/1.1\r\n", 11);
if (hwc->max_headers_sz && hwc->w_off > hwc->max_headers_sz)
{
LSQ_INFO("headers too large");
return FR_ERR_HEADERS_TOO_LARGE;
}
return 0;
#undef HWC_UH_WRITE
}
static enum frame_reader_error
convert_pseudo_headers (const struct lsquic_frame_reader *fr,
struct header_writer_ctx *hwc)
{
/* We are *reading* the message. Thus, a server expects a request, and a
* client expects a response. Unless we receive a push promise from the
* server, in which case this should also be a request.
*/
if ((fr->fr_flags & FRF_SERVER) ||
READER_PUSH_PROMISE == fr->fr_state.reader_type)
return convert_request_pseudo_headers(fr, hwc);
else
return convert_response_pseudo_headers(fr, hwc);
}
static enum frame_reader_error
save_cookie (struct header_writer_ctx *hwc)
{
char *cookie_val;
if (0 == hwc->cookie_sz)
{
hwc->cookie_nalloc = hwc->cookie_sz = hwc->val_len;
cookie_val = malloc(hwc->cookie_nalloc);
if (!cookie_val)
return FR_ERR_NOMEM;
hwc->cookie_val = cookie_val;
memcpy(hwc->cookie_val, hwc->buf + hwc->name_len, hwc->val_len);
}
else
{
hwc->cookie_sz += hwc->val_len + 2 /* "; " */;
if (hwc->cookie_sz > hwc->cookie_nalloc)
{
hwc->cookie_nalloc = hwc->cookie_nalloc * 2 + hwc->val_len + 2;
cookie_val = realloc(hwc->cookie_val, hwc->cookie_nalloc);
if (!cookie_val)
return FR_ERR_NOMEM;
hwc->cookie_val = cookie_val;
}
memcpy(hwc->cookie_val + hwc->cookie_sz - hwc->val_len - 2, "; ", 2);
memcpy(hwc->cookie_val + hwc->cookie_sz - hwc->val_len,
hwc->buf + hwc->name_len, hwc->val_len);
}
return 0;
}
static enum frame_reader_error
add_real_header_to_uh (const struct lsquic_frame_reader *fr,
struct header_writer_ctx *hwc)
{
enum frame_reader_error err;
unsigned i;
int n_upper;
if (hwc->hwc_flags & HWC_EXPECT_COLON)
{
if (0 != (err = convert_pseudo_headers(fr, hwc)))
return err;
hwc->hwc_flags &= ~HWC_EXPECT_COLON;
}
if (4 == hwc->name_len && 0 == memcmp(hwc->buf, "host", 4))
hwc->hwc_flags |= HWC_SEEN_HOST;
n_upper = 0;
for (i = 0; i < hwc->name_len; ++i)
n_upper += isupper(hwc->buf[i]);
if (n_upper > 0)
{
LSQ_INFO("Header name `%.*s' contains uppercase letters",
hwc->name_len, hwc->buf);
return FR_ERR_UPPERCASE_HEADER;
}
if (6 == hwc->name_len && memcmp(hwc->buf, "cookie", 6) == 0)
{
return save_cookie(hwc);
}
#define HWC_UH_WRITE(h, buf, sz) do { \
if (0 != hwc_uh_write(h, buf, sz)) \
return FR_ERR_NOMEM; \
} while (0)
HWC_UH_WRITE(hwc, hwc->buf, hwc->name_len);
HWC_UH_WRITE(hwc, ": ", 2);
HWC_UH_WRITE(hwc, hwc->buf + hwc->name_len, hwc->val_len);
HWC_UH_WRITE(hwc, "\r\n", 2);
if (hwc->max_headers_sz && hwc->w_off > hwc->max_headers_sz)
{
LSQ_INFO("headers too large");
return FR_ERR_HEADERS_TOO_LARGE;
}
return 0;
#undef HWC_UH_WRITE
}
static enum frame_reader_error
add_header_to_uh (const struct lsquic_frame_reader *fr,
struct header_writer_ctx *hwc)
{
LSQ_DEBUG("Got header '%.*s': '%.*s'", hwc->name_len, hwc->buf,
hwc->val_len, hwc->buf + hwc->name_len);
if (':' == hwc->buf[0])
return add_pseudo_header_to_uh(fr, hwc);
else
return add_real_header_to_uh(fr, hwc);
}
static int
decode_and_pass_payload (struct lsquic_frame_reader *fr)
{
@ -924,11 +504,26 @@ decode_and_pass_payload (struct lsquic_frame_reader *fr)
const unsigned char *comp, *end;
enum frame_reader_error err;
int s;
struct header_writer_ctx hwc;
uint32_t name_idx;
lshpack_strlen_t name_len, val_len;
char *buf;
struct uncompressed_headers *uh = NULL;
void *hset = NULL;
err = init_hwc(&hwc, fr->fr_mm, fr->fr_max_headers_sz, fr->fr_header_block_sz);
if (0 != err)
buf = lsquic_mm_get_16k(fr->fr_mm);
if (!buf)
{
err = FR_ERR_NOMEM;
goto stream_error;
}
hset = fr->fr_hsi_if->hsi_create_header_set(fr->fr_hsi_ctx,
READER_PUSH_PROMISE == fr->fr_state.reader_type);
if (!hset)
{
err = FR_ERR_NOMEM;
goto stream_error;
}
comp = fr->fr_header_block;
end = comp + fr->fr_header_block_sz;
@ -936,11 +531,12 @@ decode_and_pass_payload (struct lsquic_frame_reader *fr)
while (comp < end)
{
s = lshpack_dec_decode(fr->fr_hdec, &comp, end,
hwc.buf, hwc.buf + 16 * 1024,
&hwc.name_len, &hwc.val_len);
buf, buf + 16 * 1024, &name_len, &val_len, &name_idx);
if (s == 0)
{
err = add_header_to_uh(fr, &hwc);
err = (enum frame_reader_error)
fr->fr_hsi_if->hsi_process_header(hset, name_idx, buf,
name_len, buf + name_len, val_len);
if (err == 0)
continue;
}
@ -949,93 +545,63 @@ decode_and_pass_payload (struct lsquic_frame_reader *fr)
goto stream_error;
}
assert(comp == end);
lsquic_mm_put_16k(fr->fr_mm, buf);
buf = NULL;
if (hwc.hwc_flags & HWC_EXPECT_COLON)
err = (enum frame_reader_error)
fr->fr_hsi_if->hsi_process_header(hset, 0, 0, 0, 0, 0);
if (err)
goto stream_error;
uh = calloc(1, sizeof(*uh));
if (!uh)
{
err = convert_pseudo_headers(fr, &hwc);
if (0 != err)
goto stream_error;
hwc.hwc_flags &= ~HWC_EXPECT_COLON;
}
#define HWC_UH_WRITE(h, buf, sz) do { \
err = hwc_uh_write(h, buf, sz); \
if (0 != err) \
goto stream_error; \
} while (0)
if ((hwc.pseh_mask & BIT(PSEH_AUTHORITY)) &&
0 == (hwc.hwc_flags & HWC_SEEN_HOST))
{
LSQ_DEBUG("Setting 'Host: %.*s'", HWC_PSEH_LEN(&hwc, PSEH_AUTHORITY),
HWC_PSEH_VAL(&hwc, PSEH_AUTHORITY));
HWC_UH_WRITE(&hwc, "Host: ", 6);
HWC_UH_WRITE(&hwc, HWC_PSEH_VAL(&hwc, PSEH_AUTHORITY), HWC_PSEH_LEN(&hwc, PSEH_AUTHORITY));
HWC_UH_WRITE(&hwc, "\r\n", 2);
}
if (hwc.cookie_val)
{
LSQ_DEBUG("Setting 'Cookie: %.*s'", hwc.cookie_sz, hwc.cookie_val);
HWC_UH_WRITE(&hwc, "Cookie: ", 8);
HWC_UH_WRITE(&hwc, hwc.cookie_val, hwc.cookie_sz);
HWC_UH_WRITE(&hwc, "\r\n", 2);
}
HWC_UH_WRITE(&hwc, "\r\n", 2 + 1 /* NUL byte */);
hwc.w_off -= 1; /* Do not count NUL byte */
if (hwc.max_headers_sz && hwc.w_off > hwc.max_headers_sz)
{
LSQ_INFO("headers too large");
err = FR_ERR_HEADERS_TOO_LARGE;
err = FR_ERR_NOMEM;
goto stream_error;
}
memcpy(&hwc.uh->uh_stream_id, fr->fr_state.header.hfh_stream_id,
sizeof(hwc.uh->uh_stream_id));
hwc.uh->uh_stream_id = ntohl(hwc.uh->uh_stream_id);
hwc.uh->uh_size = hwc.w_off;
hwc.uh->uh_oth_stream_id = hs->oth_stream_id;
hwc.uh->uh_off = 0;
memcpy(&uh->uh_stream_id, fr->fr_state.header.hfh_stream_id,
sizeof(uh->uh_stream_id));
uh->uh_stream_id = ntohl(uh->uh_stream_id);
uh->uh_oth_stream_id = hs->oth_stream_id;
if (HTTP_FRAME_HEADERS == fr->fr_state.by_type.headers_state.frame_type)
{
hwc.uh->uh_weight = hs->weight;
hwc.uh->uh_exclusive = hs->exclusive;
hwc.uh->uh_flags = 0;
uh->uh_weight = hs->weight;
uh->uh_exclusive = hs->exclusive;
uh->uh_flags = 0;
}
else
{
assert(HTTP_FRAME_PUSH_PROMISE ==
fr->fr_state.by_type.headers_state.frame_type);
hwc.uh->uh_weight = 0; /* Zero unused value */
hwc.uh->uh_exclusive = 0; /* Zero unused value */
hwc.uh->uh_flags = UH_PP;
uh->uh_weight = 0; /* Zero unused value */
uh->uh_exclusive = 0; /* Zero unused value */
uh->uh_flags = UH_PP;
}
if (fr->fr_state.header.hfh_flags & HFHF_END_STREAM)
hwc.uh->uh_flags |= UH_FIN;
uh->uh_flags |= UH_FIN;
if (fr->fr_hsi_if == lsquic_http1x_if)
uh->uh_flags |= UH_H1H;
uh->uh_hset = hset;
EV_LOG_HTTP_HEADERS_IN(LSQUIC_LOG_CONN_ID, fr->fr_flags & FRF_SERVER,
hwc.uh);
EV_LOG_HTTP_HEADERS_IN(LSQUIC_LOG_CONN_ID, fr->fr_flags & FRF_SERVER, uh);
if (HTTP_FRAME_HEADERS == fr->fr_state.by_type.headers_state.frame_type)
fr->fr_callbacks->frc_on_headers(fr->fr_cb_ctx, hwc.uh);
fr->fr_callbacks->frc_on_headers(fr->fr_cb_ctx, uh);
else
fr->fr_callbacks->frc_on_push_promise(fr->fr_cb_ctx, hwc.uh);
hwc.uh = NULL;
deinit_hwc(&hwc);
fr->fr_callbacks->frc_on_push_promise(fr->fr_cb_ctx, uh);
return 0;
stream_error:
LSQ_INFO("%s: stream error %u", __func__, err);
deinit_hwc(&hwc);
if (hset)
fr->fr_hsi_if->hsi_discard_header_set(hset);
if (uh)
free(uh);
if (buf)
lsquic_mm_put_16k(fr->fr_mm, buf);
fr->fr_callbacks->frc_on_error(fr->fr_cb_ctx, fr_get_stream_id(fr), err);
return 0;
#undef HWC_UH_WRITE
}

View File

@ -16,6 +16,8 @@ struct lshpack_dec;
struct lsquic_mm;
struct lsquic_stream;
struct lsquic_frame_reader;
struct lsquic_hset_if;
struct uncompressed_headers;
enum frame_reader_flags
@ -31,19 +33,18 @@ enum frame_reader_flags
*/
enum frame_reader_error
{
FR_ERR_DUPLICATE_PSEH = 1, /* Duplicate pseudo-header */
FR_ERR_INCOMPL_REQ_PSEH, /* Not all request pseudo-headers are present */
FR_ERR_UNNEC_REQ_PSEH, /* Unnecessary request pseudo-header present in
* the response.
*/
FR_ERR_INCOMPL_RESP_PSEH, /* Not all response pseudo-headers are present */
FR_ERR_UNNEC_RESP_PSEH, /* Unnecessary response pseudo-header present in
* the response.
*/
FR_ERR_UNKNOWN_PSEH, /* Unknown pseudo-header */
FR_ERR_UPPERCASE_HEADER, /* Uppercase letter in header */
FR_ERR_MISPLACED_PSEH,
FR_ERR_MISSING_PSEH,
FR_ERR_DUPLICATE_PSEH = LSQUIC_HDR_ERR_DUPLICATE_PSDO_HDR,
FR_ERR_INCOMPL_REQ_PSEH = LSQUIC_HDR_ERR_INCOMPL_REQ_PSDO_HDR,
FR_ERR_UNNEC_REQ_PSEH = LSQUIC_HDR_ERR_UNNEC_REQ_PSDO_HDR,
FR_ERR_INCOMPL_RESP_PSEH = LSQUIC_HDR_ERR_INCOMPL_RESP_PSDO_HDR,
FR_ERR_UNNEC_RESP_PSEH = LSQUIC_HDR_ERR_UNNEC_RESP_PSDO_HDR,
FR_ERR_UNKNOWN_PSEH = LSQUIC_HDR_ERR_UNKNOWN_PSDO_HDR,
FR_ERR_UPPERCASE_HEADER = LSQUIC_HDR_ERR_UPPERCASE_HEADER,
FR_ERR_MISPLACED_PSEH = LSQUIC_HDR_ERR_MISPLACED_PSDO_HDR,
FR_ERR_MISSING_PSEH = LSQUIC_HDR_ERR_MISSING_PSDO_HDR,
FR_ERR_HEADERS_TOO_LARGE = LSQUIC_HDR_ERR_HEADERS_TOO_LARGE,
FR_ERR_NOMEM = LSQUIC_HDR_ERR_NOMEM,
FR_ERR_DECOMPRESS,
FR_ERR_INVALID_FRAME_SIZE, /* E.g. a SETTINGS frame length is not a multiple
* of 6 (RFC 7540, Section 6.5.1).
@ -53,45 +54,11 @@ enum frame_reader_error
FR_ERR_SELF_DEP_STREAM, /* A stream in priority frame cannot depend on
* itself (RFC 7540, Section 5.3.1).
*/
FR_ERR_HEADERS_TOO_LARGE,
FR_ERR_UNEXPECTED_PUSH,
FR_ERR_NOMEM, /* Cannot allocate any more memory. */
FR_ERR_EXPECTED_CONTIN, /* Expected continuation frame. */
};
/* This struct is used to return decoded HEADERS and PUSH_PROMISE frames.
* Some of the fields are only used for HEADERS frames. They are marked
* with "H" comment below.
*/
struct uncompressed_headers
{
uint32_t uh_stream_id;
uint32_t uh_oth_stream_id; /* For HEADERS frame, the ID of the
* stream that this stream depends
* on. (Zero means unset.) For
* PUSH_PROMISE, the promised stream
* ID.
*/
unsigned uh_size; /* Number of characters in uh_headers, not
* counting the NUL byte.
*/
unsigned /* H */ uh_off;
unsigned short /* H */ uh_weight; /* 1 - 256; 0 means not set */
signed char /* H */ uh_exclusive; /* 0 or 1 when set; -1 means not set */
enum {
/* H */ UH_FIN = (1 << 0),
UH_PP = (1 << 1), /* Push promise */
} uh_flags:8;
char uh_headers[ /* NUL-terminated C string */
#if FRAME_READER_TESTING
FRAME_READER_TESTING
#else
0
#endif
];
};
struct frame_reader_callbacks
{
void (*frc_on_headers) (void *frame_cb_ctx, struct uncompressed_headers *);
@ -111,8 +78,8 @@ struct lsquic_frame_reader *
lsquic_frame_reader_new (enum frame_reader_flags, unsigned max_headers_sz,
struct lsquic_mm *, struct lsquic_stream *,
fr_stream_read_f, struct lshpack_dec *,
const struct frame_reader_callbacks *,
void *fr_cb_ctx);
const struct frame_reader_callbacks *, void *fr_cb_ctx,
const struct lsquic_hset_if *, void *hsi_ctx);
int
lsquic_frame_reader_read (struct lsquic_frame_reader *);

View File

@ -50,8 +50,6 @@ struct frame_buf
#define frab_left_to_write(f) ((unsigned short) sizeof((f)->frab_buf) - (f)->frab_size)
#define frab_write_to(f) ((f)->frab_buf + (f)->frab_size)
#define MAX_HEADERS_SIZE (64 * 1024)
/* Make sure that frab_buf is at least five bytes long, otherwise a frame
* won't fit into two adjacent frabs.
*/

View File

@ -9,6 +9,8 @@
#include <stddef.h>
#include <stdint.h>
#define MAX_HEADERS_SIZE (64 * 1024)
struct iovec;
struct lshpack_enc;
struct lsquic_mm;

View File

@ -42,12 +42,15 @@
#include "lsquic_headers_stream.h"
#include "lsquic_frame_common.h"
#include "lsquic_frame_reader.h"
#include "lsquic_frame_writer.h"
#include "lsquic_http1x_if.h"
#include "lsquic_mm.h"
#include "lsquic_engine_public.h"
#include "lsquic_spi.h"
#include "lsquic_ev_log.h"
#include "lsquic_version.h"
#include "lsquic_hash.h"
#include "lsquic_headers.h"
#include "lsquic_conn.h"
#include "lsquic_conn_public.h"
@ -595,7 +598,7 @@ new_conn_common (lsquic_cid_t cid, struct lsquic_engine_public *enpub,
if (conn->fc_flags & FC_HTTP)
{
conn->fc_pub.hs = lsquic_headers_stream_new(
!!(conn->fc_flags & FC_SERVER), conn->fc_pub.mm, conn->fc_settings,
!!(conn->fc_flags & FC_SERVER), conn->fc_enpub,
headers_callbacks_ptr, conn);
if (!conn->fc_pub.hs)
goto cleanup_on_error;

View File

@ -0,0 +1,43 @@
/* Copyright (c) 2017 - 2018 LiteSpeed Technologies Inc. See LICENSE. */
#ifndef LSQUIC_HEADERS_H
#define LSQUIC_HEADERS_H 1
#include <stdint.h>
/* When ea_hsi_if is not specified, the headers are converted to a C string
* that contains HTTP/1.x-like header structure.
*/
struct http1x_headers
{
unsigned h1h_size; /* Number of characters in h1h_buf, not
* counting the NUL byte.
*/
unsigned h1h_off; /* Reading offset */
char *h1h_buf;
};
/* This struct is used to return decoded HEADERS and PUSH_PROMISE frames.
* Some of the fields are only used for HEADERS frames. They are marked
* with "H" comment below.
*/
struct uncompressed_headers
{
uint32_t uh_stream_id;
uint32_t uh_oth_stream_id; /* For HEADERS frame, the ID of the
* stream that this stream depends
* on. (Zero means unset.) For
* PUSH_PROMISE, the promised stream
* ID.
*/
unsigned short /* H */ uh_weight; /* 1 - 256; 0 means not set */
signed char /* H */ uh_exclusive; /* 0 or 1 when set; -1 means not set */
enum {
/* H */ UH_FIN = (1 << 0),
UH_PP = (1 << 1), /* Push promise */
UH_H1H = (1 << 2), /* uh_hset points to http1x_headers */
} uh_flags:8;
void *uh_hset;
};
#endif

View File

@ -13,12 +13,15 @@
#include <vc_compat.h>
#endif
#include "lsquic.h"
#include "lsquic_types.h"
#include "lsquic_int_types.h"
#include "lsquic_frame_common.h"
#include "lsquic_frame_reader.h"
#include "lsquic_frame_writer.h"
#include "lsquic_mm.h"
#include "lsquic_engine_public.h"
#include "lshpack.h"
#include "lsquic.h"
#include "lsquic_headers_stream.h"
@ -38,16 +41,14 @@ struct headers_stream
struct lsquic_frame_writer *hs_fw;
const struct headers_stream_callbacks
*hs_callbacks;
const struct lsquic_engine_settings
*hs_settings;
void *hs_cb_ctx;
struct lsquic_mm *hs_mm;
struct lshpack_enc hs_henc;
struct lshpack_dec hs_hdec;
enum {
HS_IS_SERVER = (1 << 0),
HS_HENC_INITED = (1 << 1),
} hs_flags;
struct lsquic_engine_public *hs_enpub;
};
@ -85,18 +86,18 @@ headers_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
hs->hs_stream = stream;
LSQ_DEBUG("stream created");
hs->hs_fr = lsquic_frame_reader_new((hs->hs_flags & HS_IS_SERVER) ? FRF_SERVER : 0,
MAX_HEADERS_SIZE, hs->hs_mm,
MAX_HEADERS_SIZE, &hs->hs_enpub->enp_mm,
stream, lsquic_stream_read, &hs->hs_hdec,
frame_callbacks_ptr, hs);
frame_callbacks_ptr, hs,
hs->hs_enpub->enp_hsi_if, hs->hs_enpub->enp_hsi_ctx);
if (!hs->hs_fr)
{
LSQ_WARN("could not create frame reader: %s", strerror(errno));
hs->hs_callbacks->hsc_on_conn_error(hs->hs_cb_ctx);
return NULL;
}
hs->hs_fw = lsquic_frame_writer_new(hs->hs_mm, stream, 0, &hs->hs_henc,
lsquic_stream_write,
(hs->hs_flags & HS_IS_SERVER));
hs->hs_fw = lsquic_frame_writer_new(&hs->hs_enpub->enp_mm, stream, 0,
&hs->hs_henc, lsquic_stream_write, (hs->hs_flags & HS_IS_SERVER));
if (!hs->hs_fw)
{
LSQ_WARN("could not create frame writer: %s", strerror(errno));
@ -193,8 +194,7 @@ lsquic_headers_stream_send_priority (struct headers_stream *hs,
struct headers_stream *
lsquic_headers_stream_new (int is_server, struct lsquic_mm *mm,
const struct lsquic_engine_settings *settings,
lsquic_headers_stream_new (int is_server, struct lsquic_engine_public *enpub,
const struct headers_stream_callbacks *callbacks,
void *cb_ctx)
{
@ -207,8 +207,7 @@ lsquic_headers_stream_new (int is_server, struct lsquic_mm *mm,
hs->hs_flags = HS_IS_SERVER;
else
hs->hs_flags = 0;
hs->hs_settings = settings;
hs->hs_mm = mm;
hs->hs_enpub = enpub;
return hs;
}

View File

@ -16,7 +16,7 @@ struct lsquic_http_headers;
struct lsquic_frame_reader;
struct lsquic_frame_writer;
struct uncompressed_headers;
struct lsquic_engine_settings;
struct lsquic_engine_public;
struct lsquic_http2_setting;
@ -38,8 +38,7 @@ struct headers_stream_callbacks
struct headers_stream *
lsquic_headers_stream_new (int is_server, struct lsquic_mm *,
const struct lsquic_engine_settings *,
lsquic_headers_stream_new (int is_server, struct lsquic_engine_public *,
const struct headers_stream_callbacks *,
void *hs_cb_ctx);

View File

@ -0,0 +1,525 @@
/* Copyright (c) 2017 - 2018 LiteSpeed Technologies Inc. See LICENSE. */
#include <assert.h>
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "lsquic.h"
#include "lsquic_headers.h"
#include "lsquic_http1x_if.h"
#include "lshpack.h"
#define LSQUIC_LOGGER_MODULE LSQLM_HTTP1X
#define LSQUIC_LOG_CONN_ID hwc->hwc_cid
#include "lsquic_logger.h"
enum pseudo_header
{
PSEH_METHOD,
PSEH_SCHEME,
PSEH_AUTHORITY,
PSEH_PATH,
PSEH_STATUS,
N_PSEH
};
#define BIT(x) (1 << (x))
#define ALL_REQUEST_PSEH (BIT(PSEH_METHOD)|BIT(PSEH_SCHEME)|BIT(PSEH_AUTHORITY)|BIT(PSEH_PATH))
#define REQUIRED_REQUEST_PSEH (BIT(PSEH_METHOD)|BIT(PSEH_SCHEME)|BIT(PSEH_PATH))
#define ALL_SERVER_PSEH BIT(PSEH_STATUS)
#define REQUIRED_SERVER_PSEH ALL_SERVER_PSEH
#define PSEH_LEN(h) (sizeof(#h) - 5)
struct header_writer_ctx
{
lsquic_cid_t hwc_cid;
char *buf;
char *cookie_val;
unsigned cookie_sz, cookie_nalloc;
unsigned max_headers_sz,
headers_sz,
w_off;
enum {
HWC_SERVER = 1 << 0,
HWC_EXPECT_COLON = 1 << 1,
HWC_SEEN_HOST = 1 << 2,
HWC_PUSH_PROMISE = 1 << 3,
} hwc_flags;
enum pseudo_header pseh_mask;
char *pseh_bufs[N_PSEH];
struct http1x_headers hwc_h1h;
};
#define HWC_PSEH_LEN(hwc, ph) ((int) strlen((hwc)->pseh_bufs[ph]))
#define HWC_PSEH_VAL(hwc, ph) ((hwc)->pseh_bufs[ph])
static void *
h1h_create_header_set (void *ctx, int is_push_promise)
{
const struct http1x_ctor_ctx *hcc = ctx;
struct header_writer_ctx *hwc;
hwc = calloc(1, sizeof(*hwc));
if (!hwc)
return NULL;
hwc->hwc_flags = HWC_EXPECT_COLON;
if (hcc->is_server)
hwc->hwc_flags |= HWC_SERVER;
if (is_push_promise)
hwc->hwc_flags |= HWC_PUSH_PROMISE;
hwc->max_headers_sz = hcc->max_headers_sz;
hwc->hwc_cid = hcc->cid;
return &hwc->hwc_h1h;
}
static int
hwc_uh_write (struct header_writer_ctx *hwc, const void *buf, size_t sz)
{
char *h1h_buf;
if (hwc->w_off + sz > hwc->headers_sz)
{
if (hwc->headers_sz * 2 >= hwc->w_off + sz)
hwc->headers_sz *= 2;
else
hwc->headers_sz = hwc->w_off + sz;
h1h_buf = realloc(hwc->hwc_h1h.h1h_buf, hwc->headers_sz);
if (!buf)
return -1;
hwc->hwc_h1h.h1h_buf = h1h_buf;
}
memcpy(&hwc->hwc_h1h.h1h_buf[hwc->w_off], buf, sz);
hwc->w_off += sz;
return 0;
}
static enum lsquic_header_status
save_pseudo_header (struct header_writer_ctx *hwc, enum pseudo_header ph,
const char *val, unsigned val_len)
{
if (0 == (hwc->pseh_mask & BIT(ph)))
{
assert(!hwc->pseh_bufs[ph]);
hwc->pseh_bufs[ph] = malloc(val_len + 1);
if (!hwc->pseh_bufs[ph])
return LSQUIC_HDR_ERR_NOMEM;
hwc->pseh_mask |= BIT(ph);
memcpy(hwc->pseh_bufs[ph], val, val_len);
hwc->pseh_bufs[ph][val_len] = '\0';
return LSQUIC_HDR_OK;
}
else
{
LSQ_INFO("header %u is already present", ph);
return LSQUIC_HDR_ERR_DUPLICATE_PSDO_HDR;
}
}
static enum lsquic_header_status
add_pseudo_header (struct header_writer_ctx *hwc, const char *name,
unsigned name_len, const char *val, unsigned val_len)
{
if (!(hwc->hwc_flags & HWC_EXPECT_COLON))
{
LSQ_INFO("unexpected colon");
return LSQUIC_HDR_ERR_MISPLACED_PSDO_HDR;
}
switch (name_len)
{
case 5:
if (0 == memcmp(name, ":path", 5))
return save_pseudo_header(hwc, PSEH_PATH, val, val_len);
break;
case 7:
switch (name[2])
{
case 'c':
if (0 == memcmp(name, ":scheme", 7))
return save_pseudo_header(hwc, PSEH_SCHEME, val, val_len);
break;
case 'e':
if (0 == memcmp(name, ":method", 7))
return save_pseudo_header(hwc, PSEH_METHOD, val, val_len);
break;
case 't':
if (0 == memcmp(name, ":status", 7))
return save_pseudo_header(hwc, PSEH_STATUS, val, val_len);
break;
}
break;
case 10:
if (0 == memcmp(name, ":authority", 10))
return save_pseudo_header(hwc, PSEH_AUTHORITY, val, val_len);
break;
}
LSQ_INFO("unknown pseudo-header `%.*s'", name_len, name);
return LSQUIC_HDR_ERR_UNKNOWN_PSDO_HDR;
}
#define HTTP_CODE_LEN 3
static const char *
code_str_to_reason (const char code_str[HTTP_CODE_LEN])
{
/* RFC 7231, Section 6: */
static const char *const http_reason_phrases[] =
{
#define HTTP_REASON_CODE(code, reason) [code - 100] = reason
HTTP_REASON_CODE(100, "Continue"),
HTTP_REASON_CODE(101, "Switching Protocols"),
HTTP_REASON_CODE(200, "OK"),
HTTP_REASON_CODE(201, "Created"),
HTTP_REASON_CODE(202, "Accepted"),
HTTP_REASON_CODE(203, "Non-Authoritative Information"),
HTTP_REASON_CODE(204, "No Content"),
HTTP_REASON_CODE(205, "Reset Content"),
HTTP_REASON_CODE(206, "Partial Content"),
HTTP_REASON_CODE(300, "Multiple Choices"),
HTTP_REASON_CODE(301, "Moved Permanently"),
HTTP_REASON_CODE(302, "Found"),
HTTP_REASON_CODE(303, "See Other"),
HTTP_REASON_CODE(304, "Not Modified"),
HTTP_REASON_CODE(305, "Use Proxy"),
HTTP_REASON_CODE(307, "Temporary Redirect"),
HTTP_REASON_CODE(400, "Bad Request"),
HTTP_REASON_CODE(401, "Unauthorized"),
HTTP_REASON_CODE(402, "Payment Required"),
HTTP_REASON_CODE(403, "Forbidden"),
HTTP_REASON_CODE(404, "Not Found"),
HTTP_REASON_CODE(405, "Method Not Allowed"),
HTTP_REASON_CODE(406, "Not Acceptable"),
HTTP_REASON_CODE(407, "Proxy Authentication Required"),
HTTP_REASON_CODE(408, "Request Timeout"),
HTTP_REASON_CODE(409, "Conflict"),
HTTP_REASON_CODE(410, "Gone"),
HTTP_REASON_CODE(411, "Length Required"),
HTTP_REASON_CODE(412, "Precondition Failed"),
HTTP_REASON_CODE(413, "Payload Too Large"),
HTTP_REASON_CODE(414, "URI Too Long"),
HTTP_REASON_CODE(415, "Unsupported Media Type"),
HTTP_REASON_CODE(416, "Range Not Satisfiable"),
HTTP_REASON_CODE(417, "Expectation Failed"),
HTTP_REASON_CODE(426, "Upgrade Required"),
HTTP_REASON_CODE(500, "Internal Server Error"),
HTTP_REASON_CODE(501, "Not Implemented"),
HTTP_REASON_CODE(502, "Bad Gateway"),
HTTP_REASON_CODE(503, "Service Unavailable"),
HTTP_REASON_CODE(504, "Gateway Timeout"),
HTTP_REASON_CODE(505, "HTTP Version Not Supported"),
#undef HTTP_REASON_CODE
};
long code;
char code_buf[HTTP_CODE_LEN + 1];
memcpy(code_buf, code_str, HTTP_CODE_LEN);
code_buf[HTTP_CODE_LEN] = '\0';
code = strtol(code_buf, NULL, 10) - 100;
if (code > 0 && code < (long) (sizeof(http_reason_phrases) /
sizeof(http_reason_phrases[0])))
return http_reason_phrases[code];
else
return NULL;
}
static enum lsquic_header_status
convert_response_pseudo_headers (struct header_writer_ctx *hwc)
{
if ((hwc->pseh_mask & REQUIRED_SERVER_PSEH) != REQUIRED_SERVER_PSEH)
{
LSQ_INFO("not all response pseudo-headers are specified");
return LSQUIC_HDR_ERR_INCOMPL_RESP_PSDO_HDR;
}
if (hwc->pseh_mask & ALL_REQUEST_PSEH)
{
LSQ_INFO("response pseudo-headers contain request-only headers");
return LSQUIC_HDR_ERR_UNNEC_REQ_PSDO_HDR;
}
const char *code_str, *reason;
int code_len;
code_str = HWC_PSEH_VAL(hwc, PSEH_STATUS);
code_len = HWC_PSEH_LEN(hwc, PSEH_STATUS);
#define HWC_UH_WRITE(h, buf, sz) do { \
if (0 != hwc_uh_write(h, buf, sz)) \
return LSQUIC_HDR_ERR_NOMEM; \
} while (0)
HWC_UH_WRITE(hwc, "HTTP/1.1 ", 9);
HWC_UH_WRITE(hwc, code_str, code_len);
if (HTTP_CODE_LEN == code_len && (reason = code_str_to_reason(code_str)))
{
HWC_UH_WRITE(hwc, " ", 1);
HWC_UH_WRITE(hwc, reason, strlen(reason));
HWC_UH_WRITE(hwc, "\r\n", 2);
}
else
HWC_UH_WRITE(hwc, " \r\n", 3);
if (hwc->max_headers_sz && hwc->w_off > hwc->max_headers_sz)
{
LSQ_INFO("headers too large");
return LSQUIC_HDR_ERR_HEADERS_TOO_LARGE;
}
return LSQUIC_HDR_OK;
#undef HWC_UH_WRITE
}
static enum lsquic_header_status
convert_request_pseudo_headers (struct header_writer_ctx *hwc)
{
if ((hwc->pseh_mask & REQUIRED_REQUEST_PSEH) != REQUIRED_REQUEST_PSEH)
{
LSQ_INFO("not all request pseudo-headers are specified");
return LSQUIC_HDR_ERR_INCOMPL_REQ_PSDO_HDR;
}
if (hwc->pseh_mask & ALL_SERVER_PSEH)
{
LSQ_INFO("request pseudo-headers contain response-only headers");
return LSQUIC_HDR_ERR_UNNEC_RESP_PSDO_HDR;
}
#define HWC_UH_WRITE(h, buf, sz) do { \
if (0 != hwc_uh_write(h, buf, sz)) \
return LSQUIC_HDR_ERR_NOMEM; \
} while (0)
HWC_UH_WRITE(hwc, HWC_PSEH_VAL(hwc, PSEH_METHOD), HWC_PSEH_LEN(hwc, PSEH_METHOD));
HWC_UH_WRITE(hwc, " ", 1);
HWC_UH_WRITE(hwc, HWC_PSEH_VAL(hwc, PSEH_PATH), HWC_PSEH_LEN(hwc, PSEH_PATH));
HWC_UH_WRITE(hwc, " HTTP/1.1\r\n", 11);
if (hwc->max_headers_sz && hwc->w_off > hwc->max_headers_sz)
{
LSQ_INFO("headers too large");
return LSQUIC_HDR_ERR_HEADERS_TOO_LARGE;
}
return 0;
#undef HWC_UH_WRITE
}
static enum lsquic_header_status
convert_pseudo_headers (struct header_writer_ctx *hwc)
{
/* We are *reading* the message. Thus, a server expects a request, and a
* client expects a response. Unless we receive a push promise from the
* server, in which case this should also be a request.
*/
if (hwc->hwc_flags & (HWC_SERVER|HWC_PUSH_PROMISE))
return convert_request_pseudo_headers(hwc);
else
return convert_response_pseudo_headers(hwc);
}
static enum lsquic_header_status
save_cookie (struct header_writer_ctx *hwc, const char *val, unsigned val_len)
{
char *cookie_val;
if (0 == hwc->cookie_sz)
{
hwc->cookie_nalloc = hwc->cookie_sz = val_len;
cookie_val = malloc(hwc->cookie_nalloc);
if (!cookie_val)
return LSQUIC_HDR_ERR_NOMEM;
hwc->cookie_val = cookie_val;
memcpy(hwc->cookie_val, val, val_len);
}
else
{
hwc->cookie_sz += val_len + 2 /* "; " */;
if (hwc->cookie_sz > hwc->cookie_nalloc)
{
hwc->cookie_nalloc = hwc->cookie_nalloc * 2 + val_len + 2;
cookie_val = realloc(hwc->cookie_val, hwc->cookie_nalloc);
if (!cookie_val)
return LSQUIC_HDR_ERR_NOMEM;
hwc->cookie_val = cookie_val;
}
memcpy(hwc->cookie_val + hwc->cookie_sz - val_len - 2, "; ", 2);
memcpy(hwc->cookie_val + hwc->cookie_sz - val_len, val, val_len);
}
return 0;
}
static enum lsquic_header_status
add_real_header (struct header_writer_ctx *hwc, const char *name,
unsigned name_len, const char *val, unsigned val_len)
{
enum lsquic_header_status err;
unsigned i;
int n_upper;
if (hwc->hwc_flags & HWC_EXPECT_COLON)
{
if (0 != (err = convert_pseudo_headers(hwc)))
return err;
hwc->hwc_flags &= ~HWC_EXPECT_COLON;
}
if (4 == name_len && 0 == memcmp(name, "host", 4))
hwc->hwc_flags |= HWC_SEEN_HOST;
n_upper = 0;
for (i = 0; i < name_len; ++i)
n_upper += isupper(name[i]);
if (n_upper > 0)
{
LSQ_INFO("Header name `%.*s' contains uppercase letters",
name_len, name);
return LSQUIC_HDR_ERR_UPPERCASE_HEADER;
}
if (6 == name_len && memcmp(name, "cookie", 6) == 0)
{
return save_cookie(hwc, val, val_len);
}
#define HWC_UH_WRITE(h, buf, sz) do { \
if (0 != hwc_uh_write(h, buf, sz)) \
return LSQUIC_HDR_ERR_NOMEM; \
} while (0)
HWC_UH_WRITE(hwc, name, name_len);
HWC_UH_WRITE(hwc, ": ", 2);
HWC_UH_WRITE(hwc, val, val_len);
HWC_UH_WRITE(hwc, "\r\n", 2);
if (hwc->max_headers_sz && hwc->w_off > hwc->max_headers_sz)
{
LSQ_INFO("headers too large");
return LSQUIC_HDR_ERR_HEADERS_TOO_LARGE;
}
return 0;
#undef HWC_UH_WRITE
}
static enum lsquic_header_status
add_header_to_uh (struct header_writer_ctx *hwc, const char *name,
unsigned name_len, const char *val, unsigned val_len)
{
LSQ_DEBUG("Got header '%.*s': '%.*s'", name_len, name, val_len, val);
if (':' == name[0])
return add_pseudo_header(hwc, name, name_len, val, val_len);
else
return add_real_header(hwc, name, name_len, val, val_len);
}
static enum lsquic_header_status
h1h_finish_hset (struct header_writer_ctx *hwc)
{
enum lsquic_header_status st;
if (hwc->hwc_flags & HWC_EXPECT_COLON)
{
st = convert_pseudo_headers(hwc);
if (0 != st)
return st;
hwc->hwc_flags &= ~HWC_EXPECT_COLON;
}
#define HWC_UH_WRITE(h, buf, sz) do { \
st = hwc_uh_write(h, buf, sz); \
if (0 != st) \
return st; \
} while (0)
if ((hwc->pseh_mask & BIT(PSEH_AUTHORITY)) &&
0 == (hwc->hwc_flags & HWC_SEEN_HOST))
{
LSQ_DEBUG("Setting 'Host: %.*s'", HWC_PSEH_LEN(hwc, PSEH_AUTHORITY),
HWC_PSEH_VAL(hwc, PSEH_AUTHORITY));
HWC_UH_WRITE(hwc, "Host: ", 6);
HWC_UH_WRITE(hwc, HWC_PSEH_VAL(hwc, PSEH_AUTHORITY),
HWC_PSEH_LEN(hwc, PSEH_AUTHORITY));
HWC_UH_WRITE(hwc, "\r\n", 2);
}
if (hwc->cookie_val)
{
LSQ_DEBUG("Setting 'Cookie: %.*s'", hwc->cookie_sz, hwc->cookie_val);
HWC_UH_WRITE(hwc, "Cookie: ", 8);
HWC_UH_WRITE(hwc, hwc->cookie_val, hwc->cookie_sz);
HWC_UH_WRITE(hwc, "\r\n", 2);
}
HWC_UH_WRITE(hwc, "\r\n", 2 + 1 /* NUL byte */);
hwc->w_off -= 1; /* Do not count NUL byte */
hwc->hwc_h1h.h1h_size = hwc->w_off;
if (hwc->max_headers_sz && hwc->w_off > hwc->max_headers_sz)
{
LSQ_INFO("headers too large");
return LSQUIC_HDR_ERR_HEADERS_TOO_LARGE;
}
return LSQUIC_HDR_OK;
}
#define HWC_PTR(data_in) (struct header_writer_ctx *) \
((unsigned char *) (hset) - offsetof(struct header_writer_ctx, hwc_h1h))
static enum lsquic_header_status
h1h_process_header (void *hset, unsigned name_idx,
const char *name, unsigned name_len,
const char *value, unsigned value_len)
{
struct header_writer_ctx *const hwc = HWC_PTR(hset);
if (name)
return add_header_to_uh(hwc, name, name_len, value, value_len);
else
return h1h_finish_hset(hwc);
}
static void
h1h_discard_header_set (void *hset)
{
struct header_writer_ctx *const hwc = HWC_PTR(hset);
unsigned i;
for (i = 0; i < sizeof(hwc->pseh_bufs) / sizeof(hwc->pseh_bufs[0]); ++i)
if (hwc->pseh_bufs[i])
free(hwc->pseh_bufs[i]);
if (hwc->cookie_val)
free(hwc->cookie_val);
free(hwc->hwc_h1h.h1h_buf);
free(hwc);
}
static const struct lsquic_hset_if http1x_if =
{
.hsi_create_header_set = h1h_create_header_set,
.hsi_process_header = h1h_process_header,
.hsi_discard_header_set = h1h_discard_header_set,
};
const struct lsquic_hset_if *const lsquic_http1x_if = &http1x_if;

View File

@ -0,0 +1,16 @@
/* Copyright (c) 2017 - 2018 LiteSpeed Technologies Inc. See LICENSE. */
#ifndef LSQUIC_HTTP1X_IF_H
#define LSQUIC_HTTP1X_IF_H 1
struct lsquic_hset_if;
struct http1x_ctor_ctx
{
lsquic_cid_t cid; /* Used for logging */
unsigned max_headers_sz;
int is_server;
};
extern const struct lsquic_hset_if *const lsquic_http1x_if;
#endif

View File

@ -72,6 +72,7 @@ enum lsq_log_level lsq_log_levels[N_LSQUIC_LOGGER_MODULES] = {
[LSQLM_DI] = LSQ_LOG_WARN,
[LSQLM_PACER] = LSQ_LOG_WARN,
[LSQLM_MIN_HEAP] = LSQ_LOG_WARN,
[LSQLM_HTTP1X] = LSQ_LOG_WARN,
};
const char *const lsqlm_to_str[N_LSQUIC_LOGGER_MODULES] = {
@ -100,6 +101,7 @@ const char *const lsqlm_to_str[N_LSQUIC_LOGGER_MODULES] = {
[LSQLM_DI] = "di",
[LSQLM_PACER] = "pacer",
[LSQLM_MIN_HEAP] = "min-heap",
[LSQLM_HTTP1X] = "http1x",
};
const char *const lsq_loglevel2str[N_LSQUIC_LOG_LEVELS] = {

View File

@ -71,6 +71,7 @@ enum lsquic_logger_module {
LSQLM_DI,
LSQLM_PACER,
LSQLM_MIN_HEAP,
LSQLM_HTTP1X,
N_LSQUIC_LOGGER_MODULES
};

View File

@ -40,7 +40,6 @@
#include "lsquic_util.h"
#include "lsquic_mm.h"
#include "lsquic_headers_stream.h"
#include "lsquic_frame_reader.h"
#include "lsquic_conn.h"
#include "lsquic_data_in_if.h"
#include "lsquic_parse.h"
@ -50,6 +49,7 @@
#include "lsquic_pacer.h"
#include "lsquic_cubic.h"
#include "lsquic_send_ctl.h"
#include "lsquic_headers.h"
#include "lsquic_ev_log.h"
#define LSQUIC_LOGGER_MODULE LSQLM_STREAM
@ -315,6 +315,20 @@ drop_buffered_data (struct lsquic_stream *stream)
}
static void
destroy_uh (struct lsquic_stream *stream)
{
if (stream->uh)
{
if (stream->uh->uh_hset)
stream->conn_pub->enpub->enp_hsi_if
->hsi_discard_header_set(stream->uh->uh_hset);
free(stream->uh);
stream->uh = NULL;
}
}
void
lsquic_stream_destroy (lsquic_stream_t *stream)
{
@ -336,8 +350,14 @@ lsquic_stream_destroy (lsquic_stream_t *stream)
drop_buffered_data(stream);
lsquic_sfcw_consume_rem(&stream->fc);
drop_frames_in(stream);
free(stream->push_req);
free(stream->uh);
if (stream->push_req)
{
if (stream->push_req->uh_hset)
stream->conn_pub->enpub->enp_hsi_if
->hsi_discard_header_set(stream->push_req->uh_hset);
free(stream->push_req);
}
destroy_uh(stream);
free(stream->sm_buf);
LSQ_DEBUG("destroyed stream %u @%p", stream->id, stream);
SM_HISTORY_DUMP_REMAINING(stream);
@ -682,17 +702,16 @@ lsquic_stream_rst_frame_sent (lsquic_stream_t *stream)
static size_t
read_uh (lsquic_stream_t *stream, unsigned char *dst, size_t len)
{
struct uncompressed_headers *uh = stream->uh;
size_t n_avail = uh->uh_size - uh->uh_off;
struct http1x_headers *h1h = stream->uh->uh_hset;
size_t n_avail = h1h->h1h_size - h1h->h1h_off;
if (n_avail < len)
len = n_avail;
memcpy(dst, uh->uh_headers + uh->uh_off, len);
uh->uh_off += len;
if (uh->uh_off == uh->uh_size)
memcpy(dst, h1h->h1h_buf + h1h->h1h_off, len);
h1h->h1h_off += len;
if (h1h->h1h_off == h1h->h1h_size)
{
LSQ_DEBUG("read all uncompressed headers for stream %u", stream->id);
free(uh);
stream->uh = NULL;
destroy_uh(stream);
if (stream->stream_flags & STREAM_HEAD_IN_FIN)
{
stream->stream_flags |= STREAM_FIN_REACHED;
@ -749,18 +768,31 @@ lsquic_stream_readv (lsquic_stream_t *stream, const struct iovec *iov,
iovidx = -1;
NEXT_IOV();
if (stream->uh && AVAIL())
if (stream->uh)
{
read_unc_headers = 1;
do
if (stream->uh->uh_flags & UH_H1H)
{
nread = read_uh(stream, p, AVAIL());
p += nread;
total_nread += nread;
if (p == end)
NEXT_IOV();
if (AVAIL())
{
read_unc_headers = 1;
do
{
nread = read_uh(stream, p, AVAIL());
p += nread;
total_nread += nread;
if (p == end)
NEXT_IOV();
}
while (stream->uh && AVAIL());
}
else
read_unc_headers = 0;
}
else
{
LSQ_INFO("header set not claimed: cannot read from stream");
return -1;
}
while (stream->uh && AVAIL());
}
else
read_unc_headers = 0;
@ -2006,14 +2038,13 @@ lsquic_stream_is_pushed (const lsquic_stream_t *stream)
int
lsquic_stream_push_info (const lsquic_stream_t *stream,
uint32_t *ref_stream_id, const char **headers, size_t *headers_sz)
uint32_t *ref_stream_id, void **hset)
{
if (lsquic_stream_is_pushed(stream))
{
assert(stream->push_req);
*ref_stream_id = stream->push_req->uh_stream_id;
*headers = stream->push_req->uh_headers;
*headers_sz = stream->push_req->uh_size;
*hset = stream->push_req->uh_hset;
return 0;
}
else
@ -2145,3 +2176,34 @@ lsquic_stream_cid (const struct lsquic_stream *stream)
}
void *
lsquic_stream_get_hset (struct lsquic_stream *stream)
{
void *hset;
if ((stream->stream_flags & (STREAM_USE_HEADERS|STREAM_HAVE_UH))
!= (STREAM_USE_HEADERS|STREAM_HAVE_UH))
{
LSQ_INFO("%s: unexpected call, flags: 0x%X", __func__,
stream->stream_flags);
return NULL;
}
if (!stream->uh)
{
LSQ_INFO("%s: headers unavailable (already fetched?)", __func__);
return NULL;
}
if (stream->uh->uh_flags & UH_H1H)
{
LSQ_INFO("%s: uncompressed headers have internal format", __func__);
return NULL;
}
hset = stream->uh->uh_hset;
stream->uh->uh_hset = NULL;
destroy_uh(stream);
LSQ_DEBUG("return header set");
return hset;
}

View File

@ -51,6 +51,12 @@ static int randomly_reprioritize_streams;
*/
static int promise_fd = -1;
/* Set to true value to use header bypass. This means that the use code
* creates header set via callbacks and then fetches it by calling
* lsquic_stream_get_hset() when the first "on_read" event is called.
*/
static int g_header_bypass;
struct lsquic_conn_ctx;
struct path_elem {
@ -83,6 +89,7 @@ struct http_client_ctx {
HCC_DISCARD_RESPONSE = (1 << 0),
HCC_SEEN_FIN = (1 << 1),
HCC_ABORT_ON_INCOMPLETE = (1 << 2),
HCC_PROCESSED_HEADERS = (1 << 3),
} hcc_flags;
struct prog *prog;
};
@ -97,6 +104,23 @@ struct lsquic_conn_ctx {
};
struct hset_elem
{
STAILQ_ENTRY(hset_elem) next;
unsigned name_idx;
char *name;
char *value;
};
STAILQ_HEAD(hset, hset_elem);
static void
hset_dump (const struct hset *, FILE *);
static void
hset_destroy (void *hset);
static void
create_connections (struct http_client_ctx *client_ctx)
{
@ -362,6 +386,7 @@ static void
http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
struct http_client_ctx *const client_ctx = st_h->client_ctx;
struct hset *hset;
ssize_t nread;
unsigned old_prio, new_prio;
unsigned char buf[0x200];
@ -370,6 +395,21 @@ http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
srand(GetTickCount());
#endif
if (g_header_bypass
&& !(client_ctx->hcc_flags & HCC_PROCESSED_HEADERS))
{
hset = lsquic_stream_get_hset(stream);
if (!hset)
{
LSQ_ERROR("could not get header set from stream");
exit(2);
}
if (!(client_ctx->hcc_flags & HCC_DISCARD_RESPONSE))
hset_dump(hset, stdout);
hset_destroy(hset);
client_ctx->hcc_flags |= HCC_PROCESSED_HEADERS;
}
do
{
nread = lsquic_stream_read(stream, buf, sizeof(buf));
@ -588,6 +628,99 @@ verify_server_cert (void *ctx, STACK_OF(X509) *chain)
#endif
static void *
hset_create (void *hsi_ctx, int is_push_promise)
{
struct hset *hset;
hset = malloc(sizeof(*hset));
if (hset)
{
STAILQ_INIT(hset);
return hset;
}
else
return NULL;
}
static enum lsquic_header_status
hset_add_header (void *hset_p, unsigned name_idx,
const char *name, unsigned name_len,
const char *value, unsigned value_len)
{
struct hset *hset = hset_p;
struct hset_elem *el;
if (!name) /* This signals end of headers. We do no post-processing. */
return LSQUIC_HDR_OK;
el = malloc(sizeof(*el));
if (!el)
return LSQUIC_HDR_ERR_NOMEM;
el->name = strndup(name, name_len);
el->value = strndup(value, value_len);
if (!(el->name && el->value))
{
free(el->name);
free(el->value);
free(el);
return LSQUIC_HDR_ERR_NOMEM;
}
el->name_idx = name_idx;
STAILQ_INSERT_TAIL(hset, el, next);
return LSQUIC_HDR_OK;
}
static void
hset_destroy (void *hset_p)
{
struct hset *hset = hset_p;
struct hset_elem *el, *next;
for (el = STAILQ_FIRST(hset); el; el = next)
{
next = STAILQ_NEXT(el, next);
free(el->name);
free(el->value);
free(el);
}
free(hset);
}
static void
hset_dump (const struct hset *hset, FILE *out)
{
const struct hset_elem *el;
STAILQ_FOREACH(el, hset, next)
if (el->name_idx)
fprintf(out, "%s (static table idx %u): %s\n", el->name,
el->name_idx, el->value);
else
fprintf(out, "%s: %s\n", el->name, el->value);
fprintf(out, "\n");
fflush(out);
}
/* These are basic and for illustration purposes only. You will want to
* do your own verification by doing something similar to what is done
* in src/liblsquic/lsquic_http1x_if.c
*/
static const struct lsquic_hset_if header_bypass_api =
{
.hsi_create_header_set = hset_create,
.hsi_process_header = hset_add_header,
.hsi_discard_header_set = hset_destroy,
};
int
main (int argc, char **argv)
{
@ -615,7 +748,7 @@ main (int argc, char **argv)
prog_init(&prog, LSENG_HTTP, &sports, &http_client_if, &client_ctx);
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "46r:R:IKu:EP:M:n:H:p:h"
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "46Br:R:IKu:EP:M:n:H:p:h"
#ifndef WIN32
"C:"
#endif
@ -626,6 +759,11 @@ main (int argc, char **argv)
case '6':
prog.prog_ipver = opt - '0';
break;
case 'B':
g_header_bypass = 1;
prog.prog_api.ea_hsi_if = &header_bypass_api;
prog.prog_api.ea_hsi_ctx = NULL;
break;
case 'I':
client_ctx.hcc_flags |= HCC_ABORT_ON_INCOMPLETE;
break;

View File

@ -1,6 +1,7 @@
/* Copyright (c) 2017 - 2018 LiteSpeed Technologies Inc. See LICENSE. */
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "lsquic.h"
@ -15,13 +16,10 @@ main (void)
lsquic_engine_init_settings(&settings, flags);
struct lsquic_engine_api api = {
&settings,
NULL, NULL, /* stream if and ctx */
(void *) (uintptr_t) 1, NULL, /* packets out and ctx */
NULL, NULL, /* packout mem interface and ctx */
NULL, NULL, /* verify server cert */
};
struct lsquic_engine_api api;
memset(&api, 0, sizeof(api));
api.ea_settings = &settings;
api.ea_packets_out = (void *) (uintptr_t) 1;
engine = lsquic_engine_new(flags, &api);
assert(engine);

View File

@ -15,10 +15,18 @@
#include "lsquic_frame_common.h"
#include "lshpack.h"
#include "lsquic_mm.h"
#include "lsquic_int_types.h"
#include "lsquic_conn_flow.h"
#include "lsquic_sfcw.h"
#include "lsquic_rtt.h"
#include "lsquic_conn.h"
#include "lsquic_stream.h"
#include "lsquic_conn_public.h"
#include "lsquic_logger.h"
#define FRAME_READER_TESTING 0x100
#include "lsquic_frame_reader.h"
#include "lsquic_headers.h"
#include "lsquic_http1x_if.h"
struct callback_value /* What callback returns */
@ -32,7 +40,16 @@ struct callback_value /* What callback returns */
} type;
unsigned stream_off; /* Checked only if not zero */
union {
struct uncompressed_headers uh;
struct headers {
uint32_t stream_id;
uint32_t oth_stream_id;
unsigned short weight;
signed char exclusive;
unsigned char flags;
unsigned size;
unsigned off;
char buf[0x100];
} headers;
struct {
uint16_t id;
uint32_t value;
@ -53,31 +70,30 @@ struct callback_value /* What callback returns */
void
compare_headers (const struct uncompressed_headers *got_uh,
const struct uncompressed_headers *exp_uh)
compare_headers (const struct headers *got_h, const struct headers *exp_h)
{
assert(got_uh->uh_stream_id == exp_uh->uh_stream_id);
assert(got_uh->uh_oth_stream_id == exp_uh->uh_oth_stream_id);
assert(got_uh->uh_weight == exp_uh->uh_weight);
assert(got_uh->uh_exclusive == exp_uh->uh_exclusive);
assert(got_uh->uh_size == exp_uh->uh_size);
assert(strlen(got_uh->uh_headers) == got_uh->uh_size);
assert(got_uh->uh_off == exp_uh->uh_off);
assert(got_uh->uh_flags == exp_uh->uh_flags);
assert(0 == memcmp(got_uh->uh_headers, exp_uh->uh_headers, got_uh->uh_size));
assert(got_h->stream_id == exp_h->stream_id);
assert(got_h->oth_stream_id == exp_h->oth_stream_id);
assert(got_h->weight == exp_h->weight);
assert(got_h->exclusive == exp_h->exclusive);
assert(got_h->size == exp_h->size);
assert(strlen(got_h->buf) == got_h->size);
assert(got_h->off == exp_h->off);
assert(got_h->flags == exp_h->flags);
assert(0 == memcmp(got_h->buf, exp_h->buf, got_h->size));
}
void
compare_push_promises (const struct uncompressed_headers *got_uh,
const struct uncompressed_headers *exp_uh)
compare_push_promises (const struct headers *got_h, const struct headers *exp_h)
{
assert(got_uh->uh_stream_id == exp_uh->uh_stream_id);
assert(got_uh->uh_oth_stream_id == exp_uh->uh_oth_stream_id);
assert(got_uh->uh_size == exp_uh->uh_size);
assert(strlen(got_uh->uh_headers) == got_uh->uh_size);
assert(got_uh->uh_flags == exp_uh->uh_flags);
assert(0 == memcmp(got_uh->uh_headers, exp_uh->uh_headers, got_uh->uh_size));
assert(got_h->stream_id == exp_h->stream_id);
assert(got_h->oth_stream_id == exp_h->oth_stream_id);
assert(got_h->size == exp_h->size);
assert(strlen(got_h->buf) == got_h->size);
assert(got_h->off == exp_h->off);
assert(got_h->flags == exp_h->flags);
assert(0 == memcmp(got_h->buf, exp_h->buf, got_h->size));
}
@ -111,10 +127,10 @@ compare_cb_vals (const struct callback_value *got,
switch (got->type)
{
case CV_HEADERS:
compare_headers(&got->u.uh, &exp->u.uh);
compare_headers(&got->u.headers, &exp->u.headers);
break;
case CV_PUSH_PROMISE:
compare_push_promises(&got->u.uh, &exp->u.uh);
compare_push_promises(&got->u.headers, &exp->u.headers);
break;
case CV_ERROR:
compare_errors(&got->u.error, &exp->u.error);
@ -152,10 +168,19 @@ reset_cb_ctx (struct cb_ctx *cb_ctx)
}
static size_t
uh_size (const struct uncompressed_headers *uh)
static void
copy_uh_to_headers (const struct uncompressed_headers *uh, struct headers *h)
{
return sizeof(*uh) - FRAME_READER_TESTING + uh->uh_size;
const struct http1x_headers *h1h = uh->uh_hset;
h->flags = uh->uh_flags;
h->weight = uh->uh_weight;
h->stream_id = uh->uh_stream_id;
h->exclusive = uh->uh_exclusive;
h->oth_stream_id = uh->uh_oth_stream_id;
h->size = h1h->h1h_size;
h->off = h1h->h1h_off;
memcpy(h->buf, h1h->h1h_buf, h->size);
h->buf[h->size] = '\0';
}
@ -168,8 +193,9 @@ on_incoming_headers (void *ctx, struct uncompressed_headers *uh)
assert(i < sizeof(cb_ctx->cb_vals) / sizeof(cb_ctx->cb_vals[0]));
cb_ctx->cb_vals[i].type = CV_HEADERS;
cb_ctx->cb_vals[i].stream_off = input.in_off;
assert(uh_size(uh) <= sizeof(*uh));
memcpy(&cb_ctx->cb_vals[i].u.uh, uh, uh_size(uh) + 1 /* NUL byte */);
copy_uh_to_headers(uh, &cb_ctx->cb_vals[i].u.headers);
assert(uh->uh_flags & UH_H1H);
lsquic_http1x_if->hsi_discard_header_set(uh->uh_hset);
free(uh);
}
@ -183,8 +209,9 @@ on_push_promise (void *ctx, struct uncompressed_headers *uh)
assert(i < sizeof(cb_ctx->cb_vals) / sizeof(cb_ctx->cb_vals[0]));
cb_ctx->cb_vals[i].type = CV_PUSH_PROMISE;
cb_ctx->cb_vals[i].stream_off = input.in_off;
assert(uh_size(uh) <= sizeof(*uh));
memcpy(&cb_ctx->cb_vals[i].u.uh, uh, uh_size(uh) + 1 /* NUL byte */);
copy_uh_to_headers(uh, &cb_ctx->cb_vals[i].u.headers);
assert(uh->uh_flags & UH_H1H);
lsquic_http1x_if->hsi_discard_header_set(uh->uh_hset);
free(uh);
}
@ -273,7 +300,7 @@ struct frame_reader_test {
};
#define UH_HEADERS(str) .uh_headers = (str), .uh_size = sizeof(str) - 1
#define HEADERS(str) .buf = (str), .size = sizeof(str) - 1
static const struct frame_reader_test tests[] = {
{ .frt_lineno = __LINE__,
@ -292,13 +319,14 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0,
.uh_weight = 0,
.uh_exclusive = -1,
.uh_off = 0,
UH_HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0,
.weight = 0,
.exclusive = -1,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
},
@ -323,13 +351,14 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0,
.uh_weight = 0,
.uh_exclusive = -1,
.uh_off = 0,
UH_HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0,
.weight = 0,
.exclusive = -1,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
},
@ -366,14 +395,14 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0x1234,
.uh_weight = 0xFF + 1,
.uh_exclusive = 1,
.uh_off = 0,
.uh_flags = UH_FIN,
UH_HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 0xFF + 1,
.exclusive = 1,
.off = 0,
.flags = UH_FIN | UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
{
@ -407,13 +436,14 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0x1234,
.uh_weight = 1,
.uh_exclusive = 0,
.uh_off = 0,
UH_HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
},
@ -439,13 +469,14 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0x1234,
.uh_weight = 1,
.uh_exclusive = 0,
.uh_off = 0,
UH_HEADERS("HTTP/1.1 302 Found\r\n"
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n"
"Cookie: a=b\r\n\r\n"),
},
},
@ -474,13 +505,14 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0x1234,
.uh_weight = 1,
.uh_exclusive = 0,
.uh_off = 0,
UH_HEADERS("HTTP/1.1 302 Found\r\n"
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n"
"Cookie: a=b; c=d; e=f\r\n\r\n"),
},
},
@ -517,13 +549,14 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0x1234,
.uh_weight = 1,
.uh_exclusive = 0,
.uh_off = 0,
UH_HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
},
},
},
@ -559,13 +592,14 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0x1234,
.uh_weight = 1,
.uh_exclusive = 0,
.uh_off = 0,
UH_HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x1234,
.weight = 1,
.exclusive = 0,
.off = 0,
.flags = UH_H1H,
HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
},
},
},
@ -707,13 +741,14 @@ static const struct frame_reader_test tests[] = {
},
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0,
.uh_weight = 0,
.uh_exclusive = -1,
.uh_off = 0,
UH_HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0,
.weight = 0,
.exclusive = -1,
.off = 0,
.flags = UH_H1H,
HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
},
},
},
@ -737,11 +772,11 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_PUSH_PROMISE,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0x123456,
.uh_flags = UH_PP,
UH_HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0x123456,
.flags = UH_PP | UH_H1H,
HEADERS("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"),
},
},
},
@ -769,13 +804,14 @@ static const struct frame_reader_test tests[] = {
.frt_cb_vals = {
{
.type = CV_HEADERS,
.u.uh = {
.uh_stream_id = 12345,
.uh_oth_stream_id = 0,
.uh_weight = 0,
.uh_exclusive = -1,
.uh_off = 0,
UH_HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
.u.headers = {
.stream_id = 12345,
.oth_stream_id = 0,
.weight = 0,
.exclusive = -1,
.off = 0,
.flags = UH_H1H,
HEADERS("HTTP/1.1 302 Found\r\n\r\n"),
},
},
},
@ -1064,8 +1100,17 @@ test_one_frt (const struct frame_reader_test *frt)
unsigned short exp_off;
struct lshpack_dec hdec;
struct lsquic_mm mm;
struct lsquic_conn lconn;
struct lsquic_conn_public conn_pub;
struct lsquic_stream stream;
int s;
memset(&stream, 0, sizeof(stream));
memset(&lconn, 0, sizeof(lconn));
memset(&conn_pub, 0, sizeof(conn_pub));
stream.conn_pub = &conn_pub;
conn_pub.lconn = &lconn;
lsquic_mm_init(&mm);
lshpack_dec_init(&hdec);
memset(&input, 0, sizeof(input));
@ -1079,7 +1124,8 @@ test_one_frt (const struct frame_reader_test *frt)
++input.in_max_sz;
fr = lsquic_frame_reader_new(frt->frt_fr_flags, frt->frt_max_headers_sz,
&mm, NULL, read_from_stream, &hdec, &frame_callbacks, &g_cb_ctx);
&mm, &stream, read_from_stream, &hdec, &frame_callbacks, &g_cb_ctx,
lsquic_http1x_if, NULL);
do
{
s = lsquic_frame_reader_read(fr);

View File

@ -27,6 +27,8 @@
#include "lsquic_frame_common.h"
#include "lsquic_frame_writer.h"
#include "lsquic_frame_reader.h"
#include "lsquic_headers.h"
#include "lsquic_http1x_if.h"
struct lsquic_stream
@ -140,11 +142,14 @@ static struct lsquic_http_header header_arr[N_HEADERS];
static void
compare_headers (struct uncompressed_headers *uh)
{
struct http1x_headers *h1h;
char line[0x100], *s;
FILE *in;
unsigned i;
in = fmemopen(uh->uh_headers, uh->uh_size, "r");
assert(uh->uh_flags & UH_H1H);
h1h = uh->uh_hset;
in = fmemopen(h1h->h1h_buf, h1h->h1h_size, "r");
for (i = 0; i < N_HEADERS; ++i)
{
s = fgets(line, sizeof(line), in);
@ -211,7 +216,7 @@ test_rw (unsigned max_frame_sz)
stream->sm_off = 0;
fr = lsquic_frame_reader_new(0, 0, &mm, stream, read_from_stream, &hdec,
&frame_callbacks, &uh);
&frame_callbacks, &uh, lsquic_http1x_if, NULL);
do
{
s = lsquic_frame_reader_read(fr);