summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorlpsolit%gmail.com <>2009-04-09 13:37:56 +0200
committerlpsolit%gmail.com <>2009-04-09 13:37:56 +0200
commit719303d9928bf0a727478c32f83a39b015a25136 (patch)
treef1b91f5e441fcba753483298426893d0927154fb /Bugzilla
parent4afa077fc379af8eeec47f3dbd245b7c8fa9ff80 (diff)
downloadbugzilla-719303d9928bf0a727478c32f83a39b015a25136.tar.gz
bugzilla-719303d9928bf0a727478c32f83a39b015a25136.tar.xz
Bug 454251: Implement Bugzilla::Attachment->create() and $attachment->update() - Patch by Frédéric Buclin <LpSolit@gmail.com> a=LpSolit (module owner)
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Attachment.pm635
-rw-r--r--Bugzilla/Constants.pm29
-rw-r--r--Bugzilla/Flag.pm40
3 files changed, 381 insertions, 323 deletions
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm
index 4aa74dcdf..33c761cf1 100644
--- a/Bugzilla/Attachment.pm
+++ b/Bugzilla/Attachment.pm
@@ -86,6 +86,39 @@ sub DB_COLUMNS {
$dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i') . ' AS creation_ts';
}
+use constant REQUIRED_CREATE_FIELDS => qw(
+ bug
+ data
+ description
+ filename
+ mimetype
+);
+
+use constant UPDATE_COLUMNS => qw(
+ description
+ filename
+ isobsolete
+ ispatch
+ isprivate
+ mimetype
+ modification_time
+);
+
+use constant VALIDATORS => {
+ bug => \&_check_bug,
+ description => \&_check_description,
+ isprivate => \&_check_is_private,
+ isurl => \&_check_is_url,
+ store_in_file => \&_check_store_in_file,
+};
+
+use constant UPDATE_VALIDATORS => {
+ filename => \&_check_filename,
+ isobsolete => \&Bugzilla::Object::check_boolean,
+ ispatch => \&Bugzilla::Object::check_boolean,
+ mimetype => \&_check_content_type,
+};
+
###############################
#### Accessors ######
###############################
@@ -123,7 +156,7 @@ sub bug {
my $self = shift;
require Bugzilla::Bug;
- $self->{bug} = Bugzilla::Bug->new($self->bug_id);
+ $self->{bug} ||= Bugzilla::Bug->new($self->bug_id);
return $self->{bug};
}
@@ -393,6 +426,13 @@ sub 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;
+}
+
=over
=item C<flags>
@@ -439,23 +479,121 @@ sub flag_types {
#### Validators ######
###############################
-# Instance methods; no POD documentation here yet because the only ones so far
-# are private.
+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_obsolete { $_[0]->set('isobsolete', $_[1]); }
+sub set_is_patch { $_[0]->set('ispatch', $_[1]); }
+sub set_is_private { $_[0]->set('isprivate', $_[1]); }
-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 _check_bug {
+ my ($invocant, $bug) = @_;
+ my $user = Bugzilla->user;
+
+ $bug = ref $invocant ? $invocant->bug : $bug;
+ ($user->can_see_bug($bug->id) && $user->can_edit_product($bug->product_id))
+ || ThrowUserError("illegal_attachment_edit_bug", { bug_id => $bug->id });
+
+ return $bug;
}
-sub _validate_filename {
- my ($throw_error) = @_;
- my $cgi = Bugzilla->cgi;
- defined $cgi->upload('data')
- || ($throw_error ? ThrowUserError("file_not_specified") : return 0);
+sub _check_content_type {
+ my ($invocant, $content_type) = @_;
+
+ $content_type = 'text/plain' if (ref $invocant && ($invocant->isurl || $invocant->ispatch));
+ my $legal_types = join('|', LEGAL_CONTENT_TYPES);
+ if ($content_type !~ /^($legal_types)\/.+$/) {
+ ThrowUserError("invalid_content_type", { contenttype => $content_type });
+ }
+ trick_taint($content_type);
+
+ return $content_type;
+}
+
+sub _check_data {
+ my ($invocant, $params) = @_;
+
+ my $data;
+ if ($params->{isurl}) {
+ $data = $params->{data};
+ ($data && $data =~ m#^(http|https|ftp)://\S+#)
+ || ThrowUserError('attachment_illegal_url', { url => $data });
+
+ $params->{mimetype} = 'text/plain';
+ $params->{ispatch} = 0;
+ $params->{store_in_file} = 0;
+ }
+ else {
+ if ($params->{store_in_file} || !ref $params->{data}) {
+ # If it's a filehandle, just store it, not the content of the file
+ # itself as the file may be quite large. If it's not a filehandle,
+ # it already contains the content of the file.
+ $data = $params->{data};
+
+ # We don't compress BMP images stored locally, nor do we check
+ # their size. No need to go further.
+ return $data if $params->{store_in_file};
+ }
+ else {
+ # The file will be stored in the DB. We need the content of the file.
+ local $/;
+ my $fh = $params->{data};
+ $data = <$fh>;
+ }
+
+ $data || ThrowUserError('zero_length_file');
+
+ # This should go away, see bug 480986.
+ # 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'}
+ && $params->{mimetype} 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;
+ $params->{mimetype} = 'image/png';
+ # $hr_vars->{'convertedbmp'} = 1;
+ }
+
+ # Make sure the attachment does not exceed the maximum permitted size.
+ my $max_size = Bugzilla->params->{'maxattachmentsize'} * 1024; # Convert from K
+ my $len = length($data);
+ if ($len > $max_size) {
+ my $vars = { filesize => sprintf("%.0f", $len/1024) };
+ if ($params->{ispatch}) {
+ ThrowUserError('patch_too_large', $vars);
+ }
+ else {
+ ThrowUserError('file_too_large', $vars);
+ }
+ }
+ }
+ return $data;
+}
+
+sub _check_description {
+ my ($invocant, $description) = @_;
+
+ $description = trim($description);
+ $description || ThrowUserError('missing_attachment_description');
+ return $description;
+}
+
+sub _check_filename {
+ my ($invocant, $filename, $is_url) = @_;
+
+ $is_url = $invocant->isurl if ref $invocant;
+ # No file is attached, so it has no name.
+ return '' if $is_url;
- my $filename = $cgi->upload('data');
+ $filename = trim($filename);
+ $filename || 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
@@ -467,64 +605,38 @@ sub _validate_filename {
# 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);
+ trick_taint($filename);
return $filename;
}
-sub _validate_data {
- my ($throw_error, $hr_vars) = @_;
- my $cgi = Bugzilla->cgi;
+sub _check_is_private {
+ my ($invocant, $is_private) = @_;
- 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');
+ if (((!ref $invocant && $is_private)
+ || (ref $invocant && $invocant->isprivate != $is_private))
+ && !Bugzilla->user->is_insider) {
+ ThrowUserError('user_not_insider');
}
- my $data;
+ return $is_private ? 1 : 0;
+}
- # 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>;
- }
+sub _check_is_url {
+ my ($invocant, $is_url) = @_;
- $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;
+ if ($is_url && !Bugzilla->params->{'allow_attach_url'}) {
+ ThrowCodeError('attachment_url_disabled');
}
+ return $is_url ? 1 : 0;
+}
- # Make sure the attachment does not exceed the maximum permitted size
- my $maxsize = Bugzilla->params->{'maxattachmentsize'} * 1024; # Convert from K
- 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;
- }
- }
+sub _check_store_in_file {
+ my ($invocant, $store_in_file) = @_;
- return $data || '';
+ if ($store_in_file && !Bugzilla->params->{'maxlocalattachment'}) {
+ ThrowCodeError('attachment_local_storage_disabled');
+ }
+ return $store_in_file ? 1 : 0;
}
=pod
@@ -587,105 +699,6 @@ sub get_attachments_by_bug {
=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.
@@ -709,7 +722,7 @@ sub validate_can_edit {
|| ((!$attachment->isprivate || $user->is_insider)
&& $user->in_group('editbugs', $product_id)));
- # If we come here, then this attachment cannot be seen by the user.
+ # If we come here, then this attachment cannot be edited by the user.
ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
}
@@ -727,14 +740,13 @@ Returns: 1 on success. Else an error is thrown.
=cut
sub validate_obsolete {
- my ($class, $bug) = @_;
- my $cgi = Bugzilla->cgi;
+ 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 ($cgi->param('obsolete')) {
+ foreach my $attachid (@$list) {
my $vars = {};
$vars->{'attach_id'} = $attachid;
@@ -771,192 +783,147 @@ sub validate_obsolete {
=pod
-=item C<create($throw_error, $bug, $user, $timestamp, $hr_vars)>
+=item C<create>
-Description: inserts an attachment from CGI input for the given bug.
+Description: inserts an attachment into the given bug.
-Params: C<$bug> - Bugzilla::Bug object - the bug for which to insert
+Params: takes a hashref with the following keys:
+ 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.
+ C<data> - Either a filehandle pointing to the content of the
+ attachment, or the content of the attachment itself.
+ C<description> - string - describe what the attachment is about.
+ C<filename> - string - the name of the attachment (used by the
+ browser when downloading it). If the attachment is a URL, this
+ parameter has no effect.
+ C<mimetype> - string - a valid MIME type.
+ C<creation_ts> - string (optional) - timestamp of the insert
+ as returned by SELECT NOW().
+ C<ispatch> - boolean (optional, default false) - true if the
+ attachment is a patch.
+ C<isprivate> - boolean (optional, default false) - true if
+ the attachment is private.
+ C<isurl> - boolean (optional, default false) - true if the
+ attachment is a URL pointing to some external ressource.
+ C<store_in_file> - boolean (optional, default false) - true
+ if the attachment must be stored in data/attachments/ instead
+ of in the DB.
+
+Returns: The new attachment object.
=cut
-# FIXME: needs to follow the way Object->create() works.
sub create {
- my ($class, $throw_error, $bug, $user, $timestamp, $hr_vars) = @_;
-
- my $cgi = Bugzilla->cgi;
+ my $class = shift;
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);
+ $class->check_required_create_fields(@_);
+ my $params = $class->run_create_validators(@_);
- $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';
- }
+ # Extract everything which is not a valid column name.
+ my $bug = delete $params->{bug};
+ $params->{bug_id} = $bug->id;
+ my $fh = delete $params->{data};
+ my $store_in_file = delete $params->{store_in_file};
- # 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');
+ my $attachment = $class->insert_create_data($params);
+ my $attachid = $attachment->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, ?)");
+ my $sth = $dbh->prepare("INSERT INTO attach_data
+ (id, thedata) VALUES ($attachid, ?)");
+
+ my $data = $store_in_file ? "" : $fh;
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')) {
+ if ($store_in_file) {
+ my $limit = Bugzilla->params->{"maxlocalattachment"} * 1048576;
+ # If $fh is not a filehandle, we already know its size.
+ ThrowUserError("local_file_too_large") if (!ref($fh) && length($fh) > $limit);
+
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;
+ if (ref $fh) {
+ my $sizecount = 0;
+ while (<$fh>) {
+ print AH $_;
+ $sizecount += length($_);
+ if ($sizecount > $limit) {
+ close AH;
+ close $fh;
+ unlink "$attachdir/$hash/attachment.$attachid";
+ ThrowUserError("local_file_too_large");
+ }
}
+ close $fh;
+ }
+ else {
+ print AH $fh;
}
close AH;
- close $fh;
}
- # Make existing attachments obsolete.
- my $fieldid = get_field_id('attachments.isobsolete');
+ # Return the new attachment object.
+ return $attachment;
+}
+
+sub run_create_validators {
+ my $class = shift;
+ my $params = $class->SUPER::run_create_validators(@_);
+
+ $params->{data} = $class->_check_data($params);
+ # We couldn't call these checkers earlier as _check_data() could alter values.
+ $params->{ispatch} = $params->{ispatch} ? 1 : 0;
+ $params->{filename} = $class->_check_filename($params->{filename}, $params->{isurl});
+ $params->{mimetype} = $class->_check_content_type($params->{mimetype});
+ $params->{creation_ts} ||= Bugzilla->dbh->selectrow_array('SELECT NOW()');
+ $params->{modification_time} = $params->{creation_ts};
+ $params->{submitter_id} = Bugzilla->user->id || ThrowCodeError('invalid_user');
- 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);
+ return $params;
+}
+
+sub update {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my $bug = $self->bug;
- $dbh->do('UPDATE attachments SET isobsolete = 1, modification_time = ?
- WHERE attach_id = ?',
- undef, ($timestamp, $obsolete_attachment->id));
+ my $timestamp = shift || $dbh->selectrow_array('SELECT NOW()');
+ $self->{modification_time} = $timestamp;
- $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 ($changes, $old_self) = $self->SUPER::update(@_);
+ # Ignore this change.
+ delete $changes->{modification_time};
+
+ # 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 (?, ?, ?, ?, ?, ?, ?)');
+
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
+ my $fieldid = get_field_id("attachments.$field");
+ $sth->execute($bug->id, $self->id, $user->id, $timestamp,
+ $fieldid, $change->[0], $change->[1]);
}
- my $attachment = new Bugzilla::Attachment($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'} = $@;
+ if (scalar(keys %$changes)) {
+ $dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
+ undef, $timestamp, $bug->id);
}
- # Return the new attachment object.
- return $attachment;
+ return $changes;
}
=pod
@@ -985,4 +952,50 @@ sub remove_from_db {
$dbh->bz_commit_transaction();
}
+###############################
+#### Helpers #####
+###############################
+
+# Extract the content type from the attachment form.
+sub get_content_type {
+ my $cgi = Bugzilla->cgi;
+
+ return 'text/plain' if ($cgi->param('ispatch') || $cgi->param('attachurl'));
+
+ my $content_type;
+ if (!defined $cgi->param('contenttypemethod')) {
+ ThrowUserError("missing_content_type_method");
+ }
+ elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
+ 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");
+
+ # Set the ispatch flag to 1 if the content type
+ # is text/x-diff or text/x-patch
+ if ($content_type =~ m{text/x-(?:diff|patch)}) {
+ $cgi->param('ispatch', 1);
+ $content_type = 'text/plain';
+ }
+ }
+ elsif ($cgi->param('contenttypemethod') eq 'list') {
+ # The user selected a content type from the list, so use their
+ # selection.
+ $content_type = $cgi->param('contenttypeselection');
+ }
+ elsif ($cgi->param('contenttypemethod') eq 'manual') {
+ # The user entered a content type manually, so use their entry.
+ $content_type = $cgi->param('contenttypeentry');
+ }
+ else {
+ ThrowCodeError("illegal_content_type_method",
+ { contenttypemethod => $cgi->param('contenttypemethod') });
+ }
+ return $content_type;
+}
+
+
1;
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index 0150f3124..91a97b7a2 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -148,6 +148,7 @@ use File::Basename;
MAX_LOGINCOOKIE_AGE
SAFE_PROTOCOLS
+ LEGAL_CONTENT_TYPES
MIN_SMALLINT
MAX_SMALLINT
@@ -236,18 +237,6 @@ use constant LOGOUT_ALL => 0;
use constant LOGOUT_CURRENT => 1;
use constant LOGOUT_KEEP_CURRENT => 2;
-use constant contenttypes =>
- {
- "html"=> "text/html" ,
- "rdf" => "application/rdf+xml" ,
- "atom"=> "application/atom+xml" ,
- "xml" => "application/xml" ,
- "js" => "application/x-javascript" ,
- "csv" => "text/csv" ,
- "png" => "image/png" ,
- "ics" => "text/calendar" ,
- };
-
use constant GRANT_DIRECT => 0;
use constant GRANT_REGEXP => 2;
@@ -377,6 +366,22 @@ use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet',
'view-source', 'wais');
+# Valid MIME types for attachments.
+use constant LEGAL_CONTENT_TYPES => ('application', 'audio', 'image', 'message',
+ 'model', 'multipart', 'text', 'video');
+
+use constant contenttypes =>
+ {
+ "html"=> "text/html" ,
+ "rdf" => "application/rdf+xml" ,
+ "atom"=> "application/atom+xml" ,
+ "xml" => "application/xml" ,
+ "js" => "application/x-javascript" ,
+ "csv" => "text/csv" ,
+ "png" => "image/png" ,
+ "ics" => "text/calendar" ,
+ };
+
# Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode.
use constant USAGE_MODE_BROWSER => 0;
use constant USAGE_MODE_CMDLINE => 1;
diff --git a/Bugzilla/Flag.pm b/Bugzilla/Flag.pm
index 130756459..66c392198 100644
--- a/Bugzilla/Flag.pm
+++ b/Bugzilla/Flag.pm
@@ -1057,6 +1057,46 @@ sub FormToNewFlags {
return \@flags;
}
+# This is a helper to set flags on a new bug or attachment.
+# For existing bugs and attachments, errors must be reported.
+sub set_flags {
+ my ($class, $bug, $attachment, $timestamp, $vars) = @_;
+ my $cgi = Bugzilla->cgi;
+
+ # 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);
+
+ $vars->{'match_field'} = 'requestee';
+ if ($match_status == USER_MATCH_FAILED) {
+ $vars->{'message'} = 'user_match_failed';
+ }
+ elsif ($match_status == USER_MATCH_MULTIPLE) {
+ $vars->{'message'} = 'user_match_multiple';
+ }
+
+ # 1. Add flags, if any. To avoid dying if something goes wrong
+ # while processing flags, we will eval() flag validation.
+ #
+ # 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 {
+ validate($bug->bug_id, $attachment ? -1 : undef, SKIP_REQUESTEE_ON_ERROR);
+ $class->process($bug, $attachment, $timestamp, $vars);
+ };
+ Bugzilla->error_mode($error_mode_cache);
+ if ($@) {
+ $vars->{'message'} = 'flag_creation_failed';
+ $vars->{'flag_creation_error'} = $@;
+ }
+}
+
=pod
=over