aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile11
-rw-r--r--favicon.ico0
-rw-r--r--include/sql.h44
-rw-r--r--jals.dbbin0 -> 16384 bytes
-rw-r--r--src/common.c6
-rw-r--r--src/endpoints.c121
-rw-r--r--src/main.c129
-rw-r--r--src/sql.c208
-rw-r--r--www/docs.html12
-rw-r--r--www/index.html5
10 files changed, 372 insertions, 164 deletions
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
--- /dev/null
+++ b/favicon.ico
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 <stdbool.h>
+#include <sqlite3.h>
-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
--- /dev/null
+++ b/jals.db
Binary files 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 <microhttpd.h>
+
+#include <log.h>
#define CONTAINER_IMPLEMENTATION
#define JACSON_IMPLEMENTATION
#define JACSON_EXPORT_RSNPRINTF
#include <jacson.h>
-
-#define COMMON_IMPLEMENTATION
-#include <common.h>
+#include "common.h"
+#include "sql.h"
+
const char *const JSON_ERROR = "{\"error\":\"%s\"}";
const char *const HTML_ERROR = "<!DOCTYPE html><html><body>Error: %s</body></html>";
-const char *const JSON_SERVER_ERROR = "{\"error\":\"%s\",\"what\":\"%s\"}";
-const char *const HTML_SERVER_ERROR = "<!DOCTYPE html><html><body>Error: %s<br>Server error message: <p style=\"font-style: monospace\">%s</p></body></html>";
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, "<li><p style=\"font-style:monospace;\">%s: %s</p></li>", 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, "<li><p style=\"font-style:monospace;\">%s: %s</p></li>", 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, "</ol></body></html>");
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: <a href\"%s\">/%s</a>%s", HTML_PROLOGUE, newurl, newurl, HTML_EPILOGUE);
+ if (glob.format == GA_FMT_JSON) sbprintf(&resp, "{\"url\":\"%s\"}", newurlp);
+ else sbprintf(&resp, "%sYour URL: <a href=\"/%s\">/%s</a>%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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+/* unix */
#include <dlfcn.h>
-
+#include <signal.h>
+/* dynamic */
#include <microhttpd.h>
#include <sqlite3.h>
-
+/* my libs */
#include <log.h>
#define COMMON_IMPLEMENTATION
#include <common.h>
-/* 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 = "<!DOCTYPE html><html><body>Temporary server error: could not open database</body></html>";
- 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 = "<!DOCTYPE html><html><body>Page does not exist</body></html>"; */
- /* res = MHD_create_response_from_buffer(strlen(response), (void*)response, MHD_RESPMEM_PERSISTENT); */
- /* status = MHD_HTTP_NOT_FOUND; */
- /* goto response; */
- /* } */
response = "<!DOCTYPE html><html><body>Recirecting...</body></html>";
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 <ctype.h>
+#include <limits.h>
+#include <microhttpd.h>
+#include <string.h>
+
+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 @@
<h3>API reference</h3>
<p>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)</p>
- <p>Example request: <span>http://ln.twistea.su/api/linkadd?url=https%3A%2F%2Fexample.com&amp;format=html</span>.<br>
+ <p>Example request: <span>http://ln.twistea.su/api/linkadd?url=https%3A%2F%2Fexample.com&amp;format=html</span>.<br/>
This request may return something like: <span>&lt;a href="http://server.com/DEADF00D"&gt;your link: DEADF00D&lt;/a&gt;</span></p>
<p>Note: server may return json string like <span>null</span> or <span>true</span>: according to
<a href="https://www.rfc-editor.org/rfc/rfc8259#section-13">rfc-8259</a>, this is valid json and clients have to handle this accordingly.</p>
@@ -30,8 +30,9 @@
<li><span>invalid arguments</span>: argument's type/value does not match with what server expects.</li>
<li><span>occupied</span>: the path at which the link was attempted to be created is occupied already.</li>
<li><span>server error</span>: something went wrong on the server. Will include additional json field "what" with error message.</li>
+ <li><span>path too long</span>: the path is too long.</li>
<li><span>unknown</span>: server does not know what the hell went wrong</li>
- </ul>Plese note that the HTTP return status for malformed request may be BAD_REQUEST (400) or INTERNAL_SERVER_ERROR (500)</p>
+ </ul>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)</p>
<table>
<thead>
<caption>Table 1. Global parameters (applicable to all endpoints)</caption>
@@ -82,7 +83,10 @@
</tr>
<tr>
<td>try?</td>
- <td class="uns">string: <span>PATH</span> to try to put link under <br>
+ <td class="uns">string: <span>PATH</span> to try to put link under <br/>
+ shoud match regex <span>[a-zA-Z0-9]+</span>, length &leq; 10 <br/>
+ may work with length = 11, but not always
+ <br/>
(if not occupied, WITHOUT <span>https://server.com/</span>)</td>
</tr>
<tr>
@@ -95,7 +99,7 @@
<td>/api/linkget</td>
<td>path</td>
<td>string: PATH to get link info from</td>
- <td>{"url": "original url", created: int unix_timestamp}<br>
+ <td>{"url": "original url", created: int unix_timestamp}<br/>
OR null
</td>
</tr>
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 @@
<title>JAC's link shortener</title>
</head>
<body>
- <h1>JAC's link shortener</h1>
+ <div>
+ <h1>Just another link shortener</h1>
<p>Try it out!</p>
<form action="/api/linkadd" target="result">
<div>
@@ -28,5 +29,7 @@
</form>
<h3>Result:</h3>
<iframe name="result"scrolling="no"/>
+ </div>
+ <p> For API documentation, go to <a href="/docs">documentation page</a></p>
</body>
</html>