home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
DOS/V Power Report 2000 April
/
VPR0004A.BIN
/
OLS
/
HTMLLINT
/
htmllint.lzh
/
htmllint.cgi
< prev
next >
Wrap
Text File
|
2000-01-04
|
30KB
|
975 lines
#!/usr/local/bin/perl
# CGI script for Another HTML-lint gateway ###################
require 5.004;
$PROGNAME = 'Another HTML-lint';
$VERSION = '0.89';
$myADDRESS = 'k16@chiba.email.ne.jp';
$version = <<EndOfVersion;
Another HTML-lint gateway script ver$VERSION
Copyright (c) 1997-2000 by ISHINO Keiichiro <$myADDRESS>.
All rights reserved.
EndOfVersion
use File::Basename;
use File::Find;
$CGI_NAME = &basename($0);
$LINT_NAME = 'htmllint.pm';
$WIN = $^O =~ /Win32/oi;
$MAC = $^O =~ /MacOS/oi;
#$OS2 = UNSUPPORTED;
$UNIX = !($WIN || $MAC || $OS2);
require 'htmllint.env';
require $LINT_NAME;
if ($ENV{'QUERY_STRING'} eq '' && @ARGV) {
# 非 CGI インタフェース
my $arg = shift;
if ($arg eq '-vv') {
print "$CGI_NAME $VERSION / $LINT_NAME $htmllint::VERSION";
} else {
print $version;
}
exit;
}
require 'common.rul';
require CGI; CGI->import(qw(:cgi-lib));
$CGIVer = "CGI $CGI::VERSION";
if ($Jcode = (!$NOUSEJCODE && eval('require Jcode'))) {
$JcodeVer = "Jcode $Jcode::VERSION";
*Jgetcode = \&Jcode::getcode;
*Jconvert = \&Jcode::convert;
} else {
require 'jcode.pl';
$JcodeVer = "jcode.pl $jcode::version";
*Jgetcode = \&jcode::getcode;
*Jconvert = sub { &jcode::to($_[1], $_[0], $_[2]); };
}
$msgCantLint = '申し訳ありません。ただいま調整中です。もうしばらくしてから再チェックしてください。';
$msgInURL = '指定されたURL (';
$msgNoHTML = ') は HTML ではありません。';
$msgBadResp = ') は HTTPレスポンスヘッダに問題があります。';
$msgInHTML = '指定されたHTML (';
$msgCantGet = ') を取得することができませんでした。';
$msgNoData = '入力されたデータはありませんでした。';
$myCODE = &Jgetcode(\$msgCantLint); # euc または sjis
&ReadParse(); # GET/POST データを %in に読む
&ShortName;
# 出力する漢字コードの選択
&DetectCode($in{'CharCode'} or $KANJICODE) or &DetectCode('JIS');
$| = 1;
# ビジーチェック
if (defined(&BusyCheck)) {
my $msg = &BusyCheck;
&ErrorExit($msg) if $msg;
}
$URL = $RURL = ($in{'Method'} !~ /^Data$/oi)?
&AbsoluteURL($ENV{'HTTP_REFERER'}, $in{'URL'}): '';
# チェックオプションを得る
&GetOptions;
push @OPT, '-banner', '-score', '-w', 'long';
#push @OPT, '-r', $RULEDIR if $RULEDIR;
# HTML をローカルに得る
$HTML = $TMPDIR.'htmllint'.$$.'.html';
if ($UNIX) {
$SIG{'INT'} = $SIG{'QUIT'} = $SIG{'TERM'} = $SIG{'PIPE'} = sub {
&Unlink;
&Exit;
}
}
if ($URL ne '') {
if ($GETLOCALFILE) {
if ($URL =~ m#^file:///?(.*)#oi) {
($LOCALFILE = $1) =~ s/^(\w)\|(.*)/$1:$2/o;
} elsif ($WIN && $URL =~ /^\w:/o) {
$LOCALFILE = $URL;
}
}
if (defined($LOCALFILE)) {
# ローカルファイルを取得
$HTML = $RURL = $LOCALFILE;
if ($MAC) {
$HTML =~ s#/#:#og;
$HTML = ($HTML =~ m#^:(.*)#)? $1: ':'.$HTML;
}
# %XX のデコードを行なう
$HTML =~ s/\%([0-9A-Fa-f][0-9A-Fa-f])/pack('C', hex($1))/oge;
} else {
my $host = (&ParseURL($URL))[1];
if (@EXCEPTDOMAINS) {
# 除外ドメインのチェック
my $ok = 1;
foreach (@EXCEPTDOMAINS) {
if (&CheckDomain($host, $_)) {
$ok = 0;
foreach (@PERMITDOMAINS) {
if (&CheckDomain($host, $_)) {
# 非除外
$ok = 1;
last;
}
}
last;
}
}
&ErrorExit($msgInHTML.&HrefURL($URL).$msgCantGet) unless $ok;
}
if (@EXCEPTSCORES) {
# 得点記録除外ドメインのチェック
foreach (@EXCEPTSCORES) {
if (&CheckDomain($host, $_)) {
$SCOREFILE = $STATFILE = '';
last;
}
}
}
# HTML を読み込んで改行を変換してテンポラリに書く
if (!$NOUSELWP &&
eval('require LWP::UserAgent') && eval('require HTTP::Request')) {
# original code by KUSANO Takayuki (AE5T-KSN@asahi-net.or.jp)
$URLGETVer = "LWP $LWP::VERSION";
# Create a user agent object
$LWPUA = new LWP::UserAgent;
my $uagent = "Another HTML-lint/$VERSION +".$LWPUA->agent;
$LWPUA->agent($uagent);
$LWPUA->timeout($TIMEOUT) if $TIMEOUT > 0;
$LWPUA->max_size($MAXHTMLSIZE*1024) if $MAXHTMLSIZE > 0;
$LWPUA->proxy('http', "http://$HTTP_PROXY/") if $HTTP_PROXY;
$LWPUA->no_proxy(@HTTP_NOPROXY) if @HTTP_NOPROXY;
# Create a request
my $req = new HTTP::Request GET => $URL;
# Pass request to the user agent and get a response back
my $res = $LWPUA->request($req, $HTML);
# Check the outcome of the response
if ($res->is_success()) {
$RURL = $res->request->url();
$RESULT = $res->headers_as_string();
my $warning = $res->header('Client-Warning');
if ($warning ne '') {
&Unlink;
$warning = qq|(<code>$warning</code>)|;
&ErrorExit($msgInURL.&HrefURL($URL).$msgBadResp.$warning);
}
$CTYPE = $res->header('Content-Type');
unless ($CTYPE =~ m#\btext/html\b#oi) {
&Unlink;
$CTYPE = qq|(<code>$CTYPE</code>)| if $CTYPE ne '';
&ErrorExit($msgInURL.&HrefURL($URL).$msgNoHTML.$CTYPE);
}
($CTYPE) = $CTYPE =~ /charset\s*=\s*([^\s;,]+)/oi;
my $lang = $res->header('Content-Language');
push @OPT, '-lang', $lang if $lang ne '';
} else {
($STAT = '<br>'.$res->status_line()) =~ s/\s*\(\@INC contains:.+//o;
&Unlink;
}
if ($CTYPE ne '') {
push @OPT, '-charset', $CTYPE;
$CTYPE = ($Jcode && lc($CTYPE) eq 'utf-8')? 'utf8': undef;
}
} elsif (eval("require 'httpreq.pl'")) {
$URLGETVer = "httpreq.pl $httpreq::VERSION";
$httpreq::http_proxy = $HTTP_PROXY if $HTTP_PROXY;
$httpreq::user_agent = "Another HTML-lint/$VERSION +$httpreq::httpreq";
$httpreq::timeout = $TIMEOUT if $TIMEOUT > 0;
$httpreq::maxsize = $MAXHTMLSIZE*1024 if $MAXHTMLSIZE > 0;
$httpreq::http_proxy = $HTTP_PROXY if $HTTP_PROXY;
@httpreq::http_noproxy = @HTTP_NOPROXY if @HTTP_NOPROXY;
($STAT, $RURL, $RESULT) = &httpreq::get($URL, $HTML);
if ($STAT >= 200 && $STAT < 300) {
($CTYPE) = $RESULT =~ /Content-Type:\s*([^\r\n]+)/oi;
unless ($CTYPE =~ m#\btext/html\b#oi) {
&Unlink;
&ErrorExit($msgInURL.&HrefURL($URL)."$msgNoHTML(<code>$CTYPE</code>)");
}
($CTYPE) = $CTYPE =~ /charset\s*=\s*([^\s;,]+)/oi;
my ($lang) = $RESULT =~ /Content-Language:\s*([^\s\r\n]+)/oi;
push @OPT, '-lang', $lang if $lang ne '';
} else {
&Unlink;
}
}
push @OPT, '-nolo', '-base', $URL;
}
push @OPT, '-usec';
} else {
# TEXTEREA の内容をテンポラリに書く
open(HTML, ">$HTML");
print HTML $in{'Data'};
close(HTML);
push @OPT, '-ignc';
}
push @OPT, '-stat', $STATFILE if $STATFILE && $in{'Stat'};
if (!(-e $HTML) || (-z $HTML)) {
# テンポラリファイルがうまくできていない
my $japURL = (&Jgetcode(\$URL) =~ /^(jis|euc|sjis)$/)?
'URLに日本語などのASCII以外の文字を使うことはできません。': '';
&Unlink;
&EscapeRef(\$STAT);
&ErrorExit(($URL ne '')? $msgInHTML.&HrefURL($URL).$msgCantGet.$japURL.$STAT:
$msgNoData);
}
$LYNX = '' if !$in{'LynxView'} || $MAC;
$LYNX =~ s/^\s+//o;
if (!$in{'NoCheck'} || $LYNX eq '') {
push @OPT, '--', $HTML;
# 結果用PIPEファイルを作る
my $PIPE = $TMPDIR.'htmllint'.$$.'.result';
open(PIPE, ">$PIPE");
my $oldfh = select PIPE;
# さあ行け!
&htmllint::HTMLlint(@OPT);
&DetectCode($TXTCODE) if $in{'CharCode'} eq '' || uc($in{'CharCode'}) eq 'AUTO';
select $oldfh;
# 結果を読み込む
$header = $footer = '';
# close(PIPE);
open(PIPE, "<$PIPE");
while (<PIPE>) {
local $RESULT;
chomp($RESULT = $_);
&EscapeRef(\$RESULT);
if ($RESULT =~ /^\d+: /o) {
push(@line, $RESULT);
} else {
if ($header) { $footer = $RESULT; }
else { $header = $RESULT; }
}
}
close(PIPE);
unlink($PIPE);
($WARNS) = $footer =~ /^(\d+)/o;
($SCORE) = $footer =~ / (-?\d+)(.*)/o;
($KIND, $TAGS) = $2 =~ / (\d+)\D+ (\d+)/o;
($RULE) = $header =~ /\Qを\E (.+) \Qとして\E/o;
if ($RULE eq '' || $SCORE eq '') {
&Unlink;
&ErrorExit("$msgCantLint<br>$header");
}
$counter = $SCOREFILE? &LogScore: 0;
# 結果の表示
($img, $alt) = (!$WARNS && $SCORE >= 100)? ('verygood', 'たいへんよくできました'):
($SCORE >= 80)? ('good', 'よくできました'):
($SCORE >= 35)? ('normal', 'ふつうです'):
('fight', 'がんばりましょう');
$img .= '.gif';
if ($in{'Image'} ne '') {
&Unlink;
if ($COUNTER && uc($in{'Image'}) eq 'SCORE') {
$SCORE = sprintf("%0$in{'md'}d", $SCORE) if $in{'md'};
$query = "lit=$SCORE";
foreach ('dd', 'tr', 'pad', 'ft', 'frgb', 'trgb', 'srgb', 'prgb',
'chcolor', 'negate', 'degrees', 'rotate') {
$query .= "&$_=$in{$_}" if $in{$_};
}
$ENV{'QUERY_STRING'} = $query;
# $ENV{'HTTP_REFERER'} = $ENV{'REMOTE_ADDR'};
$ENV{'HTTP_REFERER'} = 'http://'.$ENV{'HTTP_HOST'}.$ENV{'HTTP_URI'};
$ENV{'REQUEST_METHOD'} = 'GET';
exec $COUNTER;
} else {
$img = $IMGDIR.'ahl-'.$img;
if (open(IMG, "<$img")) {
binmode(IMG);
# $len = (stat(IMG))[7];
$len = -s IMG;
sysread(IMG, $buff, $len);
close(IMG);
print qq|Content-type: image/gif\n|,
qq|Content-length: $len\n\n|,
$buff;
}
}
} else {
&PrintHTMLHeader("Check result of $PROGNAME");
# $useimage = $ENV{'HTTP_ACCEPT'} =~ m#image/gif#o;
$useimage = 1;
&Jprint(qq|<a href="https://sw.vector.co.jp/swreg/step1.info?srno=SR011941&site=v&sid=335404740" class="image"><img src="$IMGROOT$img" alt="$alt" width="68" height="68" align="right" border="0"></a>|) if $useimage;
$footer =~ s#(\Q\(^o^)/\E)#<code>$1</code>#o;
# print "$_ => '$in{$_}'<br>" foreach (keys(%in)); print "OPT = @OPT<br>";
print('<h2>');
&Jprint('チェックの結果は以下のとおりです。');
print("</h2>\n<p>\n");
&Jprint(&HrefURL($URL), ' を ') if $URL ne '';
&Jprint($RULE, ' としてチェックしました。', "<br>\n", ($TAGS ne '0')?
"$footer<br>\n": 'タグのひとつもないHTMLは採点できません。'."<br>\n");
if (!$Jcode && $in{'CharCode'} =~ /^UTF8$/oi) {
&Jprint("<br>\n".'このサーバではUTF-8は扱えません。');
}
if ($LYNX ne '') {
&Jprint(qq|<br>\n<a href="#LynxView">|.'Lynxでの見え方はこちら</a>にあります。');
}
print("</p>\n");
&PrintHTTPHeader;
print("<p>\n");
$nopenalty = 0;
$gray = '#666666';
$br = '';
$tar = $in{'OtherWindow'}? ' target="explain"': '';
foreach (sort { $a <=> $b } @line) {
/^(\d+): ([^:]+):\s*(.*)/o;
my $n = $1;
my $id = $2;
my $body = &PrintableCtrlCharacter($3);
$warn{$n}++;
print("<br>\n") if $br++;
print($in{'ViewSource'}? qq|<a href="#$n">line $n</a>: |: qq|line $n: |);
if ($whines{$id}) {
&Jprint($body);
} else {
print(qq|<font color="$gray">(|);
&Jprint($body);
print(q|)</font>|);
$nopenalty++;
}
$n = ${$htmllint::messages{$id}}[1];
unless (defined($n)) {
$id = $htmllint::alias_messages{$id};
$n = ${$htmllint::messages{$id}}[1];
}
&Jprint(' → '.qq|<a href="$EXPLAIN#$id"$tar>|.'解説'." $n</a>");
}
print($useimage? qq|<br clear="all">\n|: "<br>\n");
&Jprint(qq|<br><font color="$gray">|.
'(グレイ)</font> のエラーは軽度のエラーで減点対象外です。') if $nopenalty;
print("</p>\n");
if ($in{'ViewSource'}) {
&Jprint('<hr><h2>チェックしたHTMLは以下のとおりです。'."</h2>\n");
if ($RURL ne '' || $LYNX ne '') {
print('<p>');
&Jprint(&HrefURL($RURL)) if $RURL ne '';
&Jprint(' → <a href="#LynxView">Lynxでの見え方はこちら</a>') if $LYNX ne '';
print("</p>\n");
}
print("<ol>\n");
open(HTML, $HTML);
local $/ = &DetectSeparator;
my $ln = 0;
while ($RESULT = <HTML>) {
$ln = $.;
$RESULT =~ s/\s+$//g;
&ConvertAndEscape($TXTCODE);
$RESULT =~ s/ |\t/ /og;
# ($RESULT = &PrintableCtrlCharacter($RESULT)) =~ s/ |\t/ /og;
print('<li>');
if ($warn{$ln}) {
print(qq|<code><a name="$ln"><font color="red">|, $RESULT,
q|</font></a></code>|);
} elsif ($RESULT ne '') {
print('<code>', $RESULT, '</code>');
}
print("</li>\n");
}
$ln++;
print(qq|<li><a name="$ln"><font color="$gray">[EOF]</font></a>\n|)
if $warn{$ln};
print("</ol>\n");
close(HTML);
}
if ($LYNX ne '') {
# Lynx も見たければ実行する
print('<hr>');
&LynxView;
} elsif ($in{'LynxView'}) {
&Jprint('<hr><h2>このサーバではLynxはサポートされていません。</h2>'."\n");
}
&Unlink;
&PrintHTMLFooter(1);
}
} else {
# Lynx の表示だけ見る
&PrintHTMLHeader("Lynx View by $PROGNAME");
&LynxView;
&Unlink;
&PrintHTMLFooter(0);
}
&Exit;
sub PrintHTTPHeader
{
if ($URL ne '' && $in{'HTTPHeader'}) {
print('<blockquote>');
if ($RESULT ne '') {
$RESULT =~ s/(\r?\n)+$//o;
&ConvertAndEscape($CTYPE);
print('<pre>', $RESULT, '</pre>');
} else {
&Jprint('このサーバの設定ではHTTPヘッダを得られません。');
}
print("</blockquote>\n");
}
}
sub LynxView
{
my $opt;
if ($LYNX =~ /^(\S+)\s+(.*)/o) {
$LYNX = $1;
$opt = $2;
}
$opt = '-dump -nolist' if $opt eq '';
$RESULT = `$LYNX $opt $HTML`;
&ConvertAndEscape();
$LYNXVER = `$LYNX -version`;
$LYNXVER =~ s#\n#<br>\n#og;
$LYNXVER =~ s# (http:\S+) # <a href="$1">$1</a> #og;
&Jprint('<h2><a name="LynxView">Lynxでの見え方は以下のとおりです。</a>');
print(qq|</h2>\n<div class="lynx"><pre>\n|, $RESULT, "</pre></div>\n",
q|<blockquote><hr class="none">|, $LYNXVER, "</blockquote>\n");
}
sub Jprint
{
foreach (@_) { print &Jconvert($_, $outCODE, $myCODE); }
}
sub DetectSeparator
{
my $sep = "\n";
my $buff;
read(HTML, $buff, 1024);
if ($buff !~ /\x0D\x0A/o) {
$sep = "\x0A" if $buff =~ /\x0A/o;
$sep = "\x0D" if $buff =~ /\x0D/o;
}
seek(HTML, 0, 0);
$sep;
}
sub DetectCode
{
my $ccode = uc(shift);
if ($ccode eq 'EUC') {
$outCODE = 'euc';
$CHARSET = 'EUC-JP';
} elsif ($ccode eq 'SJIS') {
$outCODE = 'sjis';
$CHARSET = 'Shift_JIS';
} elsif ($ccode eq 'JIS') {
$outCODE = 'jis';
$CHARSET = 'ISO-2022-JP';
} elsif ($Jcode && $ccode eq 'UTF8') {
$outCODE = 'utf8';
$CHARSET = 'UTF-8';
} else {
return 0;
}
1;
}
# テンポラリファイルを消す
sub Unlink
{
unlink($HTML) unless defined($LOCALFILE);
}
# IPを得る
sub GetIP
{
my $host = shift;
$host =~ s#^//##o;
my (@addr) = (gethostbyname($host))[4];
my (@ip) = unpack('C4', $addr[0]);
((((($ip[0]<<8)+$ip[1])<<8)+$ip[2])<<8)+$ip[3];
}
# ドメイン名が指定のものか調べる
sub CheckDomain
{ # original code by HOSOKAWA Tatsumi (hosokawa@ntc.keio.ac.jp)
my ($host, $domain) = @_;
if ($domain =~ m#^(\d+\.\d+\.\d+\.\d+)(?:/(\d+))?(?:([*!])(.+))?$#) {
my $rule = $4;
my $cond = $3;
my $mask = $2;
my $domip = &GetIP($1);
my $hostip = &GetIP($host);
return 0 if $rule && $cond eq (&CheckDomain($ENV{'REMOTE_ADDR'}, $rule)? '!': '*');
if (defined($mask)) {
$mask = ~((1<<(32-$mask))-1);
} else {
$mask = ~0;
foreach (0xFFFFFFFF, 0xFFFFFF, 0xFFFF, 0xFF) {
unless ($domip & $_) {
$mask = ~$_;
last;
}
}
}
return 1 if ($hostip & $mask) == ($domip & $mask);
} else {
$domain =~ s/\./\\\./og;
if ($host =~ m#(^//|\.)$domain$#) {
# 指定ドメイン名で終わるホスト
return 1;
}
}
0;
}
# URL を分解する (http のみ)
sub ParseURL
{
my $url = shift;
$url =~ s/^\s*//o;
my $proto = ($url =~ s@^(\w*:)@@o)? lc($1): '';
my $host = ($url =~ s@^(//[\w\-.]*)@@o)? $1: '';
my $port = ($url =~ s@^(:\d*)@@o)? $1: '';
my $path = '';
my $file = ($url =~ s@([^#]*)@@o)? $1: '';
($path, $file) = ($1, $2) if $file =~ m@^(/(?:[^/]*/)*)([^/]*)$@o;
($proto eq ':' || $host eq '//' || $port eq ':')?
undef: ($proto, $host, $port, $path, $file);
}
# URL を絶対パスにする (http のみ)
sub AbsoluteURL
{
my ($base, $url) = @_;
my ($bproto, $bhost, $bport, $bpath, $bfile) = &ParseURL($base);
my ($uproto, $uhost, $uport, $upath, $ufile) = &ParseURL($url);
&NormalizeURL(
(!($url ne '' && $upath eq '' && $ufile eq '') &&
(($uproto eq '' || $uproto =~ /^http/oi) && $bproto =~ /^http/oi))?
(($uproto ne '')? $uproto: $bproto).
(($uhost ne '')? $uhost.$uport: $bhost.$bport).
(($upath ne '')? $upath.$ufile: ($bpath.
(($ufile ne '')? $ufile: $bfile))): $url);
}
# URL 中の . を解決する
sub NormalizeURL
{
my @files;
my ($domain, $filespec) = ('', shift);
if ($filespec =~ m#^(\w+://(?:[^/]+))(.*)$#o) {
($domain, $filespec) = ($1, $2);
}
foreach (split(m#/+#, $filespec, -1)) {
next if $_ eq '.';
if ($_ eq '..' && @files) {
my $parent = pop(@files);
next if $parent ne '' && $parent ne $_;
push(@files, $parent);
}
push(@files, $_);
}
$domain.join('/', @files);
}
# URL へのリンク参照を求める
sub HrefURL
{
my $url = shift;
&EscapeRef(\$url);
$url =~ m#^\w+://#o? qq|<a href="$url">$url</a>|: $url;
}
# URL が存在するか調べステータスを返す (http のみ)
# 戻り値は (stat, url, content-type, content-length) の配列
sub AskHTML
{
my $stat = 200;
my ($rurl, $type, $length, $header);
my $TIMEOUT = $in{'TimeOut'}+0;
if ($TIMEOUT > 0.0) {
$TIMEOUT = 60 if $TIMEOUT > 60.0;
my $url = &AbsoluteURL;
if ($LWPUA) {
$LWPUA->timeout($TIMEOUT);
my $req = new HTTP::Request HEAD => $url;
my $res = $LWPUA->request($req);
$stat = $res->code();
if ($in{'CheckGET'} && $stat >= 400) {
$req = new HTTP::Request GET => $url;
$res = $LWPUA->request($req);
$stat = $res->code();
}
$rurl = $res->request->url();
$type = $res->header('Content-Type');
$length = $res->header('Content-Length');
} else {
$httpreq::timeout = $TIMEOUT;
($stat, $rurl, $header) = &httpreq::head($url);
if ($in{'CheckGET'} && $stat >= 400) {
($stat, $rurl, $header) = &httpreq::get($url);
}
($header =~ /(?:^|\n)Content-Type:\s*(.+)\n/omi) and $type = $1;
($header =~ /(?:^|\n)Content-Length:\s*(.+)\n/omi) and $length = $1;
}
}
[$stat, $rurl, $type, $length];
}
# コード変換して実体参照にエスケープする
sub ConvertAndEscape
{
$icode = shift;
if ($outCODE eq 'jis') {
&Jconvert(\$RESULT, $myCODE, $icode);
&EscapeRef(\$RESULT);
&Jconvert(\$RESULT, $outCODE, $myCODE);
} else {
&Jconvert(\$RESULT, $outCODE, $icode);
&EscapeRef(\$RESULT);
}
}
# 実体参照にエスケープする
sub EscapeRef
{
$str = shift;
$$str =~ s/&/&/og;
$$str =~ s/</</og;
$$str =~ s/>/>/og;
$$str =~ s/"/"/og;
}
# 制御文字を印字可能に変換する
sub PrintableCtrlCharacter
{
my $str = shift;
$str =~ s#([\x00-\x08\x0B\x0C\x0E-\x1F])#'<i>^'.pack('C',unpack('C',$1)+0x40).'</i>'#eog;
$str;
}
# エラー出力して終了する
sub ErrorExit
{
my (@msgs) = @_;
&PrintHTMLHeader("$PROGNAME error!");
&Jprint(qq|<h2>$PROGNAME error!</h2>\n|);
while (@msgs) {
print('<p>');
&Jprint(shift(@msgs));
print("</p>\n");
}
&PrintHTTPHeader if $RESULT ne '';
&PrintHTMLFooter(0);
&Exit;
}
sub Exit
{
# 消されていないテンポラリの始末をする
$File::Find::prune = 1;
&find(\&CleanupTmp, $TMPDIR? $TMPDIR: '.');
exit;
}
sub CleanupTmp
{
if (!-d && /^htmllint-?\d+\.(html|result)$/o && (stat($_))[9] < time-24*60*60) {
# 24時間以前のファイルを消す
unlink($_);
}
}
# HTML ヘッダ部分を出力する (PrintHeaderという関数は cgi-lib に既存)
sub PrintHTMLHeader {
my ($title) = @_;
print(qq|Content-Type: text/html; charset=$CHARSET\x0D\x0A\x0D\x0A|,
<<EndOfHTMLHeader);
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja"><head>
<meta http-equiv="Content-Type" content="text/html; charset=$CHARSET">
<link rev="made" href="mailto:$myADDRESS">
<link rel="stylesheet" type="text/css" href="${HTMLDIR}htmllint.css">
<link rel="contents" href="./index.html">
<title>$title</title>
</head>
<body bgcolor="#FFFFF0" text="#000000" link="#0000FF" vlink="#663399" alink="#FF0000">
<div align="center">
[<a href="./index.html">about</a>]
[<a href="./sitemap.html">sitemap</a>]
[<a href="./htmllint.html">gateway</a>]
[<a href="./htmllintl.html">lite</a>]
[<a href="./htmllinte.html">dyn</a>]
</div><hr>
EndOfHTMLHeader
}
# HTML フッタ部分を出力する
sub PrintHTMLFooter
{
my $cntstr;
if (shift) {
$cntstr = ($counter? "-- #$counter": '').' -- cost '.(time - $^T).' sec';
$cntstr .= " -- $LINTER run" if $LINTER > 1;
$cntstr .= ' --<br>';
}
$JcodeVer .= ' NoXS' if $Jcode && defined($Jcode::Unicode::NoXS::VERSION);
print(<<EndOfHTMLFooter);
<hr><div align="center">
<address>${cntstr}This page was generated by $CGI_NAME $VERSION / $LINT_NAME $htmllint::VERSION<br>
$URLGETVer / $CGIVer / $JcodeVer<br>
1997-2000 (c) by <a href="mailto:k16\@chiba.email.ne.jp">k16\@chiba.email.ne.jp</a> / PostPet: k16pet\@kinchan.com</address></div>
<hr><div align=center>
[<a href="./index.html">about</a>]
[<a href="./sitemap.html">sitemap</a>]
[<a href="./htmllint.html">gateway</a>]
[<a href="./htmllintl.html">lite</a>]
[<a href="./htmllinte.html">dyn</a>]
</div>
</body>
</html>
EndOfHTMLFooter
}
# 得点の記録
sub LogScore
{
my $rule = $RULE;
foreach (keys(%doctypes)) {
if (${$doctypes{$_}}{'guide'} eq $rule) {
$rule = $_;
last;
}
}
my $cnt = 0;
my $file = $SCOREFILE;
my $url = $URL;
$url =~ s/ /%20/og;
$url = '<TEXTAREA>' if $url eq '';
my ($sec, $min, $hour, $mday, $mon, $year) = localtime(time);
my $post = sprintf('%04d%02d', 1900+$year, 1+$mon);
$file =~ s/#/$post/g;
my $existfile = -e $file;
if (open(LOGF, ($existfile? '>>': '>').$file)) {
flock(LOGF, 2) if $UNIX;
if ($SCORECOUNTER) {
open(CNTF, $SCORECOUNTER);
$cnt = <CNTF>+1;
if (open(CNTF, ">$SCORECOUNTER")) {
print CNTF "$cnt\n";
}
close(CNTF);
chmod 0766, $SCORECOUNTER;
}
$WARNS = 0 unless $WARNS;
my $rhost = ($ENV{'REMOTE_HOST'} or $ENV{'REMOTE_ADDR'});
print LOGF sprintf('%04d/%02d/%02d %02d:%02d:%02d',
1900+$year, 1+$mon, $mday, $hour, $min, $sec),
" $rhost $url ",
($TAGS ne '0')? "$SCORE/$WARNS/$TAGS/$KIND": "//$TAGS/$KIND",
" $rule\n";
# flock(LOGF, 8) if $UNIX;
close(LOGF);
chmod 0766, $file;
}
$cnt;
}
# 警告情報を収集する (htmllint.pm が呼ぶ)
sub PushStat
{
my $name = shift;
push(@{'stat'.$name}, shift);
}
# 警告の統計を取る (htmllint.pm が呼ぶ)
sub TakeStatistics
{
my $stat = shift;
if ($stat ne '') {
local ($statstart, $statsample, $seensample, *STAT);
my @lt = localtime;
my $suffix = sprintf('%04d%02d', $lt[5]+1900, $lt[4]+1);
$stat =~ s/#/$suffix/g;
if ($stat ne $stdio) {
if (-e $stat) {
open(STAT, "+<$stat") || return;
flock(STAT, 2) if $UNIX;
# 排他制御が起こったときSTATの内容は古いのでもう一度オープンし直す
open(STAT, "+<$stat") || return;
flock(STAT, 2) if $UNIX;
local $err = 0;
local $SIG{__WARN__} = sub { $err++; }; # 次の do のエラーをトラップ
do $stat;
if (!defined($statstart) && !$err) {
# 何らかの理由で読み込みに失敗した ($stat が破損しているときは修復する)
# flock(STAT, 8) if $UNIX;
close(STAT);
return;
}
seek(STAT, 0, 0);
} else {
open(STAT, ">$stat") || return;
flock(STAT, 2) if $UNIX;
}
} else {
*STAT = *STDOUT;
}
foreach (keys(%whinesStat)) { $statistics{$_} += $whinesStat{$_}; }
undef %whinesStat;
foreach (keys(%seenTagsStat)) { $statSeenTags{$_} += $seenTagsStat{$_}; }
undef %seenTagsStat;
foreach (keys(%seenTagsKind)) { $statKindTags{$_} += $seenTagsKind{$_}; }
undef %seenTagsKind;
foreach (keys(%seenMultiBody)) { $statMultiBody{$_} += $seenMultiBody{$_}; }
undef %seenMultiBody;
$statcurrent = sprintf('%4d/%02d/%02d %02d:%02d:%02d',
$lt[5]+1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0]);
$statstart = $statcurrent unless defined($statstart);
print STAT '$statstart = \'', $statstart, "';\n",
'$statcurrent = \'', $statcurrent, "';\n",
'$statsample = ', ++$statsample, ";\n",
'$seensample = ', ++$seensample, ";\n";
&PrintStatArray('statistics',
'statUnknownDoctype',
'statOnceOnly',
'statOnceOnlyGroup',
'statUnclosedElement',
'statExcludedElement',
'statOmitEndTag',
'statDeprecatedElement',
'statDeprecatedTag',
'statDeprecatedAttr',
'statElementOverlap',
'statMustFollow',
'statEmptyContainer',
'statIllegalClosing',
'statRequired',
'statRequiredAttr',
'statRequiredValue',
'statUnknownElement',
'statUnknownAttribute',
'statUnexpectedPCDATA',
'statOmitAttributeName',
'statMinimizedAttribute',
'statHereAnchor',
'statNoRegCharset',
'statNoTextHtml',
'statUnknownProtocol',
'statIllegalFormatURL',
'statBadJISX0208',
'statExcludedURLRef',
'statSeenTags',
'statKindTags',
'statMultiBody');
if ($stat ne $stdio) {
truncate(STAT, tell(STAT));
# flock(STAT, 8) if $UNIX;
close(STAT);
chmod 0766, $stat;
}
}
}
sub PrintStatArray
{
foreach $name (@_) {
my $esc;
if ($name ne 'statistics') {
foreach (@$name) { $$name{$_}++; }
undef @$name;
}
if (%$name) {
print STAT "\%$name = (\n";
foreach (sort {$$name{$b} <=> $$name{$a} || $a cmp $b} keys(%$name)) {
$esc = $_;
$esc =~ s/[\x00-\x1F]/ /og; # 暫定
$esc =~ s/\\/\\\\/og;
$esc =~ s/'/\\'/og;
# $esc =~ s/\n//o;
print STAT " '$esc' => $$name{$_},\n";
}
print STAT ");\n";
}
undef %$name;
}
}
# 短縮問い合わせデータの調整
sub ShortName
{
foreach (split(/[&;]/, $in{'keywords'})) { $in{$_} = 'on'; }
foreach (keys(%in)) { $in{$_} = 'on' if $in{$_} eq ''; }
my %shortNames = (
Method => 'M',
# URL => '',
# Data => '',
CharCode => 'C',
NoWarnings => 'N',
ViewSource => 'V',
LynxView => 'L',
# HTTPHead => '',
OtherWindow => 'O',
# NoCheck => '',
IgnoreDOCTYPE => 'I',
HTMLVersion => 'H',
Pedantic => 'P',
NoReligious => 'R',
NoAccessibility => 'A',
TimeOut => 'T',
# CheckGET => '';
# CheckList => '',
Enable => 'E',
Disable => 'D',
# LimitWhines => '',
);
foreach (keys(%shortNames)) { $in{$_} = $in{$shortNames{$_}} if !defined($in{$_}); }
}
# チェックオプションを得る
sub GetOptions
{
my $x = 'html20';
foreach (keys(%doctypes)) {
if ($in{'HTMLVersion'} =~ /^(${$doctypes{$_}}{'name'})$/i) {
$x = $_;
last;
}
}
push @OPT, '-x', $x,
$in{'IgnoreDOCTYPE'}? '-ignd': '-used',
$in{'NoWarnings'}? '-nowar': '-war',
$in{'NoReligious'}? '-norel': '-rel',
$in{'NoAccessibility'}? '-noacc': '-acc';
push @OPT, '-limit', $in{'LimitWhines'} if $in{'LimitWhines'} > 0;
my (@warnings, @enable, @disable);
&htmllint::ListWarnings(\@warnings);
foreach (@warnings) {
if (/^(\S+)\s+(\S+)\s+(ENABLED|DISABLED)\s+(\S+)(?:\s+(\S+)\s+(\S+))?/) {
my ($id, $sh, $ed, $n, $swa, $wna) = ($1, $2, $3, $4, $5, $6);
$whines{$id} = $n;
$whines{$swa} = $wna if $swa;
next if $id eq 'over-limit-whines';
$sh = $id if $sh eq '-';
if ($in{$id}) { push(@enable, $sh) if $ed =~ /^D/o; }
else { push(@disable, $sh) if $ed =~ /^E/o; }
}
}
if ($in{'Pedantic'}) {
push @OPT, '-ped';
} else {
push @OPT, '-noped';
if ($in{'CheckList'}) {
push @OPT, '-e', join(',', @enable) if @enable;
push @OPT, '-d', join(',', @disable) if @disable;
} else {
push @OPT, '-e', $in{'Enable'} if $in{'Enable'} ne '';
push @OPT, '-d', $in{'Disable'} if $in{'Disable'} ne '';
}
}
}