use base qw(Exporter);
use Debbugs::URI;
use HTML::Entities;
-use Debbugs::Common qw(getparsedaddrs);
+use Debbugs::Common qw(getparsedaddrs make_list);
use Params::Validate qw(validate_with :types);
use Debbugs::Config qw(:config);
use Debbugs::Status qw(splitpackages isstrongseverity);
use POSIX qw(ceil);
use Storable qw(dclone);
+use Carp;
+
use Debbugs::Text qw(fill_in_template);
our %URL_PARAMS = ();
@EXPORT = ();
%EXPORT_TAGS = (url => [qw(bug_url bug_links bug_linklist maybelink),
qw(set_url_params pkg_url version_url),
- qw(submitterurl mainturl munge_url)
+ qw(submitterurl mainturl munge_url),
+ qw(package_links bug_links),
],
html => [qw(html_escape htmlize_bugs htmlize_packagelinks),
qw(maybelink htmlize_addresslinks htmlize_maintlinks),
=head HTML
-=head2 htmlize_bugs
+=head2 htmlize_packagelinks
- htmlize_bugs({bug=>1,status=>\%status,extravars=>\%extra},{bug=>...}});
+ htmlize_packagelinks
-Turns a list of bugs into an html snippit of the bugs.
+Given a scalar containing a list of packages separated by something
+that L<Debbugs::CGI/splitpackages> can separate, returns a
+formatted set of links to packages.
=cut
-# htmlize_bugs(bugs=>[@bugs]);
-sub htmlize_bugs{
- my @bugs = @_;
- my @html;
-
- for my $bug (@bugs) {
- my $html = sprintf "<li><a href=\"%s\">#%d: %s</a>\n<br>",
- bug_url($bug->{bug}), $bug->{bug}, html_escape($bug->{status}{subject});
- $html .= htmlize_bugstatus($bug->{status}) . "\n";
- }
- return @html;
+
+sub htmlize_packagelinks {
+ my ($pkgs) = @_;
+ return '' unless defined $pkgs and $pkgs ne '';
+ my @pkglist = splitpackages($pkgs);
+
+ carp "htmlize_packagelinks is deprecated";
+
+ return 'Package' . (@pkglist > 1 ? 's' : '') . ': ' .
+ join(', ',
+ package_links(package =>\@pkglist,
+ class => 'submitter'
+ )
+ );
}
+=head2 package_links
-sub htmlize_bugstatus {
- my %status = %{$_[0]};
+ join(', ', package_links(packages => \@packages))
- my $result = "";
+Given a list of packages, return a list of html which links to the package
- my $showseverity;
- if ($status{severity} eq $config{default_severity}) {
- $showseverity = '';
- } elsif (isstrongseverity($status{severity})) {
- $showseverity = "Severity: <em class=\"severity\">$status{severity}</em>;\n";
- } else {
- $showseverity = "Severity: <em>$status{severity}</em>;\n";
- }
+=over
- $result .= htmlize_packagelinks($status{"package"}, 1);
+=item package -- arrayref or scalar of package(s)
- my $showversions = '';
- if (@{$status{found_versions}}) {
- my @found = @{$status{found_versions}};
- local $_;
- s{/}{ } foreach @found;
- $showversions .= join ', ', map html_escape($_), @found;
- }
- if (@{$status{fixed_versions}}) {
- $showversions .= '; ' if length $showversions;
- $showversions .= '<strong>fixed</strong>: ';
- my @fixed = @{$status{fixed_versions}};
- $showversions .= join ', ', map {s#/##; html_escape($_)} @fixed;
+=item submitter -- arrayref or scalar of submitter(s)
+
+=item source -- arrayref or scalar of source(s)
+
+=item maintainer -- arrayref or scalar of maintainer(s)
+
+=item links_only -- return only links, not htmlized links, defaults to
+returning htmlized links.
+
+=item class -- class of the a href, defaults to ''
+
+=back
+
+=cut
+
+sub package_links {
+ my %param = validate_with(params => \@_,
+ spec => {package => {type => SCALAR|ARRAYREF,
+ optional => 1,
+ },
+ source => {type => SCALAR|ARRAYREF,
+ optional => 1,
+ },
+ maintainer => {type => SCALAR|ARRAYREF,
+ optional => 1,
+ },
+ submitter => {type => SCALAR|ARRAYREF,
+ optional => 1,
+ },
+ owner => {type => SCALAR|ARRAYREF,
+ optional => 1,
+ },
+ links_only => {type => BOOLEAN,
+ default => 0,
+ },
+ class => {type => SCALAR,
+ default => '',
+ },
+ separator => {type => SCALAR,
+ default => ', ',
+ },
+ },
+ );
+ my @links = ();
+ push @links, map {(pkg_url(source => $_),$_)
+ } make_list($param{source}) if exists $param{source};
+ push @links, map {my $addr = getparsedaddrs($_);
+ $addr = defined $addr?$addr->address:'';
+ (pkg_url(maint => $addr),$_)
+ } make_list($param{maintainer}) if exists $param{maintainer};
+ push @links, map {my $addr = getparsedaddrs($_);
+ $addr = defined $addr?$addr->address:'';
+ (pkg_url(owner => $addr),$_)
+ } make_list($param{owner}) if exists $param{owner};
+ push @links, map {my $addr = getparsedaddrs($_);
+ $addr = defined $addr?$addr->address:'';
+ (pkg_url(submitter => $addr),$_)
+ } make_list($param{submitter}) if exists $param{submitter};
+ push @links, map {(pkg_url(pkg => $_),
+ html_escape($_))
+ } make_list($param{package}) if exists $param{package};
+ my @return = ();
+ my ($link,$link_name);
+ my $class = '';
+ if (length $param{class}) {
+ $class = q( class=").html_escape($param{class}).q(");
}
- $result .= " ($showversions)" if length $showversions;
- $result .= ";\n";
-
- $result .= $showseverity;
- $result .= htmlize_addresslinks("Reported by: ", \&submitterurl,
- $status{originator});
- $result .= ";\nOwned by: " . html_escape($status{owner})
- if length $status{owner};
- $result .= ";\nTags: <strong>"
- . html_escape(join(", ", sort(split(/\s+/, $status{tags}))))
- . "</strong>"
- if (length($status{tags}));
-
- $result .= ";\nMerged with ".
- bug_linklist(', ',
- 'submitter',
- split(/ /,$status{mergedwith}))
- if length $status{mergedwith};
- $result .= ";\nBlocked by ".
- bug_linklist(", ",
- 'submitter',
- split(/ /,$status{blockedby}))
- if length $status{blockedby};
- $result .= ";\nBlocks ".
- bug_linklist(", ",
- 'submitter',
- split(/ /,$status{blocks})
- )
- if length $status{blocks};
-
- my $days = 0;
- if (length($status{done})) {
- $result .= "<br><strong>Done:</strong> " . html_escape($status{done});
- $days = ceil($debbugs::gRemoveAge - -M buglog($status{id}));
- if ($days >= 0) {
- $result .= ";\n<strong>Will be archived" . ( $days == 0 ? " today" : $days == 1 ? " in $days day" : " in $days days" ) . "</strong>";
- } else {
- $result .= ";\n<strong>Archived</strong>";
+ while (($link,$link_name) = splice(@links,0,2)) {
+ if ($param{links_only}) {
+ push @return,$link
}
+ else {
+ push @return,
+ qq(<a$class href=").
+ html_escape($link).q(">).
+ html_escape($link_name).q(</a>);
+ }
+ }
+ if (wantarray) {
+ return @return;
}
else {
- if (length($status{forwarded})) {
- $result .= ";\n<strong>Forwarded</strong> to "
- . maybelink($status{forwarded});
- }
- my $daysold = int((time - $status{date}) / 86400); # seconds to days
- if ($daysold >= 7) {
- my $font = "";
- my $efont = "";
- $font = "em" if ($daysold > 30);
- $font = "strong" if ($daysold > 60);
- $efont = "</$font>" if ($font);
- $font = "<$font>" if ($font);
-
- my $yearsold = int($daysold / 365);
- $daysold -= $yearsold * 365;
-
- $result .= ";\n $font";
- my @age;
- push @age, "1 year" if ($yearsold == 1);
- push @age, "$yearsold years" if ($yearsold > 1);
- push @age, "1 day" if ($daysold == 1);
- push @age, "$daysold days" if ($daysold > 1);
- $result .= join(" and ", @age);
- $result .= " old$efont";
- }
- }
+ return join($param{separator},@return);
+ }
+}
- $result .= ".";
+=head2 bug_links
- return $result;
-}
+ join(', ', bug_links(bug => \@packages))
-=head2 htmlize_packagelinks
+Given a list of bugs, return a list of html which links to the bugs
- htmlize_packagelinks
+=over
-Given a scalar containing a list of packages separated by something
-that L<Debbugs::CGI/splitpackages> can separate, returns a
-formatted set of links to packages.
+=item bug -- arrayref or scalar of bug(s)
-=cut
+=item links_only -- return only links, not htmlized links, defaults to
+returning htmlized links.
-sub htmlize_packagelinks {
- my ($pkgs,$strong) = @_;
- return unless defined $pkgs and $pkgs ne '';
- my @pkglist = splitpackages($pkgs);
+=item class -- class of the a href, defaults to ''
- $strong = 0;
- my $openstrong = $strong ? '<strong>' : '';
- my $closestrong = $strong ? '</strong>' : '';
+=back
- return 'Package' . (@pkglist > 1 ? 's' : '') . ': ' .
- join(', ',
- map {
- '<a class="submitter" href="' . pkg_url(pkg=>$_||'') . '">' .
- $openstrong . html_escape($_) . $closestrong . '</a>'
- } @pkglist
- );
+=cut
+
+sub bug_links {
+ my %param = validate_with(params => \@_,
+ spec => {bug => {type => SCALAR|ARRAYREF,
+ optional => 1,
+ },
+ links_only => {type => BOOLEAN,
+ default => 0,
+ },
+ class => {type => SCALAR,
+ default => '',
+ },
+ },
+ );
+ my @links;
+ push @links, map {(bug_url($_),$_)
+ } make_list($param{bug}) if exists $param{bug};
+ my @return;
+ my ($link,$link_name);
+ my $class = '';
+ if (length $param{class}) {
+ $class = q( class=").html_escape($param{class}).q(");
+ }
+ while (($link,$link_name) = splice(@links,0,2)) {
+ if ($param{links_only}) {
+ push @return,$link
+ }
+ else {
+ push @return,
+ qq(<a$class href=").
+ html_escape($link).q(">).
+ html_escape($link_name).q(</a>);
+ }
+ }
+ return @return;
}
+
=head2 maybelink
maybelink($in);
sub htmlize_addresslinks {
my ($prefixfunc, $urlfunc, $addresses,$class) = @_;
+ carp "htmlize_addresslinks is deprecated";
+
$class = defined $class?qq(class="$class" ):'';
if (defined $addresses and $addresses ne '') {
my @addrs = getparsedaddrs($addresses);
sub submitterurl { pkg_url(submitter => emailfromrfc822($_[0])); }
sub htmlize_maintlinks {
my ($prefixfunc, $maints) = @_;
+ carp "htmlize_maintlinks is deprecated";
return htmlize_addresslinks($prefixfunc, \&mainturl, $maints);
}
our $_maintainer;
our $_maintainer_rev;
-=head2 bug_links
-
- bug_links($one_bug);
- bug_links($starting_bug,$stoping_bugs,);
-
-Creates a set of links to bugs, starting with bug number
-$starting_bug, and finishing with $stoping_bug; if only one bug is
-passed, makes a link to only a single bug.
-
-The content of the link is the bug number.
-
-XXX Use L<Params::Validate>; we want to be able to support query
-arguments here too.
-
-=cut
-
-sub bug_links{
- my ($start,$stop,$query_arguments) = @_;
- $stop = $stop || $start;
- $query_arguments ||= '';
- my @output;
- for my $bug ($start..$stop) {
- push @output,'<a href="'.bug_url($bug,'').qq(">$bug</a>);
- }
- return join(', ',@output);
-}
-
=head2 bug_linklist
bug_linklist($separator,$class,@bugs)
sub bug_linklist{
my ($sep,$class,@bugs) = @_;
- if (length $class) {
- $class = qq(class="$class" );
- }
- return join($sep,map{qq(<a ${class}href=").
- bug_url($_).qq(">#$_</a>)
- } @bugs);
+ return join($sep,bug_links(bug=>\@bugs,class=>$class));
}
use base qw(Exporter);
use IO::Scalar;
+use Params::Validate qw(validate_with :types);
+use Debbugs::MIME qw(convert_to_utf8 decode_rfc1522 create_mime_message);
+use Debbugs::CGI qw(:url :html :util);
+use POSIX qw(strftime);
BEGIN{
($VERSION) = q$Revision: 494 $ =~ /^Revision:\s+([^\s+])/;
=cut
sub display_entity {
- my %param = valid_with(params => \@_,
- spec => {entity => {type => OBJECT,
- },
- bug_num => {type => SCALAR,
- regex => qr/^\d+$/,
- },
- outer => {type => BOOLEAN,
- default => 1,
- },
- msg_num => {type => SCALAR,
- },
- attachments => {type => ARRAYREF,
- default => [],
- },
- output => {type => SCALARREF|HANDLE,
- default => \*STDOUT,
- },
- terse => {type => BOOLEAN,
- default => 0,
- },
- msg => {type => SCALAR,
- optional => 1,
- },
- attachment => {type => SCALAR,
- optional => 1,
- },
- }
- );
+ my %param = validate_with(params => \@_,
+ spec => {entity => {type => OBJECT,
+ },
+ bug_num => {type => SCALAR,
+ regex => qr/^\d+$/,
+ },
+ outer => {type => BOOLEAN,
+ default => 1,
+ },
+ msg_num => {type => SCALAR,
+ },
+ attachments => {type => ARRAYREF,
+ default => [],
+ },
+ output => {type => SCALARREF|HANDLE,
+ default => \*STDOUT,
+ },
+ terse => {type => BOOLEAN,
+ default => 0,
+ },
+ msg => {type => SCALAR,
+ optional => 1,
+ },
+ att => {type => SCALAR,
+ optional => 1,
+ },
+ trim_headers => {type => BOOLEAN,
+ default => 1,
+ },
+ }
+ );
my $entity = $param{entity};
my $ref = $param{bug_num};
- my $top = $param{outer}
+ my $top = $param{outer};
my $xmessage = $param{msg_num};
- if (defined ref($options) and
+ if (defined ref($param{output}) and
ref($param{output}) eq 'SCALAR' and
not UNIVERSAL::isa($param{output},'GLOB')) {
$param{output} = IO::Scalar->new($param{output});
if ($top and not $param{terse}) {
my $header = $entity->head;
print {$param{output}} "<pre class=\"headers\">\n";
- if ($trim_headers) {
+ if ($param{trim_headers}) {
my @headers;
foreach (qw(From To Cc Subject Date)) {
my $head_field = $head->get($_);
"($type, $disposition)]</pre>\n";
if (exists $param{msg} and exists $param{att} and
- $att == $#$attachments) {
+ $param{att} == $#$attachments) {
my $head = $entity->head;
chomp(my $type = $entity->effective_type);
my $body = $entity->stringify_body;
}
}
- return if not $top and $disposition eq 'attachment' and not defined($att);
+ return if not $top and $disposition eq 'attachment' and not defined($param{att});
return unless ($type =~ m[^text/?] and
$type !~ m[^text/(?:html|enriched)(?:;|$)]) or
$type =~ m[^application/pgp(?:;|$)] or
# flowed e-mails cause they don't really matter.
}
# Add links to URLs
- # We don't html escape here because we escape above
- $body =~ s{((ftp|http|https)://[\S~-]+?/?)((\>\;)?[)]?[']?[:.\,]?(\s|$))}
- {<a href=\"$1\">$1</a>$3}go;
+ # We don't html escape here because we escape above;
+ # wierd terminators are because of that
+ $body =~ s{((?:ftp|http|https|svn|ftps|rsync)://[\S~-]+?/?) # Url
+ ((?:\>\;)?[)]?(?:'|\&\#39\;)?[:.\,]?(?:\s|$)) # terminators
+ }{<a href=\"$1\">$1</a>$2}gox;
# Add links to bug closures
$body =~ s[(closes:\s*(?:bug)?\#?\s?\d+(?:,?\s*(?:bug)?\#?\s?\d+)*)]
[my $temp = $1;
handle_email_message($record->{text},
ref => $bug_number,
- msg_number => $msg_number,
+ msg_num => $msg_number,
);
Returns a decoded e-mail message and displays entities/attachments as
=cut
sub handle_email_message{
- my ($email,%options) = @_;
+ my ($email,%param) = @_;
my $output = '';
my $parser = new MIME::Parser;
my $entity = $parser->parse_data( $email);
my @attachments = ();
display_entity(entity => $entity,
- bug_num => $options{ref},
+ bug_num => $param{ref},
outer => 1,
- msg_number => $options{msg_number},
- ouput => $output,
+ msg_num => $param{msg_num},
+ output => \$output,
attachments => \@attachments,
- terse => $params{terse},
+ terse => $param{terse},
exists $param{msg}?(msg=>$param{msg}):(),
exists $param{att}?(attachment=>$param{att}):(),
);
# (even though these links already exist at the top)
$output =~ s,((?:ftp|http|https)://[\S~-]+?/?)([\)\'\:\.\,]?(?:\s|\.<|$)),<a href=\"$1\">$1</a>$2,go;
# Add links to the cloned bugs
- $output =~ s{(Bug )(\d+)( cloned as bugs? )(\d+)(?:\-(\d+)|)}{$1.bug_links($2).$3.bug_links($4,$5)}eo;
+ $output =~ s{(Bug )(\d+)( cloned as bugs? )(\d+)(?:\-(\d+)|)}{$1.bug_links(bug=>$2).$3.bug_links(bug=>[$4..$5])}eo;
# Add links to merged bugs
- $output =~ s{(?<=Merged )([\d\s]+)(?=\.)}{join(' ',map {bug_links($_)} (split /\s+/, $1))}eo;
+ $output =~ s{(?<=Merged )([\d\s]+)(?=\.)}{join(' ',map {bug_links(bug=>$_)} (split /\s+/, $1))}eo;
# Add links to blocked bugs
$output =~ s{(?<=Blocking bugs)(?:( of )(\d+))?( (?:added|set to|removed):\s+)([\d\s\,]+)}
- {(defined $2?$1.bug_links($2):'').$3.
- join(' ',map {bug_links($_)} (split /\,?\s+/, $4))}eo;
+ {(defined $2?$1.bug_links(bug=>$2):'').$3.
+ join(' ',map {bug_links(bug=>$_)} (split /\,?\s+/, $4))}eo;
# Add links to reassigned packages
$output =~ s{(Bug reassigned from package \`)([^']+?)((?:'|\&\#39;) to \`)([^']+?)((?:'|\&\#39;))}
{$1.q(<a href=").html_escape(pkg_url(pkg=>$2)).qq(">$2</a>).$3.q(<a href=").html_escape(pkg_url(pkg=>$4)).qq(">$4</a>).$5}eo;
if (defined $time) {
$output .= ' ('.strftime('%a, %d %b %Y %T GMT',gmtime($time)).') ';
}
- $output .= '<a href="' . html_escape(bug_url($ref, msg => ($msg_number+1))) . '">Full text</a> and <a href="' .
- html_escape(bug_url($ref, msg => ($msg_number+1), mbox => 'yes')) . '">rfc822 format</a> available.';
+ $output .= '<a href="' . html_escape(bug_url($bug_number, msg => ($msg_number+1))) . '">Full text</a> and <a href="' .
+ html_escape(bug_url($bug_number, msg => ($msg_number+1), mbox => 'yes')) . '">rfc822 format</a> available.';
$output = qq(<div class="$class"><hr>\n<a name="$msg_number"></a>\n) . $output . "</div>\n";
}
$$seen_msg_ids{$msg_id} = 1;
}
$output .= qq(<hr><p class="msgreceived"><a name="$msg_number"></a>\n);
- $output .= 'View this message in <a href="' . html_escape(bug_url($ref, msg=>$msg_number, mbox=>'yes')) . '">rfc822 format</a></p>';
+ $output .= 'View this message in <a href="' . html_escape(bug_url($bug_number, msg=>$msg_number, mbox=>'yes')) . '">rfc822 format</a></p>';
$output .= handle_email_message($record->{text},
- ref => $bug_number,
- msg_number => $msg_number,
- );
+ ref => $bug_number,
+ msg_num => $msg_number,
+ );
}
elsif (/autocheck/) {
# Do nothing
my ($received,$hostname) = $record->{text} =~ m/Received: \(at (\S+)\) by (\S+)\;/;
$output .= qq|<hr><p class="msgreceived"><a name="$msg_number"></a><a name="msg$msg_number"></a><a href="#$msg_number">Message #$msg_number</a> received at |.
html_escape("$received\@$hostname") .
- q| (<a href="| . html_escape(bug_url($ref, msg=>$msg_number)) . '">full text</a>'.
- q|, <a href="| . html_escape(bug_url($ref, msg=>$msg_number,mbox=>'yes')) .'">mbox</a>)'.":</p>\n";
+ q| (<a href="| . html_escape(bug_url($bug_number, msg=>$msg_number)) . '">full text</a>'.
+ q|, <a href="| . html_escape(bug_url($bug_number, msg=>$msg_number,mbox=>'yes')) .'">mbox</a>)'.":</p>\n";
$output .= handle_email_message($record->{text},
- ref => $bug_number,
- msg_number => $msg_number,
- );
+ ref => $bug_number,
+ msg_num => $msg_number,
+ );
}
else {
die "Unknown record type $_";
=cut
sub quit {
- print $DEBUG_FH "quitting >$_[0]<\n" if $DEBUG;
+ print {$DEBUG_FH} "quitting >$_[0]<\n" if $DEBUG;
my ($u);
while ($u= $cleanups[$#cleanups]) { &$u; }
die "*** $_[0]\n";
qw(@gDefaultArchitectures),
qw($gTemplateDir),
qw($gDefaultPackage),
- qw($gSpamMaxThreads $gSpamSpamsPerThread $gSpamKeepRunning $gSpamScan $gSpamCrossassassinDb)
+ qw($gSpamMaxThreads $gSpamSpamsPerThread $gSpamKeepRunning $gSpamScan $gSpamCrossassassinDb),
],
text => [qw($gBadEmailPrefix $gHTMLTail $gHTMLExpireNote),
],
@EXPORT_OK = ();
Exporter::export_ok_tags(qw(globals text config));
$EXPORT_TAGS{all} = [@EXPORT_OK];
+ $ENV{HOME} = '' if not defined $ENV{HOME};
}
use File::Basename qw(dirname);
set_default(\%config,'spam_rules_dir','/usr/share/spamassassin');
-
=back
]));
}
-sub decode_rfc1522 ($)
-{
+sub decode_rfc1522 {
my ($string) = @_;
# this is craptacular, but leading space is hacked off by unmime.
# We cannot use MIME::Words::encode_mimewords because that function
# does not handle spaces properly at all.
-sub encode_rfc1522 ($) {
+sub encode_rfc1522 {
my ($rawstr) = @_;
# handle being passed undef properly
=head1 SYNOPSIS
-use Debbugs::Text qw(:templates);
-print fill_in_template(template => 'cgi/foo');
+ use Debbugs::Text qw(:templates);
+ print fill_in_template(template => 'cgi/foo');
=head1 DESCRIPTION
qw(padsv padav padhv padany),
qw(rv2gv refgen srefgen ref),
qw(caller require entereval),
+ qw(gmtime sprintf prtf),
);
$safe->share('*STDERR');
$safe->share('%config');
my $tt;
if ($tt_type eq 'FILE' and
defined $tt_templates{$tt_source} and
- (stat $tt_source)[9] > $tt_templates{$tt_source}{mtime}
+ (stat $tt_source)[9] <= $tt_templates{$tt_source}{mtime}
) {
$tt = $tt_templates{$tt_source}{template};
}
}
$tt = Text::Template->new(TYPE => $tt_type,
SOURCE => $tt_source,
+ UNTAINT => 1,
);
if ($tt_type eq 'FILE') {
$tt_templates{$tt_source}{template} = $tt;
if (not defined $tt) {
die "Unable to create Text::Template for $tt_type:$tt_source";
}
- my $ret = $tt->fill_in(#(defined $param{nosafe} and $param{nosafe})?():(HASH=>$param{variables}),
- #(defined $param{nosafe} and $param{nosafe})?():(SAFE=>$safe),
- SAFE => $safe,
- #(defined $param{nosafe} and $param{nosafe})?(PACKAGE => 'main'):(),
+ my $ret = $tt->fill_in(SAFE => $safe,
defined $param{output}?(OUTPUT=>$param{output}):(),
);
if (not defined $ret) {
use warnings;
use strict;
-use POSIX qw(strftime tzset);
+use POSIX qw(strftime);
use MIME::Parser;
use MIME::Decoder;
use IO::Scalar;
# for read_log_records
use Debbugs::Log qw(read_log_records);
-use Debbugs::MIME qw(convert_to_utf8 decode_rfc1522 create_mime_message);
use Debbugs::CGI qw(:url :html :util);
use Debbugs::CGI::Bugreport qw(:all);
use Debbugs::Common qw(buglog getmaintainers);
use Debbugs::Status qw(splitpackages get_bug_status isstrongseverity);
use Scalar::Util qw(looks_like_number);
+
+use Debbugs::Text qw(:templates);
+
use CGI::Simple;
my $q = new CGI::Simple;
);
# This is craptacular.
-my $tail_html;
-
my $ref = $param{bug} or quitcgi("No bug number");
$ref =~ /(\d+)/ or quitcgi("Invalid bug number");
$ref = $1;
my $short = "#$ref";
-my $msg = $param{'msg'};
-my $att = $param{'att'};
+my ($msg) = $param{msg} =~ /^\d+$/ if exists $param{msg};
+my ($att) = $param{att} =~ /^\d+$/ if exists $param{att};
my $boring = $param{'boring'} eq 'yes';
my $terse = $param{'terse'} eq 'yes';
my $reverse = $param{'reverse'} eq 'yes';
my $repeatmerged = $param{'repeatmerged'} eq 'yes';
my $buglog = buglog($ref);
+my @stat = stat $buglog;
+my $mtime = '';
+if (@stat) {
+ $mtime = strftime '%a, %d %b %Y %T GMT', gmtime($stat[9]);
+}
-if (defined $ENV{REQUEST_METHOD} and $ENV{REQUEST_METHOD} eq 'HEAD' and not defined($att) and not $mbox) {
- print "Content-Type: text/html; charset=utf-8\n";
- my @stat = stat $buglog;
- if (@stat) {
- my $mtime = strftime '%a, %d %b %Y %T GMT', gmtime($stat[9]);
- print "Last-Modified: $mtime\n";
- }
- print "\n";
- exit 0;
+if ($q->request_method() eq 'HEAD' and not defined($att) and not $mbox) {
+ print $q->header(-type => "text/html",
+ -charset => 'utf-8',
+ (length $mtime)?(-last_modified => $mtime):(),
+ );
+ exit 0;
}
if ($buglog =~ m/\.gz$/) {
my $oldpath = $ENV{'PATH'};
$ENV{'PATH'} = '/bin:/usr/bin';
- $buglogfh = new IO::File "zcat $buglog |" or &quitcgi("open log for $ref: $!");
+ $buglogfh = IO::File->new("zcat $buglog |") or quitcgi("open log for $ref: $!");
$ENV{'PATH'} = $oldpath;
} else {
- $buglogfh = new IO::File "<$buglog" or &quitcgi("open log for $ref: $!");
+ $buglogfh = IO::File->new($buglog,'r') or quitcgi("open log for $ref: $!");
}
+my %status = %{get_bug_status(bug=>$ref)};
+
my @records;
eval{
@records = read_log_records($buglogfh);
my $tmain;
my $dtime = strftime "%a, %e %b %Y %T UTC", gmtime;
-$tail_html = $gHTMLTail;
-$tail_html =~ s/SUBSTITUTE_DTIME/$dtime/;
-my %status = %{get_bug_status(bug=>$ref)};
unless (%status) {
- print "Content-Type: text/html; charset=utf-8\n\n";
+ print $q->header(-type => "text/html",
+ -charset => 'utf-8',
+ (length $mtime)?(-last_modified => $mtime):(),
+ );
print fill_in_template(template=>'cgi/no_such_bug',
variables => {modify_time => $dtime,
bug_num => $ref,
},
- )
+ );
exit 0;
}
-$|=1;
+#$|=1;
-$tpack = lc $status{'package'};
-my @tpacks = splitpackages($tpack);
+my %package;
+my @packages = splitpackages($status{package});
-if ($status{severity} eq 'normal') {
- $showseverity = '';
-} elsif (isstrongseverity($status{severity})) {
- $showseverity = "Severity: <em class=\"severity\">$status{severity}</em>;\n";
-} else {
- $showseverity = "Severity: $status{severity};\n";
+foreach my $pkg (@packages) {
+ $package{$pkg} = {maintainer => exists($maintainer{$pkg}) ? $maintainer{$pkg} : '(unknown)',
+ source => exists($pkgsrc{$pkg}) ? $pkgsrc{$pkg} : '(unknown)',
+ package => $pkg,
+ };
}
+# fixup various bits of the status
+$status{tags_array} = [sort(split(/\s+/, $status{tags}))];
+$status{date_text} = strftime('%a, %e %b %Y %T UTC', gmtime($status{date}));
+$status{mergedwith_array} = [split(/ /,$status{mergedwith})];
+
+
+my $version_graph = '';
if (@{$status{found_versions}} or @{$status{fixed_versions}}) {
- $indexentry.= q(<div style="float:right"><a href=").
+ $version_graph = q(<a href=").
html_escape(version_url(package => $status{package},
found => $status{found_versions},
fixed => $status{fixed_versions},
- )).
+ )
+ ).
q("><img alt="version graph" src=").
- html_escape(version_url(package => $status{package},
- found => $status{found_versions},
- fixed => $status{fixed_versions},
- width => 2,
- height => 2,
- )).qq{"></a></div>};
-}
-
-
-$indexentry .= "<div class=\"msgreceived\">\n";
-$indexentry .= htmlize_packagelinks($status{package}, 0) . ";\n";
-
-foreach my $pkg (@tpacks) {
- my $tmaint = defined($maintainer{$pkg}) ? $maintainer{$pkg} : '(unknown)';
- my $tsrc = defined($pkgsrc{$pkg}) ? $pkgsrc{$pkg} : '(unknown)';
-
- $indexentry .=
- htmlize_maintlinks(sub { $_[0] == 1 ? "Maintainer for $pkg is\n"
- : "Maintainers for $pkg are\n" },
- $tmaint);
- $indexentry .= ";\nSource for $pkg is\n".
- '<a href="'.html_escape(pkg_url(src=>$tsrc))."\">$tsrc</a>" if ($tsrc ne "(unknown)");
- $indexentry .= ".\n";
-}
-
-$indexentry .= "<br>";
-$indexentry .= htmlize_addresslinks("Reported by: ", \&submitterurl,
- $status{originator}) . ";\n";
-$indexentry .= sprintf "Date: %s.\n",
- (strftime "%a, %e %b %Y %T UTC", localtime($status{date}));
-
-$indexentry .= "<br>Owned by: " . html_escape($status{owner}) . ".\n"
- if length $status{owner};
-
-$indexentry .= "</div>\n";
-
-my @descstates;
-
-$indexentry .= "<h3>$showseverity";
-$indexentry .= sprintf "Tags: %s;\n",
- html_escape(join(", ", sort(split(/\s+/, $status{tags}))))
- if length($status{tags});
-$indexentry .= "<br>" if (length($showseverity) or length($status{tags}));
-
-my @merged= split(/ /,$status{mergedwith});
-if (@merged) {
- my $descmerged = 'Merged with ';
- my $mseparator = '';
- for my $m (@merged) {
- $descmerged .= $mseparator."<a href=\"" . html_escape(bug_url($m)) . "\">#$m</a>";
- $mseparator= ",\n";
- }
- push @descstates, $descmerged;
-}
-
-if (@{$status{found_versions}}) {
- my $foundtext = 'Found in ';
- $foundtext .= (@{$status{found_versions}} == 1) ? 'version ' : 'versions ';
- $foundtext .= join ', ', map html_escape($_), @{$status{found_versions}};
- push @descstates, $foundtext;
-}
-if (@{$status{fixed_versions}}) {
- my $fixedtext = '<strong>Fixed</strong> in ';
- $fixedtext .= (@{$status{fixed_versions}} == 1) ? 'version ' : 'versions ';
- $fixedtext .= join ', ', map html_escape($_), @{$status{fixed_versions}};
- if (length($status{done})) {
- $fixedtext .= ' by ' . html_escape(decode_rfc1522($status{done}));
- }
- push @descstates, $fixedtext;
-}
-
-if (@{$status{found_versions}} or @{$status{fixed_versions}}) {
- push @descstates, '<a href="'.
- html_escape(version_url($status{package},
- $status{found_versions},
- $status{fixed_versions},
- )).qq{">Version Graph</a>};
-}
-
-if (length($status{done})) {
- push @descstates, "<strong>Done:</strong> ".html_escape(decode_rfc1522($status{done}));
+ html_escape(version_url(package => $status{package},
+ found => $status{found_versions},
+ fixed => $status{fixed_versions},
+ width => 2,
+ height => 2,
+ )
+ ).
+ qq{"></a>};
}
-if (length($status{forwarded})) {
- my $forward_link = html_escape($status{forwarded});
- $forward_link =~ s,((ftp|http|https)://[\S~-]+?/?)((\>\;)?[)]?[']?[:.\,]?(\s|$)),<a href="$1">$1</a>$3,go;
- push @descstates, "<strong>Forwarded</strong> to $forward_link";
-}
my @blockedby= split(/ /, $status{blockedby});
+$status{blockedby_array} = [];
if (@blockedby && $status{"pending"} ne 'fixed' && ! length($status{done})) {
for my $b (@blockedby) {
my %s = %{get_bug_status($b)};
next if $s{"pending"} eq 'fixed' || length $s{done};
- push @descstates, "Fix blocked by <a href=\"" . html_escape(bug_url($b)) . "\">#$b</a>: ".html_escape($s{subject});
- }
+ push @{$status{blockedby_array}},{bug_num => $b, subject => $s{subject}, status => \%s};
+ }
}
my @blocks= split(/ /, $status{blocks});
+$status{blocks_array} = [];
if (@blocks && $status{"pending"} ne 'fixed' && ! length($status{done})) {
for my $b (@blocks) {
my %s = %{get_bug_status($b)};
next if $s{"pending"} eq 'fixed' || length $s{done};
- push @descstates, "Blocking fix for <a href=\"" . html_escape(bug_url($b)) . "\">#$b</a>: ".html_escape($s{subject});
+ push @{$status{blocks_array}}, {bug_num => $b, subject => $s{subject}, status => \%s};
}
}
if ($buglog !~ m#^\Q$gSpoolDir/db#) {
- push @descstates, "Bug is archived. No further changes may be made";
+ $status{archived} = 1;
}
-$indexentry .= join(";\n<br>", @descstates) . ".\n" if @descstates;
-$indexentry .= "</h3>\n";
-
my $descriptivehead = $indexentry;
-print "Content-Type: text/html; charset=utf-8\n";
-
-my @stat = stat $buglog;
-if (@stat) {
- my $mtime = strftime '%a, %d %b %Y %T GMT', gmtime($stat[9]);
- print "Last-Modified: $mtime\n";
-}
-
-print "\n";
-
-my $title = html_escape($status{subject});
-
-my $dummy2 = $gWebHostBugDir;
-
-print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
-print <<END;
-<HTML><HEAD>
-<TITLE>$short - $title - $gProject $gBug report logs</TITLE>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
-<link rel="stylesheet" href="$gWebHostBugDir/css/bugs.css" type="text/css">
-<script type="text/javascript">
-<!--
-function toggle_infmessages()
-{
- allDivs=document.getElementsByTagName("div");
- for (var i = 0 ; i < allDivs.length ; i++ )
- {
- if (allDivs[i].className == "infmessage")
- {
- allDivs[i].style.display=(allDivs[i].style.display == 'none' | allDivs[i].style.display == '') ? 'block' : 'none';
- }
- }
-}
--->
-</script>
-</HEAD>
-<BODY>
-END
-print "<H1>" . "$gProject $gBug report logs - <A HREF=\"mailto:$ref\@$gEmailDomain\">$short</A>" .
- "<BR>" . $title . "</H1>\n";
-print "$descriptivehead\n";
-
-if (looks_like_number($msg)) {
- printf qq(<p><a href="%s">Full log</a></p>),html_escape(bug_url($ref));
-}
-else {
- print qq(<p><a href="mailto:$ref\@$gEmailDomain">Reply</a> ),
- qq(or <a href="mailto:$ref-subscribe\@$gEmailDomain">subscribe</a> ),
- qq(to this bug.</p>\n);
- print qq(<p><a href="javascript:toggle_infmessages();">Toggle useless messages</a></p>);
- printf qq(<div class="msgreceived"><p>View this report as an <a href="%s">mbox folder</a>, ).
- qq(<a href="%s">status mbox</a>, <a href="%s">maintainer mbox</a></p></div>\n),
- html_escape(bug_url($ref, mbox=>'yes')),
- html_escape(bug_url($ref, mbox=>'yes',mboxstatus=>'yes')),
- html_escape(bug_url($ref, mbox=>'yes',mboxmaint=>'yes'));
-}
-print "$log";
-print "<HR>";
-print "<p class=\"msgreceived\">Send a report that <a href=\"/cgi-bin/bugspam.cgi?bug=$ref\">this bug log contains spam</a>.</p>\n<HR>\n";
-print $tail_html;
-
-print "</BODY></HTML>\n";
-
-exit 0;
+print $q->header(-type => "text/html",
+ -charset => 'utf-8',
+ (length $mtime)?(-last_modified => $mtime):(),
+ );
+
+print fill_in_template(template => 'cgi/bugreport',
+ variables => {status => \%status,
+ package => \%package,
+ log => $log,
+ bug_num => $ref,
+ version_graph => $version_graph,
+ isstrongseverity => \&Debbugs::Status::isstrongseverity,
+ html_escape => \&Debbugs::CGI::html_escape,
+ looks_like_number => \&Scalar::Util::looks_like_number,
+ },
+ hole_var => {'&package_links' => \&Debbugs::CGI::package_links,
+ '&bug_links' => \&Debbugs::CGI::bug_links,
+ '&version_url' => \&Debbugs::CGI::version_url,
+ '&bug_url' => \&Debbugs::CGI::bug_url,
+ '&strftime' => \&POSIX::strftime,
+ }
+ );
+++ /dev/null
-html {
- color: #000;
- background: #fefefe;
- font-family: serif;
- margin: 1em;
- border: 0;
- padding: 0;
- line-height: 120%;
-}
-
-body {
- margin: 0;
- border: 0;
- padding: 0;
-}
-
-h1, h2, h3 {
- text-align: left;
- font-family: sans-serif;
-}
-
-h1 {
- font-size: 150%;
- line-height: 150%;
-}
-
-h2 {
- font-size: 130%;
-}
-
-h3 {
- font-size: 100%;
-}
-
-a:link {
- color: #1b56ce;
- font-weight: bold;
-}
-
-a:visited {
- color:#1b56ce;
-}
-
-a:link:active, a:link:visited {
- color:#ff0000;
-}
-
-a:link:hover, a:visited:hover {
- color: #d81e1e;
-}
-
-a.submitter:link {
- color: #242424;
- font-family: sans-serif;
- font-size: 95%;
- text-decoration: underline;
- font-weight: normal;
-}
-
-a.submitter:visited, a.submitter:active {
- color: #6e6e6e;
- font-family: sans-serif;
- font-size: 95%;
-}
-
-a.submitter:hover, a.submitter:visited:hover {
- color: #d01414;
- font-family: sans-serif;
- font-size: 95%;
-}
-
-pre.message {
- font-family: monospace;
- padding-top: 0;
- margin-top: 0;
- border-top: 0;
-}
-
-.sparse li {
- padding-top: 1ex;
- margin-top: 1ex;
- border-top: 1ex;
-}
-
-a.bugtitle {
- font-weight: bold;
- font-size: 110%;
-}
-
-
-pre.headers {
- font-family: sans-serif;
- font-size: 95%;
- color: #3c3c3c;
- background-color: #f0f0f0;
- padding: 2px;
- border: #a7a7a7 1px solid;
- line-height: 120%
-}
-
-pre.mime {
- font-family: monospace;
- font-size: 95%;
- color: #797979;
-}
-
-/* This must be separate from the other CSS to make the showing of
- unimportant messages work in bugreport.cgi. */
-.infmessage { display: none; }
-
-.infmessage {
- font-family: sans-serif;
- font-size: 90%;
- color: #686868;
-}
-
-.msgreceived {
- font-family: sans-serif;
- font-size: 90%;
- color: #686868;
-}
-
-pre.tags {
- color: #a3a3a3;
- font-size: 90%;
-}
-
-hr.thin {
- color: #a1a1a1;
-}
-
-em.severity {
- color: #c31212;
-}
-
-code, address {
- font-family: sans-serif;
-}
-
-p {
- font-family: sans-serif;
- font-size: 95%;
-}
-
-h2.outstanding {
- font-family: sans-serif;
- background-color: #f0f0f0;
- color: #3c3c3c;
- border: #a7a7a7 1px solid;
- padding: 10px;
-}
-
-a.options:link, a.options:visited {
- font-family: sans-serif;
- background-color: #f0f0f0;
- color: #3c3c3c;
- text-decoration: none;
- border-bottom: #3c3c3c 1px dotted;
-}
-
-li {
- list-style-type: square;
-}
-
-.bugs li {
- margin-top: 5px;
-}
-
-input {
- border: #000 1px solid;
- margin: 3px;
-}
-
-table.forms {
- font-size: 95%;
- font-family: sans-serif;
- margin-left: 10px;
-}
-
-select {
- margin: 3px;
- border: #000 1px solid;
-}
-
$result .= ";\n";
$result .= $showseverity;
- $result .= pkg_htmladdresslinks("Reported by: ", \&submitterurl,
- $status{originator});
- $result .= ";\nOwned by: " . html_escape($status{owner})
+ $result .= "Reported by: ".package_links(submitter=>$status{originator});
+ $result .= ";\nOwned by: " . package_links(owner => $status{owner})
if length $status{owner};
$result .= ";\nTags: <strong>"
. html_escape(join(", ", sort(split(/\s+/, $status{tags}))))
);
}
-sub pkg_htmladdresslinks {
- htmlize_addresslinks(@_,'submitter');
-}
-
sub pkg_javascript {
return <<EOF ;
<script type="text/javascript">
color: #000;
background: #fefefe;
font-family: serif;
- margin: 1em;
+ margin: 0;
border: 0;
padding: 0;
line-height: 120%;
}
body {
- margin: 0;
+ margin: 10px;
border: 0;
padding: 0;
}
color: #686868;
}
+.buginfo p
+{
+ font-family: sans-serif;
+ font-size: 110%;
+ margin-bottom: 0px
+}
+
+.buginfo p + p
+{
+ margin: 0;
+ margin-top: 0px;
+ border: 0;
+}
+
+
+.pkginfo p
+{ font-family: sans-serif;
+ font-size: 110%;
+ margin-bottom: 0px
+}
+
+.pkginfo p + p
+{
+ margin: 0;
+ margin-top: 0px;
+ padding: 0;
+ border: 0;
+}
+
+
+.versiongraph
+{
+ float: right
+
+}
+
pre.tags {
color: #a3a3a3;
font-size: 90%;
$mech->get_ok('http://localhost:'.$port.'/?bug=1',
'Page received ok');
-ok($mech->content() =~ qr/\<title\>\#1\s+\-\s+Submitting a bug/i,
+ok($mech->content() =~ qr/\<title\>\#1.+Submitting a bug/i,
'Title of bug is submitting a bug');
# Other tests for bugs in the page should be added here eventually
--- /dev/null
+{include(q(html/pre_title))}#{$bug_num} - {html_escape($status{subject})} - {html_escape($config{project})} {html_escape($config{bug})} report logs{include(q(html/post_title.tmpl))}
+<script type="text/javascript">
+<!--
+function toggle_infmessages()
+\{
+ allDivs=document.getElementsByTagName("div");
+ for (var i = 0 ; i < allDivs.length ; i++ )
+ \{
+ if (allDivs[i].className == "infmessage")
+ \{
+ allDivs[i].style.display=(allDivs[i].style.display == 'none' | allDivs[i].style.display == '') ? 'block' : 'none';
+ \}
+ \}
+\}
+-->
+</script>
+</head>
+<body>
+<h1>{html_escape($config{project})} {html_escape($config{bug})} report logs -
+<a href="mailto:{$bug_num}@{html_escape($config{email_domain})}">#{$bug_num}</a><br/>
+{html_escape($status{subject})}</h1>
+<div class="versiongraph">{$version_graph}</div>
+{include(q(cgi/bugreport_pkginfo))}
+{include(q(cgi/bugreport_buginfo))}
+{ my $output = '';
+ if (looks_like_number($msg)) {
+ $output .= sprintf qq(<p><a href="%s">Full log</a></p>),html_escape(bug_url($ref));
+ }
+ else {
+ $output .= qq(<p><a href="mailto:$bug_num\@$config{email_domain}">Reply</a> ).
+ qq(or <a href="mailto:$bug_num-subscribe\@$config{email_domain}">subscribe</a> ).
+ qq(to this bug.</p>\n);
+ $output .= qq(<p><a href="javascript:toggle_infmessages();">Toggle useless messages</a></p>);
+ $output .= sprintf qq(<div class="msgreceived"><p>View this report as an <a href="%s">mbox folder</a>, ).
+ qq(<a href="%s">status mbox</a>, <a href="%s">maintainer mbox</a></p></div>\n),
+ html_escape(bug_url($bug_num, mbox=>'yes')),
+ html_escape(bug_url($bug_num, mbox=>'yes',mboxstatus=>'yes')),
+ html_escape(bug_url($bug_num, mbox=>'yes',mboxmaint=>'yes'));
+ }
+ $output;
+}
+{$log}
+<hr>
+<p class="msgreceived">Send a report that <a href="{$config{cgi_domain}}/bugspam.cgi">this bug log contains spam</a>.</p>
+<hr>
+{include(q(html/html_tail))}
+</body>
+</html>
--- /dev/null
+<div class="buginfo">
+ <p>Reported by: {package_links(submitter=>$status{originator})}</p>
+ <p>Date: {$status{date_text}}</p>
+{ my $output = '';
+ if (defined $status{owner} and length $status{owner}) {
+ $output = q(<p>Owned by: ).package_links(owner=>$status{owner}).q(</p>);
+ }
+ $output;
+}
+<p>Severity: {my $output = $status{severity};
+ if (isstrongseverity($status{severity})) {
+ $output = q(<em class="severity">).$status{severity}.q(</em>);
+ }
+ $output;
+ }</p>
+<p>{@{$status{tags_array}}?q(Tags: ).html_escape(join(q(, ),@{$status{tags_array}})):''}</p>
+{my $output = '';
+ if (@{$status{mergedwith_array}}) {
+ $output .= q(<p>Merged with ).join(qq(,\n),bug_links(bug=>$status{mergedwith_array})).qq(</p>\n);
+ }
+ $output;
+}
+{my $output = '';
+ if (@{$status{found_versions}}) {
+ $output .= q(<p>Found in );
+ $output .= (@{$status{found_versions}} == 1) ? 'version ' : 'versions ';
+ $output .= join(qq(, ),map {html_escape($_);} @{$status{found_versions}}).qq(</p>\n);
+ }
+ if (@{$status{fixed_versions}}) {
+ $output .= q(<p>Fixed in );
+ $output .= (@{$status{fixed_versions}} == 1) ? 'version ' : 'versions ';
+ $output .= join(qq(, ),map {html_escape($_);} @{$status{fixed_versions}}).qq(</p>\n);
+ }
+ $output;
+}
+{ my $output = '';
+ if (length($status{done})) {
+ $output .= q(<p><strong>Done:</strong> ).html_escape($status{done}).q(</p>)
+ }
+ $output;
+}
+{ my $output = '';
+ if (@{$status{blockedby_array}}) {
+ $output .= q(<p>Fix blocked by ).
+ join(q(, ),
+ map {bug_links(bug=>$_->{bug_num}).q(: ).html_escape($_->{subject})}
+ @{$status{blockedby_array}}).q(</p>)
+ }
+ if (@{$status{blocks_array}}) {
+ $output .= q(<p>Blocking fix for ).
+ join(q(, ),
+ map {bug_links(bug=>$_->{bug_num}).q(: ).html_escape($_->{subject})}
+ @{$status{blocks_array}}).q(</p>)
+ }
+ $output;
+}
+{ my $output = '';
+ if (exists $status{archived} and $status{archived}) {
+ $output .= q(<p>Bug is archived. No further changes may be made.<p>)
+ }
+ $output
+}</div>
--- /dev/null
+<div class="pkginfo">
+ <p>{if (keys %package > 1) { q(Packages)} else {q(Package)}}:
+ {join(q(, ),package_links(package => [map {$_->{package}} values %package],
+ class => q(submitter),
+ )
+ )};
+{my $output ='';
+ for my $package (values %package) {
+ $output .= q(Maintainer for ).package_links(package=>$package->{package}).qq( is ).
+ package_links(maintainer => $package->{maintainer}).qq(; );
+ $output .= q(Source for ).package_links(package=>$package->{package}).qq( is ).
+ package_links(source => $package->{source}).qq(. );
+ }
+ $output;
+}</p>
+</div>
+++ /dev/null
-<ADDRESS>{$config{maintainer}} <<A HREF="mailto:$config{maintainer_email}">$config{maintainer_email}</A>>.
-Last modified:
-<!--timestamp-->
-{$last_modified||strftime('%c',gmtime)}
-<!--timestamp-->
-<P>
-<A HREF="http://{$config{web_domain}}/">{$config{project}} {$config{bug}} tracking system</A><BR>
-Copyright (C) 1999 Darren O. Benham,
-1997,2003 nCipher Corporation Ltd,
-1994-97 Ian Jackson.
-</ADDRESS>
--- /dev/null
+<ADDRESS>{$config{maintainer}} <<A HREF="mailto:{$config{maintainer_email}}">{$config{maintainer_email}}</A>>.
+Last modified:
+<!--timestamp-->
+{$last_modified||strftime('%c',gmtime)}
+<!--timestamp-->
+<P>
+<A HREF="http://{$config{web_domain}}/">{$config{project}} {$config{bug}} tracking system</A><BR>
+Copyright (C) 1999 Darren O. Benham,
+1997,2003 nCipher Corporation Ltd,
+1994-97 Ian Jackson.
+</ADDRESS>
--- /dev/null
+</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+<link rel="stylesheet" href="{$config{web_host_bug_dir}}/css/bugs.css" type="text/css">
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html><head>
+<title>
\ No newline at end of file