mirror of
https://github.com/DaedalusX64/daedalus.git
synced 2025-04-02 10:21:48 -04:00
1725 lines
44 KiB
C
1725 lines
44 KiB
C
|
|
#include "webby.h"
|
|
|
|
/* Copyright (c) 2012, Andreas Fredriksson < dep at defmacro dot se > */
|
|
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <sys/select.h>
|
|
|
|
#if defined(__PS3__)
|
|
#include "webby_ps3.h"
|
|
#elif defined(__XBOX__)
|
|
#include "webby_xbox.h"
|
|
#elif defined(_WIN32)
|
|
#include "webby_win32.h"
|
|
#else
|
|
#include "webby_unix.h"
|
|
#endif
|
|
|
|
#define WB_WEBSOCKET_VERSION "13"
|
|
#define WB_ALIGN_ARB(x, a) (((x) + ((a)-1)) & ~((a)-1))
|
|
#define WB_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
|
|
|
|
static const char continue_header[] = "HTTP/1.1 100 Continue\r\n\r\n";
|
|
static const size_t continue_header_len = sizeof(continue_header) - 1;
|
|
|
|
static const char websocket_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
static const size_t websocket_guid_len = sizeof(websocket_guid) - 1;
|
|
|
|
static const unsigned char websocket_pong[] = { 0x80, WEBBY_WS_OP_PONG, 0x00 };
|
|
|
|
static const struct WebbyHeader plain_text_headers[] =
|
|
{
|
|
{ "Content-Type", "text/plain" },
|
|
};
|
|
|
|
#ifdef _MSC_VER
|
|
/* MSVC keeps complaining about constant conditionals inside the FD_SET() macro. */
|
|
#pragma warning(disable: 4127)
|
|
#endif
|
|
|
|
struct WebbyConnection;
|
|
struct WebbyRequest;
|
|
|
|
enum
|
|
{
|
|
WB_ALIVE = 1 << 0,
|
|
WB_FRESH_CONNECTION = 1 << 1,
|
|
WB_CLOSE_AFTER_RESPONSE = 1 << 2,
|
|
WB_CHUNKED_RESPONSE = 1 << 3,
|
|
WB_WEBSOCKET = 1 << 4
|
|
};
|
|
|
|
enum
|
|
{
|
|
WBC_REQUEST,
|
|
WBC_SEND_CONTINUE,
|
|
WBC_SERVE,
|
|
WBC_WEBSOCKET
|
|
};
|
|
|
|
struct WebbyBuffer
|
|
{
|
|
int used;
|
|
int max;
|
|
unsigned char* data;
|
|
};
|
|
|
|
struct WebbyConnectionPrv
|
|
{
|
|
struct WebbyConnection public_data;
|
|
|
|
unsigned short flags;
|
|
unsigned short state; /* WBC_xxx */
|
|
webby_socket_t socket;
|
|
|
|
struct WebbyBuffer header_buf;
|
|
struct WebbyBuffer io_buf;
|
|
int header_body_left;
|
|
int io_data_left;
|
|
int continue_data_left;
|
|
int body_bytes_read;
|
|
struct WebbyServer* server;
|
|
struct WebbyWsFrame ws_frame;
|
|
unsigned char ws_opcode;
|
|
int blocking_count; /* number of times blocking has been requested */
|
|
};
|
|
|
|
struct WebbyServer
|
|
{
|
|
struct WebbyServerConfig config;
|
|
size_t memory_size;
|
|
webby_socket_t socket;
|
|
int connection_count;
|
|
struct WebbyConnectionPrv connections[1];
|
|
};
|
|
|
|
static void dbg(struct WebbyServer *srv, const char *fmt, ...)
|
|
{
|
|
char buffer[1024];
|
|
va_list args;
|
|
|
|
if (srv->config.flags & WEBBY_SERVER_LOG_DEBUG)
|
|
{
|
|
va_start(args, fmt);
|
|
vsnprintf(buffer, sizeof buffer, fmt, args);
|
|
va_end(args);
|
|
|
|
buffer[(sizeof buffer)-1] = '\0';
|
|
(*srv->config.log)(buffer);
|
|
}
|
|
}
|
|
|
|
static int make_connection_blocking(struct WebbyConnectionPrv *conn)
|
|
{
|
|
if (0 == conn->blocking_count)
|
|
{
|
|
if (0 != wb_set_blocking(conn->socket, 1))
|
|
{
|
|
dbg(conn->server, "failed to switch connection to blocking");
|
|
conn->flags &= ~WB_ALIVE;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
++conn->blocking_count;
|
|
return 0;
|
|
}
|
|
|
|
static int make_connection_nonblocking(struct WebbyConnectionPrv *conn)
|
|
{
|
|
int count = conn->blocking_count;
|
|
|
|
if (1 == count)
|
|
{
|
|
if (0 != wb_set_blocking(conn->socket, 0))
|
|
{
|
|
dbg(conn->server, "failed to switch connection to non-blocking");
|
|
conn->flags &= ~WB_ALIVE;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
conn->blocking_count = count - 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* URL-decode input buffer into destination buffer.
|
|
* 0-terminate the destination buffer. Return the length of decoded data.
|
|
* form-url-encoded data differs from URI encoding in a way that it
|
|
* uses '+' as character for space, see RFC 1866 section 8.2.1
|
|
* http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
|
|
*
|
|
* This bit of code was taken from mongoose.
|
|
*/
|
|
static size_t url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, int is_form_url_encoded)
|
|
{
|
|
size_t i, j;
|
|
int a, b;
|
|
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
|
|
|
|
for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
|
|
if (src[i] == '%' &&
|
|
isxdigit(* (const unsigned char *) (src + i + 1)) &&
|
|
isxdigit(* (const unsigned char *) (src + i + 2))) {
|
|
a = tolower(* (const unsigned char *) (src + i + 1));
|
|
b = tolower(* (const unsigned char *) (src + i + 2));
|
|
dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
|
|
i += 2;
|
|
} else if (is_form_url_encoded && src[i] == '+') {
|
|
dst[j] = ' ';
|
|
} else {
|
|
dst[j] = src[i];
|
|
}
|
|
}
|
|
|
|
#undef HEXTOI
|
|
|
|
dst[j] = '\0'; /* Null-terminate the destination */
|
|
|
|
return j;
|
|
}
|
|
|
|
/* Pulled from mongoose */
|
|
int WebbyFindQueryVar(const char *buf, const char *name, char *dst, size_t dst_len)
|
|
{
|
|
const char *p, *e, *s;
|
|
size_t name_len;
|
|
int len;
|
|
size_t buf_len = strlen(buf);
|
|
|
|
name_len = strlen(name);
|
|
e = buf + buf_len;
|
|
len = -1;
|
|
dst[0] = '\0';
|
|
|
|
// buf is "var1=val1&var2=val2...". Find variable first
|
|
for (p = buf; p != NULL && p + name_len < e; p++)
|
|
{
|
|
if ((p == buf || p[-1] == '&') && p[name_len] == '=' && 0 == strncasecmp(name, p, name_len))
|
|
{
|
|
// Point p to variable value
|
|
p += name_len + 1;
|
|
|
|
// Point s to the end of the value
|
|
s = (const char *) memchr(p, '&', (size_t)(e - p));
|
|
if (s == NULL) {
|
|
s = e;
|
|
}
|
|
assert(s >= p);
|
|
|
|
// Decode variable into destination buffer
|
|
if ((size_t) (s - p) < dst_len)
|
|
{
|
|
len = (int) url_decode(p, (size_t)(s - p), dst, dst_len, 1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
enum
|
|
{
|
|
BASE64_QUADS_BEFORE_LINEBREAK = 19
|
|
};
|
|
|
|
static size_t base64_bufsize(size_t input_size)
|
|
{
|
|
size_t triplets = (input_size + 2) / 3;
|
|
size_t base_size = 4 * triplets;
|
|
size_t line_breaks = 2 * (triplets / BASE64_QUADS_BEFORE_LINEBREAK);
|
|
size_t null_termination = 1;
|
|
return base_size + line_breaks + null_termination;
|
|
}
|
|
|
|
static int base64_encode(char* output, size_t output_size, const unsigned char *input, size_t input_size)
|
|
{
|
|
static const char enc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
size_t i = 0;
|
|
int line_out = 0;
|
|
|
|
if (output_size < base64_bufsize(input_size))
|
|
return 1;
|
|
|
|
while (i < input_size)
|
|
{
|
|
unsigned int idx_0, idx_1, idx_2, idx_3;
|
|
unsigned int i0;
|
|
|
|
i0 = (input[i]) << 16; i++;
|
|
i0 |= (i < input_size ? input[i] : 0) << 8; i++;
|
|
i0 |= (i < input_size ? input[i] : 0); i++;
|
|
|
|
idx_0 = (i0 & 0xfc0000) >> 18; i0 <<= 6;
|
|
idx_1 = (i0 & 0xfc0000) >> 18; i0 <<= 6;
|
|
idx_2 = (i0 & 0xfc0000) >> 18; i0 <<= 6;
|
|
idx_3 = (i0 & 0xfc0000) >> 18;
|
|
|
|
if (i - 1 > input_size)
|
|
idx_2 = 64;
|
|
if (i > input_size)
|
|
idx_3 = 64;
|
|
|
|
*output++ = enc[idx_0];
|
|
*output++ = enc[idx_1];
|
|
*output++ = enc[idx_2];
|
|
*output++ = enc[idx_3];
|
|
|
|
if (++line_out == BASE64_QUADS_BEFORE_LINEBREAK) {
|
|
*output++ = '\r';
|
|
*output++ = '\n';
|
|
}
|
|
}
|
|
|
|
*output = '\0';
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int sha1_rol(unsigned int value, unsigned int bits)
|
|
{
|
|
return ((value) << bits) | (value >> (32 - bits));
|
|
}
|
|
|
|
struct sha1 {
|
|
unsigned int state[5];
|
|
unsigned int msg_size[2];
|
|
size_t buf_used;
|
|
unsigned char buffer[64];
|
|
};
|
|
|
|
static void sha1_hash_block(unsigned int state[5], const unsigned char *block)
|
|
{
|
|
int i;
|
|
unsigned int a, b, c, d, e;
|
|
unsigned int w[80];
|
|
|
|
/* Prepare message schedule */
|
|
for (i = 0; i < 16; ++i)
|
|
w[i] =
|
|
(((unsigned int)block[(i*4)+0]) << 24) |
|
|
(((unsigned int)block[(i*4)+1]) << 16) |
|
|
(((unsigned int)block[(i*4)+2]) << 8) |
|
|
(((unsigned int)block[(i*4)+3]) << 0);
|
|
|
|
for (i = 16; i < 80; ++i)
|
|
w[i] = sha1_rol(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
|
|
|
|
/* Initialize working variables */
|
|
a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4];
|
|
|
|
/* This is the core loop for each 20-word span. */
|
|
#define SHA1_LOOP(start, end, func, constant) \
|
|
for (i = (start); i < (end); ++i) \
|
|
{ \
|
|
unsigned int t = sha1_rol(a, 5) + (func) + e + (constant) + w[i]; \
|
|
e = d; d = c; c = sha1_rol(b, 30); b = a; a = t; \
|
|
}
|
|
|
|
SHA1_LOOP( 0, 20, ((b & c) ^ (~b & d)), 0x5a827999)
|
|
SHA1_LOOP(20, 40, (b ^ c ^ d), 0x6ed9eba1)
|
|
SHA1_LOOP(40, 60, ((b & c) ^ (b & d) ^ (c & d)), 0x8f1bbcdc)
|
|
SHA1_LOOP(60, 80, (b ^ c ^ d), 0xca62c1d6)
|
|
|
|
#undef SHA1_LOOP
|
|
|
|
/* Update state */
|
|
state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e;
|
|
}
|
|
|
|
static void sha1_init(struct sha1 *s)
|
|
{
|
|
s->state[0] = 0x67452301;
|
|
s->state[1] = 0xefcdab89;
|
|
s->state[2] = 0x98badcfe;
|
|
s->state[3] = 0x10325476;
|
|
s->state[4] = 0xc3d2e1f0;
|
|
s->msg_size[0] = 0;
|
|
s->msg_size[1] = 0;
|
|
s->buf_used = 0;
|
|
}
|
|
|
|
static void sha1_update(struct sha1 *s, const void *data_, size_t size)
|
|
{
|
|
const char *data = (const char*) data_;
|
|
unsigned int size_lo;
|
|
unsigned int size_lo_orig;
|
|
size_t remain = size;
|
|
|
|
while (remain > 0)
|
|
{
|
|
size_t buf_space = (sizeof s->buffer) - s->buf_used;
|
|
size_t copy_size = remain < buf_space ? remain : buf_space;
|
|
memcpy(s->buffer + s->buf_used, data, copy_size);
|
|
s->buf_used += copy_size;
|
|
data += copy_size;
|
|
remain -= copy_size;
|
|
|
|
if (s->buf_used == sizeof s->buffer)
|
|
{
|
|
sha1_hash_block(s->state, s->buffer);
|
|
s->buf_used = 0;
|
|
}
|
|
}
|
|
|
|
size_lo = size_lo_orig = s->msg_size[1];
|
|
size_lo += (unsigned int) (size * 8);
|
|
|
|
if (size_lo < size_lo_orig)
|
|
s->msg_size[0] += 1;
|
|
|
|
s->msg_size[1] = size_lo;
|
|
}
|
|
|
|
static void sha1_final(unsigned char digest[20], struct sha1 *s)
|
|
{
|
|
unsigned char zero = 0x00;
|
|
unsigned char one_bit = 0x80;
|
|
unsigned char count_data[8];
|
|
int i;
|
|
|
|
/* Generate size data in bit endian format */
|
|
for (i = 0; i < 8; ++i)
|
|
{
|
|
unsigned int word = s->msg_size[i >> 2];
|
|
count_data[i] = (unsigned char) (word >> ((3 - (i & 3)) * 8));
|
|
}
|
|
|
|
/* Set trailing one-bit */
|
|
sha1_update(s, &one_bit, 1);
|
|
|
|
/* Emit null padding to to make room for 64 bits of size info in the last 512 bit block */
|
|
while (s->buf_used != 56)
|
|
sha1_update(s, &zero, 1);
|
|
|
|
/* Write size in bits as last 64-bits */
|
|
sha1_update(s, count_data, 8);
|
|
|
|
/* Make sure we actually finalized our last block */
|
|
assert(0 == s->buf_used);
|
|
|
|
/* Generate digest */
|
|
for (i = 0; i < 20; ++i)
|
|
{
|
|
unsigned int word = s->state[i >> 2];
|
|
unsigned char byte = (unsigned char) ((word >> ((3 - (i & 3)) * 8)) & 0xff);
|
|
digest[i] = byte;
|
|
}
|
|
}
|
|
|
|
static int discard_incoming_data(struct WebbyConnection* conn, int count)
|
|
{
|
|
while (count > 0)
|
|
{
|
|
char buffer[1024];
|
|
int read_size = count > (int) sizeof buffer ? (int) sizeof buffer : count;
|
|
if (0 != WebbyRead(conn, buffer, (size_t) read_size))
|
|
return -1;
|
|
|
|
count -= read_size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *WebbyFindHeader(struct WebbyConnection *conn, const char *name)
|
|
{
|
|
int i, count;
|
|
for (i = 0, count = conn->request.header_count; i < count; ++i)
|
|
{
|
|
if (0 == strcasecmp(conn->request.headers[i].name, name))
|
|
{
|
|
return conn->request.headers[i].value;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int
|
|
WebbyServerMemoryNeeded(const struct WebbyServerConfig *config)
|
|
{
|
|
return
|
|
WB_ALIGN_ARB(sizeof(struct WebbyServer), 16) +
|
|
WB_ALIGN_ARB((config->connection_max - 1) * sizeof(struct WebbyConnectionPrv), 16) +
|
|
config->connection_max * config->request_buffer_size +
|
|
config->connection_max * config->io_buffer_size;
|
|
}
|
|
|
|
struct WebbyServer*
|
|
WebbyServerInit(struct WebbyServerConfig *config, void *memory, size_t memory_size)
|
|
{
|
|
int i;
|
|
struct WebbyServer *server = (struct WebbyServer*) memory;
|
|
unsigned char *buffer = (unsigned char*) memory;
|
|
|
|
memset(buffer, 0, memory_size);
|
|
|
|
server->config = *config;
|
|
server->memory_size = memory_size;
|
|
server->socket = WB_INVALID_SOCKET;
|
|
|
|
buffer +=
|
|
WB_ALIGN_ARB(sizeof(struct WebbyServer), 16) +
|
|
WB_ALIGN_ARB((config->connection_max - 1) * sizeof(struct WebbyConnectionPrv), 16);
|
|
|
|
for (i = 0; i < config->connection_max; ++i)
|
|
{
|
|
server->connections[i].server = server;
|
|
|
|
server->connections[i].header_buf.data = buffer;
|
|
buffer += config->request_buffer_size;
|
|
|
|
server->connections[i].io_buf.data = buffer;
|
|
buffer += config->io_buffer_size;
|
|
}
|
|
|
|
assert((size_t)(buffer - (unsigned char*) memory) <= memory_size);
|
|
|
|
server->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
|
|
dbg(server, "Server socket = %d", (int) server->socket);
|
|
|
|
if (!wb_valid_socket(server->socket))
|
|
{
|
|
dbg(server, "failed to initialized server socket: %d", wb_socket_error());
|
|
goto error;
|
|
}
|
|
|
|
{
|
|
int on = 1;
|
|
int off = 0;
|
|
setsockopt(server->socket, SOL_SOCKET, SO_REUSEADDR, (const char*) &on, sizeof(int));
|
|
setsockopt(server->socket, SOL_SOCKET, SO_LINGER, (const char*) &off, sizeof(int));
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
/* Don't generate SIGPIPE when writing to dead socket, we check all writes. */
|
|
signal(SIGPIPE, SIG_IGN);
|
|
#endif
|
|
|
|
if (0 != wb_set_blocking(server->socket, 0))
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
{
|
|
struct sockaddr_in bind_addr;
|
|
|
|
dbg(server, "binding to %s:%d", config->bind_address, config->listening_port);
|
|
|
|
memset(&bind_addr, 0, sizeof bind_addr); // use 0.0.0.0
|
|
bind_addr.sin_family = AF_INET;
|
|
bind_addr.sin_addr.s_addr = inet_addr(config->bind_address);
|
|
bind_addr.sin_port = htons((unsigned short) config->listening_port);
|
|
|
|
if (0 != bind(server->socket, (struct sockaddr*) &bind_addr, sizeof bind_addr))
|
|
{
|
|
dbg(server, "bind() failed: %d", wb_socket_error());
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (0 != listen(server->socket, SOMAXCONN))
|
|
{
|
|
dbg(server, "listen() failed: %d", wb_socket_error());
|
|
wb_close_socket(server->socket);
|
|
goto error;
|
|
}
|
|
|
|
dbg(server, "server initialized");
|
|
return server;
|
|
|
|
error:
|
|
if (wb_valid_socket(server->socket))
|
|
{
|
|
wb_close_socket(server->socket);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void WebbyServerShutdown(struct WebbyServer *srv)
|
|
{
|
|
int i;
|
|
wb_close_socket(srv->socket);
|
|
|
|
for (i = 0; i < srv->connection_count; ++i)
|
|
{
|
|
wb_close_socket(srv->connections[i].socket);
|
|
}
|
|
|
|
memset(srv, 0, srv->memory_size);
|
|
}
|
|
|
|
static int wb_config_incoming_socket(webby_socket_t socket)
|
|
{
|
|
int err;
|
|
|
|
if (0 != (err = wb_set_blocking(socket, 0)))
|
|
{
|
|
return err;
|
|
}
|
|
|
|
{
|
|
int off = 0;
|
|
setsockopt(socket, SOL_SOCKET, SO_LINGER, (const char*) &off, sizeof(int));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void reset_connection(struct WebbyServer *srv, struct WebbyConnectionPrv *conn)
|
|
{
|
|
conn->header_buf.used = 0;
|
|
conn->header_buf.max = srv->config.request_buffer_size;
|
|
conn->io_buf.used = 0;
|
|
conn->io_buf.max = srv->config.io_buffer_size;
|
|
conn->header_body_left = 0;
|
|
conn->io_data_left = 0;
|
|
conn->continue_data_left = 0;
|
|
conn->body_bytes_read = 0;
|
|
conn->state = WBC_REQUEST;
|
|
conn->public_data.user_data = NULL;
|
|
conn->blocking_count = 0;
|
|
}
|
|
|
|
static int wb_on_incoming(struct WebbyServer *srv)
|
|
{
|
|
int connection_index;
|
|
char WB_ALIGN(8) client_addr[64];
|
|
struct WebbyConnectionPrv* connection;
|
|
webby_socklen_t client_addr_len = sizeof client_addr;
|
|
webby_socket_t fd;
|
|
|
|
/* Make sure we have space for a new connection */
|
|
connection_index = srv->connection_count;
|
|
|
|
if (connection_index == srv->config.connection_max)
|
|
{
|
|
dbg(srv, "out of connection slots");
|
|
return 1;
|
|
}
|
|
|
|
/* Accept the incoming connection. */
|
|
fd = accept(srv->socket, (struct sockaddr*) &client_addr[0], &client_addr_len);
|
|
|
|
if (!wb_valid_socket(fd))
|
|
{
|
|
int err = wb_socket_error();
|
|
if (!wb_is_blocking_error(err))
|
|
dbg(srv, "accept() failed: %d", err);
|
|
return 1;
|
|
}
|
|
|
|
connection = &srv->connections[connection_index];
|
|
|
|
reset_connection(srv, connection);
|
|
|
|
connection->flags = WB_FRESH_CONNECTION;
|
|
|
|
srv->connection_count = connection_index + 1;
|
|
|
|
/* Configure socket */
|
|
if (0 != wb_config_incoming_socket(fd))
|
|
{
|
|
wb_close_socket(fd);
|
|
return 1;
|
|
}
|
|
|
|
/* OK, keep this connection */
|
|
dbg(srv, "tagging connection %d as alive", connection_index);
|
|
connection->flags |= WB_ALIVE;
|
|
connection->socket = fd;
|
|
return 0;
|
|
}
|
|
|
|
static int wb_peek_request_size(const unsigned char *buf, int len)
|
|
{
|
|
int i;
|
|
int max = len - 3;
|
|
|
|
for (i = 0; i < max; ++i)
|
|
{
|
|
if ('\r' != buf[i])
|
|
continue;
|
|
|
|
if ('\n' != buf[i + 1])
|
|
continue;
|
|
|
|
if ('\r' != buf[i + 2])
|
|
continue;
|
|
|
|
if ('\n' != buf[i + 3])
|
|
continue;
|
|
|
|
/* OK; we have CRLFCRLF which indicates the end of the header section */
|
|
return i + 4;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static char* skipws(char *p)
|
|
{
|
|
for (;;)
|
|
{
|
|
char ch = *p;
|
|
if (' ' == ch || '\t' == ch)
|
|
++p;
|
|
else
|
|
break;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
enum
|
|
{
|
|
WB_TOK_SKIPWS = 1 << 0
|
|
};
|
|
|
|
static int tok_inplace(char *buf, const char* separator, char *tokens[], int max, int flags)
|
|
{
|
|
int token_count = 0;
|
|
char *b = buf;
|
|
char *e = buf;
|
|
int separator_len = (int) strlen(separator);
|
|
|
|
while (token_count < max)
|
|
{
|
|
if (flags & WB_TOK_SKIPWS)
|
|
{
|
|
b = skipws(b);
|
|
}
|
|
|
|
if (NULL != (e = strstr(b, separator)))
|
|
{
|
|
int len = (int) (e - b);
|
|
if (len > 0)
|
|
{
|
|
tokens[token_count++] = b;
|
|
}
|
|
*e = '\0';
|
|
b = e + separator_len;
|
|
}
|
|
else
|
|
{
|
|
tokens[token_count++] = b;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return token_count;
|
|
}
|
|
|
|
static void wb_close_client(struct WebbyServer *srv, struct WebbyConnectionPrv* connection)
|
|
{
|
|
(void) srv;
|
|
|
|
if (connection->socket != WB_INVALID_SOCKET)
|
|
{
|
|
wb_close_socket(connection->socket);
|
|
connection->socket = WB_INVALID_SOCKET;
|
|
}
|
|
|
|
connection->flags = 0;
|
|
}
|
|
|
|
static int send_fully(webby_socket_t socket, const unsigned char *buffer, int size)
|
|
{
|
|
while (size > 0)
|
|
{
|
|
int err = send(socket, (const char*) buffer, size, 0);
|
|
|
|
if (err <= 0)
|
|
return 1;
|
|
|
|
buffer += err;
|
|
size -= err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int wb_setup_request(struct WebbyServer *srv, struct WebbyConnectionPrv *connection, int request_size)
|
|
{
|
|
char* lines[WEBBY_MAX_HEADERS + 2];
|
|
int line_count;
|
|
char* tok[16];
|
|
char* query_params;
|
|
int tok_count;
|
|
|
|
int i;
|
|
int header_count;
|
|
|
|
char *buf = (char*) connection->header_buf.data;
|
|
struct WebbyRequest *req = &connection->public_data.request;
|
|
|
|
/* Null-terminate the request envelope by overwriting the last CRLF with 00LF */
|
|
buf[request_size - 2] = '\0';
|
|
|
|
/* Split header into lines */
|
|
line_count = tok_inplace(buf, "\r\n", lines, WB_ARRAY_SIZE(lines), 0);
|
|
|
|
header_count = line_count - 2;
|
|
|
|
if (line_count < 1 || header_count > (int) WB_ARRAY_SIZE(req->headers))
|
|
return 1;
|
|
|
|
/* Parse request line */
|
|
tok_count = tok_inplace(lines[0], " ", tok, WB_ARRAY_SIZE(tok), 0);
|
|
|
|
if (3 != tok_count)
|
|
return 1;
|
|
|
|
req->method = tok[0];
|
|
req->uri = tok[1];
|
|
req->http_version = tok[2];
|
|
req->content_length = 0;
|
|
|
|
/* See if there are any query parameters */
|
|
if (NULL != (query_params = (char*) strchr(req->uri, '?')))
|
|
{
|
|
req->query_params = query_params + 1;
|
|
*query_params = '\0';
|
|
}
|
|
else
|
|
req->query_params = NULL;
|
|
|
|
/* Decode the URI in place */
|
|
{
|
|
size_t uri_len = strlen(req->uri);
|
|
url_decode(req->uri, uri_len, (char*) req->uri, uri_len + 1, /* url encoded: */ 1);
|
|
}
|
|
|
|
/* Parse headers */
|
|
for (i = 0; i < header_count; ++i)
|
|
{
|
|
tok_count = tok_inplace(lines[i + 1], ":", tok, 2, WB_TOK_SKIPWS);
|
|
|
|
if (tok_count != 2)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
req->headers[i].name = tok[0];
|
|
req->headers[i].value = tok[1];
|
|
|
|
if (0 == strcasecmp("content-length", tok[0]))
|
|
{
|
|
req->content_length = strtoul(tok[1], NULL, 10);
|
|
dbg(srv, "request has body; content length is %d", req->content_length);
|
|
}
|
|
else if (0 == strcasecmp("transfer-encoding", tok[0]))
|
|
{
|
|
dbg(srv, "cowardly refusing to handle Transfer-Encoding: %s", tok[1]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
req->header_count = header_count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
enum
|
|
{
|
|
WB_FILL_OK,
|
|
WB_FILL_ERROR,
|
|
WB_FILL_FULL
|
|
};
|
|
|
|
/* Read as much as possible without blocking while there is buffer space. */
|
|
static int wb_fill_buffer(struct WebbyServer *srv, struct WebbyBuffer *buf, webby_socket_t socket)
|
|
{
|
|
int err;
|
|
int buf_left;
|
|
|
|
for (;;)
|
|
{
|
|
buf_left = buf->max - buf->used;
|
|
|
|
dbg(srv, "buffer space left = %d", buf_left);
|
|
|
|
if (0 == buf_left)
|
|
{
|
|
return WB_FILL_FULL;
|
|
}
|
|
|
|
/* Read what we can into the current buffer space. */
|
|
err = recv(socket, (char*) buf->data + buf->used, buf_left, 0);
|
|
|
|
if (err < 0)
|
|
{
|
|
int sock_err = wb_socket_error();
|
|
|
|
if (wb_is_blocking_error(sock_err))
|
|
{
|
|
return WB_FILL_OK;
|
|
}
|
|
else
|
|
{
|
|
/* Read error. Give up. */
|
|
dbg(srv, "read error %d - connection dead", sock_err);
|
|
return WB_FILL_ERROR;
|
|
}
|
|
}
|
|
else if (err == 0)
|
|
{
|
|
/* The peer has closed the connection. */
|
|
dbg(srv, "peer has closed the connection");
|
|
return WB_FILL_ERROR;
|
|
}
|
|
else
|
|
{
|
|
buf->used += err;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int is_websocket_request(struct WebbyConnection* conn)
|
|
{
|
|
const char *hdr;
|
|
|
|
if (NULL == (hdr = WebbyFindHeader(conn, "Connection")))
|
|
return 0;
|
|
|
|
if (0 != strcasecmp(hdr, "Upgrade"))
|
|
return 0;
|
|
|
|
if (NULL == (hdr = WebbyFindHeader(conn, "Upgrade")))
|
|
return 0;
|
|
|
|
if (0 != strcasecmp(hdr, "websocket"))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int send_websocket_upgrade(struct WebbyServer *srv, struct WebbyConnectionPrv* connection)
|
|
{
|
|
const char *hdr;
|
|
struct sha1 sha;
|
|
unsigned char digest[20];
|
|
char output_digest[64];
|
|
struct WebbyHeader headers[3];
|
|
struct WebbyConnection *conn = &connection->public_data;
|
|
|
|
if (0 == (srv->config.flags & WEBBY_SERVER_WEBSOCKETS))
|
|
{
|
|
dbg(srv, "websockets not enabled in server config");
|
|
return 1;
|
|
}
|
|
|
|
if (NULL == (hdr = WebbyFindHeader(conn, "Sec-WebSocket-Version")))
|
|
{
|
|
dbg(srv, "Sec-WebSocket-Version header not present");
|
|
return 1;
|
|
}
|
|
|
|
if (0 != strcmp(hdr, WB_WEBSOCKET_VERSION))
|
|
{
|
|
dbg(srv, "WebSocket version %s not supported (we only do %s)", hdr, WB_WEBSOCKET_VERSION);
|
|
return 1;
|
|
}
|
|
|
|
if (NULL == (hdr = WebbyFindHeader(conn, "Sec-WebSocket-Key")))
|
|
{
|
|
dbg(srv, "Sec-WebSocket-Key header not present");
|
|
return 1;
|
|
}
|
|
|
|
/* Compute SHA1 hash of Sec-Websocket-Key + the websocket guid as required by
|
|
* the RFC.
|
|
*
|
|
* This handshake is bullshit. It adds zero security. Just forces me to drag
|
|
* in SHA1 and create a base64 encoder.
|
|
*/
|
|
sha1_init(&sha);
|
|
sha1_update(&sha, hdr, (int) strlen(hdr));
|
|
sha1_update(&sha, websocket_guid, websocket_guid_len);
|
|
sha1_final(&digest[0], &sha);
|
|
|
|
if (0 != base64_encode(output_digest, sizeof output_digest, &digest[0], sizeof digest))
|
|
return 1;
|
|
|
|
headers[0].name = "Upgrade";
|
|
headers[0].value = "websocket";
|
|
headers[1].name = "Connection";
|
|
headers[1].value = "Upgrade";
|
|
headers[2].name = "Sec-WebSocket-Accept";
|
|
headers[2].value = output_digest;
|
|
|
|
WebbyBeginResponse(&connection->public_data, 101, 0, headers, WB_ARRAY_SIZE(headers));
|
|
WebbyEndResponse(&connection->public_data);
|
|
return 0;
|
|
}
|
|
|
|
static int scan_websocket_frame(const struct WebbyBuffer *buf, struct WebbyWsFrame *frame)
|
|
{
|
|
unsigned char flags = 0;
|
|
unsigned int len = 0;
|
|
unsigned int opcode = 0;
|
|
unsigned char* data = buf->data;
|
|
unsigned char* data_max = data + buf->used;
|
|
int i;
|
|
int len_bytes = 0;
|
|
int mask_bytes = 0;
|
|
unsigned char header0, header1;
|
|
|
|
if (buf->used < 2)
|
|
return -1;
|
|
|
|
header0 = *data++;
|
|
header1 = *data++;
|
|
|
|
if (header0 & 0x80)
|
|
{
|
|
flags |= WEBBY_WSF_FIN;
|
|
}
|
|
|
|
if (header1 & 0x80)
|
|
{
|
|
flags |= WEBBY_WSF_MASKED;
|
|
mask_bytes = 4;
|
|
}
|
|
|
|
opcode = header0 & 0xf;
|
|
len = header1 & 0x7f;
|
|
|
|
if (len == 126)
|
|
len_bytes = 2;
|
|
else if (len == 127)
|
|
len_bytes = 8;
|
|
|
|
if (data + len_bytes + mask_bytes > data_max)
|
|
return -1;
|
|
|
|
/* Read big endian length from length bytes (if greater than 125) */
|
|
len = len_bytes == 0 ? len : 0;
|
|
for (i = 0; i < len_bytes; ++i)
|
|
{
|
|
/* This will totally overflow for 64-bit values. I don't care.
|
|
* If you're transmitting more than 4 GB of data using Webby,
|
|
* seek help. */
|
|
len <<= 8;
|
|
len |= *data++;
|
|
}
|
|
|
|
/* Read mask word if present */
|
|
for (i = 0; i < mask_bytes; ++i)
|
|
{
|
|
frame->mask_key[i] = *data++;
|
|
}
|
|
|
|
frame->header_size = (unsigned char) (data - buf->data);
|
|
frame->flags = flags;
|
|
frame->opcode = (unsigned char) opcode;
|
|
frame->payload_length = (int) len;
|
|
return 0;
|
|
}
|
|
|
|
static void wb_update_client(struct WebbyServer *srv, struct WebbyConnectionPrv* connection)
|
|
{
|
|
/* This is no longer a fresh connection. Only read from it when select() says
|
|
* so in the future. */
|
|
connection->flags &= ~WB_FRESH_CONNECTION;
|
|
|
|
for (;;)
|
|
{
|
|
switch (connection->state)
|
|
{
|
|
case WBC_REQUEST: {
|
|
const char *expect_header;
|
|
int result = wb_fill_buffer(srv, &connection->header_buf, connection->socket);
|
|
int request_size;
|
|
|
|
if (WB_FILL_ERROR == result)
|
|
{
|
|
connection->flags &= ~WB_ALIVE;
|
|
return;
|
|
}
|
|
|
|
/* Scan to see if the buffer has a complete HTTP request header package. */
|
|
request_size = wb_peek_request_size(connection->header_buf.data, connection->header_buf.used);
|
|
|
|
dbg(srv, "peek request size: %d", request_size);
|
|
if (request_size < 0)
|
|
{
|
|
/* Nothing yet. */
|
|
if (connection->header_buf.max == connection->header_buf.used)
|
|
{
|
|
dbg(srv, "giving up as buffer is full");
|
|
/* Give up, we can't fit the request in our buffer. */
|
|
connection->flags &= ~WB_ALIVE;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Set up request data. */
|
|
if (0 != wb_setup_request(srv, connection, request_size))
|
|
{
|
|
dbg(srv, "failed to set up request");
|
|
connection->flags &= ~WB_ALIVE;
|
|
return;
|
|
}
|
|
|
|
/* Remember how much of the remaining buffer is body data. */
|
|
connection->header_body_left = connection->header_buf.used - request_size;
|
|
|
|
/* If the client expects a 100 Continue, send one now. */
|
|
if (NULL != (expect_header = WebbyFindHeader(&connection->public_data, "Expect")))
|
|
{
|
|
if (0 == strcasecmp(expect_header, "100-continue"))
|
|
{
|
|
dbg(srv, "connection expects a 100 Continue header.. making him happy");
|
|
connection->continue_data_left = (int) continue_header_len;
|
|
connection->state = WBC_SEND_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
dbg(srv, "unrecognized Expected header %s", expect_header);
|
|
connection->state = WBC_SERVE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
connection->state = WBC_SERVE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case WBC_SEND_CONTINUE: {
|
|
int left = connection->continue_data_left;
|
|
int written = 0;
|
|
|
|
written = send(connection->socket, continue_header + continue_header_len - left, left, 0);
|
|
|
|
dbg(srv, "continue write: %d bytes", written);
|
|
|
|
if (written < 0)
|
|
{
|
|
dbg(srv, "failed to write 100-continue header");
|
|
connection->flags &= ~WB_ALIVE;
|
|
return;
|
|
}
|
|
|
|
left -= written;
|
|
connection->continue_data_left = left;
|
|
|
|
if (0 == left)
|
|
{
|
|
connection->state = WBC_SERVE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case WBC_SERVE: {
|
|
/* Clear I/O buffer for output */
|
|
connection->io_buf.used = 0;
|
|
|
|
/* Switch socket to blocking mode. */
|
|
if (0 != make_connection_blocking(connection))
|
|
return;
|
|
|
|
/* Figure out if this is a request to upgrade to WebSockets */
|
|
if (is_websocket_request(&connection->public_data))
|
|
{
|
|
dbg(srv, "received a websocket upgrade request");
|
|
|
|
if (!srv->config.ws_connect || 0 != (*srv->config.ws_connect)(&connection->public_data))
|
|
{
|
|
dbg(srv, "user callback failed connection attempt");
|
|
WebbyBeginResponse(&connection->public_data, 400, -1, plain_text_headers, WB_ARRAY_SIZE(plain_text_headers));
|
|
WebbyPrintf(&connection->public_data, "WebSockets not supported at %s\r\n", connection->public_data.request.uri);
|
|
WebbyEndResponse(&connection->public_data);
|
|
}
|
|
else
|
|
{
|
|
/* OK, let's try to upgrade the connection to WebSockets */
|
|
if (0 != send_websocket_upgrade(srv, connection))
|
|
{
|
|
dbg(srv, "websocket upgrade failed");
|
|
WebbyBeginResponse(&connection->public_data, 400, -1, plain_text_headers, WB_ARRAY_SIZE(plain_text_headers));
|
|
WebbyPrintf(&connection->public_data, "WebSockets couldn't not be enabled\r\n");
|
|
WebbyEndResponse(&connection->public_data);
|
|
}
|
|
else
|
|
{
|
|
/* OK, we're now a websocket */
|
|
connection->flags |= WB_WEBSOCKET;
|
|
dbg(srv, "connection %d upgraded to websocket", (int) (connection - srv->connections));
|
|
|
|
(*srv->config.ws_connected)(&connection->public_data);
|
|
}
|
|
}
|
|
}
|
|
else if (0 != (*srv->config.dispatch)(&connection->public_data))
|
|
{
|
|
static const struct WebbyHeader headers[] =
|
|
{
|
|
{ "Content-Type", "text/plain" },
|
|
};
|
|
WebbyBeginResponse(&connection->public_data, 404, -1, headers, WB_ARRAY_SIZE(headers));
|
|
WebbyPrintf(&connection->public_data, "No handler for %s\r\n", connection->public_data.request.uri);
|
|
WebbyEndResponse(&connection->public_data);
|
|
}
|
|
|
|
/* Back to non-blocking mode, can make the socket die. */
|
|
make_connection_nonblocking(connection);
|
|
|
|
/* Ready for another request, unless we should close the connection. */
|
|
if (connection->flags & WB_ALIVE)
|
|
{
|
|
if (connection->flags & WB_CLOSE_AFTER_RESPONSE)
|
|
{
|
|
connection->flags &= ~WB_ALIVE;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
/* Reset connection for next request. */
|
|
reset_connection(srv, connection);
|
|
|
|
if (0 == (connection->flags & WB_WEBSOCKET))
|
|
{
|
|
/* Loop back to request state */
|
|
connection->state = WBC_REQUEST;
|
|
}
|
|
else
|
|
{
|
|
/* Clear I/O buffer for input */
|
|
connection->io_buf.used = 0;
|
|
/* Go to the web socket serving state */
|
|
connection->state = WBC_WEBSOCKET;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case WBC_WEBSOCKET: {
|
|
|
|
/* In this state, we're trying to read a websocket frame into the I/O
|
|
* buffer. Once we have enough data, we call the websocket frame
|
|
* callback and let the client read the data through WebbyRead.
|
|
*/
|
|
|
|
if (WB_FILL_ERROR == wb_fill_buffer(srv, &connection->io_buf, connection->socket))
|
|
{
|
|
/* Give up on this connection */
|
|
connection->flags &= ~WB_ALIVE;
|
|
return;
|
|
}
|
|
|
|
if (0 != scan_websocket_frame(&connection->io_buf, &connection->ws_frame))
|
|
{
|
|
/* Nothing yet */
|
|
return;
|
|
}
|
|
|
|
connection->body_bytes_read = 0;
|
|
connection->io_data_left = connection->io_buf.used - connection->ws_frame.header_size;
|
|
dbg(srv, "%d bytes of incoming websocket data buffered", (int) connection->io_data_left);
|
|
|
|
/* Switch socket to blocking mode */
|
|
if (0 != make_connection_blocking(connection))
|
|
return;
|
|
|
|
switch (connection->ws_frame.opcode)
|
|
{
|
|
case WEBBY_WS_OP_CLOSE:
|
|
dbg(srv, "received websocket close request");
|
|
connection->flags &= ~WB_ALIVE;
|
|
return;
|
|
|
|
case WEBBY_WS_OP_PING:
|
|
dbg(srv, "received websocket ping request");
|
|
if (0 != send_fully(connection->socket, websocket_pong, sizeof websocket_pong))
|
|
{
|
|
connection->flags &= ~WB_ALIVE;
|
|
return;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Dispatch frame to user handler. */
|
|
if (0 != (*srv->config.ws_frame)(&connection->public_data, &connection->ws_frame))
|
|
{
|
|
connection->flags &= ~WB_ALIVE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Discard any data the client didn't read to retain the socket state. */
|
|
if (connection->body_bytes_read < connection->ws_frame.payload_length)
|
|
{
|
|
int size = connection->ws_frame.payload_length - connection->body_bytes_read;
|
|
if (0 != discard_incoming_data(&connection->public_data, size))
|
|
{
|
|
connection->flags &= ~WB_ALIVE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Back to non-blocking mode */
|
|
if (0 != make_connection_nonblocking(connection))
|
|
return;
|
|
|
|
reset_connection(srv, connection);
|
|
connection->state = WBC_WEBSOCKET;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
WebbyServerUpdate(struct WebbyServer *srv)
|
|
{
|
|
int i, count, err;
|
|
webby_socket_t max_socket;
|
|
fd_set read_fds, write_fds, except_fds;
|
|
struct timeval timeout;
|
|
|
|
/* Build set of sockets to check for events */
|
|
FD_ZERO(&read_fds);
|
|
FD_ZERO(&write_fds);
|
|
FD_ZERO(&except_fds);
|
|
|
|
max_socket = 0;
|
|
|
|
/* Only accept incoming connections if we have space */
|
|
if (srv->connection_count < srv->config.connection_max)
|
|
{
|
|
FD_SET(srv->socket, &read_fds);
|
|
FD_SET(srv->socket, &except_fds);
|
|
max_socket = srv->socket;
|
|
}
|
|
|
|
for (i = 0, count = srv->connection_count; i < count; ++i)
|
|
{
|
|
webby_socket_t socket = srv->connections[i].socket;
|
|
FD_SET(socket, &read_fds);
|
|
FD_SET(socket, &except_fds);
|
|
|
|
if (srv->connections[i].state == WBC_SEND_CONTINUE)
|
|
FD_SET(socket, &write_fds);
|
|
|
|
if (socket > max_socket)
|
|
{
|
|
max_socket = socket;
|
|
}
|
|
}
|
|
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 5;
|
|
|
|
err = select((int) (max_socket + 1), &read_fds, &write_fds, &except_fds, &timeout);
|
|
|
|
/* Handle incoming connections */
|
|
if (FD_ISSET(srv->socket, &read_fds))
|
|
{
|
|
do
|
|
{
|
|
dbg(srv, "awake on incoming");
|
|
err = wb_on_incoming(srv);
|
|
} while (0 == err);
|
|
}
|
|
|
|
/* Handle incoming connection data */
|
|
for (i = 0, count = srv->connection_count; i < count; ++i)
|
|
{
|
|
struct WebbyConnectionPrv *conn = &srv->connections[i];
|
|
|
|
if (FD_ISSET(conn->socket, &read_fds) || FD_ISSET(conn->socket, &write_fds) || conn->flags & WB_FRESH_CONNECTION)
|
|
{
|
|
dbg(srv, "reading from connection %d", i);
|
|
wb_update_client(srv, conn);
|
|
}
|
|
}
|
|
|
|
/* Close stale connections & compact connection array. */
|
|
for (i = 0; i < srv->connection_count; )
|
|
{
|
|
struct WebbyConnectionPrv *connection = &srv->connections[i];
|
|
if (0 == (connection->flags & WB_ALIVE))
|
|
{
|
|
int remain;
|
|
dbg(srv, "closing connection %d (%08x)", i, connection->flags);
|
|
|
|
if (connection->flags & WB_WEBSOCKET)
|
|
{
|
|
(*srv->config.ws_closed)(&connection->public_data);
|
|
}
|
|
|
|
remain = srv->connection_count - i - 1;
|
|
wb_close_client(srv, connection);
|
|
memmove(&srv->connections[i], &srv->connections[i + 1], remain*sizeof(srv->connections[i]));
|
|
--srv->connection_count;
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int wb_flush(struct WebbyBuffer *buf, webby_socket_t socket)
|
|
{
|
|
if (buf->used > 0)
|
|
{
|
|
if (0 != send_fully(socket, buf->data, buf->used))
|
|
return 1;
|
|
}
|
|
buf->used = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int wb_push(struct WebbyServer *srv, struct WebbyConnectionPrv *conn, const void *data_, int len)
|
|
{
|
|
struct WebbyBuffer *buf = &conn->io_buf;
|
|
const unsigned char* data = (const unsigned char*) data_;
|
|
|
|
if (conn->state != WBC_SERVE)
|
|
{
|
|
dbg(srv, "attempt to write in non-serve state");
|
|
return 1;
|
|
}
|
|
|
|
if (0 == len)
|
|
{
|
|
return wb_flush(buf, conn->socket);
|
|
}
|
|
|
|
while (len > 0)
|
|
{
|
|
int buf_space = buf->max - buf->used;
|
|
int copy_size = len < buf_space ? len : buf_space;
|
|
memcpy(buf->data + buf->used, data, copy_size);
|
|
buf->used += copy_size;
|
|
|
|
data += copy_size;
|
|
len -= copy_size;
|
|
|
|
if (buf->used == buf->max)
|
|
{
|
|
if (0 != wb_flush(buf, conn->socket))
|
|
return 1;
|
|
|
|
if (len >= buf->max)
|
|
{
|
|
return send_fully(conn->socket, data, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int WebbyPrintf(struct WebbyConnection* conn, const char* fmt, ...)
|
|
{
|
|
int len;
|
|
char buffer[1024];
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
len = vsnprintf(buffer, sizeof buffer, fmt, args);
|
|
va_end(args);
|
|
|
|
return WebbyWrite(conn, buffer, len);
|
|
}
|
|
|
|
static const short status_nums[] = {
|
|
100, 101, 200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305,
|
|
307, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413,
|
|
414, 415, 416, 417, 500, 501, 502, 503, 504, 505
|
|
};
|
|
|
|
static const char* status_text[] = {
|
|
"Continue", "Switching Protocols", "OK", "Created", "Accepted",
|
|
"Non-Authoritative Information", "No Content", "Reset Content",
|
|
"Partial Content", "Multiple Choices", "Moved Permanently", "Found",
|
|
"See Other", "Not Modified", "Use Proxy", "Temporary Redirect", "Bad Request",
|
|
"Unauthorized", "Payment Required", "Forbidden", "Not Found", "Method Not Allowed",
|
|
"Not Acceptable", "Proxy Authentication Required", "Request Time-out", "Conflict",
|
|
"Gone", "Length Required", "Precondition Failed", "Request Entity Too Large",
|
|
"Request-URI Too Large", "Unsupported Media Type", "Requested range not satisfiable",
|
|
"Expectation Failed", "Internal Server Error", "Not Implemented", "Bad Gateway",
|
|
"Service Unavailable", "Gateway Time-out", "HTTP Version not supported"
|
|
};
|
|
|
|
static const char *wb_status_text(int status_code)
|
|
{
|
|
int i;
|
|
for (i = 0; i < (int) WB_ARRAY_SIZE(status_nums); ++i)
|
|
{
|
|
if (status_nums[i] == status_code)
|
|
return status_text[i];
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
int WebbyBeginResponse(
|
|
struct WebbyConnection *conn_pub,
|
|
int status_code,
|
|
int content_length,
|
|
const struct WebbyHeader headers[],
|
|
int header_count)
|
|
{
|
|
int i = 0;
|
|
struct WebbyConnectionPrv *conn = (struct WebbyConnectionPrv *) conn_pub;
|
|
|
|
if (conn->body_bytes_read < conn->public_data.request.content_length)
|
|
{
|
|
int body_left = conn->public_data.request.content_length - conn->body_bytes_read;
|
|
if (0 != discard_incoming_data(conn_pub, body_left))
|
|
{
|
|
conn->flags &= ~WB_ALIVE;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
WebbyPrintf(conn_pub, "HTTP/1.1 %d %s\r\n", status_code, wb_status_text(status_code));
|
|
|
|
if (content_length >= 0)
|
|
{
|
|
WebbyPrintf(conn_pub, "Content-Length: %d\r\n", content_length);
|
|
}
|
|
else
|
|
{
|
|
WebbyPrintf(conn_pub, "Transfer-Encoding: chunked\r\n");
|
|
}
|
|
|
|
WebbyPrintf(conn_pub, "Server: Webby\r\n");
|
|
|
|
for (i = 0; i < header_count; ++i)
|
|
{
|
|
if (0 == strcasecmp(headers[i].name, "Connection"))
|
|
{
|
|
if (0 == strcasecmp(headers[i].value, "close"))
|
|
{
|
|
conn->flags |= WB_CLOSE_AFTER_RESPONSE;
|
|
}
|
|
}
|
|
WebbyPrintf(conn_pub, "%s: %s\r\n", headers[i].name, headers[i].value);
|
|
}
|
|
|
|
if (0 == (conn->flags & WB_CLOSE_AFTER_RESPONSE))
|
|
{
|
|
/* See if the client wants us to close the connection. */
|
|
const char* connection_header = WebbyFindHeader(conn_pub, "Connection");
|
|
if (connection_header && 0 == strcasecmp("close", connection_header))
|
|
{
|
|
conn->flags |= WB_CLOSE_AFTER_RESPONSE;
|
|
WebbyPrintf(conn_pub, "Connection: close\r\n");
|
|
}
|
|
}
|
|
|
|
WebbyPrintf(conn_pub, "\r\n");
|
|
|
|
if (content_length < 0)
|
|
{
|
|
conn->flags |= WB_CHUNKED_RESPONSE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t make_websocket_header(unsigned char buffer[10], unsigned char opcode, int payload_len, int fin)
|
|
{
|
|
buffer[0] = (fin ? 0x80 : 0x00) | opcode;
|
|
|
|
if (payload_len < 126)
|
|
{
|
|
buffer[1] = (unsigned char) (payload_len & 0x7f);
|
|
return 2;
|
|
}
|
|
else if (payload_len < 65536)
|
|
{
|
|
buffer[1] = 126;
|
|
buffer[2] = (unsigned char) (payload_len >> 8);
|
|
buffer[3] = (unsigned char) payload_len;
|
|
return 4;
|
|
}
|
|
else
|
|
{
|
|
buffer[1] = 127;
|
|
/* Ignore high 32-bits. I didn't want to require 64-bit types and typdef hell in the API. */
|
|
buffer[2] = buffer[3] = buffer[4] = buffer[5] = 0;
|
|
buffer[6] = (unsigned char) (payload_len >> 24);
|
|
buffer[7] = (unsigned char) (payload_len >> 16);
|
|
buffer[8] = (unsigned char) (payload_len >> 8);
|
|
buffer[9] = (unsigned char) payload_len;
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
int
|
|
WebbyBeginSocketFrame(struct WebbyConnection *conn_pub, int opcode)
|
|
{
|
|
struct WebbyConnectionPrv *conn = (struct WebbyConnectionPrv *) conn_pub;
|
|
|
|
conn->ws_opcode = (unsigned char) opcode;
|
|
|
|
/* Switch socket to blocking mode */
|
|
return make_connection_blocking(conn);
|
|
}
|
|
|
|
int
|
|
WebbyEndSocketFrame(struct WebbyConnection *conn_pub)
|
|
{
|
|
struct WebbyConnectionPrv *conn = (struct WebbyConnectionPrv *) conn_pub;
|
|
unsigned char header[10];
|
|
size_t header_size;
|
|
|
|
header_size = make_websocket_header(header, conn->ws_opcode, 0, 1);
|
|
|
|
if (0 != send_fully(conn->socket, header, (int) header_size))
|
|
conn->flags &= ~WB_ALIVE;
|
|
|
|
/* Switch socket to non-blocking mode */
|
|
return make_connection_nonblocking(conn);
|
|
}
|
|
|
|
static int read_buffered_data(int *data_left, struct WebbyBuffer* buffer, char **dest_ptr, size_t *dest_len)
|
|
{
|
|
int offset, read_size;
|
|
int left = *data_left;
|
|
int len;
|
|
|
|
if (left == 0)
|
|
return 0;
|
|
|
|
len = (int) *dest_len;
|
|
offset = buffer->used - left;
|
|
read_size = len > left ? left : len;
|
|
|
|
memcpy(*dest_ptr, buffer->data + offset, read_size);
|
|
|
|
(*dest_ptr) += read_size;
|
|
(*dest_len) -= (size_t) read_size;
|
|
(*data_left) -= read_size;
|
|
|
|
return read_size;
|
|
}
|
|
|
|
int WebbyRead(struct WebbyConnection *conn, void *ptr_, size_t len)
|
|
{
|
|
struct WebbyConnectionPrv* conn_prv = (struct WebbyConnectionPrv*) conn;
|
|
char *ptr = (char*) ptr_;
|
|
int count;
|
|
int start_pos = conn_prv->body_bytes_read;
|
|
|
|
if (conn_prv->header_body_left > 0)
|
|
{
|
|
count = read_buffered_data(&conn_prv->header_body_left, &conn_prv->header_buf, &ptr, &len);
|
|
conn_prv->body_bytes_read += count;
|
|
}
|
|
|
|
/* Read buffered websocket data */
|
|
if (conn_prv->io_data_left > 0)
|
|
{
|
|
count = read_buffered_data(&conn_prv->io_data_left, &conn_prv->io_buf, &ptr, &len);
|
|
conn_prv->body_bytes_read += count;
|
|
}
|
|
|
|
while (len > 0)
|
|
{
|
|
int err = recv(conn_prv->socket, ptr, (int) len, 0);
|
|
|
|
if (err < 0)
|
|
{
|
|
conn_prv->flags &= ~WB_ALIVE;
|
|
return err;
|
|
}
|
|
|
|
len -= err;
|
|
ptr += err;
|
|
conn_prv->body_bytes_read += err;
|
|
}
|
|
|
|
if ((conn_prv->flags & WB_WEBSOCKET) && (conn_prv->ws_frame.flags & WEBBY_WSF_MASKED))
|
|
{
|
|
/* XOR outgoing data with websocket ofuscation key */
|
|
int i;
|
|
int end_pos = conn_prv->body_bytes_read;
|
|
const unsigned char *mask = conn_prv->ws_frame.mask_key;
|
|
ptr = (char*) ptr_; /* start over */
|
|
for (i = start_pos; i < end_pos; ++i)
|
|
{
|
|
unsigned char byte = *ptr;
|
|
*ptr++ = byte ^ mask[i & 3];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int WebbyWrite(struct WebbyConnection *conn, const void *ptr, size_t len)
|
|
{
|
|
struct WebbyConnectionPrv *conn_priv = (struct WebbyConnectionPrv *) conn;
|
|
|
|
if (conn_priv->flags & WB_WEBSOCKET)
|
|
{
|
|
unsigned char header[10];
|
|
size_t header_size;
|
|
header_size = make_websocket_header(header, conn_priv->ws_opcode, (int) len, 0);
|
|
|
|
/* Overwrite opcode to be continuation packages from here on out */
|
|
conn_priv->ws_opcode = WEBBY_WS_OP_CONTINUATION;
|
|
|
|
if (0 != send_fully(conn_priv->socket, header, (int) header_size))
|
|
{
|
|
conn_priv->flags &= ~WB_ALIVE;
|
|
return -1;
|
|
}
|
|
if (0 != send_fully(conn_priv->socket, (const unsigned char*) ptr, (int) len))
|
|
{
|
|
conn_priv->flags &= ~WB_ALIVE;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
else if (conn_priv->flags & WB_CHUNKED_RESPONSE)
|
|
{
|
|
char chunk_header[128];
|
|
int header_len = snprintf(chunk_header, sizeof chunk_header, "%x\r\n", (int) len);
|
|
wb_push(conn_priv->server, conn_priv, chunk_header, header_len);
|
|
wb_push(conn_priv->server, conn_priv, ptr, (int) len);
|
|
return wb_push(conn_priv->server, conn_priv, "\r\n", 2);
|
|
}
|
|
else
|
|
{
|
|
return wb_push(conn_priv->server, conn_priv, ptr, (int) len);
|
|
}
|
|
}
|
|
|
|
void WebbyEndResponse(struct WebbyConnection *conn)
|
|
{
|
|
struct WebbyConnectionPrv *conn_priv = (struct WebbyConnectionPrv *) conn;
|
|
|
|
if (conn_priv->flags & WB_CHUNKED_RESPONSE)
|
|
{
|
|
/* Write final chunk */
|
|
wb_push(conn_priv->server, conn_priv, "0\r\n\r\n", 5);
|
|
|
|
conn_priv->flags &= ~WB_CHUNKED_RESPONSE;
|
|
}
|
|
|
|
/* Flush buffers */
|
|
wb_push(conn_priv->server, conn_priv, "", 0);
|
|
}
|