ADMIN_GROUP_NAME
SENDMAIL_EXE
+
+ SAFE_PROTOCOLS
);
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
# Path to sendmail.exe (Windows only)
use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
+# Protocols which are considered as safe.
+use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
+ 'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet',
+ 'view-source', 'wais');
+
1;
}
return $var;
},
-
+
+ html_light => \&Bugzilla::Util::html_light_quote,
+
# iCalendar contentline filter
ics => [ sub {
my ($context, @args) = @_;
@Bugzilla::Util::EXPORT = qw(is_tainted trick_taint detaint_natural
detaint_signed
html_quote url_quote value_quote xml_quote
- css_class_quote
+ css_class_quote html_light_quote
i_am_cgi correct_urlbase
lsearch max min
diff_arrays diff_strings
return $var;
}
+sub html_light_quote {
+ my ($text) = @_;
+
+ # List of allowed HTML elements having no attributes.
+ my @allow = qw(b strong em i u p br abbr acronym ins del cite code var
+ dfn samp kbd big small sub sup tt dd dt dl ul li ol);
+
+ # Are HTML::Scrubber and HTML::Parser installed?
+ eval { require HTML::Scrubber;
+ require HTML::Parser;
+ };
+
+ # We need utf8_mode() from HTML::Parser 3.40 if running Perl >= 5.8.
+ if ($@ || ($] >= 5.008 && $HTML::Parser::VERSION < 3.40)) { # Package(s) not installed.
+ my $safe = join('|', @allow);
+ my $chr = chr(1);
+
+ # First, escape safe elements.
+ $text =~ s#<($safe)>#$chr$1$chr#go;
+ $text =~ s#</($safe)>#$chr/$1$chr#go;
+ # Now filter < and >.
+ $text =~ s#<#<#g;
+ $text =~ s#>#>#g;
+ # Restore safe elements.
+ $text =~ s#$chr/($safe)$chr#</$1>#go;
+ $text =~ s#$chr($safe)$chr#<$1>#go;
+ return $text;
+ }
+ else { # Packages installed.
+ # We can be less restrictive. We can accept elements with attributes.
+ push(@allow, qw(a blockquote q span));
+
+ # Allowed protocols.
+ my $safe_protocols = join('|', SAFE_PROTOCOLS);
+ my $protocol_regexp = qr{(^(?:$safe_protocols):|^[^:]+$)}i;
+
+ # Deny all elements and attributes unless explicitly authorized.
+ my @default = (0 => {
+ id => 1,
+ name => 1,
+ class => 1,
+ '*' => 0, # Reject all other attributes.
+ }
+ );
+
+ # Specific rules for allowed elements. If no specific rule is set
+ # for a given element, then the default is used.
+ my @rules = (a => {
+ href => $protocol_regexp,
+ title => 1,
+ id => 1,
+ name => 1,
+ class => 1,
+ '*' => 0, # Reject all other attributes.
+ },
+ blockquote => {
+ cite => $protocol_regexp,
+ id => 1,
+ name => 1,
+ class => 1,
+ '*' => 0, # Reject all other attributes.
+ },
+ 'q' => {
+ cite => $protocol_regexp,
+ id => 1,
+ name => 1,
+ class => 1,
+ '*' => 0, # Reject all other attributes.
+ },
+ );
+
+ my $scrubber = HTML::Scrubber->new(default => \@default,
+ allow => \@allow,
+ rules => \@rules,
+ comment => 0,
+ process => 0);
+
+ # Avoid filling the web server error log with Perl 5.8.x.
+ # In HTML::Scrubber 0.08, the HTML::Parser object is stored in
+ # the "_p" key, but this may change in future versions.
+ if ($] >= 5.008 && ref($scrubber->{_p}) eq 'HTML::Parser') {
+ $scrubber->{_p}->utf8_mode(1);
+ }
+ return $scrubber->scrub($text);
+ }
+}
+
# This originally came from CGI.pm, by Lincoln D. Stein
sub url_quote {
my ($toencode) = (@_);
Returns a value quoted for use in HTML, with &, E<lt>, E<gt>, and E<34> being
replaced with their appropriate HTML entities.
+=item C<html_light_quote($val)>
+
+Returns a string where only explicitly allowed HTML elements and attributes
+are kept. All HTML elements and attributes not being in the whitelist are either
+escaped (if HTML::Scrubber is not installed) or removed.
+
=item C<url_quote($val)>
Quotes characters so that they may be included as part of a url.
my $gdtextalign = have_vers("GD::Text::Align",0);
my $patchreader = have_vers("PatchReader","0.9.4");
my $imagemagick = have_vers("Image::Magick",0);
+my $html_parser = have_vers("HTML::Parser", ($] >= 5.008) ? "3.40" : 0);
+my $scrubber = have_vers("HTML::Scrubber", 0);
print "\n" unless $silent;
print "install the \nPatchReader module:\n";
print "PatchReader: " . install_command("PatchReader") . "\n";
}
+if ((!$scrubber || !$html_parser) && !$silent) {
+ print "If you want additional HTML tags within product and group ";
+ print "descriptions,\nyou should install:\n";
+ print "HTML::Scrubber: " . install_command("HTML::Scrubber") . "\n"
+ if !$scrubber;
+ print "HTML::Parser: " . install_command("HTML::Parser") . "\n"
+ if !$html_parser;
+ print "\n";
+}
if (%missing) {
print "\n\n";
my $tmp;
# non-mailto protocols
- my $protocol_re = qr/(afs|cid|ftp|gopher|http|https|irc|mid|news|nntp|prospero|telnet|view-source|wais)/i;
+ my $safe_protocols = join('|', SAFE_PROTOCOLS);
+ my $protocol_re = qr/($safe_protocols)/i;
$text =~ s~\b(${protocol_re}: # The protocol:
[^\s<>\"]+ # Any non-whitespace
text-align: center;
white-space: nowrap;
}
+
+.missing {
+ color: red;
+ border-color: inherit;
+}
# Note: If a single directive prints two things, and only one is
# filtered, we may not catch that case.
return 1 if $directive =~ /FILTER\ (html|csv|js|base64|url_quote|css_class_quote|
- ics|quoteUrls|time|uri|xml|lower|
+ ics|quoteUrls|time|uri|xml|lower|html_light|
obsolete|inactive|closed|unitconvert|
none)\b/x;
<table align="center">
[% FOREACH bit_description = has_bits %]
<tr>
- <td>[% bit_description.name %]</td>
- <td>[% bit_description.desc %]</td>
+ <td>[% bit_description.name FILTER html %]</td>
+ <td>[% bit_description.desc FILTER html_light %]</td>
</tr>
[% END %]
</table>
<table align="center">
[% FOREACH bit_description = set_bits %]
<tr>
- <td>[% bit_description.name %]</td>
- <td>[% bit_description.desc %]</td>
+ <td>[% bit_description.name FILTER html %]</td>
+ <td>[% bit_description.desc FILTER html_light %]</td>
</tr>
[% END %]
</table>
</td>
<td>
[% IF settings.${name}.is_enabled %]
- <select name="[% name %]" id="[% name %]">
- <option value="[% default_name %]"
+ <select name="[% name FILTER html %]" id="[% name FILTER html %]">
+ <option value="[% default_name FILTER html %]"
[% ' selected="selected"' IF settings.${name}.is_default %]>
Site Default ([% setting_descs.${default_val} OR default_val FILTER html %])
</option>
[% END %]
</select>
[% ELSE %]
- <select name="[% name %]" id="[% name %]" disabled="disabled">
- <option value="[% default_name %]">
+ <select name="[% name FILTER html %]" id="[% name FILTER html %]" disabled="disabled">
+ <option value="[% default_name FILTER html %]">
Site Default ([% setting_descs.${default_val} OR default_val FILTER html %])
</option>
</select>
<td valign="top">Description:</td>
<td valign="top">
[% IF classification.description %]
- [% classification.description FILTER none %]
+ [% classification.description FILTER html_light %]
[% ELSE %]
<font color="red">description missing</font>
[% END %]
<tr>
<th align="right">Description:</th>
<td><textarea rows=4 cols=64 name="description">
- [% classification.description FILTER none %]</textarea>
+ [% classification.description FILTER html %]</textarea>
</td>
</tr>
<tr valign=top>
<th align=right valign=top>[% product.name FILTER html %]</th>
<td valign=top>
[% IF product.description %]
- [% product.description FILTER none %]
+ [% product.description FILTER html_light %]
[% ELSE %]
<font color="red">description missing</font>
[% END %]
<td valign="top">Description:</td>
<td valign="top" colspan=3>
[% IF classification.description %]
- [% classification.description FILTER none %]
+ [% classification.description FILTER html_light %]
[% ELSE %]
<font color="red">description missing</font>
[% END %]
<td valign="top"><a href="editclassifications.cgi?action=edit&classification=[% cl.name FILTER url_quote %]"><b>[% cl.name FILTER html %]</b></a></td>
<td valign="top">
[% IF cl.description %]
- [% cl.description %]
+ [% cl.description FILTER html_light %]
[% ELSE %]
<font color="red">none</font>
[% END %]
</tr>
<tr>
<td valign="top">Component Description:</td>
- <td valign="top">[% comp.description FILTER html %]</td>
+ <td valign="top">[% comp.description FILTER html_light %]</td>
</tr>
<tr>
<td valign="top">Default assignee:</td>
</tr>
<tr>
<td valign="top">Product Description:</td>
- <td valign="top">[% prod.description FILTER html %]</td>
+ <td valign="top">[% prod.description FILTER html_light %]</td>
[% END %]
[% IF Param('usetargetmilestone') %]
<table>
<tr>
<td>Updated description to:</td>
- <td>'[% description FILTER html %]'</td>
+ <td>'[% description FILTER html_light %]'</td>
</tr>
</table>
[% END %]
<tr>
<td>[% gid FILTER html %]</td>
<td>[% name FILTER html %]</td>
- <td>[% description FILTER html %]</td>
+ <td>[% description FILTER html_light %]</td>
</tr>
</table>
[% group.grpnam FILTER html %]
</a>
</td>
- <td align="left" class="groupdesc">[% group.grpdesc FILTER html %]</td>
+ <td align="left" class="groupdesc">[% group.grpdesc FILTER html_light %]</td>
</tr>
[% END %]
</table>
}
{name => 'description'
heading => 'Description'
+ allow_html_content => 1
}
{name => 'userregexp'
heading => 'User RegExp'
},
{
name => "description"
- heading => "Description"
+ heading => "Description"
+ allow_html_content => 1
},
{
name => "bug_count"
[%# descriptions are intentionally not filtered to allow html content %]
<td>
[% IF classification.description %]
- [% classification.description FILTER none %]
+ [% classification.description FILTER html_light %]
[% ELSE %]
<span style="color: red">missing</span>
[% END %]
[%# descriptions are intentionally not filtered to allow html content %]
<td valign="top">
[% IF product.description %]
- [% product.description FILTER none %]
+ [% product.description FILTER html_light %]
[% ELSE %]
<span style="color: red">missing</span>
[% END %]
[%# descriptions are intentionally not filtered to allow html content %]
<td>
[% IF c.description %]
- [% c.description FILTER none %]
+ [% c.description FILTER html_light %]
[% ELSE %]
<span style="color: red">missing</span>
[% END %]
<tr>
<th align="right">Description:</th>
<td><textarea rows="4" cols="64" wrap="virtual" name="description">
- [% product.description FILTER none %]</textarea>
+ [% product.description FILTER html %]</textarea>
</td>
</tr>
[% FOREACH component = product.components %]
<b>[% component.name FILTER html %]:</b>
[% IF component.description %]
- [% component.description FILTER none %]
+ [% component.description FILTER html_light %]
[% ELSE %]
<font color="red">description missing</font>
[% END %]
<p>
Updated description to:</p>
</p>
- <p style="margin: 1em 3em 1em 3em">[% product.description FILTER html %]</p>
+ <p style="margin: 1em 3em 1em 3em">[% product.description FILTER html_light %]</p>
[% updated = 1 %]
[% END %]
[% setting_descs.$name OR name FILTER html %]
</td>
<td>
- <select name="[% name %]" id="[% name %]">
+ <select name="[% name FILTER html %]" id="[% name FILTER html %]">
[% FOREACH x = settings.${name}.legal_values %]
<option value="[% x FILTER html %]"
[% " selected=\"selected\"" IF x == settings.${name}.default_value %]>
</td>
<td align="center">
<input type="checkbox"
- name="[% checkbox_name %]"
- id="[% checkbox_name %]"
+ name="[% checkbox_name FILTER html %]"
+ id="[% checkbox_name FILTER html %]"
[% " checked=\"checked\"" IF settings.${name}.is_enabled %]>
<br>
</td>
# with the key xxx in data hash of the current row.
# content: If specified, the content of this variable is used
# instead of the data pulled from the current row.
- # NOTE: This value is not HTML filtered at output!
+ # NOTE: This value is only partially HTML filtered!
# content_use_field: If defined and true, then each value in the
# column corresponds with a key in the
# field_descs field, and that value from the
# This content WILL be HTML-filtered in this case.
# align: left/center/right. Controls the horizontal alignment of the
# text in the column.
- # allow_html_content: if defined, then this column allows html content
- # so it will not be filtered
+ # allow_html_content: if defined, then this column allows some html content
+ # and so it will be only partially filtered.
# yesno_field: Turn the data from 0/!0 into Yes/No
#
# data:
content = c.content
content_use_field = c.content_use_field
align = c.align
+ class = c.class
allow_html_content = c.allow_html_content
yesno_field = c.yesno_field
%]
IF override.override_content_use_field %]
[% SET align = override.align
IF override.override_align %]
+ [% SET class = override.class
+ IF override.override_class %]
[% SET allow_html_content = override.allow_html_content
IF override.override_allow_html_content %]
[% SET yesno_field = override.yesno_field
[% END %]
[% END %]
- <td [% IF align %] align="[% align FILTER html %]" [% END %]>
+ <td [% IF align %] align="[% align FILTER html %]" [% END %]
+ [% IF class %] class="[% class FILTER html %]" [% END %]>
[% IF contentlink %]
[% link_uri = contentlink %]
[% colname = row.${c.name} %]
[% field_descs.${colname} FILTER html %]
[% ELSIF content %]
- [% content FILTER none %]
+ [% content FILTER html_light %]
[% ELSE %]
[% IF yesno_field %]
[% IF row.${c.name} %]
[% END %]
[% ELSE %]
[% IF allow_html_content %]
- [% row.${c.name} FILTER none %]
+ [% row.${c.name} FILTER html_light %]
[% ELSE %]
[% row.${c.name} FILTER html %]
[% END %]
<td class="groupname">
<label for="group_[% group.id %]">
<strong>[% group.name FILTER html %]:</strong>
- [%+ group.description FILTER html %]
+ [%+ group.description FILTER html_light %]
</label>
</td>
</tr>
heading => 'Edit user...'
contentlink => 'editusers.cgi?action=edit&userid=%%userid%%' _
listselectionurlparams
- allow_html_content => 1
}
{name => 'realname'
heading => 'Real name'
- allow_html_content => 1
}
]
%]
%]
[% END %]
+[%# Disabled users are crossed out. Missing realnames are noticed in red. %]
+[% overrides.login_name = [] %]
+[% overrides.realname = [] %]
+
[% FOREACH thisuser = users %]
- [%# We FILTER html here because we need admin/table.html.tmpl to accept HTML
- # for styling, so we cannot let admin/table.html.tmpl do the FILTER.
- #%]
- [% thisuser.login_name = BLOCK %]
- [% thisuser.login_name FILTER html %]
- [% END %]
- [% IF thisuser.realname %]
- [% thisuser.realname = BLOCK %]
- [% thisuser.realname FILTER html %]
- [% END %]
- [% ELSE %]
- [% SET thisuser.realname = '<span style="color: red">missing</span>' %]
+ [% IF !thisuser.realname %]
+ [%# We cannot pass one class now and one class later. %]
+ [% SET classes = (thisuser.disabledtext ? "bz_inactive missing" : "missing") %]
+ [% overrides.realname.push({
+ match_value => "$thisuser.login_name"
+ match_field => 'login_name'
+ content => "missing"
+ override_content => 1
+ class => "$classes"
+ override_class => 1 })
+ %]
[% END %]
+
[% IF thisuser.disabledtext %]
- [% thisuser.login_name = "<span class=\"bz_inactive\">$thisuser.login_name</span>" %]
- [% thisuser.realname = "<span class=\"bz_inactive\">$thisuser.realname</span>" %]
+ [% overrides.login_name.push({
+ match_value => "$thisuser.login_name"
+ match_field => 'login_name'
+ class => "bz_inactive"
+ override_class => 1 })
+ %]
+
+ [% overrides.realname.push({
+ match_value => "$thisuser.login_name"
+ match_field => 'login_name'
+ class => "bz_inactive"
+ override_class => 1 })
+ %]
[% END %]
[% END %]
[% PROCESS admin/table.html.tmpl
columns = columns
data = users
+ overrides = overrides
%]
<p>
<input type="checkbox" id="bit-[% g.bit %]"
name="bit-[% g.bit %]" value="1"
[% " checked=\"checked\"" IF g.checked %]>
- <label for="bit-[% g.bit %]">[% g.description %]</label><br>
+ <label for="bit-[% g.bit %]">[% g.description FILTER html_light %]</label><br>
[% END %]
<br>
[% END %]
<td>
[% resolution_descs.${bug.resolution} FILTER html %]
[% IF bug.resolution == "DUPLICATE" %]
- of [% terms.bug %] [%+ "${bug.dup_id}" FILTER bug_link(bug.dup_id) %]
+ of [% terms.bug %] [%+ "${bug.dup_id}" FILTER bug_link(bug.dup_id) FILTER none %]
[% END %]
</td>
</tr>
<input type="checkbox" name="bit-[% group.bit %]" value="1"
[% " checked=\"checked\"" IF group.ison %]
[% " disabled=\"disabled\"" IF NOT group.ingroup %]>
- [% group.description %]
+ [% group.description FILTER html_light %]
<br>
[% END %]
[% END %]
<th align="right">[% terms.Bug %] [%+ bug.bug_id %] [%+ dep.title %]:</th>
<td>
[% FOREACH depbug = bug.${dep.fieldname} %]
- [% depbug FILTER bug_link(depbug) %][% " " %]
+ [% depbug FILTER bug_link(depbug) FILTER none %][% " " %]
[% END %]
</td>
<td>
'reports/components.html.tmpl' => [
'numcols',
- 'comp.description',
],
'reports/duplicates-table.html.tmpl' => [
],
'reports/keywords.html.tmpl' => [
- 'keyword.description',
'keyword.bugcount',
],
'list/edit-multiple.html.tmpl' => [
'group.id',
- 'group.description',
- 'group.description FILTER inactive',
'knum',
'menuname',
],
-'list/list-simple.html.tmpl' => [
- 'title',
-],
-
'list/list.rdf.tmpl' => [
'template_version',
'bug.bug_id',
'VERSION',
],
-'global/choose-classification.html.tmpl' => [
- 'class.description',
-],
-
'global/choose-product.html.tmpl' => [
'target',
- 'proddesc.$p',
],
# You are not permitted to add any values here. Everything in this file should
'bug.bug_id',
'bug.votes',
'group.bit',
- 'group.description',
'dep.title',
'dep.fieldname',
'accesskey',
'bug.${dep.fieldname}.join(\', \')',
'selname',
- 'depbug FILTER bug_link(depbug)',
- '"${bug.dup_id}" FILTER bug_link(bug.dup_id)',
],
'bug/knob.html.tmpl' => [
'bug/create/create.html.tmpl' => [
'g.bit',
- 'g.description',
'sel.name',
'sel.description',
'cloned_bug_id'
'link_uri'
],
-'admin/classifications/select.html.tmpl' => [
- 'cl.description',
-],
-
'admin/products/groupcontrol/confirm-edit.html.tmpl' => [
'group.count',
],
'bug_count'
],
-'admin/settings/edit.html.tmpl' => [
- 'name',
- 'checkbox_name'
-],
-
'account/login.html.tmpl' => [
'target',
],
'prefname',
],
-'account/prefs/permissions.html.tmpl' => [
- 'bit_description.name',
- 'bit_description.desc',
-],
-
'account/prefs/prefs.html.tmpl' => [
'current_tab.label',
'current_tab.name',
],
-'account/prefs/settings.html.tmpl' => [
- 'name',
- 'default_name'
-],
-
);
</th>
[% IF class.description %]
- <td valign="top"> [% class.description %]</td>
+ <td valign="top"> [% class.description FILTER html_light %]</td>
[% END %]
</tr>
[% END %]
</th>
[% IF proddesc.$p %]
- <td valign="top">[% proddesc.$p %]</td>
+ <td valign="top">[% proddesc.$p FILTER html_light %]</td>
[% END %]
</tr>
[% END %]
[% END %]
<td>
- [% IF group.isactive %]
- [% group.description %]
- [% ELSE %]
- [% group.description FILTER inactive %]
- [% END %]
+ [% SET inactive = !group.isactive %]
+ [% group.description FILTER html_light FILTER inactive(inactive) %]
</td>
</tr>
[%############################################################################%]
[% DEFAULT title = "$terms.Bug List" %]
-[% title = title FILTER html %]
-
[%############################################################################%]
[%# Bug Table #%]
<html>
<head>
- <title>[% title %]</title>
+ <title>[% title FILTER html %]</title>
<base href="[% Param("urlbase") %]">
<link href="skins/standard/buglist.css" rel="stylesheet" type="text/css">
</head>
</tr>
<tr>
<td colspan="[% numcols - 1 %]">
- [% comp.description %]
+ [% comp.description FILTER html_light %]
</td>
</tr>
[% END %]
[%# INTERFACE:
# keywords: array of hashes. May be empty. Each has has three members:
# name: the name of the keyword
- # description: keyword description. May be HTML.
+ # description: keyword description. Can contain some limited HTML code.
# bugcount: number of bugs with that keyword
# caneditkeywords: boolean. True if this user can edit keywords
%]
<a name="[% keyword.name FILTER html %]">
[% keyword.name FILTER html %]</a>
</th>
- <td>[% keyword.description %]</td>
+ <td>[% keyword.description FILTER html_light %]</td>
<td align="right">
[% IF keyword.bugcount > 0 %]
<a href="buglist.cgi?keywords=[% keyword.name FILTER url_quote %]">