(CVE-2013-0786) [SECURITY] build_subselect() leaks the existence of products and...
[WebKit-https.git] / Websites / bugs.webkit.org / Bugzilla / Series.pm
1 # -*- Mode: perl; indent-tabs-mode: nil -*-
2 #
3 # The contents of this file are subject to the Mozilla Public
4 # License Version 1.1 (the "License"); you may not use this file
5 # except in compliance with the License. You may obtain a copy of
6 # the License at http://www.mozilla.org/MPL/
7 #
8 # Software distributed under the License is distributed on an "AS
9 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10 # implied. See the License for the specific language governing
11 # rights and limitations under the License.
12 #
13 # The Original Code is the Bugzilla Bug Tracking System.
14 #
15 # The Initial Developer of the Original Code is Netscape Communications
16 # Corporation. Portions created by Netscape are
17 # Copyright (C) 1998 Netscape Communications Corporation. All
18 # Rights Reserved.
19 #
20 # Contributor(s): Gervase Markham <gerv@gerv.net>
21 #                 Lance Larsh <lance.larsh@oracle.com>
22
23 use strict;
24
25 # This module implements a series - a set of data to be plotted on a chart.
26 #
27 # This Series is in the database if and only if self->{'series_id'} is defined. 
28 # Note that the series being in the database does not mean that the fields of 
29 # this object are the same as the DB entries, as the object may have been 
30 # altered.
31
32 package Bugzilla::Series;
33
34 use Bugzilla::Error;
35 use Bugzilla::Util;
36 use Bugzilla::User;
37
38 sub new {
39     my $invocant = shift;
40     my $class = ref($invocant) || $invocant;
41   
42     # Create a ref to an empty hash and bless it
43     my $self = {};
44     bless($self, $class);
45
46     my $arg_count = scalar(@_);
47     
48     # new() can return undef if you pass in a series_id and the user doesn't 
49     # have sufficient permissions. If you create a new series in this way,
50     # you need to check for an undef return, and act appropriately.
51     my $retval = $self;
52
53     # There are three ways of creating Series objects. Two (CGI and Parameters)
54     # are for use when creating a new series. One (Database) is for retrieving
55     # information on existing series.
56     if ($arg_count == 1) {
57         if (ref($_[0])) {
58             # We've been given a CGI object to create a new Series from.
59             # This series may already exist - external code needs to check
60             # before it calls writeToDatabase().
61             $self->initFromCGI($_[0]);
62         }
63         else {
64             # We've been given a series_id, which should represent an existing
65             # Series.
66             $retval = $self->initFromDatabase($_[0]);
67         }
68     }
69     elsif ($arg_count >= 6 && $arg_count <= 8) {
70         # We've been given a load of parameters to create a new Series from.
71         # Currently, undef is always passed as the first parameter; this allows
72         # you to call writeToDatabase() unconditionally. 
73         $self->initFromParameters(@_);
74     }
75     else {
76         die("Bad parameters passed in - invalid number of args: $arg_count");
77     }
78
79     return $retval;
80 }
81
82 sub initFromDatabase {
83     my $self = shift;
84     my $series_id = shift;
85     
86     detaint_natural($series_id) 
87       || ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
88     
89     my $dbh = Bugzilla->dbh;
90     my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
91         "cc2.name, series.name, series.creator, series.frequency, " .
92         "series.query, series.is_public " .
93         "FROM series " .
94         "LEFT JOIN series_categories AS cc1 " .
95         "    ON series.category = cc1.id " .
96         "LEFT JOIN series_categories AS cc2 " .
97         "    ON series.subcategory = cc2.id " .
98         "LEFT JOIN category_group_map AS cgm " .
99         "    ON series.category = cgm.category_id " .
100         "LEFT JOIN user_group_map AS ugm " .
101         "    ON cgm.group_id = ugm.group_id " .
102         "    AND ugm.user_id = " . Bugzilla->user->id .
103         "    AND isbless = 0 " .
104         "WHERE series.series_id = $series_id AND " .
105         "(is_public = 1 OR creator = " . Bugzilla->user->id . " OR " .
106         "(ugm.group_id IS NOT NULL)) " . 
107         $dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
108                            'series.name, series.creator, series.frequency, ' .
109                            'series.query, series.is_public'));
110     
111     if (@series) {
112         $self->initFromParameters(@series);
113         return $self;
114     }
115     else {
116         return undef;
117     }
118 }
119
120 sub initFromParameters {
121     # Pass undef as the first parameter if you are creating a new series.
122     my $self = shift;
123
124     ($self->{'series_id'}, $self->{'category'},  $self->{'subcategory'},
125      $self->{'name'}, $self->{'creator'}, $self->{'frequency'},
126      $self->{'query'}, $self->{'public'}) = @_;
127
128     # If the first parameter is undefined, check if this series already
129     # exists and update it series_id accordingly
130     $self->{'series_id'} ||= $self->existsInDatabase();
131 }
132
133 sub initFromCGI {
134     my $self = shift;
135     my $cgi = shift;
136
137     $self->{'series_id'} = $cgi->param('series_id') || undef;
138     if (defined($self->{'series_id'})) {
139         detaint_natural($self->{'series_id'})
140           || ThrowCodeError("invalid_series_id", 
141                                { 'series_id' => $self->{'series_id'} });
142     }
143     
144     $self->{'category'} = $cgi->param('category')
145       || $cgi->param('newcategory')
146       || ThrowUserError("missing_category");
147
148     $self->{'subcategory'} = $cgi->param('subcategory')
149       || $cgi->param('newsubcategory')
150       || ThrowUserError("missing_subcategory");
151
152     $self->{'name'} = $cgi->param('name')
153       || ThrowUserError("missing_name");
154
155     $self->{'creator'} = Bugzilla->user->id;
156
157     $self->{'frequency'} = $cgi->param('frequency');
158     detaint_natural($self->{'frequency'})
159       || ThrowUserError("missing_frequency");
160
161     $self->{'query'} = $cgi->canonicalise_query("format", "ctype", "action",
162                                         "category", "subcategory", "name",
163                                         "frequency", "public", "query_format");
164     trick_taint($self->{'query'});
165                                         
166     $self->{'public'} = $cgi->param('public') ? 1 : 0;
167     
168     # Change 'admin' here and in series.html.tmpl, or remove the check
169     # completely, if you want to change who can make series public.
170     $self->{'public'} = 0 unless Bugzilla->user->in_group('admin');
171 }
172
173 sub writeToDatabase {
174     my $self = shift;
175
176     my $dbh = Bugzilla->dbh;
177     $dbh->bz_start_transaction();
178
179     my $category_id = getCategoryID($self->{'category'});
180     my $subcategory_id = getCategoryID($self->{'subcategory'});
181
182     my $exists;
183     if ($self->{'series_id'}) { 
184         $exists = 
185             $dbh->selectrow_array("SELECT series_id FROM series
186                                    WHERE series_id = $self->{'series_id'}");
187     }
188     
189     # Is this already in the database?                              
190     if ($exists) {
191         # Update existing series
192         my $dbh = Bugzilla->dbh;
193         $dbh->do("UPDATE series SET " .
194                  "category = ?, subcategory = ?," .
195                  "name = ?, frequency = ?, is_public = ?  " .
196                  "WHERE series_id = ?", undef,
197                  $category_id, $subcategory_id, $self->{'name'},
198                  $self->{'frequency'}, $self->{'public'}, 
199                  $self->{'series_id'});
200     }
201     else {
202         # Insert the new series into the series table
203         $dbh->do("INSERT INTO series (creator, category, subcategory, " .
204                  "name, frequency, query, is_public) VALUES " . 
205                  "(?, ?, ?, ?, ?, ?, ?)", undef,
206                  $self->{'creator'}, $category_id, $subcategory_id, $self->{'name'},
207                  $self->{'frequency'}, $self->{'query'}, $self->{'public'});
208
209         # Retrieve series_id
210         $self->{'series_id'} = $dbh->selectrow_array("SELECT MAX(series_id) " .
211                                                      "FROM series");
212         $self->{'series_id'}
213           || ThrowCodeError("missing_series_id", { 'series' => $self });
214     }
215     
216     $dbh->bz_commit_transaction();
217 }
218
219 # Check whether a series with this name, category and subcategory exists in
220 # the DB and, if so, returns its series_id.
221 sub existsInDatabase {
222     my $self = shift;
223     my $dbh = Bugzilla->dbh;
224
225     my $category_id = getCategoryID($self->{'category'});
226     my $subcategory_id = getCategoryID($self->{'subcategory'});
227     
228     trick_taint($self->{'name'});
229     my $series_id = $dbh->selectrow_array("SELECT series_id " .
230                               "FROM series WHERE category = $category_id " .
231                               "AND subcategory = $subcategory_id AND name = " .
232                               $dbh->quote($self->{'name'}));
233                               
234     return($series_id);
235 }
236
237 # Get a category or subcategory IDs, creating the category if it doesn't exist.
238 sub getCategoryID {
239     my ($category) = @_;
240     my $category_id;
241     my $dbh = Bugzilla->dbh;
242
243     # This seems for the best idiom for "Do A. Then maybe do B and A again."
244     while (1) {
245         # We are quoting this to put it in the DB, so we can remove taint
246         trick_taint($category);
247
248         $category_id = $dbh->selectrow_array("SELECT id " .
249                                       "from series_categories " .
250                                       "WHERE name =" . $dbh->quote($category));
251
252         last if defined($category_id);
253
254         $dbh->do("INSERT INTO series_categories (name) " .
255                  "VALUES (" . $dbh->quote($category) . ")");
256     }
257
258     return $category_id;
259 }
260
261 1;