CGI.pm: a Perl Module for Web CGI Programming
Lincoln Stein
Capricorn Consulting
(Converted to HTML by Marjorie Roswell)
What you can do with CGI.pm
- Generate dynamic Web pages
- Create fill-out forms and process them
- File upload
- Multi-frame pages
- Dynamic style sheets
- Generate & process cookies
- Javascript-enhanced pages
What CGI.pm Does
- Generates page elements:
- HTTP headers
- HTML tags
- Stylesheet elements
- Processes CGI parameters
- Processes file uploads
- Creates cookies
- Helps maintain state in a variety of ways
- Integrates with FastCGI & mod_perl
CGI is easy; I can do it myself!
- Don’t reinvent the wheel
- Many tricks & gotchas in CGI protocol
- Published examples contain bugs & security holes
- Some stuff is obscure:
- cookies
- expiration dates
- file upload
First Example
#!/usr/bin/perl
use CGI ':standard'
print
header,
start_html('Example 1'),
h1('Hello World!'),
"Wow, I'm speaking HTML!",
hr,
end_html;
Output from 1st Example
Content-type: text/html
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML><HEAD>
<TITLE>Example 1</TITLE>
</HEAD>
<BODY>
<H1>Hello World!</H1>
Wow, I'm speaking HTML!
<HR>
</BODY>
</HTML>
What it Looks Like
Hello World!
Wow, I'm speaking HTML!
|
CGI Script Basics, Part 1
- Script must be executable
- Must be recognized by server as a script
- In a special directory: cgi-bin
- Special extension: .cgi
- Must write to standard output:
- HTTP header
- <CRLF><CRLF>
- Document to be displayed (optional)
How CGI Scripts are Executed
The HTTP Header
- Information used by browser and server
- Status line (usually added automatically):
HTTP/1.0 200 OK
Content-type: text/html
Content-language: en
Content-length: 1255
Expires: 05-Jun-1998 08:20:33 GMT
The header() Function
Content-type: text/html\r\n\r\n
- print header('image/gif');
Content-type: image/gif\r\n\r\n
- print header(-type=>'image/gif');
Content-type: image/gif\r\n\r\n
- print header(-type=>'image/gif',
-expires=>'+3d';
Expires: 22-Aug-1997 08:20:33 GMT
Content-type: image/gif\r\n\r\n
The -status Argument
- print header(-status=>'403 Forbidden');
- Generates a "you are not authorized to access this page" message.
- print header(
-status=>'401 Authentication required',
'-auth-type'=>'Basic'
);
- Browser puts up user name & password dialog.
header() Arguments
-type -content-encoding
-expires -content-language
-status -content-transfer-encoding
-location -date
-server -auth-type
anything else in current or future specs
print header(-type=>'image/gif',
-status=>'402 Payment Required',
-cost=>'$0.02');
HTML Shortcuts
h1( )
h2( )
h3( )
strong( )
li( )
dl( )
tt( )
p( )
a( )
em( )
ol( )
dt( )
b( )
hr( )
pre( )
ul( )
dd( )
i( )
blockquote( )
etcetera
Using HTML Shortcuts
<P>
- print p('This is a paragraph.');
<P>This is a paragraph.</P>
- print p('This is ',b(bold),' face.');
This is <B>bold</B> face.
- print p({-align=>CENTER},
'This is centered.');
<P ALIGN="CENTER">This is centered.</P>
- print hr({-width=>3,-style=>raised});
<HR WIDTH="3" STYLE="raised">
- print a({-href=>'index.html'},'Home');
<A HREF="index.html">Home</A>
HTML Shortcuts Nest
print ol({-type=>'A'},
li('FORTRAN'),
li('C')
li('Java'),
li('Perl')
);
<OL TYPE="A">
<li>FORTRAN
<li>C
<li>Java
<li>Perl
</OL>
HTML Shortcuts are Distributive
@lang = qw(FORTRAN C Java Perl);
print ol({-type=>'A'},li(\@lang));
<OL TYPE="A">
<LI>FORTRAN
<LI>C
<LI>Java
<LI>Perl
</OL>
HTML 3.2 Extensions
use CGI qw(:standard :html3);
print table({-border=>''},
caption('Table 1'),
TR([th(['Language','Power']),
td(['Fortran', 'Low']),
td(['C', 'Medium']),
td(['Java', 'Medium']),
td(['Perl', 'High'])
]
)
);
What it Looks Like
Table 1
| Language | Power |
| Fortran | Low |
| C | Medium |
| Java | Medium |
| Perl | High |
Other Importable Sets
Creating New Shortcuts
use CGI qw(:standard marquee object
embed);
print
marquee({-text=>'Late breaking news...',
-fgcolor=>'yellow',
-bgcolor=>'black',
-speed=>30});
Non-Standard Shortcuts
- start_html()
- end_html()
- start_form()
- end_form()
- All form elements
start_html()
- print start_html(-title=>'ABC DEF');
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML><HEAD>
<TITLE>ABC DEF</TITLE>
</HEAD>
<BODY>
</BODY></HTML>
- Avoids a layer of nesting
start_html()Arguments
- -title
- -author
- -meta
- <META> information, such as keywords
- -bgcolor, -text, -vlink, -alink...
- various JavaScript arguments
A start_html()Example
#!/usr/bin/perl
use CGI ':standard';
print
header,
start_html(-title=>'Secrets of the Pyramids',
-author=>'fred@capricorn.org',
-meta=>{'keywords'=>'pharoah secret mummy',
'copyright'=>' 1996 King Tut'},
-text=>'yellow',
-bgcolor=>'black');
print h1("I've gone all yellow!"),
"Isn't CGI fun?",
hr(),
end_html();
What it Looks Like
I've gone all yellow!
Isn't CGI fun?
|
|
CGI Environment
- Server passes session information to CGI scripts
- Information includes
- host name
- server software & version
- browser software & version
- remote IP address
- remote DNS name
- remote user name (if authenticated)
- etc.
Access to CGI Environment
- Client
- remote_host()
- user_name()
- user_agent()
- referer()
- accept()
- query_string()
- path_info()
- Server
- server_name()
- server_software()
- virtual_host()
- server_port()
- script_name()
A Dynamic Page
use CGI ':standard';
$date = localtime();
$host = remote_host();
$referer = referer();
print
header,
start_html(-title=>'A Dynamic Page'),
h1('A Dynamic Page'),
<<END,end_html();
Hello! I see that the name of your machine is
<I>$host</I> and that you were just now reading
the page at <I>$referer</I>.
<HR>
Last modified: <$date<
END
Dynamic Page Output
Interactive Scripts
Creating Fill-Out Forms
- start_form()
- end_form()
- start_multipart_form()
- submit()
- reset()
- defaults()
- textfield()
- textarea()
- password_field()
- popup_menu()
- scrolling_list()
- checkbox()
- checkbox_group()
- radio_group()
- filefield()
- hidden()
- image_button()
- button()
start_form() & end_form()
<FORM METHOD=POST>
</FORM>
- startform() & endform() work too
- Avoids a layer of nesting
start_form parameters
- -actionURL to execute (default: current)
- -methodGET or POST
- -encodingapplication/x-www-encoded or multipart/form-data
- -frame
- various JavaScript arguments
Form Element Arguments
- -name
- field name
- -default(AKA -value)
- initial field value
- -values
- multivalued fields
- -override
- if non-zero, overrides the default
- element-specific arguments
textfield()
- print textfield(-name=>'comments',
-default=>'CGI rocks');
<INPUT TYPE="textfield" NAME="comments" VALUE="CGI rocks">
- print textfield(-name=>'comments',
-size=>50,
-maxlength=>80);
<INPUT TYPE="textfield" NAME="comments" SIZE=50 MAXLENGTH=80>
- textarea(), password_field(), hidden() and filefield() similar
popup_menu()
print start_form;
print popup_menu(-name=>'flavor',
-values=>['vanilla','orange',
'lemon'],
-default=>'orange');
print end_form;
Output:
<FORM METHOD="POST" ENCTYPE="application/x-www-form-urlencoded">
<SELECT NAME="flavor" SIZE=1>
<OPTION>vanilla</OPTION>
<OPTION SELECTED>orange</OPTION>
<OPTION>lemon</OPTION>
</SELECT>
</FORM>
popup_menu() output
scrolling_list()
print start_form;
print scrolling_list(-name=>'toppings',
-values=>['nuts','sprinkles','Oreos'],
-defaults=>['sprinkles','nuts'],
-multiple=>1);
print end_form;
Output:
<FORM METHOD="POST" ENCTYPE="application/x-www-form-urlencoded">
<SELECT NAME="toppings" MULTIPLE SIZE=3>
<OPTION SELECTED>nuts</OPTION>
<OPTION SELECTED>sprinkles</OPTION>
<OPTION>Oreos</OPTION>
</SELECT>
</form>
scrolling_list() output
Changing List Labels
scrolling_list(-name=>'toppings',
-values=>['nuts','sprinkles','Oreos'],
-defaults=>['sprinkles','nuts'],
-multiple=>1,
-labels=>{nuts=>'Mixed Nuts',
sprinkles=>'Jimmies',
Oreos=>'Cookie Bits'}
);
submit(), reset() & defaults()
- submit(-name=>'search',
-value=>'Start Search');
- Send current values of form to script
- reset(-value=>'Reset Form');
- Undo all changes made by user
- defaults(-value=>'Defaults');
- Force default values
Putting it Together
sub generate_form {
print hr, start_form,
strong('Your name: '),
textfield(-name=>'customer'),br,
strong('Flavor: '),
popup_menu(-name=>'flavor',
-values=>[qw/chocolate vanilla lemon/]),br,
strong('Toppings: '),
scrolling_list(-name=>'toppings',
-values=>[qw/nuts sprinkles Oreos/]),br,
strong('Cone: '),
radio_group(-name=>'cone',-multiple=>1,
-values=>[qw/sugar waffle/]),br,
submit(-value=>'Send Order'),
end_form,hr;
}
How it Looks
Retrieving the Field Values
- CGI protocol is annoyingly complicated (but you don't have to worry about it).
- param(field_name);
- Return value of named field
- param() with no arguments
- Returns array of all named fields
- Works both for scalar & multivalued fields
$customer = param('customer');
@toppings = param('topping');
Complete Example
#!/usr/bin/perl
use CGI ':standard';
print header,start_html('Order Ice Cream'),
h1('Order Ice Cream');
generate_form();
print_results if param();
print end_html();
sub print_results {
my @top = param('toppings');
print b('Customer name: '),param('customer'),br,
"You ordered a ",param('flavor'),' ',
param('cone'),' cone with ';
print @top ? join(',',@top) : 'no',' toppings';
}
sub generate_form {
print hr, start_form,
strong('Your name: '),
textfield(-name=>'customer'),br,
strong('Flavor: '),
popup_menu(-name=>'flavor',
-values=>[qw/chocolate vanilla lemon/]),br,
strong('Toppings: '),
scrolling_list(-name=>'toppings',
-values=>[qw/nuts sprinkles Oreos/]),br,
strong('Cone: '),
radio_group(-name=>'cone',-multiple=>1,
-values=>[qw/sugar waffle/]),br,
submit(-value=>'Send Order'),
end_form,hr;
}
"Sticky" Field Values
- Form elements "remember" previous values
- No CGI fields passed, so defaults used
- Second & subsequent times
- If a field of same name is present, form element uses it instead of default
- Can modify this behavior with
- -override parameter
- Setting value of field manually
Example Stickiness
Manually Setting Fields
- param(-name=>'customer',
-value=>'Dr. Smith');
- param(-name=>'toppings',
-value=>['nuts','Oreos']);
- param('toppings','nuts','oreos')
Importing CGI Fields
- import_names(namespace)
- Example:
param(-name=>'customer',
-value=>'Dr. Smith');
param(-name=>'toppings',
-value=>['nuts','Oreos']);
import_names('Q');
print "Customer = $Q::customer";
Customer = Dr. Smith
print "Toppings = @Q::toppings";
Toppings = nuts Oreos
Redirection
- Sometimes useful to direct browser to another URL
- Use redirect() instead of header()
- redirect('http://somewhere.else/');
$wday = (localtime())[6];
if ($wday == 0) {
print redirect('/closed_sunday.html');
} else {
print header()...
Part 2: Advanced Techniques
Debugging from Command Line
2 Ways to Run Without Waiting
- Give script an empty argument
% doit.pl ''
- Redirect from standard input
% doit.pl </dev/null
Handling Funny Characters
- Backslashes work as expected
% doit dinosaur=dino owner=Barney\ Rubble
- As do quote marks
% doit dinosaur=dino 'owner=Barney Rubble'
- URL hex escapes work too
% doit dinosaur=dino owner=Barney%20Rubble
- Same applies to standard input
Object-Oriented Style
- Create multiple "CGI objects"
- Each object contains CGI parameters
- Can change contents of object
- Save & restore state to file or process
- Why use it?
- OO purism
- Subclassing
- No namespace contamination
Example OO Style
#!/usr/bin/perl
use CGI;
$q = new CGI;
print $q->header(),
$q->start_html('Object Oriented'),
$q->h1('Object Oriented');
if ($q->param) {
print "Your name is ",
$q->param('name');
CGI Object Initialization
- From an associative array
$q = new CGI({'dinosaur'=>'barney',
'color'=>'purple',
'friends'=>[qw/Jessica George Ann/]});
$q=new CGI('dinosaur=barney&color=purple')
open(INPUT,"foobar.txt");
$q=new CGI(INPUT);
Saving & Restoring CGI Objects
use CGI;
$q = new CGI;
open (STATE,">save.txt");
$q->save(STATE); # save(\*STATE);
close STATE;
open (IN,"save.txt"):
$r = new CGI(IN); # new CGI(\*IN)
close IN;
Format of a Saved CGI Object
dinosaur=Barney
color=purple
friends=Jessica
friends=George
friends=Ann
=
dinosaur=Godzilla
color=green
friends=Tokyo Redevelopment Authority
=
Reading States from a Database
use CGI;
open (IN,"state.txt"):
while (!eof(IN)) {
push(@q,new CGI(IN));
}
close IN;
Accessing Internal CGI Object
- In function-oriented style, a CGI object named $CGI::Q is created behind the scenes
- Can access it directly if you want
- Warning
: module creates this object as needed, such as after a param() call
use CGI ':standard';
@fields = param();
$CGI::Q->save(OUT);
File Upload
- User can upload text files or arbitrary binary formats
- Caveats
- Netscape 3 or higher
- Internet Explorer 4 or higher
- Netscape servers break when using SSLv2
- Binary/textmode nightmares on DOS machines
Generating Fill-Out Form
print start_multipart_form(),
"Enter the file to upload: ",
filefield(-name=>'uploaded file'),
end_form();
Reading the File
- Retrieving a file handle for the file
$filehandle = param('uploaded file');
exit 0 unless $filehandle;
while (<$filehandle>) { do_something(); }
- Reading from a binary file
while (read($filehandle,$scalar,1024)) {
do_something($scalar);
}
Other Things You Can Do
- Get the original file's name
$filename = param('uploaded file');
- (Name and filehandle are the same!)
- Get the file's MIME type
$filename = param('uploaded file');
$info = uploadInfo($filename);
$type = $info->{'Content-Type'};
die "Text files only"
unless $type =~ /^text/;
Accessing Temporary File
- Internally CGI.pm creates temp file
- Deleted when script done
- Not guaranteed to be supported in future
$filename = param('uploaded file');
$tmpfile = tmpFileName($filename);
- Privacy issues
- One CGI script can read another's temp files
- Avoid by setting $CGI::PRIVATE_TEMPFILE=1
Cookies
Working with Cookies
1. Create cookie
2. Send it in outgoing HTTP header
3. Recover old cookies from incoming HTTP header
Creating & Sending Cookies
use CGI qw/:standard/;
$cookie = cookie(
-name=>'favorite_color',
-value=>'puce',
-expires=>'+3d');
print header(-cookie=>$cookie);
cookie() arguments
- -name, -value
name and value of cookie
- -expires
expiration time (+3s, +3m, +3d, +3M, +3y)
- -path, -domain
path to send cookie to (/cgi-bin/database)
domain to send cookie to (.capricorn.com)
- -secure
cookie sent if SSL in use
-value flexible
$c = cookie(-name=>'color',-value=>'puce';
$c = cookie(-name=>'colors',
-value=>['puce','mauve','red'];
Sending Multiple Cookies
$c1 = cookie(
-name=>'favorite_color',
-value=>'puce',
-expires=>'+3d');
$c2 = cookie(
-name=>'favorite_shape',
-value=>'octagon');
print header(-cookie=>[$c1,$c2]);
Retrieving Cookies
- cookie(name)
retrieves named cookie
$color = cookie('favorite_color');
@color = cookie('colors');
%preferences = cookie('preferences');
- cookie()
with no args returns all cookie names
foreach $c (cookie()) {
process_cookie(cookie($c));
}
Cascading Style Sheets
- Control over HTML formatting
- Define named styles
print start_html(
-style=>{-src=>'/style/style1.css'});
- Apply styles to blocks of text
print h1({-class=>'Fancy'},
"Style sheets are fun");
Defining a Style Sheet
- Use start_html() with -style argument
- -style
can point to
- a scalar containing text of local style sheet
- an associative array
- -src
=> URL of global style sheet
- -code
=> text of local style sheet
- Styles named in local stylesheets override like-named styles in global ones
Stylesheet example
$newStyle=<<:END;
<!-- P.Tip {
margin-right: 50pt;
margin-left: 50pt; }
P.Alert {
font-size: 30pt;
font-family: sans-serif;
color: red; } -->
END
print start_html(-style=>$newStyle);
print p({-class=>Alert},"Watch out!");
print span({-style=>"Color: magenta"},
"Here's some magenta text.");
JavaScript
- JavaScript event handlers can be added to:
- HTML head
- HTML body
- forms
- form elements
- JavaScript extensions recognized by CGI.pm shortcuts
Attach JavaScript to Page with start_html()
$jscript=<<END;
function riddle_me_this() {
var r = prompt("What walks on four legs, " +
"two legs, three legs?");
response(r);}
END
print start_html(-title=>'A Riddle',
-script=>$jscript);
print start_html(-title=>'A Riddle',
-script=>{-language=>'JavaScript",
-src=>'/JS/riddle.js'}
);
Creating Event Handlers
print start_form(-onSubmit=>'check_answer()'),
button(-name=>'button1',
-value=>'Ask the riddle',
-onClick=>'riddle_me_this()'),
submit(-name=>'submission button',
-value=>'Check answer'),
end_form();
Clickable Image Maps
- Create the map with image_button()
print image_button(-name=>'toaster',
-src=>'/images/toaster.gif');
- Get X and Y coordinates from param()
($x,$y) = (
param('toaster.x'),
param('toaster.y')
);
Using Frames
- Several functions recognize frame arguments
- Direct output into named frame
print header(-target=>'frame3');
- Direct output of form into named frame
print start_form(-target=>'results');
- frameset()
function available with ":netscape" set
NPH Scripts
- Output of NPH scripts passed directly to browser without server parsing
- Useful to
- Display unbuffered output to browser
- Fool with date and status codes yourself
- Stupid servers that require them
- When NPH activated, CGI.pm makes extra HTTP headers
Activating NPH Mode
- $CGI::NPH = 1;
- use CGI qw/:nph :standard/;
- $q = new CGI;
$q->nph(1);
- print header(-nph=>1);
mod_perl
- CGI.pm compatible with Doug MacEachern's mod_perl for Apache
- Whole CGI program becomes a preloaded function
- Most scripts run without modification
- but much faster
- warning:
- scripts don't go away when done
- must be very careful with global variables
FastCGI
- FastCGI is OpenMarket's CGI variant
- CGI program exists as a separate process, accepting requests from server
- Must modify programs:
use CGI::Fast qw/:standard/;
while (new CGI::Fast) {
print header,
start_html('Hi!')...
}
Server Push
- Script produces a series of pages one after another.
- Used to create crude animations and other special effects
use CGI::Push ':standard';
do_push(-next_page=>\&next_page,
-last_page=>\&last_page,
-type=>'text/html',
-delay=>0.5);
Better Error Reporting
- CGI scripts dump standard error into server error log
- CGI::Carp
- timestamps error messages
- allows you to log to separate file
- allows you to redirect errors to browser
Using CGI::Carp
Send error messages to log file
use CGI::Carp 'carpout';
open(LOG,">>/usr/logs/cgi.log");
carpout(LOG);
die "Oh dear, something bad happened!\n";
Future Directions for CGI.pm
- Resurrect the more elegant CGI::* modules
- Self-compiling style sheets
- Better DTD selection
- Support for new DHTML and XMP syntax
URL FOR THIS TALK
- http://www.genome.wi.mit.edu/~lstein/