# HG changeset patch
# User Shoshi TAMAKI which can be obtained from the first sequence by
+deleting some items, and from the secend sequence by deleting other
+items. You also want I to be as long as possible. In this case
+I is
+
+ a b c d f g j z
+
+From there it's only a small step to get diff-like output:
+
+ e h i k q r x y
+ + - + + - + + +
+
+This module solves the LCS problem. It also includes a canned
+function to generate Cの中にスペースだけの行が含まれたときに一度
になってしまうのを防ぐため。
+
+*2002-08-28
+- Yuki::YukiWikiDBにflockを入れた。
+- Yuki::YukiWikiDBのデバッグプリントをdieに変更した。
+- hyuki.comで運用していたYukiWikiのデータベースを復旧。
+- AnyDBM_FileからYuki::YukiWikiDBに手動で変換。
+- wiki.cgiスクリプトには変更なし。
+
+*2002-08-22
+- YukiWiki 2.0.5公開。
+
+*2002-08-22
+- 2.0.4のXSS対策に不十分な点があったので&print_editform修正(関村さんの指摘による)。
+$form{mypassword}を&escapeしていなかった。
+- $resource{anydbmfileerroranydbmfileerror}のtypo修正。
+
+*2002-07-12
+- YukiWiki 2.0.4公開。
+
+*2002-07-12
+- 2.0.3のXSS対策に不十分な点があったので&make_link修正(塚本牧生さんの指摘による)。
+- 配布物にデバッグ用のコードが残っていたのでコメントアウト。
+
+*2002-07-11
+- YukiWiki 2.0.3公開。
+
+*2002-07-11
+- officeさんから「クロスサイトスクリプティング脆弱性」の指摘を受ける。
+- ページ名にスクリプトが混入させられた場合に対処。
+- 生でページ名を見せず、必ず&escapeする修正を行う。
+
+*2002-06-30
+- do_indexでページ漏れが生じる問題はまだ未解決。
+- use AnyDBM_File;をevalでくるみ、エラーにならないように注意する。
+- エラーになったとき、$modifier_dbtypeがAnyDBM_Fileになっていたらエラーページを表示。
+- (自動的に$modifier_dbtypeを変更することはしない)
+
+*2002-06-06
+- YukiWiki 2.0.0公開。
+
+*2002-05-31
+- history1.txt, history2.txt作成。
+- wikiman_ja.pl作成。
+
+*2002-05-22
+- #で始まるページ名はnot is_editable.
+- 衝突が起きたときの画面を修正。
+
+*2002-05-21
+- RSSをXMLファイルではなくCGI経由で出すように修正。
+- &を正しくページ名として扱えるように修正。
+- wiki.cgiをreadme2.txtをreadme_en.txtとreadme_ja.txtに分離。
+- CGI中のPODは最小限にした。
+- データディレクトリを別ディレクトリにできるように$modifier_dir_dataを導入した。
+- alpha6
+
+*2002-05-19
+- footerの部分はaddress要素に変更。shinoさんの指摘による。
+- 目次リンクにバグあり。余計な#があり、うまく目次リンクからジャンプできなかった。
+
+*2002-05-18
+- WalWikiを参考にして、ページの * や ** は目次として収集するようにした。
+
+*2002-05-17
+- プレビュー画面(YukiWiki1から持ってきた)
+- YukiWiki1からYukiWiki2への移行ツール(WalWikiのconvert.cgiを改造。感謝)。
+- wiki.txt→frontpage.txtにリネーム。
+- YukiWikiDBを別ファイルにしてYuki::YukiWikiDBに移動。
+- AnyDBM_File, YukiWikiDB, dbmopenの3方式に対応(ふう…)。
+- alpha5のスクリプトを公開。
+- YukiWiki1のデータをごそっとYukiWiki2に移行してみる。大丈夫だろうか…。
+- とりあえず、YukiWiki1のほうは編集できなくしてみた。そのうち、自動的にYukiWiki2に転送するようにするつもり。
+
+*2002-05-16
+- existsがない場合にはdefinedで代用できるように$use_existsを導入。
+- RecentChangesの更新ミスを修正。
+
+*2002-05-14
+- RSS機能(Yuki::RSS)を追加した。
+- 差分用のコードがごちゃごちゃしていたので、Yuki::DiffTextを作った。
+- WalWiki(http://digit.que.ne.jp/work/index.cgi?WalWiki)からコードを借りてテーブルを実装。
+- alpha3のスクリプトを公開。
+
+*2002-05-13
+- 普通のページを凍結する手段はすでに作られている。
+- FrontPageに大きなロゴをつけた。
+- 差分をまずは組み込んだ。完全にはリソース化されていない。HTML::Templateが使いたくなる。
+
+*2002-05-09
+- 画像をimg化するようにした。
+- FormatRuleを編集画面の下のほうに出すようにした。
+
+*2002-05-08
+- TfWikiをYukiWiki2として、ディレクトリ移動。
+- 元のTfWikiのほうへのリンクはYukiWiki2へリダイレクト。
+
+*2002-04-14
+- [[YukiWiki2]]を次回のC MAGAZINEの連載題材にしようと考慮中。
+- [[YukiWiki2]]のロゴ作成依頼中。
+
+*2002-03-27
+- [[一言コメント機能]]で書き込んでも[[更新履歴]]が更新されない不具合の修正。
+- [[一言コメント逆順機能]]を試しに実装する。
+
+*2002-03-26
+- [[大阪弁:TfWiki作業記録]]というのはどうでしょうか。[[極悪]]さんのご紹介。
+- 画像のロゴは不要かもしれないが、リンク用バナーアイコン88x31はあってもいいかな。今度は割とシャープな感じにしたいな。
+- 編集のリンクに画像を使うことはやらない予定。ああいうアイコンって分かりにくいと思うから。
+- メニューバーは漢字表記になっているけれど、実はスクリプト中にはまったく日本語はないのだ。リソースファイルとして外部ファイル化している。だからやる気になれば、スクリプトはそのままで、(クッキーやCGIへ渡す引数などで)英語/日本語の全切り替えも可能なのだ。
+- 知らないうちに[[英語版:FrontPage]]もできそうな感じ。InterWikiNameに追加してくれた人、ありがとうございます。
+
+*2002-03-25
+- AdminEditでのエンコード忘れ修正。
+- タイトルを検索リンクにした。
+- TfWikiもいいけれど、YukiWikiをバージョンアップしたくなってきた。
+- 急に整形ルールを変更してみる。当然のごとくあちこちのレイアウトがおかしくなるのであった。ひえー。
+- だんだんTfWikiではなく、YukiWiki2.0を作っている気分になってきました。
+- [[一言コメント機能]]を実装しました。
+- RecentChangesや一言コメントの日付けはmonospaceになるようにスタイル指定。
+- 一言コメントをつけたときにもRecentChangesが更新されるように修正。
+- PlugIn機能を実装しているWikiがあるけれど、私はPlugInにはあまり興味なし。
+- できるだけシンプルに、でもあちこちのWikiClonesで実装されている便利な機能は取り込んでいくつもり。
+- で、適当なタイミングで現在のYukiWikiレポジトリをEUCに変換して、YukiWikiのバージョンアップへとつなげていこうと思っている。
+- [[YukiWiki2]]向けのロゴも作りたいな。
+- このページを観察しているページを発見(^_^) => http://www1.u-netsurf.ne.jp/~dune/wiki_2Epl.html?
+
+*2002-03-24
+- InterWikiを実験的に実装中。[[google:結城浩]]
+InterWikiはクリックされたときに展開したほうがよい、という指摘がありました。
+でも、それほど大きな負担ではないと思うので、まずはこのままにしておきます。
+- タイムスタンプの更新をやめることも可能にしました。
+- タッチファイルをいれて、更新情報でチェックしやすくしました。
+- 管理者という存在を入れました。外からは見えないけれど。
+- [[凍結ページ機能]]を実装しました。ちなみにこのページ自身も凍結。
+- 凍結ページの場合には右下にAdminEditのリンクを用意するようにした。
+
+*2002-03-23
+- 日本語のWikiNameは[[ ]]でくくることにしました。
+-- KbWikiのように「 」にしようかな…。
+-- それ、Tikiでそうなっているけど、なにかと不便です‥‥‥。
+-- ふうん、やっぱり難しいものなのですね。
+-- 思い付きですが、"ほげほげ。。"というように、まるを2つ付ければWikinameになるというのどうでしょうか。いちいち、IMEを切替えなくて楽かも知れません。
+-- または、"#ほげほげ"のように、全角の#を付けたときとか。#は滅多に使いませんし。
+-- アイディアどうもです。EUCにすると[[ ]]でくくってもあまりきにならなくなっちゃいました...
+
+- 衝突のチェック。
+-- ちなみに、衝突のチェックはYukiWikiのときと同じようにページのハッシュ値を使う。
+-- Last modifiedの情報を入れる
+-- これ、もう少し汎用にして、ページの「ヘッダ」を作る。Subjectもそこに入れる。
+-- 結局、衝突のチェックはLast-Modifedの情報を別データベースにいれた。
+-- ページのヘッダは煩雑すぎるのでやめ。
+
+*2002-03-22
+- 以降は、YukiWiki2の作業記録である。
+- IndexPage, SearchPage, HelpPageなどを作る
+-- これ、InterWikiの機能を使って実現できそうだな。自分自身をサーバとする。
+
+*2002-03-06
+- Version 1.6.8。
+- スタイルシートを別ファイルとした($stylesheet)。
+- Content-typeを出力する際にcharsetを明示的に出すようにした(print_header)。
+- プレビュー画面でも更新の衝突をチェックするように修正(check_conflict)。
+- 差分表示のとき、モノクロなブラウザでも分かるように表示(do_diff)。
+
+*2002-03-01
+- Version 1.6.7。
+- パッケージにDiff.pmを同梱。
+
+*2001-10-20
+- Version 1.6.6。
+- 更新の衝突対策。
+- 元ページの簡単なチェックサムを取っておき、更新前にチェックサムを比較する。
+修正個所はdigestという文字列を検索すれば分かる。
+本来はMD5などでちゃんとやった方がいいのだけれど。
+- 衝突時に表示されるメッセージなどは「極悪」さんのページを参考にした。
+
+*2001-10-17
+- Version 1.6.5。
+- プレビュー画面で、更新ボタンを押したときに送信される
+メッセージの内容をinput要素のtype="hidden"を使って埋め込むのをやめる。
+代わりに、textarea要素を使う。
+- 再プレビュー用にmyspecial_を導入。でもきれいな対策ではない。
+
+*2001-08-30
+- Version 1.6.4。
+- URLでダイレクトにページ名を指定しても、
+$WikiNameと$BracketName以外のページを作れないようにした。
+(is_valid_nameとis_editable参照)。
+
+*2001-08-30
+- Version 1.6.3。
+- RecentChangesを編集・再編集不可とした。
+- 編集不可ページは@uneditableにページ名を入れる。
+
+*2001-02-25
+- Version 1.6.1, 1.6.2。
+- 差分機能のバグ修正。
+- do_previewで'>'が扱えないバグを修正(ユーザからの指摘)。
+
+*2001-02-22
+- Version 1.6.0。
+- 差分機能を実装した。
+
+*2001-02-19
+- Version 1.5.4。
+- 画像ファイルへのリンクは画像にしてみた。
+
+*2001-02-19
+- Version 1.5.3。
+- RecentChangesの中に削除したページがあるのをやめた。
+- use strict;で引っかかる部分を少し整理(完全ではない)。
+
+*2001-02-16
+- Version 1.5.2。
+- textareaに表示およびプレビューする前に < や > を < や > に変換した
+(do_preview, editpage, print_preview_buttons)。
+
+*2000-12-27
+- Version 1.5.1。
+- プレビュー画面を整理した。
+
+*2000-12-22
+- Version 1.5.0。
+- 全体的にずいぶん書き直した。
+- 一覧を別途作成するようにした(do_list)。
+- 書き込む前に確認画面を出すようにした(do_preview)。
+- テキストの書き方を編集画面に入れた(do_edit, do_reedit)。
+- WhatsNew→RecentChanges、TopPage→FrontPageに変更した。
+
+*2000-12-20
+- Version 1.1.0。
+- tieを利用して、dbmopenが使えない場合でも動作するように修正。
+- 利用者の1人である「極悪」さんから送っていただいたコードを元にしています。
+
+*2000-09-05
+- Version 1.0.2。
+- →
+- 利用者からの指摘による。感謝。
+
+*2000-08-06
+- Version 1.0.1を公開。
+- C MAGAZINE(ソフトバンクパブリッシング)2000年10月号連載記事向け公開版。
+- [[ ]] の最後が「望」のようにシフトJISで
+- 0x5Dになる場合の回避を行なった。
+
+*2000-08-05
+- Version 1.0.0を公開。
+
+*2000-07-23
+- Version 0.82を公開。
+- 編集時のリンクミス。
+-
"); + 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"); + } elsif ($modifier_dbtype eq 'Cassandra::CassHash') { + #use cassandra + tie(%database, "Cassandra::CassHash","localhost",9161,"Keyspace1","Standard1","yukiwiki3") or &print_error("(tie Cassandra::CassHash) $dataname"); + tie(%infobase, "Cassandra::CassHash","localhost",9161,"Keyspace1","Standard1","yukiwiki5") or &print_error("(tie Cassandra::CassHash) $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) or &print_error("(tie AnyDBM_File) $diffname"); + } elsif ($modifier_dbtype eq 'Cassandra::CassHash') { + #use cassandra + tie(%diffbase, "Cassandra::CassHash","localhost",9161,"Keyspace1","Standard1","yukiwiki4") or &print_error("(tie Cassandra::CassHash) $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(); + print qq($1\n); + } elsif (/^\=(.*)/) { + print qq($1\n); + } else { + print qq|??? $_\n|; + } + } + 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