gecko-dev/webtools/bugzilla/CGI.pl

403 lines
14 KiB
Perl

# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Dave Miller <justdave@syndicomm.com>
# Christopher Aillon <christopher@aillon.com>
# Gervase Markham <gerv@gerv.net>
# Christian Reis <kiko@async.com.br>
# Contains some global routines used throughout the CGI scripts of Bugzilla.
use strict;
use lib ".";
# use Carp; # for confess
BEGIN {
if ($^O =~ /MSWin32/i) {
# Help CGI find the correct temp directory as the default list
# isn't Windows friendly (Bug 248988)
$ENV{'TMPDIR'} = $ENV{'TEMP'} || $ENV{'TMP'} || "$ENV{'WINDIR'}\\TEMP";
}
}
use Bugzilla::Util;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::BugMail;
use Bugzilla::Bug;
use Bugzilla::User;
# Used in LogActivityEntry(). Gives the max length of lines in the
# activity table.
use constant MAX_LINE_LENGTH => 254;
# Shut up misguided -w warnings about "used only once". For some reason,
# "use vars" chokes on me when I try it here.
sub CGI_pl_sillyness {
my $zz;
$zz = $::buffer;
}
use CGI::Carp qw(fatalsToBrowser);
require 'globals.pl';
use vars qw($template $vars);
# If Bugzilla is shut down, do not go any further, just display a message
# to the user about the downtime. (do)editparams.cgi is exempted from
# this message, of course, since it needs to be available in order for
# the administrator to open Bugzilla back up.
if (Param("shutdownhtml") && $0 !~ m:(^|[\\/])(do)?editparams\.cgi$:) {
$::vars->{'message'} = "shutdown";
# Return the appropriate HTTP response headers.
print Bugzilla->cgi->header();
# Generate and return an HTML message about the downtime.
$::template->process("global/message.html.tmpl", $::vars)
|| ThrowTemplateError($::template->error());
exit;
}
# Implementations of several of the below were blatently stolen from CGI.pm,
# by Lincoln D. Stein.
# Get rid of all the %xx encoding and the like from the given URL.
sub url_decode {
my ($todecode) = (@_);
$todecode =~ tr/+/ /; # pluses become spaces
$todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
return $todecode;
}
# check and see if a given field exists, is non-empty, and is set to a
# legal value. assume a browser bug and abort appropriately if not.
# if $legalsRef is not passed, just check to make sure the value exists and
# is non-NULL
sub CheckFormField ($$;\@) {
my ($cgi, # a CGI object
$fieldname, # the fieldname to check
$legalsRef # (optional) ref to a list of legal values
) = @_;
if (!defined $cgi->param($fieldname)
|| trim($cgi->param($fieldname)) eq ""
|| (defined($legalsRef)
&& lsearch($legalsRef, $cgi->param($fieldname))<0))
{
SendSQL("SELECT description FROM fielddefs WHERE name=" . SqlQuote($fieldname));
my $result = FetchOneColumn();
my $field;
if ($result) {
$field = $result;
}
else {
$field = $fieldname;
}
ThrowCodeError("illegal_field", { field => $field });
}
}
# check and see if a given field is defined, and abort if not
sub CheckFormFieldDefined ($$) {
my ($cgi, # a CGI object
$fieldname, # the fieldname to check
) = @_;
if (!defined $cgi->param($fieldname)) {
ThrowCodeError("undefined_field", { field => $fieldname });
}
}
sub ValidateBugID {
# Validates and verifies a bug ID, making sure the number is a
# positive integer, that it represents an existing bug in the
# database, and that the user is authorized to access that bug.
# We detaint the number here, too
my ($id, $field) = @_;
# Get rid of white-space around the ID.
$id = trim($id);
# If the ID isn't a number, it might be an alias, so try to convert it.
my $alias = $id;
if (!detaint_natural($id)) {
$id = bug_alias_to_id($alias);
$id || ThrowUserError("invalid_bug_id_or_alias",
{'bug_id' => $alias,
'field' => $field });
}
# Modify the calling code's original variable to contain the trimmed,
# converted-from-alias ID.
$_[0] = $id;
# First check that the bug exists
SendSQL("SELECT bug_id FROM bugs WHERE bug_id = $id");
FetchOneColumn()
|| ThrowUserError("invalid_bug_id_non_existent", {'bug_id' => $id});
return if (defined $field && ($field eq "dependson" || $field eq "blocked"));
return if Bugzilla->user->can_see_bug($id);
# The user did not pass any of the authorization tests, which means they
# are not authorized to see the bug. Display an error and stop execution.
# The error the user sees depends on whether or not they are logged in
# (i.e. $::userid contains the user's positive integer ID).
if ($::userid) {
ThrowUserError("bug_access_denied", {'bug_id' => $id});
} else {
ThrowUserError("bug_access_query", {'bug_id' => $id});
}
}
sub CheckEmailSyntax {
my ($addr) = (@_);
my $match = Param('emailregexp');
if ($addr !~ /$match/ || $addr =~ /[\\\(\)<>&,;:"\[\] \t\r\n]/) {
ThrowUserError("illegal_email_address", { addr => $addr });
}
}
sub MailPassword {
my ($login, $password) = (@_);
my $urlbase = Param("urlbase");
my $template = Param("passwordmail");
my $msg = PerformSubsts($template,
{"mailaddress" => $login . Param('emailsuffix'),
"login" => $login,
"password" => $password});
Bugzilla::BugMail::MessageToMTA($msg);
}
sub PutHeader {
($vars->{'title'}, $vars->{'h1'}, $vars->{'h2'}) = (@_);
$::template->process("global/header.html.tmpl", $::vars)
|| ThrowTemplateError($::template->error());
$vars->{'header_done'} = 1;
}
sub PutFooter {
$::template->process("global/footer.html.tmpl", $::vars)
|| ThrowTemplateError($::template->error());
}
sub CheckIfVotedConfirmed {
my ($id, $who) = (@_);
PushGlobalSQLState();
SendSQL("SELECT bugs.votes, bugs.bug_status, products.votestoconfirm, " .
" bugs.everconfirmed, NOW() " .
"FROM bugs INNER JOIN products ON products.id = bugs.product_id " .
"WHERE bugs.bug_id = $id");
my ($votes, $status, $votestoconfirm, $everconfirmed, $timestamp) = (FetchSQLData());
my $sql_timestamp = SqlQuote($timestamp);
my $ret = 0;
if ($votes >= $votestoconfirm && $status eq 'UNCONFIRMED') {
SendSQL("UPDATE bugs SET bug_status = 'NEW', everconfirmed = 1, " .
"delta_ts = $sql_timestamp WHERE bug_id = $id");
my $fieldid = GetFieldID("bug_status");
SendSQL("INSERT INTO bugs_activity " .
"(bug_id, who, bug_when, fieldid, removed, added) VALUES " .
"($id, $who, $sql_timestamp, $fieldid, 'UNCONFIRMED', 'NEW')");
if (!$everconfirmed) {
$fieldid = GetFieldID("everconfirmed");
SendSQL("INSERT INTO bugs_activity " .
"(bug_id, who, bug_when, fieldid, removed, added) VALUES " .
"($id, $who, $sql_timestamp, $fieldid, '0', '1')");
}
AppendComment($id, DBID_to_name($who),
"*** This bug has been confirmed by popular vote. ***",
0, $timestamp);
$vars->{'type'} = "votes";
$vars->{'id'} = $id;
$vars->{'mailrecipients'} = { 'changer' => $who };
$template->process("bug/process/results.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
$ret = 1;
}
PopGlobalSQLState();
return $ret;
}
sub LogActivityEntry {
my ($i,$col,$removed,$added,$whoid,$timestamp) = @_;
# in the case of CCs, deps, and keywords, there's a possibility that someone
# might try to add or remove a lot of them at once, which might take more
# space than the activity table allows. We'll solve this by splitting it
# into multiple entries if it's too long.
while ($removed || $added) {
my ($removestr, $addstr) = ($removed, $added);
if (length($removestr) > MAX_LINE_LENGTH) {
my $commaposition = find_wrap_point($removed, MAX_LINE_LENGTH);
$removestr = substr($removed,0,$commaposition);
$removed = substr($removed,$commaposition);
$removed =~ s/^[,\s]+//; # remove any comma or space
} else {
$removed = ""; # no more entries
}
if (length($addstr) > MAX_LINE_LENGTH) {
my $commaposition = find_wrap_point($added, MAX_LINE_LENGTH);
$addstr = substr($added,0,$commaposition);
$added = substr($added,$commaposition);
$added =~ s/^[,\s]+//; # remove any comma or space
} else {
$added = ""; # no more entries
}
$addstr = SqlQuote($addstr);
$removestr = SqlQuote($removestr);
my $fieldid = GetFieldID($col);
SendSQL("INSERT INTO bugs_activity " .
"(bug_id,who,bug_when,fieldid,removed,added) VALUES " .
"($i,$whoid," . SqlQuote($timestamp) . ",$fieldid,$removestr,$addstr)");
}
}
sub GetBugActivity {
my ($id, $starttime) = (@_);
my $datepart = "";
my $dbh = Bugzilla->dbh;
die "Invalid id: $id" unless $id=~/^\s*\d+\s*$/;
if (defined $starttime) {
$datepart = "AND bugs_activity.bug_when > " . SqlQuote($starttime);
}
my $suppjoins = "";
my $suppwhere = "";
if (Param("insidergroup") && !UserInGroup(Param('insidergroup'))) {
$suppjoins = "LEFT JOIN attachments
ON attachments.attach_id = bugs_activity.attach_id";
$suppwhere = "AND COALESCE(attachments.isprivate, 0) = 0";
}
my $query = "
SELECT COALESCE(fielddefs.description, bugs_activity.fieldid),
fielddefs.name, bugs_activity.attach_id, " .
$dbh->sql_date_format('bugs_activity.bug_when', '%Y.%m.%d %H:%i:%s') .
", bugs_activity.removed, bugs_activity.added, profiles.login_name
FROM bugs_activity
$suppjoins
LEFT JOIN fielddefs
ON bugs_activity.fieldid = fielddefs.fieldid
INNER JOIN profiles
ON profiles.userid = bugs_activity.who
WHERE bugs_activity.bug_id = $id
$datepart
$suppwhere
ORDER BY bugs_activity.bug_when";
SendSQL($query);
my @operations;
my $operation = {};
my $changes = [];
my $incomplete_data = 0;
while (my ($field, $fieldname, $attachid, $when, $removed, $added, $who)
= FetchSQLData())
{
my %change;
my $activity_visible = 1;
# check if the user should see this field's activity
if ($fieldname eq 'remaining_time' ||
$fieldname eq 'estimated_time' ||
$fieldname eq 'work_time' ||
$fieldname eq 'deadline') {
if (!UserInGroup(Param('timetrackinggroup'))) {
$activity_visible = 0;
} else {
$activity_visible = 1;
}
} else {
$activity_visible = 1;
}
if ($activity_visible) {
# This gets replaced with a hyperlink in the template.
$field =~ s/^Attachment// if $attachid;
# Check for the results of an old Bugzilla data corruption bug
$incomplete_data = 1 if ($added =~ /^\?/ || $removed =~ /^\?/);
# An operation, done by 'who' at time 'when', has a number of
# 'changes' associated with it.
# If this is the start of a new operation, store the data from the
# previous one, and set up the new one.
if ($operation->{'who'}
&& ($who ne $operation->{'who'}
|| $when ne $operation->{'when'}))
{
$operation->{'changes'} = $changes;
push (@operations, $operation);
# Create new empty anonymous data structures.
$operation = {};
$changes = [];
}
$operation->{'who'} = $who;
$operation->{'when'} = $when;
$change{'field'} = $field;
$change{'fieldname'} = $fieldname;
$change{'attachid'} = $attachid;
$change{'removed'} = $removed;
$change{'added'} = $added;
push (@$changes, \%change);
}
}
if ($operation->{'who'}) {
$operation->{'changes'} = $changes;
push (@operations, $operation);
}
return(\@operations, $incomplete_data);
}
############# Live code below here (that is, not subroutine defs) #############
use Bugzilla;
# XXX - mod_perl - reset this between runs
$::cgi = Bugzilla->cgi;
$::buffer = $::cgi->query_string();
# This could be needed in any CGI, so we set it here.
$vars->{'help'} = $::cgi->param('help') ? 1 : 0;
1;