Skip to content

Perl CGI Programming

Perl - CGI Programming (Historical Context & Modern Alternatives)

Section titled “Perl - CGI Programming (Historical Context & Modern Alternatives)”

Important Note: CGI (Common Gateway Interface) is an older technology for web application development. While Perl was a dominant language for CGI scripting, modern Perl web development predominantly uses frameworks like Mojolicious, Dancer2, or Catalyst, which are built on the PSGI/Plack standard. These frameworks offer better performance, scalability, security, and development practices. This chapter covers CGI for historical understanding or very simple use cases.

  • CGI is a standard protocol that defines how web servers execute external programs (CGI scripts) to generate dynamic web content.
  • When a web server receives a request for a CGI script, it executes the script and passes its output back to the client’s browser.

Normally, when you request a static file (e.g., an HTML page or image):

  • Your browser sends an HTTP request to the web server.
  • The web server locates the file and sends its content back in an HTTP response.
  • The browser displays the content.

With CGI:

  • The browser requests a URL pointing to a CGI script.
  • The web server recognizes it as a CGI script, executes it, and passes client information (like form data) to the script (often via environment variables and standard input).
  • The CGI script processes the information and generates an HTTP response (headers and content, usually HTML) by printing to its standard output.
  • The web server captures this output and sends it back to the browser.

To run CGI scripts, your web server (e.g., Apache, Nginx) must be configured to support CGI. This usually involves:

  • Designating a specific directory (often /cgi-bin/ or similar) where CGI scripts are stored.
  • Ensuring Perl scripts in this directory have execute permissions (e.g., chmod 755 script.cgi).
  • Configuring the server to treat files with a certain extension (e.g., .cgi, .pl) in that directory as executable CGI programs.

The CGI.pm module simplifies many common CGI tasks, such as parsing input and generating HTML. While it was once a core module, it’s now typically installed from CPAN.

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new; # Create a new CGI object
# Print the HTTP header
print $q->header('text/html');
# Print the HTML content
print $q->start_html('Hello World - First CGI Program');
print $q->h1('Hello World! This is my first CGI program using CGI.pm');
print $q->end_html;

If this script (hello.cgi) is placed in a configured cgi-bin directory and accessed via a browser, it would output an HTML page with the ‘Hello World’ message.

A CGI script must first output HTTP headers, followed by a blank line, then the actual content. The most important header is Content-Type, which tells the browser what kind of data to expect (e.g., text/html, application/json).

Manually printing headers (without CGI.pm):
print "Content-Type: text/html\n\n"; # Note the double newline

Common HTTP Headers for CGI:

HeaderDescription
Content-Type: type/subtypeMIME type of the content (e.g., text/html, image/png, application/json).
Location: URLRedirects the browser to a different URL (used with a 3xx status code).
Set-Cookie: name=value; attributesSends a cookie to the browser.
Content-Disposition: attachment; filename="file.ext"Prompts the browser to download the content as a file.

The web server passes information to the CGI script via environment variables. CGI.pm often abstracts access to these, but understanding them is useful.

Variable NameDescription
REQUEST_METHODThe HTTP method used (e.g., GET, POST).
QUERY_STRINGURL-encoded data sent with a GET request (after the ?).
CONTENT_LENGTHLength of data sent with a POST request (read from STDIN).
CONTENT_TYPEMIME type of data sent with a POST request (e.g., application/x-www-form-urlencoded).
REMOTE_ADDRIP address of the client.
HTTP_USER_AGENTBrowser/client identification string.
SCRIPT_NAMEPath to the CGI script.
SERVER_NAMEHostname of the server.

Example: Displaying Environment Variables (using CGI.pm)

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
print $q->header('text/html');
print $q->start_html('CGI Environment Variables');
print $q->h1('CGI Environment Variables');
print $q->start_table({-border => 1});
print $q->Tr($q->th(['Variable', 'Value']));
foreach my $key (sort keys %ENV) {
print $q->Tr($q->td([$key, $ENV{$key}]));
}
print $q->end_table;
print $q->end_html;

Browsers send data to web servers (and thus to CGI scripts) primarily using GET or POST methods.

Data is appended to the URL as a query string (e.g., script.cgi?name=Alice&age=30). Accessible via $ENV{QUERY_STRING} or more easily with $q->param('name') using CGI.pm. Suitable for small amounts of non-sensitive data. Limited by URL length.

Data is sent in the body of the HTTP request. Raw POST data is read from STDIN, with length given by $ENV{CONTENT_LENGTH}. CGI.pm handles this parsing transparently via $q->param('name'). Suitable for larger amounts of data or sensitive information.

CGI.pm makes form handling easy, regardless of whether GET or POST is used.

HTML Form Example:

<form action="/cgi-bin/submit_form.cgi" method="POST">
First Name: <input type="text" name="first_name"><br>
Last Name: <input type="text" name="last_name"><br>
<input type="submit" value="Submit">
</form>

Perl CGI Script (submit_form.cgi):

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
# Get form parameters
my $first_name = $q->param('first_name');
my $last_name = $q->param('last_name');
print $q->header('text/html');
print $q->start_html('Form Submission Result');
print $q->h1('Submission Received');
if ($first_name && $last_name) {
print $q->p("Hello, " . $q->escapeHTML($first_name) . " " . $q->escapeHTML($last_name) . "!");
} else {
print $q->p("Please provide both first and last names.");
}
print $q->end_html;

Note: escapeHTML() is crucial to prevent Cross-Site Scripting (XSS) vulnerabilities when displaying user-supplied data.

Handling Checkboxes, Radio Buttons, Textareas, Dropdowns

Section titled “Handling Checkboxes, Radio Buttons, Textareas, Dropdowns”

CGI.pm’s param() method handles these input types correctly:

  • Single value fields (text, radio, textarea, single-select dropdown): $q->param('fieldname') returns a single value.
  • Multi-value fields (checkboxes with same name, multiple-select dropdown): In list context, my @values = $q->param('fieldname') returns all selected values. In scalar context, it usually returns the first selected value.

Example (checkboxes):

HTML:
<input type="checkbox" name="interests" value="music"> Music
<input type="checkbox" name="interests" value="sports"> Sports
Perl:
my @interests = $q->param('interests');
if (@interests) {
print $q->p("Your interests: " . $q->escapeHTML(join(', ', @interests)));
} else {
print $q->p("You selected no interests.");
}

Cookies are small pieces of data stored by the browser, sent back to the server with subsequent requests. CGI.pm provides methods for setting and retrieving cookies.

Setting a Cookie:

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
# Create a cookie
my $cookie = $q->cookie(
-name => 'UserID',
-value => 'XYZ123',
-expires => '+30d', # Expires in 30 days
-path => '/'
);
# Print header with the cookie
print $q->header(-cookie => $cookie, -type => 'text/html');
print $q->start_html('Cookie Set');
print $q->p('UserID cookie has been set.');
print $q->end_html;

Retrieving a Cookie:

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
my $user_id = $q->cookie('UserID'); # Retrieve the cookie value
print $q->header('text/html');
print $q->start_html('Cookie Retrieved');
if (defined $user_id) {
print $q->p("Retrieved UserID: " . $q->escapeHTML($user_id));
} else {
print $q->p('UserID cookie not found.');
}
print $q->end_html;

CGI.pm can also handle file uploads from forms with enctype="multipart/form-data".

HTML for file upload:
<form method="POST" action="/cgi-bin/upload.cgi" enctype="multipart/form-data">
File: <input type="file" name="uploaded_file">
<input type="submit" value="Upload">
</form>
Perl (`upload.cgi`):
my $filename = $q->param('uploaded_file');
if ($filename) {
my $fh = $q->upload('uploaded_file'); # Filehandle to the uploaded temp file
if ($fh) {
# Process the file, e.g., save it
# open(my $out_fh, '>', "/path/to/save/$filename") or die "Cannot save file: $!";
# binmode $out_fh;
# while (my $bytes_read = read($fh, my $buffer, 4096)) {
# print $out_fh $buffer;
# }
# close $out_fh;
print $q->p("File '$filename' uploaded successfully (in theory).");
} else {
print $q->p("Error getting upload handle for '$filename'.");
}
} else {
print $q->p("No file was uploaded.");
}

When writing CGI scripts (or any web application):

  • Validate all input: Never trust data from the client.
  • Escape output: Use functions like CGI::escapeHTML to prevent XSS when displaying user data.
  • Prevent SQL Injection: Use placeholders with DBI if interacting with databases.
  • Taint Mode: Run Perl scripts with the -T flag (#!/usr/bin/perl -T) to enable taint checking, which helps identify insecure data flows.
  • Limit Permissions: Ensure CGI scripts run with minimal necessary permissions.

As mentioned, for new projects, using modern Perl web frameworks (Mojolicious, Dancer2, Catalyst) is strongly recommended over raw CGI scripting for better security, maintainability, and performance.