diff options
Diffstat (limited to 'vere/pkg/vere/io/http.c')
| -rw-r--r-- | vere/pkg/vere/io/http.c | 3444 |
1 files changed, 3444 insertions, 0 deletions
diff --git a/vere/pkg/vere/io/http.c b/vere/pkg/vere/io/http.c new file mode 100644 index 0000000..8c502b0 --- /dev/null +++ b/vere/pkg/vere/io/http.c @@ -0,0 +1,3444 @@ +/// @file + +#include "vere.h" + +#include <limits.h> +#include <string.h> + +#include "h2o.h" +#include "h2o/websocket.h" +#include "noun.h" +#include "openssl/err.h" +#include "openssl/ssl.h" +#include "version.h" + +typedef struct _u3_h2o_serv { + h2o_globalconf_t fig_u; // h2o global config + h2o_context_t ctx_u; // h2o ctx + h2o_accept_ctx_t cep_u; // h2o accept ctx + h2o_hostconf_t* hos_u; // h2o host config + h2o_handler_t* han_u; // h2o request handler +} u3_h2o_serv; + +/* u3_rsat: http request state. +*/ + typedef enum { + u3_rsat_init = 0, // initialized + u3_rsat_plan = 1, // planned + u3_rsat_peek = 2, // peek planned + u3_rsat_ripe = 3 // responded + } u3_rsat; + +typedef struct _u3_hws u3_hws; + +/* u3_hreq: incoming http request. +*/ + typedef struct _u3_hreq { + h2o_req_t* rec_u; // h2o request + c3_w seq_l; // sequence within connection + u3_rsat sat_e; // request state + uv_timer_t* tim_u; // timeout + void* gen_u; // response generator + struct _u3_preq* peq_u; // scry-backed (rsat_peek only) + u3_hws* wsu_u; // websocket session (optional) + struct _u3_hcon* hon_u; // connection backlink + struct _u3_hreq* nex_u; // next in connection's list + struct _u3_hreq* pre_u; // prev in connection's list + } u3_hreq; + + /* u3_preq: scry-backed http request. + */ + typedef struct _u3_preq { + struct _u3_hreq* req_u; // originating request (nullable) + struct _u3_httd* htd_u; // device backpointer + u3_noun pax; // partial scry path + c3_o las_o; // was scry at now + } u3_preq; + +/* u3_hcon: incoming http connection. +*/ + typedef struct _u3_hcon { + uv_tcp_t wax_u; // client stream handler + h2o_conn_t* con_u; // h2o connection + h2o_socket_t* sok_u; // h2o connection socket + c3_w ipf_w; // client ipv4 + c3_w coq_l; // connection number + c3_w seq_l; // next request number + struct _u3_http* htp_u; // server backlink + struct _u3_hreq* req_u; // request list + struct _u3_hcon* nex_u; // next in server's list + struct _u3_hcon* pre_u; // prev in server's list + } u3_hcon; + +/* u3_http: http server. +*/ + typedef struct _u3_http { + uv_tcp_t wax_u; // server stream handler + void* h2o_u; // libh2o configuration + c3_w sev_l; // server number + c3_w coq_l; // next connection number + c3_s por_s; // running port + c3_o dis; // manually-configured port + c3_o sec; // logically secure + c3_o lop; // loopback-only + c3_o liv; // c3n == shutdown + struct _u3_hcon* hon_u; // connection list + struct _u3_http* nex_u; // next in list + struct _u3_httd* htd_u; // device backpointer + } u3_http; + +/* u3_form: http config from %eyre +*/ + typedef struct _u3_form { + c3_o pro; // proxy + c3_o log; // keep access log + c3_o red; // redirect to HTTPS + uv_buf_t key_u; // PEM RSA private key + uv_buf_t cer_u; // PEM certificate chain + } u3_form; + +/* u3_hfig: general http configuration +*/ + typedef struct _u3_hfig { + u3_form* for_u; // config from %eyre + c3_c* key_c; // auth token key + u3_noun ses; // valid session tokens + struct _u3_hreq* seq_u; // open slog requests + uv_timer_t* sit_u; // slog stream heartbeat + } u3_hfig; + +/* u3_httd: general http device +*/ +typedef struct _u3_httd { + u3_auto car_u; // driver + c3_l sev_l; // instance number + u3_hfig fig_u; // http configuration + u3_http* htp_u; // http servers + SSL_CTX* tls_u; // server SSL_CTX* + u3p(u3h_root) sax_p; // url->scry cache + u3p(u3h_root) nax_p; // scry->noun cache + u3_hws* web_u; // websocket sessions + c3_w wid_l; // next websocket id +} u3_httd; + +typedef enum { + u3_hws_pending = 0, + u3_hws_open = 1, + u3_hws_closing = 2, + u3_hws_closed = 3, +} u3_hwsat; + +struct _u3_hws { + h2o_websocket_conn_t* woc_u; // h2o websocket connection + u3_httd* htd_u; // device backpointer + u3_hreq* req_u; // pending request (handshake) + u3_hws* nex_u; // next in list + u3_hws* pre_u; // prev in list + u3_hwsat sat_e; // websocket state + c3_w wid_l; // websocket id + c3_l sev_l; // server id + c3_l coq_l; // connection id + c3_l seq_l; // request id + c3_c key_c[29]; // sec-websocket-key buffer +}; + +static u3_weak _http_rec_to_httq(h2o_req_t* rec_u); +static u3_hreq* _http_req_prepare(h2o_req_t* rec_u, u3_hreq* (*new_f)(u3_hcon*, h2o_req_t*)); +static void _http_serv_free(u3_http* htp_u); +static void _http_serv_start_all(u3_httd* htd_u); +static void _http_form_free(u3_httd* htd_u); +static void _http_start_respond(u3_hreq* req_u, + u3_noun status, + u3_noun headers, + u3_noun data, + u3_noun complete); +static void _http_ws_handshake(u3_hreq* req_u, u3_noun req, const c3_c* client_key); +static void _http_ws_message_cb(h2o_websocket_conn_t *conn, const struct wslay_event_on_msg_recv_arg *arg); +static void _http_ws_link(u3_httd* htd_u, u3_hws* web_u); +static void _http_ws_unlink(u3_hws* web_u); +static u3_hws* _http_ws_find(u3_httd* htd_u, c3_w wid_l); +static void _http_ws_disconnect(u3_hws* web_u); +static void _http_ws_plan_event(u3_hws* web_u, u3_noun event); +static void _http_ws_close_all(u3_httd* htd_u); +static void _http_ws_detach_request(u3_hreq* req_u); + +static const c3_i TCP_BACKLOG = 16; +static const c3_w HEARTBEAT_TIMEOUT = 20 * 1000; + +/* _http_close_cb(): uv_close_cb that just free's handle +*/ +static void +_http_close_cb(uv_handle_t* han_u) +{ + c3_free(han_u); +} + +/* _http_vec_to_meth(): convert h2o_iovec_t to meth +*/ +static u3_weak +_http_vec_to_meth(h2o_iovec_t vec_u) +{ + return ( 0 == strncmp(vec_u.base, "GET", vec_u.len) ) ? u3i_string("GET") : + ( 0 == strncmp(vec_u.base, "PUT", vec_u.len) ) ? u3i_string("PUT") : + ( 0 == strncmp(vec_u.base, "POST", vec_u.len) ) ? u3i_string("POST") : + ( 0 == strncmp(vec_u.base, "HEAD", vec_u.len) ) ? u3i_string("HEAD") : + ( 0 == strncmp(vec_u.base, "CONNECT", vec_u.len) ) ? u3i_string("CONNECT") : + ( 0 == strncmp(vec_u.base, "DELETE", vec_u.len) ) ? u3i_string("DELETE") : + ( 0 == strncmp(vec_u.base, "OPTIONS", vec_u.len) ) ? u3i_string("OPTIONS") : + ( 0 == strncmp(vec_u.base, "TRACE", vec_u.len) ) ? u3i_string("TRACE") : + // TODO ?? + // ( 0 == strncmp(vec_u.base, "PATCH", vec_u.len) ) ? c3__patc : + u3_none; +} + +/* _http_vec_to_atom(): convert h2o_iovec_t to atom (cord) +*/ +static u3_noun +_http_vec_to_atom(h2o_iovec_t vec_u) +{ + return u3i_bytes(vec_u.len, (const c3_y*)vec_u.base); +} + +/* _http_vec_to_octs(): convert h2o_iovec_t to (unit octs) +*/ +static u3_noun +_http_vec_to_octs(h2o_iovec_t vec_u) +{ + if ( 0 == vec_u.len ) { + return u3_nul; + } + + // XX correct size_t -> atom? + return u3nt(u3_nul, u3i_chubs(1, (const c3_d*)&vec_u.len), + _http_vec_to_atom(vec_u)); +} + +/* _cttp_bods_free(): free body structure. +*/ +static void +_cttp_bods_free(u3_hbod* bod_u) +{ + while ( bod_u ) { + u3_hbod* nex_u = bod_u->nex_u; + + c3_free(bod_u); + bod_u = nex_u; + } +} + +/* _cttp_bod_from_octs(): translate octet-stream noun into body. +*/ +static u3_hbod* +_cttp_bod_from_octs(u3_noun oct) +{ + c3_w len_w; + + if ( !_(u3a_is_cat(u3h(oct))) ) { // 2GB max + u3m_bail(c3__fail); return 0; + } + len_w = u3h(oct); + + { + u3_hbod* bod_u = c3_malloc(1 + len_w + sizeof(*bod_u)); + bod_u->hun_y[len_w] = 0; + bod_u->len_w = len_w; + u3r_bytes(0, len_w, bod_u->hun_y, u3t(oct)); + + bod_u->nex_u = 0; + + u3z(oct); + return bod_u; + } +} + +/* _cttp_bods_to_vec(): translate body buffers to array of h2o_iovec_t +*/ +static h2o_iovec_t* +_cttp_bods_to_vec(u3_hbod* bod_u, c3_w* tot_w) +{ + h2o_iovec_t* vec_u; + c3_w len_w; + + { + u3_hbod* bid_u = bod_u; + len_w = 0; + + while( bid_u ) { + len_w++; + bid_u = bid_u->nex_u; + } + } + + vec_u = c3_malloc(sizeof(h2o_iovec_t) * len_w); + len_w = 0; + + while( bod_u ) { + vec_u[len_w] = h2o_iovec_init(bod_u->hun_y, bod_u->len_w); + len_w++; + bod_u = bod_u->nex_u; + } + + *tot_w = len_w; + + return vec_u; +} + +/* _http_heds_to_noun(): convert h2o_header_t to (list (pair @t @t)) +*/ +static u3_noun +_http_heds_to_noun(h2o_header_t* hed_u, c3_d hed_d) +{ + u3_noun hed = u3_nul; + c3_d dex_d = hed_d; + + h2o_header_t deh_u; + + while ( 0 < dex_d ) { + deh_u = hed_u[--dex_d]; + hed = u3nc(u3nc(_http_vec_to_atom(*deh_u.name), + _http_vec_to_atom(deh_u.value)), hed); + } + + return hed; +} + +/* _http_heds_free(): free header linked list +*/ +static void +_http_heds_free(u3_hhed* hed_u) +{ + while ( hed_u ) { + u3_hhed* nex_u = hed_u->nex_u; + + c3_free(hed_u->nam_c); + c3_free(hed_u->val_c); + c3_free(hed_u); + hed_u = nex_u; + } +} + +/* _http_hed_new(): create u3_hhed from nam/val cords +*/ +static u3_hhed* +_http_hed_new(u3_atom nam, u3_atom val) +{ + c3_w nam_w = u3r_met(3, nam); + c3_w val_w = u3r_met(3, val); + u3_hhed* hed_u = c3_malloc(sizeof(*hed_u)); + + hed_u->nam_c = c3_malloc(1 + nam_w); + hed_u->val_c = c3_malloc(1 + val_w); + hed_u->nam_c[nam_w] = 0; + hed_u->val_c[val_w] = 0; + hed_u->nex_u = 0; + hed_u->nam_w = nam_w; + hed_u->val_w = val_w; + + u3r_bytes(0, nam_w, (c3_y*)hed_u->nam_c, nam); + u3r_bytes(0, val_w, (c3_y*)hed_u->val_c, val); + + return hed_u; +} + +/* _http_heds_from_noun(): convert (list (pair @t @t)) to u3_hhed +*/ +static u3_hhed* +_http_heds_from_noun(u3_noun hed) +{ + u3_noun deh = hed; + u3_noun i_hed; + + u3_hhed* hed_u = 0; + + while ( u3_nul != hed ) { + i_hed = u3h(hed); + u3_hhed* nex_u = _http_hed_new(u3h(i_hed), u3t(i_hed)); + nex_u->nex_u = hed_u; + + hed_u = nex_u; + hed = u3t(hed); + } + + u3z(deh); + return hed_u; +} + +/* _http_req_is_auth(): returns c3y if rec_u contains a valid auth cookie +*/ +static c3_o +_http_req_is_auth(u3_hfig* fig_u, h2o_req_t* rec_u) +{ + // try to find a cookie header + // + h2o_iovec_t coo_u = {NULL, 0}; + { + //TODO http2 allows the client to put multiple 'cookie' headers, + // runtime should support that once eyre does too. + ssize_t hin_i = h2o_find_header_by_str(&rec_u->headers, "cookie", 6, -1); + if ( hin_i != -1 ) { + coo_u = rec_u->headers.entries[hin_i].value; + } + } + + // if there is no cookie header, it can't possibly be authenticated + // + if ( NULL == coo_u.base ) { + return c3n; + } + // if there is a cookie, see if it contains a valid auth token + // + else { + c3_c* key_c = fig_u->key_c; + c3_c val_c[128]; + c3_y val_y = 0; + size_t i_i = 0; + size_t j_i = 0; + + // step through the cookie string + // + while (i_i < coo_u.len) { + // if we found our key, read the value + // + if (key_c[j_i] == '\0' && coo_u.base[i_i] == '=') { + i_i++; + while ( i_i < coo_u.len + && coo_u.base[i_i] != ';' + && val_y < sizeof(val_c) ) { + val_c[val_y] = coo_u.base[i_i]; + val_y++; + i_i++; + } + break; + } + // keep reading the key as long as it matches + // + else if (coo_u.base[i_i] == key_c[j_i]) { + j_i++; + } + else { + j_i = 0; + } + i_i++; + } + + u3_noun aut = u3kdi_has(u3k(fig_u->ses), u3i_bytes(val_y, (c3_y*)val_c)); + u3_assert(c3y == aut || c3n == aut); + + return aut; + } +} + +/* _http_req_find(): find http request in connection by sequence. +*/ +static u3_hreq* +_http_req_find(u3_hcon* hon_u, c3_w seq_l) +{ + u3_hreq* req_u = hon_u->req_u; + + // XX glories of linear search + // + while ( req_u ) { + if ( seq_l == req_u->seq_l ) { + return req_u; + } + req_u = req_u->nex_u; + } + return 0; +} + +/* _http_req_link(): link http request to connection +*/ +static void +_http_req_link(u3_hcon* hon_u, u3_hreq* req_u) +{ + req_u->hon_u = hon_u; + req_u->seq_l = hon_u->seq_l++; + req_u->nex_u = hon_u->req_u; + + if ( 0 != req_u->nex_u ) { + req_u->nex_u->pre_u = req_u; + } + hon_u->req_u = req_u; +} + +/* _http_req_unlink(): remove http request from connection +*/ +static void +_http_req_unlink(u3_hreq* req_u) +{ + if ( 0 != req_u->pre_u ) { + req_u->pre_u->nex_u = req_u->nex_u; + + if ( 0 != req_u->nex_u ) { + req_u->nex_u->pre_u = req_u->pre_u; + } + } + else { + req_u->hon_u->req_u = req_u->nex_u; + + if ( 0 != req_u->nex_u ) { + req_u->nex_u->pre_u = 0; + } + } +} + +/* _http_seq_link(): store slog stream request in state +*/ +static void +_http_seq_link(u3_hcon* hon_u, u3_hreq* req_u) +{ + u3_hfig* fig_u = &hon_u->htp_u->htd_u->fig_u; + req_u->hon_u = hon_u; + req_u->seq_l = hon_u->seq_l++; + req_u->nex_u = fig_u->seq_u; + + if ( 0 != req_u->nex_u ) { + req_u->nex_u->pre_u = req_u; + } + fig_u->seq_u = req_u; +} + +/* _http_seq_unlink(): remove slog stream request from state +*/ +static void +_http_seq_unlink(u3_hreq* req_u) +{ + u3_hfig* fig_u = &req_u->hon_u->htp_u->htd_u->fig_u; + if ( 0 != req_u->pre_u ) { + req_u->pre_u->nex_u = req_u->nex_u; + + if ( 0 != req_u->nex_u ) { + req_u->nex_u->pre_u = req_u->pre_u; + } + } + else { + fig_u->seq_u = req_u->nex_u; + + if ( 0 != req_u->nex_u ) { + req_u->nex_u->pre_u = 0; + } + } + + // unlink from async scry request if present + // + if ( req_u->peq_u ) { + req_u->peq_u->req_u = 0; + } +} + +/* _http_req_to_duct(): translate srv/con/req to duct +*/ +static u3_noun +_http_req_to_duct(u3_hreq* req_u) +{ + return u3nc(u3i_string("http-server"), + u3nq(u3dc("scot", c3__uv, req_u->hon_u->htp_u->sev_l), + u3dc("scot", c3__ud, req_u->hon_u->coq_l), + u3dc("scot", c3__ud, req_u->seq_l), + u3_nul)); +} + +/* _http_req_kill(): kill http request in %eyre. +*/ +static void +_http_req_kill(u3_hreq* req_u) +{ + u3_httd* htd_u = req_u->hon_u->htp_u->htd_u; + u3_noun wir = _http_req_to_duct(req_u); + u3_noun cad = u3nc(u3i_string("cancel-request"), u3_nul); + + u3_auto_plan(&htd_u->car_u, u3_ovum_init(0, c3__e, wir, cad)); +} + +/* _http_ws_link(): link websocket session to device list. +*/ +static void +_http_ws_link(u3_httd* htd_u, u3_hws* web_u) +{ + web_u->htd_u = htd_u; + web_u->nex_u = htd_u->web_u; + web_u->pre_u = 0; + + if ( 0 != web_u->nex_u ) { + web_u->nex_u->pre_u = web_u; + } + htd_u->web_u = web_u; +} + +/* _http_ws_unlink(): unlink websocket session from device list. +*/ +static void +_http_ws_unlink(u3_hws* web_u) +{ + if ( 0 != web_u->pre_u ) { + web_u->pre_u->nex_u = web_u->nex_u; + + if ( 0 != web_u->nex_u ) { + web_u->nex_u->pre_u = web_u->pre_u; + } + } + else if ( web_u->htd_u->web_u == web_u ) { + web_u->htd_u->web_u = web_u->nex_u; + + if ( 0 != web_u->nex_u ) { + web_u->nex_u->pre_u = 0; + } + } + + web_u->nex_u = 0; + web_u->pre_u = 0; +} + +/* _http_ws_find(): locate websocket session by id. +*/ +static u3_hws* +_http_ws_find(u3_httd* htd_u, c3_w wid_l) +{ + u3_hws* web_u = htd_u->web_u; + + while ( 0 != web_u ) { + if ( wid_l == web_u->wid_l ) { + return web_u; + } + web_u = web_u->nex_u; + } + + return 0; +} + +/* _http_ws_to_duct(): rebuild duct for websocket session. +*/ +static u3_noun +_http_ws_to_duct(u3_hws* web_u) +{ + return u3nc(u3i_string("http-server"), + u3nq(u3dc("scot", c3__uv, web_u->sev_l), + u3dc("scot", c3__ud, web_u->coq_l), + u3dc("scot", c3__ud, web_u->seq_l), + u3_nul)); +} + +/* _http_ws_plan_event(): queue websocket event for eyre. +*/ +static void +_http_ws_plan_event(u3_hws* web_u, u3_noun event) +{ + u3_noun wir = _http_ws_to_duct(web_u); + u3_noun pay = u3nc(u3i_chub((c3_d)web_u->wid_l), event); + u3_noun cad = u3nc(u3i_string("websocket-event"), pay); + + u3l_log("http: ws emit wid=%u event=%s", web_u->wid_l, u3r_string(u3h(cad))); + + u3_auto_plan(&web_u->htd_u->car_u, u3_ovum_init(0, c3__e, wir, cad)); +} + +/* _http_ws_close_all(): close all active websocket sessions. +*/ +static void +_http_ws_close_all(u3_httd* htd_u) +{ + u3_hws* web_u = htd_u->web_u; + + while ( 0 != web_u ) { + u3_hws* nex_u = web_u->nex_u; + _http_ws_disconnect(web_u); + web_u = nex_u; + } +} + +/* _http_ws_detach_request(): release websocket session tied to request (if pending). +*/ +static void +_http_ws_detach_request(u3_hreq* req_u) +{ + if ( 0 == req_u->wsu_u ) { + return; + } + + u3_hws* web_u = req_u->wsu_u; + req_u->wsu_u = 0; + + if ( 0 != web_u->req_u && web_u->req_u == req_u ) { + web_u->req_u = 0; + web_u->sat_e = u3_hws_closed; + _http_ws_unlink(web_u); + c3_free(web_u); + } +} + +typedef struct _u3_hgen { + h2o_generator_t neg_u; // response callbacks + c3_o red; // ready to send + enum { // + u3_hgen_wait = 0, // more expected + u3_hgen_done = 1, // complete + u3_hgen_fail = 2 // failed + } sat_e; + u3_hbod* bod_u; // pending body + u3_hbod* nud_u; // pending free + u3_hhed* hed_u; // pending free + u3_hreq* req_u; // originating request +} u3_hgen; + +/* _http_req_close(): clean up & deallocate request +*/ +static void +_http_req_close(u3_hreq* req_u) +{ + if ( 0 != req_u->wsu_u ) { + _http_ws_detach_request(req_u); + } + + // client canceled request before response + // + if ( u3_rsat_plan == req_u->sat_e ) { + _http_req_kill(req_u); + } + + if ( 0 != req_u->tim_u ) { + uv_close((uv_handle_t*)req_u->tim_u, _http_close_cb); + req_u->tim_u = 0; + } +} + +/* _http_req_done(): request finished, deallocation callback +*/ +static void +_http_req_done(void* ptr_v) +{ + u3_hreq* req_u = (u3_hreq*)ptr_v; + _http_req_close(req_u); + _http_req_unlink(req_u); +} + +/* _http_seq_done(): slog stream request finished, deallocation callback +*/ +static void +_http_seq_done(void* ptr_v) +{ + u3_hreq* seq_u = (u3_hreq*)ptr_v; + _http_req_close(seq_u); + _http_seq_unlink(seq_u); +} + +static void +_http_hgen_send(u3_hgen* gen_u); + +/* _http_req_timer_cb(): request timeout callback +*/ +static void +_http_req_timer_cb(uv_timer_t* tim_u) +{ + u3_hreq* req_u = tim_u->data; + + switch ( req_u->sat_e ) { + case u3_rsat_init: u3_assert(0); + + case u3_rsat_plan: { + _http_req_kill(req_u); + req_u->sat_e = u3_rsat_ripe; + + c3_c* msg_c = "gateway timeout"; + h2o_send_error_generic(req_u->rec_u, 504, msg_c, msg_c, 0); + + if ( 0 != req_u->wsu_u ) { + _http_ws_detach_request(req_u); + } + } break; + + case u3_rsat_peek: { + req_u->peq_u->req_u = 0; + c3_c* msg_c = "gateway timeout"; + h2o_send_error_generic(req_u->rec_u, 504, msg_c, msg_c, 0); + } break; + + case u3_rsat_ripe: { + u3_hgen* gen_u = req_u->gen_u; + + // inform %eyre if response was incomplete + // + if ( u3_hgen_wait == gen_u->sat_e ) { + _http_req_kill(req_u); + } + + gen_u->sat_e = u3_hgen_fail; + + if ( c3y == gen_u->red ) { + _http_hgen_send(gen_u); + } + } break; + } +} + +/* _http_req_new(): receive standard http request. +*/ +static u3_hreq* +_http_req_new(u3_hcon* hon_u, h2o_req_t* rec_u) +{ + u3_hreq* req_u = h2o_mem_alloc_shared(&rec_u->pool, sizeof(*req_u), + _http_req_done); + memset(req_u, 0, sizeof(*req_u)); + req_u->rec_u = rec_u; + req_u->sat_e = u3_rsat_init; + + _http_req_link(hon_u, req_u); + + return req_u; +} + +/* _http_seq_new(): receive slog stream http request. +*/ +static u3_hreq* +_http_seq_new(u3_hcon* hon_u, h2o_req_t* rec_u) +{ + u3_hreq* req_u = h2o_mem_alloc_shared(&rec_u->pool, sizeof(*req_u), + _http_seq_done); + memset(req_u, 0, sizeof(*req_u)); + req_u->rec_u = rec_u; + req_u->sat_e = u3_rsat_plan; + + _http_seq_link(hon_u, req_u); + + return req_u; +} + +static void +_http_cache_respond(u3_hreq* req_u, u3_noun nun); + +static void +_http_scry_respond(u3_hreq* req_u, u3_noun nun); + +typedef struct _byte_range { + c3_z beg_z; + c3_z end_z; +} byte_range; + +/* _chunk_align(): align range to a nearby chunk +*/ +static void +_chunk_align(byte_range* rng_u) +{ + c3_z siz_z = 4194304; // 4MiB + + if ( SIZE_MAX != rng_u->beg_z ) { + if ( rng_u->beg_z > rng_u->end_z ) { + rng_u->beg_z = SIZE_MAX; + rng_u->end_z = SIZE_MAX; + } + else { + // XX an out-of-bounds request could be aligned to in-bounds + // resulting in a 200 or 206 response instead of 416. + // browsers should have the total length from content-range, + // and send reasonable range requests. + // + rng_u->beg_z = (rng_u->beg_z / siz_z) * siz_z; + rng_u->end_z = (rng_u->beg_z + siz_z) - 1; + } + } + else if ( SIZE_MAX != rng_u->end_z ) { + // round up to multiple of siz_z + rng_u->end_z = siz_z * ((rng_u->end_z / siz_z) + 1); + } +} + +/* _parse_range(): get a range from '-' delimited text +*/ +static byte_range +_parse_range(c3_c* txt_c, c3_w len_w) +{ + c3_c* hep_c = memchr(txt_c, '-', len_w); + byte_range rng_u; + rng_u.beg_z = SIZE_MAX; + rng_u.end_z = SIZE_MAX; + + if ( hep_c ) { + rng_u.beg_z = h2o_strtosize(txt_c, hep_c - txt_c); + rng_u.end_z = h2o_strtosize(hep_c + 1, len_w - ((hep_c + 1) - txt_c)); + // strange -> [SIZE_MAX SIZE_MAX] + if ( ((SIZE_MAX == rng_u.beg_z) && (hep_c != txt_c)) + || ((SIZE_MAX == rng_u.end_z) && (len_w - ((hep_c + 1) - txt_c) > 0)) + || ((SIZE_MAX != rng_u.beg_z) && (rng_u.beg_z > rng_u.end_z)) ) + { + rng_u.beg_z = SIZE_MAX; + rng_u.end_z = SIZE_MAX; + } + } + return rng_u; +} + +/* _get_range(): get a _byte_range from headers +*/ +static c3_o +_get_range(h2o_headers_t req_headers, byte_range* rng_u) +{ + rng_u->beg_z = SIZE_MAX; + rng_u->end_z = SIZE_MAX; + + c3_w inx_w = h2o_find_header(&req_headers, H2O_TOKEN_RANGE, -1); + if ( UINT32_MAX == inx_w) { + return c3n; + } + + if ( (req_headers.entries[inx_w].value.len >= 6) + && (0 == memcmp("bytes=", req_headers.entries[inx_w].value.base, 6)) ) + { + byte_range tmp_u = _parse_range(req_headers.entries[inx_w].value.base + 6, + req_headers.entries[inx_w].value.len - 6); + rng_u->beg_z = tmp_u.beg_z; + rng_u->end_z = tmp_u.end_z; + } + + return c3y; +} + +/* _http_scry_cb(): respond and maybe cache scry result +*/ +static void +_http_scry_cb(void* vod_p, u3_noun nun) +{ + u3_preq* peq_u = vod_p; + u3_httd* htd_u = peq_u->htd_u; + u3_hreq* req_u = peq_u->req_u; + u3_hfig* fig_u = &req_u->hon_u->htp_u->htd_u->fig_u; + c3_o auth = _http_req_is_auth(fig_u, req_u->rec_u); + + if ( req_u ) { + u3_assert(u3_rsat_peek == req_u->sat_e); + req_u->peq_u = 0; + _http_scry_respond(req_u, u3k(nun)); + } + + // cache only if peek was not at now, and nun isn't u3_nul + if ( (c3n == peq_u->las_o) + && (u3_nul != nun) ) + { + u3_noun key = u3nc(auth, u3k(peq_u->pax)); + u3h_put(htd_u->nax_p, key, nun); + u3z(key); + } + else { + u3z(nun); + } + + u3z(peq_u->pax); + c3_free(peq_u); +} + +/* _beam: ship desk case spur +*/ +typedef struct _beam { + u3_weak who; + u3_weak des; + u3_weak cas; + u3_weak pur; +} beam; + +/* _free_beam(): free a beam +*/ +static void +_free_beam(beam* bem) +{ + u3z(bem->who); + u3z(bem->des); + u3z(bem->cas); + u3z(bem->pur); +} + +/* _get_beam(): get a _beam from url +*/ +static beam +_get_beam(u3_hreq* req_u, c3_c* txt_c, c3_w len_w) +{ + beam bem; + + // get beak + // + for ( c3_w i_w = 0; i_w < 3; ++i_w ) { + u3_noun* wer; + if ( 0 == i_w ) { + wer = &bem.who; + } + else if ( 1 == i_w ) { + wer = &bem.des; + } + else { + wer = &bem.cas; + } + + // find '//' + if ( (len_w >= 2) + && ('/' == txt_c[0]) + && ('/' == txt_c[1]) ) + { + *wer = u3_nul; + txt_c++; + len_w--; + } + // skip '/' + else if ( (len_w > 0) && ('/' == txt_c[0]) ) { + txt_c++; + len_w--; + } + + // '=' + if ( (len_w > 0) && ('=' == txt_c[0]) ) { + if ( 0 == i_w ) { + u3_http* htp_u = req_u->hon_u->htp_u; + u3_httd* htd_u = htp_u->htd_u; + *wer = u3dc("scot", 'p', u3i_chubs(2, htd_u->car_u.pir_u->who_d)); + } + else if ( 1 == i_w ) { + *wer = c3__base; + } + else { + req_u->peq_u->las_o = c3y; + } + txt_c++; + len_w--; + } + // slice cord + else { + c3_c* nex_c; + c3_c* tis_c = memchr(txt_c, '=', len_w); + c3_c* fas_c = memchr(txt_c, '/', len_w); + + if ( tis_c && fas_c ) { + nex_c = c3_min(tis_c, fas_c); + } + else { + nex_c = ( tis_c ) ? tis_c : fas_c; + } + + if ( !nex_c ) { + *wer = u3_none; + return bem; + } + else { + c3_w dif_w = (c3_p)(nex_c - txt_c); + *wer = u3i_bytes(dif_w, (const c3_y*)txt_c); + txt_c = nex_c; + len_w = len_w - dif_w; + } + } + } + + // get spur + u3_noun tmp = u3dc("rush", u3i_bytes(len_w, (const c3_y*)txt_c), u3v_wish("stap")); + bem.pur = ( u3_nul == tmp ) ? u3_none : u3k(u3t(tmp)); + u3z(tmp); + + return bem; +} + +/* _http_req_dispatch(): dispatch http request +*/ +static void +_http_req_dispatch(u3_hreq* req_u, u3_noun req) +{ + u3_assert(u3_rsat_init == req_u->sat_e); + req_u->sat_e = u3_rsat_plan; + + { + u3_http* htp_u = req_u->hon_u->htp_u; + u3_httd* htd_u = htp_u->htd_u; + + c3_c* bas_c = req_u->rec_u->input.path.base; + c3_w len_w = req_u->rec_u->input.path.len; + + // check if base url starts with '/_~_/' + if ( (len_w < 6) + || (0 != memcmp("/_~_/", bas_c, 5)) ) + { + // no: inject to arvo + u3_noun wir = _http_req_to_duct(req_u); + u3_noun cad; + u3_noun adr = u3nc(c3__ipv4, u3i_words(1, &req_u->hon_u->ipf_w)); + // XX loopback automatically secure too? + // + u3_noun dat = u3nt(htp_u->sec, adr, req); + + cad = ( c3y == req_u->hon_u->htp_u->lop ) + ? u3nc(u3i_string("request-local"), dat) + : u3nc(u3i_string("request"), dat); + u3_auto_plan(&htd_u->car_u, u3_ovum_init(0, c3__e, wir, cad)); + } + else { + // '/_~_/' found + bas_c = bas_c + 4; // retain '/' after /_~_ + len_w = len_w - 4; + + req_u->peq_u = c3_malloc(sizeof(*req_u->peq_u)); + req_u->peq_u->req_u = req_u; + req_u->peq_u->htd_u = htd_u; + req_u->peq_u->las_o = c3n; + req_u->sat_e = u3_rsat_peek; + req_u->peq_u->pax = u3_nul; + + u3_hfig* fig_u = &req_u->hon_u->htp_u->htd_u->fig_u; + h2o_req_t* rec_u = req_u->rec_u; + + // set gang to [~ ~] or ~ + u3_noun gang; + c3_o auth = _http_req_is_auth(fig_u, rec_u); + if ( auth == c3y ) { + gang = u3nc(u3_nul, u3_nul); + } + else { + gang = u3_nul; + } + + beam bem = _get_beam(req_u, bas_c, len_w); + if ( (u3_none == bem.who) + || (u3_none == bem.des) + || (u3_none == bem.cas) + || (u3_none == bem.pur) ) + { + c3_c* msg_c = "bad request"; + h2o_send_error_generic(req_u->rec_u, 400, msg_c, msg_c, 0); + u3z(gang); + u3z(req_u->peq_u->pax); + _free_beam(&bem); + return; + } + + h2o_headers_t req_headers = req_u->rec_u->headers; + byte_range rng_u; + c3_o rng_o = _get_range(req_headers, &rng_u); + + // prepare spur for eyre range scry + // + u3_noun spur; + if ( c3n == rng_o ) { + // full range: '/range/0//foo' + spur = u3nq(u3i_string("range"), c3_s1('0'), u3_blip, u3k(bem.pur)); + } + else { + _chunk_align(&rng_u); + + u3_atom beg = ( SIZE_MAX == rng_u.beg_z) ? + u3_blip : u3dc("scot", c3__ud, u3i_chub(rng_u.beg_z)); + u3_atom end = ( SIZE_MAX == rng_u.end_z) ? + u3_blip : u3dc("scot", c3__ud, u3i_chub(rng_u.end_z)); + + spur = u3nq(u3i_string("range"), beg, end, u3k(bem.pur)); + } + + // peek or respond from cache + // + if ( c3y == req_u->peq_u->las_o ) { + u3_noun our = u3dc("scot", 'p', u3i_chubs(2, htd_u->car_u.pir_u->who_d)); + if ( c3y == u3r_sing(our, bem.who) ) { + u3_pier_peek_last(htd_u->car_u.pir_u, gang, c3__ex, + u3k(bem.des), spur, req_u->peq_u, _http_scry_cb); + } + else { + c3_c* msg_c = "bad request"; + h2o_send_error_generic(req_u->rec_u, 400, msg_c, msg_c, 0); + u3z(gang); + u3z(spur); + u3z(req_u->peq_u->pax); + } + u3z(our); + } + else { + u3_noun bam = u3nq(u3k(bem.who), u3k(bem.des), u3k(bem.cas), spur); + u3_noun key = u3nc(auth, u3k(bam)); + u3_weak nac = u3h_get(htd_u->nax_p, key); + u3z(key); + + if ( (u3_none == nac) + || ((u3_nul == gang) && (c3y == u3r_at(14, nac))) ) + { + // maybe cache, then serve subsequent range requests from cache + u3z(req_u->peq_u->pax); + req_u->peq_u->pax = u3k(bam); + u3_pier_peek(htd_u->car_u.pir_u, gang, u3nt(0, c3__ex, bam), + req_u->peq_u, _http_scry_cb); + u3z(nac); + } + else { + _http_scry_respond(req_u, nac); + u3z(bam); + u3z(gang); + } + } + _free_beam(&bem); + } + } +} + +/* _http_ws_handshake(): handle websocket handshake request. +*/ +static void +_http_ws_handshake(u3_hreq* req_u, u3_noun req, const c3_c* client_key) +{ + // capture the owning HTTP server/device so we can reuse shared state + // + u3_http* htp_u = req_u->hon_u->htp_u; + u3_httd* htd_u = htp_u->htd_u; + + // websocket ids start at 1; initialize the counter lazily + // + if ( 0 == htd_u->wid_l ) { + htd_u->wid_l = 1; + } + + // allocate a websocket session, mark it pending, and record the + // identifiers needed to upgrade/respond later (server/connection/request) + // + u3_hws* web_u = c3_calloc(sizeof(*web_u)); + web_u->sat_e = u3_hws_pending; + web_u->wid_l = htd_u->wid_l++; + web_u->sev_l = htp_u->sev_l; + web_u->coq_l = req_u->hon_u->coq_l; + web_u->seq_l = req_u->seq_l; + web_u->req_u = req_u; + web_u->woc_u = 0; + + // stash the Sec-WebSocket-Key so h2o can finalize the upgrade later + // + ssize_t key_index = h2o_find_header_by_str(&req_u->rec_u->headers, + H2O_STRLIT("sec-websocket-key"), -1); + size_t key_len = ( -1 != key_index ) + ? req_u->rec_u->headers.entries[key_index].value.len + : 0; + if ( key_len >= sizeof(web_u->key_c) ) { + key_len = sizeof(web_u->key_c) - 1; + } + memcpy(web_u->key_c, client_key, key_len); + web_u->key_c[key_len] = 0; + + // link the session into the device list and note the pending plan on req + // + _http_ws_link(htd_u, web_u); + req_u->wsu_u = web_u; + req_u->sat_e = u3_rsat_plan; + + // build the `%http-server` duct: ["http-server" uv-id conn-id req-id] + // + u3_noun wir = _http_req_to_duct(req_u); + // peer IPv4 as `[c3__ipv4 (atom ip-address)]` + // + c3_w ipf_w = req_u->hon_u->ipf_w; + u3_noun adr = u3nc(c3__ipv4, u3i_words(1, &ipf_w)); + // + u3_noun dat = u3nt(htp_u->sec, adr, u3k(req)); + // + u3_noun pay = u3nc(u3i_chub((c3_d)web_u->wid_l), dat); + // final ovum: ["websocket-handshake" pay], i.e. [%websocket-handshake websocket-id=@ secure=? =address:eyre =request:http] + // + u3_noun cad = u3nc(u3i_string("websocket-handshake"), pay); + + // enqueue the ovum for %eyre and drop the temporary request noun + // + u3_auto_plan(&htd_u->car_u, u3_ovum_init(0, c3__e, wir, cad)); + + u3z(req); +} + +/* _http_ws_accept(): accept websocket handshake. +*/ +static void +_http_ws_accept(u3_hws* web_u) +{ + if ( 0 == web_u || u3_hws_pending != web_u->sat_e ) { + return; + } + + u3_hreq* req_u = web_u->req_u; + if ( 0 == req_u ) { + return; + } + + if ( 0 != req_u->tim_u ) { + uv_timer_stop(req_u->tim_u); + } + + req_u->sat_e = u3_rsat_ripe; + + u3l_log("http: ws accept wid=%u req=%p rec=%p", web_u->wid_l, (void*)req_u, (void*)req_u->rec_u); + + // guard against h2o freeing the request while we upgrade; if that happens, + // _http_req_close() will no longer tear down the websocket session. + web_u->req_u = 0; + + web_u->woc_u = h2o_upgrade_to_websocket(req_u->rec_u, + web_u->key_c, + web_u, + _http_ws_message_cb); + + if ( 0 == web_u->woc_u ) { + c3_c* msg_c = "websocket upgrade failed"; + h2o_send_error_generic(req_u->rec_u, 500, msg_c, msg_c, 0); + web_u->req_u = req_u; + req_u->wsu_u = 0; + web_u->sat_e = u3_hws_closed; + _http_ws_unlink(web_u); + c3_free(web_u); + return; + } + + web_u->sat_e = u3_hws_open; + req_u->wsu_u = web_u; + + u3l_log("http: ws proceed scheduled wid=%u conn=%p", web_u->wid_l, (void*)web_u->woc_u); + + /* h2o will invoke h2o_websocket_proceed() once the HTTP upgrade completes + * (see on_complete in lib/websocket.c). Calling it here would dereference a + * null sock before the upgrade is finalized. + */ +} + +/* _http_ws_reject(): reject websocket handshake. +*/ +static void +_http_ws_reject(u3_hws* web_u) +{ + if ( 0 == web_u || u3_hws_pending != web_u->sat_e ) { + return; + } + + web_u->sat_e = u3_hws_closed; + + if ( 0 != web_u->req_u ) { + u3_hreq* req_u = web_u->req_u; + if ( 0 != req_u->tim_u ) { + uv_timer_stop(req_u->tim_u); + } + req_u->sat_e = u3_rsat_ripe; + req_u->wsu_u = 0; + + c3_c* msg_c = "websocket rejected"; + h2o_send_error_generic(req_u->rec_u, 403, msg_c, msg_c, 0); + } + + _http_ws_unlink(web_u); + c3_free(web_u); +} + +/* _http_ws_disconnect(): close websocket connection. +*/ +static void +_http_ws_disconnect(u3_hws* web_u) +{ + if ( 0 == web_u ) { + return; + } + + if ( u3_hws_pending == web_u->sat_e ) { + _http_ws_reject(web_u); + return; + } + + if ( (u3_hws_open == web_u->sat_e) && (0 != web_u->woc_u) ) { + web_u->sat_e = u3_hws_closing; + h2o_websocket_close(web_u->woc_u); + } +} + +/* _http_ws_send_message(): serialize and send websocket message to client. +*/ +static c3_o +_http_ws_send_message(u3_hws* web_u, u3_noun msg) +{ + if ( 0 == web_u || u3_hws_open != web_u->sat_e || 0 == web_u->woc_u ) { + u3z(msg); + return c3n; + } + + u3_noun opcode = u3h(msg); + u3_noun body = u3t(msg); + + c3_w opc_w; + if ( c3n == u3r_safe_word(opcode, &opc_w) ) { + u3l_log("http: bad websocket opcode"); + u3z(msg); + return c3n; + } + + c3_y* buf_y = 0; + size_t len_w = 0; + + if ( u3_nul != body ) { + if ( u3_nul != u3h(body) ) { + u3l_log("http: malformed websocket message body"); + u3z(msg); + return c3n; + } + + u3_noun oct = u3t(body); + c3_d len_d = u3r_chub(0, u3h(oct)); + if ( len_d > SIZE_MAX ) { + u3l_log("http: websocket message too large"); + u3z(msg); + return c3n; + } + len_w = (size_t)len_d; + + if ( 0 != len_w ) { + buf_y = c3_malloc(len_w); + u3r_bytes(0, len_w, buf_y, u3t(oct)); + } + } + + struct wslay_event_msg out = { + .opcode = (uint8_t)(opc_w & 0xFF), + .msg = buf_y, + .msg_length = len_w, + }; + + int sas_i = wslay_event_queue_msg(web_u->woc_u->ws_ctx, &out); + if ( 0 != sas_i ) { + u3l_log("http: websocket queue failed (%d)", sas_i); + if ( buf_y ) { + c3_free(buf_y); + } + u3z(msg); + return c3n; + } + + if ( buf_y ) { + c3_free(buf_y); + } + + h2o_websocket_proceed(web_u->woc_u); + u3z(msg); + return c3y; +} + +/* _http_ws_message_cb(): websocket receive / close callback. +*/ +static void +_http_ws_message_cb(h2o_websocket_conn_t *conn, + const struct wslay_event_on_msg_recv_arg *arg) +{ + u3_hws* web_u = (u3_hws*)conn->data; + + if ( 0 == web_u ) { + return; + } + + if ( NULL == arg ) { + if ( u3_hws_closed != web_u->sat_e ) { + web_u->sat_e = u3_hws_closed; + _http_ws_plan_event(web_u, u3nc(u3i_string("disconnect"), u3_nul)); + } + + _http_ws_unlink(web_u); + c3_free(web_u); + return; + } + + u3_noun payload; + if ( 0 == arg->msg_length ) { + payload = u3_nul; + } + else { + u3_noun octs = u3nc(u3i_chub((c3_d)arg->msg_length), + u3i_bytes(arg->msg_length, (const c3_y*)arg->msg)); + // the unit + payload = u3nc(u3_nul, octs); + } + + u3_noun event = u3nc(u3i_string("message"), + u3nc(u3i_chub((c3_d)arg->opcode), payload)); + + _http_ws_plan_event(web_u, event); +} + +/* _http_cache_respond(): respond with a simple-payload:http +*/ +static void +_http_cache_respond(u3_hreq* req_u, u3_noun nun) +{ + h2o_req_t* rec_u = req_u->rec_u; + u3_httd* htd_u = req_u->hon_u->htp_u->htd_u; + + if ( u3_nul == nun ) { + u3_weak req = _http_rec_to_httq(rec_u); + if ( u3_none == req ) { + if ( (u3C.wag_w & u3o_verbose) ) { + u3l_log("strange %.*s request", (c3_i)rec_u->method.len, + rec_u->method.base); + } + c3_c* msg_c = "bad request"; + h2o_send_error_generic(rec_u, 400, msg_c, msg_c, 0); + } + else { + u3_hreq* req_u = _http_req_prepare(rec_u, _http_req_new); + _http_req_dispatch(req_u, req); + } + } + else if ( u3_none == u3r_at(7, nun) ) { + h2o_send_error_500(rec_u, "Internal Server Error", "scry failed", 0); + } + else { + u3_noun auth, response_header, data; + u3x_qual(u3t(u3t(nun)), &auth, 0, &response_header, &data); + u3_noun status, headers; + u3x_cell(response_header, &status, &headers); + + // check auth + if ( (c3y == auth) + && (c3n == _http_req_is_auth(&htd_u->fig_u, rec_u)) ) + { + h2o_send_error_403(rec_u, "Unauthorized", "unauthorized", 0); + } + else { + req_u->sat_e = u3_rsat_plan; + _http_start_respond(req_u, u3k(status), u3k(headers), u3k(data), c3y); + } + } + u3z(nun); +} + +/* _http_scry_respond(): respond with a simple-payload:http +*/ +static void +_http_scry_respond(u3_hreq* req_u, u3_noun nun) +{ + h2o_req_t* rec_u = req_u->rec_u; + u3_httd* htd_u = req_u->hon_u->htp_u->htd_u; + + if ( u3_nul == nun ) { + u3_weak req = _http_rec_to_httq(rec_u); + if ( u3_none == req ) { + if ( (u3C.wag_w & u3o_verbose) ) { + u3l_log("strange %.*s request", (c3_i)rec_u->method.len, + rec_u->method.base); + } + c3_c* msg_c = "bad request"; + h2o_send_error_generic(rec_u, 400, msg_c, msg_c, 0); + } + else { + h2o_send_error_500(rec_u, "Internal Server Error", "scry failed", 0); + } + } + else if ( u3_none == u3r_at(7, nun) ) { + h2o_send_error_500(rec_u, "Internal Server Error", "scry failed", 0); + } + else { + u3_noun auth, response_header, data; + u3x_qual(u3t(u3t(nun)), &auth, 0, &response_header, &data); + u3_noun status, headers; + u3x_cell(response_header, &status, &headers); + + // check auth + if ( (c3y == auth) + && (c3n == _http_req_is_auth(&htd_u->fig_u, rec_u)) ) + { + h2o_send_error_403(rec_u, "Unauthorized", "unauthorized", 0); + } + else { + req_u->sat_e = u3_rsat_plan; + _http_start_respond(req_u, u3k(status), u3k(headers), u3k(data), c3y); + } + } + u3z(nun); +} + +/* _http_cache_scry_cb(): insert scry result into noun cache +*/ +static void +_http_cache_scry_cb(void* vod_p, u3_noun nun) +{ + u3_preq* peq_u = vod_p; + u3_httd* htd_u = peq_u->htd_u; + u3_hreq* req_u = peq_u->req_u; + + if ( req_u ) { + u3_assert(u3_rsat_peek == req_u->sat_e); + req_u->peq_u = 0; + _http_cache_respond(req_u, u3k(nun)); + } + + u3h_put(htd_u->nax_p, peq_u->pax, nun); + u3z(peq_u->pax); + c3_free(peq_u); +} + +/* _http_req_cache(): attempt to serve http request from cache +*/ +static c3_o +_http_req_cache(u3_hreq* req_u) +{ + u3_assert(u3_rsat_init == req_u->sat_e); + + h2o_iovec_t method = req_u->rec_u->method; + if (0 != strncmp("GET", method.base, method.len)) { + return c3n; + } + + u3_httd* htd_u = req_u->hon_u->htp_u->htd_u; + + u3_noun url = u3dc("scot", 't', _http_vec_to_atom(req_u->rec_u->path)); + u3_weak sac = u3h_get(htd_u->sax_p, url); + u3z(url); + + if ( u3_none == sac ) { + return c3n; + } + + u3_weak nac = u3h_get(htd_u->nax_p, sac); + if ( u3_none == nac ) { + // noun not in cache; scry it + req_u->peq_u = c3_malloc(sizeof(*req_u->peq_u)); + req_u->peq_u->req_u = req_u; + req_u->peq_u->htd_u = htd_u; + req_u->peq_u->pax = u3k(sac); + + req_u->sat_e = u3_rsat_peek; + + u3_noun gang = u3nc(u3_nul, u3_nul); + u3_pier_peek_last(htd_u->car_u.pir_u, gang, c3__ex, + u3_nul, sac, req_u->peq_u, _http_cache_scry_cb); + return c3y; + } + u3z(sac); + _http_cache_respond(req_u, nac); + return c3y; +} + +/* _http_hgen_dispose(): dispose response generator and buffers +*/ +static void +_http_hgen_dispose(void* ptr_v) +{ + u3_hgen* gen_u = (u3_hgen*)ptr_v; + _http_heds_free(gen_u->hed_u); + gen_u->hed_u = 0; + _cttp_bods_free(gen_u->nud_u); + gen_u->nud_u = 0; + _cttp_bods_free(gen_u->bod_u); + gen_u->bod_u = 0; +} + +/* _http_hgen_send(): send (some/more of a) response. +*/ +static void +_http_hgen_send(u3_hgen* gen_u) +{ + u3_hreq* req_u = gen_u->req_u; + h2o_req_t* rec_u = req_u->rec_u; + c3_w len_w; + h2o_iovec_t* vec_u = _cttp_bods_to_vec(gen_u->bod_u, &len_w); + + // not ready again until _proceed + // + u3_assert( c3y == gen_u->red ); + gen_u->red = c3n; + + // stash [bod_u] to free later + // + _cttp_bods_free(gen_u->nud_u); + gen_u->nud_u = gen_u->bod_u; + gen_u->bod_u = 0; + + switch ( gen_u->sat_e ) { + case u3_hgen_wait: { + h2o_send(rec_u, vec_u, len_w, H2O_SEND_STATE_IN_PROGRESS); + uv_timer_start(req_u->tim_u, _http_req_timer_cb, 45 * 1000, 0); + } break; + + case u3_hgen_done: { + // close connection if shutdown pending + // + u3_h2o_serv* h2o_u = req_u->hon_u->htp_u->h2o_u; + + if ( 0 != h2o_u->ctx_u.shutdown_requested ) { + rec_u->http1_is_persistent = 0; + } + + h2o_send(rec_u, vec_u, len_w, H2O_SEND_STATE_FINAL); + } break; + + case u3_hgen_fail: { + h2o_send(rec_u, vec_u, len_w, H2O_SEND_STATE_ERROR); + } break; + } + + c3_free(vec_u); +} + +/* _http_hgen_stop(): h2o is closing an in-progress response. +*/ +static void +_http_hgen_stop(h2o_generator_t* neg_u, h2o_req_t* rec_u) +{ + u3_hgen* gen_u = (u3_hgen*)neg_u; + + // response not complete, enqueue cancel + // + if ( u3_hgen_wait == gen_u->sat_e ) { + _http_req_kill(gen_u->req_u); + } +} + +/* _http_hgen_proceed(): h2o is ready for more response data. +*/ +static void +_http_hgen_proceed(h2o_generator_t* neg_u, h2o_req_t* rec_u) +{ + u3_hgen* gen_u = (u3_hgen*)neg_u; + u3_hreq* req_u = gen_u->req_u; + + // sanity check + u3_assert( rec_u == req_u->rec_u ); + + gen_u->red = c3y; + + if ( gen_u->bod_u || (u3_hgen_wait != gen_u->sat_e) ) { + _http_hgen_send(gen_u); + } +} + +/* _http_start_respond(): write a [%http-response %start ...] to h2o_req_t->res +*/ +static void +_http_start_respond(u3_hreq* req_u, + u3_noun status, + u3_noun headers, + u3_noun data, + u3_noun complete) +{ + if ( u3_rsat_plan != req_u->sat_e ) { + u3l_log("http: %%start not sane"); + u3z(status); u3z(headers); u3z(data); u3z(complete); + return; + } + + req_u->sat_e = u3_rsat_ripe; + + uv_timer_stop(req_u->tim_u); + + h2o_req_t* rec_u = req_u->rec_u; + + rec_u->res.status = status; + rec_u->res.reason = (status < 200) ? "weird" : + (status < 300) ? "ok" : + (status < 400) ? "moved" : + (status < 500) ? "missing" : + "hosed"; + + u3_hhed* hed_u = _http_heds_from_noun(u3k(headers)); + u3_hhed* deh_u = hed_u; + + c3_i has_len_i = 0; + + while ( 0 != hed_u ) { + if ( 0x200 <= rec_u->version ) { + h2o_strtolower(hed_u->nam_c, hed_u->nam_w); + + if ( 0 == strncmp(hed_u->nam_c, "connection", 10) ) { + hed_u = hed_u->nex_u; + continue; + } + } + if ( 0 == strncmp(hed_u->nam_c, "content-length", 14) ) { + has_len_i = 1; + } + else { + h2o_add_header_by_str(&rec_u->pool, &rec_u->res.headers, + hed_u->nam_c, hed_u->nam_w, 0, 0, + hed_u->val_c, hed_u->val_w); + } + + hed_u = hed_u->nex_u; + } + + u3_hgen* gen_u = h2o_mem_alloc_shared(&rec_u->pool, sizeof(*gen_u), + _http_hgen_dispose); + gen_u->neg_u = (h2o_generator_t){ _http_hgen_proceed, _http_hgen_stop }; + gen_u->red = c3y; + gen_u->sat_e = ( c3y == complete ) ? u3_hgen_done : u3_hgen_wait; + gen_u->bod_u = ( u3_nul == data ) ? + 0 : _cttp_bod_from_octs(u3k(u3t(data))); + gen_u->nud_u = 0; + gen_u->hed_u = deh_u; + gen_u->req_u = req_u; + + // if we don't explicitly set this field, h2o will send with + // transfer-encoding: chunked + // + if ( 1 == has_len_i ) { + rec_u->res.content_length = ( 0 == gen_u->bod_u ) ? + 0 : gen_u->bod_u->len_w; + } + + req_u->gen_u = gen_u; + + h2o_start_response(rec_u, &gen_u->neg_u); + + _http_hgen_send(gen_u); + + u3z(status); u3z(headers); u3z(data); u3z(complete); +} + +/* _http_continue_respond(): apply [%http-response %continue ...]. +*/ +static void +_http_continue_respond(u3_hreq* req_u, + u3_noun data, + u3_noun complete) +{ + if ( u3_rsat_ripe != req_u->sat_e ) { + u3l_log("http: %%continue before %%start"); + u3z(data); u3z(complete); + return; + } + + u3_hgen* gen_u = req_u->gen_u; + + uv_timer_stop(req_u->tim_u); + + gen_u->sat_e = ( c3y == complete ) ? u3_hgen_done : u3_hgen_wait; + + if ( u3_nul != data ) { + u3_hbod* bod_u = _cttp_bod_from_octs(u3k(u3t(data))); + + if ( 0 == gen_u->bod_u ) { + gen_u->bod_u = bod_u; + } + else { + u3_hbod* pre_u = gen_u->bod_u; + + while ( 0 != pre_u->nex_u ) { + pre_u = pre_u->nex_u; + } + + pre_u->nex_u = bod_u; + } + } + + if ( c3y == gen_u->red ) { + _http_hgen_send(gen_u); + } + + u3z(data); u3z(complete); +} + +/* _http_cancel_respond(): apply [%http-response %cancel ~]. +*/ +static void +_http_cancel_respond(u3_hreq* req_u) +{ + switch ( req_u->sat_e ) { + case u3_rsat_init: u3_assert(0); + case u3_rsat_peek: u3_assert(0); + + case u3_rsat_plan: { + req_u->sat_e = u3_rsat_ripe; // XX confirm + + c3_c* msg_c = "hosed"; + h2o_send_error_generic(req_u->rec_u, 500, msg_c, msg_c, 0); + + if ( 0 != req_u->wsu_u ) { + _http_ws_detach_request(req_u); + } + } break; + + case u3_rsat_ripe: { + u3_hgen* gen_u = req_u->gen_u; + + uv_timer_stop(req_u->tim_u); + gen_u->sat_e = u3_hgen_fail; + + if ( c3y == gen_u->red ) { + _http_hgen_send(gen_u); + } + } + } +} + +/* _http_rec_to_httq(): convert h2o_req_t to httq +*/ +static u3_weak +_http_rec_to_httq(h2o_req_t* rec_u) +{ + u3_noun med = _http_vec_to_meth(rec_u->method); + + if ( u3_none == med ) { + return u3_none; + } + + u3_noun url = _http_vec_to_atom(rec_u->path); + u3_noun hed = _http_heds_to_noun(rec_u->headers.entries, + rec_u->headers.size); + + // restore host header + hed = u3nc(u3nc(u3i_string("host"), + _http_vec_to_atom(rec_u->authority)), + hed); + + u3_noun bod = _http_vec_to_octs(rec_u->entity); + + return u3nq(med, url, hed, bod); +} + +typedef struct _h2o_uv_sock { // see private st_h2o_uv_socket_t + h2o_socket_t sok_u; // socket + uv_stream_t* han_u; // client stream handler (u3_hcon) +} h2o_uv_sock; + +/* _http_rec_sock(): u3 http connection from h2o request; hacky. +*/ +static u3_hcon* +_http_rec_sock(h2o_req_t* rec_u) +{ + h2o_uv_sock* suv_u = (h2o_uv_sock*)rec_u->conn-> + callbacks->get_socket(rec_u->conn); + u3_hcon* hon_u = (u3_hcon*)suv_u->han_u; + + // sanity check + // + u3_assert( hon_u->sok_u == &suv_u->sok_u ); + + return hon_u; +} + +/* _http_req_prepare(): creates u3 req from h2o req and initializes its timer +*/ +static u3_hreq* +_http_req_prepare(h2o_req_t* rec_u, + u3_hreq* (*new_f)(u3_hcon*, h2o_req_t*)) +{ + u3_hcon* hon_u = _http_rec_sock(rec_u); + u3_hreq* seq_u = new_f(hon_u, rec_u); + + seq_u->tim_u = c3_malloc(sizeof(*seq_u->tim_u)); + seq_u->tim_u->data = seq_u; + uv_timer_init(u3L, seq_u->tim_u); + uv_timer_start(seq_u->tim_u, _http_req_timer_cb, 600 * 1000, 0); + + return seq_u; +} + +/* _http_seq_accept(): handle incoming http request on slogstream endpoint +*/ +static c3_i +_http_seq_accept(h2o_handler_t* han_u, h2o_req_t* rec_u) +{ + u3_hcon* hon_u = _http_rec_sock(rec_u); + c3_o aut_o = _http_req_is_auth(&hon_u->htp_u->htd_u->fig_u, rec_u); + + // if the request is not authenticated, reject it + // + if ( c3n == aut_o ) { + u3_hreq* req_u = _http_req_prepare(rec_u, _http_req_new); + req_u->sat_e = u3_rsat_plan; + _http_start_respond(req_u, 403, u3_nul, u3_nul, c3y); + } + // if it is authenticated, send slogstream/sse headers + // + else { + u3_hreq* req_u = _http_req_prepare(rec_u, _http_seq_new); + u3_noun hed = u3nl(u3nc(u3i_string("Content-Type"), + u3i_string("text/event-stream")), + u3nc(u3i_string("Cache-Control"), + u3i_string("no-cache")), + u3nc(u3i_string("Connection"), + u3i_string("keep-alive")), + u3_none); + + _http_start_respond(req_u, 200, hed, u3_nul, c3n); + + //TODO auth token may expire at some point. if we want to close the + // slogstream when that happens, we need to store the token that + // was used alongside it... + } + + return 0; +} + +/* _http_sat_accept(): handle incoming http request on status endpoint +*/ +static c3_i +_http_sat_accept(h2o_handler_t* han_u, h2o_req_t* rec_u) +{ + c3_o bus_o; + { + u3_hcon* hon_u = _http_rec_sock(rec_u); + u3_httd* htd_u = hon_u->htp_u->htd_u; + u3_pier* pir_u = htd_u->car_u.pir_u; + bus_o = pir_u->god_u->pin_o; + } + + if ( c3y == bus_o ) { + rec_u->res.status = 429; + rec_u->res.reason = "busy"; + } + else { + rec_u->res.status = 204; + rec_u->res.reason = "no content"; + } + + rec_u->res.content_length = 0; + h2o_send_inline(rec_u, NULL, 0); + + return 0; +} + +/* _http_rec_accept(); handle incoming http request from h2o. +*/ +static c3_i +_http_rec_accept(h2o_handler_t* han_u, h2o_req_t* rec_u) +{ + u3_weak req = _http_rec_to_httq(rec_u); + + if ( u3_none == req ) { + if ( (u3C.wag_w & u3o_verbose) ) { + u3l_log("strange %.*s request", (c3_i)rec_u->method.len, + rec_u->method.base); + } + c3_c* msg_c = "bad request"; + h2o_send_error_generic(rec_u, 400, msg_c, msg_c, 0); + } + else { + u3_hreq* req_u = _http_req_prepare(rec_u, _http_req_new); + const c3_c* client_key = 0; + c3_i ws_rc = h2o_is_websocket_handshake(rec_u, &client_key); + + if ( (0 == ws_rc) && (0 != client_key) ) { + _http_ws_handshake(req_u, req, client_key); + } + else { + if ( ws_rc < 0 ) { + if ( 0 != req_u->tim_u ) { + uv_timer_stop(req_u->tim_u); + } + req_u->sat_e = u3_rsat_ripe; + u3l_log("http: invalid websocket handshake"); + c3_c* msg_c = "bad websocket handshake"; + h2o_send_error_generic(rec_u, 400, msg_c, msg_c, 0); + u3z(req); + } + else if ( c3n == _http_req_cache(req_u) ) { + _http_req_dispatch(req_u, req); + } + else { + u3z(req); + } + } + } + + return 0; +} + +/* _http_conn_find(): find http connection in server by sequence. +*/ +static u3_hcon* +_http_conn_find(u3_http *htp_u, c3_w coq_l) +{ + u3_hcon* hon_u = htp_u->hon_u; + + // XX glories of linear search + // + while ( hon_u ) { + if ( coq_l == hon_u->coq_l ) { + return hon_u; + } + hon_u = hon_u->nex_u; + } + return 0; +} + +/* _http_conn_link(): link http request to connection +*/ +static void +_http_conn_link(u3_http* htp_u, u3_hcon* hon_u) +{ + hon_u->htp_u = htp_u; + hon_u->coq_l = htp_u->coq_l++; + hon_u->nex_u = htp_u->hon_u; + + if ( 0 != hon_u->nex_u ) { + hon_u->nex_u->pre_u = hon_u; + } + htp_u->hon_u = hon_u; +} + +/* _http_conn_unlink(): remove http request from connection +*/ +static void +_http_conn_unlink(u3_hcon* hon_u) +{ + if ( 0 != hon_u->pre_u ) { + hon_u->pre_u->nex_u = hon_u->nex_u; + + if ( 0 != hon_u->nex_u ) { + hon_u->nex_u->pre_u = hon_u->pre_u; + } + } + else { + hon_u->htp_u->hon_u = hon_u->nex_u; + + if ( 0 != hon_u->nex_u ) { + hon_u->nex_u->pre_u = 0; + } + } +} + +/* _http_conn_free(): free http connection on close. +*/ +static void +_http_conn_free(uv_handle_t* han_t) +{ + u3_hcon* hon_u = (u3_hcon*)han_t; + u3_http* htp_u = hon_u->htp_u; + u3_h2o_serv* h2o_u = htp_u->h2o_u; + + u3_assert( 0 == hon_u->req_u ); + +#if 0 + { + c3_w len_w = 0; + + u3_hcon* noh_u = htp_u->hon_u; + + while ( 0 != noh_u ) { + len_w++; + noh_u = noh_u->nex_u; + } + + u3l_log("http conn free %d of %u server %d", hon_u->coq_l, len_w, htp_u->sev_l); + } +#endif + + _http_conn_unlink(hon_u); + +#if 0 + { + c3_w len_w = 0; + + u3_hcon* noh_u = htp_u->hon_u; + + while ( 0 != noh_u ) { + len_w++; + noh_u = noh_u->nex_u; + } + + u3l_log("http conn free %u remaining", len_w); + } +#endif + + if ( (0 == htp_u->hon_u) && (0 != h2o_u->ctx_u.shutdown_requested) ) { +#if 0 + u3l_log("http conn free %d free server %d", hon_u->coq_l, htp_u->sev_l); +#endif + _http_serv_free(htp_u); + } + + c3_free(hon_u); +} + +/* _http_conn_new(): create and accept http connection. +*/ +static u3_hcon* +_http_conn_new(u3_http* htp_u) +{ + u3_hcon* hon_u = c3_malloc(sizeof(*hon_u)); + hon_u->seq_l = 1; + hon_u->ipf_w = 0; + hon_u->req_u = 0; + hon_u->sok_u = 0; + hon_u->con_u = 0; + hon_u->pre_u = 0; + + _http_conn_link(htp_u, hon_u); + +#if 0 + u3l_log("http conn neww %d server %d", hon_u->coq_l, htp_u->sev_l); +#endif + + return hon_u; +} + +/* _http_serv_find(): find http server by sequence. +*/ +static u3_http* +_http_serv_find(u3_httd* htd_u, c3_l sev_l) +{ + u3_http* htp_u = htd_u->htp_u; + + // XX glories of linear search + // + while ( htp_u ) { + if ( sev_l == htp_u->sev_l ) { + return htp_u; + } + htp_u = htp_u->nex_u; + } + return 0; +} + +/* _http_serv_link(): link http server to global state. +*/ +static void +_http_serv_link(u3_httd* htd_u, u3_http* htp_u) +{ + // XX link elsewhere initially, relink on start? + + if ( 0 != htd_u->htp_u ) { + htp_u->sev_l = 1 + htd_u->htp_u->sev_l; + } + else { + htp_u->sev_l = htd_u->sev_l; + } + + htp_u->nex_u = htd_u->htp_u; + htp_u->htd_u = htd_u; + htd_u->htp_u = htp_u; +} + +/* _http_serv_unlink(): remove http server from global state. +*/ +static void +_http_serv_unlink(u3_http* htp_u) +{ + // XX link elsewhere initially, relink on start? +#if 0 + u3l_log("http serv unlink %d", htp_u->sev_l); +#endif + u3_http* pre_u = htp_u->htd_u->htp_u; + + if ( pre_u == htp_u ) { + pre_u = htp_u->nex_u; + } + else { + // XX glories of linear search + // + while ( pre_u ) { + if ( pre_u->nex_u == htp_u ) { + pre_u->nex_u = htp_u->nex_u; + } + else pre_u = pre_u->nex_u; + } + } +} + +/* _http_h2o_context_dispose(): h2o_context_dispose, inlined and cleaned up. +*/ +static void +_http_h2o_context_dispose(h2o_context_t* ctx) +{ + h2o_globalconf_t *config = ctx->globalconf; + size_t i, j; + + for (i = 0; config->hosts[i] != NULL; ++i) { + h2o_hostconf_t *hostconf = config->hosts[i]; + for (j = 0; j != hostconf->paths.size; ++j) { + h2o_pathconf_t *pathconf = hostconf->paths.entries + j; + h2o_context_dispose_pathconf_context(ctx, pathconf); + } + h2o_context_dispose_pathconf_context(ctx, &hostconf->fallback_path); + } + + c3_free(ctx->_pathconfs_inited.entries); + c3_free(ctx->_module_configs); + + h2o_timeout_dispose(ctx->loop, &ctx->zero_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->hundred_ms_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->handshake_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->http1.req_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->http2.idle_timeout); + + // NOTE: linked in http2/connection, never unlinked + h2o_timeout_unlink(&ctx->http2._graceful_shutdown_timeout); + + h2o_timeout_dispose(ctx->loop, &ctx->http2.graceful_shutdown_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->proxy.io_timeout); + h2o_timeout_dispose(ctx->loop, &ctx->one_sec_timeout); + + h2o_filecache_destroy(ctx->filecache); + ctx->filecache = NULL; + + /* clear storage */ + for (i = 0; i != ctx->storage.size; ++i) { + h2o_context_storage_item_t *item = ctx->storage.entries + i; + if (item->dispose != NULL) { + item->dispose(item->data); + } + } + + c3_free(ctx->storage.entries); + + h2o_multithread_unregister_receiver(ctx->queue, &ctx->receivers.hostinfo_getaddr); + h2o_multithread_destroy_queue(ctx->queue); + + if (ctx->_timestamp_cache.value != NULL) { + h2o_mem_release_shared(ctx->_timestamp_cache.value); + } + + // NOTE: explicit uv_run removed +} + +/* _http_serv_really_free(): free http server. +*/ +static void +_http_serv_really_free(u3_http* htp_u) +{ + u3_assert( 0 == htp_u->hon_u ); + + if ( 0 != htp_u->h2o_u ) { + u3_h2o_serv* h2o_u = htp_u->h2o_u; + + if ( 0 != h2o_u->cep_u.ssl_ctx ) { + SSL_CTX_free(h2o_u->cep_u.ssl_ctx); + } + + h2o_config_dispose(&h2o_u->fig_u); + + // XX h2o_cleanup_thread if not restarting? + + c3_free(htp_u->h2o_u); + htp_u->h2o_u = 0; + } + + _http_serv_unlink(htp_u); + c3_free(htp_u); +} + +/* http_serv_free_cb(): timer callback for freeing http server. +*/ +static void +http_serv_free_cb(uv_timer_t* tim_u) +{ + u3_http* htp_u = tim_u->data; + +#if 0 + u3l_log("http serv free cb %d", htp_u->sev_l); +#endif + + _http_serv_really_free(htp_u); + + uv_close((uv_handle_t*)tim_u, _http_close_cb); +} + +/* _http_serv_free(): begin to free http server. +*/ +static void +_http_serv_free(u3_http* htp_u) +{ +#if 0 + u3l_log("http serv free %d", htp_u->sev_l); +#endif + + u3_assert( 0 == htp_u->hon_u ); + + if ( 0 == htp_u->h2o_u ) { + _http_serv_really_free(htp_u); + } + else { + u3_h2o_serv* h2o_u = htp_u->h2o_u; + + _http_h2o_context_dispose(&h2o_u->ctx_u); + + // NOTE: free deferred to allow timers to be closed + // this is a heavy-handed workaround for the lack of + // close callbacks in h2o_timer_t + // it's unpredictable how many event-loop turns will + // be required to finish closing the underlying uv_timer_t + // and we can't free until that's done (or we have UB) + // testing reveals 5s to be a long enough deferral + uv_timer_t* tim_u = c3_malloc(sizeof(*tim_u)); + + tim_u->data = htp_u; + + uv_timer_init(u3L, tim_u); + uv_timer_start(tim_u, http_serv_free_cb, 5000, 0); + } +} + +/* _http_serv_close_cb(): http server uv_close callback. +*/ +static void +_http_serv_close_cb(uv_handle_t* han_u) +{ + u3_http* htp_u = (u3_http*)han_u; + u3_httd* htd_u = htp_u->htd_u; + htp_u->liv = c3n; + + // otherwise freed by the last linked connection + if ( 0 == htp_u->hon_u ) { + _http_serv_free(htp_u); + } + + // restart if all linked servers have been shutdown + { + htp_u = htd_u->htp_u; + c3_o res = c3y; + + while ( 0 != htp_u ) { + if ( c3y == htp_u->liv ) { + res = c3n; + } + htp_u = htp_u->nex_u; + } + + if ( (c3y == res) && (0 != htd_u->fig_u.for_u) ) { + _http_serv_start_all(htd_u); + } + } +} + +/* _http_serv_close(): close http server gracefully. +*/ +static void +_http_serv_close(u3_http* htp_u) +{ + u3_h2o_serv* h2o_u = htp_u->h2o_u; + h2o_context_request_shutdown(&h2o_u->ctx_u); + +#if 0 + u3l_log("http serv close %d %p", htp_u->sev_l, &htp_u->wax_u); +#endif + + uv_close((uv_handle_t*)&htp_u->wax_u, _http_serv_close_cb); +} + +/* _http_serv_new(): create new http server. +*/ +static u3_http* +_http_serv_new(u3_httd* htd_u, c3_s por_s, c3_o dis, c3_o sec, c3_o lop) +{ + u3_http* htp_u = c3_malloc(sizeof(*htp_u)); + + htp_u->coq_l = 1; + htp_u->por_s = por_s; + htp_u->dis = dis; + htp_u->sec = sec; + htp_u->lop = lop; + htp_u->liv = c3y; + htp_u->h2o_u = 0; + htp_u->hon_u = 0; + htp_u->nex_u = 0; + + _http_serv_link(htd_u, htp_u); + + return htp_u; +} + +/* _http_serv_accept(): accept new http connection. +*/ +static void +_http_serv_accept(u3_http* htp_u) +{ + u3_hcon* hon_u = _http_conn_new(htp_u); + + uv_tcp_init(u3L, &hon_u->wax_u); + + c3_i sas_i; + + if ( 0 != (sas_i = uv_accept((uv_stream_t*)&htp_u->wax_u, + (uv_stream_t*)&hon_u->wax_u)) ) { + if ( (u3C.wag_w & u3o_verbose) ) { + u3l_log("http: accept: %s", uv_strerror(sas_i)); + } + + uv_close((uv_handle_t*)&hon_u->wax_u, _http_conn_free); + return; + } + + hon_u->sok_u = h2o_uv_socket_create((uv_stream_t*)&hon_u->wax_u, + _http_conn_free); + + h2o_accept(&((u3_h2o_serv*)htp_u->h2o_u)->cep_u, hon_u->sok_u); + + // capture h2o connection (XX fragile) + hon_u->con_u = (h2o_conn_t*)hon_u->sok_u->data; + + struct sockaddr_in adr_u; + h2o_socket_getpeername(hon_u->sok_u, (struct sockaddr*)&adr_u); + hon_u->ipf_w = ( adr_u.sin_family != AF_INET ) ? + 0 : ntohl(adr_u.sin_addr.s_addr); +} + +/* _http_serv_listen_cb(): uv_connection_cb for uv_listen +*/ +static void +_http_serv_listen_cb(uv_stream_t* str_u, c3_i sas_i) +{ + u3_http* htp_u = (u3_http*)str_u; + + if ( 0 != sas_i ) { + u3l_log("http: listen_cb: %s", uv_strerror(sas_i)); + } + else { + _http_serv_accept(htp_u); + } +} + +/* _http_serv_init_h2o(): initialize h2o ctx and handlers for server. +*/ +static u3_h2o_serv* +_http_serv_init_h2o(SSL_CTX* tls_u, c3_o log, c3_o red) +{ + u3_h2o_serv* h2o_u = c3_calloc(sizeof(*h2o_u)); + + h2o_config_init(&h2o_u->fig_u); + h2o_u->fig_u.server_name = h2o_iovec_init( + H2O_STRLIT("urbit/vere-" URBIT_VERSION)); + + // set maximum request size to 512 MiB + // + h2o_u->fig_u.max_request_entity_size = 512 * 1024 * 1024; + + // XX default pending vhost/custom-domain design + // XX revisit the effect of specifying the port + h2o_u->hos_u = h2o_config_register_host(&h2o_u->fig_u, + h2o_iovec_init(H2O_STRLIT("default")), + 65535); + + h2o_u->cep_u.ctx = (h2o_context_t*)&h2o_u->ctx_u; + h2o_u->cep_u.hosts = h2o_u->fig_u.hosts; + h2o_u->cep_u.ssl_ctx = tls_u; + + h2o_u->han_u = h2o_create_handler(&h2o_u->hos_u->fallback_path, + sizeof(*h2o_u->han_u)); + if ( c3y == red ) { + // XX h2o_redirect_register + h2o_u->han_u->on_req = _http_rec_accept; + } + else { + h2o_u->han_u->on_req = _http_rec_accept; + } + + // register runtime endpoints + // + { + h2o_pathconf_t* pac_u; + h2o_handler_t* han_u; + + // slog stream + // + pac_u = h2o_config_register_path(h2o_u->hos_u, "/~_~/slog", 0); + han_u = h2o_create_handler(pac_u, sizeof(*han_u)); + han_u->on_req = _http_seq_accept; + + // status (per spinner) + // + pac_u = h2o_config_register_path(h2o_u->hos_u, "/~_~/healthz", 0); + han_u = h2o_create_handler(pac_u, sizeof(*han_u)); + han_u->on_req = _http_sat_accept; + } + + if ( c3y == log ) { + // XX move this to post serv_start and put the port in the name +#if 0 + c3_c* pax_c = u3_Host.dir_c; + u3_noun now = u3dc("scot", c3__da, u3k(u3A->now)); + c3_c* now_c = u3r_string(now); + c3_c* nam_c = ".access.log"; + c3_w len_w = 1 + strlen(pax_c) + 1 + strlen(now_c) + strlen(nam_c); + + c3_c* paf_c = c3_malloc(len_w); + snprintf(paf_c, len_w, "%s/%s%s", pax_c, now_c, nam_c); + + h2o_access_log_filehandle_t* fil_u = + h2o_access_log_open_handle(paf_c, 0, H2O_LOGCONF_ESCAPE_APACHE); + + h2o_access_log_register(&h2o_u->hos_u->fallback_path, fil_u); + + c3_free(paf_c); + c3_free(now_c); + u3z(now); +#endif + } + + // XX h2o_compress_register + + h2o_context_init(&h2o_u->ctx_u, u3L, &h2o_u->fig_u); + + return h2o_u; +} + +/* _http_serv_start(): start http server. +*/ +static void +_http_serv_start(u3_http* htp_u) +{ + u3_pier* pir_u = htp_u->htd_u->car_u.pir_u; + struct sockaddr_in adr_u; + + memset(&adr_u, 0, sizeof(adr_u)); + adr_u.sin_family = AF_INET; + adr_u.sin_addr.s_addr = ( c3y == htp_u->lop ) ? + htonl(INADDR_LOOPBACK) : + INADDR_ANY; + + if ( 0 != u3_Host.ops_u.bin_c && c3n == htp_u->lop ) { + inet_pton(AF_INET, u3_Host.ops_u.bin_c, &adr_u.sin_addr); + } + + uv_tcp_init(u3L, &htp_u->wax_u); + + /* Try ascending ports. + */ + while ( 1 ) { + c3_i sas_i; + + adr_u.sin_port = htons(htp_u->por_s); + + if ( 0 != (sas_i = uv_tcp_bind(&htp_u->wax_u, + (const struct sockaddr*)&adr_u, 0)) || + 0 != (sas_i = uv_listen((uv_stream_t*)&htp_u->wax_u, + TCP_BACKLOG, _http_serv_listen_cb)) ) { + if ( UV_EADDRNOTAVAIL == sas_i ) { + u3l_log("http: ip address not available"); + u3_king_bail(); + } + if ( c3y == htp_u->dis ) { + u3l_log("http: listen (%" PRIu16 "): %s", htp_u->por_s, + uv_strerror(sas_i)); + u3_king_bail(); + } + if ( (UV_EADDRINUSE == sas_i) || (UV_EACCES == sas_i) ) { + if ( (c3y == htp_u->sec) && (443 == htp_u->por_s) ) { + htp_u->por_s = 8443; + } + else if ( (c3n == htp_u->sec) && (80 == htp_u->por_s) ) { + htp_u->por_s = 8080; + } + else { + htp_u->por_s++; + // XX + // + if ( c3n == htp_u->lop ) { + if ( c3y == htp_u->sec ) { + pir_u->pes_s = htp_u->por_s; + } + else { + pir_u->per_s = htp_u->por_s; + } + } + } + + continue; + } + + u3l_log("http: listen: %s", uv_strerror(sas_i)); + + _http_serv_free(htp_u); + return; + } + + u3l_log("http: %s live on %s://localhost:%d", + (c3y == htp_u->lop) ? "loopback" : "web interface", + (c3y == htp_u->sec) ? "https" : "http", + htp_u->por_s); + + break; + } +} + +static uv_buf_t +_http_wain_to_buf(u3_noun wan) +{ + c3_w len_w = u3_mcut_path(0, 0, (c3_c)10, u3k(wan)); + c3_c* buf_c = c3_malloc(1 + len_w); + + u3_mcut_path(buf_c, 0, (c3_c)10, wan); + buf_c[len_w] = 0; + + return uv_buf_init(buf_c, len_w); +} + +/* _http_init_tls: initialize OpenSSL context +*/ +static SSL_CTX* +_http_init_tls(uv_buf_t key_u, uv_buf_t cer_u) +{ + // XX require 1.1.0 and use TLS_server_method() + SSL_CTX* tls_u = SSL_CTX_new(SSLv23_server_method()); + // XX use SSL_CTX_set_max_proto_version() and SSL_CTX_set_min_proto_version() + SSL_CTX_set_options(tls_u, SSL_OP_NO_SSLv2 | + SSL_OP_NO_SSLv3 | + // SSL_OP_NO_TLSv1 | // XX test + SSL_OP_NO_COMPRESSION); + + SSL_CTX_set_default_verify_paths(tls_u); + SSL_CTX_set_session_cache_mode(tls_u, SSL_SESS_CACHE_OFF); + SSL_CTX_set_cipher_list(tls_u, + "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:" + "ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:" + "RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS"); + + // enable ALPN for HTTP 2 support +#if H2O_USE_ALPN + { + SSL_CTX_set_ecdh_auto(tls_u, 1); + h2o_ssl_register_alpn_protocols(tls_u, h2o_http2_alpn_protocols); + } +#endif + + { + BIO* bio_u = BIO_new_mem_buf(key_u.base, key_u.len); + EVP_PKEY* pky_u = PEM_read_bio_PrivateKey(bio_u, 0, 0, 0); + c3_i sas_i = SSL_CTX_use_PrivateKey(tls_u, pky_u); + + EVP_PKEY_free(pky_u); + BIO_free(bio_u); + + if( 0 == sas_i ) { + u3l_log("http: load private key failed:"); + FILE* fil_u = u3_term_io_hija(); + ERR_print_errors_fp(fil_u); + u3_term_io_loja(1, fil_u); + + SSL_CTX_free(tls_u); + + return 0; + } + } + + { + BIO* bio_u = BIO_new_mem_buf(cer_u.base, cer_u.len); + X509* xer_u = PEM_read_bio_X509_AUX(bio_u, 0, 0, 0); + c3_i sas_i = SSL_CTX_use_certificate(tls_u, xer_u); + + X509_free(xer_u); + + if( 0 == sas_i ) { + u3l_log("http: load certificate failed:"); + FILE* fil_u = u3_term_io_hija(); + ERR_print_errors_fp(fil_u); + u3_term_io_loja(1,fil_u); + + BIO_free(bio_u); + SSL_CTX_free(tls_u); + + return 0; + } + + // get any additional CA certs, ignoring errors + while ( 0 != (xer_u = PEM_read_bio_X509(bio_u, 0, 0, 0)) ) { + // XX require 1.0.2 or newer and use SSL_CTX_add0_chain_cert + SSL_CTX_add_extra_chain_cert(tls_u, xer_u); + } + + BIO_free(bio_u); + } + + return tls_u; +} + +/* _http_write_ports_file(): update .http.ports +*/ +static void +_http_write_ports_file(u3_httd* htd_u, c3_c *pax_c) +{ + c3_c* nam_c = ".http.ports"; + c3_w len_w = 1 + strlen(pax_c) + 1 + strlen(nam_c); + + c3_c* paf_c = c3_malloc(len_w); + snprintf(paf_c, len_w, "%s/%s", pax_c, nam_c); + + c3_i por_i = c3_open(paf_c, O_WRONLY | O_CREAT | O_TRUNC, 0666); + c3_free(paf_c); + + u3_http* htp_u = htd_u->htp_u; + u3_pier* pir_u = htd_u->car_u.pir_u; + + c3_c temp[32]; + while ( 0 != htp_u ) { + if ( 0 < htp_u->por_s ) { + u3_write_fd(por_i, temp, snprintf(temp, 32, "%u %s %s\n", htp_u->por_s, + (c3y == htp_u->sec) ? "secure" : "insecure", + (c3y == htp_u->lop) ? "loopback" : "public")); + } + + htp_u = htp_u->nex_u; + } + + c3_sync(por_i); + close(por_i); +} + +/* _http_release_ports_file(): remove .http.ports +*/ +static void +_http_release_ports_file(c3_c *pax_c) +{ + c3_c* nam_c = ".http.ports"; + c3_w len_w = 1 + strlen(pax_c) + 1 + strlen(nam_c); + c3_c* paf_c = c3_malloc(len_w); + c3_i wit_i; + + wit_i = snprintf(paf_c, len_w, "%s/%s", pax_c, nam_c); + u3_assert(wit_i > 0); + u3_assert(len_w == (c3_w)wit_i + 1); + + c3_unlink(paf_c); + c3_free(paf_c); +} + +static u3_hreq* +_http_search_req(u3_httd* htd_u, + c3_l sev_l, + c3_l coq_l, + c3_l seq_l) +{ + u3_http* htp_u; + u3_hcon* hon_u; + u3_hreq* req_u; + c3_w bug_w = u3C.wag_w & u3o_verbose; + + if ( !(htp_u = _http_serv_find(htd_u, sev_l)) ) { + if ( bug_w ) { + u3l_log("http: server not found: %x", sev_l); + } + return 0; + } + else if ( !(hon_u = _http_conn_find(htp_u, coq_l)) ) { + if ( bug_w ) { + u3l_log("http: connection not found: %x/%d", sev_l, coq_l); + } + return 0; + } + else if ( !(req_u = _http_req_find(hon_u, seq_l)) ) { + if ( bug_w ) { + u3l_log("http: request not found: %x/%d/%d", + sev_l, coq_l, seq_l); + } + return 0; + } + + return req_u; +} + +/* _http_serv_start_all(): initialize and start servers based on saved config. +*/ +static void +_http_serv_start_all(u3_httd* htd_u) +{ + u3_http* htp_u; + u3_pier* pir_u = htd_u->car_u.pir_u; + c3_s por_s; + u3_noun sec = u3_nul; + u3_noun non = u3_none; + u3_noun dis; + u3_form* for_u = htd_u->fig_u.for_u; + + u3_assert( 0 != for_u ); + + // if the SSL_CTX existed, it'll be freed with the servers + htd_u->tls_u = 0; + + // HTTPS server. + if ( (0 != for_u->key_u.base) && (0 != for_u->cer_u.base) ) { + htd_u->tls_u = _http_init_tls(for_u->key_u, for_u->cer_u); + + // Note: if tls_u is used for additional servers, + // its reference count must be incremented with SSL_CTX_up_ref + + if ( 0 != htd_u->tls_u ) { + if ( 0 == pir_u->pes_s ) { + por_s = ( c3y == for_u->pro ) ? 8443 : 443; + dis = c3n; + } + else { + por_s = pir_u->pes_s; + dis = c3y; + } + htp_u = _http_serv_new(htd_u, por_s, dis, c3y, c3n); + htp_u->h2o_u = _http_serv_init_h2o(htd_u->tls_u, for_u->log, for_u->red); + + _http_serv_start(htp_u); + sec = u3nc(u3_nul, htp_u->por_s); + } + } + + // HTTP server. + { + if ( 0 == pir_u->per_s ) { + por_s = ( c3y == for_u->pro ) ? 8080 : 80; + dis = c3n; + } + else { + por_s = pir_u->per_s; + dis = c3y; + } + htp_u = _http_serv_new(htd_u, por_s, dis, c3n, c3n); + htp_u->h2o_u = _http_serv_init_h2o(0, for_u->log, for_u->red); + + _http_serv_start(htp_u); + non = htp_u->por_s; + } + + // Loopback server. + { + por_s = 12321; + htp_u = _http_serv_new(htd_u, por_s, c3n, c3n, c3y); + htp_u->h2o_u = _http_serv_init_h2o(0, for_u->log, for_u->red); + + _http_serv_start(htp_u); + } + + // send listening ports to %eyre + { + u3_assert( u3_none != non ); + + // XX remove [sen] + // + u3_noun wir = u3nt(u3i_string("http-server"), + u3dc("scot", c3__uv, htd_u->sev_l), + u3_nul); + u3_noun cad = u3nt(c3__live, non, sec); + + u3_auto_plan(&htd_u->car_u, u3_ovum_init(0, c3__e, wir, cad)); + } + + _http_write_ports_file(htd_u, u3_Host.dir_c); + _http_form_free(htd_u); +} + +/* _http_serv_restart(): gracefully shutdown, then start servers. +*/ +static void +_http_serv_restart(u3_httd* htd_u) +{ + u3_http* htp_u = htd_u->htp_u; + + if ( 0 == htp_u ) { + _http_serv_start_all(htd_u); + } + else { + u3l_log("http: restarting servers to apply configuration"); + + while ( 0 != htp_u ) { + if ( c3y == htp_u->liv ) { + _http_serv_close(htp_u); + } + htp_u = htp_u->nex_u; + } + + _http_release_ports_file(u3_Host.dir_c); + } +} + +/* _http_form_free(): free and unlink saved config. +*/ +static void +_http_form_free(u3_httd* htd_u) +{ + u3_form* for_u = htd_u->fig_u.for_u; + + if ( 0 == for_u ) { + return; + } + + _http_ws_close_all(htd_u); + + if ( 0 != for_u->key_u.base ) { + c3_free(for_u->key_u.base); + } + + if ( 0 != for_u->cer_u.base ) { + c3_free(for_u->cer_u.base); + } + + c3_free(for_u); + htd_u->fig_u.for_u = 0; +} + +/* _http_auth_free(): free stored auth token state +*/ +static void +_http_auth_free(u3_httd* htd_u) +{ + u3z(htd_u->fig_u.ses); + htd_u->fig_u.ses = u3_nul; + c3_free(htd_u->fig_u.key_c); +} + +/* u3_http_ef_form(): apply configuration, restart servers. +*/ +void +u3_http_ef_form(u3_httd* htd_u, u3_noun fig) +{ + u3_noun sec, pro, log, red; + + if ( (c3n == u3r_qual(fig, &sec, &pro, &log, &red) ) || + // confirm sec is a valid (unit ^) + !( u3_nul == sec || ( c3y == u3du(sec) && + c3y == u3du(u3t(sec)) && + u3_nul == u3h(sec) ) ) || + // confirm valid flags ("loobeans") + !( c3y == pro || c3n == pro ) || + !( c3y == log || c3n == log ) || + !( c3y == red || c3n == red ) ) { + u3l_log("http: form: invalid card"); + u3z(fig); + return; + } + + u3_form* for_u = c3_malloc(sizeof(*for_u)); + for_u->pro = (c3_o)pro; + for_u->log = (c3_o)log; + for_u->red = (c3_o)red; + + if ( u3_nul != sec ) { + u3_noun key = u3h(u3t(sec)); + u3_noun cer = u3t(u3t(sec)); + + for_u->key_u = _http_wain_to_buf(u3k(key)); + for_u->cer_u = _http_wain_to_buf(u3k(cer)); + } + else { + for_u->key_u = uv_buf_init(0, 0); + for_u->cer_u = uv_buf_init(0, 0); + } + + u3z(fig); + _http_form_free(htd_u); + + htd_u->fig_u.for_u = for_u; + + _http_serv_restart(htd_u); + + htd_u->car_u.liv_o = c3y; +} + +/* u3_http_ef_form(): store set of auth tokens +*/ +void +u3_http_ef_auth(u3_httd* htd_u, u3_noun fig) +{ + u3z(htd_u->fig_u.ses); + htd_u->fig_u.ses = fig; +} + +/* _http_io_talk(): start http I/O. +*/ +static void +_http_io_talk(u3_auto* car_u) +{ + u3_httd* htd_u = (u3_httd*)car_u; + + // XX remove [sen] + // + u3_noun wir = u3nt(u3i_string("http-server"), + u3dc("scot", c3__uv, htd_u->sev_l), + u3_nul); + u3_noun cad = u3nc(c3__born, u3_nul); + + u3_auto_plan(car_u, u3_ovum_init(0, c3__e, wir, cad)); + + // XX set liv_o on done/swap? + // +} + +/* _http_ef_http_server(): dispatch an %http-server effect from %light. +*/ +void +_http_ef_http_server(u3_httd* htd_u, + c3_l sev_l, + c3_l coq_l, + c3_l seq_l, + u3_noun tag, + u3_noun dat) +{ + u3_hreq* req_u; + u3_hws* web_u; + + // sets server configuration + // + if ( c3y == u3r_sing_c("set-config", tag) ) { + u3_http_ef_form(htd_u, u3k(dat)); + } + else if ( c3y == u3r_sing_c("sessions", tag) ) { + u3_http_ef_auth(htd_u, u3k(dat)); + } + // handles a cache notification + // + else if ( c3y == u3r_sing_c("grow", tag) ) { + // cache paths are /cache/(scot %ud aeon)/(scot %t url) + u3_noun pax = u3k(dat); + u3_noun url = u3h(u3t(u3t(pax))); + u3h_put(htd_u->sax_p, url, pax); + } + // responds to an open request + // + else if ( c3y == u3r_sing_c("websocket-response", tag) ) { + u3_noun wid = u3k(u3h(dat)); + u3_noun res = u3k(u3t(dat)); + + c3_w wid_w; + if ( c3n == u3r_safe_word(wid, &wid_w) ) { + u3l_log("http: invalid websocket id"); + } + else if ( 0 == (web_u = _http_ws_find(htd_u, wid_w)) ) { + u3l_log("http: unknown websocket id %u", wid_w); + } + else { + u3_noun typ = u3h(res); + + if ( c3y == u3r_sing_c("accept", typ) ) { + _http_ws_accept(web_u); + } + else if ( c3y == u3r_sing_c("reject", typ) ) { + _http_ws_reject(web_u); + } + else if ( c3y == u3r_sing_c("disconnect", typ) ) { + _http_ws_disconnect(web_u); + } + else if ( c3y == u3r_sing_c("message", typ) ) { + _http_ws_send_message(web_u, u3k(u3t(res))); + } + else { + u3l_log("http: unexpected websocket response"); + } + } + + u3z(wid); + u3z(res); + } + else if ( 0 != (req_u = _http_search_req(htd_u, sev_l, coq_l, seq_l)) ) { + if ( c3y == u3r_sing_c("response", tag) ) { + u3_noun response = dat; + + if ( 0 != req_u->wsu_u && (u3C.wag_w & u3o_verbose) ) { + u3l_log("http: ws pending got http response sev=%u coq=%u seq=%u wid=%u", + (c3_w)sev_l, + (c3_w)coq_l, + (c3_w)seq_l, + req_u->wsu_u->wid_l); + } + + if ( c3y == u3r_sing_c("start", u3h(response)) ) { + // Separate the %start message into its components. + // + u3_noun response_header, data, complete; + u3_noun status, headers; + u3x_trel(u3t(response), &response_header, &data, &complete); + u3x_cell(response_header, &status, &headers); + + _http_start_respond(req_u, u3k(status), u3k(headers), u3k(data), + u3k(complete)); + } + else if ( c3y == u3r_sing_c("continue", u3h(response)) ) { + // Separate the %continue message into its components. + // + u3_noun data, complete; + u3x_cell(u3t(response), &data, &complete); + + _http_continue_respond(req_u, u3k(data), u3k(complete)); + } + else if (c3y == u3r_sing_c("cancel", u3h(response))) { + _http_cancel_respond(req_u); + } + else { + u3l_log("http: strange response"); + } + } + else { + u3l_log("http: strange response"); + } + } + + u3z(tag); + u3z(dat); +} + +/* _http_stream_slog(): emit slog to open connections +*/ +static void +_http_stream_slog(void* vop_p, c3_w pri_w, u3_noun tan) +{ + u3_httd* htd_u = (u3_httd*)vop_p; + u3_hreq* seq_u = htd_u->fig_u.seq_u; + + // only do the work if there are open slog streams + // + if ( 0 != seq_u ) { + u3_weak data = u3_none; + + if ( c3y == u3a_is_atom(tan) ) { + u3_noun lin = u3i_list(u3i_string("data:"), + u3k(tan), + c3_s2('\n', '\n'), + u3_none); + u3_atom txt = u3qc_rap(3, lin); + data = u3nt(u3_nul, u3r_met(3, txt), txt); + u3z(lin); + } + else { + u3_weak wol = u3_none; + + // if we have no arvo kernel and can't evaluate nock, + // only send %leaf tanks + // + if ( 0 == u3A->roc ) { + if ( c3__leaf == u3h(tan) ) { + wol = u3nc(u3k(u3t(tan)), u3_nul); + } + } + else { + u3_noun blu = u3_term_get_blew(0); + c3_l col_l = u3h(blu); + wol = u3dc("wash", u3nc(0, col_l), u3k(tan)); + u3z(blu); + } + + if ( u3_none != wol ) { + u3_noun low = wol; + u3_noun paz = u3_nul; + while ( u3_nul != low ) { + u3_noun lin = u3i_list(u3i_string("data:"), + u3qc_rap(3, u3h(low)), + c3_s2('\n', '\n'), + u3_none); + paz = u3kb_weld(paz, lin); + low = u3t(low); + } + u3_atom txt = u3qc_rap(3, paz); + data = u3nt(u3_nul, u3r_met(3, txt), txt); + u3z(paz); + } + + u3z(wol); + } + + if ( u3_none != data ) { + while ( 0 != seq_u ) { + _http_continue_respond(seq_u, u3k(data), c3n); + seq_u = seq_u->nex_u; + } + } + + u3z(data); + } + + u3z(tan); +} + +/* _http_seq_heartbeat_cb(): send heartbeat to slog streams and restart timer +*/ +static void +_http_seq_heartbeat_cb(uv_timer_t* tim_u) +{ + u3_httd* htd_u = tim_u->data; + u3_hreq* seq_u = htd_u->fig_u.seq_u; + + if ( 0 != seq_u ) { + u3_noun dat = u3nt(u3_nul, 1, c3_s1('\n')); + while ( 0 != seq_u ) { + _http_continue_respond(seq_u, u3k(dat), c3n); + seq_u = seq_u->nex_u; + } + u3z(dat); + } + + uv_timer_start(htd_u->fig_u.sit_u, _http_seq_heartbeat_cb, + HEARTBEAT_TIMEOUT, 0); +} + +/* _http_io_kick(): apply effects. +*/ +static c3_o +_http_io_kick(u3_auto* car_u, u3_noun wir, u3_noun cad) +{ + u3_httd* htd_u = (u3_httd*)car_u; + + u3_noun tag, dat, i_wir, t_wir; + + if ( (c3n == u3r_cell(wir, &i_wir, &t_wir)) + || (c3n == u3r_cell(cad, &tag, &dat)) + || (c3n == u3r_sing_c("http-server", i_wir)) ) + { + u3z(wir); u3z(cad); + return c3n; + } + + // XX this needs to be rewritten, it defers (c3n) in cases it should not + // + { + u3_noun pud = t_wir; + u3_noun p_pud, t_pud, tt_pud, q_pud, r_pud, s_pud; + c3_l sev_l, coq_l, seq_l; + + + if ( (c3n == u3r_cell(pud, &p_pud, &t_pud)) || + (c3n == u3v_lily(c3__uv, u3k(p_pud), &sev_l)) ) + { + u3z(wir); u3z(cad); + return c3n; + } + + if ( u3_nul == t_pud ) { + coq_l = seq_l = 0; + } + else { + if ( (c3n == u3r_cell(t_pud, &q_pud, &tt_pud)) || + (c3n == u3v_lily(c3__ud, u3k(q_pud), &coq_l)) ) + { + u3z(wir); u3z(cad); + return c3n; + } + + if ( u3_nul == tt_pud ) { + seq_l = 0; + } else { + if ( (c3n == u3r_cell(tt_pud, &r_pud, &s_pud)) || + (u3_nul != s_pud) || + (c3n == u3v_lily(c3__ud, u3k(r_pud), &seq_l)) ) + { + u3z(wir); u3z(cad); + return c3n; + } + } + } + + _http_ef_http_server(htd_u, sev_l, coq_l, seq_l, u3k(tag), u3k(dat)); + u3z(wir); u3z(cad); + return c3y; + } +} + +/* _http_io_exit(): shut down http. +*/ +static void +_http_io_exit(u3_auto* car_u) +{ + u3_httd* htd_u = (u3_httd*)car_u; + + _http_ws_close_all(htd_u); + + u3h_free(htd_u->sax_p); + u3h_free(htd_u->nax_p); + + // dispose of configuration to avoid restarts + // + _http_form_free(htd_u); + _http_auth_free(htd_u); + + // close all servers + // + // XX broken + // + // for ( u3_http* htp_u = htd_u->htp_u; htp_u; htp_u = htp_u->nex_u ) { + // _http_serv_close(htp_u); + // } + + { + u3_atom lin = u3i_string("data:urbit shutting down\n\n"); + u3_noun dat = u3nt(u3_nul, u3r_met(3, lin), lin); + u3_hreq* seq_u = htd_u->fig_u.seq_u; + while ( 0 != seq_u ) { + _http_continue_respond(seq_u, u3k(dat), c3y); + seq_u = seq_u->nex_u; + } + u3z(dat); + } + + _http_release_ports_file(u3_Host.dir_c); +} + +/* _http_io_info(): produce status info. +*/ +static u3_noun +_http_io_info(u3_auto* car_u) +{ + u3_httd* htd_u = (u3_httd*)car_u; + u3_http* htp_u = htd_u->htp_u; + c3_w sec_w = 0; + u3_hreq* seq_u = htd_u->fig_u.seq_u; + u3_noun res; + + // XX review: metrics + // + while ( 0 != seq_u ) { + sec_w++; + seq_u = seq_u->nex_u; + } + res = u3i_list( + u3_pier_mase("instance", htd_u->sev_l), + u3_pier_mase("open-slogstreams", u3i_word(sec_w)), + u3_none); + + while ( 0 != htp_u ) { + res = u3nc( + u3_pier_mass( + u3dc("scot", c3__uv, htp_u->sev_l), + u3i_list( + u3_pier_mase("secure", htp_u->sec), + u3_pier_mase("loopback", htp_u->lop), + u3_pier_mase("live", htp_u->liv), + u3_pier_mase("port", htp_u->por_s), + u3_pier_mase("connections", htp_u->coq_l), + u3_none)), + res); + htp_u = htp_u->nex_u; + } + return u3kb_flop(res); +} + +/* _http_io_slog(): print status info. +*/ +static void +_http_io_slog(u3_auto* car_u) +{ + u3_httd* htd_u = (u3_httd*)car_u; + c3_y sec_y = 0; + u3_hreq* seq_u = htd_u->fig_u.seq_u; + while ( 0 != seq_u ) { + sec_y++; + seq_u = seq_u->nex_u; + } + u3l_log(" open slogstreams: %d", sec_y); +} + +/* u3_http_io_init(): initialize http I/O. +*/ +u3_auto* +u3_http_io_init(u3_pier* pir_u) +{ + u3_httd* htd_u = c3_calloc(sizeof(*htd_u)); + htd_u->sax_p = u3h_new(); + htd_u->nax_p = u3h_new_cache(512); + htd_u->web_u = 0; + htd_u->wid_l = 1; + + { + u3_noun key = u3dt("cat", 3, + u3i_string("urbauth-"), + u3dc("scot", 'p', u3i_chubs(2, pir_u->who_d))); + htd_u->fig_u.ses = u3_nul; + htd_u->fig_u.key_c = u3r_string(key); + u3z(key); + } + + u3_auto* car_u = &htd_u->car_u; + car_u->nam_m = c3__http; + car_u->liv_o = c3n; + car_u->io.talk_f = _http_io_talk; + car_u->io.info_f = _http_io_info; + car_u->io.slog_f = _http_io_slog; + car_u->io.kick_f = _http_io_kick; + car_u->io.exit_f = _http_io_exit; + + pir_u->sop_p = htd_u; + pir_u->sog_f = _http_stream_slog; + + uv_timer_t* sit_u = c3_malloc(sizeof(*sit_u)); + sit_u->data = htd_u; + uv_timer_init(u3L, sit_u); + uv_timer_start(sit_u, _http_seq_heartbeat_cb, HEARTBEAT_TIMEOUT, 0); + htd_u->fig_u.sit_u = sit_u; + + { + u3_noun now; + struct timeval tim_u; + gettimeofday(&tim_u, 0); + + now = u3_time_in_tv(&tim_u); + htd_u->sev_l = u3r_mug(now); + u3z(now); + } + + // XX retry up to N? + // + // car_u->ev.bail_f = ...; + + return car_u; +} |
