Prevent access to the /lib/ directory
[WebKit-https.git] / Websites / bugs.webkit.org / request.cgi
1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
3 #
4 # The contents of this file are subject to the Mozilla Public
5 # License Version 1.1 (the "License"); you may not use this file
6 # except in compliance with the License. You may obtain a copy of
7 # the License at http://www.mozilla.org/MPL/
8 #
9 # Software distributed under the License is distributed on an "AS
10 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11 # implied. See the License for the specific language governing
12 # rights and limitations under the License.
13 #
14 # The Original Code is the Bugzilla Bug Tracking System.
15 #
16 # The Initial Developer of the Original Code is Netscape Communications
17 # Corporation. Portions created by Netscape are
18 # Copyright (C) 1998 Netscape Communications Corporation. All
19 # Rights Reserved.
20 #
21 # Contributor(s): Myk Melez <myk@mozilla.org>
22 #                 Frédéric Buclin <LpSolit@gmail.com>
23
24 ################################################################################
25 # Script Initialization
26 ################################################################################
27
28 # Make it harder for us to do dangerous things in Perl.
29 use strict;
30
31 use lib qw(. lib);
32
33 use Bugzilla;
34 use Bugzilla::Util;
35 use Bugzilla::Error;
36 use Bugzilla::Flag;
37 use Bugzilla::FlagType;
38 use Bugzilla::User;
39 use Bugzilla::Product;
40 use Bugzilla::Component;
41
42 # Make sure the user is logged in.
43 my $user = Bugzilla->login();
44 my $cgi = Bugzilla->cgi;
45 # Force the script to run against the shadow DB. We already validated credentials.
46 Bugzilla->switch_to_shadow_db;
47 my $template = Bugzilla->template;
48 my $action = $cgi->param('action') || '';
49
50 print $cgi->header();
51
52 ################################################################################
53 # Main Body Execution
54 ################################################################################
55
56 my $fields;
57 $fields->{'requester'}->{'type'} = 'single';
58 # If the user doesn't restrict his search to requests from the wind
59 # (requestee ne '-'), include the requestee for completion.
60 unless (defined $cgi->param('requestee')
61         && $cgi->param('requestee') eq '-')
62 {
63     $fields->{'requestee'}->{'type'} = 'single';
64 }
65
66 Bugzilla::User::match_field($fields);
67
68 if ($action eq 'queue') {
69     queue();
70 }
71 else {
72     my $flagtypes = get_flag_types();
73     my @types = ('all', @$flagtypes);
74
75     my $vars = {};
76     $vars->{'types'} = \@types;
77     $vars->{'requests'} = {};
78
79     my %components;
80     foreach my $prod (@{$user->get_selectable_products}) {
81         foreach my $comp (@{$prod->components}) {
82             $components{$comp->name} = 1;
83         }
84     }
85     $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
86
87     $template->process('request/queue.html.tmpl', $vars)
88       || ThrowTemplateError($template->error());
89 }
90 exit;
91
92 ################################################################################
93 # Functions
94 ################################################################################
95
96 sub queue {
97     my $cgi = Bugzilla->cgi;
98     my $dbh = Bugzilla->dbh;
99     my $template = Bugzilla->template;
100     my $user = Bugzilla->user;
101     my $userid = $user->id;
102     my $vars = {};
103
104     my $status = validateStatus($cgi->param('status'));
105     my $form_group = validateGroup($cgi->param('group'));
106
107     my $query = 
108     # Select columns describing each flag, the bug/attachment on which
109     # it has been set, who set it, and of whom they are requesting it.
110     " SELECT    flags.id, flagtypes.name,
111                 flags.status,
112                 flags.bug_id, bugs.short_desc,
113                 products.name, components.name,
114                 flags.attach_id, attachments.description,
115                 requesters.realname, requesters.login_name,
116                 requestees.realname, requestees.login_name, COUNT(privs.group_id),
117     " . $dbh->sql_date_format('flags.modification_date', '%Y.%m.%d %H:%i') .
118     # Use the flags and flagtypes tables for information about the flags,
119     # the bugs and attachments tables for target info, the profiles tables
120     # for setter and requestee info, the products/components tables
121     # so we can display product and component names, and the bug_group_map
122     # table to help us weed out secure bugs to which the user should not have
123     # access.
124     "
125       FROM           flags 
126            LEFT JOIN attachments
127                   ON flags.attach_id = attachments.attach_id
128           INNER JOIN flagtypes
129                   ON flags.type_id = flagtypes.id
130           INNER JOIN profiles AS requesters
131                   ON flags.setter_id = requesters.userid
132            LEFT JOIN profiles AS requestees
133                   ON flags.requestee_id  = requestees.userid
134           INNER JOIN bugs
135                   ON flags.bug_id = bugs.bug_id
136           INNER JOIN products
137                   ON bugs.product_id = products.id
138           INNER JOIN components
139                   ON bugs.component_id = components.id
140            LEFT JOIN bug_group_map AS bgmap
141                   ON bgmap.bug_id = bugs.bug_id
142                  AND bgmap.group_id NOT IN (" .
143                      $user->groups_as_string . ")
144            LEFT JOIN bug_group_map AS privs
145                   ON privs.bug_id = bugs.bug_id
146            LEFT JOIN cc AS ccmap
147                   ON ccmap.who = $userid
148                  AND ccmap.bug_id = bugs.bug_id
149     " .
150
151     # Weed out bug the user does not have access to
152     " WHERE     ((bgmap.group_id IS NULL) OR
153                  (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR
154                  (bugs.reporter = $userid AND bugs.reporter_accessible = 1) OR
155                  (bugs.assigned_to = $userid) " .
156                  (Bugzilla->params->{'useqacontact'} ? "OR
157                  (bugs.qa_contact = $userid))" : ")");
158
159     unless ($user->is_insider) {
160         $query .= " AND (attachments.attach_id IS NULL
161                          OR attachments.isprivate = 0
162                          OR attachments.submitter_id = $userid)";
163     }
164
165     # Limit query to pending requests.
166     $query .= " AND flags.status = '?' " unless $status;
167
168     # The set of criteria by which we filter records to display in the queue.
169     my @criteria = ();
170
171     # A list of columns to exclude from the report because the report conditions
172     # limit the data being displayed to exact matches for those columns.
173     # In other words, if we are only displaying "pending" , we don't
174     # need to display a "status" column in the report because the value for that
175     # column will always be the same.
176     my @excluded_columns = ();
177     
178     # Filter requests by status: "pending", "granted", "denied", "all" 
179     # (which means any), or "fulfilled" (which means "granted" or "denied").
180     if ($status) {
181         if ($status eq "+-") {
182             push(@criteria, "flags.status IN ('+', '-')");
183             push(@excluded_columns, 'status') unless $cgi->param('do_union');
184         }
185         elsif ($status ne "all") {
186             push(@criteria, "flags.status = '$status'");
187             push(@excluded_columns, 'status') unless $cgi->param('do_union');
188         }
189     }
190     
191     # Filter results by exact email address of requester or requestee.
192     if (defined $cgi->param('requester') && $cgi->param('requester') ne "") {
193         my $requester = $dbh->quote($cgi->param('requester'));
194         trick_taint($requester); # Quoted above
195         push(@criteria, $dbh->sql_istrcmp('requesters.login_name', $requester));
196         push(@excluded_columns, 'requester') unless $cgi->param('do_union');
197     }
198     if (defined $cgi->param('requestee') && $cgi->param('requestee') ne "") {
199         if ($cgi->param('requestee') ne "-") {
200             my $requestee = $dbh->quote($cgi->param('requestee'));
201             trick_taint($requestee); # Quoted above
202             push(@criteria, $dbh->sql_istrcmp('requestees.login_name',
203                             $requestee));
204         }
205         else { push(@criteria, "flags.requestee_id IS NULL") }
206         push(@excluded_columns, 'requestee') unless $cgi->param('do_union');
207     }
208     
209     # Filter results by exact product or component.
210     if (defined $cgi->param('product') && $cgi->param('product') ne "") {
211         my $product = Bugzilla::Product->check(scalar $cgi->param('product'));
212         push(@criteria, "bugs.product_id = " . $product->id);
213         push(@excluded_columns, 'product') unless $cgi->param('do_union');
214         if (defined $cgi->param('component') && $cgi->param('component') ne "") {
215             my $component = Bugzilla::Component->check({ product => $product,
216                                                          name => scalar $cgi->param('component') });
217             push(@criteria, "bugs.component_id = " . $component->id);
218             push(@excluded_columns, 'component') unless $cgi->param('do_union');
219         }
220     }
221
222     # Filter results by flag types.
223     my $form_type = $cgi->param('type');
224     if (defined $form_type && !grep($form_type eq $_, ("", "all"))) {
225         # Check if any matching types are for attachments.  If not, don't show
226         # the attachment column in the report.
227         my $has_attachment_type =
228             Bugzilla::FlagType::count({ 'name' => $form_type,
229                                         'target_type' => 'attachment' });
230
231         if (!$has_attachment_type) { push(@excluded_columns, 'attachment') }
232
233         my $quoted_form_type = $dbh->quote($form_type);
234         trick_taint($quoted_form_type); # Already SQL quoted
235         push(@criteria, "flagtypes.name = " . $quoted_form_type);
236         push(@excluded_columns, 'type') unless $cgi->param('do_union');
237     }
238     
239     # Add the criteria to the query.  We do an intersection by default 
240     # but do a union if the "do_union" URL parameter (for which there is no UI 
241     # because it's an advanced feature that people won't usually want) is true.
242     my $and_or = $cgi->param('do_union') ? " OR " : " AND ";
243     $query .= " AND (" . join($and_or, @criteria) . ") " if scalar(@criteria);
244     
245     # Group the records by flag ID so we don't get multiple rows of data
246     # for each flag.  This is only necessary because of the code that
247     # removes flags on bugs the user is unauthorized to access.
248     $query .= ' ' . $dbh->sql_group_by('flags.id',
249                'flagtypes.name, flags.status, flags.bug_id, bugs.short_desc,
250                 products.name, components.name, flags.attach_id,
251                 attachments.description, requesters.realname,
252                 requesters.login_name, requestees.realname,
253                 requestees.login_name, flags.modification_date,
254                 cclist_accessible, bugs.reporter, bugs.reporter_accessible,
255                 bugs.assigned_to');
256
257     # Group the records, in other words order them by the group column
258     # so the loop in the display template can break them up into separate
259     # tables every time the value in the group column changes.
260
261     $form_group ||= "requestee";
262     if ($form_group eq "requester") {
263         $query .= " ORDER BY requesters.realname, requesters.login_name";
264     }
265     elsif ($form_group eq "requestee") {
266         $query .= " ORDER BY requestees.realname, requestees.login_name";
267     }
268     elsif ($form_group eq "category") {
269         $query .= " ORDER BY products.name, components.name";
270     }
271     elsif ($form_group eq "type") {
272         $query .= " ORDER BY flagtypes.name";
273     }
274
275     # Order the records (within each group).
276     $query .= " , flags.modification_date";
277
278     # Pass the query to the template for use when debugging this script.
279     $vars->{'query'} = $query;
280     $vars->{'debug'} = $cgi->param('debug') ? 1 : 0;
281     
282     my $results = $dbh->selectall_arrayref($query);
283     my @requests = ();
284     foreach my $result (@$results) {
285         my @data = @$result;
286         my $request = {
287           'id'              => $data[0] , 
288           'type'            => $data[1] , 
289           'status'          => $data[2] , 
290           'bug_id'          => $data[3] , 
291           'bug_summary'     => $data[4] , 
292           'category'        => "$data[5]: $data[6]" , 
293           'attach_id'       => $data[7] , 
294           'attach_summary'  => $data[8] ,
295           'requester'       => ($data[9] ? "$data[9] <$data[10]>" : $data[10]) , 
296           'requestee'       => ($data[11] ? "$data[11] <$data[12]>" : $data[12]) , 
297           'restricted'      => $data[13] ? 1 : 0,
298           'created'         => $data[14]
299         };
300         push(@requests, $request);
301     }
302
303     # Get a list of request type names to use in the filter form.
304     my @types = ("all");
305     my $flagtypes = get_flag_types();
306     push(@types, @$flagtypes);
307
308     $vars->{'excluded_columns'} = \@excluded_columns;
309     $vars->{'group_field'} = $form_group;
310     $vars->{'requests'} = \@requests;
311     $vars->{'types'} = \@types;
312
313     my %components;
314     foreach my $prod (@{$user->get_selectable_products}) {
315         foreach my $comp (@{$prod->components}) {
316             $components{$comp->name} = 1;
317         }
318     }
319     $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
320
321     # Generate and return the UI (HTML page) from the appropriate template.
322     $template->process("request/queue.html.tmpl", $vars)
323       || ThrowTemplateError($template->error());
324 }
325
326 ################################################################################
327 # Data Validation / Security Authorization
328 ################################################################################
329
330 sub validateStatus {
331     my $status = shift;
332     return if !defined $status;
333
334     grep($status eq $_, qw(? +- + - all))
335       || ThrowUserError("flag_status_invalid", { status => $status });
336     trick_taint($status);
337     return $status;
338 }
339
340 sub validateGroup {
341     my $group = shift;
342     return if !defined $group;
343
344     grep($group eq $_, qw(requester requestee category type))
345       || ThrowUserError("request_queue_group_invalid", { group => $group });
346     trick_taint($group);
347     return $group;
348 }
349
350 # Returns all flag types which have at least one flag of this type.
351 # If a flag type is inactive but still has flags, we want it.
352 sub get_flag_types {
353     my $dbh = Bugzilla->dbh;
354     my $flag_types = $dbh->selectcol_arrayref('SELECT DISTINCT name
355                                                  FROM flagtypes
356                                                 WHERE flagtypes.id IN
357                                                       (SELECT DISTINCT type_id FROM flags)
358                                              ORDER BY name');
359     return $flag_types;
360 }