| #!/usr/bin/env perl -wT |
| # -*- 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 Netscape Communications |
| # Corporation. Portions created by Netscape are |
| # Copyright (C) 1998 Netscape Communications Corporation. All |
| # Rights Reserved. |
| # |
| # Contributor(s): Terry Weissman <terry@mozilla.org> |
| # Myk Melez <myk@mozilla.org> |
| # Daniel Raichle <draichle@gmx.net> |
| # Dave Miller <justdave@syndicomm.com> |
| # Alexander J. Vincent <ajvincent@juno.com> |
| # Max Kanat-Alexander <mkanat@bugzilla.org> |
| # Greg Hendricks <ghendricks@novell.com> |
| # Frédéric Buclin <LpSolit@gmail.com> |
| # Marc Schumann <wurblzap@gmail.com> |
| # Byron Jones <bugzilla@glob.com.au> |
| |
| ################################################################################ |
| # Script Initialization |
| ################################################################################ |
| |
| # Make it harder for us to do dangerous things in Perl. |
| use strict; |
| |
| use lib qw(. lib); |
| |
| use Bugzilla; |
| use Bugzilla::Constants; |
| use Bugzilla::Error; |
| use Bugzilla::Flag; |
| use Bugzilla::FlagType; |
| use Bugzilla::User; |
| use Bugzilla::Util; |
| use Bugzilla::Bug; |
| use Bugzilla::Field; |
| use Bugzilla::Attachment; |
| use Bugzilla::Attachment::PatchReader; |
| use Bugzilla::Token; |
| use Bugzilla::Keyword; |
| |
| #if WEBKIT_CHANGES |
| use IPC::Open2; |
| #endif // WEBKIT_CHANGES |
| |
| # For most scripts we don't make $cgi and $template global variables. But |
| # when preparing Bugzilla for mod_perl, this script used these |
| # variables in so many subroutines that it was easier to just |
| # make them globals. |
| local our $cgi = Bugzilla->cgi; |
| local our $template = Bugzilla->template; |
| local our $vars = {}; |
| |
| ################################################################################ |
| # Main Body Execution |
| ################################################################################ |
| |
| # All calls to this script should contain an "action" variable whose |
| # value determines what the user wants to do. The code below checks |
| # the value of that variable and runs the appropriate code. If none is |
| # supplied, we default to 'view'. |
| |
| # Determine whether to use the action specified by the user or the default. |
| my $action = $cgi->param('action') || 'view'; |
| |
| # You must use the appropriate urlbase/sslbase param when doing anything |
| # but viewing an attachment. |
| if ($action ne 'view') { |
| my $urlbase = Bugzilla->params->{'urlbase'}; |
| my $sslbase = Bugzilla->params->{'sslbase'}; |
| my $path_regexp = $sslbase ? qr/^(\Q$urlbase\E|\Q$sslbase\E)/ : qr/^\Q$urlbase\E/; |
| if (use_attachbase() && $cgi->self_url !~ /$path_regexp/) { |
| $cgi->redirect_to_urlbase; |
| } |
| Bugzilla->login(); |
| } |
| |
| # Determine if PatchReader is installed |
| eval { |
| require PatchReader; |
| $vars->{'patchviewerinstalled'} = 1; |
| }; |
| |
| # When viewing an attachment, do not request credentials if we are on |
| # the alternate host. Let view() decide when to call Bugzilla->login. |
| if ($action eq "view") |
| { |
| view(); |
| } |
| elsif ($action eq "interdiff") |
| { |
| interdiff(); |
| } |
| elsif ($action eq "diff") |
| { |
| diff(); |
| } |
| elsif ($action eq "viewall") |
| { |
| viewall(); |
| } |
| elsif ($action eq "enter") |
| { |
| Bugzilla->login(LOGIN_REQUIRED); |
| enter(); |
| } |
| elsif ($action eq "insert") |
| { |
| Bugzilla->login(LOGIN_REQUIRED); |
| insert(); |
| } |
| elsif ($action eq "edit") |
| { |
| edit(); |
| } |
| #if WEBKIT_CHANGES |
| elsif ($action eq "review") |
| { |
| edit("review"); |
| } |
| elsif ($action eq "reviewform") |
| { |
| edit("reviewform"); |
| } |
| #endif // WEBKIT_CHANGES |
| elsif ($action eq "update") |
| { |
| Bugzilla->login(LOGIN_REQUIRED); |
| update(); |
| } |
| #if WEBKIT_CHANGES |
| elsif ($action eq "prettypatch") |
| { |
| prettyPatch(); |
| } |
| #endif // WEBKIT_CHANGES |
| elsif ($action eq "delete") { |
| delete_attachment(); |
| } |
| else |
| { |
| ThrowCodeError("unknown_action", { action => $action }); |
| } |
| |
| exit; |
| |
| ################################################################################ |
| # Data Validation / Security Authorization |
| ################################################################################ |
| |
| # Validates an attachment ID. Optionally takes a parameter of a form |
| # variable name that contains the ID to be validated. If not specified, |
| # uses 'id'. |
| # If the second parameter is true, the attachment ID will be validated, |
| # however the current user's access to the attachment will not be checked. |
| # Will throw an error if 1) attachment ID is not a valid number, |
| # 2) attachment does not exist, or 3) user isn't allowed to access the |
| # attachment. |
| # |
| # Returns an attachment object. |
| |
| sub validateID { |
| my($param, $dont_validate_access) = @_; |
| $param ||= 'id'; |
| |
| # If we're not doing interdiffs, check if id wasn't specified and |
| # prompt them with a page that allows them to choose an attachment. |
| # Happens when calling plain attachment.cgi from the urlbar directly |
| if ($param eq 'id' && !$cgi->param('id')) { |
| print $cgi->header(); |
| $template->process("attachment/choose.html.tmpl", $vars) || |
| ThrowTemplateError($template->error()); |
| exit; |
| } |
| |
| my $attach_id = $cgi->param($param); |
| |
| # Validate the specified attachment id. detaint kills $attach_id if |
| # 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) }); |
| |
| # Make sure the attachment exists in the database. |
| my $attachment = Bugzilla::Attachment->get($attach_id) |
| || ThrowUserError("invalid_attach_id", { attach_id => $attach_id }); |
| |
| return $attachment if ($dont_validate_access || check_can_access($attachment)); |
| } |
| |
| # Make sure the current user has access to the specified attachment. |
| sub check_can_access { |
| my $attachment = shift; |
| my $user = Bugzilla->user; |
| |
| # Make sure the user is authorized to access this attachment's bug. |
| ValidateBugID($attachment->bug_id); |
| if ($attachment->isprivate && $user->id != $attachment->attacher->id && !$user->is_insider) { |
| ThrowUserError('auth_failure', {action => 'access', |
| object => 'attachment'}); |
| } |
| return 1; |
| } |
| |
| # Determines if the attachment is public -- that is, if users who are |
| # not logged in have access to the attachment |
| sub attachmentIsPublic { |
| my $attachment = shift; |
| |
| return 0 if Bugzilla->params->{'requirelogin'}; |
| return 0 if $attachment->isprivate; |
| |
| my $anon_user = new Bugzilla::User; |
| return $anon_user->can_see_bug($attachment->bug_id); |
| } |
| |
| # Validates format of a diff/interdiff. Takes a list as an parameter, which |
| # defines the valid format values. Will throw an error if the format is not |
| # in the list. Returns either the user selected or default format. |
| sub validateFormat |
| { |
| # receives a list of legal formats; first item is a default |
| my $format = $cgi->param('format') || $_[0]; |
| if ( lsearch(\@_, $format) == -1) |
| { |
| ThrowUserError("invalid_format", { format => $format, formats => \@_ }); |
| } |
| |
| return $format; |
| } |
| |
| # Validates context of a diff/interdiff. Will throw an error if the context |
| # is not number, "file" or "patch". Returns the validated, detainted context. |
| sub validateContext |
| { |
| my $context = $cgi->param('context') || "patch"; |
| if ($context ne "file" && $context ne "patch") { |
| detaint_natural($context) |
| || ThrowUserError("invalid_context", { context => $cgi->param('context') }); |
| } |
| |
| return $context; |
| } |
| |
| sub validateCanChangeBug |
| { |
| my ($bugid) = @_; |
| my $dbh = Bugzilla->dbh; |
| my ($productid) = $dbh->selectrow_array( |
| "SELECT product_id |
| FROM bugs |
| WHERE bug_id = ?", undef, $bugid); |
| |
| Bugzilla->user->can_edit_product($productid) |
| || ThrowUserError("illegal_attachment_edit_bug", |
| { bug_id => $bugid }); |
| } |
| |
| ################################################################################ |
| # Functions |
| ################################################################################ |
| |
| # Display an attachment. |
| sub view { |
| my $attachment; |
| |
| if (use_attachbase()) { |
| $attachment = validateID(undef, 1); |
| # Replace %bugid% by the ID of the bug the attachment belongs to, if present. |
| my $attachbase = Bugzilla->params->{'attachment_base'}; |
| my $bug_id = $attachment->bug_id; |
| $attachbase =~ s/%bugid%/$bug_id/; |
| my $path = 'attachment.cgi?id=' . $attachment->id; |
| |
| # Make sure the attachment is served from the correct server. |
| if ($cgi->self_url !~ /^\Q$attachbase\E/) { |
| # We couldn't call Bugzilla->login earlier as we first had to make sure |
| # we were not going to request credentials on the alternate host. |
| Bugzilla->login(); |
| if (attachmentIsPublic($attachment)) { |
| # No need for a token; redirect to attachment base. |
| print $cgi->redirect(-location => $attachbase . $path); |
| exit; |
| } else { |
| # Make sure the user can view the attachment. |
| check_can_access($attachment); |
| # Create a token and redirect. |
| my $token = url_quote(issue_session_token($attachment->id)); |
| print $cgi->redirect(-location => $attachbase . "$path&t=$token"); |
| exit; |
| } |
| } else { |
| # No need to validate the token for public attachments. We cannot request |
| # credentials as we are on the alternate host. |
| if (!attachmentIsPublic($attachment)) { |
| my $token = $cgi->param('t'); |
| my ($userid, undef, $token_attach_id) = Bugzilla::Token::GetTokenData($token); |
| unless ($userid |
| && detaint_natural($token_attach_id) |
| && ($token_attach_id == $attachment->id)) |
| { |
| # Not a valid token. |
| print $cgi->redirect('-location' => correct_urlbase() . $path); |
| exit; |
| } |
| # Change current user without creating cookies. |
| Bugzilla->set_user(new Bugzilla::User($userid)); |
| # Tokens are single use only, delete it. |
| delete_token($token); |
| } |
| } |
| } else { |
| # No alternate host is used. Request credentials if required. |
| Bugzilla->login(); |
| $attachment = validateID(); |
| } |
| |
| # At this point, Bugzilla->login has been called if it had to. |
| my $contenttype = $attachment->contenttype; |
| my $filename = $attachment->filename; |
| |
| # Bug 111522: allow overriding content-type manually in the posted form |
| # params. |
| if (defined $cgi->param('content_type')) |
| { |
| $cgi->param('contenttypemethod', 'manual'); |
| $cgi->param('contenttypeentry', $cgi->param('content_type')); |
| Bugzilla::Attachment->validate_content_type(THROW_ERROR); |
| $contenttype = $cgi->param('content_type'); |
| } |
| |
| # Return the appropriate HTTP response headers. |
| $attachment->datasize || ThrowUserError("attachment_removed"); |
| |
| $filename =~ s/^.*[\/\\]//; |
| # escape quotes and backslashes in the filename, per RFCs 2045/822 |
| $filename =~ s/\\/\\\\/g; # escape backslashes |
| $filename =~ s/"/\\"/g; # escape quotes |
| |
| my $disposition = Bugzilla->params->{'allow_attachment_display'} ? 'inline' : 'attachment'; |
| |
| print $cgi->header(-type=>"$contenttype; name=\"$filename\"", |
| -content_disposition=> "$disposition; filename=\"$filename\"", |
| -content_length => $attachment->datasize); |
| disable_utf8(); |
| print $attachment->data; |
| } |
| |
| sub interdiff { |
| # Retrieve and validate parameters |
| my $old_attachment = validateID('oldid'); |
| my $new_attachment = validateID('newid'); |
| my $format = validateFormat('html', 'raw'); |
| my $context = validateContext(); |
| |
| Bugzilla::Attachment::PatchReader::process_interdiff( |
| $old_attachment, $new_attachment, $format, $context); |
| } |
| |
| #if WEBKIT_CHANGES |
| sub prettyPatch |
| { |
| # Retrieve and validate parameters |
| my $attachment = validateID(); |
| my $format = validateFormat('html', 'raw'); |
| my $context = validateContext(); |
| |
| # If it is not a patch, view normally. |
| if (!$attachment->ispatch) { |
| view(); |
| return; |
| } |
| |
| use vars qw($cgi); |
| print $cgi->header(-type => 'text/html', |
| -expires => '+3M'); |
| |
| open2(\*OUT, \*IN, "/usr/bin/ruby", "-I", "PrettyPatch", "PrettyPatch/prettify.rb", "--html-exceptions"); |
| print IN $attachment->data . "\n"; |
| close(IN); |
| while (<OUT>) { |
| print; |
| } |
| close(OUT); |
| } |
| #endif // WEBKIT_CHANGES |
| |
| sub diff { |
| # Retrieve and validate parameters |
| my $attachment = validateID(); |
| my $format = validateFormat('html', 'raw'); |
| my $context = validateContext(); |
| |
| # If it is not a patch, view normally. |
| if (!$attachment->ispatch) { |
| view(); |
| return; |
| } |
| |
| Bugzilla::Attachment::PatchReader::process_diff($attachment, $format, $context); |
| } |
| |
| # Display all attachments for a given bug in a series of IFRAMEs within one |
| # HTML page. |
| sub viewall { |
| # Retrieve and validate parameters |
| my $bugid = $cgi->param('bugid'); |
| ValidateBugID($bugid); |
| my $bug = new Bugzilla::Bug($bugid); |
| |
| my $attachments = Bugzilla::Attachment->get_attachments_by_bug($bugid); |
| |
| # Define the variables and functions that will be passed to the UI template. |
| $vars->{'bug'} = $bug; |
| $vars->{'attachments'} = $attachments; |
| |
| print $cgi->header(); |
| |
| # Generate and return the UI (HTML page) from the appropriate template. |
| $template->process("attachment/show-multiple.html.tmpl", $vars) |
| || ThrowTemplateError($template->error()); |
| } |
| |
| # Display a form for entering a new attachment. |
| sub enter { |
| # Retrieve and validate parameters |
| my $bugid = $cgi->param('bugid'); |
| ValidateBugID($bugid); |
| validateCanChangeBug($bugid); |
| my $dbh = Bugzilla->dbh; |
| my $user = Bugzilla->user; |
| |
| my $bug = new Bugzilla::Bug($bugid, $user->id); |
| # Retrieve the attachments the user can edit from the database and write |
| # them into an array of hashes where each hash represents one attachment. |
| my $canEdit = ""; |
| if (!$user->in_group('editbugs', $bug->product_id)) { |
| $canEdit = "AND submitter_id = " . $user->id; |
| } |
| my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments |
| WHERE bug_id = ? AND isobsolete = 0 $canEdit |
| ORDER BY attach_id", undef, $bugid); |
| |
| # Define the variables and functions that will be passed to the UI template. |
| $vars->{'bug'} = $bug; |
| $vars->{'attachments'} = Bugzilla::Attachment->get_list($attach_ids); |
| |
| my $flag_types = Bugzilla::FlagType::match({'target_type' => 'attachment', |
| 'product_id' => $bug->product_id, |
| 'component_id' => $bug->component_id}); |
| $vars->{'flag_types'} = $flag_types; |
| $vars->{'any_flags_requesteeble'} = grep($_->is_requesteeble, @$flag_types); |
| $vars->{'token'} = issue_session_token('createattachment:'); |
| |
| print $cgi->header(); |
| |
| # Generate and return the UI (HTML page) from the appropriate template. |
| $template->process("attachment/create.html.tmpl", $vars) |
| || ThrowTemplateError($template->error()); |
| } |
| |
| # Insert a new attachment into the database. |
| sub insert { |
| my $dbh = Bugzilla->dbh; |
| my $user = Bugzilla->user; |
| |
| $dbh->bz_start_transaction; |
| |
| # Retrieve and validate parameters |
| my $bugid = $cgi->param('bugid'); |
| ValidateBugID($bugid); |
| validateCanChangeBug($bugid); |
| my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()"); |
| |
| # Detect if the user already used the same form to submit an attachment |
| my $token = trim($cgi->param('token')); |
| if ($token) { |
| my ($creator_id, $date, $old_attach_id) = Bugzilla::Token::GetTokenData($token); |
| unless ($creator_id |
| && ($creator_id == $user->id) |
| && ($old_attach_id =~ "^createattachment:")) |
| { |
| # The token is invalid. |
| ThrowUserError('token_does_not_exist'); |
| } |
| |
| $old_attach_id =~ s/^createattachment://; |
| |
| if ($old_attach_id) { |
| $vars->{'bugid'} = $bugid; |
| $vars->{'attachid'} = $old_attach_id; |
| print $cgi->header(); |
| $template->process("attachment/cancel-create-dupe.html.tmpl", $vars) |
| || ThrowTemplateError($template->error()); |
| exit; |
| } |
| } |
| |
| my $bug = new Bugzilla::Bug($bugid); |
| my $attachment = |
| Bugzilla::Attachment->insert_attachment_for_bug(THROW_ERROR, $bug, $user, |
| $timestamp, $vars); |
| |
| # Insert a comment about the new attachment into the database. |
| my $comment = "Created an attachment (id=" . $attachment->id . ")\n" . |
| $attachment->description . "\n"; |
| $comment .= ("\n" . $cgi->param('comment')) if defined $cgi->param('comment'); |
| |
| $bug->add_comment($comment, { isprivate => $attachment->isprivate }); |
| |
| # Assign the bug to the user, if they are allowed to take it |
| my $owner = ""; |
| if ($cgi->param('takebug') && $user->in_group('editbugs', $bug->product_id)) { |
| # When taking a bug, we have to follow the workflow. |
| my $bug_status = $cgi->param('bug_status') || ''; |
| ($bug_status) = grep {$_->name eq $bug_status} @{$bug->status->can_change_to}; |
| |
| if ($bug_status && $bug_status->is_open |
| && ($bug_status->name ne 'UNCONFIRMED' || $bug->product_obj->votes_to_confirm)) |
| { |
| $bug->set_status($bug_status->name); |
| $bug->clear_resolution(); |
| } |
| # Make sure the person we are taking the bug from gets mail. |
| $owner = $bug->assigned_to->login; |
| $bug->set_assigned_to($user); |
| } |
| $bug->update($timestamp); |
| |
| if ($token) { |
| trick_taint($token); |
| $dbh->do('UPDATE tokens SET eventdata = ? WHERE token = ?', undef, |
| ("createattachment:" . $attachment->id, $token)); |
| } |
| |
| $dbh->bz_commit_transaction; |
| |
| # Define the variables and functions that will be passed to the UI template. |
| $vars->{'mailrecipients'} = { 'changer' => $user->login, |
| 'owner' => $owner }; |
| $vars->{'attachment'} = $attachment; |
| # We cannot reuse the $bug object as delta_ts has eventually been updated |
| # since the object was created. |
| $vars->{'bugs'} = [new Bugzilla::Bug($bugid)]; |
| $vars->{'header_done'} = 1; |
| $vars->{'contenttypemethod'} = $cgi->param('contenttypemethod'); |
| $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count(); |
| |
| print $cgi->header(); |
| # Generate and return the UI (HTML page) from the appropriate template. |
| $template->process("attachment/created.html.tmpl", $vars) |
| || ThrowTemplateError($template->error()); |
| } |
| |
| # Displays a form for editing attachment properties. |
| # Any user is allowed to access this page, unless the attachment |
| # is private and the user does not belong to the insider group. |
| # Validations are done later when the user submits changes. |
| sub edit { |
| #if WEBKIT_CHANGES |
| my ($template_name) = @_; |
| $template_name = $template_name || "edit"; |
| #endif // WEBKIT_CHANGES |
| |
| my $attachment = validateID(); |
| my $dbh = Bugzilla->dbh; |
| |
| # Retrieve a list of attachments for this bug as well as a summary of the bug |
| # to use in a navigation bar across the top of the screen. |
| my $bugattachments = |
| Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id); |
| # We only want attachment IDs. |
| @$bugattachments = map { $_->id } @$bugattachments; |
| |
| my ($bugsummary, $product_id, $component_id) = |
| $dbh->selectrow_array('SELECT short_desc, product_id, component_id |
| FROM bugs |
| WHERE bug_id = ?', undef, $attachment->bug_id); |
| |
| # Get a list of flag types that can be set for this attachment. |
| my $flag_types = Bugzilla::FlagType::match({ 'target_type' => 'attachment' , |
| 'product_id' => $product_id , |
| 'component_id' => $component_id }); |
| foreach my $flag_type (@$flag_types) { |
| $flag_type->{'flags'} = Bugzilla::Flag->match({ 'type_id' => $flag_type->id, |
| 'attach_id' => $attachment->id }); |
| } |
| $vars->{'flag_types'} = $flag_types; |
| $vars->{'any_flags_requesteeble'} = grep($_->is_requesteeble, @$flag_types); |
| $vars->{'attachment'} = $attachment; |
| $vars->{'bugsummary'} = $bugsummary; |
| $vars->{'attachments'} = $bugattachments; |
| |
| #if WEBKIT_CHANGES |
| if ($attachment->ispatch) { |
| my $quotedpatch = $attachment->data; |
| $quotedpatch =~ s/^/> /mg; |
| $vars->{'quotedpatch'} = $quotedpatch; |
| } |
| #endif // WEBKIT_CHANGES |
| |
| print $cgi->header(); |
| |
| # Generate and return the UI (HTML page) from the appropriate template. |
| $template->process("attachment/$template_name.html.tmpl", $vars) # WEBKIT_CHANGES |
| || ThrowTemplateError($template->error()); |
| } |
| |
| # Updates an attachment record. Users with "editbugs" privileges, (or the |
| # original attachment's submitter) can edit the attachment's description, |
| # content type, ispatch and isobsolete flags, and statuses, and they can |
| # also submit a comment that appears in the bug. |
| # Users cannot edit the content of the attachment itself. |
| sub update { |
| my $user = Bugzilla->user; |
| my $dbh = Bugzilla->dbh; |
| |
| # Retrieve and validate parameters |
| my $attachment = validateID(); |
| my $bug = new Bugzilla::Bug($attachment->bug_id); |
| $attachment->validate_can_edit($bug->product_id); |
| validateCanChangeBug($bug->id); |
| Bugzilla::Attachment->validate_description(THROW_ERROR); |
| Bugzilla::Attachment->validate_is_patch(THROW_ERROR); |
| Bugzilla::Attachment->validate_content_type(THROW_ERROR) unless $cgi->param('ispatch'); |
| $cgi->param('isobsolete', $cgi->param('isobsolete') ? 1 : 0); |
| $cgi->param('isprivate', $cgi->param('isprivate') ? 1 : 0); |
| |
| # Now make sure the attachment has not been edited since we loaded the page. |
| if (defined $cgi->param('delta_ts') |
| && $cgi->param('delta_ts') ne $attachment->modification_time) |
| { |
| ($vars->{'operations'}) = |
| Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $cgi->param('delta_ts')); |
| |
| # The token contains the old modification_time. We need a new one. |
| $cgi->param('token', issue_hash_token([$attachment->id, $attachment->modification_time])); |
| |
| # If the modification date changed but there is no entry in |
| # the activity table, this means someone commented only. |
| # In this case, there is no reason to midair. |
| if (scalar(@{$vars->{'operations'}})) { |
| $cgi->param('delta_ts', $attachment->modification_time); |
| $vars->{'attachment'} = $attachment; |
| |
| print $cgi->header(); |
| # Warn the user about the mid-air collision and ask them what to do. |
| $template->process("attachment/midair.html.tmpl", $vars) |
| || ThrowTemplateError($template->error()); |
| exit; |
| } |
| } |
| |
| # We couldn't do this check earlier as we first had to validate attachment ID |
| # and display the mid-air collision page if modification_time changed. |
| my $token = $cgi->param('token'); |
| check_hash_token($token, [$attachment->id, $attachment->modification_time]); |
| |
| # If the submitter of the attachment is not in the insidergroup, |
| # be sure that he cannot overwrite the private bit. |
| # This check must be done before calling Bugzilla::Flag*::validate(), |
| # because they will look at the private bit when checking permissions. |
| # XXX - This is a ugly hack. Ideally, we shouldn't have to look at the |
| # old private bit twice (first here, and then below again), but this is |
| # the less risky change. |
| unless ($user->is_insider) { |
| $cgi->param('isprivate', $attachment->isprivate); |
| } |
| |
| # If the user submitted a comment while editing the attachment, |
| # add the comment to the bug. Do this after having validated isprivate! |
| if ($cgi->param('comment')) { |
| # Prepend a string to the comment to let users know that the comment came |
| # from the "edit attachment" screen. |
| my $comment = "(From update of attachment " . $attachment->id . ")\n" . |
| $cgi->param('comment'); |
| |
| $bug->add_comment($comment, { isprivate => $cgi->param('isprivate') }); |
| } |
| |
| # The order of these function calls is important, as Flag::validate |
| # assumes User::match_field has ensured that the values in the |
| # requestee fields are legitimate user email addresses. |
| Bugzilla::User::match_field($cgi, { |
| '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' } |
| }); |
| Bugzilla::Flag::validate($bug->id, $attachment->id); |
| |
| # Start a transaction in preparation for updating the attachment. |
| $dbh->bz_start_transaction(); |
| |
| # Quote the description and content type for use in the SQL UPDATE statement. |
| my $description = $cgi->param('description'); |
| my $contenttype = $cgi->param('contenttype'); |
| my $filename = $cgi->param('filename'); |
| # we can detaint this way thanks to placeholders |
| trick_taint($description); |
| trick_taint($contenttype); |
| trick_taint($filename); |
| |
| # Figure out when the changes were made. |
| my ($timestamp) = $dbh->selectrow_array("SELECT NOW()"); |
| |
| # Update flags. We have to do this before committing changes |
| # to attachments so that we can delete pending requests if the user |
| # is obsoleting this attachment without deleting any requests |
| # the user submits at the same time. |
| Bugzilla::Flag->process($bug, $attachment, $timestamp, $vars); |
| |
| # Update the attachment record in the database. |
| $dbh->do("UPDATE attachments |
| SET description = ?, |
| mimetype = ?, |
| filename = ?, |
| ispatch = ?, |
| isobsolete = ?, |
| isprivate = ?, |
| modification_time = ? |
| WHERE attach_id = ?", |
| undef, ($description, $contenttype, $filename, |
| $cgi->param('ispatch'), $cgi->param('isobsolete'), |
| $cgi->param('isprivate'), $timestamp, $attachment->id)); |
| |
| my $updated_attachment = Bugzilla::Attachment->get($attachment->id); |
| # Record changes in the activity table. |
| my $sth = $dbh->prepare('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, |
| fieldid, removed, added) |
| VALUES (?, ?, ?, ?, ?, ?, ?)'); |
| |
| if ($attachment->description ne $updated_attachment->description) { |
| my $fieldid = get_field_id('attachments.description'); |
| $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid, |
| $attachment->description, $updated_attachment->description); |
| } |
| if ($attachment->contenttype ne $updated_attachment->contenttype) { |
| my $fieldid = get_field_id('attachments.mimetype'); |
| $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid, |
| $attachment->contenttype, $updated_attachment->contenttype); |
| } |
| if ($attachment->filename ne $updated_attachment->filename) { |
| my $fieldid = get_field_id('attachments.filename'); |
| $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid, |
| $attachment->filename, $updated_attachment->filename); |
| } |
| if ($attachment->ispatch != $updated_attachment->ispatch) { |
| my $fieldid = get_field_id('attachments.ispatch'); |
| $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid, |
| $attachment->ispatch, $updated_attachment->ispatch); |
| } |
| if ($attachment->isobsolete != $updated_attachment->isobsolete) { |
| my $fieldid = get_field_id('attachments.isobsolete'); |
| $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid, |
| $attachment->isobsolete, $updated_attachment->isobsolete); |
| } |
| if ($attachment->isprivate != $updated_attachment->isprivate) { |
| my $fieldid = get_field_id('attachments.isprivate'); |
| $sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid, |
| $attachment->isprivate, $updated_attachment->isprivate); |
| } |
| |
| # Commit the transaction now that we are finished updating the database. |
| $dbh->bz_commit_transaction(); |
| |
| # Commit the comment, if any. |
| $bug->update(); |
| |
| # Define the variables and functions that will be passed to the UI template. |
| $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login }; |
| $vars->{'attachment'} = $attachment; |
| # We cannot reuse the $bug object as delta_ts has eventually been updated |
| # since the object was created. |
| $vars->{'bugs'} = [new Bugzilla::Bug($bug->id)]; |
| $vars->{'header_done'} = 1; |
| $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count(); |
| |
| print $cgi->header(); |
| |
| # Generate and return the UI (HTML page) from the appropriate template. |
| $template->process("attachment/updated.html.tmpl", $vars) |
| || ThrowTemplateError($template->error()); |
| } |
| |
| # Only administrators can delete attachments. |
| sub delete_attachment { |
| my $user = Bugzilla->login(LOGIN_REQUIRED); |
| my $dbh = Bugzilla->dbh; |
| |
| print $cgi->header(); |
| |
| $user->in_group('admin') |
| || ThrowUserError('auth_failure', {group => 'admin', |
| action => 'delete', |
| object => 'attachment'}); |
| |
| Bugzilla->params->{'allow_attachment_deletion'} |
| || ThrowUserError('attachment_deletion_disabled'); |
| |
| # Make sure the administrator is allowed to edit this attachment. |
| my $attachment = validateID(); |
| validateCanChangeBug($attachment->bug_id); |
| |
| $attachment->datasize || ThrowUserError('attachment_removed'); |
| |
| # We don't want to let a malicious URL accidentally delete an attachment. |
| my $token = trim($cgi->param('token')); |
| if ($token) { |
| my ($creator_id, $date, $event) = Bugzilla::Token::GetTokenData($token); |
| unless ($creator_id |
| && ($creator_id == $user->id) |
| && ($event eq 'attachment' . $attachment->id)) |
| { |
| # The token is invalid. |
| ThrowUserError('token_does_not_exist'); |
| } |
| |
| my $bug = new Bugzilla::Bug($attachment->bug_id); |
| |
| # The token is valid. Delete the content of the attachment. |
| my $msg; |
| $vars->{'attachment'} = $attachment; |
| $vars->{'date'} = $date; |
| $vars->{'reason'} = clean_text($cgi->param('reason') || ''); |
| $vars->{'mailrecipients'} = { 'changer' => $user->login }; |
| |
| $template->process("attachment/delete_reason.txt.tmpl", $vars, \$msg) |
| || ThrowTemplateError($template->error()); |
| |
| # Paste the reason provided by the admin into a comment. |
| $bug->add_comment($msg); |
| |
| # If the attachment is stored locally, remove it. |
| if (-e $attachment->_get_local_filename) { |
| unlink $attachment->_get_local_filename; |
| } |
| $attachment->remove_from_db(); |
| |
| # Now delete the token. |
| delete_token($token); |
| |
| # Insert the comment. |
| $bug->update(); |
| |
| # Required to display the bug the deleted attachment belongs to. |
| $vars->{'bugs'} = [$bug]; |
| $vars->{'header_done'} = 1; |
| $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count(); |
| |
| $template->process("attachment/updated.html.tmpl", $vars) |
| || ThrowTemplateError($template->error()); |
| } |
| else { |
| # Create a token. |
| $token = issue_session_token('attachment' . $attachment->id); |
| |
| $vars->{'a'} = $attachment; |
| $vars->{'token'} = $token; |
| |
| $template->process("attachment/confirm-delete.html.tmpl", $vars) |
| || ThrowTemplateError($template->error()); |
| } |
| } |