#!/usr/pkg/bin/perl
#
# wiki.cgi - This is YukiWiki, yet another Wiki clone.
#
# Copyright (C) 2000-2004 by Hiroshi Yuki.
# $msg
);
my $maxrecent = 50;
my $max_message_length = 500_000; # -1 for unlimited.
my $cols = 80;
my $rows = 20;
##############################
#
# You MAY modify following variables.
#
my $dataname = "$modifier_dir_data/wiki";
my $infoname = "$modifier_dir_data/info";
my $diffname = "$modifier_dir_data/diff";
my $editchar = '?';
my $subject_delimiter = ' - ';
my $use_autoimg = 1; # automatically convert image URL into tag.
my $use_exists = 0; # If you can use 'exists' method for your DB.
my $use_FixedFrontPage = 0;
##############################
my $InterWikiName = 'InterWikiName';
my $RecentChanges = 'RecentChanges';
my $AdminChangePassword = 'AdminChangePassword';
my $CompletedSuccessfully = 'CompletedSuccessfully';
my $FrontPage = 'FrontPage';
my $IndexPage = 'IndexPage';
my $SearchPage = 'SearchPage';
my $CreatePage = 'CreatePage';
my $ErrorPage = 'ErrorPage';
my $RssPage = 'RssPage';
my $AdminSpecialPage = 'Admin Special Page'; # must include spaces.
##############################
my $wiki_name = '\b([A-Z][a-z]+([A-Z][a-z]+)+)\b';
my $bracket_name = '\[\[(\S+?)\]\]';
my $embedded_name = '\[\[(#\S+?)\]\]';
my $interwiki_definition = '\[\[(\S+?)\ (\S+?)\]\]';
my $interwiki_name = '([^:]+):([^:].*)';
# Sorry for wierd regex.
my $inline_plugin = '\&(\w+)\((([^()]*(\([^()]*\))?)*)\)';
##############################
my $embed_comment = '[[#comment]]';
my $embed_rcomment = '[[#rcomment]]';
##############################
my $info_ConflictChecker = 'ConflictChecker';
my $info_LastModified = 'LastModified';
my $info_IsFrozen = 'IsFrozen';
my $info_AdminPassword = 'AdminPassword';
##############################
my $kanjicode = 'euc';
my $charset = 'EUC-JP';
my $lang = 'ja';
my %fixedpage = (
$IndexPage => 1,
$CreatePage => 1,
$ErrorPage => 1,
$RssPage => 1,
$RecentChanges => 1,
$SearchPage => 1,
$AdminChangePassword => 1,
$CompletedSuccessfully => 1,
$FrontPage => $use_FixedFrontPage,
);
my %form;
my %database;
my %infobase;
my %diffbase;
my %resource;
my %interwiki;
my $plugin_manager;
my $plugin_context = {
debug => 0,
database => \%database,
infobase => \%infobase,
resource => \%resource,
form => \%form,
interwiki => \%interwiki,
url_cgi => $url_cgi,
};
##############################
my %page_command = (
$IndexPage => 'index',
$SearchPage => 'searchform',
$CreatePage => 'create',
$RssPage => 'rss',
$AdminChangePassword => 'adminchangepasswordform',
$FrontPage => 'FrontPage',
);
my %command_do = (
read => \&do_read,
edit => \&do_edit,
adminedit => \&do_adminedit,
adminchangepasswordform => \&do_adminchangepasswordform,
adminchangepassword => \&do_adminchangepassword,
write => \&do_write,
index => \&do_index,
searchform => \&do_searchform,
search => \&do_search,
create => \&do_create,
createresult => \&do_createresult,
FrontPage => \&do_FrontPage,
comment => \&do_comment,
rss => \&do_rss,
diff => \&do_diff,
);
##############################
# &test_convert;
&main;
exit(0);
##############################
sub main {
&init_resource;
# &check_modifiers;
&open_db;
&init_form;
&init_InterWikiName;
&init_plugin;
if ($command_do{$form{mycmd}}) {
&{$command_do{$form{mycmd}}};
} else {
&do_FrontPage;
}
&close_db;
}
sub do_read {
&print_header($form{mypage});
&print_content($database{$form{mypage}});
&print_footer($form{mypage});
}
sub do_edit {
my ($page) = &unarmor_name(&armor_name($form{mypage}));
&print_header($page);
if (not &is_editable($page)) {
&print_message($resource{cantchange});
} elsif (&is_frozen($page)) {
&print_message($resource{cantchange});
} else {
&print_editform($database{$page}, &get_info($page, $info_ConflictChecker), admin=>0);
}
&print_footer($page);
}
sub do_adminedit {
my ($page) = &unarmor_name(&armor_name($form{mypage}));
&print_header($page);
if (not &is_editable($page)) {
&print_message($resource{cantchange});
} else {
&print_message($resource{passwordneeded});
&print_editform($database{$page}, &get_info($page, $info_ConflictChecker), admin=>1);
}
&print_footer($page);
}
sub do_adminchangepasswordform {
&print_header($AdminChangePassword);
&print_passwordform;
&print_footer($AdminChangePassword);
}
sub do_adminchangepassword {
if ($form{mynewpassword} ne $form{mynewpassword2}) {
&print_error($resource{passwordmismatcherror});
}
my ($validpassword_crypt) = &get_info($AdminSpecialPage, $info_AdminPassword);
if ($validpassword_crypt) {
if (not &valid_password($form{myoldpassword})) {
&send_mail_to_admin(<<"EOD", "AdminChangePassword");
myoldpassword=$form{myoldpassword}
mynewpassword=$form{mynewpassword}
mynewpassword2=$form{mynewpassword2}
EOD
&print_error($resource{passworderror});
}
}
my ($sec, $min, $hour, $day, $mon, $year, $weekday) = localtime(time);
my (@token) = ('0'..'9', 'A'..'Z', 'a'..'z');
my $salt1 = $token[(time | $$) % scalar(@token)];
my $salt2 = $token[($sec + $min*60 + $hour*60*60) % scalar(@token)];
my $crypted = crypt($form{mynewpassword}, "$salt1$salt2");
&set_info($AdminSpecialPage, $info_AdminPassword, $crypted);
&print_header($CompletedSuccessfully);
&print_message($resource{passwordchanged});
&print_footer($CompletedSuccessfully);
}
sub do_index {
&print_header($IndexPage);
print qq(
);
foreach my $page (sort keys %database) {
if (&is_editable($page)) {
print qq(
);
&print_footer($IndexPage);
}
sub do_write {
if (&keyword_reject()) {
return;
}
if (&frozen_reject()) {
return;
}
if (&length_reject()) {
return;
}
if (not &is_editable($form{mypage})) {
&print_header($form{mypage});
&print_message($resource{cantchange});
&print_footer($form{mypage});
return;
}
if (&conflict($form{mypage}, $form{mymsg})) {
return;
}
# Making diff
if (1) {
&open_diff;
my @msg1 = split(/\r?\n/, $database{$form{mypage}});
my @msg2 = split(/\r?\n/, $form{mymsg});
$diffbase{$form{mypage}} = &difftext(\@msg1, \@msg2);
&close_diff;
}
if ($form{mymsg}) {
$database{$form{mypage}} = $form{mymsg};
&send_mail_to_admin($form{mypage}, "Modify");
&set_info($form{mypage}, $info_ConflictChecker, '' . localtime);
if ($form{mytouch}) {
&set_info($form{mypage}, $info_LastModified, '' . localtime);
&update_recent_changes;
}
&set_info($form{mypage}, $info_IsFrozen, 0 + $form{myfrozen});
&print_header($CompletedSuccessfully);
&print_message($resource{saved});
&print_content("$resource{continuereading} @{[&armor_name($form{mypage})]}");
&print_footer($CompletedSuccessfully);
} else {
&send_mail_to_admin($form{mypage}, "Delete");
delete $database{$form{mypage}};
delete $infobase{$form{mypage}};
if ($form{mytouch}) {
&update_recent_changes;
}
&print_header($form{mypage});
&print_message($resource{deleted});
&print_footer($form{mypage});
}
}
sub do_searchform {
&print_header($SearchPage);
&print_searchform("");
&print_footer($SearchPage);
}
sub do_search {
my $word = &escape($form{mymsg});
&print_header($SearchPage);
&print_searchform($word);
my $counter = 0;
foreach my $page (sort keys %database) {
next if $page =~ /^$RecentChanges$/;
if ($database{$page} =~ /\Q$form{mymsg}\E/ or $page =~ /\Q$form{mymsg}\E/) {
if ($counter == 0) {
print qq||;
}
print qq(
|;
}
&print_footer($SearchPage);
}
sub do_create {
&print_header($CreatePage);
print <<"EOD";
EOD
&print_footer($CreatePage);
}
sub do_FrontPage {
if ($fixedpage{$FrontPage}) {
open(FILE, $file_FrontPage) or &print_error("($file_FrontPage)");
my $content = join('', @{[&escape($page)]}@{[&escape(&get_subjectline($page))]}
EOD
}
sub print_footer {
my ($page) = @_;
print <<"EOD";
Powered by YukiWiki $version
Modified by $modifier_name.
EOD
}
sub escape {
my $s = shift;
$s =~ s|
|__BR_TAG__|g;
$s =~ s|\r\n|\n|g;
$s =~ s|\&|&|g;
$s =~ s|<|<|g;
$s =~ s|>|>|g;
$s =~ s|"|"|g;
$s =~ s|__BR_TAG__|
|g;
return $s;
}
sub unescape {
my $s = shift;
# $s =~ s|\n|\r\n|g;
$s =~ s|\&|\&|g;
$s =~ s|\<|\<|g;
$s =~ s|\>|\>|g;
$s =~ s|\"|\"|g;
return $s;
}
sub print_content {
my ($rawcontent) = @_;
print &text_to_html($rawcontent, toc=>1);
}
sub text_to_html {
my ($txt, %option) = @_;
my (@txt) = split(/\r?\n/, $txt);
my (@toc);
my $verbatim;
my $tocnum = 0;
my (@saved, @result);
unshift(@saved, "
"); foreach (@txt) { chomp; # verbatim. if ($verbatim->{func}) { if (/^\Q$verbatim->{done}\E$/) { undef $verbatim; push(@result, splice(@saved)); } else { push(@result, $verbatim->{func}->($_)); } next; } # non-verbatim follows. push(@result, shift(@saved)) if (@saved and $saved[0] eq '' and /^[^ \t]/); if (/^(\*{1,3})(.+)/) { # $hn = 'h2', 'h3' or 'h4' my $hn = "h" . (length($1) + 1); push(@toc, '-' x length($1) . qq( ) . &remove_tag(&inline($2)) . qq(\n)); push(@result, splice(@saved), qq(<$hn> ) . &inline($2) . qq($hn>)); $tocnum++; } elsif (/^(-{2,3})\($/) { if ($& eq '--(') { $verbatim = { func => \&inline, done => '--)', class => 'verbatim-soft' }; } else { $verbatim = { func => \&escape, done => '---)', class => 'verbatim-hard' }; } &back_push('pre', 1, \@saved, \@result, " class='$verbatim->{class}'"); } elsif (/^----/) { push(@result, splice(@saved), '
"); } elsif (/^(\s+.*)$/) { &back_push('pre', 1, \@saved, \@result); push(@result, &escape($1)); # Not &inline, but &escape } elsif (/^\,(.*?)[\x0D\x0A]*$/) { &back_push('table', 1, \@saved, \@result, ' border="1"'); ####### # This part is taken from Mr. Ohzaki's Perl Memo and Makio Tsukamoto's WalWiki. # XXXXX my $tmp = "$1,"; my @value = map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_} ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g); my @align = map {(s/^\s+//) ? ((s/\s+$//) ? ' align="center"' : ' align="right"') : ''} @value; my @colspan = map {($_ eq '==') ? 0 : 1} @value; for (my $i = 0; $i < @value; $i++) { if ($colspan[$i]) { while ($i + $colspan[$i] < @value and $value[$i + $colspan[$i]] eq '==') { $colspan[$i]++; } $colspan[$i] = ($colspan[$i] > 1) ? sprintf(' colspan="%d"', $colspan[$i]) : ''; $value[$i] = sprintf('
$msg
); } sub init_form { if (param()) { foreach my $var (param()) { $form{$var} = param($var); } } else { $ENV{QUERY_STRING} = $FrontPage; } my $query = &decode($ENV{QUERY_STRING}); if ($page_command{$query}) { $form{mycmd} = $page_command{$query}; $form{mypage} = $query; } elsif ($query =~ /^($wiki_name)$/) { $form{mycmd} = 'read'; $form{mypage} = $1; } elsif ($database{$query}) { $form{mycmd} = 'read'; $form{mypage} = $query; } # mypreview_edit -> do_edit, with preview. # mypreview_adminedit -> do_adminedit, with preview. # mypreview_write -> do_write, without preview. foreach (keys %form) { if (/^mypreview_(.*)$/) { $form{mycmd} = $1; $form{mypreview} = 1; } } # # $form{mycmd} is frozen here. # $form{mymsg} = &code_convert(\$form{mymsg}, $kanjicode); $form{myname} = &code_convert(\$form{myname}, $kanjicode); } sub update_recent_changes { my $update = "- @{[&get_now]} @{[&armor_name($form{mypage})]} @{[&get_subjectline($form{mypage})]}"; my @oldupdates = split(/\r?\n/, $database{$RecentChanges}); my @updates; foreach (@oldupdates) { /^\- \d\d\d\d\-\d\d\-\d\d \(...\) \d\d:\d\d:\d\d (\S+)/; # date format. my $name = &unarmor_name($1); if (&is_exist_page($name) and ($name ne $form{mypage})) { push(@updates, $_); } } if (&is_exist_page($form{mypage})) { unshift(@updates, $update); } splice(@updates, $maxrecent + 1); $database{$RecentChanges} = join("\n", @updates); if ($file_touch) { open(FILE, "> $file_touch"); print FILE localtime() . "\n"; close(FILE); } if ($file_rss) { &update_rssfile; } } sub get_subjectline { my ($page, %option) = @_; if (not &is_editable($page)) { return ""; } else { # Delimiter check. my $delim = $subject_delimiter; if (defined($option{delimiter})) { $delim = $option{delimiter}; } # Get the subject of the page. my $subject = $database{$page}; $subject =~ s/\r?\n.*//s; return "$delim$subject"; } } sub send_mail_to_admin { my ($page, $mode) = @_; return unless $modifier_sendmail; my $message = <<"EOD"; To: $modifier_mail From: $modifier_mail Subject: [Wiki/$mode] MIME-Version: 1.0 Content-Type: text/plain; charset=ISO-2022-JP Content-Transfer-Encoding: 7bit -------- MODE = $mode REMOTE_ADDR = $ENV{REMOTE_ADDR} REMOTE_HOST = $ENV{REMOTE_HOST} -------- $page -------- $database{$page} -------- EOD &code_convert(\$message, 'jis'); open(MAIL, "| $modifier_sendmail"); print MAIL $message; close(MAIL); } sub open_db { if ($modifier_dbtype eq 'dbmopen') { dbmopen(%database, $dataname, 0666) or &print_error("(dbmopen) $dataname"); dbmopen(%infobase, $infoname, 0666) or &print_error("(dbmopen) $infoname"); } elsif ($modifier_dbtype eq 'AnyDBM_File') { tie(%database, "AnyDBM_File", $dataname, O_RDWR|O_CREAT, 0666) or &print_error("(tie AnyDBM_File) $dataname"); tie(%infobase, "AnyDBM_File", $infoname, O_RDWR|O_CREAT, 0666) or &print_error("(tie AnyDBM_File) $infoname"); } else { tie(%database, "Yuki::YukiWikiDB", $dataname) or &print_error("(tie Yuki::YukiWikiDB) $dataname"); tie(%infobase, "Yuki::YukiWikiDB", $infoname) or &print_error("(tie Yuki::YukiWikiDB) $infoname"); } } sub close_db { if ($modifier_dbtype eq 'dbmopen') { dbmclose(%database); dbmclose(%infobase); } elsif ($modifier_dbtype eq 'AnyDBM_File') { untie(%database); untie(%infobase); } else { untie(%database); untie(%infobase); } } sub open_diff { if ($modifier_dbtype eq 'dbmopen') { dbmopen(%diffbase, $diffname, 0666) or &print_error("(dbmopen) $diffname"); } elsif ($modifier_dbtype eq 'AnyDBM_File') { tie(%diffbase, "AnyDBM_File", $diffname, O_RDWR|O_CREAT, 0666) or &print_error("(tie AnyDBM_File) $diffname"); } else { tie(%diffbase, "Yuki::YukiWikiDB", $diffname) or &print_error("(tie Yuki::YukiWikiDB) $diffname"); } } sub close_diff { if ($modifier_dbtype eq 'dbmopen') { dbmclose(%diffbase); } elsif ($modifier_dbtype eq 'AnyDBM_File') { untie(%diffbase); } else { untie(%diffbase); } } sub print_searchform { my ($word) = @_; print <<"EOD"; EOD } sub print_editform { my ($mymsg, $conflictchecker, %mode) = @_; my $frozen = &is_frozen($form{mypage}); if ($form{mypreview}) { if ($form{mymsg}) { unless ($mode{conflict}) { print qq();
foreach (split(/\n/, $_)) {
if (/^\+(.*)/) {
print qq($1\n);
} elsif (/^\-(.*)/) {
print qq($1\n);
} elsif (/^\=(.*)/) {
print qq($1\n);
} else {
print qq|??? $_\n|;
}
}
print qq();
print qq((print_plugin_log)\n", join("\n", @{$plugin_manager->{log}}), "";
}
}
sub keyword_reject {
my $s = $form{mymsg};
my @reject_words = qw(
buy-cheap.com
ultram.online-buy.com
);
for (@reject_words) {
if ($s =~ /\Q$_\E/) {
&send_mail_to_admin($form{mypage}, "Rejectword: $_");
sleep(30);
return 1;
}
}
return 0;
}
# Thanks to Makio Tsukamoto for dc_date.
sub update_rssfile {
my $rss = new Yuki::RSS(
version => '1.0',
encoding => $charset,
);
$rss->channel(
title => $modifier_rss_title,
link => $modifier_rss_link,
about => $modifier_rss_about,
description => $modifier_rss_description,
);
my $recentchanges = $database{$RecentChanges};
my $count = 0;
foreach (split(/\n/, $recentchanges)) {
last if ($count >= 15);
/^\- (\d\d\d\d\-\d\d\-\d\d) \(...\) (\d\d:\d\d:\d\d) (\S+)/; # date format.
my $dc_date = "$1T$2$modifier_rss_timezone";
my $title = &unarmor_name($3);
my $escaped_title = &escape($title);
my $link = $modifier_rss_link . '?' . &encode($title);
my $description = $escaped_title . &escape(&get_subjectline($title));
$rss->add_item(
title => $escaped_title,
link => $link,
description => $description,
dc_date => $dc_date,
);
$count++;
}
open(FILE, "> $file_rss") or &print_error("($file_rss)");
print FILE $rss->as_string;
close(FILE);
}
1;
__END__
=head1 NAME
wiki.cgi - This is YukiWiki, yet another Wiki clone.
=head1 DESCRIPTION
YukiWiki is yet another Wiki clone.
YukiWiki can treat Japanese WikiNames (enclosed with [[ and ]]).
YukiWiki provides 'InterWiki' feature, RDF Site Summary (RSS),
and some embedded commands (such as [[#comment]] to add comments).
=head1 AUTHOR
Hiroshi Yuki