#!/usr/bin/perl -w # # Nonsense -- Generates random text from recursive datafiles. # # See the README for full details. # # Author: James Baughn, nonsense@i-want-a-website.com # with CGI support contributed by Fred Hirsch, truehand@darkhart.com # with small changes contributed by Peter Suschlik, peter@zilium.de # # Homepage: http://i-want-a-website.com/about-linux/downloads.shtml # Version: 0.5 (December 21, 2000) # License: GNU General Public License 2.0 # # COMMAND LINE USAGE: # nonsense [ -f file.data ] [ -t file.template ] # [ -n number ] [ -p ] [ -b bullet string ] [ -e ] # [ -D | -d ] [ command string ] # # -f Specify a datafile to load in. Use multiple -f parameters # to include additional files. The default.data file is # is always loaded. # -F Load all data files (i.e. all files in the current directory # with a .data extension). # # -t Use a template # # -n Repeat n times # -p Separate each item with a blank line (i.e. paragraph break) # -b Specify a "bullet" to go in front of each item. # # -e Disable direct eval()'s # # -d Debug mode (shows each substituation) # -D Verbose debug mode (shows each substitution and the result) # ###################################################################### use strict; use POSIX qw( strftime ); # Just in case somebody needs the date use CGI; my %pool; # Where the datafiles are slurped into my %static; # Hash of persistent data (to maintain state) my $ignoreparameters = 1; # Set this to 1 if you want Nonsense to ignore # command-line or CGI parameters (for security # reasons). The program will use the hard-coded # defaults below. See the README first! my @datafiles = qw(default.data, ict.data); my $DEBUG = 0; # my $template = '{Default}'; # my $template_meta = ''; my($template, $template_meta) = LoadTemplate('ict.html.template'); my $cgi_mode = 0; my $output_mode = 'html'; my $header = ''; my $footer = ''; my $spacer = "\n"; my $bullet = ''; my $iters = 1; my $evalokay = 1; # By default, allow direct eval my $query; if (@ARGV <= 0) { # Is this in a CGI environment? $query = new CGI; $output_mode = 'html'; $cgi_mode = 1; $spacer = "<BR>\n"; $evalokay = 0; # Just to be safe, disable this feature # in CGI scripts } ## Read CGI parameters if (defined $query && $query->param && !$ignoreparameters) { my $cmd; if (defined $query->param('cmd') && $query->param('cmd') ne "") { $cmd = $query->param('cmd'); } else { $cmd = 'Default'; } if (defined $query->param('debug') && $query->param('debug') ne "") { $DEBUG = $query->param('debug'); } if (defined $query->param('number') && $query->param('number') ne "") { $iters = $query->param('number'); } if (defined $query->param('file') && $query->param('file') ne "") { push (@datafiles, $query->param('file')) ; } if (defined $query->param('allfiles') && $query->param('allfiles') ne "") { @datafiles = GlobCurrentDirectory(); } if (defined $query->param('template') && $query->param('template') ne "") { my $file = $query->param('template'); ($template, $template_meta) = LoadTemplate( $file ); if( $file !~ /\.html/ ) { $output_mode = 'verbatim'; } } else { $template = '{' . ucfirst( $cmd ) . '}'; if (defined $query->param('standalone') && $query->param('standalone') ne "") { $header = "<HTML><HEAD><TITLE>Nonsense</TITLE></HEAD><BODY>\n"; $footer = "</BODY></HTML>\n"; } } if (defined $query->param('spacer') && $query->param('spacer') ne "") { $spacer = $query->param('spacer' ); if( $spacer eq 'P' || $spacer eq 'p' ) { $spacer = "\n<P>\n"; } elsif( $spacer =~ /^nl*$/i ) { $spacer = "\n"; } elsif( $spacer =~ /^br*$/i ) { $spacer = "<BR>\n"; } else { # Literal $spacer = s/\\n/\n/g; } } if (defined $query->param('bullet') && $query->param('bullet') ne "") { my $layout = $query->param('bullet' ); if( $layout =~ /^o/i ) { $header .= "<OL>\n"; $footer = "</OL>\n$footer"; $bullet = "<LI>"; } elsif( $layout =~ /^[ul]/i ) { $header .= "<UL>\n"; $footer = "</UL>\n$footer"; $bullet = "<LI>"; } } ## Read command line parameters } elsif(!$ignoreparameters) { while( my $cmd = shift @ARGV ) { if( $cmd =~ /^-(\w)/ ) { my $switch = $1; if( $switch eq 'd' ) { $DEBUG = 1; } elsif( $switch eq 'D' ) { $DEBUG = 2; } elsif( $switch eq 'n' ) { $iters = shift @ARGV; } elsif( $switch eq 'e' ) { $evalokay = 0; } elsif( $switch eq 'p' ) { $spacer = "\n\n"; } elsif( $switch eq 'b' ) { $bullet = shift @ARGV; } elsif( $switch eq 't' ) { my $file = shift @ARGV; ($template, $template_meta) = LoadTemplate( $file ); } elsif( $switch eq 'f' ) { push( @datafiles, shift @ARGV ); } elsif( $switch eq 'F' ) { @datafiles = GlobCurrentDirectory(); } } else { $template = '{' . ucfirst( $cmd ) . '}'; } } } ## Check if there was any meta-data specified in the template file if( $template_meta ne '' ) { if( $template_meta =~ /prereq\w*:\s*(.*)\n/i ) { my( @newfiles ) = split /\s*[,;]\s*/, $1; push( @datafiles, @newfiles ); # Add new prerequisite datafiles # to the list } } foreach my $datafile ( @datafiles ) { LoadDataFile( $datafile ); } if( $cgi_mode ) { if( $output_mode eq 'html' ) { # HTML output print $query->header; } else { # Not an HTML template, treat as plain text print $query->header( -type=>'text/plain' ); } print $header; } for( my $i = 0; $i < $iters; $i++ ) { my $workcopy = $template; $workcopy =~ s/{([^}]+)}/Pick($1)/eg; # The meat of the program print "${bullet}${workcopy}${spacer}"; } print $footer if( $cgi_mode ); exit(0); # Done! ######## SUBROUTINES ######################################################## ### Recursively process a command sub Pick { my $key = shift; my $case; my $pick; ## Number range if( $key =~ /^#(\d+)-(\d+)$/ ) { $pick = int( rand( $2 - $1 ) + $1 ); ## Current time (fed through strftime) } elsif( $key =~ /^\@([^|]+)$/ ) { $pick = strftime( $1, localtime( time ) ); ## Time maintained by a state variable (and decreased by a random value) } elsif( $key =~ /^\@(.*?)\|\$(\w+)\|(\d+)$/ ) { my $usekey = uc $2; my $s = $1; my $t; my $elapse = int( rand( $3 ) ); if( exists $static{$usekey} ) { $t = $static{$usekey} - $elapse; } else { $t = time - $elapse; } $pick = strftime( $s, localtime( $t ) ); $static{$usekey} = $t; ## Current time minus a random value } elsif( $key =~ /^\@(.*?)\|(\d+)\|(\d+)$/ ) { $pick = int( rand( $3 - $2 ) + $3 ); $pick = strftime( $1, localtime( time - $pick ) ); ## Direct eval (literal Perl code) -- Dangerous! } elsif( $key =~ /^;(.*)$/ ) { if( $evalokay ) { $pick = eval( $1 ); } else { $pick = ''; } ## Literal list } elsif( $key =~ /^\[(.*)$/ ) { my @temp = split /\|/, $1; if(scalar @temp > 1) { # More than one element $pick = $temp[ rand @temp ]; } else { # ...Or single element $pick = int rand 2 ? shift @temp : ""; # Pick it or pick nothing } ## Embedded character } elsif( $key =~ /^\\(.*)$/ ) { $pick = EmbeddedCharacter( $1 ); ## Assignment (state variable:=command) } elsif( $key =~ /^(.*?):=(.*)$/ ) { my $usekey = uc $1; $key = $2; $static{$usekey} = Pick($key); $pick = ''; ## Literal assignment (state variable=literal string) } elsif( $key =~ /^(.*?)=(.*)$/ ) { $key = $2; $static{uc $1} = $key; $pick = ''; ## Retrieve a state variable } elsif( $key =~ /^[\$<](.*)$/ ) { $case = $1; my $usekey = uc $case; $usekey =~ s/\W//g; # Strip special characters if( !exists $static{$usekey} ) { $pick = Pick($usekey); # Variable isn't defined } else { $pick = $static{$usekey}; } ## Pick something from the pool a random number of times [NEW] } elsif( $key =~ /^(.*?)#(\d+)-(\d+)$/ ) { my $usekey = $1; $pick = ''; my $num = int( rand( $3 - $2 ) + $2 ); foreach( my $i = 0; $i < $num; $i++ ) { $pick .= Pick($usekey); } $case = $usekey; ## Pick something from the pool (not a special case) } else { my $usekey = uc $key; $usekey =~ s/\W//g; if( !exists $pool{$usekey} ) { print "{$usekey} not found\n"; $pick = ''; } else { $pick = $pool{ $usekey }[ rand @{ $pool{ $usekey } } ]; $case = $key; } } ## Print debugging info if necessary if( $DEBUG == 1 ) { if( $output_mode ne 'text' ) { print "<!--$key-->"; # Output it as an unobtrusive HTML comment } else { print "[$key]"; } } elsif( $DEBUG == 2 ) { if( $output_mode ne 'text' ) { print "<!--$key=$pick-->\n"; } else { print "[$key=$pick]\n"; } } ## Recursively process it $pick =~ s/{([^}]+)}/Pick($1)/eg; ## Handle lowercase/uppercase conversions if( !defined $case ) { # No need to worry about case return $pick; } elsif( $case =~ /^[A-Z0-9]+$/ ) { # UPPERCASE return uc $pick; } elsif( $case =~ /^[a-z0-9]+$/ ) { # lowercase return lc $pick; } elsif( $case =~ /^\^/ ) { # begins with '^' -- Ucfirst return ucfirst $pick; } else { # Mixed Case -- don't touch case return $pick; } } ### Return a literal character sub EmbeddedCharacter { my $in = shift; if( $in eq 'n' ) { # Newline return "\n"; } elsif( $in eq '0' ) { # Null return ''; } elsif( $in eq 'L' ) { # Left brace return '{'; } elsif( $in eq 'R' ) { # Right brace return '}'; } elsif( $in =~ /^\d+/ ) { # ASCII code in decimal return chr( $in ); } return ''; # Character not in list } ### Load and parse a datafile, slurping the contents into the %pool hash sub LoadDataFile { my $file = shift; $file = SafeFile( $file ) if $cgi_mode; open IN, $file or die "Error opening $file... $!\n"; local $/ = ''; SECTION: while( <IN> ) { my( @temp ) = split /\n/, $_; my $key = shift @temp; $pool{$key} = [ @temp ]; } close IN; } ### Slurp a template file into core sub LoadTemplate { my $file = shift; my $m = ''; $file = SafeFile( $file ) if $cgi_mode; open IN, $file or die "Error opening $file template... $!\n"; local $/; undef $/; my $t = <IN>; close IN; if( $t =~ /__BEGIN__/ ) { # Check for a header ($m, $t) = split /__BEGIN__\s/, $t, 2; } return( $t, $m ); } ### Remove special characters from a filename to prevent maliciousness sub SafeFile { my( $file ) = shift; $file =~ s/([^\w.-])//g; # Ignore special characters except dots and hyphens warn("[" . localtime() . "] [warning] [client $ENV{REMOTE_ADDR}] Attempt to override filename safety feature!") if $1; return $file; } ### Return all of the datafiles in the current directory sub GlobCurrentDirectory { opendir(DIR, "."); my @datafiles = grep { /\.data$/ } readdir(DIR); closedir(DIR); return @datafiles; }