summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorDave Lawrence <dlawrence@mozilla.com>2012-05-01 17:37:15 +0200
committerDave Lawrence <dlawrence@mozilla.com>2012-05-01 17:37:15 +0200
commitb9ce8358e9822243421a37548e3d1f0371b97455 (patch)
tree37e983d92587ee30f93c35109200567e499ec491 /Bugzilla
parent228c7bf9ea03a5faafe22f003e5d56da1b7b2162 (diff)
parent07c6bfa4cea83c8284b04add26729f552c93bafc (diff)
downloadbugzilla-b9ce8358e9822243421a37548e3d1f0371b97455.tar.gz
bugzilla-b9ce8358e9822243421a37548e3d1f0371b97455.tar.xz
merged with bugzilla/4.2
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Config/Advanced.pm3
-rw-r--r--Bugzilla/Config/Common.pm11
-rw-r--r--Bugzilla/Constants.pm2
-rw-r--r--Bugzilla/DB/Schema.pm2
-rw-r--r--Bugzilla/Error.pm90
-rw-r--r--Bugzilla/Hook.pm114
-rw-r--r--Bugzilla/Search.pm39
-rw-r--r--Bugzilla/Search/Quicksearch.pm72
-rw-r--r--Bugzilla/Util.pm108
9 files changed, 320 insertions, 121 deletions
diff --git a/Bugzilla/Config/Advanced.pm b/Bugzilla/Config/Advanced.pm
index b70dcb9e3..a5ae3048a 100644
--- a/Bugzilla/Config/Advanced.pm
+++ b/Bugzilla/Config/Advanced.pm
@@ -46,7 +46,8 @@ use constant get_param_list => (
{
name => 'inbound_proxies',
type => 't',
- default => ''
+ default => '',
+ checker => \&check_ip
},
{
diff --git a/Bugzilla/Config/Common.pm b/Bugzilla/Config/Common.pm
index 9fffe02ee..00c699217 100644
--- a/Bugzilla/Config/Common.pm
+++ b/Bugzilla/Config/Common.pm
@@ -48,7 +48,7 @@ use base qw(Exporter);
qw(check_multi check_numeric check_regexp check_url check_group
check_sslbase check_priority check_severity check_platform
check_opsys check_shadowdb check_urlbase check_webdotbase
- check_user_verify_class
+ check_user_verify_class check_ip
check_mail_delivery_method check_notification check_utf8
check_bug_status check_smtp_auth check_theschwartz_available
check_maxattachmentsize check_email
@@ -129,6 +129,15 @@ sub check_sslbase {
return "";
}
+sub check_ip {
+ my $inbound_proxies = shift;
+ my @proxies = split(/[\s,]+/, $inbound_proxies);
+ foreach my $proxy (@proxies) {
+ validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address";
+ }
+ return "";
+}
+
sub check_utf8 {
my $utf8 = shift;
# You cannot turn off the UTF-8 parameter if you've already converted
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index 3d3d4a41d..78336818f 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -202,7 +202,7 @@ use Memoize;
# CONSTANTS
#
# Bugzilla version
-use constant BUGZILLA_VERSION => "4.2+";
+use constant BUGZILLA_VERSION => "4.2.1";
# Location of the remote and local XML files to track new releases.
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index 874a99ce0..00ff4acc9 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -2938,7 +2938,7 @@ unsigned)
=item C<SMALLSERIAL>
-An auto-increment L</INT1>
+An auto-increment L</INT2>
=item C<MEDIUMSERIAL>
diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm
index df03dfd6d..2114bba47 100644
--- a/Bugzilla/Error.pm
+++ b/Bugzilla/Error.pm
@@ -93,6 +93,8 @@ sub _throw_error {
}
my $template = Bugzilla->template;
+ my $message;
+
if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
if (arecibo_should_notify($vars->{error})) {
$vars->{maintainers_notified} = 1;
@@ -111,52 +113,62 @@ sub _throw_error {
$vars->{error}, $vars->{processed}->{error_message}, $vars->{uid});
}
}
+
# There are some tests that throw and catch a lot of errors,
# and calling $template->process over and over for those errors
# is too slow. So instead, we just "die" with a dump of the arguments.
+ if (Bugzilla->error_mode != ERROR_MODE_TEST) {
+ $template->process($name, $vars, \$message)
+ || ThrowTemplateError($template->error());
+ }
+
+ # Let's call the hook first, so that extensions can override
+ # or extend the default behavior, or add their own error codes.
+ require Bugzilla::Hook;
+ Bugzilla::Hook::process('error_catch', { error => $error, vars => $vars,
+ message => \$message });
+
+ if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
+ print Bugzilla->cgi->header();
+ print $message;
+ }
elsif (Bugzilla->error_mode == ERROR_MODE_TEST) {
die Dumper($vars);
}
- else {
- my $message;
- $template->process($name, $vars, \$message)
- || ThrowTemplateError($template->error());
- if (Bugzilla->error_mode == ERROR_MODE_DIE) {
- die("$message\n");
+ elsif (Bugzilla->error_mode == ERROR_MODE_DIE) {
+ die("$message\n");
+ }
+ elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
+ || Bugzilla->error_mode == ERROR_MODE_JSON_RPC)
+ {
+ # Clone the hash so we aren't modifying the constant.
+ my %error_map = %{ WS_ERROR_CODE() };
+ Bugzilla::Hook::process('webservice_error_codes',
+ { error_map => \%error_map });
+ my $code = $error_map{$error};
+ if (!$code) {
+ $code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
+ $code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
}
- elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
- || Bugzilla->error_mode == ERROR_MODE_JSON_RPC)
- {
- # Clone the hash so we aren't modifying the constant.
- my %error_map = %{ WS_ERROR_CODE() };
- require Bugzilla::Hook;
- Bugzilla::Hook::process('webservice_error_codes',
- { error_map => \%error_map });
- my $code = $error_map{$error};
- if (!$code) {
- $code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
- $code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
- }
-
- if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
- die SOAP::Fault->faultcode($code)->faultstring($message);
- }
- else {
- my $server = Bugzilla->_json_server;
- # Technically JSON-RPC isn't allowed to have error numbers
- # higher than 999, but we do this to avoid conflicts with
- # the internal JSON::RPC error codes.
- $server->raise_error(code => 100000 + $code,
- message => $message,
- id => $server->{_bz_request_id},
- version => $server->version);
- # Most JSON-RPC Throw*Error calls happen within an eval inside
- # of JSON::RPC. So, in that circumstance, instead of exiting,
- # we die with no message. JSON::RPC checks raise_error before
- # it checks $@, so it returns the proper error.
- die if _in_eval();
- $server->response($server->error_response_header);
- }
+
+ if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
+ die SOAP::Fault->faultcode($code)->faultstring($message);
+ }
+ else {
+ my $server = Bugzilla->_json_server;
+ # Technically JSON-RPC isn't allowed to have error numbers
+ # higher than 999, but we do this to avoid conflicts with
+ # the internal JSON::RPC error codes.
+ $server->raise_error(code => 100000 + $code,
+ message => $message,
+ id => $server->{_bz_request_id},
+ version => $server->version);
+ # Most JSON-RPC Throw*Error calls happen within an eval inside
+ # of JSON::RPC. So, in that circumstance, instead of exiting,
+ # we die with no message. JSON::RPC checks raise_error before
+ # it checks $@, so it returns the proper error.
+ die if _in_eval();
+ $server->response($server->error_response_header);
}
}
exit;
diff --git a/Bugzilla/Hook.pm b/Bugzilla/Hook.pm
index 564c5bc49..da17946c0 100644
--- a/Bugzilla/Hook.pm
+++ b/Bugzilla/Hook.pm
@@ -127,6 +127,30 @@ This describes what hooks exist in Bugzilla currently. They are mostly
in alphabetical order, but some related hooks are near each other instead
of being alphabetical.
+=head2 admin_editusers_action
+
+This hook allows you to add additional actions to the admin Users page.
+
+Params:
+
+=over
+
+=item C<vars>
+
+You can add as many new key/value pairs as you want to this hashref.
+It will be passed to the template.
+
+=item C<action>
+
+A text which indicates the different behaviors that editusers.cgi will have.
+With this hook you can change the behavior of an action or add new actions.
+
+=item C<user>
+
+This is a Bugzilla::User object of the user.
+
+=back
+
=head2 attachment_process_data
This happens at the very beginning process of the attachment creation.
@@ -432,6 +456,41 @@ The definition is structured as:
=back
+=head2 buglist_column_joins
+
+This allows you to join additional tables to display additional columns
+in buglists. This hook is generally used in combination with the
+C<buglist_columns> hook.
+
+Params:
+
+=over
+
+=item C<column_joins> - A hashref containing data to return back to
+L<Bugzilla::Search>. This hashref contains names of the columns as keys and
+a hashref about table to join as values. This hashref has the following keys:
+
+=over
+
+=item C<table> - The name of the additional table to join.
+
+=item C<as> - (optional) The alias used for the additional table. This alias
+must not conflict with an existing alias already used in the query.
+
+=item C<from> - (optional) The name of the column in the C<bugs> table which
+the additional table should be linked to. If omitted, C<bug_id> will be used.
+
+=item C<to> - (optional) The name of the column in the additional table which
+should be linked to the column in the C<bugs> table, see C<from> above.
+If omitted, C<bug_id> will be used.
+
+=item C<join> - (optional) Either INNER or LEFT. Determine how the additional
+table should be joined with the C<bugs> table. If omitted, LEFT is used.
+
+=back
+
+=back
+
=head2 search_operator_field_override
This allows you to modify L<Bugzilla::Search/OPERATOR_FIELD_OVERRIDE>,
@@ -628,6 +687,37 @@ Params:
=back
+=head2 error_catch
+
+This hook allows extensions to catch errors thrown by Bugzilla and
+take the appropriate actions.
+
+Params:
+
+=over
+
+=item C<error>
+
+A string representing the error code thrown by Bugzilla. This string
+matches the C<error> variable in C<global/user-error.html.tmpl> and
+C<global/code-error.html.tmpl>.
+
+=item C<message>
+
+If the error mode is set to C<ERROR_MODE_WEBPAGE>, you get a reference to
+the whole HTML page with the error message in it, including its header and
+footer. If you need to extract the error message itself, you can do it by
+looking at the content of the table cell whose ID is C<error_msg>.
+If the error mode is not set to C<ERROR_MODE_WEBPAGE>, you get a reference
+to the error message itself.
+
+=item C<vars>
+
+This hash contains all the data passed to the error template. Its content
+depends on the error thrown.
+
+=back
+
=head2 flag_end_of_update
This happens at the end of L<Bugzilla::Flag/update_flags>, after all other
@@ -1354,30 +1444,6 @@ name), you can get it from here.
=back
-=head2 admin_editusers_action
-
-This hook allows you to add additional actions to the admin Users page.
-
-Params:
-
-=over
-
-=item C<vars>
-
-You can add as many new key/value pairs as you want to this hashref.
-It will be passed to the template.
-
-=item C<action>
-
-A text which indicates the different behaviors that edit_users.cgi will have.
-With this hook you can change the behavior of an action or add new actions.
-
-=item C<user>
-
-This is a Bugzilla::User object of the user.
-
-=back
-
=head2 user_preferences
This hook allows you to add additional panels to the User Preferences page,
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 7d2d63284..c0a58c3f0 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -792,8 +792,8 @@ sub _param_array {
}
sub _params { $_[0]->{params} }
-
sub _user { return $_[0]->{user} }
+sub _sharer_id { $_[0]->{sharer} }
##############################
# Internal Accessors: SELECT #
@@ -959,7 +959,8 @@ sub _column_join {
my ($self, $field) = @_;
# The _realname fields require the same join as the username fields.
$field =~ s/_realname$//;
- my $join_info = COLUMN_JOINS->{$field};
+ my $column_joins = $self->_get_column_joins();
+ my $join_info = $column_joins->{$field};
if ($join_info) {
# Don't allow callers to modify the constant.
$join_info = dclone($join_info);
@@ -1800,6 +1801,20 @@ sub _get_operator_field_override {
return $cache->{operator_field_override};
}
+sub _get_column_joins {
+ my $self = shift;
+ my $cache = Bugzilla->request_cache;
+
+ return $cache->{column_joins} if defined $cache->{column_joins};
+
+ my %column_joins = %{ COLUMN_JOINS() };
+ Bugzilla::Hook::process('buglist_column_joins',
+ { column_joins => \%column_joins });
+
+ $cache->{column_joins} = \%column_joins;
+ return $cache->{column_joins};
+}
+
###########################
# Search Function Helpers #
###########################
@@ -1912,16 +1927,22 @@ sub _timestamp_translate {
my $value = $args->{value};
my $dbh = Bugzilla->dbh;
- return if $value !~ /^[\+\-]?\d+[hdwmy]s?$/i;
-
- $args->{value} = SqlifyDate($value);
- $args->{quoted} = $dbh->quote($args->{value});
+ return if $value !~ /^(?:[\+\-]?\d+[hdwmy]s?|now)$/i;
+
+ # By default, the time is appended to the date, which we don't want
+ # for deadlines.
+ $value = SqlifyDate($value);
+ if ($args->{field} eq 'deadline') {
+ ($value) = split(/\s/, $value);
+ }
+ $args->{value} = $value;
+ $args->{quoted} = $dbh->quote($value);
}
sub SqlifyDate {
my ($str) = @_;
my $fmt = "%Y-%m-%d %H:%M:%S";
- $str = "" if !defined $str;
+ $str = "" if (!defined $str || lc($str) eq 'now');
if ($str eq "") {
my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime(time());
return sprintf("%4d-%02d-%02d 00:00:00", $year+1900, $month+1, $mday);
@@ -2549,8 +2570,8 @@ sub _multiselect_table {
}
elsif ($field eq 'tag') {
$args->{full_field} = 'tag.name';
- return "bug_tag INNER JOIN tag ON bug_tag.tag_id = tag.id"
- . " AND user_id = " . $self->_user->id;
+ return "bug_tag INNER JOIN tag ON bug_tag.tag_id = tag.id AND user_id = "
+ . ($self->_sharer_id || $self->_user->id);
}
elsif ($field eq 'bug_group') {
$args->{full_field} = 'groups.name';
diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm
index 563d2387c..54592f07e 100644
--- a/Bugzilla/Search/Quicksearch.pm
+++ b/Bugzilla/Search/Quicksearch.pm
@@ -129,7 +129,7 @@ use constant COMPONENT_EXCEPTIONS => (
);
# Quicksearch-wide globals for boolean charts.
-our ($chart, $and, $or, $fulltext);
+our ($chart, $and, $or, $fulltext, $bug_status_set);
sub quicksearch {
my ($searchstring) = (@_);
@@ -199,7 +199,8 @@ sub quicksearch {
}
}
- _handle_status_and_resolution(\@qswords);
+ _handle_status_and_resolution($qswords[0]);
+ shift(@qswords) if $bug_status_set;
my (@unknownFields, %ambiguous_fields);
$fulltext = Bugzilla->user->setting('quicksearch_fulltext') eq 'on' ? 1 : 0;
@@ -233,6 +234,12 @@ sub quicksearch {
$or = 0;
}
+ # If there is no mention of a bug status, we restrict the query
+ # to open bugs by default.
+ unless ($bug_status_set) {
+ $cgi->param('bug_status', BUG_STATE_OPEN);
+ }
+
# Inform user about any unknown fields
if (scalar(@unknownFields) || scalar(keys %ambiguous_fields)) {
ThrowUserError("quicksearch_unknown_field",
@@ -303,48 +310,26 @@ sub _handle_alias {
}
sub _handle_status_and_resolution {
- my ($words) = @_;
+ my $word = shift;
my $legal_statuses = get_legal_field_values('bug_status');
- my $legal_resolutions = get_legal_field_values('resolution');
-
- my @openStates = BUG_STATE_OPEN;
- my @closedStates;
my (%states, %resolutions);
+ $bug_status_set = 1;
- foreach (@$legal_statuses) {
- push(@closedStates, $_) unless is_open_state($_);
- }
- foreach (@openStates) { $states{$_} = 1 }
- if ($words->[0] eq 'ALL') {
- foreach (@$legal_statuses) { $states{$_} = 1 }
- shift @$words;
+ if ($word eq 'OPEN') {
+ $states{$_} = 1 foreach BUG_STATE_OPEN;
}
- elsif ($words->[0] eq 'OPEN') {
- shift @$words;
- }
- elsif ($words->[0] =~ /^[A-Z_]+(,[_A-Z]+)*$/) {
- # e.g. CON,IN_PR,FIX
- undef %states;
- if (matchPrefixes(\%states,
- \%resolutions,
- [split(/,/, $words->[0])],
- $legal_statuses,
- $legal_resolutions)) {
- shift @$words;
- }
- else {
- # Carry on if no match found
- foreach (@openStates) { $states{$_} = 1 }
- }
- }
- else {
- # Default: search for unresolved bugs only.
- # Put custom code here if you would like to change this behaviour.
+ # If we want all bugs, then there is nothing to do.
+ elsif ($word ne 'ALL'
+ && !matchPrefixes(\%states, \%resolutions, $word, $legal_statuses))
+ {
+ $bug_status_set = 0;
}
# If we have wanted resolutions, allow closed states
if (keys(%resolutions)) {
- foreach (@closedStates) { $states{$_} = 1 }
+ foreach my $status (@$legal_statuses) {
+ $states{$status} = 1 unless is_open_state($status);
+ }
}
Bugzilla->cgi->param('bug_status', keys(%states));
@@ -416,6 +401,9 @@ sub _handle_field_names {
$ambiguous_fields->{$field} = $translated;
}
else {
+ if ($translated eq 'bug_status' || $translated eq 'resolution') {
+ $bug_status_set = 1;
+ }
foreach my $value (@values) {
my $operator = FIELD_OPERATOR->{$translated} || 'substring';
# If the string was quoted to protect some special
@@ -575,14 +563,14 @@ sub _matches_phrase {
# Expand found prefixes to states or resolutions
sub matchPrefixes {
- my $hr_states = shift;
- my $hr_resolutions = shift;
- my $ar_prefixes = shift;
- my $ar_check_states = shift;
- my $ar_check_resolutions = shift;
+ my ($hr_states, $hr_resolutions, $word, $ar_check_states) = @_;
+ return unless $word =~ /^[A-Z_]+(,[A-Z_]+)*$/;
+
+ my @ar_prefixes = split(/,/, $word);
+ my $ar_check_resolutions = get_legal_field_values('resolution');
my $foundMatch = 0;
- foreach my $prefix (@$ar_prefixes) {
+ foreach my $prefix (@ar_prefixes) {
foreach (@$ar_check_states) {
if (/^$prefix/) {
$$hr_states{$_} = 1;
diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm
index 7ecaddc88..c2dbdc97d 100644
--- a/Bugzilla/Util.pm
+++ b/Bugzilla/Util.pm
@@ -35,7 +35,7 @@ use base qw(Exporter);
detaint_signed
html_quote url_quote xml_quote
css_class_quote html_light_quote
- i_am_cgi correct_urlbase remote_ip
+ i_am_cgi correct_urlbase remote_ip validate_ip
do_ssl_redirect_if_required use_attachbase
diff_arrays on_main_db
trim wrap_hard wrap_comment find_wrap_point
@@ -285,12 +285,103 @@ sub correct_urlbase {
sub remote_ip {
my $ip = $ENV{'REMOTE_ADDR'} || '127.0.0.1';
my @proxies = split(/[\s,]+/, Bugzilla->params->{'inbound_proxies'});
- if (first { $_ eq $ip } @proxies) {
- $ip = $ENV{'HTTP_X_FORWARDED_FOR'} if $ENV{'HTTP_X_FORWARDED_FOR'};
+
+ # If the IP address is one of our trusted proxies, then we look at
+ # the X-Forwarded-For header to determine the real remote IP address.
+ if ($ENV{'HTTP_X_FORWARDED_FOR'} && first { $_ eq $ip } @proxies) {
+ my @ips = split(/[\s,]+/, $ENV{'HTTP_X_FORWARDED_FOR'});
+ # This header can contain several IP addresses. We want the
+ # IP address of the machine which connected to our proxies as
+ # all other IP addresses may be fake or internal ones.
+ # Note that this may block a whole external proxy, but we have
+ # no way to determine if this proxy is malicious or trustable.
+ foreach my $remote_ip (reverse @ips) {
+ if (!first { $_ eq $remote_ip } @proxies) {
+ # Keep the original IP address if the remote IP is invalid.
+ $ip = validate_ip($remote_ip) || $ip;
+ last;
+ }
+ }
}
return $ip;
}
+sub validate_ip {
+ my $ip = shift;
+ return is_ipv4($ip) || is_ipv6($ip);
+}
+
+# Copied from Data::Validate::IP::is_ipv4().
+sub is_ipv4 {
+ my $ip = shift;
+ return unless defined $ip;
+
+ my @octets = $ip =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
+ return unless scalar(@octets) == 4;
+
+ foreach my $octet (@octets) {
+ return unless ($octet >= 0 && $octet <= 255 && $octet !~ /^0\d{1,2}$/);
+ }
+
+ # The IP address is valid and can now be detainted.
+ return join('.', @octets);
+}
+
+# Copied from Data::Validate::IP::is_ipv6().
+sub is_ipv6 {
+ my $ip = shift;
+ return unless defined $ip;
+
+ # If there is a :: then there must be only one :: and the length
+ # can be variable. Without it, the length must be 8 groups.
+ my @chunks = split(':', $ip);
+
+ # Need to check if the last chunk is an IPv4 address, if it is we
+ # pop it off and exempt it from the normal IPv6 checking and stick
+ # it back on at the end. If there is only one chunk and it's an IPv4
+ # address, then it isn't an IPv6 address.
+ my $ipv4;
+ my $expected_chunks = 8;
+ if (@chunks > 1 && is_ipv4($chunks[$#chunks])) {
+ $ipv4 = pop(@chunks);
+ $expected_chunks--;
+ }
+
+ my $empty = 0;
+ # Workaround to handle trailing :: being valid.
+ if ($ip =~ /[0-9a-f]{1,4}::$/) {
+ $empty++;
+ # Single trailing ':' is invalid.
+ } elsif ($ip =~ /:$/) {
+ return;
+ }
+
+ foreach my $chunk (@chunks) {
+ return unless $chunk =~ /^[0-9a-f]{0,4}$/i;
+ $empty++ if $chunk eq '';
+ }
+ # More than one :: block is bad, but if it starts with :: it will
+ # look like two, so we need an exception.
+ if ($empty == 2 && $ip =~ /^::/) {
+ # This is ok
+ } elsif ($empty > 1) {
+ return;
+ }
+
+ push(@chunks, $ipv4) if $ipv4;
+ # Need 8 chunks, or we need an empty section that could be filled
+ # to represent the missing '0' sections.
+ return unless (@chunks == $expected_chunks || @chunks < $expected_chunks && $empty);
+
+ my $ipv6 = join(':', @chunks);
+ # The IP address is valid and can now be detainted.
+ trick_taint($ipv6);
+
+ # Need to handle the exception of trailing :: being valid.
+ return "${ipv6}::" if $ip =~ /::$/;
+ return $ipv6;
+}
+
sub use_attachbase {
my $attachbase = Bugzilla->params->{'attachment_base'};
return ($attachbase ne ''
@@ -884,6 +975,17 @@ in a command-line script.
Returns either the C<sslbase> or C<urlbase> parameter, depending on the
current setting for the C<ssl_redirect> parameter.
+=item C<remote_ip()>
+
+Returns the IP address of the remote client. If Bugzilla is behind
+a trusted proxy, it will get the remote IP address by looking at the
+X-Forwarded-For header.
+
+=item C<validate_ip($ip)>
+
+Returns the sanitized IP address if it is a valid IPv4 or IPv6 address,
+else returns undef.
+
=item C<use_attachbase()>
Returns true if an alternate host is used to display attachments; false