return $docs_urlbase;
},
+ # Check whether the URL is safe.
+ 'is_safe_url' => sub {
+ my $url = shift;
+ return 0 unless $url;
+
+ my $safe_protocols = join('|', SAFE_PROTOCOLS);
+ return 1 if $url =~ /^($safe_protocols):[^\s<>\"]+[\w\/]$/i;
+ # Pointing to a local file with no colon in its name is fine.
+ return 1 if $url =~ /^[^\s<>\":]+[\w\/]$/i;
+ # If we come here, then we cannot guarantee it's safe.
+ return 0;
+ },
+
# Allow templates to generate a token themselves.
'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
<tr>
<td class="field_label">
<label for="bug_file_loc" accesskey="u"><b>
- [% IF bug.bug_file_loc
- AND NOT bug.bug_file_loc.match("^(javascript|data)") %]
+ [% IF is_safe_url(bug.bug_file_loc) %]
<a href="[% bug.bug_file_loc FILTER html %]"><u>U</u>RL</a>
[% ELSE %]
<u>U</u>RL
<td>
[% IF bug.check_can_change_field("bug_file_loc", 0, 1) %]
<span id="bz_url_edit_container" class="bz_default_hidden">
- [% IF bug.bug_file_loc
- AND NOT bug.bug_file_loc.match("^(javascript|data)") %]
+ [% IF is_safe_url(bug.bug_file_loc) %]
<a href="[% bug.bug_file_loc FILTER html %]" target="_blank"
title="[% bug.bug_file_loc FILTER html %]">
[% bug.bug_file_loc FILTER truncate(40) FILTER html %]</a>
[% END %]
<span id="bz_url_input_area">
[% url_output = PROCESS input no_td=1 inputname => "bug_file_loc" size => "40" colspan => 2 %]
- [% IF NOT bug.check_can_change_field("bug_file_loc", 0, 1) %]
+ [% IF NOT bug.check_can_change_field("bug_file_loc", 0, 1)
+ AND is_safe_url(bug.bug_file_loc) %]
<a href="[% bug.bug_file_loc FILTER html %]">[% url_output FILTER none %]</a>
[% ELSE %]
[% url_output FILTER none %]
<tr>
<th>[% field_descs.bug_file_loc FILTER html %]:</th>
<td colspan="3">
- [% IF bug.bug_file_loc.match("^(javascript|data)") %]
- [% bug.bug_file_loc FILTER html %]
- [% ELSE %]
+ [% IF is_safe_url(bug.bug_file_loc) %]
<a href="[% bug.bug_file_loc FILTER html %]">
[% bug.bug_file_loc FILTER html %]</a>
+ [% ELSE %]
+ [% bug.bug_file_loc FILTER html %]
[% END %]
</td>
</tr>