From c5b5258160e96a16ba11d48d306c5c26c13ee0e7 Mon Sep 17 00:00:00 2001 From: elioat Date: Wed, 5 Jun 2024 22:32:40 -0400 Subject: Refactoring and cleaning up --- lua/sandborb/.gitignore | 1 + lua/sandborb/README.md | 0 lua/sandborb/app.c | 95 ---- lua/sandborb/build.sh | 4 +- lua/sandborb/giblang.lua | 34 ++ lua/sandborb/route_handler.lua | 65 ++- lua/sandborb/sandbird.c | 979 ----------------------------------------- lua/sandborb/sandbird.h | 91 ---- lua/sandborb/src/app.c | 95 ++++ lua/sandborb/src/sandbird.c | 979 +++++++++++++++++++++++++++++++++++++++++ lua/sandborb/src/sandbird.h | 91 ++++ 11 files changed, 1248 insertions(+), 1186 deletions(-) create mode 100644 lua/sandborb/README.md delete mode 100644 lua/sandborb/app.c create mode 100644 lua/sandborb/giblang.lua delete mode 100644 lua/sandborb/sandbird.c delete mode 100644 lua/sandborb/sandbird.h create mode 100644 lua/sandborb/src/app.c create mode 100644 lua/sandborb/src/sandbird.c create mode 100644 lua/sandborb/src/sandbird.h (limited to 'lua') diff --git a/lua/sandborb/.gitignore b/lua/sandborb/.gitignore index 92e5c23..d284b97 100644 --- a/lua/sandborb/.gitignore +++ b/lua/sandborb/.gitignore @@ -1,3 +1,4 @@ +/sandborb /a.out /.vscode *.gch \ No newline at end of file diff --git a/lua/sandborb/README.md b/lua/sandborb/README.md new file mode 100644 index 0000000..e69de29 diff --git a/lua/sandborb/app.c b/lua/sandborb/app.c deleted file mode 100644 index 1cfa6d0..0000000 --- a/lua/sandborb/app.c +++ /dev/null @@ -1,95 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "sandbird.h" - -/* Functions to be called from Lua */ -static int l_sb_send_header(lua_State *L) { - sb_Event *e = lua_touserdata(L, 1); - const char *key = lua_tostring(L, 2); - const char *value = lua_tostring(L, 3); - sb_send_header(e->stream, key, value); - return 0; /* Number of return values */ -} - -static int l_sb_send_status(lua_State *L) { - sb_Event *e = lua_touserdata(L, 1); - int status = lua_tointeger(L, 2); - const char *message = lua_tostring(L, 3); - sb_send_status(e->stream, status, message); - return 0; /* Number of return values */ -} - -static int event_handler(sb_Event *e) { - if (e->type == SB_EV_REQUEST) { - printf("%s - %s %s\n", e->address, e->method, e->path); - - lua_State *L = luaL_newstate(); /* create a new Lua state */ - luaL_openlibs(L); /* load Lua libraries */ - - /* Expose our functions to Lua */ - lua_pushcfunction(L, l_sb_send_header); - lua_setglobal(L, "sb_send_header"); - lua_pushcfunction(L, l_sb_send_status); - lua_setglobal(L, "sb_send_status"); - - /* load and run the Lua script */ - if (luaL_dofile(L, "route_handler.lua") != LUA_OK) { - fprintf(stderr, "Failed to load Lua script: %s\n", lua_tostring(L, -1)); - lua_close(L); - return SB_RES_OK; - } - - /* call the Lua function */ - lua_getglobal(L, "HANDLE_ROUTE"); /* all caps because it is a global function */ - lua_pushlightuserdata(L, e); /* Pass the event as userdata to Lua */ - lua_pushstring(L, e->path); - if (lua_pcall(L, 2, 1, 0) != LUA_OK) { - fprintf(stderr, "Failed to call Lua function: %s\n", lua_tostring(L, -1)); - lua_close(L); - return SB_RES_OK; - } - - /* get the result from the Lua function */ - const char *response = lua_tostring(L, -1); - lua_pop(L, 1); - - sb_writef(e->stream, response); - - lua_close(L); /* close the Lua state */ - } - return SB_RES_OK; -} - - -int main(void) { - sb_Options opt; - sb_Server *server; - - memset(&opt, 0, sizeof(opt)); - opt.port = "8000"; - opt.handler = event_handler; - - server = sb_new_server(&opt); - - if (!server) { - fprintf(stderr, "failed to initialize server\n"); - exit(EXIT_FAILURE); - } - - printf("Server running at http://localhost:%s\n", opt.port); - - for (;;) { - sb_poll_server(server, 1000); - } - - sb_close_server(server); - return EXIT_SUCCESS; -} - - - diff --git a/lua/sandborb/build.sh b/lua/sandborb/build.sh index 28f1724..dc90a60 100755 --- a/lua/sandborb/build.sh +++ b/lua/sandborb/build.sh @@ -1,10 +1,10 @@ #!/usr/bin/env sh if [ -f a.out ]; then - rm a.out + rm sandborb fi -cc app.c sandbird.c -I /opt/homebrew/include/lua5.4 sandbird.h -std=c99 -pedantic -Wall -Wextra -llua +cc ./src/app.c ./src/sandbird.c -I /opt/homebrew/include/lua5.4 ./src/sandbird.h -std=c99 -pedantic -Wall -Wextra -llua if [ -f a.out ]; then ./a.out diff --git a/lua/sandborb/giblang.lua b/lua/sandborb/giblang.lua new file mode 100644 index 0000000..d1ab3a2 --- /dev/null +++ b/lua/sandborb/giblang.lua @@ -0,0 +1,34 @@ +local giblang = {_VERSION = "2024.07"} +local cons = {"b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "r", "s", "t", "v", "w", "z", "ch", "sh", "zh", "th"} +local vow = {"a", "e", "i", "o", "u", "y", "ee", "ai", "ae", "au"} + +math.randomseed(os.time()) + +function giblang.rpick(t) + return t[math.floor(math.random() * #t) + 1] +end + +function giblang.syl() + return giblang.rpick(cons) .. giblang.rpick(vow) +end + +function giblang.word() + local str = "" + for i=1, math.floor(math.random() * 3) + 1 do + str = str .. giblang.syl() + end + if math.random() > 0.2 then + str = str .. giblang.rpick(cons) + end + return str +end + +function giblang.speak(number_of_words) + local words = "" + for i=1,number_of_words do + words = words .. giblang.word() .. " " + end + return words +end + +return giblang diff --git a/lua/sandborb/route_handler.lua b/lua/sandborb/route_handler.lua index ae69875..7819d3d 100644 --- a/lua/sandborb/route_handler.lua +++ b/lua/sandborb/route_handler.lua @@ -1,37 +1,64 @@ local json = require "json" +local giblang = require "giblang" local function brackets(e) - sb_send_status(e, 200, "OK") - sb_send_header(e, "Content-Type", "application/json") - return json.encode({ 1, 2, 3, { x = 10 } }) + return { + content_type = "application/json", + body = json.encode({ 1, 2, 3, { x = 10 } }) + } end local function banana(e) - sb_send_status(e, 200, "OK") - sb_send_header(e, "Content-Type", "text/html, charset=utf-8") - return "

Bananas are delicious!

" + return { + content_type = "text/html, charset=utf-8", + body = "

Bananas are delicious!

" + } end -local function error_404(e,p) - sb_send_status(e, 404, "Not Found") - sb_send_header(e, "Content-Type", "text/html, charset=utf-8") - return "

404 - Page not found: " .. p .. "

" +local function gib(e,n) + return { + content_type = "text/html, charset=utf-8", + body = "

" .. giblang.speak(n) .. "

" + } +end + +local function error_404(e, p) + return { + code = 404, + status = "Not Found", + content_type = "text/html, charset=utf-8", + body = "

404 - Page not found: " .. p .. "

" + } end function HANDLE_ROUTE(e, path) + local response = { + content_type = "text/html, charset=utf-8", + body = "" + } + if path == "/" then - sb_send_status(e, 200, "OK") - sb_send_header(e, "Content-Type", "text/html, charset=utf-8") - return "Hello, you are at the root!" + response.body = "Hello, you are at the root!" elseif path == "/about" then - sb_send_status(e, 200, "OK") - sb_send_header(e, "Content-Type", "text/plain") - return "This is the about page!" + response.body = "This is the about page!" elseif path == "/banana" then - return banana(e) + response = banana(e) elseif path == "/json" then - return brackets(e) + response = brackets(e) + elseif path == "/giblang" then + math.randomseed(os.time()) + local number_of_words = math.random(2, 10) + response = gib(e, number_of_words) else - return error_404(e, path) + response = error_404(e, path) end + + if not response.code then + response.status = "OK" + response.code = 200 + end + + sb_send_status(e, response.code, response.status) + sb_send_header(e, "Content-Type", response.content_type) + return response.body end \ No newline at end of file diff --git a/lua/sandborb/sandbird.c b/lua/sandborb/sandbird.c deleted file mode 100644 index e08c287..0000000 --- a/lua/sandborb/sandbird.c +++ /dev/null @@ -1,979 +0,0 @@ -/** - * Copyright (c) 2016 rxi - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the MIT license. See LICENSE for details. - */ - - -#ifdef _WIN32 - #ifndef _WIN32_WINNT - #define _WIN32_WINNT 0x501 - #endif - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif - #ifndef FD_SETSIZE - #define FD_SETSIZE 2048 - #endif - #include - #include - #include -#else - #ifndef _POSIX_C_SOURCE - #define _POSIX_C_SOURCE 200809L - #endif - #include - #include - #include - #include - #include - #include - #include - #include -#endif -#include -#include -#include -#include -#include -#include -#include - -#include "sandbird.h" - - -#ifdef _WIN32 - #define close(a) closesocket(a) - #define setsockopt(a, b, c, d, e) setsockopt(a, b, c, (char*)(d), e) - - #undef errno - #define errno WSAGetLastError() - - #undef EWOULDBLOCK - #define EWOULDBLOCK WSAEWOULDBLOCK - - const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { - union { struct sockaddr sa; struct sockaddr_in sai; - struct sockaddr_in6 sai6; } addr; - int res; - memset(&addr, 0, sizeof(addr)); - addr.sa.sa_family = af; - if (af == AF_INET6) { - memcpy(&addr.sai6.sin6_addr, src, sizeof(addr.sai6.sin6_addr)); - } else { - memcpy(&addr.sai.sin_addr, src, sizeof(addr.sai.sin_addr)); - } - res = WSAAddressToStringA(&addr.sa, sizeof(addr), 0, dst, (LPDWORD) &size); - if (res != 0) return NULL; - return dst; - } -#endif - -#ifdef _WIN32 - typedef SOCKET sb_Socket; -#else - typedef int sb_Socket; - #define INVALID_SOCKET -1 -#endif - -typedef struct sb_Buffer sb_Buffer; - -struct sb_Buffer { char *s; size_t len, cap; }; - -struct sb_Stream { - int state; /* Current state of the stream */ - sb_Server *server; /* The server object which owns this stream */ - char address[46]; /* Remote IP address */ - time_t init_time; /* Time the stream was created */ - time_t last_activity; /* Time of Last I/O activity on the stream */ - size_t expected_recv_len; /* Expected length of the stream's request */ - size_t data_idx; /* Index of data section in recv_buf */ - sb_Socket sockfd; /* Socket for this streams connection */ - sb_Buffer recv_buf; /* Data received from client */ - sb_Buffer send_buf; /* Data waiting to be sent to client */ - FILE *send_fp; /* File currently being sent to client */ - sb_Stream *next; /* Next stream in linked list */ -}; - -struct sb_Server { - sb_Stream *streams; /* Linked list of all streams */ - sb_Handler handler; /* Event handler callback function */ - sb_Socket sockfd; /* Listeneing server socket */ - void *udata; /* User data value passed to all events */ - time_t now; /* The current time */ - time_t timeout; /* Stream no-activity timeout */ - time_t max_lifetime; /* Maximum time a stream can exist */ - size_t max_request_size; /* Maximum request size in bytes */ -}; - -enum { - STATE_RECEIVING_HEADER, - STATE_RECEIVING_REQUEST, - STATE_SENDING_STATUS, - STATE_SENDING_HEADER, - STATE_SENDING_DATA, - STATE_SENDING_FILE, - STATE_CLOSING -}; - - -/*=========================================================================== - * Utility - *===========================================================================*/ - -static void set_socket_non_blocking(sb_Socket sockfd) { -#ifdef _WIN32 - u_long mode = 1; - ioctlsocket(sockfd, FIONBIO, &mode); -#else - int flags = fcntl(sockfd, F_GETFL); - fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); -#endif -} - - -static int get_socket_address(sb_Socket sockfd, char *dst) { - int err; - union { struct sockaddr sa; struct sockaddr_storage sas; - struct sockaddr_in sai; struct sockaddr_in6 sai6; } addr; - socklen_t sz = sizeof(addr); - err = getpeername(sockfd, &addr.sa, &sz); - if (err == -1) { - *dst = '\0'; - return SB_EFAILURE; - } - if (addr.sas.ss_family == AF_INET6) { - inet_ntop(AF_INET6, &addr.sai6.sin6_addr, dst, INET6_ADDRSTRLEN); - } else { - inet_ntop(AF_INET, &addr.sai.sin_addr, dst, INET_ADDRSTRLEN); - } - return SB_ESUCCESS; -} - - -static unsigned str_to_uint(const char *str) { - unsigned n; - if (!str || sscanf(str, "%u", &n) != 1) return 0; - return n; -} - - -static int hex_to_int(int chr) { - return isdigit(chr) ? (chr - '0') : (tolower(chr) - 'a' + 10); -} - - -static int url_decode(char *dst, const char *src, size_t len) { - len--; - while (*src && !strchr("?& \t\r\n", *src) && len) { - if (src[0] == '%' && src[1] && src[2]) { - *dst = (hex_to_int(src[1]) << 4) | hex_to_int(src[2]); - src += 2; - } else if (*src == '+') { - *dst = ' '; - } else { - *dst = *src; - } - dst++, src++, len--; - } - *dst = '\0'; - return (len == 0) ? SB_ETRUNCATED : SB_ESUCCESS; -} - - -static int mem_equal(const void *a, const void *b, size_t len) { - const char *p = a, *q = b; - while (len) { - if (*p != *q) return 0; - p++, q++, len--; - } - return 1; -} - - -static int mem_case_equal(const void *a, const void *b, size_t len) { - const char *p = a, *q = b; - while (len) { - if (tolower(*p) != tolower(*q)) return 0; - p++, q++, len--; - } - return 1; -} - - -static const char *find_header_value(const char *str, const char *field) { - size_t len = strlen(field); - while (*str && !mem_equal(str, "\r\n", 2)) { - if (mem_case_equal(str, field, len) && str[len] == ':') { - str += len + 1; - return str + strspn(str, " \t"); - } - str += strcspn(str, "\r"); - str += mem_equal(str, "\r\n", 2) ? 2 : 0; - } - return NULL; -} - - -static const char *find_var_value(const char *str, const char *name) { - size_t len = strlen(name); - for (;;) { - if (mem_equal(str, name, len) && str[len] == '=') { - return str + len + 1; - } - str += strcspn(str, "& \t\r\n"); - if (*str != '&') break; - str++; - } - return NULL; -} - - -const char *sb_error_str(int code) { - switch (code) { - case SB_ESUCCESS : return "success"; - case SB_EFAILURE : return "failure"; - case SB_EOUTOFMEM : return "out of memory"; - case SB_ETRUNCATED : return "result truncated"; - case SB_EBADSTATE : return "bad stream state for this operation"; - case SB_EBADRESULT : return "bad result code from event handler"; - case SB_ECANTOPEN : return "cannot open file"; - case SB_ENOTFOUND : return "not found"; - case SB_EFDTOOBIG : return "got socket fd larger than FD_SETSIZE"; - default : return "unknown"; - } -} - - -/*=========================================================================== - * Buffer - *===========================================================================*/ - -static void sb_buffer_init(sb_Buffer *buf) { - memset(buf, 0, sizeof(*buf)); -} - - -static void sb_buffer_deinit(sb_Buffer *buf) { - free(buf->s); -} - - -static void sb_buffer_shift(sb_Buffer *buf, size_t n) { - buf->len -= n; - memmove(buf->s, buf->s + n, buf->len); -} - - -static int sb_buffer_reserve(sb_Buffer *buf, size_t n) { - void *p; - if (buf->cap >= n) return SB_ESUCCESS; - p = realloc(buf->s, n); - if (!p) return SB_EOUTOFMEM; - buf->s = p; - buf->cap = n; - return SB_ESUCCESS; -} - - -static int sb_buffer_push_char(sb_Buffer *buf, char chr) { - if (buf->len == buf->cap) { - int err = sb_buffer_reserve(buf, (buf->cap << 1) | (!buf->cap)); - if (err) return err; - } - buf->s[buf->len++] = chr; - return SB_ESUCCESS; -} - - -static int sb_buffer_push_str(sb_Buffer *buf, const char *p, size_t len) { - int err; - size_t orig_len = buf->len; - while (len) { - err = sb_buffer_push_char(buf, *p); - if (err) { - buf->len = orig_len; - return err; - } - p++, len--; - } - return SB_ESUCCESS; -} - - -static int sb_buffer_vwritef(sb_Buffer *buf, const char *fmt, va_list args) { - int err; - size_t orig_len = buf->len; - char fbuf[64]; - char lbuf[512]; - char *s; - - while (*fmt) { - if (*fmt == '%') { - switch (*++fmt) { - - case 's': - s = va_arg(args, char*); - if (s == NULL) s = "(null)"; - err = sb_buffer_push_str(buf, s, strlen(s)); - if (err) goto fail; - break; - - default: - fbuf[0] = '%'; - s = fbuf + 1; - while ( !isalpha(*fmt) && *fmt != '%' ) *s++ = *fmt++; - s[0] = *fmt, s[1] = '\0'; - switch (*fmt) { - case 'f': - case 'g': sprintf(lbuf, fbuf, va_arg(args, double)); break; - case 'c': - case 'd': - case 'i': sprintf(lbuf, fbuf, va_arg(args, int)); break; - case 'u': - case 'x': - case 'X': sprintf(lbuf, fbuf, va_arg(args, unsigned)); break; - case 'p': sprintf(lbuf, fbuf, va_arg(args, void*)); break; - default : lbuf[0] = *fmt, lbuf[1] = '\0'; - } - err = sb_buffer_push_str(buf, lbuf, strlen(lbuf)); - if (err) goto fail; - } - } else { - err = sb_buffer_push_char(buf, *fmt); - if (err) goto fail; - } - fmt++; - } - - return SB_ESUCCESS; - -fail: - buf->len = orig_len; - return err; -} - - -static int sb_buffer_writef(sb_Buffer *buf, const char *fmt, ...) { - int err; - va_list args; - va_start(args, fmt); - err = sb_buffer_vwritef(buf, fmt, args); - va_end(args); - return err; -} - - -static int sb_buffer_null_terminate(sb_Buffer *buf) { - int err = sb_buffer_push_char(buf, '\0'); - if (err) return err; - buf->len--; - return SB_ESUCCESS; -} - - -/*=========================================================================== - * Stream - *===========================================================================*/ - -static sb_Stream *sb_stream_new(sb_Server *srv, sb_Socket sockfd) { - sb_Stream *st = malloc( sizeof(*st) ); - if (!st) return NULL; - memset(st, 0, sizeof(*st)); - sb_buffer_init(&st->recv_buf); - sb_buffer_init(&st->send_buf); - st->sockfd = sockfd; - st->server = srv; - st->init_time = srv->now; - st->last_activity = srv->now; - set_socket_non_blocking(sockfd); - get_socket_address(sockfd, st->address); - return st; -} - - -static void sb_stream_close(sb_Stream *st) { - st->state = STATE_CLOSING; -} - - -static int sb_stream_emit(sb_Stream *st, sb_Event *e) { - int res; - e->stream = st; - e->udata = st->server->udata; - e->server = st->server; - e->address = st->address; - res = e->server->handler(e); - if (res < 0) return res; - switch (res) { - case SB_RES_CLOSE : sb_stream_close(st); /* Fall through */ - case SB_RES_OK : return SB_ESUCCESS; - default : return SB_EBADRESULT; - } -} - - -static void sb_stream_destroy(sb_Stream *st) { - sb_Event e; - /* Emit close event */ - e.type = SB_EV_CLOSE; - sb_stream_emit(st, &e); - /* Clean up */ - close(st->sockfd); - if (st->send_fp) fclose(st->send_fp); - sb_buffer_deinit(&st->recv_buf); - sb_buffer_deinit(&st->send_buf); - free(st); -} - - -static int sb_stream_recv(sb_Stream *st) { - for (;;) { - char buf[4096]; - int err, i, sz; - - /* Receive data */ - sz = recv(st->sockfd, buf, sizeof(buf) - 1, 0); - if (sz <= 0) { - /* Disconnected? */ - if (sz == 0 || errno != EWOULDBLOCK) { - sb_stream_close(st); - } - return SB_ESUCCESS; - } - - /* Update last_activity */ - st->last_activity = st->server->now; - - /* Write to recv_buf */ - for (i = 0; i < sz; i++) { - err = sb_buffer_push_char(&st->recv_buf, buf[i]); - if (err) return err; - - /* Have we received the whole header? */ - if ( - st->state == STATE_RECEIVING_HEADER && - st->recv_buf.len >= 4 && - mem_equal(st->recv_buf.s + st->recv_buf.len - 4, "\r\n\r\n", 4) - ) { - const char *s; - /* Update stream's current state */ - st->state = STATE_RECEIVING_REQUEST; - /* Assure recv_buf is null-terminated */ - err = sb_buffer_null_terminate(&st->recv_buf); - if (err) return err; - /* If the header contains the Content-Length field we set the - * expected_recv_len and continue writing to the recv_buf, otherwise we - * assume the request is complete */ - s = find_header_value(st->recv_buf.s, "Content-Length"); - if (s) { - st->expected_recv_len = st->recv_buf.len + str_to_uint(s); - st->data_idx = st->recv_buf.len; - } else { - goto handle_request; - } - } - - /* Have we received all the data we're expecting? */ - if (st->expected_recv_len == st->recv_buf.len) { - /* Handle request */ - sb_Event e; - int n, path_idx; - char method[16], path[512], ver[16]; -handle_request: - st->state = STATE_SENDING_STATUS; - /* Assure recv_buf string is NULL-terminated */ - err = sb_buffer_null_terminate(&st->recv_buf); - if (err) return err; - /* Get method, path, version */ - n = sscanf(st->recv_buf.s, "%15s %n%*s %15s", method, &path_idx, ver); - /* Is request line invalid? */ - if (n != 2 || !mem_equal(ver, "HTTP", 4)) { - sb_stream_close(st); - return SB_ESUCCESS; - } - /* Build and emit `request` event */ - url_decode(path, st->recv_buf.s + path_idx, sizeof(path)); - e.type = SB_EV_REQUEST; - e.method = method; - e.path = path; - err = sb_stream_emit(st, &e); - if (err) return err; - /* No more data needs to be received (nor should it exist) */ - return SB_ESUCCESS; - } - } - } - - return SB_ESUCCESS; -} - - -static int sb_stream_send(sb_Stream *st) { - if (st->send_buf.len > 0) { - int sz; - - /* Send data */ -send_data: - sz = send(st->sockfd, st->send_buf.s, st->send_buf.len, 0); - if (sz <= 0) { - /* Disconnected? */ - if (errno != EWOULDBLOCK) { - sb_stream_close(st); - } - return SB_ESUCCESS; - } - - /* Remove sent bytes from buffer */ - sb_buffer_shift(&st->send_buf, sz); - - /* Update last_activity */ - st->last_activity = st->server->now; - - } else if (st->send_fp) { - /* Read chunk, write to stream and continue sending */ - int err = sb_buffer_reserve(&st->send_buf, 8192); - if (err) return err; - st->send_buf.len = fread(st->send_buf.s, 1, st->send_buf.cap, st->send_fp); - if (st->send_buf.len > 0) goto send_data; - - /* Reached end of file */ - fclose(st->send_fp); - st->send_fp = NULL; - - } else { - /* No more data left -- disconnect */ - sb_stream_close(st); - } - - return SB_ESUCCESS; -} - - -static int sb_stream_finalize_header(sb_Stream *st) { - int err; - if (st->state < STATE_SENDING_HEADER) { - err = sb_send_status(st, 200, "OK"); - if (err) return err; - } - err = sb_buffer_push_str(&st->send_buf, "\r\n", 2); - if (err) return err; - st->state = STATE_SENDING_DATA; - return SB_ESUCCESS; -} - - -int sb_send_status(sb_Stream *st, int code, const char *msg) { - int err; - if (st->state != STATE_SENDING_STATUS) { - return SB_EBADSTATE; - } - err = sb_buffer_writef(&st->send_buf, "HTTP/1.1 %d %s\r\n", code, msg); - if (err) return err; - st->state = STATE_SENDING_HEADER; - return SB_ESUCCESS; -} - - -int sb_send_header(sb_Stream *st, const char *field, const char *val) { - int err; - if (st->state > STATE_SENDING_HEADER) { - return SB_EBADSTATE; - } - if (st->state < STATE_SENDING_HEADER) { - err = sb_send_status(st, 200, "OK"); - if (err) return err; - } - err = sb_buffer_writef(&st->send_buf, "%s: %s\r\n", field, val); - if (err) return err; - return SB_ESUCCESS; -} - - -int sb_send_file(sb_Stream *st, const char *filename) { - int err; - char buf[32]; - size_t sz; - FILE *fp = NULL; - if (st->state > STATE_SENDING_HEADER) { - return SB_EBADSTATE; - } - /* Try to open file */ - fp = fopen(filename, "rb"); - if (!fp) return SB_ECANTOPEN; - - /* Get file size and write headers */ - fseek(fp, 0, SEEK_END); - sz = ftell(fp); - sprintf(buf, "%u", (unsigned) sz); - err = sb_send_header(st, "Content-Length", buf); - if (err) goto fail; - err = sb_stream_finalize_header(st); - if (err) goto fail; - - /* Rewind file, set stream's fp and state */ - fseek(fp, 0, SEEK_SET); - st->send_fp = fp; - st->state = STATE_SENDING_FILE; - return SB_ESUCCESS; - -fail: - if (fp) fclose(fp); - return err; -} - - -int sb_write(sb_Stream *st, const void *data, size_t len) { - if (st->state < STATE_SENDING_DATA) { - int err = sb_stream_finalize_header(st); - if (err) return err; - } - if (st->state != STATE_SENDING_DATA) return SB_EBADSTATE; - return sb_buffer_push_str(&st->send_buf, data, len); -} - - -int sb_vwritef(sb_Stream *st, const char *fmt, va_list args) { - if (st->state < STATE_SENDING_DATA) { - int err = sb_stream_finalize_header(st); - if (err) return err; - } - if (st->state != STATE_SENDING_DATA) return SB_EBADSTATE; - return sb_buffer_vwritef(&st->send_buf, fmt, args); -} - - -int sb_writef(sb_Stream *st, const char *fmt, ...) { - int err; - va_list args; - va_start(args, fmt); - err = sb_vwritef(st, fmt, args); - va_end(args); - return err; -} - - -int sb_get_header(sb_Stream *st, const char *field, char *dst, size_t len) { - size_t n; - int res = SB_ESUCCESS; - const char *s = find_header_value(st->recv_buf.s, field); - if (!s) { - *dst = '\0'; - return SB_ENOTFOUND; - } - n = strchr(s, '\r') - s; - while (n > 1 && strchr(" \t", s[n-1])) n--; /* trim whitespace from end */ - if (n > len - 1) { - n = len - 1; - res = SB_ETRUNCATED; - } - memcpy(dst, s, n); - dst[n] = '\0'; - return res; -} - - -int sb_get_var(sb_Stream *st, const char *name, char *dst, size_t len) { - const char *q, *s = NULL; - - /* Find beginning of query string */ - q = st->recv_buf.s + strcspn(st->recv_buf.s, "?\r"); - q = (*q == '?') ? (q + 1) : NULL; - - /* Try to get var from query string, then data string */ - if (q) s = find_var_value(q, name); - if (!s && st->data_idx) { - s = find_var_value(st->recv_buf.s + st->data_idx, name); - } - if (!s) { - *dst = '\0'; - return SB_ENOTFOUND; - } - return url_decode(dst, s, len); -} - - -int sb_get_cookie(sb_Stream *st, const char *name, char *dst, size_t len) { - size_t n; - const char *s = st->recv_buf.s; - int res = SB_ESUCCESS; - size_t name_len = strlen(name); - - /* Get cookie header */ - s = find_header_value(st->recv_buf.s, "Cookie"); - if (!s) goto fail; - - /* Find var */ - while (*s) { - s += strspn(s, " \t"); - /* Found var? find value, get len, copy value and return */ - if ( mem_case_equal(s, name, name_len) && strchr(" =", s[name_len]) ) { - s += name_len; - s += strspn(s, "= \t\r"); - n = strcspn(s, ";\r"); - if (n >= len - 1) { - n = len - 1; - res = SB_ETRUNCATED; - } - memcpy(dst, s, n); - dst[n] = '\0'; - return res; - } - s += strcspn(s, ";\r"); - if (*s != ';') goto fail; - s++; - } - -fail: - *dst = '\0'; - return SB_ENOTFOUND; -} - - -#define P_ATCHK(x) do { if (!(p = (x))) goto fail; } while (0) -#define P_AFTERL(x, l) do {\ - size_t len__ = (l);\ - for (;; p++) {\ - if (p == end - len__) goto fail;\ - if (mem_equal(p, x, len__)) break;\ - }\ - p += len__;\ - } while (0) -#define P_AFTER(s) P_AFTERL(s, strlen(s)) - -const void *sb_get_multipart(sb_Stream *st, const char *name, size_t *len) { - const char *boundary; - size_t boundary_len; - size_t name_len = strlen(name); - const char *p = st->recv_buf.s; - char *end = st->recv_buf.s + st->recv_buf.len; - - /* Get boundary string */ - P_ATCHK( find_header_value(p, "Content-Type") ); - P_AFTER( "boundary=" ); - boundary = p; - P_AFTER( "\r\n" ); - boundary_len = p - boundary - 2; - -next: - /* Move to after first boundary, then to start of name */ - P_AFTERL( boundary, boundary_len ); - P_AFTER( "\r\n" ); - P_ATCHK( find_header_value(p, "Content-Disposition") ); - P_AFTER( "name=\"" ); - - /* Does the name match what we were looking for? */ - if (mem_equal(p, name, name_len) && p[name_len] == '"') { - const char *res; - /* Move to start of data */ - P_AFTER( "\r\n\r\n" ); - res = p; - /* Find boundary, set length and return result */ - P_AFTERL( boundary, boundary_len ); - *len = p - res - boundary_len - 4; - return res; - } - - /* Try the next part */ - goto next; - -fail: - *len = 0; - return NULL; -} - - -/*=========================================================================== - * Server - *===========================================================================*/ - -sb_Server *sb_new_server(const sb_Options *opt) { - sb_Server *srv; - struct addrinfo hints, *ai = NULL; - int err, optval; - -#ifdef _WIN32 - { WSADATA dat; WSAStartup(MAKEWORD(2, 2), &dat); } -#else - /* Stops the SIGPIPE signal being raised when writing to a closed socket */ - signal(SIGPIPE, SIG_IGN); -#endif - - /* Create server object */ - srv = malloc( sizeof(*srv) ); - if (!srv) goto fail; - memset(srv, 0, sizeof(*srv)); - srv->sockfd = INVALID_SOCKET; - srv->handler = opt->handler; - srv->udata = opt->udata; - srv->timeout = opt->timeout ? str_to_uint(opt->timeout) : 30000; - srv->max_request_size = str_to_uint(opt->max_request_size); - srv->max_lifetime = str_to_uint(opt->max_lifetime); - - /* Get addrinfo */ - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; - err = getaddrinfo(opt->host, opt->port, &hints, &ai); - if (err) goto fail; - - /* Init socket */ - srv->sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (srv->sockfd == INVALID_SOCKET) goto fail; - set_socket_non_blocking(srv->sockfd); - - /* Set SO_REUSEADDR so that the socket can be immediately bound without - * having to wait for any closed socket on the same port to timeout */ - optval = 1; - setsockopt(srv->sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); - - /* Bind and listen */ - err = bind(srv->sockfd, ai->ai_addr, ai->ai_addrlen); - if (err) goto fail; - err = listen(srv->sockfd, 1023); - if (err) goto fail; - - /* Clean up */ - freeaddrinfo(ai); - ai = NULL; - - return srv; - -fail: - if (ai) freeaddrinfo(ai); - if (srv) sb_close_server(srv); - return NULL; -} - - -void sb_close_server(sb_Server *srv) { - /* Destroy all streams */ - while (srv->streams) { - sb_Stream *st = srv->streams; - srv->streams = st->next; - sb_stream_destroy(st); - } - - /* Clean up */ - if (srv->sockfd != INVALID_SOCKET) { - close(srv->sockfd); - } - free(srv); -} - - -int sb_poll_server(sb_Server *srv, int timeout) { - sb_Stream *st, **st_next; - fd_set fds_read, fds_write; - sb_Socket max_fd = srv->sockfd; - struct timeval tv; - int err; - - /* Init fd_sets */ - FD_ZERO(&fds_read); - FD_ZERO(&fds_write); - - /* Add server sockfd to fd_set */ - FD_SET(srv->sockfd, &fds_read); - - /* Add streams to fd_sets */ - for (st = srv->streams; st; st = st->next) { - if (st->state >= STATE_SENDING_STATUS) { - FD_SET(st->sockfd, &fds_write); - } else { - FD_SET(st->sockfd, &fds_read); - } - if (st->sockfd > max_fd) max_fd = st->sockfd; - } - - /* Init timeout timeval */ - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; - - /* Do select */ - select(max_fd + 1, &fds_read, &fds_write, NULL, &tv); - - /* Get and store current time */ - srv->now = time(NULL); - - /* Handle existing streams */ - st_next = &srv->streams; - while (*st_next) { - st = *st_next; - - /* Receive data */ - if (FD_ISSET(st->sockfd, &fds_read)) { - err = sb_stream_recv(st); - if (err) return err; - } - - /* Send data */ - if (FD_ISSET(st->sockfd, &fds_write)) { - err = sb_stream_send(st); - if (err) return err; - } - - /* Check stream against timeout, max request length and max lifetime */ - if ( - (srv->timeout && srv->now - st->last_activity > srv->timeout / 1000) || - (srv->max_lifetime && - srv->now - st->init_time > srv->max_lifetime / 1000) || - (srv->max_request_size && st->recv_buf.len >= srv->max_request_size) - ) { - sb_stream_close(st); - } - - /* Handle disconnect -- destroy stream */ - if (st->state == STATE_CLOSING) { - *st_next = st->next; - sb_stream_destroy(st); - continue; - } - - /* Next */ - st_next = &(*st_next)->next; - } - - /* Handle new streams */ - if (FD_ISSET(srv->sockfd, &fds_read)) { - sb_Event e; - sb_Socket sockfd; - - /* Accept connections */ - while ( (sockfd = accept(srv->sockfd, NULL, NULL)) != INVALID_SOCKET ) { - -#ifdef _WIN32 - /* As the fd_set on windows is an array rather than a bitset, an fd - * value can never be too large for it; thus this check is omitted */ -#else - /* Check FD size, error if it is larger than FD_SETSIZE */ - if (sockfd > FD_SETSIZE) { - close(sockfd); - return SB_EFDTOOBIG; - } -#endif - - /* Init new stream */ - st = sb_stream_new(srv, sockfd); - if (!st) { - close(sockfd); - return SB_EOUTOFMEM; - } - - /* Push stream to list */ - st->next = srv->streams; - srv->streams = st; - - /* Do `connect` event */ - e.type = SB_EV_CONNECT; - err = sb_stream_emit(st, &e); - if (err) return err; - } - } - - return SB_ESUCCESS; -} - - diff --git a/lua/sandborb/sandbird.h b/lua/sandborb/sandbird.h deleted file mode 100644 index e1c0e7c..0000000 --- a/lua/sandborb/sandbird.h +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2016 rxi - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the MIT license. See LICENSE for details. - */ - - -#ifndef SANDBIRD_H -#define SANDBIRD_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define SB_VERSION "0.1.3" - -typedef struct sb_Server sb_Server; -typedef struct sb_Stream sb_Stream; -typedef struct sb_Event sb_Event; -typedef struct sb_Options sb_Options; -typedef int (*sb_Handler)(sb_Event*); - -struct sb_Event { - int type; - void *udata; - sb_Server *server; - sb_Stream *stream; - const char *address; - const char *method; - const char *path; -}; - -struct sb_Options { - sb_Handler handler; - void *udata; - const char *host; - const char *port; - const char *timeout; - const char *max_lifetime; - const char *max_request_size; -}; - -enum { - SB_ESUCCESS = 0, - SB_EFAILURE = -1, - SB_EOUTOFMEM = -2, - SB_ETRUNCATED = -3, - SB_EBADSTATE = -4, - SB_EBADRESULT = -5, - SB_ECANTOPEN = -6, - SB_ENOTFOUND = -7, - SB_EFDTOOBIG = -8 -}; - -enum { - SB_EV_CONNECT, - SB_EV_CLOSE, - SB_EV_REQUEST -}; - -enum { - SB_RES_OK, - SB_RES_CLOSE -}; - -const char *sb_error_str(int code); -sb_Server *sb_new_server(const sb_Options *opt); -void sb_close_server(sb_Server *srv); -int sb_poll_server(sb_Server *srv, int timeout); -int sb_send_status(sb_Stream *st, int code, const char *msg); -int sb_send_header(sb_Stream *st, const char *field, const char *val); -int sb_send_file(sb_Stream *st, const char *filename); -int sb_write(sb_Stream *st, const void *data, size_t len); -int sb_vwritef(sb_Stream *st, const char *fmt, va_list args); -int sb_writef(sb_Stream *st, const char *fmt, ...); -int sb_get_header(sb_Stream *st, const char *field, char *dst, size_t len); -int sb_get_var(sb_Stream *st, const char *name, char *dst, size_t len); -int sb_get_cookie(sb_Stream *st, const char *name, char *dst, size_t len); -const void *sb_get_multipart(sb_Stream *st, const char *name, size_t *len); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif - - diff --git a/lua/sandborb/src/app.c b/lua/sandborb/src/app.c new file mode 100644 index 0000000..1cfa6d0 --- /dev/null +++ b/lua/sandborb/src/app.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include + +#include "sandbird.h" + +/* Functions to be called from Lua */ +static int l_sb_send_header(lua_State *L) { + sb_Event *e = lua_touserdata(L, 1); + const char *key = lua_tostring(L, 2); + const char *value = lua_tostring(L, 3); + sb_send_header(e->stream, key, value); + return 0; /* Number of return values */ +} + +static int l_sb_send_status(lua_State *L) { + sb_Event *e = lua_touserdata(L, 1); + int status = lua_tointeger(L, 2); + const char *message = lua_tostring(L, 3); + sb_send_status(e->stream, status, message); + return 0; /* Number of return values */ +} + +static int event_handler(sb_Event *e) { + if (e->type == SB_EV_REQUEST) { + printf("%s - %s %s\n", e->address, e->method, e->path); + + lua_State *L = luaL_newstate(); /* create a new Lua state */ + luaL_openlibs(L); /* load Lua libraries */ + + /* Expose our functions to Lua */ + lua_pushcfunction(L, l_sb_send_header); + lua_setglobal(L, "sb_send_header"); + lua_pushcfunction(L, l_sb_send_status); + lua_setglobal(L, "sb_send_status"); + + /* load and run the Lua script */ + if (luaL_dofile(L, "route_handler.lua") != LUA_OK) { + fprintf(stderr, "Failed to load Lua script: %s\n", lua_tostring(L, -1)); + lua_close(L); + return SB_RES_OK; + } + + /* call the Lua function */ + lua_getglobal(L, "HANDLE_ROUTE"); /* all caps because it is a global function */ + lua_pushlightuserdata(L, e); /* Pass the event as userdata to Lua */ + lua_pushstring(L, e->path); + if (lua_pcall(L, 2, 1, 0) != LUA_OK) { + fprintf(stderr, "Failed to call Lua function: %s\n", lua_tostring(L, -1)); + lua_close(L); + return SB_RES_OK; + } + + /* get the result from the Lua function */ + const char *response = lua_tostring(L, -1); + lua_pop(L, 1); + + sb_writef(e->stream, response); + + lua_close(L); /* close the Lua state */ + } + return SB_RES_OK; +} + + +int main(void) { + sb_Options opt; + sb_Server *server; + + memset(&opt, 0, sizeof(opt)); + opt.port = "8000"; + opt.handler = event_handler; + + server = sb_new_server(&opt); + + if (!server) { + fprintf(stderr, "failed to initialize server\n"); + exit(EXIT_FAILURE); + } + + printf("Server running at http://localhost:%s\n", opt.port); + + for (;;) { + sb_poll_server(server, 1000); + } + + sb_close_server(server); + return EXIT_SUCCESS; +} + + + diff --git a/lua/sandborb/src/sandbird.c b/lua/sandborb/src/sandbird.c new file mode 100644 index 0000000..e08c287 --- /dev/null +++ b/lua/sandborb/src/sandbird.c @@ -0,0 +1,979 @@ +/** + * Copyright (c) 2016 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + + +#ifdef _WIN32 + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x501 + #endif + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef FD_SETSIZE + #define FD_SETSIZE 2048 + #endif + #include + #include + #include +#else + #ifndef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 200809L + #endif + #include + #include + #include + #include + #include + #include + #include + #include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "sandbird.h" + + +#ifdef _WIN32 + #define close(a) closesocket(a) + #define setsockopt(a, b, c, d, e) setsockopt(a, b, c, (char*)(d), e) + + #undef errno + #define errno WSAGetLastError() + + #undef EWOULDBLOCK + #define EWOULDBLOCK WSAEWOULDBLOCK + + const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { + union { struct sockaddr sa; struct sockaddr_in sai; + struct sockaddr_in6 sai6; } addr; + int res; + memset(&addr, 0, sizeof(addr)); + addr.sa.sa_family = af; + if (af == AF_INET6) { + memcpy(&addr.sai6.sin6_addr, src, sizeof(addr.sai6.sin6_addr)); + } else { + memcpy(&addr.sai.sin_addr, src, sizeof(addr.sai.sin_addr)); + } + res = WSAAddressToStringA(&addr.sa, sizeof(addr), 0, dst, (LPDWORD) &size); + if (res != 0) return NULL; + return dst; + } +#endif + +#ifdef _WIN32 + typedef SOCKET sb_Socket; +#else + typedef int sb_Socket; + #define INVALID_SOCKET -1 +#endif + +typedef struct sb_Buffer sb_Buffer; + +struct sb_Buffer { char *s; size_t len, cap; }; + +struct sb_Stream { + int state; /* Current state of the stream */ + sb_Server *server; /* The server object which owns this stream */ + char address[46]; /* Remote IP address */ + time_t init_time; /* Time the stream was created */ + time_t last_activity; /* Time of Last I/O activity on the stream */ + size_t expected_recv_len; /* Expected length of the stream's request */ + size_t data_idx; /* Index of data section in recv_buf */ + sb_Socket sockfd; /* Socket for this streams connection */ + sb_Buffer recv_buf; /* Data received from client */ + sb_Buffer send_buf; /* Data waiting to be sent to client */ + FILE *send_fp; /* File currently being sent to client */ + sb_Stream *next; /* Next stream in linked list */ +}; + +struct sb_Server { + sb_Stream *streams; /* Linked list of all streams */ + sb_Handler handler; /* Event handler callback function */ + sb_Socket sockfd; /* Listeneing server socket */ + void *udata; /* User data value passed to all events */ + time_t now; /* The current time */ + time_t timeout; /* Stream no-activity timeout */ + time_t max_lifetime; /* Maximum time a stream can exist */ + size_t max_request_size; /* Maximum request size in bytes */ +}; + +enum { + STATE_RECEIVING_HEADER, + STATE_RECEIVING_REQUEST, + STATE_SENDING_STATUS, + STATE_SENDING_HEADER, + STATE_SENDING_DATA, + STATE_SENDING_FILE, + STATE_CLOSING +}; + + +/*=========================================================================== + * Utility + *===========================================================================*/ + +static void set_socket_non_blocking(sb_Socket sockfd) { +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(sockfd, FIONBIO, &mode); +#else + int flags = fcntl(sockfd, F_GETFL); + fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); +#endif +} + + +static int get_socket_address(sb_Socket sockfd, char *dst) { + int err; + union { struct sockaddr sa; struct sockaddr_storage sas; + struct sockaddr_in sai; struct sockaddr_in6 sai6; } addr; + socklen_t sz = sizeof(addr); + err = getpeername(sockfd, &addr.sa, &sz); + if (err == -1) { + *dst = '\0'; + return SB_EFAILURE; + } + if (addr.sas.ss_family == AF_INET6) { + inet_ntop(AF_INET6, &addr.sai6.sin6_addr, dst, INET6_ADDRSTRLEN); + } else { + inet_ntop(AF_INET, &addr.sai.sin_addr, dst, INET_ADDRSTRLEN); + } + return SB_ESUCCESS; +} + + +static unsigned str_to_uint(const char *str) { + unsigned n; + if (!str || sscanf(str, "%u", &n) != 1) return 0; + return n; +} + + +static int hex_to_int(int chr) { + return isdigit(chr) ? (chr - '0') : (tolower(chr) - 'a' + 10); +} + + +static int url_decode(char *dst, const char *src, size_t len) { + len--; + while (*src && !strchr("?& \t\r\n", *src) && len) { + if (src[0] == '%' && src[1] && src[2]) { + *dst = (hex_to_int(src[1]) << 4) | hex_to_int(src[2]); + src += 2; + } else if (*src == '+') { + *dst = ' '; + } else { + *dst = *src; + } + dst++, src++, len--; + } + *dst = '\0'; + return (len == 0) ? SB_ETRUNCATED : SB_ESUCCESS; +} + + +static int mem_equal(const void *a, const void *b, size_t len) { + const char *p = a, *q = b; + while (len) { + if (*p != *q) return 0; + p++, q++, len--; + } + return 1; +} + + +static int mem_case_equal(const void *a, const void *b, size_t len) { + const char *p = a, *q = b; + while (len) { + if (tolower(*p) != tolower(*q)) return 0; + p++, q++, len--; + } + return 1; +} + + +static const char *find_header_value(const char *str, const char *field) { + size_t len = strlen(field); + while (*str && !mem_equal(str, "\r\n", 2)) { + if (mem_case_equal(str, field, len) && str[len] == ':') { + str += len + 1; + return str + strspn(str, " \t"); + } + str += strcspn(str, "\r"); + str += mem_equal(str, "\r\n", 2) ? 2 : 0; + } + return NULL; +} + + +static const char *find_var_value(const char *str, const char *name) { + size_t len = strlen(name); + for (;;) { + if (mem_equal(str, name, len) && str[len] == '=') { + return str + len + 1; + } + str += strcspn(str, "& \t\r\n"); + if (*str != '&') break; + str++; + } + return NULL; +} + + +const char *sb_error_str(int code) { + switch (code) { + case SB_ESUCCESS : return "success"; + case SB_EFAILURE : return "failure"; + case SB_EOUTOFMEM : return "out of memory"; + case SB_ETRUNCATED : return "result truncated"; + case SB_EBADSTATE : return "bad stream state for this operation"; + case SB_EBADRESULT : return "bad result code from event handler"; + case SB_ECANTOPEN : return "cannot open file"; + case SB_ENOTFOUND : return "not found"; + case SB_EFDTOOBIG : return "got socket fd larger than FD_SETSIZE"; + default : return "unknown"; + } +} + + +/*=========================================================================== + * Buffer + *===========================================================================*/ + +static void sb_buffer_init(sb_Buffer *buf) { + memset(buf, 0, sizeof(*buf)); +} + + +static void sb_buffer_deinit(sb_Buffer *buf) { + free(buf->s); +} + + +static void sb_buffer_shift(sb_Buffer *buf, size_t n) { + buf->len -= n; + memmove(buf->s, buf->s + n, buf->len); +} + + +static int sb_buffer_reserve(sb_Buffer *buf, size_t n) { + void *p; + if (buf->cap >= n) return SB_ESUCCESS; + p = realloc(buf->s, n); + if (!p) return SB_EOUTOFMEM; + buf->s = p; + buf->cap = n; + return SB_ESUCCESS; +} + + +static int sb_buffer_push_char(sb_Buffer *buf, char chr) { + if (buf->len == buf->cap) { + int err = sb_buffer_reserve(buf, (buf->cap << 1) | (!buf->cap)); + if (err) return err; + } + buf->s[buf->len++] = chr; + return SB_ESUCCESS; +} + + +static int sb_buffer_push_str(sb_Buffer *buf, const char *p, size_t len) { + int err; + size_t orig_len = buf->len; + while (len) { + err = sb_buffer_push_char(buf, *p); + if (err) { + buf->len = orig_len; + return err; + } + p++, len--; + } + return SB_ESUCCESS; +} + + +static int sb_buffer_vwritef(sb_Buffer *buf, const char *fmt, va_list args) { + int err; + size_t orig_len = buf->len; + char fbuf[64]; + char lbuf[512]; + char *s; + + while (*fmt) { + if (*fmt == '%') { + switch (*++fmt) { + + case 's': + s = va_arg(args, char*); + if (s == NULL) s = "(null)"; + err = sb_buffer_push_str(buf, s, strlen(s)); + if (err) goto fail; + break; + + default: + fbuf[0] = '%'; + s = fbuf + 1; + while ( !isalpha(*fmt) && *fmt != '%' ) *s++ = *fmt++; + s[0] = *fmt, s[1] = '\0'; + switch (*fmt) { + case 'f': + case 'g': sprintf(lbuf, fbuf, va_arg(args, double)); break; + case 'c': + case 'd': + case 'i': sprintf(lbuf, fbuf, va_arg(args, int)); break; + case 'u': + case 'x': + case 'X': sprintf(lbuf, fbuf, va_arg(args, unsigned)); break; + case 'p': sprintf(lbuf, fbuf, va_arg(args, void*)); break; + default : lbuf[0] = *fmt, lbuf[1] = '\0'; + } + err = sb_buffer_push_str(buf, lbuf, strlen(lbuf)); + if (err) goto fail; + } + } else { + err = sb_buffer_push_char(buf, *fmt); + if (err) goto fail; + } + fmt++; + } + + return SB_ESUCCESS; + +fail: + buf->len = orig_len; + return err; +} + + +static int sb_buffer_writef(sb_Buffer *buf, const char *fmt, ...) { + int err; + va_list args; + va_start(args, fmt); + err = sb_buffer_vwritef(buf, fmt, args); + va_end(args); + return err; +} + + +static int sb_buffer_null_terminate(sb_Buffer *buf) { + int err = sb_buffer_push_char(buf, '\0'); + if (err) return err; + buf->len--; + return SB_ESUCCESS; +} + + +/*=========================================================================== + * Stream + *===========================================================================*/ + +static sb_Stream *sb_stream_new(sb_Server *srv, sb_Socket sockfd) { + sb_Stream *st = malloc( sizeof(*st) ); + if (!st) return NULL; + memset(st, 0, sizeof(*st)); + sb_buffer_init(&st->recv_buf); + sb_buffer_init(&st->send_buf); + st->sockfd = sockfd; + st->server = srv; + st->init_time = srv->now; + st->last_activity = srv->now; + set_socket_non_blocking(sockfd); + get_socket_address(sockfd, st->address); + return st; +} + + +static void sb_stream_close(sb_Stream *st) { + st->state = STATE_CLOSING; +} + + +static int sb_stream_emit(sb_Stream *st, sb_Event *e) { + int res; + e->stream = st; + e->udata = st->server->udata; + e->server = st->server; + e->address = st->address; + res = e->server->handler(e); + if (res < 0) return res; + switch (res) { + case SB_RES_CLOSE : sb_stream_close(st); /* Fall through */ + case SB_RES_OK : return SB_ESUCCESS; + default : return SB_EBADRESULT; + } +} + + +static void sb_stream_destroy(sb_Stream *st) { + sb_Event e; + /* Emit close event */ + e.type = SB_EV_CLOSE; + sb_stream_emit(st, &e); + /* Clean up */ + close(st->sockfd); + if (st->send_fp) fclose(st->send_fp); + sb_buffer_deinit(&st->recv_buf); + sb_buffer_deinit(&st->send_buf); + free(st); +} + + +static int sb_stream_recv(sb_Stream *st) { + for (;;) { + char buf[4096]; + int err, i, sz; + + /* Receive data */ + sz = recv(st->sockfd, buf, sizeof(buf) - 1, 0); + if (sz <= 0) { + /* Disconnected? */ + if (sz == 0 || errno != EWOULDBLOCK) { + sb_stream_close(st); + } + return SB_ESUCCESS; + } + + /* Update last_activity */ + st->last_activity = st->server->now; + + /* Write to recv_buf */ + for (i = 0; i < sz; i++) { + err = sb_buffer_push_char(&st->recv_buf, buf[i]); + if (err) return err; + + /* Have we received the whole header? */ + if ( + st->state == STATE_RECEIVING_HEADER && + st->recv_buf.len >= 4 && + mem_equal(st->recv_buf.s + st->recv_buf.len - 4, "\r\n\r\n", 4) + ) { + const char *s; + /* Update stream's current state */ + st->state = STATE_RECEIVING_REQUEST; + /* Assure recv_buf is null-terminated */ + err = sb_buffer_null_terminate(&st->recv_buf); + if (err) return err; + /* If the header contains the Content-Length field we set the + * expected_recv_len and continue writing to the recv_buf, otherwise we + * assume the request is complete */ + s = find_header_value(st->recv_buf.s, "Content-Length"); + if (s) { + st->expected_recv_len = st->recv_buf.len + str_to_uint(s); + st->data_idx = st->recv_buf.len; + } else { + goto handle_request; + } + } + + /* Have we received all the data we're expecting? */ + if (st->expected_recv_len == st->recv_buf.len) { + /* Handle request */ + sb_Event e; + int n, path_idx; + char method[16], path[512], ver[16]; +handle_request: + st->state = STATE_SENDING_STATUS; + /* Assure recv_buf string is NULL-terminated */ + err = sb_buffer_null_terminate(&st->recv_buf); + if (err) return err; + /* Get method, path, version */ + n = sscanf(st->recv_buf.s, "%15s %n%*s %15s", method, &path_idx, ver); + /* Is request line invalid? */ + if (n != 2 || !mem_equal(ver, "HTTP", 4)) { + sb_stream_close(st); + return SB_ESUCCESS; + } + /* Build and emit `request` event */ + url_decode(path, st->recv_buf.s + path_idx, sizeof(path)); + e.type = SB_EV_REQUEST; + e.method = method; + e.path = path; + err = sb_stream_emit(st, &e); + if (err) return err; + /* No more data needs to be received (nor should it exist) */ + return SB_ESUCCESS; + } + } + } + + return SB_ESUCCESS; +} + + +static int sb_stream_send(sb_Stream *st) { + if (st->send_buf.len > 0) { + int sz; + + /* Send data */ +send_data: + sz = send(st->sockfd, st->send_buf.s, st->send_buf.len, 0); + if (sz <= 0) { + /* Disconnected? */ + if (errno != EWOULDBLOCK) { + sb_stream_close(st); + } + return SB_ESUCCESS; + } + + /* Remove sent bytes from buffer */ + sb_buffer_shift(&st->send_buf, sz); + + /* Update last_activity */ + st->last_activity = st->server->now; + + } else if (st->send_fp) { + /* Read chunk, write to stream and continue sending */ + int err = sb_buffer_reserve(&st->send_buf, 8192); + if (err) return err; + st->send_buf.len = fread(st->send_buf.s, 1, st->send_buf.cap, st->send_fp); + if (st->send_buf.len > 0) goto send_data; + + /* Reached end of file */ + fclose(st->send_fp); + st->send_fp = NULL; + + } else { + /* No more data left -- disconnect */ + sb_stream_close(st); + } + + return SB_ESUCCESS; +} + + +static int sb_stream_finalize_header(sb_Stream *st) { + int err; + if (st->state < STATE_SENDING_HEADER) { + err = sb_send_status(st, 200, "OK"); + if (err) return err; + } + err = sb_buffer_push_str(&st->send_buf, "\r\n", 2); + if (err) return err; + st->state = STATE_SENDING_DATA; + return SB_ESUCCESS; +} + + +int sb_send_status(sb_Stream *st, int code, const char *msg) { + int err; + if (st->state != STATE_SENDING_STATUS) { + return SB_EBADSTATE; + } + err = sb_buffer_writef(&st->send_buf, "HTTP/1.1 %d %s\r\n", code, msg); + if (err) return err; + st->state = STATE_SENDING_HEADER; + return SB_ESUCCESS; +} + + +int sb_send_header(sb_Stream *st, const char *field, const char *val) { + int err; + if (st->state > STATE_SENDING_HEADER) { + return SB_EBADSTATE; + } + if (st->state < STATE_SENDING_HEADER) { + err = sb_send_status(st, 200, "OK"); + if (err) return err; + } + err = sb_buffer_writef(&st->send_buf, "%s: %s\r\n", field, val); + if (err) return err; + return SB_ESUCCESS; +} + + +int sb_send_file(sb_Stream *st, const char *filename) { + int err; + char buf[32]; + size_t sz; + FILE *fp = NULL; + if (st->state > STATE_SENDING_HEADER) { + return SB_EBADSTATE; + } + /* Try to open file */ + fp = fopen(filename, "rb"); + if (!fp) return SB_ECANTOPEN; + + /* Get file size and write headers */ + fseek(fp, 0, SEEK_END); + sz = ftell(fp); + sprintf(buf, "%u", (unsigned) sz); + err = sb_send_header(st, "Content-Length", buf); + if (err) goto fail; + err = sb_stream_finalize_header(st); + if (err) goto fail; + + /* Rewind file, set stream's fp and state */ + fseek(fp, 0, SEEK_SET); + st->send_fp = fp; + st->state = STATE_SENDING_FILE; + return SB_ESUCCESS; + +fail: + if (fp) fclose(fp); + return err; +} + + +int sb_write(sb_Stream *st, const void *data, size_t len) { + if (st->state < STATE_SENDING_DATA) { + int err = sb_stream_finalize_header(st); + if (err) return err; + } + if (st->state != STATE_SENDING_DATA) return SB_EBADSTATE; + return sb_buffer_push_str(&st->send_buf, data, len); +} + + +int sb_vwritef(sb_Stream *st, const char *fmt, va_list args) { + if (st->state < STATE_SENDING_DATA) { + int err = sb_stream_finalize_header(st); + if (err) return err; + } + if (st->state != STATE_SENDING_DATA) return SB_EBADSTATE; + return sb_buffer_vwritef(&st->send_buf, fmt, args); +} + + +int sb_writef(sb_Stream *st, const char *fmt, ...) { + int err; + va_list args; + va_start(args, fmt); + err = sb_vwritef(st, fmt, args); + va_end(args); + return err; +} + + +int sb_get_header(sb_Stream *st, const char *field, char *dst, size_t len) { + size_t n; + int res = SB_ESUCCESS; + const char *s = find_header_value(st->recv_buf.s, field); + if (!s) { + *dst = '\0'; + return SB_ENOTFOUND; + } + n = strchr(s, '\r') - s; + while (n > 1 && strchr(" \t", s[n-1])) n--; /* trim whitespace from end */ + if (n > len - 1) { + n = len - 1; + res = SB_ETRUNCATED; + } + memcpy(dst, s, n); + dst[n] = '\0'; + return res; +} + + +int sb_get_var(sb_Stream *st, const char *name, char *dst, size_t len) { + const char *q, *s = NULL; + + /* Find beginning of query string */ + q = st->recv_buf.s + strcspn(st->recv_buf.s, "?\r"); + q = (*q == '?') ? (q + 1) : NULL; + + /* Try to get var from query string, then data string */ + if (q) s = find_var_value(q, name); + if (!s && st->data_idx) { + s = find_var_value(st->recv_buf.s + st->data_idx, name); + } + if (!s) { + *dst = '\0'; + return SB_ENOTFOUND; + } + return url_decode(dst, s, len); +} + + +int sb_get_cookie(sb_Stream *st, const char *name, char *dst, size_t len) { + size_t n; + const char *s = st->recv_buf.s; + int res = SB_ESUCCESS; + size_t name_len = strlen(name); + + /* Get cookie header */ + s = find_header_value(st->recv_buf.s, "Cookie"); + if (!s) goto fail; + + /* Find var */ + while (*s) { + s += strspn(s, " \t"); + /* Found var? find value, get len, copy value and return */ + if ( mem_case_equal(s, name, name_len) && strchr(" =", s[name_len]) ) { + s += name_len; + s += strspn(s, "= \t\r"); + n = strcspn(s, ";\r"); + if (n >= len - 1) { + n = len - 1; + res = SB_ETRUNCATED; + } + memcpy(dst, s, n); + dst[n] = '\0'; + return res; + } + s += strcspn(s, ";\r"); + if (*s != ';') goto fail; + s++; + } + +fail: + *dst = '\0'; + return SB_ENOTFOUND; +} + + +#define P_ATCHK(x) do { if (!(p = (x))) goto fail; } while (0) +#define P_AFTERL(x, l) do {\ + size_t len__ = (l);\ + for (;; p++) {\ + if (p == end - len__) goto fail;\ + if (mem_equal(p, x, len__)) break;\ + }\ + p += len__;\ + } while (0) +#define P_AFTER(s) P_AFTERL(s, strlen(s)) + +const void *sb_get_multipart(sb_Stream *st, const char *name, size_t *len) { + const char *boundary; + size_t boundary_len; + size_t name_len = strlen(name); + const char *p = st->recv_buf.s; + char *end = st->recv_buf.s + st->recv_buf.len; + + /* Get boundary string */ + P_ATCHK( find_header_value(p, "Content-Type") ); + P_AFTER( "boundary=" ); + boundary = p; + P_AFTER( "\r\n" ); + boundary_len = p - boundary - 2; + +next: + /* Move to after first boundary, then to start of name */ + P_AFTERL( boundary, boundary_len ); + P_AFTER( "\r\n" ); + P_ATCHK( find_header_value(p, "Content-Disposition") ); + P_AFTER( "name=\"" ); + + /* Does the name match what we were looking for? */ + if (mem_equal(p, name, name_len) && p[name_len] == '"') { + const char *res; + /* Move to start of data */ + P_AFTER( "\r\n\r\n" ); + res = p; + /* Find boundary, set length and return result */ + P_AFTERL( boundary, boundary_len ); + *len = p - res - boundary_len - 4; + return res; + } + + /* Try the next part */ + goto next; + +fail: + *len = 0; + return NULL; +} + + +/*=========================================================================== + * Server + *===========================================================================*/ + +sb_Server *sb_new_server(const sb_Options *opt) { + sb_Server *srv; + struct addrinfo hints, *ai = NULL; + int err, optval; + +#ifdef _WIN32 + { WSADATA dat; WSAStartup(MAKEWORD(2, 2), &dat); } +#else + /* Stops the SIGPIPE signal being raised when writing to a closed socket */ + signal(SIGPIPE, SIG_IGN); +#endif + + /* Create server object */ + srv = malloc( sizeof(*srv) ); + if (!srv) goto fail; + memset(srv, 0, sizeof(*srv)); + srv->sockfd = INVALID_SOCKET; + srv->handler = opt->handler; + srv->udata = opt->udata; + srv->timeout = opt->timeout ? str_to_uint(opt->timeout) : 30000; + srv->max_request_size = str_to_uint(opt->max_request_size); + srv->max_lifetime = str_to_uint(opt->max_lifetime); + + /* Get addrinfo */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + err = getaddrinfo(opt->host, opt->port, &hints, &ai); + if (err) goto fail; + + /* Init socket */ + srv->sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (srv->sockfd == INVALID_SOCKET) goto fail; + set_socket_non_blocking(srv->sockfd); + + /* Set SO_REUSEADDR so that the socket can be immediately bound without + * having to wait for any closed socket on the same port to timeout */ + optval = 1; + setsockopt(srv->sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); + + /* Bind and listen */ + err = bind(srv->sockfd, ai->ai_addr, ai->ai_addrlen); + if (err) goto fail; + err = listen(srv->sockfd, 1023); + if (err) goto fail; + + /* Clean up */ + freeaddrinfo(ai); + ai = NULL; + + return srv; + +fail: + if (ai) freeaddrinfo(ai); + if (srv) sb_close_server(srv); + return NULL; +} + + +void sb_close_server(sb_Server *srv) { + /* Destroy all streams */ + while (srv->streams) { + sb_Stream *st = srv->streams; + srv->streams = st->next; + sb_stream_destroy(st); + } + + /* Clean up */ + if (srv->sockfd != INVALID_SOCKET) { + close(srv->sockfd); + } + free(srv); +} + + +int sb_poll_server(sb_Server *srv, int timeout) { + sb_Stream *st, **st_next; + fd_set fds_read, fds_write; + sb_Socket max_fd = srv->sockfd; + struct timeval tv; + int err; + + /* Init fd_sets */ + FD_ZERO(&fds_read); + FD_ZERO(&fds_write); + + /* Add server sockfd to fd_set */ + FD_SET(srv->sockfd, &fds_read); + + /* Add streams to fd_sets */ + for (st = srv->streams; st; st = st->next) { + if (st->state >= STATE_SENDING_STATUS) { + FD_SET(st->sockfd, &fds_write); + } else { + FD_SET(st->sockfd, &fds_read); + } + if (st->sockfd > max_fd) max_fd = st->sockfd; + } + + /* Init timeout timeval */ + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + /* Do select */ + select(max_fd + 1, &fds_read, &fds_write, NULL, &tv); + + /* Get and store current time */ + srv->now = time(NULL); + + /* Handle existing streams */ + st_next = &srv->streams; + while (*st_next) { + st = *st_next; + + /* Receive data */ + if (FD_ISSET(st->sockfd, &fds_read)) { + err = sb_stream_recv(st); + if (err) return err; + } + + /* Send data */ + if (FD_ISSET(st->sockfd, &fds_write)) { + err = sb_stream_send(st); + if (err) return err; + } + + /* Check stream against timeout, max request length and max lifetime */ + if ( + (srv->timeout && srv->now - st->last_activity > srv->timeout / 1000) || + (srv->max_lifetime && + srv->now - st->init_time > srv->max_lifetime / 1000) || + (srv->max_request_size && st->recv_buf.len >= srv->max_request_size) + ) { + sb_stream_close(st); + } + + /* Handle disconnect -- destroy stream */ + if (st->state == STATE_CLOSING) { + *st_next = st->next; + sb_stream_destroy(st); + continue; + } + + /* Next */ + st_next = &(*st_next)->next; + } + + /* Handle new streams */ + if (FD_ISSET(srv->sockfd, &fds_read)) { + sb_Event e; + sb_Socket sockfd; + + /* Accept connections */ + while ( (sockfd = accept(srv->sockfd, NULL, NULL)) != INVALID_SOCKET ) { + +#ifdef _WIN32 + /* As the fd_set on windows is an array rather than a bitset, an fd + * value can never be too large for it; thus this check is omitted */ +#else + /* Check FD size, error if it is larger than FD_SETSIZE */ + if (sockfd > FD_SETSIZE) { + close(sockfd); + return SB_EFDTOOBIG; + } +#endif + + /* Init new stream */ + st = sb_stream_new(srv, sockfd); + if (!st) { + close(sockfd); + return SB_EOUTOFMEM; + } + + /* Push stream to list */ + st->next = srv->streams; + srv->streams = st; + + /* Do `connect` event */ + e.type = SB_EV_CONNECT; + err = sb_stream_emit(st, &e); + if (err) return err; + } + } + + return SB_ESUCCESS; +} + + diff --git a/lua/sandborb/src/sandbird.h b/lua/sandborb/src/sandbird.h new file mode 100644 index 0000000..e1c0e7c --- /dev/null +++ b/lua/sandborb/src/sandbird.h @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2016 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + + +#ifndef SANDBIRD_H +#define SANDBIRD_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SB_VERSION "0.1.3" + +typedef struct sb_Server sb_Server; +typedef struct sb_Stream sb_Stream; +typedef struct sb_Event sb_Event; +typedef struct sb_Options sb_Options; +typedef int (*sb_Handler)(sb_Event*); + +struct sb_Event { + int type; + void *udata; + sb_Server *server; + sb_Stream *stream; + const char *address; + const char *method; + const char *path; +}; + +struct sb_Options { + sb_Handler handler; + void *udata; + const char *host; + const char *port; + const char *timeout; + const char *max_lifetime; + const char *max_request_size; +}; + +enum { + SB_ESUCCESS = 0, + SB_EFAILURE = -1, + SB_EOUTOFMEM = -2, + SB_ETRUNCATED = -3, + SB_EBADSTATE = -4, + SB_EBADRESULT = -5, + SB_ECANTOPEN = -6, + SB_ENOTFOUND = -7, + SB_EFDTOOBIG = -8 +}; + +enum { + SB_EV_CONNECT, + SB_EV_CLOSE, + SB_EV_REQUEST +}; + +enum { + SB_RES_OK, + SB_RES_CLOSE +}; + +const char *sb_error_str(int code); +sb_Server *sb_new_server(const sb_Options *opt); +void sb_close_server(sb_Server *srv); +int sb_poll_server(sb_Server *srv, int timeout); +int sb_send_status(sb_Stream *st, int code, const char *msg); +int sb_send_header(sb_Stream *st, const char *field, const char *val); +int sb_send_file(sb_Stream *st, const char *filename); +int sb_write(sb_Stream *st, const void *data, size_t len); +int sb_vwritef(sb_Stream *st, const char *fmt, va_list args); +int sb_writef(sb_Stream *st, const char *fmt, ...); +int sb_get_header(sb_Stream *st, const char *field, char *dst, size_t len); +int sb_get_var(sb_Stream *st, const char *name, char *dst, size_t len); +int sb_get_cookie(sb_Stream *st, const char *name, char *dst, size_t len); +const void *sb_get_multipart(sb_Stream *st, const char *name, size_t *len); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif + + -- cgit 1.4.1-2-gfad0