/* * Copyright (c) 2004-2009 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "pathnames.h" #include "nls.h" #include "session.h" struct post_comment { u_int id; /* Comment identifier */ time_t ptime; /* Time of original comment post */ time_t mtime; /* Time of last comment modification */ char author[16]; /* Author of comment */ int score; /* Moderation */ TAILQ_ENTRY(post_comment) comments; }; struct post { u_int id; /* Post identifier */ time_t ptime; /* Time of original post */ time_t mtime; /* Time of last modification */ char *title; /* Title of post */ char *author; /* Author of original post */ char *cats; /* Associated categories */ char *data; /* Post buffer (NULL = not resident) */ size_t len; /* Post size */ TAILQ_HEAD(,post_comment) comments; }; enum { TITLE_MAX = 2048, CATS_MAX = 1024, TEXT_MAX = 262144, POST_ID_MAX = 5, OP_MAX = 64 }; CGI_Application weblogApp; CGI_Module mainModule; struct post *posts = NULL; unsigned int nposts = 0; TBL *tblCategories; int ncurrent_ents = 10; int nrecent_ents = 20; static void Cleanup(void) { int i; for (i = 0; i < nposts; i++) { struct post *post = &posts[i]; struct post_comment *comm, *ncomm; if (post->title != NULL) free(post->title); if (post->author != NULL) free(post->author); if (post->cats != NULL) free(post->cats); if (post->data != NULL) free(post->data); for (comm = TAILQ_FIRST(&post->comments); comm != TAILQ_END(&post->comments); comm = ncomm) { ncomm = TAILQ_NEXT(comm, comments); free(comm); } } nposts = 0; free(posts); } static void ReadPostData(const char *dir, const char *file, char **data, size_t *lenp) { char path[FILENAME_MAX]; int fd; size_t len, nRead = 0; ssize_t rv; strlcpy(path, dir, sizeof(path)); strlcat(path, "/", sizeof(path)); strlcat(path, file, sizeof(path)); if (*data != NULL) free(*data); if ((fd = open(path, O_RDONLY)) == -1) { goto empty; } len = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); if (len == 0) { close(fd); goto empty; } if ((*data = (char *)malloc(len)) == NULL) { close(fd); goto empty; } while (nRead < len) { rv = read(fd, (*data)+nRead, len-nRead); if (rv == -1) { if (errno == EINTR) { continue; } goto empty; } if (rv == 0) { goto empty; } nRead += rv; } close(fd); if ((*data)[len-1] == '\n') (*data)[(len--)-1] = '\0'; if (lenp != NULL) *lenp = len; return; empty: *data = NULL; if (lenp != NULL) *lenp = 0; } static void ReadPostFromFile(const char *dir, struct post *post) { ReadPostData(dir, "title", &post->title, NULL); ReadPostData(dir, "author", &post->author, NULL); ReadPostData(dir, "cats", &post->cats, NULL); ReadPostData(dir, "data", &post->data, &post->len); TAILQ_INIT(&post->comments); } static void ReadPosts(void) { struct dirent *dent; DIR *dir; int created = 0; open: if ((dir = opendir(_PATH_POSTS)) == NULL) { if (errno == ENOENT && !created && mkdir(_PATH_POSTS, 0755) == 0) { created = 1; sleep(1); goto open; } CGI_Log(LOG_ERR, "%s: %s", _PATH_POSTS, strerror(errno)); return; } while ((dent = readdir(dir)) != NULL) { char postdir[FILENAME_MAX]; struct post *post; unsigned int postid; if (dent->d_namlen <= 2 && (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)) continue; postid = (int)strtol(dent->d_name, (char **)NULL, 10); if ((postid+1) > nposts) { nposts = postid+1; posts = Realloc(posts, nposts*sizeof(struct post)); } post = &posts[postid]; post->id = postid; post->ptime = time(NULL); post->mtime = time(NULL); post->title = NULL; post->author = NULL; post->cats = NULL; post->data = NULL; post->len = 0; TAILQ_INIT(&post->comments); strlcpy(postdir, _PATH_POSTS, sizeof(postdir)); strlcat(postdir, dent->d_name, sizeof(postdir)); ReadPostFromFile(postdir, post); } closedir(dir); } static void output_current(VAR *v, struct post *post) { CatS(v, "" "" "
"); Cat(v, "" "%s" "
", Get("prog"), post->id, post->title); Cat(v, "%s", post->cats); Cat(v, "

%s

", post->data); CatS(v, "
"); Cat(v, _("Posted by %s on %s."), post->author, ctime(&post->ptime)); CatS(v, "
" "

"); } static void SetRecentEntries(void) { VAR *v; unsigned int i; int ndisp = 0; v = SetS("weblog_recent_entries", NULL); for (i = 0; i < nposts; i++) { struct post *post = &posts[i]; if (post->title == NULL || post->data == NULL) continue; Cat(v, "%s
", Get("prog"), post->id, post->title); if (++ndisp > nrecent_ents) break; } } static void SetCategories(void) { VAR *v; TBL_Entry *ent; v = SetS("weblog_categories", NULL); TBL_FOREACH(ent, tblCategories) { Cat(v, "", ent->key, ent->key); } } static int Index(CGI_Query *q) { VAR *v; int i, ndisp = 0; SetCategories(); ReadPosts(); v = SetS("weblog_current_entries", NULL); for (i = 0; i < nposts; i++) { struct post *post = &posts[i]; if (post->title == NULL || post->data == NULL) { continue; } output_current(v, post); if (++ndisp > ncurrent_ents) break; } SetRecentEntries(); HTML_Output(q, "default"); return (0); } static int ViewEntry(CGI_Query *q) { const char *postid_s = CGI_Get(q, "postid", POST_ID_MAX); unsigned int postid; struct post *post; if (postid_s == NULL) { HTML_SetError(_("Missing post ID")); return Index(q); } postid = (unsigned int)strtoul(postid_s, NULL, 10); if (postid >= nposts) { HTML_SetError(_("No such post: %u"), postid); CGI_Unset(q, "postid"); return Index(q); } post = &posts[postid]; Set("post_id", "%u", post->id); SetS("post_title", post->title); SetS("post_cats", post->cats); SetS("post_data", post->data); SetS("post_author", post->author); SetS("post_date", ctime(&post->ptime)); SetRecentEntries(); HTML_Output(q, "disp_entry"); return (0); } static int AddEntry(CGI_Query *q) { const char *title = CGI_Get(q, "title", TITLE_MAX); const char *cats = CGI_Get(q, "cats", CATS_MAX); const char *text = CGI_Get(q, "text", TEXT_MAX); char path[FILENAME_MAX]; unsigned int postid; FILE *f; if (title == NULL || cats == NULL || text == NULL) { SetRecentEntries(); SetCategories(); HTML_Output(q, "add_entry"); return (0); } postid = nposts++; snprintf(path, sizeof(path), "%s%u", _PATH_POSTS, postid); if (mkdir(path, 0755) == -1 || chdir(path) == -1) { HTML_SetError("%s: %s", path, strerror(errno)); return Index(q); } if ((f = fopen("author", "w")) != NULL) { fprintf(f, "%s\n", "vedge"); fclose(f); } if ((f = fopen("title", "w")) != NULL) { fputs(title, f); fputc('\n', f); fclose(f); } if ((f = fopen("cats", "w")) != NULL) { fputs(cats, f); fputc('\n', f); fclose(f); } if ((f = fopen("data", "w")) != NULL) { fputs(text, f); fputc('\n', f); fclose(f); } if ((f = fopen("source", "w")) != NULL) { fprintf(f, "%s\n%s\n", getenv("REMOTE_ADDR"), getenv("HTTP_USER_AGENT")); fclose(f); } if (chdir("../..") == -1) CGI_Exit(1, "../..: %s", strerror(errno)); snprintf(path, sizeof(path), "%lu", (unsigned long)postid); CGI_SetS(q, "postid", path); return ViewEntry(q); } static int DelEntry(CGI_Query *q) { char path[FILENAME_MAX]; const char *postid = CGI_Get(q, "postid", POST_ID_MAX); unsigned int id = (int)strtol(postid, (char **)NULL, 10); if (id >= nposts) { HTML_SetError(_("No such post: %s"), postid); goto out; } snprintf(path, sizeof(path), "%s%u", _PATH_POSTS, id); if (chdir(path) == -1) { HTML_SetError( "%s: %s", path, strerror(errno)); goto out; } unlink("author"); unlink("cats"); unlink("data"); unlink("title"); unlink("source"); if (chdir("../..") == -1) CGI_Exit(1, "../..: %s", strerror(errno)); out: return Index(q); } int main(int argc, char *argv[]) { CGI_Query *q; CGI_Init(&weblogApp); CGI_RegisterModule(&mainModule); ReadPosts(); if ((tblCategories = TBL_Load(_PATH_CATEGORIES, 1)) == NULL) { CGI_Log(LOG_EMERG, "%s: %s", _PATH_CATEGORIES, CGI_GetError()); return (-1); } CGI_QueryLoop(&sessionOps); CGI_Exit(EX_OK, NULL); return (0); } CGI_Application weblogApp = { N_("PerCGI Minimal Weblog"), N_("Copyright (c) 2009 Hypertriton, Inc."), { "en", "fr", NULL }, "main", CGI_HTML_ERRORS, Cleanup, NULL /* log */ }; static CGI_Command commands[] = { { "index", Index, "text/html" }, { "Index", Index, "text/html" }, { "ViewEntry", ViewEntry, "text/html" }, { "AddEntry", AddEntry, "text/html" }, { "DelEntry", DelEntry, "text/html" }, { NULL, NULL, NULL }, }; CGI_Module mainModule = { "main", N_("Main"), N_("Main weblog module"), NULL, /* init */ NULL, /* destroy */ NULL, /* sessOpen */ NULL, /* sessClose */ Index, &commands[0] };