4 # Author: Rael Dornfest (2002-2003), The Blosxom Development Team (2005-2008)
5 # Version: 2.1.2 ($Id: blosxom.cgi,v 1.87 2008/11/13 16:39:59 alfie Exp $)
6 # Home/Docs/Licensing: http://blosxom.sourceforge.net/
7 # Development/Downloads: http://sourceforge.net/projects/blosxom
13 blosxom - A lightweight yet feature-packed weblog
17 B<blosxom> is a simple web log (blog) CGI script written in perl.
21 B<Blosxom> (pronounced "I<blossom>") is a lightweight yet feature-packed
22 weblog application designed from the ground up with simplicity,
23 usability, and interoperability in mind.
25 Fundamental is its reliance upon the file system, folders and files
26 as its content database. Blosxom's weblog entries are plain text
27 files like any other. Write from the comfort of your favorite text
28 editor and hit the Save button. Create, edit, rename, and delete entries
29 on the command-line, via FTP, WebDAV, or anything else you
30 might use to manipulate your files. There's no import or export; entries
31 are nothing more complex than title on the first line, body being
32 everything thereafter.
34 Despite its tiny footprint, Blosxom doesn't skimp on features, sporting
35 the majority of features one would find in any other Weblog application.
37 Blosxom is simple, straightforward, minimalist Perl affording even the
38 dabbler an opportunity for experimentation and customization. And
39 last, but not least, Blosxom is open source and free for the taking and
44 Write a weblog entry, and place it into the main data directory. Place
45 the the title is on the first line; the body is everything afterwards.
46 For example, create a file named I<first.txt> and put in it something
51 I have successfully installed blosxom on this system. For more
52 information on blosxom, see the author's <a
53 href="http://www.blosxom.com/">blosxom site</a>.
55 Place the file in the directory under the I<$datadir> points to. Be
56 sure to change the default location to be somewhere accessable by the
57 web server that runs blosxom as a CGI program.
61 # --- Configurable variables -----
63 # What's this blog's title?
64 $blog_title = "My Weblog";
66 # What's this blog's description (for outgoing RSS feed)?
67 $blog_description = "Yet another Blosxom weblog.";
69 # What's this blog's primary language (for outgoing RSS feed)?
70 $blog_language = "en";
72 # What's this blog's text encoding ?
73 $blog_encoding = "UTF-8";
75 # Where are this blog's entries kept?
76 $datadir = "/Library/WebServer/Documents/blosxom";
78 # What's my preferred base URL for this blog (leave blank for automatic)?
81 # Should I stick only to the datadir for items or travel down the
82 # directory hierarchy looking for items? If so, to what depth?
83 # 0 = infinite depth (aka grab everything), 1 = datadir only, n = n levels down
86 # How many entries should I show on the home page?
89 # What file extension signifies a blosxom entry?
90 $file_extension = "txt";
92 # What is the default flavour?
93 $default_flavour = "html";
95 # Should I show entries from the future (i.e. dated after now)?
96 $show_future_entries = 0;
98 # --- Plugins (Optional) -----
100 # File listing plugins blosxom should load
101 # (if empty blosxom will load all plugins in $plugin_dir and $plugin_path directories)
104 # Where are my plugins kept?
107 # Where should my plugins keep their state information?
108 $plugin_state_dir = "$plugin_dir/state";
110 # Additional plugins location
111 # List of directories, separated by ';' on windows, ':' everywhere else
114 # --- Static Rendering -----
116 # Where are this blog's static files to be created?
117 $static_dir = "/Library/WebServer/Documents/blog";
119 # What's my administrative password (you must set this for static rendering)?
120 $static_password = "";
122 # What flavours should I generate statically?
123 @static_flavours = qw/html rss/;
125 # Should I statically generate individual entries?
129 # Should I encode entities for xml content-types? (plugins can turn this off if they do it themselves)
130 $encode_xml_entities = 1;
132 # --------------------------------
135 qw! $version $blog_title $blog_description $blog_language $blog_encoding $datadir $url %template $template $depth $num_entries $file_extension $default_flavour $static_or_dynamic $config_dir $plugin_list $plugin_path $plugin_dir $plugin_state_dir @plugins %plugins $static_dir $static_password @static_flavours $static_entries $path_info_full $path_info $path_info_yr $path_info_mo $path_info_da $path_info_mo_num $flavour $static_or_dynamic %month2num @num2month $interpolate $entries $output $header $show_future_entries %files %indexes %others $encode_xml_entities $content_type !;
142 use CGI qw/:standard :netscape/;
146 # Load configuration from $ENV{BLOSXOM_CONFIG_DIR}/blosxom.conf, if it exists
148 if ( $ENV{BLOSXOM_CONFIG_FILE} && -r $ENV{BLOSXOM_CONFIG_FILE} ) {
149 $blosxom_config = $ENV{BLOSXOM_CONFIG_FILE};
150 ( $config_dir = $blosxom_config ) =~ s! / [^/]* $ !!x;
153 for my $blosxom_config_dir ( $ENV{BLOSXOM_CONFIG_DIR}, '/etc/blosxom',
156 if ( -r "$blosxom_config_dir/blosxom.conf" ) {
157 $config_dir = $blosxom_config_dir;
158 $blosxom_config = "$blosxom_config_dir/blosxom.conf";
164 # Load $blosxom_config
165 if ($blosxom_config) {
166 if ( -r $blosxom_config ) {
167 eval { require $blosxom_config }
168 or warn "Error reading blosxom config file '$blosxom_config'"
169 . ( $@ ? ": $@" : '' );
172 warn "Cannot find or read blosxom config file '$blosxom_config'";
176 my $fh = new FileHandle;
193 @num2month = sort { $month2num{$a} <=> $month2num{$b} } keys %month2num;
195 # Use the stated preferred URL or figure it out automatically. Set
196 # $url manually in the config section above if CGI.pm doesn't guess
197 # the base URL correctly, e.g. when called from a Server Side Includes
202 # Unescape %XX hex codes (from URI::Escape::uri_unescape)
203 $url =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
205 # Support being called from inside a SSI document
206 $url =~ s/^included:/http:/ if $ENV{SERVER_PROTOCOL} eq 'INCLUDED';
208 # Remove PATH_INFO if it is set but not removed by CGI.pm. This
209 # seems to happen when used with Apache's Alias directive or if
210 # called from inside a Server Side Include document. If that
211 # doesn't help either, set $url manually in the configuration.
212 $url =~ s/\Q$ENV{PATH_INFO}\E$// if defined $ENV{PATH_INFO};
216 # There is one case where this code does more than necessary, too:
217 # If the URL requested is e.g. http://example.org/blog/blog and
218 # the base URL is correctly determined as http://example.org/blog
219 # by CGI.pm, then this code will incorrectly normalize the base
220 # URL down to http://example.org, because the same string as
221 # PATH_INFO is part of the base URL, too. But this is such a
222 # seldom case and can be fixed by setting $url in the config file,
226 # The only modification done to a manually set base URL is to strip
227 # a trailing slash if present.
231 # Drop ending any / from dir settings
233 $plugin_dir =~ s!/$!!;
234 $static_dir =~ s!/$!!;
236 # Fix depth to take into account datadir's path
237 $depth += ( $datadir =~ tr[/][] ) - 1 if $depth;
239 if ( !$ENV{GATEWAY_INTERFACE}
240 and param('-password')
242 and param('-password') eq $static_password )
244 $static_or_dynamic = 'static';
247 $static_or_dynamic = 'dynamic';
248 param( -name => '-quiet', -value => 1 );
252 # Take a gander at HTTP's PATH_INFO for optional blog name, archive yr/mo/day
253 my @path_info = split m{/}, path_info() || param('path');
254 $path_info_full = join '/', @path_info; # Equivalent to $ENV{PATH_INFO}
257 # Flavour specified by ?flav={flav} or index.{flav}
259 if (! ($flavour = param('flav'))) {
260 if ( $path_info[$#path_info] =~ /(.+)\.(.+)$/ ) {
262 pop @path_info if $1 eq 'index';
265 $flavour ||= $default_flavour;
267 # Fix XSS in flavour name (CVE-2008-2236)
268 $flavour = blosxom_html_escape($flavour);
270 sub blosxom_html_escape {
279 my $escape_re = join '|' => keys %escape;
280 $string =~ s/($escape_re)/$escape{$1}/g;
284 # Global variable to be used in head/foot.{flavour} templates
286 # Add all @path_info elements to $path_info till we come to one that could be a year
287 while ( $path_info[0] && $path_info[0] !~ /^(19|20)\d{2}$/) {
288 $path_info .= '/' . shift @path_info;
291 # Pull date elements out of path
292 if ($path_info[0] && $path_info[0] =~ /^(19|20)\d{2}$/) {
293 $path_info_yr = shift @path_info;
295 ($path_info[0] =~ /^(0\d|1[012])$/ ||
296 exists $month2num{ ucfirst lc $path_info_mo })) {
297 $path_info_mo = shift @path_info;
298 # Map path_info_mo to numeric $path_info_mo_num
299 $path_info_mo_num = $path_info_mo =~ /^\d{2}$/
301 : $month2num{ ucfirst lc $path_info_mo };
302 if ($path_info[0] && $path_info[0] =~ /^[0123]\d$/) {
303 $path_info_da = shift @path_info;
308 # Add remaining path elements to $path_info
309 $path_info .= '/' . join('/', @path_info);
311 # Strip spurious slashes
312 $path_info =~ s!(^/*)|(/*$)!!g;
314 # Define standard template subroutine, plugin-overridable at Plugins: Template
316 my ( $path, $chunk, $flavour ) = @_;
319 return join '', <$fh>
320 if $fh->open("< $datadir/$path/$chunk.$flavour");
321 } while ( $path =~ s/(\/*[^\/]*)$// and $1 );
323 # Check for definedness, since flavour can be the empty string
324 if ( defined $template{$flavour}{$chunk} ) {
325 return $template{$flavour}{$chunk};
327 elsif ( defined $template{error}{$chunk} ) {
328 return $template{error}{$chunk};
335 # Bring in the templates
338 last if /^(__END__)$/;
339 my ( $ct, $comp, $txt ) = /^(\S+)\s(\S+)(?:\s(.*))?$/ or next;
341 $template{$ct}{$comp} .= $txt . "\n";
345 my $path_sep = $^O eq 'MSWin32' ? ';' : ':';
346 my @plugin_dirs = split /$path_sep/, $plugin_path;
347 unshift @plugin_dirs, $plugin_dir;
348 my @plugin_list = ();
349 my %plugin_hash = ();
351 # If $plugin_list is set, read plugins to use from that file
352 if ( $plugin_list ) {
353 if ( -r $plugin_list and $fh->open("< $plugin_list") ) {
354 @plugin_list = map { chomp $_; $_ } grep { /\S/ && !/^#/ } <$fh>;
358 warn "unable to read or open plugin_list '$plugin_list': $!";
363 # Otherwise walk @plugin_dirs to get list of plugins to use
364 if ( ! @plugin_list && @plugin_dirs ) {
365 for my $plugin_dir (@plugin_dirs) {
366 next unless -d $plugin_dir;
367 if ( opendir PLUGINS, $plugin_dir ) {
369 grep { /^[\w:]+$/ && !/~$/ && -f "$plugin_dir/$_" }
374 next if $plugin_hash{$plugin};
376 # Add to @plugin_list and %plugin_hash
377 $plugin_hash{$plugin} = "$plugin_dir/$plugin";
378 push @plugin_list, $plugin;
383 @plugin_list = sort @plugin_list;
386 # Load all plugins in @plugin_list
387 unshift @INC, @plugin_dirs;
388 foreach my $plugin (@plugin_list) {
389 my ( $plugin_name, $off ) = $plugin =~ /^\d*([\w:]+?)(_?)$/;
390 my $plugin_file = $plugin_list ? $plugin_name : $plugin;
391 my $on_off = $off eq '_' ? -1 : 1;
393 # Allow perl module plugins
394 # The -z test is a hack to allow a zero-length placeholder file in a
395 # $plugin_path directory to indicate an @INC module should be loaded
396 if ( $plugin =~ m/::/ && ( $plugin_list || -z $plugin_hash{$plugin} ) ) {
398 # For Blosxom::Plugin::Foo style plugins, we need to use a string require
399 eval "require $plugin_file";
402 { # we try first to load from $plugin_dir before attempting from $plugin_path
403 eval { require "$plugin_dir/$plugin_file" }
404 or eval { require $plugin_file };
408 warn "error finding or loading blosxom plugin '$plugin_name': $@";
411 if ( $plugin_name->start() and ( $plugins{$plugin_name} = $on_off ) ) {
412 push @plugins, $plugin_name;
416 shift @INC foreach @plugin_dirs;
419 # Allow for the first encountered plugin::template subroutine to override the
420 # default built-in template subroutine
421 foreach my $plugin (@plugins) {
422 if ( $plugins{$plugin} > 0 and $plugin->can('template') ) {
423 if ( my $tmp = $plugin->template() ) {
430 # Provide backward compatibility for Blosxom < 2.0rc1 plug-ins
432 return &$template(@_);
435 # Define default entries subroutine
437 my ( %files, %indexes, %others );
441 my $curr_depth = $File::Find::dir =~ tr[/][];
442 return if $depth and $curr_depth > $depth;
448 =~ m!^$datadir/(?:(.*)/)?(.+)\.$file_extension$!
450 # not an index, .file, and is readable
451 and $2 ne 'index' and $2 !~ /^\./ and ( -r $File::Find::name )
455 # read modification time
456 my $mtime = stat($File::Find::name)->mtime or return;
458 # to show or not to show future entries
459 return unless ( $show_future_entries or $mtime < time );
461 # add the file and its associated mtime to the list of files
462 $files{$File::Find::name} = $mtime;
464 # static rendering bits
466 = "$static_dir/$1/index." . $static_flavours[0];
469 or stat($static_file)->mtime < $mtime )
472 $d = join( '/', ( nice_date($mtime) )[ 5, 2, 3 ] );
474 $indexes{ ( $1 ? "$1/" : '' ) . "$2.$file_extension" } = 1
479 # not an entries match
480 elsif ( !-d $File::Find::name and -r $File::Find::name ) {
481 $others{$File::Find::name} = stat($File::Find::name)->mtime;
487 return ( \%files, \%indexes, \%others );
491 # Allow for the first encountered plugin::entries subroutine to override the
492 # default built-in entries subroutine
493 foreach my $plugin (@plugins) {
494 if ( $plugins{$plugin} > 0 and $plugin->can('entries') ) {
495 if ( my $tmp = $plugin->entries() ) {
502 my ( $files, $indexes, $others ) = &$entries();
503 %indexes = %$indexes;
506 if ( !$ENV{GATEWAY_INTERFACE}
507 and param('-password')
509 and param('-password') eq $static_password )
512 param('-quiet') or print "Blosxom is generating static index pages...\n";
514 # Home Page and Directory Indexes
516 foreach my $path ( sort keys %indexes ) {
518 foreach ( ( '', split /\//, $path ) ) {
522 mkdir "$static_dir/$p", 0755
523 unless ( -d "$static_dir/$p" or $p =~ /\.$file_extension$/ );
524 foreach $flavour (@static_flavours) {
526 = ( &$template( $p, 'content_type', $flavour ) );
527 $content_type =~ s!\n.*!!s;
528 my $fn = $p =~ m!^(.+)\.$file_extension$! ? $1 : "$p/index";
529 param('-quiet') or print "$fn.$flavour\n";
530 my $fh_w = new FileHandle "> $static_dir/$fn.$flavour"
531 or die "Couldn't open $static_dir/$p for writing: $!";
533 if ( $indexes{$path} == 1 ) {
539 $path_info =~ s!\.$file_extension$!\.$flavour!;
540 print $fh_w &generate( 'static', $path_info, '', $flavour,
547 $path_info_yr, $path_info_mo,
548 $path_info_da, $path_info
549 ) = split /\//, $p, 4;
550 unless ( defined $path_info ) { $path_info = "" }
551 print $fh_w &generate( 'static', '', $p, $flavour,
562 $content_type = ( &$template( $path_info, 'content_type', $flavour ) );
563 $content_type =~ s!\n.*!!s;
565 $content_type =~ s/(\$\w+(?:::\w+)*)/"defined $1 ? $1 : ''"/gee;
566 $header = { -type => $content_type };
568 print generate( 'dynamic', $path_info,
569 "$path_info_yr/$path_info_mo_num/$path_info_da",
570 $flavour, $content_type );
574 foreach my $plugin (@plugins) {
575 if ( $plugins{$plugin} > 0 and $plugin->can('end') ) {
576 $entries = $plugin->end();
582 my ( $static_or_dynamic, $currentdir, $date, $flavour, $content_type )
586 %others = ref $others ? %$others : ();
589 foreach my $plugin (@plugins) {
590 if ( $plugins{$plugin} > 0 and $plugin->can('filter') ) {
591 $entries = $plugin->filter( \%files, \%others );
598 # Allow plugins to decide if we can cut short story generation
600 foreach my $plugin (@plugins) {
601 if ( $plugins{$plugin} > 0 and $plugin->can('skip') ) {
602 if ( my $tmp = $plugin->skip() ) {
609 # Define default interpolation subroutine
612 my $template = shift;
613 # Interpolate scalars, namespaced scalars, and hash/hashref scalars
614 $template =~ s/(\$\w+(?:::\w+)*(?:(?:->)?{(['"]?)[-\w]+\2})?)/"defined $1 ? $1 : ''"/gee;
618 unless ( defined($skip) and $skip ) {
620 # Plugins: Interpolate
621 # Allow for the first encountered plugin::interpolate subroutine to
622 # override the default built-in interpolate subroutine
623 foreach my $plugin (@plugins) {
624 if ( $plugins{$plugin} > 0 and $plugin->can('interpolate') ) {
625 if ( my $tmp = $plugin->interpolate() ) {
633 my $head = ( &$template( $currentdir, 'head', $flavour ) );
636 foreach my $plugin (@plugins) {
637 if ( $plugins{$plugin} > 0 and $plugin->can('head') ) {
638 $entries = $plugin->head( $currentdir, \$head );
642 $head = &$interpolate($head);
648 my $ne = $num_entries;
650 if ( $currentdir =~ /(.*?)([^\/]+)\.(.+)$/ and $2 ne 'index' ) {
651 $currentdir = "$1$2.$file_extension";
652 %f = ( "$datadir/$currentdir" => $files{"$datadir/$currentdir"} )
653 if $files{"$datadir/$currentdir"};
656 $currentdir =~ s!/index\..+$!!;
659 # Define a default sort subroutine
661 my ($files_ref) = @_;
663 sort { $files_ref->{$b} <=> $files_ref->{$a} }
668 # Allow for the first encountered plugin::sort subroutine to override the
669 # default built-in sort subroutine
670 foreach my $plugin (@plugins) {
671 if ( $plugins{$plugin} > 0 and $plugin->can('sort') ) {
672 if ( my $tmp = $plugin->sort() ) {
679 foreach my $path_file ( &$sort( \%f, \%others ) ) {
680 last if $ne <= 0 && $date !~ /\d/;
681 use vars qw/ $path $fn /;
683 = $path_file =~ m!^$datadir/(?:(.*)/)?(.*)\.$file_extension!;
685 # Only stories in the right hierarchy
686 $path =~ /^$currentdir/
687 or $path_file eq "$datadir/$currentdir"
690 # Prepend a slash for use in templates only if a path exists
693 # Date fiddling for by-{year,month,day} archive views
695 qw/ $dw $mo $mo_num $da $ti $yr $hr $min $hr12 $ampm $utc_offset/;
696 ( $dw, $mo, $mo_num, $da, $ti, $yr, $utc_offset )
697 = nice_date( $files{"$path_file"} );
698 ( $hr, $min ) = split /:/, $ti;
699 ( $hr12, $ampm ) = $hr >= 12 ? ( $hr - 12, 'pm' ) : ( $hr, 'am' );
701 if ( $hr12 == 0 ) { $hr12 = 12 }
703 # Only stories from the right date
704 my ( $path_info_yr, $path_info_mo_num, $path_info_da )
706 next if $path_info_yr && $yr != $path_info_yr;
707 last if $path_info_yr && $yr < $path_info_yr;
708 next if $path_info_mo_num && $mo ne $num2month[$path_info_mo_num];
709 next if $path_info_da && $da != $path_info_da;
710 last if $path_info_da && $da < $path_info_da;
713 my $date = ( &$template( $path, 'date', $flavour ) );
716 foreach my $plugin (@plugins) {
717 if ( $plugins{$plugin} > 0 and $plugin->can('date') ) {
719 = $plugin->date( $currentdir, \$date,
720 $files{$path_file}, $dw, $mo, $mo_num, $da, $ti,
725 $date = &$interpolate($date);
727 if ( $date && $curdate ne $date ) {
732 use vars qw/ $title $body $raw /;
733 if ( -f "$path_file" && $fh->open("< $path_file") ) {
734 chomp( $title = <$fh> );
735 chomp( $body = join '', <$fh> );
737 $raw = "$title\n$body";
739 my $story = ( &$template( $path, 'story', $flavour ) );
742 foreach my $plugin (@plugins) {
743 if ( $plugins{$plugin} > 0 and $plugin->can('story') ) {
744 $entries = $plugin->story( $path, $fn, \$story, \$title,
749 if ( $encode_xml_entities &&
750 $content_type =~ m{\bxml\b} &&
751 $content_type !~ m{\bxhtml\b} ) {
752 # Escape special characters inside the <link> container
754 # The following line should be moved more towards to top for
755 # performance reasons -- Axel Beckert, 2008-07-22
756 my $url_escape_re = qr([^-/a-zA-Z0-9:._]);
758 $url =~ s($url_escape_re)(sprintf('%%%02X', ord($&)))eg;
759 $path =~ s($url_escape_re)(sprintf('%%%02X', ord($&)))eg;
760 $fn =~ s($url_escape_re)(sprintf('%%%02X', ord($&)))eg;
762 # Escape <, >, and &, and to produce valid RSS
763 $title = blosxom_html_escape($title);
764 $body = blosxom_html_escape($body);
765 $url = blosxom_html_escape($url);
766 $path = blosxom_html_escape($path);
767 $fn = blosxom_html_escape($fn);
770 $story = &$interpolate($story);
779 my $foot = ( &$template( $currentdir, 'foot', $flavour ) );
782 foreach my $plugin (@plugins) {
783 if ( $plugins{$plugin} > 0 and $plugin->can('foot') ) {
784 $entries = $plugin->foot( $currentdir, \$foot );
788 $foot = &$interpolate($foot);
792 foreach my $plugin (@plugins) {
793 if ( $plugins{$plugin} > 0 and $plugin->can('last') ) {
794 $entries = $plugin->last();
800 # Finally, add the header, if any and running dynamically
801 $output = header($header) . $output
802 if ( $static_or_dynamic eq 'dynamic' and $header );
810 my $c_time = CORE::localtime($unixtime);
811 my ( $dw, $mo, $da, $hr, $min, $sec, $yr )
813 =~ /(\w{3}) +(\w{3}) +(\d{1,2}) +(\d{2}):(\d{2}):(\d{2}) +(\d{4})$/
816 $da = sprintf( "%02d", $da );
817 my $mo_num = $month2num{$mo};
820 = timegm( $sec, $min, $hr, $da, $mo_num - 1, $yr - 1900 ) - $unixtime;
821 my $utc_offset = sprintf( "%+03d", int( $offset / 3600 ) )
822 . sprintf( "%02d", ( $offset % 3600 ) / 60 );
824 return ( $dw, $mo, $mo_num, $da, $ti, $yr, $utc_offset );
827 # Default HTML and RSS template bits
829 html content_type text/html; charset=$blog_encoding
831 html head <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
834 html head <meta http-equiv="content-type" content="$content_type" >
835 html head <link rel="alternate" type="application/rss+xml" title="RSS" href="$url/index.rss" >
836 html head <title>$blog_title $path_info_da $path_info_mo $path_info_yr</title>
839 html head <div align="center">
840 html head <h1>$blog_title</h1>
841 html head <p>$path_info_da $path_info_mo $path_info_yr</p>
845 html story <h3><a name="$fn">$title</a></h3>
846 html story <div>$body</div>
847 html story <p>posted at: $ti | path: <a href="$url$path">$path</a> | <a href="$url/$yr/$mo_num/$da#$fn">permanent link to this entry</a></p>
850 html date <h2>$dw, $da $mo $yr</h2>
853 html foot <div align="center">
854 html foot <a href="http://blosxom.sourceforge.net/"><img src="http://blosxom.sourceforge.net/images/pb_blosxom.gif" alt="powered by blosxom" border="0" width="90" height="33" ></a>
859 rss content_type text/xml; charset=$blog_encoding
861 rss head <?xml version="1.0" encoding="$blog_encoding"?>
862 rss head <rss version="2.0">
864 rss head <title>$blog_title</title>
865 rss head <link>$url/$path_info</link>
866 rss head <description>$blog_description</description>
867 rss head <language>$blog_language</language>
868 rss head <docs>http://blogs.law.harvard.edu/tech/rss</docs>
869 rss head <generator>blosxom/$version</generator>
872 rss story <title>$title</title>
873 rss story <pubDate>$dw, $da $mo $yr $ti:00 $utc_offset</pubDate>
874 rss story <link>$url/$yr/$mo_num/$da#$fn</link>
875 rss story <category>$path</category>
876 rss story <guid isPermaLink="false">$url$path/$fn</guid>
877 rss story <description>$body</description>
885 error content_type text/html
887 error head <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
889 error head <head><title>Error: unknown Blosxom flavour "$flavour"</title></head>
891 error head <h1><font color="red">Error: unknown Blosxom flavour "$flavour"</font></h1>
892 error head <p>I'm afraid this is the first I've heard of a "$flavour" flavoured Blosxom. Try dropping the "/+$flavour" bit from the end of the URL.</p>
894 error story <h3>$title</h3>
895 error story <div>$body</div> <p><a href="$url/$yr/$mo_num/$da#fn.$default_flavour">#</a></p>
897 error date <h2>$dw, $da $mo $yr</h2>