mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-12 21:05:36 +00:00
Bug 252272: Allow extremely large attachments to be stored locally
r=wurblzap.a=justdave
This commit is contained in:
parent
28d1b7868f
commit
9511285084
@ -33,6 +33,7 @@ package Bugzilla::Attachment;
|
||||
|
||||
# Use the Flag module to handle flags.
|
||||
use Bugzilla::Flag;
|
||||
use Bugzilla::Config qw(:locations);
|
||||
|
||||
############################################################################
|
||||
# Functions
|
||||
@ -92,6 +93,17 @@ sub query
|
||||
# Retrieve a list of flags for this attachment.
|
||||
$a{'flags'} = Bugzilla::Flag::match({ 'attach_id' => $a{'attachid'},
|
||||
'is_active' => 1 });
|
||||
|
||||
# A zero size indicates that the attachment is stored locally.
|
||||
if ($a{'datasize'} == 0) {
|
||||
my $attachid = $a{'attachid'};
|
||||
my $hash = ($attachid % 100) + 100;
|
||||
$hash =~ s/.*(\d\d)$/group.$1/;
|
||||
if (open(AH, "$attachdir/$hash/attachment.$attachid")) {
|
||||
$a{'datasize'} = (stat(AH))[7];
|
||||
close(AH);
|
||||
}
|
||||
}
|
||||
|
||||
# We will display the edit link if the user can edit the attachment;
|
||||
# ie the are the submitter, or they have canedit.
|
||||
|
@ -55,6 +55,7 @@ use Bugzilla::Util;
|
||||
our $libpath = '.';
|
||||
our $localconfig = "$libpath/localconfig";
|
||||
our $datadir = "$libpath/data";
|
||||
our $attachdir = "$datadir/attachments";
|
||||
our $templatedir = "$libpath/template";
|
||||
our $webdotdir = "$datadir/webdot";
|
||||
|
||||
@ -72,7 +73,8 @@ our $webdotdir = "$datadir/webdot";
|
||||
(
|
||||
admin => [qw(GetParamList UpdateParams SetParam WriteParams)],
|
||||
db => [qw($db_driver $db_host $db_port $db_name $db_user $db_pass $db_sock)],
|
||||
locations => [qw($libpath $localconfig $datadir $templatedir $webdotdir)],
|
||||
locations => [qw($libpath $localconfig $attachdir
|
||||
$datadir $templatedir $webdotdir)],
|
||||
);
|
||||
Exporter::export_ok_tags('admin', 'db', 'locations');
|
||||
|
||||
|
@ -40,6 +40,7 @@ use vars qw(
|
||||
|
||||
# Include the Bugzilla CGI and general utility library.
|
||||
require "CGI.pl";
|
||||
use Bugzilla::Config qw(:locations);
|
||||
|
||||
# Use these modules to handle flags.
|
||||
use Bugzilla::Constants;
|
||||
@ -360,12 +361,18 @@ sub validateData
|
||||
{
|
||||
my $maxsize = $::FORM{'ispatch'} ? Param('maxpatchsize') : Param('maxattachmentsize');
|
||||
$maxsize *= 1024; # Convert from K
|
||||
|
||||
my $fh = $cgi->upload('data');
|
||||
my $fh;
|
||||
# Skip uploading into a local variable if the user wants to upload huge
|
||||
# attachments into local files.
|
||||
if (!$::FORM{'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 (!$::FORM{'bigfile'})
|
||||
{
|
||||
# enable 'slurp' mode
|
||||
local $/;
|
||||
@ -373,10 +380,11 @@ sub validateData
|
||||
}
|
||||
|
||||
$data
|
||||
|| ($::FORM{'bigfile'})
|
||||
|| ThrowUserError("zero_length_file");
|
||||
|
||||
# Make sure the attachment does not exceed the maximum permitted size
|
||||
my $len = length($data);
|
||||
my $len = $data ? length($data) : 0;
|
||||
if ($maxsize && $len > $maxsize) {
|
||||
my $vars = { filesize => sprintf("%.0f", $len/1024) };
|
||||
if ( $::FORM{'ispatch'} ) {
|
||||
@ -504,6 +512,23 @@ sub view
|
||||
# Return the appropriate HTTP response headers.
|
||||
$filename =~ s/^.*[\/\\]//;
|
||||
my $filesize = length($thedata);
|
||||
# A zero length attachment in the database means the attachment is
|
||||
# stored in a local file
|
||||
if ($filesize == 0)
|
||||
{
|
||||
my $attachid = $::FORM{'id'};
|
||||
my $hash = ($attachid % 100) + 100;
|
||||
$hash =~ s/.*(\d\d)$/group.$1/;
|
||||
if (open(AH, "$attachdir/$hash/attachment.$attachid")) {
|
||||
binmode AH;
|
||||
$filesize = (stat(AH))[7];
|
||||
}
|
||||
}
|
||||
if ($filesize == 0)
|
||||
{
|
||||
ThrowUserError("attachment_removed");
|
||||
}
|
||||
|
||||
|
||||
# escape quotes and backslashes in the filename, per RFCs 2045/822
|
||||
$filename =~ s/\\/\\\\/g; # escape backslashes
|
||||
@ -513,7 +538,15 @@ sub view
|
||||
-content_disposition=> "inline; filename=\"$filename\"",
|
||||
-content_length => $filesize);
|
||||
|
||||
print $thedata;
|
||||
if ($thedata) {
|
||||
print $thedata;
|
||||
} else {
|
||||
while (<AH>) {
|
||||
print $_;
|
||||
}
|
||||
close(AH);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub interdiff
|
||||
@ -771,7 +804,7 @@ sub viewall
|
||||
$privacy = "AND isprivate < 1 ";
|
||||
}
|
||||
SendSQL("SELECT attach_id, DATE_FORMAT(creation_ts, '%Y.%m.%d %H:%i'),
|
||||
mimetype, description, ispatch, isobsolete, isprivate,
|
||||
mimetype, description, ispatch, isobsolete, isprivate,
|
||||
LENGTH(thedata)
|
||||
FROM attachments WHERE bug_id = $::FORM{'bugid'} $privacy
|
||||
ORDER BY attach_id");
|
||||
@ -779,7 +812,7 @@ sub viewall
|
||||
while (MoreSQLData())
|
||||
{
|
||||
my %a; # the attachment hash
|
||||
($a{'attachid'}, $a{'date'}, $a{'contenttype'},
|
||||
($a{'attachid'}, $a{'date'}, $a{'contenttype'},
|
||||
$a{'description'}, $a{'ispatch'}, $a{'isobsolete'}, $a{'isprivate'},
|
||||
$a{'datasize'}) = FetchSQLData();
|
||||
$a{'isviewable'} = isViewable($a{'contenttype'});
|
||||
@ -889,11 +922,39 @@ sub insert
|
||||
# Retrieve the ID of the newly created attachment record.
|
||||
my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
|
||||
|
||||
# If the file is to be stored locally, stream the file from the webserver
|
||||
# to the local file without reading it into a local variable.
|
||||
if ($::FORM{'bigfile'})
|
||||
{
|
||||
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 = (Param("maxlocalattachment") * 1048576);
|
||||
while (<$fh>) {
|
||||
print AH $_;
|
||||
$sizecount += length($_);
|
||||
if ($sizecount > $limit) {
|
||||
close AH;
|
||||
close $fh;
|
||||
unlink "$attachdir/$hash/attachment.$attachid";
|
||||
ThrowUserError("local_file_too_large");
|
||||
}
|
||||
}
|
||||
close AH;
|
||||
close $fh;
|
||||
}
|
||||
|
||||
|
||||
# Insert a comment about the new attachment into the database.
|
||||
my $comment = "Created an attachment (id=$attachid)\n$::FORM{'description'}\n";
|
||||
$comment .= ("\n" . $::FORM{'comment'}) if $::FORM{'comment'};
|
||||
|
||||
AppendComment($::FORM{'bugid'},
|
||||
AppendComment($::FORM{'bugid'},
|
||||
Bugzilla->user->login,
|
||||
$comment,
|
||||
$isprivate,
|
||||
@ -906,7 +967,7 @@ sub insert
|
||||
SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when, fieldid, removed, added)
|
||||
VALUES ($::FORM{'bugid'}, $obsolete_id, $::userid, $sql_timestamp, $fieldid, '0', '1')");
|
||||
# If the obsolete attachment has pending flags, migrate them to the new attachment.
|
||||
if (Bugzilla::Flag::count({ 'attach_id' => $obsolete_id ,
|
||||
if (Bugzilla::Flag::count({ 'attach_id' => $obsolete_id ,
|
||||
'status' => 'pending',
|
||||
'is_active' => 1 })) {
|
||||
Bugzilla::Flag::migrate($obsolete_id, $attachid, $timestamp);
|
||||
@ -1009,11 +1070,11 @@ sub edit
|
||||
# Get a list of flag types that can be set for this attachment.
|
||||
SendSQL("SELECT product_id, component_id FROM bugs WHERE bug_id = $bugid");
|
||||
my ($product_id, $component_id) = FetchSQLData();
|
||||
my $flag_types = Bugzilla::FlagType::match({ 'target_type' => 'attachment' ,
|
||||
'product_id' => $product_id ,
|
||||
my $flag_types = Bugzilla::FlagType::match({ 'target_type' => 'attachment' ,
|
||||
'product_id' => $product_id ,
|
||||
'component_id' => $component_id });
|
||||
foreach my $flag_type (@$flag_types) {
|
||||
$flag_type->{'flags'} = Bugzilla::Flag::match({ 'type_id' => $flag_type->{'id'},
|
||||
$flag_type->{'flags'} = Bugzilla::Flag::match({ 'type_id' => $flag_type->{'id'},
|
||||
'attach_id' => $::FORM{'id'},
|
||||
'is_active' => 1 });
|
||||
}
|
||||
@ -1087,10 +1148,10 @@ sub update
|
||||
# Update the attachment record in the database.
|
||||
# Sets the creation timestamp to itself to avoid it being updated automatically.
|
||||
SendSQL("UPDATE attachments
|
||||
SET description = $quoteddescription ,
|
||||
mimetype = $quotedcontenttype ,
|
||||
SET description = $quoteddescription ,
|
||||
mimetype = $quotedcontenttype ,
|
||||
filename = $quotedfilename ,
|
||||
ispatch = $::FORM{'ispatch'} ,
|
||||
ispatch = $::FORM{'ispatch'},
|
||||
isobsolete = $::FORM{'isobsolete'} ,
|
||||
isprivate = $::FORM{'isprivate'}
|
||||
WHERE attach_id = $::FORM{'id'}
|
||||
@ -1143,7 +1204,7 @@ sub update
|
||||
# Unlock all database tables now that we are finished updating the database.
|
||||
$dbh->bz_unlock_tables();
|
||||
|
||||
# If the user submitted a comment while editing the attachment,
|
||||
# If the user submitted a comment while editing the attachment,
|
||||
# add the comment to the bug.
|
||||
if ( $::FORM{'comment'} )
|
||||
{
|
||||
|
@ -904,6 +904,14 @@ unless (-d $datadir && -e "$datadir/nomail") {
|
||||
open FILE, '>>', "$datadir/mail"; close FILE;
|
||||
}
|
||||
|
||||
|
||||
unless (-d $attachdir) {
|
||||
print "Creating local attachments directory ...\n";
|
||||
# permissions for non-webservergroup are fixed later on
|
||||
mkdir $attachdir, 0770;
|
||||
}
|
||||
|
||||
|
||||
# 2000-12-14 New graphing system requires a directory to put the graphs in
|
||||
# This code copied from what happens for the data dir above.
|
||||
# If the graphs dir is not present, we assume that they have been using
|
||||
@ -1088,6 +1096,17 @@ END
|
||||
}
|
||||
|
||||
}
|
||||
if (!-e "$attachdir/.htaccess") {
|
||||
print "Creating $attachdir/.htaccess...\n";
|
||||
open HTACCESS, ">$attachdir/.htaccess";
|
||||
print HTACCESS <<'END';
|
||||
# nothing in this directory is retrievable unless overriden by an .htaccess
|
||||
# in a subdirectory;
|
||||
deny from all
|
||||
END
|
||||
close HTACCESS;
|
||||
chmod $fileperm, "$attachdir/.htaccess";
|
||||
}
|
||||
if (!-e "Bugzilla/.htaccess") {
|
||||
print "Creating Bugzilla/.htaccess...\n";
|
||||
open HTACCESS, '>', 'Bugzilla/.htaccess';
|
||||
@ -1428,6 +1447,7 @@ if ($^O !~ /MSWin32/i) {
|
||||
fixPerms("$datadir/duplicates", $<, $webservergid, 027, 1);
|
||||
fixPerms("$datadir/mining", $<, $webservergid, 027, 1);
|
||||
fixPerms("$datadir/template", $<, $webservergid, 007, 1); # webserver will write to these
|
||||
fixPerms($attachdir, $<, $webservergid, 007, 1); # webserver will write to these
|
||||
fixPerms($webdotdir, $<, $webservergid, 007, 1);
|
||||
fixPerms("$webdotdir/.htaccess", $<, $webservergid, 027);
|
||||
fixPerms("$datadir/params", $<, $webservergid, 017);
|
||||
|
@ -1269,6 +1269,17 @@ Reason: %reason%
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'maxlocalattachment',
|
||||
desc => 'The maximum size (in Megabytes) of attachments identified by ' .
|
||||
'the user as "Big Files" to be stored locally on the webserver. ' .
|
||||
'If set to zero, attachments will never be kept on the local ' .
|
||||
'filesystem.',
|
||||
type => 't',
|
||||
default => '0',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'chartgroup',
|
||||
desc => 'The name of the group of users who can use the "New Charts" ' .
|
||||
|
@ -65,6 +65,18 @@
|
||||
<input type="file" id="data" name="data" size="50">
|
||||
</td>
|
||||
</tr>
|
||||
[% IF Param("maxlocalattachment") %]
|
||||
<tr>
|
||||
<th>BigFile:</th>
|
||||
<td>
|
||||
<input type="checkbox" id="bigfile"
|
||||
name="bigfile" value="bigfile">
|
||||
<label for="bigfile">
|
||||
Big File - Stored locally and may be purged
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
<tr>
|
||||
<th><label for="description">Description:</label></th>
|
||||
<td>
|
||||
|
@ -156,6 +156,10 @@
|
||||
[% title = "Access Denied" %]
|
||||
You are not authorized to access this attachment.
|
||||
|
||||
[% ELSIF error == "attachment_removed" %]
|
||||
[% title = "Attachment Removed" %]
|
||||
The attachment you are attempting to access has been removed.
|
||||
|
||||
[% ELSIF error == "bug_access_denied" %]
|
||||
[% title = "Access Denied" %]
|
||||
You are not authorized to access [% terms.bug %] #[% bug_id FILTER html %].
|
||||
@ -604,6 +608,11 @@
|
||||
[% title = "Invalid Keyword Name" %]
|
||||
You may not use commas or whitespace in a keyword name.
|
||||
|
||||
[% ELSIF error == "local_file_too_large" %]
|
||||
[% title = "Local File Too Large" %]
|
||||
Local file uploads must not exceed
|
||||
[% Param('maxlocalattachment') %] MB in size.
|
||||
|
||||
[% ELSIF error == "login_needed_for_password_change" %]
|
||||
[% title = "Login Name Required" %]
|
||||
You must enter a login name when requesting to change your password.
|
||||
|
Loading…
Reference in New Issue
Block a user