From 08403b616f9dad83cb1204eec83ba1252e3eb129 Mon Sep 17 00:00:00 2001 From: justanothercatgirl Date: Mon, 31 Mar 2025 15:44:36 +0300 Subject: Early working state. TODO: * /api/linkdel, /api/linkget * finish website --- Makefile | 11 +-- favicon.ico | 0 include/sql.h | 44 +++++++++++- jals.db | Bin 0 -> 16384 bytes src/common.c | 6 +- src/endpoints.c | 121 +++++++------------------------- src/main.c | 129 +++++++++++++++++++++-------------- src/sql.c | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- www/docs.html | 12 ++-- www/index.html | 5 +- 10 files changed, 372 insertions(+), 164 deletions(-) create mode 100644 favicon.ico create mode 100644 jals.db diff --git a/Makefile b/Makefile index 3ba5288..372caed 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ # Makefile GPERF = gperf -CC = cc +CC = gcc -CFLAGS += -Ic_headers/include -Iinclude -ggdb +CFLAGS += -Ic_headers/include -Iinclude -ggdb -Wall -Wextra -DDEFAULT_ENDPOINTS_PATH=\"./endpoints.so\" BLDDIR ?= build all: $(BLDDIR) endpoints.so main @@ -19,6 +19,9 @@ $(BLDDIR)/endpoints.o: src/endpoints.c $(BLDDIR)/rename.ld $(BLDDIR) # PIC here is important $(CC) -fPIC -c $< $(CFLAGS) -o $@ +$(BLDDIR)/sql.o: src/sql.c + $(CC) -fPIC -c $< $(CFLAGS) -o $@ + $(BLDDIR)/rename.ld: $(BLDDIR)/endpoints.o $(BLDDIR) # Looks terrible # All it does is generating ld script that creates aliases for functions @@ -26,8 +29,8 @@ $(BLDDIR)/rename.ld: $(BLDDIR)/endpoints.o $(BLDDIR) @readelf --syms --wide $(BLDDIR)/endpoints.o | awk '/FUNC/ && /GLOBAL/ && /ENDP/ {old = $$8; gsub(/_/, "/", $$8); gsub(/ENDP/, "", $$8); printf "\t%s = %s;\n", $$8, old }' >> $@ echo } >> $@ -endpoints.so: $(BLDDIR)/endpoints.o $(BLDDIR)/common.o $(BLDDIR)/mime.o - $(CC) -shared -fPIE -Wl,$(BLDDIR)/rename.ld $^ -lmicrohttpd -o $@ +endpoints.so: $(BLDDIR)/endpoints.o $(BLDDIR)/common.o $(BLDDIR)/mime.o $(BLDDIR)/sql.o + $(CC) -shared -fPIC -Wl,$(BLDDIR)/rename.ld $(CFLAGS) $^ -lmicrohttpd -lsqlite3 -o $@ # main diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/include/sql.h b/include/sql.h index e14167b..a90fde6 100644 --- a/include/sql.h +++ b/include/sql.h @@ -1,6 +1,48 @@ #ifndef SQL_H #define SQL_H +#include +#include -extern const char *INIT_SQL, *INSERT_SQL, *DELETE_SQL; +struct slidid { sqlite3_int64 il, in, res; }; +extern struct sql_state { + sqlite3 *db; +} state; +enum statements_index { + STMTIDX_INIT = 0, + STMTIDX_INSERT, + STMTIDX_DELETE, + STMTIDX_SELECT, + STMTIDX_RESET, + STMTIDX_GETN, + STMTIDX_SIZE, +}; + +enum dberror { + DBERROR_SUCCESS = 0, + DBERROR_ARGS, + DBERROR_INVAL, + DBERROR_OCCUPIED, + DBERROR_SERVER, + DBERROR_TOOBIG, + DBERROR_UNKNOWN, +}; + +extern struct sqlite3_stmt *statements[STMTIDX_SIZE]; + +void sqlite_init(const char *db); +void sqlite_deinit(void); + +enum dberror db_add_url(const char *url, const char *path, char **out); +bool db_del_url(const char *path); +// dong: output parameter +char *db_get_info(const char *path, long *dong); +// server should use this one. Memory must be freed. +char *db_get_url(const char *path); + +static char *itou(register unsigned long i, register char *buf); +static unsigned long utoi(const char *s); + +const char *db_error(enum dberror e); +int db_error_status(enum dberror e); #endif diff --git a/jals.db b/jals.db new file mode 100644 index 0000000..fdd26d0 Binary files /dev/null and b/jals.db differ diff --git a/src/common.c b/src/common.c index 7d8d836..f4e6cdb 100644 --- a/src/common.c +++ b/src/common.c @@ -17,14 +17,15 @@ char *duplicate(const char* x) { // either get a file in a directory %URL or www/%URL.html struct MHD_Response *get_from_file(const char* path) { int fd = open(path, O_RDONLY); + char *wwwdx = NULL; if (fd < 0) { size_t psz = strlen(path); - char *wwwdx = malloc(psz + 4 + 5 + 1); + wwwdx = malloc(psz + 4 + 5 + 1); strcpy(wwwdx, "www/"); strcpy(wwwdx + 4, path); strcpy(wwwdx + 4 + psz, ".html"); fd = open(wwwdx, O_RDONLY); - free(wwwdx); + path = wwwdx; if (fd < 0) return NULL; } struct stat st; @@ -36,5 +37,6 @@ struct MHD_Response *get_from_file(const char* path) { if (lastdot++) MHD_add_response_header(res, "Content-Type", mimetype_get(path + lastdot, len - lastdot)); LDEBUGF("mimetype = %s\n", mimetype_get(path + lastdot, len - lastdot)); + if (wwwdx) free(wwwdx); return res; } diff --git a/src/endpoints.c b/src/endpoints.c index 990ef29..8d1c05f 100644 --- a/src/endpoints.c +++ b/src/endpoints.c @@ -8,18 +8,18 @@ #include + +#include #define CONTAINER_IMPLEMENTATION #define JACSON_IMPLEMENTATION #define JACSON_EXPORT_RSNPRINTF #include - -#define COMMON_IMPLEMENTATION -#include +#include "common.h" +#include "sql.h" + const char *const JSON_ERROR = "{\"error\":\"%s\"}"; const char *const HTML_ERROR = "Error: %s"; -const char *const JSON_SERVER_ERROR = "{\"error\":\"%s\",\"what\":\"%s\"}"; -const char *const HTML_SERVER_ERROR = "Error: %s
Server error message:

%s

"; const char *const INSUFFICIENT_ARGUMENTS = "insufficient arguments"; const char *const INVALID_ARGUMENTS = "invalid arguments"; const char *const OCCUPIED = "occupied"; @@ -36,11 +36,11 @@ struct global_args { }; struct fmt_const { - const char *error, *server_error, *content_type; + const char *error, *content_type; }; -static struct fmt_const fmts[] = { - [GA_FMT_JSON] = {JSON_ERROR, JSON_SERVER_ERROR, "application/json"}, - [GA_FMT_HTML] = {HTML_ERROR, HTML_SERVER_ERROR, "text/html"} +static const struct fmt_const fmts[] = { + [GA_FMT_JSON] = {JSON_ERROR,"application/json"}, + [GA_FMT_HTML] = {HTML_ERROR,"text/html"} }; // string builder @@ -56,12 +56,6 @@ size_t sbprintf(struct sb* s, const char* fmt, ...) { return s->ln += __jacson_vrsnprintf(&s->str, &s->sz, s->ln, fmt, l); } -enum MHD_Result arg_builder (void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { - struct sb *html = cls; - html->ln += __jacson_rsnprintf(&html->str, &html->sz, html->ln, "
  • %s: %s

  • ", key, value); - return MHD_YES; -} - struct global_args parse_global_args(struct MHD_Connection *connection) { const char* key, *val; struct global_args ret = { .format = GA_FMT_JSON }; @@ -71,82 +65,16 @@ struct global_args parse_global_args(struct MHD_Connection *connection) { return ret; } -char *db_add_url(const char *url, const char *path) { - return "test"; -} - -bool db_del_url(const char *path) { - return false; -} - -// dong: output parameter -char *db_get_info(const char *path, long *dong) { - *dong = 0; - return NULL; -} - -char *db_get_url(const char *path) { - return NULL; -} - -// alphabet: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ -// returns 0 if no conversion is available -static inline char itoc(register unsigned long l) { -#ifdef __GNUC__ - char add[256] = { - [0 ... 9] = '0', - [10 ... 35] = 'a' - 10, - [36 ... 61] = 'A' - 10 - 26, - }; - return (l < 62) * (l + add[l]); -#else - if (l < 10) return '0' + l; - if (l < 10 + 26) return 'a' + l - 10; - if (l < 10 + 2 * 26) return 'A' + l - 10 - 26; - return '\0'; -#endif -} -// returns negative number if conversion not available -static inline unsigned long ctoi(register char c) { -#ifdef __GNUC__ - char sub[127] = { - [0 ... '0'-1] = 128, - ['0' ... '9'] = '0', - ['A' ... 'Z'] = 'A' - 26 - 10, - ['a' ... 'z'] = 'a' - 10, - }; - return c - sub[c]; -#else - TODO; -#endif -} -// buf must be at least 12 characters (11 for strnig and 1 for termination byte) -// returns a pointer from which the string starts -static char *itou(register unsigned long i, register char *buf) { - register ssize_t ind = 11; - buf[ind] = '\0'; - buf[ind - 1] = '0'; - while (ind >= 0 && i) { - buf[--ind] = itoc(i % 62); - i /= 62; - } - return buf + ind; -} -static unsigned long utoi(const char *s) { - register unsigned long result = 0; - while (*s) { - result *= 62; - result += ctoi(*s); - ++s; - } - return result; -} - // index, path: / struct MHD_Response *ENDP_(struct MHD_Connection* connection, int *status) { return get_from_file("www/index.html"); } +enum MHD_Result arg_builder (void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { + struct sb *html = cls; + html->ln += __jacson_rsnprintf(&html->str, &html->sz, html->ln, "
  • %s: %s

  • ", key, value); + return MHD_YES; +} // path: /getargs struct MHD_Response *ENDP_getargs(struct MHD_Connection *connection, int *status) { struct sb html = {calloc(64, 1), 63, 0}; @@ -155,31 +83,34 @@ struct MHD_Response *ENDP_getargs(struct MHD_Connection *connection, int *status MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, &arg_builder, &html); html.ln += __jacson_rsnprintf(&html.str, &html.sz, html.ln, ""); const char *val = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "test"); - if (val) fprintf(stderr, "test = %s\n", val); - fprintf(stderr, "test pointer = %p", val); + if (val) LDEBUGVF(stderr, "test = %s\n", val); + LDEBUGVF(stderr, "test pointer = %p", val); return MHD_create_response_from_buffer(html.ln, html.str, MHD_RESPMEM_MUST_FREE); } // API, path: /api/linkadd struct MHD_Response *ENDP_api_linkadd(struct MHD_Connection *connection, int *status) { const char *url = NULL, *path = NULL; - char *newurl; + char newurl[12] = {0}, *newurlp = newurl; struct sb resp = {malloc(64), 63, 0}; struct global_args glob = parse_global_args(connection); struct MHD_Response *response; if ((url = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "url")) == NULL || *url == '\0') { - *status = MHD_HTTP_BAD_REQUEST; + *status = db_error_status(DBERROR_ARGS); sbprintf(&resp, fmts[glob.format].error, INSUFFICIENT_ARGUMENTS); goto exit; } path = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "try"); - if ((newurl = db_add_url(url, path)) == NULL) { - *status = MHD_HTTP_INTERNAL_SERVER_ERROR; - sbprintf(&resp, fmts[glob.format].server_error, SERVER_ERRORMSG, "database write failed"); + if (*path == '\0') path = NULL; + enum dberror err; + if ((err = db_add_url(url, path, &newurlp)) != DBERROR_SUCCESS) { + LWARNVF("Database write failed with error %i. Sql error: %s", err, sqlite3_errmsg(state.db)); + *status = db_error_status(err); + sbprintf(&resp, fmts[glob.format].error, db_error(err)); goto exit; } - if (glob.format == GA_FMT_JSON) sbprintf(&resp, "{\"url\":\"%s\"}", newurl); - else sbprintf(&resp, "%sYour URL: /%s%s", HTML_PROLOGUE, newurl, newurl, HTML_EPILOGUE); + if (glob.format == GA_FMT_JSON) sbprintf(&resp, "{\"url\":\"%s\"}", newurlp); + else sbprintf(&resp, "%sYour URL: /%s%s", HTML_PROLOGUE, newurlp, newurlp, HTML_EPILOGUE); exit: response = MHD_create_response_from_buffer(resp.ln, resp.str, MHD_RESPMEM_MUST_FREE); MHD_add_response_header(response, "Content-Type", fmts[glob.format].content_type); diff --git a/src/main.c b/src/main.c index 36df30a..431a9a7 100644 --- a/src/main.c +++ b/src/main.c @@ -1,78 +1,62 @@ +/* stdlib */ +#include #include #include #include +/* unix */ #include - +#include +/* dynamic */ #include #include - +/* my libs */ #include #define COMMON_IMPLEMENTATION #include -/* sqlite leastunused implementation */ -struct slidid { sqlite3_int64 il, in, res; }; -void step_leastunused(sqlite3_context* ctx, int argc, sqlite3_value** argv) { - struct slidid *idx = sqlite3_aggregate_context(ctx, sizeof *idx); - if (idx->res) return; - idx->in = sqlite3_value_int64(argv[0]); - if (idx->il + 1 != idx->in) idx->res = idx->il + 1; - idx->il = idx->in; -} -void final_leastunused(sqlite3_context* ctx) { - struct slidid *idx = sqlite3_aggregate_context(ctx, 0); - sqlite3_result_int(ctx, idx->res); -} +#ifndef DEFAULT_ENDPOINTS_PATH +# define DEFAULT_ENDPOINTS_PATH "./endpoints.so" +#endif +#ifndef DEFAULT_DATABASE_PATH +# define DEFAULT_DATABASE_PATH "./jals.db" +#endif -sqlite3 *sqlite_init(const char *db) { - sqlite3 *ret; - sqlite3_open_v2(db, &ret, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL); - if (ret == NULL) LCRIT(1, "could not open sqlite3 database"); - sqlite3_create_function(ret, "leastunused", 1, SQLITE_UTF8, NULL, NULL, step_leastunused, final_leastunused); - return ret; -} +struct connarg { + void* dylib; + query_func query; + void (*constructor)(const char*); + void (*destructor)(void); +} arg = {NULL, NULL, NULL, NULL}; -enum MHD_Result process_connection(void *dylib, struct MHD_Connection *connection, const char *url, +volatile char reload_library = 0; + +enum MHD_Result process_connection(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **req_cls) { LDEBUGF("\nurl: %s\nmethod:%s\nversion:%s\nupload_data:%s\nsize:%zu\n", url, method, version, upload_data, *upload_data_size); + struct connarg *arg = cls; /* Load function with the name of the url from shared library */ - endpoint handler = dlsym(dylib, url); - query_func query = dlsym(dylib, "db_get_url"); + endpoint handler = dlsym(arg->dylib, url); struct MHD_Response *res; int status = MHD_HTTP_OK; const char *response; char *newurl; - if (query == NULL) { - response = "Temporary server error: could not open database"; - res = MHD_create_response_from_buffer(strlen(response), (void*)response, MHD_RESPMEM_PERSISTENT); - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto response; - } if (strcasecmp(method, "GET")) { status = MHD_HTTP_METHOD_NOT_ALLOWED; res = MHD_create_response_from_buffer(18, "Method not allowed", MHD_RESPMEM_PERSISTENT); - goto response; - } - if (handler != NULL) { + } else if (handler != NULL) { /* endpoint access */ LINFOF("Accessed endpoint at url %s", url); res = handler(connection, &status); - } else if ((newurl = query(url)) != NULL) { + } else if ((newurl = arg->query(url + 1)) != NULL) { LINFOF("Redirect from %s to %s", url, newurl); - /* if (newurl == NULL) { */ - /* free(newurl); */ - /* response = "Page does not exist"; */ - /* res = MHD_create_response_from_buffer(strlen(response), (void*)response, MHD_RESPMEM_PERSISTENT); */ - /* status = MHD_HTTP_NOT_FOUND; */ - /* goto response; */ - /* } */ response = "Recirecting..."; res = MHD_create_response_from_buffer(strlen(response), (void*)response, MHD_RESPMEM_PERSISTENT); MHD_add_response_header(res, "Location", newurl); status = MHD_HTTP_FOUND; + free(newurl); } else if ((res = get_from_file(url + 1)) != NULL) { /* fallback file access */ LINFOF("Accessed file at url %s", url); @@ -83,22 +67,67 @@ enum MHD_Result process_connection(void *dylib, struct MHD_Connection *connectio res = MHD_create_response_from_buffer(strlen(error), (void*)error, MHD_RESPMEM_PERSISTENT); status = MHD_HTTP_NOT_FOUND; } -response: MHD_queue_response(connection, status, res); MHD_destroy_response(res); return MHD_YES; } +/* Because calling dlclose and dlopen from here is undefined */ +void sigusr1_handler(int i) { + reload_library = 1; +} -int main(int argc, char *argv[]){ - void* dylib = dlopen("./endpoints.so", RTLD_NOW); - if (!dylib) LCRITF(1, "Could not open dynamic library: %s\n", dlerror()); - struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_POLL | MHD_USE_INTERNAL_POLLING_THREAD, 8080, NULL, NULL, process_connection, dylib, MHD_OPTION_END); - if (daemon == NULL) LCRITV(1, "could not initialize daemon"); +void reload_dylib(const char *so_path, const char *db_path) { + LINFOF("Reloading dymanic library: %s", so_path); + if (arg.destructor) arg.destructor(); + if (arg.dylib != NULL) dlclose(arg.dylib); + arg.dylib = dlopen(so_path, RTLD_LAZY); + if (!arg.dylib) LCRITVF(1, "Could not open dynamic library: %s\n", dlerror()); + arg.query = dlsym(arg.dylib, "db_get_url"); + if (!arg.query) LCRITVF(1, "Could not load `db_get_url` from shared library: %s", dlerror()); + arg.constructor = dlsym(arg.dylib, "sqlite_init"); + if (!arg.constructor) LCRITVF(1, "Could not load `sqlite_init` from shared library: %s", dlerror()); + arg.constructor(db_path); + arg.destructor = dlsym(arg.dylib, "sqlite_deinit"); + if (!arg.destructor) LCRITVF(1, "Could not load `sqlite_deinit` from shared library: %s", dlerror()); +} + +struct MHD_Daemon *init(sigset_t *sigset, const char *dlpath, const char *dbpath) { + sigset_t newmask; + errno = 0; + reload_dylib(dlpath, dbpath); + sigemptyset(&newmask); + sigaddset(&newmask, SIGUSR1); + + struct sigaction handler = {.sa_handler = sigusr1_handler}; + if (sigaction(SIGUSR1, &handler, NULL) < 0) LFAILV("Setting signal handler for SIGUSR1"); + + sigset_t oldmask; + pthread_sigmask(SIG_BLOCK, &newmask, sigset); /* To prevent MHD from recieving the signal */ + struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_ERROR_LOG | MHD_USE_POLL | MHD_USE_INTERNAL_POLLING_THREAD, 8080, NULL, NULL, process_connection, &arg, MHD_OPTION_END); + pthread_sigmask(SIG_UNBLOCK, &newmask, NULL); + + if (daemon == NULL) LFAILV("could not initialize daemon"); + + return daemon; +} + +int main(int argc, char *argv[]) { + sigset_t sig; + const char *dlpath = DEFAULT_ENDPOINTS_PATH; + const char *dbpath = DEFAULT_DATABASE_PATH; + struct MHD_Daemon *daemon = init(&sig, dlpath, dbpath); + /* while (reload_library == 0) { */ + /* sigsuspend(&sig); */ + /* if (reload_library == 1) { */ + /* reload_dylib(dlpath, dbpath); */ + /* reload_library = 0; */ + /* } */ + /* } */ getchar(); - - dlclose(dylib); + if (arg.destructor) arg.destructor(); + dlclose(arg.dylib); MHD_stop_daemon(daemon); return 0; } diff --git a/src/sql.c b/src/sql.c index 6054745..6b66ce7 100644 --- a/src/sql.c +++ b/src/sql.c @@ -1,13 +1,207 @@ #include "sql.h" +#include "log.h" +#include +#include +#include +#include + +struct sqlite3_stmt *statements[STMTIDX_SIZE] = {NULL}; + +struct sql_state state = {NULL}; const char *INIT_SQL = - "CREATE TABLE IF NOT EXISTS links (" - "id INTEGER PRIMARY KEY," - "url STRING NOT NULL," - "created INTEGER NOT NULL" + "CREATE TABLE IF NOT EXISTS links ( " + "id INTEGER PRIMARY KEY, " + "url STRING NOT NULL, " + "created INTEGER NOT NULL " ");", *INSERT_SQL = - "INSERT INTO links (url,created)" - "VALUES (?1, ?2);", -*DELETE_SQL = "DELETE FROM links WHERE id = ?1;"; + "INSERT INTO links (id,url,created) " + "VALUES (?1, ?2, unixepoch());", +*DELETE_SQL = + "DELETE FROM links " + "WHERE id = ?1;", +*SELECT_SQL = + "SELECT url,created FROM links " + "WHERE id = ?1;", +*RESET_SQL = "DROP TABLE links;", +*GETN_SQL = "SELECT leastunused(id) FROM links;"; + + +/* sqlite leastunused implementation */ +void step_leastunused(sqlite3_context* ctx, int argc, sqlite3_value** argv) { + struct slidid *idx = sqlite3_aggregate_context(ctx, sizeof *idx); + if (idx->res) return; + idx->in = sqlite3_value_int64(argv[0]); + if (idx->in <= 0) return; + if (idx->il + 1 != idx->in) idx->res = idx->il + 1; + idx->il = idx->in; +} +void final_leastunused(sqlite3_context* ctx) { + struct slidid *idx = sqlite3_aggregate_context(ctx, 0); + if (idx == NULL) { + sqlite3_result_int64(ctx, 1); + return; + } + if (idx->res == 0) { + sqlite3_result_int64(ctx, idx->in + 1); + return; + } + sqlite3_result_int64(ctx, idx->res); +} + +void sqlite_init(const char *db) { + sqlite3 *ret; + sqlite3_open_v2(db, &ret, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL); + if (ret == NULL) LCRIT(1, "could not open sqlite3 database"); + + sqlite3_prepare(ret, INIT_SQL, -1, statements +STMTIDX_INIT, NULL); + if (SQLITE_DONE != sqlite3_step(statements[STMTIDX_INIT])) LERRVF("Error initializing database: %s", sqlite3_errmsg(ret)); + sqlite3_reset(statements[STMTIDX_INIT]); + + sqlite3_create_function(ret, "leastunused", 1, SQLITE_UTF8, NULL, NULL, step_leastunused, final_leastunused); + + const char *queries[STMTIDX_SIZE] = {INIT_SQL, INSERT_SQL, DELETE_SQL, SELECT_SQL, RESET_SQL, GETN_SQL}; + for (int i = 1; i < STMTIDX_SIZE; ++i) sqlite3_prepare(ret, queries[i], -1, statements + i, NULL); + state.db = ret; +} + +// alphabet: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ +// returns 0 if no conversion is available +static inline char itoc(register unsigned long l) { +#ifdef __GNUC__ + char add[256] = { + [0 ... 9] = '0', + [10 ... 35] = 'a' - 10, + [36 ... 61] = 'A' - 10 - 26, + }; + return (l < 62) * (l + add[l]); +#else + if (l < 10) return '0' + l; + if (l < 10 + 26) return 'a' + l - 10; + if (l < 10 + 2 * 26) return 'A' + l - 10 - 26; + return '\0'; +#endif +} +// returns negative number if conversion not available +static inline unsigned long ctoi(register char c) { +#ifdef __GNUC__ + char sub[127] = { + [0 ... '0'-1] = 128, + ['0' ... '9'] = '0', + ['A' ... 'Z'] = 'A' - 26 - 10, + ['a' ... 'z'] = 'a' - 10, + }; + return c - sub[c]; +#else + TODO; +#endif +} +// buf must be at least 12 characters (11 for strnig and 1 for termination byte) +// returns a pointer from which the string starts +static char *itou(register unsigned long i, register char *buf) { + register ssize_t ind = 11; + buf[ind] = '\0'; + buf[ind - 1] = '0'; + while (ind >= 0 && i) { + buf[--ind] = itoc(i % 62); + i /= 62; + } + return buf + ind; +} +static unsigned long utoi(const char *s) { + register unsigned long result = 0; + while (*s) { + unsigned long ctoires = ctoi(*s); + // overflow check + if ((ULONG_MAX - ctoires) / 62 <= result) return 0; + result *= 62; + result += ctoires; + ++s; + } + return result; +} + +void sqlite_deinit(void) { + for (size_t i = 0; i < STMTIDX_SIZE; ++i) + sqlite3_finalize(statements[i]); + sqlite3_close(state.db); +} + +enum dberror db_add_url(const char *url, const char *path, char **out) { + sqlite3_int64 index; + if (path) { + for(const char *i = path; *i; ++i) + if (!isalpha(*i) && !isdigit(*i)) return DBERROR_INVAL; + index = utoi(path); + if (index == 0) return DBERROR_TOOBIG; + } else { + if (SQLITE_ROW != sqlite3_step(statements[STMTIDX_GETN])) { + sqlite3_reset(statements[STMTIDX_GETN]); + return DBERROR_SERVER; + } + index = sqlite3_column_int64(statements[STMTIDX_GETN], 0); + sqlite3_reset(statements[STMTIDX_GETN]); + } + if (SQLITE_OK != sqlite3_bind_int64(statements[STMTIDX_INSERT], 1, index)) return DBERROR_SERVER; + if (SQLITE_OK != sqlite3_bind_text(statements[STMTIDX_INSERT], 2, url, -1, SQLITE_TRANSIENT)) return DBERROR_SERVER; + + *out = itou(index, *out); + int res; + if (SQLITE_DONE != (res = sqlite3_step(statements[STMTIDX_INSERT]))) { + LWARNVF("sqlite insert failed with error %i", res); + *out = NULL; + sqlite3_reset(statements[STMTIDX_INSERT]); + return DBERROR_OCCUPIED; + } + sqlite3_reset(statements[STMTIDX_INSERT]); + return DBERROR_SUCCESS; +} + +bool db_del_url(const char *path) { + return false; // lick my balls +} + +char *db_get_info(const char *path, long *dong) { + *dong = 0; + return NULL; +} + +char *db_get_url(const char *path) { + char *ret = NULL; + sqlite3_int64 index = utoi(path); + if (SQLITE_OK != sqlite3_bind_int64(statements[STMTIDX_SELECT], 1, index)) return NULL; + if (SQLITE_ROW == sqlite3_step(statements[STMTIDX_SELECT])) { + const unsigned char *c = sqlite3_column_text(statements[STMTIDX_SELECT], 0); + size_t sz = sqlite3_column_bytes(statements[STMTIDX_SELECT], 0); + ret = malloc(sz + 1); + memcpy(ret, c, sz); + ret[sz] = '\0'; + } + sqlite3_reset(statements[STMTIDX_SELECT]); + return ret; +} + +const char *db_error(enum dberror e) { + switch (e) { + case DBERROR_SUCCESS: return "success"; + case DBERROR_ARGS: return "insufficient arguments"; + case DBERROR_INVAL: return "invalid arguments"; + case DBERROR_OCCUPIED: return "occupied"; + case DBERROR_SERVER: return "server error"; + case DBERROR_TOOBIG: return "path too long"; + case DBERROR_UNKNOWN: return "unknown"; + } +} +int db_error_status(enum dberror e) { + switch (e) { + case DBERROR_SUCCESS: return MHD_HTTP_OK; + case DBERROR_ARGS: return MHD_HTTP_BAD_REQUEST; + case DBERROR_INVAL: return MHD_HTTP_BAD_REQUEST; + case DBERROR_OCCUPIED: return MHD_HTTP_BAD_REQUEST; + case DBERROR_SERVER: return MHD_HTTP_INTERNAL_SERVER_ERROR; + case DBERROR_TOOBIG: return MHD_HTTP_URI_TOO_LONG; + case DBERROR_UNKNOWN: return MHD_HTTP_INTERNAL_SERVER_ERROR; + } +} diff --git a/www/docs.html b/www/docs.html index a2fa287..8e892df 100644 --- a/www/docs.html +++ b/www/docs.html @@ -20,7 +20,7 @@

    API reference

    The API follows this convention: everything is done through GET requests, client supplies all necessary parameters in URLencoded format. Whatever server returns depends on parameters; it may be application/json (for programmable API) OR text/html (to render in HTML)

    -

    Example request: http://ln.twistea.su/api/linkadd?url=https%3A%2F%2Fexample.com&format=html.
    +

    Example request: http://ln.twistea.su/api/linkadd?url=https%3A%2F%2Fexample.com&format=html.
    This request may return something like: <a href="http://server.com/DEADF00D">your link: DEADF00D</a>

    Note: server may return json string like null or true: according to rfc-8259, this is valid json and clients have to handle this accordingly.

    @@ -30,8 +30,9 @@
  • invalid arguments: argument's type/value does not match with what server expects.
  • occupied: the path at which the link was attempted to be created is occupied already.
  • server error: something went wrong on the server. Will include additional json field "what" with error message.
  • +
  • path too long: the path is too long.
  • unknown: server does not know what the hell went wrong
  • - Plese note that the HTTP return status for malformed request may be BAD_REQUEST (400) or INTERNAL_SERVER_ERROR (500)

    + Plese note that the HTTP return status for malformed request may be BAD_REQUEST (400), URI_TOO_LONG (414), NOT_FOUND (404) or INTERNAL_SERVER_ERROR (500)

    @@ -82,7 +83,10 @@ - @@ -95,7 +99,7 @@ - diff --git a/www/index.html b/www/index.html index a2af744..ed48fc5 100644 --- a/www/index.html +++ b/www/index.html @@ -12,7 +12,8 @@ JAC's link shortener -

    JAC's link shortener

    +
    +

    Just another link shortener

    Try it out!

    @@ -28,5 +29,7 @@

    Result:

    Table 1. Global parameters (applicable to all endpoints)
    try?string: PATH to try to put link under
    +
    string: PATH to try to put link under
    + shoud match regex [a-zA-Z0-9]+, length ≤ 10
    + may work with length = 11, but not always +
    (if not occupied, WITHOUT https://server.com/)
    /api/linkget path string: PATH to get link info from{"url": "original url", created: int unix_timestamp}
    +
    {"url": "original url", created: int unix_timestamp}
    OR null