/*
* Configurable variables
*/
/* What's my blog's title? */
static const char blog_title[] = "Blosxom";
/* What's my blog's description (for outgoing RSS feed)? */
static const char blog_description[] = "Yet another Blosxom blog.";
/* What's my blog's primary language (for outgoing RSS feed)? */
static const char blog_language[] = "en";
/* Where are my blog entries kept? */
static const char datadir[] = "/Library/WebServer/Documents/blosxom";
/* How many entries should I show on the home page? */
static const int num_entries = 20;
#define MOD_BLOSXOM_STRING_VERSION "mod_blosxom/0.05"
/*
* Default HTML template */
#define TEXT_HTML_HEAD "$blog_title $blog_title
"
#define TEXT_HTML_STORY "$title $body Posted at $ti # G
\n"
#define TEXT_HTML_FOOT "
Powered -- for some narrow definition of powered -- by Blosxom "
/* Default RSS template */
#define TEXT_XML_HEAD "\n\n\n\n\n \n $blog_title \n $url\n $blog_description \n $blog_language \n"
#define TEXT_XML_STORY " - \n
$title \n $url/$yr/$mo/$da#$fn\n $body \n \n"
#define TEXT_XML_FOOT " \n "
enum {
CONTENT_TYPE_TEXT_HTML = 0,
CONTENT_TYPE_TEXT_XML = 1,
};
module blosxom_module;
struct blogfile {
char *name;
time_t mtime;
struct blogfile *next;
};
typedef struct {
char *title;
char *description;
char *language;
char *datadir;
int num_entries;
} blosxom_dir_config;
void blosxom_module_init(server_rec * s, pool * p)
{
ap_add_version_component(MOD_BLOSXOM_STRING_VERSION);
}
static void *blosxom_create_dir_config(pool * p, char *path)
{
blosxom_dir_config *cfg;
cfg = (blosxom_dir_config *) ap_pcalloc(p, sizeof(blosxom_dir_config));
cfg->title = ap_pstrdup(p, blog_title);
cfg->description = ap_pstrdup(p, blog_description);
cfg->language = ap_pstrdup(p, blog_language);
cfg->datadir = ap_pstrdup(p, datadir);
cfg->num_entries = num_entries;
return (void *) cfg;
}
static const char *blosxom_cmd_title(cmd_parms * parms, void *mconfig, char *title)
{
blosxom_dir_config *cfg;
cfg = (blosxom_dir_config *) mconfig;
cfg->title = (char *) ap_pstrdup(parms->pool, title);
return NULL;
}
static const char *blosxom_cmd_description(cmd_parms * parms, void *mconfig, char *desc)
{
blosxom_dir_config *cfg;
cfg = (blosxom_dir_config *) mconfig;
cfg->description = (char *) ap_pstrdup(parms->pool, desc);
return NULL;
}
static const char *blosxom_cmd_language(cmd_parms * parms, void *mconfig, char *lang)
{
blosxom_dir_config *cfg;
cfg = (blosxom_dir_config *) mconfig;
cfg->language = (char *) ap_pstrdup(parms->pool, lang);
return NULL;
}
static const char *blosxom_cmd_datadir(cmd_parms * parms, void *mconfig, char *path)
{
blosxom_dir_config *cfg;
cfg = (blosxom_dir_config *) mconfig;
cfg->datadir = (char *) ap_pstrdup(parms->pool, path);
return NULL;
}
static const char *blosxom_cmd_entries(cmd_parms * parms, void *mconfig, int max)
{
blosxom_dir_config *cfg;
cfg = (blosxom_dir_config *) mconfig;
cfg->num_entries = max;
return NULL;
}
static const command_rec blosxom_cmds[] =
{
{
"BlogTitle",
blosxom_cmd_title,
NULL,
OR_ALL,
TAKE1,
"What's my blog's title? default 'Blosxom'"
},
{
"BlogDescription",
blosxom_cmd_description,
NULL,
OR_ALL,
TAKE1,
"What's my blog's description (for outgoing RSS feed)?"
},
{
"BlogLanguage",
blosxom_cmd_language,
NULL,
OR_ALL,
TAKE1,
"Where are my blog entries kept?"
},
{
"BlogDatadir",
blosxom_cmd_datadir,
NULL,
OR_ALL,
TAKE1,
"Where are my blog entries kept?"
},
{
"BlogEntries",
blosxom_cmd_entries,
NULL,
OR_ALL,
TAKE1,
"How many entries should I show on the home page?"
},
{NULL}
};
static int is_want_file(struct dirent * node, char *file_exp)
{
char *exp_name;
if (node->d_type != DT_REG)
return -1;
exp_name = strrchr(node->d_name, '.');
if (exp_name == NULL || exp_name <= node->d_name
|| strncmp(file_exp, exp_name, strlen(file_exp) + 1) != 0) {
return -1;
}
return 0;
}
/*
* Compare the struct 'blogfile' by mtime. This function is used by qsort().
*/
static int mtime_revcmp(const void *p1, const void *p2)
{
struct blogfile **e1, **e2;
e1 = (struct blogfile **) p1;
e2 = (struct blogfile **) p2;
if ((*e2)->mtime < (*e1)->mtime)
return -1;
if ((*e2)->mtime > (*e1)->mtime)
return 1;
else
return 0;
}
static struct blogfile **create_blog_list(request_rec * r, char *path)
{
DIR *dh;
struct dirent *node;
struct stat filest;
struct blogfile *entry, *head;
struct blogfile **ar = NULL;
char *full_path;
int i, j;
int last = 0;
int count = 0;
dh = ap_popendir(r->pool, path);
if (dh == NULL) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Can't open Blog directory");
return NULL;
}
head = NULL;
i = 0;
while ((node = readdir(dh)) != NULL) {
if (is_want_file(node, ".txt") != 0)
continue;
full_path = ap_make_full_path(r->pool, path, node->d_name);
if (stat(full_path, &filest) < 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "stat");
continue;
}
entry = (struct blogfile *) ap_pcalloc(r->pool, sizeof(struct blogfile));
entry->mtime = filest.st_mtime;
entry->name = ap_pstrdup(r->pool, full_path);
entry->next = head;
head = entry;
++i;
}
ap_pclosedir(r->pool, dh);
entry = head;
ar = (struct blogfile **) ap_palloc(r->pool,
sizeof(struct blogfile *) * (i + 1));
j = 0;
while (entry) {
ar[j++] = entry;
entry = entry->next;
}
qsort(ar, i, sizeof(struct blogfile *), mtime_revcmp);
ar[j] = NULL;
return ar;
}
static void create_and_send_template(request_rec * r, char *template, table *t)
{
char *pos;
int max = strlen(template) + 1;
char *head;
for (pos = template; *pos != '\0'; pos++) {
char *key;
char *value;
if (max < (pos + 2) - template) {
ap_rputs(pos, r);
break;
}
if (*pos == '$') {
pos += 1;
head = pos;
for (; *pos != '\0' && ap_isalpha(*pos) || *pos == '_'; pos++);
key = ap_pstrndup(r->pool, head, pos - head);
key[pos - head] = '\0';
value = (char *)ap_table_get(t, key);
if (value != NULL)
ap_rputs(value, r);
pos--;
}
else {
ap_rprintf(r, "%c", *pos);
}
}
}
static char *load_template(request_rec * r, char *path)
{
FILE *fh;
char *body;
char buff[IOBUFSIZE];
if (!(fh = ap_pfopen(r->pool, path, "r"))) {
return NULL;
}
body = ap_pstrdup(r->pool, "");
while (fgets(buff, sizeof(buff), fh) != NULL) {
body = (char *) ap_pstrcat(r->pool, body, buff, NULL);
}
ap_pfclose(r->pool, fh);
return body;
}
static char *each_sub_blog(request_rec *r, char *path_info)
{
char *head, *subblog;
if (*path_info != '/')
return NULL;
++path_info;
if (!ap_isalpha(*path_info))
return NULL;
subblog = ap_pstrdup(r->pool, path_info);
head = subblog;
for (; *subblog != '\0'; subblog++) {
if (*subblog == '/') {
*subblog = '\0';
break;
}
else if (!ap_isalnum(*subblog) && *subblog != '_') {
return NULL;
}
}
return head;
}
static int blosxom_handler(request_rec * r)
{
struct blogfile **list;
char buff[IOBUFSIZE];
FILE *bodyh;
DIR *open_test;
char *title, *body, *uri;
blosxom_dir_config *cfg;
table *template;
char *pi_yr, *pi_mo, *pi_da, *id, *pi;
char *p;
char *user_t;
char *curdate, *lastdate;
int max_ent;
char *search_xml;
short content_type = CONTENT_TYPE_TEXT_HTML;
char *subblog, *data_dir;
int rc; /* Return Code, use by meets_conditions() */
cfg = (blosxom_dir_config *) ap_get_module_config(r->per_dir_config,
&blosxom_module);
/*
* Take a gander at HTTP's PATH_INFO for optional blog name, archive
* yr/mo/day
*/
pi = ap_pstrdup(r->pool, r->path_info);
search_xml = strrchr(pi, '/');
if (search_xml && strcmp(search_xml, "/xml") == 0) {
*search_xml = '\0';
content_type = CONTENT_TYPE_TEXT_XML;
}
/* Check the existence and readability of a specified sub-blog */
p = pi;
subblog = each_sub_blog(r, p);
if (subblog != NULL)
p += strlen(subblog);
data_dir = ap_make_full_path(r->pool, cfg->datadir, subblog);
(void) ap_getword(r->pool, &p, '/');
pi_yr = ap_getword(r->pool, &p, '/');
pi_mo = ap_getword(r->pool, &p, '/');
pi_da = ap_getword(r->pool, &p, '/');
/* Bring in the templates */
open_test = ap_popendir(r->pool, data_dir);
if (open_test == NULL) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Can't open Blog directory");
return HTTP_INTERNAL_SERVER_ERROR;
}
ap_pclosedir(r->pool, open_test);
if (content_type == CONTENT_TYPE_TEXT_HTML)
r->content_type = "text/html";
else if (content_type == CONTENT_TYPE_TEXT_XML)
r->content_type = "text/xml";
else
return HTTP_FORBIDDEN;
ap_soft_timeout("send blosxom call trace", r);
/* Inspection and response of a Conditional GET request */
list = create_blog_list(r, data_dir);
if (list != NULL) {
ap_update_mtime(r, (*list)->mtime);
ap_set_last_modified(r);
ap_set_etag(r);
}
rc = ap_meets_conditions(r);
if (rc != OK) {
ap_kill_timeout(r);
return rc;
}
ap_send_http_header(r);
if (r->header_only) {
ap_kill_timeout(r);
return OK;
}
template = ap_make_table(r->pool, 12);
ap_table_set(template, "blog_title", cfg->title);
ap_table_set(template, "blog_description", cfg->description);
ap_table_set(template, "blog_language", cfg->language);
uri = ap_pstrcat(r->pool, r->uri);
uri[strlen(uri) - strlen(r->path_info)] = '\0';
if (subblog != NULL)
uri = ap_make_full_path(r->pool, uri, subblog);
uri = ap_construct_url(r->pool, uri, r);
ap_table_set(template, "url", uri);
if (content_type == CONTENT_TYPE_TEXT_HTML) {
user_t = load_template(r, ap_make_full_path(r->pool,
data_dir, "head.html"));
if (user_t == NULL)
create_and_send_template(r, ap_pstrdup(r->pool,
TEXT_HTML_HEAD), template);
else
create_and_send_template(r, user_t, template);
user_t = load_template(r, ap_make_full_path(r->pool,
data_dir, "story.html"));
} else {
create_and_send_template(r, ap_pstrdup(r->pool,
TEXT_XML_HEAD), template);
user_t = ap_pstrdup(r->pool, TEXT_XML_STORY);
}
/* Send in the blogs */
max_ent = cfg->num_entries;
lastdate = "";
for (; (*list) != NULL && list != NULL; list++) {
char *fn, *yr, *mo, *da, *ti;
char *pos;
int max;
max_ent--;
if (max_ent < 0 && strlen(pi_yr) == 0)
break;
fn = strrchr((*list)->name, '/');
if (fn != NULL) {
++fn;
ap_table_set(template, "fn", ap_getword(r->pool, &fn, '.'));
}
/* Date fiddling for by-{year,month,day} archive views */
ti = ap_ht_time(r->pool, (*list)->mtime, "%H:%M", 0);
yr = ap_ht_time(r->pool, (*list)->mtime, "%Y", 0);
mo = ap_ht_time(r->pool, (*list)->mtime, "%b", 0);
da = ap_ht_time(r->pool, (*list)->mtime, "%d", 0);
if (strlen(pi_yr) > 0 && strcmp(pi_yr, yr) != 0)
continue;
if (strlen(pi_yr) > 0 && atoi(yr) < atoi(pi_yr))
break;
if (strlen(pi_mo) > 0 && strcmp(mo, pi_mo) != 0)
continue;
if (strlen(pi_da) > 0 && strcmp(da, pi_da) != 0)
continue;
if (strlen(pi_da) > 0 && atoi(da) < atoi(pi_da))
break;
ap_table_set(template, "ti", ti);
ap_table_set(template, "yr", yr);
ap_table_set(template, "mo", mo);
ap_table_set(template, "da", da);
curdate = ap_ht_time(r->pool, (*list)->mtime, "%a, %d %b %Y", 0);
if (content_type == CONTENT_TYPE_TEXT_HTML && strcmp(curdate, lastdate) != 0) {
ap_rprintf(r, "%s \n", curdate);
lastdate = curdate;
}
/* Entry */
if (!(bodyh = ap_pfopen(r->pool, (*list)->name, "r"))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "ap_pfopen");
continue;
}
fgets(buff, sizeof(buff), bodyh);
title = ap_pstrdup(r->pool, buff);
title[strlen(title) - 1] = '\0';
ap_table_set(template, "title", title);
body = ap_pstrdup(r->pool, "");
while (fgets(buff, sizeof(buff), bodyh) != NULL) {
body = (char *) ap_pstrcat(r->pool, body, buff, NULL);
}
ap_table_set(template, "body", body);
if (content_type == CONTENT_TYPE_TEXT_XML) {
ap_table_set(template, "title", ap_escape_html(r->pool, title));
ap_table_set(template, "body", ap_escape_html(r->pool, body));
}
if (user_t == NULL)
create_and_send_template(r,ap_pstrdup(r->pool, TEXT_HTML_STORY), template);
else
create_and_send_template(r, user_t, template);
ap_pfclose(r->pool, bodyh);
}
/* Foot */
if (content_type == CONTENT_TYPE_TEXT_HTML) {
user_t = load_template(r, ap_make_full_path(r->pool, data_dir, "foot.html"));
if (user_t == NULL)
create_and_send_template(r, TEXT_HTML_FOOT, template);
else
create_and_send_template(r, user_t, template);
} else {
create_and_send_template(r, TEXT_XML_FOOT, template);
}
ap_kill_timeout(r);
return OK;
}
static const handler_rec blosxom_handlers[] =
{
{"blosxom-handler", blosxom_handler},
{NULL}
};
module blosxom_module =
{
STANDARD_MODULE_STUFF,
blosxom_module_init, /* module initializer */
blosxom_create_dir_config, /* per-directory config creator */
NULL, /* dir config merger */
NULL, /* server config creator */
NULL, /* server config merger */
blosxom_cmds, /* command table */
blosxom_handlers, /* [7] list of handlers */
NULL, /* [2] filename-to-URI translation */
NULL, /* [5] check/validate user_id */
NULL, /* [6] check user_id is valid *here* */
NULL, /* [4] check access by host address */
NULL, /* [7] MIME type checker/setter */
NULL, /* [8] fixups */
NULL, /* [10] logger */
NULL, /* [3] header parser */
NULL, /* process initializer */
NULL, /* process exit/cleanup */
NULL, /* [1] post read_request handling */
};