# HG changeset patch # User David Demelier # Date 1678995959 -3600 # Node ID 9bfe5ce3cc45c0f1806d45d187335e422b0b41da # Parent fe78b16c694d6c4bbd3a742ffc17e27695b2d179 pasterd: rework themes diff -r fe78b16c694d -r 9bfe5ce3cc45 .hgignore --- a/.hgignore Thu Mar 16 15:05:26 2023 +0100 +++ b/.hgignore Thu Mar 16 20:45:59 2023 +0100 @@ -18,6 +18,7 @@ ^config\.mk$ ^extern/bcc/bcc$ ^html/.*\.h$ +^sql/.*\.h$ ^libpaster\.a$ # Executables. diff -r fe78b16c694d -r 9bfe5ce3cc45 GNUmakefile --- a/GNUmakefile Thu Mar 16 15:05:26 2023 +0100 +++ b/GNUmakefile Thu Mar 16 20:45:59 2023 +0100 @@ -58,7 +58,6 @@ LIBPASTER_SRCS += page-search.c LIBPASTER_SRCS += page-static.c LIBPASTER_SRCS += page.c -LIBPASTER_SRCS += paste.c LIBPASTER_SRCS += util.c LIBPASTER_OBJS := $(LIBPASTER_SRCS:.c=.o) LIBPASTER_DEPS := $(LIBPASTER_SRCS:.c=.d) @@ -73,6 +72,14 @@ LIBPASTER_HTML_SRCS += html/status.html LIBPASTER_HTML_OBJS := $(LIBPASTER_HTML_SRCS:.html=.h) +LIBPASTER_SQL_SRCS := sql/clear.sql +LIBPASTER_SQL_SRCS += sql/get.sql +LIBPASTER_SQL_SRCS += sql/init.sql +LIBPASTER_SQL_SRCS += sql/insert.sql +LIBPASTER_SQL_SRCS += sql/recents.sql +LIBPASTER_SQL_SRCS += sql/search.sql +LIBPASTER_SQL_OBJS := $(LIBPASTER_SQL_SRCS:.sql=.h) + TESTS_SRCS := tests/test-database.c TESTS_OBJS := $(TESTS_SRCS:.c=.o) TESTS := $(TESTS_SRCS:.c=) @@ -103,6 +110,9 @@ %.h: %.html $(BCC) -cs0 $< html_${ $@ +%.h: %.sql + $(BCC) -cs0 $< sql_${ $@ + %: %.sh $(SED) < $< > $@ @@ -110,7 +120,7 @@ $(AR) -rc $@ $^ $(LIBPASTER_HTML_OBJS): extern/bcc/bcc -$(LIBPASTER_SRCS): $(LIBPASTER_HTML_OBJS) +$(LIBPASTER_SRCS): $(LIBPASTER_HTML_OBJS) $(LIBPASTER_SQL_OBJS) $(LIBPASTER): $(LIBPASTER_OBJS) pasterd: private LDLIBS += $(KCGI_LIBS) $(JANSSON_LIBS) @@ -118,7 +128,7 @@ clean: rm -f extern/bcc/bcc extern/bcc/bcc.d - rm -f $(LIBPASTER) $(LIBPASTER_OBJS) $(LIBPASTER_DEPS) $(LIBPASTER_HTML_OBJS) + rm -f $(LIBPASTER) $(LIBPASTER_OBJS) $(LIBPASTER_DEPS) $(LIBPASTER_HTML_OBJS) $(LIBPASTER_SQL_OBJS) rm -f paster pasterd pasterd.d rm -f test.db $(TESTS_OBJS) diff -r fe78b16c694d -r 9bfe5ce3cc45 database.c --- a/database.c Thu Mar 16 15:05:26 2023 +0100 +++ b/database.c Thu Mar 16 20:45:59 2023 +0100 @@ -24,118 +24,43 @@ #include #include "database.h" +#include "json-util.h" #include "log.h" -#include "paste.h" #include "util.h" +#include "sql/clear.h" +#include "sql/get.h" +#include "sql/init.h" +#include "sql/insert.h" +#include "sql/recents.h" +#include "sql/search.h" + +#define ID_MAX (12 + 1) + static sqlite3 *db; -static const char *sql_init = - "BEGIN EXCLUSIVE TRANSACTION;\n" - "\n" - "CREATE TABLE IF NOT EXISTS paste(\n" - " uuid TEXT PRIMARY KEY,\n" - " title TEXT,\n" - " author TEXT,\n" - " language TEXT,\n" - " code TEXT,\n" - " date INT DEFAULT CURRENT_TIMESTAMP,\n" - " visible INTEGER DEFAULT 0,\n" - " duration INT\n" - ");\n" - "\n" - "END TRANSACTION"; - -static const char *sql_get = - "SELECT uuid\n" - " , title\n" - " , author\n" - " , language\n" - " , code\n" - " , strftime('%s', date)\n" - " , visible\n" - " , duration\n" - " FROM paste\n" - " WHERE uuid = ?"; - -static const char *sql_insert = - "INSERT INTO paste(\n" - " uuid,\n" - " title,\n" - " author,\n" - " language,\n" - " code,\n" - " visible,\n" - " duration\n" - ") VALUES (?, ?, ?, ?, ?, ?, ?)"; - -static const char *sql_recents = - "SELECT uuid\n" - " , title\n" - " , author\n" - " , language\n" - " , code\n" - " , strftime('%s', date) AS date\n" - " , visible\n" - " , duration\n" - " FROM paste\n" - " WHERE visible = 1\n" - " ORDER BY date DESC\n" - " LIMIT ?\n"; - -static const char *sql_clear = - "BEGIN EXCLUSIVE TRANSACTION;\n" - "\n" - "DELETE\n" - " FROM paste\n" - " WHERE strftime('%s', 'now') - strftime('%s', date) >= duration;" - "\n" - "END TRANSACTION"; - -static const char *sql_search = - "SELECT uuid\n" - " , title\n" - " , author\n" - " , language\n" - " , code\n" - " , strftime('%s', date) AS date\n" - " , visible\n" - " , duration\n" - " FROM paste\n" - " WHERE title like ?\n" - " AND author like ?\n" - " AND language like ?\n" - " AND visible = 1\n" - " ORDER BY date DESC\n" - " LIMIT ?\n"; - -/* sqlite3 use const unsigned char *. */ -static char * -dup(const unsigned char *s) +static inline json_t * +convert(sqlite3_stmt *stmt) { - return estrdup(s ? (const char *)(s) : ""); + return json_pack("{ss ss ss ss ss sI si si}", + "id", sqlite3_column_text(stmt, 0), + "title", sqlite3_column_text(stmt, 1), + "author", sqlite3_column_text(stmt, 2), + "language", sqlite3_column_text(stmt, 3), + "code", sqlite3_column_text(stmt, 4), + "timestamp", (json_int_t)sqlite3_column_int64(stmt, 5), + "visible", sqlite3_column_int(stmt, 6), + "duration", sqlite3_column_int(stmt, 7) + ); } -static void -convert(sqlite3_stmt *stmt, struct paste *paste) -{ - paste->id = dup(sqlite3_column_text(stmt, 0)); - paste->title = dup(sqlite3_column_text(stmt, 1)); - paste->author = dup(sqlite3_column_text(stmt, 2)); - paste->language = dup(sqlite3_column_text(stmt, 3)); - paste->code = dup(sqlite3_column_text(stmt, 4)); - paste->timestamp = sqlite3_column_int64(stmt, 5); - paste->visible = sqlite3_column_int(stmt, 6); - paste->duration = sqlite3_column_int64(stmt, 7); -} - -static bool +static int exists(const char *id) { assert(id); sqlite3_stmt *stmt = NULL; - bool ret = true; + int ret = 1; if (sqlite3_prepare(db, sql_get, -1, &stmt, NULL) == SQLITE_OK) { sqlite3_bind_text(stmt, 1, id, -1, NULL); @@ -147,24 +72,19 @@ } static const char * -create_id(void) +create_id(char *id) { static const char table[] = "abcdefghijklmnopqrstuvwxyz1234567890"; - static char id[12]; - for (int i = 0; i < sizeof (id); ++i) + for (int i = 0; i < ID_MAX; ++i) id[i] = table[rand() % (sizeof (table) - 1)]; - return id; + id[ID_MAX - 1] = 0; } -static bool -set_id(struct paste *paste) +static int +set_id(json_t *paste) { - assert(paste); - - paste->id = NULL; - /* * Avoid infinite loop, we only try to create a new id in 30 steps. * @@ -172,16 +92,21 @@ * not try to save with that id. */ int tries = 0; + char id[ID_MAX]; do { - free(paste->id); - paste->id = estrdup(create_id()); - } while (++tries < 30 && exists(paste->id)); + create_id(id); + } while (++tries < 30 && exists(id)); - return tries < 30; + if (tries >= 30) + return -1; + + json_object_set_new(paste, "id", json_string(id)); + + return 0; } -bool +int database_open(const char *path) { assert(path); @@ -190,7 +115,7 @@ if (sqlite3_open(path, &db) != SQLITE_OK) { log_warn("database: unable to open %s: %s", path, sqlite3_errmsg(db)); - return false; + return -1; } /* Wait for 30 seconds to lock the database. */ @@ -198,37 +123,34 @@ if (sqlite3_exec(db, sql_init, NULL, NULL, NULL) != SQLITE_OK) { log_warn("database: unable to initialize %s: %s", path, sqlite3_errmsg(db)); - return false; + return -1; } - return true; + return 0; } -bool -database_recents(struct paste *pastes, size_t *max) +json_t * +database_recents(size_t limit) { - assert(pastes); - assert(max); + json_t *array = NULL; + sqlite3_stmt *stmt = NULL; + size_t i = 0; - sqlite3_stmt *stmt = NULL; - - memset(pastes, 0, *max * sizeof (struct paste)); log_debug("database: accessing most recents"); if (sqlite3_prepare(db, sql_recents, -1, &stmt, NULL) != SQLITE_OK || - sqlite3_bind_int64(stmt, 1, *max) != SQLITE_OK) + sqlite3_bind_int64(stmt, 1, limit) != SQLITE_OK) goto sqlite_err; - size_t i = 0; + array = json_array(); - for (; i < *max && sqlite3_step(stmt) == SQLITE_ROW; ++i) - convert(stmt, &pastes[i]); + for (; i < limit && sqlite3_step(stmt) == SQLITE_ROW; ++i) + json_array_append_new(array, convert(stmt)); log_debug("database: found %zu pastes", i); sqlite3_finalize(stmt); - *max = i; - return true; + return array; sqlite_err: log_warn("database: error (recents): %s\n", sqlite3_errmsg(db)); @@ -236,29 +158,26 @@ if (stmt) sqlite3_finalize(stmt); - return (*max = 0); + return NULL; } -bool -database_get(struct paste *paste, const char *uuid) +json_t * +database_get(const char *id) { - assert(paste); - assert(uuid); + assert(id); + json_t *object = NULL; sqlite3_stmt* stmt = NULL; - bool found = false; - memset(paste, 0, sizeof (struct paste)); - log_debug("database: accessing paste with uuid: %s", uuid); + log_debug("database: accessing paste with uuid: %s", id); if (sqlite3_prepare(db, sql_get, -1, &stmt, NULL) != SQLITE_OK || - sqlite3_bind_text(stmt, 1, uuid, -1, NULL) != SQLITE_OK) + sqlite3_bind_text(stmt, 1, id, -1, NULL) != SQLITE_OK) goto sqlite_err; switch (sqlite3_step(stmt)) { case SQLITE_ROW: - convert(stmt, paste); - found = true; + object = convert(stmt); break; case SQLITE_MISUSE: case SQLITE_ERROR: @@ -269,7 +188,7 @@ sqlite3_finalize(stmt); - return found; + return object; sqlite_err: if (stmt) @@ -277,11 +196,11 @@ log_warn("database: error (get): %s", sqlite3_errmsg(db)); - return false; + return NULL; } -bool -database_insert(struct paste *paste) +int +database_insert(json_t *paste) { assert(paste); @@ -291,25 +210,25 @@ if (sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { log_warn("database: could not lock database: %s", sqlite3_errmsg(db)); - return false; + return -1; } - if (!set_id(paste)) { + if (set_id(paste) < 0) { log_warn("database: unable to randomize unique identifier"); sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL); - return false; + return -1; } if (sqlite3_prepare(db, sql_insert, -1, &stmt, NULL) != SQLITE_OK) goto sqlite_err; - sqlite3_bind_text(stmt, 1, paste->id, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, paste->title, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, paste->author, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 4, paste->language, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 5, paste->code, -1, SQLITE_STATIC); - sqlite3_bind_int(stmt, 6, paste->visible); - sqlite3_bind_int64(stmt, 7, paste->duration); + sqlite3_bind_text(stmt, 1, ju_get_string(paste, "id"), -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, ju_get_string(paste, "title"), -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, ju_get_string(paste, "author"), -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 4, ju_get_string(paste, "language"), -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 5, ju_get_string(paste, "code"), -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 6, ju_get_bool(paste, "visible")); + sqlite3_bind_int64(stmt, 7, ju_get_int(paste, "duration")); if (sqlite3_step(stmt) != SQLITE_DONE) goto sqlite_err; @@ -318,9 +237,12 @@ sqlite3_finalize(stmt); log_info("database: new paste (%s) from %s expires in one %lld seconds", - paste->id, paste->author, paste->duration); + ju_get_string(paste, "id"), + ju_get_string(paste, "author"), + ju_get_int(paste, "duration") + ); - return true; + return 0; sqlite_err: log_warn("database: error (insert): %s", sqlite3_errmsg(db)); @@ -329,31 +251,27 @@ if (stmt) sqlite3_finalize(stmt); - free(paste->id); - paste->id = NULL; + /* Make sure it is not used anymore. */ + json_object_del(paste, "id"); - return false; + return 0; } -bool -database_search(struct paste *pastes, - size_t *max, +json_t * +database_search(size_t limit, const char *title, const char *author, const char *language) { - assert(pastes); - assert(max); - + json_t *array = NULL; sqlite3_stmt *stmt = NULL; + size_t i = 0; log_debug("database: searching title=%s, author=%s, language=%s", title ? title : "", author ? author : "", language ? language : ""); - memset(pastes, 0, *max * sizeof (struct paste)); - /* Select everything if not specified. */ title = title ? title : "%"; author = author ? author : "%"; @@ -367,19 +285,18 @@ goto sqlite_err; if (sqlite3_bind_text(stmt, 3, language, -1, NULL) != SQLITE_OK) goto sqlite_err; - if (sqlite3_bind_int64(stmt, 4, *max) != SQLITE_OK) + if (sqlite3_bind_int64(stmt, 4, limit) != SQLITE_OK) goto sqlite_err; - size_t i = 0; + array = json_array(); - for (; i < *max && sqlite3_step(stmt) == SQLITE_ROW; ++i) - convert(stmt, &pastes[i]); + for (; i < limit && sqlite3_step(stmt) == SQLITE_ROW; ++i) + json_array_append_new(array, convert(stmt)); log_debug("database: found %zu pastes", i); sqlite3_finalize(stmt); - *max = i; - return true; + return array; sqlite_err: log_warn("database: error (search): %s\n", sqlite3_errmsg(db)); @@ -387,7 +304,7 @@ if (stmt) sqlite3_finalize(stmt); - return (*max = 0); + return NULL; } void diff -r fe78b16c694d -r 9bfe5ce3cc45 database.h --- a/database.h Thu Mar 16 15:05:26 2023 +0100 +++ b/database.h Thu Mar 16 20:45:59 2023 +0100 @@ -19,33 +19,79 @@ #ifndef PASTER_DATABASE_H #define PASTER_DATABASE_H -#include #include -struct paste; +#include -bool -database_open(const char *); +/** + * Open the database specified by path. + * + * \pre path != NULL + * \param path path to the SQLite file + * \return 0 on success or -1 on error + */ +int +database_open(const char *path); -bool -database_recents(struct paste *, size_t *); +/** + * Obtain a list of recent pastes. + * + * \param limit max number of items to fetch + * \return a JSON array of paste objects or NULL on failure + */ +json_t * +database_recents(size_t limit); -bool -database_get(struct paste *, const char *); +/** + * Obtain a specific paste by id. + * + * \pre id != NULL + * \param id the paste identifier + * \return NULL on failure or if not found + */ +json_t * +database_get(const char *id); -bool -database_insert(struct paste *); +/** + * Insert a new paste into the database. + * + * On insertion, the paste gets a new string "id" property generated from the + * database. + * + * \pre paste != NULL + * \param paste the paste object + * \return 0 on success or -1 on failure + */ +int +database_insert(json_t *paste); -bool -database_search(struct paste *, - size_t *, - const char *, - const char *, - const char *); +/** + * Search for pastes based on criterias. + * + * If any of the criterias is NULL it is considered as ignored (and then match a + * database item). + * + * \param limit max number of items to fetch + * \param title paste title (or NULL to match any) + * \param author paste author (or NULL to match any) + * \param language paste language (or NULL to match any) + * \return a JSON array of objects or NULL on failure + */ +json_t * +database_search(size_t limit, + const char *title, + const char *author, + const char *language); +/** + * Cleanup expired pastes. + */ void database_clear(void); +/** + * Close the database handle + */ void database_finish(void); diff -r fe78b16c694d -r 9bfe5ce3cc45 html/new.html --- a/html/new.html Thu Mar 16 15:05:26 2023 +0100 +++ b/html/new.html Thu Mar 16 20:45:59 2023 +0100 @@ -9,7 +9,7 @@ Author - + @@ -35,8 +35,8 @@ - Private - + Public + diff -r fe78b16c694d -r 9bfe5ce3cc45 html/paste.html --- a/html/paste.html Thu Mar 16 15:05:26 2023 +0100 +++ b/html/paste.html Thu Mar 16 20:45:59 2023 +0100 @@ -33,7 +33,7 @@ Expires in - {{expiration}} + {{expires}} diff -r fe78b16c694d -r 9bfe5ce3cc45 json-util.c --- a/json-util.c Thu Mar 16 15:05:26 2023 +0100 +++ b/json-util.c Thu Mar 16 20:45:59 2023 +0100 @@ -21,7 +21,6 @@ #include "json-util.h" #include "util.h" -#include "paste.h" json_t * ju_languages(const char *selected) @@ -59,17 +58,84 @@ } json_t * -ju_date(const struct paste *paste) +ju_date(time_t timestamp) { - assert(paste); - - return json_string(bstrftime("%c", localtime(&paste->timestamp))); + return json_string(bstrftime("%c", localtime(×tamp))); } json_t * -ju_expiration(const struct paste *paste) +ju_expires(time_t timestamp, int duration) +{ + return json_string(ttl(timestamp, duration)); +} + +const char * +ju_get_string(const json_t *doc, const char *key) +{ + const json_t *val; + + if (!doc || !(val = json_object_get(doc, key)) || !json_is_string(val)) + return NULL; + + return json_string_value(val); +} + +intmax_t +ju_get_int(const json_t *doc, const char *key) +{ + const json_t *val; + + if (!doc || !(val = json_object_get(doc, key)) || !json_is_integer(val)) + return 0; + + return json_integer_value(val); +} + +int +ju_get_bool(const json_t *doc, const char *key) { - assert(paste); + const json_t *val; + + if (!doc || !(val = json_object_get(doc, key)) || !json_is_boolean(val)) + return 0; + + return json_boolean_value(val); +} + +json_t * +ju_paste_new(void) +{ + return json_pack("{ss ss ss ss si si}", + "title", "Untitled", + "author", "Anonymous", + "language", "nohighlight", + "code", "The best code is no code", + "visible", 0, + "duration", PASTE_DURATION_HOUR + ); +} - return json_string(ttl(paste->timestamp, paste->duration)); +json_t * +ju_extend(json_t *doc, const char *fmt, ...) +{ + assert(fmt); + + json_t *ret, *val; + json_error_t err; + va_list ap; + const char *key; + + va_start(ap, fmt); + ret = json_vpack_ex(&err, 0, fmt, ap); + va_end(ap); + + /* Now steal every nodes from doc and put them in ret. */ + if (doc) { + json_object_foreach(doc, key, val) + json_object_set(ret, key, val); + + json_decref(doc); + } + + return ret; } diff -r fe78b16c694d -r 9bfe5ce3cc45 json-util.h --- a/json-util.h Thu Mar 16 15:05:26 2023 +0100 +++ b/json-util.h Thu Mar 16 20:45:59 2023 +0100 @@ -19,9 +19,10 @@ #ifndef PASTER_PASTER_JSON_UTIL_H #define PASTER_PASTER_JSON_UTIL_H -#include +#include +#include -struct paste; +#include /** * Create an array of all possible languages supported by the application. If @@ -73,26 +74,40 @@ /** * Create a convenient ISO date string containing the paste creation date. * - * \pre paste != NULL - * \param paste this paste + * \param timestamp the timestamp * \return a string with an ISO date */ json_t * -ju_date(const struct paste *paste); +ju_date(time_t timestamp); /** - * Create a convenient remaining time for the given paste. + * Create a convenient remaining time for the given timestamp/duration. * * Returns strings in the form: * * - `2 day(s)` * - `3 hours(s)` * - * \pre paste != NULL - * \param paste this paste + * \param timestamp the timestamp + * \param duration the duration in seconds (e.g. 3600) * \return a string containing the expiration time */ json_t * -ju_expiration(const struct paste *paste); +ju_expires(time_t timestamp, int duration); + +const char * +ju_get_string(const json_t *, const char *); + +intmax_t +ju_get_int(const json_t *, const char *); + +int +ju_get_bool(const json_t *, const char *); + +json_t * +ju_paste_new(void); + +json_t * +ju_extend(json_t *doc, const char *fmt, ...); #endif /* !PASTER_PASTER_JSON_UTIL_H */ diff -r fe78b16c694d -r 9bfe5ce3cc45 page-download.c --- a/page-download.c Thu Mar 16 15:05:26 2023 +0100 +++ b/page-download.c Thu Mar 16 20:45:59 2023 +0100 @@ -2,11 +2,11 @@ * page-download.c -- page /download/ * * Copyright (c) 2020-2023 David Demelier - * + * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. - * + * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR @@ -16,49 +16,48 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include #include -#include -#include - -#include #include "database.h" #include "page.h" -#include "paste.h" +#include "json-util.h" static void -get(struct kreq *r) +get(struct kreq *req) { - struct paste paste = {0}; + json_t *paste; - if (!database_get(&paste, r->path)) - page_status(r, KHTTP_404); + if (!(paste = database_get(req->path))) + page_status(req, KHTTP_404); else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_OCTET_STREAM]); + khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_OCTET_STREAM]); #if 0 /* TODO: this seems to generated truncated files. */ - khttp_head(r, kresps[KRESP_CONTENT_LENGTH], "%zu", strlen(paste.code)); + khttp_head(req, kresps[KRESP_CONTENT_LENGTH], "%zu", strlen(paste.code)); #endif - khttp_head(r, kresps[KRESP_CONNECTION], "keep-alive"); - khttp_head(r, kresps[KRESP_CONTENT_DISPOSITION], - "attachment; filename=\"%s.%s\"", paste.id, paste.language); - khttp_body(r); - khttp_puts(r, paste.code); - khttp_free(r); - paste_finish(&paste); + khttp_head(req, kresps[KRESP_CONNECTION], "keep-alive"); + khttp_head(req, kresps[KRESP_CONTENT_DISPOSITION], "attachment; filename=\"%s.%s\"", + ju_get_string(paste, "id"), + ju_get_string(paste, "language") + ); + khttp_body(req); + khttp_puts(req, ju_get_string(paste, "code")); + khttp_free(req); + json_decref(paste); } } void -page_download(struct kreq *r) +page_download(struct kreq *req) { - switch (r->method) { + assert(req); + + switch (req->method) { case KMETHOD_GET: - get(r); + get(req); break; default: - page_status(r, KHTTP_400); + page_status(req, KHTTP_400); break; } } diff -r fe78b16c694d -r 9bfe5ce3cc45 page-fork.c --- a/page-fork.c Thu Mar 16 15:05:26 2023 +0100 +++ b/page-fork.c Thu Mar 16 20:45:59 2023 +0100 @@ -19,21 +19,19 @@ #include #include "database.h" +#include "json-util.h" #include "page-new.h" #include "page.h" -#include "paste.h" static void get(struct kreq *req) { - struct paste paste = {0}; + json_t *paste; - if (!database_get(&paste, req->path)) + if (!(paste = database_get(req->path))) page_status(req, KHTTP_404); - else { - page_new_render(req, &paste); - paste_finish(&paste); - } + else + page_new_render(req, paste); } void diff -r fe78b16c694d -r 9bfe5ce3cc45 page-index.c --- a/page-index.c Thu Mar 16 15:05:26 2023 +0100 +++ b/page-index.c Thu Mar 16 20:45:59 2023 +0100 @@ -22,64 +22,34 @@ #include "json-util.h" #include "page-index.h" #include "page.h" -#include "paste.h" #include "util.h" #include "html/index.h" -static void -get(struct kreq *r) -{ - struct paste pastes[10] = {0}; - size_t pastesz = NELEM(pastes); - - if (!database_recents(pastes, &pastesz)) - page_status(r, KHTTP_500); - else { - page_index_render(r, pastes, pastesz); - - for (size_t i = 0; i < pastesz; ++i) - paste_finish(&pastes[i]); - } -} - -static inline json_t * -create_pastes(const struct paste *pastes, size_t pastesz) -{ - json_t *array = json_array(); - const struct paste *paste; +#define LIMIT 16 +#define TITLE "paster -- recent pastes" - for (size_t i = 0; i < pastesz; ++i) { - paste = &pastes[i]; +static void +get(struct kreq *req) +{ + json_t *pastes; - json_array_append_new(array, json_pack("{ss ss ss ss so so}", - "id", paste->id, - "author", paste->author, - "title", paste->title, - "date", ju_date(paste), - "expiration", ju_expiration(paste) - )); - } - - return array; -} - -static inline json_t * -create_doc(const struct paste *pastes, size_t pastesz) -{ - return json_pack("{ss so}", - "pagetitle", "paster -- recent pastes", - "pastes", create_pastes(pastes, pastesz) - ); + if (!(pastes = database_recents(LIMIT))) + page_status(req, KHTTP_500); + else + page_index_render(req, pastes); } void -page_index_render(struct kreq *req, const struct paste *pastes, size_t pastesz) +page_index_render(struct kreq *req, json_t *pastes) { assert(req); assert(pastes); - page(req, KHTTP_200, html_index, create_doc(pastes, pastesz)); + page(req, KHTTP_200, html_index, json_pack("{ss so}", + "title", TITLE, + "pastes", pastes + )); } void diff -r fe78b16c694d -r 9bfe5ce3cc45 page-index.h --- a/page-index.h Thu Mar 16 15:05:26 2023 +0100 +++ b/page-index.h Thu Mar 16 20:45:59 2023 +0100 @@ -2,11 +2,11 @@ * page-index.h -- page / * * Copyright (c) 2020-2023 David Demelier - * + * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. - * + * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR @@ -19,13 +19,12 @@ #ifndef PASTER_PAGE_INDEX_H #define PASTER_PAGE_INDEX_H -#include +#include struct kreq; -struct paste; void -page_index_render(struct kreq *, const struct paste *, size_t); +page_index_render(struct kreq *, json_t *pastes); void page_index(struct kreq *); diff -r fe78b16c694d -r 9bfe5ce3cc45 page-new.c --- a/page-new.c Thu Mar 16 15:05:26 2023 +0100 +++ b/page-new.c Thu Mar 16 20:45:59 2023 +0100 @@ -23,18 +23,11 @@ #include "json-util.h" #include "page-new.h" #include "page.h" -#include "paste.h" #include "util.h" #include "html/new.h" -static const struct paste paste_default = { - .id = "", - .title = "unknown", - .author = "anonymous", - .language = "nohighlight", - .code = "" -}; +#define TITLE "paster -- create a new paste" static long long int duration(const char *val) @@ -43,8 +36,8 @@ if (strcmp(val, durations[i].title) == 0) return durations[i].secs; - /* Default to month. */ - return PASTE_DURATION_MONTH; + /* Default to day. */ + return 60 * 60 * 24; } static void @@ -54,76 +47,69 @@ } static void -post(struct kreq *r) +post(struct kreq *req) { - struct paste paste = { - .author = estrdup("Anonymous"), - .title = estrdup("Untitled"), - .language = estrdup("nohighlight"), - .code = estrdup(""), - .visible = true, - .duration = PASTE_DURATION_DAY - }; + const char *key, *val, *id, *scheme; + json_t *paste; int raw = 0; - for (size_t i = 0; i < r->fieldsz; ++i) { - const char *key = r->fields[i].key; - const char *val = r->fields[i].val; + paste = ju_paste_new(); - if (strcmp(key, "title") == 0) - replace(&paste.title, val); - else if (strcmp(key, "author") == 0) - replace(&paste.author, val); + for (size_t i = 0; i < req->fieldsz; ++i) { + key = req->fields[i].key; + val = req->fields[i].val; + + if (strcmp(key, "title") == 0 && strlen(val)) + json_object_set_new(paste, "title", json_string(val)); + else if (strcmp(key, "author") == 0 && strlen(val)) + json_object_set_new(paste, "author", json_string(val)); else if (strcmp(key, "language") == 0) - replace(&paste.language, val); + json_object_set_new(paste, "language", json_string(val)); else if (strcmp(key, "duration") == 0) - paste.duration = duration(val); + json_object_set_new(paste, "duration", json_integer(duration(val))); else if (strcmp(key, "code") == 0) - paste.code = estrdup(val); - else if (strcmp(key, "private") == 0) - paste.visible = strcmp(val, "on") != 0; - else if (strcmp(key, "raw") == 0) { + json_object_set_new(paste, "code", json_string(val)); + else if (strcmp(key, "visible") == 0) + json_object_set_new(paste, "visible", json_boolean(strcmp(val, "on") == 0)); + else if (strcmp(key, "raw") == 0) raw = strcmp(val, "on") == 0; - } } - if (!database_insert(&paste)) - page_status(r,KHTTP_500); + if (database_insert(paste) < 0) + page_status(req, KHTTP_500); else { + id = ju_get_string(paste, "id"); + scheme = req->scheme == KSCHEME_HTTP ? "http" : "https"; + if (raw) { /* For CLI users (e.g. paster) just print the location. */ - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_201]); - khttp_body(r); - khttp_printf(r, "%s://%s/paste/%s\n", - r->scheme == KSCHEME_HTTP ? "http" : "https", - r->host, paste.id); - khttp_free(r); + khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_201]); + khttp_body(req); + khttp_printf(req, "%s://%s/paste/%s\n", scheme, req->host, id); } else { /* Otherwise, redirect to paste details. */ - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_302]); - khttp_head(r, kresps[KRESP_LOCATION], "/paste/%s", paste.id); - khttp_body(r); - khttp_free(r); + khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_302]); + khttp_head(req, kresps[KRESP_LOCATION], "/paste/%s", id); + khttp_body(req); } + + khttp_free(req); } - paste_finish(&paste); + json_decref(paste); } +#include "log.h" + void -page_new_render(struct kreq *req, const struct paste *paste) +page_new_render(struct kreq *req, json_t *paste) { assert(req); - if (!paste) - paste = &paste_default; - - page(req, KHTTP_200, html_new, json_pack("{ss ss so so ss}", - "pagetitle", "paster -- create new paste", - "title", paste->title, - "languages", ju_languages(paste->language), - "durations", ju_durations(), - "code", paste->code + page(req, KHTTP_200, html_new, ju_extend(paste, "{ss so so}", + "pagetitle", TITLE, + "durations", ju_durations(), + "languages", ju_languages(ju_get_string(paste, "language")) )); } diff -r fe78b16c694d -r 9bfe5ce3cc45 page-new.h --- a/page-new.h Thu Mar 16 15:05:26 2023 +0100 +++ b/page-new.h Thu Mar 16 20:45:59 2023 +0100 @@ -2,11 +2,11 @@ * page-new.h -- page /new * * Copyright (c) 2020-2023 David Demelier - * + * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. - * + * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR @@ -19,11 +19,12 @@ #ifndef PASTER_PAGE_NEW_H #define PASTER_PAGE_NEW_H +#include + struct kreq; -struct paste; void -page_new_render(struct kreq *, const struct paste *); +page_new_render(struct kreq *, json_t *); void page_new(struct kreq *); diff -r fe78b16c694d -r 9bfe5ce3cc45 page-paste.c --- a/page-paste.c Thu Mar 16 15:05:26 2023 +0100 +++ b/page-paste.c Thu Mar 16 20:45:59 2023 +0100 @@ -22,43 +22,53 @@ #include "json-util.h" #include "page-paste.h" #include "page.h" -#include "paste.h" #include "util.h" #include "html/paste.h" static inline json_t * -create_pagetitle(const struct paste *paste) +mk_pagetitle(const json_t *paste) { - return json_sprintf("paster -- %s", paste->title); + return json_sprintf("paster -- %s", ju_get_string(paste, "title")); +} + +static inline json_t * +mk_date(const json_t *paste) +{ + return ju_date(ju_get_int(paste, "timestamp")); } static inline json_t * -create_paste(const struct paste *paste) +mk_public(const json_t *paste) { - return json_pack("{so ss ss ss ss ss ss so so}", - "pagetitle", create_pagetitle(paste), - "id", paste->id, - "title", paste->title, - "author", paste->author, - "language", paste->language, - "code", paste->code, - "public", paste->visible ? "Yes" : "No", - "date", ju_date(paste), - "expiration", ju_expiration(paste) + const intmax_t visible = ju_get_int(paste, "visible"); + + return json_string(visible ? "Yes" : "No"); +} + +static inline json_t * +mk_expires(const json_t *paste) +{ + return ju_expires( + ju_get_int(paste, "timestamp"), + ju_get_int(paste, "duration") ); } static void -get(struct kreq *r) +get(struct kreq *req) { - struct paste paste = {0}; + json_t *paste; - if (!database_get(&paste, r->path)) - page_status(r, KHTTP_404); + if (!(paste = database_get(req->path))) + page_status(req, KHTTP_404); else { - page(r, KHTTP_200, html_paste, create_paste(&paste)); - paste_finish(&paste); + page(req, KHTTP_200, html_paste, ju_extend(paste, "{so so so so}", + "pagetitle", mk_pagetitle(paste), + "date", mk_date(paste), + "public", mk_public(paste), + "expires", mk_expires(paste) + )); } } diff -r fe78b16c694d -r 9bfe5ce3cc45 page-search.c --- a/page-search.c Thu Mar 16 15:05:26 2023 +0100 +++ b/page-search.c Thu Mar 16 20:45:59 2023 +0100 @@ -24,31 +24,26 @@ #include "page-index.h" #include "page-search.h" #include "page.h" -#include "paste.h" #include "util.h" #include "html/search.h" -static inline json_t * -create_root(void) -{ - return json_pack("{ss so}", - "pagetitle", "paster -- search", - "languages", ju_languages(NULL) - ); -} +#define TITLE "paster -- search" +#define LIMIT 16 static void get(struct kreq *req) { - page(req, KHTTP_200, html_search, create_root()); + page(req, KHTTP_200, html_search, json_pack("{ss so}", + "pagetitle", "paster -- search", + "languages", ju_languages(NULL) + )); } static void post(struct kreq *req) { - struct paste pastes[10] = {0}; - size_t pastesz = NELEM(pastes); + json_t *pastes; const char *key, *val, *title = NULL, *author = NULL, *language = NULL; for (size_t i = 0; i < req->fieldsz; ++i) { @@ -69,13 +64,10 @@ if (author && strlen(author) == 0) author = NULL; - if (!database_search(pastes, &pastesz, title, author, language)) + if (!(pastes = database_search(16, title, author, language))) page_status(req, KHTTP_500); else - page_index_render(req, pastes, pastesz); - - for (size_t i = 0; i < pastesz; ++i) - paste_finish(&pastes[i]); + page_index_render(req, pastes); } void diff -r fe78b16c694d -r 9bfe5ce3cc45 paste.c --- a/paste.c Thu Mar 16 15:05:26 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -/* - * paste.c -- paste definition - * - * Copyright (c) 2020-2023 David Demelier - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include - -#include "paste.h" - -void -paste_finish(struct paste *paste) -{ - assert(paste); - - free(paste->id); - free(paste->title); - free(paste->author); - free(paste->language); - free(paste->code); - memset(paste, 0, sizeof (struct paste)); -} diff -r fe78b16c694d -r 9bfe5ce3cc45 paste.h --- a/paste.h Thu Mar 16 15:05:26 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -/* - * paste.h -- paste definition - * - * Copyright (c) 2020-2023 David Demelier - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef PASTER_PASTE_H -#define PASTER_PASTE_H - -#include -#include - -#define PASTE_DURATION_HOUR 3600 /* Seconds in one hour. */ -#define PASTE_DURATION_DAY 86400 /* Seconds in one day. */ -#define PASTE_DURATION_WEEK 604800 /* Seconds in one week. */ -#define PASTE_DURATION_MONTH 2592000 /* Rounded to 30 days. */ - -struct paste { - char *id; - char *title; - char *author; - char *language; - char *code; - time_t timestamp; - bool visible; - long long int duration; -}; - -void -paste_finish(struct paste *); - -#endif /* !PASTER_PASTE_H */ diff -r fe78b16c694d -r 9bfe5ce3cc45 pasterd.c --- a/pasterd.c Thu Mar 16 15:05:26 2023 +0100 +++ b/pasterd.c Thu Mar 16 20:45:59 2023 +0100 @@ -42,7 +42,7 @@ if (!config.databasepath[0]) die("abort: no database specified\n"); - if (!database_open(config.databasepath)) + if (database_open(config.databasepath) < 0) die("abort: could not open database\n"); } diff -r fe78b16c694d -r 9bfe5ce3cc45 sql/clear.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sql/clear.sql Thu Mar 16 20:45:59 2023 +0100 @@ -0,0 +1,21 @@ +-- +-- clear.sql -- cleanup expirated pastes +-- +-- Copyright (c) 2020-2023 David Demelier +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +DELETE + FROM paste + WHERE strftime('%s', 'now') - strftime('%s', date) >= `duration` diff -r fe78b16c694d -r 9bfe5ce3cc45 sql/get.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sql/get.sql Thu Mar 16 20:45:59 2023 +0100 @@ -0,0 +1,28 @@ +-- +-- get.sql -- get a paste by id +-- +-- Copyright (c) 2020-2023 David Demelier +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +SELECT `id` + , `title` + , `author` + , `language` + , `code` + , strftime('%s', `date`) + , `visible` + , `duration` + FROM `paste` + WHERE `id` = ? diff -r fe78b16c694d -r 9bfe5ce3cc45 sql/init.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sql/init.sql Thu Mar 16 20:45:59 2023 +0100 @@ -0,0 +1,32 @@ +-- +-- init.sql -- database initialization +-- +-- Copyright (c) 2020-2023 David Demelier +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +BEGIN EXCLUSIVE TRANSACTION; + +CREATE TABLE IF NOT EXISTS paste( + `id` TEXT primary key, + `title` TEXT not null, + `author` TEXT not null, + `language` TEXT not null, + `code` TEXT not null, + `date` INT default CURRENT_TIMESTAMP, + `visible` INT default 0, + `duration` INT +); + +END TRANSACTION; diff -r fe78b16c694d -r 9bfe5ce3cc45 sql/insert.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sql/insert.sql Thu Mar 16 20:45:59 2023 +0100 @@ -0,0 +1,27 @@ +-- +-- insert.sql -- create a new paste +-- +-- Copyright (c) 2020-2023 David Demelier +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +INSERT INTO paste( + `id`, + `title`, + `author`, + `language`, + `code`, + `visible`, + `duration` +) VALUES (?, ?, ?, ?, ?, ?, ?) diff -r fe78b16c694d -r 9bfe5ce3cc45 sql/recents.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sql/recents.sql Thu Mar 16 20:45:59 2023 +0100 @@ -0,0 +1,30 @@ +-- +-- recents.sql -- get a list of recent pastes +-- +-- Copyright (c) 2020-2023 David Demelier +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +SELECT `id` + , `title` + , `author` + , `language` + , `code` + , strftime('%s', date) + , `visible` + , `duration` + FROM paste + WHERE `visible` = 1 + ORDER BY date DESC + LIMIT ? diff -r fe78b16c694d -r 9bfe5ce3cc45 sql/search.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sql/search.sql Thu Mar 16 20:45:59 2023 +0100 @@ -0,0 +1,33 @@ +-- +-- search.sql -- search existing public pastes +-- +-- Copyright (c) 2020-2023 David Demelier +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- + +SELECT `id` + , `title` + , `author` + , `language` + , `code` + , strftime('%s', date) + , `visible` + , `duration` + FROM paste + WHERE `title` like ? + AND `author` like ? + AND `language` like ? + AND `visible` = 1 + ORDER BY date DESC + LIMIT ? diff -r fe78b16c694d -r 9bfe5ce3cc45 util.c --- a/util.c Thu Mar 16 15:05:26 2023 +0100 +++ b/util.c Thu Mar 16 20:45:59 2023 +0100 @@ -28,7 +28,6 @@ #include "config.h" #include "util.h" -#include "paste.h" const char * const languages[] = { "nohighlight", diff -r fe78b16c694d -r 9bfe5ce3cc45 util.h --- a/util.h Thu Mar 16 15:05:26 2023 +0100 +++ b/util.h Thu Mar 16 20:45:59 2023 +0100 @@ -24,6 +24,11 @@ #define NELEM(x) (sizeof (x) / sizeof (x)[0]) +#define PASTE_DURATION_HOUR (3600) +#define PASTE_DURATION_DAY (PASTE_DURATION_HOUR * 24) +#define PASTE_DURATION_WEEK (PASTE_DURATION_DAY * 7) +#define PASTE_DURATION_MONTH (PASTE_DURATION_DAY * 30) + struct tm; struct kreq;