aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Attachment.pm')
-rw-r--r--Bugzilla/Attachment.pm997
1 files changed, 514 insertions, 483 deletions
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm
index 33183797b..26f768c2f 100644
--- a/Bugzilla/Attachment.pm
+++ b/Bugzilla/Attachment.pm
@@ -57,55 +57,51 @@ use parent qw(Bugzilla::Object);
use constant DB_TABLE => 'attachments';
use constant ID_FIELD => 'attach_id';
use constant LIST_ORDER => ID_FIELD;
+
# Attachments are tracked in bugs_activity.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant DB_COLUMNS => qw(
- attach_id
- bug_id
- creation_ts
- description
- filename
- isobsolete
- ispatch
- isprivate
- mimetype
- modification_time
- submitter_id
+ attach_id
+ bug_id
+ creation_ts
+ description
+ filename
+ isobsolete
+ ispatch
+ isprivate
+ mimetype
+ modification_time
+ submitter_id
);
-use constant REQUIRED_FIELD_MAP => {
- bug_id => 'bug',
-};
+use constant REQUIRED_FIELD_MAP => {bug_id => 'bug',};
use constant EXTRA_REQUIRED_FIELDS => qw(data);
use constant UPDATE_COLUMNS => qw(
- description
- filename
- isobsolete
- ispatch
- isprivate
- mimetype
+ description
+ filename
+ isobsolete
+ ispatch
+ isprivate
+ mimetype
);
use constant VALIDATORS => {
- bug => \&_check_bug,
- description => \&_check_description,
- filename => \&_check_filename,
- ispatch => \&Bugzilla::Object::check_boolean,
- isprivate => \&_check_is_private,
- mimetype => \&_check_content_type,
+ bug => \&_check_bug,
+ description => \&_check_description,
+ filename => \&_check_filename,
+ ispatch => \&Bugzilla::Object::check_boolean,
+ isprivate => \&_check_is_private,
+ mimetype => \&_check_content_type,
};
-use constant VALIDATOR_DEPENDENCIES => {
- content_type => ['ispatch'],
- mimetype => ['ispatch'],
-};
+use constant VALIDATOR_DEPENDENCIES =>
+ {content_type => ['ispatch'], mimetype => ['ispatch'],};
-use constant UPDATE_VALIDATORS => {
- isobsolete => \&Bugzilla::Object::check_boolean,
-};
+use constant UPDATE_VALIDATORS =>
+ {isobsolete => \&Bugzilla::Object::check_boolean,};
###############################
#### Accessors ######
@@ -126,7 +122,7 @@ the ID of the bug to which the attachment is attached
=cut
sub bug_id {
- return $_[0]->{bug_id};
+ return $_[0]->{bug_id};
}
=over
@@ -140,8 +136,8 @@ the bug object to which the attachment is attached
=cut
sub bug {
- require Bugzilla::Bug;
- return $_[0]->{bug} //= Bugzilla::Bug->new({ id => $_[0]->bug_id, cache => 1 });
+ require Bugzilla::Bug;
+ return $_[0]->{bug} //= Bugzilla::Bug->new({id => $_[0]->bug_id, cache => 1});
}
=over
@@ -155,7 +151,7 @@ user-provided text describing the attachment
=cut
sub description {
- return $_[0]->{description};
+ return $_[0]->{description};
}
=over
@@ -169,7 +165,7 @@ the attachment's MIME media type
=cut
sub contenttype {
- return $_[0]->{mimetype};
+ return $_[0]->{mimetype};
}
=over
@@ -183,8 +179,8 @@ the user who attached the attachment
=cut
sub attacher {
- return $_[0]->{attacher}
- //= new Bugzilla::User({ id => $_[0]->{submitter_id}, cache => 1 });
+ return $_[0]->{attacher}
+ //= new Bugzilla::User({id => $_[0]->{submitter_id}, cache => 1});
}
=over
@@ -198,7 +194,7 @@ the date and time on which the attacher attached the attachment
=cut
sub attached {
- return $_[0]->{creation_ts};
+ return $_[0]->{creation_ts};
}
=over
@@ -212,7 +208,7 @@ the date and time on which the attachment was last modified.
=cut
sub modification_time {
- return $_[0]->{modification_time};
+ return $_[0]->{modification_time};
}
=over
@@ -226,7 +222,7 @@ the name of the file the attacher attached
=cut
sub filename {
- return $_[0]->{filename};
+ return $_[0]->{filename};
}
=over
@@ -240,7 +236,7 @@ whether or not the attachment is a patch
=cut
sub ispatch {
- return $_[0]->{ispatch};
+ return $_[0]->{ispatch};
}
=over
@@ -254,7 +250,7 @@ whether or not the attachment is obsolete
=cut
sub isobsolete {
- return $_[0]->{isobsolete};
+ return $_[0]->{isobsolete};
}
=over
@@ -268,7 +264,7 @@ whether or not the attachment is private
=cut
sub isprivate {
- return $_[0]->{isprivate};
+ return $_[0]->{isprivate};
}
=over
@@ -285,23 +281,24 @@ matches, because this will return a value even if it's matched by the generic
=cut
sub is_viewable {
- my $contenttype = $_[0]->contenttype;
- my $cgi = Bugzilla->cgi;
+ my $contenttype = $_[0]->contenttype;
+ my $cgi = Bugzilla->cgi;
- # We assume we can view all text and image types.
- return 1 if ($contenttype =~ /^(text|image)\//);
+ # 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\//));
+ # 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(,.*)?$/);
+ # 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;
+ return 0;
}
=over
@@ -315,28 +312,29 @@ the content of the attachment
=cut
sub data {
- my $self = shift;
- return $self->{data} if exists $self->{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
+ # 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);
- }
+ 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};
+ return $self->{data};
}
=over
@@ -358,37 +356,37 @@ the length (in bytes) of the attachment content
# LENGTH() function or stat()ing the file instead. I've left it in for now.
sub datasize {
- my $self = shift;
- return $self->{datasize} if defined $self->{datasize};
+ my $self = shift;
+ return $self->{datasize} if defined $self->{datasize};
- # If we have already retrieved the data, return its size.
- return length($self->{data}) if exists $self->{data};
+ # 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)
+ $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);
- }
+ 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};
+ return $self->{datasize};
}
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;
+ my $self = shift;
+ my $hash = ($self->id % 100) + 100;
+ $hash =~ s/.*(\d\d)$/group.$1/;
+ return bz_locations()->{'attachdir'} . "/$hash/attachment." . $self->id;
}
=over
@@ -402,8 +400,9 @@ flags that have been set on the attachment
=cut
sub flags {
- # Don't cache it as it must be in sync with ->flag_types.
- return $_[0]->{flags} = [map { @{$_->{flags}} } @{$_[0]->flag_types}];
+
+ # Don't cache it as it must be in sync with ->flag_types.
+ return $_[0]->{flags} = [map { @{$_->{flags}} } @{$_[0]->flag_types}];
}
=over
@@ -418,202 +417,216 @@ already set, grouped by flag type.
=cut
sub flag_types {
- my $self = shift;
- return $self->{flag_types} if exists $self->{flag_types};
+ my $self = shift;
+ return $self->{flag_types} if exists $self->{flag_types};
- my $vars = { target_type => 'attachment',
- product_id => $self->bug->product_id,
- component_id => $self->bug->component_id,
- attach_id => $self->id };
+ my $vars = {
+ target_type => 'attachment',
+ product_id => $self->bug->product_id,
+ component_id => $self->bug->component_id,
+ attach_id => $self->id
+ };
- return $self->{flag_types} = Bugzilla::Flag->_flag_types($vars);
+ return $self->{flag_types} = Bugzilla::Flag->_flag_types($vars);
}
###############################
#### Validators ######
###############################
-sub set_content_type { $_[0]->set('mimetype', $_[1]); }
+sub set_content_type { $_[0]->set('mimetype', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
-sub set_filename { $_[0]->set('filename', $_[1]); }
-sub set_is_patch { $_[0]->set('ispatch', $_[1]); }
-sub set_is_private { $_[0]->set('isprivate', $_[1]); }
-
-sub set_is_obsolete {
- my ($self, $obsolete) = @_;
-
- my $old = $self->isobsolete;
- $self->set('isobsolete', $obsolete);
- my $new = $self->isobsolete;
-
- # If the attachment is being marked as obsolete, cancel pending requests.
- if ($new && $old != $new) {
- my @requests = grep { $_->status eq '?' } @{$self->flags};
- return unless scalar @requests;
-
- my %flag_ids = map { $_->id => 1 } @requests;
- foreach my $flagtype (@{$self->flag_types}) {
- @{$flagtype->{flags}} = grep { !$flag_ids{$_->id} } @{$flagtype->{flags}};
- }
+sub set_filename { $_[0]->set('filename', $_[1]); }
+sub set_is_patch { $_[0]->set('ispatch', $_[1]); }
+sub set_is_private { $_[0]->set('isprivate', $_[1]); }
+
+sub set_is_obsolete {
+ my ($self, $obsolete) = @_;
+
+ my $old = $self->isobsolete;
+ $self->set('isobsolete', $obsolete);
+ my $new = $self->isobsolete;
+
+ # If the attachment is being marked as obsolete, cancel pending requests.
+ if ($new && $old != $new) {
+ my @requests = grep { $_->status eq '?' } @{$self->flags};
+ return unless scalar @requests;
+
+ my %flag_ids = map { $_->id => 1 } @requests;
+ foreach my $flagtype (@{$self->flag_types}) {
+ @{$flagtype->{flags}} = grep { !$flag_ids{$_->id} } @{$flagtype->{flags}};
}
+ }
}
sub set_flags {
- my ($self, $flags, $new_flags) = @_;
+ my ($self, $flags, $new_flags) = @_;
- Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
+ Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
}
sub _check_bug {
- my ($invocant, $bug) = @_;
- my $user = Bugzilla->user;
+ my ($invocant, $bug) = @_;
+ my $user = Bugzilla->user;
- $bug = ref $invocant ? $invocant->bug : $bug;
+ $bug = ref $invocant ? $invocant->bug : $bug;
- $bug || ThrowCodeError('param_required',
- { function => "$invocant->create", param => 'bug' });
+ $bug
+ || ThrowCodeError('param_required',
+ {function => "$invocant->create", param => 'bug'});
- ($user->can_see_bug($bug->id) && $user->can_edit_product($bug->product_id))
- || ThrowUserError("illegal_attachment_edit_bug", { bug_id => $bug->id });
+ ($user->can_see_bug($bug->id) && $user->can_edit_product($bug->product_id))
+ || ThrowUserError("illegal_attachment_edit_bug", {bug_id => $bug->id});
- return $bug;
+ return $bug;
}
sub _check_content_type {
- my ($invocant, $content_type, undef, $params) = @_;
-
- my $is_patch = ref($invocant) ? $invocant->ispatch : $params->{ispatch};
- $content_type = 'text/plain' if $is_patch;
- $content_type = clean_text($content_type);
- # The subsets below cover all existing MIME types and charsets registered by IANA.
- # (MIME type: RFC 2045 section 5.1; charset: RFC 2278 section 3.3)
- my $legal_types = join('|', LEGAL_CONTENT_TYPES);
- if (!$content_type
- || $content_type !~ /^($legal_types)\/[a-z0-9_\-\+\.]+(;\s*charset=[a-z0-9_\-\+]+)?$/i)
- {
- ThrowUserError("invalid_content_type", { contenttype => $content_type });
+ my ($invocant, $content_type, undef, $params) = @_;
+
+ my $is_patch = ref($invocant) ? $invocant->ispatch : $params->{ispatch};
+ $content_type = 'text/plain' if $is_patch;
+ $content_type = clean_text($content_type);
+
+# The subsets below cover all existing MIME types and charsets registered by IANA.
+# (MIME type: RFC 2045 section 5.1; charset: RFC 2278 section 3.3)
+ my $legal_types = join('|', LEGAL_CONTENT_TYPES);
+ if (!$content_type
+ || $content_type
+ !~ /^($legal_types)\/[a-z0-9_\-\+\.]+(;\s*charset=[a-z0-9_\-\+]+)?$/i)
+ {
+ ThrowUserError("invalid_content_type", {contenttype => $content_type});
+ }
+ trick_taint($content_type);
+
+ # $ENV{HOME} must be defined when using File::MimeInfo::Magic,
+ # see https://rt.cpan.org/Public/Bug/Display.html?id=41744.
+ local $ENV{HOME} = $ENV{HOME} || File::Spec->rootdir();
+
+ # If we have autodetected application/octet-stream from the Content-Type
+ # header, let's have a better go using a sniffer if available.
+ if ( defined Bugzilla->input_params->{contenttypemethod}
+ && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
+ && $content_type eq 'application/octet-stream'
+ && Bugzilla->feature('typesniffer'))
+ {
+ import File::MimeInfo::Magic qw(mimetype);
+ require IO::Scalar;
+
+ # data is either a filehandle, or the data itself.
+ my $fh = $params->{data};
+ if (!ref($fh)) {
+ $fh = new IO::Scalar \$fh;
}
- trick_taint($content_type);
-
- # $ENV{HOME} must be defined when using File::MimeInfo::Magic,
- # see https://rt.cpan.org/Public/Bug/Display.html?id=41744.
- local $ENV{HOME} = $ENV{HOME} || File::Spec->rootdir();
-
- # If we have autodetected application/octet-stream from the Content-Type
- # header, let's have a better go using a sniffer if available.
- if (defined Bugzilla->input_params->{contenttypemethod}
- && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
- && $content_type eq 'application/octet-stream'
- && Bugzilla->feature('typesniffer'))
- {
- import File::MimeInfo::Magic qw(mimetype);
- require IO::Scalar;
-
- # data is either a filehandle, or the data itself.
- my $fh = $params->{data};
- if (!ref($fh)) {
- $fh = new IO::Scalar \$fh;
- }
- elsif (!$fh->isa('IO::Handle')) {
- # CGI.pm sends us an Fh that isn't actually an IO::Handle, but
- # has a method for getting an actual handle out of it.
- $fh = $fh->handle;
- # ->handle returns an literal IO::Handle, even though the
- # underlying object is a file. So we rebless it to be a proper
- # IO::File object so that we can call ->seek on it and so on.
- # Just in case CGI.pm fixes this some day, we check ->isa first.
- if (!$fh->isa('IO::File')) {
- bless $fh, 'IO::File';
- }
- }
-
- my $mimetype = mimetype($fh);
- $fh->seek(0, 0);
- $content_type = $mimetype if $mimetype;
+ elsif (!$fh->isa('IO::Handle')) {
+
+ # CGI.pm sends us an Fh that isn't actually an IO::Handle, but
+ # has a method for getting an actual handle out of it.
+ $fh = $fh->handle;
+
+ # ->handle returns an literal IO::Handle, even though the
+ # underlying object is a file. So we rebless it to be a proper
+ # IO::File object so that we can call ->seek on it and so on.
+ # Just in case CGI.pm fixes this some day, we check ->isa first.
+ if (!$fh->isa('IO::File')) {
+ bless $fh, 'IO::File';
+ }
}
- # Make sure patches are viewable in the browser
- if (!ref($invocant)
- && defined Bugzilla->input_params->{contenttypemethod}
- && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
- && $content_type =~ m{text/x-(?:diff|patch)})
- {
- $params->{ispatch} = 1;
- $content_type = 'text/plain';
- }
-
- return $content_type;
+ my $mimetype = mimetype($fh);
+ $fh->seek(0, 0);
+ $content_type = $mimetype if $mimetype;
+ }
+
+ # Make sure patches are viewable in the browser
+ if (!ref($invocant)
+ && defined Bugzilla->input_params->{contenttypemethod}
+ && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
+ && $content_type =~ m{text/x-(?:diff|patch)})
+ {
+ $params->{ispatch} = 1;
+ $content_type = 'text/plain';
+ }
+
+ return $content_type;
}
sub _check_data {
- my ($invocant, $params) = @_;
+ my ($invocant, $params) = @_;
- my $data = $params->{data};
- $params->{filesize} = ref $data ? -s $data : length($data);
+ my $data = $params->{data};
+ $params->{filesize} = ref $data ? -s $data : length($data);
- Bugzilla::Hook::process('attachment_process_data', { data => \$data,
- attributes => $params });
+ Bugzilla::Hook::process('attachment_process_data',
+ {data => \$data, attributes => $params});
- $params->{filesize} || ThrowUserError('zero_length_file');
- # Make sure the attachment does not exceed the maximum permitted size.
- my $max_size = max(Bugzilla->params->{'maxlocalattachment'} * 1048576,
- Bugzilla->params->{'maxattachmentsize'} * 1024);
+ $params->{filesize} || ThrowUserError('zero_length_file');
- if ($params->{filesize} > $max_size) {
- my $vars = { filesize => sprintf("%.0f", $params->{filesize}/1024) };
- ThrowUserError('file_too_large', $vars);
- }
- return $data;
+ # Make sure the attachment does not exceed the maximum permitted size.
+ my $max_size = max(
+ Bugzilla->params->{'maxlocalattachment'} * 1048576,
+ Bugzilla->params->{'maxattachmentsize'} * 1024
+ );
+
+ if ($params->{filesize} > $max_size) {
+ my $vars = {filesize => sprintf("%.0f", $params->{filesize} / 1024)};
+ ThrowUserError('file_too_large', $vars);
+ }
+ return $data;
}
sub _check_description {
- my ($invocant, $description) = @_;
+ my ($invocant, $description) = @_;
- $description = trim($description);
- $description || ThrowUserError('missing_attachment_description');
- return $description;
+ $description = trim($description);
+ $description || ThrowUserError('missing_attachment_description');
+ return $description;
}
sub _check_filename {
- my ($invocant, $filename) = @_;
-
- $filename = clean_text($filename);
- if (!$filename) {
- if (ref $invocant) {
- ThrowUserError('filename_not_specified');
- }
- else {
- ThrowUserError('file_not_specified');
- }
- }
+ my ($invocant, $filename) = @_;
- # 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 MAX_ATTACH_FILENAME_LENGTH characters, counting
- # from the end of the string to make sure we keep the filename extension.
- $filename = substr($filename,
- -&MAX_ATTACH_FILENAME_LENGTH,
- MAX_ATTACH_FILENAME_LENGTH);
- trick_taint($filename);
-
- return $filename;
+ $filename = clean_text($filename);
+ if (!$filename) {
+ if (ref $invocant) {
+ ThrowUserError('filename_not_specified');
+ }
+ else {
+ ThrowUserError('file_not_specified');
+ }
+ }
+
+ # 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 MAX_ATTACH_FILENAME_LENGTH characters, counting
+ # from the end of the string to make sure we keep the filename extension.
+ $filename
+ = substr($filename, -&MAX_ATTACH_FILENAME_LENGTH, MAX_ATTACH_FILENAME_LENGTH);
+ trick_taint($filename);
+
+ return $filename;
}
sub _check_is_private {
- my ($invocant, $is_private) = @_;
-
- $is_private = $is_private ? 1 : 0;
- if (((!ref $invocant && $is_private)
- || (ref $invocant && $invocant->isprivate != $is_private))
- && !Bugzilla->user->is_insider) {
- ThrowUserError('user_not_insider');
- }
- return $is_private;
+ my ($invocant, $is_private) = @_;
+
+ $is_private = $is_private ? 1 : 0;
+ if (
+ (
+ (!ref $invocant && $is_private)
+ || (ref $invocant && $invocant->isprivate != $is_private)
+ )
+ && !Bugzilla->user->is_insider
+ )
+ {
+ ThrowUserError('user_not_insider');
+ }
+ return $is_private;
}
=pod
@@ -635,69 +648,74 @@ Returns: a reference to an array of attachment objects.
=cut
sub get_attachments_by_bug {
- my ($class, $bug, $vars) = @_;
- 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 ($class, $bug, $vars) = @_;
+ 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->new_from_list($attach_ids);
+ $_->{bug} = $bug foreach @$attachments;
+
+ # To avoid $attachment->flags and $attachment->flag_types running SQL queries
+ # themselves for each attachment listed here, we collect all the data at once and
+ # populate $attachment->{flag_types} ourselves. We also load all attachers and
+ # datasizes at once for the same reason.
+ if ($vars->{preload}) {
+
+ # Preload flag types and flags
+ my $vars = {
+ target_type => 'attachment',
+ product_id => $bug->product_id,
+ component_id => $bug->component_id,
+ attach_id => $attach_ids
+ };
+ my $flag_types = Bugzilla::Flag->_flag_types($vars);
+
+ foreach my $attachment (@$attachments) {
+ $attachment->{flag_types} = [];
+ my $new_types = dclone($flag_types);
+ foreach my $new_type (@$new_types) {
+ $new_type->{flags}
+ = [grep($_->attach_id == $attachment->id, @{$new_type->{flags}})];
+ push(@{$attachment->{flag_types}}, $new_type);
+ }
}
- my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
- WHERE bug_id = ? $and_restriction",
- undef, @values);
-
- my $attachments = Bugzilla::Attachment->new_from_list($attach_ids);
- $_->{bug} = $bug foreach @$attachments;
-
- # To avoid $attachment->flags and $attachment->flag_types running SQL queries
- # themselves for each attachment listed here, we collect all the data at once and
- # populate $attachment->{flag_types} ourselves. We also load all attachers and
- # datasizes at once for the same reason.
- if ($vars->{preload}) {
- # Preload flag types and flags
- my $vars = { target_type => 'attachment',
- product_id => $bug->product_id,
- component_id => $bug->component_id,
- attach_id => $attach_ids };
- my $flag_types = Bugzilla::Flag->_flag_types($vars);
-
- foreach my $attachment (@$attachments) {
- $attachment->{flag_types} = [];
- my $new_types = dclone($flag_types);
- foreach my $new_type (@$new_types) {
- $new_type->{flags} = [ grep($_->attach_id == $attachment->id,
- @{ $new_type->{flags} }) ];
- push(@{ $attachment->{flag_types} }, $new_type);
- }
- }
-
- # Preload attachers.
- my %user_ids = map { $_->{submitter_id} => 1 } @$attachments;
- my $users = Bugzilla::User->new_from_list([keys %user_ids]);
- my %user_map = map { $_->id => $_ } @$users;
- foreach my $attachment (@$attachments) {
- $attachment->{attacher} = $user_map{$attachment->{submitter_id}};
- }
-
- # Preload datasizes.
- my $sizes =
- $dbh->selectall_hashref('SELECT attach_id, LENGTH(thedata) AS datasize
+ # Preload attachers.
+ my %user_ids = map { $_->{submitter_id} => 1 } @$attachments;
+ my $users = Bugzilla::User->new_from_list([keys %user_ids]);
+ my %user_map = map { $_->id => $_ } @$users;
+ foreach my $attachment (@$attachments) {
+ $attachment->{attacher} = $user_map{$attachment->{submitter_id}};
+ }
+
+ # Preload datasizes.
+ my $sizes = $dbh->selectall_hashref(
+ 'SELECT attach_id, LENGTH(thedata) AS datasize
FROM attachments LEFT JOIN attach_data ON attach_id = id
- WHERE bug_id = ?',
- 'attach_id', undef, $bug->id);
+ WHERE bug_id = ?', 'attach_id', undef, $bug->id
+ );
- # Force the size of attachments not in the DB to be recalculated.
- $_->{datasize} = $sizes->{$_->id}->{datasize} || undef foreach @$attachments;
- }
+ # Force the size of attachments not in the DB to be recalculated.
+ $_->{datasize} = $sizes->{$_->id}->{datasize} || undef foreach @$attachments;
+ }
- return $attachments;
+ return $attachments;
}
=pod
@@ -716,13 +734,15 @@ Returns: 1 on success, 0 otherwise.
=cut
sub validate_can_edit {
- my $attachment = shift;
- my $user = Bugzilla->user;
-
- # The submitter can edit their attachments.
- return ($attachment->attacher->id == $user->id
- || ((!$attachment->isprivate || $user->is_insider)
- && $user->in_group('editbugs', $attachment->bug->product_id))) ? 1 : 0;
+ my $attachment = shift;
+ my $user = Bugzilla->user;
+
+ # The submitter can edit their attachments.
+ return (
+ $attachment->attacher->id == $user->id
+ || ((!$attachment->isprivate || $user->is_insider)
+ && $user->in_group('editbugs', $attachment->bug->product_id))
+ ) ? 1 : 0;
}
=item C<validate_obsolete($bug, $attach_ids)>
@@ -741,37 +761,36 @@ Returns: The list of attachment objects to mark as obsolete.
=cut
sub validate_obsolete {
- my ($class, $bug, $list) = @_;
+ my ($class, $bug, $list) = @_;
- # 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 (@$list) {
- my $vars = {};
- $vars->{'attach_id'} = $attachid;
+ # 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 (@$list) {
+ my $vars = {};
+ $vars->{'attach_id'} = $attachid;
- detaint_natural($attachid)
- || ThrowUserError('invalid_attach_id', $vars);
+ detaint_natural($attachid) || ThrowUserError('invalid_attach_id', $vars);
- # Make sure the attachment exists in the database.
- my $attachment = new Bugzilla::Attachment($attachid)
- || ThrowUserError('invalid_attach_id', $vars);
+ # Make sure the attachment exists in the database.
+ my $attachment = new Bugzilla::Attachment($attachid)
+ || ThrowUserError('invalid_attach_id', $vars);
- # Check that the user can view and edit this attachment.
- $attachment->validate_can_edit
- || ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
+ # Check that the user can view and edit this attachment.
+ $attachment->validate_can_edit
+ || ThrowUserError('illegal_attachment_edit', {attach_id => $attachment->id});
- if ($attachment->bug_id != $bug->bug_id) {
- $vars->{'my_bug_id'} = $bug->bug_id;
- ThrowUserError('mismatched_bug_ids_on_obsolete', $vars);
- }
+ if ($attachment->bug_id != $bug->bug_id) {
+ $vars->{'my_bug_id'} = $bug->bug_id;
+ ThrowUserError('mismatched_bug_ids_on_obsolete', $vars);
+ }
- next if $attachment->isobsolete;
+ next if $attachment->isobsolete;
- push(@obsolete_attachments, $attachment);
- }
- return @obsolete_attachments;
+ push(@obsolete_attachments, $attachment);
+ }
+ return @obsolete_attachments;
}
###############################
@@ -806,112 +825,119 @@ Returns: The new attachment object.
=cut
sub create {
- my $class = shift;
- my $dbh = Bugzilla->dbh;
-
- $class->check_required_create_fields(@_);
- my $params = $class->run_create_validators(@_);
-
- # Extract everything which is not a valid column name.
- my $bug = delete $params->{bug};
- $params->{bug_id} = $bug->id;
- my $data = delete $params->{data};
- my $size = delete $params->{filesize};
-
- my $attachment = $class->insert_create_data($params);
- my $attachid = $attachment->id;
-
- # The file is too large to be stored in the DB, so we store it locally.
- if ($size > Bugzilla->params->{'maxattachmentsize'} * 1024) {
- my $attachdir = bz_locations()->{'attachdir'};
- my $hash = ($attachid % 100) + 100;
- $hash =~ s/.*(\d\d)$/group.$1/;
- mkdir "$attachdir/$hash", 0770;
- chmod 0770, "$attachdir/$hash";
- if (ref $data) {
- copy($data, "$attachdir/$hash/attachment.$attachid");
- close $data;
- }
- else {
- open(AH, '>', "$attachdir/$hash/attachment.$attachid");
- binmode AH;
- print AH $data;
- close AH;
- }
- $data = ''; # Will be stored in the DB.
- }
- # If we have a filehandle, we need its content to store it in the DB.
- elsif (ref $data) {
- local $/;
- # Store the content in a temp variable while we close the FH.
- my $tmp = <$data>;
- close $data;
- $data = $tmp;
- }
+ my $class = shift;
+ my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare("INSERT INTO attach_data
- (id, thedata) VALUES ($attachid, ?)");
+ $class->check_required_create_fields(@_);
+ my $params = $class->run_create_validators(@_);
- trick_taint($data);
- $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
- $sth->execute();
+ # Extract everything which is not a valid column name.
+ my $bug = delete $params->{bug};
+ $params->{bug_id} = $bug->id;
+ my $data = delete $params->{data};
+ my $size = delete $params->{filesize};
- $attachment->{bug} = $bug;
+ my $attachment = $class->insert_create_data($params);
+ my $attachid = $attachment->id;
- # Return the new attachment object.
- return $attachment;
-}
+ # The file is too large to be stored in the DB, so we store it locally.
+ if ($size > Bugzilla->params->{'maxattachmentsize'} * 1024) {
+ my $attachdir = bz_locations()->{'attachdir'};
+ my $hash = ($attachid % 100) + 100;
+ $hash =~ s/.*(\d\d)$/group.$1/;
+ mkdir "$attachdir/$hash", 0770;
+ chmod 0770, "$attachdir/$hash";
+ if (ref $data) {
+ copy($data, "$attachdir/$hash/attachment.$attachid");
+ close $data;
+ }
+ else {
+ open(AH, '>', "$attachdir/$hash/attachment.$attachid");
+ binmode AH;
+ print AH $data;
+ close AH;
+ }
+ $data = ''; # Will be stored in the DB.
+ }
-sub run_create_validators {
- my ($class, $params) = @_;
+ # If we have a filehandle, we need its content to store it in the DB.
+ elsif (ref $data) {
+ local $/;
+
+ # Store the content in a temp variable while we close the FH.
+ my $tmp = <$data>;
+ close $data;
+ $data = $tmp;
+ }
- $params->{submitter_id} = Bugzilla->user->id || ThrowUserError('invalid_user');
+ my $sth = $dbh->prepare(
+ "INSERT INTO attach_data
+ (id, thedata) VALUES ($attachid, ?)"
+ );
- # Let's validate the attachment content first as it may
- # alter some other attachment attributes.
- $params->{data} = $class->_check_data($params);
- $params = $class->SUPER::run_create_validators($params);
+ trick_taint($data);
+ $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
+ $sth->execute();
- $params->{creation_ts} ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
- $params->{modification_time} = $params->{creation_ts};
+ $attachment->{bug} = $bug;
- return $params;
+ # Return the new attachment object.
+ return $attachment;
}
-sub update {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- my $timestamp = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+sub run_create_validators {
+ my ($class, $params) = @_;
- my ($changes, $old_self) = $self->SUPER::update(@_);
+ $params->{submitter_id} = Bugzilla->user->id || ThrowUserError('invalid_user');
- my ($removed, $added) = Bugzilla::Flag->update_flags($self, $old_self, $timestamp);
- if ($removed || $added) {
- $changes->{'flagtypes.name'} = [$removed, $added];
- }
+ # Let's validate the attachment content first as it may
+ # alter some other attachment attributes.
+ $params->{data} = $class->_check_data($params);
+ $params = $class->SUPER::run_create_validators($params);
- # Record changes in the activity table.
- require Bugzilla::Bug;
- foreach my $field (keys %$changes) {
- my $change = $changes->{$field};
- $field = "attachments.$field" unless $field eq "flagtypes.name";
- Bugzilla::Bug::LogActivityEntry($self->bug_id, $field, $change->[0],
- $change->[1], $user->id, $timestamp, undef, $self->id);
- }
+ $params->{creation_ts}
+ ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $params->{modification_time} = $params->{creation_ts};
- if (scalar(keys %$changes)) {
- $dbh->do('UPDATE attachments SET modification_time = ? WHERE attach_id = ?',
- undef, ($timestamp, $self->id));
- $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
- undef, ($timestamp, $self->bug_id));
- $self->{modification_time} = $timestamp;
- # because we updated the attachments table after SUPER::update(), we
- # need to ensure the cache is flushed.
- Bugzilla->memcached->clear({ table => 'attachments', id => $self->id });
- }
+ return $params;
+}
- return $changes;
+sub update {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my $timestamp = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ my ($changes, $old_self) = $self->SUPER::update(@_);
+
+ my ($removed, $added)
+ = Bugzilla::Flag->update_flags($self, $old_self, $timestamp);
+ if ($removed || $added) {
+ $changes->{'flagtypes.name'} = [$removed, $added];
+ }
+
+ # Record changes in the activity table.
+ require Bugzilla::Bug;
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
+ $field = "attachments.$field" unless $field eq "flagtypes.name";
+ Bugzilla::Bug::LogActivityEntry($self->bug_id, $field, $change->[0],
+ $change->[1], $user->id, $timestamp, undef, $self->id);
+ }
+
+ if (scalar(keys %$changes)) {
+ $dbh->do('UPDATE attachments SET modification_time = ? WHERE attach_id = ?',
+ undef, ($timestamp, $self->id));
+ $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+ undef, ($timestamp, $self->bug_id));
+ $self->{modification_time} = $timestamp;
+
+ # because we updated the attachments table after SUPER::update(), we
+ # need to ensure the cache is flushed.
+ Bugzilla->memcached->clear({table => 'attachments', id => $self->id});
+ }
+
+ return $changes;
}
=pod
@@ -929,30 +955,33 @@ Returns: nothing
=cut
sub remove_from_db {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction();
- my $flag_ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM flags WHERE attach_id = ?', undef, $self->id);
- $dbh->do('DELETE FROM flags WHERE ' . $dbh->sql_in('id', $flag_ids))
- if @$flag_ids;
- $dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
- $dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isobsolete = ?
- WHERE attach_id = ?', undef, ('text/plain', 0, 1, $self->id));
- $dbh->bz_commit_transaction();
-
- my $filename = $self->_get_local_filename;
- if (-e $filename) {
- unlink $filename or warn "Couldn't unlink $filename: $!";
- }
-
- # As we don't call SUPER->remove_from_db we need to manually clear
- # memcached here.
- Bugzilla->memcached->clear({ table => 'attachments', id => $self->id });
- foreach my $flag_id (@$flag_ids) {
- Bugzilla->memcached->clear({ table => 'flags', id => $flag_id });
- }
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+ my $flag_ids
+ = $dbh->selectcol_arrayref('SELECT id FROM flags WHERE attach_id = ?',
+ undef, $self->id);
+ $dbh->do('DELETE FROM flags WHERE ' . $dbh->sql_in('id', $flag_ids))
+ if @$flag_ids;
+ $dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
+ $dbh->do(
+ 'UPDATE attachments SET mimetype = ?, ispatch = ?, isobsolete = ?
+ WHERE attach_id = ?', undef, ('text/plain', 0, 1, $self->id)
+ );
+ $dbh->bz_commit_transaction();
+
+ my $filename = $self->_get_local_filename;
+ if (-e $filename) {
+ unlink $filename or warn "Couldn't unlink $filename: $!";
+ }
+
+ # As we don't call SUPER->remove_from_db we need to manually clear
+ # memcached here.
+ Bugzilla->memcached->clear({table => 'attachments', id => $self->id});
+ foreach my $flag_id (@$flag_ids) {
+ Bugzilla->memcached->clear({table => 'flags', id => $flag_id});
+ }
}
###############################
@@ -961,37 +990,39 @@ sub remove_from_db {
# Extract the content type from the attachment form.
sub get_content_type {
- my $cgi = Bugzilla->cgi;
+ my $cgi = Bugzilla->cgi;
- return 'text/plain' if ($cgi->param('ispatch') || $cgi->param('attach_text'));
+ return 'text/plain' if ($cgi->param('ispatch') || $cgi->param('attach_text'));
- my $content_type;
- my $method = $cgi->param('contenttypemethod') || '';
+ my $content_type;
+ my $method = $cgi->param('contenttypemethod') || '';
- if ($method eq 'list') {
- # The user selected a content type from the list, so use their
- # selection.
- $content_type = $cgi->param('contenttypeselection');
- }
- elsif ($method eq 'manual') {
- # The user entered a content type manually, so use their entry.
- $content_type = $cgi->param('contenttypeentry');
- }
- else {
- defined $cgi->upload('data') || ThrowUserError('file_not_specified');
- # The user asked us to auto-detect the content type, so use the type
- # specified in the HTTP request headers.
- $content_type =
- $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
- $content_type || ThrowUserError("missing_content_type");
-
- # Internet Explorer sends image/x-png for PNG images,
- # so convert that to image/png to match other browsers.
- if ($content_type eq 'image/x-png') {
- $content_type = 'image/png';
- }
+ if ($method eq 'list') {
+
+ # The user selected a content type from the list, so use their
+ # selection.
+ $content_type = $cgi->param('contenttypeselection');
+ }
+ elsif ($method eq 'manual') {
+
+ # The user entered a content type manually, so use their entry.
+ $content_type = $cgi->param('contenttypeentry');
+ }
+ else {
+ defined $cgi->upload('data') || ThrowUserError('file_not_specified');
+
+ # The user asked us to auto-detect the content type, so use the type
+ # specified in the HTTP request headers.
+ $content_type = $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
+ $content_type || ThrowUserError("missing_content_type");
+
+ # Internet Explorer sends image/x-png for PNG images,
+ # so convert that to image/png to match other browsers.
+ if ($content_type eq 'image/x-png') {
+ $content_type = 'image/png';
}
- return $content_type;
+ }
+ return $content_type;
}