LevSelector.com New York
home > http, cgi, cookies

HTTP, CGI and Cookies
On this page: Other pages:
* http protocol
* books
* simple cgi_examples
* security - tainting
* debugging CGI scripts
* CGI.pm
* CGI.pm freq.used elements
* CGI.pm propagates values
* CGI::Carp
* libs of cgi scripts
* cookies
* cgi in C
* cgi in shell

* CGI::Application module
* HTML::Template module
* Redirect
* Cookie


HTTP home - top of the page -

HTTP protocol (mostly ver. 1.0,  or 1.1) is the protocol carring most of web messages.
It is the protocol used when you enter http address in your browser.
It is a simple protocol.

Every time browser wants something from a web server - it sends a request.
Request consists of a header,
   followed by an empty line
     followed by an optional body.

Header starts with a command.
  Most common commands - GET or POST (others are HEAD, PUT, DELETE, etc.).

Here is an example of a successful GET request to retrieve a file.

The client (browser) sends:
GET /index.html HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/3.0Gold (WinNT; I)
Host: www.vedanco.com
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*

The server responds with:
HTTP/1.1 200 OK
Date: Fri, 13 Oct 2000 00:29:37 GMT
Server: Apache/1.3.9 (Unix)  (Red Hat/Linux)
Last-Modified: Sun, 17 Sep 2000 17:47:28 GMT
Content-Length: 7871
Content-Type: text/html

(rest of the HTML document here)

As you see, web server responds sending header followed by HTML page or an image - they will be in the body of the message.  The Content-type of this data (for example "text/html") and the length of the data will be indicated in the header of the server response.

When the browser receives the HTML page - it may find that this page needs some images. Then it may send more requests to the server, like this one:
GET /images/logo.gif HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/3.0Gold (WinNT; I)
Host: www.vedanco.com
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*

Server will send responses like this one:
HTTP/1.1 200 OK
Date: Fri, 13 Oct 2000 00:29:37 GMT
Server: Apache/1.3.9 (Unix)  (Red Hat/Linux)
Content-Type: image/gif
Content-Length: 10381
Last-Modified: Sun, 17 Sep 2000 17:47:28 GMT

(data of GIF file here)

To pretend that you are a browser, try to send this one line header followed by an empty line from a unix prompt:

telnet   www.somesite.com   80 <ENTER>
GET   /   HTTP/1.0<ENTER>
To send data from the form in the browser, you usually use either GET or POST.
When using GET, the data is passed in one long QUERY STRING appended via question mark, for example:

GET /work.cgi?first=John&last=Smith HTTP/1.0

When you use POST - the same data is passed in the body of the HTTP request

BOOKS home - top of the page -

* CGI Programming 101 by Jacqueline D. Hamilton - This is probably the best book about Perl/CGI for a beginner.  All examples for this book are here: *www.cgi101.com/class/www.cgi101.com/class/indices.html -
* CGI Programming with Perl (O'Reilly, 2nd Edition) by Scott Guelich, Linda Mui, Gunther Birznieks, Shishir Gundavaram
* Web Client Programming With Perl by Clinton Wong - excellent intro into HTTP protocol. Learn how to write your own web spiders. it is out of print, but is available for free online: www.oreilly.com/openbook/webclient/index.html
* Perl Debugged (2001) by Peter J. Scott, Ed Wright - excellent chapter #13 "Debugging CGI Programs"

* How to Set Up and Maintain a Web Site - Lincoln D. Stein
* Network Programming with Perl - Lincoln D. Stein
* Official Guide to Programming With Cgi.Pm by Lincoln Stein (1998)
* Professional Perl Development (2001, Wrox Press)
* Perl and Cgi for the World Wide Web : Visual Quickstart Guide (Visual Quickstart Guide Series) 
* Writing CGI Applications with Perl (2001) - by Kevin Meltzer, Brent Michalski 

simple CGI examples home - top of the page -

The logic of the typical script is:
  - get request (parameters)
  - do something
  - print http header and html page back to the browser

Here is a very simple Perl/CGI script:
#!/usr/local/bin/perl -wT
$|++; # unbuffer STDOUT (because with default buffer ~8kb the output may be delayed) 
print "Content-type: text/html\n\n";  # print http header and an empty line separator
    # do something interesting here
print "<html>test</html>\n"; # print output

Here is a CGI script showing ENV variables:
#!/usr/local/bin/perl -wT
print "Content-type: text/html\n\n";
print "<html><head><title>ENV</title></head><body>\n";
print "<h3>Table on ENV variables and their values</h3>\n<table>\n";
for my $key (keys %ENV) {
    print "<tr><td>$key</td><td>$ENV{$key}</td></tr>\n";
print "</table></body></html>\n";

Here is a simple function parse_request
to get access to form parameters:  $in{$name} = $value;
# die_www()
sub die_www {
 my $message = shift;
 print "Content-Type: text/html\n\n";
 print "<html><head><title>die-message</title></head>

# mydecode()
sub mydecode {
  $sss = shift;
  $sss =~ tr/+/ /;
  $sss =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  return $sss;

# myclean()
sub myclean {
 my $sss = shift;  $sss =~ s/^\s*//;  $sss =~ s/\s*$//;  return $sss;

# parse_request( \%in)
#   - get name/value pairs HTTP header (GET & POST)
#   - save them into hash:    $in{$name} = $value;
#   Note: doesn't work with <select> with "multiple" option
sub parse_request {
 my $rgg = shift;
 my @pairs = ();
 my $buffer = '';
 if ($ENV{'REQUEST_METHOD'} eq 'GET') {
  @pairs = split(/[&;]/, $ENV{'QUERY_STRING'});
elsif ($ENV{'REQUEST_METHOD'} eq 'POST') {
  read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
  @pairs = split(/[&;]/, $buffer);
else {
  &die2www("Error, no data, press BACK button and try again");

 foreach my $pair (@pairs) {
   my ($name, $value) = split(/=/, $pair);
  $name = mydecode($name);
  $value = mydecode($value);
  $value =~ s/<!*(.|\n)*-->//g;
  $rgg->{in}->{$name} = &myclean($value);

Security - tainting home - top of the page -

Classical example of a security problem:
Let's say you have a form which asks to enter email address. 
One day a bad-bad user enters a command ' ; rm * ' in this field. 
Your script saves this into a variable $email and tries to send an email:
     open MAIL,"|$SENDMAIL $email"
the 'rm *' command may be triggered at this point - and delete all files in the directory (on your server !!).

The solution - use -T option (taint mode):
#!/usr/bin/perl -T
In taint mode any external data ($email in this case) is tainted by a special flag. Any attempt to to use tainted data for some outside action results in an error. The only way to remove taint from data is to use regular expression to select something from this data into $1,$2, etc. - and use them instead.

Debugging CGI scripts home - top of the page -

check that the script
  - has proper extension (usually "cgi")
  - is in proper directory (usually "cgi-bin")
  - made executable (chmod 755 test.cgi) - and subdirectory also executable
  - has no syntax errors or warnings
  - can be run from prompt, shebang line is correct (points on the right version of perl)

Look in the access and error log of the web server on the unix prompt.
Or you can use separate cgi script for that:
# err.cgi
use strict;
my @tail = qx(tail -10 /etc/httpd/logs/access_log);
print " @tail\n";
   @tail = qx(tail -10 /etc/httpd/logs/error_log);
print " @tail\n"; 

You can show errors on the browser, for example using this:
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser); 

Another good module - CGI::Debug
Only report errors:
    use CGI::Debug( report => 'errors' );

Do not bother about warnings:
    use CGI::Debug( on => 'fatals' );

Allways show complete debugging info:
    use CGI::Debug( report => 'everything', on => 'anything' );

Send debug data as mail to file owner:
    use CGI::Debug( to => 'mail' );

Here is how to email info about errors (without using CGI::Debug):
  use lib "/home/merlyn/lib";
  use FatalsToEmail qw(Address merlyn@stonehenge.comm);
# Randal Schwartz - www.stonehenge.com/merlyn/LinuxMag/col14.html
package FatalsToEmail;
use strict;

my %config = (
  Address => "webmaster",      # email address
  Mailhost => "localhost",     # mail server
  Cache => undef,              # undef means don't use
  Seconds => 60,

# ------------------------------------------------
# import()
# ------------------------------------------------
sub import {
  my $package = shift;
  while (@_) {
    my $key = ucfirst lc shift;
    die "missing argument to $key" 
      unless @_;
    die "unknown argument $key" 
      unless exists $config{$key};
    $config{$key} = shift;

$SIG{__DIE__} = \&trapper;

# ------------------------------------------------
# trapper()
# ------------------------------------------------
sub trapper {
  my $message = shift;
  my $time = localtime;
  my ($pack, $file, $line) = caller;

  my $prefix = localtime;
  $prefix .= ":$$:$file:$line: ";
  $message =~ s/^/$prefix/mig;

  print STDOUT <<END;
Content-Type: text/html
<p>An error has occurred; details have been logged.
Please try your request again later.

  die "${prefix}died - email sent to $config{Address} via $config{Mailhost}\n";

# ------------------------------------------------
# send_mail()
# ------------------------------------------------
sub send_mail {
  my $message = shift;
  eval {
    ## ----- do I need to cache this?
    if (defined (my $cache = $config{Cache})) {
      if (open CACHE, "+<$cache") {
        flock CACHE, 2;
        ## ----- it's mine, see if it's old enough
        if (time - (stat(CACHE))[9] > $config{Seconds}) {
          ## ----- yes, suck any content, and zero the file
          my $buf;
          $buf .= "\n...[truncated]...\n" 
            if read(CACHE, $buf, 8192) >= 8192;
          $message = $buf . $message;
          seek CACHE, 0, 0;
          truncate CACHE, 0;
          close CACHE;
        } else {
          ## ----- no, so just drop the stuff at the end
          seek CACHE, 0, 2;
          print CACHE $message;
          close CACHE;
      } else {
        ## ----- it doesn't exist, so create an empty file for stamping, and email
        open CACHE, ">>$cache" 
          or die "Cannot create $cache: $!";
        close CACHE;

    eval { require Net::SMTP; 1 } or die "no Net::SMTP";
    my $mail = Net::SMTP->new($config{Mailhost})
      or die "Net::SMTP->new returned $@";
    $mail->mail($config{Address}) or die "from: $@";
    $mail->to($config{Address}) or die "to: $@";
    $mail->data("Subject: CGI FATAL ERROR in $0\n\n", $message)
      or die "data: $@";
    $mail->quit or die "quit: $@";
  if ($@) {
    die "$message(send_mail saw $@)\n";

CGI.pm module home - top of the page -

CGI.pm module:
CGI.pm module (written by Lincoln D. Stein) comes standard with Perl.
I recommend to always use it.
- http://stein.cshl.org/WWW/CGI/examples/ - examles by Lincoln D. Stein himself
This module is a base of many other good packages, for example ( CGI::Application module ).
Below is a summary from the book "CGI programming with Perl" 2nd ed. by O'Reilly - I will refer to it as "CGI programming book".
Here is a simple example using CGI.pm:

Object-oriented style:
#!/usr/local/bin/perl -wT
use strict;
use CGI;
my $q = new CGI;
my $name = $q->param("name");

print $q->header("text/html"),
        $q->p("Hi, $name!"),

Standard syntax:
#!/usr/local/bin/perl -wT
use strict;
use CGI qw(:standard);
my $name = param("name");

print header("text/html"),
        p("Hi, $name!"),

You have access to environment information via %ENV hash, for example:
  my $qstr = $ENV{QUERY_STRING};
CGI.pm also gives you more than 30 method calls with names similar to keys of the hash.

Some methods:
query_string method:
  my $qstr = $q->qwery_string;

Accept method:
  my @accept = $q->Accept;
  if (grep $_ eq "image/png", @accept) { ... }
http method:
  - without parameters returns an array of the names of env.variables
  - with the name of a particular env.variable - returns its  value.
   print $q->header("text/plain");
   for ($q->http) { print "$_:\n  ", $q->http($_), "\n"; }
Some other methods:
   https - similarly returns HTTPS_  env.variables
   url - returns url of current script without parameters
   self_url - returns url of current script with parameters included
   virtual_host - returns the value of the HTTP_HOST env.variable if set,  SERVER_NAME otherwise.

Accessing parameters:
#!/usr/local/bin/perl -wT
use strict;
use CGI;
my $q = new CGI;
print $q->header("text/plain");
my ($name, $value);
for $name ($q->param) {
  print "$name:\n";
  for $value ($q->param($name) ) {
    print "$value:\n";

You can access parameters from both POST (STDIN) and GET (QUERY_STRING) - you will need to uncomment a line of code in CGI.pm (page 95 in the CGI programming book).

There are 4 ways to use param() function:
$scalar   =   $q->param('name');  # returns the value
@array   =   $q->param('name');  # returns array of values for this name
$scalar   =   $q->param( );  # returns the number of all named form elements
@array   =   $q->param( );  # returns an array of names of all form elements

Changing / deleting parameters:
$q->param(title => "Some Title");
$q->param(account => "Acc1", "Acc2");
$q->param(account => "Acc1", "Acc2");

To include parameters into strings, you can:
 -- create intermediate variables:
 my $user = q->param('user');  print "Hi, $user!";
 -- use @{[...]} syntax:
  print "Hi, @{[q->param('user')]}!";
 -- import names of parameters into a separate namepace:
  $q->import_names( "Q");  print "Hi, $Q::user!";

File uploads:
<form action="/cgi/upload.cgi" method="POST" enctype="multipart/form-data">
<p>Please choose a file to upload:
<input type="FILE" name="file">
<input type="SUBMIT"></form>
my $fname = $q->param("file"); # this can cause problems on some problems - read workarounds - pp.97,98 in the CGI programming book).
my $fh = $q->upload("$fname");  # this is file handler to get the file contents - see full example pp.99-101.

Generating output:
#!/usr/local/bin/perl -wT
use strict;
use CGI;
my $q = new CGI;
my $time1 = localtime;
print $q->header("text/plain"),
        $q->start_html ( -title => "The Time", -bgcolor => "#ffffff" ),
        $q->h2( "Current Time" ),
        $q->p( "The current time is:", $q->b( $time1 ) ),

Output - Excel file:

#!/usr/local/bin/perl -wT
# to avoid cahcing, we use 2 tricks.
# First, we return Excel file from a cgi script, not from static link.
# Second, on the link we add a timestamp parameter. Thus URL changes - thus we go around caching.

use strict;

use CGI;
my $q = new CGI;

sub return_excel_file {
  my $file = "$mydir/myfile.xls";
  print $q->header(
    "-Content-type" => "application/excel",
    "-Content-Disposition" => "attachment;filename= myfile.xls",

  binmode STDOUT;
  open(EXCELFILE, $file) or die "Cannot open file $file: $!";
  my $buffer;
  while ( read( EXCELFILE, $buffer, 1024 ) ) {
    print $buffer;


Some other elements:
print $q->start_html(
  -xbase e=>$SomeBaseURL,
# or 
  -script=>{  -language => 'JavaScript',  -src=>'/javascript/somefile.js'},
# or
  -script=>{ -language => 'JavaScript',   -code=>$SomeJavaScript},

  -style=>{ -code => $someStyleCode, -src=>$src_file },

print $q->p( $paragraph_text );
print $q->a( { -href => "http://www.yahoo.com",  "link_text" } );


Note confilcts:
The <tr> tag conflicts with the perl translate function tr///. Use TR() or Tr() instead. 
The <param> tag (applets) conflicts with CGI's own param() method. Use PARAM() instead. 
The <select> tag in select-lists conflicts with perl's select() function. Use Select() instead.

print $q->table(
  { -border => 1,  -width => "100%" },
  $q->Tr( [
        $q->th( { -bgcolor => "#cccccc" }, ["Name", "Age" ] ),
        $q->td(["Mary", 29]),   # row
        $q->td(["Bill", 27]),  # row
        $q->td(["Sue", 26]),  # row
             ] )  # end of rows 
          );    # end of table

use CGI qw(:standard);
print header;
print start_html('A Simple Example'),
    h1('A Simple Example'),
    "What's your name? ",textfield('name'),
    "What's the combination?",
    "What's your favorite color? ",
    popup_menu(-name=>'color',  -values=>['red','green','blue','chartreuse']),    # this is equivalent of <select> list

if (param()) {
    print "Your name is",em(param('name')),
       p, "The keywords are: ",em(join(", ",param('words'))),
       p, "Your favorite color is ",em(param('color')),
print a({href=>'../cgi_docs.html'},'Go to the documentation');

All form elements:
start_form, end_form, 
textfield, password_field, filefield, 
button, submit, reset, 
checkbox, checkbox_group, radio_group, 
popup_menu,     # <select size="1">
scrolling_list,      # <select size="n">   where n>1 
textarea, hidden

   print $q->textfield(-name=>'username',  -default=>'Anonymous',  -size=>50,  -maxlength=>80);
   print $q->password_field(-name=>'secret',  -value=>'starting value',  -size=>50,  -maxlength=>80);
   print $q=>radio_group( -name=>"curtain", -values=>["A","B","C"], -default=>"B", 
           -labels=>{ A => "Curtain A", B => "Curtain B", C => "Curtain C", } );

cookie method:
To retrieve cookie - use cookie method without value parameter
 use CGI;
 $query = new CGI;
 %answers = $query->cookie(-name=>'answers');
 # $query->cookie('answers') will work too!

To set cookie:
$cookie = $q->cookie(-name=>'sessionID',
print $q->header(-cookie=>$cookie);

Store many values (hash) into a cookie:
$cookie=$q->cookie(-name=>'family information',

Store 2 or more cookies: 
$cookie1 = $query->cookie(-name=>'riddle_name',
      -value=>"The Sphynx's Question");
 $cookie2 = $query->cookie(-name=>'answers',
 print $query->header(-cookie=>[$cookie1,$cookie2]);

CGI.pm script with most freq.used elements home - top of the page -

Real working script with most frequently used elements:

# file cgi_all.cgi

use strict; 
use CGI; 
my $q = new CGI; 
my $time1 = localtime;
sub nn { "\n" }; 

print $q->header("text/html"), 
  $q->start_html ( 
    -title => "The Time",

function a(mes){

    ),  # end of javascript definition 

    -style=>{ -src=>'style0.css' },
    -bgcolor => "#ffffff",

print $q->h4( "Current Time" ),nn, 
  $q->p( "The current time is:", $q->b( $time1 ) ),nn;
  $q->a( { href => 'http://www.google.com', target => '_new' }, 'google' ),nn;
  $q->img( {src=>'fred.gif',align=>'LEFT'} );
  $q->comment('here is my comment');

print $q->table( 
  $q->Tr( [ $q->th( { -bgcolor => "#cccccc" }, ["Name", "Age" ] ), 
        $q->td(["Mary", 29]), # row 
        $q->td(["Bill", 27]), # row 
        ] )  # end of rows 
);  # end of table

# --------------------------------------
# show the form
# --------------------------------------

print $q->start_form(-name=>'form1', -method=>'POST', -action=>'cgi_all.cgi'),
  $q->password_field(-name=>'secret',-value=>'starting value',-size=>50,-maxlength=>80),nn,
  "What's your name? ",$q->textfield('name'),nn,
  $q->hidden(-name=>'hid_name', -value=>'hid_value'),nn,
  $q->br,"What's the combination?",nn,
       -defaults=>['eenie','minie'] ), nn,
  "Select Curtain ",
  $q->radio_group( -name=>"curtain", -values=>["A","B","C"], -default=>"B",
           -labels=>{ A => "Curtain A", B => "Curtain B", C => "Curtain C", } ),nn,
  "What's your favorite color? ",
      -values=>['red','green','blue','chartreuse'] # this is <select> list
  ), nn,
  $q->scrolling_list(-name=>'scr_list', -size=>'3',
      -values=>['red','green','blue','chartreuse'], # this is multiple <select> list
  ), nn,

  $q->textarea(-name=>"Comments", -rows=>'3', -columns=>'60',
           -default=>"line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\n"),nn,

# filefield


# --------------------------------------
# show received parameters
# --------------------------------------
my @kk = $q->param(); # array of names of parameters
for my $p (@kk) {
  my $ss = join(", ", $q->param($p));
  print "$p => $ss<br>\n";

print $q->end_html;

Note that some functions in Perl capitalized to avoid conflicts with html-tag names:

CGI.pm propagates values home - top of the page -

CGI.pm, by default, propagates existing values of form fields.  This is normally A Good Thing, as it allows you to easily have data move from screen to screen.  This mechanism allows you to set a value (which acts like a default), which is overridden by an existing value stored in the CGI query object.  When a user submits "name=Terry" on a form, the next screen will automagically have "name=Terry" without necessitating that the programmer explicitally copy it from the query to the new screen.

   $output .= $q->hidden(-name=>'name');  # Nice and simple
Instead of:
   $output .= $q->hidden(-name=>'name', -value=>$q->param('name'));  # Ugh!

However, there are some cases where you do not want the value of a particular form variable to persist.  Case in point:  Your run-mode form parameter.  In this case, you always want your new value to override any pre-existing form value.  CGI.pm provides an "override" attribute which provides this function:

    my $q = $self->query();
    $output .= $q->hidden(-name=>'rm', -value=>$which_one, -override=>1);

This will cause the form parameter "rm" to always be set to $which_one regardless of any pre-existing value submitted for "rm".

CGI::Carp module home - top of the page -

CGI::Carp is also written by Lincoln Stein, distributed with CGI.pm
   - writes to error log
   - creates an error (html) page

Example: creating your own Error.pm module:
#!/usr/local/bin/perl -wT

package CGIBook::Error;

use Exporter;
$VERSION = "0.01";

use strict;
use CGI
use CGI::Carp qw(fatalsToBrowser);

  sub carp_error {
    my $error_message = shift;
    my $q = new CGI;
      $discard_this = q->header("text/html");
      error ($q, $error_message);
  }  # end of sub
  CGI::Carp::set_message( \&carp_error );

sub error {
  my($q, $error_message) = @_;
  print $q->header("text/html"),
      $q->p( "Sorry, the following error has occurred:"),


Example using this module in your cgi script:
#!/usr/local/bin/perl -wT

use strict;
use CGI
use CGIBook::Error;
my $q = new CGI;

  or error ($q, "Something bad happenned");

CGI libs home - top of the page -

Many-many CGI scripts:
* www.worldwidemart.com/scripts/ - Matt's Script Archives
* www.cgi-resources.com/ -
* www.scriptsearch.com/pages/l4.shtml - Script Search - The world's largest CGI library

* CGI::Application module -

Cookies home - top of the page -

A cookie is information that a Web site puts on your hard disk so that it can remember something about you at a later time.
*www.LevSelector.com/cookie.html - short Perl/Javascript/Java examples
* do search for cookielib on search engines ( http://www.google.com/search?q=cookielib ) - Matt's Script Archive: HTTP Cookie Library.
* www.zdnet.com/devhead/resources/scriptlibrary/javascript/cookies.html -
* www.hidaho.com/cookies/cookie.txt -
* www.cookiecentral.com/-
* www.worldwidemart.com/scripts/cookielib.shtml -
* home.netscape.com/newsref/std/cookie_spec.html -
* www.netscapeworld.com/netscapeworld/nw-07-1996/nw-07-cookies.html -

CGI in C home - top of the page -

Click here to see an example of a C program processing a CGI input.
In this example some other process took the QUERY string for GET or STDIN for POST and passed it to this CGI program via STDIN
(length is in CONTENT_LENGTH variable). Note how long is the code in comparison to Perl version.

CGI in a unix shell (ksh) home - top of the page -

Form processing example.

<title> Forms </title>
<FORM METHOD="POST" action="http://www.name.com/cgi-bin/forms1.ksh">
Choose your option
<SELECT NAME="Selection list" SIZE=1>
</SELECT> <br>
Enter Text Here <INPUT TYPE="TEXT" NAME="Text Input" SIZE=20 MAXLENGTH=25>
   Enter Your Password here
Pick one of the following <br>
<INPUT TYPE="RADIO" NAME="Radio" VALUE="First"> First <BR>
<INPUT TYPE="RADIO" NAME="Radio" VALUE="Second" CHECKED> Second <br>
Pick from of the following <br>
<INPUT TYPE="CHECKBOX" NAME="check" VALUE="First"> First
<INPUT TYPE="CHECKBOX" NAME="check" VALUE="third" CHECKED> Third <br>
Enter your comments <TEXTAREA NAME="Comments" ROWS=2 COLUMNS=60> </textarea>
<p>When done, press the button below <br>
<INPUT TYPE="Submit" NAME="Submit This Form">
<INPUT TYPE="Reset" NAME="Clear">

# forms1.ksh
echo "Content-type: text/html"
echo ""
echo "<html>"
echo "<head>"
#  Handle error conditions or send success message to user
if [[ $CONTENT_LENGTH = 0 ]]; then
   echo "<title>Error Occured</title>"
   echo "</head> <body>"
   echo "<p>The form is empty, please enter some data!"
   echo "<br>"
   echo "Press the BACK key to return to the form."
   echo "</p> </body> </html>"
   exit 0
elif [[ $CONTENT_TYPE != "application/x-www-form-urlencoded" ]]; then
   echo "<title>Content Error Occurred</title>"
   echo "</head> <body>"
   echo "<p>Internal error - invalid content type.  Please report"
   echo "<br>"
   echo "Press the BACK key to return to the form."
   echo "</p> </body> </html>"
   exit 0
echo "<title>Form Submitted</title>"
echo "</head> <body>"
echo "<h1>Your Form Has Been Submitted</h1>"
echo "<p> Thank you very much for your input, it has been "
echo "submitted to our people to deal with... "
echo "<br>"
echo "Press the BACK key to return to the form."
#   Create empty temporary file.  Use process-id to make unique
#   Read STDIN and write to the temporary file (translate & and +).
#   NOTE: this is dangerous and sloppy - you should translate the
#         encode characters and ensure there is not any bad information
read input_line
echo 'date' >> $FNAME
echo $input_line | tr "&+" "\n " >> $FNAME
#  Send the form data via mail, clean up the temporary file
mail -s "Form data" someuser@somecompany.com < $FNAME
echo "</p> </body> </html>"
exit 0