Commit cf1da041 authored by Per Cederqvist's avatar Per Cederqvist

Imported Bugzilla 4.2.

parent 38de49e2
bzr://bzr.mozilla.org/bugzilla/4.0/
\ No newline at end of file
bzr://bzr.mozilla.org/bugzilla/4.2/
\ No newline at end of file
No preview for this file type
.htaccess
/lib/*
/template/en/custom
/docs/bugzilla.ent
/docs/en/xml/bugzilla.ent
/docs/en/txt
/docs/en/html
/docs/en/pdf
/skins/custom
/graphs
/data
/localconfig
/index.html
/skins/contrib/Dusk/IE-fixes.css
/skins/contrib/Dusk/admin.css
/skins/contrib/Dusk/attachment.css
/skins/contrib/Dusk/create_attachment.css
/skins/contrib/Dusk/dependency-tree.css
/skins/contrib/Dusk/duplicates.css
/skins/contrib/Dusk/editusers.css
/skins/contrib/Dusk/enter_bug.css
/skins/contrib/Dusk/help.css
/skins/contrib/Dusk/panel.css
/skins/contrib/Dusk/page.css
/skins/contrib/Dusk/params.css
/skins/contrib/Dusk/reports.css
/skins/contrib/Dusk/show_bug.css
/skins/contrib/Dusk/search_form.css
/skins/contrib/Dusk/show_multiple.css
/skins/contrib/Dusk/summarize-time.css
.DS_Store
ca0d4160c10a48d7764e03edb9ca8f4073691a4c
\ No newline at end of file
# Don't allow people to retrieve non-cgi executable files or our private data
<FilesMatch (\.pm|\.pl|\.tmpl|localconfig.*)$>
<FilesMatch ^(.*\.pm|.*\.pl|.*localconfig.*)$>
deny from all
</FilesMatch>
<IfModule mod_expires.c>
......
......@@ -76,6 +76,10 @@ use constant SHUTDOWNHTML_EXIT_SILENTLY => qw(
whine.pl
);
# shutdownhtml pages are sent as an HTTP 503. After how many seconds
# should search engines attempt to index the page again?
use constant SHUTDOWNHTML_RETRY_AFTER => 3600;
#####################################################################
# Global Code
#####################################################################
......@@ -170,7 +174,12 @@ sub init_page {
else {
$extension = 'txt';
}
print Bugzilla->cgi->header() if i_am_cgi();
if (i_am_cgi()) {
# Set the HTTP status to 503 when Bugzilla is down to avoid pages
# being indexed by search engines.
print Bugzilla->cgi->header(-status => 503,
-retry_after => SHUTDOWNHTML_RETRY_AFTER);
}
my $t_output;
$template->process("global/message.$extension.tmpl", $vars, \$t_output)
|| ThrowTemplateError($template->error);
......@@ -538,14 +547,52 @@ sub switch_to_main_db {
return $class->dbh_main;
}
sub get_fields {
my $class = shift;
my $criteria = shift;
# This function may be called during installation, and Field::match
# may fail at that time. so we want to return an empty list in that
# case.
my $fields = eval { Bugzilla::Field->match($criteria) } || [];
return @$fields;
sub fields {
my ($class, $criteria) = @_;
$criteria ||= {};
my $cache = $class->request_cache;
# We create an advanced cache for fields by type, so that we
# can avoid going back to the database for every fields() call.
# (And most of our fields() calls are for getting fields by type.)
#
# We also cache fields by name, because calling $field->name a few
# million times can be slow in calling code, but if we just do it
# once here, that makes things a lot faster for callers.
if (!defined $cache->{fields}) {
my @all_fields = Bugzilla::Field->get_all;
my (%by_name, %by_type);
foreach my $field (@all_fields) {
my $name = $field->name;
$by_type{$field->type}->{$name} = $field;
$by_name{$name} = $field;
}
$cache->{fields} = { by_type => \%by_type, by_name => \%by_name };
}
my $fields = $cache->{fields};
my %requested;
if (my $types = delete $criteria->{type}) {
$types = ref($types) ? $types : [$types];
%requested = map { %{ $fields->{by_type}->{$_} || {} } } @$types;
}
else {
%requested = %{ $fields->{by_name} };
}
my $do_by_name = delete $criteria->{by_name};
# Filtering before returning the fields based on
# the criterias.
foreach my $filter (keys %$criteria) {
foreach my $field (keys %requested) {
if ($requested{$field}->$filter != $criteria->{$filter}) {
delete $requested{$field};
}
}
}
return $do_by_name ? \%requested : [values %requested];
}
sub active_custom_fields {
......@@ -607,6 +654,12 @@ sub _cleanup {
$dbh->disconnect;
}
undef $_request_cache;
# These are both set by CGI.pm but need to be undone so that
# Apache can actually shut down its children if it needs to.
foreach my $signal (qw(TERM PIPE)) {
$SIG{$signal} = 'DEFAULT' if $SIG{$signal} && $SIG{$signal} eq 'IGNORE';
}
}
sub END {
......@@ -783,6 +836,30 @@ Essentially, causes calls to C<Bugzilla-E<gt>user> to return C<undef>. This has
effect of logging out a user for the current request only; cookies and
database sessions are left intact.
=item C<fields>
This is the standard way to get arrays or hashes of L<Bugzilla::Field>
objects when you need them. It takes the following named arguments
in a hashref:
=over
=item C<by_name>
If false (or not specified), this method will return an arrayref of
the requested fields. The order of the returned fields is random.
If true, this method will return a hashref of fields, where the keys
are field names and the valules are L<Bugzilla::Field> objects.
=item C<type>
Either a single C<FIELD_TYPE_*> constant or an arrayref of them. If specified,
the returned fields will be limited to the types in the list. If you don't
specify this argument, all fields will be returned.
=back
=item C<error_mode>
Call either C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE)>
......
......@@ -59,6 +59,9 @@ use Bugzilla::Util;
use Bugzilla::Field;
use Bugzilla::Hook;
use File::Copy;
use List::Util qw(max);
use base qw(Bugzilla::Object);
###############################
......@@ -68,6 +71,9 @@ use base 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;
sub DB_COLUMNS {
my $dbh = Bugzilla->dbh;
......@@ -80,7 +86,6 @@ sub DB_COLUMNS {
isobsolete
ispatch
isprivate
isurl
mimetype
modification_time
submitter_id),
......@@ -104,19 +109,17 @@ use constant UPDATE_COLUMNS => qw(
use constant VALIDATORS => {
bug => \&_check_bug,
description => \&_check_description,
filename => \&_check_filename,
ispatch => \&Bugzilla::Object::check_boolean,
isprivate => \&_check_is_private,
isurl => \&_check_is_url,
mimetype => \&_check_content_type,
store_in_file => \&_check_store_in_file,
};
use constant VALIDATOR_DEPENDENCIES => {
mimetype => ['ispatch', 'isurl'],
mimetype => ['ispatch'],
};
use constant UPDATE_VALIDATORS => {
filename => \&_check_filename,
isobsolete => \&Bugzilla::Object::check_boolean,
};
......@@ -270,21 +273,6 @@ sub 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
......@@ -528,20 +516,15 @@ sub _check_bug {
sub _check_content_type {
my ($invocant, $content_type, undef, $params) = @_;
my ($is_url, $is_patch) = @$params{qw(isurl ispatch)};
if (ref $invocant) {
$is_url = $invocant->isurl;
$is_patch = $invocant->ispatch;
}
$content_type = 'text/plain' if ($is_url || $is_patch);
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_\-\+\.]+(;.+)?$/i)
|| $content_type !~ /^($legal_types)\/[a-z0-9_\-\+\.]+(;\s*charset=[a-z0-9_\-\+]+)?$/i)
{
ThrowUserError("invalid_content_type", { contenttype => $content_type });
}
......@@ -553,53 +536,20 @@ sub _check_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 });
my $data = $params->{data};
$params->{filesize} = ref $data ? -s $data : length($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};
}
else {
# The file will be stored in the DB. We need the content of the file.
local $/;
my $fh = $params->{data};
$data = <$fh>;
close $fh;
}
}
Bugzilla::Hook::process('attachment_process_data', { data => \$data,
attributes => $params });
# Do not validate the size if we have a filehandle. It will be checked later.
return $data if ref $data;
$data || ThrowUserError('zero_length_file');
$params->{filesize} || ThrowUserError('zero_length_file');
# Make sure the attachment does not exceed the maximum permitted size.
my $len = length($data);
my $max_size = $params->{store_in_file} ? Bugzilla->params->{'maxlocalattachment'} * 1048576
: Bugzilla->params->{'maxattachmentsize'} * 1024;
if ($len > $max_size) {
my $vars = { filesize => sprintf("%.0f", $len/1024) };
if ($params->{ispatch}) {
ThrowUserError('patch_too_large', $vars);
}
elsif ($params->{store_in_file}) {
ThrowUserError('local_file_too_large');
}
else {
ThrowUserError('file_too_large', $vars);
}
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;
}
......@@ -613,14 +563,17 @@ sub _check_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 ($invocant, $filename) = @_;
$filename = clean_text($filename);
$filename || ThrowUserError('file_not_specified');
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
......@@ -649,24 +602,6 @@ sub _check_is_private {
return $is_private;
}
sub _check_is_url {
my ($invocant, $is_url) = @_;
if ($is_url && !Bugzilla->params->{'allow_attach_url'}) {
ThrowCodeError('attachment_url_disabled');
}
return $is_url ? 1 : 0;
}
sub _check_store_in_file {
my ($invocant, $store_in_file) = @_;
if ($store_in_file && !Bugzilla->params->{'maxlocalattachment'}) {
ThrowCodeError('attachment_local_storage_disabled');
}
return $store_in_file ? 1 : 0;
}
=pod
=head2 Class Methods
......@@ -788,8 +723,11 @@ sub validate_obsolete {
$attachment->validate_can_edit($bug->product_id)
|| ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->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);
}
......@@ -826,11 +764,6 @@ Params: takes a hashref with the following keys:
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.
......@@ -846,53 +779,47 @@ sub create {
# 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};
my $data = delete $params->{data};
my $size = delete $params->{filesize};
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.
my $sth = $dbh->prepare("INSERT INTO attach_data
(id, thedata) VALUES ($attachid, ?)");
my $data = $store_in_file ? "" : $fh;
trick_taint($data);