#!/usr/bin/perl $ETCDIR = "/etc"; $BINDIR = "/usr/bin"; # cgi.pl - CGI assistant routines # Copyright (C) by TOYODA Eizi, 1999. All rights reserved. # $rcsid .= '$Id: cgi.pl,v 1.4 1999/11/06 18:04:25 toyoda Exp $'; # # ホスト名 (FQDN) を返す。 # Debian GNU/Linux システムで DNS 設定が行われた状態しか考えていない sub Hostname () { local($hostname) = '/bin/hostname'; return 'localhost' unless -x $hostname; delete $ENV{'PATH'}; $hostname = `$hostname -f`; chop $hostname; return $hostname; } # # HTML を作るためのサブルーチン. # 名前が print で始まるものは基本的に print 文の群れであり, # 標準出力 (select で変更可能) に HTML の一部を出力する. # # 文字列を HTML 文書に挿入できるように変形する. # 引数 # $line 任意の文字列 # 返却値 # HTML 中で特殊な意味をもつ文字 & < > " を変換したもの # バグ # \0 は & と混同されてしまう. sub HTMLEscape { local($line) = @_; $line =~ s/&/\0/g; $line =~ s//\>/g; $line =~ s/"/\"/g; $line =~ s/\0/\&/g; return $line; } # HTTP ヘッダおよび HTML の先頭部を作成する。 # 引数 # $title 文書のタイトル # $color (省略可) 色指定 sub printHtmlOpening { local($title, $color) = @_; $color = "#CCEECC" unless $color; local($hostname) = &Hostname(); print <<"END"; Content-type: text/html $title dcbib ($hostname にて動作中)

$title

END } # &printTextField が生成するテキストフィールドの大きさ # 変えたいやつはあとで変えてくれ. $printTextFieldLength = 45; $printTextFieldHeight = 4; # テキストエリアでだけ使用 # 登録フォームの のなかでひとつのテキストフィールドの # 入力を要求する を生成する. # # 引数 # $fieldname 欄の名称 # $description 欄の説明 # $default 省略時初期値 (なくてもよい) # %card{$fieldname} が存在すればそれが優先 # 暗黙の引数 # %card sub printTextField { local($fieldname, $description, $default) = @_; local($size) = $printTextFieldLength; local($defaultvalue) = $card{$fieldname} || $default; if ($fieldname eq "token") { # # 欄の名称が token の場合はパスワード欄にする # また初期値は空にする print <<"END"; END } elsif ($default =~ /\n/) { # # 省略時初期値に改行が含まれていればテキストフィールド # (スクロールできる) # print <<"END"; END } else { print <<"END"; END } } # 登録フォームの
$description
$description
$description
のなかで選択リストのラジオボタンをもって # 入力を要求する を生成する. # 省略時初期値に "\n" が含まれていれば複数行オプションをつける. # 引数 # $fieldname 欄の名称 # $description 欄の説明 # $default デフォルト値 # @ValueDescriptionList # 可能な値のリスト. 各項が "\t" を含んでいれば # その後の説明が表示される. sub printRadioButton { local($fieldname, $description, $default, @ValueDescriptionList) = @_; print <<"END"; END } # HTML 文書の末尾の作成 # 引数 # $title 文書のタイトル sub printHtmlClosing { local($title) = @_; print "
\n"; print "$title にもどる
\n" if &FormSubmitted(); print <<"END";
管理人: $MAILADDR
$rcsid
END } # # CGI (Common Gateway Interface) 関係のサブルーチン # # ユーザ情報カードを読み込んだ連想配列 %param から # クッキーにセットすべき文字列を作る. sub HashToCookie { local(%param) = @_; local(@pairs) = (); local($key, $value); foreach $key (sort(keys(%param))) { $value = $param{$key}; push(@pairs, &URLEncode($key) . ":" . &URLEncode($value)); } join('&', @pairs); } # CGI スクリプトから呼び出した場合, 自分自身のファイル名を返す. # これは応答の送り先や戻り先のリンクとして使われる. sub CGISelfName { $ENV{'SCRIPT_NAME'}; } # CGI データの長さを返す. sub CGIDataLength { $ENV{'CONTENT_LENGTH'}; } # ユーザが Submit ボタンを押した場合真が、 # そうでない場合偽が返される。 # CGI プロトコル参照. sub FormSubmitted() { return ($ENV{"CONTENT_LENGTH"} > 0); } # CGI データを読み込んで連想配列として返す. # データが与えられていない場合は空リストが返る. sub getCGIData { local(%card); local($data, $key, $value); require 'jcode.pl'; return () unless &FormSubmitted(); read(STDIN, $alldata, &CGIDataLength); foreach $data (split(/&/, $alldata)) { ($key, $value) = split(/=/, $data); $value =~ tr/+/ /; $value = &URLDecode($value); &jcode'convert(*value, "euc"); $card{$key} = $value; } %card; } # 引数 $x を URL エンコードして返す. # クッキーにいれる文字列は全てこの処理をしなくてはならない。 # CGI プロトコル参照. sub URLEncode { local($x) = @_; $x =~ s/([&:;=%\x00-\x21])/sprintf("%%%02X",unpack("C",$1))/ge; return $x; } # URL エンコードされた文字列 (引数 $x) を解読して返す. # クッキー起源の文字列は全てこの処理をしなくてはならない。 # CGI プロトコル参照. sub URLDecode { local($x) = @_; $x =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("C",hex($1))/ge; return $x; } 0; # addcgi.pl - CGI main program for dcbib # Copyright (C) TOYODA Eizi, 1999. All rights reserved. # $rcsid .= '$Id: addcgi.pl,v 1.20 1999/11/06 18:14:50 toyoda Exp $'; # require 'open3.pl'; $TITLE = "dcbib 文献登録のページ"; $MAILADDR = "toyoda\@gfd-dennou.org"; $BINDIR = '/usr/bin'; if (not &FormSubmitted()) { &SyokiGamen(); exit 0; } %card = &getCGIData(); if ($card{'phase'} eq 'initial') { if (not &EmailCheck($card{'from'})) { &SyokiGamen("電子メールアドレスを入力してください."); exit 0; } &NyuuryokuGamen(); } else { &KekkaGamen(); } exit 0; sub SyokiGamen { local($message) = @_; &printHtmlOpening($TITLE); if ($message) { print "
", $message; } print <
すべての欄を記入したら ボタンを押してください。
  1. あなたの電子メールアドレスを入力してください:
  2. 登録する文献の種類を選択してください:
    article
    論文誌に掲載された記事ひとつです。
    book
    書籍全体です。
    inbook
    書籍の一部です。
EOF &printHtmlClosing($TITLE); } sub NyuuryokuGamen { $lang = $card{'lang'} || 'ja'; $user = $card{'from'}; $user =~ s/@.*//; &printHtmlOpening($TITLE); print <

登録種別 $card{'cardtype'} の文献を登録します。 重複する登録がないかどうか 検索してから登録することをお勧めします。 入力が済んだら ボタンを押してください。

注意: 著者または編者が複数いる場合は改行するか '/' で区切ってください。 人名が姓名順である場合は姓のあとにカンマ (',') を入れてください。

$description 以下のリストから選んでください:
END local($vdpair, $value, $desc, $checked); foreach $vdpair (@ValueDescriptionList) { print "
" if $value; ($value, $desc) = split(/\t/, $vdpair, 2); $checked = ($value eq $default) ? "CHECKED" : ""; $desc = $value unless $desc; print "$desc\n"; } print <<"END";
EOF %setumei = ( 'author' => "著者", 'author-or-editor' => "著者または編者", 'title' => '表題', 'title-ja' => '表題(日本語)', 'journal' => '掲載雑誌', 'publisher' => '発行者', 'year' => '発表年度', 'volume' => '巻数', 'chapter' => '章', 'pages' => 'ページ範囲', 'number' => '号数', 'month' => '月', 'edition' => '版', 'series' => 'シリーズ', 'address' => '出版者所在地', ); if ($card{'cardtype'} eq 'article') { @needed = qw(author title journal year); @optional = qw(title-ja volume number pages month); } elsif ($card{'cardtype'} eq 'book') { @needed = qw(author-or-editor title publisher year); @optional = qw(title-ja volume series address edition month); } elsif ($card{'cardtype'} eq 'inbook') { @needed = qw(author-or-editor title publisher year pages chapter); @optional = qw(title-ja volume series address edition month); } else { print "oops: unsupported cardtype"; } foreach $key (qw(author editor author-or-editor title title-ja note)) { $card{$key} .= "\n"; } $hr = "―" x 20; print < EOF foreach $key (@needed) { if ($key eq 'author-or-editor') { &printRadioButton('select-author-or-editor', "著者または編者", "author", ("author\t著者", "editor\t編者")); } &printTextField($key, $setumei{$key}, $card{$key}); } print "\n"; foreach $key (@optional) { &printTextField($key, $setumei{$key}, $card{$key}); } &printTextField("Abstract", "概要", "\n"); &printTextField("Abstract-$lang", "概要邦訳", "\n"); print < EOF &printTextField("Keyword-$user-$lang", "検索キーワード", "\n"); &printTextField("ID-$user-$lang", "所在情報", "\n"); &printTextField("Note-$user-$lang", "備考", "\n"); $date = &date; &printTextField("History", "履歴", "$date $user initial\n"); print < EOF &printTextField("mainpart", "備考", "\n"); print <
必須項目:$hr
登録種別 $card{'cardtype'}
送信者 $card{'from'}
任意項目:$hr
$user 独自の情報: $hr
その他自由記入欄: $hr
$hr
EOF &printHtmlClosing($TITLE); } sub KekkaGamen { ($exitcode, $pid, @result) = &processCard; &printHtmlOpening($TITLE); print "
\n"; if ($exitcode == 0) { $result = "成功"; } elsif ($exitcode == 1) { $result = "(警告はあるものの) いちおう成功"; } else { $result = "失敗"; } print "

処理は${result}しました

\n"; print join("
\n", @result); print < 送信内容は以下のとおりです。
EOF foreach $key (keys %card) { print "
$key:
$card{$key}\n"; } print "
\n"; &printHtmlClosing($TITLE); } sub processCard { if ($select = $card{'select-author-or-editor'}) { $card{$select} = $card{'author-or-editor'}; delete $card{'author-or-editor'}; delete $card{'select-author-or-editor'}; } delete $card{'phase'}; local($READ, $WRITE, $pid, @result, $exitcode); $READ = "READ"; $WRITE = "WRITE"; $pid = &open3($WRITE, $READ, '', "$BINDIR/dcbib-add"); &writeCardStream($WRITE, %card); close($WRITE); @result = <$READ>; (waitpid($pid, 0) >= 0) || (@result = ("$?: invocation failed.")); $exitcode = (($? >> 8) & 255); ($exitcode, $pid, @result) } sub EmailCheck { local($email) = @_; return 0 unless $email; return ($email =~ /.@./); } sub writeCardStream { local($WRITE, %card) = @_; local(%xcard); foreach $key (keys %card) { $xcard{"$key:"} = $card{$key}; } &emlWrite($WRITE, %xcard); } sub date { local($sec, $min, $hour, $day, $mon, $year) = gmtime(time); return sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $day); } # error.pl: error handler # $Id: error.pl,v 1.3 1999/05/18 12:47:51 toyoda Exp $ # Copyright (c) TOYODA Eizi, 1998. All rights reserved. # see COPYING.TXT for terms of license. sub die { local($msg) = @_; print STDERR "ERROR: $msg\n"; exit 2; } sub warn { local($msg) = @_; print STDERR "WARNING: $msg\n"; $exit = 1; } sub okay { local($msg) = join(' ', @_); print STDERR "OK: $msg\n"; } sub exit { exit $exit; } # RFC-822-like file reading/writing module # $Id: rfc822.pl,v 1.4 1999/11/06 18:04:25 toyoda Exp $ # Copyright (c) TOYODA Eizi, 1998. All rights reserved. # see COPYING.TXT for terms of license. sub emlOpenFile { local($file) = @_; &open_r($file) || &die("cannot open <$file>"); &emlSetFile($file); } sub emlSetFile { ($emlFile) = @_; } sub emlReadLine { &readline($emlFile); } # -- emlRead() --- # # Reads RFC 822 headers from $inputfile and returns hash with which # $returned_hash{$field_name} == $filed_value, # where $field_name is lowercased and colon-containing filed name. # If $field_value contains '\n', it means that the header # $field_name appeared more than once. # Undefined return value means there is a format error. sub emlRead { local(%headers) = (); local($name, $val) = ("", ""); while ($_ = &emlReadLine) { s/\r?\n$//; last if /^$/; if (!/^\s/) { next if /^From /; if (!/^([-A-Za-z0-9]*:)\s*(.*)/) { &warn("broken header <$_> in $emlFile"); return undef; } ($name = $1) =~ tr/A-Z/a-z/; ($val = $2) =~ s/[\t\r]/ /g; if (defined $headers{$name}) { # 同名複数ヘッダは改行区切 $headers{$name} .= "\n$val"; } else { $headers{$name} = $val; } } else { s/[\t\r]/ /g; # 継続行はタブ区切 s/^ */\t/; $headers{$name} .= $_; } } %headers; } sub emlWrite { local($outfnam, %headers) = @_; local($fieldname, @values, $value, $folded); foreach $fieldname (keys %headers) { local(@values) = split(/\n/, $headers{$fieldname}); foreach $value (@values) { next if $value =~ /^[ \n\t]*$/; $folded = $value; $folded =~ s/\t/\n\t/g; print $outfnam "$fieldname $value\n"; } } } # misc. file io concerned subroutines # $Id: fio.pl,v 1.4 1999/05/18 12:47:51 toyoda Exp $ # Copyright (c) TOYODA Eizi, 1998. All rights reserved. # see COPYING.TXT for terms of license. # create file to write. make directory if necessary. sub creat { local($fnam) = @_; local($parent) = $fnam; &CheckUmask; &mkdir($parent) if ($parent =~ s/\/[^\/]+$//); open($fnam, ">$fnam") || &die("creat $fnam"); $fnam; } sub CheckUmask { return unless ((umask) & 0660); local($umask) = (umask) & ~0660; &warn(sprintf("warning: umask set to %04lo\n", $umask)); umask $umask; } sub mkdir { local($dnam) = @_; return if (-d $dnam); local($parent) = $dnam; if ($parent =~ s/\/[^\/]+$//) { &mkdir($parent); } else { return; } mkdir($dnam, 0777) || &die("mkdir $dnam"); } # test.pl - 文献カードの内容のチェック # $Id: test.pl,v 1.6 1999/11/01 10:42:09 toyoda Exp $ # Copyright (c) TOYODA Eizi, 1998. All rights reserved. # see COPYING.TXT for terms of license. # 仮定: # 各ヘッダの内容は %HEADER に格納されている。 sub testCard { local(*HEADER) = @_; if (!$HEADER{'cardtype:'}) { &die("cardtype: not given."); } elsif ($HEADER{'cardtype:'} eq 'article') { &testCardAuthor; &testCardTitle; &testCardJournal; &testCardYear; &testCardPages(0); } elsif ($HEADER{'cardtype:'} eq 'inbook') { &testCardAuthorOrEditor; &testCardTitle; &testCardPublisher; &testCardYear; &testCardChapter; &testCardPages(1); } elsif ($HEADER{'cardtype:'} eq 'book') { &testCardAuthorOrEditor; &testCardTitle; &testCardPublisher; &testCardYear; } else { &die("unknown CardType: $HEADER{'cardtype:'}."); } } sub testCardChapter { &die("chapter: required.") if ($required && !$HEADER{'chapter:'}); } sub testCardPublisher { &die("publisher: required.") if ($required && !$HEADER{'publisher:'}); } sub testCardPages { local($required) = @_; &die("pages: required.") if (!$required && !$HEADER{'pages:'}); &warn("pages: has no number. Are you sure?") if ($HEADER{'pages:'} !~ /[0-9]/); } sub testCardAuthorOrEditor { local($fail) = 1; if ($HEADER{'author:'}) { &testCardAuthor; $fail = 0; } if ($HEADER{'editor:'}) { &testCardEditor; $fail = 0; } } sub testCardEditor { &die("editor: required.") unless $HEADER{'editor:'}; } sub testCardAuthor { &die("author: required.") unless $HEADER{'author:'}; &warn("author: should begin in uppercase letter.") if ($HEADER{'author:'} =~ /^[a-z]/); &warn("author: should begin in A-Za-z. ($HEADER{'author:'})") unless ($HEADER{'author:'} =~ /^[A-Za-z]/); } sub testCardJournal { &die("journal: required.") if (!$HEADER{'journal:'}); } sub testCardTitle { &die("title: required.") if (!$HEADER{'title:'}); # 本当は ASCII を強制してもよいが、もうちょっと弱気に # アルファベットが1文字もみあたらないと警告する &warn("title: has no letter. Are you sure?") if ($HEADER{'title:'} !~ /[A-Za-z]/); } sub testCardYear { $HEADER{'year:'} =~ s/\s+//g; if ($HEADER{'year:'} =~ /([^0-9x]+)/) { local($bad) = $1; $HEADER{'year:'} =~ tr/0-9/x/c; &warn("year: has bad character '$bad' --- converted to 'x'."); } &die("year: required.") if ($HEADER{'year:'} !~ /[0-9x]/); } # card.pl - 文献カードのフィールドたちに関する情報 # $Id: card.pl,v 1.2 1999/05/30 13:33:54 toyoda Exp $ # Copyright (c) TOYODA Eizi, 1998. All rights reserved. # see COPYING.TXT for terms of license. sub cardTypeHash { %CARDTYPE = ( 'article' => '定期刊行物または学術雑誌の記事', 'book' => '書籍 (出版者が明確なもの)', 'booklet' => '書籍 (出版者の明確でないもの)', 'inbook' => '書籍の一部 (固有の表題をもたないもの)', 'incollection' => '書籍の一部 (表題のついたまとまり)', 'inproceedings' => '会議の予稿集に収録された論文', 'manual' => '技術文書', 'masterthesis' => '修士論文', 'misc' => 'その他', 'phdthesis' => '博士論文', 'proceedings' => '会議の予稿集', 'techreport' => '学校や団体の刊行した報告集のひとつ', 'unpublished' => '未公刊文献', ); %REQUIREDFIELD = ( 'article' => 'author title journal year', 'book' => 'author/editor title publisher year', 'booklet' => 'title', 'inbook' => 'author/editor title chapter/pages publisher year', 'incollection' => 'author title booktitle publisher year', 'inproceedings' => 'author title booktitle year', 'manual' => 'title', 'masterthesis' => 'author title school year', 'misc' => '', 'phdthesis' => 'author title school year', 'proceedings' => 'title year', 'techreport' => 'author title institution year', 'unpublished' => 'author title note', ); %OPTIONFIELD = ( 'article' => 'volume number pages month', 'book' => 'volume series address edition month', 'booklet' => 'author howpublished address month year', 'inbook' => 'volume series address edition month', 'incollection' => 'editor chapter pages address month', 'inproceedings' => 'editor pages organization publisher address', 'manual' => 'author organization address edition month year', 'masterthesis' => 'address month', 'misc' => 'author title howpublished month year', 'phdthesis' => 'address month', 'proceedings' => 'editor publisher organization address month', 'techreport' => 'type number address month', 'unpublished' => 'month year', ); %FIELDDESCRIPTION = ( 'address' => '出版者の住所 (著名な出版社ならば都市名だけで可)', 'author' => '著者 (複数いる場合はそれぞれ別の行に記述)', 'booktitle' => '書籍の表題', 'chapter' => '章番号', 'edition' => '書籍の版数', 'editor' => '編集者', 'howpublished' => 'どのように刊行されたか', 'institution' => '発行元組織', 'journal' => '定期刊行物名称', 'month' => '刊行された月 (未刊行の場合は書かれた月)', 'note' => '付加情報', 'number' => '号数', 'organization' => '発行元組織 (会議のスポンサー)', 'pages' => 'ページ範囲', 'publisher' => '出版者', 'school' => '大学', 'series' => '書籍シリーズの表題', 'title' => '表題', 'type' => '報告書の種別', 'volume' => '巻番号', 'year' => '刊行された年 (未刊行の場合は書かれた年)', ); } sub cardTypeList { defined %CARDTYPE || cardTypeHash; keys %CARDTYPE; } sub cardTypeDescription { local($type) = @_; defined %CARDTYPE || cardTypeHash; $CARDTYPE{$type}; } sub requiredFields { local($type) = @_; defined %REQUIREDFIELD || cardTypeHash; split(' ', $REQUIREDFIELD{$type}); } sub optionFields { local($type) = @_; defined %OPTIONFIELD || cardTypeHash; split(' ', $OPTIONFIELD{$type}); } sub availableFields { local($type) = @_; defined %OPTIONFIELD || cardTypeHash; split(' ', "$REQUIREDFIELD{$type} $OPTIONFIELD{$type}"); } # japanese.pl - 日本語文字コード変換ルーチン # $Id: japanese.pl,v 1.5 1999/05/18 12:47:51 toyoda Exp $ # Copyright (C) TOYODA Eizi, 1999. All rights reserved. # # プログラムの内部表現として日本語 EUC を使うためのサブルーチン。 # # &initKC # 漢字コード変換手段を選択する. 自動的に起動されるが # 自動判定履歴を初期化する場合には明示的に呼んでもよい. # # &open_r($filename) # ファイルを読み込みのために開き, そのハンドルを返す. # # &readline($handle) # $handle から 1 行読み取り内部コードに変換したものを返す. # # $KCMethod # 'jcodepl': Perl で自ら jcode.pl を読みに行く. 最善の手段. # 'nkf': /usr/bin にあるものを起動する. 次善の策. # undef: 最悪. # # $KCICode, $KCOCode # 'jis' ISO-2022-JP # 'euc' 日本語 EUC (デフォルト) # 'sjis' JIS X 0208-1997 付属書 シフト符号化表現 sub initKC { $KC_NKF = '/usr/bin/nkf'; if (require 'jcode.pl') { $KCMethod = 'jcodepl'; } elsif (-x $KC_NKF) { $KCMethod = 'nkf'; } else { die "no kanji conversion method found."; } $KCIcode = undef; } sub open_r { local($filename) = @_; defined($KCMethod) || &initKC; local($path); if ($KCMethod eq 'nkf') { $path = "$KC_NKF -e \"$filename\"|" } else { $path = "<$filename"; } open($filename, "$path") && $filename; } sub readline { local($handle) = @_; local($line, $code); defined($KCMethod) || &initKC; ($line = <$handle>) || return undef; return $line if ($line !~ /[\033\200-\377]/); if ($KCMethod eq 'jcodepl') { ($code = &jcode'getcode(*line)) && ($KCIcode = $code); &jcode'convert(*line, 'euc', $KCIcode); &jcode'h2z_euc(*line); } $line; } # debug code: not executed if this file follows 'exit;' line. while ($fnam = shift) { $handle = &open_r($fnam); while ($_ = &readline($handle)) { print; } }