/[pub]/test/html-webhacc/cc.cgi
Suika

Diff of /test/html-webhacc/cc.cgi

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.3 by wakaba, Wed Jun 27 13:30:15 2007 UTC revision 1.27 by wakaba, Mon Nov 19 12:20:14 2007 UTC
# Line 1  Line 1 
1  #!/usr/bin/perl  #!/usr/bin/perl
2  use strict;  use strict;
3    use utf8;
4    
5  use lib qw[/home/httpd/html/www/markup/html/whatpm  use lib qw[/home/httpd/html/www/markup/html/whatpm
6             /home/wakaba/work/manakai/lib             /home/wakaba/work/manakai2/lib];
            /home/wakaba/public_html/-temp/wiki/lib];  
7  use CGI::Carp qw[fatalsToBrowser];  use CGI::Carp qw[fatalsToBrowser];
8  use Scalar::Util qw[refaddr];  use Scalar::Util qw[refaddr];
9    use Time::HiRes qw/time/;
 use SuikaWiki::Input::HTTP; ## TODO: Use some better CGI module  
10    
11  sub htescape ($) {  sub htescape ($) {
12    my $s = $_[0];    my $s = $_[0];
# Line 15  sub htescape ($) { Line 14  sub htescape ($) {
14    $s =~ s/</&lt;/g;    $s =~ s/</&lt;/g;
15    $s =~ s/>/&gt;/g;    $s =~ s/>/&gt;/g;
16    $s =~ s/"/&quot;/g;    $s =~ s/"/&quot;/g;
17    $s =~ s!([\x00-\x09\x0B-\x1F\x7F-\x80])!sprintf '<var>U+%04X</var>', ord $1!ge;    $s =~ s{([\x00-\x09\x0B-\x1F\x7F-\xA0\x{FEFF}\x{FFFC}-\x{FFFF}])}{
18        sprintf '<var>U+%04X</var>', ord $1;
19      }ge;
20    return $s;    return $s;
21  } # htescape  } # htescape
22    
23  my $http = SuikaWiki::Input::HTTP->new;    use Message::CGI::HTTP;
24      my $http = Message::CGI::HTTP->new;
 ## TODO: _charset_  
   
   my $input_format = $http->parameter ('i') || 'text/html';  
   my $inner_html_element = $http->parameter ('e');  
   my $input_uri = 'thismessage:/';  
25    
26    my $s = $http->parameter ('s');    if ($http->get_meta_variable ('PATH_INFO') ne '/') {
27    if (length $s > 1000_000) {      print STDOUT "Status: 404 Not Found\nContent-Type: text/plain; charset=us-ascii\n\n400";
     print STDOUT "Status: 400 Document Too Long\nContent-Type: text/plain; charset=us-ascii\n\nToo long";  
28      exit;      exit;
29    }    }
30    
31      binmode STDOUT, ':utf8';
32      $| = 1;
33    
34      require Message::DOM::DOMImplementation;
35      my $dom = Message::DOM::DOMImplementation->new;
36    
37      load_text_catalog ('en'); ## TODO: conneg
38    
39    my @nav;    my @nav;
40    print STDOUT qq[Content-Type: text/html; charset=utf-8    print STDOUT qq[Content-Type: text/html; charset=utf-8
41    
# Line 43  my $http = SuikaWiki::Input::HTTP->new; Line 46  my $http = SuikaWiki::Input::HTTP->new;
46  <link rel="stylesheet" href="../cc-style.css" type="text/css">  <link rel="stylesheet" href="../cc-style.css" type="text/css">
47  </head>  </head>
48  <body>  <body>
49  <h1>Web Document Conformance Checker (<em>beta</em>)</h1>  <h1><a href="../cc-interface">Web Document Conformance Checker</a>
50    (<em>beta</em>)</h1>
51    ];
52    
53  <div id="document-info" section="section">    $| = 0;
54      my $input = get_input_document ($http, $dom);
55      my $char_length = 0;
56      my %time;
57    
58      print qq[
59    <div id="document-info" class="section">
60  <dl>  <dl>
61    <dt>Request URI</dt>
62        <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{request_uri}]}">@{[htescape $input->{request_uri}]}</a>&gt;</code></dd>
63  <dt>Document URI</dt>  <dt>Document URI</dt>
64      <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input_uri]}">@{[htescape $input_uri]}</a>&gt;</code></dd>      <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{uri}]}" id=anchor-document-uri>@{[htescape $input->{uri}]}</a>&gt;</code>
65  <dt>Internet Media Type</dt>      <script>
66      <dd><code class="MIME" lang="en">@{[htescape $input_format]}</code></dd>        document.title = '<'
67              + document.getElementById ('anchor-document-uri').href + '> \\u2014 '
68              + document.title;
69        </script></dd>
70  ]; # no </dl> yet  ]; # no </dl> yet
71    push @nav, ['#document-info' => 'Information'];    push @nav, ['#document-info' => 'Information'];
72    
73    require Message::DOM::DOMImplementation;  if (defined $input->{s}) {
74    my $dom = Message::DOM::DOMImplementation->____new;    $char_length = length $input->{s};
   my $doc;  
   my $el;  
   
   if ($input_format eq 'text/html') {  
     require Encode;  
     require Whatpm::HTML;  
       
     $s = Encode::decode ('utf-8', $s);  
75    
76      print STDOUT qq[    print STDOUT qq[
77    <dt>Base URI</dt>
78        <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{base_uri}]}">@{[htescape $input->{base_uri}]}</a>&gt;</code></dd>
79    <dt>Internet Media Type</dt>
80        <dd><code class="MIME" lang="en">@{[htescape $input->{media_type}]}</code>
81        @{[$input->{media_type_overridden} ? '<em>(overridden)</em>' : defined $input->{official_type} ? $input->{media_type} eq $input->{official_type} ? '' : '<em>(sniffed; official type is: <code class=MIME lang=en>'.htescape ($input->{official_type}).'</code>)' : '<em>(sniffed)</em>']}</dd>
82  <dt>Character Encoding</dt>  <dt>Character Encoding</dt>
83      <dd>(none)</dd>      <dd>@{[defined $input->{charset} ? '<code class="charset" lang="en">'.htescape ($input->{charset}).'</code>' : '(none)']}
84        @{[$input->{charset_overridden} ? '<em>(overridden)</em>' : '']}</dd>
85    <dt>Length</dt>
86        <dd>$char_length byte@{[$char_length == 1 ? '' : 's']}</dd>
87  </dl>  </dl>
88  </div>  </div>
89    ];
90    
91  <div id="source-string" class="section">    my $result = {conforming_min => 1, conforming_max => 1};
92  <h2>Document Source</h2>    print_http_header_section ($input, $result);
93    
94      my $doc;
95      my $el;
96      my $manifest;
97    
98      if ($input->{media_type} eq 'text/html') {
99        ($doc, $el) = print_syntax_error_html_section ($input, $result);
100        print_source_string_section
101            (\($input->{s}), $input->{charset} || $doc->input_encoding);
102      } elsif ({
103                'text/xml' => 1,
104                'application/atom+xml' => 1,
105                'application/rss+xml' => 1,
106                'application/svg+xml' => 1,
107                'application/xhtml+xml' => 1,
108                'application/xml' => 1,
109               }->{$input->{media_type}}) {
110        ($doc, $el) = print_syntax_error_xml_section ($input, $result);
111        print_source_string_section (\($input->{s}), $doc->input_encoding);
112      } elsif ($input->{media_type} eq 'text/cache-manifest') {
113    ## TODO: MUST be text/cache-manifest
114        $manifest = print_syntax_error_manifest_section ($input, $result);
115        print_source_string_section (\($input->{s}), 'utf-8');
116      } else {
117        ## TODO: Change HTTP status code??
118        print_result_unknown_type_section ($input, $result);
119      }
120    
121      if (defined $doc or defined $el) {
122        print_structure_dump_dom_section ($doc, $el);
123        my $elements = print_structure_error_dom_section ($doc, $el, $result);
124        print_table_section ($elements->{table}) if @{$elements->{table}};
125        print_id_section ($elements->{id}) if keys %{$elements->{id}};
126        print_term_section ($elements->{term}) if keys %{$elements->{term}};
127        print_class_section ($elements->{class}) if keys %{$elements->{class}};
128      } elsif (defined $manifest) {
129        print_structure_dump_manifest_section ($manifest);
130        print_structure_error_manifest_section ($manifest, $result);
131      }
132    
133      print_result_section ($result);
134    } else {
135      print STDOUT qq[</dl></div>];
136      print_result_input_error_section ($input);
137    }
138    
139      print STDOUT qq[
140    <ul class="navigation" id="nav-items">
141  ];  ];
142      push @nav, ['#source-string' => 'Source'];    for (@nav) {
143      print_source_string (\$s);      print STDOUT qq[<li><a href="$_->[0]">$_->[1]</a></li>];
144      print STDOUT qq[    }
145  </div>    print STDOUT qq[
146    </ul>
147    </body>
148    </html>
149    ];
150    
151      for (qw/decode parse parse_html parse_xml parse_manifest
152              check check_manifest/) {
153        next unless defined $time{$_};
154        open my $file, '>>', ".cc-$_.txt" or die ".cc-$_.txt: $!";
155        print $file $char_length, "\t", $time{$_}, "\n";
156      }
157    
158    exit;
159    
160    sub add_error ($$$) {
161      my ($layer, $err, $result) = @_;
162      if (defined $err->{level}) {
163        if ($err->{level} eq 's') {
164          $result->{$layer}->{should}++;
165          $result->{$layer}->{score_min} -= 2;
166          $result->{conforming_min} = 0;
167        } elsif ($err->{level} eq 'w' or $err->{level} eq 'g') {
168          $result->{$layer}->{warning}++;
169        } elsif ($err->{level} eq 'unsupported') {
170          $result->{$layer}->{unsupported}++;
171          $result->{unsupported} = 1;
172        } else {
173          $result->{$layer}->{must}++;
174          $result->{$layer}->{score_max} -= 2;
175          $result->{$layer}->{score_min} -= 2;
176          $result->{conforming_min} = 0;
177          $result->{conforming_max} = 0;
178        }
179      } else {
180        $result->{$layer}->{must}++;
181        $result->{$layer}->{score_max} -= 2;
182        $result->{$layer}->{score_min} -= 2;
183        $result->{conforming_min} = 0;
184        $result->{conforming_max} = 0;
185      }
186    } # add_error
187    
188    sub print_http_header_section ($$) {
189      my ($input, $result) = @_;
190      return unless defined $input->{header_status_code} or
191          defined $input->{header_status_text} or
192          @{$input->{header_field}};
193      
194      push @nav, ['#source-header' => 'HTTP Header'];
195      print STDOUT qq[<div id="source-header" class="section">
196    <h2>HTTP Header</h2>
197    
198    <p><strong>Note</strong>: Due to the limitation of the
199    network library in use, the content of this section might
200    not be the real header.</p>
201    
202    <table><tbody>
203    ];
204    
205      if (defined $input->{header_status_code}) {
206        print STDOUT qq[<tr><th scope="row">Status code</th>];
207        print STDOUT qq[<td><code>@{[htescape ($input->{header_status_code})]}</code></td></tr>];
208      }
209      if (defined $input->{header_status_text}) {
210        print STDOUT qq[<tr><th scope="row">Status text</th>];
211        print STDOUT qq[<td><code>@{[htescape ($input->{header_status_text})]}</code></td></tr>];
212      }
213      
214      for (@{$input->{header_field}}) {
215        print STDOUT qq[<tr><th scope="row"><code>@{[htescape ($_->[0])]}</code></th>];
216        print STDOUT qq[<td><code>@{[htescape ($_->[1])]}</code></td></tr>];
217      }
218    
219      print STDOUT qq[</tbody></table></div>];
220    } # print_http_header_section
221    
222    sub print_syntax_error_html_section ($$) {
223      my ($input, $result) = @_;
224      
225      require Encode;
226      require Whatpm::HTML;
227      
228      print STDOUT qq[
229  <div id="parse-errors" class="section">  <div id="parse-errors" class="section">
230  <h2>Parse Errors</h2>  <h2>Parse Errors</h2>
231    
232  <ul>  <dl>];
 ];  
233    push @nav, ['#parse-errors' => 'Parse Error'];    push @nav, ['#parse-errors' => 'Parse Error'];
234    
235    my $onerror = sub {    my $onerror = sub {
236      my (%opt) = @_;      my (%opt) = @_;
237        my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
238      if ($opt{column} > 0) {      if ($opt{column} > 0) {
239        print STDOUT qq[<li><a href="#line-$opt{line}">Line $opt{line}</a> column $opt{column}: ];        print STDOUT qq[<dt class="$cls"><a href="#line-$opt{line}">Line $opt{line}</a> column $opt{column}</dt>\n];
240      } else {      } else {
241        $opt{line}--;        $opt{line} = $opt{line} - 1 || 1;
242        print STDOUT qq[<li><a href="#line-$opt{line}">Line $opt{line}</a>: ];        print STDOUT qq[<dt class="$cls"><a href="#line-$opt{line}">Line $opt{line}</a></dt>\n];
243      }      }
244      print STDOUT qq[@{[htescape $opt{type}]}</li>\n];      $type =~ tr/ /-/;
245        $type =~ s/\|/%7C/g;
246        $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
247        print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
248        print STDOUT qq[$msg</dd>\n];
249    
250        add_error ('syntax', \%opt => $result);
251    };    };
252    
253    $doc = $dom->create_document;    my $doc = $dom->create_document;
254      my $el;
255      my $inner_html_element = $http->get_parameter ('e');
256    if (defined $inner_html_element and length $inner_html_element) {    if (defined $inner_html_element and length $inner_html_element) {
257        $input->{charset} ||= 'windows-1252'; ## TODO: for now.
258        my $time1 = time;
259        my $t = Encode::decode ($input->{charset}, $input->{s});
260        $time{decode} = time - $time1;
261        
262      $el = $doc->create_element_ns      $el = $doc->create_element_ns
263          ('http://www.w3.org/1999/xhtml', [undef, $inner_html_element]);          ('http://www.w3.org/1999/xhtml', [undef, $inner_html_element]);
264      Whatpm::HTML->set_inner_html ($el, $s, $onerror);      $time1 = time;
265        Whatpm::HTML->set_inner_html ($el, $t, $onerror);
266        $time{parse} = time - $time1;
267    } else {    } else {
268      Whatpm::HTML->parse_string ($s => $doc, $onerror);      my $time1 = time;
269        Whatpm::HTML->parse_byte_string
270            ($input->{charset}, $input->{s} => $doc, $onerror);
271        $time{parse_html} = time - $time1;
272    }    }
273      $doc->manakai_charset ($input->{official_charset})
274          if defined $input->{official_charset};
275      
276      print STDOUT qq[</dl></div>];
277    
278      return ($doc, $el);
279    } # print_syntax_error_html_section
280    
281    sub print_syntax_error_xml_section ($$) {
282      my ($input, $result) = @_;
283      
284      require Message::DOM::XMLParserTemp;
285      
286    print STDOUT qq[    print STDOUT qq[
 </ul>  
 </div>  
 ];  
   } elsif ($input_format eq 'application/xhtml+xml') {  
     require Message::DOM::XMLParserTemp;  
     require Encode;  
       
     my $t = Encode::decode ('utf-8', $s);  
   
     print STDOUT qq[  
 <dt>Character Encoding</dt>  
     <dd>(none)</dd>  
 </dl>  
 </div>  
   
 <div id="source-string" class="section">  
 <h2>Document Source</h2>  
 ];  
     push @nav, ['#source-string' => 'Source'];  
     print_source_string (\$t);  
     print STDOUT qq[  
 </div>  
   
287  <div id="parse-errors" class="section">  <div id="parse-errors" class="section">
288  <h2>Parse Errors</h2>  <h2>Parse Errors</h2>
289    
290  <ul>  <dl>];
 ];  
291    push @nav, ['#parse-errors' => 'Parse Error'];    push @nav, ['#parse-errors' => 'Parse Error'];
292    
293    my $onerror = sub {    my $onerror = sub {
294      my $err = shift;      my $err = shift;
295      my $line = $err->location->line_number;      my $line = $err->location->line_number;
296      print STDOUT qq[<li><a href="#line-$line">Line $line</a> column ];      print STDOUT qq[<dt><a href="#line-$line">Line $line</a> column ];
297      print STDOUT $err->location->column_number, ": ";      print STDOUT $err->location->column_number, "</dt><dd>";
298      print STDOUT htescape $err->text, "</li>\n";      print STDOUT htescape $err->text, "</dd>\n";
299    
300        add_error ('syntax', {type => $err->text,
301                    level => [
302                              $err->SEVERITY_FATAL_ERROR => 'm',
303                              $err->SEVERITY_ERROR => 'm',
304                              $err->SEVERITY_WARNING => 's',
305                             ]->[$err->severity]} => $result);
306    
307      return 1;      return 1;
308    };    };
309    
310    open my $fh, '<', \$s;    my $time1 = time;
311    $doc = Message::DOM::XMLParserTemp->parse_byte_stream    open my $fh, '<', \($input->{s});
312        ($fh => $dom, $onerror, charset => 'utf-8');    my $doc = Message::DOM::XMLParserTemp->parse_byte_stream
313          ($fh => $dom, $onerror, charset => $input->{charset});
314      print STDOUT qq[    $time{parse_xml} = time - $time1;
315  </ul>    $doc->manakai_charset ($input->{official_charset})
316  </div>        if defined $input->{official_charset};
317  ];  
318    } else {    print STDOUT qq[</dl></div>];
     print STDOUT qq[  
 </dl>  
   
 <div id="result-summary" class="section">  
 <p><em>Media type <code class="MIME" lang="en">@{[htescape $input_format]}</code> is not supported!</em></p>  
 </div>  
 ];  
     push @nav, ['#result-summary' => 'Result'];  
   }  
319    
320      return ($doc, undef);
321    } # print_syntax_error_xml_section
322    
323    if (defined $doc or defined $el) {  sub print_syntax_error_manifest_section ($$) {
324      print STDOUT qq[    my ($input, $result) = @_;
 <div id="document-tree" class="section">  
 <h2>Document Tree</h2>  
 ];  
     push @nav, ['#document-tree' => 'Tree'];  
325    
326      print_document_tree ($el || $doc);    require Whatpm::CacheManifest;
327    
328      print STDOUT qq[    print STDOUT qq[
329  </div>  <div id="parse-errors" class="section">
330    <h2>Parse Errors</h2>
331    
332  <div id="document-errors" class="section">  <dl>];
333  <h2>Document Errors</h2>    push @nav, ['#parse-errors' => 'Parse Error'];
334    
335  <ul>    my $onerror = sub {
336  ];      my (%opt) = @_;
337      push @nav, ['#document-errors' => 'Document Error'];      my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
338        print STDOUT qq[<dt class="$cls">], get_error_label (\%opt), qq[</dt>];
339        $type =~ tr/ /-/;
340        $type =~ s/\|/%7C/g;
341        $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
342        print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
343        print STDOUT qq[$msg</dd>\n];
344    
345      require Whatpm::ContentChecker;      add_error ('syntax', \%opt => $result);
346      my $onerror = sub {    };
       my %opt = @_;  
       print STDOUT qq[<li><a href="#node-@{[refaddr $opt{node}]}">],  
           htescape get_node_path ($opt{node}),  
           "</a>: ", htescape $opt{type}, "</li>\n";  
     };  
347    
348      if ($el) {    my $time1 = time;
349        Whatpm::ContentChecker->check_element ($el, $onerror);    my $manifest = Whatpm::CacheManifest->parse_byte_string
350      } else {        ($input->{s}, $input->{uri}, $input->{base_uri}, $onerror);
351        Whatpm::ContentChecker->check_document ($doc, $onerror);    $time{parse_manifest} = time - $time1;
352    
353      print STDOUT qq[</dl></div>];
354    
355      return $manifest;
356    } # print_syntax_error_manifest_section
357    
358    sub print_source_string_section ($$) {
359      require Encode;
360      my $enc = Encode::find_encoding ($_[1]); ## TODO: charset name -> Perl name
361      return unless $enc;
362    
363      my $s = \($enc->decode (${$_[0]}));
364      my $i = 1;                            
365      push @nav, ['#source-string' => 'Source'];
366      print STDOUT qq[<div id="source-string" class="section">
367    <h2>Document Source</h2>
368    <ol lang="">\n];
369      if (length $$s) {
370        while ($$s =~ /\G([^\x0A]*?)\x0D?\x0A/gc) {
371          print STDOUT qq[<li id="line-$i">], htescape $1, "</li>\n";
372          $i++;
373      }      }
374        if ($$s =~ /\G([^\x0A]+)/gc) {
375      print STDOUT qq[        print STDOUT qq[<li id="line-$i">], htescape $1, "</li>\n";
376  </ul>      }
377  </div>    } else {
378  ];      print STDOUT q[<li id="line-1"></li>];
   }  
   
   ## TODO: Show result  
   
   print STDOUT qq[  
 <ul class="navigation" id="nav-items">  
 ];  
   for (@nav) {  
     print STDOUT qq[<li><a href="$_->[0]">$_->[1]</a></li>];  
   }  
   print STDOUT qq[  
 </ul>  
 </body>  
 </html>  
 ];  
   
 exit;  
   
 sub print_source_string ($) {  
   my $s = $_[0];  
   my $i = 1;  
   print STDOUT qq[<ol lang="">\n];  
   while ($$s =~ /\G([^\x0A]*?)\x0D?\x0A/gc) {  
     print STDOUT qq[<li id="line-$i">], htescape $1, "</li>\n";  
     $i++;  
   }  
   if ($$s =~ /\G([^\x0A]+)/gc) {  
     print STDOUT qq[<li id="line-$i">], htescape $1, "</li>\n";  
379    }    }
380    print STDOUT "</ol>";    print STDOUT "</ol></div>";
381  } # print_input_string  } # print_input_string_section
382    
383  sub print_document_tree ($) {  sub print_document_tree ($) {
384    my $node = shift;    my $node = shift;
# Line 250  sub print_document_tree ($) { Line 395  sub print_document_tree ($) {
395      my $node_id = 'node-'.refaddr $child;      my $node_id = 'node-'.refaddr $child;
396      my $nt = $child->node_type;      my $nt = $child->node_type;
397      if ($nt == $child->ELEMENT_NODE) {      if ($nt == $child->ELEMENT_NODE) {
398        $r .= qq'<li id="$node_id"><code>' . htescape ($child->tag_name) .        my $child_nsuri = $child->namespace_uri;
399          $r .= qq[<li id="$node_id" class="tree-element"><code title="@{[defined $child_nsuri ? $child_nsuri : '']}">] . htescape ($child->tag_name) .
400            '</code>'; ## ISSUE: case            '</code>'; ## ISSUE: case
401    
402        if ($child->has_attributes) {        if ($child->has_attributes) {
403          $r .= '<ul class="attributes">';          $r .= '<ul class="attributes">';
404          for my $attr (sort {$a->[0] cmp $b->[0]} map { [$_->name, $_->value, 'node-'.refaddr $_] }          for my $attr (sort {$a->[0] cmp $b->[0]} map { [$_->name, $_->value, $_->namespace_uri, 'node-'.refaddr $_] }
405                        @{$child->attributes}) {                        @{$child->attributes}) {
406            $r .= qq'<li id="$attr->[2]"><code>' . htescape ($attr->[0]) . '</code> = '; ## ISSUE: case?            $r .= qq[<li id="$attr->[3]" class="tree-attribute"><code title="@{[defined $attr->[2] ? htescape ($attr->[2]) : '']}">] . htescape ($attr->[0]) . '</code> = '; ## ISSUE: case?
407            $r .= '<q>' . htescape ($attr->[1]) . '</q></li>'; ## TODO: children            $r .= '<q>' . htescape ($attr->[1]) . '</q></li>'; ## TODO: children
408          }          }
409          $r .= '</ul>';          $r .= '</ul>';
410        }        }
411    
412        if ($node->has_child_nodes) {        if ($child->has_child_nodes) {
413          $r .= '<ol class="children">';          $r .= '<ol class="children">';
414          unshift @node, @{$child->child_nodes}, '</ol>';          unshift @node, @{$child->child_nodes}, '</ol></li>';
415          } else {
416            $r .= '</li>';
417        }        }
418      } elsif ($nt == $child->TEXT_NODE) {      } elsif ($nt == $child->TEXT_NODE) {
419        $r .= qq'<li id="$node_id"><q>' . htescape ($child->data) . '</q></li>';        $r .= qq'<li id="$node_id" class="tree-text"><q lang="">' . htescape ($child->data) . '</q></li>';
420      } elsif ($nt == $child->CDATA_SECTION_NODE) {      } elsif ($nt == $child->CDATA_SECTION_NODE) {
421        $r .= qq'<li id="$node_id"><code>&lt;[CDATA[</code><q>' . htescape ($child->data) . '</q><code>]]&gt;</code></li>';        $r .= qq'<li id="$node_id" class="tree-cdata"><code>&lt;[CDATA[</code><q lang="">' . htescape ($child->data) . '</q><code>]]&gt;</code></li>';
422      } elsif ($nt == $child->COMMENT_NODE) {      } elsif ($nt == $child->COMMENT_NODE) {
423        $r .= qq'<li id="$node_id"><code>&lt;!--</code><q>' . htescape ($child->data) . '</q><code>--&gt;</code></li>';        $r .= qq'<li id="$node_id" class="tree-comment"><code>&lt;!--</code><q lang="">' . htescape ($child->data) . '</q><code>--&gt;</code></li>';
424      } elsif ($nt == $child->DOCUMENT_NODE) {      } elsif ($nt == $child->DOCUMENT_NODE) {
425        $r .= qq'<li id="$node_id">Document</li>';        $r .= qq'<li id="$node_id" class="tree-document">Document';
426          $r .= qq[<ul class="attributes">];
427          my $cp = $child->manakai_charset;
428          if (defined $cp) {
429            $r .= qq[<li><code>charset</code> parameter = <code>];
430            $r .= htescape ($cp) . qq[</code></li>];
431          }
432          $r .= qq[<li><code>inputEncoding</code> = ];
433          my $ie = $child->input_encoding;
434          if (defined $ie) {
435            $r .= qq[<code>@{[htescape ($ie)]}</code>];
436            if ($child->manakai_has_bom) {
437              $r .= qq[ (with <code class=charname><abbr>BOM</abbr></code>)];
438            }
439          } else {
440            $r .= qq[(<code>null</code>)];
441          }
442          $r .= qq[<li>@{[scalar get_text ('manakaiIsHTML:'.($child->manakai_is_html?1:0))]}</li>];
443          $r .= qq[<li>@{[scalar get_text ('manakaiCompatMode:'.$child->manakai_compat_mode)]}</li>];
444          unless ($child->manakai_is_html) {
445            $r .= qq[<li>XML version = <code>@{[htescape ($child->xml_version)]}</code></li>];
446            if (defined $child->xml_encoding) {
447              $r .= qq[<li>XML encoding = <code>@{[htescape ($child->xml_encoding)]}</code></li>];
448            } else {
449              $r .= qq[<li>XML encoding = (null)</li>];
450            }
451            $r .= qq[<li>XML standalone = @{[$child->xml_standalone ? 'true' : 'false']}</li>];
452          }
453          $r .= qq[</ul>];
454        if ($child->has_child_nodes) {        if ($child->has_child_nodes) {
455          $r .= '<ol>';          $r .= '<ol class="children">';
456          unshift @node, @{$child->child_nodes}, '</ol>';          unshift @node, @{$child->child_nodes}, '</ol></li>';
457        }        }
458      } elsif ($nt == $child->DOCUMENT_TYPE_NODE) {      } elsif ($nt == $child->DOCUMENT_TYPE_NODE) {
459        $r .= qq'<li id="$node_id"><code>&lt;!DOCTYPE&gt;</code><ul>';        $r .= qq'<li id="$node_id" class="tree-doctype"><code>&lt;!DOCTYPE&gt;</code><ul class="attributes">';
460        $r .= '<li>Name = <q>@{[htescape ($child->name)]}</q></li>';        $r .= qq[<li class="tree-doctype-name">Name = <q>@{[htescape ($child->name)]}</q></li>];
461        $r .= '<li>Public identifier = <q>@{[htescape ($child->public_id)]}</q></li>';        $r .= qq[<li class="tree-doctype-publicid">Public identifier = <q>@{[htescape ($child->public_id)]}</q></li>];
462        $r .= '<li>System identifier = <q>@{[htescape ($child->system_id)]}</q></li>';        $r .= qq[<li class="tree-doctype-systemid">System identifier = <q>@{[htescape ($child->system_id)]}</q></li>];
463        $r .= '</ul></li>';        $r .= '</ul></li>';
464      } elsif ($nt == $child->PROCESSING_INSTRUCTION_NODE) {      } elsif ($nt == $child->PROCESSING_INSTRUCTION_NODE) {
465        $r .= qq'<li id="$node_id"><code>&lt;?@{[htescape ($child->target)]}?&gt;</code>';        $r .= qq'<li id="$node_id" class="tree-id"><code>&lt;?@{[htescape ($child->target)]}</code> <q>@{[htescape ($child->data)]}</q><code>?&gt;</code></li>';
       $r .= '<ul><li>@{[htescape ($child->data)]}</li></ul></li>';  
466      } else {      } else {
467        $r .= qq'<li id="$node_id">@{[$child->node_type]} @{[htescape ($child->node_name)]}</li>'; # error        $r .= qq'<li id="$node_id" class="tree-unknown">@{[$child->node_type]} @{[htescape ($child->node_name)]}</li>'; # error
468      }      }
469    }    }
470    
# Line 297  sub print_document_tree ($) { Line 472  sub print_document_tree ($) {
472    print STDOUT $r;    print STDOUT $r;
473  } # print_document_tree  } # print_document_tree
474    
475    sub print_structure_dump_dom_section ($$) {
476      my ($doc, $el) = @_;
477    
478      print STDOUT qq[
479    <div id="document-tree" class="section">
480    <h2>Document Tree</h2>
481    ];
482      push @nav, ['#document-tree' => 'Tree'];
483    
484      print_document_tree ($el || $doc);
485    
486      print STDOUT qq[</div>];
487    } # print_structure_dump_dom_section
488    
489    sub print_structure_dump_manifest_section ($) {
490      my $manifest = shift;
491    
492      print STDOUT qq[
493    <div id="dump-manifest" class="section">
494    <h2>Cache Manifest</h2>
495    ];
496      push @nav, ['#dump-manifest' => 'Caceh Manifest'];
497    
498      print STDOUT qq[<dl><dt>Explicit entries</dt>];
499      for my $uri (@{$manifest->[0]}) {
500        my $euri = htescape ($uri);
501        print STDOUT qq[<dd><code class=uri>&lt;<a href="$euri">$euri</a>></code></dd>];
502      }
503    
504      print STDOUT qq[<dt>Fallback entries</dt><dd>
505          <table><thead><tr><th scope=row>Oppotunistic Caching Namespace</th>
506          <th scope=row>Fallback Entry</tr><tbody>];
507      for my $uri (sort {$a cmp $b} keys %{$manifest->[1]}) {
508        my $euri = htescape ($uri);
509        my $euri2 = htescape ($manifest->[1]->{$uri});
510        print STDOUT qq[<tr><td><code class=uri>&lt;<a href="$euri">$euri</a>></code></td>
511            <td><code class=uri>&lt;<a href="$euri2">$euri2</a>></code></td>];
512      }
513    
514      print STDOUT qq[</table><dt>Online whitelist</dt>];
515      for my $uri (@{$manifest->[2]}) {
516        my $euri = htescape ($uri);
517        print STDOUT qq[<dd><code class=uri>&lt;<a href="$euri">$euri</a>></code></dd>];
518      }
519    
520      print STDOUT qq[</dl></div>];
521    } # print_structure_dump_manifest_section
522    
523    sub print_structure_error_dom_section ($$$) {
524      my ($doc, $el, $result) = @_;
525    
526      print STDOUT qq[<div id="document-errors" class="section">
527    <h2>Document Errors</h2>
528    
529    <dl>];
530      push @nav, ['#document-errors' => 'Document Error'];
531    
532      require Whatpm::ContentChecker;
533      my $onerror = sub {
534        my %opt = @_;
535        my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level}, $opt{node});
536        $type =~ tr/ /-/;
537        $type =~ s/\|/%7C/g;
538        $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
539        print STDOUT qq[<dt class="$cls">] . get_error_label (\%opt) .
540            qq[</dt>\n<dd class="$cls">], get_error_level_label (\%opt);
541        print STDOUT $msg, "</dd>\n";
542        add_error ('structure', \%opt => $result);
543      };
544    
545      my $elements;
546      my $time1 = time;
547      if ($el) {
548        $elements = Whatpm::ContentChecker->check_element ($el, $onerror);
549      } else {
550        $elements = Whatpm::ContentChecker->check_document ($doc, $onerror);
551      }
552      $time{check} = time - $time1;
553    
554      print STDOUT qq[</dl></div>];
555    
556      return $elements;
557    } # print_structure_error_dom_section
558    
559    sub print_structure_error_manifest_section ($$$) {
560      my ($manifest, $result) = @_;
561    
562      print STDOUT qq[<div id="document-errors" class="section">
563    <h2>Document Errors</h2>
564    
565    <dl>];
566      push @nav, ['#document-errors' => 'Document Error'];
567    
568      require Whatpm::CacheManifest;
569      Whatpm::CacheManifest->check_manifest ($manifest, sub {
570        my %opt = @_;
571        my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level}, $opt{node});
572        $type =~ tr/ /-/;
573        $type =~ s/\|/%7C/g;
574        $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
575        print STDOUT qq[<dt class="$cls">] . get_error_label (\%opt) .
576            qq[</dt>\n<dd class="$cls">], $msg, "</dd>\n";
577        add_error ('structure', \%opt => $result);
578      });
579    
580      print STDOUT qq[</div>];
581    } # print_structure_error_manifest_section
582    
583    sub print_table_section ($) {
584      my $tables = shift;
585      
586      push @nav, ['#tables' => 'Tables'];
587      print STDOUT qq[
588    <div id="tables" class="section">
589    <h2>Tables</h2>
590    
591    <!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
592    <script src="../table-script.js" type="text/javascript"></script>
593    <noscript>
594    <p><em>Structure of tables are visualized here if scripting is enabled.</em></p>
595    </noscript>
596    ];
597      
598      require JSON;
599      
600      my $i = 0;
601      for my $table_el (@$tables) {
602        $i++;
603        print STDOUT qq[<div class="section" id="table-$i"><h3>] .
604            get_node_link ($table_el) . q[</h3>];
605    
606        ## TODO: Make |ContentChecker| return |form_table| result
607        ## so that this script don't have to run the algorithm twice.
608        my $table = Whatpm::HTMLTable->form_table ($table_el);
609        
610        for (@{$table->{column_group}}, @{$table->{column}}, $table->{caption}) {
611          next unless $_;
612          delete $_->{element};
613        }
614        
615        for (@{$table->{row_group}}) {
616          next unless $_;
617          next unless $_->{element};
618          $_->{type} = $_->{element}->manakai_local_name;
619          delete $_->{element};
620        }
621        
622        for (@{$table->{cell}}) {
623          next unless $_;
624          for (@{$_}) {
625            next unless $_;
626            for (@$_) {
627              $_->{id} = refaddr $_->{element} if defined $_->{element};
628              delete $_->{element};
629              $_->{is_header} = $_->{is_header} ? 1 : 0;
630            }
631          }
632        }
633            
634        print STDOUT '</div><script type="text/javascript">tableToCanvas (';
635        print STDOUT JSON::objToJson ($table);
636        print STDOUT qq[, document.getElementById ('table-$i'));</script>];
637      }
638      
639      print STDOUT qq[</div>];
640    } # print_table_section
641    
642    sub print_id_section ($) {
643      my $ids = shift;
644      
645      push @nav, ['#identifiers' => 'IDs'];
646      print STDOUT qq[
647    <div id="identifiers" class="section">
648    <h2>Identifiers</h2>
649    
650    <dl>
651    ];
652      for my $id (sort {$a cmp $b} keys %$ids) {
653        print STDOUT qq[<dt><code>@{[htescape $id]}</code></dt>];
654        for (@{$ids->{$id}}) {
655          print STDOUT qq[<dd>].get_node_link ($_).qq[</dd>];
656        }
657      }
658      print STDOUT qq[</dl></div>];
659    } # print_id_section
660    
661    sub print_term_section ($) {
662      my $terms = shift;
663      
664      push @nav, ['#terms' => 'Terms'];
665      print STDOUT qq[
666    <div id="terms" class="section">
667    <h2>Terms</h2>
668    
669    <dl>
670    ];
671      for my $term (sort {$a cmp $b} keys %$terms) {
672        print STDOUT qq[<dt>@{[htescape $term]}</dt>];
673        for (@{$terms->{$term}}) {
674          print STDOUT qq[<dd>].get_node_link ($_).qq[</dd>];
675        }
676      }
677      print STDOUT qq[</dl></div>];
678    } # print_term_section
679    
680    sub print_class_section ($) {
681      my $classes = shift;
682      
683      push @nav, ['#classes' => 'Classes'];
684      print STDOUT qq[
685    <div id="classes" class="section">
686    <h2>Classes</h2>
687    
688    <dl>
689    ];
690      for my $class (sort {$a cmp $b} keys %$classes) {
691        print STDOUT qq[<dt><code>@{[htescape $class]}</code></dt>];
692        for (@{$classes->{$class}}) {
693          print STDOUT qq[<dd>].get_node_link ($_).qq[</dd>];
694        }
695      }
696      print STDOUT qq[</dl></div>];
697    } # print_class_section
698    
699    sub print_result_section ($) {
700      my $result = shift;
701    
702      print STDOUT qq[
703    <div id="result-summary" class="section">
704    <h2>Result</h2>];
705    
706      if ($result->{unsupported} and $result->{conforming_max}) {  
707        print STDOUT qq[<p class=uncertain id=result-para>The conformance
708            checker cannot decide whether the document is conforming or
709            not, since the document contains one or more unsupported
710            features.  The document might or might not be conforming.</p>];
711      } elsif ($result->{conforming_min}) {
712        print STDOUT qq[<p class=PASS id=result-para>No conformance-error is
713            found in this document.</p>];
714      } elsif ($result->{conforming_max}) {
715        print STDOUT qq[<p class=SEE-RESULT id=result-para>This document
716            is <strong>likely <em>non</em>-conforming</strong>, but in rare case
717            it might be conforming.</p>];
718      } else {
719        print STDOUT qq[<p class=FAIL id=result-para>This document is
720            <strong><em>non</em>-conforming</strong>.</p>];
721      }
722    
723      print STDOUT qq[<table>
724    <colgroup><col><colgroup><col><col><col><colgroup><col>
725    <thead>
726    <tr><th scope=col></th>
727    <th scope=col><a href="../error-description#level-m"><em class=rfc2119>MUST</em>‐level
728    Errors</a></th>
729    <th scope=col><a href="../error-description#level-s"><em class=rfc2119>SHOULD</em>‐level
730    Errors</a></th>
731    <th scope=col><a href="../error-description#level-w">Warnings</a></th>
732    <th scope=col>Score</th></tr></thead><tbody>];
733    
734      my $must_error = 0;
735      my $should_error = 0;
736      my $warning = 0;
737      my $score_min = 0;
738      my $score_max = 0;
739      my $score_base = 20;
740      my $score_unit = $score_base / 100;
741      for (
742        [Transfer => 'transfer', ''],
743        [Character => 'char', ''],
744        [Syntax => 'syntax', '#parse-errors'],
745        [Structure => 'structure', '#document-errors'],
746      ) {
747        $must_error += ($result->{$_->[1]}->{must} += 0);
748        $should_error += ($result->{$_->[1]}->{should} += 0);
749        $warning += ($result->{$_->[1]}->{warning} += 0);
750        $score_min += (($result->{$_->[1]}->{score_min} *= $score_unit) += $score_base);
751        $score_max += (($result->{$_->[1]}->{score_max} *= $score_unit) += $score_base);
752    
753        my $uncertain = $result->{$_->[1]}->{unsupported} ? '?' : '';
754        my $label = $_->[0];
755        if ($result->{$_->[1]}->{must} or
756            $result->{$_->[1]}->{should} or
757            $result->{$_->[1]}->{warning} or
758            $result->{$_->[1]}->{unsupported}) {
759          $label = qq[<a href="$_->[2]">$label</a>];
760        }
761    
762        print STDOUT qq[<tr class="@{[$uncertain ? 'uncertain' : '']}"><th scope=row>$label</th><td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : '']}">$result->{$_->[1]}->{must}$uncertain</td><td class="@{[$result->{$_->[1]}->{should} ? 'SEE-RESULT' : '']}">$result->{$_->[1]}->{should}$uncertain</td><td>$result->{$_->[1]}->{warning}$uncertain</td>];
763        if ($uncertain) {
764          print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : $result->{$_->[1]}->{should} ? 'SEE-RESULT' : '']}">&#x2212;&#x221E;..$result->{$_->[1]}->{score_max}</td>];
765        } elsif ($result->{$_->[1]}->{score_min} != $result->{$_->[1]}->{score_max}) {
766          print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : 'SEE-RESULT']}">$result->{$_->[1]}->{score_min}..$result->{$_->[1]}->{score_max}</td></tr>];
767        } else {
768          print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : '']}">$result->{$_->[1]}->{score_min}</td></tr>];
769        }
770      }
771    
772      $score_max += $score_base;
773    
774      print STDOUT qq[
775    <tr class=uncertain><th scope=row>Semantics</th><td>0?</td><td>0?</td><td>0?</td><td>&#x2212;&#x221E;..$score_base</td></tr>
776    </tbody>
777    <tfoot><tr class=uncertain><th scope=row>Total</th>
778    <td class="@{[$must_error ? 'FAIL' : '']}">$must_error?</td>
779    <td class="@{[$should_error ? 'SEE-RESULT' : '']}">$should_error?</td>
780    <td>$warning?</td>
781    <td class="@{[$must_error ? 'FAIL' : $should_error ? 'SEE-RESULT' : '']}"><strong>&#x2212;&#x221E;..$score_max</strong></td></tr></tfoot>
782    </table>
783    
784    <p><strong>Important</strong>: This conformance checking service
785    is <em>under development</em>.  The result above might be <em>wrong</em>.</p>
786    </div>];
787      push @nav, ['#result-summary' => 'Result'];
788    } # print_result_section
789    
790    sub print_result_unknown_type_section ($$) {
791      my ($input, $result) = @_;
792    
793      my $euri = htescape ($input->{uri});
794      print STDOUT qq[
795    <div id="parse-errors" class="section">
796    <h2>Errors</h2>
797    
798    <dl>
799    <dt class=unsupported><code>&lt;<a href="$euri">$euri</a>&gt;</code></dt>
800        <dd class=unsupported><strong><a href="../error-description#level-u">Not
801            supported</a></strong>:
802        Media type
803        <code class="MIME" lang="en">@{[htescape $input->{media_type}]}</code>
804        is not supported.</dd>
805    </dl>
806    </div>
807    ];
808      push @nav, ['#parse-errors' => 'Errors'];
809      add_error (char => {level => 'unsupported'} => $result);
810      add_error (syntax => {level => 'unsupported'} => $result);
811      add_error (structure => {level => 'unsupported'} => $result);
812    } # print_result_unknown_type_section
813    
814    sub print_result_input_error_section ($) {
815      my $input = shift;
816      print STDOUT qq[<div class="section" id="result-summary">
817    <p><em><strong>Input Error</strong>: @{[htescape ($input->{error_status_text})]}</em></p>
818    </div>];
819      push @nav, ['#result-summary' => 'Result'];
820    } # print_Result_input_error_section
821    
822    sub get_error_label ($) {
823      my $err = shift;
824    
825      my $r = '';
826    
827      if (defined $err->{line}) {
828        if ($err->{column} > 0) {
829          $r = qq[<a href="#line-$err->{line}">Line $err->{line}</a> column $err->{column}];
830        } else {
831          $err->{line} = $err->{line} - 1 || 1;
832          $r = qq[<a href="#line-$err->{line}">Line $err->{line}</a>];
833        }
834      }
835    
836      if (defined $err->{node}) {
837        $r .= ' ' if length $r;
838        $r = get_node_link ($err->{node});
839      }
840    
841      if (defined $err->{index}) {
842        $r .= ' ' if length $r;
843        $r .= 'Index ' . (0+$err->{index});
844      }
845    
846      if (defined $err->{value}) {
847        $r .= ' ' if length $r;
848        $r .= '<q><code>' . htescape ($err->{value}) . '</code></q>';
849      }
850    
851      return $r;
852    } # get_error_label
853    
854    sub get_error_level_label ($) {
855      my $err = shift;
856    
857      my $r = '';
858    
859      if (not defined $err->{level} or $err->{level} eq 'm') {
860        $r = qq[<strong><a href="../error-description#level-m"><em class=rfc2119>MUST</em>‐level
861            error</a></strong>: ];
862      } elsif ($err->{level} eq 's') {
863        $r = qq[<strong><a href="../error-description#level-s"><em class=rfc2119>SHOULD</em>‐level
864            error</a></strong>: ];
865      } elsif ($err->{level} eq 'w') {
866        $r = qq[<strong><a href="../error-description#level-w">Warning</a></strong>:
867            ];
868      } elsif ($err->{level} eq 'unsupported') {
869        $r = qq[<strong><a href="../error-description#level-u">Not
870            supported</a></strong>: ];
871      } else {
872        my $elevel = htescape ($err->{level});
873        $r = qq[<strong><a href="../error-description#level-$elevel">$elevel</a></strong>:
874            ];
875      }
876    
877      return $r;
878    } # get_error_level_label
879    
880  sub get_node_path ($) {  sub get_node_path ($) {
881    my $node = shift;    my $node = shift;
882    my @r;    my @r;
# Line 312  sub get_node_path ($) { Line 892  sub get_node_path ($) {
892        $rs = '"' . $node->data . '"';        $rs = '"' . $node->data . '"';
893        $node = $node->parent_node;        $node = $node->parent_node;
894      } elsif ($node->node_type == 9) {      } elsif ($node->node_type == 9) {
895          @r = ('') unless @r;
896        $rs = '';        $rs = '';
897        $node = $node->parent_node;        $node = $node->parent_node;
898      } else {      } else {
# Line 323  sub get_node_path ($) { Line 904  sub get_node_path ($) {
904    return join '/', @r;    return join '/', @r;
905  } # get_node_path  } # get_node_path
906    
907    sub get_node_link ($) {
908      return qq[<a href="#node-@{[refaddr $_[0]]}">] .
909          htescape (get_node_path ($_[0])) . qq[</a>];
910    } # get_node_link
911    
912    {
913      my $Msg = {};
914    
915    sub load_text_catalog ($) {
916      my $lang = shift; # MUST be a canonical lang name
917      open my $file, '<:utf8', "cc-msg.$lang.txt"
918          or die "$0: cc-msg.$lang.txt: $!";
919      while (<$file>) {
920        if (s/^([^;]+);([^;]*);//) {
921          my ($type, $cls, $msg) = ($1, $2, $_);
922          $msg =~ tr/\x0D\x0A//d;
923          $Msg->{$type} = [$cls, $msg];
924        }
925      }
926    } # load_text_catalog
927    
928    sub get_text ($) {
929      my ($type, $level, $node) = @_;
930      $type = $level . ':' . $type if defined $level;
931      my @arg;
932      {
933        if (defined $Msg->{$type}) {
934          my $msg = $Msg->{$type}->[1];
935          $msg =~ s{<var>\$([0-9]+)</var>}{
936            defined $arg[$1] ? htescape ($arg[$1]) : '(undef)';
937          }ge;
938          $msg =~ s{<var>{\@([A-Za-z0-9:_.-]+)}</var>}{
939            UNIVERSAL::can ($node, 'get_attribute_ns')
940                ? htescape ($node->get_attribute_ns (undef, $1)) : ''
941          }ge;
942          $msg =~ s{<var>{\@}</var>}{
943            UNIVERSAL::can ($node, 'value') ? htescape ($node->value) : ''
944          }ge;
945          $msg =~ s{<var>{local-name}</var>}{
946            UNIVERSAL::can ($node, 'manakai_local_name')
947              ? htescape ($node->manakai_local_name) : ''
948          }ge;
949          $msg =~ s{<var>{element-local-name}</var>}{
950            (UNIVERSAL::can ($node, 'owner_element') and
951             $node->owner_element)
952              ? htescape ($node->owner_element->manakai_local_name)
953              : ''
954          }ge;
955          return ($type, $Msg->{$type}->[0], $msg);
956        } elsif ($type =~ s/:([^:]*)$//) {
957          unshift @arg, $1;
958          redo;
959        }
960      }
961      return ($type, '', htescape ($_[0]));
962    } # get_text
963    
964    }
965    
966    sub get_input_document ($$) {
967      my ($http, $dom) = @_;
968    
969      my $request_uri = $http->get_parameter ('uri');
970      my $r = {};
971      if (defined $request_uri and length $request_uri) {
972        my $uri = $dom->create_uri_reference ($request_uri);
973        unless ({
974                 http => 1,
975                }->{lc $uri->uri_scheme}) {
976          return {uri => $request_uri, request_uri => $request_uri,
977                  error_status_text => 'URI scheme not allowed'};
978        }
979    
980        require Message::Util::HostPermit;
981        my $host_permit = new Message::Util::HostPermit;
982        $host_permit->add_rule (<<EOH);
983    Allow host=suika port=80
984    Deny host=suika
985    Allow host=suika.fam.cx port=80
986    Deny host=suika.fam.cx
987    Deny host=localhost
988    Deny host=*.localdomain
989    Deny ipv4=0.0.0.0/8
990    Deny ipv4=10.0.0.0/8
991    Deny ipv4=127.0.0.0/8
992    Deny ipv4=169.254.0.0/16
993    Deny ipv4=172.0.0.0/11
994    Deny ipv4=192.0.2.0/24
995    Deny ipv4=192.88.99.0/24
996    Deny ipv4=192.168.0.0/16
997    Deny ipv4=198.18.0.0/15
998    Deny ipv4=224.0.0.0/4
999    Deny ipv4=255.255.255.255/32
1000    Deny ipv6=0::0/0
1001    Allow host=*
1002    EOH
1003        unless ($host_permit->check ($uri->uri_host, $uri->uri_port || 80)) {
1004          return {uri => $request_uri, request_uri => $request_uri,
1005                  error_status_text => 'Connection to the host is forbidden'};
1006        }
1007    
1008        require LWP::UserAgent;
1009        my $ua = WDCC::LWPUA->new;
1010        $ua->{wdcc_dom} = $dom;
1011        $ua->{wdcc_host_permit} = $host_permit;
1012        $ua->agent ('Mozilla'); ## TODO: for now.
1013        $ua->parse_head (0);
1014        $ua->protocols_allowed ([qw/http/]);
1015        $ua->max_size (1000_000);
1016        my $req = HTTP::Request->new (GET => $request_uri);
1017        my $res = $ua->request ($req);
1018        ## TODO: 401 sets |is_success| true.
1019        if ($res->is_success or $http->get_parameter ('error-page')) {
1020          $r->{base_uri} = $res->base; ## NOTE: It does check |Content-Base|, |Content-Location|, and <base>. ## TODO: Use our own code!
1021          $r->{uri} = $res->request->uri;
1022          $r->{request_uri} = $request_uri;
1023    
1024          ## TODO: More strict parsing...
1025          my $ct = $res->header ('Content-Type');
1026          if (defined $ct and $ct =~ /;\s*charset\s*=\s*"?([^\s;"]+)"?/i) {
1027            $r->{charset} = lc $1;
1028            $r->{charset} =~ tr/\\//d;
1029            $r->{official_charset} = $r->{charset};
1030          }
1031    
1032          my $input_charset = $http->get_parameter ('charset');
1033          if (defined $input_charset and length $input_charset) {
1034            $r->{charset_overridden}
1035                = (not defined $r->{charset} or $r->{charset} ne $input_charset);
1036            $r->{charset} = $input_charset;
1037          }
1038    
1039          ## TODO: Support for HTTP Content-Encoding
1040    
1041          $r->{s} = ''.$res->content;
1042    
1043          require Whatpm::ContentType;
1044          ($r->{official_type}, $r->{media_type})
1045              = Whatpm::ContentType->get_sniffed_type
1046                  (get_file_head => sub {
1047                     return substr $r->{s}, 0, shift;
1048                   },
1049                   http_content_type_byte => $ct,
1050                   has_http_content_encoding =>
1051                       defined $res->header ('Content-Encoding'),
1052                   supported_image_types => {});
1053        } else {
1054          $r->{uri} = $res->request->uri;
1055          $r->{request_uri} = $request_uri;
1056          $r->{error_status_text} = $res->status_line;
1057        }
1058    
1059        $r->{header_field} = [];
1060        $res->scan (sub {
1061          push @{$r->{header_field}}, [$_[0], $_[1]];
1062        });
1063        $r->{header_status_code} = $res->code;
1064        $r->{header_status_text} = $res->message;
1065      } else {
1066        $r->{s} = ''.$http->get_parameter ('s');
1067        $r->{uri} = q<thismessage:/>;
1068        $r->{request_uri} = q<thismessage:/>;
1069        $r->{base_uri} = q<thismessage:/>;
1070        $r->{charset} = ''.$http->get_parameter ('_charset_');
1071        $r->{charset} =~ s/\s+//g;
1072        $r->{charset} = 'utf-8' if $r->{charset} eq '';
1073        $r->{official_charset} = $r->{charset};
1074        $r->{header_field} = [];
1075    
1076        require Whatpm::ContentType;
1077        ($r->{official_type}, $r->{media_type})
1078            = Whatpm::ContentType->get_sniffed_type
1079                (get_file_head => sub {
1080                   return substr $r->{s}, 0, shift;
1081                 },
1082                 http_content_type_byte => undef,
1083                 has_http_content_encoding => 0,
1084                 supported_image_types => {});
1085      }
1086    
1087      my $input_format = $http->get_parameter ('i');
1088      if (defined $input_format and length $input_format) {
1089        $r->{media_type_overridden}
1090            = (not defined $r->{media_type} or $input_format ne $r->{media_type});
1091        $r->{media_type} = $input_format;
1092      }
1093      if (defined $r->{s} and not defined $r->{media_type}) {
1094        $r->{media_type} = 'text/html';
1095        $r->{media_type_overridden} = 1;
1096      }
1097    
1098      if ($r->{media_type} eq 'text/xml') {
1099        unless (defined $r->{charset}) {
1100          $r->{charset} = 'us-ascii';
1101          $r->{official_charset} = $r->{charset};
1102        } elsif ($r->{charset_overridden} and $r->{charset} eq 'us-ascii') {
1103          $r->{charset_overridden} = 0;
1104        }
1105      }
1106    
1107      if (length $r->{s} > 1000_000) {
1108        $r->{error_status_text} = 'Entity-body too large';
1109        delete $r->{s};
1110        return $r;
1111      }
1112    
1113      return $r;
1114    } # get_input_document
1115    
1116    package WDCC::LWPUA;
1117    BEGIN { push our @ISA, 'LWP::UserAgent'; }
1118    
1119    sub redirect_ok {
1120      my $ua = shift;
1121      unless ($ua->SUPER::redirect_ok (@_)) {
1122        return 0;
1123      }
1124    
1125      my $uris = $_[1]->header ('Location');
1126      return 0 unless $uris;
1127      my $uri = $ua->{wdcc_dom}->create_uri_reference ($uris);
1128      unless ({
1129               http => 1,
1130              }->{lc $uri->uri_scheme}) {
1131        return 0;
1132      }
1133      unless ($ua->{wdcc_host_permit}->check ($uri->uri_host, $uri->uri_port || 80)) {
1134        return 0;
1135      }
1136      return 1;
1137    } # redirect_ok
1138    
1139  =head1 AUTHOR  =head1 AUTHOR
1140    
1141  Wakaba <w@suika.fam.cx>.  Wakaba <w@suika.fam.cx>.

Legend:
Removed from v.1.3  
changed lines
  Added in v.1.27

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24