1 |
#============================================================= -*-perl-*- |
2 |
# |
3 |
# BackupPC::CGI::Lib package |
4 |
# |
5 |
# DESCRIPTION |
6 |
# |
7 |
# This library defines a BackupPC::Lib class and a variety of utility |
8 |
# functions used by BackupPC. |
9 |
# |
10 |
# AUTHOR |
11 |
# Craig Barratt <cbarratt@users.sourceforge.net> |
12 |
# |
13 |
# COPYRIGHT |
14 |
# Copyright (C) 2003 Craig Barratt |
15 |
# |
16 |
# This program is free software; you can redistribute it and/or modify |
17 |
# it under the terms of the GNU General Public License as published by |
18 |
# the Free Software Foundation; either version 2 of the License, or |
19 |
# (at your option) any later version. |
20 |
# |
21 |
# This program is distributed in the hope that it will be useful, |
22 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
23 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
24 |
# GNU General Public License for more details. |
25 |
# |
26 |
# You should have received a copy of the GNU General Public License |
27 |
# along with this program; if not, write to the Free Software |
28 |
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
29 |
# |
30 |
#======================================================================== |
31 |
# |
32 |
# Version 2.1.0, released 20 Jun 2004. |
33 |
# |
34 |
# See http://backuppc.sourceforge.net. |
35 |
# |
36 |
#======================================================================== |
37 |
|
38 |
package BackupPC::CGI::Lib; |
39 |
|
40 |
use strict; |
41 |
use BackupPC::Lib; |
42 |
|
43 |
require Exporter; |
44 |
|
45 |
use vars qw( @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS ); |
46 |
|
47 |
use vars qw($Cgi %In $MyURL $User %Conf $TopDir $BinDir $bpc); |
48 |
use vars qw(%Status %Info %Jobs @BgQueue @UserQueue @CmdQueue |
49 |
%QueueLen %StatusHost); |
50 |
use vars qw($Hosts $HostsMTime $ConfigMTime $PrivAdmin); |
51 |
use vars qw(%UserEmailInfo $UserEmailInfoMTime %RestoreReq %ArchiveReq); |
52 |
use vars qw($Lang); |
53 |
|
54 |
@ISA = qw(Exporter); |
55 |
|
56 |
@EXPORT = qw( ); |
57 |
|
58 |
@EXPORT_OK = qw( |
59 |
timeStamp2 |
60 |
HostLink |
61 |
UserLink |
62 |
EscHTML |
63 |
EscURI |
64 |
ErrorExit |
65 |
ServerConnect |
66 |
GetStatusInfo |
67 |
ReadUserEmailInfo |
68 |
CheckPermission |
69 |
GetUserHosts |
70 |
ConfirmIPAddress |
71 |
Header |
72 |
Trailer |
73 |
NavSectionTitle |
74 |
NavSectionStart |
75 |
NavSectionEnd |
76 |
NavLink |
77 |
h1 |
78 |
h2 |
79 |
$Cgi %In $MyURL $User %Conf $TopDir $BinDir $bpc |
80 |
%Status %Info %Jobs @BgQueue @UserQueue @CmdQueue |
81 |
%QueueLen %StatusHost |
82 |
$Hosts $HostsMTime $ConfigMTime $PrivAdmin |
83 |
%UserEmailInfo $UserEmailInfoMTime %RestoreReq %ArchiveReq |
84 |
$Lang |
85 |
); |
86 |
|
87 |
%EXPORT_TAGS = ( |
88 |
'all' => [ @EXPORT_OK ], |
89 |
); |
90 |
|
91 |
sub NewRequest |
92 |
{ |
93 |
$Cgi = new CGI; |
94 |
%In = $Cgi->Vars; |
95 |
|
96 |
if ( !defined($bpc) ) { |
97 |
ErrorExit($Lang->{BackupPC__Lib__new_failed__check_apache_error_log}) |
98 |
if ( !($bpc = BackupPC::Lib->new(undef, undef, 1)) ); |
99 |
$TopDir = $bpc->TopDir(); |
100 |
$BinDir = $bpc->BinDir(); |
101 |
%Conf = $bpc->Conf(); |
102 |
$Lang = $bpc->Lang(); |
103 |
$ConfigMTime = $bpc->ConfigMTime(); |
104 |
} elsif ( $bpc->ConfigMTime() != $ConfigMTime ) { |
105 |
$bpc->ServerMesg("log Re-read config file because mtime changed"); |
106 |
$bpc->ServerMesg("server reload"); |
107 |
} |
108 |
|
109 |
# |
110 |
# Default REMOTE_USER so in a miminal installation the user |
111 |
# has a sensible default. |
112 |
# |
113 |
$ENV{REMOTE_USER} = $Conf{BackupPCUser} if ( $ENV{REMOTE_USER} eq "" ); |
114 |
|
115 |
# |
116 |
# We require that Apache pass in $ENV{SCRIPT_NAME} and $ENV{REMOTE_USER}. |
117 |
# The latter requires .ht_access style authentication. Replace this |
118 |
# code if you are using some other type of authentication, and have |
119 |
# a different way of getting the user name. |
120 |
# |
121 |
$MyURL = $ENV{SCRIPT_NAME}; |
122 |
$User = $ENV{REMOTE_USER}; |
123 |
|
124 |
# |
125 |
# Clean up %ENV for taint checking |
126 |
# |
127 |
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; |
128 |
$ENV{PATH} = $Conf{MyPath}; |
129 |
|
130 |
# |
131 |
# Verify we are running as the correct user |
132 |
# |
133 |
if ( $Conf{BackupPCUserVerify} |
134 |
&& $> != (my $uid = (getpwnam($Conf{BackupPCUser}))[2]) ) { |
135 |
ErrorExit(eval("qq{$Lang->{Wrong_user__my_userid_is___}}"), <<EOF); |
136 |
This script needs to run as the user specified in \$Conf{BackupPCUser}, |
137 |
which is set to $Conf{BackupPCUser}. |
138 |
<p> |
139 |
This is an installation problem. If you are using mod_perl then |
140 |
it appears that Apache is not running as user $Conf{BackupPCUser}. |
141 |
If you are not using mod_perl, then most like setuid is not working |
142 |
properly on BackupPC_Admin. Check the permissions on |
143 |
$Conf{CgiDir}/BackupPC_Admin and look at the documentation. |
144 |
EOF |
145 |
} |
146 |
|
147 |
if ( !defined($Hosts) || $bpc->HostsMTime() != $HostsMTime ) { |
148 |
$HostsMTime = $bpc->HostsMTime(); |
149 |
$Hosts = $bpc->HostInfoRead(); |
150 |
|
151 |
# turn moreUsers list into a hash for quick lookups |
152 |
foreach my $host (keys %$Hosts) { |
153 |
$Hosts->{$host}{moreUsers} = |
154 |
{map {$_, 1} split(",", $Hosts->{$host}{moreUsers}) } |
155 |
} |
156 |
} |
157 |
} |
158 |
|
159 |
sub timeStamp2 |
160 |
{ |
161 |
my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) |
162 |
= localtime($_[0] == 0 ? time : $_[0] ); |
163 |
$mon++; |
164 |
if ( $Conf{CgiDateFormatMMDD} ) { |
165 |
return sprintf("$mon/$mday %02d:%02d", $hour, $min); |
166 |
} else { |
167 |
return sprintf("$mday/$mon %02d:%02d", $hour, $min); |
168 |
} |
169 |
} |
170 |
|
171 |
sub HostLink |
172 |
{ |
173 |
my($host) = @_; |
174 |
my($s); |
175 |
if ( defined($Hosts->{$host}) || defined($Status{$host}) ) { |
176 |
$s = "<a href=\"$MyURL?host=${EscURI($host)}\">$host</a>"; |
177 |
} else { |
178 |
$s = $host; |
179 |
} |
180 |
return \$s; |
181 |
} |
182 |
|
183 |
sub UserLink |
184 |
{ |
185 |
my($user) = @_; |
186 |
my($s); |
187 |
|
188 |
return \$user if ( $user eq "" |
189 |
|| $Conf{CgiUserUrlCreate} eq "" ); |
190 |
if ( $Conf{CgiUserHomePageCheck} eq "" |
191 |
|| -f sprintf($Conf{CgiUserHomePageCheck}, $user, $user, $user) ) { |
192 |
$s = "<a href=\"" |
193 |
. sprintf($Conf{CgiUserUrlCreate}, $user, $user, $user) |
194 |
. "\">$user</a>"; |
195 |
} else { |
196 |
$s = $user; |
197 |
} |
198 |
return \$s; |
199 |
} |
200 |
|
201 |
sub EscHTML |
202 |
{ |
203 |
my($s) = @_; |
204 |
$s =~ s/&/&/g; |
205 |
$s =~ s/\"/"/g; |
206 |
$s =~ s/>/>/g; |
207 |
$s =~ s/</</g; |
208 |
$s =~ s{([^[:print:]])}{sprintf("&\#x%02X;", ord($1));}eg; |
209 |
return \$s; |
210 |
} |
211 |
|
212 |
sub EscURI |
213 |
{ |
214 |
my($s) = @_; |
215 |
$s =~ s{([^\w.\/-])}{sprintf("%%%02X", ord($1));}eg; |
216 |
return \$s; |
217 |
} |
218 |
|
219 |
sub ErrorExit |
220 |
{ |
221 |
my(@mesg) = @_; |
222 |
my($head) = shift(@mesg); |
223 |
my($mesg) = join("</p>\n<p>", @mesg); |
224 |
|
225 |
if ( !defined($ENV{REMOTE_USER}) ) { |
226 |
$mesg .= <<EOF; |
227 |
<p> |
228 |
Note: \$ENV{REMOTE_USER} is not set, which could mean there is an |
229 |
installation problem. BackupPC_Admin expects Apache to authenticate |
230 |
the user and pass their user name into this script as the REMOTE_USER |
231 |
environment variable. See the documentation. |
232 |
EOF |
233 |
} |
234 |
|
235 |
$bpc->ServerMesg("log User $User (host=$In{host}) got CGI error: $head") |
236 |
if ( defined($bpc) ); |
237 |
if ( !defined($Lang->{Error}) ) { |
238 |
$mesg = <<EOF if ( !defined($mesg) ); |
239 |
There is some problem with the BackupPC installation. |
240 |
Please check the permissions on BackupPC_Admin. |
241 |
EOF |
242 |
my $content = <<EOF; |
243 |
${h1("Error: Unable to read config.pl or language strings!!")} |
244 |
<p>$mesg</p> |
245 |
EOF |
246 |
Header("BackupPC: Error", $content); |
247 |
Trailer(); |
248 |
} else { |
249 |
my $content = eval("qq{$Lang->{Error____head}}"); |
250 |
Header(eval("qq{$Lang->{Error}}"), $content); |
251 |
Trailer(); |
252 |
} |
253 |
exit(1); |
254 |
} |
255 |
|
256 |
sub ServerConnect |
257 |
{ |
258 |
# |
259 |
# Verify that the server connection is ok |
260 |
# |
261 |
return if ( $bpc->ServerOK() ); |
262 |
$bpc->ServerDisconnect(); |
263 |
if ( my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}) ) { |
264 |
if ( CheckPermission() |
265 |
&& -f $Conf{ServerInitdPath} |
266 |
&& $Conf{ServerInitdStartCmd} ne "" ) { |
267 |
my $content = eval("qq{$Lang->{Admin_Start_Server}}"); |
268 |
Header(eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server}}"), $content); |
269 |
Trailer(); |
270 |
exit(1); |
271 |
} else { |
272 |
ErrorExit(eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server}}")); |
273 |
} |
274 |
} |
275 |
} |
276 |
|
277 |
sub GetStatusInfo |
278 |
{ |
279 |
my($status) = @_; |
280 |
ServerConnect(); |
281 |
my $reply = $bpc->ServerMesg("status $status"); |
282 |
$reply = $1 if ( $reply =~ /(.*)/s ); |
283 |
eval($reply); |
284 |
# ignore status related to admin and trashClean jobs |
285 |
if ( $status =~ /\bhosts\b/ ) { |
286 |
foreach my $host ( grep(/admin/, keys(%Status)) ) { |
287 |
delete($Status{$host}) if ( $bpc->isAdminJob($host) ); |
288 |
} |
289 |
delete($Status{$bpc->trashJob}); |
290 |
} |
291 |
} |
292 |
|
293 |
sub ReadUserEmailInfo |
294 |
{ |
295 |
if ( (stat("$TopDir/log/UserEmailInfo.pl"))[9] != $UserEmailInfoMTime ) { |
296 |
do "$TopDir/log/UserEmailInfo.pl"; |
297 |
$UserEmailInfoMTime = (stat("$TopDir/log/UserEmailInfo.pl"))[9]; |
298 |
} |
299 |
} |
300 |
|
301 |
# |
302 |
# Check if the user is privileged. A privileged user can access |
303 |
# any information (backup files, logs, status pages etc). |
304 |
# |
305 |
# A user is privileged if they belong to the group |
306 |
# $Conf{CgiAdminUserGroup}, or they are in $Conf{CgiAdminUsers} |
307 |
# or they are the user assigned to a host in the host file. |
308 |
# |
309 |
sub CheckPermission |
310 |
{ |
311 |
my($host) = @_; |
312 |
my $Privileged = 0; |
313 |
|
314 |
return 0 if ( $User eq "" && $Conf{CgiAdminUsers} ne "*" |
315 |
|| $host ne "" && !defined($Hosts->{$host}) ); |
316 |
if ( $Conf{CgiAdminUserGroup} ne "" ) { |
317 |
my($n,$p,$gid,$mem) = getgrnam($Conf{CgiAdminUserGroup}); |
318 |
$Privileged ||= ($mem =~ /\b$User\b/); |
319 |
|
320 |
# check against REMOTE_NTGROUP from mod_ntlm |
321 |
$Privileged ||= $Conf{CgiAdminUserGroup} eq $ENV{REMOTE_NTGROUP}; |
322 |
} |
323 |
if ( $Conf{CgiAdminUsers} ne "" ) { |
324 |
$Privileged ||= ($Conf{CgiAdminUsers} =~ /\b$User\b/); |
325 |
$Privileged ||= $Conf{CgiAdminUsers} eq "*"; |
326 |
} |
327 |
$PrivAdmin = $Privileged; |
328 |
return $Privileged if ( !defined($host) ); |
329 |
|
330 |
$Privileged ||= $User eq $Hosts->{$host}{user}; |
331 |
$Privileged ||= defined($Hosts->{$host}{moreUsers}{$User}); |
332 |
return $Privileged; |
333 |
} |
334 |
|
335 |
# |
336 |
# Returns the list of hosts that should appear in the navigation bar |
337 |
# for this user. If $getAll is set, the admin gets all the hosts. |
338 |
# Otherwise, regular users get hosts for which they are the user or |
339 |
# are listed in the moreUsers column in the hosts file. |
340 |
# |
341 |
sub GetUserHosts |
342 |
{ |
343 |
my($getAll) = @_; |
344 |
my @hosts; |
345 |
|
346 |
if ( $getAll && CheckPermission() ) { |
347 |
@hosts = sort keys %$Hosts; |
348 |
} else { |
349 |
@hosts = sort grep { $Hosts->{$_}{user} eq $User || |
350 |
defined($Hosts->{$_}{moreUsers}{$User}) } keys(%$Hosts); |
351 |
} |
352 |
return @hosts; |
353 |
} |
354 |
|
355 |
# |
356 |
# Given a host name tries to find the IP address. For non-dhcp hosts |
357 |
# we just return the host name. For dhcp hosts we check the address |
358 |
# the user is using ($ENV{REMOTE_ADDR}) and also the last-known IP |
359 |
# address for $host. (Later we should replace this with a broadcast |
360 |
# nmblookup.) |
361 |
# |
362 |
sub ConfirmIPAddress |
363 |
{ |
364 |
my($host) = @_; |
365 |
my $ipAddr = $host; |
366 |
|
367 |
if ( defined($Hosts->{$host}) && $Hosts->{$host}{dhcp} |
368 |
&& $ENV{REMOTE_ADDR} =~ /^(\d+[\.\d]*)$/ ) { |
369 |
$ipAddr = $1; |
370 |
my($netBiosHost, $netBiosUser) = $bpc->NetBiosInfoGet($ipAddr); |
371 |
if ( $netBiosHost ne $host ) { |
372 |
my($tryIP); |
373 |
GetStatusInfo("host(${EscURI($host)})"); |
374 |
if ( defined($StatusHost{dhcpHostIP}) |
375 |
&& $StatusHost{dhcpHostIP} ne $ipAddr ) { |
376 |
$tryIP = eval("qq{$Lang->{tryIP}}"); |
377 |
($netBiosHost, $netBiosUser) |
378 |
= $bpc->NetBiosInfoGet($StatusHost{dhcpHostIP}); |
379 |
} |
380 |
if ( $netBiosHost ne $host ) { |
381 |
ErrorExit(eval("qq{$Lang->{Can_t_find_IP_address_for}}"), |
382 |
eval("qq{$Lang->{host_is_a_DHCP_host}}")); |
383 |
} |
384 |
$ipAddr = $StatusHost{dhcpHostIP}; |
385 |
} |
386 |
} |
387 |
return $ipAddr; |
388 |
} |
389 |
|
390 |
########################################################################### |
391 |
# HTML layout subroutines |
392 |
########################################################################### |
393 |
|
394 |
sub Header |
395 |
{ |
396 |
my($title, $content, $noBrowse, $contentSub, $contentPost) = @_; |
397 |
my @adminLinks = ( |
398 |
{ link => "", name => $Lang->{Status}}, |
399 |
{ link => "?action=adminOpts", name => $Lang->{Admin_Options}, |
400 |
priv => 1}, |
401 |
{ link => "?action=summary", name => $Lang->{PC_Summary}}, |
402 |
{ link => "?action=view&type=LOG", name => $Lang->{LOG_file}, |
403 |
priv => 1}, |
404 |
{ link => "?action=LOGlist", name => $Lang->{Old_LOGs}, |
405 |
priv => 1}, |
406 |
{ link => "?action=emailSummary", name => $Lang->{Email_summary}, |
407 |
priv => 1}, |
408 |
{ link => "?action=view&type=config", name => $Lang->{Config_file}, |
409 |
priv => 1}, |
410 |
{ link => "?action=view&type=hosts", name => $Lang->{Hosts_file}, |
411 |
priv => 1}, |
412 |
{ link => "?action=queue", name => $Lang->{Current_queues}, |
413 |
priv => 1}, |
414 |
{ link => "?action=search", name => $Lang->{Search_archive}, |
415 |
priv => 0}, |
416 |
{ link => "?action=burn", name => $Lang->{Burn_media}, |
417 |
priv => 1}, |
418 |
@{$Conf{CgiNavBarLinks} || []}, |
419 |
); |
420 |
my $host = $In{host}; |
421 |
|
422 |
print $Cgi->header(); |
423 |
print <<EOF; |
424 |
<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"> |
425 |
<html><head> |
426 |
<title>$title</title> |
427 |
<link rel=stylesheet type="text/css" href="$Conf{CgiImageDirURL}/$Conf{CgiCSSFile}" title="CSSFile"> |
428 |
$Conf{CgiHeaders} |
429 |
</head><body onLoad="document.getElementById('NavMenu').style.height=document.body.scrollHeight"> |
430 |
<a href="http://backuppc.sourceforge.net"><img src="$Conf{CgiImageDirURL}/logo.gif" hspace="5" vspace="7" border="0"></a><br> |
431 |
EOF |
432 |
|
433 |
if ( defined($Hosts) && defined($host) && defined($Hosts->{$host}) ) { |
434 |
print "<div class=\"NavMenu\">"; |
435 |
NavSectionTitle("${EscHTML($host)}"); |
436 |
print <<EOF; |
437 |
</div> |
438 |
<div class="NavMenu"> |
439 |
EOF |
440 |
NavLink("?host=${EscURI($host)}", |
441 |
"$host $Lang->{Home}", " class=\"navbar\""); |
442 |
NavLink("?action=browse&host=${EscURI($host)}", |
443 |
$Lang->{Browse}, " class=\"navbar\"") if ( !$noBrowse ); |
444 |
NavLink("?action=view&type=LOG&host=${EscURI($host)}", |
445 |
$Lang->{LOG_file}, " class=\"navbar\""); |
446 |
NavLink("?action=LOGlist&host=${EscURI($host)}", |
447 |
$Lang->{LOG_files}, " class=\"navbar\""); |
448 |
if ( -f "$TopDir/pc/$host/SmbLOG.bad" |
449 |
|| -f "$TopDir/pc/$host/SmbLOG.bad.z" |
450 |
|| -f "$TopDir/pc/$host/XferLOG.bad" |
451 |
|| -f "$TopDir/pc/$host/XferLOG.bad.z" ) { |
452 |
NavLink("?action=view&type=XferLOGbad&host=${EscURI($host)}", |
453 |
$Lang->{Last_bad_XferLOG}, " class=\"navbar\""); |
454 |
NavLink("?action=view&type=XferErrbad&host=${EscURI($host)}", |
455 |
$Lang->{Last_bad_XferLOG_errors_only}, |
456 |
" class=\"navbar\""); |
457 |
} |
458 |
if ( -f "$TopDir/pc/$host/config.pl" ) { |
459 |
NavLink("?action=view&type=config&host=${EscURI($host)}", |
460 |
$Lang->{Config_file}, " class=\"navbar\""); |
461 |
} |
462 |
print "</div>\n"; |
463 |
} |
464 |
print("<div id=\"Content\">\n$content\n"); |
465 |
if ( defined($contentSub) && ref($contentSub) eq "CODE" ) { |
466 |
while ( (my $s = &$contentSub()) ne "" ) { |
467 |
print($s); |
468 |
} |
469 |
} |
470 |
print($contentPost) if ( defined($contentPost) ); |
471 |
print <<EOF; |
472 |
<br><br><br> |
473 |
</div> |
474 |
<div class="NavMenu" id="NavMenu" style="height:100%"> |
475 |
EOF |
476 |
my $hostSelectbox = "<option value=\"#\">$Lang->{Select_a_host}</option>"; |
477 |
my @hosts = GetUserHosts($Conf{CgiNavBarAdminAllHosts}); |
478 |
if ( defined($Hosts) && %$Hosts > 0 && @hosts ) { |
479 |
NavSectionTitle($Lang->{Hosts}); |
480 |
foreach my $host ( @hosts ) { |
481 |
NavLink("?host=${EscURI($host)}", $host) |
482 |
if ( @hosts < $Conf{CgiNavBarAdminAllHosts} ); |
483 |
my $sel = " selected" if ( $host eq $In{host} ); |
484 |
$hostSelectbox .= "<option value=\"?host=${EscURI($host)}\"$sel>" |
485 |
. "$host</option>"; |
486 |
} |
487 |
} |
488 |
if ( @hosts >= $Conf{CgiNavBarAdminAllHosts} ) { |
489 |
print <<EOF; |
490 |
<br> |
491 |
<select onChange="document.location=this.value"> |
492 |
$hostSelectbox |
493 |
</select> |
494 |
<br><br> |
495 |
EOF |
496 |
} |
497 |
if ( $Conf{CgiSearchBoxEnable} ) { |
498 |
print <<EOF; |
499 |
<form action="$MyURL" method="get"> |
500 |
<input type="text" name="host" size="14" maxlength="64"> |
501 |
<input type="hidden" name="action" value="hostInfo"><input type="submit" value="$Lang->{Go}" name="ignore"> |
502 |
</form> |
503 |
EOF |
504 |
} |
505 |
NavSectionTitle($Lang->{NavSectionTitle_}); |
506 |
foreach my $l ( @adminLinks ) { |
507 |
if ( $PrivAdmin || !$l->{priv} ) { |
508 |
my $txt = defined($l->{lname}) ? $Lang->{$l->{lname}} : $l->{name}; |
509 |
NavLink($l->{link}, $txt); |
510 |
} |
511 |
} |
512 |
|
513 |
print <<EOF; |
514 |
<br><br><br> |
515 |
</div> |
516 |
EOF |
517 |
} |
518 |
|
519 |
sub Trailer |
520 |
{ |
521 |
print <<EOF; |
522 |
</body></html> |
523 |
EOF |
524 |
} |
525 |
|
526 |
|
527 |
sub NavSectionTitle |
528 |
{ |
529 |
my($head) = @_; |
530 |
print <<EOF; |
531 |
<div class="NavTitle">$head</div> |
532 |
EOF |
533 |
} |
534 |
|
535 |
sub NavSectionStart |
536 |
{ |
537 |
} |
538 |
|
539 |
sub NavSectionEnd |
540 |
{ |
541 |
} |
542 |
|
543 |
sub NavLink |
544 |
{ |
545 |
my($link, $text) = @_; |
546 |
if ( defined($link) ) { |
547 |
my($class); |
548 |
$class = " class=\"NavCurrent\"" |
549 |
if ( length($link) && $ENV{REQUEST_URI} =~ /\Q$link\E$/ |
550 |
|| $link eq "" && $ENV{REQUEST_URI} !~ /\?/ ); |
551 |
$link = "$MyURL$link" if ( $link eq "" || $link =~ /^\?/ ); |
552 |
print <<EOF; |
553 |
<a href="$link"$class>$text</a> |
554 |
EOF |
555 |
} else { |
556 |
print <<EOF; |
557 |
$text<br> |
558 |
EOF |
559 |
} |
560 |
} |
561 |
|
562 |
sub h1 |
563 |
{ |
564 |
my($str) = @_; |
565 |
return \<<EOF; |
566 |
<div class="h1">$str</div> |
567 |
EOF |
568 |
} |
569 |
|
570 |
sub h2 |
571 |
{ |
572 |
my($str) = @_; |
573 |
return \<<EOF; |
574 |
<div class="h2">$str</div> |
575 |
EOF |
576 |
} |