blob: bf065111d23d8caceb7fa0f67551faa629c594f4 [file] [log] [blame] [raw]
/*
Copyright: Boaz Segev, 2017
License: MIT
*/
#include "spnlock.inc"
#include "http1.h"
#include "http1_parser.h"
#include "http_internal.h"
#include "fio_ary.h"
#include "fiobj.h"
#include <assert.h>
#include <stddef.h>
/* *****************************************************************************
The HTTP/1.1 Protocol Object
***************************************************************************** */
typedef struct http1_s {
http_protocol_s p;
http1_parser_s parser;
http_s request;
uint8_t restart; /* placed here to force padding */
uint8_t close; /* placed here to force padding */
uint8_t body_is_fd; /* placed here to force padding */
uintptr_t buf_pos;
uintptr_t buf_len;
uint8_t *buf;
intptr_t id_next;
intptr_t id_counter;
fio_ary_s queue;
} http1_s;
/* *****************************************************************************
Internal Helpers
***************************************************************************** */
#define parser2http(x) \
((http1_s *)((uintptr_t)(x) - (uintptr_t)(&((http1_s *)0)->parser)))
inline static void h1_reset(http1_s *p) {
p->buf_len = p->buf_len - p->buf_pos;
if (p->buf_len) {
memmove((uint8_t *)(p + 1), p->buf + p->buf_pos, p->buf_len);
}
p->buf = (uint8_t *)(p + 1);
p->buf_pos = 0;
p->body_is_fd = 0;
p->restart = 0;
}
static fio_cstr_s http1_status2str(uintptr_t status);
/* *****************************************************************************
Virtual Functions API
***************************************************************************** */
struct header_writer_s {
fiobj_s *dest;
fiobj_s *name;
fiobj_s *value;
};
static int write_header(fiobj_s *o, void *w_) {
struct header_writer_s *w = w_;
if (!o)
return 0;
if (o->type == FIOBJ_T_COUPLET) {
w->name = fiobj_couplet2key(o);
o = fiobj_couplet2obj(o);
if (!o)
return 0;
}
if (o->type == FIOBJ_T_ARRAY) {
fiobj_each1(o, 0, write_header, w);
return 0;
}
fio_cstr_s name = fiobj_obj2cstr(w->name);
fio_cstr_s str = fiobj_obj2cstr(o);
if (!str.data)
return 0;
fiobj_str_write(w->dest, name.data, name.len);
fiobj_str_write(w->dest, ":", 1);
fiobj_str_write(w->dest, str.data, str.len);
fiobj_str_write(w->dest, "\r\n", 2);
return 0;
}
static fiobj_s *headers2str(http_s *h) {
if (!h->headers)
return NULL;
static uintptr_t connection_key;
if (!connection_key)
connection_key = fiobj_sym_hash("connection", 10);
struct header_writer_s w;
w.dest = fiobj_str_buf(4096);
fio_cstr_s t = http1_status2str(h->status);
fiobj_str_write(w.dest, t.data, t.length);
fiobj_s *tmp = fiobj_hash_get3(h->private_data.out_headers, connection_key);
if (tmp) {
t = fiobj_obj2cstr(tmp);
if (t.data[0] == 'c' || t.data[0] == 'C')
((http1_s *)h->private_data.owner)->close = 1;
} else {
tmp = fiobj_hash_get3(h->headers, connection_key);
if (tmp) {
t = fiobj_obj2cstr(tmp);
if (!t.data || !t.len || t.data[0] == 'k' || t.data[0] == 'K')
fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23);
else {
fiobj_str_write(w.dest, "connection:close\r\n", 18);
((http1_s *)h->private_data.owner)->close = 1;
}
} else {
t = fiobj_obj2cstr(h->version);
if (t.data && t.data[5] == '1' && t.data[6] == '.')
fiobj_str_write(w.dest, "connection:keep-alive\r\n", 23);
else {
fiobj_str_write(w.dest, "connection:close\r\n", 18);
((http1_s *)h->private_data.owner)->close = 1;
}
}
}
fiobj_each1(h->private_data.out_headers, 0, write_header, &w);
fiobj_str_write(w.dest, "\r\n", 2);
return w.dest;
}
/** Should send existing headers and data */
static int http1_send_body(http_s *h, void *data, uintptr_t length) {
fiobj_s *packet = headers2str(h);
if (!packet)
return -1;
fiobj_str_write(packet, data, length);
fiobj_send((((http_protocol_s *)h->private_data.owner)->uuid), packet);
if (((http1_s *)h->private_data.owner)->close)
sock_close(((http1_s *)h->private_data.owner)->p.uuid);
/* streaming? */
if (h != &((http1_s *)h->private_data.owner)->request)
free(h);
return 0;
}
/** Should send existing headers and file */
static int http1_sendfile(http_s *h, int fd, uintptr_t length,
uintptr_t offset) {
fiobj_s *packet = headers2str(h);
if (!packet) {
return -1;
}
fiobj_send((((http_protocol_s *)h->private_data.owner)->uuid), packet);
sock_sendfile((((http_protocol_s *)h->private_data.owner)->uuid), fd, offset,
length);
if (((http1_s *)h->private_data.owner)->close)
sock_close(((http1_s *)h->private_data.owner)->p.uuid);
/* streaming? */
if (h != &((http1_s *)h->private_data.owner)->request)
free(h);
return 0;
}
/** Should send existing headers and data and prepare for streaming */
static int http1_stream(http_s *h, void *data, uintptr_t length) {
return -1; /* TODO: tmp unsupported */
(void)h;
(void)data;
(void)length;
}
/** Should send existing headers or complete streaming */
static void htt1p_finish(http_s *h) {
fiobj_s *packet = headers2str(h);
if (packet)
fiobj_send((((http_protocol_s *)h->private_data.owner)->uuid), packet);
http1_s *p = (http1_s *)h->private_data.owner;
if (p->close)
sock_close(((http1_s *)h->private_data.owner)->p.uuid);
http_s_cleanup(h);
/* streaming? */
if (h != &p->request)
free(h);
}
/** Push for data - unsupported. */
static int http1_push_data(http_s *h, void *data, uintptr_t length,
fiobj_s *mime_type) {
return -1;
(void)h;
(void)data;
(void)length;
(void)mime_type;
}
/** Push for files - unsupported. */
static int http1_push_file(http_s *h, fiobj_s *filename, fiobj_s *mime_type) {
return -1;
(void)h;
(void)filename;
(void)mime_type;
}
typedef struct http_func_s {
void (*task)(http_s *);
void (*fallback)(http_s *);
} http_func_s;
/** used by defer. */
static void http1_defer_task(intptr_t uuid, protocol_s *p_, void *arg) {
http_s *h = arg;
http_func_s *func = ((http_func_s *)(h + 1));
func->task(h);
(void)p_;
(void)uuid;
}
/** used by defer. */
static void http1_defer_fallback(intptr_t uuid, void *arg) {
http_s *h = arg;
http_func_s *func = ((http_func_s *)(h + 1));
h->private_data.owner = NULL;
if (func->fallback)
func->fallback(h);
http_s_cleanup(h);
free(h);
(void)uuid;
}
/** Defer request handling for later... careful (memory concern apply). */
static int http1_defer(http_s *h, void (*task)(http_s *h),
void (*fallback)(http_s *h)) {
assert(task && h);
// if (h == &((http1_s *)h->private_data.owner)->request) {
// http_s *tmp = malloc(sizeof(*tmp) + (sizeof(void *) << 1));
// HTTP_ASSERT(tmp, "couldn't allocate memory");
// *tmp = (http_s){.cookies = h->cookies,
// .version = fiobj_str_copy(h->version),
// .path = fiobj_str_copy(h->path),
// .query = fiobj_str_copy(h->query),
// .private_data = h->private_data,
// .body = fiobj_dup(h->private_data.out_headers),
// .params = fiobj_dup(h->params),
// .status = h->status,
// .method = fiobj_str_copy(h->method),
// .received_at = h->received_at,
// .udata = h->udata};
// fiobj_dup(h->private_data.out_headers);
// fiobj_io_assert_dynamic(h->body);
// http_s_cleanup(h);
// h = tmp;
// http_func_s *tasks = (http_func_s *)(h + 1);
// tasks->task = task;
// tasks->fallback = fallback;
// facil_defer(.uuid = ((http1_s *)h->private_data.owner)->p.uuid,
// .task_type = FIO_PR_LOCK_TASK, .task = http1_defer_task,
// .arg = h, .fallback = http1_defer_fallback);
// }
return -1; /* TODO: tmp unsupported */
(void)h;
(void)task;
(void)fallback;
}
struct http_vtable_s HTTP1_VTABLE = {
.http_send_body = http1_send_body,
.http_sendfile = http1_sendfile,
.http_stream = http1_stream,
.http_finish = htt1p_finish,
.http_push_data = http1_push_data,
.http_push_file = http1_push_file,
.http_defer = http1_defer,
};
/* *****************************************************************************
Parser Callbacks
***************************************************************************** */
/** called when a request was received. */
static int http1_on_request(http1_parser_s *parser) {
http1_s *p = parser2http(parser);
p->request.private_data.request_id = p->id_counter;
p->id_counter += 1;
http_on_request_handler______internal(&p->request, p->p.settings);
http_s_cleanup(&p->request);
p->restart = 1;
return 0;
}
/** called when a response was received. */
static int http1_on_response(http1_parser_s *parser) {
http1_s *p = parser2http(parser);
p->request.private_data.request_id = p->id_counter;
p->id_counter += 1;
p->p.settings->on_request(&p->request);
http_s_cleanup(&p->request);
p->restart = 1;
return 0;
}
/** called when a request method is parsed. */
static int http1_on_method(http1_parser_s *parser, char *method,
size_t method_len) {
http_s_init(&parser2http(parser)->request, &parser2http(parser)->p);
parser2http(parser)->request.method = fiobj_str_static(method, method_len);
return 0;
}
/** called when a response status is parsed. the status_str is the string
* without the prefixed numerical status indicator.*/
static int http1_on_status(http1_parser_s *parser, size_t status,
char *status_str, size_t len) {
parser2http(parser)->request.status_str = fiobj_str_static(status_str, len);
parser2http(parser)->request.status = status;
return 0;
}
/** called when a request path (excluding query) is parsed. */
static int http1_on_path(http1_parser_s *parser, char *path, size_t len) {
parser2http(parser)->request.path = fiobj_str_static(path, len);
return 0;
}
/** called when a request path (excluding query) is parsed. */
static int http1_on_query(http1_parser_s *parser, char *query, size_t len) {
parser2http(parser)->request.query = fiobj_str_static(query, len);
return 0;
}
/** called when a the HTTP/1.x version is parsed. */
static int http1_on_http_version(http1_parser_s *parser, char *version,
size_t len) {
if (!parser2http(parser)->request.headers)
http_s_init(&parser2http(parser)->request, &parser2http(parser)->p);
parser2http(parser)->request.version = fiobj_str_static(version, len);
return 0;
}
/** called when a header is parsed. */
static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len,
char *data, size_t data_len) {
fiobj_s *sym;
fiobj_s *obj;
if (!parser2http(parser)->request.headers) {
fprintf(stderr,
"ERROR: (http1 parse ordering error) missing HashMap for header "
"%s: %s\n",
name, data);
return -1;
}
if ((uintptr_t)parser2http(parser)->buf ==
(uintptr_t)(parser2http(parser) + 1)) {
sym = fiobj_str_static(name, name_len);
obj = fiobj_str_static(data, data_len);
} else {
sym = fiobj_sym_new(name, name_len);
obj = fiobj_str_new(data, data_len);
h1_reset(parser2http(parser));
}
set_header_add(parser2http(parser)->request.headers, sym, obj);
fiobj_free(sym);
return 0;
}
/** called when a body chunk is parsed. */
static int http1_on_body_chunk(http1_parser_s *parser, char *data,
size_t data_len) {
if (parser->state.content_length >
(ssize_t)parser2http(parser)->p.settings->max_body_size ||
parser->state.read >
(ssize_t)parser2http(parser)->p.settings->max_body_size)
return -1; /* test every time, in case of chunked data */
if (!parser->state.read) {
if (parser->state.content_length > 0 &&
parser->state.content_length + parser2http(parser)->buf_pos <=
HTTP1_MAX_HEADER_SIZE) {
parser2http(parser)->request.body =
fiobj_io_newstr2(data, data_len, NULL);
} else {
parser2http(parser)->body_is_fd = 1;
parser2http(parser)->request.body = fiobj_io_newtmpfile();
fiobj_io_write(parser2http(parser)->request.body, data, data_len);
}
return 0;
}
if (parser2http(parser)->body_is_fd)
fiobj_io_write(parser2http(parser)->request.body, data, data_len);
return 0;
}
/** called when a protocol error occured. */
static int http1_on_error(http1_parser_s *parser) {
sock_close(parser2http(parser)->p.uuid);
return -1;
}
/* *****************************************************************************
Connection Callbacks
***************************************************************************** */
/**
* A string to identify the protocol's service (i.e. "http").
*
* The string should be a global constant, only a pointer comparison will be
* used (not `strcmp`).
*/
static const char *HTTP1_SERVICE_STR = "http1_protocol_facil_io";
static __thread uint8_t h1_static_buffer[HTTP1_MAX_HEADER_SIZE];
/** called when a data is available, but will not run concurrently */
static void http1_on_data(intptr_t uuid, protocol_s *protocol) {
http1_s *p = (http1_s *)protocol;
ssize_t i;
if (p->body_is_fd) {
p->buf = h1_static_buffer;
p->buf_pos = 0;
}
i = sock_read(uuid, p->buf + p->buf_pos, HTTP1_MAX_HEADER_SIZE - p->buf_pos);
if (i > 0)
p->buf_len += i;
if (p->buf_len - p->buf_pos)
p->buf_pos +=
http1_fio_parser(.parser = &p->parser, .buffer = p->buf + p->buf_pos,
.length = (p->buf_len - p->buf_pos),
.on_request = http1_on_request,
.on_response = http1_on_response,
.on_method = http1_on_method,
.on_status = http1_on_status, .on_path = http1_on_path,
.on_query = http1_on_query,
.on_http_version = http1_on_http_version,
.on_header = http1_on_header,
.on_body_chunk = http1_on_body_chunk,
.on_error = http1_on_error);
else
return;
if (p->restart) {
h1_reset(p);
} else if (i <= 0)
return;
facil_force_event(uuid, FIO_EVENT_ON_DATA);
return;
}
/** called when the connection was closed, but will not run concurrently */
static void http1_on_close(intptr_t uuid, protocol_s *protocol) {
http1_destroy(protocol);
(void)uuid;
}
/** called when a data is available for the first time */
static void http1_on_data_first_time(intptr_t uuid, protocol_s *protocol) {
http1_s *p = (http1_s *)protocol;
ssize_t i;
i = sock_read(uuid, p->buf + p->buf_pos, HTTP1_MAX_HEADER_SIZE - p->buf_pos);
if (i <= 0)
return;
if (i >= 24 && !memcmp(p->buf, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24)) {
fprintf(stderr,
"ERROR: unsupported HTTP/2 attempeted using prior knowledge.\n");
sock_close(uuid);
return;
}
p->p.protocol.on_data = http1_on_data;
p->buf_len += i;
http1_on_data(uuid, protocol);
}
/* *****************************************************************************
Public API
***************************************************************************** */
/** Creates an HTTP1 protocol object and handles any unread data in the buffer
* (if any). */
protocol_s *http1_new(uintptr_t uuid, http_settings_s *settings,
void *unread_data, size_t unread_length) {
if (unread_data && unread_length > HTTP1_MAX_HEADER_SIZE)
return NULL;
http1_s *p = malloc(sizeof(*p) + HTTP1_MAX_HEADER_SIZE);
*p = (http1_s){
.p.protocol =
{
.service = HTTP1_SERVICE_STR,
.on_data = http1_on_data_first_time,
.on_close = http1_on_close,
},
.p.uuid = uuid,
.p.settings = settings,
.p.vtable = &HTTP1_VTABLE,
.buf = (uint8_t *)(p + 1),
};
if (unread_data && unread_length <= HTTP1_MAX_HEADER_SIZE) {
memcpy(p->buf, unread_data, unread_length);
p->buf_len = unread_length;
facil_force_event(uuid, FIO_EVENT_ON_DATA);
}
return &p->p.protocol;
}
/** Manually destroys the HTTP1 protocol object. */
void http1_destroy(protocol_s *pr) {
http1_s *p = (http1_s *)pr;
p->request.status = 0;
http_s_cleanup(&p->request);
if (p->queue.arry) {
http_s *o;
while ((o = fio_ary_pop(&p->queue))) {
http_s_cleanup(o);
free(o);
}
fio_ary_free(&p->queue);
}
free(p);
}
/* *****************************************************************************
Protocol Data
***************************************************************************** */
// clang-format off
#define HTTP_SET_STATUS_STR(status, str) [status-100] = { .buffer = ("HTTP/1.1 " #status " " str "\r\n"), .length = (sizeof("HTTP/1.1 " #status " " str "\r\n") - 1) }
// #undef HTTP1_SET_STATUS_STR
// clang-format on
static fio_cstr_s http1_status2str(uintptr_t status) {
static fio_cstr_s status2str[] = {
HTTP_SET_STATUS_STR(100, "Continue"),
HTTP_SET_STATUS_STR(101, "Switching Protocols"),
HTTP_SET_STATUS_STR(102, "Processing"),
HTTP_SET_STATUS_STR(200, "OK"),
HTTP_SET_STATUS_STR(201, "Created"),
HTTP_SET_STATUS_STR(202, "Accepted"),
HTTP_SET_STATUS_STR(203, "Non-Authoritative Information"),
HTTP_SET_STATUS_STR(204, "No Content"),
HTTP_SET_STATUS_STR(205, "Reset Content"),
HTTP_SET_STATUS_STR(206, "Partial Content"),
HTTP_SET_STATUS_STR(207, "Multi-Status"),
HTTP_SET_STATUS_STR(208, "Already Reported"),
HTTP_SET_STATUS_STR(226, "IM Used"),
HTTP_SET_STATUS_STR(300, "Multiple Choices"),
HTTP_SET_STATUS_STR(301, "Moved Permanently"),
HTTP_SET_STATUS_STR(302, "Found"),
HTTP_SET_STATUS_STR(303, "See Other"),
HTTP_SET_STATUS_STR(304, "Not Modified"),
HTTP_SET_STATUS_STR(305, "Use Proxy"),
HTTP_SET_STATUS_STR(306, "(Unused), "),
HTTP_SET_STATUS_STR(307, "Temporary Redirect"),
HTTP_SET_STATUS_STR(308, "Permanent Redirect"),
HTTP_SET_STATUS_STR(400, "Bad Request"),
HTTP_SET_STATUS_STR(403, "Forbidden"),
HTTP_SET_STATUS_STR(404, "Not Found"),
HTTP_SET_STATUS_STR(401, "Unauthorized"),
HTTP_SET_STATUS_STR(402, "Payment Required"),
HTTP_SET_STATUS_STR(405, "Method Not Allowed"),
HTTP_SET_STATUS_STR(406, "Not Acceptable"),
HTTP_SET_STATUS_STR(407, "Proxy Authentication Required"),
HTTP_SET_STATUS_STR(408, "Request Timeout"),
HTTP_SET_STATUS_STR(409, "Conflict"),
HTTP_SET_STATUS_STR(410, "Gone"),
HTTP_SET_STATUS_STR(411, "Length Required"),
HTTP_SET_STATUS_STR(412, "Precondition Failed"),
HTTP_SET_STATUS_STR(413, "Payload Too Large"),
HTTP_SET_STATUS_STR(414, "URI Too Long"),
HTTP_SET_STATUS_STR(415, "Unsupported Media Type"),
HTTP_SET_STATUS_STR(416, "Range Not Satisfiable"),
HTTP_SET_STATUS_STR(417, "Expectation Failed"),
HTTP_SET_STATUS_STR(421, "Misdirected Request"),
HTTP_SET_STATUS_STR(422, "Unprocessable Entity"),
HTTP_SET_STATUS_STR(423, "Locked"),
HTTP_SET_STATUS_STR(424, "Failed Dependency"),
HTTP_SET_STATUS_STR(425, "Unassigned"),
HTTP_SET_STATUS_STR(426, "Upgrade Required"),
HTTP_SET_STATUS_STR(427, "Unassigned"),
HTTP_SET_STATUS_STR(428, "Precondition Required"),
HTTP_SET_STATUS_STR(429, "Too Many Requests"),
HTTP_SET_STATUS_STR(430, "Unassigned"),
HTTP_SET_STATUS_STR(431, "Request Header Fields Too Large"),
HTTP_SET_STATUS_STR(500, "Internal Server Error"),
HTTP_SET_STATUS_STR(501, "Not Implemented"),
HTTP_SET_STATUS_STR(502, "Bad Gateway"),
HTTP_SET_STATUS_STR(503, "Service Unavailable"),
HTTP_SET_STATUS_STR(504, "Gateway Timeout"),
HTTP_SET_STATUS_STR(505, "HTTP Version Not Supported"),
HTTP_SET_STATUS_STR(506, "Variant Also Negotiates"),
HTTP_SET_STATUS_STR(507, "Insufficient Storage"),
HTTP_SET_STATUS_STR(508, "Loop Detected"),
HTTP_SET_STATUS_STR(509, "Unassigned"),
HTTP_SET_STATUS_STR(510, "Not Extended"),
HTTP_SET_STATUS_STR(511, "Network Authentication Required"),
};
fio_cstr_s ret = (fio_cstr_s){.length = 0, .buffer = NULL};
if (status >= 100 && status < sizeof(status2str) / sizeof(status2str[0]))
ret = status2str[status - 100];
if (!ret.buffer)
ret = status2str[400];
return ret;
}
#undef HTTP_SET_STATUS_STR