Software, your way.
How To Get Good Custom Software
(Download)
(PDF)
burger menu icon
WillMaster

WillMaster > LibraryTutorials and Answers

FREE! Coding tips, tricks, and treasures.

Possibilities weekly ezine

Get the weekly email website developers read:

 

Your email address

name@example.com
YES! Send Possibilities every week!

Writing Your Own Form Handling Scripts (Perl CGI), Part II

Todays' article is the second of four installments. Here is a table of contents for the entire series:

  1. How to put information from the form into the script.
  2. How to store the form information in a database file on your server -- in any plain text format, including tab- and comma-delimited formats that can be imported into Excel and other spreadsheet and database programs.
    1. The example form and the script.
    2. The database template.
    3. Handling multi-line and multi-value form fields.
      1. Multi-line textarea fields.
      2. Duplicate name and multi-value checkbox and select box selections.
    4. Formatting date and time fields.
    5. Updating the database file.
    6. The script up to this point.
  3. How to send the form information to yourself in an email -- formatted however you please, including HTML.
  4. How to personalize the "thank you"/confirmation page for your form user.

If you haven't read Part I of this tutorial, do so now. It is linked from the Willmaster Library index.

II. How to store the form information in a database file on your server -- in any plain text format, including tab- and comma-delimited formats that can be imported into Excel and other spreadsheet and database programs.

a. The example form and the script.

So you don't have to refer to Part I just to review the example form and the form handling script this tutorial uses, they're printed here.

The example form:

<form method="POST" action="myscript.cgi">
<input type="hidden" name="Subject" value="testing...">
Name: 
<input type="text" name="username">
<br><br>
Email:
<input type="text" name="email">
<br><br>
Gender:
<input type="radio" name="gender" value="female">Female 
<input type="radio" name="gender" value="male">Male
<br><br>
Favorite Color (select one): 
<select name="favorite color">
<option value="white">White</option>
<option value="red">Red</option>
<option value="yellow">Yellow</option>
<option value="blue">Blue</option>
<option value="purple">Purple</option>
<option value="orange">Orange</option>
<option value="green">Green</option>
<option value="black">Black</option>
</select>
<br><br>
Favorite Color Runner-ups (select any):<br>
<input type="checkbox" name="c2" value="white">White<br>
<input type="checkbox" name="c2" value="red">Red<br>
<input type="checkbox" name="c2" value="yellow">Yellow<br>
<input type="checkbox" name="c2" value="blue">Blue<br>
<input type="checkbox" name="c2" value="purple">Purple<br>
<input type="checkbox" name="c2" value="orange">Orange<br>
<input type="checkbox" name="c2" value="green">Green<br>
<input type="checkbox" name="c2" value="black">Black<br>
<br>
Your Message:<br>
<textarea name="message" cols="22" rows="5"></textarea>
<br><br>
<input type="submit" value="Send Form Info">
</form>
<!-- END OF EXAMPLE FORM -->

The form handling script:

#!/usr/bin/perl
# By [your name here]
use strict;

my $AuthorizedDomain = 'mydomain.com';

my %In = ();
my $FormDomain = lc $ENV{HTTP_REFERER};
$FormDomain =~ s!^https?://(?:www\.)?(.*?)(?:/.*)$!$1!;
unless($FormDomain eq lc $AuthorizedDomain)
   { ErrorHTML('Unauthorized access.'); }
unless(ParsePost())
   { ErrorHTML('Unauthorized access.'); }
unless($In{email})
   { ErrorHTML('An email address is required.'); }
unless(ValidEmail($In{email}))
   { ErrorHTML('Sorry, invalid email address format.'); }
if(length($In{message}) > 250)
   { $In{message} = substr($In{message},0,250); }

ErrorHTML('Script paused here.'); # temporary line

Exit();

sub ParsePost
{
   return 0 unless $ENV{REQUEST_METHOD} =~ /POST/i;
   my $buffer;
   read(STDIN,$buffer,$ENV{CONTENT_LENGTH});
   my @p = split(/&/,$buffer);
   foreach(@p)
   {
      $_ =~ tr/+/ /;
      my ($n,$v) = split(/=/,$_,2);
      $n =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
      $v =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
      $v =~ s/(\<.*?)(embed|object|script|applet)(.*?\>)/$1$3/gis;
      if($In{$n}) { $In{$n} .= "\t$v"; }
      else { $In{$n} = $v; }
   }
   return 1;
} # sub ParsePost

sub ValidEmail
{
   if($_[0]=~/([\.\-\_]{2,})|(@[\.\-\_])|([\.\-\_]@)|(\A\.)/)
      { return 0; }
   if($_[0]=~/^[\w\.\-\_]+\@\[?[\w\.\-\_]+\.([\w\.\-\_]{2,3}|[0-9])\]?$/)
      { return 1; }
   return 0;
} # sub ValidEmail

sub ErrorHTML
{
   my $s = join("\n<li>",@_);
   print "Content-type: text/html\n\n";
   print <<HTML;
<html><body bgcolor="white">
<blockquote><blockquote>
<h4>Message:</h4>
<ul>
<li>$s
</ul>
</blockquote></blockquote>
</body></html>
HTML
   Exit();
} # sub ErrorHTML

sub Exit { exit; }
# END OF FORM HANDLING SCRIPT

b. The database template.

The first thing to do is create a database template. The template will be the database record's format with placeholders where the script will insert form information before updating the database file.

Placeholders can be anything we create them to be. For this tutorial, placeholders is the form field name enclosed with double square brackets. Our example form has seven field names, which results in these placeholders:

     [[Subject]]
     [[username]]
     [[email]]
     [[gender]]
     [[favorite color]]
     [[c2]]
     [[message]]

You can store any or all of the form information in your database. For now, let's assume you will be storing the form user's name, email address, and any message that was left.

For a tab-delimited database importable by stand-alone spreadsheet and database programs with import functions, the program line with the template would be:

my $DBtemplate = "[[username]]\t[[email]]\t[[message]]\n";

The 2-character sequence \t tells the script to insert a tab character. And the 2-character sequence \n tells the script to insert a line break. Both tab- and comma-delimited databases are one record per line, which is why the \n needs to be at the end of the template.

For a comma-delimited database importable by stand-alone spreadsheet and database programs with import functions, the program line with the template would be:

my $DBtemplate = "\"[[username]]\",\"[[email]]\",\"[[message]]\"\n";

Because the template is between quotes, any quotes within the template must be specified with the 2-character sequence \" (Fields are enclosed in quotes so any comma that may be in the field's data is not mistaken as a field separating comma.)

For a multi-line database, your template could be something like this:

my $DBtemplate = "[[username]]\n[[email]]\n[[message]]\n\n";

In the above example, line breaks are specified between each placeholder. And there is an extra \n at the end of the template to generate a blank line between records.

We'll use the tab-delimited database template in this tutorial's form handling script. Insert the $DBtemplate template assignment statement at about line 6, below the $AuthorizedDomain assignment statement. I.E.:

my $AuthorizedDomain = 'mydomain.com';
my $DBtemplate = "[[username]]\t[[email]]\t[[message]]\n";

c. Handling multi-line and multi-value form fields.

     i. Multi-line textarea fields.

Tab- and comma-delimited databases must be one record per line. The textarea field name="message" could contain multiple lines. The line break characters must be removed or replaced with another character or characters. This function will do that for you:

sub MakeOneLine
{
   my $s = shift;
   my $replacement = '<br>';
   if($s =~ /\n/) { $s =~ s/\r//gs; }
   else { $s =~ s/\r/\n/gs; }
   $s =~ s/\n/$replacement/gs;
   return $s;
} # sub MakeOneLine

To replace line breaks with something else (in the above function), replace <br> in the

my $replacement = '<br>';

line with your custom line break replacement. If you want to remove the line breaks (replace them with nothing), put nothing between the apostrophes.

A note on the if... and else... lines: It's possible that this script will run on UNIX/Linux, Macintosh, and/or NT servers. Each possibility must be taken into consideration.

UNIX/Linux computers use \n (named "newline") as a line break. Macintosh computers use \r (named "carriage return"). And NT servers use both: \n\r

The subroutine MakeOneLine replaces the \n 2-character sequence with whatever you specify for the value of $replacement. Therefore, if it is an NT, the \r characters must be removed. But if it is a Macintosh, then the \r characters must be converted to \n

The if... statement checks to see if any \n characters are present. If a \n character is present, it's not a Macintosh server and any \r characters can be deleted. else... it is a Macintosh and \r characters are converted to \n

To use subroutine MakeOneLine, your statement would be something like this:

$In{message} = MakeOneLine($In{message});

Put the MakeOneLine subroutine at the bottom of your current script.

     ii. Duplicate name and multi-value checkbox and select box selections.

There are several situations where the same form field name can hold multiple values:

  • A set of checkboxes have the same field name (like the example form).
  • A select list box has the "multiple" attribute.
  • Any form field has the same name as another.

Whenever a form field name holds more than one value, that value is separated with a tab character. (The subroutine ParsePost makes sure this is so.)

Okay, if you're going to include a tab-separated list into a tab-delimited database, you're in trouble. First, the tab characters must be removed or replaced with another character or characters. This function will do that for you:

sub ConvertTabValueSeparaters
{
   my $s = shift;
   my $replacement = ' -- ';
   $s =~ s/\t/$replacement/gs;
   return $s;
} # sub ConvertTabValueSeparaters

In the above function, tabs are replaced with a long dash. To customize, put your own replacement between the apostrophes in

my $replacement = ' -- ';

If you want to remove the tabs (replace them with nothing), put nothing between the apostrophes.

To use subroutine ConvertTabValueSeparaters, your statement would be something like this:

$In{c2} = ConvertTabValueSeparaters($In{c2});

Put the ConvertTabValueSeparaters subroutine at the bottom of your current script.

d. Formatting date and time fields.

In some situations, you might want to put the date and/or time into your database records. Here are two subroutines that will return a formatted date and a formatted time, respectively:

sub GetFormattedDate
{
   my @Weekday = qw(
      Sunday 
      Monday 
      Tuesday 
      Wednesday 
      Thursday 
      Friday 
      Saturday);
   my @Month = qw(
      January 
      February 
      March 
      April 
      May 
      June 
      July 
      August 
      September 
      October 
      November 
      December);
   my ($sc,$mn,$hr,$mday,$mon,$yr,$wday,$yday,$dst) = localtime;
   $yr += 1900;
   return "$Weekday[$wday], $Month[$mon] $mday, $yr";
} # sub GetFormattedDate

sub GetFormattedTime
{
   my ($sc,$mn,$hr,$mday,$mon,$yr,$wday,$yday,$dst) = localtime;
   my $s = '';
   $s .= $hr < 10 ? "0${hr}:" : "${hr}:";
   $s .= $mn < 10 ? "0${mn}:" : "${mn}:";
   $s .= $sc < 10 ? "0${sc}"  :  $sc;
   return $s;
} # sub GetFormattedTime

With the above subroutines, the date will be formatted as Weekday, Month d, yyyy and the time will be formatted according to a 24-hour clock as hh:mm:ss

The date and time will be the date and time on your server.

To use the subroutines, your statements would be something like this:

$In{Date} = &GetFormattedDate;
$In{Time} = &GetFormattedTime;

You can now add the following placeholders to your list:

     [[Date]]
     [[Time]]

Put the subroutines at the bottom of your current script.

e. Updating the database file.

You'll need to assign a file name for the script to update. Use variable $DatabaseFile and put the assignment under the $DBtemplate assignment statement at about line 7. You'll end up with:

my $AuthorizedDomain = 'mydomain.com';
my $DBtemplate = "[[username]]\t[[email]]\t[[message]]\n";
my $DatabaseFile = 'data.txt';

The following subroutine will insert form information into the template and update the database:

sub UpdateDatabase
{
   my $t_message = $In{message};
   my $t_c2 = $In{c2};
   $In{message} = MakeOneLine($In{message});
   $In{c2} = ConvertTabValueSeparaters($In{c2});
   $In{Date} = &GetFormattedDate;
   $In{Time} = &GetFormattedTime;
   for(keys %In) { $DBtemplate =~ s/\[\[$_\]\]/$In{$_}/i; }
   $DBtemplate =~ s/\[\[.*?\]\]//i;
   if(-e $DatabaseFile) { open W,">>$DatabaseFile"; }
   else { open W,">$DatabaseFile"; }
   print W $DBtemplate;
   close W;
   $In{c2} = $t_c2;
   $In{message} = $t_message;
} # sub UpdateDatabase

Notice that the first two lines of the subroutine store the contents of $In{message} and $In{c2} into temporary variables. Those hold the values of form fields named "message" and "c2", respectively. The values will be changed to be acceptable for the database. ("c2" isn't actually used in the current template, but is included as an example.)

The last two lines of the subroutine, then, restore the values to $In{message} and $In{c2} from the temporary variables.

Although the date and time aren't used in the current database format, their initialization is included here as examples.

About mid-subroutine, you'll see

for(keys %In) { $DBtemplate =~ s/\[\[$_\]\]/$In{$_}/i; }

That line replaces the template placeholders with form field values. Notice the "i" near the end of that line, just before the semi-colon. That "i" tells the script to consider the form field names to be case insensitive ("name" and "Name" and "NAME" are considered the same). If you want the field names to be case sensitive ("name" and "Name" and "NAME" are all different), then just remove the "i" from that line.

The next line removes any placeholders that weren't replaced with form information.

The if... and else... lines check to see if your database file already exists. If yes, the file is opened so any writes are appended. If no, the file is created.

Following the if... and else... lines, the script prints the information to the file and then closes it.

To use the UpdateDatabase subroutine, your statement would be:

&UpdateDatabase;

Put the UpdateDatabase subroutine at the bottom of your current script.

f. The script up to this point.

Here is the form handling script up to this point:

#!/usr/bin/perl
# By [your name here]
use strict;

my $AuthorizedDomain = 'mydomain.com';
my $DBtemplate = "[[username]]\t[[email]]\t[[message]]\n";
my $DatabaseFile = 'data.txt';

my %In = ();
my $FormDomain = lc $ENV{HTTP_REFERER};
$FormDomain =~ s!^https?://(?:www\.)?(.*?)(?:/.*)$!$1!;
unless($FormDomain eq lc $AuthorizedDomain)
   { ErrorHTML('Unauthorized access.'); }
unless(ParsePost())
   { ErrorHTML('Unauthorized access.'); }
unless($In{email})
   { ErrorHTML('An email address is required.'); }
unless(ValidEmail($In{email}))
   { ErrorHTML('Sorry, invalid email address format.'); }
if(length($In{message}) > 250)
   { $In{message} = substr($In{message},0,250); }

&UpdateDatabase;

ErrorHTML('Script paused here.'); # temporary line

Exit();

sub ParsePost
{
   return 0 unless $ENV{REQUEST_METHOD} =~ /POST/i;
   my $buffer;
   read(STDIN,$buffer,$ENV{CONTENT_LENGTH});
   my @p = split(/&/,$buffer);
   foreach(@p)
   {
      $_ =~ tr/+/ /;
      my ($n,$v) = split(/=/,$_,2);
      $n =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
      $v =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
      $v =~ s/(\<.*?)(embed|object|script|applet)(.*?\>)/$1$3/gis;
      if($In{$n}) { $In{$n} .= "\t$v"; }
      else { $In{$n} = $v; }
   }
   return 1;
} # sub ParsePost

sub ValidEmail
{
   if($_[0]=~/([\.\-\_]{2,})|(@[\.\-\_])|([\.\-\_]@)|(\A\.)/)
      { return 0; }
   if($_[0]=~/^[\w\.\-\_]+\@\[?[\w\.\-\_]+\.([\w\.\-\_]{2,3}|[0-9])\]?$/)
      { return 1; }
   return 0;
} # sub ValidEmail

sub ErrorHTML
{
   my $s = join("\n<li>",@_);
   print "Content-type: text/html\n\n";
   print <<HTML;
<html><body bgcolor="white">
<blockquote><blockquote>
<h4>Message:</h4>
<ul>
<li>$s
</ul>
</blockquote></blockquote>
</body></html>
HTML
   Exit();
} # sub ErrorHTML

sub Exit { exit; }

sub MakeOneLine
{
	my $s = shift;
	my $replacement = '<br>';
	if($s =~ /\n/) { $s =~ s/\r//gs; }
	else { $s =~ s/\r/\n/gs; }
	$s =~ s/\n/$replacement/gs;
	return $s;
} # sub MakeOneLine

sub ConvertTabValueSeparaters
{
	my $s = shift;
	my $replacement = ' -- ';
	$s =~ s/\t/$replacement/gs;
	return $s;
} # sub ConvertTabValueSeparaters

sub GetFormattedDate
{
	my @Weekday = qw(
		Sunday 
		Monday 
		Tuesday 
		Wednesday 
		Thursday 
		Friday 
		Saturday);
	my @Month = qw(
		January 
		February 
		March 
		April 
		May 
		June 
		July 
		August 
		September 
		October 
		November 
		December);
	my ($sc,$mn,$hr,$mday,$mon,$yr,$wday,$yday,$dst) = localtime;
	$yr += 1900;
	return "$Weekday[$wday], $Month[$mon] $mday, $yr";
} # sub GetFormattedDate

sub GetFormattedTime
{
	my ($sc,$mn,$hr,$mday,$mon,$yr,$wday,$yday,$dst) = localtime;
	my $s = '';
	$s .= $hr < 10 ? "0${hr}:" : "${hr}:";
	$s .= $mn < 10 ? "0${mn}:" : "${mn}:";
	$s .= $sc < 10 ? "0${sc}"  :  $sc;
	return $s;
} # sub GetFormattedTime

sub UpdateDatabase
{
   my $t_message = $In{message};
   my $t_c2 = $In{c2};
   $In{message} = MakeOneLine($In{message});
   $In{c2} = ConvertTabValueSeparaters($In{c2});
   $In{Date} = &GetFormattedDate;
   $In{Time} = &GetFormattedTime;
   for(keys %In) { $DBtemplate =~ s/\[\[$_\]\]/$In{$_}/i; }
   $DBtemplate =~ s/\[\[.*?\]\]//i;
   if(-e $DatabaseFile) { open W,">>$DatabaseFile"; }
   else { open W,">$DatabaseFile"; }
   print W $DBtemplate;
   close W;
   $In{c2} = $t_c2;
   $In{message} = $t_message;
} # sub UpdateDatabase

The "Script paused here." message in the above code will be removed when the script is completed.

Part III of this series shows you how to take the information in the %In variable and email it to yourself (or to anybody else).

See you then :)

Will Bontrager

Was this article helpful to you?
(anonymous form)

Support This Website

Some of our support is from people like you who see the value of all that's offered for FREE at this website.

"Yes, let me contribute."

Amount (USD):

Tap to Choose
Contribution
Method

All information in WillMaster Library articles is presented AS-IS.

We only suggest and recommend what we believe is of value. As remuneration for the time and research involved to provide quality links, we generally use affiliate links when we can. Whenever we link to something not our own, you should assume they are affiliate links or that we benefit in some way.

How Can We Help You? balloons
How Can We Help You?
bullet Custom Programming
bullet Ready-Made Software
bullet Technical Support
bullet Possibilities Newsletter
bullet Website "How-To" Info
bullet Useful Information List

© 1998-2001 William and Mari Bontrager
© 2001-2011 Bontrager Connection, LLC
© 2011-2024 Will Bontrager Software LLC