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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.42 - (hide annotations) (download)
Mon Mar 17 13:25:19 2008 UTC (16 years, 8 months ago) by wakaba
Branch: MAIN
Changes since 1.41: +38 -15 lines
++ ChangeLog	17 Mar 2008 13:25:16 -0000
2008-03-17  Wakaba  <wakaba@suika.fam.cx>

	* cc-script.js: The ID of the list is now given as an argument.

	* cc.cgi: List of document errors now also expanded by source
	code fragment generated by scripting.
	(get_error_label): Use line/column information from the error
	context node, if any.

1 wakaba 1.1 #!/usr/bin/perl
2     use strict;
3 wakaba 1.23 use utf8;
4 wakaba 1.1
5     use lib qw[/home/httpd/html/www/markup/html/whatpm
6 wakaba 1.16 /home/wakaba/work/manakai2/lib];
7 wakaba 1.1 use CGI::Carp qw[fatalsToBrowser];
8 wakaba 1.2 use Scalar::Util qw[refaddr];
9 wakaba 1.16 use Time::HiRes qw/time/;
10 wakaba 1.1
11 wakaba 1.2 sub htescape ($) {
12     my $s = $_[0];
13     $s =~ s/&/&amp;/g;
14     $s =~ s/</&lt;/g;
15     $s =~ s/>/&gt;/g;
16     $s =~ s/"/&quot;/g;
17 wakaba 1.12 $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 wakaba 1.2 return $s;
21     } # htescape
22    
23 wakaba 1.35 my @nav;
24     my %time;
25     require Message::DOM::DOMImplementation;
26     my $dom = Message::DOM::DOMImplementation->new;
27     {
28 wakaba 1.16 use Message::CGI::HTTP;
29     my $http = Message::CGI::HTTP->new;
30 wakaba 1.1
31 wakaba 1.16 if ($http->get_meta_variable ('PATH_INFO') ne '/') {
32 wakaba 1.8 print STDOUT "Status: 404 Not Found\nContent-Type: text/plain; charset=us-ascii\n\n400";
33     exit;
34     }
35    
36 wakaba 1.12 binmode STDOUT, ':utf8';
37 wakaba 1.14 $| = 1;
38 wakaba 1.12
39 wakaba 1.7 load_text_catalog ('en'); ## TODO: conneg
40    
41 wakaba 1.2 print STDOUT qq[Content-Type: text/html; charset=utf-8
42    
43     <!DOCTYPE html>
44     <html lang="en">
45     <head>
46     <title>Web Document Conformance Checker (BETA)</title>
47 wakaba 1.3 <link rel="stylesheet" href="../cc-style.css" type="text/css">
48 wakaba 1.2 </head>
49     <body>
50 wakaba 1.13 <h1><a href="../cc-interface">Web Document Conformance Checker</a>
51     (<em>beta</em>)</h1>
52 wakaba 1.14 ];
53 wakaba 1.2
54 wakaba 1.14 $| = 0;
55     my $input = get_input_document ($http, $dom);
56 wakaba 1.16 my $char_length = 0;
57 wakaba 1.14
58     print qq[
59 wakaba 1.4 <div id="document-info" class="section">
60 wakaba 1.2 <dl>
61 wakaba 1.9 <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 wakaba 1.2 <dt>Document URI</dt>
64 wakaba 1.25 <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{uri}]}" id=anchor-document-uri>@{[htescape $input->{uri}]}</a>&gt;</code>
65     <script>
66     document.title = '<'
67     + document.getElementById ('anchor-document-uri').href + '> \\u2014 '
68     + document.title;
69     </script></dd>
70 wakaba 1.2 ]; # no </dl> yet
71 wakaba 1.3 push @nav, ['#document-info' => 'Information'];
72 wakaba 1.1
73 wakaba 1.9 if (defined $input->{s}) {
74 wakaba 1.16 $char_length = length $input->{s};
75 wakaba 1.9
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 wakaba 1.25 @{[$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 wakaba 1.9 <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 wakaba 1.16 <dt>Length</dt>
86     <dd>$char_length byte@{[$char_length == 1 ? '' : 's']}</dd>
87 wakaba 1.9 </dl>
88     </div>
89 wakaba 1.39
90     <script src="../cc-script.js"></script>
91 wakaba 1.9 ];
92    
93 wakaba 1.35 $input->{id_prefix} = '';
94     #$input->{nested} = 0;
95 wakaba 1.20 my $result = {conforming_min => 1, conforming_max => 1};
96 wakaba 1.31 check_and_print ($input => $result);
97 wakaba 1.19 print_result_section ($result);
98 wakaba 1.9 } else {
99 wakaba 1.18 print STDOUT qq[</dl></div>];
100     print_result_input_error_section ($input);
101 wakaba 1.9 }
102 wakaba 1.3
103 wakaba 1.2 print STDOUT qq[
104 wakaba 1.3 <ul class="navigation" id="nav-items">
105     ];
106     for (@nav) {
107     print STDOUT qq[<li><a href="$_->[0]">$_->[1]</a></li>];
108     }
109     print STDOUT qq[
110     </ul>
111 wakaba 1.2 </body>
112     </html>
113     ];
114 wakaba 1.1
115 wakaba 1.24 for (qw/decode parse parse_html parse_xml parse_manifest
116     check check_manifest/) {
117 wakaba 1.16 next unless defined $time{$_};
118     open my $file, '>>', ".cc-$_.txt" or die ".cc-$_.txt: $!";
119     print $file $char_length, "\t", $time{$_}, "\n";
120     }
121    
122 wakaba 1.1 exit;
123 wakaba 1.35 }
124 wakaba 1.1
125 wakaba 1.19 sub add_error ($$$) {
126     my ($layer, $err, $result) = @_;
127     if (defined $err->{level}) {
128     if ($err->{level} eq 's') {
129     $result->{$layer}->{should}++;
130     $result->{$layer}->{score_min} -= 2;
131     $result->{conforming_min} = 0;
132     } elsif ($err->{level} eq 'w' or $err->{level} eq 'g') {
133     $result->{$layer}->{warning}++;
134 wakaba 1.30 } elsif ($err->{level} eq 'u' or $err->{level} eq 'unsupported') {
135 wakaba 1.19 $result->{$layer}->{unsupported}++;
136     $result->{unsupported} = 1;
137 wakaba 1.37 } elsif ($err->{level} eq 'i') {
138     #
139 wakaba 1.19 } 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     } else {
147     $result->{$layer}->{must}++;
148     $result->{$layer}->{score_max} -= 2;
149     $result->{$layer}->{score_min} -= 2;
150     $result->{conforming_min} = 0;
151     $result->{conforming_max} = 0;
152     }
153     } # add_error
154    
155 wakaba 1.31 sub check_and_print ($$) {
156     my ($input, $result) = @_;
157    
158     print_http_header_section ($input, $result);
159    
160     my $doc;
161     my $el;
162 wakaba 1.35 my $cssom;
163 wakaba 1.31 my $manifest;
164 wakaba 1.34 my @subdoc;
165 wakaba 1.31
166     if ($input->{media_type} eq 'text/html') {
167     ($doc, $el) = print_syntax_error_html_section ($input, $result);
168     print_source_string_section
169 wakaba 1.35 ($input,
170     \($input->{s}),
171     $input->{charset} || $doc->input_encoding);
172 wakaba 1.31 } elsif ({
173     'text/xml' => 1,
174     'application/atom+xml' => 1,
175     'application/rss+xml' => 1,
176     'application/svg+xml' => 1,
177     'application/xhtml+xml' => 1,
178     'application/xml' => 1,
179     }->{$input->{media_type}}) {
180     ($doc, $el) = print_syntax_error_xml_section ($input, $result);
181 wakaba 1.35 print_source_string_section ($input,
182     \($input->{s}),
183     $doc->input_encoding);
184     } elsif ($input->{media_type} eq 'text/css') {
185     $cssom = print_syntax_error_css_section ($input, $result);
186     print_source_string_section
187     ($input, \($input->{s}),
188     $cssom->manakai_input_encoding);
189 wakaba 1.31 } elsif ($input->{media_type} eq 'text/cache-manifest') {
190     ## TODO: MUST be text/cache-manifest
191     $manifest = print_syntax_error_manifest_section ($input, $result);
192 wakaba 1.35 print_source_string_section ($input, \($input->{s}),
193     'utf-8');
194 wakaba 1.31 } else {
195     ## TODO: Change HTTP status code??
196     print_result_unknown_type_section ($input, $result);
197     }
198    
199     if (defined $doc or defined $el) {
200 wakaba 1.34 $doc->document_uri ($input->{uri});
201     $doc->manakai_entity_base_uri ($input->{base_uri});
202 wakaba 1.32 print_structure_dump_dom_section ($input, $doc, $el);
203     my $elements = print_structure_error_dom_section
204 wakaba 1.34 ($input, $doc, $el, $result, sub {
205     push @subdoc, shift;
206     });
207 wakaba 1.32 print_table_section ($input, $elements->{table}) if @{$elements->{table}};
208 wakaba 1.33 print_listing_section ({
209     id => 'identifiers', label => 'IDs', heading => 'Identifiers',
210     }, $input, $elements->{id}) if keys %{$elements->{id}};
211     print_listing_section ({
212     id => 'terms', label => 'Terms', heading => 'Terms',
213     }, $input, $elements->{term}) if keys %{$elements->{term}};
214     print_listing_section ({
215     id => 'classes', label => 'Classes', heading => 'Classes',
216     }, $input, $elements->{class}) if keys %{$elements->{class}};
217 wakaba 1.35 } elsif (defined $cssom) {
218     print_structure_dump_cssom_section ($input, $cssom);
219     ## TODO: CSSOM validation
220 wakaba 1.36 add_error ('structure', {level => 'u'} => $result);
221 wakaba 1.31 } elsif (defined $manifest) {
222 wakaba 1.32 print_structure_dump_manifest_section ($input, $manifest);
223     print_structure_error_manifest_section ($input, $manifest, $result);
224 wakaba 1.31 }
225 wakaba 1.34
226     my $id_prefix = 0;
227     for my $subinput (@subdoc) {
228     $subinput->{id_prefix} = 'subdoc-' . ++$id_prefix;
229     $subinput->{nested} = 1;
230     $subinput->{base_uri} = $subinput->{container_node}->base_uri
231     unless defined $subinput->{base_uri};
232     my $ebaseuri = htescape ($subinput->{base_uri});
233     push @nav, ['#' . $subinput->{id_prefix} => 'Sub #' . $id_prefix];
234     print STDOUT qq[<div id="$subinput->{id_prefix}" class=section>
235     <h2>Subdocument #$id_prefix</h2>
236    
237     <dl>
238     <dt>Internet Media Type</dt>
239     <dd><code class="MIME" lang="en">@{[htescape $subinput->{media_type}]}</code>
240     <dt>Container Node</dt>
241     <dd>@{[get_node_link ($input, $subinput->{container_node})]}</dd>
242     <dt>Base <abbr title="Uniform Resource Identifiers">URI</abbr></dt>
243     <dd><code class=URI>&lt;<a href="$ebaseuri">$ebaseuri</a>></code></dd>
244     </dl>];
245    
246 wakaba 1.35 $subinput->{id_prefix} .= '-';
247 wakaba 1.34 check_and_print ($subinput => $result);
248    
249     print STDOUT qq[</div>];
250     }
251 wakaba 1.31 } # check_and_print
252    
253 wakaba 1.19 sub print_http_header_section ($$) {
254     my ($input, $result) = @_;
255 wakaba 1.9 return unless defined $input->{header_status_code} or
256     defined $input->{header_status_text} or
257 wakaba 1.34 @{$input->{header_field} or []};
258 wakaba 1.9
259 wakaba 1.32 push @nav, ['#source-header' => 'HTTP Header'] unless $input->{nested};
260     print STDOUT qq[<div id="$input->{id_prefix}source-header" class="section">
261 wakaba 1.9 <h2>HTTP Header</h2>
262    
263     <p><strong>Note</strong>: Due to the limitation of the
264     network library in use, the content of this section might
265     not be the real header.</p>
266    
267     <table><tbody>
268     ];
269    
270     if (defined $input->{header_status_code}) {
271     print STDOUT qq[<tr><th scope="row">Status code</th>];
272     print STDOUT qq[<td><code>@{[htescape ($input->{header_status_code})]}</code></td></tr>];
273     }
274     if (defined $input->{header_status_text}) {
275     print STDOUT qq[<tr><th scope="row">Status text</th>];
276     print STDOUT qq[<td><code>@{[htescape ($input->{header_status_text})]}</code></td></tr>];
277     }
278    
279     for (@{$input->{header_field}}) {
280     print STDOUT qq[<tr><th scope="row"><code>@{[htescape ($_->[0])]}</code></th>];
281     print STDOUT qq[<td><code>@{[htescape ($_->[1])]}</code></td></tr>];
282     }
283    
284     print STDOUT qq[</tbody></table></div>];
285     } # print_http_header_section
286    
287 wakaba 1.19 sub print_syntax_error_html_section ($$) {
288     my ($input, $result) = @_;
289 wakaba 1.18
290     require Encode;
291     require Whatpm::HTML;
292    
293     print STDOUT qq[
294 wakaba 1.32 <div id="$input->{id_prefix}parse-errors" class="section">
295 wakaba 1.18 <h2>Parse Errors</h2>
296    
297 wakaba 1.39 <dl id="$input->{id_prefix}parse-errors-list">];
298 wakaba 1.32 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
299 wakaba 1.18
300     my $onerror = sub {
301     my (%opt) = @_;
302     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
303 wakaba 1.38 print STDOUT qq[<dt class="$cls">], get_error_label ($input, \%opt),
304     qq[</dt>];
305 wakaba 1.18 $type =~ tr/ /-/;
306     $type =~ s/\|/%7C/g;
307     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
308 wakaba 1.23 print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
309     print STDOUT qq[$msg</dd>\n];
310 wakaba 1.19
311     add_error ('syntax', \%opt => $result);
312 wakaba 1.18 };
313    
314     my $doc = $dom->create_document;
315     my $el;
316 wakaba 1.35 my $inner_html_element = $input->{inner_html_element};
317 wakaba 1.18 if (defined $inner_html_element and length $inner_html_element) {
318 wakaba 1.26 $input->{charset} ||= 'windows-1252'; ## TODO: for now.
319 wakaba 1.24 my $time1 = time;
320     my $t = Encode::decode ($input->{charset}, $input->{s});
321     $time{decode} = time - $time1;
322    
323 wakaba 1.18 $el = $doc->create_element_ns
324     ('http://www.w3.org/1999/xhtml', [undef, $inner_html_element]);
325 wakaba 1.24 $time1 = time;
326 wakaba 1.18 Whatpm::HTML->set_inner_html ($el, $t, $onerror);
327 wakaba 1.24 $time{parse} = time - $time1;
328 wakaba 1.18 } else {
329 wakaba 1.24 my $time1 = time;
330     Whatpm::HTML->parse_byte_string
331     ($input->{charset}, $input->{s} => $doc, $onerror);
332     $time{parse_html} = time - $time1;
333 wakaba 1.18 }
334 wakaba 1.26 $doc->manakai_charset ($input->{official_charset})
335     if defined $input->{official_charset};
336 wakaba 1.24
337 wakaba 1.18 print STDOUT qq[</dl></div>];
338    
339     return ($doc, $el);
340     } # print_syntax_error_html_section
341    
342 wakaba 1.19 sub print_syntax_error_xml_section ($$) {
343     my ($input, $result) = @_;
344 wakaba 1.18
345     require Message::DOM::XMLParserTemp;
346    
347     print STDOUT qq[
348 wakaba 1.32 <div id="$input->{id_prefix}parse-errors" class="section">
349 wakaba 1.18 <h2>Parse Errors</h2>
350    
351 wakaba 1.39 <dl id="$input->{id_prefix}parse-errors-list">];
352 wakaba 1.32 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{prefix};
353 wakaba 1.18
354     my $onerror = sub {
355     my $err = shift;
356     my $line = $err->location->line_number;
357 wakaba 1.35 print STDOUT qq[<dt><a href="#$input->{id_prefix}line-$line">Line $line</a> column ];
358 wakaba 1.18 print STDOUT $err->location->column_number, "</dt><dd>";
359     print STDOUT htescape $err->text, "</dd>\n";
360 wakaba 1.19
361     add_error ('syntax', {type => $err->text,
362     level => [
363     $err->SEVERITY_FATAL_ERROR => 'm',
364     $err->SEVERITY_ERROR => 'm',
365     $err->SEVERITY_WARNING => 's',
366     ]->[$err->severity]} => $result);
367    
368 wakaba 1.18 return 1;
369     };
370    
371     my $time1 = time;
372     open my $fh, '<', \($input->{s});
373     my $doc = Message::DOM::XMLParserTemp->parse_byte_stream
374     ($fh => $dom, $onerror, charset => $input->{charset});
375     $time{parse_xml} = time - $time1;
376 wakaba 1.26 $doc->manakai_charset ($input->{official_charset})
377     if defined $input->{official_charset};
378 wakaba 1.18
379     print STDOUT qq[</dl></div>];
380    
381     return ($doc, undef);
382     } # print_syntax_error_xml_section
383    
384 wakaba 1.35 sub get_css_parser () {
385     our $CSSParser;
386     return $CSSParser if $CSSParser;
387    
388     require Whatpm::CSS::Parser;
389     my $p = Whatpm::CSS::Parser->new;
390    
391     $p->{prop}->{$_} = 1 for qw/
392 wakaba 1.37 alignment-baseline
393 wakaba 1.35 background background-attachment background-color background-image
394     background-position background-position-x background-position-y
395     background-repeat border border-bottom border-bottom-color
396     border-bottom-style border-bottom-width border-collapse border-color
397     border-left border-left-color
398     border-left-style border-left-width border-right border-right-color
399     border-right-style border-right-width
400     border-spacing -manakai-border-spacing-x -manakai-border-spacing-y
401     border-style border-top border-top-color border-top-style border-top-width
402     border-width bottom
403     caption-side clear clip color content counter-increment counter-reset
404 wakaba 1.37 cursor direction display dominant-baseline empty-cells float font
405 wakaba 1.35 font-family font-size font-size-adjust font-stretch
406     font-style font-variant font-weight height left
407     letter-spacing line-height
408     list-style list-style-image list-style-position list-style-type
409     margin margin-bottom margin-left margin-right margin-top marker-offset
410     marks max-height max-width min-height min-width opacity -moz-opacity
411     orphans outline outline-color outline-style outline-width overflow
412     overflow-x overflow-y
413     padding padding-bottom padding-left padding-right padding-top
414     page page-break-after page-break-before page-break-inside
415     position quotes right size table-layout
416 wakaba 1.37 text-align text-anchor text-decoration text-indent text-transform
417 wakaba 1.35 top unicode-bidi vertical-align visibility white-space width widows
418 wakaba 1.37 word-spacing writing-mode z-index
419 wakaba 1.35 /;
420     $p->{prop_value}->{display}->{$_} = 1 for qw/
421     block clip inline inline-block inline-table list-item none
422     table table-caption table-cell table-column table-column-group
423     table-header-group table-footer-group table-row table-row-group
424     compact marker
425     /;
426     $p->{prop_value}->{position}->{$_} = 1 for qw/
427     absolute fixed relative static
428     /;
429     $p->{prop_value}->{float}->{$_} = 1 for qw/
430     left right none
431     /;
432     $p->{prop_value}->{clear}->{$_} = 1 for qw/
433     left right none both
434     /;
435     $p->{prop_value}->{direction}->{ltr} = 1;
436     $p->{prop_value}->{direction}->{rtl} = 1;
437     $p->{prop_value}->{marks}->{crop} = 1;
438     $p->{prop_value}->{marks}->{cross} = 1;
439     $p->{prop_value}->{'unicode-bidi'}->{$_} = 1 for qw/
440     normal bidi-override embed
441     /;
442     for my $prop_name (qw/overflow overflow-x overflow-y/) {
443     $p->{prop_value}->{$prop_name}->{$_} = 1 for qw/
444     visible hidden scroll auto -webkit-marquee -moz-hidden-unscrollable
445     /;
446     }
447     $p->{prop_value}->{visibility}->{$_} = 1 for qw/
448     visible hidden collapse
449     /;
450     $p->{prop_value}->{'list-style-type'}->{$_} = 1 for qw/
451     disc circle square decimal decimal-leading-zero
452     lower-roman upper-roman lower-greek lower-latin
453     upper-latin armenian georgian lower-alpha upper-alpha none
454     hebrew cjk-ideographic hiragana katakana hiragana-iroha
455     katakana-iroha
456     /;
457     $p->{prop_value}->{'list-style-position'}->{outside} = 1;
458     $p->{prop_value}->{'list-style-position'}->{inside} = 1;
459     $p->{prop_value}->{'page-break-before'}->{$_} = 1 for qw/
460     auto always avoid left right
461     /;
462     $p->{prop_value}->{'page-break-after'}->{$_} = 1 for qw/
463     auto always avoid left right
464     /;
465     $p->{prop_value}->{'page-break-inside'}->{auto} = 1;
466     $p->{prop_value}->{'page-break-inside'}->{avoid} = 1;
467     $p->{prop_value}->{'background-repeat'}->{$_} = 1 for qw/
468     repeat repeat-x repeat-y no-repeat
469     /;
470     $p->{prop_value}->{'background-attachment'}->{scroll} = 1;
471     $p->{prop_value}->{'background-attachment'}->{fixed} = 1;
472     $p->{prop_value}->{'font-size'}->{$_} = 1 for qw/
473     xx-small x-small small medium large x-large xx-large
474     -manakai-xxx-large -webkit-xxx-large
475     larger smaller
476     /;
477     $p->{prop_value}->{'font-style'}->{normal} = 1;
478     $p->{prop_value}->{'font-style'}->{italic} = 1;
479     $p->{prop_value}->{'font-style'}->{oblique} = 1;
480     $p->{prop_value}->{'font-variant'}->{normal} = 1;
481     $p->{prop_value}->{'font-variant'}->{'small-caps'} = 1;
482     $p->{prop_value}->{'font-stretch'}->{$_} = 1 for
483     qw/normal wider narrower ultra-condensed extra-condensed
484     condensed semi-condensed semi-expanded expanded
485     extra-expanded ultra-expanded/;
486     $p->{prop_value}->{'text-align'}->{$_} = 1 for qw/
487     left right center justify begin end
488     /;
489     $p->{prop_value}->{'text-transform'}->{$_} = 1 for qw/
490     capitalize uppercase lowercase none
491     /;
492     $p->{prop_value}->{'white-space'}->{$_} = 1 for qw/
493 wakaba 1.36 normal pre nowrap pre-line pre-wrap -moz-pre-wrap
494 wakaba 1.35 /;
495 wakaba 1.37 $p->{prop_value}->{'writing-mode'}->{$_} = 1 for qw/
496     lr rl tb lr-tb rl-tb tb-rl
497     /;
498     $p->{prop_value}->{'text-anchor'}->{$_} = 1 for qw/
499     start middle end
500     /;
501     $p->{prop_value}->{'dominant-baseline'}->{$_} = 1 for qw/
502     auto use-script no-change reset-size ideographic alphabetic
503     hanging mathematical central middle text-after-edge text-before-edge
504     /;
505     $p->{prop_value}->{'alignment-baseline'}->{$_} = 1 for qw/
506     auto baseline before-edge text-before-edge middle central
507     after-edge text-after-edge ideographic alphabetic hanging
508     mathematical
509     /;
510 wakaba 1.35 $p->{prop_value}->{'text-decoration'}->{$_} = 1 for qw/
511     none blink underline overline line-through
512     /;
513     $p->{prop_value}->{'caption-side'}->{$_} = 1 for qw/
514     top bottom left right
515     /;
516     $p->{prop_value}->{'table-layout'}->{auto} = 1;
517     $p->{prop_value}->{'table-layout'}->{fixed} = 1;
518 wakaba 1.36 $p->{prop_value}->{'border-collapse'}->{collapse} = 1;
519 wakaba 1.35 $p->{prop_value}->{'border-collapse'}->{separate} = 1;
520     $p->{prop_value}->{'empty-cells'}->{show} = 1;
521     $p->{prop_value}->{'empty-cells'}->{hide} = 1;
522     $p->{prop_value}->{cursor}->{$_} = 1 for qw/
523     auto crosshair default pointer move e-resize ne-resize nw-resize n-resize
524     se-resize sw-resize s-resize w-resize text wait help progress
525     /;
526     for my $prop (qw/border-top-style border-left-style
527     border-bottom-style border-right-style outline-style/) {
528     $p->{prop_value}->{$prop}->{$_} = 1 for qw/
529     none hidden dotted dashed solid double groove ridge inset outset
530     /;
531     }
532     for my $prop (qw/color background-color
533     border-bottom-color border-left-color border-right-color
534     border-top-color border-color/) {
535     $p->{prop_value}->{$prop}->{transparent} = 1;
536     $p->{prop_value}->{$prop}->{flavor} = 1;
537     $p->{prop_value}->{$prop}->{'-manakai-default'} = 1;
538     }
539     $p->{prop_value}->{'outline-color'}->{invert} = 1;
540     $p->{prop_value}->{'outline-color'}->{'-manakai-invert-or-currentcolor'} = 1;
541     $p->{pseudo_class}->{$_} = 1 for qw/
542     active checked disabled empty enabled first-child first-of-type
543     focus hover indeterminate last-child last-of-type link only-child
544     only-of-type root target visited
545     lang nth-child nth-last-child nth-of-type nth-last-of-type not
546     -manakai-contains -manakai-current
547     /;
548     $p->{pseudo_element}->{$_} = 1 for qw/
549     after before first-letter first-line
550     /;
551    
552     return $CSSParser = $p;
553     } # get_css_parser
554    
555     sub print_syntax_error_css_section ($$) {
556     my ($input, $result) = @_;
557    
558     print STDOUT qq[
559     <div id="$input->{id_prefix}parse-errors" class="section">
560     <h2>Parse Errors</h2>
561    
562 wakaba 1.39 <dl id="$input->{id_prefix}parse-errors-list">];
563 wakaba 1.35 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
564    
565     my $p = get_css_parser ();
566 wakaba 1.37 $p->init;
567 wakaba 1.35 $p->{onerror} = sub {
568     my (%opt) = @_;
569     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
570     if ($opt{token}) {
571     print STDOUT qq[<dt class="$cls"><a href="#$input->{id_prefix}line-$opt{token}->{line}">Line $opt{token}->{line}</a> column $opt{token}->{column}];
572     } else {
573     print STDOUT qq[<dt class="$cls">Unknown location];
574     }
575     if (defined $opt{value}) {
576     print STDOUT qq[ (<code>@{[htescape ($opt{value})]}</code>)];
577     } elsif (defined $opt{token}) {
578     print STDOUT qq[ (<code>@{[htescape (Whatpm::CSS::Tokenizer->serialize_token ($opt{token}))]}</code>)];
579     }
580     $type =~ tr/ /-/;
581     $type =~ s/\|/%7C/g;
582     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
583     print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
584     print STDOUT qq[$msg</dd>\n];
585    
586     add_error ('syntax', \%opt => $result);
587     };
588     $p->{href} = $input->{uri};
589     $p->{base_uri} = $input->{base_uri};
590    
591 wakaba 1.37 # if ($parse_mode eq 'q') {
592     # $p->{unitless_px} = 1;
593     # $p->{hashless_color} = 1;
594     # }
595    
596     ## TODO: Make $input->{s} a ref.
597    
598 wakaba 1.35 my $s = \$input->{s};
599     my $charset;
600     unless ($input->{is_char_string}) {
601     require Encode;
602     if (defined $input->{charset}) {## TODO: IANA->Perl
603     $charset = $input->{charset};
604     $s = \(Encode::decode ($input->{charset}, $$s));
605     } else {
606     ## TODO: charset detection
607     $s = \(Encode::decode ($charset = 'utf-8', $$s));
608     }
609     }
610    
611     my $cssom = $p->parse_char_string ($$s);
612     $cssom->manakai_input_encoding ($charset) if defined $charset;
613    
614     print STDOUT qq[</dl></div>];
615    
616     return $cssom;
617     } # print_syntax_error_css_section
618    
619 wakaba 1.22 sub print_syntax_error_manifest_section ($$) {
620     my ($input, $result) = @_;
621    
622     require Whatpm::CacheManifest;
623    
624     print STDOUT qq[
625 wakaba 1.32 <div id="$input->{id_prefix}parse-errors" class="section">
626 wakaba 1.22 <h2>Parse Errors</h2>
627    
628 wakaba 1.39 <dl id="$input->{id_prefix}parse-errors-list">];
629 wakaba 1.32 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
630 wakaba 1.22
631     my $onerror = sub {
632     my (%opt) = @_;
633     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
634 wakaba 1.32 print STDOUT qq[<dt class="$cls">], get_error_label ($input, \%opt),
635     qq[</dt>];
636 wakaba 1.22 $type =~ tr/ /-/;
637     $type =~ s/\|/%7C/g;
638     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
639 wakaba 1.23 print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
640     print STDOUT qq[$msg</dd>\n];
641 wakaba 1.22
642     add_error ('syntax', \%opt => $result);
643     };
644    
645     my $time1 = time;
646     my $manifest = Whatpm::CacheManifest->parse_byte_string
647     ($input->{s}, $input->{uri}, $input->{base_uri}, $onerror);
648     $time{parse_manifest} = time - $time1;
649    
650     print STDOUT qq[</dl></div>];
651    
652     return $manifest;
653     } # print_syntax_error_manifest_section
654    
655 wakaba 1.35 sub print_source_string_section ($$$) {
656     my $input = shift;
657     my $s;
658     unless ($input->{is_char_string}) {
659     require Encode;
660     my $enc = Encode::find_encoding ($_[1]); ## TODO: charset name -> Perl name
661     return unless $enc;
662    
663     $s = \($enc->decode (${$_[0]}));
664     } else {
665     $s = $_[0];
666     }
667 wakaba 1.9
668     my $i = 1;
669 wakaba 1.32 push @nav, ['#source-string' => 'Source'] unless $input->{nested};
670     print STDOUT qq[<div id="$input->{id_prefix}source-string" class="section">
671 wakaba 1.9 <h2>Document Source</h2>
672     <ol lang="">\n];
673 wakaba 1.7 if (length $$s) {
674 wakaba 1.41 while ($$s =~ /\G([^\x0D\x0A]*?)(?>\x0D\x0A?|\x0A)/gc) {
675 wakaba 1.32 print STDOUT qq[<li id="$input->{id_prefix}line-$i">], htescape $1,
676     "</li>\n";
677 wakaba 1.7 $i++;
678     }
679 wakaba 1.41 if ($$s =~ /\G([^\x0D\x0A]+)/gc) {
680 wakaba 1.32 print STDOUT qq[<li id="$input->{id_prefix}line-$i">], htescape $1,
681     "</li>\n";
682 wakaba 1.7 }
683     } else {
684 wakaba 1.32 print STDOUT q[<li id="$input->{id_prefix}line-1"></li>];
685 wakaba 1.2 }
686 wakaba 1.39 print STDOUT "</ol></div>
687 wakaba 1.42 <script>
688     addSourceToParseErrorList ('$input->{id_prefix}', 'parse-errors-list');
689     </script>";
690 wakaba 1.9 } # print_input_string_section
691 wakaba 1.2
692 wakaba 1.35 sub print_document_tree ($$) {
693     my ($input, $node) = @_;
694    
695 wakaba 1.2 my $r = '<ol class="xoxo">';
696 wakaba 1.1
697 wakaba 1.2 my @node = ($node);
698 wakaba 1.1 while (@node) {
699     my $child = shift @node;
700 wakaba 1.2 unless (ref $child) {
701     $r .= $child;
702     next;
703     }
704    
705 wakaba 1.32 my $node_id = $input->{id_prefix} . 'node-'.refaddr $child;
706 wakaba 1.2 my $nt = $child->node_type;
707     if ($nt == $child->ELEMENT_NODE) {
708 wakaba 1.4 my $child_nsuri = $child->namespace_uri;
709     $r .= qq[<li id="$node_id" class="tree-element"><code title="@{[defined $child_nsuri ? $child_nsuri : '']}">] . htescape ($child->tag_name) .
710 wakaba 1.2 '</code>'; ## ISSUE: case
711    
712     if ($child->has_attributes) {
713     $r .= '<ul class="attributes">';
714 wakaba 1.4 for my $attr (sort {$a->[0] cmp $b->[0]} map { [$_->name, $_->value, $_->namespace_uri, 'node-'.refaddr $_] }
715 wakaba 1.2 @{$child->attributes}) {
716 wakaba 1.32 $r .= qq[<li id="$input->{id_prefix}$attr->[3]" class="tree-attribute"><code title="@{[defined $attr->[2] ? htescape ($attr->[2]) : '']}">] . htescape ($attr->[0]) . '</code> = '; ## ISSUE: case?
717 wakaba 1.2 $r .= '<q>' . htescape ($attr->[1]) . '</q></li>'; ## TODO: children
718     }
719     $r .= '</ul>';
720     }
721    
722 wakaba 1.7 if ($child->has_child_nodes) {
723 wakaba 1.2 $r .= '<ol class="children">';
724 wakaba 1.6 unshift @node, @{$child->child_nodes}, '</ol></li>';
725     } else {
726     $r .= '</li>';
727 wakaba 1.2 }
728     } elsif ($nt == $child->TEXT_NODE) {
729 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-text"><q lang="">' . htescape ($child->data) . '</q></li>';
730 wakaba 1.2 } elsif ($nt == $child->CDATA_SECTION_NODE) {
731 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-cdata"><code>&lt;[CDATA[</code><q lang="">' . htescape ($child->data) . '</q><code>]]&gt;</code></li>';
732 wakaba 1.2 } elsif ($nt == $child->COMMENT_NODE) {
733 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-comment"><code>&lt;!--</code><q lang="">' . htescape ($child->data) . '</q><code>--&gt;</code></li>';
734 wakaba 1.2 } elsif ($nt == $child->DOCUMENT_NODE) {
735 wakaba 1.6 $r .= qq'<li id="$node_id" class="tree-document">Document';
736 wakaba 1.7 $r .= qq[<ul class="attributes">];
737 wakaba 1.27 my $cp = $child->manakai_charset;
738     if (defined $cp) {
739     $r .= qq[<li><code>charset</code> parameter = <code>];
740     $r .= htescape ($cp) . qq[</code></li>];
741     }
742     $r .= qq[<li><code>inputEncoding</code> = ];
743     my $ie = $child->input_encoding;
744     if (defined $ie) {
745     $r .= qq[<code>@{[htescape ($ie)]}</code>];
746     if ($child->manakai_has_bom) {
747     $r .= qq[ (with <code class=charname><abbr>BOM</abbr></code>)];
748     }
749     } else {
750     $r .= qq[(<code>null</code>)];
751     }
752 wakaba 1.7 $r .= qq[<li>@{[scalar get_text ('manakaiIsHTML:'.($child->manakai_is_html?1:0))]}</li>];
753     $r .= qq[<li>@{[scalar get_text ('manakaiCompatMode:'.$child->manakai_compat_mode)]}</li>];
754 wakaba 1.9 unless ($child->manakai_is_html) {
755     $r .= qq[<li>XML version = <code>@{[htescape ($child->xml_version)]}</code></li>];
756     if (defined $child->xml_encoding) {
757     $r .= qq[<li>XML encoding = <code>@{[htescape ($child->xml_encoding)]}</code></li>];
758     } else {
759     $r .= qq[<li>XML encoding = (null)</li>];
760     }
761     $r .= qq[<li>XML standalone = @{[$child->xml_standalone ? 'true' : 'false']}</li>];
762     }
763 wakaba 1.7 $r .= qq[</ul>];
764 wakaba 1.2 if ($child->has_child_nodes) {
765 wakaba 1.7 $r .= '<ol class="children">';
766 wakaba 1.6 unshift @node, @{$child->child_nodes}, '</ol></li>';
767 wakaba 1.1 }
768 wakaba 1.2 } elsif ($nt == $child->DOCUMENT_TYPE_NODE) {
769 wakaba 1.5 $r .= qq'<li id="$node_id" class="tree-doctype"><code>&lt;!DOCTYPE&gt;</code><ul class="attributes">';
770     $r .= qq[<li class="tree-doctype-name">Name = <q>@{[htescape ($child->name)]}</q></li>];
771     $r .= qq[<li class="tree-doctype-publicid">Public identifier = <q>@{[htescape ($child->public_id)]}</q></li>];
772     $r .= qq[<li class="tree-doctype-systemid">System identifier = <q>@{[htescape ($child->system_id)]}</q></li>];
773 wakaba 1.2 $r .= '</ul></li>';
774     } elsif ($nt == $child->PROCESSING_INSTRUCTION_NODE) {
775 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-id"><code>&lt;?@{[htescape ($child->target)]}</code> <q>@{[htescape ($child->data)]}</q><code>?&gt;</code></li>';
776 wakaba 1.1 } else {
777 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-unknown">@{[$child->node_type]} @{[htescape ($child->node_name)]}</li>'; # error
778 wakaba 1.1 }
779     }
780 wakaba 1.2
781     $r .= '</ol>';
782     print STDOUT $r;
783     } # print_document_tree
784 wakaba 1.1
785 wakaba 1.32 sub print_structure_dump_dom_section ($$$) {
786     my ($input, $doc, $el) = @_;
787 wakaba 1.18
788     print STDOUT qq[
789 wakaba 1.32 <div id="$input->{id_prefix}document-tree" class="section">
790 wakaba 1.18 <h2>Document Tree</h2>
791     ];
792 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}document-tree] => 'Tree']
793     unless $input->{nested};
794 wakaba 1.18
795 wakaba 1.35 print_document_tree ($input, $el || $doc);
796 wakaba 1.18
797     print STDOUT qq[</div>];
798 wakaba 1.22 } # print_structure_dump_dom_section
799    
800 wakaba 1.35 sub print_structure_dump_cssom_section ($$) {
801     my ($input, $cssom) = @_;
802    
803     print STDOUT qq[
804     <div id="$input->{id_prefix}document-tree" class="section">
805     <h2>Document Tree</h2>
806     ];
807     push @nav, [qq[#$input->{id_prefix}document-tree] => 'Tree']
808     unless $input->{nested};
809    
810     ## TODO:
811     print STDOUT "<pre>".htescape ($cssom->css_text)."</pre>";
812    
813     print STDOUT qq[</div>];
814     } # print_structure_dump_cssom_section
815    
816 wakaba 1.32 sub print_structure_dump_manifest_section ($$) {
817     my ($input, $manifest) = @_;
818 wakaba 1.22
819     print STDOUT qq[
820 wakaba 1.32 <div id="$input->{id_prefix}dump-manifest" class="section">
821 wakaba 1.22 <h2>Cache Manifest</h2>
822     ];
823 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}dump-manifest] => 'Cache Manifest']
824     unless $input->{nested};
825 wakaba 1.22
826     print STDOUT qq[<dl><dt>Explicit entries</dt>];
827 wakaba 1.37 my $i = 0;
828 wakaba 1.22 for my $uri (@{$manifest->[0]}) {
829     my $euri = htescape ($uri);
830 wakaba 1.37 print STDOUT qq[<dd id="$input->{id_prefix}index-@{[$i++]}"><code class=uri>&lt;<a href="$euri">$euri</a>></code></dd>];
831 wakaba 1.22 }
832    
833     print STDOUT qq[<dt>Fallback entries</dt><dd>
834     <table><thead><tr><th scope=row>Oppotunistic Caching Namespace</th>
835     <th scope=row>Fallback Entry</tr><tbody>];
836     for my $uri (sort {$a cmp $b} keys %{$manifest->[1]}) {
837     my $euri = htescape ($uri);
838     my $euri2 = htescape ($manifest->[1]->{$uri});
839 wakaba 1.37 print STDOUT qq[<tr><td id="$input->{id_prefix}index-@{[$i++]}"><code class=uri>&lt;<a href="$euri">$euri</a>></code></td>
840     <td id="$input->{id_prefix}index-@{[$i++]}"><code class=uri>&lt;<a href="$euri2">$euri2</a>></code></td>];
841 wakaba 1.22 }
842    
843     print STDOUT qq[</table><dt>Online whitelist</dt>];
844     for my $uri (@{$manifest->[2]}) {
845     my $euri = htescape ($uri);
846 wakaba 1.37 print STDOUT qq[<dd id="$input->{id_prefix}index-@{[$i++]}"><code class=uri>&lt;<a href="$euri">$euri</a>></code></dd>];
847 wakaba 1.22 }
848 wakaba 1.18
849 wakaba 1.22 print STDOUT qq[</dl></div>];
850     } # print_structure_dump_manifest_section
851    
852 wakaba 1.34 sub print_structure_error_dom_section ($$$$$) {
853     my ($input, $doc, $el, $result, $onsubdoc) = @_;
854 wakaba 1.18
855 wakaba 1.32 print STDOUT qq[<div id="$input->{id_prefix}document-errors" class="section">
856 wakaba 1.18 <h2>Document Errors</h2>
857    
858 wakaba 1.42 <dl id=document-errors-list>];
859 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}document-errors] => 'Document Error']
860     unless $input->{nested};
861 wakaba 1.18
862     require Whatpm::ContentChecker;
863     my $onerror = sub {
864     my %opt = @_;
865     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level}, $opt{node});
866     $type =~ tr/ /-/;
867     $type =~ s/\|/%7C/g;
868     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
869 wakaba 1.32 print STDOUT qq[<dt class="$cls">] . get_error_label ($input, \%opt) .
870 wakaba 1.23 qq[</dt>\n<dd class="$cls">], get_error_level_label (\%opt);
871     print STDOUT $msg, "</dd>\n";
872 wakaba 1.19 add_error ('structure', \%opt => $result);
873 wakaba 1.18 };
874    
875     my $elements;
876     my $time1 = time;
877     if ($el) {
878 wakaba 1.34 $elements = Whatpm::ContentChecker->check_element
879     ($el, $onerror, $onsubdoc);
880 wakaba 1.18 } else {
881 wakaba 1.34 $elements = Whatpm::ContentChecker->check_document
882     ($doc, $onerror, $onsubdoc);
883 wakaba 1.18 }
884     $time{check} = time - $time1;
885    
886 wakaba 1.42 print STDOUT qq[</dl>
887     <script>
888     addSourceToParseErrorList ('$input->{id_prefix}', 'document-errors-list');
889     </script></div>];
890 wakaba 1.18
891     return $elements;
892 wakaba 1.22 } # print_structure_error_dom_section
893    
894     sub print_structure_error_manifest_section ($$$) {
895 wakaba 1.32 my ($input, $manifest, $result) = @_;
896 wakaba 1.22
897 wakaba 1.32 print STDOUT qq[<div id="$input->{id_prefix}document-errors" class="section">
898 wakaba 1.22 <h2>Document Errors</h2>
899    
900     <dl>];
901 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}document-errors] => 'Document Error']
902     unless $input->{nested};
903 wakaba 1.22
904     require Whatpm::CacheManifest;
905     Whatpm::CacheManifest->check_manifest ($manifest, sub {
906     my %opt = @_;
907     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level}, $opt{node});
908     $type =~ tr/ /-/;
909     $type =~ s/\|/%7C/g;
910     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
911 wakaba 1.32 print STDOUT qq[<dt class="$cls">] . get_error_label ($input, \%opt) .
912 wakaba 1.22 qq[</dt>\n<dd class="$cls">], $msg, "</dd>\n";
913     add_error ('structure', \%opt => $result);
914     });
915    
916     print STDOUT qq[</div>];
917     } # print_structure_error_manifest_section
918 wakaba 1.18
919 wakaba 1.32 sub print_table_section ($$) {
920     my ($input, $tables) = @_;
921 wakaba 1.18
922 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}tables] => 'Tables']
923     unless $input->{nested};
924 wakaba 1.18 print STDOUT qq[
925 wakaba 1.32 <div id="$input->{id_prefix}tables" class="section">
926 wakaba 1.18 <h2>Tables</h2>
927    
928     <!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
929     <script src="../table-script.js" type="text/javascript"></script>
930     <noscript>
931     <p><em>Structure of tables are visualized here if scripting is enabled.</em></p>
932     </noscript>
933     ];
934    
935     require JSON;
936    
937     my $i = 0;
938     for my $table_el (@$tables) {
939     $i++;
940 wakaba 1.32 print STDOUT qq[<div class="section" id="$input->{id_prefix}table-$i"><h3>] .
941     get_node_link ($input, $table_el) . q[</h3>];
942 wakaba 1.18
943     ## TODO: Make |ContentChecker| return |form_table| result
944     ## so that this script don't have to run the algorithm twice.
945     my $table = Whatpm::HTMLTable->form_table ($table_el);
946    
947     for (@{$table->{column_group}}, @{$table->{column}}, $table->{caption}) {
948     next unless $_;
949     delete $_->{element};
950     }
951    
952     for (@{$table->{row_group}}) {
953     next unless $_;
954     next unless $_->{element};
955     $_->{type} = $_->{element}->manakai_local_name;
956     delete $_->{element};
957     }
958    
959     for (@{$table->{cell}}) {
960     next unless $_;
961     for (@{$_}) {
962     next unless $_;
963     for (@$_) {
964     $_->{id} = refaddr $_->{element} if defined $_->{element};
965     delete $_->{element};
966     $_->{is_header} = $_->{is_header} ? 1 : 0;
967     }
968     }
969     }
970    
971     print STDOUT '</div><script type="text/javascript">tableToCanvas (';
972     print STDOUT JSON::objToJson ($table);
973 wakaba 1.32 print STDOUT qq[, document.getElementById ('$input->{id_prefix}table-$i')];
974     print STDOUT qq[, '$input->{id_prefix}');</script>];
975 wakaba 1.18 }
976    
977     print STDOUT qq[</div>];
978     } # print_table_section
979    
980 wakaba 1.33 sub print_listing_section ($$$) {
981     my ($opt, $input, $ids) = @_;
982 wakaba 1.18
983 wakaba 1.35 push @nav, ['#' . $input->{id_prefix} . $opt->{id} => $opt->{label}]
984     unless $input->{nested};
985 wakaba 1.18 print STDOUT qq[
986 wakaba 1.33 <div id="$input->{id_prefix}$opt->{id}" class="section">
987     <h2>$opt->{heading}</h2>
988 wakaba 1.18
989     <dl>
990     ];
991     for my $id (sort {$a cmp $b} keys %$ids) {
992     print STDOUT qq[<dt><code>@{[htescape $id]}</code></dt>];
993     for (@{$ids->{$id}}) {
994 wakaba 1.32 print STDOUT qq[<dd>].get_node_link ($input, $_).qq[</dd>];
995 wakaba 1.18 }
996     }
997     print STDOUT qq[</dl></div>];
998 wakaba 1.33 } # print_listing_section
999 wakaba 1.18
1000 wakaba 1.19 sub print_result_section ($) {
1001     my $result = shift;
1002    
1003     print STDOUT qq[
1004     <div id="result-summary" class="section">
1005     <h2>Result</h2>];
1006    
1007 wakaba 1.21 if ($result->{unsupported} and $result->{conforming_max}) {
1008 wakaba 1.19 print STDOUT qq[<p class=uncertain id=result-para>The conformance
1009     checker cannot decide whether the document is conforming or
1010     not, since the document contains one or more unsupported
1011 wakaba 1.21 features. The document might or might not be conforming.</p>];
1012 wakaba 1.19 } elsif ($result->{conforming_min}) {
1013     print STDOUT qq[<p class=PASS id=result-para>No conformance-error is
1014     found in this document.</p>];
1015     } elsif ($result->{conforming_max}) {
1016     print STDOUT qq[<p class=SEE-RESULT id=result-para>This document
1017     is <strong>likely <em>non</em>-conforming</strong>, but in rare case
1018     it might be conforming.</p>];
1019     } else {
1020     print STDOUT qq[<p class=FAIL id=result-para>This document is
1021     <strong><em>non</em>-conforming</strong>.</p>];
1022     }
1023    
1024     print STDOUT qq[<table>
1025     <colgroup><col><colgroup><col><col><col><colgroup><col>
1026     <thead>
1027 wakaba 1.23 <tr><th scope=col></th>
1028     <th scope=col><a href="../error-description#level-m"><em class=rfc2119>MUST</em>‐level
1029     Errors</a></th>
1030     <th scope=col><a href="../error-description#level-s"><em class=rfc2119>SHOULD</em>‐level
1031     Errors</a></th>
1032     <th scope=col><a href="../error-description#level-w">Warnings</a></th>
1033     <th scope=col>Score</th></tr></thead><tbody>];
1034 wakaba 1.19
1035     my $must_error = 0;
1036     my $should_error = 0;
1037     my $warning = 0;
1038     my $score_min = 0;
1039     my $score_max = 0;
1040     my $score_base = 20;
1041 wakaba 1.21 my $score_unit = $score_base / 100;
1042 wakaba 1.19 for (
1043     [Transfer => 'transfer', ''],
1044     [Character => 'char', ''],
1045     [Syntax => 'syntax', '#parse-errors'],
1046     [Structure => 'structure', '#document-errors'],
1047     ) {
1048     $must_error += ($result->{$_->[1]}->{must} += 0);
1049     $should_error += ($result->{$_->[1]}->{should} += 0);
1050     $warning += ($result->{$_->[1]}->{warning} += 0);
1051 wakaba 1.21 $score_min += (($result->{$_->[1]}->{score_min} *= $score_unit) += $score_base);
1052     $score_max += (($result->{$_->[1]}->{score_max} *= $score_unit) += $score_base);
1053 wakaba 1.19
1054     my $uncertain = $result->{$_->[1]}->{unsupported} ? '?' : '';
1055     my $label = $_->[0];
1056     if ($result->{$_->[1]}->{must} or
1057     $result->{$_->[1]}->{should} or
1058     $result->{$_->[1]}->{warning} or
1059     $result->{$_->[1]}->{unsupported}) {
1060     $label = qq[<a href="$_->[2]">$label</a>];
1061     }
1062    
1063     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>];
1064     if ($uncertain) {
1065 wakaba 1.21 print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : $result->{$_->[1]}->{should} ? 'SEE-RESULT' : '']}">&#x2212;&#x221E;..$result->{$_->[1]}->{score_max}</td>];
1066 wakaba 1.19 } elsif ($result->{$_->[1]}->{score_min} != $result->{$_->[1]}->{score_max}) {
1067 wakaba 1.21 print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : 'SEE-RESULT']}">$result->{$_->[1]}->{score_min}..$result->{$_->[1]}->{score_max}</td></tr>];
1068 wakaba 1.19 } else {
1069 wakaba 1.21 print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : '']}">$result->{$_->[1]}->{score_min}</td></tr>];
1070 wakaba 1.19 }
1071     }
1072    
1073     $score_max += $score_base;
1074    
1075     print STDOUT qq[
1076     <tr class=uncertain><th scope=row>Semantics</th><td>0?</td><td>0?</td><td>0?</td><td>&#x2212;&#x221E;..$score_base</td></tr>
1077     </tbody>
1078 wakaba 1.21 <tfoot><tr class=uncertain><th scope=row>Total</th>
1079     <td class="@{[$must_error ? 'FAIL' : '']}">$must_error?</td>
1080     <td class="@{[$should_error ? 'SEE-RESULT' : '']}">$should_error?</td>
1081     <td>$warning?</td>
1082     <td class="@{[$must_error ? 'FAIL' : $should_error ? 'SEE-RESULT' : '']}"><strong>&#x2212;&#x221E;..$score_max</strong></td></tr></tfoot>
1083 wakaba 1.19 </table>
1084    
1085     <p><strong>Important</strong>: This conformance checking service
1086     is <em>under development</em>. The result above might be <em>wrong</em>.</p>
1087     </div>];
1088     push @nav, ['#result-summary' => 'Result'];
1089     } # print_result_section
1090    
1091 wakaba 1.24 sub print_result_unknown_type_section ($$) {
1092     my ($input, $result) = @_;
1093 wakaba 1.18
1094 wakaba 1.24 my $euri = htescape ($input->{uri});
1095 wakaba 1.18 print STDOUT qq[
1096 wakaba 1.35 <div id="$input->{id_prefix}parse-errors" class="section">
1097 wakaba 1.24 <h2>Errors</h2>
1098    
1099     <dl>
1100     <dt class=unsupported><code>&lt;<a href="$euri">$euri</a>&gt;</code></dt>
1101     <dd class=unsupported><strong><a href="../error-description#level-u">Not
1102     supported</a></strong>:
1103     Media type
1104     <code class="MIME" lang="en">@{[htescape $input->{media_type}]}</code>
1105     is not supported.</dd>
1106     </dl>
1107 wakaba 1.18 </div>
1108     ];
1109 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}parse-errors] => 'Errors']
1110     unless $input->{nested};
1111 wakaba 1.30 add_error (char => {level => 'u'} => $result);
1112     add_error (syntax => {level => 'u'} => $result);
1113     add_error (structure => {level => 'u'} => $result);
1114 wakaba 1.18 } # print_result_unknown_type_section
1115    
1116     sub print_result_input_error_section ($) {
1117     my $input = shift;
1118     print STDOUT qq[<div class="section" id="result-summary">
1119     <p><em><strong>Input Error</strong>: @{[htescape ($input->{error_status_text})]}</em></p>
1120     </div>];
1121     push @nav, ['#result-summary' => 'Result'];
1122 wakaba 1.32 } # print_result_input_error_section
1123 wakaba 1.18
1124 wakaba 1.32 sub get_error_label ($$) {
1125     my ($input, $err) = @_;
1126 wakaba 1.22
1127     my $r = '';
1128    
1129 wakaba 1.42 my $line;
1130     my $column;
1131    
1132     if (defined $err->{node}) {
1133     $line = $err->{node}->get_user_data ('manakai_source_line');
1134     if (defined $line) {
1135     $column = $err->{node}->get_user_data ('manakai_source_column');
1136 wakaba 1.40 } else {
1137 wakaba 1.42 if ($err->{node}->node_type == $err->{node}->ATTRIBUTE_NODE) {
1138     my $owner = $err->{node}->owner_element;
1139     $line = $owner->get_user_data ('manakai_source_line');
1140     $column = $owner->get_user_data ('manakai_source_column');
1141     }
1142     }
1143     }
1144     unless (defined $line) {
1145     if (defined $err->{token} and defined $err->{token}->{line}) {
1146     $line = $err->{token}->{line};
1147     $column = $err->{token}->{column};
1148     } elsif (defined $err->{line}) {
1149     $line = $err->{line};
1150     $column = $err->{column};
1151 wakaba 1.40 }
1152 wakaba 1.42 }
1153    
1154     if (defined $line) {
1155     if (defined $column and $column > 0) {
1156     $r = qq[<a href="#$input->{id_prefix}line-$line">Line $line</a> column $column];
1157 wakaba 1.22 } else {
1158 wakaba 1.42 $line = $line - 1 || 1;
1159     $r = qq[<a href="#$input->{id_prefix}line-$line">Line $line</a>];
1160 wakaba 1.22 }
1161     }
1162    
1163     if (defined $err->{node}) {
1164     $r .= ' ' if length $r;
1165 wakaba 1.42 $r .= get_node_link ($input, $err->{node});
1166 wakaba 1.22 }
1167    
1168     if (defined $err->{index}) {
1169 wakaba 1.37 if (length $r) {
1170     $r .= ', Index ' . (0+$err->{index});
1171     } else {
1172     $r .= "<a href='#$input->{id_prefix}index-@{[0+$err->{index}]}'>Index "
1173     . (0+$err->{index}) . '</a>';
1174     }
1175 wakaba 1.22 }
1176    
1177     if (defined $err->{value}) {
1178     $r .= ' ' if length $r;
1179     $r .= '<q><code>' . htescape ($err->{value}) . '</code></q>';
1180     }
1181    
1182     return $r;
1183     } # get_error_label
1184    
1185 wakaba 1.23 sub get_error_level_label ($) {
1186     my $err = shift;
1187    
1188     my $r = '';
1189    
1190     if (not defined $err->{level} or $err->{level} eq 'm') {
1191     $r = qq[<strong><a href="../error-description#level-m"><em class=rfc2119>MUST</em>‐level
1192     error</a></strong>: ];
1193     } elsif ($err->{level} eq 's') {
1194     $r = qq[<strong><a href="../error-description#level-s"><em class=rfc2119>SHOULD</em>‐level
1195     error</a></strong>: ];
1196     } elsif ($err->{level} eq 'w') {
1197     $r = qq[<strong><a href="../error-description#level-w">Warning</a></strong>:
1198     ];
1199 wakaba 1.30 } elsif ($err->{level} eq 'u' or $err->{level} eq 'unsupported') {
1200 wakaba 1.23 $r = qq[<strong><a href="../error-description#level-u">Not
1201     supported</a></strong>: ];
1202 wakaba 1.37 } elsif ($err->{level} eq 'i') {
1203     $r = qq[<strong><a href="../error-description#level-i">Information</a></strong>: ];
1204 wakaba 1.23 } else {
1205     my $elevel = htescape ($err->{level});
1206     $r = qq[<strong><a href="../error-description#level-$elevel">$elevel</a></strong>:
1207     ];
1208     }
1209    
1210     return $r;
1211     } # get_error_level_label
1212    
1213 wakaba 1.1 sub get_node_path ($) {
1214     my $node = shift;
1215     my @r;
1216     while (defined $node) {
1217     my $rs;
1218     if ($node->node_type == 1) {
1219     $rs = $node->manakai_local_name;
1220     $node = $node->parent_node;
1221     } elsif ($node->node_type == 2) {
1222     $rs = '@' . $node->manakai_local_name;
1223     $node = $node->owner_element;
1224     } elsif ($node->node_type == 3) {
1225     $rs = '"' . $node->data . '"';
1226     $node = $node->parent_node;
1227     } elsif ($node->node_type == 9) {
1228 wakaba 1.9 @r = ('') unless @r;
1229 wakaba 1.1 $rs = '';
1230     $node = $node->parent_node;
1231     } else {
1232     $rs = '#' . $node->node_type;
1233     $node = $node->parent_node;
1234     }
1235     unshift @r, $rs;
1236     }
1237     return join '/', @r;
1238     } # get_node_path
1239    
1240 wakaba 1.32 sub get_node_link ($$) {
1241     return qq[<a href="#$_[0]->{id_prefix}node-@{[refaddr $_[1]]}">] .
1242     htescape (get_node_path ($_[1])) . qq[</a>];
1243 wakaba 1.6 } # get_node_link
1244    
1245 wakaba 1.7 {
1246     my $Msg = {};
1247    
1248     sub load_text_catalog ($) {
1249     my $lang = shift; # MUST be a canonical lang name
1250 wakaba 1.26 open my $file, '<:utf8', "cc-msg.$lang.txt"
1251     or die "$0: cc-msg.$lang.txt: $!";
1252 wakaba 1.7 while (<$file>) {
1253     if (s/^([^;]+);([^;]*);//) {
1254     my ($type, $cls, $msg) = ($1, $2, $_);
1255     $msg =~ tr/\x0D\x0A//d;
1256     $Msg->{$type} = [$cls, $msg];
1257     }
1258     }
1259     } # load_text_catalog
1260    
1261     sub get_text ($) {
1262 wakaba 1.15 my ($type, $level, $node) = @_;
1263 wakaba 1.7 $type = $level . ':' . $type if defined $level;
1264 wakaba 1.29 $level = 'm' unless defined $level;
1265 wakaba 1.7 my @arg;
1266     {
1267     if (defined $Msg->{$type}) {
1268     my $msg = $Msg->{$type}->[1];
1269 wakaba 1.10 $msg =~ s{<var>\$([0-9]+)</var>}{
1270     defined $arg[$1] ? htescape ($arg[$1]) : '(undef)';
1271     }ge;
1272 wakaba 1.15 $msg =~ s{<var>{\@([A-Za-z0-9:_.-]+)}</var>}{
1273     UNIVERSAL::can ($node, 'get_attribute_ns')
1274     ? htescape ($node->get_attribute_ns (undef, $1)) : ''
1275     }ge;
1276     $msg =~ s{<var>{\@}</var>}{
1277     UNIVERSAL::can ($node, 'value') ? htescape ($node->value) : ''
1278     }ge;
1279 wakaba 1.17 $msg =~ s{<var>{local-name}</var>}{
1280     UNIVERSAL::can ($node, 'manakai_local_name')
1281     ? htescape ($node->manakai_local_name) : ''
1282     }ge;
1283     $msg =~ s{<var>{element-local-name}</var>}{
1284     (UNIVERSAL::can ($node, 'owner_element') and
1285     $node->owner_element)
1286     ? htescape ($node->owner_element->manakai_local_name)
1287     : ''
1288     }ge;
1289 wakaba 1.29 return ($type, 'level-' . $level . ' ' . $Msg->{$type}->[0], $msg);
1290 wakaba 1.7 } elsif ($type =~ s/:([^:]*)$//) {
1291     unshift @arg, $1;
1292     redo;
1293     }
1294     }
1295 wakaba 1.29 return ($type, 'level-'.$level, htescape ($_[0]));
1296 wakaba 1.7 } # get_text
1297    
1298     }
1299    
1300 wakaba 1.9 sub get_input_document ($$) {
1301     my ($http, $dom) = @_;
1302    
1303 wakaba 1.16 my $request_uri = $http->get_parameter ('uri');
1304 wakaba 1.9 my $r = {};
1305     if (defined $request_uri and length $request_uri) {
1306     my $uri = $dom->create_uri_reference ($request_uri);
1307     unless ({
1308     http => 1,
1309     }->{lc $uri->uri_scheme}) {
1310     return {uri => $request_uri, request_uri => $request_uri,
1311     error_status_text => 'URI scheme not allowed'};
1312     }
1313    
1314     require Message::Util::HostPermit;
1315     my $host_permit = new Message::Util::HostPermit;
1316     $host_permit->add_rule (<<EOH);
1317     Allow host=suika port=80
1318     Deny host=suika
1319     Allow host=suika.fam.cx port=80
1320     Deny host=suika.fam.cx
1321     Deny host=localhost
1322     Deny host=*.localdomain
1323     Deny ipv4=0.0.0.0/8
1324     Deny ipv4=10.0.0.0/8
1325     Deny ipv4=127.0.0.0/8
1326     Deny ipv4=169.254.0.0/16
1327     Deny ipv4=172.0.0.0/11
1328     Deny ipv4=192.0.2.0/24
1329     Deny ipv4=192.88.99.0/24
1330     Deny ipv4=192.168.0.0/16
1331     Deny ipv4=198.18.0.0/15
1332     Deny ipv4=224.0.0.0/4
1333     Deny ipv4=255.255.255.255/32
1334     Deny ipv6=0::0/0
1335     Allow host=*
1336     EOH
1337     unless ($host_permit->check ($uri->uri_host, $uri->uri_port || 80)) {
1338     return {uri => $request_uri, request_uri => $request_uri,
1339     error_status_text => 'Connection to the host is forbidden'};
1340     }
1341    
1342     require LWP::UserAgent;
1343     my $ua = WDCC::LWPUA->new;
1344     $ua->{wdcc_dom} = $dom;
1345     $ua->{wdcc_host_permit} = $host_permit;
1346     $ua->agent ('Mozilla'); ## TODO: for now.
1347     $ua->parse_head (0);
1348     $ua->protocols_allowed ([qw/http/]);
1349     $ua->max_size (1000_000);
1350     my $req = HTTP::Request->new (GET => $request_uri);
1351 wakaba 1.28 $req->header ('Accept-Encoding' => 'identity, *; q=0');
1352 wakaba 1.9 my $res = $ua->request ($req);
1353 wakaba 1.16 ## TODO: 401 sets |is_success| true.
1354     if ($res->is_success or $http->get_parameter ('error-page')) {
1355 wakaba 1.9 $r->{base_uri} = $res->base; ## NOTE: It does check |Content-Base|, |Content-Location|, and <base>. ## TODO: Use our own code!
1356     $r->{uri} = $res->request->uri;
1357     $r->{request_uri} = $request_uri;
1358    
1359     ## TODO: More strict parsing...
1360     my $ct = $res->header ('Content-Type');
1361 wakaba 1.22 if (defined $ct and $ct =~ /;\s*charset\s*=\s*"?([^\s;"]+)"?/i) {
1362 wakaba 1.9 $r->{charset} = lc $1;
1363     $r->{charset} =~ tr/\\//d;
1364 wakaba 1.26 $r->{official_charset} = $r->{charset};
1365 wakaba 1.9 }
1366    
1367 wakaba 1.16 my $input_charset = $http->get_parameter ('charset');
1368 wakaba 1.9 if (defined $input_charset and length $input_charset) {
1369     $r->{charset_overridden}
1370     = (not defined $r->{charset} or $r->{charset} ne $input_charset);
1371     $r->{charset} = $input_charset;
1372 wakaba 1.25 }
1373    
1374     ## TODO: Support for HTTP Content-Encoding
1375 wakaba 1.9
1376     $r->{s} = ''.$res->content;
1377 wakaba 1.25
1378     require Whatpm::ContentType;
1379     ($r->{official_type}, $r->{media_type})
1380     = Whatpm::ContentType->get_sniffed_type
1381     (get_file_head => sub {
1382     return substr $r->{s}, 0, shift;
1383     },
1384     http_content_type_byte => $ct,
1385     has_http_content_encoding =>
1386     defined $res->header ('Content-Encoding'),
1387     supported_image_types => {});
1388 wakaba 1.9 } else {
1389     $r->{uri} = $res->request->uri;
1390     $r->{request_uri} = $request_uri;
1391     $r->{error_status_text} = $res->status_line;
1392     }
1393    
1394     $r->{header_field} = [];
1395     $res->scan (sub {
1396     push @{$r->{header_field}}, [$_[0], $_[1]];
1397     });
1398     $r->{header_status_code} = $res->code;
1399     $r->{header_status_text} = $res->message;
1400     } else {
1401 wakaba 1.16 $r->{s} = ''.$http->get_parameter ('s');
1402 wakaba 1.9 $r->{uri} = q<thismessage:/>;
1403     $r->{request_uri} = q<thismessage:/>;
1404     $r->{base_uri} = q<thismessage:/>;
1405 wakaba 1.16 $r->{charset} = ''.$http->get_parameter ('_charset_');
1406 wakaba 1.9 $r->{charset} =~ s/\s+//g;
1407     $r->{charset} = 'utf-8' if $r->{charset} eq '';
1408 wakaba 1.26 $r->{official_charset} = $r->{charset};
1409 wakaba 1.9 $r->{header_field} = [];
1410 wakaba 1.25
1411     require Whatpm::ContentType;
1412     ($r->{official_type}, $r->{media_type})
1413     = Whatpm::ContentType->get_sniffed_type
1414     (get_file_head => sub {
1415     return substr $r->{s}, 0, shift;
1416     },
1417     http_content_type_byte => undef,
1418     has_http_content_encoding => 0,
1419     supported_image_types => {});
1420 wakaba 1.9 }
1421    
1422 wakaba 1.16 my $input_format = $http->get_parameter ('i');
1423 wakaba 1.9 if (defined $input_format and length $input_format) {
1424     $r->{media_type_overridden}
1425     = (not defined $r->{media_type} or $input_format ne $r->{media_type});
1426     $r->{media_type} = $input_format;
1427     }
1428     if (defined $r->{s} and not defined $r->{media_type}) {
1429     $r->{media_type} = 'text/html';
1430     $r->{media_type_overridden} = 1;
1431     }
1432    
1433     if ($r->{media_type} eq 'text/xml') {
1434     unless (defined $r->{charset}) {
1435     $r->{charset} = 'us-ascii';
1436 wakaba 1.26 $r->{official_charset} = $r->{charset};
1437 wakaba 1.9 } elsif ($r->{charset_overridden} and $r->{charset} eq 'us-ascii') {
1438     $r->{charset_overridden} = 0;
1439     }
1440     }
1441    
1442     if (length $r->{s} > 1000_000) {
1443     $r->{error_status_text} = 'Entity-body too large';
1444     delete $r->{s};
1445     return $r;
1446     }
1447    
1448 wakaba 1.35 $r->{inner_html_element} = $http->get_parameter ('e');
1449    
1450 wakaba 1.9 return $r;
1451     } # get_input_document
1452    
1453     package WDCC::LWPUA;
1454     BEGIN { push our @ISA, 'LWP::UserAgent'; }
1455    
1456     sub redirect_ok {
1457     my $ua = shift;
1458     unless ($ua->SUPER::redirect_ok (@_)) {
1459     return 0;
1460     }
1461    
1462     my $uris = $_[1]->header ('Location');
1463     return 0 unless $uris;
1464     my $uri = $ua->{wdcc_dom}->create_uri_reference ($uris);
1465     unless ({
1466     http => 1,
1467     }->{lc $uri->uri_scheme}) {
1468     return 0;
1469     }
1470     unless ($ua->{wdcc_host_permit}->check ($uri->uri_host, $uri->uri_port || 80)) {
1471     return 0;
1472     }
1473     return 1;
1474     } # redirect_ok
1475    
1476 wakaba 1.1 =head1 AUTHOR
1477    
1478     Wakaba <w@suika.fam.cx>.
1479    
1480     =head1 LICENSE
1481    
1482 wakaba 1.35 Copyright 2007-2008 Wakaba <w@suika.fam.cx>
1483 wakaba 1.1
1484     This library is free software; you can redistribute it
1485     and/or modify it under the same terms as Perl itself.
1486    
1487     =cut
1488    
1489 wakaba 1.42 ## $Date: 2008/03/16 11:38:47 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24