#!/usr/local/bin/perl require 5.004; # 本プログラムは(少なくとも)perl 5.004以上を必要とします。 $ver = '2.1.2'; # 本プログラムの版数。禁変更。 ;#+------------------------------------------------------------------------ ;#|efCount[efStatカウンタ部] ;#|(C)1998-2000 不可思議絵の具(http://www.skipup.com/~fuka/) ;#+------------------------------------------------------------------------ ;# ☆☆☆ 共通の設定項目(必要) ☆☆☆ ;#+------------------------------------------------------------------------ ;# [動作モード] ;# SSIとして動作させるなら 1 , CGIとして動作させるなら 0 。 $USER{'SSIMode'} = 0; ;# [ログファイルを格納したディレクトリの名前] ;# ログ用ディレクトリは fstat/ 以下に作って下さい。 $USER{'DIR_Log'} = 'log'; ;# [アクセスログ最大保存数] ;# 1以上、3000未満の数値を入力して下さい。 $USER{'MaxLog'} = 500; ;# [再読み込み防止機構(IPチェック)を利用するか] ;# 1 = する(デフォルト) / 0 = しない(重複カウントを許す) $USER{'IPCheck'} = 1; ;# (IPCheckが1の場合有効) ;# [再読み込み防止機構の有効期限] (分単位で指定) ;# 指定した時間が経つと、同じIPであってもカウントします。 ;# 0を指定すると、同じIPの間はずっとカウントしません。 $USER{'IPExpire'} = 0; # [カウントアップさせないホスト名・IPアドレス] # 複数指定できますが、完全一致しなければなりません。 # ここを空欄にしてはいけません。 適当で良いですから埋めて下さい。 @USER_DenyIP = ( 'white.yuboo.net', 'hammerhead.yuboo.net', 'goblin.yuboo.net', '218.217.255.56', '210.231.56.138' ); # [カウントアップさせないブラウザ] # 複数指定できます。 前方比較を行います。 # ここを空欄にしてはいけません。 適当で良いですから埋めて下さい。 @USER_DenyAgent = ( 'WWWC', 'Kerberos', ); ;#+------------------------------------------------------------------------ ;# ☆☆☆ CGIとして使うときの設定項目 ☆☆☆ ;#+------------------------------------------------------------------------ ;# [カウンタ用画像を格納したディレクトリの名前] ;# カウンタ画像用ディレクトリは fstat/ 以下に作って下さい。 $USER{'Dir_Img'} = 'image'; ;# [カウンタのイメージの名前(デフォルトで使用する物)] $USER{'DigitName'} = 'fuksan'; ;#+------------------------------------------------------------------------ ;# ☆☆☆ SSI として使うときの設定項目 ☆☆☆ ;#+------------------------------------------------------------------------ ;# [efStat一式を格納したディレクトリ] ;# ※呼び出すHTMLから見た相対パスでも良いが、ルートからのフルパスが望ましい。 ;# ※最後のスラッシュ(/)は必ず必要。 $USER{'SSI_Pass'} = '/home/sites/home/users/fuka/web/cgi-bin/fstat/'; ;#+------------------------------------------------------------------------ ;# ☆☆☆ その他、補助的な項目 ☆☆☆ ;#+------------------------------------------------------------------------ # [gifcat.plのありか (通常は変更しないで下さい)] $USER{'GifCat'} = './lib/gifcat.pl'; #+------------------------------------------------------------------------ # (設定ここまで) #+------------------------------------------------------------------------ # ※ここからは分かる人だけ弄って下さい。 #  (タブのサイズ・[4]、折返し・[無し]で綺麗に表示されます) #+------------------------------------------------------------------------ #|&main #+------------------------------------------------------------------------ &Macro_Setup; # 各種初期化 &Macro_Access; # アクセス記録 &Macro_LoadData; # ログファイルを読み込み &Macro_Check; # チェック if ($OutputOnly) { # 重複アクセスは記録しない &Macro_Output; # 結果を出力 } else { &Macro_Count; # カウント &Macro_SaveData; # 保存 &Macro_Output; # 結果を出力 } exit; #+------------------------------------------------------------------------ #|プログラムの流れとしてのサブルーチン #+------------------------------------------------------------------------ ### 各種初期化 sub Macro_Setup { $ENV{'TZ'} = 'JST-9'; # 環境変数TZを日本時間に設定 $Digit = 0; $OutputOnly = 0; # 1=出力のみ ### 現在時刻の取得 $RUN_TIME = time; # 現在時刻(秒形式) @RUN_TIME = localtime($RUN_TIME); # 現在時刻(変換後) $RUN_TIME_E = &C62_Encode($RUN_TIME); # エンコード済現在時刻 $RUN_week = &TotalWeek($RUN_TIME[7]); # 現在週 ### 引数の解釈 foreach $data (split(/&/, $ENV{'QUERY_STRING'})) { ($key , $val) = split(/=/,$data); $P{$key} = $val; } $Filename = $P{'LOG'}; # ログファイル名 ## SSIモード if ($USER{'SSIMode'}) { $USER{'DIR_Log'} = "${USER{'SSI_Pass'}}${USER{'DIR_Log'}}/"; # ログ格納ディレクトリ # 動作モード if ($P{'MODE'} eq '-') { $OutputOnly = 1; } # "-"ならカウント無し elsif ($P{'MODE'} eq 'h') { $OutMode = 'h' ; } # "h"なら出力無し } ## CGIモード else { # 動作モード if ($P{'MODE'} =~ /^-([atyw])$/) { # 頭に - があれば出力のみ $OutMode = $1; $OutputOnly = 1; } elsif ($P{'MODE'} =~ /^([atyw])$/) { # 無ければ普通にチェック $OutMode = $1; } else { $OutMode = 'a'; } # 満たさなければ強制的に a に if ($P{'DIGIT'} > 20) { &Macro_PutError('e0001'); } # 桁数 else { $Digit = $P{'DIGIT'}-1; } $ENV{'HTTP_REFERER'} = $P{'REF'}; # 参照元 $screen = $P{'SCR'}; # 画面情報 $USER{'DigitName'} = $P{'FONT'} if ($P{'FONT'} ne ''); # フォント名 ## ディレクトリ名を修正 $USER{'DIR_Log'} = "./${USER{'DIR_Log'}}/"; # ログ格納ディレクトリ $USER{'Dir_Img'} = "./${USER{'Dir_Img'}}/${USER{'DigitName'}}/"; # フォント格納ディレクトリ ### 時刻表示モードだったらここで終わり if ($OutMode eq 'w') { $Digit = 0; print &Func_PutGIF(sprintf("%02dc%02d", $RUN_TIME[2], $RUN_TIME[1])); exit(0); } } } ### [アクセス記録] sub Macro_Access { ### 訪問者のリモートホスト取得 $host = $ENV{'REMOTE_ADDR'}; if ($host eq '') { $host = '-'; } else { # proxyチェック $host = $1 if ($ENV{'HTTP_FORWARDED'} =~ / for (.*)/); # HTTP_FORWARDED (DeleGate , Squid) # IP -> HOST $host = gethostbyaddr(pack("C4", split(/\./, $host)), 2) || $host; } ### ユーザエージェント取得 $agent = $ENV{'HTTP_USER_AGENT'}; $agent =~ s'^Mozilla/'!'; $agent =~ s"\(compatible; MSIE (.+)\)"(!$1)"; $agent = '-' if ($agent eq ''); #エージェント名がなければ'-'に置き換える ### 参照元取得 $ref = $ENV{'HTTP_REFERER'}; $ref =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("C",hex($1))/ge; # URLDecode $ref =~ s/\?$//; $ref =~ s"(/index\.)html$|\1htm$|\1shtml$|\1php3$"/"i; $ref =~ s'^http://'!'i; if (($ref eq '') || ($ref eq '[unknown origin]') || # リンク元無し, '[unknown origin]', 'bookmark'なら'-'に置き換える ($ref eq 'bookmarks') || ($ref eq "',ref,'")) { $ref = '-'; } ### 画面モード if (($screen eq '') || ($screen !~ /^[0-9]/)) { $screen = '-'; } else { ($screen_x, $screen_y, $screen_color) = split(/,/, $screen); $screen_x = &C62_Encode($screen_x); $screen_y = &C62_Encode($screen_y); $screen_color = &C62_Encode($screen_color); $screen = "$screen_x,$screen_y,$screen_color"; } } ### [ログファイルを読み込み] sub Macro_LoadData { unless (open(LOG,"+<${USER{'DIR_Log'}}${Filename}.log")) { &Macro_PutError('e0000'); } flock(LOG,2); for ($i=0 ; $i<8 ; $i++) { chomp($count[$i] = ); } # 各種カウント数 chomp(@log = ); # アクセスログ ###新規ログならフォーマット if ($count[0] eq '') { $count[0] = "FC2\t${RUN_TIME_E}"; $count[1] = "${RUN_TIME_E}\t0"; $count[2] = "0\t0\t0\t0\t0\t0\t0\t0"; $count[3] = "0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0"; $count[4] = "0\t0\t0\t0\t0\t0\t0"; $count[5] = "0\t0\t0\t0\t0\t0"; $count[6] = "0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0"; $count[7] = "0\t0\t0\t0\t0\t0"; } # 旧形式のログなら変換 elsif ($count[0] !~ /^FC2/) { &Macro_Convert; } ###データを各配列に格納 ($LOG_ID, $LOG_SINCE_E) = split(/\t/, $count[0]); #ヘッダ $LOG_SINCE = &C62_Decode($LOG_SINCE_E); &Macro_PutError('e1110') if ($LOG_ID ne 'FC2'); # ログ形式check(機能しない?) ($LOG_TIME_E, $ALL_E, $LOG_IP) = split(/\t/, $count[1]); # 各種情報 $LOG_TIME = &C62_Decode($LOG_TIME_E); $ALL = &C62_Decode($ALL_E); @DAILY = &split($count[2]); # 日別集計 @HOUR = &split($count[3]); # 時間別集計 @YOUBI = &split($count[4]); # 曜日別集計 @WEEK = &split($count[5]); # 週別集計 @MONTH = &split($count[6]); # 月別集計 @YEAR = &split($count[7]); # 月別集計 # ログの日付を求める @LOG_TIME = localtime($LOG_TIME); $LOG_week = &TotalWeek($LOG_TIME[7]); # 文字列を切り分け、10進数に戻す sub split { my($str) = @_; my(@array); @array = split(/\t/, $str); foreach (@array) { $_ = &C62_Decode($_); } return @array; } } ### カウントすべき訪問者かチェック sub Macro_Check { if ($USER{'IPCheck'}) { # 重複カウントを許すか? if ($host eq $LOG_IP) { # 前回訪問者と同じか? if ($USER{'IPExpire'} == 0) { $OutputOnly = 1; } else { if ($RUN_TIME - $LOG_TIME < $USER{'IPExpire'} * 60) { # IPは有効期限外か? $OutputOnly = 1; } else { $OutputOnly = 0; } } } else { $OutputOnly = 0; } } else { $OutputOnly = 0; } foreach (@USER_DenyIP) { # 弾くべきIPか? if ($host eq $_) { $OutputOnly = 1; last; } } foreach (@USER_DenyAgent) { # 弾くべきブラウザか? if ($agent =~ /^$_/) { $OutputOnly = 1; last; } } } ### [カウントする] sub Macro_Count { my($n); ### 何日間経過したか求める $n = $RUN_TIME[7] - $LOG_TIME[7]; $n += 366 if ($n < 0); # 年が明けた場合補正(n+366日経過) if ($n == 0) { $DAILY[0]++; } # 同じ日 elsif ($n > 0) { # n日経過 for (1 .. $n) { unshift(@DAILY, 0); } $DAILY[0] = 1; } ### 何週間経過したか求める $n = $RUN_week - $LOG_week; $n += 53 if ($n < 0); # 年が明けた場合補正(n+53週間経過) if ($n == 0) { $WEEK[0]++; } # 同じ週 elsif ($n > 0) { # n週間経過 for (1 .. $n) { unshift(@WEEK, 0); } $WEEK[0] = 1; } ### 何年間経過したか求める $n = $RUN_TIME[5] - $LOG_TIME[5]; if (($n == 0) || ($n < 0)) { $YEAR[0]++; } # 同じ年(サーバ側時計が古い場合もこの処理) elsif ($n > 0) { # n年経過 @MONTH = (0) x 12; # 月別集計をreset for (1 .. $n) { unshift(@YEAR, 0); } $YEAR[0] = 1; } ++$ALL; ++$HOUR [ ${RUN_TIME[2]} ]; ++$YOUBI[ ${RUN_TIME[6]} ]; ++$MONTH[ ${RUN_TIME[4]} ]; } ### [カウント結果を配列に格納] sub Macro_SaveData { $ALL_E = &C62_Encode($ALL); $count[0] = "FC2\t$LOG_SINCE_E"; # 各種情報 $count[1] = "$RUN_TIME_E\t$ALL_E\t$host"; # 各種情報 $count[2] = &Func_Array2Str(8, \@DAILY); # 日別集計 $count[3] = &Func_Array2Str(24, \@HOUR); # 時間別集計 $count[4] = &Func_Array2Str(7, \@YOUBI); # 曜日別集計 $count[5] = &Func_Array2Str(6, \@WEEK); # 週別集計 $count[6] = &Func_Array2Str(12, \@MONTH); # 月別集計 $count[7] = &Func_Array2Str(6, \@YEAR); # 月別集計 $new_log = "$ALL_E\t$RUN_TIME_E\t$host\t$agent\t$ref\t$screen"; #新規ログ行 unshift(@log, $new_log); # 新しいログを付け足し、 splice(@log, $USER{'MaxLog'}); # 古いログは消し去る seek(LOG,0,0); foreach (@count) { print LOG "$_\n"; } foreach (@log) { print LOG "$_\n"; } truncate(LOG,tell); flock(LOG,8); close(LOG); ### 配列の内容を一行の文字列に直す関数 ### $limitより大きなデータは切り捨てる sub Func_Array2Str { my($limit, $array) = @_; splice(@$array, $limit); foreach (@$array) { $_ = &C62_Encode($_); } return join("\t", @$array); } } ### [カウント数出力] sub Macro_Output { my($s, $m, $h, $d, $mo, $y, $w) = @RUN_TIME; my($today, $yesterday) = @DAILY; my($all) = $ALL; my(@w); unless ($OutputOnly) { $today = &C62_Decode($today); $yesterday = &C62_Decode($yesterday); } ### SSIモードなら if ($USER{'SSIMode'}) { print "Content-type: text/plain\n\n"; if ($OutMode ne 'h') { $agent =~ s'^!'Mozilla/'; $agent =~ s"\(!(.+)\)"(compatible; MSIE $1)"; $ref =~ s'^!'http://'; ++$mo; $y += 1900; ;#+--[ * メッセージ(ユーザ変更可) * ]------------[ ここから ]-+ # 好きな曜日の表記を選んで下さい。 @w = ('日', '月','火','水','木','金','土'); $w = $w[$w]; # @w = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat'); $w = $w[$w]; ;#+-----------------------------------------------------------+ ;#(メッセージ中の変数の意味) ;# ${all} …総ヒット数 ${y} …年 ${h}…時 ;# ${today} …本日ヒット数 ${mo}…月 ${m}…分 ;# ${yesterday}…昨日ヒット数 ${d} …日 ${s}…秒 ;# ${host} …訪問者ホスト ${w} …曜日 ;# ${agent} …ブラウザ名 ;# ${ref} …参照元 ※ " は \" に置き換えましょう! ;#+-----------------------------------------------------------+ @mes = ( #"${all}\n${today}\n${yesterday}\n${host}\n${agent}\n${ref}\n${y}年${mo}月${d}日(${w})\n${h}時${m}分${s}秒\n", "Total:${all} / Today:${today} / Yesterday:${yesterday}", #"ぴぃ〜かぁ〜☆ 全部で${all}万ボルト・今日は${today}万ボルト・昨日は${yesterday}万ボルトの蓄電", #"艦長ぉ! 前方に機影確認!相手識別番号${host}っ!
敵機です! コンディション・レッドッッッ!!
その数、前方に${all}機ッ! 左舷に${today}機! 右舷に${yesterday}機! 波動砲用意しますか!?(古)", ); ;#+--[ * メッセージ(ユーザ変更可) * ]------------[ ここまで ]-+ # ランダムに選ぶ srand(time + $$); $n = int(rand($#mes+1)); # 表示 print $mes[$n]; } } ### CGIモードなら else { # 総アクセス数 if ($OutMode eq 'a') { print &Func_PutGIF($all); } # 今日アクセス数 elsif ($OutMode eq 't') { print &Func_PutGIF($today); } # 昨日アクセス数 elsif ($OutMode eq 'y') { print &Func_PutGIF($yesterday); } } } ### [gifを出力] sub Func_PutGIF { my($Data) = @_; my($i, $n, @array); require $USER{'GifCat'}; print "Content-type: image/gif\n"; print "Expires: 01/01/1970 00:00:00 JST\n\n"; # キャッシュを無効にする binmode(STDOUT); ### 桁数を指定されたら、足りない分を0で補う $Data = ('0'x($Digit-(length($Data)-1))).$Data if (length($Data)-1 < $Digit); for ($i=0 ; $i < length($Data) ; $i++) { $n = substr($Data, $i, 1); push(@array, "${USER{'Dir_Img'}}${n}${USER{'DigitName'}}.gif"); } return &gifcat'gifcat(@array); } ### [エラー出力] # e0000 = ファイルオープン失敗 # e0001 = 桁数あふれ # e1110 = ログの形式が不正 # e1111 = 無効なオプション sub Macro_PutError { my($code) = @_; if ($USER{'SSIMode'}) { print "Content-type: text/plain\n\n"; if ($code eq 'e0000') { print "

[efCount $ver(SSI)]ログを開くことが出来ませんでした

\n"; } elsif ($code eq 'e1110') { print "

[efCount $ver(SSI)]ログの形式が不正です

\n"; } elsif ($code eq 'e1111') { print "

[efCount $ver(SSI)]無効なオプションです

\n"; } else { print "

[efCount $ver(SSI)]未定義のエラーです

\n"; } } else { require $USER{'GifCat'}; print "Content-type: image/gif\n\n"; binmode(STDOUT); for ($i=0 ; $i < length($code) ; $i++) { $n = substr($code, $i, 1); push(@Digit, "./lib/${n}.gif"); } print &gifcat'gifcat(@Digit); } exit(1); } sub Macro_Convert { my($num, $time, $host, $agent, $ref, $screen, $day, $week, $time, $all, $ip, $i, @work); for ($i = 1; $i < 6; $i++) { split(/#/, $count[$i]); foreach (@_) { $_ = &C62_Encode($_); } $count[$i] = join("\t", @_); } ($day, $week, $time, $all, $ip) = split(/#/, $count[0]); $all_e = &C62_Encode($all); # カウント記録部の変換 # 一旦作業用配列に入れる $work[0] = "FC2\t${RUN_TIME_E}"; $work[1] = "${RUN_TIME_E}\t${all_e}\t${ip}"; $work[2] = $count[1]; $work[3] = $count[2]; $work[4] = $count[3]; $work[5] = $count[4]; $work[6] = $count[5]; $work[7] = "0\t0\t0\t0\t0\t0"; unshift(@log,$count[6]); # 溢れて読み込まれたアクセスログ @count = @work; # 引っ越す # ログ記録部の変換 foreach (@log) { ($num, $time, $host, $agent, $ref, $screen) = split(/#/, $_); $num = &C62_Encode($num); $time = &C62_Encode($time); $agent =~ s/%23/#/g; $agent =~ s'^Mozilla/'!'; $agent =~ s"\(compatible; MSIE (.+)\)"(!$1)"; $ref =~ s'^http://'!'i; $_ = "$num\t$time\t$host\t$agent\t$ref\t$screen"; } } ### 通算週を求める関数 sub TotalWeek { my($total) = @_; my($week); if ($total < 7) { $week = 0; } # 0除算対策 else { $week = int($total/7); } return $week; } ;### 62進数→10進数 sub C62_Decode { my $str = reverse($_[0]); my($digit, $i); for ($i = 0; $i < length($str); $i++) { $digit += index('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', substr($str, $i, 1)) * (62 ** $i); } return $digit; } ;### 10進数→62進数 sub C62_Encode { my($digit) = $_[0]; my($str); if (!$digit) { return 0; } else { while ($digit) { $str .= substr('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ($digit % 62), 1); $digit = int($digit / 62); } return reverse($str); } }