123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960 |
- # -*- 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>
- # Marc Schumann <wurblzap@gmail.com>
- # Frédéric Buclin <LpSolit@gmail.com>
- use strict;
- package Bugzilla::Attachment;
- =head1 NAME
- Bugzilla::Attachment - a file related to a bug that a user has uploaded
- to the Bugzilla server
- =head1 SYNOPSIS
- use Bugzilla::Attachment;
- # Get the attachment with the given ID.
- my $attachment = Bugzilla::Attachment->get($attach_id);
- # Get the attachments with the given IDs.
- my $attachments = Bugzilla::Attachment->get_list($attach_ids);
- =head1 DESCRIPTION
- This module defines attachment objects, which represent files related to bugs
- that users upload to the Bugzilla server.
- =cut
- use Bugzilla::Constants;
- use Bugzilla::Error;
- use Bugzilla::Flag;
- use Bugzilla::User;
- use Bugzilla::Util;
- use Bugzilla::Field;
- sub get {
- my $invocant = shift;
- my $id = shift;
- my $attachments = _retrieve([$id]);
- my $self = $attachments->[0];
- bless($self, ref($invocant) || $invocant) if $self;
- return $self;
- }
- sub get_list {
- my $invocant = shift;
- my $ids = shift;
- my $attachments = _retrieve($ids);
- foreach my $attachment (@$attachments) {
- bless($attachment, ref($invocant) || $invocant);
- }
- return $attachments;
- }
- sub _retrieve {
- my ($ids) = @_;
- return [] if scalar(@$ids) == 0;
- my @columns = (
- 'attachments.attach_id AS id',
- 'attachments.bug_id AS bug_id',
- 'attachments.description AS description',
- 'attachments.mimetype AS contenttype',
- 'attachments.submitter_id AS attacher_id',
- Bugzilla->dbh->sql_date_format('attachments.creation_ts',
- '%Y.%m.%d %H:%i') . " AS attached",
- 'attachments.modification_time',
- 'attachments.filename AS filename',
- 'attachments.ispatch AS ispatch',
- 'attachments.isurl AS isurl',
- 'attachments.isobsolete AS isobsolete',
- 'attachments.isprivate AS isprivate'
- );
- my $columns = join(", ", @columns);
- my $dbh = Bugzilla->dbh;
- my $records = $dbh->selectall_arrayref(
- "SELECT $columns
- FROM attachments
- WHERE "
- . Bugzilla->dbh->sql_in('attach_id', $ids)
- . " ORDER BY attach_id",
- { Slice => {} });
- return $records;
- }
- =pod
- =head2 Instance Properties
- =over
- =item C<id>
- the unique identifier for the attachment
- =back
- =cut
- sub id {
- my $self = shift;
- return $self->{id};
- }
- =over
- =item C<bug_id>
- the ID of the bug to which the attachment is attached
- =back
- =cut
- # XXX Once Bug.pm slims down sufficiently this should become a reference
- # to a bug object.
- sub bug_id {
- my $self = shift;
- return $self->{bug_id};
- }
- =over
- =item C<description>
- user-provided text describing the attachment
- =back
- =cut
- sub description {
- my $self = shift;
- return $self->{description};
- }
- =over
- =item C<contenttype>
- the attachment's MIME media type
- =back
- =cut
- sub contenttype {
- my $self = shift;
- return $self->{contenttype};
- }
- =over
- =item C<attacher>
- the user who attached the attachment
- =back
- =cut
- sub attacher {
- my $self = shift;
- return $self->{attacher} if exists $self->{attacher};
- $self->{attacher} = new Bugzilla::User($self->{attacher_id});
- return $self->{attacher};
- }
- =over
- =item C<attached>
- the date and time on which the attacher attached the attachment
- =back
- =cut
- sub attached {
- my $self = shift;
- return $self->{attached};
- }
- =over
- =item C<modification_time>
- the date and time on which the attachment was last modified.
- =back
- =cut
- sub modification_time {
- my $self = shift;
- return $self->{modification_time};
- }
- =over
- =item C<filename>
- the name of the file the attacher attached
- =back
- =cut
- sub filename {
- my $self = shift;
- return $self->{filename};
- }
- =over
- =item C<ispatch>
- whether or not the attachment is a patch
- =back
- =cut
- sub ispatch {
- my $self = shift;
- return $self->{ispatch};
- }
- =over
- =item C<isurl>
- whether or not the attachment is a URL
- =back
- =cut
- sub isurl {
- my $self = shift;
- return $self->{isurl};
- }
- =over
- =item C<isobsolete>
- whether or not the attachment is obsolete
- =back
- =cut
- sub isobsolete {
- my $self = shift;
- return $self->{isobsolete};
- }
- =over
- =item C<isprivate>
- whether or not the attachment is private
- =back
- =cut
- sub isprivate {
- my $self = shift;
- return $self->{isprivate};
- }
- =over
- =item C<is_viewable>
- Returns 1 if the attachment has a content-type viewable in this browser.
- Note that we don't use $cgi->Accept()'s ability to check if a content-type
- matches, because this will return a value even if it's matched by the generic
- */* which most browsers add to the end of their Accept: headers.
- =back
- =cut
- sub is_viewable {
- my $self = shift;
- my $contenttype = $self->contenttype;
- my $cgi = Bugzilla->cgi;
- # We assume we can view all text and image types.
- return 1 if ($contenttype =~ /^(text|image)\//);
- # Mozilla can view XUL. Note the trailing slash on the Gecko detection to
- # avoid sending XUL to Safari.
- return 1 if (($contenttype =~ /^application\/vnd\.mozilla\./)
- && ($cgi->user_agent() =~ /Gecko\//));
- # If it's not one of the above types, we check the Accept: header for any
- # types mentioned explicitly.
- my $accept = join(",", $cgi->Accept());
- return 1 if ($accept =~ /^(.*,)?\Q$contenttype\E(,.*)?$/);
- return 0;
- }
- =over
- =item C<data>
- the content of the attachment
- =back
- =cut
- sub data {
- my $self = shift;
- return $self->{data} if exists $self->{data};
- # First try to get the attachment data from the database.
- ($self->{data}) = Bugzilla->dbh->selectrow_array("SELECT thedata
- FROM attach_data
- WHERE id = ?",
- undef,
- $self->{id});
- # If there's no attachment data in the database, the attachment is stored
- # in a local file, so retrieve it from there.
- if (length($self->{data}) == 0) {
- if (open(AH, $self->_get_local_filename())) {
- local $/;
- binmode AH;
- $self->{data} = <AH>;
- close(AH);
- }
- }
- return $self->{data};
- }
- =over
- =item C<datasize>
- the length (in characters) of the attachment content
- =back
- =cut
- # datasize is a property of the data itself, and it's unclear whether we should
- # expose it at all, since you can easily derive it from the data itself: in TT,
- # attachment.data.size; in Perl, length($attachment->{data}). But perhaps
- # it makes sense for performance reasons, since accessing the data forces it
- # to get retrieved from the database/filesystem and loaded into memory,
- # while datasize avoids loading the attachment into memory, calling SQL's
- # LENGTH() function or stat()ing the file instead. I've left it in for now.
- sub datasize {
- my $self = shift;
- return $self->{datasize} if exists $self->{datasize};
- # If we have already retrieved the data, return its size.
- return length($self->{data}) if exists $self->{data};
- $self->{datasize} =
- Bugzilla->dbh->selectrow_array("SELECT LENGTH(thedata)
- FROM attach_data
- WHERE id = ?",
- undef, $self->{id}) || 0;
- # If there's no attachment data in the database, either the attachment
- # is stored in a local file, and so retrieve its size from the file,
- # or the attachment has been deleted.
- unless ($self->{datasize}) {
- if (open(AH, $self->_get_local_filename())) {
- binmode AH;
- $self->{datasize} = (stat(AH))[7];
- close(AH);
- }
- }
- return $self->{datasize};
- }
- =over
- =item C<flags>
- flags that have been set on the attachment
- =back
- =cut
- sub flags {
- my $self = shift;
- return $self->{flags} if exists $self->{flags};
- $self->{flags} = Bugzilla::Flag->match({ 'attach_id' => $self->id });
- return $self->{flags};
- }
- # Instance methods; no POD documentation here yet because the only ones so far
- # are private.
- sub _get_local_filename {
- my $self = shift;
- my $hash = ($self->id % 100) + 100;
- $hash =~ s/.*(\d\d)$/group.$1/;
- return bz_locations()->{'attachdir'} . "/$hash/attachment." . $self->id;
- }
- sub _validate_filename {
- my ($throw_error) = @_;
- my $cgi = Bugzilla->cgi;
- defined $cgi->upload('data')
- || ($throw_error ? ThrowUserError("file_not_specified") : return 0);
- my $filename = $cgi->upload('data');
- # Remove path info (if any) from the file name. The browser should do this
- # for us, but some are buggy. This may not work on Mac file names and could
- # mess up file names with slashes in them, but them's the breaks. We only
- # use this as a hint to users downloading attachments anyway, so it's not
- # a big deal if it munges incorrectly occasionally.
- $filename =~ s/^.*[\/\\]//;
- # Truncate the filename to 100 characters, counting from the end of the
- # string to make sure we keep the filename extension.
- $filename = substr($filename, -100, 100);
- return $filename;
- }
- sub _validate_data {
- my ($throw_error, $hr_vars) = @_;
- my $cgi = Bugzilla->cgi;
- my $maxsize = $cgi->param('ispatch') ? Bugzilla->params->{'maxpatchsize'}
- : Bugzilla->params->{'maxattachmentsize'};
- $maxsize *= 1024; # Convert from K
- my $fh;
- # Skip uploading into a local variable if the user wants to upload huge
- # attachments into local files.
- if (!$cgi->param('bigfile')) {
- $fh = $cgi->upload('data');
- }
- my $data;
- # We could get away with reading only as much as required, except that then
- # we wouldn't have a size to print to the error handler below.
- if (!$cgi->param('bigfile')) {
- # enable 'slurp' mode
- local $/;
- $data = <$fh>;
- }
- $data
- || ($cgi->param('bigfile'))
- || ($throw_error ? ThrowUserError("zero_length_file") : return 0);
- # Windows screenshots are usually uncompressed BMP files which
- # makes for a quick way to eat up disk space. Let's compress them.
- # We do this before we check the size since the uncompressed version
- # could easily be greater than maxattachmentsize.
- if (Bugzilla->params->{'convert_uncompressed_images'}
- && $cgi->param('contenttype') eq 'image/bmp') {
- require Image::Magick;
- my $img = Image::Magick->new(magick=>'bmp');
- $img->BlobToImage($data);
- $img->set(magick=>'png');
- my $imgdata = $img->ImageToBlob();
- $data = $imgdata;
- $cgi->param('contenttype', 'image/png');
- $hr_vars->{'convertedbmp'} = 1;
- }
- # Make sure the attachment does not exceed the maximum permitted size
- my $len = $data ? length($data) : 0;
- if ($maxsize && $len > $maxsize) {
- my $vars = { filesize => sprintf("%.0f", $len/1024) };
- if ($cgi->param('ispatch')) {
- $throw_error ? ThrowUserError("patch_too_large", $vars) : return 0;
- }
- else {
- $throw_error ? ThrowUserError("file_too_large", $vars) : return 0;
- }
- }
- return $data || '';
- }
- =pod
- =head2 Class Methods
- =over
- =item C<get_attachments_by_bug($bug_id)>
- Description: retrieves and returns the attachments the currently logged in
- user can view for the given bug.
- Params: C<$bug_id> - integer - the ID of the bug for which
- to retrieve and return attachments.
- Returns: a reference to an array of attachment objects.
- =cut
- sub get_attachments_by_bug {
- my ($class, $bug_id) = @_;
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
- # By default, private attachments are not accessible, unless the user
- # is in the insider group or submitted the attachment.
- my $and_restriction = '';
- my @values = ($bug_id);
- unless ($user->is_insider) {
- $and_restriction = 'AND (isprivate = 0 OR submitter_id = ?)';
- push(@values, $user->id);
- }
- my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
- WHERE bug_id = ? $and_restriction",
- undef, @values);
- my $attachments = Bugzilla::Attachment->get_list($attach_ids);
- return $attachments;
- }
- =pod
- =item C<validate_is_patch()>
- Description: validates the "patch" flag passed in by CGI.
- Returns: 1 on success.
- =cut
- sub validate_is_patch {
- my ($class, $throw_error) = @_;
- my $cgi = Bugzilla->cgi;
- # Set the ispatch flag to zero if it is undefined, since the UI uses
- # an HTML checkbox to represent this flag, and unchecked HTML checkboxes
- # do not get sent in HTML requests.
- $cgi->param('ispatch', $cgi->param('ispatch') ? 1 : 0);
- # Set the content type to text/plain if the attachment is a patch.
- $cgi->param('contenttype', 'text/plain') if $cgi->param('ispatch');
- return 1;
- }
- =pod
- =item C<validate_description()>
- Description: validates the description passed in by CGI.
- Returns: 1 on success.
- =cut
- sub validate_description {
- my ($class, $throw_error) = @_;
- my $cgi = Bugzilla->cgi;
- $cgi->param('description')
- || ($throw_error ? ThrowUserError("missing_attachment_description") : return 0);
- return 1;
- }
- =pod
- =item C<validate_content_type()>
- Description: validates the content type passed in by CGI.
- Returns: 1 on success.
- =cut
- sub validate_content_type {
- my ($class, $throw_error) = @_;
- my $cgi = Bugzilla->cgi;
- if (!defined $cgi->param('contenttypemethod')) {
- $throw_error ? ThrowUserError("missing_content_type_method") : return 0;
- }
- elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
- my $contenttype =
- $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
- # The user asked us to auto-detect the content type, so use the type
- # specified in the HTTP request headers.
- if ( !$contenttype ) {
- $throw_error ? ThrowUserError("missing_content_type") : return 0;
- }
- $cgi->param('contenttype', $contenttype);
- }
- elsif ($cgi->param('contenttypemethod') eq 'list') {
- # The user selected a content type from the list, so use their
- # selection.
- $cgi->param('contenttype', $cgi->param('contenttypeselection'));
- }
- elsif ($cgi->param('contenttypemethod') eq 'manual') {
- # The user entered a content type manually, so use their entry.
- $cgi->param('contenttype', $cgi->param('contenttypeentry'));
- }
- else {
- $throw_error ?
- ThrowCodeError("illegal_content_type_method",
- { contenttypemethod => $cgi->param('contenttypemethod') }) :
- return 0;
- }
- if ( $cgi->param('contenttype') !~
- /^(application|audio|image|message|model|multipart|text|video)\/.+$/ ) {
- $throw_error ?
- ThrowUserError("invalid_content_type",
- { contenttype => $cgi->param('contenttype') }) :
- return 0;
- }
- return 1;
- }
- =pod
- =item C<validate_can_edit($attachment, $product_id)>
- Description: validates if the user is allowed to view and edit the attachment.
- Only the submitter or someone with editbugs privs can edit it.
- Only the submitter and users in the insider group can view
- private attachments.
- Params: $attachment - the attachment object being edited.
- $product_id - the product ID the attachment belongs to.
- Returns: 1 on success. Else an error is thrown.
- =cut
- sub validate_can_edit {
- my ($attachment, $product_id) = @_;
- my $user = Bugzilla->user;
- # The submitter can edit their attachments.
- return 1 if ($attachment->attacher->id == $user->id
- || ((!$attachment->isprivate || $user->is_insider)
- && $user->in_group('editbugs', $product_id)));
- # If we come here, then this attachment cannot be seen by the user.
- ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
- }
- =item C<validate_obsolete($bug)>
- Description: validates if attachments the user wants to mark as obsolete
- really belong to the given bug and are not already obsolete.
- Moreover, a user cannot mark an attachment as obsolete if
- he cannot view it (due to restrictions on it).
- Params: $bug - The bug object obsolete attachments should belong to.
- Returns: 1 on success. Else an error is thrown.
- =cut
- sub validate_obsolete {
- my ($class, $bug) = @_;
- my $cgi = Bugzilla->cgi;
- # Make sure the attachment id is valid and the user has permissions to view
- # the bug to which it is attached. Make sure also that the user can view
- # the attachment itself.
- my @obsolete_attachments;
- foreach my $attachid ($cgi->param('obsolete')) {
- my $vars = {};
- $vars->{'attach_id'} = $attachid;
- detaint_natural($attachid)
- || ThrowCodeError('invalid_attach_id_to_obsolete', $vars);
- my $attachment = Bugzilla::Attachment->get($attachid);
- # Make sure the attachment exists in the database.
- ThrowUserError('invalid_attach_id', $vars) unless $attachment;
- # Check that the user can view and edit this attachment.
- $attachment->validate_can_edit($bug->product_id);
- $vars->{'description'} = $attachment->description;
- if ($attachment->bug_id != $bug->bug_id) {
- $vars->{'my_bug_id'} = $bug->bug_id;
- $vars->{'attach_bug_id'} = $attachment->bug_id;
- ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
- }
- if ($attachment->isobsolete) {
- ThrowCodeError('attachment_already_obsolete', $vars);
- }
- push(@obsolete_attachments, $attachment);
- }
- return @obsolete_attachments;
- }
- =pod
- =item C<insert_attachment_for_bug($throw_error, $bug, $user, $timestamp, $hr_vars)>
- Description: inserts an attachment from CGI input for the given bug.
- Params: C<$bug> - Bugzilla::Bug object - the bug for which to insert
- the attachment.
- C<$user> - Bugzilla::User object - the user we're inserting an
- attachment for.
- C<$timestamp> - scalar - timestamp of the insert as returned
- by SELECT NOW().
- C<$hr_vars> - hash reference - reference to a hash of template
- variables.
- Returns: the ID of the new attachment.
- =cut
- sub insert_attachment_for_bug {
- my ($class, $throw_error, $bug, $user, $timestamp, $hr_vars) = @_;
- my $cgi = Bugzilla->cgi;
- my $dbh = Bugzilla->dbh;
- my $attachurl = $cgi->param('attachurl') || '';
- my $data;
- my $filename;
- my $contenttype;
- my $isurl;
- $class->validate_is_patch($throw_error) || return;
- $class->validate_description($throw_error) || return;
- if (Bugzilla->params->{'allow_attach_url'}
- && ($attachurl =~ /^(http|https|ftp):\/\/\S+/)
- && !defined $cgi->upload('data'))
- {
- $filename = '';
- $data = $attachurl;
- $isurl = 1;
- $contenttype = 'text/plain';
- $cgi->param('ispatch', 0);
- $cgi->delete('bigfile');
- }
- else {
- $filename = _validate_filename($throw_error) || return;
- # need to validate content type before data as
- # we now check the content type for image/bmp in _validate_data()
- unless ($cgi->param('ispatch')) {
- $class->validate_content_type($throw_error) || return;
- # Set the ispatch flag to 1 if we're set to autodetect
- # and the content type is text/x-diff or text/x-patch
- if ($cgi->param('contenttypemethod') eq 'autodetect'
- && $cgi->param('contenttype') =~ m{text/x-(?:diff|patch)})
- {
- $cgi->param('ispatch', 1);
- $cgi->param('contenttype', 'text/plain');
- }
- }
- $data = _validate_data($throw_error, $hr_vars);
- # If the attachment is stored locally, $data eq ''.
- # If an error is thrown, $data eq '0'.
- ($data ne '0') || return;
- $contenttype = $cgi->param('contenttype');
- # These are inserted using placeholders so no need to panic
- trick_taint($filename);
- trick_taint($contenttype);
- $isurl = 0;
- }
- # Check attachments the user tries to mark as obsolete.
- my @obsolete_attachments;
- if ($cgi->param('obsolete')) {
- @obsolete_attachments = $class->validate_obsolete($bug);
- }
- # 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.
- my $match_status = Bugzilla::User::match_field($cgi, {
- '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
- }, MATCH_SKIP_CONFIRM);
- $hr_vars->{'match_field'} = 'requestee';
- if ($match_status == USER_MATCH_FAILED) {
- $hr_vars->{'message'} = 'user_match_failed';
- }
- elsif ($match_status == USER_MATCH_MULTIPLE) {
- $hr_vars->{'message'} = 'user_match_multiple';
- }
- # Escape characters in strings that will be used in SQL statements.
- my $description = $cgi->param('description');
- trick_taint($description);
- my $isprivate = $cgi->param('isprivate') ? 1 : 0;
- # Insert the attachment into the database.
- my $sth = $dbh->do(
- "INSERT INTO attachments
- (bug_id, creation_ts, modification_time, filename, description,
- mimetype, ispatch, isurl, isprivate, submitter_id)
- VALUES (?,?,?,?,?,?,?,?,?,?)", undef, ($bug->bug_id, $timestamp, $timestamp,
- $filename, $description, $contenttype, $cgi->param('ispatch'),
- $isurl, $isprivate, $user->id));
- # Retrieve the ID of the newly created attachment record.
- my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
- # We only use $data here in this INSERT with a placeholder,
- # so it's safe.
- $sth = $dbh->prepare("INSERT INTO attach_data
- (id, thedata) VALUES ($attachid, ?)");
- trick_taint($data);
- $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
- $sth->execute();
- # If the file is to be stored locally, stream the file from the web server
- # to the local file without reading it into a local variable.
- if ($cgi->param('bigfile')) {
- my $attachdir = bz_locations()->{'attachdir'};
- my $fh = $cgi->upload('data');
- my $hash = ($attachid % 100) + 100;
- $hash =~ s/.*(\d\d)$/group.$1/;
- mkdir "$attachdir/$hash", 0770;
- chmod 0770, "$attachdir/$hash";
- open(AH, ">$attachdir/$hash/attachment.$attachid");
- binmode AH;
- my $sizecount = 0;
- my $limit = (Bugzilla->params->{"maxlocalattachment"} * 1048576);
- while (<$fh>) {
- print AH $_;
- $sizecount += length($_);
- if ($sizecount > $limit) {
- close AH;
- close $fh;
- unlink "$attachdir/$hash/attachment.$attachid";
- $throw_error ? ThrowUserError("local_file_too_large") : return;
- }
- }
- close AH;
- close $fh;
- }
- # Make existing attachments obsolete.
- my $fieldid = get_field_id('attachments.isobsolete');
- foreach my $obsolete_attachment (@obsolete_attachments) {
- # If the obsolete attachment has request flags, cancel them.
- # This call must be done before updating the 'attachments' table.
- Bugzilla::Flag->CancelRequests($bug, $obsolete_attachment, $timestamp);
- $dbh->do('UPDATE attachments SET isobsolete = 1, modification_time = ?
- WHERE attach_id = ?',
- undef, ($timestamp, $obsolete_attachment->id));
- $dbh->do('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
- fieldid, removed, added)
- VALUES (?,?,?,?,?,?,?)',
- undef, ($bug->bug_id, $obsolete_attachment->id, $user->id,
- $timestamp, $fieldid, 0, 1));
- }
- my $attachment = Bugzilla::Attachment->get($attachid);
- # 1. Add flags, if any. To avoid dying if something goes wrong
- # while processing flags, we will eval() flag validation.
- # This requires errors to die().
- # XXX: this can go away as soon as flag validation is able to
- # fail without dying.
- #
- # 2. Flag::validate() should not detect any reference to existing flags
- # when creating a new attachment. Setting the third param to -1 will
- # force this function to check this point.
- my $error_mode_cache = Bugzilla->error_mode;
- Bugzilla->error_mode(ERROR_MODE_DIE);
- eval {
- Bugzilla::Flag::validate($bug->bug_id, -1, SKIP_REQUESTEE_ON_ERROR);
- Bugzilla::Flag->process($bug, $attachment, $timestamp, $hr_vars);
- };
- Bugzilla->error_mode($error_mode_cache);
- if ($@) {
- $hr_vars->{'message'} = 'flag_creation_failed';
- $hr_vars->{'flag_creation_error'} = $@;
- }
- # Return the new attachment object.
- return $attachment;
- }
- =pod
- =item C<remove_from_db()>
- Description: removes an attachment from the DB.
- Params: none
- Returns: nothing
- =back
- =cut
- sub remove_from_db {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- $dbh->bz_start_transaction();
- $dbh->do('DELETE FROM flags WHERE attach_id = ?', undef, $self->id);
- $dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
- $dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isurl = ?, isobsolete = ?
- WHERE attach_id = ?', undef, ('text/plain', 0, 0, 1, $self->id));
- $dbh->bz_commit_transaction();
- }
- 1;
|