/*********************************************** * * Copyright 2001 by Sean Conner. All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Comments, questions and criticisms can be sent to: sean@conman.org * ************************************************/ #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <gdbm.h> #include <syslog.h> #include <cgilib7/conf.h> #include <cgilib7/htmltok.h> #include <cgilib7/chunk.h> #include <cgilib7/util.h> #include "backend.h" #include "blogutil.h" #include "conversion.h" /*****************************************************************/ static void tm_init(struct tm *ptm) { assert(ptm != NULL); memset(ptm,0,sizeof(struct tm)); ptm->tm_isdst = -1; } /*********************************************************************/ static void print_nav_name(FILE *out,struct btm const *date,unit__e unit,char sep) { assert(out != NULL); assert(date != NULL); assert(sep >= ' '); assert(sep <= '~'); switch(unit) { case UNIT_YEAR: fprintf(out,"%04d",date->year); break; case UNIT_MONTH: fprintf(out,"%04d%c%02d",date->year,sep,date->month); break; case UNIT_DAY: fprintf(out,"%04d%c%02d%c%02d",date->year,sep,date->month,sep,date->day); break; case UNIT_PART: fprintf(out,"%04d%c%02d%c%02d.%d",date->year,sep,date->month,sep,date->day,date->part); break; default: assert(0); break; } } /********************************************************************/ static void print_nav_url(FILE *out,struct btm const *date,unit__e unit,char const *baseurl) { assert(out != NULL); assert(date != NULL); fprintf(out,"%s",baseurl); print_nav_name(out,date,unit,'/'); } /*******************************************************************/ static void print_nav_title(FILE *out,Blog *blog,struct btm const *date,unit__e unit) { BlogEntry *entry; struct tm stm; char buffer[BUFSIZ]; assert(out != NULL); assert(date != NULL); switch(unit) { case UNIT_YEAR: fprintf(out,"%04d",date->year); break; case UNIT_MONTH: tm_init(&stm); stm.tm_year = date->year - 1900; stm.tm_mon = date->month; mktime(&stm); strftime(buffer,sizeof(buffer),"%B %Y",&stm); fputs(buffer,out); break; case UNIT_DAY: tm_init(&stm); stm.tm_year = date->year - 1900; stm.tm_mon = date->month - 1; stm.tm_mday = date->day; mktime(&stm); strftime(buffer,sizeof(buffer),"%A, %B %d, %Y",&stm); fputs(buffer,out); break; case UNIT_PART: entry = BlogEntryRead(blog,date); if (entry) { assert(entry->valid); fputs(entry->title,out); BlogEntryFree(entry); } break; default: assert(0); break; } } /**********************************************************************/ static void handle_aflinks(HtmlToken token,char const *attrib,Blog *blog) { assert(token != NULL); assert(attrib != NULL); struct pair *src = HtmlParseGetPair(token,attrib); if (src != NULL) { for (size_t i = 0 ; i < blog->config.affiliatenum ; i++) { if (strncmp(src->value,blog->config.affiliates[i].proto,blog->config.affiliates[i].psize) == 0) { char buffer[BUFSIZ]; struct pair *np; snprintf( buffer, sizeof(buffer), blog->config.affiliates[i].format, &src->value[blog->config.affiliates[i].psize + 1] ); np = PairCreate(attrib,buffer); NodeInsert(&src->node,&np->node); NodeRemove(&src->node); PairFree(src); return; } } } } /*************************************************************************/ static bool uri_scheme(char const *s) { if (!isalpha(*s++)) return false; while(*s) { if (*s == ':') return true; if (isalpha(*s) || isdigit(*s) || (*s == '+') || (*s == '-') || (*s == '.')) s++; else return false; } return false; } /*************************************************************************/ static void fixup_uri(BlogEntry *entry,HtmlToken token,char const *attrib,Blog *blog,Request const *request) { struct pair *src; struct pair *np; assert(entry != NULL); assert(entry->valid); assert(token != NULL); assert(attrib != NULL); handle_aflinks(token,attrib,blog); src = HtmlParseGetPair(token,attrib); if ((src != NULL) && !uri_scheme(src->value)) { char buffer[BUFSIZ]; char const *baseurl; /*--------------------------------------------------------------- ; If pointing to an anchor point, no further processing required ;----------------------------------------------------------------*/ if (src->value[0] == '#') return; /*----------------------------------------------------- ; Which URL to use? Full or partial? ;-----------------------------------------------------*/ if (request->f.fullurl) baseurl = blog->config.url; else baseurl = blog->config.baseurl; /*----------------------------------------------------------------------- ; all this to reassign the value, without ``knowing'' how the pair stuff ; actually works. Perhaps add a PairReValue() call or something? ; ; Anyway, check to see if baseurl ends in a '/', and if not, we then add ; one, otherwise, don't. ; ; If baseurl ends in a '/', and src->value does not start with a '/', we ; end up with a double '//'. We need to make sure we canonicalize all ; partial urls to either end in a '/', or not end in a '/'. ;------------------------------------------------------------------------*/ if (src->value[0] == '/') snprintf(buffer,sizeof(buffer),"%s%s",baseurl,&src->value[1]); else if (isdigit(src->value[0])) snprintf(buffer,sizeof(buffer),"%s%s",baseurl,src->value); else snprintf( buffer, sizeof(buffer), "%s%04d/%02d/%02d/%s", baseurl, entry->when.year, entry->when.month, entry->when.day, src->value ); np = PairCreate(attrib,buffer); NodeInsert(&src->node,&np->node); NodeRemove(&src->node); PairFree(src); } } /*************************************************************** * TEMPLATE CALL BACK FUNCTIONS ****************************************************************/ static void cb_ad(FILE *out,void *data) { struct callback_data *cbd = data; char fname[FILENAME_MAX]; assert(out != NULL); assert(data != NULL); assert(cbd->entry->valid); snprintf( fname, sizeof(fname), "%04d/%02d/%02d/%d.ad", cbd->entry->when.year, cbd->entry->when.month, cbd->entry->when.day, cbd->entry->when.part ); cbd->ad = fopen(fname,"r"); if (cbd->ad == NULL) return; generic_cb("ad",out,data); fclose(cbd->ad); cbd->ad = NULL; } /**********************************************************************/ static void cb_ad_content(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); /*------------------------------------- ; we might also do a generic_cb() here ;--------------------------------------*/ fcopy(out,cbd->ad); } /*********************************************************************/ static void cb_atom_categories(FILE *out,void *data) { struct callback_data *cbd = data; String *cats; size_t num; assert(out != NULL); assert(data != NULL); cats = tag_split(&num,cbd->entry->class); for (size_t i = 0 ; i < num ; i++) { cbd->adcat = fromstring(cats[i]); generic_cb("categories",out,cbd); free(cbd->adcat); cbd->adcat = NULL; } free(cats); } /************************************************************************/ static void cb_atom_category(FILE *out,void *data) { struct callback_data *cbd = data; FILE *eout; assert(out != NULL); assert(data != NULL); eout = fentity_encode_onwrite(out); if (eout == NULL) return; fputs(cbd->adcat,eout); fclose(eout); } /****************************************************************/ static void cb_atom_entry(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); /*---------------------------------------------------------------------- ; XXX---this code is identical to cb_rss_item(), except for which template ; is being used. Could possibly use some refactoring here ... ;-----------------------------------------------------------------------*/ for ( entry = (BlogEntry *)ListRemHead(&cbd->list); NodeValid(&entry->node); entry = (BlogEntry *)ListRemHead(&cbd->list) ) { assert(entry->valid); cbd->entry = entry; generic_cb("entry",out,data); cbd->last = entry->when; BlogEntryFree(entry); } cbd->entry = NULL; } /************************************************************************/ static void cb_begin_year(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fprintf(out,"%04d",cbd->blog->first.year); } /*******************************************************************/ static void cb_blog_adtag(FILE *out,void *data) { struct callback_data *cbd = data; char *tag; assert(out != NULL); assert(data != NULL); if (cbd->adtag == NULL) tag = UrlEncodeString(cbd->blog->config.adtag); else tag = UrlEncodeString(cbd->adtag); fputs(tag,out); free(tag); } /*********************************************************************/ static void cb_blog_adtag_entity(FILE *out,void *data) { struct callback_data *cbd = data; FILE *entityout; char const *tag; assert(out != NULL); assert(data != NULL); if (cbd->adtag == NULL) tag = cbd->blog->config.adtag; else tag = cbd->adtag; entityout = fentity_encode_onwrite(out); if (entityout == NULL) return; fputs(tag,entityout); fclose(entityout); } /********************************************************************/ static void cb_blog_author(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->blog->config.author.name,out); } /********************************************************************/ static void cb_blog_author_email(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->blog->config.author.email,out); } /**********************************************************************/ static void cb_blog_class(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->blog->config.class,out); } /**********************************************************************/ static void cb_blog_description(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->blog->config.description,out); } /*********************************************************************/ static void cb_blog_name(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->blog->config.name,out); } /*********************************************************************/ static void cb_blog_script(FILE *out,void *data) { char *script; assert(out != NULL); (void)data; script = getenv("SCRIPT_NAME"); if (script) fputs(script,out); } /**********************************************************************/ static void cb_blog_title(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->navunit == UNIT_PART) { BlogEntry *entry = (BlogEntry *)ListGetHead(&cbd->list); if (NodeValid(&entry->node) && entry->valid) fprintf(out,"%s - ",entry->title); } fputs(cbd->blog->config.name,out); } /*********************************************************************/ static void cb_blog_url(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); print_nav_url(out,&cbd->entry->when,UNIT_DAY,cbd->blog->config.baseurl); } /*************************************************************************/ static void cb_blog_url_base(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->blog->config.baseurl,out); } /*********************************************************************/ static void cb_blog_url_home(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->blog->config.url,out); } /**********************************************************************/ static void cb_comments(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->navunit != UNIT_PART) return; generic_cb("comments",out,data); } /*******************************************************************/ static void cb_comments_body(FILE *out,void *data) { struct callback_data *cbd = data; FILE *in; char fname[FILENAME_MAX]; assert(out != NULL); assert(data != NULL); snprintf( fname, sizeof(fname), "%04d/%02d/%02d/%d.comments", cbd->entry->when.year, cbd->entry->when.month, cbd->entry->when.day, cbd->entry->when.part ); in = fopen(fname,"r"); if (in == NULL) return; fcopy(out,in); fclose(in); } /*******************************************************************/ static void cb_comments_check(FILE *out,void *data) { struct callback_data *cbd = data; char fname[BUFSIZ]; assert(out != NULL); assert(data != NULL); snprintf( fname, sizeof(fname), "%04d/%02d/%02d/%d.comments", cbd->entry->when.year, cbd->entry->when.month, cbd->entry->when.day, cbd->entry->when.part ); if (access(fname,R_OK) < 0) fputs("No ",out); fputs("Comments",out); } /********************************************************************/ static void cb_comments_filename(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fprintf( out, "%04d/%02d/%02d/%d.comments", cbd->entry->when.year, cbd->entry->when.month, cbd->entry->when.day, cbd->entry->when.part ); } /*******************************************************************/ static void cb_cond_hr(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navigation && (cbd->navunit == UNIT_PART)) return; entry = cbd->entry; assert(entry->valid); if (btm_cmp_date(&entry->when,&cbd->last) == 0) { if (entry->when.part != cbd->last.part) generic_cb("cond.hr",out,data); } } /*********************************************************************/ static void cb_date_day(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); print_nav_name(out,&entry->when,UNIT_DAY,'-'); } /*************************************************************************/ static void cb_date_day_normal(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; struct tm day; char buffer[BUFSIZ]; assert(out != NULL); assert(data != NULL); tm_init(&day); entry = cbd->entry; assert(entry->valid); day.tm_year = entry->when.year - 1900; day.tm_mon = entry->when.month - 1; day.tm_mday = entry->when.day; mktime(&day); strftime(buffer,sizeof(buffer),"%A, %B %d, %Y",&day); fputs(buffer,out); } /**********************************************************************/ static void cb_date_day_url(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); print_nav_url(out,&entry->when,UNIT_DAY,cbd->blog->config.baseurl); } /********************************************************************/ static void cb_edit(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.edit == false) return; generic_cb("edit",out,data); } /*********************************************************************/ static void cb_edit_adtag(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->adtag != NULL) fputs(cbd->request->adtag,out); } /*********************************************************************/ static void cb_edit_author(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->origauthor != NULL) fputs(cbd->request->origauthor,out); else { char *name = get_remote_user(); fputs(name,out); free(name); } } /********************************************************************/ static void cb_edit_body(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->origbody) fputs(cbd->request->origbody,out); } /********************************************************************/ static void cb_edit_class(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->class) fputs(cbd->request->class,out); } /********************************************************************/ static void cb_edit_date(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->date != NULL) fputs(cbd->request->date,out); else { char buffer[BUFSIZ]; time_t now = time(NULL); struct tm *ptm = localtime(&now); strftime(buffer,sizeof(buffer),"%Y/%m/%d",ptm); fputs(buffer,out); } } /********************************************************************/ static void cb_edit_status(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->status != NULL) fputs(cbd->request->status,out); } /**********************************************************************/ static void cb_edit_title(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->title != NULL) fputs(cbd->request->title,out); } /********************************************************************/ static void cb_entry(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); if (cbd->request->f.htmldump) { fcopy(out,stdin); return; } for ( entry = (BlogEntry *)ListRemHead(&cbd->list); NodeValid(&entry->node); entry = (BlogEntry *)ListRemHead(&cbd->list) ) { assert(entry->valid); cbd->entry = entry; generic_cb("entry",out,data); cbd->last = entry->when; BlogEntryFree(entry); } cbd->entry = NULL; } /**********************************************************************/ static void cb_entry_author(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); assert(cbd->entry->valid); fputs(cbd->entry->author,out); } /*************************************************************************/ static void cb_entry_body(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; FILE *in; HtmlToken token; int t; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); in = fmemopen(entry->body,strlen(entry->body),"r"); if (in == NULL) return; token = HtmlParseNew(in); if (token == NULL) { fclose(in); return; } while((t = HtmlParseNext(token)) != T_EOF) { if (t == T_TAG) { /*--------------------------------------------- ; checked in order of *my* usage. ;----------------------------------------------*/ if (strcmp(HtmlParseValue(token),"A") == 0) fixup_uri(entry,token,"HREF",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"BLOCKQUOTE") == 0) fixup_uri(entry,token,"CITE",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"IMG") == 0) { fixup_uri(entry,token,"SRC",cbd->blog,cbd->request); fixup_uri(entry,token,"LONGDESC",cbd->blog,cbd->request); fixup_uri(entry,token,"USEMAP",cbd->blog,cbd->request); } else if (strcmp(HtmlParseValue(token),"Q") == 0) fixup_uri(entry,token,"CITE",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"INS") == 0) fixup_uri(entry,token,"CITE",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"DEL") == 0) fixup_uri(entry,token,"CITE",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"FORM") == 0) fixup_uri(entry,token,"ACTION",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"INPUT") == 0) { fixup_uri(entry,token,"SRC",cbd->blog,cbd->request); fixup_uri(entry,token,"USEMAP",cbd->blog,cbd->request); } else if (strcmp(HtmlParseValue(token),"AREA") == 0) fixup_uri(entry,token,"HREF",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"OBJECT") == 0) { fixup_uri(entry,token,"CLASSID",cbd->blog,cbd->request); fixup_uri(entry,token,"CODEBASE",cbd->blog,cbd->request); fixup_uri(entry,token,"DATA",cbd->blog,cbd->request); fixup_uri(entry,token,"ARCHIVE",cbd->blog,cbd->request); fixup_uri(entry,token,"USEMAP",cbd->blog,cbd->request); } /*----------------------------------------------------- ; elements that can only appear in the <HEAD> section ; so commented out for now but here for reference ;------------------------------------------------------*/ #if 0 else if (strcmp(HtmlParseValue(token),"LINK") == 0) fixup_uri(entry,token,"HREF",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"BASE") == 0) fixup_uri(entry,token,"HREF",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"HEAD") == 0) fixup_uri(entry,token,"PROFILE",cbd->blog,cbd->request); else if (strcmp(HtmlParseValue(token),"SCRIPT") == 0) { fixup_uri(entry,token,"SRC",cbd->blog,cbd->request); fixup_uri(entry,token,"FOR",cbd->blog,cbd->request); } #endif HtmlParsePrintTag(token,out); } else if (t == T_STRING) fputs(HtmlParseValue(token),out); else if (t == T_COMMENT) fprintf(out,"<!%s>",HtmlParseValue(token)); } HtmlParseFree(token); fclose(in); } /**********************************************************************/ static void cb_entry_body_entified(FILE *out,void *data) { assert(out != NULL); assert(data != NULL); FILE *eout = fentity_encode_onwrite(out); if (eout == NULL) return; cb_entry_body(eout,data); fclose(eout); } /*********************************************************************/ static void cb_entry_body_jsonified(FILE *out,void *data) { assert(out != NULL); assert(data != NULL); FILE *jout = fjson_encode_onwrite(out); if (jout == NULL) return; cb_entry_body(jout,data); fclose(jout); } /**********************************************************************/ static void cb_entry_class(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); char const *msg = cbd->blog->config.class; if (cbd->entry != NULL) msg = cbd->entry->class; else { if (cbd->navunit == UNIT_PART) { BlogEntry *entry = (BlogEntry *)ListGetHead(&cbd->list); if (NodeValid(&entry->node) && entry->valid) if (!empty_string(entry->class)) msg = entry->class; } } fputs(msg,out); } /***********************************************************************/ static void cb_entry_class_jsonified(FILE *out,void *data) { struct callback_data *cbd = data; String *cats; size_t num; assert(out != NULL); assert(data != NULL); cats = tag_split(&num,cbd->entry->class); for (size_t i = 0 ; i < num ; i++) { fputc('"',out); FILE *jout = fjson_encode_onwrite(out); if (jout) { fwrite(cats[i].d,cats[i].s,1,jout); fclose(jout); } fputc('"',out); if (i < num - 1) fputc(',',out); } free(cats); } /***********************************************************************/ static void cb_entry_cond_author(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); assert(cbd->entry->valid); if (strcmp(cbd->blog->config.author.name,cbd->entry->author) != 0) generic_cb("entry.cond.author",out,data); } /*********************************************************************/ static void cb_entry_cond_date(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); if (btm_cmp_date(&entry->when,&cbd->last) != 0) generic_cb("entry.cond.date",out,data); } /**********************************************************************/ static void cb_entry_date(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); print_nav_url(out,&cbd->entry->when,UNIT_DAY,cbd->blog->config.baseurl); } /*********************************************************************/ static void cb_entry_description(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); char const *msg = cbd->blog->config.description; if (cbd->navunit == UNIT_PART) { BlogEntry *entry = (BlogEntry *)ListGetHead(&cbd->list); if (NodeValid(&entry->node) && entry->valid) { if (!empty_string(entry->status)) msg = entry->status; else if (!empty_string(entry->title)) msg = entry->title; } } fputs(msg,out); } /*********************************************************************/ static void cb_entry_id(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); print_nav_name(out,&entry->when,UNIT_DAY,'-'); } /**********************************************************************/ static void cb_entry_linkdate(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); print_nav_name(out,&entry->when,UNIT_PART,'/'); } /**********************************************************************/ static void cb_entry_linkdated(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); print_nav_name(out,&entry->when,UNIT_DAY,'-'); } /**********************************************************************/ static void cb_entry_name(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); print_nav_name(out,&entry->when,UNIT_PART,'-'); } /***********************************************************************/ static void cb_entry_path(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); print_nav_name(out,&entry->when,UNIT_PART,'/'); } /**********************************************************************/ static void cb_entry_pubdate(FILE *out,void *data) { struct callback_data *cbd = data; char buffer[12]; assert(out != NULL); assert(data != NULL); assert(cbd->entry->valid); strftime(buffer,sizeof(buffer),"%F",localtime(&cbd->entry->timestamp)); fputs(buffer,out); } /*********************************************************************/ static void cb_entry_pubdatetime(FILE *out,void *data) { struct callback_data *cbd = data; char buffer[24]; assert(out != NULL); assert(data != NULL); assert(cbd->entry->valid); strftime(buffer,sizeof(buffer),"%FT%TZ",gmtime(&cbd->entry->timestamp)); fputs(buffer,out); } /********************************************************************/ static void cb_entry_status(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->entry->status,out); } /************************************************************************/ static void cb_entry_title(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); assert(cbd->entry->valid); fputs(cbd->entry->title,out); } /***********************************************************************/ static void cb_entry_url(FILE *out,void *data) { struct callback_data *cbd = data; BlogEntry *entry; assert(out != NULL); assert(data != NULL); entry = cbd->entry; assert(entry->valid); print_nav_url(out,&entry->when,UNIT_PART,cbd->blog->config.baseurl); } /**********************************************************************/ static void cb_generator(FILE *out,void *data) { assert(out != NULL); (void)data; fprintf( out, "mod_blog %s, %s, %s, %s" "", PROG_VERSION, cgilib_version, LUA_RELEASE, gdbm_version ); } /*********************************************************************/ static void cb_json_item(FILE *out,void *data) { struct callback_data *cbd = data; char const *sep = ""; assert(out != NULL); assert(data != NULL); for ( BlogEntry *entry = (BlogEntry *)ListRemHead(&cbd->list); NodeValid(&entry->node); entry = (BlogEntry *)ListRemHead(&cbd->list) ) { assert(entry->valid); cbd->entry = entry; fputs(sep,out); generic_cb("item",out,data); cbd->last = entry->when; BlogEntryFree(entry); sep = ","; } cbd->entry = NULL; } /*********************************************************************/ static void cb_navigation_bar(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navigation == false) return; generic_cb("navigation.bar",out,data); } /*******************************************************************/ static void cb_navigation_bar_next(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navnext == false) return; generic_cb("navigation.bar.next",out,data); } /*******************************************************************/ static void cb_navigation_bar_prev(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navprev == false) return; generic_cb("navigation.bar.prev",out,data); } /*******************************************************************/ static void cb_navigation_current(FILE *out,void *data) { assert(out != NULL); generic_cb("navigation.current",out,data); } /********************************************************************/ static void cb_navigation_current_url(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); print_nav_url(out,&cbd->blog->last,UNIT_MONTH,cbd->blog->config.baseurl); } /*********************************************************************/ static void cb_navigation_first_title(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navigation == false) print_nav_title(out,cbd->blog,&cbd->blog->first,UNIT_PART); else print_nav_title(out,cbd->blog,&cbd->blog->first,cbd->navunit); } /*******************************************************************/ static void cb_navigation_first_url(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navigation == false) print_nav_url(out,&cbd->blog->first,UNIT_PART,cbd->blog->config.baseurl); else print_nav_url(out,&cbd->blog->first,cbd->navunit,cbd->blog->config.baseurl); } /********************************************************************/ static void cb_navigation_last_title(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navigation == false) print_nav_title(out,cbd->blog,&cbd->blog->last,UNIT_PART); else print_nav_title(out,cbd->blog,&cbd->blog->last,cbd->navunit); } /*****************************************************************/ static void cb_navigation_last_url(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navigation == false) print_nav_url(out,&cbd->blog->last,UNIT_PART,cbd->blog->config.baseurl); else print_nav_url(out,&cbd->blog->last,cbd->navunit,cbd->blog->config.baseurl); } /******************************************************************/ static void cb_navigation_link(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navigation == false) return; generic_cb("navigation.link",out,data); } /********************************************************************/ static void cb_navigation_link_next(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navnext == false) return; generic_cb("navigation.link.next",out,data); } /*******************************************************************/ static void cb_navigation_link_prev(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->request->f.navprev == false) return; generic_cb("navigation.link.prev",out,data); } /*******************************************************************/ static void cb_navigation_next_title(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); print_nav_title(out,cbd->blog,&cbd->next,cbd->navunit); } /********************************************************************/ static void cb_navigation_next_url(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); print_nav_url(out,&cbd->next,cbd->navunit,cbd->blog->config.baseurl); } /*******************************************************************/ static void cb_navigation_prev_title(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); print_nav_title(out,cbd->blog,&cbd->previous,cbd->navunit); } /*******************************************************************/ static void cb_navigation_prev_url(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); print_nav_url(out,&cbd->previous,cbd->navunit,cbd->blog->config.baseurl); } /********************************************************************/ static void cb_now_year(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fprintf(out,"%04d",cbd->blog->now.year); } /*******************************************************************/ static void cb_request_url(FILE *out,void *data) { struct callback_data *cbd = data; char *tum; assert(out != NULL); assert(data != NULL); tum = tumbler_canonical(&cbd->request->tumbler); fprintf(out,"%s%s",cbd->blog->config.url,tum); free(tum); } /***********************************************************************/ static void cb_robots_index(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); if (cbd->navunit == UNIT_PART) fputs("index",out); else fputs("noindex",out); } /********************************************************************/ static void cb_rss_item(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); for ( BlogEntry *entry = (BlogEntry *)ListRemHead(&cbd->list); NodeValid(&entry->node); entry = (BlogEntry *)ListRemHead(&cbd->list) ) { assert(entry->valid); cbd->entry = entry; generic_cb("item",out,data); cbd->last = entry->when; BlogEntryFree(entry); } cbd->entry = NULL; } /******************************************************************/ static void cb_rss_item_url(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); assert(cbd->entry->valid); fprintf(out,"%s",cbd->blog->config.url); print_nav_name(out,&cbd->entry->when,UNIT_PART,'/'); } /********************************************************************/ static void cb_rss_pubdate(FILE *out,void *data) { struct callback_data *cbd = data; struct tm *ptm; char buffer[BUFSIZ]; assert(out != NULL); assert(data != NULL); ptm = gmtime(&cbd->blog->tnow); strftime(buffer,sizeof(buffer),"%a, %d %b %Y %H:%M:%S GMT",ptm); fputs(buffer,out); } /********************************************************************/ static void cb_rss_url(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fprintf(out,"%s",cbd->blog->config.url); } /*******************************************************************/ static void cb_update_time(FILE *out,void *data) { struct callback_data *cbd = data; char tmpbuf[24]; assert(out != NULL); assert(data != NULL); strftime(tmpbuf,sizeof(tmpbuf),"%FT%TZ",gmtime(&cbd->blog->tnow)); fputs(tmpbuf,out); } /*******************************************************************/ static void cb_webmention(FILE *out,void *data) { struct callback_data *cbd = data; char fname[FILENAME_MAX]; int rc; assert(out != NULL); assert(data != NULL); snprintf( fname, sizeof(fname), "%04d/%02d/%02d/%d.webmention", cbd->entry->when.year, cbd->entry->when.month, cbd->entry->when.day, cbd->entry->when.part ); cbd->wm = fopen(fname,"r"); if (cbd->wm == NULL) return; rc = fcntl( fileno(cbd->wm), F_SETLKW, &(struct flock){ .l_type = F_RDLCK, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0, } ); if (rc < 0) { syslog(LOG_DEBUG,"fileno('%s') = %d",fname,fileno(cbd->wm)); syslog(LOG_ERR,"fcntl('%s',F_SETLKW) = %s",fname,strerror(errno)); fclose(cbd->wm); return; } generic_cb("webmention",out,data); rc = fcntl( fileno(cbd->wm), F_SETLKW, &(struct flock){ .l_type = F_UNLCK, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0, } ); if (rc < 0) syslog(LOG_ERR,"fcntl('%s',F_UNLCK) = %s",fname,strerror(errno)); fclose(cbd->wm); cbd->wm = NULL; } /*******************************************************************/ static void cb_webmention_item(FILE *out,void *data) { struct callback_data *cbd = data; size_t buflen = 0; ssize_t len; assert(out != NULL); assert(data != NULL); while((len = getline(&cbd->wmurl,&buflen,cbd->wm)) != -1) { char *p = strchr(cbd->wmurl,'\t'); if (p == NULL) cbd->wmtitle = cbd->wmurl; else { *p++ = '\0'; cbd->wmtitle = p; } cbd->wmurl[len - 1] = '\0'; /* remove trailing '\n' */ generic_cb("webmention.item",out,data); free(cbd->wmurl); cbd->wmurl = NULL; cbd->wmtitle = NULL; buflen = 0; } free(cbd->wmurl); } /*******************************************************************/ static void cb_webmention_title(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->wmtitle,out); } /*******************************************************************/ static void cb_webmention_url(FILE *out,void *data) { struct callback_data *cbd = data; assert(out != NULL); assert(data != NULL); fputs(cbd->wmurl,out); } /*******************************************************************/ static void cb_xyzzy(FILE *out,void *data) { assert(out != NULL); (void)data; fputs("Nothing happens.",out); } /********************************************************************/ void generic_cb(char const *which,FILE *out,void *data) { /*----------------------------------------------------- ; the following table needs to be in alphabetical order ;------------------------------------------------------*/ static struct chunk_callback const callbacks[] = { { "ad" , cb_ad } , /* template "ad" */ { "ad.content" , cb_ad_content } , { "atom.categories" , cb_atom_categories } , /* template "categories" */ { "atom.category" , cb_atom_category } , { "atom.entry" , cb_atom_entry } , /* template "entry" */ { "begin.year" , cb_begin_year } , { "blog.adtag" , cb_blog_adtag } , { "blog.adtag.entity" , cb_blog_adtag_entity } , { "blog.author" , cb_blog_author } , { "blog.author.email" , cb_blog_author_email } , { "blog.class" , cb_blog_class } , { "blog.description" , cb_blog_description } , { "blog.name" , cb_blog_name } , { "blog.script" , cb_blog_script } , { "blog.title" , cb_blog_title } , { "blog.url" , cb_blog_url } , { "blog.url.base" , cb_blog_url_base } , { "blog.url.home" , cb_blog_url_home } , { "comments" , cb_comments } , /* template "comments" */ { "comments.body" , cb_comments_body } , { "comments.check" , cb_comments_check } , { "comments.filename" , cb_comments_filename } , { "cond.hr" , cb_cond_hr } , /* template "cond.hr" */ { "date.day" , cb_date_day } , { "date.day.normal" , cb_date_day_normal } , { "date.day.url" , cb_date_day_url } , { "edit" , cb_edit } , /* template "edit" */ { "edit.adtag" , cb_edit_adtag } , { "edit.author" , cb_edit_author } , { "edit.body" , cb_edit_body } , { "edit.class" , cb_edit_class } , { "edit.date" , cb_edit_date } , { "edit.status" , cb_edit_status } , { "edit.title" , cb_edit_title } , { "entry" , cb_entry } , /* template "entry" */ { "entry.author" , cb_entry_author } , { "entry.body" , cb_entry_body } , { "entry.body.entified" , cb_entry_body_entified } , { "entry.body.jsonified" , cb_entry_body_jsonified } , { "entry.class" , cb_entry_class } , { "entry.class.jsonified" , cb_entry_class_jsonified } , { "entry.cond.author" , cb_entry_cond_author } , /* template "entry.cond.author" */ { "entry.cond.date" , cb_entry_cond_date } , /* template "entry.cond.date" */ { "entry.date" , cb_entry_date } , { "entry.description" , cb_entry_description } , { "entry.id" , cb_entry_id } , { "entry.linkdate" , cb_entry_linkdate } , { "entry.linkdated" , cb_entry_linkdated } , { "entry.name" , cb_entry_name } , { "entry.path" , cb_entry_path } , { "entry.pubdate" , cb_entry_pubdate } , { "entry.pubdatetime" , cb_entry_pubdatetime } , { "entry.status" , cb_entry_status } , { "entry.title" , cb_entry_title } , { "entry.url" , cb_entry_url } , { "generator" , cb_generator } , { "json.item" , cb_json_item } , /* template "item" */ { "navigation.bar" , cb_navigation_bar } , /* template "navigation.bar" */ { "navigation.bar.next" , cb_navigation_bar_next } , /* template "navigation.bar.next" */ { "navigation.bar.prev" , cb_navigation_bar_prev } , /* template "navigation.bar.prev" */ { "navigation.current" , cb_navigation_current } , /* template "navigation.current" */ { "navigation.current.url" , cb_navigation_current_url } , { "navigation.first.title" , cb_navigation_first_title } , { "navigation.first.url" , cb_navigation_first_url } , { "navigation.last.title" , cb_navigation_last_title } , { "navigation.last.url" , cb_navigation_last_url } , { "navigation.link" , cb_navigation_link } , /* template "navigation.link" */ { "navigation.link.next" , cb_navigation_link_next } , /* template "navigation.link.next" */ { "navigation.link.prev" , cb_navigation_link_prev } , /* template "navigation.link.prev" */ { "navigation.next.title" , cb_navigation_next_title } , { "navigation.next.url" , cb_navigation_next_url } , { "navigation.prev.title" , cb_navigation_prev_title } , { "navigation.prev.url" , cb_navigation_prev_url } , { "now.year" , cb_now_year } , { "request.url" , cb_request_url } , { "robots.index" , cb_robots_index } , { "rss.item" , cb_rss_item } , /* template "item" */ { "rss.item.url" , cb_rss_item_url } , { "rss.pubdate" , cb_rss_pubdate } , { "rss.url" , cb_rss_url } , { "update.time" , cb_update_time } , { "webmention" , cb_webmention } , /* template "webmention" */ { "webmention.item" , cb_webmention_item } , /* template "webmention.item" */ { "webmention.title" , cb_webmention_title } , { "webmention.url" , cb_webmention_url } , { "xyzzy" , cb_xyzzy } , }; assert(which != NULL); assert(out != NULL); assert(data != NULL); struct callback_data *cbd = data; Chunk templates = ChunkNew(cbd->template->template,callbacks,sizeof(callbacks) / sizeof(callbacks[0])); ChunkProcess(templates,which,out,data); ChunkFree(templates); fflush(out); } /*********************************************************************/ void generic_main(FILE *out,struct callback_data *cbd) { char buf[64]; assert(out != NULL); assert(cbd != NULL); if (cbd->request->f.cgi) { if ((cbd->status == HTTP_OKAY) && HttpNotModified(cbd->blog->lastmod)) { fprintf( out, "Status: %d\r\n" "Content-Length: 0\r\n" "Last-Modified: %s\r\n" "\r\n", HTTP_NOTMODIFIED, HttpTimeStamp(buf,64,cbd->blog->lastmod) ); return; } fprintf( out, "Status: %d\r\n" "Content-Type: text/html\r\n" "Last-Modified: %s\r\n" "\r\n", cbd->status, HttpTimeStamp(buf,64,cbd->blog->lastmod) ); } generic_cb("main",out,cbd); } /*********************************************************************/