root/lang/perl/blosxom/blosxom.nyarla.cgi @ 22489

Revision 21831, 19.3 kB (checked in by nyarla, 5 years ago)

二重にエスケープしていたのを修正

  • Property svn:executable set to ON
Line 
1#!/usr/bin/perl
2
3# Blosxom
4# Author: Rael Dornfest <rael@oreilly.com>
5# Version: 2.0.2
6# Home/Docs/Licensing: http://www.blosxom.com/
7# Development/Downloads: http://sourceforge.net/projects/blosxom
8
9package blosxom;
10
11use strict;
12use warnings;
13
14our $version = '2.0.2';
15
16# -- require modules ----------------- #
17
18use FileHandle;
19use DirHandle;
20use FindBin ();
21use File::Find;
22use File::stat;
23use Time::localtime;
24use CGI qw( :standard :netscape );
25
26# -- package variables --------------- #
27
28our (
29    # weblog
30    $blog_title, $blog_description, $blog_language, $url,
31    # entries                               # flavour
32    $datadir, $depth, $file_extension,      $flavour_dir, $default_flavour,
33    # view                                  # plugins
34    $num_entries, $show_future_entries,     $plugin_dir, $plugin_state_dir, @plugin_order,
35    # request
36    $path_info, $path_info_yr, $path_info_mo, $path_info_mo_num, $path_info_da, $flavour,
37    # response              # system
38    $output, $header,       %template, @plugins, %plugins, %files, %indexes, %others,
39    # variables             # subroutines
40    %month2num, @num2month, $template, $interpolate, $entries,
41    # static
42    $static_or_dynamic, $static_dir, $static_password, @static_flavours, $static_entries,
43);
44
45my $fh = FileHandle->new;
46
47%month2num = (  nil => '00', Jan => '01', Feb => '02', Mar => '03',
48                Apr => '04', May => '05', Jun => '06', Jul => '07',
49                Aug => '08', Sep => '09', Oct => '10', Nov => '11', Dec => '12' );
50@num2month = sort { $month2num{$a} <=> $month2num{$b} } keys %month2num;
51
52# ------------------------------------ #
53
54# load configuration file
55{
56    my $file = env_value('config') || "$FindBin::Bin/config.pl";
57    eval { require $file };
58    die "Failed to load configuration file: $file: $@" if ( $@ );
59}
60
61# Use the stated preferred URL or figure it out automatically
62$url ||= url( -path_info => 1 );
63$url =~ s{^include:}{http:} if ( ( $ENV{'SERVER_PROTOCOL'} || q{} ) eq 'INCLUDED' );
64
65# NOTE: Since v3.12, it looks as if CGI.pm misbehaves for SSIs and
66# always appends path_info to the url. To fix this, we always
67# request an url with path_info, and always remove it from the end of the
68# string.
69{
70    my $path_info = ( $ENV{'PATH_INFO'} || q{} );
71    my $len = length $path_info;
72    my $frag = substr( $url, -$len );
73    substr( $url, -$len ) = q{} if ( $frag eq $path_info );
74}
75
76$url =~ s!/$!!;
77
78# Drop ending any / from dir settings
79for ( $datadir, $flavour_dir, $plugin_dir, $static_dir ) {
80    $_ =~ s{/$}{};
81}
82 
83# Fix depth to take into account datadir's path
84$depth += ( $datadir =~ tr{/}{} ) -1 if ( $depth );
85
86# Global variable to be used in head/foot.{flavour} templates
87$path_info = q{};
88
89$static_or_dynamic = (  !$ENV{'GATEWAY_INTERFACE'}
90                        && param('-password')
91                        && $static_password
92                        && param('-password') eq $static_password )
93                   ? 'static'
94                   : 'dynamic'
95                   ;
96param( -name => "-quiet", -value => 1 ) if ( $static_or_dynamic eq 'dynamic' );
97# Path Info Magic
98# Take a gander at HTTP's PATH_INFO for optional blog name, archive yr/mo/day
99my @path_info = split m{/+}, path_info() || param('path') || q{};
100shift @path_info;
101
102while ( $path_info[0] && $path_info[0] =~ /^[a-zA-Z].*$/ && $path_info[0] !~ /^.*\..*$/) {
103    $path_info .= '/' . shift @path_info;
104}
105
106# Flavour specified by ?flav={flav} or index.{flav}
107$flavour = q{};
108
109if ( $path_info[$#path_info] && $path_info[$#path_info] =~ /^(.+)\.(.+?)$/ ) {
110    $flavour = $2;
111    $path_info .= "/$1.$2" if ( $1 ne 'index' );
112    pop @path_info;
113}
114elsif ( ! -d "${datadir}${path_info}" ) {
115    $path_info .= ".${default_flavour}";
116    $flavour    = param('flav') || $default_flavour;
117}
118else {
119    $flavour = param('flav') || $default_flavour;
120}
121
122# Strip spurious slashes
123$path_info =~ s{^/*|/*$}{}g;
124
125# Date fiddling
126( $path_info_yr, $path_info_mo, $path_info_da ) = @path_info;
127$path_info_mo_num = ( $path_info_mo )
128                  ? ( ( $path_info_mo =~ /\d{2}/ )
129                      ? $path_info_mo
130                      : ($month2num{ucfirst(lc $path_info_mo)} || undef) )
131                  : undef ;
132
133$path_info_yr       ||= q{};
134$path_info_mo_num   ||= q{};
135$path_info_da       ||= q{};
136
137for ( $path_info, $path_info_yr, $path_info_mo, $path_info_mo_num, $path_info_da, $flavour ) {
138    $_ = html_escape( $_ );
139}
140
141# Define standard template subroutine, plugin-overridable at Plugins: Template
142$template = sub {
143    my ( $path, $chunk, $flavour ) = @_;
144    my $dir = $flavour_dir || $datadir;
145    $path ||= q{};
146    do {
147        if ( $fh->open("${dir}/${path}/${chunk}.${flavour}", '<') ) {
148            my $file = do { local $/; <$fh> };
149            $fh->close;
150            return $file;
151        }
152    }
153    while ( $path =~ s{(/*[^/]*)$}{} && $1 );
154
155    # Check for definedness, since flavour can be the empty string
156    if ( defined $template{$flavour}{$chunk} ) {
157        return $template{$flavour}{$chunk};
158    }
159    elsif ( defined $template{'error'}{$chunk} ) {
160        return $template{'error'}{$chunk}
161    }
162    else {
163        return q{};
164    }
165};
166# Bring in the templates
167%template = ();
168while (<DATA>) {
169    last if ( $_ =~ m{^__END__$} );
170    my ( $flav, $chunk, $text ) = ( $_ =~ m{^(\S+)\s(\S+)(?:\s(.*))?$} ) or next;
171    $text =~ s{\\n}{\n}mg;
172    $template{$flav}{$chunk} .= "${text}\n";
173}
174
175# Plugins: Start
176if ( $plugin_dir ) {
177    if ( @plugin_order > 0 ) {
178        for my $plugin ( @plugin_order ) {
179            require "${plugin_dir}/${plugin}";
180            if ( $plugin->start() ) {
181                $plugins{$plugin} = 1;
182                push @plugins, $plugin;
183            }
184        }
185    }
186    elsif ( defined( my $dh = DirHandle->new( $plugin_dir ) ) ) {
187        for my $plugin ( grep { $_ =~ m{^\w+$} && -f "${plugin_dir}/${_}" } sort $dh->read ) {
188            next if ( $plugin =~ m{~$} ); # Ignore emacs backup
189            my ( $name, $off ) = ( $plugin =~ m{^\d*(\w+?)(_?)$} );
190            my $on_off = ( $off eq '_' ) ? -1 : 1 ;
191            require "${plugin_dir}/${plugin}";
192            if ( $name->start() ) {
193                $plugins{$name} = $on_off;
194                push @plugins, $name;
195            }
196        }
197        $dh->close;
198    }
199}
200
201# Plugins: Template
202# Allow for the first encountered plugin::template subroutine to override the
203# default built-in template subroutine
204overwrite_sub('template');
205
206# Provide backward compatibility for Blosxom < 2.0rc1 plug-ins
207sub load_template { $template->( @_ ) }
208
209# Define default entries subroutine
210$entries = sub {
211    my ( %files, %indexes, %others );
212    find(
213        sub {
214            my $currnet = $File::Find::dir =~ tr{/}{};
215            return if ( $depth && $currnet > $depth );
216
217            my $name = $File::Find::name;
218
219            return if ( -d $name );
220            return if ( ! -r $name );
221
222            my $time  = time;
223            my $mtime = stat($name)->mtime;
224
225            my ( $path, $fn )
226                = ( $name =~ m{^$datadir/(?:(.*)/)?(.+)\.$file_extension$} );
227
228            if ( ! $fn && $fn eq 'index' || $fn =~ m{\.} ) {
229                $others{$name} = $mtime;
230                return;
231            }
232
233            return if ( ! $show_future_entries && $mtime > $time );
234            $files{$name} = $mtime;
235
236            if ( $static_dir && $static_flavours[0] ) {
237                my $check = "${static_dir}/${path}/index.$static_flavours[0]";
238                if ( param('-all') || ! -f $check || stat($check)->mtime < $mtime ) {
239                    $indexes{$path} = 1;
240                    my $dir = join q{/}, (nice_date($mtime))[5,2,3];
241                    $indexes{$dir} = $dir;
242                    $indexes{( $path ? '${path}/' : '' )."${fn}.${file_extension}"} = 1
243                        if ( $static_entries );
244                }
245            }
246
247        },
248        $datadir,
249    );
250
251    return ( \%files, \%indexes, \%others, );
252};
253
254# Plugins: Entries
255# Allow for the first encountered plugin::entries subroutine to override the
256# default built-in entries subroutine
257overwrite_sub('entries');
258
259my ( $files, $indexes, $others ) = $entries->();
260%indexes = %{ $indexes };
261
262# Static
263if ( $static_or_dynamic eq 'static' ) {
264    print "Blosxom is generating static index pages...\n" if ( ! param('-quiet') );
265
266    # Home Page and Directory Indexes
267    my %done;
268    for my $item ( sort keys %indexes ) {
269        my $path = q{};
270        for ( '', split m{/+}, $item ) {
271            $path .= "/$_";
272            $path =~ s{^/}{};
273            $done{$path}++ and next;
274            if ( ! -d "${static_dir}/${path}" && $path !~ m{\.$file_extension$} ) {
275                mkdir "${static_dir}/${path}", 0755;
276            }
277            for $flavour ( @static_flavours ) {
278                my $content_type = $template->( $path, 'content_type', $flavour );
279                   $content_type =~ s{\n.*}{}s;
280                my $fn = ( $path =~ m{^(.+)\.$file_extension} ) ? $1 : "${path}/index" ;
281                print "${fn}.${flavour}\n" if ( ! param('-quiet') );
282                $fh->open( "${static_dir}/${fn}.${flavour}", '>' )
283                    or die "Couldn't open ${static_dir}/${path} for writing: $!";
284                $output = q{};
285                # dir, entry
286                if ( $indexes{$path} == 1 ) {
287                    $path_info = $path;
288                    $path_info =~ s{\.$file_extension}{.$flavour};
289                    print $fh &generate('static', $path_info, '', $flavour, $content_type);
290                }
291                # date
292                else {
293                    local ( $path_info_yr, $path_info_mo, $path_info_da, $path_info )
294                        = split m{/+}, $path, 4;
295                    $path_info = q{} if ( ! defined $path_info );
296                    print $fh &generate('static', '', $path, $flavour, $content_type );
297                }
298                $fh->close;
299            }
300        }
301    }
302}
303# Dynamic
304else {
305    my $content_type = $template->( $path_info, 'content_type', $flavour );
306       $content_type =~ s{\n.*}{}s;
307
308    $header = { -type => $content_type };
309
310    print generate('dynamic', $path_info, "$path_info_yr/$path_info_mo_num/$path_info_da", $flavour, $content_type);
311}
312
313# Plugins: End
314run_plugins('end');
315
316# Generate
317sub generate {
318    my ( $static_or_dynamic, $currentdir, $date, $flavour, $content_type ) = @_;
319
320    %files = %{ $files };
321    %others = ref $others ? %{ $others } : ();
322
323    # Plugins: Filter
324    run_plugins( filter => \%files, \%others );
325
326    my %f = %files;
327
328    # Plugins: Skip
329    # Allow plugins to decide if we can cut short story generation
330    my $skip;
331    if ( defined( my $flag = run_plugins_first('skip') ) ) {
332        $skip = $flag;
333    }
334
335    # Define default interpolation subroutine
336    $interpolate = sub {
337        package blosxom;
338        my $tmpl = shift;
339        $tmpl =~ s{(\$\w+(?:::)?\w*)}{"defined $1 ? $1 : ''"}gee;
340        return $tmpl;
341    };
342
343    if ( ! defined $skip || ! $skip ) {
344        # Plugins: Interpolate
345        # Allow for the first encountered plugin::interpolate subroutine to
346        # override the default built-in interpolate subroutine
347        overwrite_sub('interpolate');
348
349        # Head
350        my $head = $template->( $currentdir, 'head', $flavour ); 
351        # Plugins: Head
352        run_plugins( head => $currentdir, \$head );
353        $head = $interpolate->($head);
354        $output .= $head;
355       
356        # Stories
357        my $curdate = q{};
358        my $ne      = $num_entries;
359        if ( $currentdir =~ m{(.*?)([^/]+)\.(.+)$} && $2 ne 'index' ) {
360            $currentdir = "$1$2.$file_extension";
361            my $fullpath = "${datadir}/${currentdir}";
362            %f = ( $fullpath => $files{$fullpath} ) if ( $files{$fullpath} );
363        }
364        else {
365            $currentdir =~ s{/index\..+$}{};
366        }
367
368        # Define a default sort subroutine
369        my $sort = sub {
370            my ( $files_ref ) = @_;
371            return sort { $files_ref->{$b} <=> $files_ref->{$a} } keys %{ $files_ref };
372        };
373        # Plugins: Sort
374        # Allow for the first encountered plugin::sort subroutine to override the
375        # default built-in sort subroutine
376        if ( defined( my $sub = run_plugins_first('sort') ) ) {
377            $sort = $sub;
378        }
379
380        for my $path_file ( $sort->( \%f, \%others ) ) {
381            last if ( $ne <= 0 && $date !~ /\d/ );
382            use vars qw( $path $fn );
383            ( $path, $fn )
384                = ( $path_file =~ m{^$datadir/(?:(.*)/)?(.*)\.$file_extension} );
385            $path ||= q{};
386            # Only stories in the right hierarchy
387            if ( $path !~ m{^$currentdir} && $path_file ne "${datadir}/${currentdir}" ) {
388                next;
389            }
390           
391            # Prepend a slash for use in templates only if a path exists
392            $path &&= "/$path";
393
394            # Date fiddling for by-{year,month,day} archive views
395            use vars qw/ $dw $mo $mo_num $da $ti $yr $hr $min $hr12 $ampm /;
396            ( $dw, $mo, $mo_num, $da, $ti, $yr ) = nice_date( $files{"$path_file"} );
397            ( $hr, $min) = split m{:}, $ti;
398            ( $hr12, $ampm ) = ( $hr >= 12 ) ? ( $hr - 12,'pm' ) : ( $hr, 'am' ) ;
399            $hr12 =~ s{^0}{};
400            $hr12 = 12 if ( $hr12 == 0 );
401
402            # Only stories from the right date
403            my ( $path_info_yr, $path_info_mo_num, $path_info_da ) = split m{/+}, $date;
404            next if ( $path_info_yr && $yr != $path_info_yr );
405            last if ( $path_info_yr && $yr < $path_info_yr );
406            next if ( $path_info_mo_num && $mo ne $num2month[$path_info_mo_num] );
407            next if ( $path_info_da && $da != $path_info_da );
408            last if ( $path_info_da && $da < $path_info_da );
409
410            # Date
411            my $date = $template->( $path, 'date', $flavour );
412
413            # Plugins: Date
414            run_plugins( date => $currentdir, \$date, $files{$path_file}, $dw, $mo, $mo_num, $da, $ti, $yr );
415            $date = $interpolate->($date);
416
417            if ( $curdate ne $date ) {
418                $curdate = $date;
419                $output .= $date;
420            }
421
422            use vars qw( $title $body $raw );
423            if ( -f $path_file && $fh->open( $path_file, '<' ) ) {
424                chomp( $title   = <$fh> );
425                chomp( $body    = do { local $/; <$fh> } );
426                $fh->close;
427                $raw = "${title}\n{$body}";
428            }
429            my $story = $template->( $path, 'story', $flavour );
430
431            # Plugins: Story
432            run_plugins( story => $path, $fn, \$story, \$title, \$body );
433
434            if ( $content_type =~ m{\bxml\b} ) {
435                # Escape <, >, and &, and to produce valid RSS
436                for ( $title, $body ) {
437                    $_ = html_escape( $_ );
438                }
439            }
440
441            $story   = $interpolate->( $story );
442            $output .= $story;
443            $ne--;
444        }
445 
446        # Foot
447        my $foot = $template->( $currentdir, 'foot', $flavour );
448 
449        # Plugins: Foot
450        run_plugins( foot => $currentdir, \$foot );
451        $foot    = $interpolate->( $foot );
452        $output .= $foot;
453
454        # Plugins: Last
455        run_plugins('last');
456    }# End skip
457
458    # Finally, add the header, if any and running dynamically
459    $output = header( $header ) . $output
460        if ( $static_or_dynamic eq 'dynamic' && $header );
461    return $output;
462}
463
464sub run_plugins {
465    my ( $method, @args ) = @_;
466
467    for my $plugin ( @plugins ) {
468        if ( $plugins{$plugin} > 0 && $plugin->can($method) ) {
469            $entries = $plugin->$method( @args );
470        }
471    }
472}
473
474sub run_plugins_first {
475    my ( $method, @args ) = @_;
476
477    for my $plugin ( @plugins ) {
478        if (    $plugins{$plugin} > 0
479                && $plugin->can($method)
480                && defined( my $ret = $plugin->$method( @args ) )   ) {
481            return $ret;
482        }
483    }
484
485    return;
486}
487
488sub overwrite_sub {
489    my ( $method, @args ) = @_;
490
491    if ( defined( my $sub = run_plugins_first( $method => @args ) ) ) {
492        die "Return value of plugin is not CODE reference: method:$method"
493            if ( ref $sub ne 'CODE' );
494        no strict 'refs';
495        ${"${method}"} = $sub;
496    }
497}
498
499sub nice_date {
500    my ( $unixtime ) = @_;
501
502    my $ctime = ctime($unixtime);
503    my ( $dw, $mo, $da, $ti, $yr )
504        = ( $ctime =~ m{(\w{3})[ ]+(\w{3})[ ]+(\d{1,2})[ ]+(\d{2}:\d{2}):\d{2}[ ]+(\d{4})$} );
505    $da = sprintf('%02d', $da);
506    my $mo_num = $month2num{$mo};
507
508    return ( $dw, $mo, $mo_num, $da, $ti, $yr );
509}
510
511sub env_value {
512    my $prefix = uc __PACKAGE__;
513    my $key    = uc( shift @_ );
514
515    my $env = "${prefix}_${key}";
516
517    if ( exists $ENV{$env} ) {
518        return $ENV{$env};
519    }
520    return;
521}
522
523
524
525sub html_escape {
526    my ( $str ) = @_;
527
528    my %escape = (
529        '<' => '&lt;',
530        '>' => '&gt;',
531        '&' => '&amp;',
532        '"' => '&quot;',
533        "'" => '&apos;',
534    );
535
536    my $escape_re = join q{|}, keys %escape;
537
538    $str =~ s{($escape_re)}{$escape{$1}}sg;
539
540    return $str;
541}
542
543# Default HTML and RSS template bits
544__DATA__
545html content_type text/html
546
547html head <html>
548html head     <head>
549html head         <link rel="alternate" type="type="application/rss+xml" title="RSS" href="$url/index.rss" />
550html head         <title>$blog_title $path_info_da $path_info_mo $path_info_yr
551html head         </title>
552html head     </head>
553html head     <body>
554html head         <center>
555html head             <font size="+3">$blog_title</font><br />
556html head             $path_info_da $path_info_mo $path_info_yr
557html head         </center>
558html head         <p />
559
560html story        <p>
561html story            <a name="$fn"><b>$title</b></a><br />
562html story            $body<br />
563html story            <br />
564html story            posted at: $ti | path: <a href="$url$path">$path </a> | <a href="$url/$yr/$mo_num/$da#$fn">permanent link to this entry</a>
565html story        </p>
566
567html date         <h3>$dw, $da $mo $yr</h3>
568
569html foot
570html foot         <p />
571html foot         <center>
572html foot             <a href="http://www.blosxom.com/"><img src="http://www.blosxom.com/images/pb_blosxom.gif" border="0" /></a>
573html foot         </center>
574html foot     </body>
575html foot </html>
576
577rss content_type text/xml
578
579rss head <?xml version="1.0"?>
580rss head <!-- name="generator" content="blosxom/$version" -->
581rss head <!DOCTYPE rss PUBLIC "-//Netscape Communications//DTD RSS 0.91//EN" "http://my.netscape.com/publish/formats/rss-0.91.dtd">
582rss head
583rss head <rss version="0.91">
584rss head   <channel>
585rss head     <title>$blog_title $path_info_da $path_info_mo $path_info_yr</title>
586rss head     <link>$url</link>
587rss head     <description>$blog_description</description>
588rss head     <language>$blog_language</language>
589
590rss story   <item>
591rss story     <title>$title</title>
592rss story     <link>$url/$yr/$mo_num/$da#$fn</link>
593rss story     <description>$body</description>
594rss story   </item>
595
596rss date
597
598rss foot   </channel>
599rss foot </rss>
600
601error content_type text/html
602
603error head <html>
604error head <body>
605error head     <p><font color="red">Error: 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.</font>
606
607
608error story <p><b>$title</b><br />
609error story $body <a href="$url/$yr/$mo_num/$da#fn.$default_flavour">#</a></p>
610
611error date <h3>$dw, $da $mo $yr</h3>
612
613error foot     </body>
614error foot </html>
615__END__
616
617=head1 AUTHOR
618
619Original script by Rael Dornfest
620
621Based on blosxom 2.0.2 in SourceForge.net
622
623Modified by Naoki Okamura (Nyarla) E<lt>thotep@nyarla.netE<gt>
Note: See TracBrowser for help on using the browser.