Refactoring
[CGIscriptor.git] / CGIscriptor.pl
blob12dfea19ec8d74fc660d459fe74831f1c8ede5b7
1 #! /usr/bin/perl
3 # (configure the first line to contain YOUR path to perl 5.000+)
5 # CGIscriptor.pl
6 # Version 2.3
7 # 15 January 2002
9 # YOU NEED:
11 # perl 5.0 or higher (see: "http://www.perl.org/")
13 # Notes:
15 if(grep(/\-\-help/i, @ARGV))
17 print << 'ENDOFPREHELPTEXT1';
18 # CGIscriptor.pl is a Perl program will run on any WWW server that
19 # runs Perl scripts, just add a line like the following to your
20 # httpd.conf file (Apache example):
22 # ScriptAlias /SHTML/ "/real-path/CGIscriptor.pl/"
24 # URL's that refer to http://www.your.address/SHTML/... will now be handled
25 # by CGIscriptor.pl, which can use a private directory tree (default is the
26 # DOCUMENT_ROOT directory tree, but it can be anywhere, see below).
27 # NOTE: if you cannot use a ScriptAlias, there is a way to use .htaccess
28 # instead. See below.
30 # This file contains all documentation as comments. These comments
31 # can be removed to speed up loading (e.g., `egrep -v '^#' CGIscriptor.pl` >
32 # leanScriptor.pl). A bare bones version of CGIscriptor.pl, lacking
33 # documentation, most comments, access control, example functions etc.
34 # (but still with the copyright notice and some minimal documentation)
35 # can be obtained by calling CGIscriptor.pl with the '-slim'
36 # command line argument, e.g.,
37 # >CGIscriptor.pl -slim >slimCGIscriptor.pl
39 # CGIscriptor.pl can be run from the command line as
40 # `CGIscriptor.pl <path> <query>`, inside a perl script with
41 # 'do CGIscriptor.pl' after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING},
42 # or CGIscriptor.pl can be loaded with 'require "/real-path/CGIscriptor.pl"'.
43 # In the latter case, requests are processed by 'Handle_Request();'
44 # (again after setting $ENV{PATH_INFO} and $ENV{QUERY_STRING}).
46 # The --help command line switch will print the manual.
48 # Running demo's and more information can be found at
49 # http://www.fon.hum.uva.nl/rob/OSS/OSS.html
51 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site
52 # or CPAN that can use CGIscriptor.pl as the base of a µWWW server and
53 # demonstrates its use.
55 ENDOFPREHELPTEXT1
57 # Configuration, copyright notice, and user manual follow the next
58 # (Changes) section.
60 ############################################################################
62 # Changes (document ALL changes with date, name and email here):
63 # 11 Jun 2012 - Securing CGIvariable setting. Made
64 # 'if($ENV{QUERY_STRING} =~ /$name/)' into elsif in
65 # defineCGIvariable/List/Hash to give precedence to ENV{$name}
66 # This was a very old security bug. Added ProtectCGIvariable($name).
67 # 06 Jun 2012 - Added IP only session types after login.
68 # 31 May 2012 - Session ticket system added for handling login sessions.
69 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
70 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
71 # correctly.
72 # 22 May 2012 - Added Access control with Session Tickets linked to
73 # IP Address and PATH_INFO.
74 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
75 # Will link to current base URL when the HTTP server is '.' or '~'
76 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
77 # CGIsafeFileName does not accept filenames starting with '-'
78 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
79 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
80 # 28 Jan 2005 - Added a file selector to performTranslation.
81 # Changed %TranslationTable to @TranslationTable
82 # and patterns to lists.
83 # 27 Jan 2005 - Added a %TranslationTable with associated
84 # performTranslation(\$text) function to allow
85 # run changes in the web pages. Say, to translate
86 # legacy pages with <%=...%> delimiters to the new
87 # <SCRIPT TYPE=..></SCRIPT> format.
88 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
89 # Other Languages Code.
90 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
91 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
92 # language call
93 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
94 # 20 May 2003 - Added a --help switch to print the manual.
95 # 06 Mar 2003 - Adapted the blurb at the end of the file.
96 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
97 # "die" calls. Also "enhanced" the STDERR printout.
98 # 10 Feb 2003 - Split off the reading of the POST part of a query
99 # from Initialize_output. This was suggested by Gerd Franke
100 # to allow for the catching of the file_path using a
101 # POST based lookup. That is, he needed the POST part
102 # to change the file_path.
103 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
104 # 03 Feb 2003 - \1 better written as $1 in
105 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
106 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
107 # added:
108 # $ServerScriptContentClass = "ssperl";
109 # changed in ProcessFile():
110 # unless(($CurrentContentType =~
111 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
112 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
113 # Update comments and documentation.
114 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
115 # Files didn't work.
116 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
117 # Also added a note that RawFilePattern can be a
118 # complete file name.
119 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
120 # switch to prepending or to appending the content
121 # of the SRC attribute. Default is prefixing. You
122 # can add as many of these switches as you like.
123 # 13 Mar 2002 - Do not search for tag content if a tag closes with
124 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
125 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
126 # (replaces wget).
127 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
128 # (i.o. a list context). This is necessary for binary results.
129 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
130 # changed the order of if/elsif to allow removing these
131 # conditions in systems with broken -T functions.
132 # (I also removed a spurious ')' bracket)
133 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
134 # to support binary files.
135 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
136 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
137 # stipped from the path.
138 # 15 Jan 2002 - Version 2.2
139 # 15 Jan 2002 - Debugged and completed URL support (including
140 # CGIscriptor::read_url() function)
141 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
142 # with the main::GET_URL function. Uses wget -O underlying.
143 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
144 # (i.e., my $NewDirective = "";) to clear old output
145 # (this was a realy anoying bug).
146 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
147 # tags that assign the body text as-is (literally)
148 # to $varname. Allows standard HTML-tools to handle
149 # Cascading Style Sheet templates. This implements a
150 # design by Gerd Franke (franke@roo.de).
151 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
152 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
153 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
154 # detect unwanted indexing of TAR files by webcrawlers.
155 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
156 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
157 # processed file type. Was processed via BinaryMapFile.
158 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
159 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
160 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
161 # be manipulated to achieve a "Sub Root".
162 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
163 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
164 # and debugging (it now works).
165 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
166 # hashes,instead of replacing existing hashes.
167 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
168 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
169 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
170 # CGIparseValueHash (used with structured parameters).
171 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
172 # Will collect all <CGI-partial-name><key>=value pairs in
173 # $<CGI-partial-name>{<key>} = value;
174 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
175 # 09 Aug 2000 - Added support for non-filesystem input by way of
176 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
177 # environment variables.
178 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
179 # This allows CGIscriptor to be used in pipes.
180 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
181 # in an HTTP request (i.e., in a web server).
182 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
183 # is not HTTP or another protocol. Changed the default
184 # source directory to DOCUMENT_ROOT (i.o. the incorrect
185 # SERVER_ROOT).
186 # 24 Jul 2000 - -slim Command-line argument added to remove all
187 # comments, security, etc.. Updated documentation.
188 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
189 # execution of all <META> and <SCRIPT> code
190 # conditional.
191 # 05 Jul 2000 - Rewrote and isolated the code for extracting
192 # quoted items from CGI and SRC attributes.
193 # Now all attributes expect the same set of
194 # quotes: '', "", ``, (), {}, [] and the same
195 # preceded by a \, e.g., "\((aap)\)" will be
196 # extracted as "(aap)".
197 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
198 # name-space (i.o. by evaluation) from
199 # CGI attributes to prevent interference with
200 # the processing for non perl scripts.
201 # Changed CGIparseValueList to prevent runaway
202 # loops.
203 # 16 Jun 2000 - Added a direct (interpolated) display mode
204 # (text/ssdisplay) and a user log mode
205 # (text/sslogfile).
206 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
207 # allow large string output.
208 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
209 # remove all control characters. Changed Interpreter
210 # initialization to shrub interpolated CGI parameters.
211 # Added 'text/ssmailto' interpreter script.
212 # 22 May 2000 - Changed some of the comments
213 # 09 May 2000 - Added list extraction for CGI parameters with
214 # CGIparseValueList (used with multiple selections).
215 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
216 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
217 # 27 Apr 2000 - Updated documentation to reflect changes.
218 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
219 # interpreters.
220 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
221 # 27 Apr 2000 - Gprolog, M4 support added.
222 # 26 Apr 2000 - Lisp (rep) support added.
223 # 20 Apr 2000 - Use of external interpreters now functional.
224 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
225 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
226 # the use of $#foo, i.e., I changed
227 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
228 # to
229 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
230 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
231 # things like, e.g., CVS information in CVS subtrees
232 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
233 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
234 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
235 # over a "burried" BinaryMapFile.
236 # 04 Oct 1999 - Added two functions to check file names and email addresses
237 # (CGIscriptor::CGIsafeFileName and
238 # CGIscriptor::CGIsafeEmailAddress)
239 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
240 # to allow LONG posts.
241 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
242 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
243 # this directory exists.
244 # 07 June 1999 - Limit file-pattern matching to LAST extension
245 # 04 June 1999 - Default text/html content type is printed only once.
246 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
247 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
248 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
249 # Changed the processing of the Accept and Reject file.
250 # Added a full expression evaluation to Access Control.
251 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
252 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
253 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
254 # Requests are serviced by "Handle_Request()". CGIscriptor
255 # can still be called as a isolated perl script and a shell
256 # command.
257 # Changed the "factory default setting" so that it will run
258 # from the DOCUMENT_ROOT directory.
259 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
260 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
261 # to subroutines to change CGIscriptor into a module.
262 # Added mapping to process unsupported file types (e.g., binary
263 # pictures). See $BinaryMapFile.
264 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
265 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@uva.nl)
266 # Removed a double setting of filepatterns and maximum query
267 # size. Changed email address. Removed some typos from the
268 # comments.
269 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
270 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@uva.nl)
271 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
272 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
275 # Known Bugs:
277 # 23 Mar 2000
278 # It is not possible to use operators or variables to construct variable names,
279 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
280 # will indeed work. If someone could tell me why, I would be obliged.
283 ############################################################################
285 # OBLIGATORY USER CONFIGURATION
287 # Configure the directories where all user files can be found (this
288 # is the equivalent of the server root directory of a WWW-server).
289 # These directories can be located ANYWHERE. For security reasons, it is
290 # better to locate them outside the WWW-tree of your HTTP server, unless
291 # CGIscripter handles ALL requests.
293 # For convenience, the defaults are set to the root of the WWW server.
294 # However, this might not be safe!
296 # ~/ text files
297 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
298 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
299 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
301 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
302 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
305 # Just enter your own directory path here
306 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
308 # ./ script files (recommended to be different from the previous)
309 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
310 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
312 # End of obligatory user configuration
313 # (note: there is more non-essential user configuration below)
315 ############################################################################
317 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
319 # Script content-types: TYPE="Content-type" (user defined mime-type)
320 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
321 # CSS require a simple class
322 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
323 $' : "ssperl"; # Server Side Perl CSS classes
325 $ShellScriptContentType = "text/osshell"; # OS shell scripts
326 # # (Server Side perl ``-execution)
328 # Accessible file patterns, block any request that doesn't match.
329 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
330 # (\. is used in regexp)
331 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
332 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js";
334 # The table with the content type MIME types
335 # (allows to differentiate MIME types, if needed)
336 %ContentTypeTable =
338 '.html' => 'text/html',
339 '.shtml' => 'text/html',
340 '.htm' => 'text/html',
341 '.xml' => 'text/xml',
342 '.txt' => 'text/plain',
343 '.js' => 'text/plain'
347 # File pattern post-processing
348 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
350 # SHAsum command needed for Authorization and Login
351 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
352 my $shasum = "shasum -a 256";
353 if(qx{uname} =~ /Darwin/)
355 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
357 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
358 $ENV{"SHASUMCMD"} = $SHASUMCMD;
359 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
360 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
362 # Hash a string, return hex of hash
363 sub hash_string # ($string) -> hex_hash
365 my $string = shift || "";
366 # Catch nasty \'-quotes, embed them in '..'"'"'..'
367 $string =~ s/\'/\'\"\'\"\'/isg;
368 my $hash = `printf '%s' '$string'| $ENV{"SHASUMCMD"}`;
369 chomp($hash);
370 return $hash;
373 # Generate random hex hash
374 sub get_random_hex # () -> hex
376 # Create Random Hash Salt
377 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
378 my $RANDOMSALT= <URANDOM>;
379 close(URANDOM);
380 chomp($RANDOMSALT);
382 return $RANDOMSALT;
386 # File patterns of files which are handled by session tickets.
387 %TicketRequiredPatterns = (
388 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
390 # Used to set cookies, only session cookies supported
391 my %SETCOOKIELIST = ();
393 # Session Ticket Directory: Private/.Sessions
394 # Password Directory: Private/.Passwords
395 # Login page (url path): /Private/Login.html
396 # Expiration time (s): +3600
397 # +<seconds> = relative time <seconds> is absolute date-time
399 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
400 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
401 $RawFilePattern = ".xmr";
402 # (In principle, this could contain a full file specification, e.g.,
403 # ".xmr|relocated.html")
405 # Raw File pattern post-processing
406 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
408 # Server protocols for which "Content-type: text/html\n\n" should be printed
409 # (you should not bother with these, except for HTTP, they are mostly imaginary)
410 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
412 # Block access to all (sub-) paths and directories that match the
413 # following (URL) path (is used as:
414 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
415 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
417 # All (blocked) other file-types can be mapped to a single "binary-file"
418 # processor (a kind of pseudo-file path). This can either be an error
419 # message (e.g., "illegal file") or contain a script that serves binary
420 # files.
421 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
422 $BinaryMapFile = "/BinaryMapFile.xmr";
423 # Allow for the addition of a CGIscriptor directory
424 # Note that a BinaryMapFile in the root "~/" directory has precedence
425 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
426 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
427 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
430 # List of all characters that are allowed in file names and paths.
431 # All requests containing illegal characters are blocked. This
432 # blocks most tricks (e.g., adding "\000", "\n", or other control
433 # characters, also blocks URI's using %FF)
434 # THIS IS A SECURITY FEATURE
435 # (this is also used to parse filenames in SRC= features, note the
436 # '-quotes, they are essential)
437 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
439 # Maximum size of the Query (number of characters clients can send
440 # covers both GET & POST combined)
441 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
444 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
445 # (returns a string with the PERL code to transfer the URL contents, e.g.,
446 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
447 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
448 # Be sure to handle <BASE HREF='URL'> and allow BOTH
449 # direct printing GET_URL($URL [, 0]) and extracting the content of
450 # the $URL for post-processing GET_URL($URL, 1).
451 # You get the WHOLE file, including HTML header.
452 # The shell command Use $URL where the URL should go
453 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
454 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
455 #my $GET_URL_shell_command = 'snarf $URL -';
456 #my $GET_URL_shell_command = 'curl $URL';
458 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
460 my $URL = shift || return;
461 my $ValueNotPrint = shift || 0;
463 # Check URL for illegal characters
464 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
466 # Include URL in final command
467 my $CurrentCommand = $GET_URL_shell_command;
468 $CurrentCommand =~ s/\$URL/$URL/g;
470 # Print to STDOUT or return a value
471 my $BlockPrint = "print STDOUT ";
472 $BlockPrint = "" if $ValueNotPrint;
474 my $Commands = <<"GETURLCODE";
475 # Get URL
477 my \$Page = "";
479 # Simple, using shell command
480 \$Page = SAFEqx('$CurrentCommand');
482 # Add a BASE tage to the header
483 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
485 # Print the URL value, or return it as a value
486 $BlockPrint\$Page;
488 GETURLCODE
489 return $Commands;
492 # As files can get rather large (and binary), you might want to use
493 # some more intelligent reading procedure, e.g.,
494 # Direct Perl
495 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
496 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
497 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
498 # my \$text = "";
499 # while(sysread(URLHANDLE,\$text, 1024) > 0)
501 # \$Page .= \$text;
502 # };
503 # close(URLHANDLE) || die "\$!";
504 # However, this doesn't work with the CGIexecute->evaluate() function.
505 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
507 # You can forget the next two variables, they are only needed when
508 # you don't want to use a regular file system (i.e., with open)
509 # but use some kind of database/RAM image for accessing (generating)
510 # the data.
512 # Name of the environment variable that contains the file contents
513 # when reading directly from Database/RAM. When this environment variable,
514 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
515 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
516 # Uncomment the following if you want to force the use of the data access code
517 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
519 # Name of the environment variable that contains the RAM access perl
520 # code needed to read additional "files", i.e.,
521 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
522 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
523 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
525 # You can, of course, fill this yourself, e.g.,
526 # $ENV{$CGI_DATA_ACCESS_CODE} =
527 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
530 # DEBUGGING
532 # Suppress error messages, this can be changed for debugging or error-logging
533 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
535 # SPECIAL: Remove Comments, security, etc. if the command line is
536 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
537 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
539 # If CGIscriptor is used from the command line, the command line
540 # arguments are interpreted as the file (1st) and the Query String (rest).
541 # Get the arguments
542 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
543 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
546 # Handle bail-outs in a user definable way.
547 # Catch Die and replace it with your own function.
548 # Ends with a call to "die $_[0];"
550 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
552 my $ErrorCode = shift;
553 my $ErrorMessage = shift;
555 # Place your own reporting functions here
557 # Now, kill everything (default)
558 print STDERR "$ErrorCode: $ErrorMessage\n";
559 die $ErrorMessage;
563 # End of optional user configuration
564 # (note: there is more non-essential user configuration below)
566 if(grep(/\-\-help/i, @ARGV))
568 print << 'ENDOFPREHELPTEXT2';
570 ###############################################################################
572 # Author and Copyright (c):
573 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
574 # NKI-AVL Amsterdam
575 # r.v.son@nki.nl
576 # Institute of Phonetic Sciences & IFOTT/ACLS
577 # University of Amsterdam
578 # Email: R.J.J.H.vanSon@gmail.com
579 # Email: R.J.J.H.vanSon@uva.nl
580 # WWW : http://www.fon.hum.uva.nl/rob/
582 # License for use and disclaimers
584 # CGIscriptor merges plain ASCII HTML files transparantly
585 # with CGI variables, in-line PERL code, shell commands,
586 # and executable scripts in other scripting languages.
588 # This program is free software; you can redistribute it and/or
589 # modify it under the terms of the GNU General Public License
590 # as published by the Free Software Foundation; either version 2
591 # of the License, or (at your option) any later version.
593 # This program is distributed in the hope that it will be useful,
594 # but WITHOUT ANY WARRANTY; without even the implied warranty of
595 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
596 # GNU General Public License for more details.
598 # You should have received a copy of the GNU General Public License
599 # along with this program; if not, write to the Free Software
600 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
603 # Contributors:
604 # Rob van Son (R.J.J.H.vanSon@uva.nl)
605 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
607 #######################################################
608 ENDOFPREHELPTEXT2
610 #######################################################>>>>>>>>>>Start Remove
612 # You can skip the following code, it is an auto-splice
613 # procedure.
615 # Construct a slimmed down version of CGIscriptor
616 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
618 if($TrimDownCGIscriptor)
620 open(CGISCRIPTOR, "<CGIscriptor.pl")
621 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
622 my $SKIPtext = 0;
623 my $SKIPComments = 0;
625 while(<CGISCRIPTOR>)
627 my $SKIPline = 0;
629 ++$LineCount;
631 # Start of SKIP text
632 $SKIPtext = 1 if /[\>]{10}Start Remove/;
633 $SKIPComments = 1 if $SKIPtext == 1;
635 # Skip this line?
636 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
638 ++$PrintCount unless $SKIPline;
640 print STDOUT $_ unless $SKIPline;
642 # End of SKIP text ?
643 $SKIPtext = 0 if /[\<]{10}End Remove/;
645 # Ready!
646 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
647 exit;
650 #######################################################
652 if(grep(/\-\-help/i, @ARGV))
654 print << 'ENDOFHELPTEXT';
656 # HYPE
658 # CGIscriptor merges plain ASCII HTML files transparantly and safely
659 # with CGI variables, in-line PERL code, shell commands, and executable
660 # scripts in many languages (on-line and real-time). It combines the
661 # "ease of use" of HTML files with the versatillity of specialized
662 # scripts and PERL programs. It hides all the specifics and
663 # idiosyncrasies of correct output and CGI coding and naming. Scripts
664 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
665 # files can be ignorant of scripts and the associated values. CGIscriptor
666 # complies with the W3C HTML 4.0 recommendations.
667 # In addition to its use as a WWW embeded CGI processor, it can
668 # be used as a command-line document preprocessor (text-filter).
670 # THIS IS HOW IT WORKS
672 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
673 # using any required CGIparameters and environment variables. It
674 # is optimized to transparantly process HTML files inside a WWW server.
675 # The native language is Perl, but many other scripting languages
676 # can be used.
678 # CGIscriptor reads text files from the requested input file (i.e., from
679 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
680 # client requesting the service) preceded by the obligatory
681 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
682 # (except for "raw" files which supply their own Content-type message
683 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
685 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
687 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
688 # PERL script
689 # </SCRIPT>
691 # or
693 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
694 # OS Shell script
695 # </SCRIPT>
697 # construct (anything between []-brackets is optional, other MIME-types
698 # and scripting languages are supported), the embedded script is removed
699 # and both the contents of the source file (i.e., "do 'ScriptSource'")
700 # AND the script are evaluated as a PERL program (i.e., by eval()),
701 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
702 # interpreter. The output of the eval() function takes the place of the
703 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
704 # parameters declared by the CGI attribute are available as simple perl
705 # variables, and can subsequently be made available as variables to other
706 # scripting languages (e.g., bash, python, or lisp).
708 # Example: printing "Hello World"
709 # <HTML><HEAD><TITLE>Hello World</TITLE>
710 # <BODY>
711 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
712 # </BODY></HTML>
714 # Save this in a file, hello.html, in the directory you indicated with
715 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
716 # (or to whatever name you use as an alias for CGIscriptor.pl).
717 # This is realy ALL you need to do to get going.
719 # You can use any values that are delivered in CGI-compliant form (i.e.,
720 # the "?name=value" type URL additions) transparently as "$name" variables
721 # in your scripts IFF you have declared them in the CGI attribute of
722 # a META or SCRIPT tag before e.g.:
723 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
724 # [SRC='ScriptSource']">
725 # or
726 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
727 # [SRC='ScriptSource']>
728 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
729 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
730 # The CGIscriptor script allows you to write ordinary HTML files which will
731 # include dynamic CGI aware (run time) features, such as on-line answers
732 # to specific CGI requests, queries, or the results of calculations.
734 # For example, if you wanted to answer questions of clients, you could write
735 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
736 # that prints out the answer to requests given as arguments. You then write
737 # an HTML page "Respond.html" containing the following fragment:
739 # <center>
740 # The Answer to your question
741 # <META CONTENT="text/ssperl; CGI='$Question'">
742 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
743 # is
744 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
745 # AnswerQuestion($Question);
746 # </SCRIPT></h3>
747 # </center>
748 # <FORM ACTION=Respond.html METHOD=GET>
749 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
750 # <INPUT TYPE=SUBMIT VALUE="Ask">
751 # </FORM>
753 # The output could look like the following (in HTML-speak):
755 # <CENTER>
756 # The Answer to your question
757 # <h3>What is the capital of the Netherlands?</h3>
758 # is
759 # <h3>Amsterdam</h3>
760 # </CENTER>
761 # <FORM ACTION=Respond.html METHOD=GET>
762 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
763 # <INPUT TYPE=SUBMIT VALUE="Ask">
765 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
766 # it just prints out answers to arguments. Likewise, the text has no
767 # provisions for scripts or CGI like constructs. Also, it is completely
768 # trivial to extend this "program" to use the "Answer" later in the page
769 # to call up other information or pictures/sounds. The final text never
770 # shows any cue as to what the original "source" looked like, i.e.,
771 # where you store your scripts and how they are called.
773 # There are some extra's. The argument of the files called in a SRC= tag
774 # can access the CGI variables declared in the preceding META tag from
775 # the @ARGV array. Executable files are called as:
776 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
777 # The files called from SRC can even be (CGIscriptor) html files which are
778 # processed in-line. Furthermore, the SRC= tag can contain a perl block
779 # that is evaluated. That is,
780 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
781 # will result in the evaluation of "print do {$Question};" and the VALUE
782 # of $Question will be printed. Note that these "SRC-blocks" can be
783 # preceded and followed by other file names, but only a single block is
784 # allowed in a SRC= tag.
786 # One of the major hassles of dynamic WWW pages is the fact that several
787 # mutually incompatible browsers and platforms must be supported. For example,
788 # the way sound is played automatically is different for Netscape and
789 # Internet Explorer, and for each browser it is different again on
790 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
791 # (form-) values to construct email addresses, file names, or database
792 # queries. All Apache WWW-server exploits reported in the media are
793 # based on faulty CGI-scripts that didn't check their user-data properly.
795 # There is no panacee for these problems, but a lot of work and problems
796 # can be saved by allowing easy and transparent control over which
797 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
798 # supplies such a method in the form of a pair of attributes:
799 # IF='...condition..' and UNLESS='...condition...'. When added to a
800 # script tag, the whole block (including the SRC attribute) will be
801 # ignored if the condition is false (IF) or true (UNLESS).
802 # For example, the following block will NOT be evaluated if the value
803 # of the CGI variable FILENAME is NOT a valid filename:
805 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
806 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
807 # .....
808 # </SCRIPT>
810 # (the function CGIsafeFileName(String) returns an empty string ("")
811 # if the String argument is not a valid filename).
812 # The UNLESS attribute is the mirror image of IF.
814 # A user manual follows the HTML 4 and security paragraphs below.
816 ##########################################################################
818 # HTML 4 compliance
820 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
821 # the W3C. This means that any software to manage Web sites will be able
822 # to handle CGIscriptor files, as will web agents.
824 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
825 # script type is indicated with TYPE="mime-type", the LANGUAGE
826 # feature is ignored, and a SRC feature is implemented. All CGI specific
827 # features are delegated to the CGI attribute.
829 # However, the behavior deviates from the W3C recommendations at some
830 # points. Most notably:
831 # 0- The scripts are executed at the server side, invissible to the
832 # client (i.e., the browser)
833 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
834 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
835 # when a SRC feature is present.
836 # 3- The SRC attribute reads a list of files.
837 # 4- The files in a SRC attribute are processed according to file type.
838 # 5- The SRC attribute evaluates inline Perl code.
839 # 6- Processed META, DIV, INS tags are removed from the output
840 # document.
841 # 7- All attributes of the processed META tags, except CONTENT, are ignored
842 # (i.e., deleted from the output).
843 # 8- META tags can be placed ANYWHERE in the document.
844 # 9- Through the SRC feature, META tags can have visible output in the
845 # document.
846 # 10- The CGI attribute that declares CGI parameters, can be used
847 # inside the <SCRIPT> tag.
848 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
849 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
850 # \{\}, \[\].
851 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
852 # 13- <DIV> tags cannot be nested, DIV tags are not
853 # rendered with new-lines.
854 # 14- The XML style <TAG .... /> is recognized and handled correctly.
855 # (i.e., no content is processed)
857 # The reasons for these choices are:
858 # You can still write completely HTML4 compliant documents. CGIscriptor
859 # will not force you to write "deviant" code. However, it allows you to
860 # do so (which is, in fact, just as bad). The prime design principle
861 # was to allow users to include plain Perl code. The code itself should
862 # be "enhancement free". Therefore, extra features were needed to
863 # supply easy access to CGI and Web site components. For security
864 # reasons these have to be declared explicitly. The SRC feature
865 # transparently manages access to external files, especially the safe
866 # use of executable files.
867 # The CGI attribute handles the declarations of external (CGI) variables
868 # in the SCRIPT and META tag's.
869 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
870 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
872 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
873 # evaluation) were build into the META and SCRIPT tags is part laziness,
874 # part security. The SRC blocks allows more compact documents and easier
875 # debugging. The values of the CGI variables can be immediately screened
876 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
877 # email addresses and file names), and a few commands can be called
878 # without having to add another Perl TAG pair. This is especially important
879 # for documents that require the use of other (more restricted) "scripting"
880 # languages and facilities that lag transparent control structures.
882 ##########################################################################
884 # SECURITY
886 # Your WWW site is a few keystrokes away from a few hundred million internet
887 # users. A fair percentage of these users knows more about your computer
888 # than you do. And some of these just might have bad intentions.
890 # To ensure uncompromized operation of your server and platform, several
891 # features are incorporated in CGIscriptor.pl to enhance security.
892 # First of all, you should check the source of this program. No security
893 # measures will help you when you download programs from anonymous sources.
894 # If you want to use THIS file, please make sure that it is uncompromized.
895 # The best way to do this is to contact the source and try to determine
896 # whether s/he is reliable (and accountable).
898 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
899 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
901 # I would like to ask any user who finds bugs that could compromise
902 # security to report them to me (and any other bug too,
903 # Email: R.J.J.H.vanSon@uva.nl or ifa@hum.uva.nl).
905 # Security features
907 # 1 Invisibility
908 # The inner workings of the HTML source files are completely hidden
909 # from the client. Only the HTTP header and the ever changing content
910 # of the output distinguish it from the output of a plain, fixed HTML
911 # file. Names, structures, and arguments of the "embedded" scripts
912 # are invisible to the client. Error output is suppressed except
913 # during debugging (user configurable).
915 # 2 Separate directory trees
916 # Directories containing Inline text and script files can reside on
917 # separate trees, distinct from those of the HTTP server. This means
918 # that NEITHER the text files, NOR the script files can be read by
919 # clients other than through CGIscriptor.pl, UNLESS they are
920 # EXPLICITELY made available.
922 # 3 Requests are NEVER "evaluated"
923 # All client supplied values are used as literal values (''-quoted).
924 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
925 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
926 # supply executable commands. Be sure to AVOID scripts like:
928 # <META CONTENT="text/ssperl; CGI='$UserValue'">
929 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
931 # These are a recipe for disaster. However, the following quoted
932 # form should be save (but is still not adviced):
934 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
936 # A special function, SAFEqx(), will automatically do exactly this,
937 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
938 # with $UserValue interpolated. I recommend to use SAFEqx() instead
939 # of backticks whenever you can. The OS shell scripts inside
941 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
943 # are handeld by SAFEqx and automatically ''-quoted.
945 # 4 Logging of requests
946 # All requests can be logged separate from the Host server. The level of
947 # detail is user configurable: Including or excluding the actual queries.
948 # This allows for the inspection of (im-) proper use.
950 # 5 Access control: Clients
951 # The Remote addresses can be checked against a list of authorized
952 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
953 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
954 # HOST name can be (in-) excluded by their IP-address. Client patterns
955 # containing all numbers and dots are considered IP-addresses, all others
956 # domain names. No wild-cards or regexp's are allowed, only partial
957 # addresses.
958 # Matching of names is done from the back to the front (domain first,
959 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
960 # accept or reject all clients from the domain EDU. Matching of
961 # IP-addresses is done from the front to the back (domain first, i.e.,
962 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
963 # exclude all clients whose IP-address starts with 128.
964 # There are two special symbols: "-" matches HOSTs with no name and "*"
965 # matches ALL HOSTS/clients.
966 # For those needing more expressional power, lines starting with
967 # "-e" are evaluated by the perl eval() function. E.g.,
968 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
969 # domain '.edu'.
971 # 6 Access control: Files
972 # In principle, CGIscriptor could read ANY file in the directory
973 # tree as discussed in 1. However, for security reasons this is
974 # restricted to text files. It can be made more restricted by entering
975 # a global file pattern (e.g., ".html"). This is done by default.
976 # For each client requesting access, the file pattern(s) can be made
977 # more restrictive than the global pattern by entering client specific
978 # file patterns in the Access Control files (see 5).
979 # For example: if the ACCEPT file contained the lines
980 # * DEMO
981 # .hum.uva.nl LET
982 # 145.18.230.
983 # Then all clients could request paths containing "DEMO" or "demo", e.g.
984 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
985 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
986 # "/my/let/file.html", and clients from the local cluster
987 # 145.18.230.[0-9]+ could access ALL files.
988 # Again, for those needing more expressional power, lines starting with
989 # "-e" are evaluated. For instance:
990 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
991 # will accept/reject requests for files from the directory "/demo/" from
992 # clients from the domain '.edu'.
994 # 7 Access control: Server side session tickets
995 # Specific paths can be controlled by Session Tickets which must be
996 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
997 # are defined in %TicketRequiredPatterns as pairs of:
998 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
999 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
1000 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
1001 # CGI. The following is an example:
1002 # Type: SESSION
1003 # IPaddress: 127.0.0.1
1004 # AllowedPaths: ^/Private/Name/
1005 # Expires: 3600
1006 # Username: test
1007 # ...
1008 # Other content can follow.
1010 # It is adviced that Session Tickets should be deleted
1011 # after some (idle) time. The IP address should be the IP number at login, and
1012 # the SESSIONTICKET will be rejected if it is presented from another IP address.
1013 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
1014 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
1015 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
1016 # time the ticket will remain valid after creation (file ctime). Time can be given
1017 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
1018 # None of these need be present, but the Ticket must have a non-zero size.
1020 # Next to Session Tickets, there are two other type of ticket files:
1021 # - LOGIN tickets store information about a current login request
1022 # - PASSWORD ticket store account information to authorize login requests
1024 # 8 Query length limiting
1025 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
1026 # than this limit, the request is rejected. The combined length of the
1027 # Query string and the POST input is checked before any processing is done.
1028 # This will prevent clients from overloading the scripts.
1029 # The actual, combined, Query Size is accessible as a variable through
1030 # $CGI_Content_Length.
1032 # 9 Illegal filenames, paths, and protected directories
1033 # One of the primary security concerns in handling CGI-scripts is the
1034 # use of "funny" characters in the requests that con scripts in executing
1035 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1036 # characters in URL's and filenames, followed by executable commands. A
1037 # special variable $FileAllowedChars stores a string of all allowed
1038 # characters. Any request that translates to a filename with a character
1039 # OUTSIDE this set will be rejected.
1040 # In general, all (readable files) in the DocumentRoot tree are accessible.
1041 # This might not be what you want. For instance, your DocumentRoot directory
1042 # might be the working directory of a CVS project and contain sensitive
1043 # information (e.g., the password to get to the repository). You can block
1044 # access to these subdirectories by adding the corresponding patterns to
1045 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1046 # will block any request that contains '/CVS/' or:
1047 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1049 #10 The execution of code blocks can be controlled in a transparent way
1050 # by adding IF or UNLESS conditions in the tags themselves. That is,
1051 # a simple check of the validity of filenames or email addresses can
1052 # be done before any code is executed.
1054 ###############################################################################
1056 # USER MANUAL (sort of)
1058 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1059 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1060 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1061 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1062 # the necessary code is already available in the TAG itself (e.g.,
1063 # using external files). The contents of the directive are executed by
1064 # the PERL eval() and `` functions (in a separate name space). The
1065 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1066 # in the output file. You can use the values that are delivered in
1067 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1068 # transparently as "$name" variables in your directives after they are
1069 # defined in a <META> or <SCRIPT> tag.
1070 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1071 # subsequent <SCRIPT> and <META> results (including the defining
1072 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1073 # behaves like any other, ordinary list and can be manipulated.
1075 # Both GET and POST requests are accepted. These two methods are treated
1076 # equal. Variables, i.e., those values that are determined when a file is
1077 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1078 # in which <name> is the name of the variable and <default> is the value
1079 # used when there is NO current CGI value for <name> (you can use
1080 # white-spaces in $<name>=<default> but really DO make sure that the
1081 # default value is followed by white space or is quoted). Names can contain
1082 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1083 # If the Content-type: is 'multipart/*', the input is treated as a
1084 # MIME multipart message and automatically delimited. CGI variables get
1085 # the "raw" (i.e., undecoded) body of the corresponding message part.
1087 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1088 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1089 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1090 # query string), CGI_Content_Length (the length of the decoded query
1091 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1092 # date and time).
1094 # All these are available when defined in a CGI attribute. All environment
1095 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1096 # and the REMOTE_USER, use, e.g.:
1098 # <SCRIPT TYPE='text/ssperl'>
1099 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1100 # </SCRIPT>
1102 # (This will print a "-" if REMOTE_HOST is not known)
1103 # Another way to do this is:
1105 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1106 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1107 # or
1108 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1109 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1111 # This is possible because ALL environment variables are available as
1112 # CGI variables. The environment variables take precedence over CGI
1113 # names in case of a "name clash". For instance:
1114 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1115 # Will print the current HOME directory (environment) irrespective whether
1116 # there is a CGI variable from the query
1117 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1118 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1119 # the values of defined environment variables (e.g., by supplying
1120 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1121 # it would make the use of declared variables insecure. You can still
1122 # access CGI variables after a name clash with
1123 # CGIscriptor::CGIparseValue(<name>).
1125 # Some CGI variables are present several times in the query string
1126 # (e.g., from multiple selections). These should be defined as
1127 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1128 # will contain ALL VARIABLENAME values from the query, or a single
1129 # default value. If there is an ENVIRONMENT variable of the
1130 # same name, it will be used instead of the default AND the query
1131 # values. The corresponding function is
1132 # CGIscriptor::CGIparseValueList(<name>)
1134 # CGI variables collected in a @VARIABLENAME list are unordered.
1135 # When more structured variables are needed, a hash table can be used.
1136 # A variable defined as %VARIABLE=default will collect all
1137 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1138 # the remainder of the name as a key. For instance, %PERSON will
1139 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1140 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1141 # equals 'Alice'. Any default value or environment value will be stored
1142 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1143 # it will be used instead of the default AND the query values. The
1144 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1146 # This method of first declaring your environment and CGI variables
1147 # before being able to use them in the scripts might seem somewhat
1148 # clumsy, but it protects you from inadvertedly printing out the values of
1149 # system environment variables when their names coincide with those used
1150 # in the CGI forms. It also prevents "clients" from supplying CGI
1151 # parameter values for your private variables.
1152 # THIS IS A SECURITY FEATURE!
1155 # NON-HTML CONTENT TYPES
1157 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1158 # message before anything is printed. This has been extended to include
1159 # plain text (.txt) files, for which the Content-type (MIME type)
1160 # 'text/plain' is printed. In all other respects, text files are treated
1161 # as HTML files (this can be switched off by removing '.txt' from the
1162 # $FilePattern variable) . When the content type should be something else,
1163 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1164 # item). CGIscriptor will not print a Content-type message for this file
1165 # type (which must supply its OWN Content-type message). Raw files must
1166 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1169 # NON-HTML FILES
1171 # CGIscriptor is intended to process HTML and text files only. You can
1172 # create documents of any mime-type on-the-fly using "raw" text files,
1173 # e.g., with the .xmr extension. However, CGIscriptor will not process
1174 # binary files of any type, e.g., pictures or sounds. Given the sheer
1175 # number of formats, I do not have any intention to do so. However,
1176 # an escape route has been provided. You can construct a genuine raw
1177 # (.xmr) text file that contains the perl code to service any file type
1178 # you want. If the global $BinaryMapFile variable contains the path to
1179 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1180 # whenever an unsupported (non-HTML) file type is requested. The path
1181 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1182 # and can be used like any other CGI-variable. Servicing binary files
1183 # then becomes supplying the correct Content-type (e.g., print
1184 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1185 # to STDOUT (e.g., using sysread() and syswrite()).
1188 # THE META TAG
1190 # All attributes of a META tag are ignored, except the
1191 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1192 # inside the quotes following the CONTENT= indication (white-space is
1193 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1194 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1195 # text/osshell) and a comma or semicolon.
1196 # The quoted string following CGI= contains a white-space separated list
1197 # of declarations of the CGI (and Environment) values and default values
1198 # used when no CGI values are supplied by the query string.
1200 # If the default value is a longer string containing special characters,
1201 # possibly spanning several lines, the string must be enclosed in quotes.
1202 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1203 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1204 # is different from (...)). The outermost pair will always be used and any
1205 # other quotes inside the string are considered to be part of the string
1206 # value, e.g.,
1208 # $Value = {['this'
1209 # "and" (this)]}
1210 # will result in $Value getting the default value: ['this'
1211 # "and" (this)]
1212 # (NOTE that the newline is part of the default value!).
1214 # Internally, for defining and initializing CGI (ENV) values, the META
1215 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1216 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1217 # These functions can be used inside scripts as
1218 # "CGIscriptor::defineCGIvariable($name, $default)" and
1219 # "CGIscriptor::defineCGIvariableList($name, $default)".
1220 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1222 # The CGI attribute will be processed exactly identical when used inside
1223 # the <SCRIPT> tag. However, this use is not according to the
1224 # HTML 4.0 specifications of the W3C.
1227 # THE DIV/INS TAGS
1229 # There is a problem when constructing html files containing
1230 # server-side perl scripts with standard HTML tools. These
1231 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1232 # tags. This is quite annoying when you want to use large
1233 # HTML templates where you will fill in values.
1235 # For this purpose, CGIscriptor will read the neutral
1236 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1237 # <INS CLASS="ssperl" ID="varname"></INS>
1238 # tag (in Cascading Style Sheet manner) Note that
1239 # "varname" has NO '$' before it, it is a bare name.
1240 # Any text between these <DIV ...></DIV> or
1241 # <INS ...></INS>tags will be assigned to '$varname'
1242 # as is (e.g., as a literal).
1243 # No processing or interpolation will be performed.
1244 # There is also NO nesting possible. Do NOT nest a
1245 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1246 # DIV tags do ensure a block structure in the final
1247 # rendering (i.e., no empty lines).
1249 # Note that <DIV CLASS="ssperl" ID="varname"/>
1250 # is handled the XML way. No content is processed,
1251 # but varname is defined, and any SRC directives are
1252 # processed.
1254 # You can use $varname like any other variable name.
1255 # However, $varname is NOT a CGI variable and will be
1256 # completely internal to your script. There is NO
1257 # interaction between $varname and the outside world.
1259 # To interpolate a DIV derived text, you can use:
1260 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1261 # $varname = eval("qq[$varname]"); # Interpolate all values
1263 # The DIV tags will process IF, UNLESS, CGI and
1264 # SRC attributes. The SRC files will be pre-pended to the
1265 # body text of the tag. SRC blocks are NOT executed.
1267 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1269 # It is often necessary to include code-blocks that should be executed
1270 # conditionally, e.g., only for certain browsers or operating system.
1271 # Furthermore, quite often sanity and security checks are necessary
1272 # before user (form) data can be processed, e.g., with respect to
1273 # email addresses and filenames.
1275 # Checks added to the code are often difficult to find, interpret or
1276 # maintain and in general mess up the code flow. This kind of confussion
1277 # is dangerous.
1278 # Also, for many of the supported "foreign" scripting languages, adding
1279 # these checks is cumbersome or even impossible.
1281 # As a uniform method for asserting the correctness of "context", two
1282 # attributes are added to all supported tags: IF and UNLESS.
1283 # They both evaluate their value and block execution when the
1284 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1285 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1286 # the backslash in the '\>' is removed and only used to differentiate
1287 # this conditional '>' from the tag-closing '>'. For symmetry, the
1288 # backslash in '\<' is also removed. Inside these conditionals,
1289 # ~/ and ./ are expanded to their respective directory root paths.
1291 # For example, the following tag will be ignored when the filename is
1292 # invalid:
1294 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1295 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1296 # ...
1297 # </SCRIPT>
1299 # The IF and UNLESS values must be quoted. The same quotes are supported
1300 # as with the other attributes. The SRC attribute is ignored when IF and
1301 # UNLESS block execution.
1303 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1306 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1308 # The SRC attribute inside tags accepts a list of filenames and URL's
1309 # separated by "," comma's (or ";" semicolons).
1310 # ALL the variable values defined in the CGI attribute are available
1311 # in @ARGV as if the file or block was executed from the command line,
1312 # in the exact order in which they were declared in the preceding CGI
1313 # attribute.
1315 # First, a SRC={}-block will be evaluated as if the code inside the
1316 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1317 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1318 # Only a single block is evaluated. Note that this is processed less
1319 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1320 # depends on the content-type: Perl for text/ssperl and OS shell for
1321 # text/osshell. For other mime types (scripting languages), anything in
1322 # the source block is put in front of the code block "inside" the tag.
1324 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1325 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1326 # That is, you can actually call executables savely from the SRC tag.
1328 # Third, text files that match the file pattern, used by CGIscriptor to
1329 # check whether files should be processed ($FilePattern), are
1330 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1331 # was inserted in the original source file. Recursions, i.e., calling
1332 # a file inside itself, are blocked. If you need them, you have to code
1333 # them explicitely using "main::ProcessFile($file_path)".
1335 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1336 # "do FileName;'';".
1338 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1339 # 'TELNET://', 'WHOIS://' etc.) are loaded
1340 # and printed. The loading and handling of <BASE> and document header
1341 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1342 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1344 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1345 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1346 # content of the tag to appending the code after the content of the tag
1347 # (POSTFIX). The switches are done in the order in which the PREFIX and
1348 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1349 # in any order with the SRC files. Note that the ORDER of file execution
1350 # is determined for prefixed and postfixed files seperately.
1352 # File paths can be preceded by the URL protocol prefix "file://". This
1353 # is simply STRIPPED from the name.
1355 # Example:
1356 # The request
1357 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1358 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1359 # With QUERY_STRING = "positive=8&negative=22"
1361 # on encountering the lines:
1362 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1363 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1364 # </SCRIPT></b><p>"
1366 # This line will be processed as:
1367 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1369 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1370 # This line will end up printed as:
1371 # "<b>p <= 0.0161</b><p>"
1373 # Note that the META tag itself will never be printed, and is invisible to
1374 # the outside world.
1376 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1377 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1378 # need any content, you can use the <DIV...../> format.
1381 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1383 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1384 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1385 # public directories can be accessed without direct reference to the
1386 # actual paths. Filepaths starting with "./" are replaced by
1387 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1389 # Note: this replacement can seriously affect Perl scripts. Watch
1390 # out for constructs like $a =~ s/aap\./noot./g, use
1391 # $a =~ s@aap\.@noot.@g instead.
1393 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1394 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1395 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1396 # executed.
1397 # Values not preceded by $, ~/, or ./ are used as literals
1400 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1402 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1403 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1404 # interpolate the script and replace all user-supplied CGI-variables by
1405 # their ''-quoted values (actually, all variables defined in CGI attributes
1406 # are quoted). Other Perl variables are interpolated in a simple fasion,
1407 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1408 # their name=value pairs. Complex references, e.g., @$variable, are all
1409 # evaluated in a scalar context. Quotes should be used with care.
1410 # NOTE: the results of the shell script evaluation will appear in the
1411 # @CGIscriptorResults stack just as any other result.
1412 # All occurrences of $@% that should NOT be interpolated must be
1413 # preceeded by a "\". Interpolation can be switched off completely by
1414 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1415 # (set to 0 or undef to switch interpolation on again)
1416 # i.e.,
1417 # <SCRIPT TYPE="text/ssperl">
1418 # $CGIscriptor::NoShellScriptInterpolation = 1;
1419 # </SCRIPT>
1422 # RUN TIME TRANSLATION OF INPUT FILES
1424 # Allows general and global conversions of files using Regular Expressions.
1425 # Very handy (but costly) to rewrite legacy pages to a new format.
1426 # Select files to use it on with
1427 # my $TranslationPaths = 'filepattern';
1428 # This is costly. For efficiency, define:
1429 # $TranslationPaths = ''; when not using translations.
1430 # Accepts general regular expressions: [$pattern, $replacement]
1432 # Define:
1433 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1435 # push(@TranslationTable, ['pattern', 'replacement']);
1436 # e.g. (for Ruby Rails):
1437 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1438 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1440 # Runs:
1441 # my $currentRegExp;
1442 # foreach $currentRegExp (@TranslationTable)
1444 # my ($pattern, $replacement) = @$currentRegExp;
1445 # $$text =~ s!$pattern!$replacement!msg;
1446 # };
1449 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1451 # Adding a MIME-type and an interpreter command to
1452 # %ScriptingLanguages automatically will catch any other
1453 # scripting language in the standard
1454 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1455 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1456 # will actually execute the folowing code in an HTML page
1457 # (ignore 'REMOTE_HOST' for the moment):
1458 # <SCRIPT TYPE="text/sspython">
1459 # # A Python script
1460 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1461 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1462 # </SCRIPT>
1464 # The script code is NOT interpolated by perl, EXCEPT for those
1465 # interpreters that cannot handle variables themselves.
1466 # Currently, several interpreters are pre-installed:
1468 # Perl test - "text/testperl" => 'perl',
1469 # Python - "text/sspython" => 'python',
1470 # Ruby - "text/ssruby" => 'ruby',
1471 # Tcl - "text/sstcl" => 'tcl',
1472 # Awk - "text/ssawk" => 'awk -f-',
1473 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1474 # "| egrep -v '> |^rep. |^nil\\\$'",
1475 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1476 # "| egrep -v '> \\\$|^NIL'",
1477 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1478 # M4 macro's- "text/ssm4" => 'm4',
1479 # Born shell- "text/sh" => 'sh',
1480 # Bash - "text/bash" => 'bash',
1481 # C-shell - "text/csh" => 'csh',
1482 # Korn shell- "text/ksh" => 'ksh',
1483 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1484 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1485 # REBOL - "text/ssrebol" =>
1486 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1487 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1488 # (psql)
1490 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1491 # that reads Standard Input and writes to standard output. Any extra
1492 # output of interactive interpreters (banners, echo's, prompts)
1493 # should be removed by piping the output through 'tail', 'grep',
1494 # 'sed', or even 'awk' or 'perl'.
1496 # For access to CGI variables there is a special hashtable:
1497 # %ScriptingCGIvariables.
1498 # CGI variables can be accessed in three ways.
1499 # 1. If the mime type is not present in %ScriptingCGIvariables,
1500 # nothing is done and the script itself should parse the relevant
1501 # environment variables.
1502 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1503 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1504 # the script text is interpolated by perl. That is, all $var, @array,
1505 # %hash, and \-slashes are replaced by their respective values.
1506 # 3. In all other cases, the CGI and environment variables are added
1507 # in front of the script according to the format stored in
1508 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1509 # executed for each CGI- or Environment variable defined in the CGI-tag:
1510 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1512 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1513 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1514 # (note that these definitions are not save, the real ones contain '-quotes).
1516 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1517 # instead.
1519 # The $CGI_VALUE parameters are "shrubed" of all control characters
1520 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1521 # Control characters are replaced by \0<octal ascii value> (the exception
1522 # is \015, the newline, which is replaced by \n) and quotes
1523 # and backslashes by their HTML character
1524 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1525 # For example:
1526 # if a client would supply the string value (in standard perl, e.g.,
1527 # \n means <newline>)
1528 # "/dev/null';\nrm -rf *;\necho '"
1529 # it would be processed as
1530 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1531 # (e.g., sh or bash would process the latter more according to your
1532 # intentions).
1533 # If your intepreter requires different protection measures, you will
1534 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1535 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1537 # Currently, the following definitions are used:
1538 # %ScriptingCGIvariables = (
1539 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1540 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1541 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1542 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1543 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1544 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1545 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1546 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1547 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1548 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1549 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1550 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1551 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1552 # "text/sspraat" => '', # Praat (interpolation)
1553 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1554 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1555 # "text/postgresql" => '', # PostgreSQL (interpolation)
1556 # "" => ""
1557 # );
1559 # Four tables allow fine-tuning of interpreter with code that should be
1560 # added before and after each code block:
1562 # Code added before each script block
1563 # %ScriptingPrefix = (
1564 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1565 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1566 # );
1567 # Code added at the end of each script block
1568 # %ScriptingPostfix = (
1569 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1570 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1571 # );
1572 # Initialization code, inserted directly after opening (NEVER interpolated)
1573 # %ScriptingInitialization = (
1574 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1575 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1576 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1577 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1578 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1579 # );
1580 # Cleanup code, inserted before closing (NEVER interpolated)
1581 # %ScriptingCleanup = (
1582 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1583 # "text/sspraat" => 'Quit',
1584 # "text/ssawk" => '};', # Server Side awk scripts
1585 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1586 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1587 # "text/postgresql" => '\q',
1588 # );
1591 # The SRC attribute is NOT magical for these interpreters. In short,
1592 # all code inside a source file or {} block is written verbattim
1593 # to the interpreter. No (pre-)processing or executional magic is done.
1595 # A serious shortcomming of the described mechanism for handling other
1596 # (scripting) languages, with respect to standard perl scripts
1597 # (i.e., 'text/ssperl'), is that the code is only executed when
1598 # the pipe to the interpreter is closed. So the pipe has to be
1599 # closed at the end of each block. This means that the state of the
1600 # interpreter (e.g., all variable values) is lost after the closing of
1601 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1602 # all values and definitions.
1604 # APPLICATION MIME TYPES
1606 # To ease some important auxilliary functions from within the
1607 # html pages I have added them as MIME types. This uses
1608 # the mechanism that is also used for the evaluation of
1609 # other scripting languages, with interpolation of CGI
1610 # parameters (and perl-variables). Actually, these are
1611 # defined exactly like any other "scripting language".
1613 # text/ssdisplay: display some (HTML) text with interpolated
1614 # variables (uses `cat`).
1615 # text/sslogfile: write (append) the interpolated block to the file
1616 # mentioned on the first, non-empty line
1617 # (the filename can be preceded by 'File: ',
1618 # note the space after the ':',
1619 # uses `awk .... >> <filename>`).
1620 # text/ssmailto: send email directly from within the script block.
1621 # The first line of the body must contain
1622 # To:Name@Valid.Email.Address
1623 # (note: NO space between 'To:' and the email adres)
1624 # For other options see the mailto man pages.
1625 # It works by directly sending the (interpolated)
1626 # content of the text block to a pipe into the
1627 # Linux program 'mailto'.
1629 # In these script blocks, all Perl variables will be
1630 # replaced by their values. All CGI variables are cleaned before
1631 # they are used. These CGI variables must be redefined with a
1632 # CGI attribute to restore their original values.
1633 # In general, this will be more secure than constructing
1634 # e.g., your own email command lines. For instance, Mailto will
1635 # not execute any odd (forged) email addres, but just stops
1636 # when the email address is invalid and awk will construct
1637 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1638 # as a "valid" UNIX filename). Note that it will also gladly
1639 # store this file anywhere (/../../../etc/passwd will work!).
1640 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1641 # filename.
1643 # SHELL SCRIPT PIPING
1645 # If a shell script starts with the UNIX style "#! <shell command> \n"
1646 # line, the rest of the shell script is piped into the indicated command,
1647 # i.e.,
1648 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1650 # In many ways this is equivalent to the MIME-type profiling for
1651 # evaluating other scripting languages as discussed above. The
1652 # difference breaks down to convenience. Shell script piping is a
1653 # "raw" implementation. It allows you to control all aspects of
1654 # execution. Using the MIME-type profiling is easier, but has a
1655 # lot of defaults built in that might get in the way. Another
1656 # difference is that shell script piping uses the SAFEqx() function,
1657 # and MIME-type profiling does not.
1659 # Execution of shell scripts is under the control of the Perl Script blocks
1660 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1661 # blocks can be simulated easily. You can switch to a different shell,
1662 # e.g. tcl, completely by executing the following Perl commands inside
1663 # your document:
1665 # <SCRIPT TYPE="text/ssperl">
1666 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1667 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1668 # $CGIscriptor::NoShellScriptInterpolation = 1;
1669 # </SCRIPT>
1671 # After this script is executed, CGIscriptor will parse scripts of
1672 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1673 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1674 # The crucial function is :
1675 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1676 # After executing this function, all shell scripts AND all
1677 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1678 # of RedirectShellScript is empty, e.g., '', the original (default)
1679 # value is reset.
1681 # The standard output, STDOUT, of any pipe is send to the client.
1682 # Currently, you should be carefull with quotes in such a piped script.
1683 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1684 # As a result, you do not have access to the output of any piped (#!)
1685 # process! If you want such access, execute
1686 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1687 # or
1688 # <SCRIPT TYPE="text/ssperl">
1689 # $resultvar = SAFEqx('echo "script"|command');
1690 # </SCRIPT>.
1692 # Safety is never complete. Although SAFEqx() prevents some of the
1693 # most obvious forms of attacks and security slips, it cannot prevent
1694 # them all. Especially, complex combinations of quotes and intricate
1695 # variable references cannot be handled safely by SAFEqx. So be on
1696 # guard.
1699 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1701 # All PERL scripts are evaluated inside a PERL package. This package
1702 # has a separate name space. This isolated name space protects the
1703 # CGIscriptor.pl program against interference from user code. However,
1704 # some variables, e.g., $_, are global and cannot be protected. You are
1705 # advised NOT to use such global variable names. You CAN write
1706 # directives that directly access the variables in the main program.
1707 # You do so at your own risk (there is definitely enough rope available
1708 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1709 # you change its private variables during run time. The PERL code
1710 # directives are used as in:
1711 # $Result = eval($directive); print $Result;'';
1712 # ($directive contains all text between <SCRIPT></SCRIPT>).
1713 # That is, the <directive> is treated as ''-quoted string and
1714 # the result is treated as a scalar. To prevent the VALUE of the code
1715 # block from appearing on the client's screen, end the directive with
1716 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1717 # eval(), blocks, and subroutines, but only as a scalar.
1719 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1720 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1721 # (e.g., for "my $var;" declarations). This means that values assigned
1722 # to a PERL variable can be used throughout the document unless they
1723 # were declared with "my". The following will actually work as intended
1724 # (note that the ``-quotes in this example are NOT evaluated, but used
1725 # as simple quotes):
1727 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1728 # anything ...
1729 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1730 # anything ...
1731 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1733 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1734 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1735 # construct will print the elements of $String separated by commas, leaving
1736 # out the first element, i.e., $List[0].
1738 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1739 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1740 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1741 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1742 # s@<regexp>.@<replacement>.@g instead.
1745 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1747 # An infrastructure for user acount authorization and file access control
1748 # is available. Each request is matched against a list of URL path patterns.
1749 # If the request matches, a Session Ticket is required to access the URL.
1750 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1752 # CGI: SESSIONTICKET=&lt;value&gt;
1753 # Cookie: CGIscriptorSESSION=&lt;value&gt;
1755 # The example implementation stores Session Tickets as files in a local
1756 # directory. To create Session Tickets, a Login request must be given
1757 # with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
1758 # password. The user name and (singly hashed) password are stored in a
1759 # PASSWORD ticket with the same name as the user account (name cleaned up
1760 # for security).
1762 # The example session model implements 4 functions:
1763 # - Login
1764 # The password is hashed with the user name and server side salt, and then
1765 # hashed with a random salt. Client and Server both perform these actions
1766 # and the Server only grants access if restults are the same. The server
1767 # side only stores the password hashed with the user name and
1768 # server side salt. Neither the plain password, nor the hashed password is
1769 # ever exchanged. Only values hashed with the one-time salt are exchanged.
1770 # - Session
1771 # For every access to a restricted URL, the Session Ticket is checked before
1772 # access is granted. There are three session modes. The first uses a fixed
1773 # Session Ticket that is stored as a cookie value in the browser (actually,
1774 # as a sessionStorage value). The second uses only the IP address at login
1775 # to authenticate requests. The third
1776 # is a Challenge mode, where the client has to calculate the value of the
1777 # next one-time Session Ticket from a value derived from the password and
1778 # a random string.
1779 # - Password Change
1780 # A new password is hashed with the user name and server side salt, and
1781 # then encrypted (XORed)
1782 # with the old password hashed with the user name and salt. That value is
1783 # exchanged and XORed with the stored old hashed(salt+password+username).
1784 # Again, the stored password value is never exchanged unencrypted.
1785 # - New Account
1786 # The text of a new account (Type: PASSWORD) file is constructed from
1787 # the new username (CGI: NEWUSERNAME, converted to lowercase) and
1788 # hashed new password (CGI: NEWPASSWORD). The same process is used to encrypt
1789 # the new password as is used for the Password Change function.
1790 # Again, the stored password value is never exchanged unencrypted.
1791 # Some default setting are encoded. For display in the browser, the new password
1792 # is reencrypted (XORed) with a special key, the old password hash
1793 # hashed with a session specific random hex value sent initially with the
1794 # session login ticket ($RANDOMSALT).
1795 # For example for user "NewUser" and password "NewPassword" with filename
1796 # "newuser":
1798 # Type: PASSWORD
1799 # Username: newuser
1800 # Password: 84b26fd2aaacae1c2e42fe07da1793e8232ffe548eceb519b46646fe9ff32612
1801 # Salt: 970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1a
1802 # AllowedPaths: ^/Private/[\w\-]+\.html?
1803 # AllowedPaths: ^/Private/newuser/
1804 # Session: SESSION
1805 # Date: Thu Jun 14 12:34:40 2012 UTC
1806 # Time: 1339677280
1808 # The password is created with the Unix commands:
1809 # printf '%s' '970e68017413fb0ea84d7fe3c463077636dd6d53486910d4a53c693dd4109b1aNewPasswordnewuser'|shasum -a 256
1813 # Implementation
1815 # The session authentication mechanism is based on the exchange of ticket
1816 # identifiers. A ticket identifier is just a string of characters, a name
1817 # or a random 64 character hexadecimal string. Ticket identifiers should be
1818 # "safe" filenames (except user names). There are four types of tickets:
1819 # PASSWORD: User account descriptors, including a user name and password
1820 # LOGIN: Temporary anonymous tickets used during login
1821 # IPADDRESS: Authetication tokens that allow access based on the IP address of the request
1822 # SESSION: Reusable authetication tokens
1823 # CHALLENGE: One-time authetication tokens
1824 # All tickets can have an expiration date in the form of a time duration
1825 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1826 # An absolute time can be given in seconds since the epoch of the server host.
1827 # Note that expiration times of CHALLENGE authetication tokens are calculated
1828 # from the last access time. Accounts can include a maximal lifetime
1829 # for session tickets (MaxLifetime).
1831 # A Login page should create a LOGIN ticket file locally and send a
1832 # server specific salt, a Random salt, and a LOGIN ticket
1833 # identifier. The server side compares the username and hashed password,
1834 # actually hashed(Random salt+hashed(serversalt+password)) from the client with
1835 # the values it calculates from the stored Random salt from the LOGIN
1836 # ticket and the hashed(serversalt+password) from the PASSWORD ticket. If
1837 # successful, a new SESSION ticket is generated as a hash sum of the LOGIN
1838 # ticket and the stored password. This SESSION ticket should also be
1839 # generated by the client and stored as sessionStorage and cookie values
1840 # as needed. The Username, IP address and Path are available as
1841 # $LoginUsername, $LoginIPaddress, and $LoginPath, respectively.
1843 # The CHALLENGE protocol stores the same value as the SESSION tickets.
1844 # However, this value is not exchanged, but kept secret in the JavaScript
1845 # sessionStorage object. Instead, every page returned from the
1846 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1847 # has to be hashed with the stored value to return the current ticket
1848 # id string.
1850 # In the current example implementation, all random values are created as
1851 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
1852 # /dev/urandom.
1854 # Security considerations with Session tickets
1856 # For strong security, please use end-to-end encryption. This can be
1857 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
1858 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
1859 # is intended to be used as a simple authentication mechanism WITHOUT
1860 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
1861 # simple means to protect the authentication process from eavesdropping.
1862 # For this it uses a secure hash function, SHA256. For all practial purposes,
1863 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
1864 # only as secure as your browser. Which, in general, is not very secure.
1866 # Humans tend to reuse passwords. A compromise of a site running
1867 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
1868 # other sites. Therefore, plain text passwords are never stored, used, or
1869 # exchanged. Instead, a server site salt value is "encrypted" with
1870 # the plain password and user name. Actually, all are concatenated and hashed
1871 # with a one-way secure hash function (SHA256) into a single string.
1872 # Whenever the word "password" is used, this hash sum is meant. Note that
1873 # the salts are generated from /dev/urandom. You should check whether the
1874 # implementation of /dev/urandom on your platform is secure before
1875 # relying on it. This might be a problem when running CGIscriptor under
1876 # Cygwin on MS Windows.
1877 # Note: no attempt is made to slow down the password hash, so bad
1878 # passwords can be cracked by brute force
1880 # For the authentication and a change of password, the (old) password
1881 # is used to "encrypt" a random one-time token or the new password,
1882 # respectively. For authentication, decryption is not needed, so a secure
1883 # hash function (SHA256) is used to create a one-way hash sum "encryption".
1884 # A new password must be decrypted. New passwords are encryped by XORing
1885 # them with the old password.
1887 # Strong Passwords: It is so easy
1888 # If you only could see what you are typing
1890 # Your password might be vulnerable to brute force guessing
1891 # (https://en.wikipedia.org/wiki/Brute_force_attack).
1892 # Protections against such attacks are costly in terms of code
1893 # complexity, bugs, and execution time. However, there is a very
1894 # simple and secure counter measure. See the XKCD comic
1895 # (http://xkcd.com/936/). The phrase, "There is no password like more
1896 # password" would be both much easier to remember, and still stronger
1897 # than "h4]D%@m:49", at least before this phrase was pasted as an
1898 # example on the Internet.
1900 # For the procedures used at this site, a basic computer setup can
1901 # check in the order of a billion passwords per second. You need a
1902 # password (or phrase) strength in the order of 56 bits to be a
1903 # little secure (one year on a single computer). Please be so kind
1904 # and add the name of your favorite flower, dish, fictional
1905 # character, or small town to your password. Say, Oleander, Curry,
1906 # Sherlock, or Bath (each adds ~12 bits) or even the phrase "Sherlock
1907 # investigates oleander curry in Bath" (adds > 56 bits, note that
1908 # oleander is poisonous, so do not try this curry at home). That
1909 # would be more effective than adding a thousand rounds of encryption.
1910 # Typing long passwords without seeing what you are typing is
1911 # problematic. So a button should be included to make password
1912 # visible.
1915 # USER EXTENSIONS
1917 # A CGIscriptor package is attached to the bottom of this file. With
1918 # this package you can personalize your version of CGIscriptor by
1919 # including often used perl routines. These subroutines can be
1920 # accessed by prefixing their names with CGIscriptor::, e.g.,
1921 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
1922 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
1923 # </SCRIPT>
1924 # It already contains some useful subroutines for Document Management.
1925 # As it is a separate package, it has its own namespace, isolated from
1926 # both the evaluator and the main program. To access variables from
1927 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
1929 # Currently, the following functions are implemented
1930 # (precede them with CGIscriptor::, see below for more information)
1931 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
1932 # Is used by text/osshell Shell scripts. Protects all CGI
1933 # (client-supplied) values with single quotes before executing the
1934 # commands (one of the few functions that also works WITHOUT CGIscriptor::
1935 # in front)
1936 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
1937 # Is used by the META tag to define and initialize CGI and ENV
1938 # name/value pairs. Tries to obtain an initializing value from (in order):
1939 # $ENV{$name}
1940 # The Query string
1941 # The default value given (if any)
1942 # (one of the few functions that also works WITHOUT CGIscriptor::
1943 # in front)
1944 # - CGIsafeFileName (FileName) -> FileName or ""
1945 # Check a string against the Allowed File Characters (and ../ /..).
1946 # Returns an empty string for unsafe filenames.
1947 # - CGIsafeEmailAddress (Email) -> Email or ""
1948 # Check a string against correct email address pattern.
1949 # Returns an empty string for unsafe addresses.
1950 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
1951 # Open a named PIPE for SAFEqx to receive ALL shell scripts
1952 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
1953 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
1954 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
1955 # Extract the value of a CGI variable from the global or a private
1956 # URL-encoded query (multipart POST raw, NOT decoded)
1957 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
1958 # -> List of decoded values
1959 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
1960 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
1961 # Extract the header of a multipart CGI variable from the global or a private
1962 # URL-encoded query ("" when not a multipart variable or absent)
1963 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
1964 # Decode the complete global URL-encoded query or a private
1965 # URL-encoded query
1966 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
1967 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
1968 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
1969 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
1970 # all documents, e.g., ListDocs("/*", "dl");.
1971 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
1972 # local links starting from a given document, e.g.,
1973 # HTMLdocTree("/Welcome.html", "dl");
1976 # THE RESULTS STACK: @CGISCRIPTORRESULTS
1978 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
1979 # META tag, all subsequent SCRIPT and META results are pushed
1980 # on the @CGIscriptorResults stack. This list is just another
1981 # Perl variable and can be used and manipulated like any other list.
1982 # $CGIscriptorResults[-1] is always the last result.
1983 # This is only of limited use, e.g., to use the results of an OS shell
1984 # script inside a Perl script. Will NOT contain the results of Pipes
1985 # or code from MIME-profiling.
1988 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
1990 # $CGI_HOME - The DocumentRoot directory
1991 # $CGI_Decoded_QS - The complete decoded Query String
1992 # $CGI_Content_Length - The ACTUAL length of the Query String
1993 # $CGI_Date - Current date and time
1994 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
1995 # $CGI_Time - Current Time
1996 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
1997 # GMT Date/Time:
1998 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
1999 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
2002 # USEFULL CGI ENVIRONMENT VARIABLES
2004 # Variables accessible (in APACHE) as $ENV{<name>}
2005 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
2007 # QUERY_STRING - The query part of URL, that is, everything that follows the
2008 # question mark.
2009 # PATH_INFO - Extra path information given after the script name
2010 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
2011 # (This doesn't always make sense.)
2012 # REMOTE_USER - If the server supports user authentication, and the script is
2013 # protected, this is the username they have authenticated as.
2014 # REMOTE_HOST - The hostname making the request. If the server does not have
2015 # this information, it should set REMOTE_ADDR and leave this unset
2016 # REMOTE_ADDR - The IP address of the remote host making the request.
2017 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
2018 # variable will be set to the remote user name retrieved from
2019 # the server. Usage of this variable should be limited to logging
2020 # only.
2021 # AUTH_TYPE - If the server supports user authentication, and the script
2022 # is protected, this is the protocol-specific authentication
2023 # method used to validate the user.
2024 # CONTENT_TYPE - For queries which have attached information, such as HTTP
2025 # POST and PUT, this is the content type of the data.
2026 # CONTENT_LENGTH - The length of the said content as given by the client.
2027 # SERVER_SOFTWARE - The name and version of the information server software
2028 # answering the request (and running the gateway).
2029 # Format: name/version
2030 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
2031 # would appear in self-referencing URLs
2032 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
2033 # server complies. Format: CGI/revision
2034 # SERVER_PROTOCOL - The name and revision of the information protocol this
2035 # request came in with. Format: protocol/revision
2036 # SERVER_PORT - The port number to which the request was sent.
2037 # REQUEST_METHOD - The method with which the request was made. For HTTP,
2038 # this is "GET", "HEAD", "POST", etc.
2039 # SCRIPT_NAME - A virtual path to the script being executed, used for
2040 # self-referencing URLs.
2041 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
2042 # HTTP headers. Other protocols may need to get this
2043 # information from elsewhere. Each item in this list should
2044 # be separated by commas as per the HTTP spec.
2045 # Format: type/subtype, type/subtype
2046 # HTTP_USER_AGENT - The browser the client is using to send the request.
2047 # General format: software/version library/version.
2050 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
2052 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
2053 # a line like the following to your srm.conf file (Apache example):
2055 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
2057 # URL's that refer to http://www.your.address/SHTML/... will now be handled
2058 # by CGIscriptor.pl, which can use a private directory tree (default is the
2059 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
2061 # If your hosting ISP won't let you add ScriptAlias lines you can use
2062 # the following "rewrite"-based "scriptalias" in .htaccess
2063 # (from Gerd Franke)
2065 # RewriteEngine On
2066 # RewriteBase /
2067 # RewriteCond %{REQUEST_FILENAME} .html$
2068 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
2069 # RewriteCond %{REQUEST_FILENAME} -f
2070 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
2072 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
2073 # in the url and where the file "path/filename.html" exists is redirected
2074 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
2075 # The user configuration should get the same path-level as the
2076 # .htaccess-file:
2078 # # Just enter your own directory path here
2079 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
2080 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
2082 # If this .htaccess goes in a specific directory, the path to this
2083 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2085 # The CGIscriptor file contains all documentation as comments. These
2086 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2087 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2088 # CGIscriptor.pl, lacking documentation, most comments, access control,
2089 # example functions etc. (but still with the copyright notice and some
2090 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2091 # command line with the '-slim' command line argument, e.g.,
2093 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2095 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2096 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2097 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2098 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2099 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2100 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2101 # $ENV{QUERY_STRING}).
2103 # Using the command line execution option, CGIscriptor.pl can be used as a
2104 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2105 # For example:
2107 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2109 # This command line will produce a STATIC file with the DYNAMIC content of
2110 # MyDocument.html "interpolated".
2112 # This option would be very dangerous when available over the internet.
2113 # If someone could sneak a 'http://www.your.domain/-' URL past your
2114 # server, CGIscriptor could EXECUTE any POSTED contend.
2115 # Therefore, for security reasons, STDIN will NOT be read
2116 # if ANY of the HTTP server environment variables is set (e.g.,
2117 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2118 # HTTP_USER_AGENT, REMOTE_ADDR).
2119 # This block on processing STDIN on HTTP requests can be lifted by setting
2120 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2121 # In the security configuration. Butbe carefull when doing this.
2122 # It can be very dangerous.
2124 # Running demo's and more information can be found at
2125 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2127 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2128 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2129 # demonstrates its use.
2132 # PROCESSING NON-FILESYSTEM DATA
2134 # Normally, HTTP (WWW) requests map onto file that can be accessed
2135 # using the perl open() function. That is, the web server runs on top of
2136 # some directory structure. However, we can envission (and put to good
2137 # use) other systems that do not use a normal file system. The whole CGI
2138 # was developed to make dynamic document generation possible.
2140 # A special case is where we want to have it both: A normal web server
2141 # with normal "file data", but not a normal files system. For instance,
2142 # we want or normal Web Site to run directly from a RAM hash table or
2143 # other database, instead of from disk. But we do NOT want to code the
2144 # whole site structure in CGI.
2146 # CGIscriptor can do this. If the web server fills an environment variable
2147 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2148 # of this variable is processed instead of opening a file. If this environment
2149 # variable has the value '-', the content of another environment variable,
2150 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2151 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2152 # and the result is processed as if it was the content of the requested
2153 # file.
2154 # (actually, the names of the environment variables are user configurable,
2155 # they are stored in the local variables $CGI_FILE_CONTENT and
2156 # $CGI_DATA_ACCESS_CODE)
2158 # When using this mechanism, the SRC attribute mechanism will only partially work.
2159 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2160 # will work, the automagical execution of SRC files won't. (In this case,
2161 # the SRC attribute won't work either for other scripting languages)
2164 # NON-UNIX PLATFORMS
2166 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2167 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2168 # CGIscriptor did run under MacPerl (with command line options). But only
2169 # as an independend script, not as part of a HTTP server. I have used it
2170 # under Apache in Windows XP.
2172 ENDOFHELPTEXT
2173 exit;
2175 ###############################################################################
2177 # SECURITY CONFIGURATION
2179 # Special configurations related to SECURITY
2180 # (i.e., optional, see also environment variables below)
2182 # LOGGING
2183 # Log Clients and the requested paths (Redundant when loging Queries)
2185 $ClientLog = "./Client.log"; # (uncomment for use)
2187 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2188 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2190 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2191 # to log queries. If you log Queries, the loging of Clients is redundant
2192 # (note that queries can be quite long, so this might not be a good idea)
2194 #$QueryLog = "./Query.log"; # (uncomment for use)
2196 # ACCESS CONTROL
2197 # the Access files should contain Hostnames or IP addresses,
2198 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2199 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2200 # Matching is done "domain first". For example ".edu" matches all
2201 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2202 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2203 # (both matchings are done case-insensitive).
2204 # The name special symbol "-" matches ALL clients who do not supply a
2205 # REMOTE_HOST name, "*" matches all clients.
2206 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2207 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2208 # lines are evaluated in the program's own name-space. So DO NOT assign to
2209 # variables.
2211 # Accept the following users (remove comment # and adapt filename)
2212 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2214 # Reject requests from the following users (remove comment # and
2215 # adapt filename, this is only of limited use)
2216 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2218 # Empty lines or comment lines starting with '#' are ignored in both
2219 # $CGI_Accept and $CGI_Reject.
2221 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2222 # Comment this out if you realy want to use STDIN in an on-line web server
2223 $BLOCK_STDIN_HTTP_REQUEST = 1;
2226 # End of security configuration
2228 ##################################################<<<<<<<<<<End Remove
2230 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2232 # The CGI parse commands. These commands extract the values of the
2233 # CGI variables from the URL encoded Query String.
2234 # If you want to use your own CGI decoders, you can call them here
2235 # instead, using your own PATH and commenting/uncommenting the
2236 # appropriate lines
2238 # CGI parse command for individual values
2239 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2240 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2242 my $Name = shift;
2243 my $List = shift || 0;
2244 # Use one of the following by uncommenting
2245 if(!$List) # Simple value
2247 return CGIscriptor::CGIparseValue($Name) ;
2249 elsif($List < 0) # Hash tables
2251 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2253 else # Lists
2255 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2258 # return `/PATH/cgiparse -value $Name`; # Shell commands
2259 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2261 # Complete queries
2262 sub YOUR_CGIQUERYDECODE
2264 # Use one of the following by uncommenting
2265 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2266 # return `/PATH/cgiparse -form`; # Shell commands
2267 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2270 # End of configuration
2272 #######################################################################
2274 # Translating input files.
2275 # Allows general and global conversions of files using Regular Expressions
2276 # Translations are applied in the order of definition.
2278 # Define:
2279 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2281 # push(@TranslationTable, ['pattern', 'replacement']);
2282 # e.g. (for Ruby Rails):
2283 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2284 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2286 # Runs:
2287 # my $currentRegExp;
2288 # foreach $currentRegExp (keys(%TranslationTable))
2290 # my $currentRegExp;
2291 # foreach $currentRegExp (@TranslationTable)
2293 # my ($pattern, $replacement) = @$currentRegExp;
2294 # $$text =~ s!$pattern!$replacement!msg;
2295 # };
2296 # };
2298 # Configuration section
2300 #######################################################################
2302 # The file paths on which to apply the translation
2303 my $TranslationPaths = ''; # NO files
2304 #$TranslationPaths = '.'; # ANY file
2305 # $TranslationPaths = '\.html'; # HTML files
2307 my @TranslationTable = ();
2308 # Some legacy code
2309 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2310 # Ruby Rails?
2311 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2312 push(@TranslationTable, ['%>', '</SCRIPT>']);
2314 sub performTranslation # (\$text)
2316 my $text = shift || return;
2317 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2319 my $currentRegExp;
2320 foreach $currentRegExp (@TranslationTable)
2322 my ($pattern, $replacement) = @$currentRegExp;
2323 $$text =~ s!$pattern!$replacement!msg;
2328 #######################################################################
2330 # Seamless access to other (Scripting) Languages
2331 # TYPE='text/ss<interpreter>'
2333 # Configuration section
2335 #######################################################################
2337 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2338 # Yes, it realy is this simple! (unbelievable, isn't it)
2339 # NOTE: Some interpreters require some filtering to obtain "clean" output
2341 %ScriptingLanguages = (
2342 "text/testperl" => 'perl', # Perl for testing
2343 "text/sspython" => 'python', # Python
2344 "text/ssruby" => 'ruby', # Ruby
2345 "text/sstcl" => 'tcl', # TCL
2346 "text/ssawk" => 'awk -f-', # Awk
2347 "text/sslisp" => # lisp (rep, GNU)
2348 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2349 "text/xlispstat" => # xlispstat
2350 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2351 "text/ssprolog" => # Prolog (GNU)
2352 "gprolog | tail +4 | sed 's/^| ?- //'",
2353 "text/ssm4" => 'm4', # M4 macro's
2354 "text/sh" => 'sh', # Born shell
2355 "text/bash" => 'bash', # Born again shell
2356 "text/csh" => 'csh', # C shell
2357 "text/ksh" => 'ksh', # Korn shell
2358 "text/sspraat" => # Praat (sound/speech analysis)
2359 "praat - | sed 's/Praat > //g'",
2360 "text/ssr" => # R
2361 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2362 "text/ssrebol" => # REBOL
2363 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2364 "text/postgresql" => 'psql 2>/dev/null',
2366 # Not real scripting, but the use of other applications
2367 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2368 "text/ssdisplay" => 'cat', # Display, (interpolation)
2369 "text/sslogfile" => # Log to file, (interpolation)
2370 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2372 "" => ""
2375 # To be able to access the CGI variables in your script, they
2376 # should be passed to the scripting language in a readable form
2377 # Here you can enter how they should be printed (the first %s
2378 # is replaced by the NAME of the CGI variable as it apears in the
2379 # META tag, the second by its VALUE).
2380 # For Perl this would be:
2381 # "text/testperl" => '$%s = "%s";',
2382 # which would be executed as
2383 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2385 # If the hash table value doesn't exist, nothing is done
2386 # (you have to parse the Environment variables yourself).
2387 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2388 # Perl string interpolation of variables (i.e., $var, @array,
2389 # %hash) is performed. This means that $@%\ must be protected
2390 # with a \.
2392 %ScriptingCGIvariables = (
2393 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2394 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2395 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2396 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2397 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2398 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2399 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2400 "text/ssprolog" => '', # Gnu prolog (interpolated)
2401 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2402 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2403 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2404 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2405 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2407 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2408 "text/sspraat" => '', # Praat (interpolation)
2409 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2410 "text/postgresql" => '', # PostgreSQL (interpolation)
2412 # Not real scripting, but the use of other applications
2413 "text/ssmailto" => '', # MAILTO, (interpolation)
2414 "text/ssdisplay" => '', # Display, (interpolation)
2415 "text/sslogfile" => '', # Log to file, (interpolation)
2417 "" => ""
2420 # If you want something added in front or at the back of each script
2421 # block as send to the interpreter add it here.
2422 # mime => "string", e.g., "text/sspython" => "python commands"
2423 %ScriptingPrefix = (
2424 "text/testperl" => "\# Prefix Code;", # Perl script testing
2425 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2427 "" => ""
2429 # If you want something added at the end of each script block
2430 %ScriptingPostfix = (
2431 "text/testperl" => "\# Postfix Code;", # Perl script testing
2432 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2434 "" => ""
2436 # If you need initialization code, directly after opening
2437 %ScriptingInitialization = (
2438 "text/testperl" => "\# Initialization Code;", # Perl script testing
2439 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2440 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2441 "text/xlispstat" => '(prog1 nil ', # xlispstat
2442 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2444 "" => ""
2446 # If you need cleanup code before closing
2447 %ScriptingCleanup = (
2448 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2449 "text/sspraat" => 'Quit',
2450 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2451 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2452 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2453 "text/postgresql" => '\q', # quit psql
2454 "text/ssdisplay" => "", # close cat
2456 "" => ""
2459 # End of configuration for foreign scripting languages
2461 ###############################################################################
2463 # Initialization Code
2466 sub Initialize_Request
2468 ###############################################################################
2470 # ENVIRONMENT VARIABLES
2472 # Use environment variables to configure CGIscriptor on a temporary basis.
2473 # If you define any of the configurable variables as environment variables,
2474 # these are used instead of the "hard coded" values above.
2476 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2477 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2480 # Substitution strings, these are used internally to handle the
2481 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2482 $HOME_SUB = $SS_PUB;
2483 $SCRIPT_SUB = $SS_SCRIPT;
2486 # Make sure all script are reliably loaded
2487 push(@INC, $SS_SCRIPT);
2490 # Add the directory separator to the "home" directories.
2491 # (This is required for ~/ and ./ substitution)
2492 $HOME_SUB .= '/' if $HOME_SUB;
2493 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2495 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2496 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2497 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2498 $default_values{'CGI_HOME'} = $CGI_HOME;
2499 $ENV{'HOME'} = $CGI_HOME;
2500 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2501 # to the scripts)
2502 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2503 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2505 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2506 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2507 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2508 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2509 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2510 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2512 # Parse file names
2513 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2514 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2515 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2516 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2518 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2519 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2520 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2521 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2523 @CGIscriptorResults = (); # A stack of results
2525 # end of Environment variables
2527 #############################################################################
2529 # Define and Store "standard" values
2531 # BEFORE doing ANYTHING check the size of Query String
2532 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2534 # The Translated Query String and the Actual length of the (decoded)
2535 # Query String
2536 if($ENV{'QUERY_STRING'})
2538 # If this can contain '`"-quotes, be carefull to use it QUOTED
2539 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2540 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2543 # Get the current Date and time and store them as default variables
2545 # Get Local Time
2546 $LocalTime = localtime;
2548 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2549 # CGI_Hour CGI_Minutes CGI_Seconds
2551 $default_values{CGI_Date} = $LocalTime;
2552 ($default_values{CGI_WeekDay},
2553 $default_values{CGI_Month},
2554 $default_values{CGI_Day},
2555 $default_values{CGI_Time},
2556 $default_values{CGI_Year}) = split(' ', $LocalTime);
2557 ($default_values{CGI_Hour},
2558 $default_values{CGI_Minutes},
2559 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2561 # GMT:
2562 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2563 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2565 ($default_values{CGI_GMTSeconds},
2566 $default_values{CGI_GMTMinutes},
2567 $default_values{CGI_GMTHour},
2568 $default_values{CGI_GMTDay},
2569 $default_values{CGI_GMTMonth},
2570 $default_values{CGI_GMTYear},
2571 $default_values{CGI_GMTWeekDay},
2572 $default_values{CGI_GMTYearDay},
2573 $default_values{CGI_GMTisdst}) = gmtime;
2577 # End of Initialize Request
2579 ###################################################################
2581 # SECURITY: ACCESS CONTROL
2583 # Check the credentials of each client (use pattern matching, domain first).
2584 # This subroutine will kill-off (die) the current process whenever access
2585 # is denied.
2587 sub Access_Control
2589 # >>>>>>>>>>Start Remove
2591 # ACCEPTED CLIENTS
2593 # Only accept clients which are authorized, reject all unnamed clients
2594 # if REMOTE_HOST is given.
2595 # If file patterns are given, check whether the user is authorized for
2596 # THIS file.
2597 if($CGI_Accept)
2599 # Use local variables, REMOTE_HOST becomes '-' if undefined
2600 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2601 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2602 my $PATH_INFO = $ENV{'PATH_INFO'};
2604 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2605 $NoAccess = 1;
2606 while(<CGI_Accept>)
2608 next unless /\S/; # Skip empty lines
2609 next if /^\s*\#/; # Skip comments
2611 # Full expressions
2612 if(/^\s*-e\s/is)
2614 my $Accept = $'; # Get the expression
2615 $NoAccess &&= eval($Accept); # evaluate the expresion
2617 else
2619 my ($Accept, @FilePatternList) = split;
2620 if($Accept eq '*' # Always match
2621 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2622 || (
2623 $Accept =~ /^[0-9\.]+$/
2624 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2628 if($FilePatternList[0])
2630 foreach $Pattern (@FilePatternList)
2632 # Check whether this patterns is accepted
2633 $NoAccess &&= ($PATH_INFO !~ m@\Q$Pattern\E@is);
2636 else
2638 $NoAccess = 0; # No file patterns -> Accepted
2642 # Blocked
2643 last unless $NoAccess;
2645 close(CGI_Accept);
2646 if($NoAccess){ dieHandler(4, "No Access: $PATH_INFO\n");};
2650 # REJECTED CLIENTS
2652 # Reject named clients, accept all unnamed clients
2653 if($CGI_Reject)
2655 # Use local variables, REMOTE_HOST becomes '-' if undefined
2656 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
2657 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2658 my $PATH_INFO = $ENV{'PATH_INFO'};
2660 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
2661 $NoAccess = 0;
2662 while(<CGI_Reject>)
2664 next unless /\S/; # Skip empty lines
2665 next if /^\s*\#/; # Skip comments
2667 # Full expressions
2668 if(/^-e\s/is)
2670 my $Reject = $'; # Get the expression
2671 $NoAccess ||= eval($Reject); # evaluate the expresion
2673 else
2675 my ($Reject, @FilePatternList) = split;
2676 if($Reject eq '*' # Always match
2677 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
2678 ||($Reject =~ /^[0-9\.]+$/
2679 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
2683 if($FilePatternList[0])
2685 foreach $Pattern (@FilePatternList)
2687 $NoAccess ||= ($PATH_INFO =~ m@\Q$Pattern\E@is);
2690 else
2692 $NoAccess = 1; # No file patterns -> Rejected
2696 last if $NoAccess;
2698 close(CGI_Reject);
2699 if($NoAccess){ dieHandler(6, "Request rejected: $PATH_INFO\n");};
2702 ##########################################################<<<<<<<<<<End Remove
2705 # Get the filename
2707 # Does the filename contain any illegal characters (e.g., |, >, or <)
2708 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
2709 # Does the pathname contain an illegal (blocked) "directory"
2710 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
2711 # Does the pathname contain a direct referencer to BinaryMapFile
2712 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
2714 # SECURITY: Is PATH_INFO allowed?
2715 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
2716 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
2718 # Unsupported file types can be processed by a special raw-file
2719 if($BinaryMapFile)
2721 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
2722 $ENV{'PATH_INFO'} = $BinaryMapFile;
2724 else
2726 dieHandler(10, "Illegal file\n");
2732 # End of Security Access Control
2735 ############################################################################
2737 # Get the POST part of the query and add it to the QUERY_STRING.
2740 sub Get_POST_part_of_query
2743 # If POST, Read data from stdin to QUERY_STRING
2744 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
2746 # SECURITY: Check size of Query String
2747 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
2748 my $QueryRead = 0;
2749 my $SystemRead = $ENV{'CONTENT_LENGTH'};
2750 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
2751 while($SystemRead > 0)
2753 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
2754 $ENV{'QUERY_STRING'} .= $Post;
2755 $SystemRead -= $QueryRead;
2757 # Update decoded Query String
2758 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2759 $default_values{CGI_Content_Length} =
2760 length($default_values{CGI_Decoded_QS});
2764 # End of getting POST part of query
2767 ############################################################################
2769 # Start (HTML) output and logging
2770 # (if there are irregularities, it can kill the current process)
2773 sub Initialize_output
2775 # Construct the REAL file path (except for STDIN on the command line)
2776 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
2777 $file_path =~ s/\?.*$//; # Remove query
2778 # This is only necessary if your server does not catch ../ directives
2779 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
2781 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
2782 if($file_path eq '-')
2784 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
2785 && ($ENV{'SERVER_SOFTWARE'}
2786 || $ENV{'SERVER_NAME'}
2787 || $ENV{'GATEWAY_INTERFACE'}
2788 || $ENV{'SERVER_PROTOCOL'}
2789 || $ENV{'SERVER_PORT'}
2790 || $ENV{'REMOTE_ADDR'}
2791 || $ENV{'HTTP_USER_AGENT'});
2796 if($ClientLog)
2798 open(ClientLog, ">>$ClientLog");
2799 print ClientLog "$LocalTime | ",
2800 ($ENV{REMOTE_USER} || "-"), " ",
2801 ($ENV{REMOTE_IDENT} || "-"), " ",
2802 ($ENV{REMOTE_HOST} || "-"), " ",
2803 $ENV{REMOTE_ADDR}, " ",
2804 $ENV{PATH_INFO}, " ",
2805 $ENV{'CGI_BINARY_FILE'}, " ",
2806 ($default_values{CGI_Content_Length} || "-"),
2807 "\n";
2808 close(ClientLog);
2810 if($QueryLog)
2812 open(QueryLog, ">>$QueryLog");
2813 print QueryLog "$LocalTime\n",
2814 ($ENV{REMOTE_USER} || "-"), " ",
2815 ($ENV{REMOTE_IDENT} || "-"), " ",
2816 ($ENV{REMOTE_HOST} || "-"), " ",
2817 $ENV{REMOTE_ADDR}, ": ",
2818 $ENV{PATH_INFO}, " ",
2819 $ENV{'CGI_BINARY_FILE'}, "\n";
2821 # Write Query to Log file
2822 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
2823 close(QueryLog);
2826 # Return the file path
2827 return $file_path;
2830 # End of Initialize output
2833 ############################################################################
2835 # Handle login access
2837 # Access is based on a valid session ticket.
2838 # Session tickets should be dependend on user name
2839 # and IP address. The patterns of URLs for which a
2840 # session ticket is needed and the login URL are stored in
2841 # %TicketRequiredPatterns as:
2842 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
2845 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
2847 # No patterns, no login
2848 goto Return unless %TicketRequiredPatterns;
2850 # Get and initialize values (watch out for stuff processed by BinaryMap files)
2851 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
2852 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
2853 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2854 goto Return if $REMOTE_ADDR =~ /[^0-9\.]/;
2855 # Extract TICKETs, starting with returned cookies
2856 CGIexecute::defineCGIvariable('LOGINTICKET', "");
2857 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
2858 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
2859 if($ENV{'COOKIE_JAR'})
2861 my $CurrentCookieJar = $ENV{'COOKIE_JAR'};
2862 $CurrentCookieJar =~ s/\w+\=\-\s*(\;\s*|$)//isg;
2863 if($CurrentCookieJar =~ /\s*CGIscriptorLOGIN\=\s*([^\;]+)/)
2865 ${"CGIexecute::LOGINTICKET"} = $1;
2867 if($CurrentCookieJar =~ /\s*CGIscriptorCHALLENGE\=\s*([^\;]+)/ && $1 ne '-')
2869 ${"CGIexecute::CHALLENGETICKET"} = $1;
2871 if($CurrentCookieJar =~ /\s*CGIscriptorSESSION\=\s*([^\;]+)/ && $1 ne '-')
2873 ${"CGIexecute::SESSIONTICKET"} = $1;
2876 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
2877 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
2878 goto Return if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
2879 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
2880 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
2881 my $CHALLENGETICKET = ${"CGIexecute::CHALLENGETICKET"};
2882 goto Return if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
2883 # Look for a LOGOUT message
2884 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
2885 # Username and password
2886 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
2887 my $username = lc(${"CGIexecute::CGIUSERNAME"});
2888 goto Return if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
2889 my $userfile = lc($username);
2890 $userfile =~ s/[^\w]/_/isg;
2891 CGIexecute::defineCGIvariable('PASSWORD', "");
2892 my $password = ${"CGIexecute::PASSWORD"};
2893 CGIexecute::defineCGIvariable('NEWUSERNAME', "");
2894 my $newuser = lc(${"CGIexecute::NEWUSERNAME"});
2895 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
2896 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
2898 foreach my $pattern (keys(%TicketRequiredPatterns))
2900 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
2901 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
2903 # Fall through a sieve of requirements
2904 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
2905 # If a LOGOUT is present, remove everything
2906 if($LOGOUT && !$LOGINTICKET)
2908 unlink "$SessionPath/$LOGINTICKET" if $LOGINTICKET && (-s "$SessionPath/$LOGINTICKET");
2909 $LOGINTICKET = "";
2910 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
2911 $SESSIONTICKET = "";
2912 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
2913 $CHALLENGETICKET = "";
2914 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$REMOTE_ADDR");
2915 $CHALLENGETICKET = "";
2916 goto Login;
2918 # Is there a change password request?
2919 if($newuser && $LOGINTICKET && $username)
2921 goto Login unless (-s "$SessionPath/$LOGINTICKET");
2922 goto Login unless (-s "$PasswordsPath/$userfile");
2923 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
2924 goto Login unless $ticket_valid;
2925 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
2926 goto Login unless $ticket_valid;
2928 my ($sessiontype, $currentticket) = ("", "");
2929 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
2930 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
2931 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
2933 if($sessiontype)
2935 goto Login unless (-s "$SessionPath/$currentticket");
2936 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
2937 goto Login unless $ticket_valid;
2939 # Authorize
2940 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
2941 goto Login unless $TMPTICKET;
2943 # Create a new user account
2944 CGIexecute::defineCGIvariable('NEWSESSION', "");
2945 my $newsession = ${"CGIexecute::NEWSESSION"};
2946 my $newaccount = create_newuser("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket",
2947 "$PasswordsPath/$userfile", $password, $newuser, $newpassword, $newsession);
2948 CGIexecute::defineCGIvariable('NEWACCOUNTTEXT', $newaccount);
2949 ${CGIexecute::NEWACCOUNTTEXT} = $newaccount;
2950 # NEWACCOUNTTEXT is NOT to be set by the query
2951 CGIexecute::ProtectCGIvariable('NEWACCOUNTTEXT');
2954 # Ready
2955 goto Return;
2957 # Is there a change password request?
2958 elsif($newpassword && $LOGINTICKET && $username)
2960 goto Login unless (-s "$SessionPath/$LOGINTICKET");
2961 goto Login unless (-s "$PasswordsPath/$userfile");
2962 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
2963 goto Login unless $ticket_valid;
2964 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
2965 goto Login unless $ticket_valid;
2967 my ($sessiontype, $currentticket) = ("", "");
2968 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
2969 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
2970 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
2972 if($sessiontype)
2974 goto Login unless (-s "$SessionPath/$currentticket");
2975 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
2976 goto Login unless $ticket_valid;
2978 # Authorize
2979 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
2980 # After a change of password, you have to login again for a CHALLENGE
2981 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
2982 # Ready
2983 goto Return;
2985 # Is there a login ticket of this name?
2986 elsif($LOGINTICKET)
2988 my $tickets_removed = remove_expired_tickets($SessionPath);
2989 goto Login unless (-s "$SessionPath/$LOGINTICKET");
2990 goto Login unless (-s "$PasswordsPath/$userfile");
2991 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
2992 goto Login unless $ticket_valid;
2993 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
2994 goto Login unless $ticket_valid;
2996 # Remove any lingering tickets
2997 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
2998 $SESSIONTICKET = "";
2999 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
3000 $CHALLENGETICKET = "";
3003 # Authorize
3004 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
3005 if($TMPTICKET)
3007 my $authorization = read_ticket("$PasswordsPath/$userfile");
3008 goto Login unless $authorization;
3009 # Session type is read from the userfile
3010 if($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "CHALLENGE")
3012 # Create New Random CHALLENGETICKET
3013 $CHALLENGETICKET = $TMPTICKET;
3014 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3016 elsif($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "IPADDRESS")
3018 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3020 else
3022 $SESSIONTICKET = $TMPTICKET;
3023 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
3024 $SETCOOKIELIST{"CGIscriptorSESSION"} = "-";
3027 # Login ticket file has been used, remove it
3028 unlink($loginfile);
3030 # Is there a session ticket of this name?
3031 # CHALLENGE
3032 if($CHALLENGETICKET)
3034 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
3035 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
3036 goto Login unless $ticket_valid;
3038 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
3039 goto Login unless $oldchallenge;
3040 my $userfile = lc($oldchallenge->{"Username"}->[0]);
3041 $userfile =~ s/[^\w]/_/isg;
3042 goto Login unless (-s "$PasswordsPath/$userfile");
3044 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
3045 goto Login unless $ticket_valid;
3047 my $NEWCHALLENGETICKET = "";
3048 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
3049 # Sessionticket is available to scripts, do NOT set the cookie
3050 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3051 goto Return;
3053 # IPADDRESS
3054 elsif(-s "$SessionPath/$REMOTE_ADDR")
3056 my $ticket_valid = check_ticket_validity("IPADDRESS", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
3057 goto Login unless $ticket_valid;
3058 goto Return;
3060 # SESSION
3061 elsif($SESSIONTICKET)
3063 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
3064 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
3065 goto Login unless $ticket_valid;
3066 # Sessionticket is available to scripts
3067 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3068 goto Return;
3071 goto Login;
3072 goto Return;
3075 Return:
3076 # The Masterkey should NOT be accessible by the parsed files
3077 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3078 return 0;
3080 Login:
3081 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
3082 # Note, cookies are set only ONCE
3083 $SETCOOKIELIST{"CGIscriptorLOGIN"} = "-";
3084 # The Masterkey should NOT be accessible by the parsed files
3085 $ENV{'CGIMasterKey'} = "" if $ENV{'CGIMasterKey'};
3086 return "$YOUR_HTML_FILES/$Login";
3089 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath) => SESSIONTICKET First two arguments are file paths
3091 my $loginfile = shift || "";
3092 my $authorizationfile = shift || "";
3093 my $password = shift || "";
3094 my $SessionPath = shift || "";
3096 # Get Login session ticket
3097 my $loginticket = read_ticket($loginfile);
3098 return 0 unless $loginticket;
3099 # Get User credentials for authorization
3100 my $authorization = read_ticket($authorizationfile);
3101 return 0 unless $authorization;
3103 # Get Randomsalt
3104 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3105 return "" unless $Randomsalt;
3107 my $storedpassword = $authorization->{'Password'}->[0];
3108 return "" unless $storedpassword;
3109 my $Hashedpassword = hash_string("$Randomsalt$storedpassword");
3110 return "" unless $password eq $Hashedpassword;
3112 # Extract Session Ticket
3113 my $loginsession = $loginticket->{'Session'}->[0];
3114 my $sessionticket = hash_string("$loginsession$storedpassword");
3115 chomp($sessionticket);
3116 $sessionticket = "" if -x "$SessionPath/$sessionticket";
3118 return $sessionticket;
3121 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First three arguments are file paths
3123 my $loginfile = shift || "";
3124 my $sessionfile = shift || "";
3125 my $authorizationfile = shift || "";
3126 my $password = shift || "";
3127 my $newpassword = shift || "";
3128 # Get Login session ticket
3129 my $loginticket = read_ticket($loginfile);
3130 return "" unless $loginticket;
3131 # Login ticket file has been used, remove it
3132 unlink($loginfile);
3133 # Get Randomsalt
3134 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3135 return "" unless $Randomsalt;
3136 my $LoginID = $loginticket->{'Session'}->[0];
3137 return "" unless $LoginID;
3139 # Get session ticket
3140 my $sessionticket = read_ticket($sessionfile);
3141 return "" unless $sessionticket;
3143 # Get User credentials for authorization
3144 my $authorization = read_ticket($authorizationfile);
3145 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3147 my $storedpassword = $authorization->{'Password'}->[0];
3148 my $Hashedpassword = hash_string("$Randomsalt$storedpassword");
3149 chomp($Hashedpassword);
3150 return "" unless $password eq $Hashedpassword;
3151 my $secretkey = hash_string("$Randomsalt$LoginID$storedpassword");
3153 # Decrypt the $newpassword
3154 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3155 return "" unless $decryptedPassword;
3156 # Authorization succeeded, change password
3157 $authorization->{'Password'}->[0] = $decryptedPassword;
3158 # Apply masterkey
3159 EncryptTicketWithMasterKey($authorization, $authorization->{'Salt'}->[0]) || return "";
3162 open(USERFILE, "<$authorizationfile") || die "<$authorizationfile: $!\n";
3163 my @USERlines = <USERFILE>;
3164 close(USERFILE);
3165 # Change
3166 open(USERFILE, ">$authorizationfile") || die ">$authorizationfile: $!\n";
3167 foreach my $line (@USERlines)
3169 $line =~ s/^Password: ($storedpassword)$/Password: $decryptedPassword/ig;
3170 print USERFILE $line;
3172 close(USERFILE);
3174 return $newpassword;
3177 sub create_newuser # ($loginfile, $sessionfile, $authorizationfile, $password, $newuser, $newpassword, $newsession) First two arguments are file paths
3179 my $loginfile = shift || "";
3180 my $sessionfile = shift || "";
3181 my $authorizationfile = shift || "";
3182 my $password = shift || "";
3183 my $newuser = shift || "";
3184 my $newpassword = shift || "";
3185 my $newsession = shift || "";
3187 # Get Login session ticket
3188 my $loginticket = read_ticket($loginfile);
3189 return "" unless $loginticket;
3190 # Login ticket file has been used, remove it
3191 unlink($loginfile);
3192 # Get Randomsalt
3193 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3194 return "" unless $Randomsalt;
3195 my $LoginID = $loginticket->{'Session'}->[0];
3196 return "" unless $LoginID;
3198 # Get session ticket
3199 my $sessionticket = read_ticket($sessionfile);
3200 return "" unless $sessionticket;
3201 # Get User credentials for authorization
3202 my $authorization = read_ticket($authorizationfile);
3203 return "" unless $authorization && lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3204 my $sessionkey = $sessionticket->{'Key'}->[0];
3205 my $serversalt = $authorization->{'Salt'}->[0];
3206 return "" unless $serversalt;
3208 my $storedpassword = $authorization->{'Password'}->[0];
3209 my $Hashedpassword = hash_string("$Randomsalt$storedpassword");
3210 return "" unless $password eq $Hashedpassword;
3211 my $secretkey = hash_string("$Randomsalt$LoginID$storedpassword");
3213 # Decrypt the $newpassword
3214 my $decryptedPassword = XOR_hex_strings($secretkey, $newpassword);
3215 return "" unless $decryptedPassword;
3217 # Authorization succeeded, create new account
3218 my $newaccount = {};
3219 $newaccount->{'Type'} = ['PASSWORD'];
3220 $newaccount->{'Username'} = [$newuser];
3221 $newaccount->{'Password'} = [$decryptedPassword];
3222 $newaccount->{'Salt'} = [$serversalt];
3223 $newaccount->{'Session'} = ['SESSION'];
3224 if($newsession eq 'IPADDRESS'){$newaccount->{'Session'} = ['IPADDRESS'];};
3225 if($newsession eq 'CHALLENGE'){$newaccount->{'Session'} = ['CHALLENGE'];};
3226 my $timesec = time();
3227 $newaccount->{'Time'} = [$timesec];
3229 # Encrypt all passwords with the CGIMasterKey
3230 EncryptTicketWithMasterKey($newaccount, $serversalt) ||
3231 die "Encryption failed: EncryptTicketWithMasterKey ($newaccount, $serversalt)\n";
3233 # Re-encrypt the new password for transmission
3234 my $plainpasswordline = $newaccount->{'Password'}->[0];
3235 my $reencryptedpassword = XOR_hex_strings($secretkey, $newaccount->{'Password'}->[0]);
3236 my $encryptedpasswordline = "<span id='newaccount'>$reencryptedpassword</span>";
3238 # AllowedPaths
3239 my $NewAllowedPaths = "";
3240 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
3241 my $currentRoot = "";
3242 $currentRoot = $1 if $PATH_INFO =~ m!^([\w\-\. /]+)!isg;
3243 $currentRoot =~ s![^/]+$!!isg;
3244 if($currentRoot)
3246 $currentRoot .= '/' unless $currentRoot =~ m!/$!;
3247 my $newpath = ${currentRoot}.'[\w\-]+\.html?';
3248 $NewAllowedPaths .= 'AllowedPaths: ^'.${currentRoot}.'[\w\-]+\.html?'."\n";
3249 $newaccount->{'AllowedPaths'} = [$newpath];
3251 else
3253 # Tricky PATH_INFO, deny all
3254 $NewAllowedPaths .= "DeniedPaths: ^/\n";
3255 $newaccount->{'DeniedPaths'} = ["DeniedPaths: ^/\n"];
3258 # Construct home directory path
3259 my $currentHome = lc($newaccount->{'Username'}->[0]);
3260 $currentHome =~ s/[^\w]/_/isg;
3261 my $newpath = "^${currentRoot}$currentHome/";
3262 $NewAllowedPaths .= "AllowedPaths: $newpath\n";
3263 push(@{$newaccount->{'AllowedPaths'}}, $newpath);
3264 chomp($NewAllowedPaths);
3266 # Sign the new ticket
3267 my $Signature = SignTicketWithMasterkey($newaccount, $newaccount->{'Salt'}->[0]);
3269 # Write
3270 my $datetime = gmtime();
3271 my $passwordline = "<span id='newaccount'>".($newaccount->{'Password'}->[0])."</span>";
3272 my $newaccounttext = << "ENDOFNEWACCOUNTTEXT";
3273 Type: $newaccount->{'Type'}->[0]
3274 Username: $newaccount->{'Username'}->[0]
3275 Password: $encryptedpasswordline
3276 Salt: $newaccount->{'Salt'}->[0]
3277 $NewAllowedPaths
3278 Session: $newaccount->{'Session'}->[0]
3279 Date: $datetime UTC
3280 Time: $newaccount->{'Time'}->[0]
3281 Signature: $newaccount->{'Signature'}->[0]
3282 ENDOFNEWACCOUNTTEXT
3283 return $newaccounttext;
3286 sub XOR_hex_strings # (hex1, hex2) -> hex
3288 my $hex1 = shift || "";
3289 my $hex2 = shift || "";
3290 my @hex1list = split('', $hex1);
3291 my @hex2list = split('', $hex2);
3292 my @hexresultlist = ();
3293 for(my $i; $i < scalar(@hex1list); ++$i)
3295 my $d1 = hex($hex1list[$i]);
3296 my $d2 = hex($hex2list[$i]);
3297 my $dresult = ($d1 ^ $d2);
3298 $hexresultlist[$i] = sprintf("%x", $dresult);
3300 $hexresult = join('', @hexresultlist);
3301 return $hexresult;
3304 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3305 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3307 my $oldchallengefile = shift || "";
3308 my $authorizationfile = shift || "";
3309 my $sessionpath = shift || "";
3310 $sessionpath =~ s!/+$!!g;
3312 # Get Login session ticket
3313 my $oldchallenge = read_ticket($oldchallengefile);
3314 return "" unless $oldchallenge;
3316 # Get Authorization (user) session file
3317 my $authorization = read_ticket($authorizationfile);
3318 return "" unless $authorization;
3319 my $storedpassword = $authorization->{'Password'}->[0];
3320 return "" unless $storedpassword;
3321 my $challengekey = $oldchallenge->{'Key'}->[0];
3322 return "" unless $challengekey;
3324 # Create Random Hash Salt
3325 my $NEWCHALLENGETICKET = get_random_hex();;
3326 my $newchallengefile = hash_string("$NEWCHALLENGETICKET$challengekey");
3327 return "" unless $newchallengefile;
3329 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3330 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3331 ${"CGIexecute::CHALLENGETICKET"} = $NEWCHALLENGETICKET;
3333 # Write Session Ticket
3334 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3335 my @OldChallengeLines = <OLDCHALLENGE>;
3336 close(OLDCHALLENGE);
3337 # Old file should now be removed
3338 unlink($oldchallengefile);
3340 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3341 foreach $line (@OldChallengeLines)
3343 print SESSION $line;
3345 close(SESSION);
3347 return $NEWCHALLENGETICKET;
3350 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3352 my $PasswordDir = shift || "";
3353 my $SessionDir = shift || "";
3354 my $IPaddress = shift || "";
3356 # Create Login Ticket
3357 my $LOGINTICKET= get_random_hex ();
3359 # Create Random Hash Salt
3360 my $RANDOMSALT= get_random_hex();
3362 # Create SALT file if it does not exist
3363 # Remove this, including test account for life system
3364 unless(-d "$SessionDir")
3366 `mkdir -p "$SessionDir"`;
3368 unless(-d "$PasswordDir")
3370 `mkdir -p "$PasswordDir"`;
3372 # Create SERVERSALT and default test account
3373 my $SERVERSALT = "";
3374 unless(-s "$PasswordDir/SALT")
3376 $SERVERSALT= get_random_hex();
3377 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
3378 print SALTFILE "$SERVERSALT\n";
3379 close(SALTFILE);
3381 # Update test account (should be removed in live system)
3382 my @alltestusers = ("test", "testip", "testchallenge", "admin");
3383 foreach my $testuser (@alltestusers)
3385 if(-s "$PasswordDir/$testuser")
3387 my $plainpassword = $testuser eq 'admin' ? "There is no password like more password" : "test";
3388 my $storedpassword = hash_string("${SERVERSALT}${plainpassword}${testuser}");
3389 # Encrypt the new password with the MasterKey
3390 my $authorization = read_ticket("$PasswordDir/$testuser") || return "";
3391 set_password($authorization, $SERVERSALT, $plainpassword);
3392 write_ticket("$PasswordDir/$testuser", $authorization, $SERVERSALT);
3397 # Read in site Salt
3398 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3399 $SERVERSALT=<SALTFILE>;
3400 close(SALTFILE);
3401 chomp($SERVERSALT);
3403 # Create login session ticket
3404 my $datetime = gmtime();
3405 my $timesec = time();
3406 my $loginticket = {};
3407 $loginticket->{Type} = ['LOGIN'];
3408 $loginticket->{IPaddress} = [$IPaddress];
3409 $loginticket->{Salt} = [$SERVERSALT];
3410 $loginticket->{Session} = [$LOGINTICKET];
3411 $loginticket->{Randomsalt} = [$RANDOMSALT];
3412 $loginticket->{Expires} = ['+600s'];
3413 $loginticket->{Date} = ["$datetime UTC"];
3414 $loginticket->{Time} = [$timesec];
3415 write_ticket("$SessionDir/$LOGINTICKET", $loginticket, $SERVERSALT);
3417 # Set global variables
3418 # $SERVERSALT
3419 $ENV{'SERVERSALT'} = $SERVERSALT;
3420 CGIexecute::defineCGIvariable('SERVERSALT', "");
3421 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3423 # $SESSIONTICKET
3424 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3425 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3426 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3428 # $RANDOMSALT
3429 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3430 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3431 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3433 # $LOGINTICKET
3434 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3435 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3436 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3438 return $ENV{'LOGINTICKET'};
3441 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3443 my $sessionfile = shift || "";
3444 my $loginfile = shift || "";
3445 my $authorizationfile = shift || "";
3446 my $path = shift || "";
3448 # Get Login session ticket
3449 my $loginticket = read_ticket($loginfile);
3450 return unlink($loginfile) unless $loginticket;
3452 # Get Authorization (user) session file
3453 my $authorization = read_ticket($authorizationfile);
3454 return unlink($loginfile) unless $authorization;
3456 # For a Session or a Challenge, we need a stored key
3457 my $sessionkey = "";
3458 my $secretkey = "";
3459 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3461 my $storedpassword = $authorization->{'Password'}->[0];
3462 my $loginticketid = $loginticket->{'Session'}->[0];
3463 my $randomsalt = $loginticket->{'Randomsalt'}->[0];
3464 $sessionkey = hash_string("$loginticketid$storedpassword");
3465 $secretkey = hash_string("$randomsalt$loginticketid$storedpassword");
3467 # Get Session id
3468 my $sessionid = "";
3469 if($sessionfile =~ m!([^/]+)$!)
3471 $sessionid = $1;
3474 # Convert Authorization content to Session content
3475 my $sessionContent = {};
3476 my $SessionType = $authorization->{'Session'}->[0] ? $authorization->{'Session'}->[0] : "SESSION";
3477 $sessionContent->{Type} = [$SessionType];
3478 $sessionContent->{Username} = [$authorization->{'Username'}->[0]];
3479 $sessionContent->{Session} = [$sessionid];
3480 $sessionContent->{Time} = [time];
3481 $sessionContent->{IPaddress} = $loginticket->{'IPaddress'};
3482 $sessionContent->{Salt} = $authorization->{'Salt'};
3483 $sessionContent->{Randomsalt} = $loginticket->{'Randomsalt'};
3484 $sessionContent->{AllowedPaths} = $authorization->{'AllowedPaths'};
3485 $sessionContent->{DeniedPaths} = $authorization->{'DeniedPaths'};
3486 $sessionContent->{Expires} = $authorization->{'MaxLifetime'};
3487 $sessionContent->{Capabilities} = $authorization->{'Capabilities'};
3488 foreach my $pattern (keys(%TicketRequiredPatterns))
3490 if($path =~ m#$pattern#)
3492 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3493 push(@{$sessionContent->{Expires}}, $validtime);
3496 $sessionContent->{Key} = [$sessionkey] if $sessionkey;
3497 $sessionContent->{Secretkey} = [$secretkey] if $secretkey;
3498 $sessionContent->{Date} = [gmtime()." UTC\n"];
3500 # Sign Session Ticket
3501 my $Signature = SignTicketWithMasterkey($sessionContent, $authorization->{'Salt'}->[0]);
3503 # Write Session Ticket
3504 write_ticket($sessionfile, $sessionContent, $authorization->{'Salt'}->[0]);
3506 # Login file should now be removed
3507 return unlink($loginfile);
3510 sub check_ticket_validity # ($type, $ticketfile, $address, $path)
3512 my $type = shift || "SESSION";
3513 my $ticketfile = shift || "";
3514 my $address = shift || "";
3515 my $path = shift || "";
3517 # Is there a session ticket of this name?
3518 return 0 unless -s "$ticketfile";
3520 # There is a session ticket, is it linked to this IP address?
3521 my $ticket = read_ticket($ticketfile);
3523 # Is this the right type of ticket
3524 return unless $ticket && $ticket->{"Type"}->[0] eq $type;
3526 # Does the IP address match?
3527 my $IPmatches = 0;
3528 for $IPpattern (@{$ticket->{"IPaddress"}})
3530 ++$IPmatches if $address =~ m#^$IPpattern#ig;
3532 return 0 unless !$ticket->{"IPaddress"} || $IPmatches;
3534 # Is the path denied
3535 my $Pathmatches = 0;
3536 foreach $Pathpattern (@{$ticket->{"DeniedPaths"}})
3538 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3540 return 0 if @{$ticket->{"DeniedPaths"}} && $Pathmatches;
3542 # Is the path allowed
3543 $Pathmatches = 0;
3544 foreach $Pathpattern (@{$ticket->{"AllowedPaths"}})
3546 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3548 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
3550 # Is the ticket expired?
3551 my $Expired = 0;
3552 if($ticket->{"Expires"} && @{$ticket->{"Expires"}})
3554 my $CurrentTime = time();
3555 ++$Expired if($CurrentTime > $ticket->{"Expires"}->[0]);
3557 return 0 if $Expired;
3559 # Check signature
3560 my $Signature = TicketSignature($ticket, $ticket->{'Salt'}->[0]);
3562 if($Signature ne $ticket->{'Signature'}->[0])
3564 print STDERR "$ticket->{'Type'}->[0]: $ticket->{'Username'}->[0]\n";
3565 print STDERR "$Signature ne $ticket->{'Signature'}->[0]\n";
3568 # Make login values available (will also protect against resetting by query)
3569 $ENV{"LOGINUSERNAME"} = lc($ticket->{'Username'}->[0]);
3570 $ENV{"LOGINIPADDRESS"} = $address;
3571 $ENV{"LOGINPATH"} = $path;
3572 $ENV{"SESSIONTYPE"} = $type unless $type eq "PASSWORD";
3574 # Set Capabilities, if present
3575 if($ticket->{'Username'}->[0] && @{$ticket->{'Capabilities'}})
3577 $ENV{'CAPABILITIES'} = $ticket->{'Username'}->[0];
3578 CGIexecute::defineCGIvariableList('CAPABILITIES', "");
3579 @{"CGIexecute::CAPABILITIES"} = @{$ticket->{'Capabilities'}};
3580 # Capabilities should not be changed anymore by CGI query!
3582 # Capabilities are NOT to be set by the query
3583 CGIexecute::ProtectCGIvariable('CAPABILITIES');
3585 return 1;
3589 sub remove_expired_tickets # ($path) -> number of tickets removed
3591 my $path = shift || "";
3592 return 0 unless $path;
3593 $path =~ s!/+$!!g;
3594 my $removed_tickets = 0;
3595 my @ticketlist = glob("$path/*");
3596 foreach my $ticketfile (@ticketlist)
3598 my $ticket = read_ticket($ticketfile);
3599 unless($ticket && @{$ticket->{'Expires'}} && $ticket->{'Expires'}->[0] > time)
3601 unlink $ticketfile;
3602 ++$removed_tickets;
3605 return $removed_tickets;
3608 sub set_password # ($ticket, $salt, $plainpassword) -> $password
3610 my $ticket = shift || "";
3611 my $salt = shift || "";
3612 my $plainpassword = shift || "";
3614 my $user = $ticket->{'Username'}->[0];
3615 return "" unless $user;
3616 my $storedpassword = hash_string("${salt}${plainpassword}${user}");
3618 $ticket->{'Password'} = [$storedpassword];
3619 return $ticket->{'Password'}->[0];
3622 sub write_ticket # ($ticketfile, $ticket, $salt [, $masterkey]) -> &%ticket
3624 my $ticketfile = shift || "";
3625 my $ticket = shift || "";
3626 my $salt = shift || "";
3627 my $masterkey = shift || "";
3629 # Encrypt password
3630 EncryptTicketWithMasterKey($ticket, $salt, $masterkey);
3632 # Sign the new ticket
3633 my @orderlist = ('Type', 'Username', 'IPaddress', 'AllowedPaths', 'DeniedPaths',
3634 'Expires', 'Capabilities', 'Salt', 'Session', 'Randomsalt',
3635 'Date', 'Time', 'Signature', 'Key', 'Secretkey');
3636 my @labellist = keys(%{$ticket});
3637 my $signature = SignTicketWithMasterkey($ticket, "");
3638 open(TICKET, ">$ticketfile") || die "$ticketfile: $!\n";
3639 foreach my $label (@orderlist)
3641 @labellist = grep(!/\b$label\b/, @labellist);
3643 foreach my $label (@orderlist, @labellist)
3645 next unless exists($ticket->{$label}) && $ticket->{$label}->[0];
3646 foreach my $value (@{$ticket->{$label}})
3648 print TICKET "$label: $value\n";
3651 close(TICKET);
3653 return $ticketfile;
3656 sub read_ticket # ($ticketfile [, $masterkey]) -> &%ticket
3658 my $ticketfile = shift || "";
3659 my $masterkey = shift || "";
3661 my $ticket = {};
3662 if($ticketfile && -s $ticketfile)
3664 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
3665 my @alllines = <TICKETFILE>;
3666 close(TICKETFILE);
3667 foreach my $currentline (@alllines)
3669 # Skip empty lines and comments
3670 next unless $currentline =~ /\S/;
3671 next if $currentline =~ /^\s*\#/;
3673 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
3675 my $Label = $1;
3676 my $Value = $2;
3677 # Recalculate expire date from relative time
3678 if($Label =~ /^Expires$/ig && $Value =~ /^\+/)
3680 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
3682 $ExpireTime = 24*3600*$1;
3684 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
3686 $ExpireTime = 60*$1;
3688 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
3690 $ExpireTime = 3600*$1;
3692 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
3694 $ExpireTime = $1;
3696 elsif($Value =~ /^\+(\d+)\s*$/)
3698 $ExpireTime = $1;
3701 my $ActualExpireTime = $ExpireTime;
3702 $Value = $ActualExpireTime;
3704 $ticket->{$Label} = () unless exists($ticket->{$Label});
3705 push(@{$ticket->{$Label}}, $Value);
3709 if($ENV{'CGIMasterKey'} && exists($ticket->{'Password'}) && $ticket->{'Password'}->[0])
3711 my $serversalt = "";
3712 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
3713 if(exists($ticket->{Salt}) && $ticket->{Salt}->[0])
3715 $serversalt = $ticket->{Salt}->[0];
3717 else
3719 my $saltfile = $ticketfile;
3720 $saltfile =~ s![^/]+$!!isg;
3721 $saltfile .= "SALT";
3722 if(-s "$saltfile")
3724 open(SERVERSALT, "<$saltfile") || die "<$saltfile: $!\n";
3725 $serversalt = <SERVERSALT>;
3726 chomp($serversalt);
3727 close(SERVERSALT);
3730 # Decrypt all passwords
3731 DecryptTicketWithMasterKey($ticket, $serversalt, $masterkey) ||
3732 die "Decryption failed: DecryptTicketWithMasterKey ($ticket, $serversalt)\n";
3735 if(exists($ticket->{Expires}))
3737 my $StartTime = 0;
3738 if(exists($ticket->{Time}) && $ticket->{Time}->[0] > 0)
3740 $StartTime = [(sort(@{$ticket->{Time}}))]->[0];
3742 else
3744 # Get SessionTicket file stats
3745 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
3746 = stat($ticketfile);
3747 $StartTime = $ctime;
3749 foreach my $absoluteTime (@{$ticket->{Expires}})
3751 $absoluteTime += $StartTime;
3752 return 0 unless $absoluteTime > time;
3754 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
3756 return $ticket;
3759 # Add a signature from $masterkey to a ticket in the label $signlabel
3760 sub SignTicketWithMasterkey # ($ticket, $serversalt [, $masterkey, $signlabel]) -> $Signature
3762 my $ticket = shift || return 0;
3763 my $serversalt = shift || "";
3764 my $masterkey = shift || $ENV{'CGIMasterKey'};
3765 my $signlabel = shift || 'Signature';
3766 return "" unless $masterkey;
3768 my $Signature = TicketSignature($ticket, $serversalt, $masterkey);
3770 $ticket->{$signlabel} = [$Signature];
3772 return $Signature;
3775 # Determine ticket signature
3776 sub TicketSignature # ($ticket, $serversalt [, $masterkey]) -> $Signature
3778 my $ticket = shift || return 0;
3779 my $serversalt = shift || "";
3780 my $masterkey = shift || $ENV{'CGIMasterKey'};
3781 my $Signature = "";
3783 if($masterkey)
3785 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
3786 if(exists($ticket->{Salt}) && $ticket->{Salt}->[0])
3788 $serversalt = $ticket->{Salt}->[0];
3790 # Sign
3791 if($serversalt)
3793 my $hash1 = hash_string(${serversalt}.$masterkey);
3794 my $SignText = "Type: ".$ticket->{'Type'}->[0]."\n";
3795 $SignText .= "Username: ".(sort(@{$ticket->{'Username'}}))."\n";
3796 $SignText .= "IPaddress: ".(sort(@{$ticket->{'IPaddress'}}))."\n";
3797 $SignText .= "AllowedPaths: ".(sort(@{$ticket->{'AllowedPaths'}}))."\n";
3798 $SignText .= "DeniedPaths: ".(sort(@{$ticket->{'DeniedPaths'}}))."\n";
3799 $SignText .= "Session: ".(sort(@{$ticket->{'Session'}}))."\n";
3800 $SignText .= "Time: ".(sort(@{$ticket->{'Time'}}))."\n";
3801 $SignText .= "Expires: ".(sort(@{$ticket->{'Expires'}})),"\n";
3802 $SignText .= "Capabilities: ".(sort(@{$ticket->{'Capabilities'}}))."\n";
3803 $Signature = hash_string(${'hash1'}.$SignText);
3806 return $Signature;
3809 # Decrypts a password list IN PLACE
3810 sub DecryptTicketWithMasterKey # ($ticket, $serversalt) -> \@password_list
3812 my $ticket = shift || return 0;
3813 my $serversalt = shift || "";
3815 if($ENV{'CGIMasterKey'} && exists($ticket->{Password}) && $ticket->{Password}->[0])
3817 # If the ServerSalt is not stored in the ticket, the SALT file has to be found
3818 if(exists($ticket->{Salt}) && $ticket->{Salt}->[0])
3820 $serversalt = $ticket->{Salt}->[0];
3822 # Decrypt password(s)
3823 if($serversalt)
3825 my $hash1 = hash_string(${serversalt}.$ENV{'CGIMasterKey'});
3826 my $CryptKey = hash_string(${'hash1'}.$ticket->{'Username'}->[0]);
3827 foreach my $password (@{$ticket->{Password}})
3829 $password = XOR_hex_strings($CryptKey,$password);
3833 return $ticket->{Password};
3835 sub EncryptTicketWithMasterKey # ($ticket, $serversalt) -> \@password_list
3837 DecryptTicketWithMasterKey(@_);
3840 # End of Handle login access
3843 ############################################################################
3845 # Handle foreign interpreters (i.e., scripting languages)
3847 # Insert perl code to execute scripts in foreign scripting languages.
3848 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
3849 # into an interpreter.
3850 # The code presented here is fairly confusing because it
3851 # actually writes perl code code to the output.
3853 # A table with the file handles
3854 %SCRIPTINGINPUT = ();
3856 # A function to clean up Client delivered CGI parameter values
3857 # (i.e., quote all odd characters)
3858 %SHRUBcharacterTR =
3860 "\'" => '&#39;',
3861 "\`" => '&#96;',
3862 "\"" => '&quot;',
3863 '&' => '&amper;',
3864 "\\" => '&#92;'
3867 sub shrubCGIparameter # ($String) -> Cleaned string
3869 my $String = shift || "";
3871 # Change all quotes [`'"] into HTML character entities
3872 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
3874 # Protect &
3875 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
3877 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
3879 next if $Char eq '&';
3880 $String =~ s/\Q$Char\E/$Transcript/isg;
3883 # Replace newlines
3884 $String =~ s/[\n]/\\n/g;
3885 # Replace control characters with their backslashed octal ordinal numbers
3886 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
3887 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
3889 return $String;
3893 # The initial open statements: Open a pipe to the foreign script interpreter
3894 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
3896 my $ContentType = lc(shift) || return "";
3897 my $NewDirective = "";
3899 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
3901 # Construct a unique file handle name
3902 $SCRIPTINGFILEHANDLE = uc($ContentType);
3903 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
3904 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
3905 unless $SCRIPTINGINPUT{$ContentType};
3907 # Create the relevant script: Open the pipe to the interpreter
3908 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
3909 # Open interpreter for '$ContentType'
3910 # Open pipe to interpreter (if it isn't open already)
3911 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
3912 BLOCKCGISCRIPTOROPEN
3914 # Insert Initialization code and CGI variables
3915 $NewDirective .= InitializeForeignScript($ContentType);
3917 # Ready
3918 return $NewDirective;
3922 # The final closing code to stop the interpreter
3923 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
3925 my $ContentType = lc(shift) || return "";
3926 my $NewDirective = "";
3928 # Do nothing unless the pipe realy IS open
3929 return "" unless $SCRIPTINGINPUT{$ContentType};
3931 # Initial comment
3932 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
3935 # Write the Postfix code
3936 $NewDirective .= CleanupForeignScript($ContentType);
3938 # Create the relevant script: Close the pipe to the interpreter
3939 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
3940 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
3941 select(STDOUT); \$|=1;
3943 BLOCKCGISCRIPTORCLOSE
3945 # Remove the file handler of the foreign script
3946 delete($SCRIPTINGINPUT{$ContentType});
3948 return $NewDirective;
3952 # The initialization code for the foreign script interpreter
3953 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
3955 my $ContentType = lc(shift) || return "";
3956 my $NewDirective = "";
3958 # Add initialization code
3959 if($ScriptingInitialization{$ContentType})
3961 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
3962 # Initialization Code for '$ContentType'
3963 # Select relevant output filehandle
3964 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3966 # The Initialization code (if any)
3967 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
3968 $ScriptingInitialization{$ContentType}
3969 ${ContentType}INITIALIZATIONCODE
3971 BLOCKCGISCRIPTORINIT
3974 # Add all CGI variables defined
3975 if(exists($ScriptingCGIvariables{$ContentType}))
3977 # Start writing variable definitions to the Interpreter
3978 if($ScriptingCGIvariables{$ContentType})
3980 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
3981 # CGI variables (from the %default_values table)
3982 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
3983 BLOCKCGISCRIPTORVARDEF
3986 my ($N, $V);
3987 foreach $N (keys(%default_values))
3989 # Determine whether the parameter has been defined
3990 # (the eval is a workaround to get at the variable value)
3991 next unless eval("defined(\$CGIexecute::$N)");
3993 # Get the value from the EXECUTION environment
3994 $V = eval("\$CGIexecute::$N");
3995 # protect control characters (i.e., convert them to \0.. form)
3996 $V = shrubCGIparameter($V);
3998 # Protect interpolated variables
3999 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
4001 # Print the actual declaration for this scripting language
4002 if($ScriptingCGIvariables{$ContentType})
4004 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
4005 $NewDirective .= "\n";
4009 # Stop writing variable definitions to the Interpreter
4010 if($ScriptingCGIvariables{$ContentType})
4012 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
4013 ${ContentType}CGIVARIABLES
4014 BLOCKCGISCRIPTORVARDEFEND
4019 $NewDirective .= << "BLOCKCGISCRIPTOREND";
4021 # Select STDOUT filehandle
4022 select(STDOUT); \$|=1;
4024 BLOCKCGISCRIPTOREND
4026 return $NewDirective;
4030 # The cleanup code for the foreign script interpreter
4031 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
4033 my $ContentType = lc(shift) || return "";
4034 my $NewDirective = "";
4036 # Return if not needed
4037 return $NewDirective unless $ScriptingCleanup{$ContentType};
4039 # Create the relevant script: Open the pipe to the interpreter
4040 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
4041 # Cleanup Code for '$ContentType'
4042 # Select relevant output filehandle
4043 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4044 # Print Cleanup code to foreign script
4045 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
4046 $ScriptingCleanup{$ContentType}
4047 ${ContentType}SCRIPTSTOP
4049 # Select STDOUT filehandle
4050 select(STDOUT); \$|=1;
4051 BLOCKCGISCRIPTORSTOP
4053 return $NewDirective;
4057 # The prefix code for each <script></script> block
4058 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
4060 my $ContentType = lc(shift) || return "";
4061 my $NewDirective = "";
4063 # Return if not needed
4064 return $NewDirective unless $ScriptingPrefix{$ContentType};
4066 my $Quote = "\'";
4067 # If the CGIvariables parameter is defined, but empty, interpolate
4068 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4069 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4070 !$ScriptingCGIvariables{$ContentType};
4072 # Add initialization code
4073 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
4074 # Prefix Code for '$ContentType'
4075 # Select relevant output filehandle
4076 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4078 # The block Prefix code (if any)
4079 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
4080 $ScriptingPrefix{$ContentType}
4081 ${ContentType}PREFIXCODE
4082 # Select STDOUT filehandle
4083 select(STDOUT); \$|=1;
4084 BLOCKCGISCRIPTORPREFIX
4086 return $NewDirective;
4090 # The postfix code for each <script></script> block
4091 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
4093 my $ContentType = lc(shift) || return "";
4094 my $NewDirective = "";
4096 # Return if not needed
4097 return $NewDirective unless $ScriptingPostfix{$ContentType};
4099 my $Quote = "\'";
4100 # If the CGIvariables parameter is defined, but empty, interpolate
4101 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4102 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4103 !$ScriptingCGIvariables{$ContentType};
4105 # Create the relevant script: Open the pipe to the interpreter
4106 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
4107 # Postfix Code for '$ContentType'
4108 # Select filehandle to interpreter
4109 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4110 # Print postfix code to foreign script
4111 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
4112 $ScriptingPostfix{$ContentType}
4113 ${ContentType}SCRIPTPOSTFIX
4114 # Select STDOUT filehandle
4115 select(STDOUT); \$|=1;
4116 BLOCKCGISCRIPTORPOSTFIX
4118 return $NewDirective;
4121 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
4123 my $ContentType = lc(shift) || return "";
4124 my $directive = shift || return "";
4125 my @SRCfile = @_;
4126 my $NewDirective = "";
4128 my $Quote = "\'";
4129 # If the CGIvariables parameter is defined, but empty, interpolate
4130 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
4131 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
4132 !$ScriptingCGIvariables{$ContentType};
4134 # Create the relevant script
4135 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4136 # Insert Code for '$ContentType'
4137 # Select filehandle to interpreter
4138 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
4139 BLOCKCGISCRIPTORINSERT
4141 # Use SRC feature files
4142 my $ThisSRCfile;
4143 while($ThisSRCfile = shift(@_))
4145 # Handle blocks
4146 if($ThisSRCfile =~ /^\s*\{\s*/)
4148 my $Block = $';
4149 $Block = $` if $Block =~ /\s*\}\s*$/;
4150 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
4151 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
4152 $Block
4153 ${ContentType}SRCBLOCKCODE
4154 BLOCKCGISCRIPTORSRCBLOCK
4156 next;
4159 # Handle files
4160 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
4161 # Read $ThisSRCfile
4162 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
4163 while(<SCRIPTINGSOURCE>)
4165 print $SCRIPTINGINPUT{$ContentType} \$_;
4167 close(SCRIPTINGSOURCE);
4169 BLOCKCGISCRIPTORSRCFILES
4173 # Add the directive
4174 if($directive)
4176 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
4177 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
4178 $directive
4179 ${ContentType}DIRECTIVECODE
4180 BLOCKCGISCRIPTORINSERT
4184 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
4185 # Select STDOUT filehandle
4186 select(STDOUT); \$|=1;
4187 BLOCKCGISCRIPTORSELECT
4189 # Ready
4190 return $NewDirective;
4193 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
4195 my $ContentType;
4196 foreach $ContentType (keys(%SCRIPTINGINPUT))
4198 my $directive = CloseForeignScript($ContentType);
4199 print STDERR "\nDirective $CGI_Date: ", $directive;
4200 CGIexecute->evaluate($directive);
4205 # End of handling foreign (external) scripting languages.
4207 ############################################################################
4209 # A subroutine to handle "nested" quotes, it cuts off the leading
4210 # item or quoted substring
4211 # E.g.,
4212 # ' A_word and more words' -> @('A_word', ' and more words')
4213 # '"quoted string" The rest' -> @('quoted string', ' The rest')
4214 # (this is needed for parsing the <TAGS> and their attributes)
4215 my $SupportedQuotes = "\'\"\`\(\{\[";
4216 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
4217 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
4219 my @Result = ();
4220 my $String = shift || return @Result;
4222 if($String =~ /^\s*([\w\/\-\.]+)/is)
4224 push(@Result, $1, $');
4226 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
4228 my $BackSlash = $1 || "";
4229 my $OpenQuote = $2;
4230 my $CloseQuote = $OpenQuote;
4231 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
4233 if($BackSlash)
4235 $String =~ /^\s*\\\Q$OpenQuote\E/i;
4236 my $Onset = $';
4237 $Onset =~ /\\\Q$CloseQuote\E/i;
4238 my $Rest = $';
4239 my $Item = $`;
4240 push(@Result, $Item, $Rest);
4243 else
4245 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
4246 push(@Result, $1, $');
4249 else
4251 push(@Result, "", $String);
4253 return @Result;
4256 # Now, start with the real work
4258 # Control the output of the Content-type: text/html\n\n message
4259 my $SupressContentType = 0;
4261 # Process a file
4262 sub ProcessFile # ($file_path)
4264 my $file_path = shift || return 0;
4267 # Generate a unique file handle (for recursions)
4268 my @SRClist = ();
4269 my $FileHandle = "file";
4270 my $n = 0;
4271 while(!eof($FileHandle.$n)) {++$n;};
4272 $FileHandle .= $n;
4274 # Start HTML output
4275 # Use the default Content-type if this is NOT a raw file
4276 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
4277 || $SupressContentType)
4279 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
4280 my $ContentType = $ContentTypeTable{$1};
4281 print "Content-type: $ContentType\n";
4282 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
4284 foreach my $name (keys(%SETCOOKIELIST))
4286 my $value = $SETCOOKIELIST{$name};
4287 print "Set-Cookie: $name=$value\n";
4289 # Cookies are set only ONCE
4290 %SETCOOKIELIST = ();
4292 print "\n";
4293 $SupressContentType = 1; # Content type has been printed
4297 # Get access to the actual data. This can be from RAM (by way of an
4298 # environment variable) or by opening a file.
4300 # Handle the use of RAM images (file-data is stored in the
4301 # $CGI_FILE_CONTENTS environment variable)
4302 # Note that this environment variable will be cleared, i.e., it is strictly for
4303 # single-use only!
4304 if($ENV{$CGI_FILE_CONTENTS})
4306 # File has been read already
4307 $_ = $ENV{$CGI_FILE_CONTENTS};
4308 # Sorry, you have to do the reading yourself (dynamic document creation?)
4309 # NOTE: you must read the whole document at once
4310 if($_ eq '-')
4312 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
4314 else # Clear environment variable
4316 $ENV{$CGI_FILE_CONTENTS} = '-';
4319 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
4320 # THIS IS A SECURITY FEATURE!
4321 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
4323 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
4324 push(@OpenFiles, $file_path);
4325 $_ = <$FileHandle>; # Read first line
4327 else
4329 print "<h2>File not found</h2>\n";
4330 dieHandler(18, "$file_path\n");
4333 $| = 1; # Flush output buffers
4335 # Initialize variables
4336 my $METAarguments = ""; # The CGI arguments from the latest META tag
4337 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
4338 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
4341 # Send document to output
4342 # Process the requested document.
4343 # Do a loop BEFORE reading input again (this catches the RAM/Database
4344 # type of documents).
4345 do {
4348 # Handle translations if needed
4350 performTranslation(\$_) if $TranslationPaths;
4352 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
4353 # There can be more than 1 <SCRIPT> or META tags on a line
4354 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
4356 my $directive = "";
4357 # Store rest of line
4358 my $Before = $`;
4359 my $ScriptTag = $&;
4360 my $After = $';
4361 my $TagType = uc($1);
4362 # The before part can be send to the output
4363 print $Before;
4365 # Read complete Tag from after and/or file
4366 until($After =~ /([^\\])\>/)
4368 $After .= <$FileHandle>;
4369 performTranslation(\$After) if $TranslationPaths;
4372 if($After =~ /([^\\])\>/)
4374 $ScriptTag .= $`.$&; # Keep the Script Tag intact
4375 $After = $';
4377 else
4379 dieHandler(19, "Closing > not found\n");
4382 # The tag could be closed by />, we handle this in the XML way
4383 # and don't process any content (we ignore whitespace)
4384 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
4387 # TYPE or CLASS?
4388 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
4389 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
4391 # Parse <SCRIPT> or <META> directive
4392 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
4393 # send the line to the output and go to the next loop
4394 my $CurrentContentType = "";
4395 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
4397 my ($Type) = ExtractQuotedItem($');
4398 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
4399 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
4400 # CSS classes are aliases of $ServerScriptContentType
4401 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
4403 $CurrentContentType = $ServerScriptContentType;
4408 # Not a known server-side content type, print and continue
4409 unless(($CurrentContentType =~
4410 /$ServerScriptContentType|$ShellScriptContentType/is) ||
4411 $ScriptingLanguages{$CurrentContentType})
4413 print $ScriptTag;
4414 $_ = $After;
4415 next;
4419 # A known server-side content type, evaluate
4421 # First, handle \> and \<
4422 $ScriptTag =~ s/\\\>/\>/isg;
4423 $ScriptTag =~ s/\\\</\</isg;
4425 # Extract the CGI, SRC, ID, IF and UNLESS attributes
4426 my %ScriptTagAttributes = ();
4427 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
4429 my $Attribute = $2;
4430 my $Rest = $';
4431 my $Value = "";
4432 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
4433 $ScriptTagAttributes{uc($Attribute)} = $Value;
4437 # The attribute used to define the CGI variables
4438 # Extract CGI-variables from
4439 # <META CONTENT="text/ssperl; CGI='' SRC=''">
4440 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
4441 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
4442 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
4443 if($ScriptTagAttributes{'CGI'})
4445 @ARGV = (); # Reset ARGV
4446 $ARGC = 0;
4447 $METAarguments = ""; # Reset the META CGI arguments
4448 @METAvalues = ();
4449 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
4451 # Process default values of variables ($<name> = 'default value')
4452 # Allowed quotes are '', "", ``, (), [], and {}
4453 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
4455 my $varType = $2 || '$'; # Variable or list
4456 my $name = $3; # The Name
4457 my $default = "";
4458 $Meta_CGI = $';
4460 if($Meta_CGI =~ /^\s*\=\s*/is)
4462 # Locate (any) default value
4463 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
4465 $RemainingTag = $Meta_CGI;
4468 # Define CGI (or ENV) variable, initalize it from the
4469 # Query string or the default value
4471 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
4472 # scripts to access the CGI arguments defined in the META tag
4473 # (Not for CGI inside <SCRIPT> tags)
4474 if($varType eq '$')
4476 CGIexecute::defineCGIvariable($name, $default)
4477 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
4478 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
4479 # Add value to the @ARGV list
4480 push(@ARGV, ${"CGIexecute::$name"});
4481 ++$ARGC;
4483 elsif($varType eq '@')
4485 CGIexecute::defineCGIvariableList($name, $default)
4486 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
4487 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
4488 # Add value to the @ARGV list
4489 push(@ARGV, @{"CGIexecute::$name"});
4490 $ARGC = scalar(@CGIexecute::ARGV);
4492 elsif($varType eq '%')
4494 CGIexecute::defineCGIvariableHash($name, $default)
4495 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
4496 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
4497 push(@METAvalues, "'".join("'", @PairList)."'");
4498 # Add value to the @ARGV list
4499 push(@ARGV, %{"CGIexecute::$name"});
4500 $ARGC = scalar(@CGIexecute::ARGV);
4503 # Store the values for internal and later use
4504 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
4506 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
4511 # The IF (conditional execution) Attribute
4512 # Evaluate the condition and stop unless it evaluates to true
4513 if($ScriptTagAttributes{'IF'})
4515 my $IFcondition = $ScriptTagAttributes{'IF'};
4517 # Convert SCRIPT calls, ./<script>
4518 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4520 # Convert FILE calls, ~/<file>
4521 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4523 # Block execution if necessary
4524 unless(CGIexecute->evaluate($IFcondition))
4526 %ScriptTagAttributes = ();
4527 $CurrentContentType = "";
4531 # The UNLESS (conditional execution) Attribute
4532 # Evaluate the condition and stop if it evaluates to true
4533 if($ScriptTagAttributes{'UNLESS'})
4535 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
4537 # Convert SCRIPT calls, ./<script>
4538 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4540 # Convert FILE calls, ~/<file>
4541 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4543 # Block execution if necessary
4544 if(CGIexecute->evaluate($UNLESScondition))
4546 %ScriptTagAttributes = ();
4547 $CurrentContentType = "";
4551 # The SRC (Source File) Attribute
4552 # Extract any source script files and add them in
4553 # front of the directive
4554 # The SRC list should be emptied
4555 @SRClist = ();
4556 my $SRCtag = "";
4557 my $Prefix = 1;
4558 my $PrefixDirective = "";
4559 my $PostfixDirective = "";
4560 # There is a SRC attribute
4561 if($ScriptTagAttributes{'SRC'})
4563 $SRCtag = $ScriptTagAttributes{'SRC'};
4564 # Remove "file://" prefixes
4565 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
4566 # Expand script filenames "./Script"
4567 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
4568 # Expand script filenames "~/Script"
4569 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
4572 # File source tags
4573 while($SRCtag =~ /\S/is)
4575 my $SRCdirective = "";
4577 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
4578 # SRC files
4579 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
4581 my $InsertionPlace = $1;
4582 $SRCtag = $2.$';
4584 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
4585 # Go to next round
4586 next;
4588 # {}-blocks are just evaluated by "do"
4589 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
4591 my $SRCblock = $';
4592 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
4594 $SRCblock = $`;
4595 $SRCtag = $1.$';
4596 # SAFEqx shell script blocks
4597 if($CurrentContentType =~ /$ShellScriptContentType/is)
4599 # Handle ''-quotes inside the script
4600 $SRCblock =~ s/[\']/\\$&/gis;
4602 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
4603 $SRCdirective .= $SRCblock."\n";
4605 # do { SRCblocks }
4606 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
4608 $SRCblock = "print do { $SRCblock };'';";
4609 $SRCdirective .= $SRCblock."\n";
4611 else # The interpreter should handle this
4613 push(@SRClist, "{ $SRCblock }");
4617 else
4618 { dieHandler(23, "Closing \} missing\n");};
4620 # Files are processed as Text or Executable files
4621 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
4623 my $SrcFile = $1;
4624 $SRCtag = $';
4626 # We are handling one of the external interpreters
4627 if($ScriptingLanguages{$CurrentContentType})
4629 push(@SRClist, $SrcFile);
4631 # We are at the start of a DIV tag, just load all SRC files and/or URL's
4632 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
4634 # $SrcFile is a URL pointing to an HTTP or FTP server
4635 if($SrcFile =~ m!^([a-z]+)\://!)
4637 my $URLoutput = CGIscriptor::read_url($SrcFile);
4638 $SRCdirective .= $URLoutput;
4640 # SRC file is an existing file
4641 elsif(-e "$SrcFile")
4643 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
4644 my $Content;
4645 while(sysread(DIVSOURCE, $Content, 1024) > 0)
4647 $SRCdirective .= $Content;
4649 close(DIVSOURCE);
4652 # Executable files are executed as
4653 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
4654 elsif(-x "$SrcFile")
4656 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
4658 # Handle 'standard' files, using ProcessFile
4659 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
4660 && $SrcFile =~ m@($FilePattern)$@) # A recursion
4663 # Do not process still open files because it can lead
4664 # to endless recursions
4665 if(grep(/^$SrcFile$/, @OpenFiles))
4666 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
4667 # Prepare meta arguments
4668 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
4669 # Process the file
4670 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
4672 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
4674 $SRCdirective .= GET_URL($SrcFile);
4676 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
4678 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
4679 $SRCdirective .= "do \'$SrcFile\';'';\n";
4681 else # This one could not be resolved (should be handled by BinaryMapFile)
4683 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
4688 # Postfix or Prefix
4689 if($Prefix)
4691 $PrefixDirective .= $SRCdirective;
4693 else
4695 $PostfixDirective .= $SRCdirective;
4698 # The prefix should be handled immediately
4699 $directive .= $PrefixDirective;
4700 $PrefixDirective = "";
4704 # Handle the content of the <SCRIPT></SCRIPT> tags
4705 # Do not process the content of <SCRIPT/>
4706 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
4708 my $EndScriptTag = "";
4710 # Execute SHELL scripts with SAFEqx()
4711 if($CurrentContentType =~ /$ShellScriptContentType/is)
4713 $directive .= "SAFEqx(\'";
4716 # Extract Program
4717 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
4719 $After .= <$FileHandle>;
4720 performTranslation(\$After) if $TranslationPaths;
4723 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
4725 $directive .= $`;
4726 $EndScriptTag = $&;
4727 $After = $';
4729 else
4731 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
4734 # Process only when content should be executed
4735 if($CurrentContentType)
4738 # Remove all comments from Perl scripts
4739 # (NOT from OS shell scripts)
4740 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
4741 if $CurrentContentType =~ /$ServerScriptContentType/i;
4743 # Convert SCRIPT calls, ./<script>
4744 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4746 # Convert FILE calls, ~/<file>
4747 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4749 # Execute SHELL scripts with SAFEqx(), closing bracket
4750 if($CurrentContentType =~ /$ShellScriptContentType/i)
4752 # Handle ''-quotes inside the script
4753 $directive =~ /SAFEqx\(\'/;
4754 $directive = $`.$&;
4755 my $Executable = $';
4756 $Executable =~ s/[\']/\\$&/gs;
4758 $directive .= $Executable."\');"; # Closing bracket
4761 else
4763 $directive = "";
4766 # Handle the content of the <DIV></DIV> tags
4767 # Do not process the content of <DIV/>
4768 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
4770 my $EndScriptTag = "";
4772 # Extract Text
4773 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
4775 $After .= <$FileHandle>;
4776 performTranslation(\$After) if $TranslationPaths;
4779 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
4781 $directive .= $`;
4782 $EndScriptTag = $&;
4783 $After = $';
4785 else
4787 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
4790 # Add the Postfixed directives (but only when it contains something printable)
4791 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
4792 $PostfixDirective = "";
4795 # Process only when content should be handled
4796 if($CurrentContentType)
4799 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
4800 # a valid Perl name). Names should not contain $, but we can handle it.
4801 my $name = $ScriptTagAttributes{'ID'};
4802 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
4803 $name = $1;
4805 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
4806 CGIexecute::defineCGIexecuteVariable($name, $directive);
4807 $directive = "";
4810 # Nothing to execute
4811 $directive = "";
4815 # Handle Foreign scripting languages
4816 if($ScriptingLanguages{$CurrentContentType})
4818 my $newDirective = "";
4819 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
4820 $newDirective .= PrefixForeignScript($CurrentContentType);
4821 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
4822 $newDirective .= PostfixForeignScript($CurrentContentType);
4823 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
4825 $newDirective .= '"";';
4827 $directive = $newDirective;
4831 # Add the Postfixed directives (but only when it contains something printable)
4832 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
4833 $PostfixDirective = "";
4836 # EXECUTE the script and print the results
4838 # Use this to debug the program
4839 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
4841 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
4842 $Result =~ s/\n$//g; # Remove final newline
4844 # Print the Result of evaluating the directive
4845 # (this will handle LARGE, >64 kB output)
4846 my $BytesWritten = 1;
4847 while($Result && $BytesWritten)
4849 $BytesWritten = syswrite(STDOUT, $Result, 64);
4850 $Result = substr($Result, $BytesWritten);
4852 # print $Result; # Could be used instead of above code
4854 # Store result if wanted, i.e., if $CGIscriptorResults has been
4855 # defined in a <META> tag.
4856 push(@CGIexecute::CGIscriptorResults, $Result)
4857 if exists($default_values{'CGIscriptorResults'});
4859 # Process the rest of the input line (this could contain
4860 # another directive)
4861 $_ = $After;
4863 print $_;
4864 } while(<$FileHandle>); # Read and Test AFTER first loop!
4866 close ($FileHandle);
4867 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
4871 ###############################################################################
4873 # Call the whole package
4875 sub Handle_Request
4877 my $file_path = "";
4879 # Initialization Code
4880 Initialize_Request();
4882 # SECURITY: ACCESS CONTROL
4883 Access_Control();
4885 # Read the POST part of the query, if there is one
4886 Get_POST_part_of_query();
4888 # Start (HTML) output and logging
4889 $file_path = Initialize_output();
4891 # Check login access or divert to login procedure
4892 $Use_Login = Log_In_Access();
4893 $file_path = $Use_Login if $Use_Login;
4895 # Record which files are still open (to avoid endless recursions)
4896 my @OpenFiles = ();
4898 # Record whether the default HTML ContentType has already been printed
4899 # but only if the SERVER uses HTTP or some other protocol that might interpret
4900 # a content MIME type.
4902 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
4904 # Process the specified file
4905 ProcessFile($file_path) if $file_path ne $SS_PUB;
4907 # Cleanup all open external (foreign) interpreters
4908 CloseAllForeignScripts();
4911 "" # SUCCESS
4914 # Make a single call to handle an (empty) request
4915 Handle_Request();
4918 # END OF PACKAGE MAIN
4921 ####################################################################################
4923 # The CGIEXECUTE PACKAGE
4925 ####################################################################################
4927 # Isolate the evaluation of directives as PERL code from the rest of the program.
4928 # Remember that each package has its own name space.
4929 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
4930 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
4932 package CGIexecute;
4934 sub evaluate
4936 my $self = shift;
4937 my $directive = shift;
4938 $directive = eval($directive);
4939 warn $@ if $@; # Write an error message to STDERR
4940 $directive; # Return value of directive
4944 # defineCGIexecuteVariable($name [, $value]) -> 0/1
4946 # Define and intialize variables inside CGIexecute
4947 # Does no sanity checking, for internal use only
4949 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
4951 my $name = shift || return 0; # The Name
4952 my $value = shift || ""; # The value
4954 ${$name} = $value;
4956 return 1;
4959 # Protect certain CGI variables values when set internally
4960 # If not defined internally, there will be no variable set AT ALL
4961 my %CGIprotectedVariable = ();
4962 sub ProtectCGIvariable # ($name) -> 0/1
4964 my $name = shift || "";
4965 return 0 unless $name && $name =~ /\w/;
4967 ++$CGIprotectedVariable{$name};
4969 return $CGIprotectedVariable{$name};
4972 # defineCGIvariable($name [, $default]) -> 0/1
4974 # Define and intialize CGI variables
4975 # Tries (in order) $ENV{$name}, the Query string and the
4976 # default value.
4977 # Removes all '-quotes etc.
4979 sub defineCGIvariable # ($name [, $default]) -> 0/1
4981 my $name = shift || return 0; # The Name
4982 my $default = shift || ""; # The default value
4984 # Protect variables set internally
4985 return 1 if !$name || exists($CGIprotectedVariable{$name});
4987 # Remove \-quoted characters
4988 $default =~ s/\\(.)/$1/g;
4989 # Store default values
4990 $::default_values{$name} = $default if $default;
4992 # Process variables
4993 my $temp = undef;
4994 # If there is a user supplied value, it replaces the
4995 # default value.
4997 # Environment values have precedence
4998 if(exists($ENV{$name}))
5000 $temp = $ENV{$name};
5002 # Get name and its value from the query string
5003 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5005 $temp = ::YOUR_CGIPARSE($name);
5007 # Defined values must exist for security
5008 elsif(!exists($::default_values{$name}))
5010 $::default_values{$name} = undef;
5013 # SECURITY, do not allow '- and `-quotes in
5014 # client values.
5015 # Remove all existing '-quotes
5016 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5017 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5018 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5019 # If $temp is empty, use the default value (if it exists)
5020 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
5022 $temp = $::default_values{$name};
5023 # Remove all existing '-quotes
5024 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5025 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
5026 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5028 else # Store current CGI values and remove defaults
5030 $::default_values{$name} = $temp;
5032 # Define the CGI variable and its value (in the execute package)
5033 ${$name} = $temp;
5035 # return SUCCES
5036 return 1;
5039 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
5041 my $name = shift || return 0; # The Name
5042 my $default = shift || ""; # The default value
5044 # Protect variables set internally
5045 return 1 if !$name || exists($CGIprotectedVariable{$name});
5047 # Defined values must exist for security
5048 if(!exists($::default_values{$name}))
5050 $::default_values{$name} = $default;
5053 my @temp = ();
5056 # For security:
5057 # Environment values have precedence
5058 if(exists($ENV{$name}))
5060 push(@temp, $ENV{$name});
5062 # Get name and its values from the query string
5063 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5065 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
5067 else
5069 push(@temp, $::default_values{$name});
5073 # SECURITY, do not allow '- and `-quotes in
5074 # client values.
5075 # Remove all existing '-quotes
5076 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
5077 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
5078 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
5080 # Store current CGI values and remove defaults
5081 $::default_values{$name} = $temp[0];
5083 # Define the CGI variable and its value (in the execute package)
5084 @{$name} = @temp;
5086 # return SUCCES
5087 return 1;
5090 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
5092 my $name = shift || return 0; # The Name
5093 my $default = shift || ""; # The default value
5095 # Protect variables set internally
5096 return 1 if !$name || exists($CGIprotectedVariable{$name});
5098 # Defined values must exist for security
5099 if(!exists($::default_values{$name}))
5101 $::default_values{$name} = $default;
5104 my %temp = ();
5107 # For security:
5108 # Environment values have precedence
5109 if(exists($ENV{$name}))
5111 $temp{""} = $ENV{$name};
5113 # Get name and its values from the query string
5114 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
5116 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
5118 elsif($::default_values{$name} ne "")
5120 $temp{""} = $::default_values{$name};
5124 # SECURITY, do not allow '- and `-quotes in
5125 # client values.
5126 # Remove all existing '-quotes
5127 my $Key;
5128 foreach $Key (keys(%temp))
5130 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
5131 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
5132 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
5135 # Store current CGI values and remove defaults
5136 $::default_values{$name} = $temp{""};
5138 # Define the CGI variable and its value (in the execute package)
5139 %{$name} = ();
5140 my $tempKey;
5141 foreach $tempKey (keys(%temp))
5143 ${$name}{$tempKey} = $temp{$tempKey};
5146 # return SUCCES
5147 return 1;
5151 # SAFEqx('CommandString')
5153 # A special function that is a safe alternative to backtick quotes (and qx//)
5154 # with client-supplied CGI values. All CGI variables are surrounded by
5155 # single ''-quotes (except between existing \'\'-quotes, don't try to be
5156 # too smart). All variables are then interpolated. Simple (@) lists are
5157 # expanded with join(' ', @List), and simple (%) hash tables expanded
5158 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
5159 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
5160 # $@% that should NOT be interpolated must be preceeded by a "\".
5161 # If the first line of the String starts with "#! interpreter", the
5162 # remainder of the string is piped into interpreter (after interpolation), i.e.,
5163 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
5164 # just like in UNIX. There are some problems with quotes. Be carefull in
5165 # using them. You do not have access to the output of any piped (#!)
5166 # process! If you want such access, execute
5167 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
5168 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
5169 # </SCRIPT>.
5171 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
5172 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
5173 sub SAFEqx # ('String') -> result of executing qx/"String"/
5175 my $CommandString = shift;
5176 my $NewCommandString = "";
5178 # Only interpolate when required (check the On/Off switch)
5179 unless($CGIscriptor::NoShellScriptInterpolation)
5182 # Handle existing single quotes around CGI values
5183 while($CommandString =~ /\'[^\']+\'/s)
5185 my $CurrentQuotedString = $&;
5186 $NewCommandString .= $`;
5187 $CommandString = $'; # The remaining string
5188 # Interpolate CGI variables between quotes
5189 # (e.g., '$CGIscriptorResults[-1]')
5190 $CurrentQuotedString =~
5191 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
5192 "$1".eval("$2$3")}else{"$&"}/egs;
5194 # Combine result with previous result
5195 $NewCommandString .= $CurrentQuotedString;
5197 $CommandString = $NewCommandString.$CommandString;
5199 # Select known CGI variables and surround them with single quotes,
5200 # then interpolate all variables
5201 $CommandString =~
5202 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
5203 if($2 eq '$' && exists($main::default_values{$4}))
5204 {"$1\'".eval("\$$3")."\'";}
5205 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
5206 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
5207 keys(%{"$3"});$t}
5208 else{$1.eval("${2}$3");
5209 }/egs;
5211 # Remove backslashed [$@%]
5212 $CommandString =~ s/\\([\$\@\%])/$1/gs;
5215 # Debugging
5216 # return $CommandString;
5218 # Handle UNIX style "#! shell command\n" constructs as
5219 # a pipe into the shell command. The output cannot be tapped.
5220 my $ReturnValue = "";
5221 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
5223 my $ShellScripts = $';
5224 my $ShellCommand = $1;
5225 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
5226 select(INTERPRETER);$| = 1;
5227 print INTERPRETER $ShellScripts;
5228 close(INTERPRETER);
5229 select(STDOUT);$| = 1;
5231 # Shell scripts which are redirected to an existing named pipe.
5232 # The output cannot be tapped.
5233 elsif($CGIscriptor::ShellScriptPIPE)
5235 CGIscriptor::printSAFEqxPIPE($CommandString);
5237 else # Plain ``-backtick execution
5239 # Execute the commands
5240 $ReturnValue = qx/$CommandString/;
5242 return $ReturnValue;
5245 ####################################################################################
5247 # The CGIscriptor PACKAGE
5249 ####################################################################################
5251 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
5252 # "CGIscriptor::"
5254 package CGIscriptor;
5257 # The Interpolation On/Off switch
5258 my $NoShellScriptInterpolation = undef;
5259 # The ShellScript redirection pipe
5260 my $ShellScriptPIPE = undef;
5262 # Open a named PIPE for SAFEqx to receive ALL shell scripts
5263 sub RedirectShellScript # ('CommandString')
5265 my $CommandString = shift || undef;
5267 if($CommandString)
5269 $ShellScriptPIPE = "ShellScriptNamedPipe";
5270 open($ShellScriptPIPE, "|$CommandString")
5271 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
5273 else
5275 close($ShellScriptPIPE);
5276 $ShellScriptPIPE = undef;
5278 return $ShellScriptPIPE;
5281 # Print to redirected shell script pipe
5282 sub printSAFEqxPIPE # ("String") -> print return value
5284 my $String = shift || undef;
5286 select($ShellScriptPIPE); $| = 1;
5287 my $returnvalue = print $ShellScriptPIPE ($String);
5288 select(STDOUT); $| = 1;
5290 return $returnvalue;
5293 # a pointer to CGIexecute::SAFEqx
5294 sub SAFEqx # ('String') -> result of qx/"String"/
5296 my $CommandString = shift;
5297 return CGIexecute::SAFEqx($CommandString);
5301 # a pointer to CGIexecute::defineCGIvariable
5302 sub defineCGIvariable # ($name[, $default]) ->0/1
5304 my $name = shift;
5305 my $default = shift;
5306 return CGIexecute::defineCGIvariable($name, $default);
5310 # a pointer to CGIexecute::defineCGIvariable
5311 sub defineCGIvariableList # ($name[, $default]) ->0/1
5313 my $name = shift;
5314 my $default = shift;
5315 return CGIexecute::defineCGIvariableList($name, $default);
5319 # a pointer to CGIexecute::defineCGIvariable
5320 sub defineCGIvariableHash # ($name[, $default]) ->0/1
5322 my $name = shift;
5323 my $default = shift;
5324 return CGIexecute::defineCGIvariableHash($name, $default);
5328 # Decode URL encoded arguments
5329 sub URLdecode # (URL encoded input) -> string
5331 my $output = "";
5332 my $char;
5333 my $Value;
5334 foreach $Value (@_)
5336 my $EncodedValue = $Value; # Do not change the loop variable
5337 # Convert all "+" to " "
5338 $EncodedValue =~ s/\+/ /g;
5339 # Convert all hexadecimal codes (%FF) to their byte values
5340 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
5342 $output .= $`.chr(hex($1));
5343 $EncodedValue = $';
5345 $output .= $EncodedValue; # The remaining part of $Value
5347 $output;
5350 # Encode arguments as URL codes.
5351 sub URLencode # (input) -> URL encoded string
5353 my $output = "";
5354 my $char;
5355 my $Value;
5356 foreach $Value (@_)
5358 my @CharList = split('', $Value);
5359 foreach $char (@CharList)
5361 if($char =~ /\s/)
5362 { $output .= "+";}
5363 elsif($char =~ /\w\-/)
5364 { $output .= $char;}
5365 else
5367 $output .= uc(sprintf("%%%2.2x", ord($char)));
5371 $output;
5374 # Extract the value of a CGI variable from the URL-encoded $string
5375 # Also extracts the data blocks from a multipart request. Does NOT
5376 # decode the multipart blocks
5377 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
5379 my $ValueName = shift;
5380 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5381 my $ReturnReference = shift || undef;
5382 my $output = "";
5384 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
5386 $output = URLdecode($2);
5387 $$ReturnReference = $' if ref($ReturnReference);
5389 # Get multipart POST or PUT methods
5390 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5392 my $MultipartType = $2;
5393 my $BoundaryString = $3;
5394 # Remove the boundary-string
5395 my $temp = $QueryString;
5396 $temp =~ /^\Q--$BoundaryString\E/m;
5397 $temp = $';
5399 # Identify the newline character(s), this is the first character in $temp
5400 my $NewLine = "\r\n"; # Actually, this IS the correct one
5401 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5403 # Is this correct??? I have to check.
5404 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5405 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5406 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5407 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5410 # search through all data blocks
5411 while($temp =~ /^\Q--$BoundaryString\E/m)
5413 my $DataBlock = $`;
5414 $temp = $';
5415 # Get the empty line after the header
5416 $DataBlock =~ /$NewLine$NewLine/;
5417 $Header = $`;
5418 $output = $';
5419 my $Header = $`;
5420 $output = $';
5422 # Remove newlines from the header
5423 $Header =~ s/$NewLine/ /g;
5425 # Look whether this block is the one you are looking for
5426 # Require the quotes!
5427 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5429 my $i;
5430 for($i=length($NewLine); $i; --$i)
5432 chop($output);
5434 # OK, get out
5435 last;
5437 # reinitialize the output
5438 $output = "";
5440 $$ReturnReference = $temp if ref($ReturnReference);
5442 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
5444 return undef;
5445 $$ReturnReference = undef if ref($ReturnReference);
5447 else
5449 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
5451 return $output;
5455 # Get a list of values for the same ValueName. Uses CGIparseValue
5457 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
5459 my $ValueName = shift;
5460 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5461 my @output = ();
5462 my $RestQueryString;
5463 my $Value;
5464 while($QueryString &&
5465 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
5466 || defined($Value)))
5468 push(@output, $Value);
5469 $QueryString = $RestQueryString; # QueryString is consumed!
5471 # ready, return list with values
5472 return @output;
5475 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
5477 my $ValueName = shift;
5478 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5479 my $RestQueryString;
5480 my %output = ();
5481 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
5483 my $Key = $2;
5484 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
5485 $output{$Key} = $Value;
5486 $QueryString = $RestQueryString; # QueryString is consumed!
5488 # ready, return list with values
5489 return %output;
5492 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
5494 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5495 my $output = "";
5497 $QueryString =~ s/\&/\n/g;
5498 $output = URLdecode($QueryString);
5500 $output;
5503 # Extract the header of a multipart CGI variable from the POST input
5504 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
5506 my $ValueName = shift;
5507 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5508 my $output = "";
5510 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5512 my $MultipartType = $2;
5513 my $BoundaryString = $3;
5514 # Remove the boundary-string
5515 my $temp = $QueryString;
5516 $temp =~ /^\Q--$BoundaryString\E/m;
5517 $temp = $';
5519 # Identify the newline character(s), this is the first character in $temp
5520 my $NewLine = "\r\n"; # Actually, this IS the correct one
5521 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5523 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5524 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5525 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5526 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5529 # search through all data blocks
5530 while($temp =~ /^\Q--$BoundaryString\E/m)
5532 my $DataBlock = $`;
5533 $temp = $';
5534 # Get the empty line after the header
5535 $DataBlock =~ /$NewLine$NewLine/;
5536 $Header = $`;
5537 my $Header = $`;
5539 # Remove newlines from the header
5540 $Header =~ s/$NewLine/ /g;
5542 # Look whether this block is the one you are looking for
5543 # Require the quotes!
5544 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5546 $output = $Header;
5547 last;
5549 # reinitialize the output
5550 $output = "";
5553 return $output;
5557 # Checking variables for security (e.g., file names and email addresses)
5558 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
5559 sub CGIsafeFileName # FileName -> FileName or ""
5561 my $FileName = shift || "";
5562 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
5563 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
5564 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
5565 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
5566 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
5568 return $FileName;
5571 sub CGIsafeEmailAddress # email -> email or ""
5573 my $Email = shift || "";
5574 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
5575 return $Email;
5578 # Get a URL from the web. Needs main::GET_URL($URL) function
5579 # (i.e., curl, snarf, or wget)
5580 sub read_url # ($URL) -> page/file
5582 my $URL = shift || return "";
5584 # Get the commands to read the URL, do NOT add a print command
5585 my $URL_command = main::GET_URL($URL, 1);
5586 # execute the commands, i.e., actually read it
5587 my $URLcontent = CGIexecute->evaluate($URL_command);
5589 # Ready, return the content.
5590 return $URLcontent;
5593 ################################################>>>>>>>>>>Start Remove
5595 # BrowseAllDirs(Directory, indexfile)
5597 # usage:
5598 # <SCRIPT TYPE='text/ssperl'>
5599 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
5600 # </SCRIPT>
5602 # Allows to browse all directories. Stops at '/'. If the directory contains
5603 # an indexfile, eg, index.html, that file will be used instead. Files must match
5604 # the $Pattern, if it is given. Default is
5605 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
5607 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
5609 my $Directory = shift || '/';
5610 my $indexfile = shift || 'index.html';
5611 my $Pattern = shift || '';
5612 $Directory =~ s!/$!!g;
5614 # If the index directory exists, use that one
5615 if(-s "$::CGI_HOME$Directory/$indexfile")
5617 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
5620 # No indexfile, continue
5621 my @DirectoryList = glob("$::CGI_HOME$Directory");
5622 $CurrentDirectory = shift(@DirectoryList);
5623 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
5624 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
5625 print "<h1>";
5626 print "$CurrentDirectory" if $CurrentDirectory;
5627 print "</h1>\n";
5629 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
5630 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
5632 # Print directories
5633 my $file;
5634 print "<pre><ul TYPE='NONE'>\n";
5635 foreach $file (@AllFiles)
5637 next unless -d "$::CGI_HOME$Directory/$file";
5638 # Check whether this file should be visible
5639 next if $::BlockPathAccess &&
5640 "$Directory/$file/" =~ m@$::BlockPathAccess@;
5641 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
5643 print "</ul></pre>\n";
5645 # Print files
5646 print "<pre><ul TYPE='CIRCLE'>\n";
5647 my $TotalSize = 0;
5648 foreach $file (@AllFiles)
5650 next if $file =~ /^\./;
5651 next if -d "$::CGI_HOME$Directory/$file";
5652 next if -l "$::CGI_HOME$Directory/$file";
5653 # Check whether this file should be visible
5654 next if $::BlockPathAccess &&
5655 "$Directory/$file" =~ m@$::BlockPathAccess@;
5657 if(!$Pattern || $file =~ m@$Pattern@)
5659 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
5660 my $Size = -s "$::CGI_HOME$Directory/$file";
5661 $Size = sprintf("%6.0F kB", $Size/1024);
5662 my $Type = `file $::CGI_HOME$Directory/$file`;
5663 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
5664 chomp($Type);
5666 print "<li>";
5667 print "<a href='$Directory/$file'>";
5668 printf("%-40s", "$file</a>");
5669 print "\t$Size\t$Date\t$Type";
5670 print "</li>\n";
5673 print "</ul></pre>";
5675 return 1;
5679 ################################################
5681 # BrowseDirs(RootDirectory [, Pattern, Start])
5683 # usage:
5684 # <SCRIPT TYPE='text/ssperl'>
5685 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
5686 # </SCRIPT>
5688 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
5689 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
5690 # Only files which fit /$Pattern/ and directories are displayed.
5691 # Directories down or up the directory tree are supplied with a
5692 # GET request with the name of the CGI variable in the fourth argument (default
5693 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
5694 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
5696 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
5698 my $RootDirectory = shift; # || return 0;
5699 my $Pattern = shift || '\S';
5700 my $Start = shift || "";
5701 my $CGIvariable = shift || "BROWSEDIRS";
5702 my $HTTPserver = shift || '';
5704 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
5705 $Start =~ s@//+@/@g;
5706 $Start =~ s@[^/]+/\.\.@@ig;
5707 $Start =~ s@^\.\.@@ig;
5708 $Start =~ s@/\.$@@ig;
5709 $Start =~ s!/+$!!g;
5710 $Start .= "/" if $Start;
5712 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
5713 $CurrentDirectory = shift(@Directory);
5714 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
5715 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
5716 print "<h1>";
5717 print "$CurrentDirectory" if $CurrentDirectory;
5718 print "</h1>\n";
5719 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
5720 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
5722 # Print directories
5723 my $file;
5724 print "<pre><ul TYPE='NONE'>\n";
5725 foreach $file (@AllFiles)
5727 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
5728 # Check whether this file should be visible
5729 next if $::BlockPathAccess &&
5730 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
5732 my $NewURL = $Start ? "$Start$file" : $file;
5733 $NewURL = CGIscriptor::URLencode($NewURL);
5734 print "<dt><a href='";
5735 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
5736 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
5738 print "</ul></pre>\n";
5740 # Print files
5741 print "<pre><ul TYPE='CIRCLE'>\n";
5742 my $TotalSize = 0;
5743 foreach $file (@AllFiles)
5745 next if $file =~ /^\./;
5746 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
5747 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
5748 # Check whether this file should be visible
5749 next if $::BlockPathAccess &&
5750 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
5752 if($file =~ m@$Pattern@)
5754 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
5755 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
5756 $Size = sprintf("%6.0F kB", $Size/1024);
5757 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
5758 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
5759 chomp($Type);
5761 print "<li>";
5762 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
5764 print "<a href='$RootDirectory/$Start$file'>";
5766 elsif($HTTPserver)
5768 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
5770 printf("%-40s", "$file</a>") if $HTTPserver;
5771 printf("%-40s", "$file") unless $HTTPserver;
5772 print "\t$Size\t$Date\t$Type";
5773 print "</li>\n";
5776 print "</ul></pre>";
5778 return 1;
5782 # ListDocs(Pattern [,ListType])
5784 # usage:
5785 # <SCRIPT TYPE=text/ssperl>
5786 # CGIscriptor::ListDocs("/*", "dl");
5787 # </SCRIPT>
5789 # This subroutine is very usefull to manage collections of independent
5790 # documents. The resulting list will display the tree-like directory
5791 # structure. If this routine is too slow for online use, you can
5792 # store the result and use a link to that stored file.
5794 # List HTML and Text files with title and first header (HTML)
5795 # or filename and first meaningfull line (general text files).
5796 # The listing starts at the ServerRoot directory. Directories are
5797 # listed recursively.
5799 # You can change the list type (default is dl).
5800 # e.g.,
5801 # <dt><a href=<file.html>>title</a>
5802 # <dd>First Header
5803 # <dt><a href=<file.txt>>file.txt</a>
5804 # <dd>First meaningfull line of text
5806 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
5808 my $Pattern = shift;
5809 $Pattern =~ /\*/;
5810 my $ListType = shift || "dl";
5811 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
5812 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
5813 my @FileList = glob("$::CGI_HOME$Pattern");
5814 my ($FileName, $Path, $Link);
5816 # Print List markers
5817 print "<$ListType>\n";
5819 # Glob all files
5820 File: foreach $FileName (@FileList)
5822 # Check whether this file should be visible
5823 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
5825 # Recursively list files in all directories
5826 if(-d $FileName)
5828 $FileName =~ m@([^/]*)$@;
5829 my $DirName = $1;
5830 print "<$Prefix>$DirName\n";
5831 $Pattern =~ m@([^/]*)$@;
5832 &ListDocs("$`$DirName/$1", $ListType);
5833 next;
5835 # Use textfiles
5836 elsif(-T "$FileName")
5838 open(TextFile, $FileName) || next;
5840 # Ignore all other file types
5841 else
5842 { next;};
5844 # Get file path for link
5845 $FileName =~ /$::CGI_HOME/;
5846 print "<$Prefix><a href=$URL_root$'>";
5847 # Initialize all variables
5848 my $Line = "";
5849 my $TitleFound = 0;
5850 my $Caption = "";
5851 my $Title = "";
5852 # Read file and step through
5853 while(<TextFile>)
5855 chop $_;
5856 $Line = $_;
5857 # HTML files
5858 if($FileName =~ /\.ht[a-zA-Z]*$/i)
5860 # Catch Title
5861 while(!$Title)
5863 if($Line =~ m@<title>([^<]*)</title>@i)
5865 $Title = $1;
5866 $Line = $';
5868 else
5870 $Line .= <TextFile> || goto Print;
5871 chop $Line;
5874 # Catch First Header
5875 while(!$Caption)
5877 if($Line =~ m@</h1>@i)
5879 $Caption = $`;
5880 $Line = $';
5881 $Caption =~ m@<h1>@i;
5882 $Caption = $';
5883 $Line = $`.$Caption.$Line;
5885 else
5887 $Line .= <TextFile> || goto Print;
5888 chop $Line;
5892 # Other text files
5893 else
5895 # Title equals file name
5896 $FileName =~ /([^\/]+)$/;
5897 $Title = $1;
5898 # Catch equals First Meaningfull line
5899 while(!$Caption)
5901 if($Line =~ /[A-Z]/ &&
5902 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
5903 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
5905 $Line =~ s/\<[^\>]+\>//g;
5906 $Caption = $Line;
5908 else
5910 $Line = <TextFile> || goto Print;
5914 Print: # Print title and subject
5915 print "$Title</a>\n";
5916 print "<dd>$Caption\n" if $ListType eq "dl";
5917 $TitleFound = 0;
5918 $Caption = "";
5919 close TextFile;
5920 next File;
5923 # Print Closing List Marker
5924 print "</$ListType>\n";
5925 ""; # Empty return value
5929 # HTMLdocTree(Pattern [,ListType])
5931 # usage:
5932 # <SCRIPT TYPE=text/ssperl>
5933 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
5934 # </SCRIPT>
5936 # The following subroutine is very usefull for checking large document
5937 # trees. Starting from the root (s), it reads all files and prints out
5938 # a nested list of links to all attached files. Non-existing or misplaced
5939 # files are flagged. This is quite a file-i/o intensive routine
5940 # so you would not like it to be accessible to everyone. If you want to
5941 # use the result, save the whole resulting page to disk and use a link
5942 # to this file.
5944 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
5945 # with links to *local* files (i.e., only links to the local server are
5946 # followed). The list entries are the document titles.
5947 # If the list type is <dl>, the first <H1> header is used too.
5948 # For each file matching the pattern, a list is made recursively of all
5949 # HTML documents that are linked from it and are stored in the same directory
5950 # or a sub-directory. Warnings are given for missing files.
5951 # The listing starts for the ServerRoot directory.
5952 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
5954 %LinkUsed = ();
5956 sub HTMLdocTree # ($Pattern [, listtype])
5957 # e.g., ("/Welcome.html", [, "ul"])
5959 my $Pattern = shift;
5960 my $ListType = shift || "dl";
5961 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
5962 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
5963 my ($Filename, $Path, $Link);
5964 my %LocalLinks = {};
5966 # Read files (glob them for expansion of wildcards)
5967 my @FileList = glob("$::CGI_HOME$Pattern");
5968 foreach $Path (@FileList)
5970 # Get URL_path
5971 $Path =~ /$::CGI_HOME/;
5972 my $URL_path = $';
5973 # Check whether this file should be visible
5974 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
5976 my $Title = $URL_path;
5977 my $Caption = "";
5978 # Current file should not be used again
5979 ++$LinkUsed{$URL_path};
5980 # Open HTML doc
5981 unless(open(TextFile, $Path))
5983 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
5984 next;
5986 while(<TextFile>)
5988 chop $_;
5989 $Line = $_;
5990 # Catch Title
5991 while($Line =~ m@<title>@i)
5993 if($Line =~ m@<title>([^<]*)</title>@i)
5995 $Title = $1;
5996 $Line = $';
5998 else
6000 $Line .= <TextFile>;
6001 chop $Line;
6004 # Catch First Header
6005 while(!$Caption && $Line =~ m@<h1>@i)
6007 if($Line =~ m@</h[1-9]>@i)
6009 $Caption = $`;
6010 $Line = $';
6011 $Caption =~ m@<h1>@i;
6012 $Caption = $';
6013 $Line = $`.$Caption.$Line;
6015 else
6017 $Line .= <TextFile>;
6018 chop $Line;
6021 # Catch and print Links
6022 while($Line =~ m@<a href\=([^>]*)>@i)
6024 $Link = $1;
6025 $Line = $';
6026 # Remove quotes
6027 $Link =~ s/\"//g;
6028 # Remove extras
6029 $Link =~ s/[\#\?].*$//g;
6030 # Remove Servername
6031 if($Link =~ m@(http://|^)@i)
6033 $Link = $';
6034 # Only build tree for current server
6035 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
6036 # Remove server name and port
6037 $Link =~ s@^[^\/]*@@g;
6039 # Store the current link
6040 next if $LinkUsed{$Link} || $Link eq $URL_path;
6041 ++$LinkUsed{$Link};
6042 ++$LocalLinks{$Link};
6046 close TextFile;
6047 print "<$Prefix>";
6048 print "<a href=http://";
6049 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
6050 print "$Title</a>\n";
6051 print "<br>$Caption\n"
6052 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
6053 print "<$ListType>\n";
6054 foreach $Link (keys(%LocalLinks))
6056 &HTMLdocTree($Link, $ListType);
6058 print "</$ListType>\n";
6062 ###########################<<<<<<<<<<End Remove
6064 # Make require happy
6067 =head1 NAME
6069 CGIscriptor -
6071 =head1 DESCRIPTION
6073 A flexible HTML 4 compliant script/module for CGI-aware
6074 embeded Perl, shell-scripts, and other scripting languages,
6075 executed at the server side.
6077 =head1 README
6079 Executes embeded Perl code in HTML pages with easy
6080 access to CGI variables. Also processes embeded shell
6081 scripts and scripts in any other language with an
6082 interactive interpreter (e.g., in-line Python, Tcl,
6083 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
6084 sh, bash, csh, ksh).
6086 CGIscriptor is very flexible and hides all the specifics
6087 and idiosyncrasies of correct output and CGI coding and naming.
6088 CGIscriptor complies with the W3C HTML 4.0 recommendations.
6090 This Perl program will run on any WWW server that runs
6091 Perl scripts, just add a line like the following to your
6092 srm.conf file (Apache example):
6094 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
6096 URL's that refer to http://www.your.address/SHTML/... will
6097 now be handled by CGIscriptor.pl, which can use a private
6098 directory tree (default is the DOCUMENT_ROOT directory tree,
6099 but it can be anywhere).
6101 =head1 PREREQUISITES
6104 =head1 COREQUISITES
6107 =pod OSNAMES
6109 Linux, *BSD, *nix, MS WinXP
6111 =pod SCRIPT CATEGORIES
6113 Servers
6117 =cut