From 5f5fa631013417dd4c9010e3b12084f3bd805895 Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Sun, 1 Jul 2012 13:18:47 +0100 Subject: Transfer current state of netsurfweb as a fresh line of history --- cgissi.fcgi | 638 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 638 insertions(+) create mode 100755 cgissi.fcgi (limited to 'cgissi.fcgi') diff --git a/cgissi.fcgi b/cgissi.fcgi new file mode 100755 index 0000000..09e3c5e --- /dev/null +++ b/cgissi.fcgi @@ -0,0 +1,638 @@ +#!/usr/bin/perl + +# \file +# cgissi.fcgi -- A FastCGI script to emulate Server-Side Includes +# +# At the time of writing, it supports #include, #fsize and #flastmod +# + +use warnings; +use strict; + +use Digest::MD5 qw(md5_base64); +use File::Spec; +use FCGI; +use POSIX qw(strftime); + +# Content cache to avoid regenerating pages unless it's absolutely necessary +# +# This hash maps between the full local path to the URL data +# and a hash containing cache data. For example: +# +# { "/foo/bar/baz" => +# { last_modified => 0123456789, +# data_digest => "base64(MD5($data))" +# data_last_modified => 0123456789, +# data => "generated page content", +# dependencies => +# { "/foo/bar/bat" => +# { last_modified => 0123456789, +# commands => +# [ +# { command => "include" | "fsize" | "flastmod", +# start_offset = 123, +# end_offset = 456 +# }, +# { command => "include" | "fsize" | "flastmod", +# start_offset = 678, +# end_offset = 901 +# } +# ] +# } +# } +# } +# } +# +my %cache; + +# Request object +my $request = FCGI::Request(); + +# Loop until the server tells us to quit +while ($request->Accept() >= 0) { + # Get document root for this request + my $docroot = $ENV{DOCUMENT_ROOT}; + + # Get path of item for this request + # Yes, this is hacky. It appears that REDIRECT_PATH contains + # the destination document resulting from content negotiation. + # As this is an absolute path, we strip the document root from it. + my ($path) = ($ENV{REDIRECT_PATH} =~ m/$docroot(.*)/); + + # Find item in cache + my $cachedata = $cache{$docroot . $path}; + + # Determine if we need to (re)generate page + validate_cache_entry($docroot, $path, $cachedata); + + # Refetch cache entry + # (as it may have been created by validate_cache_entry) + $cachedata = $cache{$docroot . $path}; + + # TODO It would be nice to send 304 responses where appropriate + # and also to pay attention to conditional requests namely + # If-(None-)Match and If-(Un)Modified-Since + + # Cache-related headers + print "ETag: " . $$cachedata{data_digest} . "\r\n"; + # Don't use %Z here, as perl's strftime appears to assume that the + # time is always in the local timezone + print "Last-Modified: " . strftime("%a, %d %b %Y %H:%M:%S GMT", + gmtime($$cachedata{data_last_modified})) . "\r\n"; + + # Send Content-Type header + print "Content-Type: text/html; charset=ISO-8859-1\r\n"; + print "\r\n"; + + # And the page data + print $$cachedata{data}; +} + +################################################################################ + +# Run an include command +# +# \param filepath The full path of the file to include +# \param data Data buffer to append file data to (reference) +# +sub do_include +{ + my ($filepath, $data) = @_; + + if (-e $filepath) { + # Unlike Zeus, we don't recursively apply SSI. + open FILE, $filepath; + { + local $/ = undef; + $$data .= ; + } + close FILE; + } +} + +# Run a fsize command +# +# \param filepath The full path of the file to measure the size of +# \param data Data buffer to append size to (reference) +# +sub do_fsize +{ + my ($filepath, $data) = @_; + my @units = ( "B", "kB", "MB", "GB", "TB" ); # should be sufficient ;) + + if (-e $filepath) { + my $size = -s $filepath; + my $index = 0; + + while ($size > 1000) { + $size /= 1024; + $index++; + } + + $$data .= sprintf("%.1f %s", $size, $units[$index]); + } +} + +# Run a flastmod command +# +# \param filepath The full path of the file to get the modification time of +# \param data Data buffer to append modification time to (reference) +# +sub do_flastmod +{ + my ($filepath, $data) = @_; + + if (-e $filepath) { + my $mod_string = + strftime("%d %b %Y %R", gmtime((stat($filepath))[9])); + + $$data .= $mod_string . " UTC"; + } +} + +# Process an include command +# +# \param command The command string to process +# \param data Data buffer to append output to (reference) +# \param docroot The document root +# \param path Path of the current document, relative to ::docroot +# \return The full path of the included file +# +sub process_include_command +{ + my ($command, $data, $docroot, $path) = @_; + my $filepath; + + if ($command =~ /file=/) { + my ($relpath) = ($command =~ m/file="(.*)"/); + my ($vol, $dirs, $file) = + File::Spec->splitpath($docroot . $path); + $filepath = File::Spec->rel2abs($relpath, + File::Spec->catpath($vol, $dirs, "")); + } elsif ($command =~ /virtual=/) { + my ($vpath) = ($command =~ m/virtual="(.*)"/); + if ($vpath =~ m#^/.*#) { + $filepath = $docroot . $vpath; + } else { + my ($vol, $dirs, $file) = + File::Spec->splitpath($docroot . $path); + $filepath = File::Spec->rel2abs($vpath, + File::Spec->catpath($vol, $dirs, "")); + } + } + + do_include($filepath, $data); + + return $filepath; +} + +# Process an fsize command +# +# \param command The command string to process +# \param data Data buffer to append output to (reference) +# \param docroot The document root +# \param path Path of the current document, relative to ::docroot +# \return The full path of the included file +# +sub process_fsize_command +{ + my ($command, $data, $docroot, $path) = @_; + my $filepath; + + if ($command =~ /file=/) { + my ($relpath) = ($command =~ m/file="(.*)"/); + my ($vol, $dirs, $file) = + File::Spec->splitpath($docroot . $path); + $filepath = File::Spec->rel2abs($relpath, + File::Spec->catpath($vol, $dirs, "")); + } elsif ($command =~ /virtual=/) { + my ($vpath) = ($command =~ m/virtual="(.*)"/); + if ($vpath =~ m#^/.*#) { + $filepath = $docroot . $vpath; + } else { + my ($vol, $dirs, $file) = + File::Spec->splitpath($docroot . $path); + $filepath = File::Spec->rel2abs($vpath, + File::Spec->catpath($vol, $dirs, "")); + } + } + + do_fsize($filepath, $data); + + return $filepath; +} + +# Process an flastmod command +# +# \param command The command string to process +# \param data Data buffer to append output to (reference) +# \param docroot The document root +# \param path Path of the current document, relative to ::docroot +# \return The full path of the included file +# +sub process_flastmod_command +{ + my ($command, $data, $docroot, $path) = @_; + my $filepath; + + if ($command =~ /file=/) { + my ($relpath) = ($command =~ m/file="(.*)"/); + my ($vol, $dirs, $file) = + File::Spec->splitpath($docroot . $path); + $filepath = File::Spec->rel2abs($relpath, + File::Spec->catpath($vol, $dirs, "")); + } elsif ($command =~ /virtual=/) { + my ($vpath) = ($command =~ m/virtual="(.*)"/); + if ($vpath =~ m#^/.*#) { + $filepath = $docroot . $vpath; + } else { + my ($vol, $dirs, $file) = + File::Spec->splitpath($docroot . $path); + $filepath = File::Spec->rel2abs($vpath, + File::Spec->catpath($vol, $dirs, "")); + } + } + + do_flastmod($filepath, $data); + + return $filepath; +} + +# Complete parsing of and process a command string +# +# \param template Open file handle of template input +# \param char Current input character +# \param command The command string read so far +# \param data Buffer to append data into (reference) +# \param docroot The document root +# \param path Path of the current document relative to ::docroot +# \param start_offset Offset into template input of start of SSI command +# \param cacheentry The cache entry for the current document +# +sub process_command +{ + my ($template, $char, $command, $data, + $docroot, $path, $start_offset, $cacheentry) = @_; + my $keyword = lc($command); + my $dash_count = 0; + my $filepath; + + # See state machine documentation in generate_page_full for details of + # how this function works. + + # Reject command if it's one we know nothing about + if ($keyword ne "include" && $keyword ne "fsize" && + $keyword ne "flastmod") { + # ProcessCmd -> Initial transition occurs in generate_page_full + $$data .= "