#! /usr/local/bin/perl #--------------------------------------------------------------------- # # PNGカウンタ Ver.1.4.3 (2002/10/23) # (c) 2000-2002 桜月 # http://www.aurora.dti.ne.jp/~zom/Counter/ # #--------------------------------------------------------------------- # 設置者による設定。 $keta = 0; # カウンタの表示桁を設定します。 # 0以外を指定すると、ここで指定された桁数になる # よう0で埋められます。 $logdir = 'log'; # ログファイルの入っているサブディレクトリ名。 # (このスクリプトからの相対パス) $pgc = 'pngcntr';# 数字画像用のPNGファイル名(拡張子は不要)。 $pngdir = '.'; # そのPNGファイルのあるサブディレクトリ名。 # (このスクリプトからの相対パス) $relup = 0; # 再読込のときもカウントアップするなら1に。 $locktype = 0; # ロック法。0ならflock式、1ならrename式。 # 設置者による設定箇所ここまで。 #--------------------------------------------------------------------- # グローバル。 $logFile = ''; # ログファイル。 $lockFile = ''; # ロック用ファイル。 %opts = (); # オプション。 @touka = (); # 透過。 $errorBangou = 0; # エラー番号。 @crcTable = (); # CRCテーブル。 $pngData = ''; # SI-PNGデータ。 $ch_data = ''; # チャンクデータ。 # メイン。 &CrcTable(); &Option(); &Suuji(); if(!$errorBangou){ &Tsunagu(); } if($errorBangou) { &Error($errorBangou); } exit($errorBangou); # チャンク出力ルーチン(FTRM)。 sub Pr_chunk(){ my($crc) = 0xffffffff; print pack('N', length($ch_data) - 4).$ch_data; foreach(unpack('C*', $ch_data)){ $crc = $crcTable[($crc ^ $_) & 0xff] ^ ($crc >> 8); } print pack('N', ~$crc); undef($ch_data); } # CRC用初期設定(FTRM)。 sub CrcTable(){ my($i, $j, $crc); for($i = 0; $i < 256; $i++){ $crc = $i; for($j = 0; $j < 8; $j++){ if($crc & 1){ $crc = 0xedb88320 ^ ($crc >> 1); } else{ $crc = $crc >> 1; } } $crcTable[$i] = $crc; } } # オプション取り出し(FTR)。 sub Option(){ my($env, $wake); ($logFile, $env) = split(/;/, $ENV{'QUERY_STRING'}, 2); foreach(split(/;/, $env)){ if(substr($_, 0, 1) eq 'T'){ push(@touka, split(/,/, substr($_, 1))); } else{ if($wake = index($_, '=') + 1){ $opts{substr($_, 0, $wake - 1)} = substr($_, $wake); } else{ $opts{$_} = 1; } } } $logFile = '' if(index($logFile, '/') + 1); } # カウントアップと数字の整形(FTM)。 sub Suuji(){ my($nagasa); unless($logFile){ $kazu = 1234567890; } else{ if(!&CountUp()){ $errorBangou = 1; return 0; } $kazu -= 0; } # 桁の調整。 $nagasa = length($kazu); $keta = $opts{'keta'} || $keta; if($keta){ if($keta < $nagasa){ $keta = $nagasa } # $kazu = sprintf("%0$keta"."s", $kazu); $kazu = substr(('0' x $keta).$kazu, -$keta); } else{ $keta = $nagasa; } return 1; } # PNG読みとり(FT)。 sub ReadSIPNG(){ $pgc = "./$pngdir/".($opts{'png'} || $pgc).'.png'; if(!open(IN, $pgc)){ $errorBangou = 2; return 0; } binmode(IN); seek(IN, 0, 0); read(IN, $pngData, -s $pgc); close(IN); if(substr($pngData, 0, 8) ne "\x89PNG\x0d\x0a\x1a\x0a"){ $errorBangou = 4; # PNGではない。 return 0; } elsif(substr($pngData, 0x18, 5) ne "\x08\x03\0\0\0"){ $errorBangou = 4; } elsif(substr($pngData, 0x25, 3) ne 'pgC'){ $errorBangou = 4; } elsif(substr($pngData, 0x28, 1) ne 'I'){ $errorBangou = 5; } else{ return 1; } return 0; } # PNG出力(FT)。 sub Tsunagu(){ my($pgcHdr) = 0x29; my($x_kei, $y_kei, $nagasa); my($pltehjmr, $pltengsa, $trnshjmr, $trnsngsa); local($idatData); if(!&ReadSIPNG()){ return 0; } $idatData = ''; ($x_kei, $y_kei) = &Renketsu(*idatData, $pgcHdr); if($x_kei == -1){ $errorBangou = 6; return 0; } # ここから後は、Error()に処理を渡してはならん。 &MIMEtoHeader(); $ch_data = 'IHDR'.pack('N2', $x_kei, $y_kei)."\x08\x03\0\0\0"; &Pr_chunk(); # PLTE. ($pltehjmr, $pltengsa, $trnshjmr, $trnsngsa) = unpack('V4', substr($pngData, $pgcHdr, 16)); print substr($pngData, $pltehjmr - 8, $pltengsa + 12); # tRNS. if($#touka < 0){ if($trnsngsa){ print substr($pngData, $trnshjmr - 8, $trnsngsa + 12); } } # &Tオプションによる透過機能が不要なら以下19行(*1まで)は不要。 else{ my($pal, $atai); $ch_data = "\xff" x 0x100; substr($ch_data, 0, $trnsngsa) = substr($pngData, $trnshjmr, $trnsngsa); $trnsngsa--; foreach(@touka){ ($pal, $atai) = split(/=/, $_, 2); $pal &= 255; substr($ch_data, $pal, 1) = pack('C', $atai & 255); if($trnsngsa < $pal){ $trnsngsa = $pal; } } $pltengsa /= 3; if(++$trnsngsa > $pltengsa){ $trnsngsa = $pltengsa; } $ch_data = 'tRNS'.substr($ch_data, 0, $trnsngsa); &Pr_chunk(); } # *1 # IDAT. # 無圧縮zlib化。 $s1 = 1; $s2 = 0; foreach(unpack('C*', $idatData)){ $s1 += $_; if($s1 > 65520){$s1 -= 65521; } $s2 += $s1; if($s2 > 65520){$s2 -= 65521; } } $nagasa = length($idatData); # $len=pack('S', $nagasa); どうやらNifはSが使えんらしい。 # $len=pack('C2', $nagasa & 255, $nagasa >> 8); もし下のvも駄目ならこれで。 $len=pack('v', $nagasa); $ch_data = "IDATx\x01\x01".$len.~$len.$idatData.pack('n2', $s2, $s1); &Pr_chunk(); # IEND. print "\0\0\0\0IEND\xaeB`\x82"; # ここまで。 return 1; } # 連結(FT)。 sub Renketsu(){ local(*idatData, $pgcHdr) = @_; my($x_kei, $y_kei, $x_saidai, $i, $j, $suuji); my($x, $y, $menseki, $ichi, $yomiIchi, $kakiIchi); my($pngHaba, $pgcIchi); my(@ichi, @x, @y) = ((), (), ()); $pngHaba = unpack('N', substr($pngData, 0x10, 4)) + 1; $pgcIchi = unpack('V', substr($pngData, $pgcHdr + 16, 4)); $x_saidai = $x_kei = $y_kei = 0; for($i = 0; $i < $keta; $i++){ $suuji = substr($kazu, $i, 1); unless($x[$suuji]){ ($ichi[$suuji], $x, $y) = unpack('vCC', substr($pngData, $pgcHdr + 20 + ($suuji << 2), 4)); $x[$suuji] = $x; $y[$suuji] = $y; $x_saidai = $x if($x_saidai < $x); } $x_kei += $x[$suuji]; $y_kei += $y[$suuji]; } if($opts{'tate'}){ $x_kei = $x_saidai + 1; $menseki = $x_kei * $y_kei; return (-1, -1) if($menseki >> 15); $idatData = "\0" x $menseki; $kakiIchi = 1; for($i = 0; $i < $keta; $i++){ $suuji = substr($kazu, $i, 1); $yomiIchi = $pgcIchi + $ichi[$suuji]; $x = $x[$suuji]; for($j = $y[$suuji]; $j > 0; $j--){ substr($idatData, $kakiIchi, $x) = substr($pngData, $yomiIchi, $x); $kakiIchi += $x_kei; $yomiIchi += $pngHaba; } } } else{ $x_kei++; $y_kei = unpack('N', substr($pngData, 0x14, 4)); $menseki = $x_kei * $y_kei; return (-1, -1) if($menseki >> 15); $idatData = "\0" x $menseki; $ichi = 1; for($i = 0; $i < $keta; $i++){ $suuji = substr($kazu, $i, 1); $yomiIchi = $pgcIchi + $ichi[$suuji]; $kakiIchi = $ichi; $x = $x[$suuji]; for($j = $y[$suuji]; $j > 0; $j--){ substr($idatData, $kakiIchi, $x) = substr($pngData, $yomiIchi, $x); $kakiIchi += $x_kei; $yomiIchi += $pngHaba; } $ichi += $x; } } $x_kei--; return ($x_kei, $y_kei); } # 戻り値はBOOL. sub RenLock(){ $lockFile = "$logFile.lock"; for(0 .. 9){ if(rename($logFile, $lockFile)){ # ロック成功。 utime(time, time, $lockFile); return 1; } sleep(1); } if(time - (stat($lockFile))[9] > 300){ # 5分以上前のものなら遺物と見なし、 utime(time, time, $lockFile); # そのまま横取り。 return 1; } return 0; } sub UnRenLock(){ rename($lockFile, $logFile); } sub LocktoOpen(){ $lockFile = $logFile = "./$logdir/$logFile"; # ニフのftp1のflockは使えん。 if(index($ENV{'SERVER_NAME'}, 'nifty') + 1){ $locktype = 1; } if($locktype == 1){ unless(&RenLock()){ # rename lock失敗。 $opts{'mirudake'} = 1; $locktype = -1; # 後で解除させんため。 } } unless(open(LOG, "+<$lockFile")){ if($locktype == 1){ &UnRenLock(); } return 0; } if($locktype == 0){ eval{ flock(LOG, 2); }; } return 1; } sub UnlocktoClose(){ close(LOG); if($locktype == 1){ &UnRenLock(); } } # カウントアップ(FRM)。 sub CountUp(){ my($addr, $fwrd, $clnt, $afc, $mae); $addr = $ENV{'REMOTE_ADDR'}; $fwrd = $ENV{'HTTP_X_FORWARDED_FOR'}; $clnt = $ENV{'HTTP_CLIENT_IP'}; $afc = "$addr/$fwrd/$clnt"; if(!&LocktoOpen()){ return 0; } ($kazu, $mae) = split(/\t/, , 3); if(($relup || ($mae ne $afc)) && (!$opts{'mirudake'})){ $kazu++; select(LOG); $| = 1; truncate(LOG, 0); seek(LOG, 0, 0); print LOG "$kazu\t$afc\t\n"; select(STDOUT); } &UnlocktoClose(); return 1; } # PNGヘッダ出力(FTR)。 sub MIMEtoHeader(){ binmode(STDOUT); $| = 1; print "Content-Type: image/png\n\n"; print "\x89PNG\x0d\x0a\x1a\x0a"; } # エラー処理(FTR)。 sub Error(){ my($err) = $_[0]; if ($err == 1){$rgb = "\0\0\xff"; } # 青(指定されたログがない) elsif($err == 2){$rgb = "\0\xff\xff"; } # 水色(指定されたPNGがない) elsif($err == 3){$rgb = "\0\xff\0"; } # 緑(PNGではない) elsif($err == 4){$rgb = "\xff\0\0"; } # 赤(使えぬPNG) elsif($err == 5){$rgb = "\xff\0\xff"; } # 紫(このカウンタ用ではない) elsif($err == 6){$rgb = "\xff\xff\0"; } # 黄色(データ大きすぎ) else {$rgb = "\xff\xff\xff"; } # 白(未定義なエラー) &MIMEtoHeader(); print "\0\0\0\x0dIHDR\0\0\0 \0\0\0 \x01\x03\0\0\0I\xb4\xe8\xb7"; $ch_data = "PLTE\0\0\0$rgb"; &Pr_chunk(); print "\0\0\0)IDATx\xdac\xf8\x0f\x04\x0c\x0d"; print "\x0c\x0c\x8c\xe8D\xfb\xff\xff\x0f\xd1\x89\x06\xe6\x03\x8c\x94"; print "\x13\xf3\xff\xff\xff\x89N`s\x01\xc8i\0\xeb[9"; print "\xa9\xb9\xc5K\xc5\0\0\0\0IEND\xaeB`\x82"; } #--------------------------------------------------------------------- # # PNGカウンタ 改訂履歴 # # Ver.0.1 (2000/ 2/24) # # Ver.0.2 (2000/ 2/29) # Adler32の計算を20%高速化。 # zlib処理を本体に組み込む。 # zlibの制限を16KBにする。 # データ出力処理をchunk内に組み込む。 # # Ver.0.3 (2000/ 4/14) # evalの使い方がおかしかったのを修正。 # # Ver.0.4 (2000/ 6/ 3) # バッファリングを切る。 # デバッグ用ルーチンを削除。 # 変な位置にサブルーチンがあったので移動。 # # Ver.0.5 (2000/ 7/31) # 数字を横に連結するものと縦に連結するものとを統合。 # エラー5番の処理がおかしかったのを修正。 # リロードのチェック要素にIP_CLIENTも加える。 # # Ver.0.6 (2000/ 9/ 5) # 透過機能の強化。 # (1) 複数の色を透過できるようにする。 # (2) 各色ごとに透過率を決められるようにする。 # # Ver.0.7 (2000/ 9/10) # 一度読んだpgcはキャッシュしておくようにする。 # Ver.0.5以降、複雑化したオプションの指定方法を改良。 # 忘れられがちなerror.pgcを内蔵してしまう。 # pgcのあるディレクトリをオプションで指定できるようにする。 # ->複数のpgcが使い分けられるように(指定せねば従来通り)。 # ついでにログファイルのディレクトリもオプションで # 指定できるようにする。 # # Ver.0.71 (2000/ 9/13) # ログファイルを指定する振りをして、カレントディレクトリの # ファイルを破壊できてしまうという凶悪な欠陥の修正。 # 同様の理由からlogdirオプションを廃止する。 # # Ver.1.0 (2000/10/ 8) # 大幅な仕様変更。 # pgcの使用をやめ、無圧縮pngを使って画像を合成するようにする。 # これによってPLTEをカウンタ側で計算する必要が # なくなったので、高速化が期待できるように。 # 他にもsplitの廃止などにより、高速化を図る。 # # Ver.1.01 (2000/10/ 9) # Ver.1.0で入れ忘れた機能を改めて追加。 # # Ver.1.03 (2000/10/16) # Nifty対策コードを入れる。 # # Ver.1.04 (2000/10/17) # オプションの取り出しと透過処理はsplitも併用した方が # 速いようなので書き換え。 # # Ver.1.05 (2000/10/20) # Niftyでこのスクリプトが使われているときだけ # 対策処理を行うように仕様変更。 # # Ver.1.1 (2000/10/20) # renameによるロックを可能にする。 # # Ver.1.2 (2000/11/ 4) # ロックをかけた状態でログファイルのオープンに # 失敗した時、ロックを解除せぬままにエラーに # 飛んでいたのを修正。 # エラー処理部でexitするのをやめた。 # エラー番号を1ずらし、エラーが起きた時エラー番号 # そのものでexitするようにする。 # 出力されるIDATの長さチェックを画像合成中に行うように変更。 # これにより桁数がオプション指定できるように。 # 容量制限を32768バイトに緩和。 # # Ver.1.3 (2001/ 6/ 6) # オプションの区切りを以下のように変える。 # '&' → ';' # '|' → ',' # # Ver.1.31 (2001/ 9/ 1) # リロード時にカウントアップするかどうかを # 選択できるようにする。 # # Ver.1.32 (2002/ 1/13) # コードを整理する。 # # Ver.1.4 (2002/ 5/18) # ログファイル指定に関するセキュリティーホールを塞ぐ。 # # Ver.1.4.3 (2002/10/23) # 画像の縦連結にバグが混入していたのを修正。 # #--------------------------------------------------------------------- # # オプションの指定法([]内は省略可)。 # # http://www.dokozo.ne.jp/~darezo/counter.cgi?(logfile)[option(s)] # # (logfile) # その名の通りログファイル。必須。 # # [option(s)] # 表示オプション。現在対応しているオプションは以下の通り。 # # ;Tパレット番号[=透明度] # パレット番号は0〜255まで。透明度は0(透明)〜255(不透明)まで。 # このオプションは','で区切ることにより複数指定できる。 # # ;keta=N # 数値をN桁で表示する。 # カウンタの値が指定された桁数に満たぬ時は数字の前を0で埋める。 # # ;tate # 画像縦連結指定。 # # ;png=FILE # 数字用画像ファイル(SI-PNG)の指定。省略時はpngcntr.pngが使われる。 # なお拡張子(.png)はカウンタ内部で補うので不要。 # (例) ;pgc=gothic -> gothic.pngを使ってカウンタを表示する。 # # ;mirudake # その名の通り数値を「見るだけ」で、カウントアップされぬようにする。 # # # 例 # http://〜/counter.cgi?log;T1=23,3=52;tate;png=train # # これは、 # train.pngという数字画像を使い、 # 1番パレットの透明度を23に、3番パレットの透明度を52に設定した上で、 # 画像を縦連結表示する、 # という指定を意味する。 # #---------------------------------------------------------------------