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

Legend:
Removed from v.1.2  
changed lines
  Added in v.1.31

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24