Rev 1293 | Blame | Last modification | View Log | RSS feed
################################################################################ Codestriker: Copyright (c) 2001, 2002 David Sitsky. All rights reserved.# sits@users.sourceforge.net## This program is free software; you can redistribute it and modify it under# the terms of the GPL.# Collection of routines for processing the HTTP input.package Codestriker::Http::Input;use strict;use Encode qw(decode_utf8);use CGI::Carp 'fatalsToBrowser';use Codestriker::Http::Cookie;sub _set_property_from_cookie( $$$ );sub _untaint( $$$ );sub _untaint_name( $$ );sub _untaint_digits( $$ );sub _untaint_filename( $$ );sub _untaint_revision( $$ );sub _untaint_email( $$ );sub _untaint_emails( $$ );sub _untaint_bug_ids( $$ );sub _untaint_alphanumeric( $$ );# Default valiue to set the context if it is not set.my $DEFAULT_CONTEXT = 2;# Constructor for this class.sub new($$$) {my ($type, $query, $http_response) = @_;my $self = {};$self->{query} = $query;$self->{http_response} = $http_response;return bless $self, $type;}# Process the CGI parameters, and untaint them. If any of them look# suspicious, immediately output an error.sub process($) {my ($self) = @_;my $query = $self->{query};my %cookie = Codestriker::Http::Cookie->get($query);# Retrieve all of the known Codestriker CGI parameters, and check they# are valid.$self->{action} = $query->param('action');$self->{button} = $query->param('button');$self->{topic} = $query->param('topic');$self->{line} = $query->param('line');$self->{fn} = $query->param('fn');$self->{context} = $query->param('context');$self->{action} = $query->param('action');$self->{comments} = decode_utf8($query->param('comments'));$self->{email} = $query->param('email');$self->{author} = $query->param('author');$self->{topic_text} = decode_utf8($query->param('topic_text'));$self->{topic_title} = decode_utf8($query->param('topic_title'));$self->{topic_description} = decode_utf8($query->param('topic_description'));$self->{reviewers} = $query->param('reviewers');$self->{cc} = $query->param('cc');$self->{comment_cc} = $query->param('comment_cc');$self->{topic_state} = decode_utf8($query->param('topic_state'));$self->{comment_state} = decode_utf8($query->param('comment_state'));$self->{revision} = $query->param('revision');$self->{filename} = decode_utf8($query->param('filename'));$self->{linenumber} = $query->param('linenumber');$self->{mode} = $query->param('mode');$self->{brmode} = $query->param('brmode');$self->{fview} = $query->param('fview');$self->{bug_ids} = $query->param('bug_ids');$self->{new} = $query->param('new');$self->{tabwidth} = $query->param('tabwidth');$self->{sauthor} = $query->param('sauthor');$self->{sreviewer} = $query->param('sreviewer');$self->{scc} = $query->param('scc');$self->{sbugid} = $query->param('sbugid');$self->{stext} = decode_utf8($query->param('stext'));$self->{stitle} = decode_utf8($query->param('stitle'));$self->{sdescription} = decode_utf8($query->param('sdescription'));$self->{scomments} = decode_utf8($query->param('scomments'));$self->{sbody} = decode_utf8($query->param('sbody'));$self->{sfilename} = decode_utf8($query->param('sfilename'));$self->{sstate} = decode_utf8($query->param('sstate'));$self->{sproject} = decode_utf8($query->param('sproject'));$self->{scontext} = $query->param('scontext');$self->{version} = $query->param('version');$self->{redirect} = $query->param('redirect');$self->{a} = $query->param('a');$self->{updated} = decode_utf8($query->param('updated'));$self->{repository} = decode_utf8($query->param('repository'));$self->{parallel} = $query->param('parallel');$self->{projectid} = $query->param('projectid');$self->{project_name} = decode_utf8($query->param('project_name'));$self->{project_description} = decode_utf8($query->param('project_description'));$self->{project_state} = decode_utf8($query->param('project_state'));$self->{start_tag} = decode_utf8($query->param('start_tag'));$self->{end_tag} = decode_utf8($query->param('end_tag'));$self->{module} = decode_utf8($query->param('module'));$self->{topic_sort_change} = $query->param('topic_sort_change');$self->{format} = $query->param('format');$self->{obsoletes} = $query->param('obsoletes');my @selected_topics = $query->param('selected_topics');$self->{selected_topics} = \@selected_topics;my @selected_comments = $query->param('selected_comments');$self->{selected_comments} = \@selected_comments;$self->{default_to_head} = $query->param('default_to_head');# Set things to the empty string rather than undefined.$self->{cc} = "" if ! defined $self->{cc};$self->{reviewers} = "" if ! defined $self->{reviewers};$self->{bug_ids} = "" if ! defined $self->{bug_ids};$self->{sstate} = "" if ! defined $self->{sstate};$self->{sproject} = "" if ! defined $self->{sproject};$self->{sauthor} = "" if ! defined $self->{sauthor};$self->{a} = "" if ! defined $self->{a};$self->{updated} = 0 if ! defined $self->{updated};$self->{repository} = "" if ! defined $self->{repository};$self->{project_name} = "" if ! defined $self->{project_name};$self->{project_description} = "" if ! defined $self->{project_description};$self->{project_state} = "" if ! defined $self->{project_state};$self->{topic_sort_change} = "" if ! defined $self->{topic_sort_change};$self->{format} = "html" if ! defined $self->{format};$self->{obsoletes} = "" if ! defined $self->{obsoletes};$self->{default_to_head} = 0 if ! defined $self->{default_to_head};my @topic_metrics = $query->param('topic_metric');$self->{topic_metric} = \@topic_metrics;my @author_metrics = $query->param('author_metric');$self->{author_metric} = \@author_metrics;for (my $userindex = 0; $userindex < 100; ++$userindex){my @reviewer_metrics = $query->param("reviewer_metric,$userindex");last if (scalar(@reviewer_metrics) == 0);$self->{"reviewer_metric,$userindex"} = \@reviewer_metrics;}# Set the comment state metric data.foreach my $comment_state_metric (@{$Codestriker::comment_state_metrics}) {my $name = "comment_state_metric_" . $comment_state_metric->{name};$self->{$name} = $query->param($name);}# Remove those annoying \r's in textareas.if (defined $self->{topic_description}) {$self->{topic_description} =~ s/\r//g;} else {$self->{topic_description} = "";}if (defined $self->{comments}) {$self->{comments} =~ s/\r//g;} else {$self->{comments} = "";}# Record the file handler for a topic text upload, if any. Also record the# mime type of the file if it has been set, default to text/plain# otherwise.# Note topic_file is forced to be a string to get the filename (and# not have any confusion with the file object). CGI.pm weirdness.if (defined $query->param('topic_file')) {$self->{fh_filename} = "" . $query->param('topic_file');}else {$self->{fh_filename} = undef;}$self->{fh} = $query->upload('topic_file');$self->{fh_mime_type} = 'text/plain';# This code doesn't work, it produces a warning like:## Use of uninitialized value in hash element at (eval 34) line 3.## Since mime-types aren't used yet, this code is skipped for now.## if ((defined $self->{fh_filename})) {# (defined $query->uploadInfo($query->param('topic_file'))) {# $self->{fh_mime_type} =# $query->uploadInfo($self->{fh_filename})->{'Content-Type'};# }# Set parameter values from the cookie if they are not set.$self->_set_property_from_cookie('context', $DEFAULT_CONTEXT);$self->_set_property_from_cookie('mode',$Codestriker::default_topic_create_mode);$self->_set_property_from_cookie('tabwidth',$Codestriker::default_tabwidth);$self->_set_property_from_cookie('fview',$Codestriker::default_file_to_view);$self->_set_property_from_cookie('email', "");$self->_set_property_from_cookie('repository', "");$self->_set_property_from_cookie('projectid', 0);$self->_set_property_from_cookie('module', "");$self->_set_property_from_cookie('topicsort', "");$self->_untaint('topic_sort_change', '(title)|(author)|(created)|(state)');# Untaint the required input.$self->_untaint_name('action');$self->_untaint_digits('topic');$self->_untaint_digits('projectid');$self->_untaint_email('email');$self->_untaint_email('author');$self->_untaint_emails('reviewers');$self->_untaint_emails('cc');$self->_untaint_filename('filename');$self->_untaint_revision('revision');$self->_untaint_bug_ids('bug_ids');$self->_untaint_digits('new');$self->_untaint_digits('tabwidth');$self->_untaint_filename('start_tag');$self->_untaint_filename('end_tag');# VSS module names can be things like $/TestProject/Project-name, so# this needs to be handled in a special way.$self->_untaint('module', '\$?[-_\/\w\.\s]+');$self->_untaint_digits('scontext');$self->_untaint_comma_digits('sstate');$self->_untaint_comma_digits('sproject');$self->_untaint_comma_digits('obsoletes');# Canonicalise the bug_ids and email list parameters if required.$self->{reviewers} = $self->make_canonical_email_list($self->{reviewers});$self->{cc} = $self->make_canonical_email_list($self->{cc});$self->{bug_ids} = $self->make_canonical_bug_list($self->{bug_ids});$self->{comment_cc} = $self->make_canonical_email_list($self->{comment_cc});# Initialise the feedback field to empty.$self->{feedback} = "";}# Return the query object associated with this object.sub get_query($) {my ($self) = @_;return $self->{query};}# Return the specified parameter.sub get($$) {my ($self, $param) = @_;return $self->{$param};}# Given a list of email addresses separated by commas and spaces, return# a canonical form, where they are separated by a comma and a space.sub make_canonical_email_list($$) {my ($type, $emails) = @_;if (defined $emails && $emails ne "") {# Chew off white space that is around the emails addresses.$emails =~ s/^[\s]*//;$emails =~ s/[\s]*$//;return join ', ', split /[\s,;]+/, $emails;} else {return $emails;}}# Given a list of bug ids separated by commas and spaces, return# a canonical form, where they are separated by a comma and a space.sub make_canonical_bug_list($$) {my ($type, $bugs) = @_;if (defined $bugs && $bugs ne "") {return join ', ', split /[\s,;]+/, $bugs;} else {return "";}}# Set the specified property from the cookie if it is not set. If the cookie# is not set, use the supplied default value.sub _set_property_from_cookie($$$) {my ($self, $name, $default) = @_;my %cookie = Codestriker::Http::Cookie->get($self->{query});if (! defined $self->{$name} || $self->{$name} eq "") {$self->{$name} = exists $cookie{$name} ? $cookie{$name} : $default;}}# Untaint the specified property, against the expected regular expression.# Remove leading and trailing whitespace.sub _untaint($$$) {my ($self, $name, $regexp) = @_;my $value = $self->{$name};if (defined $value && $value ne "") {if ($value =~ /^\s*(${regexp})\s*$/) {# Untaint the value.$self->{$name} = $1;} else {my $error_message = "Input parameter $name has invalid value: " ."\"$value\"";$self->{http_response}->error($error_message);}} else {$self->{$name} = "";}}# Untaint a parameter which should be a bunch of alphabetical characters and# underscores.sub _untaint_name($$) {my ($self, $name) = @_;$self->_untaint($name, '[A-Za-z_]+');}# Untaint a parameter which should be a bunch of digits.sub _untaint_digits($$) {my ($self, $name) = @_;$self->_untaint($name, '\d+');}# Untaint a parameter which should be a valid filename.sub _untaint_filename($$) {my ($self, $name) = @_;$self->_untaint($name, '[-_\/\@\w\.\s]+');}# Untaint a parameter that should be a revision number.sub _untaint_revision($$) {my ($self, $name) = @_;$self->_untaint($name, '[\d\.]+');}# Untaint a parameter that should be a comma separated list of digits.sub _untaint_comma_digits($$) {my ($self, $name) = @_;$self->_untaint($name, '[\d\,]+');}# Untaint a single email address, which should be a regular email address.sub _untaint_email($$) {my ($self, $name) = @_;$self->_untaint($name, '[\s]*[-_\w\.]{1,200}(\@[-_\w\.]{1,200})?[\s]*');}# Untaint a list of email addresses.sub _untaint_emails($$) {my ($self, $name) = @_;$self->_untaint($name, '[\s]*([-_\w\.]{1,200}(\@[-_\w\.]{1,200})?[\s,;]*){1,100}[\s]*');}# Untaint a list of bug ids.sub _untaint_bug_ids($$) {my ($self, $name) = @_;$self->_untaint($name, '([0-9]+[\s,;]*){1,100}');}1;