Subversion Repositories DevTools

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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;