diff options
Diffstat (limited to 'ep_view.c')
| -rw-r--r-- | ep_view.c | 1180 |
1 files changed, 1180 insertions, 0 deletions
diff --git a/ep_view.c b/ep_view.c new file mode 100644 index 0000000..2bc9e47 --- /dev/null +++ b/ep_view.c @@ -0,0 +1,1180 @@ +/* + * nanobbs, a tiny forums software. + * Copyright (C) 2025-2026 Xavier Del Campo Romero + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "endpoints.h" +#include "auth.h" +#include "db.h" +#include "defs.h" +#include "form.h" +#include "op.h" +#include "utils.h" +#include <libweb/http.h> +#include <libweb/html.h> +#include <sqlite3.h> +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct view +{ + unsigned long category, section, topic, msg, page; + unsigned n_topics; + char *username, *cntq; + struct op *op; + sqlite3 *db; + enum auth_role role; + struct html_node *root, *body, *section_ul, *topic_ul, *topic_div; + int (*end)(const struct http_payload *, struct http_response *, + void *, void *); + + union + { + struct category + { + unsigned *secids; + size_t n_secids; + } category; + + struct post + { + struct db_user user; + } post; + } u; +}; + +static const char timefmt[] = "%B %e %Y %H:%M:%S %Z"; + +static void free_view(struct view *const v) +{ + int error; + + if (!v) + return; + else if ((error = sqlite3_close(v->db)) != SQLITE_OK) + fprintf(stderr, "%s: sqlite3_close: %s\n", __func__, + sqlite3_errstr(error)); + + html_node_free(v->root); + free(v->username); + free(v->cntq); + free(v); +} + +static int append_section(struct category *const c, const unsigned id) +{ + const size_t n = c->n_secids + 1; + unsigned *const secids = realloc(c->secids, n * sizeof *c->secids); + + if (!secids) + { + fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); + return -1; + } + + secids[c->n_secids] = id; + c->secids = secids; + c->n_secids = n; + return 0; +} + +static int reply(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1; + struct view *const v = args; + struct dynstr d; + const int error = sqlite3_close(v->db); + + v->db = NULL; + dynstr_init(&d); + + if (error != SQLITE_OK) + { + fprintf(stderr, "%s: sqlite3_close: %s\n", __func__, + sqlite3_errstr(error)); + goto end; + } + else if (dynstr_append(&d, "%s", DOCTYPE_TAG)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (html_serialize(v->root, &d)) + { + fprintf(stderr, "%s: html_serialize failed\n", __func__); + goto end; + } + + *r = (const struct http_response) + { + .status = HTTP_STATUS_OK, + .buf.rw = d.str, + .n = d.len, + .free = free + }; + + ret = 0; + +end: + + if (ret) + dynstr_free(&d); + + free(v->u.category.secids); + free_view(v); + return ret; +} + +static int post(sqlite3_stmt *const stmt, const struct http_payload *const pl, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1, n; + char *name = NULL, *datetime = NULL, *enctext = NULL; + char id[sizeof "4294967295"]; + struct view *const v = args; + sqlite3 *const db = v->db; + struct db_post p = {0}; + struct dynstr d; + struct tm tm; + struct html_node *a, *div, *udiv, *tdiv, *pname; + + dynstr_init(&d); + + /* TODO: push user info */ + if (!(div = html_node_add_child(v->body, "div")) + || !(udiv = html_node_add_child(div, "div")) + || !(tdiv = html_node_add_child(div, "div")) + || !(pname = html_node_add_child(tdiv, "p")) + || !(a = html_node_add_child(tdiv, "a"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto end; + } + else if (db_post(db, stmt, &p)) + { + fprintf(stderr, "%s: db_post failed\n", __func__); + goto end; + } + else if (!(name = db_str(db, stmt, "name"))) + { + fprintf(stderr, "%s: db_str failed\n", __func__); + goto end; + } + else if ((n = snprintf(id, sizeof id, "%lu", p.id)) < 0 + || n >= sizeof id) + { + fprintf(stderr, "%s: snprintf(3) returned %d\n", __func__, n); + goto end; + } + else if (dynstr_append(&d, "/view/%lu/%lu/%lu#%lu", v->category, + v->section, v->topic, p.id)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (!localtime_r(&p.creat, &tm)) + { + fprintf(stderr, "%s: localtime_r(3): %s\n", __func__, strerror(errno)); + goto end; + } + else if (!(datetime = astrftime(timefmt, &tm))) + { + fprintf(stderr, "%s: astrftime failed\n", __func__); + goto end; + } + else if (!(enctext = html_encode(p.text))) + { + fprintf(stderr, "%s: html_encode failed\n", __func__); + goto end; + } + else if (html_node_add_attr(a, "href", d.str) + || html_node_add_attr(div, "id", id)) + { + fprintf(stderr, "%s: html_node_add_attr failed\n", __func__); + goto end; + } + else if (html_node_set_value(a, datetime) + || html_node_set_value(pname, name)) + { + fprintf(stderr, "%s: html_node_set_value failed\n", __func__); + goto end; + } + else if (html_node_set_value_unescaped(tdiv, enctext)) + { + fprintf(stderr, "%s: html_node_set_value_unescaped failed\n", __func__); + goto end; + } + + ret = 0; + +end: + free(name); + free(enctext); + free(datetime); + db_post_free(&p); + dynstr_free(&d); + return ret; +} + +static int end_posts(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + struct view *const v = args; + + if (form_footer(v->body, p->resource)) + { + fprintf(stderr, "%s: form_footer failed\n", __func__); + return -1; + } + + *r = (const struct http_response) + { + .step.payload = reply, + .args = v + }; + + return 0; +} + +static void view_error(void *args) +{ + free_view(args); +} + +static int view_topic(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1; + struct view *const v = args; + struct dynstr d; + struct html_node *const root = html_node_alloc("html"), *body; + + dynstr_init(&d); + + if (!root) + { + fprintf(stderr, "%s: html_node_alloc failed\n", __func__); + goto end; + } + else if (form_head(root)) + { + fprintf(stderr, "%s: form_head failed\n", __func__); + goto end; + } + else if (!(body = html_node_add_child(root, "body"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto end; + } + else if (form_login(body, v->username)) + { + fprintf(stderr, "%s: form_login failed\n", __func__); + goto end; + } + else if (v->username + && v->role >= AUTH_ROLE_USER + && form_post(body, v->category, v->section, v->topic)) + { + fprintf(stderr, "%s: form_post failed\n", __func__); + goto end; + } + /* TODO: paging */ + else if (dynstr_append(&d, "SELECT posts.*, name FROM posts " + "JOIN users ON users.id = posts.uid " + "AND posts.topid = %lu " + "ORDER BY creat ASC;", v->topic)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + + const struct op_cfg op = + { + .error = view_error, + .end = end_posts, + .row = post, + .args = v + }; + + if (!op_run(v->db, d.str, &op, r)) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + goto end; + } + + v->root = root; + v->body = body; + ret = 0; + +end: + dynstr_free(&d); + + if (ret) + { + html_node_free(root); + free_view(v); + } + + return ret; +} + +static int end_topics(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + struct view *const v = args; + + if (form_footer(v->body, p->resource)) + { + fprintf(stderr, "%s: form_footer failed\n", __func__); + return -1; + } + + *r = (const struct http_response) + { + .step.payload = reply, + .args = v + }; + + return 0; +} + +static void rollback(void *const args) +{ + struct view *const v = args; + + if (!v) + return; + + db_rollback(v->db); + free_view(v); +} + +static int commit(const struct http_payload *const p, + struct http_response *const r, struct view *const v) +{ + const struct op_cfg cfg = + { + .error = rollback, + .end = v->end, + .args = v, + }; + + if (!op_run(v->db, "COMMIT;", &cfg, r)) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + return -1; + } + + return 0; +} + +static int end_lastmsg(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + const struct view *const v = args; + + op_resume(v->op, r); + return 0; +} + +static int lastmsg(sqlite3_stmt *const stmt, + const struct http_payload *const p, struct http_response *const r, + void *const user, void *const args) +{ + int ret = -1; + char *stime = NULL; + unsigned id; + long long creat; + struct tm tm; + struct dynstr d, url; + struct html_node *span, *a; + struct view *const v = args; + sqlite3 *const db = v->db; + char *const name = db_str(db, stmt, "name"); + + dynstr_init(&d); + dynstr_init(&url); + + if (!name) + { + fprintf(stderr, "%s: db_str failed\n", __func__); + goto end; + } + else if (db_uint(db, stmt, "id", &id)) + { + fprintf(stderr, "%s: db_uint failed\n", __func__); + goto end; + } + else if (db_bigint(db, stmt, "creat", &creat)) + { + fprintf(stderr, "%s: db_bigint failed\n", __func__); + goto end; + } + else if (!localtime_r(&(time_t){creat}, &tm)) + { + fprintf(stderr, "%s: localtime_r(3): %s\n", __func__, strerror(errno)); + goto end; + } + else if (!(span = html_node_add_child(v->topic_div, "span")) + || !(a = html_node_add_child(span, "a"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto end; + } + else if (!(stime = astrftime(timefmt, &tm))) + { + fprintf(stderr, "%s: astrftime failed\n", __func__); + goto end; + } + else if (dynstr_append(&url, "/view/%u/%u/%u#%u", v->category, v->section, + v->topic, id) + || dynstr_append(&d, "Last reply by: %s at %s", name, stime)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (html_node_add_attr(a, "href", url.str)) + { + fprintf(stderr, "%s: html_node_add_attr failed\n", __func__); + goto end; + } + else if (html_node_set_value(a, d.str)) + { + fprintf(stderr, "%s: html_node_set_value failed\n", __func__); + goto end; + } + + ret = 0; + +end: + free(name); + free(stime); + dynstr_free(&d); + dynstr_free(&url); + return ret; +} + +static char *gentimestr(const time_t *const creat) +{ + char *ret = NULL, *hrtime = NULL, *datetime = NULL; + struct tm tm; + struct dynstr d; + struct html_node *const time = html_node_alloc("time"); + + dynstr_init(&d); + + if (!time) + { + fprintf(stderr, "%s: html_node_alloc failed\n", __func__); + goto end; + } + else if (!(localtime_r(creat, &tm))) + { + fprintf(stderr, "%s: localtime_r(3): %s\n", __func__, strerror(errno)); + goto end; + } + else if (!(hrtime = astrftime(timefmt, &tm)) + || !(datetime = astrftime("%Y-%m-%d %H:%M:%S%z", &tm))) + { + fprintf(stderr, "%s: astrftime failed\n", __func__); + goto end; + } + else if (html_node_add_attr(time, "datetime", datetime)) + { + fprintf(stderr, "%s: html_node_add_attr failed\n", __func__); + goto end; + } + else if (html_node_set_value(time, hrtime)) + { + fprintf(stderr, "%s: html_node_add_attr failed\n", __func__); + goto end; + } + else if (html_serialize(time, &d)) + { + fprintf(stderr, "%s: html_serialize failed\n", __func__); + goto end; + } + + ret = d.str; + +end: + if (!ret) + dynstr_free(&d); + + html_node_free(time); + free(hrtime); + free(datetime); + return ret; +} + +static int topic(sqlite3_stmt *const stmt, const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1; + char *name = NULL, *time = NULL; + struct db_topic t = {0}; + struct dynstr url, created, expr; + struct view *const v = args; + sqlite3 *const db = v->db; + struct html_node *li, *dl, *dt, *a, *span; + + dynstr_init(&url); + dynstr_init(&created); + dynstr_init(&expr); + + if (!(li = html_node_add_child(v->topic_ul, "li")) + || !(dl = html_node_add_child(li, "dl")) + || !(dt = html_node_add_child(dl, "dt")) + || !(v->topic_div = html_node_add_child(dt, "div")) + || !(a = html_node_add_child(v->topic_div, "a")) + || !(span = html_node_add_child(v->topic_div, "span"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto end; + } + else if (db_topic(db, stmt, &t)) + { + fprintf(stderr, "%s: db_topic failed\n", __func__); + goto end; + } + else if (!(name = db_str(db, stmt, "name"))) + { + fprintf(stderr, "%s: db_str failed\n", __func__); + goto end; + } + else if (!(time = gentimestr(&t.creat))) + { + fprintf(stderr, "%s: gentimestr failed\n", __func__); + goto end; + } + else if (dynstr_append(&url, "/view/%u/%u/%u", v->category, t.secid, t.id) + || dynstr_append(&created, "Created by %s at %s", name, time)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (html_node_add_attr(a, "href", url.str)) + { + fprintf(stderr, "%s: html_node_add_attr failed\n", __func__); + goto end; + } + else if (html_node_set_value(a, t.title)) + { + fprintf(stderr, "%s: html_node_set_value failed\n", __func__); + goto end; + } + else if (html_node_set_value_unescaped(span, created.str)) + { + fprintf(stderr, "%s: html_node_set_value_unescaped failed\n", __func__); + goto end; + } + else if (dynstr_append(&expr, "SELECT posts.id, posts.creat, " + "name FROM posts JOIN users ON posts.topid = %u " + "AND posts.uid = users.id " + "ORDER BY posts.creat DESC LIMIT 1;", t.id)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + + const struct op_cfg cfg = + { + .end = end_lastmsg, + .row = lastmsg, + .args = v + }; + + if (!op_run(db, expr.str, &cfg, r)) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + goto end; + } + + v->section = t.secid; + v->topic = t.id; + ret = 0; + +end: + free(name); + free(time); + dynstr_free(&expr); + dynstr_free(&created); + dynstr_free(&url); + db_topic_free(&t); + return ret; +} + +static int print_pages(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + struct view *const v = args; + + fprintf(stderr, "%s: TODO\n", __func__); + v->end = end_topics; + return commit(p, r, v); +} + +static int topic_count(sqlite3_stmt *const stmt, + const struct http_payload *const p, struct http_response *const r, + void *const user, void *const args) +{ + struct view *const v = args; + unsigned n; + + if (db_uint(v->db, stmt, "COUNT(*)", &n)) + { + fprintf(stderr, "%s: db_uint failed\n", __func__); + return -1; + } + + v->n_topics = n; + return 0; +} + +static int count_pages(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + struct view *const v = args; + const struct op_cfg cfg = + { + .end = print_pages, + .row = topic_count, + .error = rollback, + .args = v + }; + + if (!op_run(v->db, v->cntq, &cfg, r)) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + return -1; + } + + return 0; +} + +static char *query_topics(const struct view *const v, + const unsigned *const secids, const size_t n_secids) +{ + struct dynstr d; + + dynstr_init(&d); + + if (dynstr_append(&d, "SELECT topics.*, name FROM topics " + "JOIN users ON (")) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto failure; + } + + for (size_t i = 0; i < n_secids; i++) + if (dynstr_append(&d, " topics.secid = %u ", secids[i]) + || (i < n_secids - 1 && dynstr_append(&d, "OR"))) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto failure; + } + + if (dynstr_append(&d, ") AND topics.uid = users.id " + "ORDER BY creat DESC LIMIT %d OFFSET %ju", + PAGE_LIMIT, (uintmax_t)PAGE_LIMIT * v->page)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto failure; + } + + return d.str; + +failure: + dynstr_free(&d); + return NULL; +} + +static char *query_topcnt(const struct view *const v, + const unsigned *const secids, const size_t n_secids) +{ + struct dynstr d; + + dynstr_init(&d); + + if (dynstr_append(&d, "SELECT COUNT(*) FROM topics WHERE")) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto failure; + } + + for (size_t i = 0; i < n_secids; i++) + if (dynstr_append(&d, " topics.secid = %u ", secids[i]) + || (i < n_secids - 1 && dynstr_append(&d, "OR"))) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto failure; + } + + if (dynstr_append(&d, ";")) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto failure; + } + + return d.str; + +failure: + dynstr_free(&d); + return NULL; +} + +static int prepare_topics(struct view *const v, + struct http_response *const r, const unsigned *const secids, + const size_t n_secids) +{ + int ret = 1; + char *topq = NULL, *cntq = NULL; + struct html_node *div, *tdiv, *h2; + + if (!(div = html_node_add_child(v->body, "div")) + || !(tdiv = html_node_add_child(v->body, "div")) + || !(h2 = html_node_add_child(div, "h2")) + || !(v->topic_ul = html_node_add_child(tdiv, "ul"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto end; + } + else if (html_node_set_value(h2, "Topics")) + { + fprintf(stderr, "%s: html_node_set_value failed\n", __func__); + goto end; + } + else if (!(topq = query_topics(v, secids, n_secids))) + { + fprintf(stderr, "%s: query_topics failed\n", __func__); + goto end; + } + else if (!(cntq = query_topcnt(v, secids, n_secids))) + { + fprintf(stderr, "%s: query_topcnt failed\n", __func__); + goto end; + } + + const struct op_cfg op = + { + .end = count_pages, + .error = rollback, + .row = topic, + .args = v + }; + + if (!(v->op = op_run(v->db, topq, &op, r))) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + goto end; + } + + v->cntq = cntq; + ret = 0; + +end: + + if (ret) + free(cntq); + + free(topq); + return ret; +} + +static int section_tr(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + struct view *const v = args; + const unsigned secids[] = {v->section}; + + if (prepare_topics(v, r, secids, 1)) + { + fprintf(stderr, "%s: prepare_topics failed\n", __func__); + return -1; + } + + return 0; +} + +static int view_section(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + struct view *const v = args; + struct html_node *const root = html_node_alloc("html"), *body; + + if (!root) + { + fprintf(stderr, "%s: html_node_alloc failed\n", __func__); + goto failure; + } + else if (form_head(root)) + { + fprintf(stderr, "%s: form_head failed\n", __func__); + goto failure; + } + else if (!(body = html_node_add_child(root, "body"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto failure; + } + else if (form_login(body, v->username)) + { + fprintf(stderr, "%s: form_login failed\n", __func__); + goto failure; + } + else if (v->username && form_topic(body, v->category, v->section)) + { + fprintf(stderr, "%s: form_topic failed\n", __func__); + goto failure; + } + + const struct op_cfg ocfg = + { + .end = section_tr, + .error = rollback, + .args = v + }; + + if (!op_run(v->db, "BEGIN TRANSACTION;", &ocfg, r)) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + goto failure; + } + + v->root = root; + v->body = body; + return 0; + +failure: + html_node_free(root); + return -1; +} + +static int end_category(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + struct view *const v = args; + const struct category *const c = &v->u.category; + + if (!c->n_secids) + { + v->end = end_topics; + + if (commit(p, r, v)) + { + fprintf(stderr, "%s: commit failed\n", __func__); + return -1; + } + } + else if (prepare_topics(v, r, c->secids, c->n_secids)) + { + fprintf(stderr, "%s: prepare_topics failed\n", __func__); + return -1; + } + + return 0; +} + +static int section(sqlite3_stmt *const stmt, + const struct http_payload *const pl, struct http_response *const r, + void *const user, void *const args) +{ + int ret = -1; + struct view *const v = args; + struct dynstr d; + struct db_section s = {0}; + struct html_node *li, *div, *a, *p; + + dynstr_init(&d); + + if (!(li = html_node_add_child(v->section_ul, "li")) + || !(div = html_node_add_child(li, "div")) + || !(a = html_node_add_child(div, "a")) + || !(p = html_node_add_child(div, "p"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto end; + } + else if (db_section(v->db, stmt, &s)) + { + fprintf(stderr, "%s: db_section failed\n", __func__); + goto end; + } + else if (dynstr_append(&d, "/view/%u/%u", s.catid, s.id)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (html_node_add_attr(a, "href", d.str)) + { + fprintf(stderr, "%s: html_node_add_attr failed\n", __func__); + goto end; + } + else if (html_node_set_value(a, s.name) + || html_node_set_value(p, s.desc)) + { + fprintf(stderr, "%s: html_node_set_value failed\n", __func__); + goto end; + } + else if (append_section(&v->u.category, s.id)) + { + fprintf(stderr, "%s: append_section failed\n", __func__); + goto end; + } + + ret = 0; + +end: + dynstr_free(&d); + db_section_free(&s); + return ret; +} + +static int category_tr(const struct http_payload *const pl, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1; + struct view *const v = args; + struct dynstr d; + + dynstr_init(&d); + + if (dynstr_append(&d, "SELECT * FROM sections WHERE " + "sections.catid = %lu ORDER BY sections.id ASC;", v->category)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + + const struct op_cfg op = + { + .end = end_category, + .error = rollback, + .row = section, + .args = v + }; + + if (!op_run(v->db, d.str, &op, r)) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + goto end; + } + + ret = 0; + +end: + dynstr_free(&d); + return ret; +} + +static int view_category(const struct http_payload *const pl, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1; + struct view *const v = args; + struct html_node *root = NULL, *body, *div, *h2; + + if (!(root = html_node_alloc("html"))) + { + fprintf(stderr, "%s: html_node_alloc failed\n", __func__); + goto failure; + } + else if (form_head(root)) + { + fprintf(stderr, "%s: form_head failed\n", __func__); + goto failure; + } + else if (!(body = html_node_add_child(root, "body"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto failure; + } + else if (form_login(body, v->username)) + { + fprintf(stderr, "%s: form_login failed\n", __func__); + goto failure; + } + else if (v->username + && v->role >= AUTH_ROLE_MOD + && form_section(body, v->category)) + { + fprintf(stderr, "%s: form_section failed\n", __func__); + goto failure; + } + else if (!(div = html_node_add_child(body, "div")) + || !(h2 = html_node_add_child(div, "h2")) + || !(v->section_ul = html_node_add_child(body, "ul"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto failure; + } + else if (html_node_set_value(h2, "Sections")) + { + fprintf(stderr, "%s: html_node_set_value failed\n", __func__); + goto failure; + } + + const struct op_cfg op = + { + .end = category_tr, + .error = rollback, + .args = v + }; + + if (!op_run(v->db, "BEGIN TRANSACTION;", &op, r)) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + goto failure; + } + + v->root = root; + v->body = body; + return 0; + +failure: + html_node_free(root); + free_view(v); + return ret; +} + +static int parse_args(const struct http_payload *const p, struct view *const v) +{ + for (size_t i = 0; i < p->n_args; i++) + { + const struct http_arg *const arg = &p->args[i]; + const char *const key = arg->key, *const value = arg->value; + + if (!strcmp(key, "page") && getul_n(value, &v->page)) + { + fprintf(stderr, "%s: invalid page: %s\n", __func__, value); + return -1; + } + else if (!strcmp(key, "msg") && getul_n(value, &v->msg)) + { + fprintf(stderr, "%s: invalid msg: %s\n", __func__, value); + return -1; + } + } + + return 0; +} + +static int setup(const struct http_payload *const p, + struct http_response *const r, void *const user, sqlite3 *const db, + const struct auth_user *const u) +{ + int ret = -1, error; + const char *tree = p->resource + strlen("/view/"); + struct view *v = NULL; + char *userdup = NULL; + + if (!(v = malloc(sizeof *v))) + { + fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); + goto failure; + } + + *v = (const struct view){.db = db}; + *r = (const struct http_response){.args = v}; + + if (u) + { + if (!(userdup = strdup(u->username))) + { + fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); + goto failure; + } + else if (u->role <= AUTH_ROLE_BANNED) + { + ret = form_unauthorized("Banned account", r); + goto failure; + } + + v->username = userdup; + v->role = u->role; + } + + if (parse_args(p, v)) + { + ret = form_badreq("Invalid arguments", r); + goto failure; + } + else if (getul(&tree, &v->category)) + { + ret = form_badreq("Invalid category", r); + goto failure; + } + else if (!*tree) + r->step.payload = view_category; + else if (getul(&tree, &v->section)) + { + ret = form_badreq("Invalid section", r); + goto failure; + } + else if (!*tree) + r->step.payload = view_section; + else if (getul(&tree, &v->topic)) + { + ret = form_badreq("Invalid topic", r); + goto failure; + } + else + r->step.payload = view_topic; + + return 0; + +failure: + + if ((error = sqlite3_close(db)) != SQLITE_OK) + fprintf(stderr, "%s: sqlite3_close: %s\n", __func__, + sqlite3_errstr(error)); + + free(v); + free(userdup); + return ret; +} + +int ep_view(const struct http_payload *const p, struct http_response *const r, + void *const user) +{ + const int n = auth_validate(p, r, user, setup); + + if (n < 0) + fprintf(stderr, "%s: auth_validate failed\n", __func__); + else if (n) + { + const struct cfg *const cfg = user; + sqlite3 *db; + + if (db_open(cfg->dir, &db)) + { + fprintf(stderr, "%s: db_open failed\n", __func__); + return -1; + } + + return setup(p, r, user, db, NULL); + } + + return n; +} |
