/* ** mod_blosxom.c -- Apache blosxom module ** [Autogenerated via ``apxs -n blosxom -g''] ** ** To play with this blosxom module, first compile it into a ** DSO file and install it into Apache's libexec directory ** by running: ** ** $ apxs -c -i mod_blosxom.c ** ** Then activate it in Apache's httpd.conf file, for instance ** for the URL /blosxom, as follows: ** ** # httpd.conf ** LoadModule blosxom_module libexec/mod_blosxom.so ** ** SetHandler blosxom-handler ** BlogTitle "Blosxom" ** BlogDescriptioni "Yet another Blosxom blog." ** BlogLanguage en ** BlogDatadir /Library/WebServer/Documents/blosxom ** BlogEntries 20 ** ** ** Then after restarting Apache via ** ** $ apachectl restart ** ** you immediately can request the URL /%NAME and watch for the ** output of this module. This can be achieved for instance via: ** ** $ lynx -mime_header http://localhost/blosxom ** ** The output should be similar to the following one: ** ** HTTP/1.1 200 OK ** Date: Tue, 31 Mar 1998 14:42:22 GMT ** Server: Apache/1.3.4 (Unix) ** Connection: close ** Content-Type: text/html ** ** The sample page from mod_blosxom.c ** ** ** ** ** Blosxom (Original) ** Author: Rael Drnfest ** Home/Docs/Licensing: http://www.oreillynet.com/~rael/lang/perl/blosxom/ ** ** mod_blosxom.c (Rewriting by Apache API) ** Author: OYAMA Hiroyuki ** */ #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "util_script.h" #include #include #include /* * 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 */ };