ACCC Home Page ACADEMIC COMPUTING and COMMUNICATIONS CENTER
Accounts / Passwords Email Labs / Classrooms Telecom Software Computing and Network Services Education / Teaching Getting Help
 
Seminar - Perl II
0. Contents 1. Intro 2. Sources 3. References 4. Packages 5. Classes
6. Ties & DBM 7. Typeglobs 8. CGI 9. Security 10. Example  

Example

 

This long example is a CGI script that allows a select set of students to upload assignments and to check grades. It illustrates CGI security, form processing, bluestem, file upload, dbm files, and object-oriented programming.

Note This is just an example. There are ample opportunities for the reader to improve this code. For example, error reporting is minimal. Or there should be a better object orientation throughout (hint: create a Grades class). Or creating a dispatch table that uses function pointers rather than if..then..else structure. And additional funtionality for inserting grades would be useful. Caveat emptor.

#!/usr/local/bin/perl


use CGI;
use IO::File;

#
#-------------Configuration Parameters ----------------------
#
## These would be better off in some separate config file,
## and read in with 'do file'
##
    $Student::DIR     = '/homes/home1/bobg/grades'; #directory for files
    $Student::GRADES  = 'grades';		    # dbm file
    $A_MAX 	      = 10000;		    # Assignments are 10kb max
    %assignments = (			    # Allowed assignments
	a1 => 'Assignment 1',
	a2 => 'Assignment 2',
	a3 => 'Assignment 3',
	a4 => 'Assignment 4',
	a5 => 'Assignment 5',
    );

    @students = qw( moe larry curly bobg);  # Authorized netids
#
#---------------End Configuration------------------------------
#

    $ENV{PATH} = "";        # security
    print_header();	    # Beginning of HTML response

#
# $url is needed for return links in emitted html.
# Note portable treatment, not dependent on current directory
#
    my $url = "https://$ENV{SERVER_NAME}$ENV{SCRIPT_NAME}";

		## untaint netid from ENV
    $ENV{REMOTE_USER} =~ /([\w-]{2,8})/;
    $netid = $1;

    if ( $netid and grep (/^$netid$/, @students) ) {
	$parse = new CGI;	        # Initialize form parsing

		## Dispatch table
	$action = $parse->param('action');
	if ($action eq 'upload') {
	    upload( $netid );		# Allow uploading
	}
	elsif ($action eq 'check') {
	    check_grades( $netid );     # Print grades
	}
	else {
	    input_form();		# Send out initial form
	}
    }
    else {
	$url =~ s#perlwrap/#perlwrap-auth/#;	# Just in case
	print "<h1>BAD, Bad,  bad</h1>\n";
	print "<p>Hey, you didn't use bluestem or you are not authorized!\n";
	print "<a href=\"$url\">Try this.</a>\n";
    }

    print "</body></html>\n";
    exit;

#########################################
sub upload {
    my $netid = shift;

    my $a = $parse->param('assignment');
    $a =~ /(\w+)/;
    $a = $1;   # untainted

	    ## Make sure user doesn't alter form
    unless ( exists($assignments{$a})  ) {
	print "Don't be naughty. Choose a valid assignment.\n";
	return;
    }

    my $fname = $parse->param('filename');
    my $bytes_read = read($fname, $file, $A_MAX);
    if ($bytes_read >= $A_MAX) {
	print "You're assignment is too big\n";
	return;
    }
    if ($bytes_read == 0) {
	print "I couldn't read the uploaded file\n";
	return;
    }
    $file =~ /^(.*)$/s;     ## Note anchors and multiline treatment.
    $file = $1;	    	    ## Untainted.  Is this a security risk?

    $s = new Student ($netid);
    $s->add($a, $file);
}

#########################################
sub check_grades {
    my $netid = shift;

    my $s = new Student ($netid);
    my $g = $s->list;

    print "<table>\n<tr><th>Assignment</th><th>Date</th><th>Grade</th>\n";
    foreach my $a ( @$g ) {
	print "<tr>";
	foreach my $e ( @$a ) {
	    print "<td>$e</td>";
	}
	print "</tr>\n";
    }
    print "</table>\n";
}



#################################################3
sub input_form {
    print <<EOF;
<h1>Dr. Bob's Grades</h1>
<p>
Check your grades or upload a new assignment.
But hey! no cheating.
<hr>
<a href="$url?action=check">
Check your current grades</a>
<hr>
Upload a new assignment. 
<form method=post enctype="multipart/form-data" action="$url">
Assignment:
<input type=hidden name=action value=upload>
<select name=assignment size=1>
<option value=choose>Choose Assignment
EOF

    foreach my $k (sort keys %assignments) {
	print "<option value=$k>$assignments{$k}\n";
    }

    print <<EOF;
</select>
<br>
Local File:
<input type=file name=filename size=15>
<br>
<input type=submit value="Submit assignment">
</form>
EOF
}

#######################################
sub print_header {
    print <<EOF;
Content-type: text/html

<html>
<head><title>CGI Example</title></head>
<body bgcolor=#FFFFFF>
EOF
}

########################################
########################################
## Define class "Student" here
##

package Student;
use Fcntl qw(:flock);    ## Note 'use' must be inside package
use DB_File;

########################################
## Create a new object, and populate it
## from DB file if possible
##
sub new {
    my $class = shift;
    my $netid = shift;
    my $self = bless {netid=>$netid}, $class;
    my %students;

    my $glist = tie %students, 'DB_File', "$DIR/$GRADES", O_RDONLY, 0600;
    return $self unless ($glist);

    my $fd = $glist->fd;
    open (DB, "+<&=$fd");
    flock(DB, LOCK_SH); 	    ## lock for reading
    my $entry = $students{$netid};
    flock(DB, LOCK_UN); 	    ## unlock
    untie %glist;
    undef $glist;
    close DB;

	    ## Parse record and populate object
    my @items = split(/!/, $entry);
    foreach my $item (@items) {
	my ($a, $d, $g) = split(/;/, $item);
	$self->{$a} = {date=>$d, grade=>$g};
    }
    return $self;
}

#############################################
## Add new assignment to disk, DB file, and object
##
sub add {
    my ($self, $a, $file) = @_;

    my %students;
    if (exists ($self->{$a}) ) {
	print "You already submitted that assignment\n";
	return;
    }

	    ## Write assignment file to disk
    if (-e "$DIR/$self->{netid}.$a" ) {
	print "Internal error.\n";  
	print "The assignment file exists, but the record does not.\n";
	return;
    }
    open (F, ">$DIR/$self->{netid}.$a") 
			    or print "Can't open $DIR/$self->{netid}.$a\n";
    flock(F, LOCK_EX);     ## lock for writing
    print F $file;
    flock(F, LOCK_UN);     ## unlock 
    close F;

	    ## update DB file
    my $date = `/usr/bin/date`;
    chomp $date;
    $self->{$a} = {date=>$date, grade=>""};
    my $glist = tie %students, 'DB_File', "$DIR/$GRADES", O_CREAT|O_RDWR, 0600;
    my $fd = $glist->fd;
    open (DB, "+<&=$fd");
    flock(DB, LOCK_EX);     	    ## lock for writing
    $students{$self->{netid}} .= "!$a;$date;";
    $glist->sync;	    	    ## flush buffer
    flock(DB, LOCK_UN);     	    ## unlock
    undef $glist;           	    ## includes closing DB
    untie %glist;

    print "You've uploaded $main::assignments{$a}\n";
}

###############################################
## Return list of assignments and grades
##
sub list {
    my $self = shift;
    my @result = ();	# Initialize in case called many times

    foreach $a (sort keys %$self) {
	next if ($a eq 'netid');     # Clunky. Should have Grades class.
	push (@result, [ $a, $self->{$a}{date}, $self->{$a}{grade} ] );
    }
    return \@result;
}

 
 

Perl II Previous: 9. Security


1999-3-3  BobG
UIC Home Page Search UIC Pages Contact UIC