cgi-lib.pl
in some respects. It provides a simple
interface for parsing and interpreting query strings passed to CGI
scripts. However, it also offers a rich set of functions for creating
fill-out forms. Instead of remembering the syntax for HTML form
elements, you just make a series of perl function calls. An important
fringe benefit of this is that the value of the previous query is used
to initialize the form, so that the state of the form is preserved
from invocation to invocation.
Everything is done through a ``CGI'' object. When you create one of these objects it examines the environment for a query string, parses it, and stores the results. You can then ask the CGI object to return or modify the query values. CGI objects handle POST and GET methods correctly, and correctly distinguish between scripts called from <ISINDEX> documents and form-based documents. In fact you can debug your script from the command line without worrying about setting up environment variables.
A script to create a fill-out form that remembers its state each time it's invoked is very easy to write with CGI.pm:
use CGI; $query = new CGI; print $query->header; print $query->startform; print "What's your name? ",$query->textfield('name'); print "<P>What's the combination? ", $query->checkbox_group(-name=>'words', -values=>['eenie','meenie','minie','moe']); print "<P>What's your favorite color? ", $query->popup_menu(-name=>'color', -values=>['red','green','blue','chartreuse']); print "<P>",$query->submit; print $query->endform; print "<HR>\n"; if ($query->param) { print "Your name is <EM>",$query->param('name'),"</EM>\n"; print "<P>The keywords are: <EM>",join(", ",$query->param('words')),"</EM>\n"; print "<P>Your favorite color is <EM>",$query->param('color'),"</EM>\n"; }Select this link to try the script
To use this package,
install it in your perl library path. On most systems this will be
/usr/local/lib/perl5
, but check with the administrator
of your system to be sure.
Thanks to Andreas Koenig and his MakeMaker program, CGI.pm can now install itself in the right place if you're using a Unix system. Move to the directory containing CGI.pm and type the following commands:
% perl Makefile.PL % make % make installYou may need to be root to do the last step.
To use CGI.pm in your scripts, place the following statement at the top of your perl CGI programs:
use CGI;If you do not have sufficient privileges to install into /usr/local/lib/perl5, you can still use CGI.pm. Place it in some convenient place, for example, in
/usr/local/etc/httpd/cgi-bin
and preface your CGI
scripts with a preamble something like the following:
BEGIN { unshift@INC,'/usr/local/etc/httpd/cgi-bin'); } use CGI;Be sure to replace /usr/local/etc/httpd/cgi-bin with the location of CGI.pm.
Notes on using CGI.pm on non-Unix platforms
$query = new CGIThis will parse the input (from both POST and GET methods) and store it into a perl5 object called $query.
An alternative form of the new() method allows you to read the contents of the form from a previously-opened file handle:
$query = new CGI(FILEHANDLE)The filehandle can contain a URL-encoded query string, or can be a series of newline delimited TAG=VALUE pairs. This is compatible with the save() method. This lets you save the state of a form to a file and reload it later!. See advanced techniques for more information.
@keywords = $query->keywordsIf the script was invoked as the result of an <ISINDEX> search, the parsed keywords can be obtained with the keywords() method. This method will return the keywords as a perl array.
@names = $query->paramIf the script was invoked with a parameter list (e.g. "name1=value1&name2=value2&name3=value3"), the param() method will return the parameter names as a list. For backwards compatability, this method will work even if the script was invoked as an <ISINDEX> script: in this case there will be a single parameter name returned named 'keywords'.
@values = $query->param('foo'); -or- $value = $query->param('foo');Pass the param() method a single argument to fetch the value of the named parameter. If the parameter is multivalued (e.g. from multiple selections in a scrolling list), you can ask to receive an array. Otherwise the method will return a single value.
As of version 1.50 of this library, the array of parameter names returned
will be in the same order in which the browser sent them. Although
this is not guaranteed to be identical to the order in which the
parameters were defined in the fill-out form, this is usually the
case.
Setting The Value(s) Of A Named Parameter
$query->param('foo','an','array','of','values');This sets the value for the named parameter 'foo' to one or more values. These values will be used to initialize form elements, if you so desire. Note that this is the correct way to change the value of a form field from its current setting.
$query->delete('foo');This deletes a named parameter entirely. This is useful when you want to reset the value of the parameter so that it isn't passed down between invocations of the script.
$query->import_names('R'); print "Your name is $R::name\n" print "Your favorite colors are @R::colors\n";This imports all parameters into the given name space. For example, if there were parameters named 'foo1', 'foo2' and 'foo3', after executing
$query->import('R')
, the variables
@R::foo1, $R::foo1, @R::foo2, $R::foo2,
etc. would
conveniently spring into existence. Since CGI has no way of
knowing whether you expect a multi- or single-valued parameter,
it creates two variables for each parameter. One is an array,
and contains all the values, and the other is a scalar containing
the first member of the array. Use whichever one is appropriate.
For keyword (a+b+c+d) lists, the variable @R::keywords will be
created.
If you don't specify a name space, this method assumes namespace "Q".
Warning: do not import into namespace 'main'. This represents a major security risk, as evil people could then use this feature to redefine central variables such as @INC. CGI.pm will exit with an error if you try to do this.
Note: this method used to be called import() (and CGI.pm still recognizes this method call). However the official method name has been changed to import_names() for compatability with the CGI:: modules.
$query->save(FILEHANDLE)This writes the current query out to the file handle of your choice. The file handle must already be open and be writable, but other than that it can point to a file, a socket, a pipe, or whatever. The contents of the form are written out as TAG=VALUE pairs, which can be reloaded with the new() method at some later time. See advanced techniques for more information.
$my_url=$query->self_urlThis call returns a URL that, when selected, reinvokes this script with all its state information intact. This is most useful when you want to jump around within a script-generated document using internal anchors, but don't want to disrupt the current contents of the form(s). See advanced techniques for an example.
$field = $query->radio_group(-name=>'OS', -values=>[Unix,Windows,Macintosh], -default=>'Unix');The advantages of this style are that you don't have to remember the exact order of the arguments, and if you leave out a parameter, iit will usually default to some reasonable value. If you provide a parameter that the method doesn't recognize, it will usually do something useful with it, such as incorporating it into the HTML tag as an attribute. For example if Netscape decides next week to add a new JUSTIFICATION parameter to the text field tags, you can start using the feature without waiting for a new version of CGI.pm:
$field = $query->textfield(-name=>'State', -default=>'gaseous', -justification=>'RIGHT');This will result in an HTML tag that looks like this:
<INPUT TYPE="textfield" NAME="State" VALUE="gaseous" JUSTIFICATION="RIGHT">Parameter names are case insensitive: you can use -name, or -Name or -NAME. You don't have to use the hyphen if you don't want to. After creating a CGI object, call the
use_named_parameters()
method with
a nonzero value. This will tell CGI.pm that you intend to use named
parameters exclusively:
$query = new CGI; $query->use_named_parameters(1); $field = $query->radio_group('name'=>'OS', 'values'=>['Unix','Windows','Macintosh'], 'default'=>'Unix');Actually, CGI.pm only looks for a hyphen in the first parameter. So you can leave it off subsequent parameters if you like. Something to be wary of is the potential that a string constant like "values" will collide with a keyword (and in fact it does!) While Perl usually figures out when you're referring to a function and when you're referring to a string, you probably should put quotation marks around all string constants just to play it safe.
print $query->header('image/gif');This prints out the required HTTP Content-type: header and the requisite blank line beneath it. If no parameter is specified, it will default to 'text/html'.
An extended form of this method allows you to specify a status code and a message to pass back to the browser:
print $query->header(-type=>'image/gif', -status=>'204 No Response');This presents the browser with a status code of 204 (No response). Properly-behaved browsers will take no action, simply remaining on the current page. (This is appropriate for a script that does some processing but doesn't need to display any results, or for a script called when a user clicks on an empty part of a clickable image map.)
You can add any number of HTTP headers, including ones that aren't implemented, using named parameters:
print $query->header(-type=>'image/gif', -status=>'402 Payment Required', -Cost=>'$0.02');
print $query->redirect('http://somewhere.else/in/the/world');This generates a redirection request for the remote browser. It will immediately go to the indicated URL. You should exit soon after this. Nothing else will be displayed.
You can add your own headers to this as in the header() method.
named parameter style print $query->start_html(-title=>'Secrets of the Pyramids', -author=>'fred@capricorn.org', -base=>'true', -BGCOLOR=>"#00A0A0"'); old style print $query->start_html('Secrets of the Pyramids', 'fred@capricorn.org','true', 'BGCOLOR="#00A0A0"')This will return a canned HTML header and the opening <BODY> tag. All parameters are optional:
print $query->end_htmlThis ends an HTML document by printing the </BODY></HTML> tags.
General note 2. The default values that you specify for the forms are only used the first time the script is invoked. If there are already values present in the query string, they are used, even if blank. If you want to change the value of a field from its previous value, call the param() method to set it. If you want to reset the fields to their defaults, you can:
autoEscape()
method with a false value immediately after creating the CGI object:
$query = new CGI; $query->autoEscape(undef);You can turn autoescaping back on at any time with
$query->autoEscape('yes')
print $query->isindex($action);isindex() without any arguments returns an <ISINDEX> tag that designates your script as the URL to call. If you want the browser to call a different URL to handle the search, pass isindex() the URL you want to be called.
print $query->startform($method,$action,$encoding); ...various form stuff... print $query->endform;startform() will return a <FORM> tag with the optional method, action and form encoding that you specify. endform() returns a </FORM> tag.
The form encoding is a new feature introduced in version 1.57 in order to support the "file upload" feature of Netscape 2.0. The form encoding tells the browser how to package up the contents of the form in order to transmit it across the Internet. There are two types of encoding that you can specify:
$CGI::URL_ENCODED
.
$CGI::MULTIPART
Forms that use this type of encoding are not easily interpreted by CGI scripts unless they use CGI.pm or another library that knows how to handle them. Unless you are using the file upload feature, there's no particular reason to use this type of encoding.
startform()
.
print $query->start_multipart_form($method,$action,$encoding); ...various form stuff... print $query->endform;This has exactly the same usage as
startform()
, but
it specifies form encoding type multipart/form-data
as the default.
Named parameter style print $query->textfield(-name=>'field_name', -default=>'starting value', -size=>50, -maxlength=>80); Old style print $query->textfield('foo','starting value',50,80);textfield() will return a text input field.
$value = $query->param('foo');
Named parameter style print $query->textarea(-name=>'foo', -default=>'starting value', -rows=>10, -columns=>50); Old style print $query->textarea('foo','starting value',10,50);textarea() is just like textfield(), but it allows you to specify rows and columns for a multiline text entry box. You can provide a starting value for the field, which can be long and contain multiple lines.
Named parameter style print $query->password_field(-name=>'secret', -value=>'starting value', -size=>50, -maxlength=>80); Old style print $query->password_field('secret','starting value',50,80);password_field() is identical to textfield(), except that its contents will be starred out on the web page.
Named parameters style print $query->filefield(-name=>'uploaded_file', -default=>'starting value', -size=>50, -maxlength=>80); Old style print $query->filefield('uploaded_file','starting value',50,80);filefield() will return a form field that prompts the user to upload a file.
In order to take full advantage of the file upload
facility you must use the new multipart
form encoding scheme. You can do this either
by calling startform()
and specify an encoding type of $CGI::MULTIPART
or by using the new start_multipart_form()
method. If you don't use multipart encoding, then you'll be
able to retreive the name of the file selected by the remote
user, but you won't be able to access its contents.
When the form is processed, you can retrieve the entered filename by calling param().
$filename = $query->param('uploaded_file');Under Netscape 2.0beta1 the filename that gets returned is the full local filename on the remote user's machine. If the remote user is on a Unix machine, the filename will follow Unix conventions:
/path/to/the/fileOn an MS-DOS/Windows machine, the filename will follow DOS conventions:
C:\PATH\TO\THE\FILE.MSWOn a Macintosh machine, the filename will follow Mac conventions:
HD 40:Desktop Folder:Sort Through:RemindersNetscape 2.0beta2 changes this behavior and only returns the name of the file itself. Who knows what the behavior of the release browser will be.
The filename returned is also a file handle. You can read the contents of the file using standard Perl file reading calls:
# Read a text file and print it out while (<$filename>) { print; } # Copy a binary file to somewhere safe open (OUTFILE,">>/usr/local/web/users/feedback"); while ($bytesread=read($filename,$buffer,1024)) { print OUTFILE $buffer; }You can have several file upload fields in the same form, and even give them the same name if you like (in the latter case
param()
will return a list of file names).
Caveats and potential problems in
the file upload feature.
Creating A Popup Menu
Named parameter style print $query->popup_menu(-name=>'menu_name', -values=>['eenie','meenie','minie'], -default=>'meenie', -labels=>{'eenie'=>'one','meenie'=>'two', 'minie'=>'three'}); Old style print $query->popup_menu('menu_name', ['eenie','meenie','minie'],'meenie', {'eenie'=>'one','meenie'=>'two','minie'=>'three'});popup_menu() creates a menu.
$popup_menu_value = $query->param('menu_name');
Named parameter style print $query->scrolling_list(-name=>'list_name', -values=>['eenie','meenie','minie','moe'], -default=>['eenie','moe'], -size=>5, -multiple=>'true', -labels=>\%labels); Old style print $query->scrolling_list('list_name', ['eenie','meenie','minie','moe'], ['eenie','moe'],5,'true', \%labels);scrolling_list() creates a scrolling list.
%labels
has already been created.
@selected = $query->param('list_name');
Named parameter style print $query->checkbox_group(-name=>'group_name', -values=>['eenie','meenie','minie','moe'], -default=>['eenie','moe'], -linebreak=>'true', -labels=>\%labels); Old Style print $query->checkbox_group('group_name', ['eenie','meenie','minie','moe'], ['eenie','moe'],'true',\%labels); HTML3 Browsers Only print $query->checkbox_group(-name=>'group_name', -values=>['eenie','meenie','minie','moe'], -rows=>2,-columns=>2);checkbox_group() creates a list of checkboxes that are related by the same name.
%labels
has previously been created.
To include row and column headings in the returned table, you can use the -rowheader and -colheader parameters. Both of these accept a pointer to an array of headings to use. The headings are just decorative. They don't reorganize the interpetation of the checkboxes -- they're still a single named unit.
When viewed with browsers that don't understand HTML3 tables, the -rows and -columns parameters will leave you with a group of buttons that may be awkwardly formatted but still useable. However, if you add row and/or column headings, the resulting text will be very hard to read.
@turned_on = $query->param('group_name');
Named parameter list print $query->checkbox(-name=>'checkbox_name', -checked=>'checked', -value=>'TURNED ON', -label=>'Turn me on'); Old style print $query->checkbox('checkbox_name',1,'TURNED ON','Turn me on');checkbox() is used to create an isolated checkbox that isn't logically related to any others.
$turned_on = $query->param('checkbox_name');
Named parameter style print $query->radio_group(-name=>'group_name', -values=>['eenie','meenie','minie'], -default=>'meenie', -linebreak=>'true', -labels=>\%labels); Old style print $query->radio_group('group_name',['eenie','meenie','minie'], 'meenie','true',\%labels); HTML3-compatible browsers only print $query->radio_group(-name=>'group_name', -values=>['eenie','meenie','minie','moe'], -rows=2,-columns=>2);radio_group() creates a set of logically-related radio buttons. Turning one member of the group on turns the others off.
\@foo
.
%labels
has already been defined.
To include row and column headings in the returned table, you can use the -rowheader and -colheader parameters. Both of these accept a pointer to an array of headings to use. The headings are just decorative. They don't reorganize the interpetation of the radio buttons -- they're still a single named unit.
When viewed with browsers that don't understand HTML3 tables, the -rows and -columns parameters will leave you with a group of buttons that may be awkwardly formatted but still useable. However, if you add row and/or column headings, the resulting text will be very hard to read.
$which_radio_button = $query->param('group_name');
Named parameter style print $query->submit(-name=>'button_name', -value=>'value'); Old style print $query->submit('button_name','value');submit() will create the query submission button. Every form should have one of these.
$which_one = $query->param('button_name');
print $query->resetreset() creates the "reset" button. It undoes whatever changes the user has recently made to the form, but does not necessarily reset the form all the way to the defaults. See defaults() for that. It takes the optional label for the button ("Reset" by default).
print $query->defaults('button_label')defaults() creates "reset to defaults" button. It takes the optional label for the button ("Defaults" by default). When the user presses this button, the form will automagically be cleared entirely and set to the defaults you specify in your script, just as it was the first time it was called.
Named parameter style print $query->hidden(-name=>'hidden_name', -default=>['value1','value2'...]); Old style print $query->hidden('hidden_name','value1','value2'...);hidden() produces a text field that can't be seen by the user. It is useful for passing state variable information from one invocation of the script to the next.
Hidden fields used to behave differently from all other fields: the provided default values always overrode the "sticky" values. This was the behavior people seemed to expect, however it turns out to make it harder to write state-maintaining forms such as shopping cart programs. Therefore I have made the behavior consistent with other fields.
Just like all the other form elements, the value of a hidden field is "sticky". If you want to replace a hidden field with some other values after the script has been called once you'll have to do it manually before writing out the form element:
$query->param('hidden_name','new','values','here'); print $query->hidden('hidden_name');Fetch the value of a hidden field this way:
$hidden_value = $query->param('hidden_name'); -or (for values created with arrays)- @hidden_values = $query->param('hidden_name');
Named parameter style print $query->image_button(-name=>'button_name', -src=>'/images/NYNY.gif', -align=>'MIDDLE'); Old style print $query->image_button('button_name','/source/URL','MIDDLE');image_button() produces an inline image that acts as a submission button. When selected, the form is submitted and the clicked (x,y) coordinates are submitted as well.
$x = $query->param('button_name.x'); $y = $query->param('button_name.y');
autoEscape()
.
Use
$query->autoEscape(undef);to turn automatic HTML escaping off, and
$query->autoEscape('true');to turn it back on.
my_script.pl keyword1 keyword2 keyword3or this:
my_script.pl keyword1+keyword2+keyword3or this:
my_script.pl name1=value1 name2=value2or this:
my_script.pl name1=value1&name2=value2or even by sending newline-delimited parameters to standard input:
% my_script.pl first_name=fred last_name=flintstone occupation='granite miner' ^D
When debugging, you can use quotation marks and the backslash character to escape spaces and other funny characters in exactly the way you would in the shell (which isn't surprising since CGI.pm uses "shellwords.pl" internally). This lets you do this sort of thing:
my_script.pl 'name 1'='I am a long value' "name 2"=two\ wordsTable of contents
print $query->dumpProduces something that looks like this:
<UL> <LI>name1 <UL> <LI>value1 <LI>value2 </UL> <LI>name2 <UL> <LI>value1 </UL> </UL>You can achieve the same effect by incorporating the CGI object directly into a string, as in:
print "<H2>Current Contents:</H2>\n$query\n";
$query->accept('text/html')
, it will return a
floating point value corresponding to the browser's
preference for this type from 0.0 (don't want) to 1.0.
Glob types (e.g. text/*) in the browser's accept list
are handled correctly.
/cgi-bin/your_script/additional/stuff
will
result in $query->path_info()
returning
"/additional/stuff"
"/usr/local/etc/httpd/htdocs/additional/stuff"
.
#!/usr/local/bin/perl use CGI; $query = new CGI; print $query->header; print $query->start_html("Save and Restore Example"); print "<H1>Save and Restore Example</H1>\n"; # Here's where we take action on the previous request &save_parameters($query) if $query->param('action') eq 'save'; $query = &restore_parameters($query) if $query->param('action') eq 'restore'; # Here's where we create the form print $query->startform; print "Popup 1: ",$query->popup_menu('popup1',['eenie','meenie','minie']),"\n"; print "Popup 2: ",$query->popup_menu('popup2',['et','lux','perpetua']),"\n"; print "<P>"; print "Save/restore state from file: ",$query->textfield('savefile','state.sav'),"\n"; print "<P>"; print $query->submit('action','save'),$query->submit('action','restore'); print $query->submit('action','usual query'); print $query->endform; # Here we print out a bit at the end print $query->end_html; sub save_parameters { local($query) = @_; local($filename) = &clean_name($query->param('savefile')); if (open(FILE,">$filename")) { $query->save(FILE); close FILE; print "<STRONG>State has been saved to file $filename</STRONG>\n"; } else { print "<STRONG>Error:</STRONG> couldn't write to file $filename: $!\n"; } } sub restore_parameters { local($query) = @_; local($filename) = &clean_name($query->param('savefile')); if (open(FILE,$filename)) { $query = new CGI(FILE); # Throw out the old query, replace it with a new one close FILE; print "<STRONG>State has been restored from file $filename</STRONG>\n"; } else { print "<STRONG>Error:</STRONG> couldn't restore file $filename: $!\n"; } return $query; } # Very important subroutine -- get rid of all the naughty # metacharacters from the file name. If there are, we # complain bitterly and die. sub clean_name { local($name) = @_; unless ($name=~/^[\w\.-]+$/) { print "<STRONG>$name has naughty characters. Only "; print "alphanumerics are allowed. You can't use absolute names.</STRONG>"; die "Attempt to use naughty characters"; } return $name; }
Many people have experienced problems with internal links on pages that have forms. Jumping around within the document causes the state of the form to be reset. A partial solution is to use the self_url() method to generate a link that preserves state information. This script illustrates how this works.
#!/usr/local/bin/perl use CGI; $query = new CGI; # We generate a regular HTML file containing a very long list # and a popup menu that does nothing except to show that we # don't lose the state information. print $query->header; print $query->start_html("Internal Links Example"); print "<H1>Internal Links Example</H1>\n"; print "<A NAME=\"start\"></A>\n"; # an anchor point at the top # pick a default starting value; $query->param('amenu','FOO1') unless $query->param('amenu'); print $query->startform; print $query->popup_menu('amenu',[('FOO1'..'FOO9')]); print $query->submit,$query->endform; # We create a long boring list for the purposes of illustration. $myself = $query->self_url; print "<OL>\n"; for (1..100) { print qq{<LI>List item #$_<A HREF="$myself#start">Jump to top</A>\n}; } print "</OL>\n"; print $query->end_html;
There is, however, a problem with maintaining the states of multiple forms. Because the browser only sends your script the parameters from the form in which the submit button was pressed, the state of all the other forms will be lost. One way to get around this, suggested in this example, is to use hidden fields to pass as much information as possible regardless of which form the user submits.
#!/usr/local/bin/perl use CGI; $query=new CGI; print $query->header; print $query->html_header('Multiple forms'); print "<H1>Multiple forms</H1>\n"; # form 1 print "<HR>\n"; print $query->startform; print $query->textfield('text1'),$query->submit('submit1'); print $query->hidden('text2'); # pass information from the other form print $query->endform; print "<HR>\n"; # form 2 print $query->startform; print $query->textfield('text2'),$query->submit('submit2'); print $query->hidden('text1'); # pass information from the other form print $query->endform; print "<HR>\n";
Another potential problem with the file upload feature is that
it potentially involves sending more data to the CGI script
than the script can hold in main memory. For this reason
CGI.pm creates temporary files in
either the /usr/tmp
or the /tmp
directory. If neither of these These temporary files
have names like CGItemp125421
, and should be
deleted automatically.
CGITemp123456
in a temporary
directory. To find a suitable directory it first looks
for /usr/tmp
and then for /tmp
.
If it can't find either of these directories, it tries
for the current directory, which is usually the same
directory that the script resides in.
If you're on a non-Unix system you may need to modify CGI.pm to point at a suitable temporary directory. This directory must be writable by the user ID under which the server runs (usually "nobody") and must have sufficient capacity to handle large file uploads. Open up CGI.pm, and find the line:
package TempFile; foreach ('/usr/tmp','/tmp') { do {$TMPDIRECTORY = $_; last} if -w $_; }Modify the foreach() line to contain a series of one or more directories to store temporary files in.
CGI.pm must be put in the perl5 library directory, and all CGI scripts that
use it should be placed in WebSite's cgi-shl
directory. You also
need to associate the .pl
suffix with perl5 using the NT
file manager.
Find the line:
$CRLF="\r\n"and change it to:
$CRLF="\n";This should do the trick.
The current version of CGI.pm can be found at:
http://www-genome.wi.mit.edu/ftp/pub/software/WWW/
You are encouraged to look at these other Web-related modules:
For a collection of CGI scripts of various levels of complexity, see the companion pages for my book How to Set Up and Maintain a World Wide Web Site
autoEscape()
method.
Last modified September 27, 1995.