diff options
Diffstat (limited to 'Bugzilla')
-rw-r--r-- | Bugzilla/Attachment.pm | 50 | ||||
-rw-r--r-- | Bugzilla/Bug.pm | 40 | ||||
-rw-r--r-- | Bugzilla/BugMail.pm | 23 | ||||
-rw-r--r-- | Bugzilla/CGI.pm | 4 | ||||
-rw-r--r-- | Bugzilla/Component.pm | 6 | ||||
-rw-r--r-- | Bugzilla/Config/Advanced.pm | 6 | ||||
-rw-r--r-- | Bugzilla/Constants.pm | 26 | ||||
-rw-r--r-- | Bugzilla/Error.pm | 40 | ||||
-rw-r--r-- | Bugzilla/Field.pm | 167 | ||||
-rw-r--r-- | Bugzilla/Flag.pm | 41 | ||||
-rw-r--r-- | Bugzilla/FlagType.pm | 7 | ||||
-rw-r--r-- | Bugzilla/Group.pm | 5 | ||||
-rw-r--r-- | Bugzilla/Install.pm | 4 | ||||
-rw-r--r-- | Bugzilla/Install/DB.pm | 5 | ||||
-rw-r--r-- | Bugzilla/Install/Filesystem.pm | 14 | ||||
-rw-r--r-- | Bugzilla/Mailer.pm | 42 | ||||
-rw-r--r-- | Bugzilla/Product.pm | 9 | ||||
-rw-r--r-- | Bugzilla/Search.pm | 3 | ||||
-rw-r--r-- | Bugzilla/Search/Quicksearch.pm | 3 | ||||
-rw-r--r-- | Bugzilla/Send/Sendmail.pm | 95 | ||||
-rw-r--r-- | Bugzilla/Template.pm | 25 | ||||
-rw-r--r-- | Bugzilla/Token.pm | 2 | ||||
-rw-r--r-- | Bugzilla/User.pm | 7 | ||||
-rw-r--r-- | Bugzilla/UserAgent.pm | 247 | ||||
-rw-r--r-- | Bugzilla/WebService/Bug.pm | 47 | ||||
-rw-r--r-- | Bugzilla/WebService/User.pm | 4 |
26 files changed, 791 insertions, 131 deletions
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm index b1f47d0cd..b80228e78 100644 --- a/Bugzilla/Attachment.pm +++ b/Bugzilla/Attachment.pm @@ -415,6 +415,53 @@ sub datasize { return $self->{datasize}; } +=over + +=item C<linecount> + +the number of lines of the attachment content + +=back + +=cut + +# linecount allows for getting the number of lines of an attachment +# from the database directly if the data has not yet been loaded for +# performance reasons. + +sub linecount { + my ($self) = @_; + + return $self->{linecount} if exists $self->{linecount}; + + # Limit this to just text/* attachments as this could + # cause strange results for binary attachments. + return if $self->contenttype !~ /^text\//; + + # If the data has already been loaded, we can just determine + # line count from the data directly. + if ($self->{data}) { + $self->{linecount} = $self->{data} =~ tr/\n/\n/; + } + else { + $self->{linecount} = + int(Bugzilla->dbh->selectrow_array(" + SELECT LENGTH(attach_data.thedata)-LENGTH(REPLACE(attach_data.thedata,'\n',''))/LENGTH('\n') + FROM attach_data WHERE id = ?", undef, $self->id)); + + } + + # If we still do not have a linecount either the attachment + # is stored in a local file or has been deleted. If the former, + # we call self->data to force a load from the filesystem and + # then do a split on newlines and count again. + unless ($self->{linecount}) { + $self->{linecount} = $self->data =~ tr/\n/\n/; + } + + return $self->{linecount}; +} + sub _get_local_filename { my $self = shift; my $hash = ($self->id % 100) + 100; @@ -458,7 +505,8 @@ sub flag_types { my $vars = { target_type => 'attachment', product_id => $self->bug->product_id, component_id => $self->bug->component_id, - attach_id => $self->id }; + attach_id => $self->id, + active_or_has_flags => $self->bug_id }; $self->{flag_types} = Bugzilla::Flag->_flag_types($vars); return $self->{flag_types}; diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index a848c866b..5fb4551e4 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -1632,6 +1632,14 @@ sub _check_groups { : $params->{product}; my %add_groups; + # BMO: Allow extension to add groups before the + # real checks are done. + Bugzilla::Hook::process('bug_check_groups', { + product => $product, + group_names => $group_names, + add_groups => \%add_groups + }); + # In email or WebServices, when the "groups" item actually # isn't specified, then just add the default groups. if (!defined $group_names) { @@ -1650,7 +1658,12 @@ sub _check_groups { foreach my $name (@$group_names) { my $group = Bugzilla::Group->check_no_disclose({ %args, name => $name }); - if (!$product->group_is_settable($group)) { + # BMO: Do not check group_is_settable if the group is + # already added, such as from the extension hook. group_is_settable + # will reject any group the user is not currently in. + if (!$add_groups{$group->id} + && !$product->group_is_settable($group)) + { ThrowUserError('group_restriction_not_allowed', { %args, name => $name }); } $add_groups{$group->id} = $group; @@ -1659,7 +1672,7 @@ sub _check_groups { # Now enforce mandatory groups. $add_groups{$_->id} = $_ foreach @{ $product->groups_mandatory }; - + my @add_groups = values %add_groups; return \@add_groups; } @@ -3256,6 +3269,26 @@ sub depends_on_obj { return $self->{depends_on_obj}; } +sub duplicates { + my $self = shift; + return $self->{duplicates} if exists $self->{duplicates}; + return [] if $self->{error}; + $self->{duplicates} = Bugzilla::Bug->new_from_list($self->duplicate_ids); + return $self->{duplicates}; +} + +sub duplicate_ids { + my $self = shift; + return $self->{duplicate_ids} if exists $self->{duplicate_ids}; + return [] if $self->{error}; + + my $dbh = Bugzilla->dbh; + $self->{duplicate_ids} = + $dbh->selectcol_arrayref('SELECT dupe FROM duplicates WHERE dupe_of = ?', + undef, $self->id); + return $self->{duplicate_ids}; +} + sub flag_types { my ($self) = @_; return $self->{'flag_types'} if exists $self->{'flag_types'}; @@ -3264,7 +3297,8 @@ sub flag_types { my $vars = { target_type => 'bug', product_id => $self->{product_id}, component_id => $self->{component_id}, - bug_id => $self->bug_id }; + bug_id => $self->bug_id, + active_or_has_flags => $self->bug_id }; $self->{'flag_types'} = Bugzilla::Flag->_flag_types($vars); return $self->{'flag_types'}; diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm index 55eeeab25..efbba5215 100644 --- a/Bugzilla/BugMail.pm +++ b/Bugzilla/BugMail.pm @@ -130,7 +130,7 @@ sub Send { my $comments = $bug->comments({ after => $start, to => $end }); # Skip empty comments. @$comments = grep { $_->type || $_->body =~ /\S/ } @$comments; - + ########################################################################### # Start of email filtering code ########################################################################### @@ -267,7 +267,16 @@ sub Send { } # Make sure the user isn't in the nomail list, and the dep check passed. - if ($user->email_enabled && $dep_ok) { + # BMO: normally we would check the login names of the + # user objects in the bugmail_recipients hook instead. There we could + # remove the (bugs|tld)$ addresses from the recipients. But the hook comes + # to early before watchers are decided and these addresses need to be + # there for this to work. This may change with recent enhancements to + # component watching. need to investigate further. + if ($user->email_enabled && $dep_ok && + ($user->login !~ /bugs$/) && + ($user->login !~ /\.tld$/)) + { # OK, OK, if we must. Email the user. $sent_mail = sendMail( { to => $user, @@ -352,6 +361,14 @@ sub sendMail { push(@watchingrel, 'None') unless @watchingrel; push @watchingrel, map { user_id_to_login($_) } @$watchingRef; + my @changedfields = uniq map { $_->{field_name} } @display_diffs; + + # Add Attachment Created to changedfields if one or more + # comments contain information about a new attachment + if (grep($_->type == CMT_ATTACHMENT_CREATED, @send_comments)) { + push(@changedfields, 'Attachment Created'); + } + my $vars = { date => $date, to_user => $user, @@ -362,7 +379,7 @@ sub sendMail { reasonswatchheader => join(" ", @watchingrel), changer => $changer, diffs => \@display_diffs, - changedfields => [uniq map { $_->{field_name} } @display_diffs], + changedfields => \@changedfields, new_comments => \@send_comments, threadingmarker => build_thread_marker($bug->id, $user->id, !$bug->lastdiffed), }; diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm index e0e1c40ba..9d8a1c48f 100644 --- a/Bugzilla/CGI.pm +++ b/Bugzilla/CGI.pm @@ -306,6 +306,10 @@ sub header { unshift(@_, '-x_frame_options' => 'SAMEORIGIN'); } + # Add X-XSS-Protection header to prevent simple XSS attacks + # and enforce the blocking (rather than the rewriting) mode. + unshift(@_, '-x_xss_protection' => '1; mode=block'); + return $self->SUPER::header(@_) || ""; } diff --git a/Bugzilla/Component.pm b/Bugzilla/Component.pm index dc3cc1b9e..ad5166a0f 100644 --- a/Bugzilla/Component.pm +++ b/Bugzilla/Component.pm @@ -371,11 +371,13 @@ sub default_qa_contact { } sub flag_types { - my $self = shift; + my ($self, $params) = @_; + $params ||= {}; if (!defined $self->{'flag_types'}) { my $flagtypes = Bugzilla::FlagType::match({ product_id => $self->product_id, - component_id => $self->id }); + component_id => $self->id, + %$params }); $self->{'flag_types'} = {}; $self->{'flag_types'}->{'bug'} = diff --git a/Bugzilla/Config/Advanced.pm b/Bugzilla/Config/Advanced.pm index faab6bbbd..f5653ee86 100644 --- a/Bugzilla/Config/Advanced.pm +++ b/Bugzilla/Config/Advanced.pm @@ -62,6 +62,12 @@ use constant get_param_list => ( default => 'off', checker => \&check_multi }, + + { + name => 'disable_bug_updates', + type => 'b', + default => 0 + }, ); 1; diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index e215f7979..5f5489aea 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -262,7 +262,8 @@ use constant AUTH_NO_SUCH_USER => 5; use constant AUTH_LOCKOUT => 6; # The minimum length a password must have. -use constant USER_PASSWORD_MIN_LENGTH => 6; +# BMO uses 8 characters. +use constant USER_PASSWORD_MIN_LENGTH => 8; use constant LOGIN_OPTIONAL => 0; use constant LOGIN_NORMAL => 1; @@ -431,8 +432,8 @@ use constant MAX_LOGIN_ATTEMPTS => 5; use constant LOGIN_LOCKOUT_INTERVAL => 30; # The maximum number of seconds the Strict-Transport-Security header -# will remain valid. Default is one week. -use constant MAX_STS_AGE => 604800; +# will remain valid. BMO uses one month. +use constant MAX_STS_AGE => 2629744; # Protocols which are considered as safe. use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https', @@ -445,15 +446,16 @@ use constant LEGAL_CONTENT_TYPES => ('application', 'audio', 'image', 'message', use constant contenttypes => { - "html"=> "text/html" , - "rdf" => "application/rdf+xml" , - "atom"=> "application/atom+xml" , - "xml" => "application/xml" , - "js" => "application/x-javascript" , - "json"=> "application/json" , - "csv" => "text/csv" , - "png" => "image/png" , - "ics" => "text/calendar" , + "html" => "text/html" , + "rdf" => "application/rdf+xml" , + "atom" => "application/atom+xml" , + "xml" => "application/xml" , + "dtd" => "application/xml-dtd" , + "js" => "application/x-javascript" , + "json" => "application/json" , + "csv" => "text/csv" , + "png" => "image/png" , + "ics" => "text/calendar" , }; # Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode. diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm index 395cc0dc9..17a7a948a 100644 --- a/Bugzilla/Error.pm +++ b/Bugzilla/Error.pm @@ -26,7 +26,7 @@ package Bugzilla::Error; use strict; use base qw(Exporter); -@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError); +@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError ThrowErrorPage); use Bugzilla::Constants; use Bugzilla::WebService::Constants; @@ -212,6 +212,44 @@ END exit; } +sub ThrowErrorPage { + # BMO customisation for bug 659231 + my ($template_name, $message) = @_; + + my $dbh = Bugzilla->dbh; + $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction(); + + if (Bugzilla->error_mode == ERROR_MODE_DIE) { + die("error: $message"); + } + + if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT + || Bugzilla->error_mode == ERROR_MODE_JSON_RPC) + { + my $code = ERROR_UNKNOWN_TRANSIENT; + if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) { + die SOAP::Fault->faultcode($code)->faultstring($message); + } else { + my $server = Bugzilla->_json_server; + $server->raise_error(code => 100000 + $code, + message => $message, + id => $server->{_bz_request_id}, + version => $server->version); + die if _in_eval(); + $server->response($server->error_response_header); + } + } else { + my $cgi = Bugzilla->cgi; + my $template = Bugzilla->template; + my $vars = {}; + $vars->{message} = $message; + print $cgi->header(); + $template->process($template_name, $vars) + || ThrowTemplateError($template->error()); + exit; + } +} + 1; __END__ diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm index dbee5df3d..5442c6401 100644 --- a/Bugzilla/Field.pm +++ b/Bugzilla/Field.pm @@ -78,6 +78,8 @@ use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Util; use List::MoreUtils qw(any); +use Bugzilla::Config qw(SetParam write_params); +use Bugzilla::Hook; use Scalar::Util qw(blessed); @@ -918,53 +920,64 @@ sub remove_from_db { ThrowUserError('customfield_not_obsolete', {'name' => $self->name }); } - $dbh->bz_start_transaction(); + # BMO: disable bug updates during field creation + # using an eval as try/finally + eval { + SetParam('disable_bug_updates', 1); + write_params(); - # Check to see if bug activity table has records (should be fast with index) - my $has_activity = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs_activity - WHERE fieldid = ?", undef, $self->id); - if ($has_activity) { - ThrowUserError('customfield_has_activity', {'name' => $name }); - } + $dbh->bz_start_transaction(); + + # Check to see if bug activity table has records (should be fast with index) + my $has_activity = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs_activity + WHERE fieldid = ?", undef, $self->id); + if ($has_activity) { + ThrowUserError('customfield_has_activity', {'name' => $name }); + } - # Check to see if bugs table has records (slow) - my $bugs_query = ""; + # Check to see if bugs table has records (slow) + my $bugs_query = ""; - if ($self->type == FIELD_TYPE_MULTI_SELECT) { - $bugs_query = "SELECT COUNT(*) FROM bug_$name"; - } - else { - $bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL"; - if ($self->type != FIELD_TYPE_BUG_ID && $self->type != FIELD_TYPE_DATETIME) { - $bugs_query .= " AND $name != ''"; + if ($self->type == FIELD_TYPE_MULTI_SELECT) { + $bugs_query = "SELECT COUNT(*) FROM bug_$name"; } - # Ignore the default single select value - if ($self->type == FIELD_TYPE_SINGLE_SELECT) { - $bugs_query .= " AND $name != '---'"; + else { + $bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL"; + if ($self->type != FIELD_TYPE_BUG_ID && $self->type != FIELD_TYPE_DATETIME) { + $bugs_query .= " AND $name != ''"; + } + # Ignore the default single select value + if ($self->type == FIELD_TYPE_SINGLE_SELECT) { + $bugs_query .= " AND $name != '---'"; + } } - } - my $has_bugs = $dbh->selectrow_array($bugs_query); - if ($has_bugs) { - ThrowUserError('customfield_has_contents', {'name' => $name }); - } + my $has_bugs = $dbh->selectrow_array($bugs_query); + if ($has_bugs) { + ThrowUserError('customfield_has_contents', {'name' => $name }); + } - # Once we reach here, we should be OK to delete. - $dbh->do('DELETE FROM fielddefs WHERE id = ?', undef, $self->id); + # Once we reach here, we should be OK to delete. + $dbh->do('DELETE FROM fielddefs WHERE id = ?', undef, $self->id); - my $type = $self->type; + my $type = $self->type; - # the values for multi-select are stored in a seperate table - if ($type != FIELD_TYPE_MULTI_SELECT) { - $dbh->bz_drop_column('bugs', $name); - } + # the values for multi-select are stored in a seperate table + if ($type != FIELD_TYPE_MULTI_SELECT) { + $dbh->bz_drop_column('bugs', $name); + } - if ($self->is_select) { - # Delete the table that holds the legal values for this field. - $dbh->bz_drop_field_tables($self); - } + if ($self->is_select) { + # Delete the table that holds the legal values for this field. + $dbh->bz_drop_field_tables($self); + } - $dbh->bz_commit_transaction() + $dbh->bz_commit_transaction(); + }; + my $error = "$@"; + SetParam('disable_bug_updates', 0); + write_params(); + die $error if $error; } =pod @@ -1016,36 +1029,72 @@ sub create { # the parameter isn't sent to create(). $params->{sortkey} = undef if !exists $params->{sortkey}; $params->{type} ||= 0; - - $dbh->bz_start_transaction(); - $class->check_required_create_fields(@_); - my $field_values = $class->run_create_validators($params); - my $visibility_values = delete $field_values->{visibility_values}; - my $field = $class->insert_create_data($field_values); - - $field->set_visibility_values($visibility_values); - $field->_update_visibility_values(); - - $dbh->bz_commit_transaction(); - if ($field->custom) { - my $name = $field->name; - my $type = $field->type; - if (SQL_DEFINITIONS->{$type}) { - # Create the database column that stores the data for this field. - $dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type}); + # BMO: disable bug updates during field creation + # using an eval as try/finally + my $field; + eval { + if ($params->{'custom'}) { + SetParam('disable_bug_updates', 1); + write_params(); } - if ($field->is_select) { - # Create the table that holds the legal values for this field. - $dbh->bz_add_field_tables($field); + # Purpose: if the field is active in the fields list before all of the + # data structures are created, anything accessing Bug.pm will crash. So + # stash a copy of the intended obsolete value for later and force it + # to be obsolete on initial creation. + # Upstreaming: https://bugzilla.mozilla.org/show_bug.cgi?id=531243 + my $original_obsolete; + if ($params->{'custom'}) { + $original_obsolete = $params->{'obsolete'}; + $params->{'obsolete'} = 1; } - if ($type == FIELD_TYPE_SINGLE_SELECT) { - # Insert a default value of "---" into the legal values table. - $dbh->do("INSERT INTO $name (value) VALUES ('---')"); + $dbh->bz_start_transaction(); + $class->check_required_create_fields(@_); + my $field_values = $class->run_create_validators($params); + my $visibility_values = delete $field_values->{visibility_values}; + my $field = $class->insert_create_data($field_values); + + $field->set_visibility_values($visibility_values); + $field->_update_visibility_values(); + + $dbh->bz_commit_transaction(); + + if ($field->custom) { + # Restore the obsolete value that got stashed earlier (in memory) + $field->set_obsolete($original_obsolete); + + my $name = $field->name; + my $type = $field->type; + if (SQL_DEFINITIONS->{$type}) { + # Create the database column that stores the data for this field. + $dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type}); + } + + if ($field->is_select) { + # Create the table that holds the legal values for this field. + $dbh->bz_add_field_tables($field); + } + + if ($type == FIELD_TYPE_SINGLE_SELECT) { + # Insert a default value of "---" into the legal values table. + $dbh->do("INSERT INTO $name (value) VALUES ('---')"); + } + + # Safe to write the original 'obsolete' value to the database now + $field->update; } + }; + + my $error = "$@"; + if ($params->{'custom'}) { + SetParam('disable_bug_updates', 0); + write_params(); } + die $error if $error; + + Bugzilla::Hook::process("field_end_of_create", { field => $field }); return $field; } diff --git a/Bugzilla/Flag.pm b/Bugzilla/Flag.pm index a727532a6..35719ee8f 100644 --- a/Bugzilla/Flag.pm +++ b/Bugzilla/Flag.pm @@ -86,6 +86,8 @@ use constant DB_COLUMNS => qw( type_id bug_id attach_id + creation_date + modification_date requestee_id setter_id status @@ -134,6 +136,14 @@ Returns the ID of the attachment this flag belongs to, if any. Returns the status '+', '-', '?' of the flag. +=item C<creation_date> + +Returns the timestamp when the flag was created. + +=item C<modification_date> + +Returns the timestamp when the flag was last modified. + =back =cut @@ -146,6 +156,8 @@ sub attach_id { return $_[0]->{'attach_id'}; } sub status { return $_[0]->{'status'}; } sub setter_id { return $_[0]->{'setter_id'}; } sub requestee_id { return $_[0]->{'requestee_id'}; } +sub creation_date { return $_[0]->{'creation_date'}; } +sub modification_date { return $_[0]->{'modification_date'}; } ############################### #### Methods #### @@ -284,7 +296,7 @@ sub count { sub set_flag { my ($class, $obj, $params) = @_; - my ($bug, $attachment); + my ($bug, $attachment, $obj_flag, $requestee_changed); if (blessed($obj) && $obj->isa('Bugzilla::Attachment')) { $attachment = $obj; $bug = $attachment->bug; @@ -322,13 +334,14 @@ sub set_flag { ($obj_flagtype) = grep { $_->id == $flag->type_id } @{$obj->flag_types}; push(@{$obj_flagtype->{flags}}, $flag); } - my ($obj_flag) = grep { $_->id == $flag->id } @{$obj_flagtype->{flags}}; + ($obj_flag) = grep { $_->id == $flag->id } @{$obj_flagtype->{flags}}; # If the flag has the correct type but cannot be found above, this means # the flag is going to be removed (e.g. because this is a pending request # and the attachment is being marked as obsolete). return unless $obj_flag; - $class->_validate($obj_flag, $obj_flagtype, $params, $bug, $attachment); + ($obj_flag, $requestee_changed) = + $class->_validate($obj_flag, $obj_flagtype, $params, $bug, $attachment); } # Create a new flag. elsif ($params->{type_id}) { @@ -360,12 +373,21 @@ sub set_flag { } } - $class->_validate(undef, $obj_flagtype, $params, $bug, $attachment); + ($obj_flag, $requestee_changed) = + $class->_validate(undef, $obj_flagtype, $params, $bug, $attachment); } else { ThrowCodeError('param_required', { function => $class . '->set_flag', param => 'id/type_id' }); } + + if ($obj_flag + && $requestee_changed + && $obj_flag->requestee_id + && $obj_flag->requestee->setting('requestee_cc') eq 'on') + { + $bug->add_cc($obj_flag->requestee); + } } sub _validate { @@ -385,23 +407,25 @@ sub _validate { $obj_flag->_set_status($params->{status}); $obj_flag->_set_requestee($params->{requestee}, $attachment, $params->{skip_roe}); + # The requestee ID can be undefined. + my $requestee_changed = ($obj_flag->requestee_id || 0) != ($old_requestee_id || 0); + # The setter field MUST NOT be updated if neither the status # nor the requestee fields changed. - if (($obj_flag->status ne $old_status) - # The requestee ID can be undefined. - || (($obj_flag->requestee_id || 0) != ($old_requestee_id || 0))) - { + if (($obj_flag->status ne $old_status) || $requestee_changed) { $obj_flag->_set_setter($params->{setter}); } # If the flag is deleted, remove it from the list. if ($obj_flag->status eq 'X') { @{$flag_type->{flags}} = grep { $_->id != $obj_flag->id } @{$flag_type->{flags}}; + return; } # Add the newly created flag to the list. elsif (!$obj_flag->id) { push(@{$flag_type->{flags}}, $obj_flag); } + return wantarray ? ($obj_flag, $requestee_changed) : $obj_flag; } =pod @@ -440,6 +464,7 @@ sub update { if (scalar(keys %$changes)) { $dbh->do('UPDATE flags SET modification_date = ? WHERE id = ?', undef, ($timestamp, $self->id)); + $self->{'modification_date'} = $timestamp; } return $changes; } diff --git a/Bugzilla/FlagType.pm b/Bugzilla/FlagType.pm index ea81dfe46..b17e2771e 100644 --- a/Bugzilla/FlagType.pm +++ b/Bugzilla/FlagType.pm @@ -601,7 +601,7 @@ sub match { $tables = join(' ', @$tables); $criteria = join(' AND ', @criteria); - my $flagtype_ids = $dbh->selectcol_arrayref("SELECT id FROM $tables WHERE $criteria"); + my $flagtype_ids = $dbh->selectcol_arrayref("SELECT flagtypes.id FROM $tables WHERE $criteria"); return Bugzilla::FlagType->new_from_list($flagtype_ids); } @@ -679,6 +679,11 @@ sub sqlify_criteria { my $is_active = $criteria->{is_active} ? "1" : "0"; push(@criteria, "flagtypes.is_active = $is_active"); } + if (exists($criteria->{active_or_has_flags}) && $criteria->{active_or_has_flags} =~ /^\d+$/) { + push(@$tables, "LEFT JOIN flags AS f ON flagtypes.id = f.type_id " . + "AND f.bug_id = " . $criteria->{active_or_has_flags}); + push(@criteria, "(flagtypes.is_active = 1 OR f.id IS NOT NULL)"); + } if ($criteria->{product_id}) { my $product_id = $criteria->{product_id}; diff --git a/Bugzilla/Group.pm b/Bugzilla/Group.pm index b7532fe09..3f521d0f2 100644 --- a/Bugzilla/Group.pm +++ b/Bugzilla/Group.pm @@ -119,9 +119,10 @@ sub _get_members { } sub flag_types { - my $self = shift; + my ($self, $params) = @_; + $params ||= {}; require Bugzilla::FlagType; - $self->{flag_types} ||= Bugzilla::FlagType::match({ group => $self->id }); + $self->{flag_types} ||= Bugzilla::FlagType::match({ group => $self->id, %$params }); return $self->{flag_types}; } diff --git a/Bugzilla/Install.pm b/Bugzilla/Install.pm index ce8fe6bad..6019c9d18 100644 --- a/Bugzilla/Install.pm +++ b/Bugzilla/Install.pm @@ -93,6 +93,10 @@ sub SETTINGS { # 2011-06-21 glob@mozilla.com -- Bug 589128 email_format => { options => ['html', 'text_only'], default => 'html' }, + # 2011-06-16 glob@mozilla.com -- Bug 663747 + bugmail_new_prefix => { options => ['on', 'off'], default => 'on' }, + # 2011-10-11 glob@mozilla.com -- Bug 301656 + requestee_cc => { options => ['on', 'off'], default => 'on' }, } }; diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index 44646962b..a89be351c 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -3214,6 +3214,11 @@ sub _populate_bugs_fulltext { print "Populating bugs_fulltext with $num_bugs entries..."; print " (this can take a long time.)\n"; } + + # As recommended by Monty Widenius for GNOME's upgrade. + # mkanat and justdave concur it'll be helpful for bmo, too. + $dbh->do('SET SESSION myisam_sort_buffer_size = 3221225472'); + my $newline = $dbh->quote("\n"); $dbh->do( qq{$command INTO bugs_fulltext (bug_id, short_desc, comments, diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index c5215ecfa..c3f103aaa 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -170,6 +170,7 @@ sub FILESYSTEM { 'contrib/README' => { perms => OWNER_WRITE }, 'contrib/*/README' => { perms => OWNER_WRITE }, + 'contrib/sendunsentbugmail.pl' => { perms => WS_EXECUTE }, 'docs/bugzilla.ent' => { perms => OWNER_WRITE }, 'docs/makedocs.pl' => { perms => OWNER_EXECUTE }, 'docs/style.css' => { perms => WS_SERVE }, @@ -184,8 +185,10 @@ sub FILESYSTEM { # Directories that we want to set the perms on, but not # recurse through. These are directories we didn't create # in checkesetup.pl. + # + # Purpose of BMO change: unknown. my %non_recurse_dirs = ( - '.' => DIR_WS_SERVE, + '.' => 0755, docs => DIR_WS_SERVE, ); @@ -243,10 +246,13 @@ sub FILESYSTEM { dirs => DIR_WS_SERVE }, "$extensionsdir/*/web" => { files => WS_SERVE, dirs => DIR_WS_SERVE }, - + + # Purpose: allow webserver to read .bzr so we execute bzr commands + # in backticks and look at the result over the web. Used to show + # bzr history. + '.bzr' => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, # Directories only for the owner, not for the webserver. - '.bzr' => { files => OWNER_WRITE, - dirs => DIR_OWNER_WRITE }, t => { files => OWNER_WRITE, dirs => DIR_OWNER_WRITE }, xt => { files => OWNER_WRITE, diff --git a/Bugzilla/Mailer.pm b/Bugzilla/Mailer.pm index 5ee6fd2eb..cf494aa7f 100644 --- a/Bugzilla/Mailer.pm +++ b/Bugzilla/Mailer.pm @@ -87,25 +87,6 @@ sub MessageToMTA { # thus to hopefully avoid auto replies. $email->header_set('Auto-Submitted', 'auto-generated'); - $email->walk_parts(sub { - my ($part) = @_; - return if $part->parts > 1; # Top-level - my $content_type = $part->content_type || ''; - if ($content_type !~ /;/) { - my $body = $part->body; - if (Bugzilla->params->{'utf8'}) { - $part->charset_set('UTF-8'); - # encoding_set works only with bytes, not with utf8 strings. - my $raw = $part->body_raw; - if (utf8::is_utf8($raw)) { - utf8::encode($raw); - $part->body_set($raw); - } - } - $part->encoding_set('quoted-printable') if !is_7bit_clean($body); - } - }); - # MIME-Version must be set otherwise some mailsystems ignore the charset $email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version'); @@ -130,7 +111,9 @@ sub MessageToMTA { my $from = $email->header('From'); my ($hostname, @args); + my $mailer_class = $method; if ($method eq "Sendmail") { + $mailer_class = 'Bugzilla::Send::Sendmail'; if (ON_WINDOWS) { $Email::Send::Sendmail::SENDMAIL = SENDMAIL_EXE; } @@ -170,6 +153,25 @@ sub MessageToMTA { Bugzilla::Hook::process('mailer_before_send', { email => $email, mailer_args => \@args }); + $email->walk_parts(sub { + my ($part) = @_; + return if $part->parts > 1; # Top-level + my $content_type = $part->content_type || ''; + if ($content_type !~ /;/) { + my $body = $part->body; + if (Bugzilla->params->{'utf8'}) { + $part->charset_set('UTF-8'); + # encoding_set works only with bytes, not with utf8 strings. + my $raw = $part->body_raw; + if (utf8::is_utf8($raw)) { + utf8::encode($raw); + $part->body_set($raw); + } + } + $part->encoding_set('quoted-printable') if !is_7bit_clean($body); + } + }); + if ($method eq "Test") { my $filename = bz_locations()->{'datadir'} . '/mailer.testfile'; open TESTFILE, '>>', $filename; @@ -180,7 +182,7 @@ sub MessageToMTA { else { # This is useful for both Sendmail and Qmail, so we put it out here. local $ENV{PATH} = SENDMAIL_PATH; - my $mailer = Email::Send->new({ mailer => $method, + my $mailer = Email::Send->new({ mailer => $mailer_class, mailer_args => \@args }); my $retval = $mailer->send($email); ThrowCodeError('mail_send_error', { msg => $retval, mail => $email }) diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm index a0079a033..79af9cbf5 100644 --- a/Bugzilla/Product.pm +++ b/Bugzilla/Product.pm @@ -114,7 +114,7 @@ sub create { # for each product in the list, particularly with hundreds or thousands # of products. sub preload { - my ($products, $preload_flagtypes) = @_; + my ($products, $preload_flagtypes, $flagtypes_params) = @_; my %prods = map { $_->id => $_ } @$products; my @prod_ids = keys %prods; return unless @prod_ids; @@ -132,7 +132,7 @@ sub preload { } } if ($preload_flagtypes) { - $_->flag_types foreach @$products; + $_->flag_types($flagtypes_params) foreach @$products; } } @@ -779,7 +779,8 @@ sub user_has_access { } sub flag_types { - my $self = shift; + my ($self, $params) = @_; + $params ||= {}; return $self->{'flag_types'} if defined $self->{'flag_types'}; @@ -787,7 +788,7 @@ sub flag_types { my $cache = Bugzilla->request_cache->{flag_types_per_product} ||= {}; $self->{flag_types} = {}; my $prod_id = $self->id; - my $flagtypes = Bugzilla::FlagType::match({ product_id => $prod_id }); + my $flagtypes = Bugzilla::FlagType::match({ product_id => $prod_id, %$params }); foreach my $type ('bug', 'attachment') { my @flags = grep { $_->target_type eq $type } @$flagtypes; diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index 736b1af6f..0c5b65acf 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -2797,7 +2797,8 @@ sub _changedby { sub IsValidQueryType { my ($queryType) = @_; - if (grep { $_ eq $queryType } qw(specific advanced)) { + # BMO: Added google and instant + if (grep { $_ eq $queryType } qw(specific advanced google instant)) { return 1; } return 0; diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm index 8425a2be2..1d332e5ae 100644 --- a/Bugzilla/Search/Quicksearch.pm +++ b/Bugzilla/Search/Quicksearch.pm @@ -492,6 +492,9 @@ sub _default_quicksearch_word { addChart('short_desc', 'substring', $word, $negate); addChart('status_whiteboard', 'substring', $word, $negate); addChart('content', 'matches', _matches_phrase($word), $negate) if $fulltext; + + # BMO Bug 664124 - Include the crash signature (sig:) field in default quicksearches + addChart('cf_crash_signature', 'substring', $word, $negate); } sub _handle_urls { diff --git a/Bugzilla/Send/Sendmail.pm b/Bugzilla/Send/Sendmail.pm new file mode 100644 index 000000000..9513134f4 --- /dev/null +++ b/Bugzilla/Send/Sendmail.pm @@ -0,0 +1,95 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Send::Sendmail; + +use strict; + +use base qw(Email::Send::Sendmail); + +use Return::Value; +use Symbol qw(gensym); + +sub send { + my ($class, $message, @args) = @_; + my $mailer = $class->_find_sendmail; + + return failure "Couldn't find 'sendmail' executable in your PATH" + ." and Email::Send::Sendmail::SENDMAIL is not set" + unless $mailer; + + return failure "Found $mailer but cannot execute it" + unless -x $mailer; + + local $SIG{'CHLD'} = 'DEFAULT'; + + my $pipe = gensym; + + open($pipe, "| $mailer -t -oi @args") + || return failure "Error executing $mailer: $!"; + print($pipe $message->as_string) + || return failure "Error printing via pipe to $mailer: $!"; + unless (close $pipe) { + return failure "error when closing pipe to $mailer: $!" if $!; + my ($error_message, $is_transient) = _map_exitcode($? >> 8); + if (Bugzilla->params->{'use_mailer_queue'}) { + # Return success for errors which are fatal so Bugzilla knows to + # remove them from the queue + if ($is_transient) { + return failure "error when closing pipe to $mailer: $error_message"; + } else { + warn "error when closing pipe to $mailer: $error_message\n"; + return success; + } + } else { + return failure "error when closing pipe to $mailer: $error_message"; + } + } + return success; +} + +sub _map_exitcode { + # Returns (error message, is_transient) + # from the sendmail source (sendmail/sysexit.h) + my $code = shift; + if ($code == 64) { + return ("Command line usage error (EX_USAGE)", 1); + } elsif ($code == 65) { + return ("Data format error (EX_DATAERR)", 1); + } elsif ($code == 66) { + return ("Cannot open input (EX_NOINPUT)", 1); + } elsif ($code == 67) { + return ("Addressee unknown (EX_NOUSER)", 0); + } elsif ($code == 68) { + return ("Host name unknown (EX_NOHOST)", 0); + } elsif ($code == 69) { + return ("Service unavailable (EX_UNAVAILABLE)", 1); + } elsif ($code == 70) { + return ("Internal software error (EX_SOFTWARE)", 1); + } elsif ($code == 71) { + return ("System error (EX_OSERR)", 1); + } elsif ($code == 72) { + return ("Critical OS file missing (EX_OSFILE)", 1); + } elsif ($code == 73) { + return ("Can't create output file (EX_CANTCREAT)", 1); + } elsif ($code == 74) { + return ("Input/output error (EX_IOERR)", 1); + } elsif ($code == 75) { + return ("Temp failure (EX_TEMPFAIL)", 1); + } elsif ($code == 76) { + return ("Remote error in protocol (EX_PROTOCOL)", 1); + } elsif ($code == 77) { + return ("Permission denied (EX_NOPERM)", 1); + } elsif ($code == 78) { + return ("Configuration error (EX_CONFIG)", 1); + } else { + return ("Unknown Error ($code)", 1); + } +} + +1; + diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index 527b704b2..a1a6944f0 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -234,7 +234,8 @@ sub quoteUrls { ~<a href=\"mailto:$2\">$1$2</a>~igx; # attachment links - $text =~ s~\b(attachment\s*\#?\s*(\d+)(?:\s+\[details\])?) + # BMO: Bug 652332 dkl@mozilla.com 2011-07-20 + $text =~ s~\b(attachment\s*\#?\s*(\d+)(?:\s+\[diff\])?(?:\s+\[details\])?) ~($things[$count++] = get_attachment_link($2, $1)) && ("\0\0" . ($count-1) . "\0\0") ~egmxi; @@ -293,19 +294,21 @@ sub get_attachment_link { $title = html_quote(clean_text($title)); $link_text =~ s/ \[details\]$//; + $link_text =~ s/ \[diff\]$//; my $linkval = "attachment.cgi?id=$attachid"; - # If the attachment is a patch, try to link to the diff rather - # than the text, by default. + # If the attachment is a patch and patch_viewer feature is + # enabled, add link to the diff. my $patchlink = ""; if ($attachment->ispatch and Bugzilla->feature('patch_viewer')) { - $patchlink = '&action=diff'; + $patchlink = qq| <a href="${linkval}&action=diff" title="$title">[diff]</a>|; } # Whitespace matters here because these links are in <pre> tags. return qq|<span class="$className">| - . qq|<a href="${linkval}${patchlink}" name="attach_${attachid}" title="$title">$link_text</a>| + . qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>| . qq| <a href="${linkval}&action=edit" title="$title">[details]</a>| + . qq|${patchlink}| . qq|</span>|; } else { @@ -660,6 +663,18 @@ sub create { $var =~ s/>/\\x3e/g; return $var; }, + + # Sadly, different to the above. See http://www.json.org/ + # for details. + json => sub { + my ($var) = @_; + $var =~ s/([\\\"\/])/\\$1/g; + $var =~ s/\n/\\n/g; + $var =~ s/\r/\\r/g; + $var =~ s/\f/\\f/g; + $var =~ s/\t/\\t/g; + return $var; + }, # Converts data to base64 base64 => sub { diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm index 2bb68e721..4804851bb 100644 --- a/Bugzilla/Token.pm +++ b/Bugzilla/Token.pm @@ -109,6 +109,8 @@ sub IssueEmailChangeToken { $vars->{'newemailaddress'} = $new_email . $email_suffix; $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); $vars->{'token'} = $token; + # For SecureMail extension + $vars->{'to_user'} = $user; $vars->{'emailaddress'} = $old_email . $email_suffix; my $message; diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 008322b17..61ebdbab1 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -50,6 +50,7 @@ use Bugzilla::Product; use Bugzilla::Classification; use Bugzilla::Field; use Bugzilla::Group; +use Bugzilla::Hook; use DateTime::TimeZone; use List::Util qw(max); @@ -707,8 +708,8 @@ sub bless_groups { return $self->{'bless_groups'} if defined $self->{'bless_groups'}; return [] unless $self->id; - if ($self->in_group('editusers')) { - # Users having editusers permissions may bless all groups. + if ($self->in_group('admin')) { + # Users having admin permissions may bless all groups. $self->{'bless_groups'} = [Bugzilla::Group->get_all]; return $self->{'bless_groups'}; } @@ -2330,7 +2331,7 @@ Determines whether or not a user is in the given group by id. Returns an arrayref of L<Bugzilla::Group> objects. The arrayref consists of the groups the user can bless, taking into account -that having editusers permissions means that you can bless all groups, and +that having admin permissions means that you can bless all groups, and that you need to be able to see a group in order to bless it. =item C<get_products_by_permission($group)> diff --git a/Bugzilla/UserAgent.pm b/Bugzilla/UserAgent.pm new file mode 100644 index 000000000..c0cda2586 --- /dev/null +++ b/Bugzilla/UserAgent.pm @@ -0,0 +1,247 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is the Mozilla Foundation +# Portions created by the Initial Developer are Copyright (C) 2011 the +# Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Terry Weissman <terry@mozilla.org> +# Dave Miller <justdave@syndicomm.com> +# Joe Robins <jmrobins@tgix.com> +# Gervase Markham <gerv@gerv.net> +# Shane H. W. Travis <travis@sedsystems.ca> +# Nitish Bezzala <nbezzala@yahoo.com> +# Byron Jones <glob@mozilla.com> + +package Bugzilla::UserAgent; + +use strict; +use base qw(Exporter); +our @EXPORT = qw(detect_platform detect_op_sys); + +use Bugzilla::Field; +use List::MoreUtils qw(natatime); + +use constant DEFAULT_VALUE => 'Other'; + +use constant PLATFORMS_MAP => ( + # PowerPC + qr/\(.*PowerPC.*\)/i => ["PowerPC", "Macintosh"], + # AMD64, Intel x86_64 + qr/\(.*[ix0-9]86 (?:on |\()x86_64.*\)/ => ["IA32", "x86", "PC"], + qr/\(.*amd64.*\)/ => ["AMD64", "x86_64", "PC"], + qr/\(.*x86_64.*\)/ => ["AMD64", "x86_64", "PC"], + # Intel IA64 + qr/\(.*IA64.*\)/ => ["IA64", "PC"], + # Intel x86 + qr/\(.*Intel.*\)/ => ["IA32", "x86", "PC"], + qr/\(.*[ix0-9]86.*\)/ => ["IA32", "x86", "PC"], + # Versions of Windows that only run on Intel x86 + qr/\(.*Win(?:dows |)[39M].*\)/ => ["IA32", "x86", "PC"], + qr/\(.*Win(?:dows |)16.*\)/ => ["IA32", "x86", "PC"], + # Sparc + qr/\(.*sparc.*\)/ => ["Sparc", "Sun"], + qr/\(.*sun4.*\)/ => ["Sparc", "Sun"], + # Alpha + qr/\(.*AXP.*\)/i => ["Alpha", "DEC"], + qr/\(.*[ _]Alpha.\D/i => ["Alpha", "DEC"], + qr/\(.*[ _]Alpha\)/i => ["Alpha", "DEC"], + # MIPS + qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"], + qr/\(.*MIPS.*\)/i => ["MIPS", "SGI"], + # 68k + qr/\(.*68K.*\)/ => ["68k", "Macintosh"], + qr/\(.*680[x0]0.*\)/ => ["68k", "Macintosh"], + # HP + qr/\(.*9000.*\)/ => ["PA-RISC", "HP"], + # ARM + qr/\(.*ARM.*\)/ => ["ARM", "PocketPC"], + # PocketPC intentionally before PowerPC + qr/\(.*Windows CE.*PPC.*\)/ => ["ARM", "PocketPC"], + # PowerPC + qr/\(.*PPC.*\)/ => ["PowerPC", "Macintosh"], + qr/\(.*AIX.*\)/ => ["PowerPC", "Macintosh"], + # Stereotypical and broken + qr/\(.*Windows CE.*\)/ => ["ARM", "PocketPC"], + qr/\(.*Macintosh.*\)/ => ["68k", "Macintosh"], + qr/\(.*Mac OS [89].*\)/ => ["68k", "Macintosh"], + qr/\(.*WOW64.*\)/ => ["x86_64"], + qr/\(.*Win64.*\)/ => ["IA64"], + qr/\(Win.*\)/ => ["IA32", "x86", "PC"], + qr/\(.*Win(?:dows[ -])NT.*\)/ => ["IA32", "x86", "PC"], + qr/\(.*OSF.*\)/ => ["Alpha", "DEC"], + qr/\(.*HP-?UX.*\)/i => ["PA-RISC", "HP"], + qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"], + qr/\(.*(SunOS|Solaris).*\)/ => ["Sparc", "Sun"], + # Braindead old browsers who didn't follow convention: + qr/Amiga/ => ["68k", "Macintosh"], + qr/WinMosaic/ => ["IA32", "x86", "PC"], +); + +use constant OS_MAP => ( + # Sun + qr/\(.*Solaris.*\)/ => ["Solaris"], + qr/\(.*SunOS 5.11.*\)/ => [("OpenSolaris", "Opensolaris", "Solaris 11")], + qr/\(.*SunOS 5.10.*\)/ => ["Solaris 10"], + qr/\(.*SunOS 5.9.*\)/ => ["Solaris 9"], + qr/\(.*SunOS 5.8.*\)/ => ["Solaris 8"], + qr/\(.*SunOS 5.7.*\)/ => ["Solaris 7"], + qr/\(.*SunOS 5.6.*\)/ => ["Solaris 6"], + qr/\(.*SunOS 5.5.*\)/ => ["Solaris 5"], + qr/\(.*SunOS 5.*\)/ => ["Solaris"], + qr/\(.*SunOS.*sun4u.*\)/ => ["Solaris"], + qr/\(.*SunOS.*i86pc.*\)/ => ["Solaris"], + qr/\(.*SunOS.*\)/ => ["SunOS"], + # BSD + qr/\(.*BSD\/(?:OS|386).*\)/ => ["BSDI"], + qr/\(.*FreeBSD.*\)/ => ["FreeBSD"], + qr/\(.*OpenBSD.*\)/ => ["OpenBSD"], + qr/\(.*NetBSD.*\)/ => ["NetBSD"], + # Misc POSIX + qr/\(.*IRIX.*\)/ => ["IRIX"], + qr/\(.*OSF.*\)/ => ["OSF/1"], + qr/\(.*Linux.*\)/ => ["Linux"], + qr/\(.*BeOS.*\)/ => ["BeOS"], + qr/\(.*AIX.*\)/ => ["AIX"], + qr/\(.*OS\/2.*\)/ => ["OS/2"], + qr/\(.*QNX.*\)/ => ["Neutrino"], + qr/\(.*VMS.*\)/ => ["OpenVMS"], + qr/\(.*HP-?UX.*\)/ => ["HP-UX"], + # Windows + qr/\(.*Windows XP.*\)/ => ["Windows XP"], + qr/\(.*Windows NT 6\.1.*\)/ => ["Windows 7"], + qr/\(.*Windows NT 6\.0.*\)/ => ["Windows Vista"], + qr/\(.*Windows NT 5\.2.*\)/ => ["Windows Server 2003"], + qr/\(.*Windows NT 5\.1.*\)/ => ["Windows XP"], + qr/\(.*Windows 2000.*\)/ => ["Windows 2000"], + qr/\(.*Windows NT 5.*\)/ => ["Windows 2000"], + qr/\(.*Win.*9[8x].*4\.9.*\)/ => ["Windows ME"], + qr/\(.*Win(?:dows |)M[Ee].*\)/ => ["Windows ME"], + qr/\(.*Win(?:dows |)98.*\)/ => ["Windows 98"], + qr/\(.*Win(?:dows |)95.*\)/ => ["Windows 95"], + qr/\(.*Win(?:dows |)16.*\)/ => ["Windows 3.1"], + qr/\(.*Win(?:dows[ -]|)NT.*\)/ => ["Windows NT"], + qr/\(.*Windows.*NT.*\)/ => ["Windows NT"], + # OS X + qr/\(.*Mac OS X (?:|Mach-O |\()10.6.*\)/ => ["Mac OS X 10.6"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.5.*\)/ => ["Mac OS X 10.5"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.4.*\)/ => ["Mac OS X 10.4"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.3.*\)/ => ["Mac OS X 10.3"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.2.*\)/ => ["Mac OS X 10.2"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.1.*\)/ => ["Mac OS X 10.1"], + # Unfortunately, OS X 10.4 was the first to support Intel. This is fallback + # support because some browsers refused to include the OS Version. + qr/\(.*Intel.*Mac OS X.*\)/ => ["Mac OS X 10.4"], + # OS X 10.3 is the most likely default version of PowerPC Macs + # OS X 10.0 is more for configurations which didn't setup 10.x versions + qr/\(.*Mac OS X.*\)/ => [("Mac OS X 10.3", "Mac OS X 10.0", "Mac OS X")], + qr/\(.*Mac OS 9.*\)/ => [("Mac System 9.x", "Mac System 9.0")], + qr/\(.*Mac OS 8\.6.*\)/ => [("Mac System 8.6", "Mac System 8.5")], + qr/\(.*Mac OS 8\.5.*\)/ => ["Mac System 8.5"], + qr/\(.*Mac OS 8\.1.*\)/ => [("Mac System 8.1", "Mac System 8.0")], + qr/\(.*Mac OS 8\.0.*\)/ => ["Mac System 8.0"], + qr/\(.*Mac OS 8[^.].*\)/ => ["Mac System 8.0"], + qr/\(.*Mac OS 8.*\)/ => ["Mac System 8.6"], + qr/\(.*Darwin.*\)/ => [("Mac OS X 10.0", "Mac OS X")], + # Silly + qr/\(.*Mac.*PowerPC.*\)/ => ["Mac System 9.x"], + qr/\(.*Mac.*PPC.*\)/ => ["Mac System 9.x"], + qr/\(.*Mac.*68k.*\)/ => ["Mac System 8.0"], + # Evil + qr/Amiga/i => ["Other"], + qr/WinMosaic/ => ["Windows 95"], + qr/\(.*32bit.*\)/ => ["Windows 95"], + qr/\(.*16bit.*\)/ => ["Windows 3.1"], + qr/\(.*PowerPC.*\)/ => ["Mac System 9.x"], + qr/\(.*PPC.*\)/ => ["Mac System 9.x"], + qr/\(.*68K.*\)/ => ["Mac System 8.0"], +); + +sub detect_platform { + my $userAgent = $ENV{'HTTP_USER_AGENT'}; + my @detected; + my $iterator = natatime(2, PLATFORMS_MAP); + while (my($re, $ra) = $iterator->()) { + if ($userAgent =~ $re) { + push @detected, @$ra; + } + } + return _pick_valid_field_value('rep_platform', @detected); +} + +sub detect_op_sys { + my $userAgent = $ENV{'HTTP_USER_AGENT'}; + my @detected; + my $iterator = natatime(2, OS_MAP); + while (my($re, $ra) = $iterator->()) { + if ($userAgent =~ $re) { + push @detected, @$ra; + } + } + push(@detected, "Windows") if grep(/^Windows /, @detected); + push(@detected, "Mac OS") if grep(/^Mac /, @detected); + return _pick_valid_field_value('op_sys', @detected); +} + +# Takes the name of a field and a list of possible values for that field. +# Returns the first value in the list that is actually a valid value for that +# field. +# Returns 'Other' if none of the values match. +sub _pick_valid_field_value { + my ($field, @values) = @_; + foreach my $value (@values) { + return $value if check_field($field, $value, undef, 1); + } + return DEFAULT_VALUE; +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::UserAgent - UserAgent utilities for Bugzilla + +=head1 SYNOPSIS + + use Bugzilla::UserAgent; + printf "platform: %s op-sys: %s\n", detect_platform(), detect_op_sys(); + +=head1 DESCRIPTION + +The functions exported by this module all return information derived from the +remote client's user agent. + +=head1 FUNCTIONS + +=over 4 + +=item C<detect_platform> + +This function attempts to detect the remote client's platform from the +presented user-agent. If a suitable value on the I<platform> field is found, +that field value will be returned. If no suitable value is detected, +C<detect_platform> returns I<Other>. + +=item C<detect_op_sys> + +This function attempts to detect the remote client's operating system from the +presented user-agent. If a suitable value on the I<op_sys> field is found, that +field value will be returned. If no suitable value is detected, +C<detect_op_sys> returns I<Other>. + +=back + diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm index ea1becc0a..ca9ee1014 100644 --- a/Bugzilla/WebService/Bug.pm +++ b/Bugzilla/WebService/Bug.pm @@ -439,10 +439,17 @@ sub search { delete $match_params{'include_fields'}; delete $match_params{'exclude_fields'}; + my $count_only = delete $match_params{count_only}; + my $bugs = Bugzilla::Bug->match(\%match_params); my $visible = Bugzilla->user->visible_bugs($bugs); - my @hashes = map { $self->_bug_to_hash($_, $params) } @$visible; - return { bugs => \@hashes }; + if ($count_only) { + return { bug_count => scalar @$visible }; + } + else { + my @hashes = map { $self->_bug_to_hash($_, $params) } @$visible; + return { bugs => \@hashes }; + } } sub possible_duplicates { @@ -469,6 +476,12 @@ sub possible_duplicates { sub update { my ($self, $params) = validate(@_, 'ids'); + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + my $user = Bugzilla->login(LOGIN_REQUIRED); my $dbh = Bugzilla->dbh; @@ -563,6 +576,13 @@ sub update { sub create { my ($self, $params) = @_; + + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + Bugzilla->login(LOGIN_REQUIRED); $params = Bugzilla::Bug::map_fields($params); my $bug = Bugzilla::Bug->create($params); @@ -625,6 +645,12 @@ sub add_attachment { my ($self, $params) = validate(@_, 'ids'); my $dbh = Bugzilla->dbh; + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + Bugzilla->login(LOGIN_REQUIRED); defined $params->{ids} || ThrowCodeError('param_required', { param => 'ids' }); @@ -673,6 +699,12 @@ sub add_attachment { sub add_comment { my ($self, $params) = @_; + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + #The user must login in order add a comment Bugzilla->login(LOGIN_REQUIRED); @@ -717,6 +749,12 @@ sub add_comment { sub update_see_also { my ($self, $params) = @_; + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + my $user = Bugzilla->login(LOGIN_REQUIRED); # Check parameters @@ -2163,6 +2201,11 @@ C<string> Search the "Status Whiteboard" field on bugs for a substring. Works the same as the C<summary> field described above, but searches the Status Whiteboard field. +=item C<count_only> + +C<boolean> If count_only set to true, only a single hash key called C<bug_count> +will be returned which is the number of bugs that matched the search. + =back =item B<Returns> diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm index f8704a947..1efe6179e 100644 --- a/Bugzilla/WebService/User.pm +++ b/Bugzilla/WebService/User.pm @@ -29,6 +29,7 @@ use Bugzilla::Group; use Bugzilla::User; use Bugzilla::Util qw(trim); use Bugzilla::WebService::Util qw(filter validate); +use Bugzilla::Hook; # Don't need auth to login use constant LOGIN_EXEMPT => { @@ -223,6 +224,9 @@ sub get { }} @$in_group; } + Bugzilla::Hook::process('webservice_user_get', + { webservice => $self, params => $params, users => \@users }); + return { users => \@users }; } |