#define __BSD_VISIBLE 1
#include <errno.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifndef __FreeBSD__
#include <bsd/stdlib.h>
#include <bsd/string.h>
#endif
// jgm/cmark
#include "config.h"
#include "memory.h"
#include "cmark.h"
#include "node.h"
// libgit2
#include "git2.h"
#include "cgi.h"
#include "html.h"
#include "vcsid.h"
__VCSID("$Id: jwiki.c 537 2016-11-08 01:37:31Z jashank $");
#define WIKI_ROOT "/web/jashankj/_wiki/"
const char *page404 =
"\n"
"You've attempted to load a nonexistent or illegal page.\n"
"Try going [home](.) instead.\n\n";
static void render_404 (cmark_parser *);
static void render_file (git_repository *, git_commit *, const char *, cmark_parser *);
static void render_git_history (git_repository *, git_commit *, cmark_parser *);
static void render_finish (const char *, cmark_parser *);
static void cgi_header (void);
static void cgi_err (int, const char *, ...);
static void cgi_verr (int, int, const char *, va_list);
#define JW_CMARK_OPTIONS \
(CMARK_OPT_DEFAULT | CMARK_OPT_SMART | CMARK_OPT_NORMALIZE)
int
main (int argc, char *argv[]) {
struct request req;
request_populate (&req);
if (req.gateway_interface == NULL)
if (argc >= 2)
req.path_info = argv[1];
cgi_header ();
cmark_parser *parser = cmark_parser_new (JW_CMARK_OPTIONS);
int error; // probably should check this sometime...
git_repository *repo;
error = git_repository_open (&repo, WIKI_ROOT);
git_object *head_commit;
error = git_revparse_single (&head_commit, repo, "HEAD^{commit}");
git_commit *commit = (git_commit *)head_commit;
char *filename = "FrontPage";
if (req.path_info != NULL &&
strcmp (req.path_info, "/_history") == 0) {
// history rendering
render_git_history (repo, commit, parser);
render_finish ("_history", parser);
return EXIT_SUCCESS;
} else if (req.path_info != NULL && strcmp (req.path_info, "/") != 0) {
// lose the leading '/'
filename = &req.path_info[1];
}
render_file (repo, commit, filename, parser);
render_finish (filename, parser);
git_commit_free (commit);
git_repository_free (repo);
return EXIT_SUCCESS;
}
static void
render_404 (cmark_parser *parser) {
cmark_parser_feed (parser, page404, strlen (page404));
return;
}
static void
render_file (git_repository *repo, git_commit *commit, const char *filename, cmark_parser *parser) {
int error;
cmark_parser_feed (parser, filename, strlen (filename));
cmark_parser_feed (parser, "\n================\n\n", 19);
git_tree *tree;
error = git_commit_tree (&tree, commit);
const git_tree_entry *entry = git_tree_entry_byname (tree, filename);
if (entry == NULL) {
render_404 (parser);
git_tree_free (tree);
return;
}
git_object *obj;
error = git_tree_entry_to_object (&obj, repo, entry);
cmark_parser_feed (
parser,
git_blob_rawcontent ((git_blob *)obj),
git_blob_rawsize ((git_blob *)obj));
return;
}
static void
render_git_history (git_repository *repo, git_commit *commit, cmark_parser *parser) {
// code borrowed from
// https://git-scm.com/book/en/v2/Embedding-Git-in-your-Applications-Libgit2
// https://libgit2.github.com/libgit2/ex/HEAD/general.html
int error;
git_revwalk *walk;
git_revwalk_new (&walk, repo);
git_revwalk_sorting (walk, GIT_SORT_TOPOLOGICAL);
git_revwalk_push (walk, git_commit_id (commit));
git_oid oid;
while (git_revwalk_next (&oid, walk) == 0) {
git_commit *commit;
error = git_commit_lookup (&commit, repo, &oid);
const git_signature *author = git_commit_author (commit);
char *parser_fodder;
time_t t_ = git_commit_time (commit);
struct tm *t = localtime (&t_);
char time_str[21] = {0,};
strftime (time_str, 20, "%Y-%m-%d %H:%M:%S", t);
#define maybe_str(v) ((v != NULL) ? (v) : "")
int len = asprintf (
&parser_fodder, commit_message,
maybe_str (author->name),
maybe_str (time_str),
maybe_str (git_oid_tostr_s (git_commit_id (commit))),
maybe_str (git_commit_summary (commit)),
maybe_str (git_commit_body (commit))
);
cmark_parser_feed (parser, parser_fodder, len);
free (parser_fodder);
git_commit_free (commit);
}
git_revwalk_free (walk);
return;
}
static void
render_finish (const char *title, cmark_parser *parser) {
cmark_node *document = cmark_parser_finish (parser);
cmark_parser_free (parser);
char *result = cmark_render_html (document, JW_CMARK_OPTIONS);
printf (page, title, result);
cmark_node_mem (document)->free (result);
cmark_node_free (document);
}
void
cgi_header (void) {
fputs (
"Content-type: text/html; charset=utf-8\r\n"
"\r\n",
stdout);
}
// cgi_err, cgi_verr derived from FreeBSD's libc
// https://svnweb.freebsd.org/base/head/lib/libc/gen/err.c
void
cgi_err (int eval, const char *fmt, ...) {
va_list ap;
va_start (ap, fmt);
cgi_verr (eval, errno, fmt, ap);
va_end (ap);
}
void
cgi_verr (int eval, int code, const char *fmt, va_list ap) {
fprintf (stderr, "%s: ", getprogname ());
if (fmt != NULL) {
vfprintf (stderr, fmt, ap);
fprintf (stderr, ": ");
}
fprintf (stderr, "%s\n", strerror (code));
exit (eval);
}