Small but useful pieces of code that demonstrate common CGI programming techniques.
Examples of how to accomplish different tasks in Perl CGI programming.
These code snippets are useful pieces of Perl code that, while not complex enough to be standalone scripts, demonstrate important CGI programming techniques. Each snippet addresses a common challenge faced by web developers in the 1990s and early 2000s.
Generate encrypted passwords
Fix DOS line endings
Identify user agents
Track visitor info
This snippet creates encrypted passwords for use with WWWAdmin and other scripts that require password protection. Useful if you forget your admin password and need to generate a new one.
#!/usr/bin/perl
# passwd.pl - Password Creator
# Creates encrypted passwords for WWWAdmin and other scripts
use strict;
use warnings;
print "Enter password: ";
chomp(my $password = );
# Generate salt using random characters
my @chars = ('a'..'z', 'A'..'Z', '0'..'9', '.', '/');
my $salt = $chars[rand @chars] . $chars[rand @chars];
# Create encrypted password using crypt()
my $encrypted = crypt($password, $salt);
print "\nEncrypted password: $encrypted\n";
print "\nPaste this into your configuration file.\n";
<?php
// Modern PHP password hashing
// NEVER use crypt() for new applications - use password_hash()
// Generate secure password hash
function createPasswordHash($password) {
// Uses bcrypt by default (or Argon2 in PHP 7.3+)
return password_hash($password, PASSWORD_DEFAULT);
}
// Verify password against hash
function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
// Example usage
$password = 'mySecretPassword';
$hash = createPasswordHash($password);
echo "Password Hash: " . $hash . "\n";
echo "Hash Length: " . strlen($hash) . " characters\n";
// Verification
if (verifyPassword($password, $hash)) {
echo "Password verified successfully!\n";
}
// Check if rehash is needed (for upgrading old hashes)
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$newHash = createPasswordHash($password);
// Update hash in database
}
?>
// Modern password hashing in Node.js using bcrypt
const bcrypt = require('bcrypt');
// Configuration
const SALT_ROUNDS = 12; // Higher = more secure but slower
// Generate password hash
async function createPasswordHash(password) {
const salt = await bcrypt.genSalt(SALT_ROUNDS);
const hash = await bcrypt.hash(password, salt);
return hash;
}
// Verify password against hash
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
// Example usage
async function demo() {
const password = 'mySecretPassword';
const hash = await createPasswordHash(password);
console.log('Password Hash:', hash);
console.log('Hash Length:', hash.length, 'characters');
// Verification
const isValid = await verifyPassword(password, hash);
console.log('Password valid:', isValid);
// Wrong password
const isInvalid = await verifyPassword('wrongPassword', hash);
console.log('Wrong password valid:', isInvalid);
}
demo();
// Browser-side: Use Web Crypto API for client-side hashing
// (Note: Server-side hashing is preferred for security)
async function browserHash(password) {
const encoder = new TextEncoder();
const data = encoder.encode(password);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
When editing Perl scripts on DOS/Windows and uploading to Unix servers, carriage return characters (^M or \r) can cause scripts to fail. This snippet removes those characters.
#!/usr/bin/perl
# rm_cont_m.pl - Remove Control-M characters from files
# Usage: perl rm_cont_m.pl filename.pl
use strict;
use warnings;
my $file = shift || die "Usage: perl rm_cont_m.pl filename\n";
open(my $in, '<', $file) or die "Cannot open $file: $!";
my @lines = <$in>;
close($in);
# Remove carriage returns from all lines
foreach my $line (@lines) {
$line =~ s/\r//g;
}
open(my $out, '>', $file) or die "Cannot write $file: $!";
print $out @lines;
close($out);
print "Removed Control-M characters from $file\n";
<?php
// Remove Control-M characters and normalize line endings
// Usage: php rm_control_m.php filename.php
$file = $argv[1] ?? null;
if (!$file) {
die("Usage: php rm_control_m.php filename\n");
}
if (!file_exists($file)) {
die("File not found: $file\n");
}
$content = file_get_contents($file);
// Remove carriage returns (convert CRLF to LF)
$content = str_replace("\r\n", "\n", $content);
$content = str_replace("\r", "\n", $content);
file_put_contents($file, $content);
echo "Removed Control-M characters from $file\n";
// Alternative: Process multiple files
function normalizeLineEndings($directory, $extension = 'php') {
$pattern = $directory . '/*.' . $extension;
$files = glob($pattern);
foreach ($files as $file) {
$content = file_get_contents($file);
$original = $content;
$content = str_replace("\r\n", "\n", $content);
$content = str_replace("\r", "\n", $content);
if ($content !== $original) {
file_put_contents($file, $content);
echo "Fixed: $file\n";
}
}
}
// normalizeLineEndings('/path/to/scripts', 'pl');
?>
# Multiple ways to remove Control-M in Unix/Linux
# Using sed (in-place editing)
sed -i 's/\r$//' filename.pl
# Using tr
tr -d '\r' < filename.pl > filename_fixed.pl
# Using dos2unix (if available)
dos2unix filename.pl
# Using perl one-liner
perl -pi -e 's/\r$//' filename.pl
# Process all .pl files in current directory
for file in *.pl; do
sed -i 's/\r$//' "$file"
echo "Fixed: $file"
done
# Using find for recursive processing
find . -name "*.pl" -type f -exec sed -i 's/\r$//' {} \;
# Check if file has CRLF line endings
file filename.pl
# Output will show "CRLF" if Windows line endings
# Count Control-M characters in a file
cat -v filename.pl | grep -c '\^M'
core.autocrlf setting.editorconfig in projectThis snippet detects the visitor's browser type and serves appropriate content. In the 1990s, this was needed for dealing with incompatible HTML/JavaScript implementations between Netscape Navigator and Internet Explorer.
#!/usr/bin/perl
# browser.pl - Browser Identification
# Detects browser type and redirects accordingly
use strict;
use warnings;
use CGI;
my $cgi = CGI->new;
my $user_agent = $ENV{'HTTP_USER_AGENT'} || '';
print $cgi->header;
if ($user_agent =~ /MSIE/i) {
# Internet Explorer
print $cgi->redirect('ie_page.html');
}
elsif ($user_agent =~ /Mozilla.*Netscape/i || $user_agent =~ /Navigator/i) {
# Netscape Navigator
print $cgi->redirect('netscape_page.html');
}
elsif ($user_agent =~ /Lynx/i) {
# Text-only browser
print $cgi->redirect('text_page.html');
}
else {
# Default/other browsers
print $cgi->redirect('default_page.html');
}
<?php
// Modern browser detection using user agent
// Note: Feature detection is preferred over browser detection
class BrowserDetector {
private $userAgent;
private $browser;
private $version;
private $platform;
private $isMobile;
public function __construct() {
$this->userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$this->detect();
}
private function detect() {
// Detect platform
if (preg_match('/Windows/i', $this->userAgent)) {
$this->platform = 'Windows';
} elseif (preg_match('/Macintosh|Mac OS/i', $this->userAgent)) {
$this->platform = 'macOS';
} elseif (preg_match('/Linux/i', $this->userAgent)) {
$this->platform = 'Linux';
} elseif (preg_match('/iPhone|iPad/i', $this->userAgent)) {
$this->platform = 'iOS';
} elseif (preg_match('/Android/i', $this->userAgent)) {
$this->platform = 'Android';
}
// Detect browser
if (preg_match('/Edg\/([0-9.]+)/i', $this->userAgent, $matches)) {
$this->browser = 'Edge';
$this->version = $matches[1];
} elseif (preg_match('/Chrome\/([0-9.]+)/i', $this->userAgent, $matches)) {
$this->browser = 'Chrome';
$this->version = $matches[1];
} elseif (preg_match('/Firefox\/([0-9.]+)/i', $this->userAgent, $matches)) {
$this->browser = 'Firefox';
$this->version = $matches[1];
} elseif (preg_match('/Safari\/([0-9.]+)/i', $this->userAgent, $matches)) {
$this->browser = 'Safari';
$this->version = $matches[1];
} elseif (preg_match('/MSIE ([0-9.]+)/i', $this->userAgent, $matches)) {
$this->browser = 'Internet Explorer';
$this->version = $matches[1];
}
// Detect mobile
$this->isMobile = preg_match('/Mobile|Android|iPhone|iPad/i', $this->userAgent);
}
public function getBrowser() { return $this->browser; }
public function getVersion() { return $this->version; }
public function getPlatform() { return $this->platform; }
public function isMobile() { return $this->isMobile; }
public function toArray() {
return [
'browser' => $this->browser,
'version' => $this->version,
'platform' => $this->platform,
'mobile' => $this->isMobile,
'userAgent' => $this->userAgent
];
}
}
// Usage
$detector = new BrowserDetector();
echo "Browser: " . $detector->getBrowser() . "\n";
echo "Version: " . $detector->getVersion() . "\n";
echo "Platform: " . $detector->getPlatform() . "\n";
echo "Mobile: " . ($detector->isMobile() ? 'Yes' : 'No') . "\n";
?>
// Modern browser detection in JavaScript
// Remember: Feature detection is preferred over browser detection!
class BrowserDetector {
constructor() {
this.ua = navigator.userAgent;
this.detect();
}
detect() {
// Detect browser
if (this.ua.includes('Edg/')) {
this.browser = 'Edge';
this.version = this.ua.match(/Edg\/([0-9.]+)/)?.[1];
} else if (this.ua.includes('Chrome/')) {
this.browser = 'Chrome';
this.version = this.ua.match(/Chrome\/([0-9.]+)/)?.[1];
} else if (this.ua.includes('Firefox/')) {
this.browser = 'Firefox';
this.version = this.ua.match(/Firefox\/([0-9.]+)/)?.[1];
} else if (this.ua.includes('Safari/') && !this.ua.includes('Chrome')) {
this.browser = 'Safari';
this.version = this.ua.match(/Version\/([0-9.]+)/)?.[1];
}
// Detect platform
this.platform = navigator.platform;
this.isMobile = /Mobile|Android|iPhone|iPad/.test(this.ua);
}
getInfo() {
return {
browser: this.browser,
version: this.version,
platform: this.platform,
mobile: this.isMobile,
userAgent: this.ua
};
}
}
// Better approach: Feature detection
const features = {
serviceWorker: 'serviceWorker' in navigator,
webGL: (() => {
try {
const canvas = document.createElement('canvas');
return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
} catch (e) {
return false;
}
})(),
webRTC: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
localStorage: (() => {
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
return true;
} catch (e) {
return false;
}
})(),
touchEvents: 'ontouchstart' in window,
geolocation: 'geolocation' in navigator,
webSocket: 'WebSocket' in window,
fetch: 'fetch' in window,
css: {
grid: CSS.supports('display', 'grid'),
flexbox: CSS.supports('display', 'flex'),
customProperties: CSS.supports('--custom', 'value')
}
};
console.log('Browser Features:', features);
// Use feature detection for progressive enhancement
if (features.serviceWorker) {
navigator.serviceWorker.register('/sw.js');
}
if (!features.fetch) {
// Load fetch polyfill
loadScript('https://cdn.jsdelivr.net/npm/whatwg-fetch@3/dist/fetch.umd.min.js');
}
Your Browser: Detecting...
Platform: Detecting...
Mobile: Detecting...
User Agent:
Display personalized greetings like "Welcome visitor from example.com!" by detecting the remote host's domain name. This creates a more engaging user experience.
#!/usr/bin/perl
# remote_host.pl - Display visitor's domain
# Called via SSI: <!--#exec cgi="/cgi-bin/remote_host.pl"-->
use strict;
use warnings;
my $remote_host = $ENV{'REMOTE_HOST'} || 'unknown location';
my $remote_addr = $ENV{'REMOTE_ADDR'} || 'unknown';
# Try to get hostname if only IP is available
if ($remote_host eq $remote_addr || $remote_host eq 'unknown location') {
use Socket;
my $hostname = gethostbyaddr(inet_aton($remote_addr), AF_INET);
$remote_host = $hostname if $hostname;
}
# Extract domain from hostname
my $domain = $remote_host;
if ($domain =~ /\.(\w+\.\w+)$/) {
$domain = $1;
}
print "Content-type: text/html\n\n";
print "Welcome visitor from $domain!";
<?php
// Modern remote host identification
// Note: REMOTE_HOST requires DNS lookup to be enabled in web server
function getVisitorInfo() {
$ip = $_SERVER['REMOTE_ADDR'] ?? null;
$host = $_SERVER['REMOTE_HOST'] ?? null;
// Handle proxy headers
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
} elseif (isset($_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
// Perform reverse DNS lookup if host not available
if (!$host && $ip) {
$host = gethostbyaddr($ip);
// gethostbyaddr returns the IP if lookup fails
if ($host === $ip) {
$host = null;
}
}
// Extract domain from hostname
$domain = null;
if ($host && preg_match('/\.([a-z0-9-]+\.[a-z]{2,})$/i', $host, $matches)) {
$domain = $matches[1];
}
return [
'ip' => $ip,
'hostname' => $host,
'domain' => $domain
];
}
// Usage
$visitor = getVisitorInfo();
if ($visitor['domain']) {
echo "Welcome visitor from {$visitor['domain']}!";
} elseif ($visitor['hostname']) {
echo "Welcome visitor from {$visitor['hostname']}!";
} else {
echo "Welcome, visitor!";
}
// Geo-location based greeting (using external API)
function getGeoLocation($ip) {
// Using free ip-api.com service
$url = "http://ip-api.com/json/{$ip}";
$response = @file_get_contents($url);
if ($response) {
return json_decode($response, true);
}
return null;
}
$geo = getGeoLocation($visitor['ip']);
if ($geo && $geo['status'] === 'success') {
echo "Greetings from {$geo['city']}, {$geo['country']}!
";
}
?>
// Client-side geolocation greeting
// Note: Hostname detection requires server-side code
// Use Geolocation API for location-based greeting
async function getLocationGreeting() {
if (!navigator.geolocation) {
return 'Welcome, visitor!';
}
return new Promise((resolve) => {
navigator.geolocation.getCurrentPosition(
async (position) => {
const { latitude, longitude } = position.coords;
// Reverse geocode to get city/country
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}`
);
const data = await response.json();
const city = data.address.city || data.address.town || data.address.village;
const country = data.address.country;
resolve(`Welcome visitor from ${city}, ${country}!`);
} catch (error) {
resolve('Welcome, visitor!');
}
},
(error) => {
resolve('Welcome, visitor!');
},
{ timeout: 5000 }
);
});
}
// Alternative: Use IP geolocation API
async function getIPBasedGreeting() {
try {
const response = await fetch('https://ipapi.co/json/');
const data = await response.json();
return {
ip: data.ip,
city: data.city,
region: data.region,
country: data.country_name,
org: data.org,
greeting: `Welcome visitor from ${data.city}, ${data.country_name}!`
};
} catch (error) {
console.error('IP lookup failed:', error);
return { greeting: 'Welcome, visitor!' };
}
}
// Usage
getIPBasedGreeting().then(info => {
document.getElementById('greeting').textContent = info.greeting;
console.log('Visitor info:', info);
});
Provide visitors with a link back to the page they came from using HTTP referrer information. Useful for improving navigation and user experience.
#!/usr/bin/perl
# referer.pl - Display link to previous page
# Called via SSI: <!--#exec cgi="/cgi-bin/referer.pl"-->
use strict;
use warnings;
use CGI qw(:standard escapeHTML);
my $referer = $ENV{'HTTP_REFERER'} || '';
print "Content-type: text/html\n\n";
if ($referer) {
# Sanitize the referer URL
my $safe_referer = escapeHTML($referer);
# Extract domain for display
my $display = $referer;
if ($referer =~ m{^https?://([^/]+)}i) {
$display = $1;
}
print qq|Back to $display|;
}
else {
print "";
}
<?php
// Modern referrer handling with security considerations
class ReferrerHandler {
private $referrer;
private $allowedDomains = [];
public function __construct($allowedDomains = []) {
$this->referrer = $_SERVER['HTTP_REFERER'] ?? null;
$this->allowedDomains = $allowedDomains;
}
public function getReferrer() {
return $this->referrer;
}
public function getReferrerDomain() {
if (!$this->referrer) return null;
$parsed = parse_url($this->referrer);
return $parsed['host'] ?? null;
}
public function isInternalReferrer() {
$referrerDomain = $this->getReferrerDomain();
$currentDomain = $_SERVER['HTTP_HOST'] ?? '';
return $referrerDomain === $currentDomain;
}
public function isTrustedReferrer() {
$domain = $this->getReferrerDomain();
if (empty($this->allowedDomains)) {
return true; // No restrictions
}
return in_array($domain, $this->allowedDomains);
}
public function getBackLink($defaultUrl = '/', $defaultText = 'Go Back') {
if ($this->referrer && $this->isTrustedReferrer()) {
$safeUrl = htmlspecialchars($this->referrer, ENT_QUOTES, 'UTF-8');
$domain = htmlspecialchars($this->getReferrerDomain(), ENT_QUOTES, 'UTF-8');
return "<a href=\"{$safeUrl}\">Back to {$domain}</a>";
}
return "<a href=\"{$defaultUrl}\">{$defaultText}</a>";
}
// Track referrers for analytics
public function logReferrer($logFile = '/tmp/referrers.log') {
if ($this->referrer && !$this->isInternalReferrer()) {
$entry = date('Y-m-d H:i:s') . "\t" .
$this->referrer . "\t" .
$_SERVER['REQUEST_URI'] . "\n";
file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
}
}
}
// Usage
$handler = new ReferrerHandler(['google.com', 'bing.com', 'duckduckgo.com']);
echo $handler->getBackLink();
// Analytics: Top referrers
function getTopReferrers($logFile, $limit = 10) {
if (!file_exists($logFile)) return [];
$referrers = [];
$lines = file($logFile, FILE_IGNORE_NEW_LINES);
foreach ($lines as $line) {
$parts = explode("\t", $line);
if (isset($parts[1])) {
$domain = parse_url($parts[1], PHP_URL_HOST);
$referrers[$domain] = ($referrers[$domain] ?? 0) + 1;
}
}
arsort($referrers);
return array_slice($referrers, 0, $limit, true);
}
?>
// Client-side referrer and navigation handling
class NavigationHelper {
constructor() {
this.referrer = document.referrer;
this.history = [];
this.loadHistory();
}
getReferrer() {
return this.referrer;
}
getReferrerDomain() {
if (!this.referrer) return null;
try {
const url = new URL(this.referrer);
return url.hostname;
} catch (e) {
return null;
}
}
isInternalReferrer() {
const referrerDomain = this.getReferrerDomain();
return referrerDomain === window.location.hostname;
}
// Create back link
createBackLink(defaultUrl = '/', defaultText = 'Go Back') {
if (this.referrer) {
const domain = this.getReferrerDomain();
return `Back to ${this.escapeHtml(domain)}`;
}
return `${defaultText}`;
}
// Track navigation history in sessionStorage
trackPage() {
const currentPage = {
url: window.location.href,
title: document.title,
timestamp: Date.now()
};
this.history.push(currentPage);
// Keep only last 50 pages
if (this.history.length > 50) {
this.history.shift();
}
sessionStorage.setItem('navHistory', JSON.stringify(this.history));
}
loadHistory() {
const stored = sessionStorage.getItem('navHistory');
this.history = stored ? JSON.parse(stored) : [];
}
getHistory() {
return this.history;
}
// Get breadcrumb from history
getBreadcrumb(maxItems = 5) {
return this.history.slice(-maxItems).map(item => ({
url: item.url,
title: item.title
}));
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Usage
const nav = new NavigationHelper();
nav.trackPage();
// Display referrer info
if (nav.getReferrer()) {
console.log('You came from:', nav.getReferrerDomain());
}
// Create back button
document.getElementById('backLink').innerHTML = nav.createBackLink();
// Show navigation history
console.log('Your navigation history:', nav.getHistory());
Another SSI-based script for displaying date and time.
Random image display using Server Side Includes.
More community-contributed code and extensions.