#include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" static char *line = NULL; size_t linesize = 0; static struct utimbuf contenttime; static time_t comparetime; static unsigned long totalnew = 0; static struct feed **feeds = NULL; /* Unescape / decode fields printed by string_print_encoded() * "\\" to "\", "\t", to TAB, "\n" to newline. Unrecognised escape sequences * are ignored: "\z" etc. */ static void printcontent(const char *s, FILE *fp) { for (; *s; s++) { switch (*s) { case '\\': switch (*(++s)) { case '\0': return; /* ignore */ case '\\': fputc('\\', fp); break; case 't': fputc('\t', fp); break; case 'n': fputc('\n', fp); break; } break; default: fputc((int)*s, fp); } } } /* Unescape / decode fields printed by string_print_encoded() * "\\" to "\", "\t", to TAB, "\n" to newline. Unrecognised escape sequences * are ignored: "\z" etc. Encode HTML 2.0 / XML 1.0 entities. */ static void printcontentxml(const char *s, FILE *fp) { for (; *s; s++) { switch (*s) { case '\\': switch (*(++s)) { case '\0': return; /* ignore */ case '\\': fputc('\\', fp); break; case 't': fputc('\t', fp); break; case 'n': fputc('\n', fp); break; } break; /* XML entities */ case '<': fputs("<", fp); break; case '>': fputs(">", fp); break; case '\'': fputs("'", fp); break; case '&': fputs("&", fp); break; case '"': fputs(""", fp); break; default: fputc((int)*s, fp); } } } /* normalize path names, transform to lower-case and replace non-alpha and * non-digit with '-' */ static size_t normalizepath(const char *path, char *buf, size_t bufsiz) { size_t i, r = 0; for (i = 0; *path && i < bufsiz; path++) { if (isalpha((int)*path) || isdigit((int)*path)) { buf[i++] = tolower((int)*path); r = 0; } else { /* don't repeat '-', don't start with '-' */ if (!r && i) buf[i++] = '-'; r++; } } /* remove trailing '-' */ for (; i > 0 && (buf[i - 1] == '-'); i--) ; if (bufsiz > 0) buf[i] = '\0'; return i; } static void printfeed(FILE *fpitems, FILE *fpin, struct feed *f) { char dirpath[PATH_MAX], filepath[PATH_MAX]; char *fields[FieldLast], *feedname, name[128]; size_t namelen; struct stat st; FILE *fpcontent = NULL; unsigned int isnew; time_t parsedtime; int r; if (f->name[0]) feedname = f->name; else feedname = "unnamed"; /* make directory for feedname */ if (!(namelen = normalizepath(feedname, name, sizeof(name)))) return; if (strlcpy(dirpath, name, sizeof(dirpath)) >= sizeof(dirpath)) errx(1, "strlcpy: path truncation"); /* directory doesn't exist: try to create it. */ if (stat(dirpath, &st) == -1 && mkdir(dirpath, S_IRWXU) == -1) err(1, "mkdir: %s", dirpath); /* menu if not unnamed */ if (f->name[0]) { fputs("

name, fpitems); fputs("\">name, fpitems); fputs("\">", fpitems); xmlencode(f->name, fpitems); fputs("

\n", fpitems); } fputs("\n", fpitems); while (parseline(&line, &linesize, fields, fpin) > 0) { /* write content */ if (!(namelen = normalizepath(fields[FieldTitle], name, sizeof(name)))) continue; r = snprintf(filepath, sizeof(filepath), "%s/%s.html", dirpath, name); if (r == -1 || (size_t)r >= sizeof(filepath)) errx(1, "snprintf: path truncation"); /* file doesn't exist yet and has write access */ if (access(filepath, F_OK) != 0) { if (!(fpcontent = fopen(filepath, "w+b"))) err(1, "fopen: %s", filepath); fputs("" "\n" "
" "

", fpcontent); xmlencode(fields[FieldTitle], fpcontent); fputs("

", fpcontent); /* NOTE: this prints the raw HTML of the feed, this is * potentially dangerous, it is up to the user / browser * to trust a feed it's HTML content. */ if (!strcmp(fields[FieldContentType], "html")) { printcontent(fields[FieldContent], fpcontent); } else { /* plain-text, wrap with
 */
				fputs("
", fpcontent);
				printcontentxml(fields[FieldContent], fpcontent);
				fputs("
", fpcontent); } fputs("
", fpcontent); fclose(fpcontent); } /* set modified and access time of file to time of item. */ parsedtime = 0; if (strtotime(fields[FieldUnixTimestamp], &parsedtime) != -1) { contenttime.actime = parsedtime; contenttime.modtime = parsedtime; utime(filepath, &contenttime); } isnew = (parsedtime >= comparetime) ? 1 : 0; totalnew += isnew; f->totalnew += isnew; f->total++; if (isnew) fputs("", fpitems); else fputs("", fpitems); fputs("\n", fpitems); } fputs("
", fpitems); fputs(fields[FieldTimeFormatted], fpitems); fputs("", fpitems); if (isnew) fputs("", fpitems); fputs("", fpitems); xmlencode(fields[FieldTitle], fpitems); fputs("", fpitems); if (isnew) fputs("", fpitems); fputs("
\n", fpitems); } int main(int argc, char *argv[]) { FILE *fpindex, *fpitems, *fpmenu, *fp; int showsidebar = (argc > 1); int i; struct feed *f; if (!(feeds = calloc(argc, sizeof(struct feed *)))) err(1, "calloc"); if ((comparetime = time(NULL)) == -1) err(1, "time"); /* 1 day is old news */ comparetime -= 86400; /* write main index page */ if (!(fpindex = fopen("index.html", "w+b"))) err(1, "fopen: index.html"); if (!(fpmenu = fopen("menu.html", "w+b"))) err(1, "fopen: menu.html"); if (!(fpitems = fopen("items.html", "w+b"))) err(1, "fopen: items.html"); fputs("" "" "
", fpitems); if (argc == 1) { if (!(feeds[0] = calloc(1, sizeof(struct feed)))) err(1, "calloc"); feeds[0]->name = ""; printfeed(fpitems, stdin, feeds[0]); } else { for (i = 1; i < argc; i++) { if (!(feeds[i - 1] = calloc(1, sizeof(struct feed)))) err(1, "calloc"); feeds[i - 1]->name = xbasename(argv[i]); if (!(fp = fopen(argv[i], "r"))) err(1, "fopen: %s", argv[i]); printfeed(fpitems, fp, feeds[i - 1]); if (ferror(fp)) err(1, "ferror: %s", argv[i]); fclose(fp); } } fputs("\n
\n", fpitems); /* div items */ if (showsidebar) { fputs("" "\n" "\n" "", fpmenu); } fputs("\n\tNewsfeed (", fpindex); fprintf(fpindex, "%lu", totalnew); fputs(")\n\t\n" "\n" "\n", fpindex); if (showsidebar) { fputs("\n" " \n", fpindex); } else { fputs("\n", fpindex); } fputs("\t\n" "\t\t\n" "\t\t\n" "\t\n" "\n" "", fpindex); fclose(fpitems); fclose(fpmenu); fclose(fpindex); return 0; }