4 # Author: Rael Dornfest (2002-2003), The Blosxom Development Team (2005-2009)
5 # Version: 2.1.2 ($Id: blosxom.cgi,v 1.98 2009/07/19 17:18:37 xtaran 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://blosxom.sourceforge.net/">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
82 # Should I stick only to the datadir for items or travel down the
83 # directory hierarchy looking for items? If so, to what depth?
85 # 0 = infinite depth (aka grab everything), 1 = datadir only,
90 # How many entries should I show on the home page?
93 # What file extension signifies a blosxom entry?
94 $file_extension = "txt";
96 # What is the default flavour?
97 $default_flavour = "html";
99 # Should I show entries from the future (i.e. dated after now)?
100 $show_future_entries = 0;
102 # --- Plugins (Optional) -----
104 # File listing plugins blosxom should load (if empty blosxom will load
105 # all plugins in $plugin_dir and $plugin_path directories)
108 # Where are my plugins kept?
111 # Where should my plugins keep their state information?
112 $plugin_state_dir = "$plugin_dir/state";
114 # Additional plugins location. A list of directories, separated by ';'
115 # on windows, ':' everywhere else.
118 # --- Static Rendering -----
120 # Where are this blog's static files to be created?
121 $static_dir = "/Library/WebServer/Documents/blog";
123 # What's my administrative password (you must set this for static
125 $static_password = "";
127 # What flavours should I generate statically?
128 @static_flavours = qw
/html rss/;
130 # Should I statically generate individual entries?
134 # --- Advanced Encoding Options -----
136 # Should I encode entities for xml content-types? (plugins can turn
137 # this off if they do it themselves)
138 $encode_xml_entities = 1;
140 # Should I encode 8 bit special characters, e.g. umlauts in URLs, e.g.
141 # convert an ISO-Latin-1 \"o to %F6? (off by default for now; plugins
142 # can change this, too)
143 $encode_8bit_chars = 0;
145 # RegExp matching all characters which should be URL encoded in links.
146 # Defaults to anything but numbers, letters, slash, colon, dash,
147 # underscore and dot.
148 $url_escape_re = qr
([^-/a
-zA
-Z0
-9:._
]);
150 # --------------------------------
156 =item B<BLOSXOM_CONFIG_FILE>
158 Points to the location of the configuration file. This will be
159 considered as first option, if it's set.
162 =item B<BLOSXOM_CONFIG_DIR>
164 The here named directory will be tried unless the above mentioned
165 environment variable is set and tested for a contained blosxom.conf
176 =item B</usr/lib/cgi-bin/blosxom>
178 The CGI script itself. Please note that the location might depend on
181 =item B</etc/blosxom/blosxom.conf>
183 The default configuration file location. This is rather taken as last
184 ressort if no other configuration location is set through environment
192 Rael Dornfest <rael@oreilly.com> was the original author of blosxom. The
193 development was picked up by a team of dedicated users of blosxom since
194 2005. See <I<http://blosxom.sourceforge.net/>> for more information.
254 use CGI qw
/:standard :netscape/;
256 $version = "2.1.2+dev";
258 # Load configuration from $ENV{BLOSXOM_CONFIG_DIR}/blosxom.conf, if it exists
260 if ( $ENV{BLOSXOM_CONFIG_FILE
} && -r
$ENV{BLOSXOM_CONFIG_FILE
} ) {
261 $blosxom_config = $ENV{BLOSXOM_CONFIG_FILE
};
262 ( $config_dir = $blosxom_config ) =~ s! / [^/]* $ !!x;
265 for my $blosxom_config_dir ( $ENV{BLOSXOM_CONFIG_DIR
}, '/etc/blosxom',
268 if ( -r
"$blosxom_config_dir/blosxom.conf" ) {
269 $config_dir = $blosxom_config_dir;
270 $blosxom_config = "$blosxom_config_dir/blosxom.conf";
276 # Load $blosxom_config
277 if ($blosxom_config) {
278 if ( -r
$blosxom_config ) {
279 eval { require $blosxom_config }
280 or warn "Error reading blosxom config file '$blosxom_config'"
281 . ( $@ ?
": $@" : '' );
284 warn "Cannot find or read blosxom config file '$blosxom_config'";
288 my $fh = new FileHandle
;
305 @num2month = sort { $month2num{$a} <=> $month2num{$b} } keys %month2num;
307 # Use the stated preferred URL or figure it out automatically. Set
308 # $url manually in the config section above if CGI.pm doesn't guess
309 # the base URL correctly, e.g. when called from a Server Side Includes
314 # Unescape %XX hex codes (from URI::Escape::uri_unescape)
315 $url =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
317 # Support being called from inside a SSI document
318 $url =~ s/^included:/http:/ if $ENV{SERVER_PROTOCOL
} eq 'INCLUDED';
320 # Remove PATH_INFO if it is set but not removed by CGI.pm. This
321 # seems to happen when used with Apache's Alias directive or if
322 # called from inside a Server Side Include document. If that
323 # doesn't help either, set $url manually in the configuration.
324 $url =~ s/\Q$ENV{PATH_INFO}\E$// if defined $ENV{PATH_INFO
};
328 # There is one case where this code does more than necessary, too:
329 # If the URL requested is e.g. http://example.org/blog/blog and
330 # the base URL is correctly determined as http://example.org/blog
331 # by CGI.pm, then this code will incorrectly normalize the base
332 # URL down to http://example.org, because the same string as
333 # PATH_INFO is part of the base URL, too. But this is such a
334 # seldom case and can be fixed by setting $url in the config file,
338 # The only modification done to a manually set base URL is to strip
339 # a trailing slash if present.
343 # Drop ending any / from dir settings
345 $plugin_dir =~ s!/$!!;
346 $static_dir =~ s!/$!!;
348 # Fix depth to take into account datadir's path
349 $depth += ( $datadir =~ tr
[/][] ) - 1 if $depth;
351 if ( !$ENV{GATEWAY_INTERFACE
}
352 and param
('-password')
354 and param
('-password') eq $static_password )
356 $static_or_dynamic = 'static';
359 $static_or_dynamic = 'dynamic';
360 param
( -name
=> '-quiet', -value
=> 1 );
364 # Take a gander at HTTP's PATH_INFO for optional blog name, archive yr/mo/day
365 my @path_info = split m{/}, path_info
() || param
('path');
366 $path_info_full = join '/', @path_info; # Equivalent to $ENV{PATH_INFO}
369 # Flavour specified by ?flav={flav} or index.{flav}
371 if (! ($flavour = param
('flav'))) {
372 if ( $path_info[$#path_info] =~ /(.+)\.(.+)$/ ) {
374 pop @path_info if $1 eq 'index';
377 $flavour ||= $default_flavour;
379 # Fix XSS in flavour name (CVE-2008-2236)
380 $flavour = blosxom_html_escape
($flavour);
382 sub blosxom_html_escape
{
391 my $escape_re = join '|' => keys %escape;
392 $string =~ s/($escape_re)/$escape{$1}/g;
396 # Global variable to be used in head/foot.{flavour} templates
398 # Add all @path_info elements to $path_info till we come to one that could be a year
399 while ( $path_info[0] && $path_info[0] !~ /^(19|20)\d{2}$/) {
400 $path_info .= '/' . shift @path_info;
403 # Pull date elements out of path
404 if ($path_info[0] && $path_info[0] =~ /^(19|20)\d{2}$/) {
405 $path_info_yr = shift @path_info;
407 ($path_info[0] =~ /^(0\d|1[012])$/ ||
408 exists $month2num{ ucfirst lc $path_info_mo })) {
409 $path_info_mo = shift @path_info;
410 # Map path_info_mo to numeric $path_info_mo_num
411 $path_info_mo_num = $path_info_mo =~ /^\d{2}$/
413 : $month2num{ ucfirst lc $path_info_mo };
414 if ($path_info[0] && $path_info[0] =~ /^[0123]\d$/) {
415 $path_info_da = shift @path_info;
420 # Add remaining path elements to $path_info
421 $path_info .= '/' . join('/', @path_info);
423 # Strip spurious slashes
424 $path_info =~ s!(^/*)|(/*$)!!g;
426 # Define standard template subroutine, plugin-overridable at Plugins: Template
428 my ( $path, $chunk, $flavour ) = @_;
431 return join '', <$fh>
432 if $fh->open("< $datadir/$path/$chunk.$flavour");
433 } while ( $path =~ s/(\/*[^\/]*)$// and $1 );
435 # Check for definedness, since flavour can be the empty string
436 if ( defined $template{$flavour}{$chunk} ) {
437 return $template{$flavour}{$chunk};
439 elsif ( defined $template{error
}{$chunk} ) {
440 return $template{error
}{$chunk};
447 # Bring in the templates
450 last if /^(__END__)$/;
451 my ( $ct, $comp, $txt ) = /^(\S+)\s(\S+)(?:\s(.*))?$/ or next;
453 $template{$ct}{$comp} .= $txt . "\n";
457 my $path_sep = $^O
eq 'MSWin32' ?
';' : ':';
458 my @plugin_dirs = split /$path_sep/, $plugin_path;
459 unshift @plugin_dirs, $plugin_dir;
460 my @plugin_list = ();
461 my %plugin_hash = ();
463 # If $plugin_list is set, read plugins to use from that file
464 if ( $plugin_list ) {
465 if ( -r
$plugin_list and $fh->open("< $plugin_list") ) {
466 @plugin_list = map { chomp $_; $_ } grep { /\S/ && !/^#/ } <$fh>;
470 warn "unable to read or open plugin_list '$plugin_list': $!";
475 # Otherwise walk @plugin_dirs to get list of plugins to use
476 if ( ! @plugin_list && @plugin_dirs ) {
477 for my $plugin_dir (@plugin_dirs) {
478 next unless -d
$plugin_dir;
479 if ( opendir PLUGINS
, $plugin_dir ) {
481 grep { /^[\w:]+$/ && !/~$/ && -f
"$plugin_dir/$_" }
486 next if $plugin_hash{$plugin};
488 # Add to @plugin_list and %plugin_hash
489 $plugin_hash{$plugin} = "$plugin_dir/$plugin";
490 push @plugin_list, $plugin;
495 @plugin_list = sort @plugin_list;
498 # Load all plugins in @plugin_list
499 unshift @INC, @plugin_dirs;
500 foreach my $plugin (@plugin_list) {
501 my ( $plugin_name, $off ) = $plugin =~ /^\d*([\w:]+?)(_?)$/;
502 my $plugin_file = $plugin_list ?
$plugin_name : $plugin;
503 my $on_off = $off eq '_' ?
-1 : 1;
505 # Allow perl module plugins
506 # The -z test is a hack to allow a zero-length placeholder file in a
507 # $plugin_path directory to indicate an @INC module should be loaded
508 if ( $plugin =~ m/::/ && ( $plugin_list || -z
$plugin_hash{$plugin} ) ) {
510 # For Blosxom::Plugin::Foo style plugins, we need to use a string require
511 eval "require $plugin_file";
514 { # we try first to load from $plugin_dir before attempting from $plugin_path
515 eval { require "$plugin_dir/$plugin_file" }
516 or eval { require $plugin_file };
520 warn "error finding or loading blosxom plugin '$plugin_name': $@";
523 if ( $plugin_name->start() and ( $plugins{$plugin_name} = $on_off ) ) {
524 push @plugins, $plugin_name;
528 shift @INC foreach @plugin_dirs;
531 # Allow for the first encountered plugin::template subroutine to override the
532 # default built-in template subroutine
533 foreach my $plugin (@plugins) {
534 if ( $plugins{$plugin} > 0 and $plugin->can('template') ) {
535 if ( my $tmp = $plugin->template() ) {
542 # Provide backward compatibility for Blosxom < 2.0rc1 plug-ins
544 return &$template(@_);
547 # Define default entries subroutine
549 my ( %files, %indexes, %others );
553 my $curr_depth = $File::Find
::dir
=~ tr
[/][];
554 return if $depth and $curr_depth > $depth;
560 =~ m!^$datadir/(?:(.*)/)?(.+)\.$file_extension$!
562 # not an index, .file, and is readable
563 and $2 ne 'index' and $2 !~ /^\./ and ( -r
$File::Find
::name
)
567 # read modification time
568 my $mtime = stat($File::Find
::name
)->mtime or return;
570 # to show or not to show future entries
571 return unless ( $show_future_entries or $mtime < time );
573 # add the file and its associated mtime to the list of files
574 $files{$File::Find
::name
} = $mtime;
576 # static rendering bits
578 = "$static_dir/$1/index." . $static_flavours[0];
581 or stat($static_file)->mtime < $mtime )
584 $d = join( '/', ( nice_date
($mtime) )[ 5, 2, 3 ] );
586 $indexes{ ( $1 ?
"$1/" : '' ) . "$2.$file_extension" } = 1
591 # not an entries match
592 elsif ( !-d
$File::Find
::name
and -r
$File::Find
::name
) {
593 $others{$File::Find
::name
} = stat($File::Find
::name
)->mtime;
599 return ( \
%files, \
%indexes, \
%others );
603 # Allow for the first encountered plugin::entries subroutine to override the
604 # default built-in entries subroutine
605 foreach my $plugin (@plugins) {
606 if ( $plugins{$plugin} > 0 and $plugin->can('entries') ) {
607 if ( my $tmp = $plugin->entries() ) {
614 my ( $files, $indexes, $others ) = &$entries();
615 %indexes = %$indexes;
618 if ( !$ENV{GATEWAY_INTERFACE
}
619 and param
('-password')
621 and param
('-password') eq $static_password )
624 param
('-quiet') or print "Blosxom is generating static index pages...\n";
626 # Home Page and Directory Indexes
628 foreach my $path ( sort keys %indexes ) {
630 foreach ( ( '', split /\//, $path ) ) {
634 mkdir "$static_dir/$p", 0755
635 unless ( -d
"$static_dir/$p" or $p =~ /\
.$file_extension$/ );
636 foreach $flavour (@static_flavours) {
638 = ( &$template( $p, 'content_type', $flavour ) );
639 $content_type =~ s!\n.*!!s;
640 my $fn = $p =~ m!^(.+)\.$file_extension$! ?
$1 : "$p/index";
641 param
('-quiet') or print "$fn.$flavour\n";
642 my $fh_w = new FileHandle
"> $static_dir/$fn.$flavour"
643 or die "Couldn't open $static_dir/$p for writing: $!";
645 if ( $indexes{$path} == 1 ) {
651 $path_info =~ s!\.$file_extension$!\.$flavour!;
652 print $fh_w &generate
( 'static', $path_info, '', $flavour,
659 $path_info_yr, $path_info_mo,
660 $path_info_da, $path_info
661 ) = split /\//, $p, 4;
662 unless ( defined $path_info ) { $path_info = "" }
663 print $fh_w &generate
( 'static', '', $p, $flavour,
674 $content_type = ( &$template( $path_info, 'content_type', $flavour ) );
675 $content_type =~ s!\n.*!!s;
677 $content_type =~ s/(\$\w+(?:::\w+)*)/"defined $1 ? $1 : ''"/gee;
678 $header = { -type
=> $content_type };
680 print generate
( 'dynamic', $path_info,
681 "$path_info_yr/$path_info_mo_num/$path_info_da",
682 $flavour, $content_type );
686 foreach my $plugin (@plugins) {
687 if ( $plugins{$plugin} > 0 and $plugin->can('end') ) {
688 $entries = $plugin->end();
694 my ( $static_or_dynamic, $currentdir, $date, $flavour, $content_type )
698 %others = ref $others ?
%$others : ();
701 foreach my $plugin (@plugins) {
702 if ( $plugins{$plugin} > 0 and $plugin->can('filter') ) {
703 $entries = $plugin->filter( \
%files, \
%others );
710 # Allow plugins to decide if we can cut short story generation
712 foreach my $plugin (@plugins) {
713 if ( $plugins{$plugin} > 0 and $plugin->can('skip') ) {
714 if ( my $tmp = $plugin->skip() ) {
721 # Define default interpolation subroutine
724 my $template = shift;
725 # Interpolate scalars, namespaced scalars, and hash/hashref scalars
726 $template =~ s/(\$\w+(?:::\w+)*(?:(?:->)?{([\'\"]?)[-\w]+\2})?)/"defined $1 ? $1 : ''"/gee;
730 unless ( defined($skip) and $skip ) {
732 # Plugins: Interpolate
733 # Allow for the first encountered plugin::interpolate subroutine to
734 # override the default built-in interpolate subroutine
735 foreach my $plugin (@plugins) {
736 if ( $plugins{$plugin} > 0 and $plugin->can('interpolate') ) {
737 if ( my $tmp = $plugin->interpolate() ) {
745 my $head = ( &$template( $currentdir, 'head', $flavour ) );
748 foreach my $plugin (@plugins) {
749 if ( $plugins{$plugin} > 0 and $plugin->can('head') ) {
750 $entries = $plugin->head( $currentdir, \
$head );
754 $head = &$interpolate($head);
760 my $ne = $num_entries;
762 if ( $currentdir =~ /(.*?)([^\/]+)\
.(.+)$/ and $2 ne 'index' ) {
763 $currentdir = "$1$2.$file_extension";
764 %f = ( "$datadir/$currentdir" => $files{"$datadir/$currentdir"} )
765 if $files{"$datadir/$currentdir"};
768 $currentdir =~ s!/index\..+$!!;
771 # Define a default sort subroutine
773 my ($files_ref) = @_;
775 sort { $files_ref->{$b} <=> $files_ref->{$a} }
780 # Allow for the first encountered plugin::sort subroutine to override the
781 # default built-in sort subroutine
782 foreach my $plugin (@plugins) {
783 if ( $plugins{$plugin} > 0 and $plugin->can('sort') ) {
784 if ( my $tmp = $plugin->sort() ) {
791 foreach my $path_file ( &$sort( \
%f, \
%others ) ) {
792 last if $ne <= 0 && $date !~ /\d/;
793 use vars qw
/ $path $fn /;
795 = $path_file =~ m!^$datadir/(?:(.*)/)?(.*)\.$file_extension!;
797 # Only stories in the right hierarchy
798 $path =~ /^$currentdir/
799 or $path_file eq "$datadir/$currentdir"
802 # Prepend a slash for use in templates only if a path exists
805 # Date fiddling for by-{year,month,day} archive views
807 qw
/ $dw $mo $mo_num $da $ti $yr $hr $min $hr12 $ampm $utc_offset/;
808 ( $dw, $mo, $mo_num, $da, $ti, $yr, $utc_offset )
809 = nice_date
( $files{"$path_file"} );
810 ( $hr, $min ) = split /:/, $ti;
811 ( $hr12, $ampm ) = $hr >= 12 ?
( $hr - 12, 'pm' ) : ( $hr, 'am' );
813 if ( $hr12 == 0 ) { $hr12 = 12 }
815 # Only stories from the right date
816 my ( $path_info_yr, $path_info_mo_num, $path_info_da )
818 next if $path_info_yr && $yr != $path_info_yr;
819 last if $path_info_yr && $yr < $path_info_yr;
820 next if $path_info_mo_num && $mo ne $num2month[$path_info_mo_num];
821 next if $path_info_da && $da != $path_info_da;
822 last if $path_info_da && $da < $path_info_da;
825 my $date = ( &$template( $path, 'date', $flavour ) );
828 foreach my $plugin (@plugins) {
829 if ( $plugins{$plugin} > 0 and $plugin->can('date') ) {
831 = $plugin->date( $currentdir, \
$date,
832 $files{$path_file}, $dw, $mo, $mo_num, $da, $ti,
837 $date = &$interpolate($date);
839 if ( $date && $curdate ne $date ) {
844 use vars qw
/ $title $body $raw /;
845 if ( -f
"$path_file" && $fh->open("< $path_file") ) {
846 chomp( $title = <$fh> );
847 chomp( $body = join '', <$fh> );
849 $raw = "$title\n$body";
851 my $story = ( &$template( $path, 'story', $flavour ) );
854 foreach my $plugin (@plugins) {
855 if ( $plugins{$plugin} > 0 and $plugin->can('story') ) {
856 $entries = $plugin->story( $path, $fn, \
$story, \
$title,
861 # Save unescaped versions and allow them to be used in
863 use vars qw
/$url_unesc $path_unesc $fn_unesc/;
868 # Fix special characters in links inside XML content
869 if ( $encode_xml_entities &&
870 $content_type =~ m{\bxml\b} &&
871 $content_type !~ m{\bxhtml\b} ) {
872 # Escape special characters inside the <link> container
874 &url_escape_url_path_and_fn
();
876 # Escape <, >, and &, and to produce valid RSS
877 $title = blosxom_html_escape
($title);
878 $body = blosxom_html_escape
($body);
879 $url = blosxom_html_escape
($url);
880 $path = blosxom_html_escape
($path);
881 $fn = blosxom_html_escape
($fn);
884 # Fix special characters in links inside XML content
885 if ($encode_8bit_chars) {
886 &url_escape_url_path_and_fn
();
889 $story = &$interpolate($story);
898 my $foot = ( &$template( $currentdir, 'foot', $flavour ) );
901 foreach my $plugin (@plugins) {
902 if ( $plugins{$plugin} > 0 and $plugin->can('foot') ) {
903 $entries = $plugin->foot( $currentdir, \
$foot );
907 $foot = &$interpolate($foot);
911 foreach my $plugin (@plugins) {
912 if ( $plugins{$plugin} > 0 and $plugin->can('last') ) {
913 $entries = $plugin->last();
919 # Finally, add the header, if any and running dynamically
920 $output = header
($header) . $output
921 if ( $static_or_dynamic eq 'dynamic' and $header );
929 my $c_time = CORE
::localtime($unixtime);
930 my ( $dw, $mo, $da, $hr, $min, $sec, $yr )
932 =~ /(\w{3}) +(\w{3}) +(\d{1,2}) +(\d{2}):(\d{2}):(\d{2}) +(\d{4})$/
935 $da = sprintf( "%02d", $da );
936 my $mo_num = $month2num{$mo};
939 = timegm
( $sec, $min, $hr, $da, $mo_num - 1, $yr - 1900 ) - $unixtime;
940 my $utc_offset = sprintf( "%+03d", int( $offset / 3600 ) )
941 . sprintf( "%02d", ( $offset % 3600 ) / 60 );
943 return ( $dw, $mo, $mo_num, $da, $ti, $yr, $utc_offset );
946 sub url_escape_url_path_and_fn
{
947 $url =~ s
($url_escape_re)(sprintf('%%%02X', ord($&)))eg
;
948 $path =~ s
($url_escape_re)(sprintf('%%%02X', ord($&)))eg
;
949 $fn =~ s
($url_escape_re)(sprintf('%%%02X', ord($&)))eg
;
952 # Default HTML and RSS template bits
954 html content_type text
/html
; charset
=$blog_encoding
956 html head
<!DOCTYPE html PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
959 html head
<meta http
-equiv
="content-type" content
="$content_type" >
960 html head
<link rel
="alternate" type
="application/rss+xml" title
="RSS" href
="$url/index.rss" >
961 html head
<title
>$blog_title $path_info_da $path_info_mo $path_info_yr</title
>
964 html head
<div align
="center">
965 html head
<h1
>$blog_title</h1
>
966 html head
<p
>$path_info_da $path_info_mo $path_info_yr</p
>
970 html story
<h3
><a name
="$fn">$title</a></h3
>
971 html story
<div
>$body</div
>
972 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>
975 html date
<h2
>$dw, $da $mo $yr</h2
>
978 html foot
<div align
="center">
979 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
>
984 rss content_type text
/xml
; charset
=$blog_encoding
986 rss head
<?xml version
="1.0" encoding
="$blog_encoding"?
>
987 rss head
<rss version
="2.0">
989 rss head
<title
>$blog_title</title
>
990 rss head
<link>$url/$path_info</link
>
991 rss head
<description
>$blog_description</description
>
992 rss head
<language
>$blog_language</language
>
993 rss head
<docs
>http
://blogs
.law
.harvard
.edu
/tech/rss
</docs
>
994 rss head
<generator
>blosxom
/$version</generator
>
997 rss story
<title
>$title</title
>
998 rss story
<pubDate
>$dw, $da $mo $yr $ti:00 $utc_offset</pubDate
>
999 rss story
<link>$url/$yr/$mo_num/$da#$fn</link>
1000 rss story
<category
>$path</category
>
1001 rss story
<guid isPermaLink
="false">$url$path/$fn</guid>
1002 rss story
<description
>$body</description
>
1010 error content_type text
/html
1012 error head
<!DOCTYPE html PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
1014 error head
<head
><title
>Error
: unknown Blosxom flavour
"$flavour"</title></head
>
1016 error head
<h1
><font color
="red">Error
: unknown Blosxom flavour
"$flavour"</font></h1
>
1017 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
>
1019 error story
<h3
>$title</h3
>
1020 error story
<div
>$body</div> <p><a href="$url/$yr/$mo_num/$da#fn.$default_flavour">#</a></p>
1022 error date
<h2
>$dw, $da $mo $yr</h2
>