The Fool’s Guide to CGI.pm

No, this guide is not for fools but by a fool, who had kept wondering how CGI.pm is supposed to work and finally got a clue. So here’s how (I think) it works in simplest cases. I hope this might make some other people’s learning curve a little better.

Content:

Prerequisites

You need

  • a little idea of how Web pages are created, especially
    a vague idea of HTML forms;
    actually it might suffice to have used a simple query
    form, and you can hardly have avoided them when surfing.
    But just in case you have, here’s a sample form:


  • elementary knowledge about
    Perl; something like the following
    shouldn’t scare you too much:

    foo = 'hello world'; $foo =~ s/ / cruel /; print $foo;
  • a vague idea of
    what CGI is; you should faintly understand that some URLs,
    when accessed, may activate a program (script) on a server;
    the following does:

    1
    2
    3
    <a href=
    "http://www.cs.tut.fi/cgi-bin/run/~jkorpela/echo.cgi"
    >http://www.cs.tut.fi/cgi-bin/run/~jkorpela/echo.cgi</a>
  • some idea of what you might want to do with CGI scripting
  • capabilities of using CGI scripting in Perl on some
    server
    as well as local information on
    how to do that; contact the local Web server’s documentation or
    the server administration (

    1
    webmaster@

    servername)
    for information.

Hello world

OK, first do this: Check that you have the above-mentioned
local information. Double-check that you have understood it.
You should now know where to put your Perl programs in order to run
them as CGI scripts and what you might need to do with them
(setting file protections, perhaps). Fine, then test this with
the following script:

print <<END;
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<title>Hello</title>
<h1>Hello world!!</h1>
END

That is, write that into a file,
perhaps adding something into it as needed according to local
instructions
(such as a “magic”

1
#!

line that tells the path name of
the Perl interpreter on the server)
store the file where you need
to put your CGI scripts, and try accessing it via a link or
by directly typing the URL to a browser. It should work
basically the same way as the following link:

1
<a href="http://www.cs.tut.fi/cgi-bin/run/~jkorpela/hw.pl">http://www.cs.tut.fi/cgi-bin/run/~jkorpela/hw.pl</a>

You will probably need some help to make
the script work. There are several practical things that need to
be set properly, and they vary from one server to another.
There’s a useful (though Unix-oriented) general resource: the
checklist
The Idiot’s Guide to Solving Perl CGI Problems.

You haven’t used CGI.pm yet. The trivial program above just
writes some fixed text. When executed as a CGI script, the first
lines of the output (up to the first empty line) will be taken as
HTTP headers and sent to the browser,
and the rest is sent to the browser as actual data (HTML document,
in this case, and quite often).

Just echoing data

Next we’ll actually use CGI.pm but for a very simple purpose:
we have a form with a text input field and we echo back the data
that the user has typed. Trivial, but it’s the start.

Our form is the following:

<form action="http://www.cs.tut.fi/cgi-bin/run/~jkorpela/echo1.cgi"">
<div>Please type some text: <input name="sample" size="20"></div>
<div><input type="submit"></div>
</form>

It looks like the following:

Please type some text:

Try it!

The Perl program referred to in the

1
action

attribute
is the following:

use CGI qw(:standard);
$data = param('sample') || '<i>(No input)</i>';

print <<END;
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<title>Echoing user input</title>
<h1>Echoing user input</h1>
<p>You typed: $data</p>
END

So how does it work? The line

1
use CGI qw(:standard);

is a prelude needed for using CGI.pm (in a particular mode).
And the line after it,

1
$data = param('sample') || '&lt;i>(No input)&lt;/i>';

is in this case the only part of the program that uses CGI.pm,
and it uses it only by calling the

1
param

function.
That function gives the value of the form field whose name is
passed as parameter. Note that the value might be undefined,
even if a field with that name is present in the form markup;
if the user does not fill out the text input field, there will
be no such field in the form data. (In Perl we can handle undefined
values conveniently; if the value is undefined,
the code above uses a fixed string instead.)

This works irrespectively of the

1
<a href="../forms/methods.html">method</a>

(

1
"get"

or

1
"post"

) of the form. More
generally, we need not worry about the mechanisms of
form data transmission, such as “URL encoding”.

You might have tried submitting text like <b>foo</b>
and noticed that the HTML markup “works”. This is simply because the
script writes the data as such into a document that will be sent to the
browser and treated as an HTML document. To prevent characters from
being treated as part of HTML markup, you could add

1
$data&nbsp;= escapeHTML($data)

into the script (before printing anything out).

Processing the data

If we’d like to store the data, we
could do it using Perl’s
normal file operations. So this isn’t interesting from the CGI.pm
point of view, though of course it can be very relevant for
practical purposes. The same applies to other processing
of data, like sending it by E-mail.

But for illustration, here’s
how to set up a simple form that collects data and saves it
onto a file:

#!/usr/local/gnu/bin/perl
use CGI qw(:standard);

print <<END;
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
END

$regfile = '../public_html/perl/registrations.tsv';

$name = param('name');
$email = param('email');
$food = param('food');

open(REG,">>$regfile") or fail();
print REG "$name\t$email\t$food\n";
close(REG);

($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
    gmtime(time);
$now = sprintf "%4d-%02d-%02dT%02d:%02dZ\n",
    1900+$year,$mon+1,$mday,$hour,$min;

print <<END;
<title>Thank you!</title>
<h1>Thank you!</h1>
<p>Your fake registration to Virtual Nonsense Party
has been recorded at $now as follows:</p>
<p>Name: $name</p>
<p>E-mail: $email</p>
<p>Food preference: $food</p>
END

sub fail {
   print "<title>Error</title>",
   "<p>Error: cannot record your registration!</p>";
   exit; }

The form is simple:

<form action="http://www.cs.tut.fi/cgi-bin/run/~jkorpela/collect.pl"
 method="post">
<div>Fake registration to virtual party:<div>
<div>Name: <input name="name" size="30"></div>
<div>E-mail: <input name="email" size="25"></div>
<div><input type="radio" name="food" value="" checked>
Food preference (select one):<br>
<input type="radio" name="food" value="Swedish meatballs">
 Swedish meatballs<br>
<input type="radio" name="food" value="Fish sticks">
 Fish sticks<br>
<input type="radio" name="food" value="Falafel">
 Falafel (this is vegetarian food)<br>
<input type="radio" name="food" value="no food">
 None (i.e., will not eat)<div>
<div><input type="submit"></div>
</form>

OK, you can try it:

Fake registration to virtual party:
Name:
E-mail:

Food preference (select one):

Swedish meatballs

Fish sticks

Falafel (this is vegetarian food)

None (i.e., will not eat)

Notes:

  • The time stamp is in

    ISO 8601
    notation.

  • The file format used is
    Tab Separated Values, to allow
    easy processing using various programs.

  • You can view the

    registrations file
    .
    (This file is in a directory different from the one where the script is.
    Such arrangements are server-specific.)

  • There is no checking of input data, to keep the example
    reasonably small. In a real-life situation, you would probably
    want to check at least against empty fields.

Checking data

One of the great advantages of using CGI.pm is that it provides
very simple tools for
checking data acceptability and requesting the user to fix the data.
For example, we might wish to require that some name be given
(i.e. the name field is not empty or blank),
that an E-mail address satisfying some formal requirements is given,
and that some food preference is selected.
In real-life cases, you would probably want to do some real checking,
like verifying a user name and password combination.
But the basic techniques are perhaps best illustrated with an almost
trivial example. And let’s say that the E-mail is checked only against
a missing “@” sign; this at least filters out some
(accidental) erroneous submissions.

(Since you are about to ask anyway:
No, you can’t really check an E-mail address except by trying to send
E-mail to it. See entry
Can I verify the email addresses people enter in my Form?
in the
CGI Programming FAQ.)

The fundamental idea is to generate a form dynamically
by a CGI script and do this so that the same script also processes
the submitted data. Confused? Well, let’s take an example
(you could also
see the example in action):

#!/usr/local/gnu/bin/perl
use CGI qw(:standard);

$regfile = '../public_html/perl/registrations.tsv';

print header;

if(param()) {
    $name = param('name');
    $email = param('email');
    $food = param('food');
    if(ok()) {
    open(REG,">>$regfile") or fail();
    print REG "$name\t$email\t$food\n";
    close(REG);
    print <<END;
<title>Thank you!</title>
<h1>Thank you!</h1>
<p>Your fake registration to Virtual Nonsense Party
has been recorded as follows:</p>
<p>Name: $name</p>
<p>E-mail: $email</p>
<p>Food preference: $food</p>
END
    exit; } }

%labels = (
  '' => 'Food preference (select one):',
  'Fish sticks' => 'Fish sticks',
  'Falafel' => 'Falafel',
  'no food' => 'None (i.e., will not eat)' );

print start_form, 'Fake registration to virtual party',br,
  'Name: ', textfield('name'), br,
  'E-mail: ', textfield('email'), br,
  radio_group(-name=>'food', -values=>\%labels, -linebreak=>'true',
              -default=>''),
  submit, end_form;

sub fail {
   print "<title>Error</title>",
   "<p>Error: cannot record your registration!</p>";
   exit; }

sub ok() {
    $fine = 1;
    if(!$name) { print 'Your name is required!', br; $fine = 0; }
    if(!$email) { print 'Your E-mail address is required!', br; $fine = 0; }
    elsif(!($email =~ m/\@/))
       { print 'An E-mail address must contain the @ character!', br;
         $fine = 0; }
    if(!$food) { print 'A food preference (even if none) is required!',
       br; $fine = 0; }
    if(!$fine) { print 'Please fix the data and resubmit', hr; }
    return $fine; }

Instead of writing a form which is statically part of an HTML
document, we have a script which generates an HTML document (which
contains a form). We can refer to the script using a normal link;
the URL is in this case

1
<a href="http://www.cs.tut.fi/cgi-bin/run/~jkorpela/coll.cgi">http://www.cs.tut.fi/cgi-bin/run/~jkorpela/coll.cgi</a>

and
what happens when that URL is referred to depends on the context:

  • If a plain link to it is followed or there are otherwise
    no parameters passed, the script just sends back an HTML document
    (containing the form).

  • If parameters are passed but they fail the checks that the
    script has been programmed to perform, an HTML document explaining the
    errors and containing the form again, with previous data prefilled.

  • If parameters are passed and they are
    accepted in the checks,
    the action proper (whatever it is) is executed and then a “thank you”
    page is returned. In this case, the “thank you” page echoes back the
    form data. (This is very useful, since it gives the user feedback
    that something happened, and the user can then print that confirmation
    page for himself, save it locally, or whatever.)

Some key ingredients in the script:

  • 1
    use CGI qw(:standard);

    is a “standard” prelude for
    using CGI.pm

  • 1
    header

    is an example of a CGI.pm routine; it returns
    a string that constitutes a suitable HTTP header for an HTML
    document so that you need not worry about such things

  • 1
    param()

    tests whether any parameters were
    passed in the script invocation; note than when the script is
    first invoked, that test probably yields false and we fall
    down to code that generates the basic form

  • 1
    param()

    with some string as argument can be used
    to get the value of a parameter with a specific name (if defined,
    i.e. if present in the form data; note that the code checks for
    parameters being defined, using constructs like

    1
    if(!$name)

    )

  • 1
    start_form

    etc. are CGI.pm routines for generating
    form elements; technically they return strings that you need to print
    out (using

    1
    print

    ) to make things happen

  • there are nice structured tools for constructing sets of fields,
    like a set of radio buttons; in our example, the hash

    1
    %labels

    defines both the names of the fields (in the form
    data) and the visible labels associated with them.

The details will not be explained here. This was basically
to give an idea of how things work with CGI.pm.
Note that if you wish to have a form embedded into a static HTML
page, you need something little more complicated. In effect, you would
need to duplicate things by writing a static

1
form

element
and the Perl code that generates a corresponding element dynamically
in the form handler when an error in data has been detected and
the form needs to be presented to the user. The reason is that dynamic
generation is the only way to use previous user input as default
values for fields so that the user does not need to type everything anew.
(Well, technically, you could save the data onto the server, but that
would be more complicated.)

Quick reference to CGI.pm functions

The following table lists some basic CGI.pm functions for
generating form fields and related elements.
Note that the statement

1
use CGI qw(:standard);

is needed to make these work and that the invocations
just generate HTML constructs as strings; you
need to use the

1
print

function to make them
actually appear in a document generated by your CGI script.

Basic CGI.pm functions
function invocation meaning
1
<strong>startform</strong>(<var>$method</var>,<var>$action</var>,<var>$encoding</var>)
starts a form
1
<strong>start_multipart_form</strong>(<var>$method</var>,<var>$action</var>)
starts a form that may contain a file input field
1
2
3
<strong>endform</strong>()</td>
 <td>ends a form</td></tr>
<tr><td><code><strong>textfield</strong>(-name=>'<var>field_name</var>', <nobr>-default</nobr>=><var>initial value</var>, <nobr>-size</nobr>=><var>number</var>,<nobr> -maxlength</nobr>=><var>number</var>)
single-line text input field
1
<strong>textarea</strong>(-name=>'<var>field_name</var>', <nobr>-default</nobr>=><var>initial value</var>, <nobr>-rows</nobr>=><var>number</var>, <nobr>-cols</nobr>=><var>number</var>)
multi-line text input field
1
<strong>password_field</strong>(-name=>'<var>field_name</var>', <nobr>-size</nobr>=><var>number</var>, <nobr>-maxlength</nobr>=><var>number</var>)
“password” input field
1
<strong>filefield</strong>(-name=>'<var>field_name</var>', <nobr>-default</nobr>=><var>initial filename</var>)
file input field
1
<strong>popup_menu</strong>(-name=>'<var>menu_name</var>', <nobr>-values</nobr>=><var>array or hash reference</var>, <nobr>-default</nobr>=><var>initial selection</var>)
1
select

element

1
<strong>scrolling_list</strong>(-name=>'<var>menu_name</var>', <nobr>-values</nobr>=><var>array or hash reference</var>, <nobr>-default</nobr>=><var>initial selection(s)</var>, <nobr>-size</nobr>=><var>number</var>, <nobr>-multiple</nobr>=>'true')
1
select

element with

1
size

larger than 1

1
<strong>checkbox_group</strong>(-name=>'<var>group_name</var>', <nobr>-values</nobr>=><var>array or hash reference</var>, <nobr>-default</nobr>=><var>initial selection(s), <nobr>-linebreak</nobr>=>'true')
group of checkboxes with the same name
1
<strong>checkbox</strong>(-name=>'<var>checkbox_name</var>', <nobr>-checked</nobr>=>'checked', <nobr>-value</nobr>=><var>internal value</var>, <nobr>-label</nobr>=><var>visible label</var>)
a standalone checkbox
1
<strong>radio_group</strong>(-name=>'<var>group_name</var>', <nobr>-values</nobr>=><var>array or hash reference</var>, <nobr>-default</nobr>=><var>initial selection(s), <nobr>-linebreak</nobr>=>'true')
group of radio buttons that work together
1
<strong>submit</strong>(-name=>'<var>button_name</var>', <nobr>-value</nobr>=><var>button text</var>)
submit button
1
<strong>reset</strong>( -value=><var>button text</var>)
reset button
1
<strong>defaults</strong>( -value=><var>button text</var>)
reset to original defaults
1
<strong>hidden</strong>(-name=>'<var>field_name</var>', <nobr>-default</nobr>=><var>array reference</var>)
hidden field
1
<strong>image_button</strong>(-name=>'<var>button_name</var>', <nobr>-src</nobr>=><var>image URL</var>, <nobr>-align</nobr>=><var>alignment</var>, <nobr>-alt</nobr>=><var>text</var>, <nobr>-value</nobr>=><var>text</var>)
image submit button
1
<strong>button</strong>(-name=>'<var>button_name</var>', <nobr>-value</nobr>=><var>button text</var>, <nobr>-onClick</nobr>=><var>script</var>)
1
button

element, for client-side scripting

Note: The parameters

1
<nobr>-multiple</nobr>=>'true'

and

1
<nobr>-checked</nobr>=>'checked'

are optional and used
to change the default (from allowing a single choice only
and from being initially unchecked, respectively).

For resetting fields, the

1
reset()

function is seldom useful. The

1
defaults()

function can
be used to set all fields to their very initial values as specified in
the script itself; the script will in fact be called as if it were called
the first time. In contrast, the button created with

1
reset()

will clear all changes that the user has made since the last invocation
of the script. Thus, if your code contains

1
textfield(-name=>'foo',-value='42')

, then

1
defaults()

will always set

1
foo

to

1
42

; but
if the user had changed the input box content to

1
100

and submitted
the form and got, in addition to other data, the same form in response, then
that form will (normally) contain the latest value

1
100

as response,
and

1
reset()

would set it back to

1
100

, not the
original 

1
42

.

An array reference
could consist just of an “anonymous array”, like

1
['none','apples,'oranges,'kiwis']

, or a reference to a named array, such
as

1
\@foo

. In order to make the texts seen by users as different
from the strings used internally, which is often a good idea, you would
use a hash reference instead, e.g.

1
-values=\%val

with

1
%val

defined by

1
2
%val = ( '0' => 'none', 'ap' => 'apples', 'or' => 'oranges',
'ki' => 'kiwis' );

But beware that in this case the order of the fields in the generated
HTML markup may not correspond to the order of the hash constructor
(since hashes are essentially unordered. Thus, to control the order,
use a

1
-values

array and, when desired, a separate

1
-labels

hash.

Further reading


Scroll to top