#!/usr/bin/perl ########################################################## # futomi's CGI Cafe # RevoCounter Ver 1.3 # Copyright(C) futomi 2001 - 2002 # http://www.futomi.com ########################################################## require './revoconfig.cgi'; use CGI; $query = new CGI; $ID = $query->param('id'); $BaseBmpFile = $query->param('base'); $ImodeFlag = $query->param('imode'); # URLガード if($UrlGuardFlag) { unless($ENV{'HTTP_REFERER'} =~ /^$YourSiteUrl/) { &ErrorPrint("指定外のサイトからは利用できません。"); } } # カウントデータファイルの指定 if($ID) { unless($ID =~ /^\w+$/) { &ErrorPrint("IDには、半角アルファベットもしくは数字で指定してください : $ID"); } $CountDataFile = './data/'.$ID.'.dat'; } else { $CountDataFile = './data/default.dat'; } # カウンターのベースイメージ(ビットマップ)ファイルの指定 if($BaseBmpFile) { unless($BaseBmpFile =~ /^[\w\.]+$/) { &ErrorPrint("ベースイメージ(ビットマップ)ファイル名には、半角アルファベットもしくは数字で指定してください。: $BaseBmpFile"); } $BaseBmpFile = './base/'.$BaseBmpFile; } else { $BaseBmpFile = './base/default.bmp'; } if($CookieFlag) { %Cookie = &GetCookie; $LastAccess = $Cookie{"Revocount$ID"}; } else { $LastAccess = 0; } ($PreNumber, $Number) = &Count($CountDataFile, $LastAccess, $GuardTime); $| = 1; $file = $BaseBmpFile; $size = -s $file; # Input bitstream of BMP file to variable "$buf". if(!open(IN, "$file")) { &ErrorPrint("ベースイメージ(ビットマップ)ファイルの読込みに失敗しました。 : $file"); exit; } binmode(IN); # For Windows sysread(IN, $buf, $size); close(IN); # Analyze BITMAP FILEHEADER $bfOffBits = &AnalyzeFileHeader($buf); # Analyze BITMAP INFOHEADER @InfoHeaderResult = &AnalyzeInfoHeader($buf); $biWidth = $InfoHeaderResult[1]; $biHeight = $InfoHeaderResult[2]; $biBitCount = $InfoHeaderResult[4]; $biClrUsed = $InfoHeaderResult[9]; $NextBlockStart = $InfoHeaderResult[11]; unless($biWidth % 10 == 0) { &ErrorPrint("ビットマップイメージの幅は、10の倍数でなければいけません。: biWidth[$biWidth]"); } if($ImodeFlag) { unless($biHeight % 4 == 0) { &ErrorPrint("i-mode対応の場合、ビットマップイメージの高さは、4の倍数でなければいけません。: biHeight[$biHeight]"); } } # Analyze BITMAP RGBQUAD @PalletData = &AnalyzeRgbQuad($buf, $biBitCount, $biClrUsed, $NextBlockStart); # Analyze BITMAP Image Data $ImageData = &AnalyzeImageData($buf, $biWidth, $biHeight, $bfOffBits, \@PalletData); # イメージデータを各数字毎に分割する @DigitDataArray = &SplitImageData($biWidth, $biHeight, $ImageData); # カウンターイメージの幅を定義($Width) # 幅にあわせて、'0'を埋め合わせる if(($Figures && $FiguresFixFlag) || ($Figures && ! $FiguresFixFlag && length($Number) <= $Figures)) { $Width = $biWidth * $Figures / 10; my($NumberTmp) = ''; my($i); for ($i=$Figures;$i>0;$i--) { $Tmp = chop($Number); if($Tmp eq '') { $NumberTmp = '0'.$NumberTmp; } else { $NumberTmp = $Tmp.$NumberTmp; } } $Number = $NumberTmp; $NumberTmp = ''; for ($i=$Figures;$i>0;$i--) { $Tmp = chop($PreNumber); if($Tmp eq '') { $NumberTmp = '0'.$NumberTmp; } else { $NumberTmp = $Tmp.$NumberTmp; } } $PreNumber = $NumberTmp; } else { $Width = $biWidth * length($Number) / 10; $Figures = length($Number); unless (length($Number) == length($PreNumber)) { $PreNumber = '0'.$PreNumber; } } # Generate GIF Format Image File(GIF 89a) ($GifData, $GifSize) = &GenerateGif($PreNumber, $Number, $Figures, $Interval, $TransparentColor, $Width, $biHeight, \@PalletData, \@DigitDataArray); # Print GIF Image &PrintGifImage($GifData, $GifSize); exit; sub GetCookie { my(@CookieList) = split(/\; /, $ENV{'HTTP_COOKIE'}); my(%Cookie) = (); my($key, $CookieName, $CookieValue); for $key (@CookieList) { ($CookieName, $CookieValue) = split(/=/, $key); $CookieValue =~ s/\+/ /g; $CookieValue =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C",hex($1))/eg; $Cookie{$CookieName} = $CookieValue; } return %Cookie; } sub PrintGifImage { my($GifData, $GifSize) = @_; my($now) = time; my($CookieHeaderString) = &SetCookie("Revocount$ID", $now, 30); print "Content-type: image/gif\n"; if($CookieFlag) { print "$CookieHeaderString\n"; } print "Pragma: no-cache\n"; print "P3P: CP=\"NOI ADMa\"\n"; print "Content-length: $GifSize\n"; print "\n"; print $GifData; } sub SetCookie { my($CookieName, $CookieValue, $ValidDay, $Domain, $Path) = @_; my(@MonthString) = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); my(@WeekString) = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'); my($time) = time; if($ValidDay) { $time += $ValidDay*60*60*24; } else { $time += 30*60*60*24; } my($sec, $min, $hour, $monthday, $month, $year, $weekday) = gmtime($time); $year += 1900; $month = $MonthString[$month]; if($monthday < 10) {$monthday = '0'.$monthday;} if($sec < 10) {$sec = '0'.$sec;} if($min < 10) {$min = '0'.$min;} if($hour < 10) {$hour = '0'.$hour;} my($GmtString) = "$WeekString[$weekday], $monthday-$month-$year $hour:$min:$sec GMT"; # URLエンコード $CookieValue =~ s/([^\w\=\& ])/'%' . unpack("H2", $1)/eg; $CookieValue =~ tr/ /+/; my($CookieHeaderString); $CookieHeaderString .= "Set-Cookie: $CookieName=$CookieValue\; expires=$GmtString\;"; if($Domain) { $CookieHeaderString .= ' domain=$Domain;'; if($Path) { $CookieHeaderString .= ' Path=$Path;'; } } return $CookieHeaderString; } sub Count { my($CountDataFile, $LastAccess, $GuardTime) = @_; if(-e $CountDataFile) { if(!open(CNT, "+<$CountDataFile")) {&ErrorPrint("データファイル $CountDataFile をオープンできませんでした。");} } else { if(!open(CNT, ">$CountDataFile")) {&ErrorPrint("データファイル $CountDataFile を作成できませんでした。「data」ディレクトリがあるかどうか確認してください。");} } if(!&LockFile(CNT)) {&ErrorPrint("$CountDataFileをロックできませんでした。");} seek(CNT, 0, 0); $PreNumber = ; chomp($PreNumber); $PreNumber = 0 if($PreNumber eq ''); seek(CNT, 0, 0); my($now) = time; if($now < $LastAccess + $GuardTime*3600) { $Number = $PreNumber; } else { $Number = $PreNumber + 1; print CNT "$Number\n"; } &UnlockFile(CNT); close(CNT); return ($PreNumber, $Number); } sub GenerateCounterImage { my($PreNumber, $Number, $FrameNumber, $Figures, $Width, $biHeight, $DigitArray) = @_; my($DigitWidth) = $Width / $Figures; my(@NumberArray) = (); my($NumberTmp) = $Number; while(length($NumberTmp) > 0) { unshift(@NumberArray, chop($NumberTmp)); } my(@PreNumberArray) = (); my($PreNumberTmp) = $PreNumber; while(length($PreNumberTmp) > 0) { unshift(@PreNumberArray, chop($PreNumberTmp)); } if(length($Number) > length($PreNumber)) { for ($i=0;$i= $biHeight-$FrameNumber) { for ($n=0;$n<$Figures;$n++) { $StartPoint = ($h-($biHeight-$FrameNumber))*$DigitWidth; $ReturnImageData .= substr(@$DigitArray[$NumberArray[$n]], $StartPoint, $DigitWidth); } } } return $ReturnImageData; } sub GenerateGif { my($PreNumber, $Number, $Figures, $Interval, $TransparentColor, $Width, $biHeight, $PalletData, $DigitDataArray) = @_; my($FrameNumber); my($ConvImageData, $ImageBitStream, $Keta); my($Counter, $Flag, $key, $mod, $ByteCount, $BlockCount, $RestByteCount, $LZWBlock); my(@DigitData) = @$DigitDataArray; my($buf, $size); $buf = 'GIF89a'; my($LogicalScreenWidth) = sprintf("%4x", $Width); $LogicalScreenWidth = pack("H4", substr($LogicalScreenWidth, 2, 2).substr($LogicalScreenWidth, 0, 2)); $buf .= $LogicalScreenWidth; my($LogicalScreenHeight) = sprintf("%4x", $biHeight); $LogicalScreenHeight = pack("H4", substr($LogicalScreenHeight, 2, 2).substr($LogicalScreenHeight, 0, 2)); $buf .= $LogicalScreenHeight; my($ColorCount); $ColorCount = @$PalletData; $buf .= pack("C", 0xF7); # 11110111 $buf .= pack("CC", 0x00, 0x00); $size = 13; for $key (@$PalletData) { $buf .= pack("H6", $key); } $size += $ColorCount*3; if($ImodeFlag) { # Application Extension $buf .= pack("CCC", 0x21, 0xFF, 0x0B); $buf .= 'NETSCAPE2.0'; $buf .= pack("CCCCC", 0x03, 0x01, 0x01, 0x00, 0x00); $size += 19; } my($Tmp1, $Tmp2); my($AniNum) = ''; my($PreAniNum) = ''; for ($i=0;$i4 && $FrameNum != 0 && $FrameNum != $biHeight) { unless(($FrameNum % ($biHeight / 4)) == 0) { next; } } # Graphic Control Extention $buf .= pack("CCC", 0x21, 0xf9, 0x04); if($TransparentColorIndex > 255) { $buf .= pack("C", 0x04); } else { if($FrameNum == 0) { $buf .= pack("C", 0x05); } else { $buf .= pack("C", 0x09); } } $DelayTime = sprintf("%04x", $Interval); # Delay Time $buf .= pack("H2", substr($DelayTime, 2, 2)).pack("H2", substr($DelayTime, 0, 2)); if($TransparentColorIndex > 255) { # Transparent Color Index $buf .= pack("C", 0x00); } else { $buf .= pack("H2", sprintf("%02x", $TransparentColorIndex)); } $buf .= pack("C", 0x00); # Block Terminator $size += 8; # Image Block $buf .= pack("C", 0x2C); if($FrameNum == 0) { $buf .= pack("CCCC", 0x00, 0x00, 0x00, 0x00); $buf .= $LogicalScreenWidth.$LogicalScreenHeight; } else { $buf .= $ImageLeftPosition; $buf .= pack("CC", 0x00, 0x00); # Image Top Position $buf .= $AniWidthPack; # Image Width $buf .= $LogicalScreenHeight; # Image Height } $buf .= pack("C", 0x00); # $buf .= pack("C", 0x08); # LZW符号寸法 $size += 11; $ConvImageData = ''; if($FrameNum == 0) { $ConvImageData = &GenerateCounterImage($PreNumber, $Number, $FrameNum, $Figures, $Width, $biHeight, \@DigitData); } else { #****************** $ConvImageData = &GenerateCounterImage($PreAniNum, $AniNum, $FrameNum, $AniKeta, $AniWidth, $biHeight, \@DigitData); } $Counter = 0; $Flag = 0; $ImageBitStream = ''; for ($i=0;$i0; $i--) { $ImageBitStream .= '0'; } } $ByteCount = length($ImageBitStream) / 8; $Image = ''; for ($i=0; $i<$ByteCount; $i++) { $Image .= pack("b8", substr($ImageBitStream, $i*8, 8)); } $BlockCount = int($ByteCount / 255); $RestByteCount = $ByteCount % 255; $LZWBlock = ''; for ($i=0; $i<=$BlockCount; $i++) { if($i == $BlockCount) { $LZWBlock .= pack("H2", sprintf("%2x", $RestByteCount)); $LZWBlock .= substr($Image, $i*255, $RestByteCount); } else { $LZWBlock .= pack("H2", sprintf("%2x", "255")); $LZWBlock .= substr($Image, $i*255, 255) } } $LZWBlock .= pack("C", 0x00); $buf .= $LZWBlock; $size += $BlockCount * 256 + $RestByteCount +1 + 1; last if($PreNumber eq $Number); } # Trailer $buf .= pack("C", 0x3B); $size += 1; return ($buf, $size); } sub SplitImageData { my($biWidth, $biHeight, $ImageData) = @_; my($DigitWidth) = $biWidth / 10; my($h, $LineBuf, @DigitDataArray); for ($h=0;$h<$biHeight;$h++) { $LineBuf = substr($ImageData, $h*$biWidth, $biWidth); for($i=0;$i<10;$i++) { $DigitDataArray[$i] .= substr($LineBuf, $i*$DigitWidth, $DigitWidth); } } return @DigitDataArray; } sub AnalyzeFileHeader { my($buf) = @_; my($bfType) = unpack("a2", substr($buf, 0, 2)); if($bfType ne 'BM') { &ErrorPrint("ビットマップファイルではありません : bfType[$bfType]"); exit; } my($bfOffBits); for ($i=10;$i<=13;$i++) {$bfOffBits = substr($buf, $i, 1).$bfOffBits;} $bfOffBits = hex(unpack("H8", $bfOffBits)); return $bfOffBits; } sub AnalyzeInfoHeader { my($buf) = @_; my($biSize, $biWidth, $biHeight, $biPlanes, $biBitCount, $i); for ($i=14;$i<=17;$i++) {$biSize = substr($buf, $i, 1).$biSize;} $biSize = hex(unpack("H8", $biSize)); my($biWidth, $biHeight, $biPlanes, $biBitCount); for ($i=18;$i<=21;$i++) {$biWidth = substr($buf, $i, 1).$biWidth;} $biWidth = hex(unpack("H8", $biWidth)); for ($i=22;$i<=25;$i++) {$biHeight = substr($buf, $i, 1).$biHeight;} $biHeight = hex(unpack("H8", $biHeight)); # Image Size Check if($biWidth > 500 || $biHeight > 50) { &ErrorPrint("イメージサイズが大きすぎます。500×50以内にして下さい。 : $biWidth × $biHeight"); exit; } for ($i=28;$i<=29;$i++) {$biBitCount = substr($buf, $i, 1).$biBitCount;} $biBitCount = hex(unpack("H4", $biBitCount)); my($NextBlockStart); my($biCompression, $biSizeImage,$biXPelsPerMeter, $biYPelsPerMeter, $biClrUsed, $biClrImportant); # For BITMAPINFOHEADER if($biSize == 12) { # BITMAPCOREHEADER &ErrorPrint("OS/2フォーマットは変換できません。: biSize[$biSize]"); exit; } elsif($biSize == 40) { # BITMAPINFOHEADER for ($i=30;$i<=33;$i++) {$biCompression = substr($buf, $i, 1).$biCompression;} $biCompression = hex(unpack("H8", $biCompression)); for ($i=46;$i<=49;$i++) {$biClrUsed = substr($buf, $i, 1).$biClrUsed;} $biClrUsed = hex(unpack("H8", $biClrUsed)); $NextBlockStart = 54; } elsif($biSize == 108) { # BITMAPV4HEADER &ErrorPrint("[Error] Can't convert BITMAPV4HEADER : biSize[$biSize]"); exit; } else { &ErrorPrint("[Error] Unknown Bitmap Header : biSize[$biSize]"); exit; } return ($biSize, $biWidth, $biHeight, $biPlanes, $biBitCount, $biCompression, $biSizeImage,$biXPelsPerMeter, $biYPelsPerMeter, $biClrUsed, $biClrImportant, $NextBlockStart); } sub AnalyzeRgbQuad { my($buf, $biBitCount, $biClrUsed, $BlockStart) = @_; my($PalletCount, $NextBlockStart); if($biBitCount == 1 || $biBitCount == 4 || $biBitCount == 8) { $PalletCount = $biClrUsed; } elsif($biBitCount >= 24 || $biClrUsed == 0) { $PalletCount = 0; }else { $PalletCount = 2**$biBitCount; } my($NextBlockStart) = $BlockStart + $PalletCount*4; my($RgbQuadData) = substr($buf, $BlockStart, $PalletCount*4); my(@PalletData) = (); my($i,$R, $G, $B); for ($i=0;$i<$PalletCount;$i++) { $B = unpack("H2", substr($RgbQuadData, $i*4, 1)); $G = unpack("H2", substr($RgbQuadData, $i*4+1, 1)); $R = unpack("H2", substr($RgbQuadData, $i*4+2, 1)); push(@PalletData, $R.$G.$B); } return @PalletData; } sub AnalyzeImageData { my($buf, $biWidth, $biHeight, $bfOffBits, $PalletData) = @_; my($ImageDataBytes) = substr($buf, $bfOffBits, $size-$bfOffBits); my($ColorCount); $ColorCount = @$PalletData; my($i, $h, $LineBytesCount, $mod, $mod2, $LineBytes, @LineArray, @ImageData); if($ColorCount == 256) { $LineBytesCount = $biWidth; $mod = $biWidth % 4; if($mod){$LineBytesCount += 4-$mod;} for ($i=0;$i<$biHeight;$i++) { $LineBytes = substr($ImageDataBytes, $i*$LineBytesCount, $LineBytesCount); @LineArray = (); for ($h=0;$h<$biWidth;$h++) { push(@LineArray, substr($LineBytes, $h, 1)); } unshift(@ImageData, @LineArray); } } else { &ErrorPrint("256色以外のビットマップはGIFに変換できません。: $ColorCount色"); exit; } my($ReturnData); for $key (@ImageData) { $ReturnData .= $key; } return $ReturnData; } sub LockFile { local(*FILE) = @_; my($retry) = 3; my($lockresult); while($retry-- > 0) { $lockresult = eval("flock(FILE, 2)"); if($@) { $retry = -1; last; } if(!$lockresult) { sleep(2); } else { last; } } if($retry > 0) { return 1; } else { return 0; } } sub UnlockFile { local(*FILE) = @_; flock(FILE, 8); } sub ErrorPrint { my($message) = @_; print "Content-type: text/html\n\n"; print "
\n"; print "$message\n"; print "
\n"; exit; }