| 1293 |
dpurdie |
1 |
###############################################################################
|
|
|
2 |
# Codestriker: Copyright (c) 2001, 2002 David Sitsky. All rights reserved.
|
|
|
3 |
# sits@users.sourceforge.net
|
|
|
4 |
#
|
|
|
5 |
# This program is free software; you can redistribute it and modify it under
|
|
|
6 |
# the terms of the GPL.
|
|
|
7 |
|
|
|
8 |
# Collection of routines for processing the HTTP input.
|
|
|
9 |
|
|
|
10 |
package Codestriker::Http::Input;
|
|
|
11 |
|
|
|
12 |
use strict;
|
|
|
13 |
use Encode qw(decode_utf8);
|
|
|
14 |
|
|
|
15 |
use CGI::Carp 'fatalsToBrowser';
|
|
|
16 |
|
|
|
17 |
use Codestriker::Http::Cookie;
|
|
|
18 |
|
|
|
19 |
sub _set_property_from_cookie( $$$ );
|
|
|
20 |
sub _untaint( $$$ );
|
|
|
21 |
sub _untaint_name( $$ );
|
|
|
22 |
sub _untaint_digits( $$ );
|
|
|
23 |
sub _untaint_filename( $$ );
|
|
|
24 |
sub _untaint_revision( $$ );
|
|
|
25 |
sub _untaint_email( $$ );
|
|
|
26 |
sub _untaint_emails( $$ );
|
|
|
27 |
sub _untaint_bug_ids( $$ );
|
|
|
28 |
sub _untaint_alphanumeric( $$ );
|
|
|
29 |
|
|
|
30 |
# Default valiue to set the context if it is not set.
|
|
|
31 |
my $DEFAULT_CONTEXT = 2;
|
|
|
32 |
|
|
|
33 |
# Constructor for this class.
|
|
|
34 |
sub new($$$) {
|
|
|
35 |
my ($type, $query, $http_response) = @_;
|
|
|
36 |
my $self = {};
|
|
|
37 |
$self->{query} = $query;
|
|
|
38 |
$self->{http_response} = $http_response;
|
|
|
39 |
return bless $self, $type;
|
|
|
40 |
}
|
|
|
41 |
|
|
|
42 |
# Process the CGI parameters, and untaint them. If any of them look
|
|
|
43 |
# suspicious, immediately output an error.
|
|
|
44 |
sub process($) {
|
|
|
45 |
my ($self) = @_;
|
|
|
46 |
|
|
|
47 |
my $query = $self->{query};
|
|
|
48 |
my %cookie = Codestriker::Http::Cookie->get($query);
|
|
|
49 |
|
|
|
50 |
# Retrieve all of the known Codestriker CGI parameters, and check they
|
|
|
51 |
# are valid.
|
|
|
52 |
$self->{action} = $query->param('action');
|
|
|
53 |
$self->{button} = $query->param('button');
|
|
|
54 |
$self->{topic} = $query->param('topic');
|
|
|
55 |
$self->{line} = $query->param('line');
|
|
|
56 |
$self->{fn} = $query->param('fn');
|
|
|
57 |
$self->{context} = $query->param('context');
|
|
|
58 |
$self->{action} = $query->param('action');
|
|
|
59 |
$self->{comments} = decode_utf8($query->param('comments'));
|
|
|
60 |
$self->{email} = $query->param('email');
|
|
|
61 |
$self->{author} = $query->param('author');
|
|
|
62 |
$self->{topic_text} = decode_utf8($query->param('topic_text'));
|
|
|
63 |
$self->{topic_title} = decode_utf8($query->param('topic_title'));
|
|
|
64 |
$self->{topic_description} = decode_utf8($query->param('topic_description'));
|
|
|
65 |
$self->{reviewers} = $query->param('reviewers');
|
|
|
66 |
$self->{cc} = $query->param('cc');
|
|
|
67 |
$self->{comment_cc} = $query->param('comment_cc');
|
|
|
68 |
$self->{topic_state} = decode_utf8($query->param('topic_state'));
|
|
|
69 |
$self->{comment_state} = decode_utf8($query->param('comment_state'));
|
|
|
70 |
$self->{revision} = $query->param('revision');
|
|
|
71 |
$self->{filename} = decode_utf8($query->param('filename'));
|
|
|
72 |
$self->{linenumber} = $query->param('linenumber');
|
|
|
73 |
$self->{mode} = $query->param('mode');
|
|
|
74 |
$self->{brmode} = $query->param('brmode');
|
|
|
75 |
$self->{fview} = $query->param('fview');
|
|
|
76 |
$self->{bug_ids} = $query->param('bug_ids');
|
|
|
77 |
$self->{new} = $query->param('new');
|
|
|
78 |
$self->{tabwidth} = $query->param('tabwidth');
|
|
|
79 |
$self->{sauthor} = $query->param('sauthor');
|
|
|
80 |
$self->{sreviewer} = $query->param('sreviewer');
|
|
|
81 |
$self->{scc} = $query->param('scc');
|
|
|
82 |
$self->{sbugid} = $query->param('sbugid');
|
|
|
83 |
$self->{stext} = decode_utf8($query->param('stext'));
|
|
|
84 |
$self->{stitle} = decode_utf8($query->param('stitle'));
|
|
|
85 |
$self->{sdescription} = decode_utf8($query->param('sdescription'));
|
|
|
86 |
$self->{scomments} = decode_utf8($query->param('scomments'));
|
|
|
87 |
$self->{sbody} = decode_utf8($query->param('sbody'));
|
|
|
88 |
$self->{sfilename} = decode_utf8($query->param('sfilename'));
|
|
|
89 |
$self->{sstate} = decode_utf8($query->param('sstate'));
|
|
|
90 |
$self->{sproject} = decode_utf8($query->param('sproject'));
|
|
|
91 |
$self->{scontext} = $query->param('scontext');
|
|
|
92 |
$self->{version} = $query->param('version');
|
|
|
93 |
$self->{redirect} = $query->param('redirect');
|
|
|
94 |
$self->{a} = $query->param('a');
|
|
|
95 |
$self->{updated} = decode_utf8($query->param('updated'));
|
|
|
96 |
$self->{repository} = decode_utf8($query->param('repository'));
|
|
|
97 |
$self->{parallel} = $query->param('parallel');
|
|
|
98 |
$self->{projectid} = $query->param('projectid');
|
|
|
99 |
$self->{project_name} = decode_utf8($query->param('project_name'));
|
|
|
100 |
$self->{project_description} = decode_utf8($query->param('project_description'));
|
|
|
101 |
$self->{project_state} = decode_utf8($query->param('project_state'));
|
|
|
102 |
$self->{start_tag} = decode_utf8($query->param('start_tag'));
|
|
|
103 |
$self->{end_tag} = decode_utf8($query->param('end_tag'));
|
|
|
104 |
$self->{module} = decode_utf8($query->param('module'));
|
|
|
105 |
$self->{topic_sort_change} = $query->param('topic_sort_change');
|
|
|
106 |
$self->{format} = $query->param('format');
|
|
|
107 |
$self->{obsoletes} = $query->param('obsoletes');
|
|
|
108 |
my @selected_topics = $query->param('selected_topics');
|
|
|
109 |
$self->{selected_topics} = \@selected_topics;
|
|
|
110 |
my @selected_comments = $query->param('selected_comments');
|
|
|
111 |
$self->{selected_comments} = \@selected_comments;
|
|
|
112 |
$self->{default_to_head} = $query->param('default_to_head');
|
|
|
113 |
|
|
|
114 |
# Set things to the empty string rather than undefined.
|
|
|
115 |
$self->{cc} = "" if ! defined $self->{cc};
|
|
|
116 |
$self->{reviewers} = "" if ! defined $self->{reviewers};
|
|
|
117 |
$self->{bug_ids} = "" if ! defined $self->{bug_ids};
|
|
|
118 |
$self->{sstate} = "" if ! defined $self->{sstate};
|
|
|
119 |
$self->{sproject} = "" if ! defined $self->{sproject};
|
|
|
120 |
$self->{sauthor} = "" if ! defined $self->{sauthor};
|
|
|
121 |
$self->{a} = "" if ! defined $self->{a};
|
|
|
122 |
$self->{updated} = 0 if ! defined $self->{updated};
|
|
|
123 |
$self->{repository} = "" if ! defined $self->{repository};
|
|
|
124 |
$self->{project_name} = "" if ! defined $self->{project_name};
|
|
|
125 |
$self->{project_description} = "" if ! defined $self->{project_description};
|
|
|
126 |
$self->{project_state} = "" if ! defined $self->{project_state};
|
|
|
127 |
$self->{topic_sort_change} = "" if ! defined $self->{topic_sort_change};
|
|
|
128 |
$self->{format} = "html" if ! defined $self->{format};
|
|
|
129 |
$self->{obsoletes} = "" if ! defined $self->{obsoletes};
|
|
|
130 |
$self->{default_to_head} = 0 if ! defined $self->{default_to_head};
|
|
|
131 |
|
|
|
132 |
my @topic_metrics = $query->param('topic_metric');
|
|
|
133 |
$self->{topic_metric} = \@topic_metrics;
|
|
|
134 |
|
|
|
135 |
my @author_metrics = $query->param('author_metric');
|
|
|
136 |
$self->{author_metric} = \@author_metrics;
|
|
|
137 |
|
|
|
138 |
for (my $userindex = 0; $userindex < 100; ++$userindex)
|
|
|
139 |
{
|
|
|
140 |
my @reviewer_metrics = $query->param("reviewer_metric,$userindex");
|
|
|
141 |
|
|
|
142 |
last if (scalar(@reviewer_metrics) == 0);
|
|
|
143 |
$self->{"reviewer_metric,$userindex"} = \@reviewer_metrics;
|
|
|
144 |
}
|
|
|
145 |
|
|
|
146 |
# Set the comment state metric data.
|
|
|
147 |
foreach my $comment_state_metric (@{$Codestriker::comment_state_metrics}) {
|
|
|
148 |
my $name = "comment_state_metric_" . $comment_state_metric->{name};
|
|
|
149 |
$self->{$name} = $query->param($name);
|
|
|
150 |
}
|
|
|
151 |
|
|
|
152 |
# Remove those annoying \r's in textareas.
|
|
|
153 |
if (defined $self->{topic_description}) {
|
|
|
154 |
$self->{topic_description} =~ s/\r//g;
|
|
|
155 |
} else {
|
|
|
156 |
$self->{topic_description} = "";
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
if (defined $self->{comments}) {
|
|
|
160 |
$self->{comments} =~ s/\r//g;
|
|
|
161 |
} else {
|
|
|
162 |
$self->{comments} = "";
|
|
|
163 |
}
|
|
|
164 |
|
|
|
165 |
# Record the file handler for a topic text upload, if any. Also record the
|
|
|
166 |
# mime type of the file if it has been set, default to text/plain
|
|
|
167 |
# otherwise.
|
|
|
168 |
# Note topic_file is forced to be a string to get the filename (and
|
|
|
169 |
# not have any confusion with the file object). CGI.pm weirdness.
|
|
|
170 |
if (defined $query->param('topic_file')) {
|
|
|
171 |
$self->{fh_filename} = "" . $query->param('topic_file');
|
|
|
172 |
}
|
|
|
173 |
else {
|
|
|
174 |
$self->{fh_filename} = undef;
|
|
|
175 |
}
|
|
|
176 |
$self->{fh} = $query->upload('topic_file');
|
|
|
177 |
$self->{fh_mime_type} = 'text/plain';
|
|
|
178 |
|
|
|
179 |
# This code doesn't work, it produces a warning like:
|
|
|
180 |
#
|
|
|
181 |
# Use of uninitialized value in hash element at (eval 34) line 3.
|
|
|
182 |
#
|
|
|
183 |
# Since mime-types aren't used yet, this code is skipped for now.
|
|
|
184 |
#
|
|
|
185 |
# if ((defined $self->{fh_filename})) {
|
|
|
186 |
# (defined $query->uploadInfo($query->param('topic_file'))) {
|
|
|
187 |
# $self->{fh_mime_type} =
|
|
|
188 |
# $query->uploadInfo($self->{fh_filename})->{'Content-Type'};
|
|
|
189 |
# }
|
|
|
190 |
|
|
|
191 |
# Set parameter values from the cookie if they are not set.
|
|
|
192 |
$self->_set_property_from_cookie('context', $DEFAULT_CONTEXT);
|
|
|
193 |
$self->_set_property_from_cookie('mode',
|
|
|
194 |
$Codestriker::default_topic_create_mode);
|
|
|
195 |
$self->_set_property_from_cookie('tabwidth',
|
|
|
196 |
$Codestriker::default_tabwidth);
|
|
|
197 |
$self->_set_property_from_cookie('fview',
|
|
|
198 |
$Codestriker::default_file_to_view);
|
|
|
199 |
$self->_set_property_from_cookie('email', "");
|
|
|
200 |
$self->_set_property_from_cookie('repository', "");
|
|
|
201 |
$self->_set_property_from_cookie('projectid', 0);
|
|
|
202 |
$self->_set_property_from_cookie('module', "");
|
|
|
203 |
$self->_set_property_from_cookie('topicsort', "");
|
|
|
204 |
|
|
|
205 |
$self->_untaint('topic_sort_change', '(title)|(author)|(created)|(state)');
|
|
|
206 |
|
|
|
207 |
# Untaint the required input.
|
|
|
208 |
$self->_untaint_name('action');
|
|
|
209 |
$self->_untaint_digits('topic');
|
|
|
210 |
$self->_untaint_digits('projectid');
|
|
|
211 |
$self->_untaint_email('email');
|
|
|
212 |
$self->_untaint_email('author');
|
|
|
213 |
$self->_untaint_emails('reviewers');
|
|
|
214 |
$self->_untaint_emails('cc');
|
|
|
215 |
$self->_untaint_filename('filename');
|
|
|
216 |
$self->_untaint_revision('revision');
|
|
|
217 |
$self->_untaint_bug_ids('bug_ids');
|
|
|
218 |
$self->_untaint_digits('new');
|
|
|
219 |
$self->_untaint_digits('tabwidth');
|
|
|
220 |
$self->_untaint_filename('start_tag');
|
|
|
221 |
$self->_untaint_filename('end_tag');
|
|
|
222 |
|
|
|
223 |
# VSS module names can be things like $/TestProject/Project-name, so
|
|
|
224 |
# this needs to be handled in a special way.
|
|
|
225 |
$self->_untaint('module', '\$?[-_\/\w\.\s]+');
|
|
|
226 |
|
|
|
227 |
$self->_untaint_digits('scontext');
|
|
|
228 |
$self->_untaint_comma_digits('sstate');
|
|
|
229 |
$self->_untaint_comma_digits('sproject');
|
|
|
230 |
$self->_untaint_comma_digits('obsoletes');
|
|
|
231 |
|
|
|
232 |
# Canonicalise the bug_ids and email list parameters if required.
|
|
|
233 |
$self->{reviewers} = $self->make_canonical_email_list($self->{reviewers});
|
|
|
234 |
$self->{cc} = $self->make_canonical_email_list($self->{cc});
|
|
|
235 |
$self->{bug_ids} = $self->make_canonical_bug_list($self->{bug_ids});
|
|
|
236 |
$self->{comment_cc} = $self->make_canonical_email_list($self->{comment_cc});
|
|
|
237 |
|
|
|
238 |
# Initialise the feedback field to empty.
|
|
|
239 |
$self->{feedback} = "";
|
|
|
240 |
}
|
|
|
241 |
|
|
|
242 |
# Return the query object associated with this object.
|
|
|
243 |
sub get_query($) {
|
|
|
244 |
my ($self) = @_;
|
|
|
245 |
|
|
|
246 |
return $self->{query};
|
|
|
247 |
}
|
|
|
248 |
|
|
|
249 |
# Return the specified parameter.
|
|
|
250 |
sub get($$) {
|
|
|
251 |
my ($self, $param) = @_;
|
|
|
252 |
|
|
|
253 |
return $self->{$param};
|
|
|
254 |
}
|
|
|
255 |
|
|
|
256 |
# Given a list of email addresses separated by commas and spaces, return
|
|
|
257 |
# a canonical form, where they are separated by a comma and a space.
|
|
|
258 |
sub make_canonical_email_list($$) {
|
|
|
259 |
my ($type, $emails) = @_;
|
|
|
260 |
|
|
|
261 |
if (defined $emails && $emails ne "") {
|
|
|
262 |
# Chew off white space that is around the emails addresses.
|
|
|
263 |
$emails =~ s/^[\s]*//;
|
|
|
264 |
$emails =~ s/[\s]*$//;
|
|
|
265 |
|
|
|
266 |
return join ', ', split /[\s,;]+/, $emails;
|
|
|
267 |
} else {
|
|
|
268 |
return $emails;
|
|
|
269 |
}
|
|
|
270 |
}
|
|
|
271 |
|
|
|
272 |
# Given a list of bug ids separated by commas and spaces, return
|
|
|
273 |
# a canonical form, where they are separated by a comma and a space.
|
|
|
274 |
sub make_canonical_bug_list($$) {
|
|
|
275 |
my ($type, $bugs) = @_;
|
|
|
276 |
|
|
|
277 |
if (defined $bugs && $bugs ne "") {
|
|
|
278 |
return join ', ', split /[\s,;]+/, $bugs;
|
|
|
279 |
} else {
|
|
|
280 |
return "";
|
|
|
281 |
}
|
|
|
282 |
}
|
|
|
283 |
|
|
|
284 |
# Set the specified property from the cookie if it is not set. If the cookie
|
|
|
285 |
# is not set, use the supplied default value.
|
|
|
286 |
sub _set_property_from_cookie($$$) {
|
|
|
287 |
my ($self, $name, $default) = @_;
|
|
|
288 |
|
|
|
289 |
my %cookie = Codestriker::Http::Cookie->get($self->{query});
|
|
|
290 |
if (! defined $self->{$name} || $self->{$name} eq "") {
|
|
|
291 |
$self->{$name} = exists $cookie{$name} ? $cookie{$name} : $default;
|
|
|
292 |
}
|
|
|
293 |
}
|
|
|
294 |
|
|
|
295 |
# Untaint the specified property, against the expected regular expression.
|
|
|
296 |
# Remove leading and trailing whitespace.
|
|
|
297 |
sub _untaint($$$) {
|
|
|
298 |
my ($self, $name, $regexp) = @_;
|
|
|
299 |
|
|
|
300 |
my $value = $self->{$name};
|
|
|
301 |
if (defined $value && $value ne "") {
|
|
|
302 |
if ($value =~ /^\s*(${regexp})\s*$/) {
|
|
|
303 |
# Untaint the value.
|
|
|
304 |
$self->{$name} = $1;
|
|
|
305 |
} else {
|
|
|
306 |
my $error_message = "Input parameter $name has invalid value: " .
|
|
|
307 |
"\"$value\"";
|
|
|
308 |
$self->{http_response}->error($error_message);
|
|
|
309 |
}
|
|
|
310 |
} else {
|
|
|
311 |
$self->{$name} = "";
|
|
|
312 |
}
|
|
|
313 |
}
|
|
|
314 |
|
|
|
315 |
# Untaint a parameter which should be a bunch of alphabetical characters and
|
|
|
316 |
# underscores.
|
|
|
317 |
sub _untaint_name($$) {
|
|
|
318 |
my ($self, $name) = @_;
|
|
|
319 |
|
|
|
320 |
$self->_untaint($name, '[A-Za-z_]+');
|
|
|
321 |
}
|
|
|
322 |
|
|
|
323 |
# Untaint a parameter which should be a bunch of digits.
|
|
|
324 |
sub _untaint_digits($$) {
|
|
|
325 |
my ($self, $name) = @_;
|
|
|
326 |
|
|
|
327 |
$self->_untaint($name, '\d+');
|
|
|
328 |
}
|
|
|
329 |
|
|
|
330 |
# Untaint a parameter which should be a valid filename.
|
|
|
331 |
sub _untaint_filename($$) {
|
|
|
332 |
my ($self, $name) = @_;
|
|
|
333 |
|
|
|
334 |
$self->_untaint($name, '[-_\/\@\w\.\s]+');
|
|
|
335 |
}
|
|
|
336 |
|
|
|
337 |
# Untaint a parameter that should be a revision number.
|
|
|
338 |
sub _untaint_revision($$) {
|
|
|
339 |
my ($self, $name) = @_;
|
|
|
340 |
|
|
|
341 |
$self->_untaint($name, '[\d\.]+');
|
|
|
342 |
}
|
|
|
343 |
|
|
|
344 |
# Untaint a parameter that should be a comma separated list of digits.
|
|
|
345 |
sub _untaint_comma_digits($$) {
|
|
|
346 |
my ($self, $name) = @_;
|
|
|
347 |
|
|
|
348 |
$self->_untaint($name, '[\d\,]+');
|
|
|
349 |
}
|
|
|
350 |
|
|
|
351 |
# Untaint a single email address, which should be a regular email address.
|
|
|
352 |
sub _untaint_email($$) {
|
|
|
353 |
my ($self, $name) = @_;
|
|
|
354 |
|
|
|
355 |
$self->_untaint($name, '[\s]*[-_\w\.]{1,200}(\@[-_\w\.]{1,200})?[\s]*');
|
|
|
356 |
}
|
|
|
357 |
|
|
|
358 |
# Untaint a list of email addresses.
|
|
|
359 |
sub _untaint_emails($$) {
|
|
|
360 |
my ($self, $name) = @_;
|
|
|
361 |
|
|
|
362 |
$self->_untaint($name, '[\s]*([-_\w\.]{1,200}(\@[-_\w\.]{1,200})?[\s,;]*){1,100}[\s]*');
|
|
|
363 |
}
|
|
|
364 |
|
|
|
365 |
# Untaint a list of bug ids.
|
|
|
366 |
sub _untaint_bug_ids($$) {
|
|
|
367 |
my ($self, $name) = @_;
|
|
|
368 |
|
|
|
369 |
$self->_untaint($name, '([0-9]+[\s,;]*){1,100}');
|
|
|
370 |
}
|
|
|
371 |
|
|
|
372 |
1;
|