modperl branch collapsed back to MAIN trunk, man!
[MyWeb.git] / Web.pm
diff --git a/Web.pm b/Web.pm
index c0d8c3c..e41a314 100644 (file)
--- a/Web.pm
+++ b/Web.pm
@@ -1,5 +1,3 @@
-#! /usr/bin/perl
-# 
 # $Id$
 # Common functions for HTML/XHTML output generation
 # Copyright (C) 2003 Jan Kratochvil <project-www.jankratochvil.net@jankratochvil.net>
 
 package My::Web;
 require 5.6.0; # at least 'use warnings;' but we need some 5.6.0+ modules anyway
-use vars qw($VERSION);
-$VERSION=do { my @r=(q$Revision$=~/\d+/g); sprintf "%d.".("%03d"x$#r),@r; };
+our $VERSION=do { my @r=(q$Revision$=~/\d+/g); sprintf "%d.".("%03d"x$#r),@r; };
+our $CVS_ID=q$Id$;
 use strict;
 use warnings;
 
-use WebConfig; # for %WebConfig
+use Exporter;
+sub Wrequire ($);
+sub Wuse ($@);
+our $W;
+our @EXPORT=qw(&Wrequire &Wuse &a_href &a_href_cz &vskip &img &centerimg &rightimg $W &top_dir &top_dir_disk);
+our @ISA=qw(Exporter);
+
+BEGIN
+{
+       sub Wrequire ($)
+       {
+       my($file)=@_;
+
+#              print STDERR "Wrequire $file\n";
+               $file=~s#/#::#g;
+               $file=~s/[.]pm$//;
+               my $class=$file;
+               $file=~s#::#/#g;
+               $file.=".pm";
+               my $aref=($W->{"packages_used"}{$Apache::Registry::curstash}||=[]);
+               push @$aref,$class
+                               if !{ map(($_=>1),@$aref) }->{$class};  # Prevent duplicated entries.
+               CORE::require $file;
+               1;      # Otherwise 'require' would already file above.
+       }
+
+       sub Wuse ($@)
+       {
+       my($file,@list)=@_;
+
+#              print STDERR "Wuse $file\n";
+               Wrequire $file;
+               local $Exporter::ExportLevel=$Exporter::ExportLevel+1;
+               $file->import(@list);
+               1;
+       }
+}
+
+BEGIN { Wuse 'WebConfig'; }    # for %WebConfig
 require CGI;   # for &escapeHTML
 require Image::Size;   # for &imgsize
 use File::Basename;    # &basename
+use Carp qw(cluck confess);
+use URI::Escape;
+require HTTP::BrowserDetect;
+require HTTP::Negotiate;
+require Geo::IP;
+require CGI;
+
+
+# Undo 'www/engine/httpd-restart' as it may use obsolete Perl for 'mod_perl'
+delete $ENV{"PERLLIB"};
+delete $ENV{"LD_LIBRARY_PATH"};
+
+
+#our $W;
+               # $W->{"title"}
+               # $W->{"head"}
+               # $W->{"head_css"}
+               # $W->{"force_charset"}
+               # %{$W->{"packages_used"}
+               # $W->{"heading_done"}
+               # $W->{"footer_passed"}
+               # %{$W->{"headers"}}
+               # %{$W->{"headers_lc"}} # maps lc($headers_key)=>$headers_key
+               # @{$W->{"packages_used"}{$Apache::Registry::curstash}}}
+               # %{$W->{"args"}}
 
-
-my %Args;
-               # $Args{"title"}
-               # $Args{"force_charset"}
-
-my $cvs_id_html;
 sub init ($%)
 {
 my($class,%args)=@_;
 
-       %WebConfig=(%WebConfig,%args);  # override %WebConfig settings
+       my $packages_used_save=$W->{"packages_used"};
+       $W={ %WebConfig,%args };        # override %WebConfig settings
+       $W->{"packages_used"}=$packages_used_save;
+
+       $W->{"__PACKAGE__"}||="Apache::ROOT".$Apache::Registry::curstash;
+
+       $W->{"top_dir"}||=eval '$'.$W->{"__PACKAGE__"}.'::top_dir';
+
+       do { $W->{$_}=0  if !defined $W->{$_}; } for ("detect_ent");
+       do { $W->{$_}=0  if !defined $W->{$_}; } for ("detect_js");
+       do { $W->{$_}=1  if !defined $W->{$_}; } for ("have_css");      # AFAIK it does not hurt anyone.
+       do { $W->{$_}=1  if !defined $W->{$_}; } for ("heading");
+       do { $W->{$_}=1  if !defined $W->{$_}; } for ("footer");
+       do { $W->{$_}=1  if !defined $W->{$_}; } for ("footer_delimit");
+       do { $W->{$_}=1  if !defined $W->{$_}; } for ("footer_mailme");
+       do { $W->{$_}=1  if !defined $W->{$_}; } for ("footer_ids");
+       do { $W->{$_}=1  if !defined $W->{$_}; } for ("indexme");
+       do { $W->{$_}="" if !defined $W->{$_}; } for ("head");
+       do { $W->{$_}="" if !defined $W->{$_}; } for ("head_css");
+
+       my $footer_any=0;
+       for (qw(footer_mailme footer_ids)) {
+               $W->{$_}=0 if !$W->{"footer"};
+               $footer_any=1 if $W->{$_};
+               }
+       $W->{"footer"}=0 if !$footer_any;
+       $W->{"footer_delimit"}=0 if !$W->{"footer"};
+
+       $W->{"r"}=Apache->request();
+
+       $W->{"QUERY_STRING"}=$W->{"r"}->args() || "";
+          if ($W->{"QUERY_STRING"}=~/[&]amp;have_ent/)
+               { $W->{"have_ent"}=0; }
+       elsif ($W->{"QUERY_STRING"}=~    /[&]have_ent/)
+               { $W->{"have_ent"}=1; }
+       else
+               { delete $W->{"have_ent"}; }
+       if ($W->{"detect_ent"} && !defined $W->{"have_ent"} && $W->{"r"}->method() eq "GET") {
+               $W->{"head"}.='<meta http-equiv="Refresh" content="0; URL='
+                               .CGI::escapeHTML("http://".&{$W->{"web_hostname_sub"}}()."/".($W->{"r"}->uri()=~m#^/*(.*)$#)[0]
+                                               ."?".($W->{"QUERY_STRING"} || "detect_ent_glue=1").'&have_ent=detect')
+                               .'" />'."\n";
+               }
+       $W->{"QUERY_STRING"}=~s/([&])amp;/$1/g;
+       $W->{"r"}->args($W->{"QUERY_STRING"});
+       $ENV{"QUERY_STRING"}=$W->{"QUERY_STRING"};
+       # Do not: $W->{"r"}->args()
+       # as it parses only QUERY_STRING (not POST data).
+       $W->{"args"}={ CGI->new()->Vars() };
+       for (keys(%{$W->{"args"}})) {
+               my @vals=split /\x00/,$W->{"args"}{$_};
+               next if @vals<=1;
+               $W->{"args"}{$_}=[@vals];
+               }
+
+       do { $W->{$_}=$ENV{"HTTP_ACCEPT"} if !defined $W->{$_}; } for ("accept");
+       do { $W->{$_}=$ENV{"HTTP_USER_AGENT"} if !defined $W->{$_}; } for ("user_agent");
+
+       $W->{"browser"}=HTTP::BrowserDetect->new($W->{"user_agent"});
 
-       undef $WebConfig{"viewcvs"} if $ENV{"SCRIPT_NAME"} && $WebConfig{"viewcvs"} eq $ENV{"SCRIPT_NAME"};
-       my @cvs_id_split=split / +/,$::CVS_ID;
-       if (@cvs_id_split==8) {
-               $cvs_id_split[2]=""
-                               ."<a href=\"".map({ s#/viewcvs/#&~checkout~/#; } $WebConfig{"viewcvs"})."?rev=".$cvs_id_split[2]."\">"
-                               .$cvs_id_split[2]."</a>";
-               $cvs_id_split[1]="<a href=\"".$WebConfig{"viewcvs"}."\">".$cvs_id_split[1]."</a>";
-               $cvs_id_split[5]="<a href=\"mailto:".$WebConfig{"admin_mail"}."\">".$cvs_id_split[5]."</a>";
+       if (!defined $W->{"have_style"}) {
+               $W->{"have_style"}=(!$W->{"browser"}->netscape() || $W->{"browser"}->major>4 ? 1 : 0);
                }
-       $cvs_id_html=join " ",@cvs_id_split;
-}
 
-# $args{"ListItem"}=\%...;
-sub init_project ($%)
-{
-my($class,%args)=@_;
+       $W->{"have_js"}=($W->{"args"}{"have_js"} ? 1 : 0);
+       if ($W->{"detect_js"} && !$W->{"have_js"}) {
+               $W->{"head"}.='<script type="text/javascript" src="'.top_dir('/have_js.js.pl').'"></script>'."\n";
+               }
 
-       my $ListItem=$args{"ListItem"};
-       my $name=$ListItem->{"name"};
-       $name=~s#<a\s[^>]*>([^<]*)</a>#$1#g;
-       init($class,
-                       "title"=>$name,
-                       %args);
-       heading();
-       print "<h1>".$ListItem->{"name"}."</h1>\n";
-       print $ListItem->{"description"};
-       print "<hr />\n";
-       my @table=(
-               {"key"=>qr(^download\b.*),"text"=>sub ($) {
-                                               $_[0]=~s/^download//;
-                                               $_[0]=~s/^-/ /;
-                                               return "Download".$_[0];
-                                               },
-                               "format"=>sub ($) {
-                                               my $r;
-                                               if ($_[0]=~m#^[a-z]+://#) {
-                                                       $r="<a href=\"".$_[0]."\">".CGI::escapeHTML($_[0])."</a>";
-                                                       }
-                                               else {
-                                                       $r="<a href=\"".$_[0]."\">".CGI::escapeHTML(basename($_[0]))."</a>";
-                                                       my $size=(stat $_[0])[7];
-                                                       die "Cannot stat \"".$_[0]."\": $!" if !defined $size;
-                                                                if ($size>=1024*1024) { $size=int($size/(1024*1024))." MB"; }
-                                                       elsif ($size>=1024     ) { $size=int($size/(1024     ))." KB"; }
-                                                       else                     { $size=int($size            )." B"; }
-                                                       $r.=" ($size)";
-                                                       }
-                                               return $r;
-                                               }},
-               {"key"=>qr(^link\b.*),"text"=>sub ($) {
-                                               $_[0]=~s/^link-//;
-                                               return $_[0];
-                                               },
-                               "format"=>sub ($) {
-                                               return "<a href=\"".$_[0]."\">".CGI::escapeHTML($_[0])."</a>";
-                                               }},
-               {"key"=>"summary","text"=>"Summary"},
-               {"key"=>"ownership","text"=>"Ownership"},
-               {"key"=>"license","text"=>"License","format"=>sub ($) {
-                               my %known=(
-                                               "PD"=>"Public Domain",
-                                               "GPL"=>"<a href=\"http://www.gnu.org/licenses/gpl.html\">GNU General Public License</a>",
-                                               "LGPL"=>"<a href=\"http://www.gnu.org/licenses/lgpl.html\">GNU Lesser General Public License</a>",
-                                               );
-                               return $known{$_[0]};
-                               }},
-               {"key"=>"maintenance","text"=>"Currently maintained?","format"=>sub ($) {
-                               my %known=(
-                                               "finished"=>"Project is finished. No serious bugs known. No new features planned.",
-                                               "dead"=>"Project became dead code. Some updates may be needed. It is no longer used.",
-                                               );
-                               return $known{$_[0]};
-                               }},
-               {"key"=>"language","text"=>"Programming language","format"=>sub ($) {
-                               return "<a href=\"http://java.sun.com/\">".CGI::escapeHTML($_[0])."</a>"
-                                               if $_[0]=~/^Java\b/;
-                               return "<a href=\"http://www.php.net/\">".CGI::escapeHTML($_[0])."</a>"
-                                               if $_[0]=~/^PHP\b/;
-                               return undef();
-                               }},
-               );
-       print '<table border="0">'."\n";
+       do { args_check(%$_) if $_; } for ($W->{"args_check"});
+
+       return $W;
+}
 
-sub tableit_func
+sub top_dir_disk ()
 {
-my($tableit,$val,$key)=@_;
+       do { return $_ if $_; } for ($W->{"top_dir"});
+       return $INC[0]; # fallback
+}
 
-       print "<tr><td>";
-       if (!ref $tableit->{"text"}) {
-               print $tableit->{"text"};
-               }
-       else {
-               my $textfunc=$tableit->{"text"};
-               print &$textfunc($key);
-               }
-       print ":</td>";
-       if ($tableit->{"format"}) {
-               my $format=$tableit->{"format"};
-               my $valn=&$format($val);
-               $val=$valn if defined $valn;
+sub top_dir (;$)
+{
+my($in)=@_;
+
+       if (my $uri=$ENV{"REQUEST_URI"}) {
+               $uri.="Index" if $uri=~m#/$#;
+               if (defined $in) {
+                       my($inpath,$inquery)=split /[?]/,$in,2;
+                       $inpath=~tr///cs;
+                       $uri=~tr///cs;
+                       for (;;) {
+                               my($in1 ,$in2 )=($in =~m#^(/[^/]+)(/.*)$#);
+                               my($uri1,$uri2)=($uri=~m#^(/[^/]+)(/.*)$#);
+                               last if !defined $in1 || !defined $uri1 || $in1 ne $uri1;
+                               $in=$in2;
+                               $uri=$uri2;
+                               }
+                       }
+               $uri=~s#^/*##;
+               $uri=~s#[^/]+#..#g;
+               $uri=File::Basename::dirname($uri);
+               my $r=$uri.(defined $in ? $in : "");
+#              1 while $r=~s#^[.]/##;
+#              $r="./$r" if $r=~m#^(?:?.*)$#;  # empty pathname?
+               return $r;
                }
-       print "<td>$val</td></tr>\n";
+       return top_dir_disk().$in;
 }
 
-       for my $tableit (@table) {
-               if (!ref $tableit->{"key"}) {
-                       tableit_func($tableit,$ListItem->{$tableit->{"key"}},$tableit->{"key"}) if $ListItem->{$tableit->{"key"}};
-                       }
-               else {
-                       for my $key (keys(%$ListItem)) {
-                               my $keyregex=$tableit->{"key"};
-                               next if $key!~/$keyregex/;
-                               tableit_func($tableit,$ListItem->{$key},$key);
-                               }
+sub fatal (;$);
+
+sub args_check (%)
+{
+my(%tmpl)=@_;
+
+       while (my($name,$regex)=each(%tmpl)) {
+               my $name_html="Parameter <span class=\"quote\">".CGI::escapeHTML($name)."</span>";
+               $W->{"args"}{$name}="" if !defined $W->{"args"}{$name};
+               $W->{"args"}{$name}=[ $W->{"args"}{$name} ] if !ref $W->{"args"}{$name} && ref $regex;
+               fatal "$name_html passed as multivar although singlevar expected"
+                               if ref $W->{"args"}{$name} && !ref $regex;
+               $regex=${$regex}[0] if ref $regex;
+               for my $val (!ref $W->{"args"}{$name} ? $W->{"args"}{$name} : @{$W->{"args"}{$name}}) {
+                       $val="" if !defined $val;
+                       fatal "$name_html <span class=\"quote\">".CGI::escapeHTML($val)."</span>"
+                                                       ." does not match the required regex <span class=\"quote\">".CGI::escapeHTML($regex)."</span> "
+                                       if $regex ne "" && $val!~/$regex/;
                        }
                }
-       print "</table>\n";
+}
+
+sub vskip (;$)
+{
+my($height)=@_;
+
+       return '<p'.(!defined $height ? "" : ' style="height: '.$height.';"').'>&nbsp;</p>'."\n";
 }
 
 sub fatal (;$)
@@ -167,98 +239,372 @@ my($msg)=@_;
 
        $msg="UNKNOWN" if !$msg;
 
-#      heading(false/*title*/,false/*indexme*/); // notitle is always safe, don't index the error message
-       print("\n<p>&nbsp;<br />&nbsp;</p><hr /><h1 class=\"error\">FATAL ERROR: $msg!</h1>\n"
+       $W->{"indexme"}=0;      # For the case no heading was sent yet.
+       My::Web->heading();
+       print "\n".vskip("3ex")."<hr /><h1 class=\"error\">FATAL ERROR: $msg!</h1>\n"
                        ."<p>You can report this problem's details to"
-                       ." <a href=\"mailto:".$WebConfig{"admin_mail"}."\">admin of this website</a>.</p>\n");
-#      footer();
+                       ." ".a_href("mailto:".$W->{"admin_mail"},"admin of this website").".</p>\n";
+       footer();
 }
 
-my $footer_passed;
 sub footer (;$)
 {
-my($delimit)=@_;
+       exit 1 if $W->{"footer_passed"}++;      # deadlock prevention:
+
+       print vskip if $W->{"footer_delimit"};
+
+       if ($W->{"heading"}) {
+               do { &{$_}() if $_; } for ($W->{"footing_delimit"});
+               }
+
+       print "<hr />\n" if $W->{"footer"};
+
+       if ($W->{"footer_mailme"}) {
+               print '<form action="'.top_dir('/SendMsg.pl').'" method="post" onsubmit="'
+                               ."this.elements['msgscript'].value=this.elements['msghtml'].value;"
+                               ."this.elements['msghtml'].value='';"
+                               ."this.submit();"
+                               .'">'."\n";
+                       print '<p align="right">'."\n";
+                               print '<input name="msgscript" type="hidden" />'."\n";
+                               print '<input name="msghtml" type="text" size="32" alt="Message" />'."\n";
+                               print '<input name="submit" type="submit" value="Quick Note" />'."\n";
+                       print '</p>'."\n";
+               print '</form>'."\n";
+               }
+
+       if ($W->{"footer_ids"}) {
+               print '<p class="cvs-id">';
+               print join("<br />\n",map({ my $package=$_;
+                       my $cvs_id=(eval('$'.$package."::CVS_ID")
+#                                      || $package     # debug
+                                       );
+                       if (!$cvs_id) {
+                               ();
+                               }
+                       else {
+                               $cvs_id='$'.$cvs_id.'$';        # Eaten by 'q' operator.
+                               my @cvs_id_split=split / +/,$cvs_id;
+                               if (@cvs_id_split==8) {
+                                       my $file=$package;
+                                       $file=~s#::#/#g;
+                                       my $ext;
+                                       for (qw(.html.pl .pl .pm),"") {
+                                               $ext=$_;
+                                               last if -r top_dir_disk()."/$file$ext";
+                                               cluck "Class file $file not found" if !$ext;
+                                               }
+                                       $file.=$ext;
+                                       $cvs_id_split[2]=""
+                                                       .a_href((map({ my $s=$_; $s=~s#/viewcvs/#$&~checkout~/#; $s; } $W->{"viewcvs"}))[0]."$file?rev=".$cvs_id_split[2],
+                                                                       $cvs_id_split[2]);
+                                       $cvs_id_split[1]=a_href($W->{"viewcvs"}.$file,
+                                                       ($package!~/^Apache::/ ? $package : $cvs_id_split[1]));
+                                       $cvs_id_split[5]=&{$W->{"cvs_id_author"}}($cvs_id_split[5]);
+                                       }
+                               join " ",@cvs_id_split;
+                               }
+                       } (
+                                       $W->{"__PACKAGE__"},
+                                       __PACKAGE__,
+                                       @{$W->{"packages_used"}{$Apache::Registry::curstash}},
+                                       )));
+               print "</p>\n";
+               }
 
-       $delimit=1 if !defined $delimit;
+       for my $package (
+                       $W->{"__PACKAGE__"},
+                       __PACKAGE__,
+                       @{$W->{"packages_used"}{$Apache::Registry::curstash}},
+                       ) {
+               my $cvs_id=(eval('$'.$package."::CVS_ID")
+#                              || $package     # debug
+                               );
+               print '<!-- '.$package.' - $'.$cvs_id.'$ -->'."\n" if $cvs_id;
+               }
 
-       exit(1) if $footer_passed++;    # deadlock prevention:
+       if ($W->{"heading"}) {
+               do { &{$_}() if $_; } for ($W->{"footing"});
+               }
 
-       print "<p>&nbsp;</p>\n" if $delimit;
-       print "<hr />\n<p class=\"cvs-id\">$cvs_id_html</p>\n";
        print "</body></html>\n";
        exit(0);
 }
 
-my $heading_done;
-
-my %headers;
-my %headers_lc;        # maps lc($headers_key)=>$headers_key
 sub header (%)
 {
 my(%pairs)=@_;
 
        while (my($key,$val)=each(%pairs)) {
-               do { warn "Headers already sent"; next; } if $heading_done;
-               for ($headers_lc{lc $key} || ()) {
-                       delete $headers{$_};
+               do { cluck "Headers already sent"; next; } if $W->{"heading_done"};
+               for ($W->{"headers_lc"}{lc $key} || ()) {
+                       delete $W->{"headers"}{$_};
                        }
-               $headers_lc{lc $key}=$key;
-               $headers{$key}=$val;
+               $W->{"headers_lc"}{lc $key}=$key;
+               $W->{"headers"}{$key}=$val;
                }
 }
 
+sub size_display ($)
+{
+my($size)=@_;
+
+          if ($size<4096)
+               {}
+       elsif ($size<1024*1024)
+               { $size=sprintf "%.1fK",$size/1024; }
+       else
+               { $size=sprintf "%.1fM",$size/1024/1024; }
+       $size.="B";
+       return $size;
+}
+
+sub url_is_local ($)
+{
+my($url)=@_;
+
+       return $url!~m#^[a-z]+://#;
+}
+
+sub a_href ($;$%)
+{
+my($url,$contents,%args)=@_;
+
+       do { $$_=1 if !defined $$_; } for (\$args{"size"});
+       $contents=CGI::escapeHTML($url) if !defined $contents;
+       $contents=~s#<a\b[^>]*>##gi;
+       $contents=~s#</a>##gi;
+
+       $url=top_dir($url) if url_is_local $url && $url=~m#^/#;
+
+       my $r='<a href="';
+       my $urlent=CGI::escapeHTML($url);
+          if ($url eq $urlent)
+               { $r.=$url; }
+       elsif (url_is_local $url)
+               { $r.=$urlent; }
+       elsif (defined $W->{"have_ent"} && !$W->{"have_ent"})   # non-ent client
+               { $r.=$url; }
+       elsif ($W->{"have_ent"})        # ent client
+               { $r.=$urlent; }
+       else    # unknown client, &CGI::escapeHTML should not be needed here
+               { $r.=CGI::escapeHTML(top_dir('/Redirect.pl?location='.uri_escape($url))); }
+       $r.='"';
+       do { $r.=" $_" if $_; } for ($args{"attr"});
+       $r.='>'.$contents.'</a>';
+       if ($args{"size"} && url_is_local($url) && $url=~/[.](?:gz|Z|rpm|zip|deb|lha)/) {       # Downloadable?
+               $url=top_dir_disk().$url if $url=~m#^/#;
+               if (!-r $url)
+                       { cluck "File not readable: $url"; }
+               else {
+                       $r.='&nbsp;('.size_display((stat($url))[7]).')';
+                       }
+               }
+       return $r;
+}
+
+sub remote_ip ()
+{
+       # Do not: PerlModule                 Apache::ForwardedFor
+       #         PerlPostReadRequestHandler Apache::ForwardedFor
+       # As 'Apache::ForwardedFor' takes the first of $ENV{"HTTP_X_FORWARDED_FOR"}
+       # while the contents is '127.0.0.1, 213.220.195.171' if client has its own proxy.
+       # We must take the last item ourselves.
+       my $r=$ENV{"HTTP_X_FORWARDED_FOR"} || $W->{"r"}->get_remote_host();
+       $r=~s/^.*,\s*//;
+       return $r;
+}
+
+sub is_cz ()
+{
+       return "CZ" eq Geo::IP->new()->country_code_by_addr(remote_ip());
+}
+
+sub a_href_cz ($$;%)
+{
+my($url,$contents,%args)=@_;
+
+       return a_href $url,$contents,%args if is_cz();
+       return $contents;
+}
+
+sub make ($)
+{
+my($cmd)=@_;
+
+       system {'flock'} 'flock','-x',top_dir_disk(),$cmd.' >&2';
+}
+
 sub img_size ($$)
 {
 my($width,$height)=@_;
 
-       return((1 #$have_style TODO:dyn
-                       ? "style=\"border:0;width:${width}px;height:${height}px\"" : "border=\"0\"")
-                       ." width=\"$width\" height=\"$height\"");
+       return ($W->{"have_style"} ? "style=\"border:0;width:${width}px;height:${height}px\"" : "border=\"0\"")
+                       ." width=\"$width\" height=\"$height\"";
+}
+
+sub negotiate_variant (%)
+{
+my(%args)=@_;
+
+       my @fields=("id","qs","content-type","encoding","charset","lang","size");
+       return [ map(($args{$_}),@fields) ];
+}
+
+my @img_variants=(
+               { "id"=>"png","qs"=>1.0,"content-type"=>"image/png" },
+               { "id"=>"gif","qs"=>0.9,"content-type"=>"image/gif" },
+               );
+my $img_variants_re='[.](?:'.join('|',"jpeg",map(($_->{"id"}),@img_variants)).')$';
+
+sub img_src ($)
+{
+my($file_base)=@_;
+
+       if (!url_is_local($file_base)) {
+               return $file_base if !wantarray();
+               return ($file_base,$file_base);
+               }
+       # Known image extension?
+       if ($file_base=~m#$img_variants_re#o) {
+               return $file_base if !wantarray();
+               return ($file_base,$file_base) if $file_base!~m#^/#;
+               return (top_dir($file_base),top_dir_disk().$file_base);
+               }
+
+       my $file_base_disk;
+       my $file_base_uri;
+       if ($file_base!~m#^/#) {
+               $file_base_disk=$file_base_uri=$file_base;
+               }
+       else {
+               $file_base_disk=top_dir_disk().$file_base;
+               $file_base_uri=top_dir($file_base);
+               }
+
+       my @nego_variants;
+       for my $var (@img_variants) {
+               my $file=$file_base_disk.".".$var->{"id"};
+               # TODO: Somehow quickly check dependencies?
+               make('make -s --no-print-directory'
+                                               .' -C '."'".File::Basename::dirname($file)."' '".File::Basename::basename($file)."'")
+                               if !-f $file;
+               push @nego_variants,negotiate_variant(
+                               %$var,
+                               "size"=>(stat $file)[7],
+                               );
+               }
+       # Do not: ,$W->{"r"});
+       # but should we provide somehow either 'HTTP::Headers' or 'HTTP::Request' ?
+       my $ext=HTTP::Negotiate::choose(\@nego_variants);
+       $ext||=$img_variants[0]->{"id"};        # &HTTP::Negotiate::choose failed?
+
+       return $file_base_uri.".".$ext if !wantarray();
+       return ($file_base_uri.".".$ext,$file_base_disk.".".$ext);
 }
 
-sub img ($$;$)
+sub img ($$;%)
 {
-my($file,$alt,$attrs)=@_;
+my($file_base,$alt,%attr)=@_;
 
-       (my $file_det=$file)=~s/[.]mng$/.gif/;
-       my($width,$height)=Image::Size::imgsize($file_det);
+       my($file_uri,$file_disk)=img_src $file_base;
+       my($width,$height)=Image::Size::imgsize($file_disk);
+       $alt=~s/<[^>]*>//g;
        $alt=CGI::escapeHTML($alt);
-       return("<img src=\"$file\" alt=\"$alt\" title=\"$alt\" ".img_size($width,$height)
-                       .(!defined($attrs) ? "" : " ".$attrs)." />");
+       my $content="<img src=\"$file_uri\" alt=\"$alt\" title=\"$alt\" ".img_size($width,$height)
+                       .(!$attr{"attr"} ? "" : " ".$attr{"attr"})." />";
+       return a_href img_src($attr{"a_href_img"}),$content if $attr{"a_href_img"};
+       return a_href $attr{"a_href"},$content if $attr{"a_href"};
+       return $content;
+}
+
+sub centerimg
+{
+       my $r.="";
+       $r.='<table border="0" width="100%"><tr>'."\n";
+       @_=( [@_] ) if !ref $_[0];
+       for (@_) {
+               $r.="\t".'<td align="center">'.&{\&img}(@$_).'</td>'."\n";
+               }
+       $r.='</tr></table>'."\n";
+       return $r;
+}
+
+sub rightimg
+{
+my($text,@args_img)=@_;
+
+       # Workaround bug of 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)':
+       return <<"HERE";
+<table border="0" width="100%">
+       <col width="@{[ (!$W->{"browser"}->ie() ? "1*" : "90%" ) ]}" />
+       <col width="@{[ (!$W->{"browser"}->ie() ? "0*" : "10%" ) ]}" />
+       <tr>
+               <td align="left">
+                       @{[ $text ]}
+               </td>
+               <td align="right">
+                       @{[ &{\&img}(@args_img) ]}
+               </td>
+       </tr>
+</table>
+HERE
 }
 
+sub readfile ($$)
+{
+my($class,$filename)=@_;
+
+       local *F;
+       open F,$filename or die "Cannot open \"$filename\": $!";
+       local $/=undef();
+       my $data=<F>;
+       close F;
+       return $data;
+}
+
+sub arr_keys (@)
+{
+my(@arr)=@_;
+
+       my @r=();
+       while (@arr) {
+               push @r,shift @arr;     # key
+               shift @arr;     # val
+               }
+       return @r;
+}
 
-sub heading (;$$)
+sub heading ()
 {
-my($class,$showtitle,$indexme)=@_;
+my($class)=@_;
 
-       $showtitle=1 if !defined $showtitle;
-       $indexme=1 if !defined $indexme;
+       return if $W->{"heading_passed"}++;
 
        # $ENV{"CLIENT_CHARSET"} ignored (mod_czech support dropped!)
-       my $client_charset=$Args{"force_charset"} || "us-ascii";
-       header("Content-type"=>"text/html; charset=$client_charset");
+       my $client_charset=$W->{"force_charset"} || "us-ascii";
        header("Content-Style-Type"=>"text/css");
+       header("Content-Script-Type"=>"text/javascript");
 
-       if ($ENV{"SERVER_SOFTWARE"}) {
-               while (my($key,$val)=each(%headers)) {
-                       print "$key: $val\n";
-                       }
-               print "\n";
+       while (my($key,$val)=each(%{$W->{"headers"}})) {
+               $W->{"r"}->header_out($key,$val);
                }
+       $W->{"r"}->send_http_header("text/html; charset=$client_charset");      # "Content-type"; do not use header()
 
-       return if $heading_done++;
+       return if $W->{"heading_done"}++;
+       exit if $W->{"r"}->header_only();
 
        if (1) { # || !$msie_major || $msie_major>=4) # TODO:dyn
                print '<?xml version="1.0" encoding="'.$client_charset.'"?>'."\n";
                }
        print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'."\n";
-       print '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="cs">'."\n";
-       print '<head><title>'.CGI::escapeHTML($WebConfig{"title_prefix"})
-                       .join("",map({ ': '.CGI::escapeHTML($_); } ($WebConfig{"title"} || ())))
-                       .'</title>'."\n";
+       print '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">'."\n";
+       my $title=$W->{"title_prefix"}.join("",map({ ': '.$_; } ($W->{"title"} || ())));
+       $title=~s#<[^>]*>##g;
+       print "<head>";
+       print "<title>$title</title>\n";
 
-       if (1) { # || $have_css)        # TODO:dyn
+       if ($W->{"have_css"}) {
                print <<'HERE';
 <style type="text/css"><!--
 .cvs-id   { font-family: monospace; }
@@ -275,26 +621,27 @@ body {
 :link    { color: aqua;   background-color: transparent; }
 :visited { color: teal;   background-color: transparent; }
 h1,h2    { color: yellow; background-color: transparent; }
+td       { padding: 2px; }
+caption  { caption-side: bottom; }
 .footer img { vertical-align: middle; }
 HERE
-
-# TODO:dyn
-#              if (isset($head_css))
-#                      print(trim($head_css)."\n");
+               print $W->{"head_css"}."\n";
                print "--></style>\n";
                }
 
-       print '<meta name="robots" content="'.($indexme ? "" : "no" ).'index,follow" />'."\n";
-       print $_ for ($WebConfig{"head"} || ());
+       print '<meta name="robots" content="'.($W->{"indexme"} ? "" : "no" ).'index,follow" />'."\n";
+       print $W->{"head"};
+       for my $type (qw(prev next index contents start up)) {
+               do { print '<link rel="'.$type.'" href="'.$_.'" />'."\n" if $_; } for ($W->{"rel_$type"});
+               }
        print "</head><body";
-# TODO:dyn
-#      if (isset($mozilla_major) && $mozilla_major==4)
-#              print(" bgcolor=\"black\" text=\"white\" link=\"aqua\" vlink=\"teal\"");
+       print ' bgcolor="black" text="white" link="aqua" vlink="teal"'
+                       if $W->{"browser"}->netscape() && $W->{"browser"}->major<=4;
        print ">\n";
-#      if ($showtitle)
-#              print("<h1 class=\"centered\"><a href=\"/\">"
-#                              ."Energie & Peníze")
-#                              ."</a></h1>\n");
+
+       if ($W->{"heading"}) {
+               do { &{$_}() if $_; } for ($W->{"heading"});
+               }
 }
 
 1;