Commit 94a14f18 authored by Per Cederqvist's avatar Per Cederqvist

Imported Bugzilla 4.0.2.

parent 8ddcf713
No preview for this file type
......@@ -571,6 +571,7 @@ sub _check_data {
local $/;
my $fh = $params->{data};
$data = <$fh>;
close $fh;
}
}
Bugzilla::Hook::process('attachment_process_data', { data => \$data,
......
......@@ -37,6 +37,7 @@ sub process_diff {
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch.
print $cgi->header(-type => 'text/plain',
-x_content_type_options => "nosniff",
-expires => '+3M');
disable_utf8();
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
......@@ -118,6 +119,7 @@ sub process_interdiff {
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch.
print $cgi->header(-type => 'text/plain',
-x_content_type_options => "nosniff",
-expires => '+3M');
disable_utf8();
}
......
......@@ -1596,6 +1596,8 @@ sub _check_estimated_time {
sub _check_groups {
my ($invocant, $group_names, undef, $params) = @_;
my $bug_id = blessed($invocant) ? $invocant->id : undef;
my $product = blessed($invocant) ? $invocant->product_obj
: $params->{product};
my %add_groups;
......@@ -1614,12 +1616,13 @@ sub _check_groups {
if !ref $group_names;
# First check all the groups they chose to set.
my %args = ( product => $product->name, bug_id => $bug_id, action => 'add' );
foreach my $name (@$group_names) {
# We don't want to expose the existence or non-existence of groups,
# so instead of doing check(), we just do "next" on an invalid
# group.
my $group = new Bugzilla::Group({ name => $name }) or next;
next if !$product->group_is_settable($group);
my $group = Bugzilla::Group->check_no_disclose({ %args, name => $name });
if (!$product->group_is_settable($group)) {
ThrowUserError('group_restriction_not_allowed', { %args, name => $name });
}
$add_groups{$group->id} = $group;
}
}
......@@ -2117,7 +2120,13 @@ sub _set_global_validator {
# other_bugs to set_all in order for it to behave properly.
sub set_all {
my $self = shift;
my ($params) = @_;
my ($input_params) = @_;
# Clone the data as we are going to alter it, and this would affect
# subsequent bugs when calling set_all() again, as some fields would
# be modified or no longer defined.
my $params = {};
%$params = %$input_params;
# You cannot mark bugs as duplicate when changing several bugs at once
# (because currently there is no way to check for duplicate loops in that
......@@ -2480,20 +2489,18 @@ sub _set_product {
$self->set_target_milestone($tm_name);
}
}
if ($product_changed) {
# Remove groups that can't be set in the new product, or that aren't
# active.
#
# Remove groups that can't be set in the new product.
# We copy this array because the original array is modified while we're
# working, and that confuses "foreach".
my @current_groups = @{$self->groups_in};
foreach my $group (@current_groups) {
if (!$group->is_active or !$product->group_is_valid($group)) {
if (!$product->group_is_valid($group)) {
$self->remove_group($group);
}
}
# Make sure the bug is in all the mandatory groups for the new product.
foreach my $group (@{$product->groups_mandatory}) {
$self->add_group($group);
......@@ -2736,18 +2743,25 @@ sub modify_keywords {
sub add_group {
my ($self, $group) = @_;
# Invalid ids are silently ignored. (We can't tell people whether
# or not a group exists.)
$group = Bugzilla::Group->check($group) if !blessed $group;
return if !$group->is_active or !$group->is_bug_group;
# If the user enters "FoO" but the DB has "Foo", $group->name would
# return "Foo" and thus revealing the existence of the group name.
# So we have to store and pass the name as entered by the user to
# the error message, if we have it.
my $group_name = blessed($group) ? $group->name : $group;
my $args = { name => $group_name, product => $self->product,
bug_id => $self->id, action => 'add' };
$group = Bugzilla::Group->check_no_disclose($args) if !blessed $group;
# If the bug is already in this group, then there is nothing to do.
return if $self->in_group($group);
# Make sure that bugs in this product can actually be restricted
# to this group by the current user.
$self->product_obj->group_is_settable($group)
|| ThrowUserError('group_invalid_restriction',
{ product => $self->product, group => $group,
bug => $self });
|| ThrowUserError('group_restriction_not_allowed', $args);
# OtherControl people can add groups only during a product change,
# and only when the group is not NA for them.
......@@ -2756,37 +2770,38 @@ sub add_group {
if (!$self->{_old_product_name}
|| $controls->{othercontrol} == CONTROLMAPNA)
{
ThrowUserError('group_change_denied',
{ bug => $self, group => $group });
ThrowUserError('group_restriction_not_allowed', $args);
}
}
my $current_groups = $self->groups_in;
if (!grep($group->id == $_->id, @$current_groups)) {
push(@$current_groups, $group);
}
push(@$current_groups, $group);
}
sub remove_group {
my ($self, $group) = @_;
$group = Bugzilla::Group->check($group) if !blessed $group;
# First, check if this is a valid group for this product.
# You can *always* remove a group that is not valid for this product
# or that is not active, so we don't do any other checks if either of
# those are the case. (Users might remove inactive groups, and set_product
# removes groups that aren't valid for this product.)
#
# This particularly happens when isbuggroup is no longer 1, and we're
# moving a bug to a new product.
if ($group->is_active and $self->product_obj->group_is_valid($group)) {
# See add_group() for the reason why we store the user input.
my $group_name = blessed($group) ? $group->name : $group;
my $args = { name => $group_name, product => $self->product,
bug_id => $self->id, action => 'remove' };
$group = Bugzilla::Group->check_no_disclose($args) if !blessed $group;
# If the bug isn't in this group, then either the name is misspelled,
# or the group really doesn't exist. Let the user know about this problem.
$self->in_group($group) || ThrowUserError('group_invalid_removal', $args);
# Check if this is a valid group for this product. You can *always*
# remove a group that is not valid for this product (set_product does this).
# This particularly happens when we're moving a bug to a new product.
# You still have to be a member of an inactive group to remove it.
if ($self->product_obj->group_is_valid($group)) {
my $controls = $self->product_obj->group_controls->{$group->id};
# Nobody can ever remove a Mandatory group.
if ($controls->{membercontrol} == CONTROLMAPMANDATORY) {
ThrowUserError('group_invalid_removal',
{ product => $self->product, group => $group,
bug => $self });
# Nobody can ever remove a Mandatory group, unless it became inactive.
if ($controls->{membercontrol} == CONTROLMAPMANDATORY && $group->is_active) {
ThrowUserError('group_invalid_removal', $args);
}
# OtherControl people can remove groups only during a product change,
......@@ -2796,12 +2811,11 @@ sub remove_group {
|| $controls->{othercontrol} == CONTROLMAPMANDATORY
|| $controls->{othercontrol} == CONTROLMAPNA)
{
ThrowUserError('group_change_denied',
{ bug => $self, group => $group });
ThrowUserError('group_invalid_removal', $args);
}
}
}
my $current_groups = $self->groups_in;
@$current_groups = grep { $_->id != $group->id } @$current_groups;
}
......@@ -3456,6 +3470,11 @@ sub groups_in {
return $self->{'groups_in'};
}
sub in_group {
my ($self, $group) = @_;
return grep($_->id == $group->id, @{$self->groups_in}) ? 1 : 0;
}
sub user {
my $self = shift;
return $self->{'user'} if exists $self->{'user'};
......@@ -4073,7 +4092,7 @@ sub AUTOLOAD {
*$AUTOLOAD = sub {
my $self = shift;
return $self->{$attr} if defined $self->{$attr};
return $self->{$attr} if exists $self->{$attr};
$self->{_multi_selects} ||= [Bugzilla->get_fields(
{custom => 1, type => FIELD_TYPE_MULTI_SELECT })];
......
......@@ -440,7 +440,9 @@ sub redirect_search_url {
$self->clean_search_url();
if ($user->id) {
# Make sure we have params still after cleaning otherwise we
# do not want to store a list_id for an empty search.
if ($user->id && $self->param) {
# Insert a placeholder Bugzilla::Search::Recent, so that we know what
# the id of the resulting search will be. This is then pulled out
# of the Referer header when viewing show_bug.cgi to know what
......
......@@ -197,7 +197,7 @@ use File::Basename;
# CONSTANTS
#
# Bugzilla version
use constant BUGZILLA_VERSION => "4.0.1";
use constant BUGZILLA_VERSION => "4.0.2";
# These are unique values that are unlikely to match a string or a number,
# to be used in criteria for match() functions and other things. They start
......@@ -431,8 +431,8 @@ use constant MAX_STS_AGE => 604800;
# 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');
'irc', 'ircs', 'mid', 'news', 'nntp', 'prospero',
'telnet', 'view-source', 'wais');
# Valid MIME types for attachments.
use constant LEGAL_CONTENT_TYPES => ('application', 'audio', 'image', 'message',
......
......@@ -2085,8 +2085,8 @@ sub get_add_column_ddl {
if defined $init_value;
if (defined $definition->{REFERENCES}) {
push(@statements, $self->get_add_fk_sql($table, $column,
$definition->{REFERENCES}));
push(@statements, $self->get_add_fks_sql($table, { $column =>
$definition->{REFERENCES} }));
}
return (@statements);
......
......@@ -426,6 +426,21 @@ sub ValidateGroupName {
return $ret;
}
sub check_no_disclose {
my ($class, $params) = @_;
my $action = delete $params->{action};
$action =~ /^(?:add|remove)$/
or ThrowCodeError('bad_arg', { argument => $action,
function => "${class}::check_no_disclose" });
$params->{_error} = ($action eq 'add') ? 'group_restriction_not_allowed'
: 'group_invalid_removal';
my $group = $class->check($params);
return $group;
}
###############################
### Validators ###
###############################
......@@ -524,6 +539,47 @@ be a member of this group.
=over
=item C<check_no_disclose>
=over
=item B<Description>
Throws an error if the user cannot add or remove this group to/from a given
bug, but doesn't specify if this is because the group doesn't exist, or the
user is not allowed to edit this group restriction.
=item B<Params>
This method takes a single hashref as argument, with the following keys:
=over
=item C<name>
C<string> The name of the group to add or remove.
=item C<bug_id>
C<integer> The ID of the bug to which the group change applies.
=item C<product>
C<string> The name of the product the bug belongs to.
=item C<action>
C<string> Must be either C<add> or C<remove>, depending on whether the group
must be added or removed from the bug. Any other value will generate an error.
=back
=item C<Returns>
A C<Bugzilla::Group> object on success, else an error is thrown.
=back
=item C<check_remove>
=over
......
......@@ -164,8 +164,20 @@ sub install_module {
if (!$module) {
die install_string('no_such_module', { module => $name }) . "\n";
}
my $version = $module->cpan_version;
my $module_name = $name;
if ($name eq 'LWP::UserAgent' && $^V lt v5.8.8) {
# LWP 6.x requires Perl 5.8.8 or newer.
# As PAUSE only indexes the very last version of each module,
# we have to specify the path to the tarball ourselves.
$name = 'GAAS/libwww-perl-5.837.tar.gz';
# This tarball contains LWP::UserAgent 5.835.
$version = '5.835';
}
print install_string('install_module',
{ module => $name, version => $module->cpan_version }) . "\n";
{ module => $module_name, version => $version }) . "\n";
if (_always_test($name)) {
CPAN::Shell->install($name);
......
......@@ -200,12 +200,6 @@ sub OPTIONAL_MODULES {
version => 0,
feature => ['graphical_reports'],
},
{
package => 'XML-Twig',
module => 'XML::Twig',
version => 0,
feature => ['moving', 'updates'],
},
{
package => 'MIME-tools',
# MIME::Parser is packaged as MIME::Tools on ActiveState Perl
......@@ -219,6 +213,12 @@ sub OPTIONAL_MODULES {
version => 0,
feature => ['updates'],
},
{
package => 'XML-Twig',
module => 'XML::Twig',
version => 0,
feature => ['moving', 'updates'],
},
{
package => 'PatchReader',
module => 'PatchReader',
......@@ -530,9 +530,8 @@ sub print_module_instructions {
or ($output and $check_results->{any_missing}) ) ? 1 : 0;
# We only print the PPM repository note if we have to.
if ($need_module_instructions and ON_ACTIVESTATE) {
my $perl_ver = sprintf('%vd', $^V);
my $perl_ver = sprintf('%vd', $^V);
if ($need_module_instructions && ON_ACTIVESTATE && vers_cmp($perl_ver, '5.12') < 0) {
# URL when running Perl 5.8.x.
my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
# Packages for Perl 5.10 are not compatible with Perl 5.8.
......
......@@ -680,10 +680,12 @@ sub groups_mandatory {
# if this group can be validly set by the currently-logged-in user.
sub group_is_settable {
my ($self, $group) = @_;
my $group_id = blessed($group) ? $group->id : $group;
my $is_mandatory = grep { $group_id == $_->id }
return 0 unless ($group->is_active && $group->is_bug_group);
my $is_mandatory = grep { $group->id == $_->id }
@{ $self->groups_mandatory };
my $is_available = grep { $group_id == $_->id }
my $is_available = grep { $group->id == $_->id }
@{ $self->groups_available };
return ($is_mandatory or $is_available) ? 1 : 0;
}
......@@ -948,7 +950,7 @@ a bug. (In fact, the user I<must> set the Mandatory group on the bug.)
=over
=item C<$group> - Either a numeric group id or a L<Bugzilla::Group> object.
=item C<$group> - A L<Bugzilla::Group> object.
=back
......
......@@ -222,7 +222,8 @@ sub quoteUrls {
# mailto:
# Use |<nothing> so that $1 is defined regardless
$text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
# &#64; is the encoded '@' character.
$text =~ s~\b(mailto:|)?([\w\.\-\+\=]+&\#64;[\w\-]+(?:\.[\w\-]+)+)\b
~<a href=\"mailto:$2\">$1$2</a>~igx;
# attachment links
......@@ -256,8 +257,10 @@ sub quoteUrls {
~get_bug_link($1, $1)
~egmx;
# Now remove the encoding hacks
$text =~ s/\0\0(\d+)\0\0/$things[$1]/eg;
# Now remove the encoding hacks in reverse order
for (my $i = $#things; $i >= 0; $i--) {
$text =~ s/\0\0($i)\0\0/$things[$i]/eg;
}
$text =~ s/$chr1\0/\0/g;
return $text;
......@@ -685,6 +688,9 @@ sub create {
# as prefix. In addition it replaces a ' ' by a '_'.
css_class_quote => \&Bugzilla::Util::css_class_quote ,
# Removes control characters and trims extra whitespace.
clean_text => \&Bugzilla::Util::clean_text ,
quoteUrls => [ sub {
my ($context, $bug, $comment) = @_;
return sub {
......@@ -919,6 +925,10 @@ sub create {
# $template->process() is called.
'field_descs' => sub { return template_var('field_descs') },
# Calling bug/field-help.none.tmpl once per label is very
# expensive, so we generate it once per-language.
'help_html' => sub { return template_var('help_html') },
'install_string' => \&Bugzilla::Install::Util::install_string,
'report_columns' => \&Bugzilla::Search::REPORT_COLUMNS,
......
......@@ -240,14 +240,11 @@ sub xml_quote {
return $var;
}
# This function must not be relied upon to return a valid string to pass to
# the DB or the user in UTF-8 situations. The only thing you can rely upon
# it for is that if you url_decode a string, it will url_encode back to the
# exact same thing.
sub url_decode {
my ($todecode) = (@_);
$todecode =~ tr/+/ /; # pluses become spaces
$todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
utf8::decode($todecode) if Bugzilla->params->{'utf8'};
return $todecode;
}
......
......@@ -2275,9 +2275,8 @@ is private, otherwise it is assumed to be public.
=item C<groups> (array) - An array of group names to put this
bug into. You can see valid group names on the Permissions
tab of the Preferences screen, or, if you are an administrator,
in the Groups control panel. Note that invalid group names or
groups that the bug can't be restricted to are silently ignored. If
you don't specify this argument, then a bug will be added into
in the Groups control panel.
If you don't specify this argument, then the bug will be added into
all the groups that are set as being "Default" for this product. (If
you want to avoid that, you should specify C<groups> as an empty array.)
......@@ -2338,6 +2337,11 @@ You didn't specify a summary for the bug.
You specified values in the C<blocks> or C<depends_on> fields
that would cause a circular dependency between bugs.
=item 120 (Group Restriction Denied)
You tried to restrict the bug to a group which does not exist, or which
you cannot use with this product.
=item 504 (Invalid User)
Either the QA Contact, Assignee, or CC lists have some invalid user
......@@ -2354,7 +2358,9 @@ B<Required>, due to a bug in Bugzilla.
=item The C<groups> argument was added in Bugzilla B<4.0>. Before
Bugzilla 4.0, bugs were only added into Mandatory groups by this
method.
method. Since Bugzilla B<4.0.2>, passing an illegal group name will
throw an error. In Bugzilla 4.0 and 4.0.1, illegal group names were
silently ignored.
=item The C<comment_is_private> argument was added in Bugzilla B<4.0>.
Before Bugzilla 4.0, you had to use the undocumented C<commentprivacy>
......
......@@ -109,8 +109,8 @@ use constant WS_ERROR_CODE => {
dupe_loop_detected => 118,
dupe_id_required => 119,
# Group errors
group_change_denied => 120,
group_invalid_restriction => 120,
group_invalid_removal => 120,
group_restriction_not_allowed => 120,
# Status/Resolution errors
missing_resolution => 121,
resolution_not_allowed => 122,
......
......@@ -23,7 +23,11 @@ package Bugzilla::WebService::Server::XMLRPC;
use strict;
use XMLRPC::Transport::HTTP;
use Bugzilla::WebService::Server;
our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
if ($ENV{MOD_PERL}) {
our @ISA = qw(XMLRPC::Transport::HTTP::Apache Bugzilla::WebService::Server);
} else {
our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
}
use Bugzilla::WebService::Constants;
......
......@@ -74,10 +74,13 @@ local our $vars = {};
# Determine whether to use the action specified by the user or the default.
my $action = $cgi->param('action') || 'view';
my $format = $cgi->param('format') || '';
# You must use the appropriate urlbase/sslbase param when doing anything
# but viewing an attachment.
if ($action ne 'view') {
# but viewing an attachment, or a raw diff.
if ($action ne 'view'
&& (($action !~ /^(?:interdiff|diff)$/) || $format ne 'raw'))
{
do_ssl_redirect_if_required();
if ($cgi->url_is_attachment_base) {
$cgi->redirect_to_urlbase;
......@@ -167,11 +170,12 @@ sub validateID {
# non-natural, so use the original value from $cgi in our exception
# message here.
detaint_natural($attach_id)
|| ThrowUserError("invalid_attach_id", { attach_id => $cgi->param($param) });
|| ThrowUserError("invalid_attach_id",
{ attach_id => scalar $cgi->param($param) });
# Make sure the attachment exists in the database.
my $attachment = new Bugzilla::Attachment($attach_id)
|| ThrowUserError("invalid_attach_id", { attach_id => $attach_id });
|| ThrowUserError("invalid_attach_id", { attach_id => $attach_id });
return $attachment if ($dont_validate_access || check_can_access($attachment));
}
......@@ -187,7 +191,8 @@ sub check_can_access {
&& !$user->is_insider)
{
ThrowUserError('auth_failure', {action => 'access',
object => 'attachment'});
object => 'attachment',
attach_id => $attachment->id});
}
return 1;
}
......@@ -230,34 +235,51 @@ sub validateContext
return $context;
}