Packages
hackney
3.0.0
4.7.2
4.7.1
4.7.0
4.6.1
4.6.0
4.5.2
4.5.1
4.5.0
4.4.5
4.4.3
4.4.2
4.4.1
4.4.0
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.3
4.0.2
4.0.1
4.0.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.3
3.0.2
3.0.1
3.0.0
retired
2.0.1
2.0.0
2.0.0-beta.1
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.0
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.0
1.13.0
1.12.1
1.12.0
1.11.0
1.10.1
1.10.0
1.9.0
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.0
1.7.1
1.7.0
1.6.6
retired
1.6.5
1.6.4
retired
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.6
1.0.5
1.0.2
1.0.1
0.15.2
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
Simple HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support
Retired package: Release invalid - Use 3.0.1 instead
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
c_src/lsquic/src/liblsquic/lsquic_http1x_if.c
/* Copyright (c) 2017 - 2022 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 lsquic_conn_log_cid(hwc->hwc_conn)
#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
{
const struct lsquic_conn *hwc_conn;
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;
size_t hwc_header_buf_nalloc;
struct lsxpack_header hwc_xhdr;
};
#define HWC_PSEH_LEN(hwc, ph) ((int) strlen((hwc)->pseh_bufs[ph]))
#define HWC_PSEH_VAL(hwc, ph) ((hwc)->pseh_bufs[ph])
/* flags for frames with request headers */
#define HWC_REQUEST_HANDLING_FLAGS (HWC_SERVER|HWC_PUSH_PROMISE)
static void *
h1h_create_header_set (void *ctx, lsquic_stream_t *stream, 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_conn = hcc->conn;
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 (!h1h_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 int
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 -1;
hwc->pseh_mask |= BIT(ph);
memcpy(hwc->pseh_bufs[ph], val, val_len);
hwc->pseh_bufs[ph][val_len] = '\0';
return 0;
}
else
{
LSQ_INFO("header %u is already present", ph);
return 1;
}
}
static int
add_pseudo_header (struct header_writer_ctx *hwc, struct lsxpack_header *xhdr)
{
const char *name, *val;
unsigned name_len, val_len;
if (!(hwc->hwc_flags & HWC_EXPECT_COLON))
{
LSQ_INFO("unexpected colon");
return 1;
}
name = lsxpack_header_get_name(xhdr);
val = lsxpack_header_get_value(xhdr);
name_len = xhdr->name_len;
val_len = xhdr->val_len;
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 1;
}
#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 int
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 1;
}
if (hwc->pseh_mask & ALL_REQUEST_PSEH)
{
LSQ_INFO("response pseudo-headers contain request-only headers");
return 1;
}
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 -1; \
} 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 1;
}
return 0;
#undef HWC_UH_WRITE
}
static int
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 1;
}
if (hwc->pseh_mask & ALL_SERVER_PSEH)
{
LSQ_INFO("request pseudo-headers contain response-only headers");
return 1;
}
#define HWC_UH_WRITE(h, buf, sz) do { \
if (0 != hwc_uh_write(h, buf, sz)) \
return -1; \
} 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 1;
}
return 0;
#undef HWC_UH_WRITE
}
static int
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 int
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 -1;
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 -1;
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 int
add_real_header (struct header_writer_ctx *hwc, struct lsxpack_header *xhdr)
{
int err;
unsigned i;
int n_upper;
const char *name, *val;
unsigned name_len, val_len;
if (hwc->hwc_flags & HWC_EXPECT_COLON)
{
if (0 != (err = convert_pseudo_headers(hwc)))
return err;
hwc->hwc_flags &= ~HWC_EXPECT_COLON;
}
name = lsxpack_header_get_name(xhdr);
val = lsxpack_header_get_value(xhdr);
name_len = xhdr->name_len;
val_len = xhdr->val_len;
if (4 == name_len && 0 == memcmp(name, "host", 4))
{
if(hwc->pseh_mask & BIT(PSEH_AUTHORITY))
{
LSQ_INFO("authority header was sent. ignoring host header with value `%.*s'", val_len, val);
return 0;
}
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 1;
}
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 -1; \
} 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 1;
}
return 0;
#undef HWC_UH_WRITE
}
static int
add_header_to_uh (struct header_writer_ctx *hwc, struct lsxpack_header *xhdr)
{
const char *name;
name = lsxpack_header_get_name(xhdr);
LSQ_DEBUG("Got header '%.*s': '%.*s'", (int) xhdr->name_len, name,
(int) xhdr->val_len, lsxpack_header_get_value(xhdr));
if (':' == name[0])
return add_pseudo_header(hwc, xhdr);
else
return add_real_header(hwc, xhdr);
}
static int
h1h_finish_hset (struct header_writer_ctx *hwc)
{
int 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(0 == (hwc->hwc_flags & HWC_SEEN_HOST) && 0 != (hwc->hwc_flags & HWC_REQUEST_HANDLING_FLAGS))
{
if((hwc->pseh_mask & BIT(PSEH_AUTHORITY)))
{
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);
}
else
{
LSQ_INFO("both host header and authority pseudo header were not found");
return 1;
}
}
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 1;
}
return 0;
}
#define HWC_PTR(data_in) (struct header_writer_ctx *) \
((unsigned char *) (hset) - offsetof(struct header_writer_ctx, hwc_h1h))
static struct lsxpack_header *
h1h_prepare_decode (void *hset, struct lsxpack_header *xhdr, size_t req_space)
{
struct header_writer_ctx *const hwc = HWC_PTR(hset);
size_t nalloc;
char *buf;
if (req_space < 0x100)
req_space = 0x100;
if (req_space > MAX_HTTP1X_HEADERS_SIZE || req_space > LSXPACK_MAX_STRLEN)
{
LSQ_DEBUG("requested space for header is too large: %zd bytes",
req_space);
return NULL;
}
if (!xhdr)
{
if (0 == hwc->hwc_header_buf_nalloc
|| req_space > hwc->hwc_header_buf_nalloc)
{
if (0 == hwc->hwc_header_buf_nalloc)
{
buf = malloc(req_space);
}
else
{
buf = realloc(hwc->hwc_xhdr.buf, req_space);
}
if (!buf)
{
LSQ_WARN("cannot %s %zd bytes",
hwc->hwc_header_buf_nalloc ? "reallocate to" : "allocate", req_space);
return NULL;
}
hwc->hwc_xhdr.buf = buf;
hwc->hwc_header_buf_nalloc = req_space;
}
else
buf = hwc->hwc_xhdr.buf;
lsxpack_header_prepare_decode(&hwc->hwc_xhdr, buf, 0, req_space);
}
else
{
if (req_space > hwc->hwc_header_buf_nalloc)
{
if (req_space < hwc->hwc_header_buf_nalloc * 2)
nalloc = hwc->hwc_header_buf_nalloc * 2;
else
nalloc = req_space;
buf = realloc(hwc->hwc_xhdr.buf, nalloc);
if (!buf)
{
LSQ_DEBUG("cannot reallocate to %zd bytes", nalloc);
return NULL;
}
hwc->hwc_xhdr.buf = buf;
hwc->hwc_header_buf_nalloc = nalloc;
}
hwc->hwc_xhdr.val_len = req_space;
}
return &hwc->hwc_xhdr;
}
static int
h1h_process_header (void *hset, struct lsxpack_header *xhdr)
{
struct header_writer_ctx *const hwc = HWC_PTR(hset);
if (xhdr)
return add_header_to_uh(hwc, xhdr);
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->hwc_xhdr.buf);
free(hwc);
}
static const struct lsquic_hset_if http1x_if =
{
.hsi_create_header_set = h1h_create_header_set,
.hsi_prepare_decode = h1h_prepare_decode,
.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;