Code Snippets

Small but useful pieces of code that demonstrate common CGI programming techniques.

Examples of how to accomplish different tasks in Perl CGI programming.

About These Snippets

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.

Password Creator

Generate encrypted passwords

Control-M Remover

Fix DOS line endings

Browser Detection

Identify user agents

Host & Referrer

Track visitor info

Password Creator

Version 1.0

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('');
}
Security Note: The original crypt() function uses DES encryption which is now considered weak. Modern applications should use bcrypt, Argon2, or scrypt for password hashing.

Remove Control-M Characters (^M)

Version 1.0

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'
Line Ending Formats
  • Unix/Linux/macOS: LF (\n)
  • Windows: CRLF (\r\n)
  • Classic Mac: CR (\r)
Modern Solutions
  • Use Git's core.autocrlf setting
  • Configure .editorconfig in project
  • IDE settings for line endings

Browser Identification

Version 1.0

This 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');
}
Live Browser Detection

Your Browser: Detecting...

Platform: Detecting...

Mobile: Detecting...

User Agent:

Remote Host Identification

Version 1.0 Requires SSI

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);
});

Page Last Visited (Referrer Tracking)

Version 1.0 Requires SSI

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());
Privacy Note: The HTTP Referer header may not always be sent due to privacy settings, HTTPS-to-HTTP transitions, or browser privacy features. Modern applications should not rely solely on referrer information.

Frequently Asked Questions

In the 1990s, web development resources were scarce. There was no Stack Overflow, GitHub, or npm. Developers relied on sharing code snippets through mailing lists and archives like this one. These small pieces of code solved common problems that every web developer faced, from password management to browser compatibility issues.

The crypt() function was the standard Unix password hashing function using DES encryption. It's now considered obsolete and insecure. DES keys are only 56 bits and can be cracked in hours with modern hardware. Today, use bcrypt (PASSWORD_DEFAULT in PHP), Argon2, or scrypt which are designed specifically for password hashing with adjustable work factors.

Control-M (^M or \r) is the carriage return character. Windows uses CRLF (\r\n) for line endings while Unix uses just LF (\n). When Windows-edited files are uploaded to Unix servers, the extra \r characters can cause "bad interpreter" errors in scripts because the shell tries to execute /usr/bin/perl^M instead of /usr/bin/perl.

Feature detection is strongly preferred over browser detection. Browser user agents can be spoofed and change frequently, making browser detection unreliable. Feature detection tests for specific capabilities (like CSS Grid support or WebGL) and provides accurate results. Use libraries like Modernizr or native APIs like CSS.supports() for feature detection.

The Referer header may be empty due to: direct URL entry or bookmarks, HTTPS to HTTP transitions (security policy), browser privacy settings or extensions, Referrer-Policy HTTP header settings, meta referrer tags, or users coming from private/incognito browsing. Never rely solely on referrer information for security or critical functionality.

SSI is a simple server-side scripting language used to include dynamic content in HTML pages. Commands like <!--#exec cgi="/cgi-bin/script.pl"--> were common. While SSI still works on Apache servers, it has been largely replaced by PHP, Node.js, and modern frameworks that offer more powerful templating. SSI remains useful for simple includes in static hosting environments.

The examples on this page show modern PHP and JavaScript equivalents for each snippet. The core logic remains similar, but modern implementations should use secure functions (password_hash instead of crypt), proper input validation, and follow current security best practices. Libraries like PHPMailer, bcrypt, and native APIs provide safer alternatives.

Modern approaches use IP geolocation APIs like MaxMind GeoIP, ip-api.com, or ipstack to get location information from IP addresses. These services provide city, country, ISP, and organization data. For client-side, the Geolocation API with reverse geocoding provides accurate location with user permission. DNS reverse lookups are still available but less reliable and slower.

Related Resources

TextClock

Another SSI-based script for displaying date and time.

SSI Random Image

Random image display using Server Side Includes.

Community Extras

More community-contributed code and extensions.