radio

radio.ircforever.org
git clone git://git.ircforever.org/radio
Log | Files | Refs | Submodules | README | LICENSE

commit a5cbca0f8581f45c8306a426c39c43385f5d99c6
parent bc8b6750bc52d8a386acba249066c3ca94269ee5
Author: libredev <libredev@ircforever.org>
Date:   Mon, 13 Mar 2023 19:31:37 +0530

update comment system

Diffstat:
M.gitignore | 7+++++++
MMakefile | 14++++++++++----
Dcomment.c | 144-------------------------------------------------------------------------------
Dcomment.db | 8--------
Dcomment.h | 21---------------------
Dcomment.html | 19-------------------
Mconfig.mk | 11++++++++---
Mheader.html | 2+-
Mmain.c | 301+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mstyle.css | 18+++++++++++-------
Athread.c | 266+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Athread.h | 31+++++++++++++++++++++++++++++++
Athread.html | 12++++++++++++
Autil.c | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil.h | 22++++++++++++++++++++++
Dutils.c | 179-------------------------------------------------------------------------------
Dutils.h | 29-----------------------------
17 files changed, 682 insertions(+), 520 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1 +1,8 @@ +.gitignore index.cgi +make.sh +thread.db +tmp +trash/ +test.c +a.out diff --git a/Makefile b/Makefile @@ -4,9 +4,9 @@ include config.mk BIN = index.cgi -INC = comment.h utils.h http.h -SRC = main.c comment.c utils.c pdjson/pdjson.c -DEP = header.html footer.html comment.html style.css music.svg comment.db +INC = thread.h util.h http.h +SRC = main.c thread.c util.c pdjson/pdjson.c +DEP = header.html footer.html thread.html style.css music.svg all: ${BIN} @@ -20,8 +20,14 @@ clean: rm -f ${BIN} install: ${BIN} ${DEP} - mkdir -p ${INSTALL_DIR} + mkdir -p ${INSTALL_DIR}/threads + mkdir -p ${INSTALL_DIR}/tmp + chown -R ${HTTPD_USER}:${HTTPD_GROUP} ${INSTALL_DIR}/threads + chown -R ${HTTPD_USER}:${HTTPD_GROUP} ${INSTALL_DIR}/tmp cp ${BIN} ${DEP} ${INSTALL_DIR} + for f in ${DEP}; do\ + sed "s^TITLE^${TITLE}^g" < $$f > ${INSTALL_DIR}/$$f;\ + done uninstall: rm -f ${INSTALL_DIR}/${BIN} diff --git a/comment.c b/comment.c @@ -1,144 +0,0 @@ -/* This work is in the public domain. See COPYING file for details. */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "comment.h" -#include "utils.h" - -enum { - HANDLE_NAME, - HANDLE_EMAIL, - HANDLE_BODY, - HANDLE_DELIMITER, -}; - -struct comment_list * -comment_list_new(char *path) -{ - struct comment_list *head, *tail; - FILE *file; - char line[512]; - int state, lineno, len; - - /* init struct */ - head = tail = NULL; - state = HANDLE_NAME; - lineno = 0; - - if ((file = fopen(path, "rb")) == NULL) - fatal("%s:", path); - - while(fgets(line, sizeof(line), file) != NULL) { - /* if line doesn't end with newline character */ - if (line[strlen(line) - 1] != '\n' && state != HANDLE_BODY) { - fatal("%s:%d: name or email is too big", path, lineno); - } else { - lineno++; - if (state == HANDLE_BODY) { - if (strcmp(line, "---\n") == 0) { - tail->body[strlen(tail->body)-1] = '\0'; - state = HANDLE_NAME; - continue; - } - } else { - line[strlen(line) - 1] = '\0'; - } - } - switch (state) { - case HANDLE_NAME: - if (head == NULL) { - head = tail = ecalloc(1, sizeof(*tail)); - } else { - tail->next = ecalloc(1, sizeof(*tail)); - tail = tail->next; - } - tail->name = strlen(line) == 0 ? NULL : strdup(line); - state = HANDLE_EMAIL; - break; - case HANDLE_EMAIL: - tail->email = strlen(line) == 0 ? NULL : strdup(line); - state = HANDLE_BODY; - break; - case HANDLE_BODY: - if (tail->body == NULL) - len = strlen(line) + 1; - else - len = strlen(tail->body) + strlen(line) + 1; - tail->body = erealloc(tail->body, len * sizeof(char)); - strcat(tail->body, line); - break; - } - } - fclose(file); - if (state != HANDLE_NAME) - fatal("%s:%d: incomplete comment", path, lineno); - return head; -} - -void -comment_list_free(struct comment_list *clist) -{ - struct comment_list *it, *itn; - - for (it = clist; it != NULL;) { - free(it->name); - free(it->email); - free(it->body); - itn = it->next; - free(it); - it = itn; - } -} - -void -comment_list_print(struct comment_list *clist) -{ - struct comment_list *it; - - puts("<h2> Comments </h2>"); - for (it = clist; it != NULL; it = it->next) { - - puts("<div class='comment'>"); - printf("<p>"); - - if (it->name == NULL) - printf("<b>Anonymous</b>"); - else - printf("<b>%s</b>", it->name); - - if (it->email != NULL) - printf(" &lt;%s&gt;", it->email); - - printf("</p>\n"); - - printf("<pre>%s</pre>\n", it->body); - puts("</div>"); - } -} - -void -comment_write(struct input_list *ilist, char *path) -{ - FILE *file; - char *name, *email, *body; - - if ((file = fopen(path, "a")) == NULL) - fatal("%s:", path); - - /* first get all the values before writing to the file */ - name = input_list_find(ilist, "name"); - email = input_list_find(ilist, "email"); - body = input_list_find(ilist, "body"); - if (body == NULL) - fatal("comment_write: body can't be empty"); - - /* now write the values on the file */ - name == NULL ? fprintf(file, "\n") : fprintf(file, "%s\n", name); - email == NULL ? fprintf(file, "\n") : fprintf(file, "%s\n", email); - fprintf(file, "%s\n", body); - fprintf(file, "---\n"); - - fclose(file); -} diff --git a/comment.db b/comment.db @@ -1,8 +0,0 @@ -libredev -libredev@ircforever.org -I love FREEDOM. ---- - - -This is anonymous comment. ---- diff --git a/comment.h b/comment.h @@ -1,21 +0,0 @@ -/* This work is in the public domain. See COPYING file for details. */ - -#ifndef COMMENT_H -#define COMMENT_H - -struct input_list; - -struct comment_list { - char *name; - char *email; - char *body; - struct comment_list *next; -}; - -struct comment_list * - comment_list_new(char *); -void comment_list_free(struct comment_list *); -void comment_list_print(struct comment_list *); -void comment_write(struct input_list *, char *); - -#endif /* COMMENT_H */ diff --git a/comment.html b/comment.html @@ -1,19 +0,0 @@ -<!-- This work is in the public domain. See COPYING file for details. --> - -<br> -<h2>Add a comment</h2> -<form> - <fieldset> - <p><b>NOTE: By commenting, you agree to release the comment under - <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0 1.0</a>. - </b></p> - <label for="name">Name (optional):</label><br> - <input type="text" name="name" id="name"><br><br> - <label for="email">Email (optional):</label><br> - <input type="email" name="email" id="email" size="30"><br><br> - <label for="comment">Comment:</label><br> - <textarea name="body" id="body" rows="8" required></textarea><br><br> - <input type="submit" value="Submit"> - </fieldset> -</form> -<br> diff --git a/config.mk b/config.mk @@ -1,10 +1,15 @@ # This work is in the public domain. See COPYING file for details. -INSTALL_DIR=/var/www/htdocs/radio.ircforever.org -STATUS_JSON=http://theinterlude.live:8000/status-json.xsl +NAME=Example Radio +TITLE=Example Radio | https://git.ircforever.org/radio +ICECAST_URL=http://radio.example.com +INSTALL_DIR=/var/www/htdocs/radio.example.com +HTTPD_USER=httpd +HTTPD_GROUP=httpd CFLAGS = -g -std=c99 -Wall -Wextra -Wpedantic -Wfatal-errors\ -Wstrict-prototypes -Wold-style-definition\ - -D_DEFAULT_SOURCE -DSTATUS_JSON=\"$(STATUS_JSON)\" + -D_DEFAULT_SOURCE -DICECAST_URL=\"$(ICECAST_URL)\" -DNAME=\""$(NAME)"\"\ + -DSTREAM_URL_REMOVE_PORT LDFLAGS = diff --git a/header.html b/header.html @@ -6,6 +6,6 @@ <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>IRCForever Radio | Free Culture Media Streaming</title> + <title>TITLE</title> <link rel="stylesheet" type="text/css" href="/style.css"/> </head> diff --git a/main.c b/main.c @@ -6,13 +6,22 @@ #include <stdlib.h> #include <string.h> -#include "utils.h" -#include "comment.h" +#include "util.h" +#include "thread.h" #define HTTP_IMPLEMENTATION #include "http.h" #include "pdjson/pdjson.h" +#define THREAD_FILE "threads/thread.db" + +enum scope { + SCOPE_NONE, + SCOPE_ROOT, + SCOPE_ICESTATES, + SCOPE_SOURCE +}; + struct icestats { char *admin; char *host; @@ -50,8 +59,7 @@ struct source { struct icestats icestats; struct source *sources = NULL; -int sources_length = 0; -int source_active = FALSE; +int source_len = 0; int header_sent = FALSE; void @@ -96,11 +104,10 @@ icestats_set(struct icestats *s, json_stream *json, const char *key) else if (strcmp(key, "server_start_iso8601" ) == 0) var = &s->server_start_iso8601; else if (strcmp(key, "dummy" ) == 0) return; - if (var == NULL) { + if (var == NULL) fatal("failed to handle icestats key: %s", key); - } else { + else *var = json_get_value(json); - } } void @@ -157,6 +164,25 @@ source_set(struct source *s, json_stream *json, const char *key) if (var == NULL) { fatal("failed to handle source key: %s", key); +#ifdef STREAM_URL_REMOVE_PORT + /* } else if (var == &s->listenurl) { + char *tmp = json_get_value(json); + char *port_start, *port_end; + char *new_url; + + port_start = strchr(tmp, ':'); *//* ignore the colon after http */ + /* if ((port_start = strchr(++port_start, ':')) == NULL) + *var = tmp; + else { + if ((port_end = strchr(port_start, '/')) == NULL) + fatal("No mount point"); + new_url = emalloc(port_start - tmp + strlen(port_end) + 1); + strncpy(new_url, tmp, port_start - tmp); + strcat(new_url, port_end); + free(tmp); + *var = new_url; + } */ +#endif } else { *var = json_get_value(json); if (var == &s->server_name) { @@ -237,7 +263,7 @@ print_navigation_bar(int index) char *scripts[] = { "/stream/", "/music/", "/contact/", "/about/", "/login/" }; puts("<ul>"); - puts("<li><a href='/'>IRCForever Radio</a></li>"); + puts("<li><a href='/'>"NAME"</a></li>"); for (int i = 0; i < 5; i++) { printf("<li><a "); @@ -250,103 +276,134 @@ print_navigation_bar(int index) } void -source_increment(void) -{ - sources_length++; - sources = erealloc(sources, sources_length * sizeof(struct source)); - memset(&sources[sources_length-1], 0, sizeof(struct source)); -} - -void json_handle_object(json_stream *json, enum json_type jtype); - -void -json_handle_source(json_stream *json, enum json_type jtype) +json_process(json_stream *json) { - if (jtype == JSON_ARRAY) { - while (json_peek(json) != JSON_ARRAY_END - && !json_get_error(json)) { - source_increment(); - source_active = TRUE; - json_handle_object(json, json_next(json)); - source_active = FALSE; + enum scope scope = SCOPE_NONE; + char *str; + while(1) { + enum json_type type = json_next(json); + switch (type) { + case JSON_DONE: + if (scope != SCOPE_NONE) + fatal("incomplete scope"); + return; + case JSON_STRING: + str = json_get_string(json, NULL); + switch (scope) { + case SCOPE_NONE: + fatal("%s: string in none scope"); + break; + case SCOPE_ROOT: + if (strcmp(str, "icestats") == 0) { + if (json_next(json) != JSON_OBJECT) + fatal("icestats doesn't have any scope"); + scope = SCOPE_ICESTATES; + } else { + fatal("%s: undefined string in icestats scope"); + } + break; + case SCOPE_ICESTATES: + if (strcmp(str, "source") == 0) { + if (json_next(json) != JSON_OBJECT) + fatal("source doesn't have any scope"); + scope = SCOPE_SOURCE; + source_len++; + sources = erealloc(sources, (source_len) * sizeof(struct source)); + memset(&sources[source_len-1], 0, sizeof(struct source)); + } else { + icestats_set(&icestats, json, str); + } + break; + case SCOPE_SOURCE: + source_set(&sources[source_len-1], json, str); + break; + } + break; + case JSON_ARRAY: + break; + case JSON_ARRAY_END: + break; + case JSON_OBJECT: + if (scope == SCOPE_NONE) + scope = SCOPE_ROOT; + else + fatal("unknown scope"); + break; + case JSON_OBJECT_END: + switch (scope) { + case SCOPE_NONE: + fatal("extra scope"); + break; + case SCOPE_ROOT: + scope = SCOPE_NONE; + break; + case SCOPE_ICESTATES: + scope = SCOPE_ROOT; + break; + case SCOPE_SOURCE: + scope = SCOPE_ICESTATES; + break; + } + break; + case JSON_ERROR: + fatal("%zu: %s", json_get_lineno(json), + json_get_error(json)); + break; + default: + fatal("cannot handle json type: %d", type); + break; } - json_next(json); - } else if (jtype == JSON_OBJECT) { - source_increment(); - source_active = TRUE; - json_handle_object(json, JSON_OBJECT); - source_active = FALSE; - } else { - fatal("json_handle_source: invalid json_type"); } } void -json_handle_object(json_stream *json, enum json_type jtype) +redirect(char *url) { - if (jtype != JSON_OBJECT) - fatal("json_handle_object: invalid json_type"); - - while (json_peek(json) != JSON_OBJECT_END && !json_get_error(json)) { - json_next(json); - const char *key = json_get_string(json, NULL); - if (source_active == TRUE) { - source_set(&sources[sources_length-1], json, key); - } else if (strcmp(key, "source") == 0) { - json_handle_source(json, json_next(json)); - } else if (strcmp(key, "icestats") == 0) { - json_handle_object(json, json_next(json)); - } else { - icestats_set(&icestats, json, key); - } - } - json_next(json); + printf("Content-Type: text/html\r\n"); + printf("Status: 302 Found\r\n"); + printf("Location: %s\r\n", url); + printf("\r\n"); + printf("<html><body>Redirecting to <a href='%s'>%s</a></body></html>\n", url, url); + exit(0); } int main(int argc, char *argv[]) { json_stream json; - char *buffer; + char *jsonbuf; char *request_url; + char comment[4096]; + FILE *f; + char c; + int id; + + struct thread thread; - struct input_list *inputs; - struct comment_list *comments; + UNUSED(argc); + UNUSED(argv); /* body */ request_url = getenv("REQUEST_URI"); if (request_url == NULL) fatal("environment variable 'REQUEST_URI' is not set"); - inputs = input_list_new(request_url); - comments = comment_list_new("comment.db"); - if (inputs != NULL) { - comment_write(inputs, "comment.db"); - printf("Content-Type: text/html\r\n"); - printf("Status: 302 Found\r\n"); - printf("Location: %s\r\n", request_url); - printf("\r\n"); - printf("<html><body>Redirecting to <a href='%s'>%s</a></body></html>\n", request_url, request_url); - exit(0); - } - - if (!header_sent) { + if (strchr(request_url, '?') == NULL) { puts("Content-Type: text/html\r"); puts("Status: 200 OK\r"); puts("\r"); header_sent = TRUE; - } - /* print header */ - if ((buffer = read_file("header.html")) == NULL) - fatal("read_file: header.html:"); - printf("%s", buffer); - free(buffer); + /* print header */ + f = file_open("header.html"); + file_print(f, stdout); + file_close(f); + } - /* get json file to the buffer */ + /* get json file */ http_t* request; http_status_t status = HTTP_STATUS_PENDING; - request = http_get(STATUS_JSON, NULL); + request = http_get(ICECAST_URL"/status-json.xsl", NULL); if (request == NULL) fatal("invalid request"); @@ -354,34 +411,77 @@ main(int argc, char *argv[]) status = http_process(request); if(status == HTTP_STATUS_FAILED) fatal("HTTP request failed (%d): %s.\n", request->status_code, request->reason_phrase); - buffer = (char*)request->response_data; + jsonbuf = (char*)request->response_data; /* parse json buffer */ - json_open_string(&json, buffer); + json_open_string(&json, jsonbuf); json_set_streaming(&json, false); - json_handle_object(&json, json_next(&json)); - if (json_get_error(&json)) - fatal("%lu: %s", json_get_lineno(&json), json_get_error(&json)); + json_process(&json); + json_close(&json); + + http_release(request); if (request_url[strlen(request_url)-1] == '/') request_url[strlen(request_url)-1] = '\0'; - if ((strlen(request_url) == 0) || (strcmp(request_url, "/stream") == 0)) { + if (strcmp(request_url, "/stream") == 0 + || *request_url == '\0') { print_navigation_bar(0); - for (int i = 0; i < sources_length; i++) + for (int i = 0; i < source_len; i++) source_print(&sources[i]); - if ((buffer = read_file("comment.html")) == NULL) - fatal("read_file: comment.html:"); - printf("%s", buffer); - free(buffer); + puts("<h2> Comments </h2>"); + /* comment box */ + f = file_open("thread.html"); + file_print(f, stdout); + file_close(f); + /* comment threads */ + thread_open(&thread, THREAD_FILE); + while (thread_next(&thread) != EOF) + thread_write_html(&thread, stdout, thread.depth); + thread_close(&thread); + } else if (sscanf(request_url, "/stream/%9d%c", &id, &c) == 1) { + int parent_depth = -1; + thread_open(&thread, THREAD_FILE); + while (thread_next(&thread) != EOF) { + if (thread.id == id) { + parent_depth = thread.depth; + break; + } + } + if (parent_depth == -1) + not_found(); - comment_list_print(comments); + print_navigation_bar(0); + thread_write_html(&thread, stdout, 0); + f = file_open("thread.html"); + file_print(f, stdout); + file_close(f); + while (thread_next(&thread) != EOF && thread.depth > parent_depth) + thread_write_html(&thread, stdout, thread.depth - parent_depth); + thread_close(&thread); + } else if (sscanf(request_url, "/stream/?comment=%4095[^&]%c", comment, &c) == 1) { + char *msg = request_url + strlen("/stream/?comment="); + parse_html_str(msg); + thread_open(&thread, THREAD_FILE); + thread_add(&thread, 0, msg); + thread_close(&thread); + *strchr(request_url, '?') = '\0'; + redirect(request_url); + } else if (sscanf(request_url, "/stream/%9d?comment=%4095[^&]%c", &id, comment, &c) == 2) { + char *msg = strchr(request_url, '='); + msg++; + parse_html_str(msg); + thread_open(&thread, THREAD_FILE); + thread_add(&thread, id, msg); + thread_close(&thread); + *strchr(request_url, '?') = '\0'; + redirect(request_url); } else if (strcmp(request_url, "/about") == 0) { print_navigation_bar(3); icestats_print(&icestats); } else { - for (int i = 0; i < sources_length; i++) { + /* for (int i = 0; i <= source_index; i++) { if (sources[i].url == NULL) continue; if (strcmp(request_url, sources[i].url) == 0) { @@ -389,29 +489,20 @@ main(int argc, char *argv[]) source_print(&sources[i]); goto out; } - } + } */ not_found(); } out: - /* comment */ - /* comment_print("comment.db"); */ /* footer */ - if ((buffer = read_file("footer.html")) == NULL) - fatal("read_file: footer.html:"); - printf("%s", buffer); - free(buffer); + f = file_open("footer.html"); + file_print(f, stdout); + file_close(f); /* cleanup */ - comment_list_free(comments); - input_list_free(inputs); - json_close(&json); - http_release(request); - if (sources != NULL) { - for (int i = 0; i < sources_length; i++) - source_free(&sources[i]); - free(sources); - } + for (int i = 0; i < source_len; i++) + source_free(&sources[i]); + free(sources); icestats_free(&icestats); return 0; diff --git a/style.css b/style.css @@ -90,12 +90,6 @@ tr:nth-child(odd) { height: 40px; } -.comment { - margin: 10px 0px; - padding: 0px 10px; - border: 1px solid darkgrey; -} - hr { margin: 20px 0px; border: none; @@ -128,5 +122,15 @@ p.footer { } form textarea { - width: 100%; + font-size: 16px; +} + +.thread_header { + color: dimgrey; +} + +.thread { + margin: 10px 0px; + padding: 0px 10px; + border: 1px solid darkgrey; } diff --git a/thread.c b/thread.c @@ -0,0 +1,266 @@ +#include "thread.h" + +#include "util.h" + +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#define MAX_COLUMN_LEN 78 + +enum read_state +{ + HEADER, + MESSAGE +}; + +/* +static void +fprint_tabs(FILE *stream, int n) +{ + int i; + for (i = 0; i < n; i++) + fputc('\t', stream); +} +*/ + +static int +msg_push(struct thread *t, char c) +{ + int newcap; + char *newmsg; + + if (t->msglen == t->msgcap) { + newcap = t->msgcap * 2; + if ((newmsg = realloc(t->msg, newcap)) == NULL) + return -1; + t->msgcap = newcap; + t->msg = newmsg; + } + t->msg[t->msglen++] = c; + return 0; +} + +int +thread_open(struct thread *t, char *path) +{ + if ((t->stream = fopen(path, "r")) == NULL) + fatal("%s:", path); + t->msgcap = 1024; + t->msg = emalloc(t->msgcap); + thread_reset(t); + return 0; +} + +void +thread_reset(struct thread *t) +{ + t->msglen = 0; + t->depth = -1; + if (fseek(t->stream, 0, SEEK_SET) == -1) + fatal("fseek:"); + fscanf(t->stream, "%9d\n", &t->lastid); + t->lineno = 1; +} + +int +thread_close(struct thread *t) +{ + fclose(t->stream); + free(t->msg); + return 0; +} + +int +thread_next(struct thread *t) +{ + char c, lc; /* current char, last char */ + enum read_state state; + char *str; + int depth; + + state = HEADER; + t->msglen = 0; + lc = '\0'; + depth = -1; + + while ((c = fgetc(t->stream))) { + if (c == EOF && lc == '\0') + return EOF; + if (c == '\n') { + t->lineno++; + } + if (c == '\n' && state == HEADER) { + t->msg[t->msglen] = '\0'; + for(str = t->msg; *str == '#'; str++) + depth++; + if (depth == -1 || depth > t->depth+1) + goto err; + if (depth == 0) + t->pid = 0; + else + t->pid = t->depth_array[depth-1]; + t->depth = depth; + if (sscanf(str, + "%9d %31s %4d-%2d-%2d %2d:%2d:%2d %c", + &t->id, t->user, + &t->time[0], &t->time[1], &t->time[2], + &t->time[3], &t->time[4], &t->time[5], + &c) != 8) /* make sure c don't get assigned */ + goto err; + t->depth_array[t->depth] = t->id; + t->msglen = 0; + state = MESSAGE; + lc = c; + } else if (c == EOF || (lc == '\n' && c == '#')) { + if (state != MESSAGE || t->msglen == 0) + goto err; + if (c == '#') + ungetc(c, t->stream); + t->msg[t->msglen-1] = '\0'; + return 0; + } else { + msg_push(t, c); + lc = c; + } + } +err: + fatal("%d: syntax error", t->lineno); + return EOF; +} + +static void +thread_print(FILE *stream, + int depth, int id, char *user, + int year, int mon, int day, + int hour, int min, int sec, + char *msg) +{ + int i; + for (i = 0; i <= depth; i++) + fputc('#', stream); + fprintf(stream, "%d %s %04d-%02d-%02d %02d:%02d:%02d\n", + id, user, + year, mon, day, + hour, min, sec); + fprintf(stream, "%s\n", msg); +} + +int +thread_add(struct thread *t, int pid, char *msg) +{ + FILE *tmp; + time_t clock; + struct tm *tm; + int pdepth = -1; /* parent depth */ + int res, added = 0; + + if ((tmp = fopen("tmp/tmp.db", "w")) == NULL) + fatal("%s:", "tmp.db"); + fprintf(tmp, "%d\n", t->lastid+1); + + time(&clock); + tm = gmtime(&clock); + while (1) { + res = thread_next(t); + if ((pdepth != -1 && !added && + (res == EOF || t->depth <= pdepth)) || + (res == EOF && pid == 0)) { + thread_print(tmp, pdepth+1, t->lastid+1, "Anonymous", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, + msg); + added = 1; + } + if (res == EOF) + break; + if (pdepth == -1 && t->id == pid) + pdepth = t->depth; + + thread_print(tmp, t->depth, t->id, t->user, + t->time[0], t->time[1], t->time[2], + t->time[3], t->time[4], t->time[5], + t->msg); + } + fclose(tmp); + if (pid != 0 && pdepth == -1) + fatal("failed to find parent id: %d", pid); + + if (renameat(AT_FDCWD, "./tmp/tmp.db", AT_FDCWD, "./threads/thread.db") == -1) + fatal("renameat:"); + return 0; +} + +/* int +thread_write_plain(struct thread *t, FILE *stream) +{ + char *s, *last_word; + int column; + + fprint_tabs(stream, t->depth); + fprintf(stream, "* %s %d-%02d-%02d %02d:%02d:%02d\n", + t->user, + t->time[0], t->time[1], t->time[2], + t->time[3], t->time[4], t->time[5]); + + fprint_tabs(stream, t->depth); + fputs("| ", stream); + column = 0; + last_word = t->msg; + for (s = t->msg;; s++) { + if (*s == ' ' || *s == '\n' || *s == '\0') { + if ((column + s - last_word) > MAX_COLUMN_LEN + || (*last_word == '\n')) { + last_word++; + fputc('\n', stream); + fprint_tabs(stream, t->depth); + fputs("| ", stream); + column = 0; + } + fprintf(stream, "%.*s", (int)(s - last_word), last_word); + column += s - last_word; + last_word = s; + + if (*s == '\0') + break; + } + } + fputs("\n\n", stream); + return 0; +} */ + +int +thread_write_html(struct thread *t, FILE *stream, int depth) +{ + char *s; + + fprintf(stream, "<div id=\"%d\" class=\"thread\" style=\"margin-left:%d%%;\">\n", + t->id, depth * 2); + + fprintf(stream, "<p class=\"thread_header\">"); + fprintf(stream, "%s %d-%02d-%02d %02d:%02d:%02d | ", t->user, + t->time[0], t->time[1], t->time[2], + t->time[3], t->time[4], t->time[5]); + if (t->pid != 0) { + if (depth == 0) + fprintf(stream, "<a href=\"%d\">parent</a> | ", t->pid); + else + fprintf(stream, "<a href=\"#%d\">parent</a> | ", t->pid); + } + fprintf(stream, "<a href=\"#%d\">link</a> | ", t->id); + fprintf(stream, "<a href=\"%d\">reply</a>", t->id); + fprintf(stream, "</p>\n"); + + fputs("<p>", stream); + for (s = t->msg; *s != '\0'; s++) { + if (*s == '\n') { + fputs("<br>", stream); + } + fputc(*s, stream); + } + fputs("</p>\n", stream); + fputs("</div>\n", stream); + return 0; +} diff --git a/thread.h b/thread.h @@ -0,0 +1,31 @@ +#ifndef THREAD_H +#define THREAD_H + +#define MAX_DEPTH 10 + +#include <stdio.h> + +struct thread { + int id; /* unique id */ + int pid; /* parent id */ + int lastid; + int time[6]; + char user[32]; + char *msg; + int msgcap; + int msglen; + FILE *stream; + int lineno; + int depth; + int depth_array[MAX_DEPTH]; +}; + +int thread_open(struct thread *, char *); +void thread_reset(struct thread *); +int thread_close(struct thread *); +int thread_next(struct thread *); +int thread_add(struct thread *, int, char *); +int thread_write_plain(struct thread *, FILE *); +int thread_write_html(struct thread *, FILE *, int); + +#endif /* THREAD_H */ diff --git a/thread.html b/thread.html @@ -0,0 +1,12 @@ +<!-- This work is in the public domain. See COPYING file for details. --> + +<form> + <fieldset> + <p><b>Add a comment</b></p> + <textarea name="comment" id="comment" cols="80" rows="4" maxlength="1024" required></textarea> + <p><b>NOTE: By commenting, you agree to release this comment into the + <a href="https://git.ircforever.org/radio/file/COPYING.html">Public Domain</a>. + </b></p> + <input type="submit" value="add comment"> + </fieldset> +</form> diff --git a/util.c b/util.c @@ -0,0 +1,118 @@ +/* This work is in the public domain. See COPYING file for details. */ + +extern int header_sent; + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +void +fatal(const char *fmt, ...) +{ + va_list ap; + + if (!header_sent) { + puts("Content-Type: text/html\r"); + puts("Status: 200 OK\r"); + puts("\r"); + header_sent = TRUE; + } + + fputs("ERROR: ", stdout); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fprintf(stdout, " %s\n", strerror(errno)); + } else { + fprintf(stdout, "\n"); + } + + exit(1); +} + +void * +emalloc(size_t size) +{ + void *p; + if ((p = malloc(size)) == NULL) + fatal("malloc:"); + return p; +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + if ((p = calloc(nmemb, size)) == NULL) + fatal("calloc:"); + return p; +} + +void * +erealloc(void *ptr, size_t size) +{ + void *p; + if ((p = realloc(ptr, size)) == NULL) + fatal("realloc:"); + return p; +} + +FILE * +file_open(char *path) +{ + FILE *f; + if ((f = fopen(path, "r")) == NULL) + fatal("%s:", path); + return f; +} + +void +file_print(FILE *f, FILE *stream) +{ + char c; + + if (fseek(f, 0, SEEK_SET) == -1) + fatal("fseek:"); + + while ((c = fgetc(f)) != EOF) { + fputc(c, stream); + } +} + +void +file_close(FILE *f) +{ + fclose(f); +} + +void +parse_html_str(char *str) +{ + char *x, *y, tmp; + + x = y = str; + while (*y != '\0') { + if (*y == '%') { + if (*(y+1) == '\0' || *(y+2) == '\0') + fatal("parse_html_str: hex char missing"); + tmp = *(y+3); + *(y+3) = '\0'; + *x = strtol(y+1, NULL, 16); + if (*x != 13) /* ignore carriage return */ + x++; + *(y+3) = tmp; + y+=3; + } else { + if (*y == '+') + *y = ' '; + *x++ = *y++; + } + } + *x = '\0'; +} diff --git a/util.h b/util.h @@ -0,0 +1,22 @@ +/* This work is in the public domain. See COPYING file for details. */ + +#ifndef UTILS_H +#define UTILS_H + +#define TRUE 1 +#define FALSE 0 + +#define UNUSED(x) (void)(x) + +#include <stddef.h> + +void fatal(const char *, ...); +void *emalloc(size_t); +void *ecalloc(size_t, size_t); +void *erealloc(void *, size_t); +FILE *file_open(char *); +void file_print(FILE *, FILE *); +void file_close(FILE *); +void parse_html_str(char *); + +#endif /* UTILS_H */ diff --git a/utils.c b/utils.c @@ -1,179 +0,0 @@ -/* This work is in the public domain. See COPYING file for details. */ - -extern int header_sent; - -#include <errno.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "utils.h" - -void -fatal(const char *fmt, ...) -{ - va_list ap; - - if (!header_sent) { - puts("Content-Type: text/html\r"); - puts("Status: 200 OK\r"); - puts("\r"); - header_sent = TRUE; - } - - fputs("ERROR: ", stdout); - va_start(ap, fmt); - vfprintf(stdout, fmt, ap); - va_end(ap); - - if (fmt[0] && fmt[strlen(fmt)-1] == ':') { - fprintf(stdout, " %s\n", strerror(errno)); - } else { - fprintf(stdout, "\n"); - } - - exit(1); -} - -void * -emalloc(size_t size) -{ - void *p; - if ((p = malloc(size)) == NULL) - fatal("malloc:"); - return p; -} - -void * -ecalloc(size_t nmemb, size_t size) -{ - void *p; - if ((p = calloc(nmemb, size)) == NULL) - fatal("calloc:"); - return p; -} - -void * -erealloc(void *ptr, size_t size) -{ - void *p; - if ((p = realloc(ptr, size)) == NULL) - fatal("realloc:"); - return p; -} - -char * -read_file(const char *name) -{ - char *content; - long size; - FILE *file; - - file = fopen(name, "rb"); - if (file == NULL) - goto err; - if (fseek(file, 0, SEEK_END) != 0) - goto err; - if ((size = ftell(file)) == -1) - goto err; - if (fseek(file, 0, SEEK_SET) != 0) - goto err; - if ((content = malloc((size + 1) * sizeof(char))) == NULL) - goto err; - if (fread(content, sizeof(char), size, file) != (size_t)size) - goto err; - fclose(file); - (content)[size] = '\0'; - return content; -err: - fatal("%s:", name); - return NULL; -} - -void -parse_html_str(char *str) -{ - char *x, *y, tmp; - - x = y = str; - while (*y != '\0') { - if (*y == '%') { - if (*(y+1) == '\0' || *(y+2) == '\0') - fatal("parse_html_str: hex char missing"); - tmp = *(y+3); - *(y+3) = '\0'; - *x = strtol(y+1, NULL, 16); - if (*x != 13) /* ignore carriage return */ - x++; - *(y+3) = tmp; - y+=3; - } else { - if (*y == '+') - *y = ' '; - *x++ = *y++; - } - } - *x = '\0'; -} - -struct input_list * -input_list_new(char *url) -{ - struct input_list *head, *tail; - char *name, *value; - int done; - - head = tail = NULL; - done = FALSE; - - if ((url = strchr(url, '?')) == NULL) - return NULL; - *url++ = '\0'; - - while (!done) { - name = url; - if ((url = strchr(url, '&')) == NULL) - done = TRUE; - else - *url++ = '\0'; - - if ((value = strchr(name, '=')) == NULL) - fatal("parse_url: %s: no value", name); - *value++ = '\0'; - - if (head == NULL) { - head = tail = ecalloc(1, sizeof(*tail)); - } else { - tail->next = ecalloc(1, sizeof(*tail)); - tail = tail->next; - } - parse_html_str(name); - parse_html_str(value); - tail->name = strlen(name) == 0 ? NULL : name; - tail->value = strlen(value) == 0 ? NULL : value; - } - return head; -} - -void -input_list_free(struct input_list *list) -{ - struct input_list *it, *itn; - for (it = list; it != NULL;) { - itn = it->next; - free(it); - it = itn; - } -} - -char * -input_list_find(struct input_list *list, char *name) -{ - struct input_list *it; - for (it = list; it != NULL; it = it->next) { - if (strcmp(name, it->name) == 0) - return it->value; - } - return NULL; -} diff --git a/utils.h b/utils.h @@ -1,29 +0,0 @@ -/* This work is in the public domain. See COPYING file for details. */ - -#ifndef UTILS_H -#define UTILS_H - -#define TRUE 1 -#define FALSE 0 - -#include <stddef.h> - -struct input_list { - char *name; - char *value; - struct input_list *next; -}; - -void fatal(const char *, ...); -void *emalloc(size_t); -void *ecalloc(size_t, size_t); -void *erealloc(void *, size_t); -char *read_file(const char *); -void parse_html_str(char *); - -struct input_list * - input_list_new(char *); -void input_list_free(struct input_list *); -char *input_list_find(struct input_list *, char *); - -#endif /* UTILS_H */