From 1c52f9e37ed07e93c21f84779d209ee196360006 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Thu, 18 Oct 2018 07:27:55 +0200
Subject: demo-json-rpc-server -> json-rpc-test-server
---
CMakeLists.txt | 6 +-
demo-json-rpc-server.c | 2925 ------------------------------------------------
json-rpc-test-server.c | 2925 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 2928 insertions(+), 2928 deletions(-)
delete mode 100644 demo-json-rpc-server.c
create mode 100644 json-rpc-test-server.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c78736a..f6d9b00 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,9 +36,9 @@ configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h
include_directories (${PROJECT_BINARY_DIR})
# Build the executables
-add_executable (demo-json-rpc-server
- demo-json-rpc-server.c http-parser/http_parser.c)
-target_link_libraries (demo-json-rpc-server ${project_libraries})
+add_executable (json-rpc-test-server
+ json-rpc-test-server.c http-parser/http_parser.c)
+target_link_libraries (json-rpc-test-server ${project_libraries})
# The files to be installed
include (GNUInstallDirs)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
deleted file mode 100644
index 34cd378..0000000
--- a/demo-json-rpc-server.c
+++ /dev/null
@@ -1,2925 +0,0 @@
-/*
- * demo-json-rpc-server.c: JSON-RPC 2.0 demo server
- *
- * Copyright (c) 2015 - 2018, Přemysl Janouch
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- */
-
-#define print_fatal_data ((void *) LOG_ERR)
-#define print_error_data ((void *) LOG_ERR)
-#define print_warning_data ((void *) LOG_WARNING)
-#define print_status_data ((void *) LOG_INFO)
-#define print_debug_data ((void *) LOG_DEBUG)
-
-#define LIBERTY_WANT_SSL
-#define LIBERTY_WANT_PROTO_HTTP
-#define LIBERTY_WANT_PROTO_WS
-#define LIBERTY_WANT_PROTO_SCGI
-#define LIBERTY_WANT_PROTO_FASTCGI
-
-#include "config.h"
-#include "liberty/liberty.c"
-
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-#include "http-parser/http_parser.h"
-
-enum { PIPE_READ, PIPE_WRITE };
-
-#define FIND_CONTAINER(name, pointer, type, member) \
- type *name = CONTAINER_OF (pointer, type, member)
-
-// --- Utilities ---------------------------------------------------------------
-
-static bool
-flush_queue (struct write_queue *queue, int fd)
-{
- struct iovec vec[queue->len], *vec_iter = vec;
- LIST_FOR_EACH (struct write_req, iter, queue->head)
- *vec_iter++ = iter->data;
-
- ssize_t written;
-again:
- if ((written = writev (fd, vec, N_ELEMENTS (vec))) >= 0)
- {
- write_queue_processed (queue, written);
- return true;
- }
- if (errno == EINTR)
- goto again;
- if (errno == EAGAIN)
- return true;
-
- return false;
-}
-
-// --- Logging -----------------------------------------------------------------
-
-static void
-log_message_syslog (void *user_data, const char *quote, const char *fmt,
- va_list ap)
-{
- int prio = (int) (intptr_t) user_data;
-
- va_list va;
- va_copy (va, ap);
- int size = vsnprintf (NULL, 0, fmt, va);
- va_end (va);
- if (size < 0)
- return;
-
- char buf[size + 1];
- if (vsnprintf (buf, sizeof buf, fmt, ap) >= 0)
- syslog (prio, "%s%s", quote, buf);
-}
-
-// --- FastCGI -----------------------------------------------------------------
-/// @defgroup FastCGI
-/// @{
-
-enum fcgi_request_state
-{
- FCGI_REQUEST_PARAMS, ///< Reading headers
- FCGI_REQUEST_STDIN ///< Reading input
-};
-
-struct fcgi_request
-{
- struct fcgi_muxer *muxer; ///< The parent muxer
- uint16_t request_id; ///< The ID of this request
- uint8_t flags; ///< Request flags
-
- enum fcgi_request_state state; ///< Parsing state
- struct str_map headers; ///< Headers
- struct fcgi_nv_parser hdr_parser; ///< Header parser
-
- struct str output_buffer; ///< Output buffer
-
- void *handler_data; ///< Handler data
-};
-
-/// Handles a single FastCGI connection, de/multiplexing requests and responses
-struct fcgi_muxer
-{
- struct fcgi_parser parser; ///< FastCGI message parser
- uint32_t active_requests; ///< The number of active requests
- bool in_shutdown; ///< Rejecting new requests
-
- // Virtual method callbacks:
-
- /// Write data to the underlying transport
- void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len);
-
- /// Close the underlying transport. You are allowed to destroy the muxer
- /// directly from within the callback.
- void (*close_cb) (struct fcgi_muxer *);
-
- /// Start processing a request. Return false if no further action is
- /// to be done and the request should be finished.
- bool (*request_start_cb) (struct fcgi_request *);
-
- /// Handle incoming data. "len == 0" means EOF. Returns false if
- /// the underlying transport should be closed, this being the last request.
- bool (*request_push_cb)
- (struct fcgi_request *, const void *data, size_t len);
-
- /// Destroy the handler's data stored in the request object
- void (*request_finalize_cb) (struct fcgi_request *);
-
- /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID)
- struct fcgi_request *requests[1 << 8];
-};
-
-static void
-fcgi_muxer_send (struct fcgi_muxer *self,
- enum fcgi_type type, uint16_t request_id, const void *data, size_t len)
-{
- hard_assert (len <= UINT16_MAX);
-
- struct str message = str_make ();
- static char zeroes[8];
- size_t padding = -len & 7;
-
- str_pack_u8 (&message, FCGI_VERSION_1);
- str_pack_u8 (&message, type);
- str_pack_u16 (&message, request_id);
- str_pack_u16 (&message, len); // content length
- str_pack_u8 (&message, padding); // padding length
- str_pack_u8 (&message, 0); // reserved
-
- str_append_data (&message, data, len);
- str_append_data (&message, zeroes, padding);
-
- // XXX: we should probably have another write_cb that assumes ownership
- self->write_cb (self, message.str, message.len);
- str_free (&message);
-}
-
-static void
-fcgi_muxer_send_end_request (struct fcgi_muxer *self, uint16_t request_id,
- uint32_t app_status, enum fcgi_protocol_status protocol_status)
-{
- uint8_t content[8] = { app_status >> 24, app_status >> 16,
- app_status << 8, app_status, protocol_status };
- fcgi_muxer_send (self, FCGI_END_REQUEST, request_id,
- content, sizeof content);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static struct fcgi_request *
-fcgi_request_new (void)
-{
- struct fcgi_request *self = xcalloc (1, sizeof *self);
-
- self->headers = str_map_make (free);
-
- self->hdr_parser = fcgi_nv_parser_make ();
- self->hdr_parser.output = &self->headers;
-
- self->output_buffer = str_make ();
- return self;
-}
-
-static void
-fcgi_request_destroy (struct fcgi_request *self)
-{
- // TODO: consider the case where it hasn't been started yet
- self->muxer->request_finalize_cb (self);
-
- str_map_free (&self->headers);
- fcgi_nv_parser_free (&self->hdr_parser);
- free (self);
-}
-
-static void
-fcgi_request_flush (struct fcgi_request *self)
-{
- if (!self->output_buffer.len)
- return;
-
- fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id,
- self->output_buffer.str, self->output_buffer.len);
- str_reset (&self->output_buffer);
-}
-
-static void
-fcgi_request_write (struct fcgi_request *self, const void *data, size_t len)
-{
- // We're buffering the output and splitting it into messages
- bool need_flush = true;
- while (len)
- {
- size_t to_write = UINT16_MAX - self->output_buffer.len;
- if (to_write > len)
- {
- to_write = len;
- need_flush = false;
- }
-
- str_append_data (&self->output_buffer, data, to_write);
- data = (uint8_t *) data + to_write;
- len -= to_write;
-
- if (need_flush)
- fcgi_request_flush (self);
- }
-}
-
-/// Mark the request as done. Returns false if the underlying transport
-/// should be closed, this being the last request.
-static bool
-fcgi_request_finish (struct fcgi_request *self)
-{
- fcgi_request_flush (self);
- fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id, NULL, 0);
-
- fcgi_muxer_send_end_request (self->muxer, self->request_id,
- 0 /* TODO app_status, although ignored */,
- FCGI_REQUEST_COMPLETE /* TODO protocol_status, may be different */);
-
- bool should_close = !(self->flags & FCGI_KEEP_CONN);
-
- self->muxer->active_requests--;
- self->muxer->requests[self->request_id] = NULL;
- fcgi_request_destroy (self);
-
- return !should_close;
-}
-
-static bool
-fcgi_request_push_params
- (struct fcgi_request *self, const void *data, size_t len)
-{
- if (self->state != FCGI_REQUEST_PARAMS)
- {
- print_debug ("FastCGI: expected %s, got %s",
- STRINGIFY (FCGI_STDIN), STRINGIFY (FCGI_PARAMS));
- return false;
- }
-
- if (len)
- fcgi_nv_parser_push (&self->hdr_parser, data, len);
- else
- {
- if (self->hdr_parser.state != FCGI_NV_PARSER_NAME_LEN)
- print_debug ("FastCGI: request headers seem to be cut off");
-
- self->state = FCGI_REQUEST_STDIN;
- if (!self->muxer->request_start_cb (self))
- return fcgi_request_finish (self);
- }
- return true;
-}
-
-static bool
-fcgi_request_push_stdin
- (struct fcgi_request *self, const void *data, size_t len)
-{
- if (self->state != FCGI_REQUEST_STDIN)
- {
- print_debug ("FastCGI: expected %s, got %s",
- STRINGIFY (FCGI_PARAMS), STRINGIFY (FCGI_STDIN));
- return false;
- }
-
- return self->muxer->request_push_cb (self, data, len);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-typedef bool (*fcgi_muxer_handler_fn)
- (struct fcgi_muxer *, const struct fcgi_parser *);
-
-static bool
-fcgi_muxer_on_get_values
- (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- if (parser->request_id != FCGI_NULL_REQUEST_ID)
- {
- print_debug ("FastCGI: invalid %s message",
- STRINGIFY (FCGI_GET_VALUES));
- return false;
- }
-
- struct str_map values = str_map_make (free);
- struct str_map response = str_map_make (free);
-
- struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make ();
- nv_parser.output = &values;
-
- fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len);
- const char *key = NULL;
-
- // No real-world servers seem to actually use multiplexing
- // or even issue this request, but we will implement it anyway
- if (str_map_find (&values, (key = FCGI_MPXS_CONNS)))
- str_map_set (&response, key, xstrdup ("1"));
-
- // It's not clear whether FCGI_MAX_REQS means concurrently over all
- // connections or over just a single connection (multiplexed), though
- // supposedly it's actually per /web server/. Supply the strictest limit.
- if (str_map_find (&values, (key = FCGI_MAX_REQS)))
- str_map_set (&response, key,
- xstrdup_printf ("%zu", N_ELEMENTS (self->requests) - 1));
-
- // FCGI_MAX_CONNS would be basically infinity. We don't limit connections.
-
- struct str content = str_make ();
- fcgi_nv_convert (&response, &content);
- fcgi_muxer_send (self, FCGI_GET_VALUES_RESULT, parser->request_id,
- content.str, content.len);
- str_free (&content);
-
- str_map_free (&values);
- str_map_free (&response);
- return true;
-}
-
-static bool
-fcgi_muxer_on_begin_request
- (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- struct msg_unpacker unpacker =
- msg_unpacker_make (parser->content.str, parser->content.len);
-
- uint16_t role;
- uint8_t flags;
- bool success = true;
- success &= msg_unpacker_u16 (&unpacker, &role);
- success &= msg_unpacker_u8 (&unpacker, &flags);
- // Ignoring 5 reserved bytes
-
- if (!success)
- {
- print_debug ("FastCGI: invalid %s message",
- STRINGIFY (FCGI_BEGIN_REQUEST));
- return false;
- }
-
- struct fcgi_request *request = self->requests[parser->request_id];
- if (parser->request_id == FCGI_NULL_REQUEST_ID || request)
- {
- print_debug ("FastCGI: unusable request ID in %s message",
- STRINGIFY (FCGI_BEGIN_REQUEST));
- return false;
- }
-
- // We can only act as a responder, reject everything else up front
- if (role != FCGI_RESPONDER)
- {
- fcgi_muxer_send_end_request (self,
- parser->request_id, 0, FCGI_UNKNOWN_ROLE);
- return true;
- }
-
- if (parser->request_id >= N_ELEMENTS (self->requests)
- || self->in_shutdown)
- {
- fcgi_muxer_send_end_request (self,
- parser->request_id, 0, FCGI_OVERLOADED);
- return true;
- }
-
- request = fcgi_request_new ();
- request->muxer = self;
- request->request_id = parser->request_id;
- request->flags = flags;
-
- self->requests[parser->request_id] = request;
- self->active_requests++;
- return true;
-}
-
-static bool
-fcgi_muxer_on_abort_request
- (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- struct fcgi_request *request = self->requests[parser->request_id];
- if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
- {
- print_debug ("FastCGI: received %s for an unknown request",
- STRINGIFY (FCGI_ABORT_REQUEST));
- return true; // We might have just rejected it
- }
-
- return fcgi_request_finish (request);
-}
-
-static bool
-fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- struct fcgi_request *request = self->requests[parser->request_id];
- if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
- {
- print_debug ("FastCGI: received %s for an unknown request",
- STRINGIFY (FCGI_PARAMS));
- return true; // We might have just rejected it
- }
-
- // This may immediately finish and delete the request, but that's fine
- return fcgi_request_push_params (request,
- parser->content.str, parser->content.len);
-}
-
-static bool
-fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- struct fcgi_request *request = self->requests[parser->request_id];
- if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
- {
- print_debug ("FastCGI: received %s for an unknown request",
- STRINGIFY (FCGI_STDIN));
- return true; // We might have just rejected it
- }
-
- // At the end of the stream, a zero-length record is received
- return fcgi_request_push_stdin (request,
- parser->content.str, parser->content.len);
-}
-
-static bool
-fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data)
-{
- struct fcgi_muxer *self = user_data;
-
- if (parser->version != FCGI_VERSION_1)
- {
- print_debug ("FastCGI: unsupported version %d", parser->version);
- return false;
- }
-
- static const fcgi_muxer_handler_fn handlers[] =
- {
- [FCGI_GET_VALUES] = fcgi_muxer_on_get_values,
- [FCGI_BEGIN_REQUEST] = fcgi_muxer_on_begin_request,
- [FCGI_ABORT_REQUEST] = fcgi_muxer_on_abort_request,
- [FCGI_PARAMS] = fcgi_muxer_on_params,
- [FCGI_STDIN] = fcgi_muxer_on_stdin,
- };
-
- fcgi_muxer_handler_fn handler;
- if (parser->type >= N_ELEMENTS (handlers)
- || !(handler = handlers[parser->type]))
- {
- // Responding in this way even to application records, unspecified
- uint8_t content[8] = { parser->type };
- fcgi_muxer_send (self, FCGI_UNKNOWN_TYPE, parser->request_id,
- content, sizeof content);
- return true;
- }
-
- return handler (self, parser);
-}
-
-static void
-fcgi_muxer_init (struct fcgi_muxer *self)
-{
- self->parser = fcgi_parser_make ();
- self->parser.on_message = fcgi_muxer_on_message;
- self->parser.user_data = self;
-}
-
-static void
-fcgi_muxer_free (struct fcgi_muxer *self)
-{
- for (size_t i = 0; i < N_ELEMENTS (self->requests); i++)
- {
- if (!self->active_requests)
- break;
- if (self->requests[i])
- {
- fcgi_request_destroy (self->requests[i]);
- self->active_requests--;
- }
- }
-
- fcgi_parser_free (&self->parser);
-}
-
-static bool
-fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len)
-{
- return fcgi_parser_push (&self->parser, data, len);
-}
-
-/// @}
-// --- WebSockets --------------------------------------------------------------
-/// @defgroup WebSockets
-/// @{
-
-// WebSockets aren't CGI-compatible, therefore we must handle the initial HTTP
-// handshake ourselves. Luckily it's not too much of a bother with http-parser.
-// Typically there will be a normal HTTP server in front of us, proxying the
-// requests based on the URI.
-
-enum ws_handler_state
-{
- WS_HANDLER_CONNECTING, ///< Parsing HTTP
- WS_HANDLER_OPEN, ///< Parsing WebSockets frames
- WS_HANDLER_CLOSING, ///< Partial closure by us
- WS_HANDLER_FLUSHING, ///< Just waiting for client EOF
- WS_HANDLER_CLOSED ///< Dead, both sides closed
-};
-
-struct ws_handler
-{
- enum ws_handler_state state; ///< State
-
- // HTTP handshake:
-
- http_parser hp; ///< HTTP parser
- bool have_header_value; ///< Parsing header value or field?
- struct str field; ///< Field part buffer
- struct str value; ///< Value part buffer
- struct str_map headers; ///< HTTP Headers
- struct str url; ///< Request URL
- ev_timer handshake_timeout_watcher; ///< Handshake timeout watcher
-
- // WebSocket frame protocol:
-
- struct ws_parser parser; ///< Protocol frame parser
- bool expecting_continuation; ///< For non-control traffic
-
- enum ws_opcode message_opcode; ///< Opcode for the current message
- struct str message_data; ///< Concatenated message data
-
- ev_timer ping_timer; ///< Ping timer
- bool received_pong; ///< Received PONG since the last PING
-
- ev_timer close_timeout_watcher; ///< Close timeout watcher
-
- // Configuration:
-
- unsigned handshake_timeout; ///< How long to wait for the handshake
- unsigned close_timeout; ///< How long to wait for TCP close
- unsigned ping_interval; ///< Ping interval in seconds
- uint64_t max_payload_len; ///< Maximum length of any message
-
- // Event callbacks:
-
- // TODO: void (*on_handshake) (protocols) that will allow the user
- // to choose any sub-protocol, if the client has provided any.
- // This may render "on_connected" unnecessary.
- // Should also enable failing the handshake.
-
- /// Called after successfuly connecting (handshake complete)
- bool (*on_connected) (struct ws_handler *);
-
- /// Called upon reception of a single full message
- bool (*on_message) (struct ws_handler *,
- enum ws_opcode type, const void *data, size_t len);
-
- /// The connection is about to close. @a close_code may, or may not, be one
- /// of enum ws_status. The @a reason is never NULL.
- void (*on_close) (struct ws_handler *, int close_code, const char *reason);
-
- // Virtual method callbacks:
-
- /// Write a chunk of data to the stream
- void (*write_cb) (struct ws_handler *, const void *data, size_t len);
-
- /// Close the connection. If @a half_close is false, you are allowed to
- /// destroy the handler directly from within the callback.
- void (*close_cb) (struct ws_handler *, bool half_close);
-};
-
-static void
-ws_handler_send_control (struct ws_handler *self,
- enum ws_opcode opcode, const void *data, size_t len)
-{
- if (len > WS_MAX_CONTROL_PAYLOAD_LEN)
- {
- print_debug ("truncating output control frame payload"
- " from %zu to %zu bytes", len, (size_t) WS_MAX_CONTROL_PAYLOAD_LEN);
- len = WS_MAX_CONTROL_PAYLOAD_LEN;
- }
-
- uint8_t header[2] = { 0x80 | (opcode & 0x0F), len };
- self->write_cb (self, header, sizeof header);
- self->write_cb (self, data, len);
-}
-
-static void
-ws_handler_close (struct ws_handler *self,
- enum ws_status close_code, const char *reason, size_t len)
-{
- hard_assert (self->state == WS_HANDLER_OPEN);
-
- struct str payload = str_make ();
- str_pack_u16 (&payload, close_code);
- // XXX: maybe accept a null-terminated string on input? Has to be UTF-8 a/w
- str_append_data (&payload, reason, len);
- ws_handler_send_control (self, WS_OPCODE_CLOSE, payload.str, payload.len);
- self->close_cb (self, true /* half_close */);
-
- self->state = WS_HANDLER_CLOSING;
- str_free (&payload);
-}
-
-static bool
-ws_handler_fail_connection (struct ws_handler *self, enum ws_status close_code)
-{
- hard_assert (self->state == WS_HANDLER_OPEN
- || self->state == WS_HANDLER_CLOSING);
-
- if (self->state == WS_HANDLER_OPEN)
- ws_handler_close (self, close_code, NULL, 0);
-
- self->state = WS_HANDLER_FLUSHING;
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
-
- ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
- ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
- ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
- return false;
-}
-
-// TODO: add support for fragmented responses
-static void
-ws_handler_send_frame (struct ws_handler *self,
- enum ws_opcode opcode, const void *data, size_t len)
-{
- if (!soft_assert (self->state == WS_HANDLER_OPEN))
- return;
-
- struct str header = str_make ();
- str_pack_u8 (&header, 0x80 | (opcode & 0x0F));
-
- if (len > UINT16_MAX)
- {
- str_pack_u8 (&header, 127);
- str_pack_u64 (&header, len);
- }
- else if (len > 125)
- {
- str_pack_u8 (&header, 126);
- str_pack_u16 (&header, len);
- }
- else
- str_pack_u8 (&header, len);
-
- self->write_cb (self, header.str, header.len);
- self->write_cb (self, data, len);
- str_free (&header);
-}
-
-static bool
-ws_handler_on_frame_header (void *user_data, const struct ws_parser *parser)
-{
- struct ws_handler *self = user_data;
-
- // Note that we aren't expected to send any close frame before closing the
- // connection when the frame is unmasked
-
- if (parser->reserved_1 || parser->reserved_2 || parser->reserved_3
- || !parser->is_masked // client -> server payload must be masked
- || (ws_is_control_frame (parser->opcode) &&
- (!parser->is_fin || parser->payload_len > WS_MAX_CONTROL_PAYLOAD_LEN))
- || (!ws_is_control_frame (parser->opcode) &&
- (self->expecting_continuation && parser->opcode != WS_OPCODE_CONT))
- || parser->payload_len >= 0x8000000000000000ULL)
- return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
-
- if (parser->payload_len > self->max_payload_len
- || (self->expecting_continuation &&
- self->message_data.len + parser->payload_len > self->max_payload_len))
- return ws_handler_fail_connection (self, WS_STATUS_MESSAGE_TOO_BIG);
- return true;
-}
-
-static bool
-ws_handler_on_control_close
- (struct ws_handler *self, const struct ws_parser *parser)
-{
- hard_assert (self->state == WS_HANDLER_OPEN
- || self->state == WS_HANDLER_CLOSING);
- struct msg_unpacker unpacker =
- msg_unpacker_make (parser->input.str, parser->payload_len);
-
- char *reason = NULL;
- uint16_t close_code = WS_STATUS_NO_STATUS_RECEIVED;
- if (parser->payload_len >= 2)
- {
- (void) msg_unpacker_u16 (&unpacker, &close_code);
- reason = xstrndup (parser->input.str + 2, parser->payload_len - 2);
- }
- else
- reason = xstrdup ("");
-
- if (close_code < 1000 || close_code > 4999)
- // XXX: invalid close code: maybe we should fail the connection instead
- close_code = WS_STATUS_PROTOCOL_ERROR;
-
- if (self->state == WS_HANDLER_OPEN)
- {
- // Close initiated by the client
- // FIXME: not sending the potentially different close_code
- ws_handler_send_control (self, WS_OPCODE_CLOSE,
- parser->input.str, parser->payload_len);
-
- self->state = WS_HANDLER_FLUSHING;
- if (self->on_close)
- self->on_close (self, close_code, reason);
- }
- else
- self->state = WS_HANDLER_FLUSHING;
-
- free (reason);
-
- ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
- ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
- ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
- return true;
-}
-
-static bool
-ws_handler_on_control_frame
- (struct ws_handler *self, const struct ws_parser *parser)
-{
- switch (parser->opcode)
- {
- case WS_OPCODE_CLOSE:
- return ws_handler_on_control_close (self, parser);
- case WS_OPCODE_PING:
- ws_handler_send_control (self, WS_OPCODE_PONG,
- parser->input.str, parser->payload_len);
- break;
- case WS_OPCODE_PONG:
- // TODO: check the payload
- self->received_pong = true;
- break;
- default:
- // Unknown control frame
- return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
- }
- return true;
-}
-
-static bool
-ws_handler_on_frame (void *user_data, const struct ws_parser *parser)
-{
- struct ws_handler *self = user_data;
- if (ws_is_control_frame (parser->opcode))
- return ws_handler_on_control_frame (self, parser);
- if (!self->expecting_continuation)
- self->message_opcode = parser->opcode;
-
- str_append_data (&self->message_data,
- parser->input.str, parser->payload_len);
- if ((self->expecting_continuation = !parser->is_fin))
- return true;
-
- if (self->message_opcode == WS_OPCODE_TEXT
- && !utf8_validate (self->message_data.str, self->message_data.len))
- {
- return ws_handler_fail_connection
- (self, WS_STATUS_INVALID_PAYLOAD_DATA);
- }
-
- bool result = true;
- if (self->on_message)
- result = self->on_message (self, self->message_opcode,
- self->message_data.str, self->message_data.len);
- str_reset (&self->message_data);
- // TODO: if (!result), either replace this with a state check,
- // or make sure to change the state
- return result;
-}
-
-static void
-ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents)
-{
- (void) loop;
- (void) revents;
-
- struct ws_handler *self = watcher->data;
- if (!self->received_pong)
- ws_handler_fail_connection (self, 4000);
- else
- {
- // TODO: be an annoying server and send a nonce in the data
- ws_handler_send_control (self, WS_OPCODE_PING, NULL, 0);
- ev_timer_again (EV_A_ watcher);
- }
-}
-
-static void
-ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
-{
- (void) loop;
- (void) revents;
- struct ws_handler *self = watcher->data;
-
- hard_assert (self->state == WS_HANDLER_OPEN
- || self->state == WS_HANDLER_CLOSING);
-
- if (self->state == WS_HANDLER_CLOSING
- && self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "close timeout");
-
- self->state = WS_HANDLER_CLOSED;
- self->close_cb (self, false /* half_close */);
-}
-
-static void
-ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents)
-{
- (void) loop;
- (void) revents;
- struct ws_handler *self = watcher->data;
-
- // XXX: this is a no-op, since this currently doesn't even call shutdown
- // immediately but postpones it until later
- self->close_cb (self, true /* half_close */);
- self->state = WS_HANDLER_FLUSHING;
-
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "handshake timeout");
-
- self->state = WS_HANDLER_CLOSED;
- self->close_cb (self, false /* half_close */);
-}
-
-static void
-ws_handler_init (struct ws_handler *self)
-{
- memset (self, 0, sizeof *self);
-
- self->state = WS_HANDLER_CONNECTING;
-
- http_parser_init (&self->hp, HTTP_REQUEST);
- self->hp.data = self;
- self->field = str_make ();
- self->value = str_make ();
- self->headers = str_map_make (free);
- self->headers.key_xfrm = tolower_ascii_strxfrm;
- self->url = str_make ();
- ev_timer_init (&self->handshake_timeout_watcher,
- ws_handler_on_handshake_timeout, 0., 0.);
- self->handshake_timeout_watcher.data = self;
-
- self->parser = ws_parser_make ();
- self->parser.on_frame_header = ws_handler_on_frame_header;
- self->parser.on_frame = ws_handler_on_frame;
- self->parser.user_data = self;
- self->message_data = str_make ();
-
- ev_timer_init (&self->ping_timer,
- ws_handler_on_ping_timer, 0., 0.);
- self->ping_timer.data = self;
- ev_timer_init (&self->close_timeout_watcher,
- ws_handler_on_close_timeout, 0., 0.);
- self->ping_timer.data = self;
- // So that the first ping timer doesn't timeout the connection
- self->received_pong = true;
-
- self->handshake_timeout = self->close_timeout = self->ping_interval = 60;
- // This is still ridiculously high. Note that the most significant bit
- // must always be zero, i.e. the protocol maximum is 0x7FFF FFFF FFFF FFFF.
- self->max_payload_len = UINT32_MAX;
-}
-
-/// Stop all timers, not going to use the handler anymore
-static void
-ws_handler_stop (struct ws_handler *self)
-{
- ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
- ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
- ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
-}
-
-static void
-ws_handler_free (struct ws_handler *self)
-{
- ws_handler_stop (self);
-
- str_free (&self->field);
- str_free (&self->value);
- str_map_free (&self->headers);
- str_free (&self->url);
-
- ws_parser_free (&self->parser);
- str_free (&self->message_data);
-}
-
-static bool
-ws_handler_header_field_is_a_list (const char *name)
-{
- // This must contain all header fields we use for anything
- static const char *concatenable[] =
- { SEC_WS_PROTOCOL, SEC_WS_EXTENSIONS, "Connection", "Upgrade" };
-
- for (size_t i = 0; i < N_ELEMENTS (concatenable); i++)
- if (!strcasecmp_ascii (name, concatenable[i]))
- return true;
- return false;
-}
-
-static void
-ws_handler_on_header_read (struct ws_handler *self)
-{
- // The HTTP parser unfolds values and removes preceding whitespace, but
- // otherwise doesn't touch the values or the following whitespace.
-
- // RFC 7230 states that trailing whitespace is not part of a field value
- char *value = self->field.str;
- size_t len = self->field.len;
- while (len--)
- if (value[len] == '\t' || value[len] == ' ')
- value[len] = '\0';
- else
- break;
- self->field.len = len;
-
- const char *field = self->field.str;
- const char *current = str_map_find (&self->headers, field);
- if (ws_handler_header_field_is_a_list (field) && current)
- str_map_set (&self->headers, field,
- xstrdup_printf ("%s, %s", current, self->value.str));
- else
- // If the field cannot be concatenated, just overwrite the last value.
- // Maybe we should issue a warning or something.
- str_map_set (&self->headers, field, xstrdup (self->value.str));
-}
-
-static int
-ws_handler_on_header_field (http_parser *parser, const char *at, size_t len)
-{
- struct ws_handler *self = parser->data;
- if (self->have_header_value)
- {
- ws_handler_on_header_read (self);
- str_reset (&self->field);
- str_reset (&self->value);
- }
- str_append_data (&self->field, at, len);
- self->have_header_value = false;
- return 0;
-}
-
-static int
-ws_handler_on_header_value (http_parser *parser, const char *at, size_t len)
-{
- struct ws_handler *self = parser->data;
- str_append_data (&self->value, at, len);
- self->have_header_value = true;
- return 0;
-}
-
-static int
-ws_handler_on_headers_complete (http_parser *parser)
-{
- struct ws_handler *self = parser->data;
- if (self->have_header_value)
- ws_handler_on_header_read (self);
-
- // We strictly require a protocol upgrade
- if (!parser->upgrade)
- return 2;
-
- return 0;
-}
-
-static int
-ws_handler_on_url (http_parser *parser, const char *at, size_t len)
-{
- struct ws_handler *self = parser->data;
- str_append_data (&self->value, at, len);
- return 0;
-}
-
-#define HTTP_101_SWITCHING_PROTOCOLS "101 Switching Protocols"
-#define HTTP_400_BAD_REQUEST "400 Bad Request"
-#define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed"
-#define HTTP_417_EXPECTATION_FAILED "407 Expectation Failed"
-#define HTTP_426_UPGRADE_REQUIRED "426 Upgrade Required"
-#define HTTP_505_VERSION_NOT_SUPPORTED "505 HTTP Version Not Supported"
-
-static void
-ws_handler_http_responsev (struct ws_handler *self,
- const char *status, char *const *fields)
-{
- hard_assert (status != NULL);
-
- struct str response = str_make ();
- str_append_printf (&response, "HTTP/1.1 %s\r\n", status);
-
- while (*fields)
- str_append_printf (&response, "%s\r\n", *fields++);
-
- time_t now = time (NULL);
- struct tm ts;
- gmtime_r (&now, &ts);
-
- // See RFC 7231, 7.1.1.2. Date
- const char *dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
- const char *moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
- str_append_printf (&response,
- "Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n",
- dow[ts.tm_wday], ts.tm_mday, moy[ts.tm_mon], ts.tm_year + 1900,
- ts.tm_hour, ts.tm_min, ts.tm_sec);
-
- str_append (&response, "Server: "
- PROGRAM_NAME "/" PROGRAM_VERSION "\r\n\r\n");
- self->write_cb (self, response.str, response.len);
- str_free (&response);
-}
-
-static bool
-ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...)
-{
- va_list ap;
- va_start (ap, status);
-
- const char *s;
- struct strv v = strv_make ();
- while ((s = va_arg (ap, const char *)))
- strv_append (&v, s);
-
- va_end (ap);
- ws_handler_http_responsev (self, status, v.vector);
- strv_free (&v);
-
- self->close_cb (self, true /* half_close */);
- self->state = WS_HANDLER_FLUSHING;
-
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, status);
- return false;
-}
-
-#define FAIL_HANDSHAKE(...) \
- return ws_handler_fail_handshake (self, __VA_ARGS__, NULL)
-
-static bool
-ws_handler_finish_handshake (struct ws_handler *self)
-{
- if (self->hp.method != HTTP_GET)
- FAIL_HANDSHAKE (HTTP_405_METHOD_NOT_ALLOWED, "Allow: GET");
-
- // Technically, it must be /at least/ 1.1 but no other 1.x version of HTTP
- // is going to happen and 2.x is entirely incompatible
- // XXX: we probably shouldn't use 505 to reject the minor version but w/e
- if (self->hp.http_major != 1 || self->hp.http_minor != 1)
- FAIL_HANDSHAKE (HTTP_505_VERSION_NOT_SUPPORTED);
-
- // Your expectations are way too high
- if (str_map_find (&self->headers, "Expect"))
- FAIL_HANDSHAKE (HTTP_417_EXPECTATION_FAILED);
-
- // Reject URLs specifying the schema and host; we're not parsing that
- // TODO: actually do parse this and let our user decide if it matches
- struct http_parser_url url;
- if (http_parser_parse_url (self->url.str, self->url.len, false, &url)
- || (url.field_set & (1 << UF_SCHEMA | 1 << UF_HOST | 1 << UF_PORT))
- || !str_map_find (&self->headers, "Host"))
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
-
- const char *connection = str_map_find (&self->headers, "Connection");
- if (!connection || strcasecmp_ascii (connection, "Upgrade"))
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
-
- // Check if we can actually upgrade the protocol to WebSockets
- const char *upgrade = str_map_find (&self->headers, "Upgrade");
- struct http_protocol *offered_upgrades = NULL;
- bool can_upgrade = false;
- if (upgrade && http_parse_upgrade (upgrade, &offered_upgrades))
- // Case-insensitive according to RFC 6455; neither RFC 2616 nor 7230
- // say anything at all about case-sensitivity for this field
- LIST_FOR_EACH (struct http_protocol, iter, offered_upgrades)
- {
- if (!iter->version && !strcasecmp_ascii (iter->name, "websocket"))
- can_upgrade = true;
- http_protocol_destroy (iter);
- }
- if (!can_upgrade)
- FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED,
- "Upgrade: websocket", SEC_WS_VERSION ": 13");
-
- // Okay, we're finally past the basic HTTP/1.1 stuff
- const char *key = str_map_find (&self->headers, SEC_WS_KEY);
- const char *version = str_map_find (&self->headers, SEC_WS_VERSION);
-/*
- const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL);
- const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS);
-*/
-
- if (!version)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
- if (strcmp (version, "13"))
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13");
-
- struct str tmp = str_make ();
- bool key_is_valid = key
- && base64_decode (key, false, &tmp) && tmp.len == 16;
- str_free (&tmp);
- if (!key_is_valid)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
-
- struct strv fields = strv_make ();
- strv_append_args (&fields,
- "Upgrade: websocket",
- "Connection: Upgrade",
- NULL);
-
- char *response_key = ws_encode_response_key (key);
- strv_append_owned (&fields,
- xstrdup_printf (SEC_WS_ACCEPT ": %s", response_key));
- free (response_key);
-
- // TODO: make it possible to choose Sec-Websocket-{Extensions,Protocol}
-
- ws_handler_http_responsev (self,
- HTTP_101_SWITCHING_PROTOCOLS, fields.vector);
-
- strv_free (&fields);
-
- self->state = WS_HANDLER_OPEN;
- ev_timer_init (&self->ping_timer, ws_handler_on_ping_timer,
- self->ping_interval, 0);
- ev_timer_start (EV_DEFAULT_ &self->ping_timer);
- return true;
-}
-
-/// Tells the handler that the TCP connection has been established so it can
-/// timeout when the client handshake doesn't arrive soon enough
-static void
-ws_handler_start (struct ws_handler *self)
-{
- hard_assert (self->state == WS_HANDLER_CONNECTING);
-
- ev_timer_set (&self->handshake_timeout_watcher,
- self->handshake_timeout, 0.);
- ev_timer_start (EV_DEFAULT_ &self->handshake_timeout_watcher);
-}
-
-// The client should normally never close the connection, assume that it's
-// either received an EOF from our side, or that it doesn't care about our data
-// anymore, having called close() already
-static bool
-ws_handler_push_eof (struct ws_handler *self)
-{
- switch (self->state)
- {
- case WS_HANDLER_CONNECTING:
- ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
-
- self->state = WS_HANDLER_FLUSHING;
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "unexpected EOF");
- break;
- case WS_HANDLER_OPEN:
- ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
- // Fall-through
- case WS_HANDLER_CLOSING:
- self->state = WS_HANDLER_CLOSED;
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
- // Fall-through
- case WS_HANDLER_FLUSHING:
- ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
- break;
- default:
- soft_assert(self->state != WS_HANDLER_CLOSED);
- }
- self->state = WS_HANDLER_CLOSED;
- return false;
-}
-
-/// Push data to the WebSocket handler. "len == 0" means EOF.
-/// You are expected to close the connection and dispose of the handler
-/// when the function returns false.
-static bool
-ws_handler_push (struct ws_handler *self, const void *data, size_t len)
-{
- if (!len)
- return ws_handler_push_eof (self);
-
- if (self->state == WS_HANDLER_FLUSHING)
- // We're waiting for an EOF from the client, must not process data
- return true;
-
- if (self->state != WS_HANDLER_CONNECTING)
- return soft_assert (self->state != WS_HANDLER_CLOSED)
- && ws_parser_push (&self->parser, data, len);
-
- // The handshake hasn't been done yet, process HTTP headers
- static const http_parser_settings http_settings =
- {
- .on_header_field = ws_handler_on_header_field,
- .on_header_value = ws_handler_on_header_value,
- .on_headers_complete = ws_handler_on_headers_complete,
- .on_url = ws_handler_on_url,
- };
-
- size_t n_parsed =
- http_parser_execute (&self->hp, &http_settings, data, len);
-
- if (self->hp.upgrade)
- {
- ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
-
- // The handshake hasn't been finished, yet there is more data
- // to be processed after the headers already
- if (len - n_parsed)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
-
- if (!ws_handler_finish_handshake (self))
- return false;
- if (self->on_connected)
- return self->on_connected (self);
- return true;
- }
-
- enum http_errno err = HTTP_PARSER_ERRNO (&self->hp);
- if (n_parsed != len || err != HPE_OK)
- {
- ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
-
- if (err == HPE_CB_headers_complete)
- print_debug ("WS handshake failed: %s", "missing `Upgrade' field");
- else
- print_debug ("WS handshake failed: %s",
- http_errno_description (err));
-
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
- }
- return true;
-}
-
-/// @}
-// --- Server ------------------------------------------------------------------
-
-static struct simple_config_item g_config_table[] =
-{
- { "bind_host", NULL, "Address of the server" },
- { "port_fastcgi", "9000", "Port to bind for FastCGI" },
- { "port_scgi", NULL, "Port to bind for SCGI" },
- { "port_ws", NULL, "Port to bind for WebSockets" },
- { "pid_file", NULL, "Full path for the PID file" },
- // XXX: here belongs something like a web SPA that interfaces with us
- { "static_root", NULL, "The root for static content" },
- { NULL, NULL, NULL }
-};
-
-struct server_context
-{
- ev_signal sigterm_watcher; ///< Got SIGTERM
- ev_signal sigint_watcher; ///< Got SIGINT
- ev_timer quit_timeout_watcher; ///< Quit timeout watcher
- bool quitting; ///< User requested quitting
-
- struct listener *listeners; ///< Listeners
- size_t n_listeners; ///< Number of listening sockets
-
- struct client *clients; ///< Clients
- unsigned n_clients; ///< Current number of connections
-
- struct request_handler *handlers; ///< Request handlers
- struct str_map config; ///< Server configuration
-};
-
-static void initiate_quit (struct server_context *self);
-static void try_finish_quit (struct server_context *self);
-static void on_quit_timeout (EV_P_ ev_timer *watcher, int revents);
-static void close_listeners (struct server_context *self);
-
-static void
-server_context_init (struct server_context *self)
-{
- memset (self, 0, sizeof *self);
-
- self->config = str_map_make (NULL);
- simple_config_load_defaults (&self->config, g_config_table);
- ev_timer_init (&self->quit_timeout_watcher, on_quit_timeout, 3., 0.);
- self->quit_timeout_watcher.data = self;
-}
-
-static void
-server_context_free (struct server_context *self)
-{
- // We really shouldn't attempt a quit without closing the clients first
- soft_assert (!self->clients);
-
- close_listeners (self);
- free (self->listeners);
-
- str_map_free (&self->config);
-}
-
-// --- JSON-RPC ----------------------------------------------------------------
-/// @defgroup JSON-RPC
-/// @{
-
-#define JSON_RPC_ERROR_TABLE(XX) \
- XX (-32700, PARSE_ERROR, "Parse error") \
- XX (-32600, INVALID_REQUEST, "Invalid Request") \
- XX (-32601, METHOD_NOT_FOUND, "Method not found") \
- XX (-32602, INVALID_PARAMS, "Invalid params") \
- XX (-32603, INTERNAL_ERROR, "Internal error")
-
-enum json_rpc_error
-{
-#define XX(code, name, message) JSON_RPC_ERROR_ ## name,
- JSON_RPC_ERROR_TABLE (XX)
-#undef XX
- JSON_RPC_ERROR_COUNT
-};
-
-static json_t *
-json_rpc_error (enum json_rpc_error id, json_t *data)
-{
-#define XX(code, name, message) { code, message },
- static const struct json_rpc_error
- {
- int code;
- const char *message;
- }
- errors[JSON_RPC_ERROR_COUNT] =
- {
- JSON_RPC_ERROR_TABLE (XX)
- };
-#undef XX
-
- json_t *error = json_object ();
- json_object_set_new (error, "code", json_integer (errors[id].code));
- json_object_set_new (error, "message", json_string (errors[id].message));
-
- if (data)
- json_object_set_new (error, "data", data);
-
- return error;
-}
-
-static json_t *
-json_rpc_response (json_t *id, json_t *result, json_t *error)
-{
- json_t *x = json_object ();
- json_object_set_new (x, "jsonrpc", json_string ("2.0"));
- json_object_set_new (x, "id", id ? id : json_null ());
- if (result) json_object_set_new (x, "result", result);
- if (error) json_object_set_new (x, "error", error);
- return x;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-validate_json_rpc_content_type (const char *content_type)
-{
- char *type = NULL;
- char *subtype = NULL;
-
- struct str_map parameters = str_map_make (free);
- parameters.key_xfrm = tolower_ascii_strxfrm;
-
- bool result = http_parse_media_type
- (content_type, &type, &subtype, ¶meters);
- if (!result)
- goto end;
-
- if (strcasecmp_ascii (type, "application")
- || (strcasecmp_ascii (subtype, "json") &&
- strcasecmp_ascii (subtype, "json-rpc" /* obsolete */)))
- result = false;
-
- const char *charset = str_map_find (¶meters, "charset");
- if (charset && strcasecmp_ascii (charset, "UTF-8"))
- result = false;
-
- // Currently ignoring all unknown parametrs
-
-end:
- free (type);
- free (subtype);
- str_map_free (¶meters);
- return result;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-typedef json_t *(*json_rpc_handler_fn) (struct server_context *, json_t *);
-
-struct json_rpc_handler_info
-{
- const char *method_name; ///< JSON-RPC method name
- json_rpc_handler_fn handler; ///< Method handler
-};
-
-static int
-json_rpc_handler_info_cmp (const void *first, const void *second)
-{
- return strcmp (((struct json_rpc_handler_info *) first)->method_name,
- ((struct json_rpc_handler_info *) second)->method_name);
-}
-
-// TODO: a method that sends a response after a certain number of seconds.
-// This has to be owned by the server context as a background job that
-// removes itself upon completion.
-
-static json_t *
-json_rpc_ping (struct server_context *ctx, json_t *params)
-{
- (void) ctx;
- (void) params;
-
- return json_rpc_response (NULL, json_string ("pong"), NULL);
-}
-
-static json_t *
-process_json_rpc_request (struct server_context *ctx, json_t *request)
-{
- // A list of all available methods; this list has to be ordered.
- // Eventually it might be better to move this into a map in the context.
- static struct json_rpc_handler_info handlers[] =
- {
- { "ping", json_rpc_ping },
- };
-
- if (!json_is_object (request))
- return json_rpc_response (NULL, NULL,
- json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL));
-
- json_t *v = json_object_get (request, "jsonrpc");
- json_t *m = json_object_get (request, "method");
- json_t *params = json_object_get (request, "params");
- json_t *id = json_object_get (request, "id");
-
- const char *version;
- const char *method;
-
- bool ok = true;
- ok &= v && (version = json_string_value (v)) && !strcmp (version, "2.0");
- ok &= m && (method = json_string_value (m));
- ok &= !params || json_is_array (params) || json_is_object (params);
- ok &= !id || json_is_null (id) ||
- json_is_string (id) || json_is_number (id);
- if (!ok)
- return json_rpc_response (id, NULL,
- json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL));
-
- struct json_rpc_handler_info key = { .method_name = method };
- struct json_rpc_handler_info *handler = bsearch (&key, handlers,
- N_ELEMENTS (handlers), sizeof key, json_rpc_handler_info_cmp);
- if (!handler)
- return json_rpc_response (id, NULL,
- json_rpc_error (JSON_RPC_ERROR_METHOD_NOT_FOUND, NULL));
-
- json_t *response = handler->handler (ctx, params);
- if (id)
- return response;
-
- // Notifications don't get responses
- json_decref (response);
- return NULL;
-}
-
-static void
-flush_json (json_t *json, struct str *output)
-{
- char *utf8 = json_dumps (json, JSON_ENCODE_ANY);
- str_append (output, utf8);
- free (utf8);
- json_decref (json);
-}
-
-static void
-process_json_rpc (struct server_context *ctx,
- const void *data, size_t len, struct str *output)
-{
-
- json_error_t e;
- json_t *request;
- if (!(request = json_loadb (data, len, JSON_DECODE_ANY, &e)))
- {
- flush_json (json_rpc_response (NULL, NULL,
- json_rpc_error (JSON_RPC_ERROR_PARSE_ERROR, NULL)),
- output);
- return;
- }
-
- if (json_is_array (request))
- {
- if (!json_array_size (request))
- {
- flush_json (json_rpc_response (NULL, NULL,
- json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL)),
- output);
- return;
- }
-
- json_t *response = json_array ();
- json_t *iter;
- size_t i;
-
- json_array_foreach (request, i, iter)
- {
- json_t *result = process_json_rpc_request (ctx, iter);
- if (result)
- json_array_append_new (response, result);
- }
-
- if (json_array_size (response))
- flush_json (response, output);
- else
- json_decref (response);
- }
- else
- {
- json_t *result = process_json_rpc_request (ctx, request);
- if (result)
- flush_json (result, output);
- }
-}
-
-/// @}
-// --- Requests ----------------------------------------------------------------
-/// @defgroup Requests
-/// @{
-
-/// A generic CGI request abstraction, writing data indirectly through callbacks
-struct request
-{
- struct server_context *ctx; ///< Server context
-
- struct request_handler *handler; ///< Assigned request handler
- void *handler_data; ///< User data for the handler
-
- /// Callback to write some CGI response data to the output
- void (*write_cb) (struct request *, const void *data, size_t len);
-
- /// Callback to close the CGI response, simulates end of program execution.
- /// CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
- void (*close_cb) (struct request *);
-};
-
-/// An interface to detect and handle specific kinds of CGI requests.
-/// The server walks through a list of them until it finds one that can serve
-/// a particular request. If unsuccessful, the remote client gets a 404
-/// (the default handling).
-struct request_handler
-{
- LIST_HEADER (struct request_handler)
-
- /// Install ourselves as the handler for the request, if applicable.
- /// Sets @a continue_ to false if further processing should be stopped,
- /// meaning the request has already been handled.
- bool (*try_handle) (struct request *request,
- struct str_map *headers, bool *continue_);
-
- /// Handle incoming data. "len == 0" means EOF.
- /// Returns false if there is no more processing to be done.
- // FIXME: the EOF may or may not be delivered when request is cut short,
- // we should fix FastCGI not to deliver it on CONTENT_LENGTH mismatch
- bool (*push_cb) (struct request *request, const void *data, size_t len);
-
- /// Destroy the handler's data stored in the request object
- void (*finalize_cb) (struct request *request);
-};
-
-static void
-request_init (struct request *self)
-{
- memset (self, 0, sizeof *self);
-}
-
-static void
-request_free (struct request *self)
-{
- if (self->handler)
- self->handler->finalize_cb (self);
-}
-
-/// Write request CGI response data, intended for use by request handlers
-static void
-request_write (struct request *self, const void *data, size_t len)
-{
- self->write_cb (self, data, len);
-}
-
-/// This function is only intended to be run from asynchronous event handlers
-/// such as timers, not as a direct result of starting the request or receiving
-/// request data. CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
-static void
-request_finish (struct request *self)
-{
- self->close_cb (self);
-}
-
-/// Starts processing a request. Returns false if no further action is to be
-/// done and the request should be finished.
-static bool
-request_start (struct request *self, struct str_map *headers)
-{
- // XXX: it feels like this should rather be two steps:
- // bool (*can_handle) (request *, headers)
- // ... install the handler ...
- // bool (*handle) (request *)
- //
- // However that might cause some stuff to be done twice.
- //
- // Another way we could get rid of the continue_ argument is via adding
- // some way of marking the request as finished from within the handler.
-
- if (g_debug_mode)
- {
- struct str_map_iter iter = str_map_iter_make (headers);
- const char *value;
- while ((value = str_map_iter_next (&iter)))
- print_debug ("%s: %s", iter.link->key, value);
- print_debug ("--");
- }
-
- bool continue_ = true;
- LIST_FOR_EACH (struct request_handler, handler, self->ctx->handlers)
- if (handler->try_handle (self, headers, &continue_))
- {
- self->handler = handler;
- return continue_;
- }
-
- // Unable to serve the request
- struct str response = str_make ();
- str_append (&response, "Status: 404 Not Found\n");
- str_append (&response, "Content-Type: text/plain\n\n");
- request_write (self, response.str, response.len);
- str_free (&response);
- return false;
-}
-
-static bool
-request_push (struct request *self, const void *data, size_t len)
-{
- if (!soft_assert (self->handler))
- // No handler, nothing to do with any data
- return false;
-
- return self->handler->push_cb (self, data, len);
-}
-
-/// @}
-// --- Requests handlers -------------------------------------------------------
-
-static bool
-request_handler_json_rpc_try_handle
- (struct request *request, struct str_map *headers, bool *continue_)
-{
- const char *content_type = str_map_find (headers, "CONTENT_TYPE");
- const char *method = str_map_find (headers, "REQUEST_METHOD");
-
- if (!method || strcmp (method, "POST")
- || !content_type || !validate_json_rpc_content_type (content_type))
- return false;
-
- struct str *buf = xcalloc (1, sizeof *buf);
- *buf = str_make ();
-
- request->handler_data = buf;
- *continue_ = true;
- return true;
-}
-
-static bool
-request_handler_json_rpc_push
- (struct request *request, const void *data, size_t len)
-{
- struct str *buf = request->handler_data;
- if (len)
- {
- str_append_data (buf, data, len);
- return true;
- }
-
- // TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
- // client hasn't been successful in transferring all of its data.
- // See also comment on request_handler::push_cb.
-
- struct str response = str_make ();
- str_append (&response, "Status: 200 OK\n");
- str_append_printf (&response, "Content-Type: %s\n\n", "application/json");
- process_json_rpc (request->ctx, buf->str, buf->len, &response);
- request_write (request, response.str, response.len);
- str_free (&response);
- return false;
-}
-
-static void
-request_handler_json_rpc_finalize (struct request *request)
-{
- struct str *buf = request->handler_data;
- str_free (buf);
- free (buf);
-
- request->handler_data = NULL;
-}
-
-struct request_handler g_request_handler_json_rpc =
-{
- .try_handle = request_handler_json_rpc_try_handle,
- .push_cb = request_handler_json_rpc_push,
- .finalize_cb = request_handler_json_rpc_finalize,
-};
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static char *
-canonicalize_url_path (const char *path)
-{
- // XXX: this strips any slashes at the end
- struct strv v = strv_make ();
- cstr_split (path, "/", true, &v);
-
- struct strv canonical = strv_make ();
-
- // So that the joined path always begins with a slash
- strv_append (&canonical, "");
-
- for (size_t i = 0; i < v.len; i++)
- {
- const char *dir = v.vector[i];
- if (!strcmp (dir, "."))
- continue;
-
- if (strcmp (dir, ".."))
- strv_append (&canonical, dir);
- else if (canonical.len > 1)
- // ".." never goes above the root
- strv_remove (&canonical, canonical.len - 1);
- }
- strv_free (&v);
-
- char *joined = strv_join (&canonical, "/");
- strv_free (&canonical);
- return joined;
-}
-
-static char *
-detect_magic (const void *data, size_t len)
-{
- magic_t cookie;
- char *mime_type = NULL;
-
- if (!(cookie = magic_open (MAGIC_MIME)))
- return NULL;
-
- const char *magic = NULL;
- if (!magic_load (cookie, NULL)
- && (magic = magic_buffer (cookie, data, len)))
- mime_type = xstrdup (magic);
- else
- print_debug ("MIME type detection failed: %s", magic_error (cookie));
-
- magic_close (cookie);
- return mime_type;
-}
-
-static bool
-request_handler_static_try_handle
- (struct request *request, struct str_map *headers, bool *continue_)
-{
- // Serving static files is actually quite complicated as it turns out;
- // but this is only meant to serve a few tiny text files
-
- struct server_context *ctx = request->ctx;
- const char *root = str_map_find (&ctx->config, "static_root");
- if (!root)
- {
- print_debug ("static document root not configured");
- return false;
- }
-
- // TODO: implement HEAD, we don't get that for free;
- // probably implies adding Content-Length
- const char *method = str_map_find (headers, "REQUEST_METHOD");
- if (!method || strcmp (method, "GET"))
- return false;
-
- // TODO: look at , REQUEST_URI in the headers
- const char *path_info = str_map_find (headers, "PATH_INFO");
- if (!path_info)
- path_info = str_map_find (headers, "REQUEST_URI");
- if (!path_info)
- {
- print_debug ("neither PATH_INFO nor REQUEST_URI was defined");
- return false;
- }
-
- // We need to filter the path to stay in our root
- // Being able to read /etc/passwd would be rather embarrasing
- char *suffix = canonicalize_url_path (path_info);
- char *path = xstrdup_printf ("%s%s", root, suffix);
- print_debug ("trying to statically serve %s", path);
-
- // TODO: check that this is a regular file
- FILE *fp = fopen (path, "rb");
- if (!fp)
- {
- struct str response = str_make ();
- str_append (&response, "Status: 404 Not Found\n");
- str_append (&response, "Content-Type: text/plain\n\n");
- str_append_printf (&response,
- "File %s was not found on this server\n", suffix);
- request_write (request, response.str, response.len);
- str_free (&response);
-
- free (suffix);
- free (path);
- return false;
- }
-
- free (suffix);
- free (path);
-
- uint8_t buf[8192];
- size_t len;
-
- // Try to detect the Content-Type from the actual contents
- char *mime_type = NULL;
- if ((len = fread (buf, 1, sizeof buf, fp)))
- mime_type = detect_magic (buf, len);
- if (!mime_type)
- mime_type = xstrdup ("application/octet_stream");
-
- struct str response = str_make ();
- str_append (&response, "Status: 200 OK\n");
- str_append_printf (&response, "Content-Type: %s\n\n", mime_type);
- request_write (request, response.str, response.len);
- str_free (&response);
- free (mime_type);
-
- // Write the chunk we've used to help us with magic detection;
- // obviously we have to do it after we've written the headers
- if (len)
- request_write (request, buf, len);
-
- while ((len = fread (buf, 1, sizeof buf, fp)))
- request_write (request, buf, len);
- fclose (fp);
-
- // TODO: this should rather not be returned all at once but in chunks;
- // file read requests never return EAGAIN
- // TODO: actual file data should really be returned by a callback when
- // the socket is writable with nothing to be sent (pumping the entire
- // file all at once won't really work if it's huge).
- *continue_ = false;
- return true;
-}
-
-static bool
-request_handler_static_push
- (struct request *request, const void *data, size_t len)
-{
- (void) request;
- (void) data;
-
- // Aborting on content; we shouldn't receive any (GET)
- // FIXME: there should at least be some indication of this happening
- return len == 0;
-}
-
-static void
-request_handler_static_finalize (struct request *request)
-{
- (void) request;
- // Nothing to dispose of this far
-}
-
-struct request_handler g_request_handler_static =
-{
- .try_handle = request_handler_static_try_handle,
- .push_cb = request_handler_static_push,
- .finalize_cb = request_handler_static_finalize,
-};
-
-// --- Client communication handlers -------------------------------------------
-
-/// A virtual class for client connections coming either from the web server
-/// or directly from the end-client, depending on the protocol in use
-struct client
-{
- LIST_HEADER (struct client)
-
- struct client_vtable *vtable; ///< Client behaviour
-
- int socket_fd; ///< The network socket
- bool received_eof; ///< Whether EOF has been received yet
- bool flushing; ///< No more data to write, send FIN
- bool closing; ///< No more data to read or write
- bool half_closed; ///< Conn. half-closed while flushing
- struct write_queue write_queue; ///< Write queue
- ev_timer close_timeout_watcher; ///< Write queue flush timer
-
- ev_io read_watcher; ///< The socket can be read from
- ev_io write_watcher; ///< The socket can be written to
-};
-
-/// The concrete behaviour to serve a particular client's requests
-struct client_vtable
-{
- /// Process incoming data; "len == 0" means EOF.
- /// If the method returns false, client_close() is called by the caller.
- bool (*push) (struct client *client, const void *data, size_t len);
-
- // TODO: optional push_error() to inform about network I/O errors
-
- /// Attempt a graceful shutdown: make any appropriate steps before
- /// the client connection times out and gets torn down by force.
- /// The client is allowed to destroy itself immediately.
- void (*shutdown) (struct client *client);
-
- /// Do any additional cleanup for the concrete class before destruction
- void (*finalize) (struct client *client);
-};
-
-static void
-client_destroy (struct client *self)
-{
- // XXX: this codebase halfway pretends there could be other contexts
- struct server_context *ctx = ev_userdata (EV_DEFAULT);
- LIST_UNLINK (ctx->clients, self);
- ctx->n_clients--;
-
- // First uninitialize the higher-level implementation
- self->vtable->finalize (self);
-
- ev_io_stop (EV_DEFAULT_ &self->read_watcher);
- ev_io_stop (EV_DEFAULT_ &self->write_watcher);
- xclose (self->socket_fd);
- write_queue_free (&self->write_queue);
- ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
- free (self);
-
- try_finish_quit (ctx);
-}
-
-static void
-client_write (struct client *self, const void *data, size_t len)
-{
- if (!soft_assert (!self->flushing) || len == 0)
- return;
-
- struct write_req *req = xcalloc (1, sizeof *req);
- req->data.iov_base = memcpy (xmalloc (len), data, len);
- req->data.iov_len = len;
-
- write_queue_add (&self->write_queue, req);
- ev_io_start (EV_DEFAULT_ &self->write_watcher);
-}
-
-/// Half-close the connection from our side once the write_queue is flushed.
-/// It is the caller's responsibility to destroy the connection upon EOF.
-// XXX: or we might change on_client_readable to do it anyway, seems safe
-static void
-client_shutdown (struct client *self)
-{
- self->flushing = true;
- ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE);
-}
-
-/// Try to cleanly close the connection, waiting for the remote client to close
-/// its own side of the connection as a sign that it has processed all the data
-/// it wanted to. The client implementation will not receive any further data.
-/// May directly call client_destroy().
-static void
-client_close (struct client *self)
-{
- if (self->closing)
- return;
-
- self->closing = true;
- ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
- client_shutdown (self);
-
- // We assume the remote client doesn't want our data if it half-closes
- if (self->received_eof)
- client_destroy (self);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_read_loop (EV_P_ struct client *client, ev_io *watcher)
-{
- char buf[8192];
- ssize_t n_read;
-again:
- while ((n_read = recv (watcher->fd, buf, sizeof buf, 0)) >= 0)
- {
- if (!n_read)
- {
- // Don't deliver the EOF condition repeatedly
- ev_io_stop (EV_A_ watcher);
- client->received_eof = true;
- }
- if (!client->closing
- && !client->vtable->push (client, buf, n_read))
- {
- client_close (client);
- return false;
- }
- if (!n_read)
- return true;
- }
- if (errno == EINTR)
- goto again;
- if (errno == EAGAIN)
- return true;
-
- client_destroy (client);
- return false;
-}
-
-static void
-on_client_readable (EV_P_ ev_io *watcher, int revents)
-{
- struct client *client = watcher->data;
- (void) revents;
-
- if (client_read_loop (EV_A_ client, watcher)
- && client->closing && client->received_eof)
- client_destroy (client);
-}
-
-static void
-on_client_writable (EV_P_ ev_io *watcher, int revents)
-{
- struct client *client = watcher->data;
- (void) loop;
- (void) revents;
-
- // TODO: some sort of "on_buffers_flushed" callback for streaming huge
- // chunks of external (or generated) data. That will need to be
- // forwarded to "struct request_handler".
- if (!flush_queue (&client->write_queue, watcher->fd))
- {
- client_destroy (client);
- return;
- }
- if (!write_queue_is_empty (&client->write_queue))
- return;
-
- ev_io_stop (EV_A_ watcher);
- if (client->flushing && !client->half_closed)
- {
- if (!shutdown (client->socket_fd, SHUT_WR))
- client->half_closed = true;
- else
- client_destroy (client);
- }
-}
-
-static void
-on_client_timeout (EV_P_ ev_timer *watcher, int revents)
-{
- (void) loop;
- (void) revents;
-
- client_destroy (watcher->data);
-}
-
-/// Create a new instance of a subclass with the given size.
-/// The superclass is assumed to be the first member of the structure.
-static void *
-client_new (EV_P_ size_t size, int sock_fd)
-{
- struct server_context *ctx = ev_userdata (loop);
- struct client *self = xcalloc (1, size);
-
- self->write_queue = write_queue_make ();
- ev_timer_init (&self->close_timeout_watcher, on_client_timeout, 5., 0.);
- self->close_timeout_watcher.data = self;
-
- set_blocking (sock_fd, false);
- self->socket_fd = sock_fd;
-
- ev_io_init (&self->read_watcher, on_client_readable, sock_fd, EV_READ);
- ev_io_init (&self->write_watcher, on_client_writable, sock_fd, EV_WRITE);
- self->read_watcher.data = self;
- self->write_watcher.data = self;
-
- // We're only interested in reading as the write queue is empty now
- ev_io_start (EV_A_ &self->read_watcher);
-
- LIST_PREPEND (ctx->clients, self);
- ctx->n_clients++;
- return self;
-}
-
-// --- FastCGI client handler --------------------------------------------------
-
-struct client_fcgi
-{
- struct client client; ///< Parent class
- struct fcgi_muxer muxer; ///< FastCGI de/multiplexer
-};
-
-struct client_fcgi_request
-{
- struct fcgi_request *fcgi_request; ///< FastCGI request
- struct request request; ///< Request
-};
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-client_fcgi_request_write_cb (struct request *req, const void *data, size_t len)
-{
- FIND_CONTAINER (self, req, struct client_fcgi_request, request);
- fcgi_request_write (self->fcgi_request, data, len);
-}
-
-static void
-client_fcgi_request_close_cb (struct request *req)
-{
- FIND_CONTAINER (self, req, struct client_fcgi_request, request);
- struct fcgi_muxer *muxer = self->fcgi_request->muxer;
- // No more data to send, terminate the substream/request,
- // and also the transport if the client didn't specifically ask to keep it
- if (!fcgi_request_finish (self->fcgi_request))
- muxer->close_cb (muxer);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_fcgi_request_start (struct fcgi_request *fcgi_request)
-{
- struct client_fcgi_request *request =
- fcgi_request->handler_data = xcalloc (1, sizeof *request);
- request->fcgi_request = fcgi_request;
- request_init (&request->request);
- request->request.ctx = ev_userdata (EV_DEFAULT);
- request->request.write_cb = client_fcgi_request_write_cb;
- request->request.close_cb = client_fcgi_request_close_cb;
-
- return request_start (&request->request, &fcgi_request->headers);
-}
-
-static bool
-client_fcgi_request_push
- (struct fcgi_request *req, const void *data, size_t len)
-{
- struct client_fcgi_request *request = req->handler_data;
- return request_push (&request->request, data, len)
- || fcgi_request_finish (req);
-}
-
-static void
-client_fcgi_request_finalize (struct fcgi_request *req)
-{
- struct client_fcgi_request *request = req->handler_data;
- request_free (&request->request);
- free (request);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-client_fcgi_write_cb (struct fcgi_muxer *mux, const void *data, size_t len)
-{
- FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
- client_write (&self->client, data, len);
-}
-
-static void
-client_fcgi_close_cb (struct fcgi_muxer *mux)
-{
- FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
- client_close (&self->client);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_fcgi_push (struct client *client, const void *data, size_t len)
-{
- FIND_CONTAINER (self, client, struct client_fcgi, client);
- return fcgi_muxer_push (&self->muxer, data, len);
-}
-
-static void
-client_fcgi_shutdown (struct client *client)
-{
- FIND_CONTAINER (self, client, struct client_fcgi, client);
- self->muxer.in_shutdown = true;
-
- // TODO: respond with FCGI_END_REQUEST: FCGI_REQUEST_COMPLETE to everything?
- // The FastCGI specification isn't very clear about what we should do.
-}
-
-static void
-client_fcgi_finalize (struct client *client)
-{
- FIND_CONTAINER (self, client, struct client_fcgi, client);
- fcgi_muxer_free (&self->muxer);
-}
-
-static struct client_vtable client_fcgi_vtable =
-{
- .push = client_fcgi_push,
- .shutdown = client_fcgi_shutdown,
- .finalize = client_fcgi_finalize,
-};
-
-static struct client *
-client_fcgi_create (EV_P_ int sock_fd)
-{
- struct client_fcgi *self = client_new (EV_A_ sizeof *self, sock_fd);
- self->client.vtable = &client_fcgi_vtable;
-
- fcgi_muxer_init (&self->muxer);
- self->muxer.write_cb = client_fcgi_write_cb;
- self->muxer.close_cb = client_fcgi_close_cb;
- self->muxer.request_start_cb = client_fcgi_request_start;
- self->muxer.request_push_cb = client_fcgi_request_push;
- self->muxer.request_finalize_cb = client_fcgi_request_finalize;
- return &self->client;
-}
-
-// --- SCGI client handler -----------------------------------------------------
-
-struct client_scgi
-{
- struct client client; ///< Parent class
- struct scgi_parser parser; ///< SCGI stream parser
- struct request request; ///< Request (only one per connection)
- unsigned long remaining_content; ///< Length of input data to be seen
-};
-
-static void
-client_scgi_write_cb (struct request *req, const void *data, size_t len)
-{
- FIND_CONTAINER (self, req, struct client_scgi, request);
- client_write (&self->client, data, len);
-}
-
-static void
-client_scgi_close_cb (struct request *req)
-{
- FIND_CONTAINER (self, req, struct client_scgi, request);
- // NOTE: this rather really means "close me [the request]"
- client_close (&self->client);
-}
-
-static bool
-client_scgi_on_headers_read (void *user_data)
-{
- struct client_scgi *self = user_data;
- const char *cl = str_map_find (&self->parser.headers, "CONTENT_LENGTH");
- if (!cl || !xstrtoul (&self->remaining_content, cl, 10))
- {
- print_debug ("SCGI request with invalid or missing CONTENT_LENGTH");
- return false;
- }
- return request_start (&self->request, &self->parser.headers);
-}
-
-static bool
-client_scgi_on_content (void *user_data, const void *data, size_t len)
-{
- struct client_scgi *self = user_data;
- if (len > self->remaining_content)
- {
- print_debug ("SCGI request got more data than CONTENT_LENGTH");
- return false;
- }
- // We're in a slight disagreement with the specification since
- // this tries to write output before it has read all the input
- if (!request_push (&self->request, data, len))
- return false;
-
- // Signalise end of input to the request handler
- return (self->remaining_content -= len) != 0
- || request_push (&self->request, NULL, 0);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_scgi_push (struct client *client, const void *data, size_t len)
-{
- struct client_scgi *self = (struct client_scgi *) client;
- struct error *e = NULL;
- if (scgi_parser_push (&self->parser, data, len, &e))
- return true;
-
- if (e != NULL)
- {
- print_debug ("SCGI parser failed: %s", e->message);
- error_free (e);
- }
- return false;
-}
-
-static void
-client_scgi_finalize (struct client *client)
-{
- struct client_scgi *self = (struct client_scgi *) client;
- request_free (&self->request);
- scgi_parser_free (&self->parser);
-}
-
-static struct client_vtable client_scgi_vtable =
-{
- .push = client_scgi_push,
- .finalize = client_scgi_finalize,
-};
-
-static struct client *
-client_scgi_create (EV_P_ int sock_fd)
-{
- struct client_scgi *self = client_new (EV_A_ sizeof *self, sock_fd);
- self->client.vtable = &client_scgi_vtable;
-
- request_init (&self->request);
- self->request.ctx = ev_userdata (EV_DEFAULT);
- self->request.write_cb = client_scgi_write_cb;
- self->request.close_cb = client_scgi_close_cb;
-
- self->parser = scgi_parser_make ();
- self->parser.on_headers_read = client_scgi_on_headers_read;
- self->parser.on_content = client_scgi_on_content;
- self->parser.user_data = self;
- return &self->client;
-}
-
-// --- WebSockets client handler -----------------------------------------------
-
-struct client_ws
-{
- struct client client; ///< Parent class
- struct ws_handler handler; ///< WebSockets connection handler
-};
-
-static bool
-client_ws_on_message (struct ws_handler *handler,
- enum ws_opcode type, const void *data, size_t len)
-{
- FIND_CONTAINER (self, handler, struct client_ws, handler);
- if (type != WS_OPCODE_TEXT)
- {
- return ws_handler_fail_connection
- (&self->handler, WS_STATUS_UNSUPPORTED_DATA);
- }
-
- struct server_context *ctx = ev_userdata (EV_DEFAULT);
- struct str response = str_make ();
- process_json_rpc (ctx, data, len, &response);
- if (response.len)
- ws_handler_send_frame (&self->handler,
- WS_OPCODE_TEXT, response.str, response.len);
- str_free (&response);
- return true;
-}
-
-static void
-client_ws_write_cb (struct ws_handler *handler, const void *data, size_t len)
-{
- FIND_CONTAINER (self, handler, struct client_ws, handler);
- client_write (&self->client, data, len);
-}
-
-static void
-client_ws_close_cb (struct ws_handler *handler, bool half_close)
-{
- FIND_CONTAINER (self, handler, struct client_ws, handler);
- (half_close ? client_shutdown : client_destroy) (&self->client);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_ws_push (struct client *client, const void *data, size_t len)
-{
- FIND_CONTAINER (self, client, struct client_ws, client);
- // client_close() will correctly destroy the client on EOF
- return ws_handler_push (&self->handler, data, len);
-}
-
-static void
-client_ws_shutdown (struct client *client)
-{
- FIND_CONTAINER (self, client, struct client_ws, client);
- if (self->handler.state == WS_HANDLER_CONNECTING)
- // No on_close, no problem
- client_destroy (&self->client);
- else if (self->handler.state == WS_HANDLER_OPEN)
- ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0);
-}
-
-static void
-client_ws_finalize (struct client *client)
-{
- FIND_CONTAINER (self, client, struct client_ws, client);
- ws_handler_free (&self->handler);
-}
-
-static struct client_vtable client_ws_vtable =
-{
- .push = client_ws_push,
- .shutdown = client_ws_shutdown,
- .finalize = client_ws_finalize,
-};
-
-static struct client *
-client_ws_create (EV_P_ int sock_fd)
-{
- struct client_ws *self = client_new (EV_A_ sizeof *self, sock_fd);
- self->client.vtable = &client_ws_vtable;
-
- ws_handler_init (&self->handler);
- self->handler.on_message = client_ws_on_message;
- self->handler.write_cb = client_ws_write_cb;
- self->handler.close_cb = client_ws_close_cb;
-
- // One mebibyte seems to be a reasonable value
- self->handler.max_payload_len = 1 << 10;
-
- ws_handler_start (&self->handler);
- return &self->client;
-}
-
-// --- Basic server stuff ------------------------------------------------------
-
-typedef struct client *(*client_create_fn) (EV_P_ int sock_fd);
-
-struct listener
-{
- int fd; ///< Listening socket FD
- ev_io watcher; ///< New connection available
- client_create_fn create; ///< Client constructor
-};
-
-static void
-close_listeners (struct server_context *self)
-{
- for (size_t i = 0; i < self->n_listeners; i++)
- {
- struct listener *listener = &self->listeners[i];
- if (listener->fd == -1)
- continue;
-
- ev_io_stop (EV_DEFAULT_ &listener->watcher);
- xclose (listener->fd);
- listener->fd = -1;
- }
-}
-
-static void
-try_finish_quit (struct server_context *self)
-{
- if (!self->quitting || self->clients)
- return;
-
- ev_timer_stop (EV_DEFAULT_ &self->quit_timeout_watcher);
- ev_break (EV_DEFAULT_ EVBREAK_ALL);
-}
-
-static void
-on_quit_timeout (EV_P_ ev_timer *watcher, int revents)
-{
- struct server_context *self = watcher->data;
- (void) loop;
- (void) revents;
-
- LIST_FOR_EACH (struct client, iter, self->clients)
- client_destroy (iter);
-}
-
-static void
-initiate_quit (struct server_context *self)
-{
- self->quitting = true;
- close_listeners (self);
-
- // Wait a little while for all clients to clean up, if necessary
- LIST_FOR_EACH (struct client, iter, self->clients)
- if (iter->vtable->shutdown)
- iter->vtable->shutdown (iter);
- ev_timer_start (EV_DEFAULT_ &self->quit_timeout_watcher);
- try_finish_quit (self);
-}
-
-static void
-on_client_available (EV_P_ ev_io *watcher, int revents)
-{
- struct server_context *ctx = ev_userdata (loop);
- struct listener *listener = watcher->data;
- (void) revents;
-
- while (true)
- {
- int sock_fd = accept (watcher->fd, NULL, NULL);
- if (sock_fd != -1)
- listener->create (EV_A_ sock_fd);
- else if (errno == EAGAIN)
- return;
- else if (errno != EINTR && errno != EMFILE
- && errno != ECONNRESET && errno != ECONNABORTED)
- break;
- }
-
- // Stop accepting connections to prevent busy looping
- ev_io_stop (EV_A_ watcher);
-
- print_fatal ("%s: %s", "accept", strerror (errno));
- initiate_quit (ctx);
-}
-
-// --- Application setup -------------------------------------------------------
-
-/// This function handles values that require validation before their first use,
-/// or some kind of a transformation (such as conversion to an integer) needs
-/// to be done before they can be used directly.
-static bool
-parse_config (struct server_context *ctx, struct error **e)
-{
- (void) ctx;
- (void) e;
-
- return true;
-}
-
-static int
-listener_bind (struct addrinfo *gai_iter)
-{
- int fd = socket (gai_iter->ai_family,
- gai_iter->ai_socktype, gai_iter->ai_protocol);
- if (fd == -1)
- return -1;
- set_cloexec (fd);
-
- int yes = 1;
- soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE,
- &yes, sizeof yes) != -1);
- soft_assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
- &yes, sizeof yes) != -1);
-
- char host[NI_MAXHOST], port[NI_MAXSERV];
- host[0] = port[0] = '\0';
- int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen,
- host, sizeof host, port, sizeof port,
- NI_NUMERICHOST | NI_NUMERICSERV);
- if (err)
- print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
-
- char *address = format_host_port_pair (host, port);
- if (bind (fd, gai_iter->ai_addr, gai_iter->ai_addrlen))
- print_error ("bind to %s failed: %s", address, strerror (errno));
- else if (listen (fd, 16 /* arbitrary number */))
- print_error ("listen on %s failed: %s", address, strerror (errno));
- else
- {
- print_status ("listening on %s", address);
- free (address);
- return fd;
- }
-
- free (address);
- xclose (fd);
- return -1;
-}
-
-static void
-listener_add (struct server_context *ctx, const char *host, const char *port,
- const struct addrinfo *gai_hints, client_create_fn create)
-{
- struct addrinfo *gai_result, *gai_iter;
- int err = getaddrinfo (host, port, gai_hints, &gai_result);
- if (err)
- {
- char *address = format_host_port_pair (host, port);
- print_error ("bind to %s failed: %s: %s",
- address, "getaddrinfo", gai_strerror (err));
- free (address);
- return;
- }
-
- int fd;
- for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next)
- {
- if ((fd = listener_bind (gai_iter)) == -1)
- continue;
- set_blocking (fd, false);
-
- struct listener *listener = &ctx->listeners[ctx->n_listeners++];
- ev_io_init (&listener->watcher, on_client_available, fd, EV_READ);
- ev_io_start (EV_DEFAULT_ &listener->watcher);
- listener->watcher.data = listener;
- listener->create = create;
- listener->fd = fd;
- break;
- }
- freeaddrinfo (gai_result);
-}
-
-static void
-get_ports_from_config (struct server_context *ctx,
- const char *key, struct strv *out)
-{
- const char *ports;
- if ((ports = str_map_find (&ctx->config, key)))
- cstr_split (ports, ",", true, out);
-}
-
-static bool
-setup_listen_fds (struct server_context *ctx, struct error **e)
-{
- static const struct addrinfo gai_hints =
- {
- .ai_socktype = SOCK_STREAM,
- .ai_flags = AI_PASSIVE,
- };
-
- struct strv ports_fcgi = strv_make ();
- struct strv ports_scgi = strv_make ();
- struct strv ports_ws = strv_make ();
-
- get_ports_from_config (ctx, "port_fastcgi", &ports_fcgi);
- get_ports_from_config (ctx, "port_scgi", &ports_scgi);
- get_ports_from_config (ctx, "port_ws", &ports_ws);
-
- const char *bind_host = str_map_find (&ctx->config, "bind_host");
- size_t n_ports = ports_fcgi.len + ports_scgi.len + ports_ws.len;
- ctx->listeners = xcalloc (n_ports, sizeof *ctx->listeners);
-
- for (size_t i = 0; i < ports_fcgi.len; i++)
- listener_add (ctx, bind_host, ports_fcgi.vector[i],
- &gai_hints, client_fcgi_create);
- for (size_t i = 0; i < ports_scgi.len; i++)
- listener_add (ctx, bind_host, ports_scgi.vector[i],
- &gai_hints, client_scgi_create);
- for (size_t i = 0; i < ports_ws.len; i++)
- listener_add (ctx, bind_host, ports_ws.vector[i],
- &gai_hints, client_ws_create);
-
- strv_free (&ports_fcgi);
- strv_free (&ports_scgi);
- strv_free (&ports_ws);
-
- if (!ctx->n_listeners)
- {
- error_set (e, "%s: %s",
- "network setup failed", "no ports to listen on");
- return false;
- }
- return true;
-}
-
-static bool
-app_lock_pid_file (struct server_context *ctx, struct error **e)
-{
- const char *path = str_map_find (&ctx->config, "pid_file");
- if (!path)
- return true;
-
- char *resolved = resolve_filename (path, resolve_relative_runtime_filename);
- bool result = lock_pid_file (resolved, e) != -1;
- free (resolved);
- return result;
-}
-
-// --- Tests -------------------------------------------------------------------
-
-static void
-test_misc (void)
-{
- soft_assert ( validate_json_rpc_content_type
- ("application/JSON; charset=\"utf-8\""));
- soft_assert (!validate_json_rpc_content_type
- ("text/html; charset=\"utf-8\""));
-
- char *canon = canonicalize_url_path ("///../../../etc/./passwd");
- soft_assert (!strcmp (canon, "/etc/passwd"));
- free (canon);
-}
-
-int
-test_main (int argc, char *argv[])
-{
- struct test test;
- test_init (&test, argc, argv);
-
- test_add_simple (&test, "/misc", NULL, test_misc);
-
- // TODO: write more tests
- // TODO: test the server handler (happy path)
-
- return test_run (&test);
-}
-
-// --- Main program ------------------------------------------------------------
-
-static void
-on_termination_signal (EV_P_ ev_signal *handle, int revents)
-{
- struct server_context *ctx = ev_userdata (loop);
- (void) handle;
- (void) revents;
-
- if (ctx->quitting)
- {
- // Double C-c from the terminal accelerates the process
- LIST_FOR_EACH (struct client, iter, ctx->clients)
- client_destroy (iter);
- }
- else
- initiate_quit (ctx);
-}
-
-static void
-setup_signal_handlers (struct server_context *ctx)
-{
- ev_signal_init (&ctx->sigterm_watcher, on_termination_signal, SIGTERM);
- ev_signal_start (EV_DEFAULT_ &ctx->sigterm_watcher);
-
- ev_signal_init (&ctx->sigint_watcher, on_termination_signal, SIGINT);
- ev_signal_start (EV_DEFAULT_ &ctx->sigint_watcher);
-
- (void) signal (SIGPIPE, SIG_IGN);
-}
-
-static void
-daemonize (struct server_context *ctx)
-{
- print_status ("daemonizing...");
-
- if (chdir ("/"))
- exit_fatal ("%s: %s", "chdir", strerror (errno));
-
- // Because of systemd, we need to exit the parent process _after_ writing
- // a PID file, otherwise our grandchild would receive a SIGTERM
- int sync_pipe[2];
- if (pipe (sync_pipe))
- exit_fatal ("%s: %s", "pipe", strerror (errno));
-
- pid_t pid;
- if ((pid = fork ()) < 0)
- exit_fatal ("%s: %s", "fork", strerror (errno));
- else if (pid)
- {
- // Wait until all write ends of the pipe are closed, which can mean
- // either success or failure, we don't need to care
- xclose (sync_pipe[PIPE_WRITE]);
-
- char dummy;
- if (read (sync_pipe[PIPE_READ], &dummy, 1) < 0)
- exit_fatal ("%s: %s", "read", strerror (errno));
-
- exit (EXIT_SUCCESS);
- }
-
- setsid ();
- signal (SIGHUP, SIG_IGN);
-
- if ((pid = fork ()) < 0)
- exit_fatal ("%s: %s", "fork", strerror (errno));
- else if (pid)
- exit (EXIT_SUCCESS);
-
- openlog (PROGRAM_NAME, LOG_NDELAY | LOG_NOWAIT | LOG_PID, 0);
- g_log_message_real = log_message_syslog;
-
- // Write the PID file (if so configured) and get rid of the pipe, so that
- // the read() in our grandparent finally returns zero (no write ends)
- struct error *e = NULL;
- if (!app_lock_pid_file (ctx, &e))
- exit_fatal ("%s", e->message);
-
- xclose (sync_pipe[PIPE_READ]);
- xclose (sync_pipe[PIPE_WRITE]);
-
- // XXX: we may close our own descriptors this way, crippling ourselves;
- // there is no real guarantee that we will start with all three
- // descriptors open. In theory we could try to enumerate the descriptors
- // at the start of main().
- for (int i = 0; i < 3; i++)
- xclose (i);
-
- int tty = open ("/dev/null", O_RDWR);
- if (tty != 0 || dup (0) != 1 || dup (0) != 2)
- exit_fatal ("failed to reopen FD's: %s", strerror (errno));
-}
-
-static void
-parse_program_arguments (int argc, char **argv)
-{
- static const struct opt opts[] =
- {
- { 't', "test", NULL, 0, "self-test" },
- { 'd', "debug", NULL, 0, "run in debug mode" },
- { 'h', "help", NULL, 0, "display this help and exit" },
- { 'V', "version", NULL, 0, "output version information and exit" },
- { 'w', "write-default-cfg", "FILENAME",
- OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
- "write a default configuration file and exit" },
- { 0, NULL, NULL, 0, NULL }
- };
-
- struct opt_handler oh =
- opt_handler_make (argc, argv, opts, NULL, "JSON-RPC 2.0 demo server.");
-
- int c;
- while ((c = opt_handler_get (&oh)) != -1)
- switch (c)
- {
- case 't':
- test_main (argc, argv);
- exit (EXIT_SUCCESS);
- case 'd':
- g_debug_mode = true;
- break;
- case 'h':
- opt_handler_usage (&oh, stdout);
- exit (EXIT_SUCCESS);
- case 'V':
- printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
- exit (EXIT_SUCCESS);
- case 'w':
- call_simple_config_write_default (optarg, g_config_table);
- exit (EXIT_SUCCESS);
- default:
- print_error ("wrong options");
- opt_handler_usage (&oh, stderr);
- exit (EXIT_FAILURE);
- }
-
- argc -= optind;
- argv += optind;
-
- if (argc)
- {
- opt_handler_usage (&oh, stderr);
- exit (EXIT_FAILURE);
- }
- opt_handler_free (&oh);
-}
-
-int
-main (int argc, char *argv[])
-{
- parse_program_arguments (argc, argv);
-
- print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
-
- struct server_context ctx;
- server_context_init (&ctx);
-
- struct error *e = NULL;
- if (!simple_config_update_from_file (&ctx.config, &e))
- {
- print_error ("error loading configuration: %s", e->message);
- error_free (e);
- exit (EXIT_FAILURE);
- }
-
- struct ev_loop *loop;
- if (!(loop = EV_DEFAULT))
- exit_fatal ("libev initialization failed");
-
- ev_set_userdata (loop, &ctx);
- setup_signal_handlers (&ctx);
-
- LIST_PREPEND (ctx.handlers, &g_request_handler_static);
- LIST_PREPEND (ctx.handlers, &g_request_handler_json_rpc);
-
- if (!parse_config (&ctx, &e)
- || !setup_listen_fds (&ctx, &e))
- {
- print_error ("%s", e->message);
- error_free (e);
- exit (EXIT_FAILURE);
- }
-
- if (!g_debug_mode)
- daemonize (&ctx);
- else if (!app_lock_pid_file (&ctx, &e))
- exit_fatal ("%s", e->message);
-
- ev_run (loop, 0);
- ev_loop_destroy (loop);
-
- server_context_free (&ctx);
- return EXIT_SUCCESS;
-}
diff --git a/json-rpc-test-server.c b/json-rpc-test-server.c
new file mode 100644
index 0000000..ac15f0a
--- /dev/null
+++ b/json-rpc-test-server.c
@@ -0,0 +1,2925 @@
+/*
+ * json-rpc-test-server.c: JSON-RPC 2.0 demo server
+ *
+ * Copyright (c) 2015 - 2018, Přemysl Janouch
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#define print_fatal_data ((void *) LOG_ERR)
+#define print_error_data ((void *) LOG_ERR)
+#define print_warning_data ((void *) LOG_WARNING)
+#define print_status_data ((void *) LOG_INFO)
+#define print_debug_data ((void *) LOG_DEBUG)
+
+#define LIBERTY_WANT_SSL
+#define LIBERTY_WANT_PROTO_HTTP
+#define LIBERTY_WANT_PROTO_WS
+#define LIBERTY_WANT_PROTO_SCGI
+#define LIBERTY_WANT_PROTO_FASTCGI
+
+#include "config.h"
+#include "liberty/liberty.c"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "http-parser/http_parser.h"
+
+enum { PIPE_READ, PIPE_WRITE };
+
+#define FIND_CONTAINER(name, pointer, type, member) \
+ type *name = CONTAINER_OF (pointer, type, member)
+
+// --- Utilities ---------------------------------------------------------------
+
+static bool
+flush_queue (struct write_queue *queue, int fd)
+{
+ struct iovec vec[queue->len], *vec_iter = vec;
+ LIST_FOR_EACH (struct write_req, iter, queue->head)
+ *vec_iter++ = iter->data;
+
+ ssize_t written;
+again:
+ if ((written = writev (fd, vec, N_ELEMENTS (vec))) >= 0)
+ {
+ write_queue_processed (queue, written);
+ return true;
+ }
+ if (errno == EINTR)
+ goto again;
+ if (errno == EAGAIN)
+ return true;
+
+ return false;
+}
+
+// --- Logging -----------------------------------------------------------------
+
+static void
+log_message_syslog (void *user_data, const char *quote, const char *fmt,
+ va_list ap)
+{
+ int prio = (int) (intptr_t) user_data;
+
+ va_list va;
+ va_copy (va, ap);
+ int size = vsnprintf (NULL, 0, fmt, va);
+ va_end (va);
+ if (size < 0)
+ return;
+
+ char buf[size + 1];
+ if (vsnprintf (buf, sizeof buf, fmt, ap) >= 0)
+ syslog (prio, "%s%s", quote, buf);
+}
+
+// --- FastCGI -----------------------------------------------------------------
+/// @defgroup FastCGI
+/// @{
+
+enum fcgi_request_state
+{
+ FCGI_REQUEST_PARAMS, ///< Reading headers
+ FCGI_REQUEST_STDIN ///< Reading input
+};
+
+struct fcgi_request
+{
+ struct fcgi_muxer *muxer; ///< The parent muxer
+ uint16_t request_id; ///< The ID of this request
+ uint8_t flags; ///< Request flags
+
+ enum fcgi_request_state state; ///< Parsing state
+ struct str_map headers; ///< Headers
+ struct fcgi_nv_parser hdr_parser; ///< Header parser
+
+ struct str output_buffer; ///< Output buffer
+
+ void *handler_data; ///< Handler data
+};
+
+/// Handles a single FastCGI connection, de/multiplexing requests and responses
+struct fcgi_muxer
+{
+ struct fcgi_parser parser; ///< FastCGI message parser
+ uint32_t active_requests; ///< The number of active requests
+ bool in_shutdown; ///< Rejecting new requests
+
+ // Virtual method callbacks:
+
+ /// Write data to the underlying transport
+ void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len);
+
+ /// Close the underlying transport. You are allowed to destroy the muxer
+ /// directly from within the callback.
+ void (*close_cb) (struct fcgi_muxer *);
+
+ /// Start processing a request. Return false if no further action is
+ /// to be done and the request should be finished.
+ bool (*request_start_cb) (struct fcgi_request *);
+
+ /// Handle incoming data. "len == 0" means EOF. Returns false if
+ /// the underlying transport should be closed, this being the last request.
+ bool (*request_push_cb)
+ (struct fcgi_request *, const void *data, size_t len);
+
+ /// Destroy the handler's data stored in the request object
+ void (*request_finalize_cb) (struct fcgi_request *);
+
+ /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID)
+ struct fcgi_request *requests[1 << 8];
+};
+
+static void
+fcgi_muxer_send (struct fcgi_muxer *self,
+ enum fcgi_type type, uint16_t request_id, const void *data, size_t len)
+{
+ hard_assert (len <= UINT16_MAX);
+
+ struct str message = str_make ();
+ static char zeroes[8];
+ size_t padding = -len & 7;
+
+ str_pack_u8 (&message, FCGI_VERSION_1);
+ str_pack_u8 (&message, type);
+ str_pack_u16 (&message, request_id);
+ str_pack_u16 (&message, len); // content length
+ str_pack_u8 (&message, padding); // padding length
+ str_pack_u8 (&message, 0); // reserved
+
+ str_append_data (&message, data, len);
+ str_append_data (&message, zeroes, padding);
+
+ // XXX: we should probably have another write_cb that assumes ownership
+ self->write_cb (self, message.str, message.len);
+ str_free (&message);
+}
+
+static void
+fcgi_muxer_send_end_request (struct fcgi_muxer *self, uint16_t request_id,
+ uint32_t app_status, enum fcgi_protocol_status protocol_status)
+{
+ uint8_t content[8] = { app_status >> 24, app_status >> 16,
+ app_status << 8, app_status, protocol_status };
+ fcgi_muxer_send (self, FCGI_END_REQUEST, request_id,
+ content, sizeof content);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static struct fcgi_request *
+fcgi_request_new (void)
+{
+ struct fcgi_request *self = xcalloc (1, sizeof *self);
+
+ self->headers = str_map_make (free);
+
+ self->hdr_parser = fcgi_nv_parser_make ();
+ self->hdr_parser.output = &self->headers;
+
+ self->output_buffer = str_make ();
+ return self;
+}
+
+static void
+fcgi_request_destroy (struct fcgi_request *self)
+{
+ // TODO: consider the case where it hasn't been started yet
+ self->muxer->request_finalize_cb (self);
+
+ str_map_free (&self->headers);
+ fcgi_nv_parser_free (&self->hdr_parser);
+ free (self);
+}
+
+static void
+fcgi_request_flush (struct fcgi_request *self)
+{
+ if (!self->output_buffer.len)
+ return;
+
+ fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id,
+ self->output_buffer.str, self->output_buffer.len);
+ str_reset (&self->output_buffer);
+}
+
+static void
+fcgi_request_write (struct fcgi_request *self, const void *data, size_t len)
+{
+ // We're buffering the output and splitting it into messages
+ bool need_flush = true;
+ while (len)
+ {
+ size_t to_write = UINT16_MAX - self->output_buffer.len;
+ if (to_write > len)
+ {
+ to_write = len;
+ need_flush = false;
+ }
+
+ str_append_data (&self->output_buffer, data, to_write);
+ data = (uint8_t *) data + to_write;
+ len -= to_write;
+
+ if (need_flush)
+ fcgi_request_flush (self);
+ }
+}
+
+/// Mark the request as done. Returns false if the underlying transport
+/// should be closed, this being the last request.
+static bool
+fcgi_request_finish (struct fcgi_request *self)
+{
+ fcgi_request_flush (self);
+ fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id, NULL, 0);
+
+ fcgi_muxer_send_end_request (self->muxer, self->request_id,
+ 0 /* TODO app_status, although ignored */,
+ FCGI_REQUEST_COMPLETE /* TODO protocol_status, may be different */);
+
+ bool should_close = !(self->flags & FCGI_KEEP_CONN);
+
+ self->muxer->active_requests--;
+ self->muxer->requests[self->request_id] = NULL;
+ fcgi_request_destroy (self);
+
+ return !should_close;
+}
+
+static bool
+fcgi_request_push_params
+ (struct fcgi_request *self, const void *data, size_t len)
+{
+ if (self->state != FCGI_REQUEST_PARAMS)
+ {
+ print_debug ("FastCGI: expected %s, got %s",
+ STRINGIFY (FCGI_STDIN), STRINGIFY (FCGI_PARAMS));
+ return false;
+ }
+
+ if (len)
+ fcgi_nv_parser_push (&self->hdr_parser, data, len);
+ else
+ {
+ if (self->hdr_parser.state != FCGI_NV_PARSER_NAME_LEN)
+ print_debug ("FastCGI: request headers seem to be cut off");
+
+ self->state = FCGI_REQUEST_STDIN;
+ if (!self->muxer->request_start_cb (self))
+ return fcgi_request_finish (self);
+ }
+ return true;
+}
+
+static bool
+fcgi_request_push_stdin
+ (struct fcgi_request *self, const void *data, size_t len)
+{
+ if (self->state != FCGI_REQUEST_STDIN)
+ {
+ print_debug ("FastCGI: expected %s, got %s",
+ STRINGIFY (FCGI_PARAMS), STRINGIFY (FCGI_STDIN));
+ return false;
+ }
+
+ return self->muxer->request_push_cb (self, data, len);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+typedef bool (*fcgi_muxer_handler_fn)
+ (struct fcgi_muxer *, const struct fcgi_parser *);
+
+static bool
+fcgi_muxer_on_get_values
+ (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ if (parser->request_id != FCGI_NULL_REQUEST_ID)
+ {
+ print_debug ("FastCGI: invalid %s message",
+ STRINGIFY (FCGI_GET_VALUES));
+ return false;
+ }
+
+ struct str_map values = str_map_make (free);
+ struct str_map response = str_map_make (free);
+
+ struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make ();
+ nv_parser.output = &values;
+
+ fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len);
+ const char *key = NULL;
+
+ // No real-world servers seem to actually use multiplexing
+ // or even issue this request, but we will implement it anyway
+ if (str_map_find (&values, (key = FCGI_MPXS_CONNS)))
+ str_map_set (&response, key, xstrdup ("1"));
+
+ // It's not clear whether FCGI_MAX_REQS means concurrently over all
+ // connections or over just a single connection (multiplexed), though
+ // supposedly it's actually per /web server/. Supply the strictest limit.
+ if (str_map_find (&values, (key = FCGI_MAX_REQS)))
+ str_map_set (&response, key,
+ xstrdup_printf ("%zu", N_ELEMENTS (self->requests) - 1));
+
+ // FCGI_MAX_CONNS would be basically infinity. We don't limit connections.
+
+ struct str content = str_make ();
+ fcgi_nv_convert (&response, &content);
+ fcgi_muxer_send (self, FCGI_GET_VALUES_RESULT, parser->request_id,
+ content.str, content.len);
+ str_free (&content);
+
+ str_map_free (&values);
+ str_map_free (&response);
+ return true;
+}
+
+static bool
+fcgi_muxer_on_begin_request
+ (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ struct msg_unpacker unpacker =
+ msg_unpacker_make (parser->content.str, parser->content.len);
+
+ uint16_t role;
+ uint8_t flags;
+ bool success = true;
+ success &= msg_unpacker_u16 (&unpacker, &role);
+ success &= msg_unpacker_u8 (&unpacker, &flags);
+ // Ignoring 5 reserved bytes
+
+ if (!success)
+ {
+ print_debug ("FastCGI: invalid %s message",
+ STRINGIFY (FCGI_BEGIN_REQUEST));
+ return false;
+ }
+
+ struct fcgi_request *request = self->requests[parser->request_id];
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || request)
+ {
+ print_debug ("FastCGI: unusable request ID in %s message",
+ STRINGIFY (FCGI_BEGIN_REQUEST));
+ return false;
+ }
+
+ // We can only act as a responder, reject everything else up front
+ if (role != FCGI_RESPONDER)
+ {
+ fcgi_muxer_send_end_request (self,
+ parser->request_id, 0, FCGI_UNKNOWN_ROLE);
+ return true;
+ }
+
+ if (parser->request_id >= N_ELEMENTS (self->requests)
+ || self->in_shutdown)
+ {
+ fcgi_muxer_send_end_request (self,
+ parser->request_id, 0, FCGI_OVERLOADED);
+ return true;
+ }
+
+ request = fcgi_request_new ();
+ request->muxer = self;
+ request->request_id = parser->request_id;
+ request->flags = flags;
+
+ self->requests[parser->request_id] = request;
+ self->active_requests++;
+ return true;
+}
+
+static bool
+fcgi_muxer_on_abort_request
+ (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ struct fcgi_request *request = self->requests[parser->request_id];
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
+ {
+ print_debug ("FastCGI: received %s for an unknown request",
+ STRINGIFY (FCGI_ABORT_REQUEST));
+ return true; // We might have just rejected it
+ }
+
+ return fcgi_request_finish (request);
+}
+
+static bool
+fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ struct fcgi_request *request = self->requests[parser->request_id];
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
+ {
+ print_debug ("FastCGI: received %s for an unknown request",
+ STRINGIFY (FCGI_PARAMS));
+ return true; // We might have just rejected it
+ }
+
+ // This may immediately finish and delete the request, but that's fine
+ return fcgi_request_push_params (request,
+ parser->content.str, parser->content.len);
+}
+
+static bool
+fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ struct fcgi_request *request = self->requests[parser->request_id];
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
+ {
+ print_debug ("FastCGI: received %s for an unknown request",
+ STRINGIFY (FCGI_STDIN));
+ return true; // We might have just rejected it
+ }
+
+ // At the end of the stream, a zero-length record is received
+ return fcgi_request_push_stdin (request,
+ parser->content.str, parser->content.len);
+}
+
+static bool
+fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data)
+{
+ struct fcgi_muxer *self = user_data;
+
+ if (parser->version != FCGI_VERSION_1)
+ {
+ print_debug ("FastCGI: unsupported version %d", parser->version);
+ return false;
+ }
+
+ static const fcgi_muxer_handler_fn handlers[] =
+ {
+ [FCGI_GET_VALUES] = fcgi_muxer_on_get_values,
+ [FCGI_BEGIN_REQUEST] = fcgi_muxer_on_begin_request,
+ [FCGI_ABORT_REQUEST] = fcgi_muxer_on_abort_request,
+ [FCGI_PARAMS] = fcgi_muxer_on_params,
+ [FCGI_STDIN] = fcgi_muxer_on_stdin,
+ };
+
+ fcgi_muxer_handler_fn handler;
+ if (parser->type >= N_ELEMENTS (handlers)
+ || !(handler = handlers[parser->type]))
+ {
+ // Responding in this way even to application records, unspecified
+ uint8_t content[8] = { parser->type };
+ fcgi_muxer_send (self, FCGI_UNKNOWN_TYPE, parser->request_id,
+ content, sizeof content);
+ return true;
+ }
+
+ return handler (self, parser);
+}
+
+static void
+fcgi_muxer_init (struct fcgi_muxer *self)
+{
+ self->parser = fcgi_parser_make ();
+ self->parser.on_message = fcgi_muxer_on_message;
+ self->parser.user_data = self;
+}
+
+static void
+fcgi_muxer_free (struct fcgi_muxer *self)
+{
+ for (size_t i = 0; i < N_ELEMENTS (self->requests); i++)
+ {
+ if (!self->active_requests)
+ break;
+ if (self->requests[i])
+ {
+ fcgi_request_destroy (self->requests[i]);
+ self->active_requests--;
+ }
+ }
+
+ fcgi_parser_free (&self->parser);
+}
+
+static bool
+fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len)
+{
+ return fcgi_parser_push (&self->parser, data, len);
+}
+
+/// @}
+// --- WebSockets --------------------------------------------------------------
+/// @defgroup WebSockets
+/// @{
+
+// WebSockets aren't CGI-compatible, therefore we must handle the initial HTTP
+// handshake ourselves. Luckily it's not too much of a bother with http-parser.
+// Typically there will be a normal HTTP server in front of us, proxying the
+// requests based on the URI.
+
+enum ws_handler_state
+{
+ WS_HANDLER_CONNECTING, ///< Parsing HTTP
+ WS_HANDLER_OPEN, ///< Parsing WebSockets frames
+ WS_HANDLER_CLOSING, ///< Partial closure by us
+ WS_HANDLER_FLUSHING, ///< Just waiting for client EOF
+ WS_HANDLER_CLOSED ///< Dead, both sides closed
+};
+
+struct ws_handler
+{
+ enum ws_handler_state state; ///< State
+
+ // HTTP handshake:
+
+ http_parser hp; ///< HTTP parser
+ bool have_header_value; ///< Parsing header value or field?
+ struct str field; ///< Field part buffer
+ struct str value; ///< Value part buffer
+ struct str_map headers; ///< HTTP Headers
+ struct str url; ///< Request URL
+ ev_timer handshake_timeout_watcher; ///< Handshake timeout watcher
+
+ // WebSocket frame protocol:
+
+ struct ws_parser parser; ///< Protocol frame parser
+ bool expecting_continuation; ///< For non-control traffic
+
+ enum ws_opcode message_opcode; ///< Opcode for the current message
+ struct str message_data; ///< Concatenated message data
+
+ ev_timer ping_timer; ///< Ping timer
+ bool received_pong; ///< Received PONG since the last PING
+
+ ev_timer close_timeout_watcher; ///< Close timeout watcher
+
+ // Configuration:
+
+ unsigned handshake_timeout; ///< How long to wait for the handshake
+ unsigned close_timeout; ///< How long to wait for TCP close
+ unsigned ping_interval; ///< Ping interval in seconds
+ uint64_t max_payload_len; ///< Maximum length of any message
+
+ // Event callbacks:
+
+ // TODO: void (*on_handshake) (protocols) that will allow the user
+ // to choose any sub-protocol, if the client has provided any.
+ // This may render "on_connected" unnecessary.
+ // Should also enable failing the handshake.
+
+ /// Called after successfuly connecting (handshake complete)
+ bool (*on_connected) (struct ws_handler *);
+
+ /// Called upon reception of a single full message
+ bool (*on_message) (struct ws_handler *,
+ enum ws_opcode type, const void *data, size_t len);
+
+ /// The connection is about to close. @a close_code may, or may not, be one
+ /// of enum ws_status. The @a reason is never NULL.
+ void (*on_close) (struct ws_handler *, int close_code, const char *reason);
+
+ // Virtual method callbacks:
+
+ /// Write a chunk of data to the stream
+ void (*write_cb) (struct ws_handler *, const void *data, size_t len);
+
+ /// Close the connection. If @a half_close is false, you are allowed to
+ /// destroy the handler directly from within the callback.
+ void (*close_cb) (struct ws_handler *, bool half_close);
+};
+
+static void
+ws_handler_send_control (struct ws_handler *self,
+ enum ws_opcode opcode, const void *data, size_t len)
+{
+ if (len > WS_MAX_CONTROL_PAYLOAD_LEN)
+ {
+ print_debug ("truncating output control frame payload"
+ " from %zu to %zu bytes", len, (size_t) WS_MAX_CONTROL_PAYLOAD_LEN);
+ len = WS_MAX_CONTROL_PAYLOAD_LEN;
+ }
+
+ uint8_t header[2] = { 0x80 | (opcode & 0x0F), len };
+ self->write_cb (self, header, sizeof header);
+ self->write_cb (self, data, len);
+}
+
+static void
+ws_handler_close (struct ws_handler *self,
+ enum ws_status close_code, const char *reason, size_t len)
+{
+ hard_assert (self->state == WS_HANDLER_OPEN);
+
+ struct str payload = str_make ();
+ str_pack_u16 (&payload, close_code);
+ // XXX: maybe accept a null-terminated string on input? Has to be UTF-8 a/w
+ str_append_data (&payload, reason, len);
+ ws_handler_send_control (self, WS_OPCODE_CLOSE, payload.str, payload.len);
+ self->close_cb (self, true /* half_close */);
+
+ self->state = WS_HANDLER_CLOSING;
+ str_free (&payload);
+}
+
+static bool
+ws_handler_fail_connection (struct ws_handler *self, enum ws_status close_code)
+{
+ hard_assert (self->state == WS_HANDLER_OPEN
+ || self->state == WS_HANDLER_CLOSING);
+
+ if (self->state == WS_HANDLER_OPEN)
+ ws_handler_close (self, close_code, NULL, 0);
+
+ self->state = WS_HANDLER_FLUSHING;
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
+
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
+ ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
+ return false;
+}
+
+// TODO: add support for fragmented responses
+static void
+ws_handler_send_frame (struct ws_handler *self,
+ enum ws_opcode opcode, const void *data, size_t len)
+{
+ if (!soft_assert (self->state == WS_HANDLER_OPEN))
+ return;
+
+ struct str header = str_make ();
+ str_pack_u8 (&header, 0x80 | (opcode & 0x0F));
+
+ if (len > UINT16_MAX)
+ {
+ str_pack_u8 (&header, 127);
+ str_pack_u64 (&header, len);
+ }
+ else if (len > 125)
+ {
+ str_pack_u8 (&header, 126);
+ str_pack_u16 (&header, len);
+ }
+ else
+ str_pack_u8 (&header, len);
+
+ self->write_cb (self, header.str, header.len);
+ self->write_cb (self, data, len);
+ str_free (&header);
+}
+
+static bool
+ws_handler_on_frame_header (void *user_data, const struct ws_parser *parser)
+{
+ struct ws_handler *self = user_data;
+
+ // Note that we aren't expected to send any close frame before closing the
+ // connection when the frame is unmasked
+
+ if (parser->reserved_1 || parser->reserved_2 || parser->reserved_3
+ || !parser->is_masked // client -> server payload must be masked
+ || (ws_is_control_frame (parser->opcode) &&
+ (!parser->is_fin || parser->payload_len > WS_MAX_CONTROL_PAYLOAD_LEN))
+ || (!ws_is_control_frame (parser->opcode) &&
+ (self->expecting_continuation && parser->opcode != WS_OPCODE_CONT))
+ || parser->payload_len >= 0x8000000000000000ULL)
+ return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
+
+ if (parser->payload_len > self->max_payload_len
+ || (self->expecting_continuation &&
+ self->message_data.len + parser->payload_len > self->max_payload_len))
+ return ws_handler_fail_connection (self, WS_STATUS_MESSAGE_TOO_BIG);
+ return true;
+}
+
+static bool
+ws_handler_on_control_close
+ (struct ws_handler *self, const struct ws_parser *parser)
+{
+ hard_assert (self->state == WS_HANDLER_OPEN
+ || self->state == WS_HANDLER_CLOSING);
+ struct msg_unpacker unpacker =
+ msg_unpacker_make (parser->input.str, parser->payload_len);
+
+ char *reason = NULL;
+ uint16_t close_code = WS_STATUS_NO_STATUS_RECEIVED;
+ if (parser->payload_len >= 2)
+ {
+ (void) msg_unpacker_u16 (&unpacker, &close_code);
+ reason = xstrndup (parser->input.str + 2, parser->payload_len - 2);
+ }
+ else
+ reason = xstrdup ("");
+
+ if (close_code < 1000 || close_code > 4999)
+ // XXX: invalid close code: maybe we should fail the connection instead
+ close_code = WS_STATUS_PROTOCOL_ERROR;
+
+ if (self->state == WS_HANDLER_OPEN)
+ {
+ // Close initiated by the client
+ // FIXME: not sending the potentially different close_code
+ ws_handler_send_control (self, WS_OPCODE_CLOSE,
+ parser->input.str, parser->payload_len);
+
+ self->state = WS_HANDLER_FLUSHING;
+ if (self->on_close)
+ self->on_close (self, close_code, reason);
+ }
+ else
+ self->state = WS_HANDLER_FLUSHING;
+
+ free (reason);
+
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
+ ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
+ return true;
+}
+
+static bool
+ws_handler_on_control_frame
+ (struct ws_handler *self, const struct ws_parser *parser)
+{
+ switch (parser->opcode)
+ {
+ case WS_OPCODE_CLOSE:
+ return ws_handler_on_control_close (self, parser);
+ case WS_OPCODE_PING:
+ ws_handler_send_control (self, WS_OPCODE_PONG,
+ parser->input.str, parser->payload_len);
+ break;
+ case WS_OPCODE_PONG:
+ // TODO: check the payload
+ self->received_pong = true;
+ break;
+ default:
+ // Unknown control frame
+ return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
+ }
+ return true;
+}
+
+static bool
+ws_handler_on_frame (void *user_data, const struct ws_parser *parser)
+{
+ struct ws_handler *self = user_data;
+ if (ws_is_control_frame (parser->opcode))
+ return ws_handler_on_control_frame (self, parser);
+ if (!self->expecting_continuation)
+ self->message_opcode = parser->opcode;
+
+ str_append_data (&self->message_data,
+ parser->input.str, parser->payload_len);
+ if ((self->expecting_continuation = !parser->is_fin))
+ return true;
+
+ if (self->message_opcode == WS_OPCODE_TEXT
+ && !utf8_validate (self->message_data.str, self->message_data.len))
+ {
+ return ws_handler_fail_connection
+ (self, WS_STATUS_INVALID_PAYLOAD_DATA);
+ }
+
+ bool result = true;
+ if (self->on_message)
+ result = self->on_message (self, self->message_opcode,
+ self->message_data.str, self->message_data.len);
+ str_reset (&self->message_data);
+ // TODO: if (!result), either replace this with a state check,
+ // or make sure to change the state
+ return result;
+}
+
+static void
+ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents)
+{
+ (void) loop;
+ (void) revents;
+
+ struct ws_handler *self = watcher->data;
+ if (!self->received_pong)
+ ws_handler_fail_connection (self, 4000);
+ else
+ {
+ // TODO: be an annoying server and send a nonce in the data
+ ws_handler_send_control (self, WS_OPCODE_PING, NULL, 0);
+ ev_timer_again (EV_A_ watcher);
+ }
+}
+
+static void
+ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
+{
+ (void) loop;
+ (void) revents;
+ struct ws_handler *self = watcher->data;
+
+ hard_assert (self->state == WS_HANDLER_OPEN
+ || self->state == WS_HANDLER_CLOSING);
+
+ if (self->state == WS_HANDLER_CLOSING
+ && self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "close timeout");
+
+ self->state = WS_HANDLER_CLOSED;
+ self->close_cb (self, false /* half_close */);
+}
+
+static void
+ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents)
+{
+ (void) loop;
+ (void) revents;
+ struct ws_handler *self = watcher->data;
+
+ // XXX: this is a no-op, since this currently doesn't even call shutdown
+ // immediately but postpones it until later
+ self->close_cb (self, true /* half_close */);
+ self->state = WS_HANDLER_FLUSHING;
+
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "handshake timeout");
+
+ self->state = WS_HANDLER_CLOSED;
+ self->close_cb (self, false /* half_close */);
+}
+
+static void
+ws_handler_init (struct ws_handler *self)
+{
+ memset (self, 0, sizeof *self);
+
+ self->state = WS_HANDLER_CONNECTING;
+
+ http_parser_init (&self->hp, HTTP_REQUEST);
+ self->hp.data = self;
+ self->field = str_make ();
+ self->value = str_make ();
+ self->headers = str_map_make (free);
+ self->headers.key_xfrm = tolower_ascii_strxfrm;
+ self->url = str_make ();
+ ev_timer_init (&self->handshake_timeout_watcher,
+ ws_handler_on_handshake_timeout, 0., 0.);
+ self->handshake_timeout_watcher.data = self;
+
+ self->parser = ws_parser_make ();
+ self->parser.on_frame_header = ws_handler_on_frame_header;
+ self->parser.on_frame = ws_handler_on_frame;
+ self->parser.user_data = self;
+ self->message_data = str_make ();
+
+ ev_timer_init (&self->ping_timer,
+ ws_handler_on_ping_timer, 0., 0.);
+ self->ping_timer.data = self;
+ ev_timer_init (&self->close_timeout_watcher,
+ ws_handler_on_close_timeout, 0., 0.);
+ self->ping_timer.data = self;
+ // So that the first ping timer doesn't timeout the connection
+ self->received_pong = true;
+
+ self->handshake_timeout = self->close_timeout = self->ping_interval = 60;
+ // This is still ridiculously high. Note that the most significant bit
+ // must always be zero, i.e. the protocol maximum is 0x7FFF FFFF FFFF FFFF.
+ self->max_payload_len = UINT32_MAX;
+}
+
+/// Stop all timers, not going to use the handler anymore
+static void
+ws_handler_stop (struct ws_handler *self)
+{
+ ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
+}
+
+static void
+ws_handler_free (struct ws_handler *self)
+{
+ ws_handler_stop (self);
+
+ str_free (&self->field);
+ str_free (&self->value);
+ str_map_free (&self->headers);
+ str_free (&self->url);
+
+ ws_parser_free (&self->parser);
+ str_free (&self->message_data);
+}
+
+static bool
+ws_handler_header_field_is_a_list (const char *name)
+{
+ // This must contain all header fields we use for anything
+ static const char *concatenable[] =
+ { SEC_WS_PROTOCOL, SEC_WS_EXTENSIONS, "Connection", "Upgrade" };
+
+ for (size_t i = 0; i < N_ELEMENTS (concatenable); i++)
+ if (!strcasecmp_ascii (name, concatenable[i]))
+ return true;
+ return false;
+}
+
+static void
+ws_handler_on_header_read (struct ws_handler *self)
+{
+ // The HTTP parser unfolds values and removes preceding whitespace, but
+ // otherwise doesn't touch the values or the following whitespace.
+
+ // RFC 7230 states that trailing whitespace is not part of a field value
+ char *value = self->field.str;
+ size_t len = self->field.len;
+ while (len--)
+ if (value[len] == '\t' || value[len] == ' ')
+ value[len] = '\0';
+ else
+ break;
+ self->field.len = len;
+
+ const char *field = self->field.str;
+ const char *current = str_map_find (&self->headers, field);
+ if (ws_handler_header_field_is_a_list (field) && current)
+ str_map_set (&self->headers, field,
+ xstrdup_printf ("%s, %s", current, self->value.str));
+ else
+ // If the field cannot be concatenated, just overwrite the last value.
+ // Maybe we should issue a warning or something.
+ str_map_set (&self->headers, field, xstrdup (self->value.str));
+}
+
+static int
+ws_handler_on_header_field (http_parser *parser, const char *at, size_t len)
+{
+ struct ws_handler *self = parser->data;
+ if (self->have_header_value)
+ {
+ ws_handler_on_header_read (self);
+ str_reset (&self->field);
+ str_reset (&self->value);
+ }
+ str_append_data (&self->field, at, len);
+ self->have_header_value = false;
+ return 0;
+}
+
+static int
+ws_handler_on_header_value (http_parser *parser, const char *at, size_t len)
+{
+ struct ws_handler *self = parser->data;
+ str_append_data (&self->value, at, len);
+ self->have_header_value = true;
+ return 0;
+}
+
+static int
+ws_handler_on_headers_complete (http_parser *parser)
+{
+ struct ws_handler *self = parser->data;
+ if (self->have_header_value)
+ ws_handler_on_header_read (self);
+
+ // We strictly require a protocol upgrade
+ if (!parser->upgrade)
+ return 2;
+
+ return 0;
+}
+
+static int
+ws_handler_on_url (http_parser *parser, const char *at, size_t len)
+{
+ struct ws_handler *self = parser->data;
+ str_append_data (&self->value, at, len);
+ return 0;
+}
+
+#define HTTP_101_SWITCHING_PROTOCOLS "101 Switching Protocols"
+#define HTTP_400_BAD_REQUEST "400 Bad Request"
+#define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed"
+#define HTTP_417_EXPECTATION_FAILED "407 Expectation Failed"
+#define HTTP_426_UPGRADE_REQUIRED "426 Upgrade Required"
+#define HTTP_505_VERSION_NOT_SUPPORTED "505 HTTP Version Not Supported"
+
+static void
+ws_handler_http_responsev (struct ws_handler *self,
+ const char *status, char *const *fields)
+{
+ hard_assert (status != NULL);
+
+ struct str response = str_make ();
+ str_append_printf (&response, "HTTP/1.1 %s\r\n", status);
+
+ while (*fields)
+ str_append_printf (&response, "%s\r\n", *fields++);
+
+ time_t now = time (NULL);
+ struct tm ts;
+ gmtime_r (&now, &ts);
+
+ // See RFC 7231, 7.1.1.2. Date
+ const char *dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+ const char *moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+ str_append_printf (&response,
+ "Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n",
+ dow[ts.tm_wday], ts.tm_mday, moy[ts.tm_mon], ts.tm_year + 1900,
+ ts.tm_hour, ts.tm_min, ts.tm_sec);
+
+ str_append (&response, "Server: "
+ PROGRAM_NAME "/" PROGRAM_VERSION "\r\n\r\n");
+ self->write_cb (self, response.str, response.len);
+ str_free (&response);
+}
+
+static bool
+ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...)
+{
+ va_list ap;
+ va_start (ap, status);
+
+ const char *s;
+ struct strv v = strv_make ();
+ while ((s = va_arg (ap, const char *)))
+ strv_append (&v, s);
+
+ va_end (ap);
+ ws_handler_http_responsev (self, status, v.vector);
+ strv_free (&v);
+
+ self->close_cb (self, true /* half_close */);
+ self->state = WS_HANDLER_FLUSHING;
+
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, status);
+ return false;
+}
+
+#define FAIL_HANDSHAKE(...) \
+ return ws_handler_fail_handshake (self, __VA_ARGS__, NULL)
+
+static bool
+ws_handler_finish_handshake (struct ws_handler *self)
+{
+ if (self->hp.method != HTTP_GET)
+ FAIL_HANDSHAKE (HTTP_405_METHOD_NOT_ALLOWED, "Allow: GET");
+
+ // Technically, it must be /at least/ 1.1 but no other 1.x version of HTTP
+ // is going to happen and 2.x is entirely incompatible
+ // XXX: we probably shouldn't use 505 to reject the minor version but w/e
+ if (self->hp.http_major != 1 || self->hp.http_minor != 1)
+ FAIL_HANDSHAKE (HTTP_505_VERSION_NOT_SUPPORTED);
+
+ // Your expectations are way too high
+ if (str_map_find (&self->headers, "Expect"))
+ FAIL_HANDSHAKE (HTTP_417_EXPECTATION_FAILED);
+
+ // Reject URLs specifying the schema and host; we're not parsing that
+ // TODO: actually do parse this and let our user decide if it matches
+ struct http_parser_url url;
+ if (http_parser_parse_url (self->url.str, self->url.len, false, &url)
+ || (url.field_set & (1 << UF_SCHEMA | 1 << UF_HOST | 1 << UF_PORT))
+ || !str_map_find (&self->headers, "Host"))
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+
+ const char *connection = str_map_find (&self->headers, "Connection");
+ if (!connection || strcasecmp_ascii (connection, "Upgrade"))
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+
+ // Check if we can actually upgrade the protocol to WebSockets
+ const char *upgrade = str_map_find (&self->headers, "Upgrade");
+ struct http_protocol *offered_upgrades = NULL;
+ bool can_upgrade = false;
+ if (upgrade && http_parse_upgrade (upgrade, &offered_upgrades))
+ // Case-insensitive according to RFC 6455; neither RFC 2616 nor 7230
+ // say anything at all about case-sensitivity for this field
+ LIST_FOR_EACH (struct http_protocol, iter, offered_upgrades)
+ {
+ if (!iter->version && !strcasecmp_ascii (iter->name, "websocket"))
+ can_upgrade = true;
+ http_protocol_destroy (iter);
+ }
+ if (!can_upgrade)
+ FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED,
+ "Upgrade: websocket", SEC_WS_VERSION ": 13");
+
+ // Okay, we're finally past the basic HTTP/1.1 stuff
+ const char *key = str_map_find (&self->headers, SEC_WS_KEY);
+ const char *version = str_map_find (&self->headers, SEC_WS_VERSION);
+/*
+ const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL);
+ const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS);
+*/
+
+ if (!version)
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+ if (strcmp (version, "13"))
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13");
+
+ struct str tmp = str_make ();
+ bool key_is_valid = key
+ && base64_decode (key, false, &tmp) && tmp.len == 16;
+ str_free (&tmp);
+ if (!key_is_valid)
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+
+ struct strv fields = strv_make ();
+ strv_append_args (&fields,
+ "Upgrade: websocket",
+ "Connection: Upgrade",
+ NULL);
+
+ char *response_key = ws_encode_response_key (key);
+ strv_append_owned (&fields,
+ xstrdup_printf (SEC_WS_ACCEPT ": %s", response_key));
+ free (response_key);
+
+ // TODO: make it possible to choose Sec-Websocket-{Extensions,Protocol}
+
+ ws_handler_http_responsev (self,
+ HTTP_101_SWITCHING_PROTOCOLS, fields.vector);
+
+ strv_free (&fields);
+
+ self->state = WS_HANDLER_OPEN;
+ ev_timer_init (&self->ping_timer, ws_handler_on_ping_timer,
+ self->ping_interval, 0);
+ ev_timer_start (EV_DEFAULT_ &self->ping_timer);
+ return true;
+}
+
+/// Tells the handler that the TCP connection has been established so it can
+/// timeout when the client handshake doesn't arrive soon enough
+static void
+ws_handler_start (struct ws_handler *self)
+{
+ hard_assert (self->state == WS_HANDLER_CONNECTING);
+
+ ev_timer_set (&self->handshake_timeout_watcher,
+ self->handshake_timeout, 0.);
+ ev_timer_start (EV_DEFAULT_ &self->handshake_timeout_watcher);
+}
+
+// The client should normally never close the connection, assume that it's
+// either received an EOF from our side, or that it doesn't care about our data
+// anymore, having called close() already
+static bool
+ws_handler_push_eof (struct ws_handler *self)
+{
+ switch (self->state)
+ {
+ case WS_HANDLER_CONNECTING:
+ ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
+
+ self->state = WS_HANDLER_FLUSHING;
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "unexpected EOF");
+ break;
+ case WS_HANDLER_OPEN:
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ // Fall-through
+ case WS_HANDLER_CLOSING:
+ self->state = WS_HANDLER_CLOSED;
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
+ // Fall-through
+ case WS_HANDLER_FLUSHING:
+ ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
+ break;
+ default:
+ soft_assert(self->state != WS_HANDLER_CLOSED);
+ }
+ self->state = WS_HANDLER_CLOSED;
+ return false;
+}
+
+/// Push data to the WebSocket handler. "len == 0" means EOF.
+/// You are expected to close the connection and dispose of the handler
+/// when the function returns false.
+static bool
+ws_handler_push (struct ws_handler *self, const void *data, size_t len)
+{
+ if (!len)
+ return ws_handler_push_eof (self);
+
+ if (self->state == WS_HANDLER_FLUSHING)
+ // We're waiting for an EOF from the client, must not process data
+ return true;
+
+ if (self->state != WS_HANDLER_CONNECTING)
+ return soft_assert (self->state != WS_HANDLER_CLOSED)
+ && ws_parser_push (&self->parser, data, len);
+
+ // The handshake hasn't been done yet, process HTTP headers
+ static const http_parser_settings http_settings =
+ {
+ .on_header_field = ws_handler_on_header_field,
+ .on_header_value = ws_handler_on_header_value,
+ .on_headers_complete = ws_handler_on_headers_complete,
+ .on_url = ws_handler_on_url,
+ };
+
+ size_t n_parsed =
+ http_parser_execute (&self->hp, &http_settings, data, len);
+
+ if (self->hp.upgrade)
+ {
+ ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
+
+ // The handshake hasn't been finished, yet there is more data
+ // to be processed after the headers already
+ if (len - n_parsed)
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+
+ if (!ws_handler_finish_handshake (self))
+ return false;
+ if (self->on_connected)
+ return self->on_connected (self);
+ return true;
+ }
+
+ enum http_errno err = HTTP_PARSER_ERRNO (&self->hp);
+ if (n_parsed != len || err != HPE_OK)
+ {
+ ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
+
+ if (err == HPE_CB_headers_complete)
+ print_debug ("WS handshake failed: %s", "missing `Upgrade' field");
+ else
+ print_debug ("WS handshake failed: %s",
+ http_errno_description (err));
+
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+ }
+ return true;
+}
+
+/// @}
+// --- Server ------------------------------------------------------------------
+
+static struct simple_config_item g_config_table[] =
+{
+ { "bind_host", NULL, "Address of the server" },
+ { "port_fastcgi", "9000", "Port to bind for FastCGI" },
+ { "port_scgi", NULL, "Port to bind for SCGI" },
+ { "port_ws", NULL, "Port to bind for WebSockets" },
+ { "pid_file", NULL, "Full path for the PID file" },
+ // XXX: here belongs something like a web SPA that interfaces with us
+ { "static_root", NULL, "The root for static content" },
+ { NULL, NULL, NULL }
+};
+
+struct server_context
+{
+ ev_signal sigterm_watcher; ///< Got SIGTERM
+ ev_signal sigint_watcher; ///< Got SIGINT
+ ev_timer quit_timeout_watcher; ///< Quit timeout watcher
+ bool quitting; ///< User requested quitting
+
+ struct listener *listeners; ///< Listeners
+ size_t n_listeners; ///< Number of listening sockets
+
+ struct client *clients; ///< Clients
+ unsigned n_clients; ///< Current number of connections
+
+ struct request_handler *handlers; ///< Request handlers
+ struct str_map config; ///< Server configuration
+};
+
+static void initiate_quit (struct server_context *self);
+static void try_finish_quit (struct server_context *self);
+static void on_quit_timeout (EV_P_ ev_timer *watcher, int revents);
+static void close_listeners (struct server_context *self);
+
+static void
+server_context_init (struct server_context *self)
+{
+ memset (self, 0, sizeof *self);
+
+ self->config = str_map_make (NULL);
+ simple_config_load_defaults (&self->config, g_config_table);
+ ev_timer_init (&self->quit_timeout_watcher, on_quit_timeout, 3., 0.);
+ self->quit_timeout_watcher.data = self;
+}
+
+static void
+server_context_free (struct server_context *self)
+{
+ // We really shouldn't attempt a quit without closing the clients first
+ soft_assert (!self->clients);
+
+ close_listeners (self);
+ free (self->listeners);
+
+ str_map_free (&self->config);
+}
+
+// --- JSON-RPC ----------------------------------------------------------------
+/// @defgroup JSON-RPC
+/// @{
+
+#define JSON_RPC_ERROR_TABLE(XX) \
+ XX (-32700, PARSE_ERROR, "Parse error") \
+ XX (-32600, INVALID_REQUEST, "Invalid Request") \
+ XX (-32601, METHOD_NOT_FOUND, "Method not found") \
+ XX (-32602, INVALID_PARAMS, "Invalid params") \
+ XX (-32603, INTERNAL_ERROR, "Internal error")
+
+enum json_rpc_error
+{
+#define XX(code, name, message) JSON_RPC_ERROR_ ## name,
+ JSON_RPC_ERROR_TABLE (XX)
+#undef XX
+ JSON_RPC_ERROR_COUNT
+};
+
+static json_t *
+json_rpc_error (enum json_rpc_error id, json_t *data)
+{
+#define XX(code, name, message) { code, message },
+ static const struct json_rpc_error
+ {
+ int code;
+ const char *message;
+ }
+ errors[JSON_RPC_ERROR_COUNT] =
+ {
+ JSON_RPC_ERROR_TABLE (XX)
+ };
+#undef XX
+
+ json_t *error = json_object ();
+ json_object_set_new (error, "code", json_integer (errors[id].code));
+ json_object_set_new (error, "message", json_string (errors[id].message));
+
+ if (data)
+ json_object_set_new (error, "data", data);
+
+ return error;
+}
+
+static json_t *
+json_rpc_response (json_t *id, json_t *result, json_t *error)
+{
+ json_t *x = json_object ();
+ json_object_set_new (x, "jsonrpc", json_string ("2.0"));
+ json_object_set_new (x, "id", id ? id : json_null ());
+ if (result) json_object_set_new (x, "result", result);
+ if (error) json_object_set_new (x, "error", error);
+ return x;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+validate_json_rpc_content_type (const char *content_type)
+{
+ char *type = NULL;
+ char *subtype = NULL;
+
+ struct str_map parameters = str_map_make (free);
+ parameters.key_xfrm = tolower_ascii_strxfrm;
+
+ bool result = http_parse_media_type
+ (content_type, &type, &subtype, ¶meters);
+ if (!result)
+ goto end;
+
+ if (strcasecmp_ascii (type, "application")
+ || (strcasecmp_ascii (subtype, "json") &&
+ strcasecmp_ascii (subtype, "json-rpc" /* obsolete */)))
+ result = false;
+
+ const char *charset = str_map_find (¶meters, "charset");
+ if (charset && strcasecmp_ascii (charset, "UTF-8"))
+ result = false;
+
+ // Currently ignoring all unknown parametrs
+
+end:
+ free (type);
+ free (subtype);
+ str_map_free (¶meters);
+ return result;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+typedef json_t *(*json_rpc_handler_fn) (struct server_context *, json_t *);
+
+struct json_rpc_handler_info
+{
+ const char *method_name; ///< JSON-RPC method name
+ json_rpc_handler_fn handler; ///< Method handler
+};
+
+static int
+json_rpc_handler_info_cmp (const void *first, const void *second)
+{
+ return strcmp (((struct json_rpc_handler_info *) first)->method_name,
+ ((struct json_rpc_handler_info *) second)->method_name);
+}
+
+// TODO: a method that sends a response after a certain number of seconds.
+// This has to be owned by the server context as a background job that
+// removes itself upon completion.
+
+static json_t *
+json_rpc_ping (struct server_context *ctx, json_t *params)
+{
+ (void) ctx;
+ (void) params;
+
+ return json_rpc_response (NULL, json_string ("pong"), NULL);
+}
+
+static json_t *
+process_json_rpc_request (struct server_context *ctx, json_t *request)
+{
+ // A list of all available methods; this list has to be ordered.
+ // Eventually it might be better to move this into a map in the context.
+ static struct json_rpc_handler_info handlers[] =
+ {
+ { "ping", json_rpc_ping },
+ };
+
+ if (!json_is_object (request))
+ return json_rpc_response (NULL, NULL,
+ json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL));
+
+ json_t *v = json_object_get (request, "jsonrpc");
+ json_t *m = json_object_get (request, "method");
+ json_t *params = json_object_get (request, "params");
+ json_t *id = json_object_get (request, "id");
+
+ const char *version;
+ const char *method;
+
+ bool ok = true;
+ ok &= v && (version = json_string_value (v)) && !strcmp (version, "2.0");
+ ok &= m && (method = json_string_value (m));
+ ok &= !params || json_is_array (params) || json_is_object (params);
+ ok &= !id || json_is_null (id) ||
+ json_is_string (id) || json_is_number (id);
+ if (!ok)
+ return json_rpc_response (id, NULL,
+ json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL));
+
+ struct json_rpc_handler_info key = { .method_name = method };
+ struct json_rpc_handler_info *handler = bsearch (&key, handlers,
+ N_ELEMENTS (handlers), sizeof key, json_rpc_handler_info_cmp);
+ if (!handler)
+ return json_rpc_response (id, NULL,
+ json_rpc_error (JSON_RPC_ERROR_METHOD_NOT_FOUND, NULL));
+
+ json_t *response = handler->handler (ctx, params);
+ if (id)
+ return response;
+
+ // Notifications don't get responses
+ json_decref (response);
+ return NULL;
+}
+
+static void
+flush_json (json_t *json, struct str *output)
+{
+ char *utf8 = json_dumps (json, JSON_ENCODE_ANY);
+ str_append (output, utf8);
+ free (utf8);
+ json_decref (json);
+}
+
+static void
+process_json_rpc (struct server_context *ctx,
+ const void *data, size_t len, struct str *output)
+{
+
+ json_error_t e;
+ json_t *request;
+ if (!(request = json_loadb (data, len, JSON_DECODE_ANY, &e)))
+ {
+ flush_json (json_rpc_response (NULL, NULL,
+ json_rpc_error (JSON_RPC_ERROR_PARSE_ERROR, NULL)),
+ output);
+ return;
+ }
+
+ if (json_is_array (request))
+ {
+ if (!json_array_size (request))
+ {
+ flush_json (json_rpc_response (NULL, NULL,
+ json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL)),
+ output);
+ return;
+ }
+
+ json_t *response = json_array ();
+ json_t *iter;
+ size_t i;
+
+ json_array_foreach (request, i, iter)
+ {
+ json_t *result = process_json_rpc_request (ctx, iter);
+ if (result)
+ json_array_append_new (response, result);
+ }
+
+ if (json_array_size (response))
+ flush_json (response, output);
+ else
+ json_decref (response);
+ }
+ else
+ {
+ json_t *result = process_json_rpc_request (ctx, request);
+ if (result)
+ flush_json (result, output);
+ }
+}
+
+/// @}
+// --- Requests ----------------------------------------------------------------
+/// @defgroup Requests
+/// @{
+
+/// A generic CGI request abstraction, writing data indirectly through callbacks
+struct request
+{
+ struct server_context *ctx; ///< Server context
+
+ struct request_handler *handler; ///< Assigned request handler
+ void *handler_data; ///< User data for the handler
+
+ /// Callback to write some CGI response data to the output
+ void (*write_cb) (struct request *, const void *data, size_t len);
+
+ /// Callback to close the CGI response, simulates end of program execution.
+ /// CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
+ void (*close_cb) (struct request *);
+};
+
+/// An interface to detect and handle specific kinds of CGI requests.
+/// The server walks through a list of them until it finds one that can serve
+/// a particular request. If unsuccessful, the remote client gets a 404
+/// (the default handling).
+struct request_handler
+{
+ LIST_HEADER (struct request_handler)
+
+ /// Install ourselves as the handler for the request, if applicable.
+ /// Sets @a continue_ to false if further processing should be stopped,
+ /// meaning the request has already been handled.
+ bool (*try_handle) (struct request *request,
+ struct str_map *headers, bool *continue_);
+
+ /// Handle incoming data. "len == 0" means EOF.
+ /// Returns false if there is no more processing to be done.
+ // FIXME: the EOF may or may not be delivered when request is cut short,
+ // we should fix FastCGI not to deliver it on CONTENT_LENGTH mismatch
+ bool (*push_cb) (struct request *request, const void *data, size_t len);
+
+ /// Destroy the handler's data stored in the request object
+ void (*finalize_cb) (struct request *request);
+};
+
+static void
+request_init (struct request *self)
+{
+ memset (self, 0, sizeof *self);
+}
+
+static void
+request_free (struct request *self)
+{
+ if (self->handler)
+ self->handler->finalize_cb (self);
+}
+
+/// Write request CGI response data, intended for use by request handlers
+static void
+request_write (struct request *self, const void *data, size_t len)
+{
+ self->write_cb (self, data, len);
+}
+
+/// This function is only intended to be run from asynchronous event handlers
+/// such as timers, not as a direct result of starting the request or receiving
+/// request data. CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
+static void
+request_finish (struct request *self)
+{
+ self->close_cb (self);
+}
+
+/// Starts processing a request. Returns false if no further action is to be
+/// done and the request should be finished.
+static bool
+request_start (struct request *self, struct str_map *headers)
+{
+ // XXX: it feels like this should rather be two steps:
+ // bool (*can_handle) (request *, headers)
+ // ... install the handler ...
+ // bool (*handle) (request *)
+ //
+ // However that might cause some stuff to be done twice.
+ //
+ // Another way we could get rid of the continue_ argument is via adding
+ // some way of marking the request as finished from within the handler.
+
+ if (g_debug_mode)
+ {
+ struct str_map_iter iter = str_map_iter_make (headers);
+ const char *value;
+ while ((value = str_map_iter_next (&iter)))
+ print_debug ("%s: %s", iter.link->key, value);
+ print_debug ("--");
+ }
+
+ bool continue_ = true;
+ LIST_FOR_EACH (struct request_handler, handler, self->ctx->handlers)
+ if (handler->try_handle (self, headers, &continue_))
+ {
+ self->handler = handler;
+ return continue_;
+ }
+
+ // Unable to serve the request
+ struct str response = str_make ();
+ str_append (&response, "Status: 404 Not Found\n");
+ str_append (&response, "Content-Type: text/plain\n\n");
+ request_write (self, response.str, response.len);
+ str_free (&response);
+ return false;
+}
+
+static bool
+request_push (struct request *self, const void *data, size_t len)
+{
+ if (!soft_assert (self->handler))
+ // No handler, nothing to do with any data
+ return false;
+
+ return self->handler->push_cb (self, data, len);
+}
+
+/// @}
+// --- Requests handlers -------------------------------------------------------
+
+static bool
+request_handler_json_rpc_try_handle
+ (struct request *request, struct str_map *headers, bool *continue_)
+{
+ const char *content_type = str_map_find (headers, "CONTENT_TYPE");
+ const char *method = str_map_find (headers, "REQUEST_METHOD");
+
+ if (!method || strcmp (method, "POST")
+ || !content_type || !validate_json_rpc_content_type (content_type))
+ return false;
+
+ struct str *buf = xcalloc (1, sizeof *buf);
+ *buf = str_make ();
+
+ request->handler_data = buf;
+ *continue_ = true;
+ return true;
+}
+
+static bool
+request_handler_json_rpc_push
+ (struct request *request, const void *data, size_t len)
+{
+ struct str *buf = request->handler_data;
+ if (len)
+ {
+ str_append_data (buf, data, len);
+ return true;
+ }
+
+ // TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
+ // client hasn't been successful in transferring all of its data.
+ // See also comment on request_handler::push_cb.
+
+ struct str response = str_make ();
+ str_append (&response, "Status: 200 OK\n");
+ str_append_printf (&response, "Content-Type: %s\n\n", "application/json");
+ process_json_rpc (request->ctx, buf->str, buf->len, &response);
+ request_write (request, response.str, response.len);
+ str_free (&response);
+ return false;
+}
+
+static void
+request_handler_json_rpc_finalize (struct request *request)
+{
+ struct str *buf = request->handler_data;
+ str_free (buf);
+ free (buf);
+
+ request->handler_data = NULL;
+}
+
+struct request_handler g_request_handler_json_rpc =
+{
+ .try_handle = request_handler_json_rpc_try_handle,
+ .push_cb = request_handler_json_rpc_push,
+ .finalize_cb = request_handler_json_rpc_finalize,
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static char *
+canonicalize_url_path (const char *path)
+{
+ // XXX: this strips any slashes at the end
+ struct strv v = strv_make ();
+ cstr_split (path, "/", true, &v);
+
+ struct strv canonical = strv_make ();
+
+ // So that the joined path always begins with a slash
+ strv_append (&canonical, "");
+
+ for (size_t i = 0; i < v.len; i++)
+ {
+ const char *dir = v.vector[i];
+ if (!strcmp (dir, "."))
+ continue;
+
+ if (strcmp (dir, ".."))
+ strv_append (&canonical, dir);
+ else if (canonical.len > 1)
+ // ".." never goes above the root
+ strv_remove (&canonical, canonical.len - 1);
+ }
+ strv_free (&v);
+
+ char *joined = strv_join (&canonical, "/");
+ strv_free (&canonical);
+ return joined;
+}
+
+static char *
+detect_magic (const void *data, size_t len)
+{
+ magic_t cookie;
+ char *mime_type = NULL;
+
+ if (!(cookie = magic_open (MAGIC_MIME)))
+ return NULL;
+
+ const char *magic = NULL;
+ if (!magic_load (cookie, NULL)
+ && (magic = magic_buffer (cookie, data, len)))
+ mime_type = xstrdup (magic);
+ else
+ print_debug ("MIME type detection failed: %s", magic_error (cookie));
+
+ magic_close (cookie);
+ return mime_type;
+}
+
+static bool
+request_handler_static_try_handle
+ (struct request *request, struct str_map *headers, bool *continue_)
+{
+ // Serving static files is actually quite complicated as it turns out;
+ // but this is only meant to serve a few tiny text files
+
+ struct server_context *ctx = request->ctx;
+ const char *root = str_map_find (&ctx->config, "static_root");
+ if (!root)
+ {
+ print_debug ("static document root not configured");
+ return false;
+ }
+
+ // TODO: implement HEAD, we don't get that for free;
+ // probably implies adding Content-Length
+ const char *method = str_map_find (headers, "REQUEST_METHOD");
+ if (!method || strcmp (method, "GET"))
+ return false;
+
+ // TODO: look at , REQUEST_URI in the headers
+ const char *path_info = str_map_find (headers, "PATH_INFO");
+ if (!path_info)
+ path_info = str_map_find (headers, "REQUEST_URI");
+ if (!path_info)
+ {
+ print_debug ("neither PATH_INFO nor REQUEST_URI was defined");
+ return false;
+ }
+
+ // We need to filter the path to stay in our root
+ // Being able to read /etc/passwd would be rather embarrasing
+ char *suffix = canonicalize_url_path (path_info);
+ char *path = xstrdup_printf ("%s%s", root, suffix);
+ print_debug ("trying to statically serve %s", path);
+
+ // TODO: check that this is a regular file
+ FILE *fp = fopen (path, "rb");
+ if (!fp)
+ {
+ struct str response = str_make ();
+ str_append (&response, "Status: 404 Not Found\n");
+ str_append (&response, "Content-Type: text/plain\n\n");
+ str_append_printf (&response,
+ "File %s was not found on this server\n", suffix);
+ request_write (request, response.str, response.len);
+ str_free (&response);
+
+ free (suffix);
+ free (path);
+ return false;
+ }
+
+ free (suffix);
+ free (path);
+
+ uint8_t buf[8192];
+ size_t len;
+
+ // Try to detect the Content-Type from the actual contents
+ char *mime_type = NULL;
+ if ((len = fread (buf, 1, sizeof buf, fp)))
+ mime_type = detect_magic (buf, len);
+ if (!mime_type)
+ mime_type = xstrdup ("application/octet_stream");
+
+ struct str response = str_make ();
+ str_append (&response, "Status: 200 OK\n");
+ str_append_printf (&response, "Content-Type: %s\n\n", mime_type);
+ request_write (request, response.str, response.len);
+ str_free (&response);
+ free (mime_type);
+
+ // Write the chunk we've used to help us with magic detection;
+ // obviously we have to do it after we've written the headers
+ if (len)
+ request_write (request, buf, len);
+
+ while ((len = fread (buf, 1, sizeof buf, fp)))
+ request_write (request, buf, len);
+ fclose (fp);
+
+ // TODO: this should rather not be returned all at once but in chunks;
+ // file read requests never return EAGAIN
+ // TODO: actual file data should really be returned by a callback when
+ // the socket is writable with nothing to be sent (pumping the entire
+ // file all at once won't really work if it's huge).
+ *continue_ = false;
+ return true;
+}
+
+static bool
+request_handler_static_push
+ (struct request *request, const void *data, size_t len)
+{
+ (void) request;
+ (void) data;
+
+ // Aborting on content; we shouldn't receive any (GET)
+ // FIXME: there should at least be some indication of this happening
+ return len == 0;
+}
+
+static void
+request_handler_static_finalize (struct request *request)
+{
+ (void) request;
+ // Nothing to dispose of this far
+}
+
+struct request_handler g_request_handler_static =
+{
+ .try_handle = request_handler_static_try_handle,
+ .push_cb = request_handler_static_push,
+ .finalize_cb = request_handler_static_finalize,
+};
+
+// --- Client communication handlers -------------------------------------------
+
+/// A virtual class for client connections coming either from the web server
+/// or directly from the end-client, depending on the protocol in use
+struct client
+{
+ LIST_HEADER (struct client)
+
+ struct client_vtable *vtable; ///< Client behaviour
+
+ int socket_fd; ///< The network socket
+ bool received_eof; ///< Whether EOF has been received yet
+ bool flushing; ///< No more data to write, send FIN
+ bool closing; ///< No more data to read or write
+ bool half_closed; ///< Conn. half-closed while flushing
+ struct write_queue write_queue; ///< Write queue
+ ev_timer close_timeout_watcher; ///< Write queue flush timer
+
+ ev_io read_watcher; ///< The socket can be read from
+ ev_io write_watcher; ///< The socket can be written to
+};
+
+/// The concrete behaviour to serve a particular client's requests
+struct client_vtable
+{
+ /// Process incoming data; "len == 0" means EOF.
+ /// If the method returns false, client_close() is called by the caller.
+ bool (*push) (struct client *client, const void *data, size_t len);
+
+ // TODO: optional push_error() to inform about network I/O errors
+
+ /// Attempt a graceful shutdown: make any appropriate steps before
+ /// the client connection times out and gets torn down by force.
+ /// The client is allowed to destroy itself immediately.
+ void (*shutdown) (struct client *client);
+
+ /// Do any additional cleanup for the concrete class before destruction
+ void (*finalize) (struct client *client);
+};
+
+static void
+client_destroy (struct client *self)
+{
+ // XXX: this codebase halfway pretends there could be other contexts
+ struct server_context *ctx = ev_userdata (EV_DEFAULT);
+ LIST_UNLINK (ctx->clients, self);
+ ctx->n_clients--;
+
+ // First uninitialize the higher-level implementation
+ self->vtable->finalize (self);
+
+ ev_io_stop (EV_DEFAULT_ &self->read_watcher);
+ ev_io_stop (EV_DEFAULT_ &self->write_watcher);
+ xclose (self->socket_fd);
+ write_queue_free (&self->write_queue);
+ ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
+ free (self);
+
+ try_finish_quit (ctx);
+}
+
+static void
+client_write (struct client *self, const void *data, size_t len)
+{
+ if (!soft_assert (!self->flushing) || len == 0)
+ return;
+
+ struct write_req *req = xcalloc (1, sizeof *req);
+ req->data.iov_base = memcpy (xmalloc (len), data, len);
+ req->data.iov_len = len;
+
+ write_queue_add (&self->write_queue, req);
+ ev_io_start (EV_DEFAULT_ &self->write_watcher);
+}
+
+/// Half-close the connection from our side once the write_queue is flushed.
+/// It is the caller's responsibility to destroy the connection upon EOF.
+// XXX: or we might change on_client_readable to do it anyway, seems safe
+static void
+client_shutdown (struct client *self)
+{
+ self->flushing = true;
+ ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE);
+}
+
+/// Try to cleanly close the connection, waiting for the remote client to close
+/// its own side of the connection as a sign that it has processed all the data
+/// it wanted to. The client implementation will not receive any further data.
+/// May directly call client_destroy().
+static void
+client_close (struct client *self)
+{
+ if (self->closing)
+ return;
+
+ self->closing = true;
+ ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
+ client_shutdown (self);
+
+ // We assume the remote client doesn't want our data if it half-closes
+ if (self->received_eof)
+ client_destroy (self);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_read_loop (EV_P_ struct client *client, ev_io *watcher)
+{
+ char buf[8192];
+ ssize_t n_read;
+again:
+ while ((n_read = recv (watcher->fd, buf, sizeof buf, 0)) >= 0)
+ {
+ if (!n_read)
+ {
+ // Don't deliver the EOF condition repeatedly
+ ev_io_stop (EV_A_ watcher);
+ client->received_eof = true;
+ }
+ if (!client->closing
+ && !client->vtable->push (client, buf, n_read))
+ {
+ client_close (client);
+ return false;
+ }
+ if (!n_read)
+ return true;
+ }
+ if (errno == EINTR)
+ goto again;
+ if (errno == EAGAIN)
+ return true;
+
+ client_destroy (client);
+ return false;
+}
+
+static void
+on_client_readable (EV_P_ ev_io *watcher, int revents)
+{
+ struct client *client = watcher->data;
+ (void) revents;
+
+ if (client_read_loop (EV_A_ client, watcher)
+ && client->closing && client->received_eof)
+ client_destroy (client);
+}
+
+static void
+on_client_writable (EV_P_ ev_io *watcher, int revents)
+{
+ struct client *client = watcher->data;
+ (void) loop;
+ (void) revents;
+
+ // TODO: some sort of "on_buffers_flushed" callback for streaming huge
+ // chunks of external (or generated) data. That will need to be
+ // forwarded to "struct request_handler".
+ if (!flush_queue (&client->write_queue, watcher->fd))
+ {
+ client_destroy (client);
+ return;
+ }
+ if (!write_queue_is_empty (&client->write_queue))
+ return;
+
+ ev_io_stop (EV_A_ watcher);
+ if (client->flushing && !client->half_closed)
+ {
+ if (!shutdown (client->socket_fd, SHUT_WR))
+ client->half_closed = true;
+ else
+ client_destroy (client);
+ }
+}
+
+static void
+on_client_timeout (EV_P_ ev_timer *watcher, int revents)
+{
+ (void) loop;
+ (void) revents;
+
+ client_destroy (watcher->data);
+}
+
+/// Create a new instance of a subclass with the given size.
+/// The superclass is assumed to be the first member of the structure.
+static void *
+client_new (EV_P_ size_t size, int sock_fd)
+{
+ struct server_context *ctx = ev_userdata (loop);
+ struct client *self = xcalloc (1, size);
+
+ self->write_queue = write_queue_make ();
+ ev_timer_init (&self->close_timeout_watcher, on_client_timeout, 5., 0.);
+ self->close_timeout_watcher.data = self;
+
+ set_blocking (sock_fd, false);
+ self->socket_fd = sock_fd;
+
+ ev_io_init (&self->read_watcher, on_client_readable, sock_fd, EV_READ);
+ ev_io_init (&self->write_watcher, on_client_writable, sock_fd, EV_WRITE);
+ self->read_watcher.data = self;
+ self->write_watcher.data = self;
+
+ // We're only interested in reading as the write queue is empty now
+ ev_io_start (EV_A_ &self->read_watcher);
+
+ LIST_PREPEND (ctx->clients, self);
+ ctx->n_clients++;
+ return self;
+}
+
+// --- FastCGI client handler --------------------------------------------------
+
+struct client_fcgi
+{
+ struct client client; ///< Parent class
+ struct fcgi_muxer muxer; ///< FastCGI de/multiplexer
+};
+
+struct client_fcgi_request
+{
+ struct fcgi_request *fcgi_request; ///< FastCGI request
+ struct request request; ///< Request
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+client_fcgi_request_write_cb (struct request *req, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, req, struct client_fcgi_request, request);
+ fcgi_request_write (self->fcgi_request, data, len);
+}
+
+static void
+client_fcgi_request_close_cb (struct request *req)
+{
+ FIND_CONTAINER (self, req, struct client_fcgi_request, request);
+ struct fcgi_muxer *muxer = self->fcgi_request->muxer;
+ // No more data to send, terminate the substream/request,
+ // and also the transport if the client didn't specifically ask to keep it
+ if (!fcgi_request_finish (self->fcgi_request))
+ muxer->close_cb (muxer);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_fcgi_request_start (struct fcgi_request *fcgi_request)
+{
+ struct client_fcgi_request *request =
+ fcgi_request->handler_data = xcalloc (1, sizeof *request);
+ request->fcgi_request = fcgi_request;
+ request_init (&request->request);
+ request->request.ctx = ev_userdata (EV_DEFAULT);
+ request->request.write_cb = client_fcgi_request_write_cb;
+ request->request.close_cb = client_fcgi_request_close_cb;
+
+ return request_start (&request->request, &fcgi_request->headers);
+}
+
+static bool
+client_fcgi_request_push
+ (struct fcgi_request *req, const void *data, size_t len)
+{
+ struct client_fcgi_request *request = req->handler_data;
+ return request_push (&request->request, data, len)
+ || fcgi_request_finish (req);
+}
+
+static void
+client_fcgi_request_finalize (struct fcgi_request *req)
+{
+ struct client_fcgi_request *request = req->handler_data;
+ request_free (&request->request);
+ free (request);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+client_fcgi_write_cb (struct fcgi_muxer *mux, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
+ client_write (&self->client, data, len);
+}
+
+static void
+client_fcgi_close_cb (struct fcgi_muxer *mux)
+{
+ FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
+ client_close (&self->client);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_fcgi_push (struct client *client, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, client, struct client_fcgi, client);
+ return fcgi_muxer_push (&self->muxer, data, len);
+}
+
+static void
+client_fcgi_shutdown (struct client *client)
+{
+ FIND_CONTAINER (self, client, struct client_fcgi, client);
+ self->muxer.in_shutdown = true;
+
+ // TODO: respond with FCGI_END_REQUEST: FCGI_REQUEST_COMPLETE to everything?
+ // The FastCGI specification isn't very clear about what we should do.
+}
+
+static void
+client_fcgi_finalize (struct client *client)
+{
+ FIND_CONTAINER (self, client, struct client_fcgi, client);
+ fcgi_muxer_free (&self->muxer);
+}
+
+static struct client_vtable client_fcgi_vtable =
+{
+ .push = client_fcgi_push,
+ .shutdown = client_fcgi_shutdown,
+ .finalize = client_fcgi_finalize,
+};
+
+static struct client *
+client_fcgi_create (EV_P_ int sock_fd)
+{
+ struct client_fcgi *self = client_new (EV_A_ sizeof *self, sock_fd);
+ self->client.vtable = &client_fcgi_vtable;
+
+ fcgi_muxer_init (&self->muxer);
+ self->muxer.write_cb = client_fcgi_write_cb;
+ self->muxer.close_cb = client_fcgi_close_cb;
+ self->muxer.request_start_cb = client_fcgi_request_start;
+ self->muxer.request_push_cb = client_fcgi_request_push;
+ self->muxer.request_finalize_cb = client_fcgi_request_finalize;
+ return &self->client;
+}
+
+// --- SCGI client handler -----------------------------------------------------
+
+struct client_scgi
+{
+ struct client client; ///< Parent class
+ struct scgi_parser parser; ///< SCGI stream parser
+ struct request request; ///< Request (only one per connection)
+ unsigned long remaining_content; ///< Length of input data to be seen
+};
+
+static void
+client_scgi_write_cb (struct request *req, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, req, struct client_scgi, request);
+ client_write (&self->client, data, len);
+}
+
+static void
+client_scgi_close_cb (struct request *req)
+{
+ FIND_CONTAINER (self, req, struct client_scgi, request);
+ // NOTE: this rather really means "close me [the request]"
+ client_close (&self->client);
+}
+
+static bool
+client_scgi_on_headers_read (void *user_data)
+{
+ struct client_scgi *self = user_data;
+ const char *cl = str_map_find (&self->parser.headers, "CONTENT_LENGTH");
+ if (!cl || !xstrtoul (&self->remaining_content, cl, 10))
+ {
+ print_debug ("SCGI request with invalid or missing CONTENT_LENGTH");
+ return false;
+ }
+ return request_start (&self->request, &self->parser.headers);
+}
+
+static bool
+client_scgi_on_content (void *user_data, const void *data, size_t len)
+{
+ struct client_scgi *self = user_data;
+ if (len > self->remaining_content)
+ {
+ print_debug ("SCGI request got more data than CONTENT_LENGTH");
+ return false;
+ }
+ // We're in a slight disagreement with the specification since
+ // this tries to write output before it has read all the input
+ if (!request_push (&self->request, data, len))
+ return false;
+
+ // Signalise end of input to the request handler
+ return (self->remaining_content -= len) != 0
+ || request_push (&self->request, NULL, 0);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_scgi_push (struct client *client, const void *data, size_t len)
+{
+ struct client_scgi *self = (struct client_scgi *) client;
+ struct error *e = NULL;
+ if (scgi_parser_push (&self->parser, data, len, &e))
+ return true;
+
+ if (e != NULL)
+ {
+ print_debug ("SCGI parser failed: %s", e->message);
+ error_free (e);
+ }
+ return false;
+}
+
+static void
+client_scgi_finalize (struct client *client)
+{
+ struct client_scgi *self = (struct client_scgi *) client;
+ request_free (&self->request);
+ scgi_parser_free (&self->parser);
+}
+
+static struct client_vtable client_scgi_vtable =
+{
+ .push = client_scgi_push,
+ .finalize = client_scgi_finalize,
+};
+
+static struct client *
+client_scgi_create (EV_P_ int sock_fd)
+{
+ struct client_scgi *self = client_new (EV_A_ sizeof *self, sock_fd);
+ self->client.vtable = &client_scgi_vtable;
+
+ request_init (&self->request);
+ self->request.ctx = ev_userdata (EV_DEFAULT);
+ self->request.write_cb = client_scgi_write_cb;
+ self->request.close_cb = client_scgi_close_cb;
+
+ self->parser = scgi_parser_make ();
+ self->parser.on_headers_read = client_scgi_on_headers_read;
+ self->parser.on_content = client_scgi_on_content;
+ self->parser.user_data = self;
+ return &self->client;
+}
+
+// --- WebSockets client handler -----------------------------------------------
+
+struct client_ws
+{
+ struct client client; ///< Parent class
+ struct ws_handler handler; ///< WebSockets connection handler
+};
+
+static bool
+client_ws_on_message (struct ws_handler *handler,
+ enum ws_opcode type, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, handler, struct client_ws, handler);
+ if (type != WS_OPCODE_TEXT)
+ {
+ return ws_handler_fail_connection
+ (&self->handler, WS_STATUS_UNSUPPORTED_DATA);
+ }
+
+ struct server_context *ctx = ev_userdata (EV_DEFAULT);
+ struct str response = str_make ();
+ process_json_rpc (ctx, data, len, &response);
+ if (response.len)
+ ws_handler_send_frame (&self->handler,
+ WS_OPCODE_TEXT, response.str, response.len);
+ str_free (&response);
+ return true;
+}
+
+static void
+client_ws_write_cb (struct ws_handler *handler, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, handler, struct client_ws, handler);
+ client_write (&self->client, data, len);
+}
+
+static void
+client_ws_close_cb (struct ws_handler *handler, bool half_close)
+{
+ FIND_CONTAINER (self, handler, struct client_ws, handler);
+ (half_close ? client_shutdown : client_destroy) (&self->client);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_ws_push (struct client *client, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, client, struct client_ws, client);
+ // client_close() will correctly destroy the client on EOF
+ return ws_handler_push (&self->handler, data, len);
+}
+
+static void
+client_ws_shutdown (struct client *client)
+{
+ FIND_CONTAINER (self, client, struct client_ws, client);
+ if (self->handler.state == WS_HANDLER_CONNECTING)
+ // No on_close, no problem
+ client_destroy (&self->client);
+ else if (self->handler.state == WS_HANDLER_OPEN)
+ ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0);
+}
+
+static void
+client_ws_finalize (struct client *client)
+{
+ FIND_CONTAINER (self, client, struct client_ws, client);
+ ws_handler_free (&self->handler);
+}
+
+static struct client_vtable client_ws_vtable =
+{
+ .push = client_ws_push,
+ .shutdown = client_ws_shutdown,
+ .finalize = client_ws_finalize,
+};
+
+static struct client *
+client_ws_create (EV_P_ int sock_fd)
+{
+ struct client_ws *self = client_new (EV_A_ sizeof *self, sock_fd);
+ self->client.vtable = &client_ws_vtable;
+
+ ws_handler_init (&self->handler);
+ self->handler.on_message = client_ws_on_message;
+ self->handler.write_cb = client_ws_write_cb;
+ self->handler.close_cb = client_ws_close_cb;
+
+ // One mebibyte seems to be a reasonable value
+ self->handler.max_payload_len = 1 << 10;
+
+ ws_handler_start (&self->handler);
+ return &self->client;
+}
+
+// --- Basic server stuff ------------------------------------------------------
+
+typedef struct client *(*client_create_fn) (EV_P_ int sock_fd);
+
+struct listener
+{
+ int fd; ///< Listening socket FD
+ ev_io watcher; ///< New connection available
+ client_create_fn create; ///< Client constructor
+};
+
+static void
+close_listeners (struct server_context *self)
+{
+ for (size_t i = 0; i < self->n_listeners; i++)
+ {
+ struct listener *listener = &self->listeners[i];
+ if (listener->fd == -1)
+ continue;
+
+ ev_io_stop (EV_DEFAULT_ &listener->watcher);
+ xclose (listener->fd);
+ listener->fd = -1;
+ }
+}
+
+static void
+try_finish_quit (struct server_context *self)
+{
+ if (!self->quitting || self->clients)
+ return;
+
+ ev_timer_stop (EV_DEFAULT_ &self->quit_timeout_watcher);
+ ev_break (EV_DEFAULT_ EVBREAK_ALL);
+}
+
+static void
+on_quit_timeout (EV_P_ ev_timer *watcher, int revents)
+{
+ struct server_context *self = watcher->data;
+ (void) loop;
+ (void) revents;
+
+ LIST_FOR_EACH (struct client, iter, self->clients)
+ client_destroy (iter);
+}
+
+static void
+initiate_quit (struct server_context *self)
+{
+ self->quitting = true;
+ close_listeners (self);
+
+ // Wait a little while for all clients to clean up, if necessary
+ LIST_FOR_EACH (struct client, iter, self->clients)
+ if (iter->vtable->shutdown)
+ iter->vtable->shutdown (iter);
+ ev_timer_start (EV_DEFAULT_ &self->quit_timeout_watcher);
+ try_finish_quit (self);
+}
+
+static void
+on_client_available (EV_P_ ev_io *watcher, int revents)
+{
+ struct server_context *ctx = ev_userdata (loop);
+ struct listener *listener = watcher->data;
+ (void) revents;
+
+ while (true)
+ {
+ int sock_fd = accept (watcher->fd, NULL, NULL);
+ if (sock_fd != -1)
+ listener->create (EV_A_ sock_fd);
+ else if (errno == EAGAIN)
+ return;
+ else if (errno != EINTR && errno != EMFILE
+ && errno != ECONNRESET && errno != ECONNABORTED)
+ break;
+ }
+
+ // Stop accepting connections to prevent busy looping
+ ev_io_stop (EV_A_ watcher);
+
+ print_fatal ("%s: %s", "accept", strerror (errno));
+ initiate_quit (ctx);
+}
+
+// --- Application setup -------------------------------------------------------
+
+/// This function handles values that require validation before their first use,
+/// or some kind of a transformation (such as conversion to an integer) needs
+/// to be done before they can be used directly.
+static bool
+parse_config (struct server_context *ctx, struct error **e)
+{
+ (void) ctx;
+ (void) e;
+
+ return true;
+}
+
+static int
+listener_bind (struct addrinfo *gai_iter)
+{
+ int fd = socket (gai_iter->ai_family,
+ gai_iter->ai_socktype, gai_iter->ai_protocol);
+ if (fd == -1)
+ return -1;
+ set_cloexec (fd);
+
+ int yes = 1;
+ soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE,
+ &yes, sizeof yes) != -1);
+ soft_assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
+ &yes, sizeof yes) != -1);
+
+ char host[NI_MAXHOST], port[NI_MAXSERV];
+ host[0] = port[0] = '\0';
+ int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen,
+ host, sizeof host, port, sizeof port,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (err)
+ print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
+
+ char *address = format_host_port_pair (host, port);
+ if (bind (fd, gai_iter->ai_addr, gai_iter->ai_addrlen))
+ print_error ("bind to %s failed: %s", address, strerror (errno));
+ else if (listen (fd, 16 /* arbitrary number */))
+ print_error ("listen on %s failed: %s", address, strerror (errno));
+ else
+ {
+ print_status ("listening on %s", address);
+ free (address);
+ return fd;
+ }
+
+ free (address);
+ xclose (fd);
+ return -1;
+}
+
+static void
+listener_add (struct server_context *ctx, const char *host, const char *port,
+ const struct addrinfo *gai_hints, client_create_fn create)
+{
+ struct addrinfo *gai_result, *gai_iter;
+ int err = getaddrinfo (host, port, gai_hints, &gai_result);
+ if (err)
+ {
+ char *address = format_host_port_pair (host, port);
+ print_error ("bind to %s failed: %s: %s",
+ address, "getaddrinfo", gai_strerror (err));
+ free (address);
+ return;
+ }
+
+ int fd;
+ for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next)
+ {
+ if ((fd = listener_bind (gai_iter)) == -1)
+ continue;
+ set_blocking (fd, false);
+
+ struct listener *listener = &ctx->listeners[ctx->n_listeners++];
+ ev_io_init (&listener->watcher, on_client_available, fd, EV_READ);
+ ev_io_start (EV_DEFAULT_ &listener->watcher);
+ listener->watcher.data = listener;
+ listener->create = create;
+ listener->fd = fd;
+ break;
+ }
+ freeaddrinfo (gai_result);
+}
+
+static void
+get_ports_from_config (struct server_context *ctx,
+ const char *key, struct strv *out)
+{
+ const char *ports;
+ if ((ports = str_map_find (&ctx->config, key)))
+ cstr_split (ports, ",", true, out);
+}
+
+static bool
+setup_listen_fds (struct server_context *ctx, struct error **e)
+{
+ static const struct addrinfo gai_hints =
+ {
+ .ai_socktype = SOCK_STREAM,
+ .ai_flags = AI_PASSIVE,
+ };
+
+ struct strv ports_fcgi = strv_make ();
+ struct strv ports_scgi = strv_make ();
+ struct strv ports_ws = strv_make ();
+
+ get_ports_from_config (ctx, "port_fastcgi", &ports_fcgi);
+ get_ports_from_config (ctx, "port_scgi", &ports_scgi);
+ get_ports_from_config (ctx, "port_ws", &ports_ws);
+
+ const char *bind_host = str_map_find (&ctx->config, "bind_host");
+ size_t n_ports = ports_fcgi.len + ports_scgi.len + ports_ws.len;
+ ctx->listeners = xcalloc (n_ports, sizeof *ctx->listeners);
+
+ for (size_t i = 0; i < ports_fcgi.len; i++)
+ listener_add (ctx, bind_host, ports_fcgi.vector[i],
+ &gai_hints, client_fcgi_create);
+ for (size_t i = 0; i < ports_scgi.len; i++)
+ listener_add (ctx, bind_host, ports_scgi.vector[i],
+ &gai_hints, client_scgi_create);
+ for (size_t i = 0; i < ports_ws.len; i++)
+ listener_add (ctx, bind_host, ports_ws.vector[i],
+ &gai_hints, client_ws_create);
+
+ strv_free (&ports_fcgi);
+ strv_free (&ports_scgi);
+ strv_free (&ports_ws);
+
+ if (!ctx->n_listeners)
+ {
+ error_set (e, "%s: %s",
+ "network setup failed", "no ports to listen on");
+ return false;
+ }
+ return true;
+}
+
+static bool
+app_lock_pid_file (struct server_context *ctx, struct error **e)
+{
+ const char *path = str_map_find (&ctx->config, "pid_file");
+ if (!path)
+ return true;
+
+ char *resolved = resolve_filename (path, resolve_relative_runtime_filename);
+ bool result = lock_pid_file (resolved, e) != -1;
+ free (resolved);
+ return result;
+}
+
+// --- Tests -------------------------------------------------------------------
+
+static void
+test_misc (void)
+{
+ soft_assert ( validate_json_rpc_content_type
+ ("application/JSON; charset=\"utf-8\""));
+ soft_assert (!validate_json_rpc_content_type
+ ("text/html; charset=\"utf-8\""));
+
+ char *canon = canonicalize_url_path ("///../../../etc/./passwd");
+ soft_assert (!strcmp (canon, "/etc/passwd"));
+ free (canon);
+}
+
+int
+test_main (int argc, char *argv[])
+{
+ struct test test;
+ test_init (&test, argc, argv);
+
+ test_add_simple (&test, "/misc", NULL, test_misc);
+
+ // TODO: write more tests
+ // TODO: test the server handler (happy path)
+
+ return test_run (&test);
+}
+
+// --- Main program ------------------------------------------------------------
+
+static void
+on_termination_signal (EV_P_ ev_signal *handle, int revents)
+{
+ struct server_context *ctx = ev_userdata (loop);
+ (void) handle;
+ (void) revents;
+
+ if (ctx->quitting)
+ {
+ // Double C-c from the terminal accelerates the process
+ LIST_FOR_EACH (struct client, iter, ctx->clients)
+ client_destroy (iter);
+ }
+ else
+ initiate_quit (ctx);
+}
+
+static void
+setup_signal_handlers (struct server_context *ctx)
+{
+ ev_signal_init (&ctx->sigterm_watcher, on_termination_signal, SIGTERM);
+ ev_signal_start (EV_DEFAULT_ &ctx->sigterm_watcher);
+
+ ev_signal_init (&ctx->sigint_watcher, on_termination_signal, SIGINT);
+ ev_signal_start (EV_DEFAULT_ &ctx->sigint_watcher);
+
+ (void) signal (SIGPIPE, SIG_IGN);
+}
+
+static void
+daemonize (struct server_context *ctx)
+{
+ print_status ("daemonizing...");
+
+ if (chdir ("/"))
+ exit_fatal ("%s: %s", "chdir", strerror (errno));
+
+ // Because of systemd, we need to exit the parent process _after_ writing
+ // a PID file, otherwise our grandchild would receive a SIGTERM
+ int sync_pipe[2];
+ if (pipe (sync_pipe))
+ exit_fatal ("%s: %s", "pipe", strerror (errno));
+
+ pid_t pid;
+ if ((pid = fork ()) < 0)
+ exit_fatal ("%s: %s", "fork", strerror (errno));
+ else if (pid)
+ {
+ // Wait until all write ends of the pipe are closed, which can mean
+ // either success or failure, we don't need to care
+ xclose (sync_pipe[PIPE_WRITE]);
+
+ char dummy;
+ if (read (sync_pipe[PIPE_READ], &dummy, 1) < 0)
+ exit_fatal ("%s: %s", "read", strerror (errno));
+
+ exit (EXIT_SUCCESS);
+ }
+
+ setsid ();
+ signal (SIGHUP, SIG_IGN);
+
+ if ((pid = fork ()) < 0)
+ exit_fatal ("%s: %s", "fork", strerror (errno));
+ else if (pid)
+ exit (EXIT_SUCCESS);
+
+ openlog (PROGRAM_NAME, LOG_NDELAY | LOG_NOWAIT | LOG_PID, 0);
+ g_log_message_real = log_message_syslog;
+
+ // Write the PID file (if so configured) and get rid of the pipe, so that
+ // the read() in our grandparent finally returns zero (no write ends)
+ struct error *e = NULL;
+ if (!app_lock_pid_file (ctx, &e))
+ exit_fatal ("%s", e->message);
+
+ xclose (sync_pipe[PIPE_READ]);
+ xclose (sync_pipe[PIPE_WRITE]);
+
+ // XXX: we may close our own descriptors this way, crippling ourselves;
+ // there is no real guarantee that we will start with all three
+ // descriptors open. In theory we could try to enumerate the descriptors
+ // at the start of main().
+ for (int i = 0; i < 3; i++)
+ xclose (i);
+
+ int tty = open ("/dev/null", O_RDWR);
+ if (tty != 0 || dup (0) != 1 || dup (0) != 2)
+ exit_fatal ("failed to reopen FD's: %s", strerror (errno));
+}
+
+static void
+parse_program_arguments (int argc, char **argv)
+{
+ static const struct opt opts[] =
+ {
+ { 't', "test", NULL, 0, "self-test" },
+ { 'd', "debug", NULL, 0, "run in debug mode" },
+ { 'h', "help", NULL, 0, "display this help and exit" },
+ { 'V', "version", NULL, 0, "output version information and exit" },
+ { 'w', "write-default-cfg", "FILENAME",
+ OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
+ "write a default configuration file and exit" },
+ { 0, NULL, NULL, 0, NULL }
+ };
+
+ struct opt_handler oh =
+ opt_handler_make (argc, argv, opts, NULL, "JSON-RPC 2.0 demo server.");
+
+ int c;
+ while ((c = opt_handler_get (&oh)) != -1)
+ switch (c)
+ {
+ case 't':
+ test_main (argc, argv);
+ exit (EXIT_SUCCESS);
+ case 'd':
+ g_debug_mode = true;
+ break;
+ case 'h':
+ opt_handler_usage (&oh, stdout);
+ exit (EXIT_SUCCESS);
+ case 'V':
+ printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
+ exit (EXIT_SUCCESS);
+ case 'w':
+ call_simple_config_write_default (optarg, g_config_table);
+ exit (EXIT_SUCCESS);
+ default:
+ print_error ("wrong options");
+ opt_handler_usage (&oh, stderr);
+ exit (EXIT_FAILURE);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc)
+ {
+ opt_handler_usage (&oh, stderr);
+ exit (EXIT_FAILURE);
+ }
+ opt_handler_free (&oh);
+}
+
+int
+main (int argc, char *argv[])
+{
+ parse_program_arguments (argc, argv);
+
+ print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
+
+ struct server_context ctx;
+ server_context_init (&ctx);
+
+ struct error *e = NULL;
+ if (!simple_config_update_from_file (&ctx.config, &e))
+ {
+ print_error ("error loading configuration: %s", e->message);
+ error_free (e);
+ exit (EXIT_FAILURE);
+ }
+
+ struct ev_loop *loop;
+ if (!(loop = EV_DEFAULT))
+ exit_fatal ("libev initialization failed");
+
+ ev_set_userdata (loop, &ctx);
+ setup_signal_handlers (&ctx);
+
+ LIST_PREPEND (ctx.handlers, &g_request_handler_static);
+ LIST_PREPEND (ctx.handlers, &g_request_handler_json_rpc);
+
+ if (!parse_config (&ctx, &e)
+ || !setup_listen_fds (&ctx, &e))
+ {
+ print_error ("%s", e->message);
+ error_free (e);
+ exit (EXIT_FAILURE);
+ }
+
+ if (!g_debug_mode)
+ daemonize (&ctx);
+ else if (!app_lock_pid_file (&ctx, &e))
+ exit_fatal ("%s", e->message);
+
+ ev_run (loop, 0);
+ ev_loop_destroy (loop);
+
+ server_context_free (&ctx);
+ return EXIT_SUCCESS;
+}
--
cgit v1.2.3-70-g09d2