$warning
)|;
&ErrorExit($msgInURL.&HrefURL($URL).$msgBadResp.$warning);
}
$CTYPE = $res->header('Content-Type');
unless ($CTYPE =~ m#\b$acceptMIME\b#oi) {
&Unlink;
$CTYPE = qq|($CTYPE
)| if $CTYPE ne '';
&ErrorExit($msgInURL.&HrefURL($URL).$msgNoHTML.$CTYPE);
}
($CTYPE) = $CTYPE =~ /charset\s*=\s*([^\s;,]+)/oi;
$LANG = $res->header('Content-Language');
$STYLE = $res->header('Content-Style-Type');
$SCRIPT = $res->header('Content-Script-Type');
} else {
($STAT = "\n".$res->status_line()) =~ s/\s*\(\@INC contains:.+//o;
&Unlink;
}
} 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#\b$acceptMIME\b#oi) {
&Unlink;
&ErrorExit($msgInURL.&HrefURL($URL)."$msgNoHTML($CTYPE
)");
}
($CTYPE) = $CTYPE =~ /charset\s*=\s*([^\s;,]+)/oi;
($LANG) = $RESULT =~ /Content-Language:\s*([^\s\r\n]+)/oi;
($STYLE) = $RESULT =~ /Content-Style-Type:\s*([^\s\r\n]+)/oi;
($SCRIPT) = $RESULT =~ /Content-Script-Type:\s*([^\s\r\n]+)/oi;
} else {
&Unlink;
}
}
if ($CTYPE ne '') {
push @OPT, '-charset', $CTYPE;
$CTYPE = ($Jcode && lc($CTYPE) eq 'utf-8')? 'utf8': undef;
}
push @OPT, '-lang', $LANG if $LANG ne '';
push @OPT, '-style', $STYLE if $STYLE ne '';
push @OPT, '-script', $SCRIPT if $SCRIPT ne '';
push @OPT, '-nolo', '-base', $URL;
}
push @OPT, '-usec';
} else {
if ($cgi->param('Method') =~ /^File$/oi) {
# ファイルアップロード
$FILE = $cgi->param('File');
if ($FILE eq '') { &ErrorExit($msgNoFile); }
$HTML = $cgi->tmpFileName($FILE);
push @OPT, '-usec';
} else {
# TEXTEREA の内容をテンポラリに書く
open(HTML, ">$HTML");
print HTML $cgi->param('Data');
close(HTML);
push @OPT, '-ignc';
}
}
push @OPT, '-stat', $STATFILE if $STATFILE && $cgi->param('Stat');
if (!(-e $HTML) || (-z $HTML)) {
# テンポラリファイルがうまくできていない
my $japURL = (&Jgetcode(\$URL) =~ /^(jis|euc|sjis)$/)? 'URLに日本語などのASCII以外の文字を使うことはできません。': '';
&Unlink;
&EscapeRef(\$STAT);
&EscapeRef(\$FILE);
&ErrorExit(($URL ne '')? $msgInHTML.&HrefURL($URL).$msgCantGet.$japURL.$STAT:
($FILE ne '')? $msgInFile.$FILE.$msgCantGet: $msgNoData);
}
$TextView = lc($cgi->param('LynxView')? 'lynx': $cgi->param('TextView'));
$TextView = '' if $TextView ne 'lynx' && $TextView ne 'w3m';
if ($TextView ne 'lynx' || $MAC) { $LYNX = ''; } else { $LYNX =~ s/^\s+//o; }
if ($TextView ne 'w3m' || $MAC) { $W3M = ''; } else { $W3M =~ s/^\s+//o; }
if (!$cgi->param('NoCheck') || ($LYNX eq '' && $W3M eq '')) {
push @OPT, '--', $HTML;
# 結果用PIPEファイルを作る
$PIPE = $TMPDIR.'htmllint'.$$.'.result';
open(PIPE, ">$PIPE");
my $oldfh = select PIPE;
# さあ行け!
&htmllint::HTMLlint(@OPT);
&DetectCode($TXTCODE)
if $cgi->param('CharCode') eq '' || uc($cgi->param('CharCode')) eq 'AUTO';
select $oldfh;
# 結果を読み込む
my $header;
my $footer;
# close(PIPE);
open(PIPE, "<$PIPE");
my @line;
while ($1
#o;
print('\n");
if ($FILE ne '') {
&EscapeRef(\$FILE);
&Jprint($FILE.' を ');
} elsif ($URL ne '') {
&Jprint(&HrefURL($URL).' を ');
}
&Jprint($RULE.' としてチェックしました。'."
\n", ($TAGS ne '0')?
"$footer
\n": 'タグのひとつもないHTMLは採点できません。'."
\n");
if (!$Jcode && $cgi->param('CharCode') =~ /^UTF8$/oi) {
&Jprint("
\n".'このサーバではUTF-8は扱えません。');
}
if ($LYNX ne '' || $W3M ne '') {
&Jprint(qq|
\n${TextView}|.'での見え方はこちらにあります。');
}
print("
先頭の数字はエラーのおおまかな重要度を 0〜9 で示しています(減点数ではありません)。少ない数字は軽く、9 になるほど致命的です。'); foreach (@line) { /^(\d+): ([^:]+):\s*(.*)/o; my $id = $2; unless ($whines{$id}) { &Jprint('0 は減点対象外のごく軽度のエラーで '.qq||. '(グレイのかっこつき) でメッセージされています。'); last; } } print("
\n");
foreach (sort { $a <=> $b } @line) {
/^(\d+): ([^:]+):\s*(.*)/o;
my $n = $1;
my $id = $2;
my $body = &PrintableCtrlCharacter($3);
$warn{$n}++;
print("
\n") if $br++;
print("$whines{$id}: ");
print($cgi->param('ViewSource')? qq|line $n: |: qq|line $n: |);
if ($whines{$id}) {
&Jprint($body);
} else {
print(qq|(|);
&Jprint($body);
print(q|)|);
}
$n = ${$htmllint::messages{$id}}[1];
unless (defined($n)) {
$id = $htmllint::alias_messages{$id};
$n = ${$htmllint::messages{$id}}[1];
}
&Jprint(' → '.qq||.'解説'." $n");
}
print("
'); &Jprint(&HrefURL($RURL)) if $RURL ne ''; if ($LYNX ne '' || $W3M ne '') { &Jprint(qq| → ${TextView}での見え方はこちら|); } print("
\n"); } print("|, $RESULT, q|
|);
} elsif ($RESULT ne '') {
print('', $RESULT, '
');
}
print("'); if ($RESULT ne '') { $RESULT =~ s/(\r?\n)+$//o; &ConvertAndEscape($CTYPE); print('\n"); } } sub LynxView { &TextView($LYNX, '-dump -nolist -force_html'); } sub W3mView { my $opt = '-dump -T text/html -M '.(($myCODE eq 'euc')? '-e': '-s'); &TextView($W3M, $opt); } sub TextView { my $opt; my ($prog, $defopt) = @_; if ($prog =~ /^(\S+)\s+(.*)/o) { $prog = $1; $opt = $2; } my $ver; if ($TextView eq 'lynx') { $ver = `$prog -version`; $ver =~ s#\n#', $RESULT, ''); } else { &Jprint('このサーバの設定ではHTTPヘッダを得られません。'); } print("
\n|, $RESULT, "
\n") if $ver; 1; } 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) if !defined($LOCALFILE); } # IPを得る sub GetIP { my $host = shift; $host =~ s#^//##o; my @ip; if ($host =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#) { @ip = ($1,$2,$3,$4); } else { my (@addr) = (gethostbyname($host))[4]; @ip = unpack('C4', $addr[0]); } ((((($ip[0]<<8)+$ip[1])<<8)+$ip[2])<<8)+$ip[3]; } # Private IP か調べる sub CheckPrivateIP { my $host = shift; if ($host =~ m#^(?://)?(\d+)\.(\d+)\.(\d+)\.(\d+)$#) { # RFC1918に示されるのは以下 # 10.0.0.0〜10.255.255.255 # 172.16.0.0〜172.31.255.255 # 192.168.0.0〜192.168.255.255 ($1==10) || ($1==172 && $2>=16 && $2<32) || ($1==192 && $2==168) || ($1==127 && $2==0 && $3==0); # 127.0.0.1 なども利用されている } else { 0 } } # ドメイン名が指定のものか調べる sub CheckDomain { 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 へのリンク参照を求める sub HrefURL { my $url = shift; &EscapeRef(\$url); $url =~ m#^\w+://#o? qq|$url|: $url; } # URL が存在するか調べステータスを返す (http のみ) # 戻り値は (stat, url, content-type, content-length, msg) の配列 sub AskHTML { my $stat = 200; my ($rurl, $type, $length, $header, $msg); my $TIMEOUT = $cgi->param('TimeOut')+0; if ($TIMEOUT > 0.0) { $TIMEOUT = 60 if $TIMEOUT > 60.0; my $url = &htmllint::AbsoluteURL; if ($LWPUA) { $LWPUA->timeout($TIMEOUT); my $req = new HTTP::Request HEAD => $url; my $res = $LWPUA->request($req); $stat = $res->code(); if ($cgi->param('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'); $msg = $res->message(); $msg =~s /,\s* chunk \d+.+$//; } else { $httpreq::timeout = $TIMEOUT; ($stat, $rurl, $header) = &httpreq::head($url); if ($cgi->param('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, $msg]; } # コード変換して実体参照にエスケープする 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 { my $str = shift; $$str =~ s/&/&/og; $$str =~ s/</og; $$str =~ s/>/>/og; $$str =~ s/"/"/og; $$str =~ s/\n/
|, $ver, "
'); &Jprint(shift(@msgs)); print("
\n"); } &PrintHTTPHeader if $RESULT ne ''; &PrintHTMLFooter(0); &Exit; } sub Exit { # 消されていないテンポラリの始末をする &find(\&CleanupTmp, $TMPDIR? $TMPDIR: '.'); exit; } sub CleanupTmp { if (-d) { $File::Find::prune = 1 if $_ ne '.'; } elsif (/^htmllint-?\d+\.(html|result)$/o && (stat($_))[9] < time-24*60*60) { # 24時間以前のファイルを消す unlink($_); } } # HTML ヘッダ部分を出力する (PrintHeaderという関数は cgi-lib に既存) sub PrintHTMLHeader { my ($title) = @_; my $brclear = $bannerCommercial? '