Commit ec610fd6 authored by mkanat%kerio.com's avatar mkanat%kerio.com

Bug 277782: _throw_error should unlock tables when tables are locked, automatically

Patch By Tomas Kopal <Tomas.Kopal@altap.cz> r=travis, r=LpSolit, a=justdave
parent e4b8b770
...@@ -184,7 +184,7 @@ sub login { ...@@ -184,7 +184,7 @@ sub login {
# If we get here, then we've run out of options, which shouldn't happen # If we get here, then we've run out of options, which shouldn't happen
ThrowCodeError("authres_unhandled", { authres => $authres, ThrowCodeError("authres_unhandled", { authres => $authres,
type => $type, }); type => $type });
} }
# This auth style allows the user to log out. # This auth style allows the user to log out.
......
...@@ -511,21 +511,18 @@ sub ValidateTime { ...@@ -511,21 +511,18 @@ sub ValidateTime {
# (allow negatives, though, so people can back out errors in time reporting) # (allow negatives, though, so people can back out errors in time reporting)
if ($time !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) { if ($time !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) {
ThrowUserError("number_not_numeric", ThrowUserError("number_not_numeric",
{field => "$field", num => "$time"}, {field => "$field", num => "$time"});
"abort");
} }
# Only the "work_time" field is allowed to contain a negative value. # Only the "work_time" field is allowed to contain a negative value.
if ( ($time < 0) && ($field ne "work_time") ) { if ( ($time < 0) && ($field ne "work_time") ) {
ThrowUserError("number_too_small", ThrowUserError("number_too_small",
{field => "$field", num => "$time", min_num => "0"}, {field => "$field", num => "$time", min_num => "0"});
"abort");
} }
if ($time > 99999.99) { if ($time > 99999.99) {
ThrowUserError("number_too_large", ThrowUserError("number_too_large",
{field => "$field", num => "$time", max_num => "99999.99"}, {field => "$field", num => "$time", max_num => "99999.99"});
"abort");
} }
} }
......
...@@ -32,13 +32,15 @@ use Bugzilla::Util; ...@@ -32,13 +32,15 @@ use Bugzilla::Util;
use Date::Format; use Date::Format;
sub _throw_error { sub _throw_error {
my ($name, $error, $vars, $unlock_tables) = @_; my ($name, $error, $vars) = @_;
$vars ||= {}; $vars ||= {};
$vars->{error} = $error; $vars->{error} = $error;
Bugzilla->dbh->bz_unlock_tables(UNLOCK_ABORT) if $unlock_tables; # Make sure any locked tables are unlocked
# and the transaction is rolled back (if supported)
Bugzilla->dbh->bz_unlock_tables(UNLOCK_ABORT);
# If a writable data/errorlog exists, log error details there. # If a writable data/errorlog exists, log error details there.
if (-w "data/errorlog") { if (-w "data/errorlog") {
...@@ -95,6 +97,10 @@ sub ThrowCodeError { ...@@ -95,6 +97,10 @@ sub ThrowCodeError {
sub ThrowTemplateError { sub ThrowTemplateError {
my ($template_err) = @_; my ($template_err) = @_;
# Make sure any locked tables are unlocked
# and the transaction is rolled back (if supported)
Bugzilla->dbh->bz_unlock_tables(UNLOCK_ABORT);
my $vars = {}; my $vars = {};
if (Bugzilla->batch) { if (Bugzilla->batch) {
die("error: template error: $template_err"); die("error: template error: $template_err");
...@@ -149,16 +155,16 @@ Bugzilla::Error - Error handling utilities for Bugzilla ...@@ -149,16 +155,16 @@ Bugzilla::Error - Error handling utilities for Bugzilla
ThrowUserError("error_tag", ThrowUserError("error_tag",
{ foo => 'bar' }); { foo => 'bar' });
# supplying "abort" to ensure tables are unlocked
ThrowUserError("another_error_tag",
{ foo => 'bar' }, 'abort');
=head1 DESCRIPTION =head1 DESCRIPTION
Various places throughout the Bugzilla codebase need to report errors to the Various places throughout the Bugzilla codebase need to report errors to the
user. The C<Throw*Error> family of functions allow this to be done in a user. The C<Throw*Error> family of functions allow this to be done in a
generic and localisable manner. generic and localisable manner.
These functions automatically unlock the database tables, if there were any
locked. They will also roll back the transaction, if it is supported by
the underlying DB.
=head1 FUNCTIONS =head1 FUNCTIONS
=over 4 =over 4
...@@ -170,12 +176,6 @@ of variables as a second argument. These are used by the ...@@ -170,12 +176,6 @@ of variables as a second argument. These are used by the
I<global/user-error.html.tmpl> template to format the error, using the passed I<global/user-error.html.tmpl> template to format the error, using the passed
in variables as required. in variables as required.
An optional third argument may be supplied. If present, the error
handling code will unlock the database tables: it is a Bugzilla standard
to provide the string "abort" as the argument value. In the long term,
this argument will go away, to be replaced by transactional C<rollback>
calls. There is no timeframe for doing so, however.
=item C<ThrowCodeError> =item C<ThrowCodeError>
This function is used when an internal check detects an error of some sort. This function is used when an internal check detects an error of some sort.
......
...@@ -112,7 +112,7 @@ sub CheckFormField (\%$;\@) { ...@@ -112,7 +112,7 @@ sub CheckFormField (\%$;\@) {
$field = $fieldname; $field = $fieldname;
} }
ThrowCodeError("illegal_field", { field => $field }, "abort"); ThrowCodeError("illegal_field", { field => $field });
} }
} }
...@@ -213,7 +213,7 @@ sub CheckEmailSyntax { ...@@ -213,7 +213,7 @@ sub CheckEmailSyntax {
my ($addr) = (@_); my ($addr) = (@_);
my $match = Param('emailregexp'); my $match = Param('emailregexp');
if ($addr !~ /$match/ || $addr =~ /[\\\(\)<>&,;:"\[\] \t\r\n]/) { if ($addr !~ /$match/ || $addr =~ /[\\\(\)<>&,;:"\[\] \t\r\n]/) {
ThrowUserError("illegal_email_address", { addr => $addr }, 'abort'); ThrowUserError("illegal_email_address", { addr => $addr });
} }
} }
......
...@@ -300,12 +300,10 @@ if ($action eq 'update') { ...@@ -300,12 +300,10 @@ if ($action eq 'update') {
if ($classification ne $classificationold) { if ($classification ne $classificationold) {
unless ($classification) { unless ($classification) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError("classification_not_specified") ThrowUserError("classification_not_specified")
} }
if (TestClassification($classification)) { if (TestClassification($classification)) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError("classification_already_exists", { name => $classification }); ThrowUserError("classification_already_exists", { name => $classification });
} }
$sth = $dbh->prepare("UPDATE classifications $sth = $dbh->prepare("UPDATE classifications
......
...@@ -66,7 +66,7 @@ sub CheckProduct ($) ...@@ -66,7 +66,7 @@ sub CheckProduct ($)
# do we have a product? # do we have a product?
unless ($prod) { unless ($prod) {
ThrowUserError('product_not_specified'); ThrowUserError('product_not_specified');
exit; exit;
} }
...@@ -585,7 +585,6 @@ if ($action eq 'update') { ...@@ -585,7 +585,6 @@ if ($action eq 'update') {
if ($description ne $descriptionold) { if ($description ne $descriptionold) {
unless ($description) { unless ($description) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('component_blank_description', ThrowUserError('component_blank_description',
{'name' => $componentold}); {'name' => $componentold});
exit; exit;
...@@ -603,7 +602,6 @@ if ($action eq 'update') { ...@@ -603,7 +602,6 @@ if ($action eq 'update') {
my $initialownerid = login_to_id($initialowner); my $initialownerid = login_to_id($initialowner);
unless ($initialownerid) { unless ($initialownerid) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('component_need_valid_initialowner', ThrowUserError('component_need_valid_initialowner',
{'name' => $componentold}); {'name' => $componentold});
exit; exit;
...@@ -621,7 +619,6 @@ if ($action eq 'update') { ...@@ -621,7 +619,6 @@ if ($action eq 'update') {
if (Param('useqacontact') && $initialqacontact ne $initialqacontactold) { if (Param('useqacontact') && $initialqacontact ne $initialqacontactold) {
my $initialqacontactid = login_to_id($initialqacontact); my $initialqacontactid = login_to_id($initialqacontact);
if (!$initialqacontactid && $initialqacontact ne '') { if (!$initialqacontactid && $initialqacontact ne '') {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('component_need_valid_initialqacontact', ThrowUserError('component_need_valid_initialqacontact',
{'name' => $componentold}); {'name' => $componentold});
exit; exit;
...@@ -638,13 +635,11 @@ if ($action eq 'update') { ...@@ -638,13 +635,11 @@ if ($action eq 'update') {
if ($component ne $componentold) { if ($component ne $componentold) {
unless ($component) { unless ($component) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('component_must_have_a_name', ThrowUserError('component_must_have_a_name',
{'name' => $componentold}); {'name' => $componentold});
exit; exit;
} }
if (TestComponent($product, $component)) { if (TestComponent($product, $component)) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('component_already_exists', ThrowUserError('component_already_exists',
{'name' => $component}); {'name' => $component});
exit; exit;
......
...@@ -59,14 +59,14 @@ sub CheckProduct ($) ...@@ -59,14 +59,14 @@ sub CheckProduct ($)
# do we have a product? # do we have a product?
unless ($product) { unless ($product) {
&::ThrowUserError('product_not_specified'); ThrowUserError('product_not_specified');
exit; exit;
} }
# Does it exist in the DB? # Does it exist in the DB?
unless (TestProduct $product) { unless (TestProduct $product) {
&::ThrowUserError('product_doesnt_exist', ThrowUserError('product_doesnt_exist',
{'product' => $product}); {'product' => $product});
exit; exit;
} }
} }
...@@ -506,12 +506,9 @@ if ($action eq 'update') { ...@@ -506,12 +506,9 @@ if ($action eq 'update') {
my $stored_sortkey = $sortkey; my $stored_sortkey = $sortkey;
if ($sortkey != $sortkeyold) { if ($sortkey != $sortkeyold) {
if (!detaint_natural($sortkey)) { if (!detaint_natural($sortkey)) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('milestone_sortkey_invalid', ThrowUserError('milestone_sortkey_invalid',
{'name' => $milestone, {'name' => $milestone,
'sortkey' => $stored_sortkey}); 'sortkey' => $stored_sortkey});
exit; exit;
} }
...@@ -532,12 +529,10 @@ if ($action eq 'update') { ...@@ -532,12 +529,10 @@ if ($action eq 'update') {
if ($milestone ne $milestoneold) { if ($milestone ne $milestoneold) {
unless ($milestone) { unless ($milestone) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('milestone_blank_name'); ThrowUserError('milestone_blank_name');
exit; exit;
} }
if (TestMilestone($product, $milestone)) { if (TestMilestone($product, $milestone)) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('milestone_already_exists', ThrowUserError('milestone_already_exists',
{'name' => $milestone, {'name' => $milestone,
'product' => $product}); 'product' => $product});
......
...@@ -212,8 +212,7 @@ if ($action eq 'search') { ...@@ -212,8 +212,7 @@ if ($action eq 'search') {
canSeeUser($otherUserID) canSeeUser($otherUserID)
|| ThrowUserError('auth_failure', {reason => "not_visible", || ThrowUserError('auth_failure', {reason => "not_visible",
action => "modify", action => "modify",
object => "user"}, object => "user"});
'abort');
# Cleanups # Cleanups
my $login = trim($cgi->param('login') || ''); my $login = trim($cgi->param('login') || '');
...@@ -231,11 +230,10 @@ if ($action eq 'search') { ...@@ -231,11 +230,10 @@ if ($action eq 'search') {
if ($login ne $loginold) { if ($login ne $loginold) {
# Validate, then trick_taint. # Validate, then trick_taint.
$login || ThrowUserError('user_login_required', undef, 'abort'); $login || ThrowUserError('user_login_required');
CheckEmailSyntax($login); CheckEmailSyntax($login);
is_available_username($login) || ThrowUserError('account_exists', is_available_username($login) || ThrowUserError('account_exists',
{'email' => $login}, {'email' => $login});
'abort');
trick_taint($login); trick_taint($login);
push(@changedFields, 'login_name'); push(@changedFields, 'login_name');
push(@values, $login); push(@values, $login);
...@@ -493,19 +491,17 @@ if ($action eq 'search') { ...@@ -493,19 +491,17 @@ if ($action eq 'search') {
'whine_events WRITE'); 'whine_events WRITE');
Param('allowuserdeletion') Param('allowuserdeletion')
|| ThrowUserError('users_deletion_disabled', undef, 'abort'); || ThrowUserError('users_deletion_disabled');
$editusers || ThrowUserError('auth_failure', $editusers || ThrowUserError('auth_failure',
{group => "editusers", {group => "editusers",
action => "delete", action => "delete",
object => "users"}, object => "users"});
'abort');
canSeeUser($otherUserID) || ThrowUserError('auth_failure', canSeeUser($otherUserID) || ThrowUserError('auth_failure',
{reason => "not_visible", {reason => "not_visible",
action => "delete", action => "delete",
object => "user"}, object => "user"});
'abort');
productResponsibilities($otherUserID) productResponsibilities($otherUserID)
&& ThrowUserError('user_has_responsibility', undef, 'abort'); && ThrowUserError('user_has_responsibility');
Bugzilla->logout_user_by_id($otherUserID); Bugzilla->logout_user_by_id($otherUserID);
......
...@@ -406,16 +406,13 @@ if ($action eq 'update') { ...@@ -406,16 +406,13 @@ if ($action eq 'update') {
if ($version ne $versionold) { if ($version ne $versionold) {
unless ($version) { unless ($version) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('version_blank_name'); ThrowUserError('version_blank_name');
exit; exit;
} }
if (TestVersion($product,$version)) { if (TestVersion($product,$version)) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
ThrowUserError('version_already_exists', ThrowUserError('version_already_exists',
{'name' => $version, {'name' => $version,
'product' => $product}); 'product' => $product});
exit; exit;
} }
SendSQL("UPDATE bugs SendSQL("UPDATE bugs
......
...@@ -613,11 +613,11 @@ sub ValidatePassword { ...@@ -613,11 +613,11 @@ sub ValidatePassword {
my ($password, $matchpassword) = @_; my ($password, $matchpassword) = @_;
if (length($password) < 3) { if (length($password) < 3) {
ThrowUserError("password_too_short", undef, 'abort'); ThrowUserError("password_too_short");
} elsif (length($password) > 16) { } elsif (length($password) > 16) {
ThrowUserError("password_too_long", undef, 'abort'); ThrowUserError("password_too_long");
} elsif ((defined $matchpassword) && ($password ne $matchpassword)) { } elsif ((defined $matchpassword) && ($password ne $matchpassword)) {
ThrowUserError("passwords_dont_match", undef, 'abort'); ThrowUserError("passwords_dont_match");
} }
} }
...@@ -649,8 +649,7 @@ sub DBNameToIdAndCheck { ...@@ -649,8 +649,7 @@ sub DBNameToIdAndCheck {
return $result; return $result;
} }
ThrowUserError("invalid_username", ThrowUserError("invalid_username", { name => $name });
{ name => $name }, "abort");
} }
sub get_classification_id { sub get_classification_id {
......
...@@ -318,8 +318,7 @@ if (UserInGroup("editbugs") && defined($::FORM{'dependson'})) { ...@@ -318,8 +318,7 @@ if (UserInGroup("editbugs") && defined($::FORM{'dependson'})) {
} }
ThrowUserError("dependency_loop_multi", ThrowUserError("dependency_loop_multi",
{ both => $both }, { both => $both });
"abort");
} }
} }
my $tmp = $me; my $tmp = $me;
...@@ -378,14 +377,14 @@ foreach my $b (grep(/^bit-\d*$/, keys %::FORM)) { ...@@ -378,14 +377,14 @@ foreach my $b (grep(/^bit-\d*$/, keys %::FORM)) {
if ($::FORM{$b}) { if ($::FORM{$b}) {
my $v = substr($b, 4); my $v = substr($b, 4);
$v =~ /^(\d+)$/ $v =~ /^(\d+)$/
|| ThrowCodeError("group_id_invalid", undef, "abort"); || ThrowCodeError("group_id_invalid");
if (!GroupIsActive($v)) { if (!GroupIsActive($v)) {
# Prevent the user from adding the bug to an inactive group. # Prevent the user from adding the bug to an inactive group.
# Should only happen if there is a bug in Bugzilla or the user # Should only happen if there is a bug in Bugzilla or the user
# hacked the "enter bug" form since otherwise the UI # hacked the "enter bug" form since otherwise the UI
# for adding the bug to the group won't appear on that form. # for adding the bug to the group won't appear on that form.
$vars->{'bit'} = $v; $vars->{'bit'} = $v;
ThrowCodeError("inactive_group", undef, "abort"); ThrowCodeError("inactive_group");
} }
SendSQL("SELECT user_id FROM user_group_map SendSQL("SELECT user_id FROM user_group_map
WHERE user_id = $::userid WHERE user_id = $::userid
......
...@@ -109,7 +109,7 @@ foreach my $field ("estimated_time", "work_time", "remaining_time") { ...@@ -109,7 +109,7 @@ foreach my $field ("estimated_time", "work_time", "remaining_time") {
if (UserInGroup(Param('timetrackinggroup'))) { if (UserInGroup(Param('timetrackinggroup'))) {
my $wk_time = $::FORM{'work_time'}; my $wk_time = $::FORM{'work_time'};
if ($::FORM{'comment'} =~ /^\s*$/ && $wk_time && $wk_time != 0) { if ($::FORM{'comment'} =~ /^\s*$/ && $wk_time && $wk_time != 0) {
ThrowUserError('comment_required', undef, "abort"); ThrowUserError('comment_required');
} }
} }
...@@ -241,7 +241,7 @@ if ((($::FORM{'id'} && $::FORM{'product'} ne $::oldproduct) ...@@ -241,7 +241,7 @@ if ((($::FORM{'id'} && $::FORM{'product'} ne $::oldproduct)
$vars->{'newvalue'} = $::FORM{'product'}; $vars->{'newvalue'} = $::FORM{'product'};
$vars->{'field'} = 'product'; $vars->{'field'} = 'product';
$vars->{'privs'} = $PrivilegesRequired; $vars->{'privs'} = $PrivilegesRequired;
ThrowUserError("illegal_change", $vars, "abort"); ThrowUserError("illegal_change", $vars);
} }
CheckFormField(\%::FORM, 'product', \@::legal_product); CheckFormField(\%::FORM, 'product', \@::legal_product);
...@@ -1232,7 +1232,7 @@ foreach my $id (@idlist) { ...@@ -1232,7 +1232,7 @@ foreach my $id (@idlist) {
$vars->{'field'} = $col; $vars->{'field'} = $col;
} }
$vars->{'privs'} = $PrivilegesRequired; $vars->{'privs'} = $PrivilegesRequired;
ThrowUserError("illegal_change", $vars, "abort"); ThrowUserError("illegal_change", $vars);
} }
} }
...@@ -1251,13 +1251,13 @@ foreach my $id (@idlist) { ...@@ -1251,13 +1251,13 @@ foreach my $id (@idlist) {
$vars->{'newvalue'} = "no keywords"; $vars->{'newvalue'} = "no keywords";
$vars->{'field'} = "keywords"; $vars->{'field'} = "keywords";
$vars->{'privs'} = $PrivilegesRequired; $vars->{'privs'} = $PrivilegesRequired;
ThrowUserError("illegal_change", $vars, "abort"); ThrowUserError("illegal_change", $vars);
} }
$oldhash{'product'} = get_product_name($oldhash{'product_id'}); $oldhash{'product'} = get_product_name($oldhash{'product_id'});
if (!CanEditProductId($oldhash{'product_id'})) { if (!CanEditProductId($oldhash{'product_id'})) {
ThrowUserError("product_edit_denied", ThrowUserError("product_edit_denied",
{ product => $oldhash{'product'} }, "abort"); { product => $oldhash{'product'} });
} }
if (defined $::FORM{'product'} if (defined $::FORM{'product'}
...@@ -1265,7 +1265,7 @@ foreach my $id (@idlist) { ...@@ -1265,7 +1265,7 @@ foreach my $id (@idlist) {
&& $::FORM{'product'} ne $oldhash{'product'} && $::FORM{'product'} ne $oldhash{'product'}
&& !CanEnterProduct($::FORM{'product'})) { && !CanEnterProduct($::FORM{'product'})) {
ThrowUserError("entry_access_denied", ThrowUserError("entry_access_denied",
{ product => $::FORM{'product'} }, "abort"); { product => $::FORM{'product'} });
} }
if ($requiremilestone) { if ($requiremilestone) {
# musthavemilestoneonaccept applies only if at least two # musthavemilestoneonaccept applies only if at least two
...@@ -1283,7 +1283,7 @@ foreach my $id (@idlist) { ...@@ -1283,7 +1283,7 @@ foreach my $id (@idlist) {
# if musthavemilestoneonaccept == 1, then the target # if musthavemilestoneonaccept == 1, then the target
# milestone must be different from the default one. # milestone must be different from the default one.
if ($value eq $defaultmilestone) { if ($value eq $defaultmilestone) {
ThrowUserError("milestone_required", { bug_id => $id }, "abort"); ThrowUserError("milestone_required", { bug_id => $id });
} }
} }
} }
...@@ -1319,7 +1319,7 @@ foreach my $id (@idlist) { ...@@ -1319,7 +1319,7 @@ foreach my $id (@idlist) {
next if $i eq ""; next if $i eq "";
if ($id eq $i) { if ($id eq $i) {
ThrowUserError("dependency_loop_single", undef, "abort"); ThrowUserError("dependency_loop_single");
} }
if (!exists $seen{$i}) { if (!exists $seen{$i}) {
push(@{$deptree{$target}}, $i); push(@{$deptree{$target}}, $i);
...@@ -1363,8 +1363,7 @@ foreach my $id (@idlist) { ...@@ -1363,8 +1363,7 @@ foreach my $id (@idlist) {
} }
ThrowUserError("dependency_loop_multi", ThrowUserError("dependency_loop_multi",
{ both => $both }, { both => $both });
"abort");
} }
} }
my $tmp = $me; my $tmp = $me;
...@@ -1573,6 +1572,7 @@ foreach my $id (@idlist) { ...@@ -1573,6 +1572,7 @@ foreach my $id (@idlist) {
shift @oldlist; shift @oldlist;
} else { } else {
if ($oldlist[0] != $newlist[0]) { if ($oldlist[0] != $newlist[0]) {
$dbh->bz_unlock_tables(UNLOCK_ABORT);
die "Error in list comparing code"; die "Error in list comparing code";
} }
shift @oldlist; shift @oldlist;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment