# -*- 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 # Dan Mosedale # Joe Robins # Dave Miller # Christopher Aillon # Gervase Markham # Christian Reis # Contains some global routines used throughout the CGI scripts of Bugzilla. use strict; use lib "."; # use Carp; # for confess use Bugzilla::Util; use Bugzilla::Config; # commented out the following snippet of code. this tosses errors into the # CGI if you are perl 5.6, and doesn't if you have perl 5.003. # We want to check for the existence of the LDAP modules here. # eval "use Mozilla::LDAP::Conn"; # my $have_ldap = $@ ? 0 : 1; # 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 = %::MFORM; $zz = %::dontchange; } 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 "Content-Type: text/html\n\n"; # 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; } # Quotify a string, suitable for putting into a URL. sub url_quote { my($toencode) = (@_); $toencode=~s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg; return $toencode; } sub ParseUrlString { my ($buffer, $f, $m) = (@_); undef %$f; undef %$m; my %isnull; # We must make sure that the CGI params remain tainted. # This means that if for some reason you want to make this code # use a regexp and $1, $2, ... (or use a helper function which does so) # you must |use re 'taint'| _and_ make sure that you don't run into # http://bugs.perl.org/perlbug.cgi?req=bug_id&bug_id=20020704.001 my @args = split('&', $buffer); foreach my $arg (@args) { my ($name, $value) = split('=', $arg, 2); $value = '' if not defined $value; $name = url_decode($name); $value = url_decode($value); if ($value ne "") { if (defined $f->{$name}) { $f->{$name} .= $value; my $ref = $m->{$name}; push @$ref, $value; } else { $f->{$name} = $value; $m->{$name} = [$value]; } } else { $isnull{$name} = 1; } } if (%isnull) { foreach my $name (keys(%isnull)) { if (!defined $f->{$name}) { $f->{$name} = ""; $m->{$name} = []; } } } } sub ProcessFormFields { my ($buffer) = (@_); return ParseUrlString($buffer, \%::FORM, \%::MFORM); } sub ProcessMultipartFormFields { my ($boundary) = @_; # Initialize variables that store whether or not we are parsing a header, # the name of the part we are parsing, and its value (which is incomplete # until we finish parsing the part). my $inheader = 1; my $fieldname = ""; my $fieldvalue = ""; # Read the input stream line by line and parse it into a series of parts, # each one containing a single form field and its value and each one # separated from the next by the value of $boundary. my $remaining = $ENV{"CONTENT_LENGTH"}; while ($remaining > 0 && ($_ = )) { $remaining -= length($_); # If the current input line is a boundary line, save the previous # form value and reset the storage variables. if ($_ =~ m/^-*\Q$boundary\E/) { if ( $fieldname ) { chomp($fieldvalue); $fieldvalue =~ s/\r$//; if ( defined $::FORM{$fieldname} ) { $::FORM{$fieldname} .= $fieldvalue; push @{$::MFORM{$fieldname}}, $fieldvalue; } else { $::FORM{$fieldname} = $fieldvalue; $::MFORM{$fieldname} = [$fieldvalue]; } } $inheader = 1; $fieldname = ""; $fieldvalue = ""; # If the current input line is a header line, look for a blank line # (meaning the end of the headers), a Content-Disposition header # (containing the field name and, for uploaded file parts, the file # name), or a Content-Type header (containing the content type for # file parts). } elsif ( $inheader ) { if (m/^\s*$/) { $inheader = 0; } elsif (m/^Content-Disposition:\s*form-data\s*;\s*name\s*=\s*"([^\"]+)"/i) { $fieldname = $1; if (m/;\s*filename\s*=\s*"([^\"]+)"/i) { $::FILE{$fieldname}->{'filename'} = $1; } } elsif ( m|^Content-Type:\s*([^/]+/[^\s;]+)|i ) { $::FILE{$fieldname}->{'contenttype'} = $1; } # If the current input line is neither a boundary line nor a header, # it must be part of the field value, so append it to the value. } else { $fieldvalue .= $_; } } } # 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 ($formRef, # a reference to the form to check (a hash) $fieldname, # the fieldname to check $legalsRef # (optional) ref to a list of legal values ) = @_; if ( !defined $formRef->{$fieldname} || trim($formRef->{$fieldname}) eq "" || (defined($legalsRef) && lsearch($legalsRef, $formRef->{$fieldname})<0) ){ SendSQL("SELECT description FROM fielddefs WHERE name=" . SqlQuote($fieldname)); my $result = FetchOneColumn(); if ($result) { $vars->{'field'} = $result; } else { $vars->{'field'} = $fieldname; } ThrowCodeError("illegal_field", "abort"); } } # check and see if a given field is defined, and abort if not sub CheckFormFieldDefined (\%$) { my ($formRef, # a reference to the form to check (a hash) $fieldname, # the fieldname to check ) = @_; if (!defined $formRef->{$fieldname}) { $vars->{'field'} = $fieldname; ThrowCodeError("undefined_field"); } } sub BugAliasToID { # Queries the database for the bug with a given alias, and returns # the ID of the bug if it exists or the undefined value if it doesn't. my ($alias) = @_; return undef unless Param("usebugaliases"); PushGlobalSQLState(); SendSQL("SELECT bug_id FROM bugs WHERE alias = " . SqlQuote($alias)); my $id = FetchOneColumn(); PopGlobalSQLState(); return $id; } 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, $skip_authorization) = @_; # 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 = BugAliasToID($alias); if (!$id) { my $html_id = html_quote($_[0]); my $alias_specific_message = Param("usebugaliases") ? " (it is neither a bug number nor an alias to a bug number)" : ""; DisplayError(qq| The bug number $html_id is invalid$alias_specific_message. If you are trying to use QuickSearch, you need to enable JavaScript in your browser. To help us fix this limitation, add your comments to bug 70907. |); exit; } } # Modify the calling code's original variable to contain the trimmed, # converted-from-alias ID. $_[0] = $id; # Get the values of the usergroupset and userid global variables # and write them to local variables for use within this function, # setting those local variables to the default value of zero if # the global variables are undefined. # First check that the bug exists SendSQL("SELECT bug_id FROM bugs WHERE bug_id = $id"); FetchOneColumn() || DisplayError("Bug #$id does not exist.") && exit; return if $skip_authorization; return if CanSeeBug($id, $::userid, $::usergroupset); # 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) { DisplayError("You are not authorized to access bug #$id."); } else { DisplayError( qq|You are not authorized to access bug #$id. To see this bug, you must first log in to an account with the appropriate permissions.| ); } exit; } sub ValidateComment { # Make sure a comment is not too large (greater than 64K). my ($comment) = @_; if (defined($comment) && length($comment) > 65535) { DisplayError("Comments cannot be longer than 65,535 characters."); exit; } } # Adds elements for bug lists. These can be inserted into the header by # using the "header_html" parameter to PutHeader, which inserts an arbitrary # string into the header. This function is currently used only in # template/en/default/bug/edit.html.tmpl. sub navigation_links($) { my ($buglist) = @_; my $retval = ""; # We need to be able to pass in a buglist because when you sort on a column # the bugs in the cookie you are given will still be in the old order. # If a buglist isn't passed, we just use the cookie. $buglist ||= $::COOKIE{"BUGLIST"}; if (defined $buglist && $buglist ne "") { my @bugs = split(/:/, $buglist); if (defined $::FORM{'id'}) { # We are on an individual bug my $cur = lsearch(\@bugs, $::FORM{"id"}); if ($cur > 0) { $retval .= "\n"; $retval .= "\n"; } if ($cur < $#bugs) { $retval .= "\n"; $retval .= "\n"; } $retval .= "\n"; $retval .= "\n"; } else { # We are on a bug list $retval .= "\n"; $retval .= "\n"; $retval .= "\n"; } } return $retval; } $::CheckOptionValues = 1; # This sub is still used in reports.cgi. sub make_options { my ($src,$default,$isregexp) = (@_); my $last = ""; my $popup = ""; my $found = 0; $default = "" if !defined $default; if ($src) { foreach my $item (@$src) { if ($item eq "-blank-" || $item ne $last) { if ($item eq "-blank-") { $item = ""; } $last = $item; if ($isregexp ? $item =~ $default : $default eq $item) { $popup .= "