Added link to XKCD password comic to Change PAssword page
[CGIscriptor.git] / CGIscriptor.pl
blob86cd05654251d8819f55077183b6a7830b3487fe
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 # 06 Jun 2012 - Added IP only session types after login.
64 # 31 May 2012 - Session ticket system added for handling login sessions.
65 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
66 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
67 # correctly.
68 # 22 May 2012 - Added Access control with Session Tickets linked to
69 # IP Address and PATH_INFO.
70 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
71 # Will link to current base URL when the HTTP server is '.' or '~'
72 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
73 # CGIsafeFileName does not accept filenames starting with '-'
74 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
75 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
76 # 28 Jan 2005 - Added a file selector to performTranslation.
77 # Changed %TranslationTable to @TranslationTable
78 # and patterns to lists.
79 # 27 Jan 2005 - Added a %TranslationTable with associated
80 # performTranslation(\$text) function to allow
81 # run changes in the web pages. Say, to translate
82 # legacy pages with <%=...%> delimiters to the new
83 # <SCRIPT TYPE=..></SCRIPT> format.
84 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
85 # Other Languages Code.
86 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
87 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
88 # language call
89 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
90 # 20 May 2003 - Added a --help switch to print the manual.
91 # 06 Mar 2003 - Adapted the blurb at the end of the file.
92 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
93 # "die" calls. Also "enhanced" the STDERR printout.
94 # 10 Feb 2003 - Split off the reading of the POST part of a query
95 # from Initialize_output. This was suggested by Gerd Franke
96 # to allow for the catching of the file_path using a
97 # POST based lookup. That is, he needed the POST part
98 # to change the file_path.
99 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
100 # 03 Feb 2003 - \1 better written as $1 in
101 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
102 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
103 # added:
104 # $ServerScriptContentClass = "ssperl";
105 # changed in ProcessFile():
106 # unless(($CurrentContentType =~
107 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
108 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
109 # Update comments and documentation.
110 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
111 # Files didn't work.
112 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
113 # Also added a note that RawFilePattern can be a
114 # complete file name.
115 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
116 # switch to prepending or to appending the content
117 # of the SRC attribute. Default is prefixing. You
118 # can add as many of these switches as you like.
119 # 13 Mar 2002 - Do not search for tag content if a tag closes with
120 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
121 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
122 # (replaces wget).
123 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
124 # (i.o. a list context). This is necessary for binary results.
125 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
126 # changed the order of if/elsif to allow removing these
127 # conditions in systems with broken -T functions.
128 # (I also removed a spurious ')' bracket)
129 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
130 # to support binary files.
131 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
132 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
133 # stipped from the path.
134 # 15 Jan 2002 - Version 2.2
135 # 15 Jan 2002 - Debugged and completed URL support (including
136 # CGIscriptor::read_url() function)
137 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
138 # with the main::GET_URL function. Uses wget -O underlying.
139 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
140 # (i.e., my $NewDirective = "";) to clear old output
141 # (this was a realy anoying bug).
142 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
143 # tags that assign the body text as-is (literally)
144 # to $varname. Allows standard HTML-tools to handle
145 # Cascading Style Sheet templates. This implements a
146 # design by Gerd Franke (franke@roo.de).
147 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
148 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
149 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
150 # detect unwanted indexing of TAR files by webcrawlers.
151 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
152 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
153 # processed file type. Was processed via BinaryMapFile.
154 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
155 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
156 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
157 # be manipulated to achieve a "Sub Root".
158 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
159 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
160 # and debugging (it now works).
161 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
162 # hashes,instead of replacing existing hashes.
163 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
164 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
165 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
166 # CGIparseValueHash (used with structured parameters).
167 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
168 # Will collect all <CGI-partial-name><key>=value pairs in
169 # $<CGI-partial-name>{<key>} = value;
170 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
171 # 09 Aug 2000 - Added support for non-filesystem input by way of
172 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
173 # environment variables.
174 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
175 # This allows CGIscriptor to be used in pipes.
176 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
177 # in an HTTP request (i.e., in a web server).
178 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
179 # is not HTTP or another protocol. Changed the default
180 # source directory to DOCUMENT_ROOT (i.o. the incorrect
181 # SERVER_ROOT).
182 # 24 Jul 2000 - -slim Command-line argument added to remove all
183 # comments, security, etc.. Updated documentation.
184 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
185 # execution of all <META> and <SCRIPT> code
186 # conditional.
187 # 05 Jul 2000 - Rewrote and isolated the code for extracting
188 # quoted items from CGI and SRC attributes.
189 # Now all attributes expect the same set of
190 # quotes: '', "", ``, (), {}, [] and the same
191 # preceded by a \, e.g., "\((aap)\)" will be
192 # extracted as "(aap)".
193 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
194 # name-space (i.o. by evaluation) from
195 # CGI attributes to prevent interference with
196 # the processing for non perl scripts.
197 # Changed CGIparseValueList to prevent runaway
198 # loops.
199 # 16 Jun 2000 - Added a direct (interpolated) display mode
200 # (text/ssdisplay) and a user log mode
201 # (text/sslogfile).
202 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
203 # allow large string output.
204 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
205 # remove all control characters. Changed Interpreter
206 # initialization to shrub interpolated CGI parameters.
207 # Added 'text/ssmailto' interpreter script.
208 # 22 May 2000 - Changed some of the comments
209 # 09 May 2000 - Added list extraction for CGI parameters with
210 # CGIparseValueList (used with multiple selections).
211 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
212 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
213 # 27 Apr 2000 - Updated documentation to reflect changes.
214 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
215 # interpreters.
216 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
217 # 27 Apr 2000 - Gprolog, M4 support added.
218 # 26 Apr 2000 - Lisp (rep) support added.
219 # 20 Apr 2000 - Use of external interpreters now functional.
220 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
221 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
222 # the use of $#foo, i.e., I changed
223 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
224 # to
225 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
226 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
227 # things like, e.g., CVS information in CVS subtrees
228 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
229 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
230 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
231 # over a "burried" BinaryMapFile.
232 # 04 Oct 1999 - Added two functions to check file names and email addresses
233 # (CGIscriptor::CGIsafeFileName and
234 # CGIscriptor::CGIsafeEmailAddress)
235 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
236 # to allow LONG posts.
237 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
238 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
239 # this directory exists.
240 # 07 June 1999 - Limit file-pattern matching to LAST extension
241 # 04 June 1999 - Default text/html content type is printed only once.
242 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
243 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
244 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
245 # Changed the processing of the Accept and Reject file.
246 # Added a full expression evaluation to Access Control.
247 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
248 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
249 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
250 # Requests are serviced by "Handle_Request()". CGIscriptor
251 # can still be called as a isolated perl script and a shell
252 # command.
253 # Changed the "factory default setting" so that it will run
254 # from the DOCUMENT_ROOT directory.
255 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
256 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
257 # to subroutines to change CGIscriptor into a module.
258 # Added mapping to process unsupported file types (e.g., binary
259 # pictures). See $BinaryMapFile.
260 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
261 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@uva.nl)
262 # Removed a double setting of filepatterns and maximum query
263 # size. Changed email address. Removed some typos from the
264 # comments.
265 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
266 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@uva.nl)
267 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
268 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
271 # Known Bugs:
273 # 23 Mar 2000
274 # It is not possible to use operators or variables to construct variable names,
275 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
276 # will indeed work. If someone could tell me why, I would be obliged.
279 ############################################################################
281 # OBLIGATORY USER CONFIGURATION
283 # Configure the directories where all user files can be found (this
284 # is the equivalent of the server root directory of a WWW-server).
285 # These directories can be located ANYWHERE. For security reasons, it is
286 # better to locate them outside the WWW-tree of your HTTP server, unless
287 # CGIscripter handles ALL requests.
289 # For convenience, the defaults are set to the root of the WWW server.
290 # However, this might not be safe!
292 # ~/ text files
293 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
294 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
295 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
297 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
298 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
301 # Just enter your own directory path here
302 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
304 # ./ script files (recommended to be different from the previous)
305 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
306 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
308 # End of obligatory user configuration
309 # (note: there is more non-essential user configuration below)
311 ############################################################################
313 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
315 # Script content-types: TYPE="Content-type" (user defined mime-type)
316 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
317 # CSS require a simple class
318 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
319 $' : "ssperl"; # Server Side Perl CSS classes
321 $ShellScriptContentType = "text/osshell"; # OS shell scripts
322 # # (Server Side perl ``-execution)
324 # Accessible file patterns, block any request that doesn't match.
325 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
326 # (\. is used in regexp)
327 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
328 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt|.js";
330 # The table with the content type MIME types
331 # (allows to differentiate MIME types, if needed)
332 %ContentTypeTable =
334 '.html' => 'text/html',
335 '.shtml' => 'text/html',
336 '.htm' => 'text/html',
337 '.xml' => 'text/xml',
338 '.txt' => 'text/plain',
339 '.js' => 'text/plain'
343 # File pattern post-processing
344 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
346 # SHAsum command needed for Authorization and Login
347 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
348 my $shasum = "shasum -a 256";
349 if(qx{uname} =~ /Darwin/)
351 $shasum = "shasum-5.12 -a 256" unless `which shasum`;
353 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
354 $ENV{"SHASUMCMD"} = $SHASUMCMD;
355 my $RANDOMHASHCMD = 'dd bs=1 count=64 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
356 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
358 # File patterns of files which are handled by session tickets.
359 %TicketRequiredPatterns = (
360 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
362 # Used to set cookies, only session cookies supported
363 my %SETCOOKIELIST = ();
364 # Session Ticket Directory: Private/.Sessions
365 # Password Directory: Private/.Passwords
366 # Login page (url path): /Private/Login.html
367 # Expiration time (s): +3600
368 # +<seconds> = relative time <seconds> is absolute date-time
370 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
371 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
372 $RawFilePattern = ".xmr";
373 # (In principle, this could contain a full file specification, e.g.,
374 # ".xmr|relocated.html")
376 # Raw File pattern post-processing
377 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
379 # Server protocols for which "Content-type: text/html\n\n" should be printed
380 # (you should not bother with these, except for HTTP, they are mostly imaginary)
381 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
383 # Block access to all (sub-) paths and directories that match the
384 # following (URL) path (is used as:
385 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
386 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
388 # All (blocked) other file-types can be mapped to a single "binary-file"
389 # processor (a kind of pseudo-file path). This can either be an error
390 # message (e.g., "illegal file") or contain a script that serves binary
391 # files.
392 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
393 $BinaryMapFile = "/BinaryMapFile.xmr";
394 # Allow for the addition of a CGIscriptor directory
395 # Note that a BinaryMapFile in the root "~/" directory has precedence
396 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
397 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
398 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
401 # List of all characters that are allowed in file names and paths.
402 # All requests containing illegal characters are blocked. This
403 # blocks most tricks (e.g., adding "\000", "\n", or other control
404 # characters, also blocks URI's using %FF)
405 # THIS IS A SECURITY FEATURE
406 # (this is also used to parse filenames in SRC= features, note the
407 # '-quotes, they are essential)
408 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
410 # Maximum size of the Query (number of characters clients can send
411 # covers both GET & POST combined)
412 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
415 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
416 # (returns a string with the PERL code to transfer the URL contents, e.g.,
417 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
418 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
419 # Be sure to handle <BASE HREF='URL'> and allow BOTH
420 # direct printing GET_URL($URL [, 0]) and extracting the content of
421 # the $URL for post-processing GET_URL($URL, 1).
422 # You get the WHOLE file, including HTML header.
423 # The shell command Use $URL where the URL should go
424 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
425 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
426 #my $GET_URL_shell_command = 'snarf $URL -';
427 #my $GET_URL_shell_command = 'curl $URL';
429 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
431 my $URL = shift || return;
432 my $ValueNotPrint = shift || 0;
434 # Check URL for illegal characters
435 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
437 # Include URL in final command
438 my $CurrentCommand = $GET_URL_shell_command;
439 $CurrentCommand =~ s/\$URL/$URL/g;
441 # Print to STDOUT or return a value
442 my $BlockPrint = "print STDOUT ";
443 $BlockPrint = "" if $ValueNotPrint;
445 my $Commands = <<"GETURLCODE";
446 # Get URL
448 my \$Page = "";
450 # Simple, using shell command
451 \$Page = SAFEqx('$CurrentCommand');
453 # Add a BASE tage to the header
454 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
456 # Print the URL value, or return it as a value
457 $BlockPrint\$Page;
459 GETURLCODE
460 return $Commands;
463 # As files can get rather large (and binary), you might want to use
464 # some more intelligent reading procedure, e.g.,
465 # Direct Perl
466 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
467 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
468 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
469 # my \$text = "";
470 # while(sysread(URLHANDLE,\$text, 1024) > 0)
472 # \$Page .= \$text;
473 # };
474 # close(URLHANDLE) || die "\$!";
475 # However, this doesn't work with the CGIexecute->evaluate() function.
476 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
478 # You can forget the next two variables, they are only needed when
479 # you don't want to use a regular file system (i.e., with open)
480 # but use some kind of database/RAM image for accessing (generating)
481 # the data.
483 # Name of the environment variable that contains the file contents
484 # when reading directly from Database/RAM. When this environment variable,
485 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
486 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
487 # Uncomment the following if you want to force the use of the data access code
488 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
490 # Name of the environment variable that contains the RAM access perl
491 # code needed to read additional "files", i.e.,
492 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
493 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
494 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
496 # You can, of course, fill this yourself, e.g.,
497 # $ENV{$CGI_DATA_ACCESS_CODE} =
498 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
501 # DEBUGGING
503 # Suppress error messages, this can be changed for debugging or error-logging
504 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
506 # SPECIAL: Remove Comments, security, etc. if the command line is
507 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
508 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
510 # If CGIscriptor is used from the command line, the command line
511 # arguments are interpreted as the file (1st) and the Query String (rest).
512 # Get the arguments
513 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
514 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
517 # Handle bail-outs in a user definable way.
518 # Catch Die and replace it with your own function.
519 # Ends with a call to "die $_[0];"
521 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
523 my $ErrorCode = shift;
524 my $ErrorMessage = shift;
526 # Place your own reporting functions here
528 # Now, kill everything (default)
529 print STDERR "$ErrorCode: $ErrorMessage\n";
530 die $ErrorMessage;
534 # End of optional user configuration
535 # (note: there is more non-essential user configuration below)
537 if(grep(/\-\-help/i, @ARGV))
539 print << 'ENDOFPREHELPTEXT2';
541 ###############################################################################
543 # Author and Copyright (c):
544 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
545 # NKI-AVL Amsterdam
546 # r.v.son@nki.nl
547 # Institute of Phonetic Sciences & IFOTT/ACLS
548 # University of Amsterdam
549 # Email: R.J.J.H.vanSon@gmail.com
550 # Email: R.J.J.H.vanSon@uva.nl
551 # WWW : http://www.fon.hum.uva.nl/rob/
553 # License for use and disclaimers
555 # CGIscriptor merges plain ASCII HTML files transparantly
556 # with CGI variables, in-line PERL code, shell commands,
557 # and executable scripts in other scripting languages.
559 # This program is free software; you can redistribute it and/or
560 # modify it under the terms of the GNU General Public License
561 # as published by the Free Software Foundation; either version 2
562 # of the License, or (at your option) any later version.
564 # This program is distributed in the hope that it will be useful,
565 # but WITHOUT ANY WARRANTY; without even the implied warranty of
566 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
567 # GNU General Public License for more details.
569 # You should have received a copy of the GNU General Public License
570 # along with this program; if not, write to the Free Software
571 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
574 # Contributors:
575 # Rob van Son (R.J.J.H.vanSon@uva.nl)
576 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
578 #######################################################
579 ENDOFPREHELPTEXT2
581 #######################################################>>>>>>>>>>Start Remove
583 # You can skip the following code, it is an auto-splice
584 # procedure.
586 # Construct a slimmed down version of CGIscriptor
587 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
589 if($TrimDownCGIscriptor)
591 open(CGISCRIPTOR, "<CGIscriptor.pl")
592 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
593 my $SKIPtext = 0;
594 my $SKIPComments = 0;
596 while(<CGISCRIPTOR>)
598 my $SKIPline = 0;
600 ++$LineCount;
602 # Start of SKIP text
603 $SKIPtext = 1 if /[\>]{10}Start Remove/;
604 $SKIPComments = 1 if $SKIPtext == 1;
606 # Skip this line?
607 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
609 ++$PrintCount unless $SKIPline;
611 print STDOUT $_ unless $SKIPline;
613 # End of SKIP text ?
614 $SKIPtext = 0 if /[\<]{10}End Remove/;
616 # Ready!
617 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
618 exit;
621 #######################################################
623 if(grep(/\-\-help/i, @ARGV))
625 print << 'ENDOFHELPTEXT';
627 # HYPE
629 # CGIscriptor merges plain ASCII HTML files transparantly and safely
630 # with CGI variables, in-line PERL code, shell commands, and executable
631 # scripts in many languages (on-line and real-time). It combines the
632 # "ease of use" of HTML files with the versatillity of specialized
633 # scripts and PERL programs. It hides all the specifics and
634 # idiosyncrasies of correct output and CGI coding and naming. Scripts
635 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
636 # files can be ignorant of scripts and the associated values. CGIscriptor
637 # complies with the W3C HTML 4.0 recommendations.
638 # In addition to its use as a WWW embeded CGI processor, it can
639 # be used as a command-line document preprocessor (text-filter).
641 # THIS IS HOW IT WORKS
643 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
644 # using any required CGIparameters and environment variables. It
645 # is optimized to transparantly process HTML files inside a WWW server.
646 # The native language is Perl, but many other scripting languages
647 # can be used.
649 # CGIscriptor reads text files from the requested input file (i.e., from
650 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
651 # client requesting the service) preceded by the obligatory
652 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
653 # (except for "raw" files which supply their own Content-type message
654 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
656 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
658 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
659 # PERL script
660 # </SCRIPT>
662 # or
664 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
665 # OS Shell script
666 # </SCRIPT>
668 # construct (anything between []-brackets is optional, other MIME-types
669 # and scripting languages are supported), the embedded script is removed
670 # and both the contents of the source file (i.e., "do 'ScriptSource'")
671 # AND the script are evaluated as a PERL program (i.e., by eval()),
672 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
673 # interpreter. The output of the eval() function takes the place of the
674 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
675 # parameters declared by the CGI attribute are available as simple perl
676 # variables, and can subsequently be made available as variables to other
677 # scripting languages (e.g., bash, python, or lisp).
679 # Example: printing "Hello World"
680 # <HTML><HEAD><TITLE>Hello World</TITLE>
681 # <BODY>
682 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
683 # </BODY></HTML>
685 # Save this in a file, hello.html, in the directory you indicated with
686 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
687 # (or to whatever name you use as an alias for CGIscriptor.pl).
688 # This is realy ALL you need to do to get going.
690 # You can use any values that are delivered in CGI-compliant form (i.e.,
691 # the "?name=value" type URL additions) transparently as "$name" variables
692 # in your scripts IFF you have declared them in the CGI attribute of
693 # a META or SCRIPT tag before e.g.:
694 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
695 # [SRC='ScriptSource']">
696 # or
697 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
698 # [SRC='ScriptSource']>
699 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
700 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
701 # The CGIscriptor script allows you to write ordinary HTML files which will
702 # include dynamic CGI aware (run time) features, such as on-line answers
703 # to specific CGI requests, queries, or the results of calculations.
705 # For example, if you wanted to answer questions of clients, you could write
706 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
707 # that prints out the answer to requests given as arguments. You then write
708 # an HTML page "Respond.html" containing the following fragment:
710 # <center>
711 # The Answer to your question
712 # <META CONTENT="text/ssperl; CGI='$Question'">
713 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
714 # is
715 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
716 # AnswerQuestion($Question);
717 # </SCRIPT></h3>
718 # </center>
719 # <FORM ACTION=Respond.html METHOD=GET>
720 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
721 # <INPUT TYPE=SUBMIT VALUE="Ask">
722 # </FORM>
724 # The output could look like the following (in HTML-speak):
726 # <CENTER>
727 # The Answer to your question
728 # <h3>What is the capital of the Netherlands?</h3>
729 # is
730 # <h3>Amsterdam</h3>
731 # </CENTER>
732 # <FORM ACTION=Respond.html METHOD=GET>
733 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
734 # <INPUT TYPE=SUBMIT VALUE="Ask">
736 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
737 # it just prints out answers to arguments. Likewise, the text has no
738 # provisions for scripts or CGI like constructs. Also, it is completely
739 # trivial to extend this "program" to use the "Answer" later in the page
740 # to call up other information or pictures/sounds. The final text never
741 # shows any cue as to what the original "source" looked like, i.e.,
742 # where you store your scripts and how they are called.
744 # There are some extra's. The argument of the files called in a SRC= tag
745 # can access the CGI variables declared in the preceding META tag from
746 # the @ARGV array. Executable files are called as:
747 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
748 # The files called from SRC can even be (CGIscriptor) html files which are
749 # processed in-line. Furthermore, the SRC= tag can contain a perl block
750 # that is evaluated. That is,
751 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
752 # will result in the evaluation of "print do {$Question};" and the VALUE
753 # of $Question will be printed. Note that these "SRC-blocks" can be
754 # preceded and followed by other file names, but only a single block is
755 # allowed in a SRC= tag.
757 # One of the major hassles of dynamic WWW pages is the fact that several
758 # mutually incompatible browsers and platforms must be supported. For example,
759 # the way sound is played automatically is different for Netscape and
760 # Internet Explorer, and for each browser it is different again on
761 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
762 # (form-) values to construct email addresses, file names, or database
763 # queries. All Apache WWW-server exploits reported in the media are
764 # based on faulty CGI-scripts that didn't check their user-data properly.
766 # There is no panacee for these problems, but a lot of work and problems
767 # can be saved by allowing easy and transparent control over which
768 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
769 # supplies such a method in the form of a pair of attributes:
770 # IF='...condition..' and UNLESS='...condition...'. When added to a
771 # script tag, the whole block (including the SRC attribute) will be
772 # ignored if the condition is false (IF) or true (UNLESS).
773 # For example, the following block will NOT be evaluated if the value
774 # of the CGI variable FILENAME is NOT a valid filename:
776 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
777 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
778 # .....
779 # </SCRIPT>
781 # (the function CGIsafeFileName(String) returns an empty string ("")
782 # if the String argument is not a valid filename).
783 # The UNLESS attribute is the mirror image of IF.
785 # A user manual follows the HTML 4 and security paragraphs below.
787 ##########################################################################
789 # HTML 4 compliance
791 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
792 # the W3C. This means that any software to manage Web sites will be able
793 # to handle CGIscriptor files, as will web agents.
795 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
796 # script type is indicated with TYPE="mime-type", the LANGUAGE
797 # feature is ignored, and a SRC feature is implemented. All CGI specific
798 # features are delegated to the CGI attribute.
800 # However, the behavior deviates from the W3C recommendations at some
801 # points. Most notably:
802 # 0- The scripts are executed at the server side, invissible to the
803 # client (i.e., the browser)
804 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
805 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
806 # when a SRC feature is present.
807 # 3- The SRC attribute reads a list of files.
808 # 4- The files in a SRC attribute are processed according to file type.
809 # 5- The SRC attribute evaluates inline Perl code.
810 # 6- Processed META, DIV, INS tags are removed from the output
811 # document.
812 # 7- All attributes of the processed META tags, except CONTENT, are ignored
813 # (i.e., deleted from the output).
814 # 8- META tags can be placed ANYWHERE in the document.
815 # 9- Through the SRC feature, META tags can have visible output in the
816 # document.
817 # 10- The CGI attribute that declares CGI parameters, can be used
818 # inside the <SCRIPT> tag.
819 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
820 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
821 # \{\}, \[\].
822 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
823 # 13- <DIV> tags cannot be nested, DIV tags are not
824 # rendered with new-lines.
825 # 14- The XML style <TAG .... /> is recognized and handled correctly.
826 # (i.e., no content is processed)
828 # The reasons for these choices are:
829 # You can still write completely HTML4 compliant documents. CGIscriptor
830 # will not force you to write "deviant" code. However, it allows you to
831 # do so (which is, in fact, just as bad). The prime design principle
832 # was to allow users to include plain Perl code. The code itself should
833 # be "enhancement free". Therefore, extra features were needed to
834 # supply easy access to CGI and Web site components. For security
835 # reasons these have to be declared explicitly. The SRC feature
836 # transparently manages access to external files, especially the safe
837 # use of executable files.
838 # The CGI attribute handles the declarations of external (CGI) variables
839 # in the SCRIPT and META tag's.
840 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
841 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
843 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
844 # evaluation) were build into the META and SCRIPT tags is part laziness,
845 # part security. The SRC blocks allows more compact documents and easier
846 # debugging. The values of the CGI variables can be immediately screened
847 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
848 # email addresses and file names), and a few commands can be called
849 # without having to add another Perl TAG pair. This is especially important
850 # for documents that require the use of other (more restricted) "scripting"
851 # languages and facilities that lag transparent control structures.
853 ##########################################################################
855 # SECURITY
857 # Your WWW site is a few keystrokes away from a few hundred million internet
858 # users. A fair percentage of these users knows more about your computer
859 # than you do. And some of these just might have bad intentions.
861 # To ensure uncompromized operation of your server and platform, several
862 # features are incorporated in CGIscriptor.pl to enhance security.
863 # First of all, you should check the source of this program. No security
864 # measures will help you when you download programs from anonymous sources.
865 # If you want to use THIS file, please make sure that it is uncompromized.
866 # The best way to do this is to contact the source and try to determine
867 # whether s/he is reliable (and accountable).
869 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
870 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
872 # I would like to ask any user who finds bugs that could compromise
873 # security to report them to me (and any other bug too,
874 # Email: R.J.J.H.vanSon@uva.nl or ifa@hum.uva.nl).
876 # Security features
878 # 1 Invisibility
879 # The inner workings of the HTML source files are completely hidden
880 # from the client. Only the HTTP header and the ever changing content
881 # of the output distinguish it from the output of a plain, fixed HTML
882 # file. Names, structures, and arguments of the "embedded" scripts
883 # are invisible to the client. Error output is suppressed except
884 # during debugging (user configurable).
886 # 2 Separate directory trees
887 # Directories containing Inline text and script files can reside on
888 # separate trees, distinct from those of the HTTP server. This means
889 # that NEITHER the text files, NOR the script files can be read by
890 # clients other than through CGIscriptor.pl, UNLESS they are
891 # EXPLICITELY made available.
893 # 3 Requests are NEVER "evaluated"
894 # All client supplied values are used as literal values (''-quoted).
895 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
896 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
897 # supply executable commands. Be sure to AVOID scripts like:
899 # <META CONTENT="text/ssperl; CGI='$UserValue'">
900 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
902 # These are a recipe for disaster. However, the following quoted
903 # form should be save (but is still not adviced):
905 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
907 # A special function, SAFEqx(), will automatically do exactly this,
908 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
909 # with $UserValue interpolated. I recommend to use SAFEqx() instead
910 # of backticks whenever you can. The OS shell scripts inside
912 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
914 # are handeld by SAFEqx and automatically ''-quoted.
916 # 4 Logging of requests
917 # All requests can be logged separate from the Host server. The level of
918 # detail is user configurable: Including or excluding the actual queries.
919 # This allows for the inspection of (im-) proper use.
921 # 5 Access control: Clients
922 # The Remote addresses can be checked against a list of authorized
923 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
924 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
925 # HOST name can be (in-) excluded by their IP-address. Client patterns
926 # containing all numbers and dots are considered IP-addresses, all others
927 # domain names. No wild-cards or regexp's are allowed, only partial
928 # addresses.
929 # Matching of names is done from the back to the front (domain first,
930 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
931 # accept or reject all clients from the domain EDU. Matching of
932 # IP-addresses is done from the front to the back (domain first, i.e.,
933 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
934 # exclude all clients whose IP-address starts with 128.
935 # There are two special symbols: "-" matches HOSTs with no name and "*"
936 # matches ALL HOSTS/clients.
937 # For those needing more expressional power, lines starting with
938 # "-e" are evaluated by the perl eval() function. E.g.,
939 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
940 # domain '.edu'.
942 # 6 Access control: Files
943 # In principle, CGIscriptor could read ANY file in the directory
944 # tree as discussed in 1. However, for security reasons this is
945 # restricted to text files. It can be made more restricted by entering
946 # a global file pattern (e.g., ".html"). This is done by default.
947 # For each client requesting access, the file pattern(s) can be made
948 # more restrictive than the global pattern by entering client specific
949 # file patterns in the Access Control files (see 5).
950 # For example: if the ACCEPT file contained the lines
951 # * DEMO
952 # .hum.uva.nl LET
953 # 145.18.230.
954 # Then all clients could request paths containing "DEMO" or "demo", e.g.
955 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
956 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
957 # "/my/let/file.html", and clients from the local cluster
958 # 145.18.230.[0-9]+ could access ALL files.
959 # Again, for those needing more expressional power, lines starting with
960 # "-e" are evaluated. For instance:
961 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
962 # will accept/reject requests for files from the directory "/demo/" from
963 # clients from the domain '.edu'.
965 # 7 Access control: Server side session tickets
966 # Specific paths can be controlled by Session Tickets which must be
967 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
968 # are defined in %TicketRequiredPatterns as pairs of:
969 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
970 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
971 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
972 # CGI. The following is an example:
973 # Type: SESSION
974 # IPaddress: 127.0.0.1
975 # AllowedPaths: ^/Private/Name/
976 # Expires: 3600
977 # Username: test
978 # ...
979 # Other content can follow.
981 # It is adviced that Session Tickets should be deleted
982 # after some (idle) time. The IP address should be the IP number at login, and
983 # the SESSIONTICKET will be rejected if it is presented from another IP address.
984 # AllowedPaths and DeniedPaths are perl regexps. Be careful how they match. Make sure to delimit
985 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
986 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
987 # time the ticket will remain valid after creation (file ctime). Time can be given
988 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
989 # None of these need be present, but the Ticket must have a non-zero size.
991 # Next to Session Tickets, there are two other type of ticket files:
992 # - LOGIN tickets store information about a current login request
993 # - PASSWORD ticket store account information to authorize login requests
995 # 8 Query length limiting
996 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
997 # than this limit, the request is rejected. The combined length of the
998 # Query string and the POST input is checked before any processing is done.
999 # This will prevent clients from overloading the scripts.
1000 # The actual, combined, Query Size is accessible as a variable through
1001 # $CGI_Content_Length.
1003 # 9 Illegal filenames, paths, and protected directories
1004 # One of the primary security concerns in handling CGI-scripts is the
1005 # use of "funny" characters in the requests that con scripts in executing
1006 # malicious commands. Examples are inserting ';', null bytes, or <newline>
1007 # characters in URL's and filenames, followed by executable commands. A
1008 # special variable $FileAllowedChars stores a string of all allowed
1009 # characters. Any request that translates to a filename with a character
1010 # OUTSIDE this set will be rejected.
1011 # In general, all (readable files) in the DocumentRoot tree are accessible.
1012 # This might not be what you want. For instance, your DocumentRoot directory
1013 # might be the working directory of a CVS project and contain sensitive
1014 # information (e.g., the password to get to the repository). You can block
1015 # access to these subdirectories by adding the corresponding patterns to
1016 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1017 # will block any request that contains '/CVS/' or:
1018 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1020 #10 The execution of code blocks can be controlled in a transparent way
1021 # by adding IF or UNLESS conditions in the tags themselves. That is,
1022 # a simple check of the validity of filenames or email addresses can
1023 # be done before any code is executed.
1025 ###############################################################################
1027 # USER MANUAL (sort of)
1029 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1030 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1031 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1032 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1033 # the necessary code is already available in the TAG itself (e.g.,
1034 # using external files). The contents of the directive are executed by
1035 # the PERL eval() and `` functions (in a separate name space). The
1036 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1037 # in the output file. You can use the values that are delivered in
1038 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1039 # transparently as "$name" variables in your directives after they are
1040 # defined in a <META> or <SCRIPT> tag.
1041 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1042 # subsequent <SCRIPT> and <META> results (including the defining
1043 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1044 # behaves like any other, ordinary list and can be manipulated.
1046 # Both GET and POST requests are accepted. These two methods are treated
1047 # equal. Variables, i.e., those values that are determined when a file is
1048 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1049 # in which <name> is the name of the variable and <default> is the value
1050 # used when there is NO current CGI value for <name> (you can use
1051 # white-spaces in $<name>=<default> but really DO make sure that the
1052 # default value is followed by white space or is quoted). Names can contain
1053 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1054 # If the Content-type: is 'multipart/*', the input is treated as a
1055 # MIME multipart message and automatically delimited. CGI variables get
1056 # the "raw" (i.e., undecoded) body of the corresponding message part.
1058 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1059 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1060 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1061 # query string), CGI_Content_Length (the length of the decoded query
1062 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1063 # date and time).
1065 # All these are available when defined in a CGI attribute. All environment
1066 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1067 # and the REMOTE_USER, use, e.g.:
1069 # <SCRIPT TYPE='text/ssperl'>
1070 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1071 # </SCRIPT>
1073 # (This will print a "-" if REMOTE_HOST is not known)
1074 # Another way to do this is:
1076 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1077 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1078 # or
1079 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1080 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1082 # This is possible because ALL environment variables are available as
1083 # CGI variables. The environment variables take precedence over CGI
1084 # names in case of a "name clash". For instance:
1085 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1086 # Will print the current HOME directory (environment) irrespective whether
1087 # there is a CGI variable from the query
1088 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1089 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1090 # the values of defined environment variables (e.g., by supplying
1091 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1092 # it would make the use of declared variables insecure. You can still
1093 # access CGI variables after a name clash with
1094 # CGIscriptor::CGIparseValue(<name>).
1096 # Some CGI variables are present several times in the query string
1097 # (e.g., from multiple selections). These should be defined as
1098 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1099 # will contain ALL VARIABLENAME values from the query, or a single
1100 # default value. If there is an ENVIRONMENT variable of the
1101 # same name, it will be used instead of the default AND the query
1102 # values. The corresponding function is
1103 # CGIscriptor::CGIparseValueList(<name>)
1105 # CGI variables collected in a @VARIABLENAME list are unordered.
1106 # When more structured variables are needed, a hash table can be used.
1107 # A variable defined as %VARIABLE=default will collect all
1108 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1109 # the remainder of the name as a key. For instance, %PERSON will
1110 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1111 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1112 # equals 'Alice'. Any default value or environment value will be stored
1113 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1114 # it will be used instead of the default AND the query values. The
1115 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1117 # This method of first declaring your environment and CGI variables
1118 # before being able to use them in the scripts might seem somewhat
1119 # clumsy, but it protects you from inadvertedly printing out the values of
1120 # system environment variables when their names coincide with those used
1121 # in the CGI forms. It also prevents "clients" from supplying CGI
1122 # parameter values for your private variables.
1123 # THIS IS A SECURITY FEATURE!
1126 # NON-HTML CONTENT TYPES
1128 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1129 # message before anything is printed. This has been extended to include
1130 # plain text (.txt) files, for which the Content-type (MIME type)
1131 # 'text/plain' is printed. In all other respects, text files are treated
1132 # as HTML files (this can be switched off by removing '.txt' from the
1133 # $FilePattern variable) . When the content type should be something else,
1134 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1135 # item). CGIscriptor will not print a Content-type message for this file
1136 # type (which must supply its OWN Content-type message). Raw files must
1137 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1140 # NON-HTML FILES
1142 # CGIscriptor is intended to process HTML and text files only. You can
1143 # create documents of any mime-type on-the-fly using "raw" text files,
1144 # e.g., with the .xmr extension. However, CGIscriptor will not process
1145 # binary files of any type, e.g., pictures or sounds. Given the sheer
1146 # number of formats, I do not have any intention to do so. However,
1147 # an escape route has been provided. You can construct a genuine raw
1148 # (.xmr) text file that contains the perl code to service any file type
1149 # you want. If the global $BinaryMapFile variable contains the path to
1150 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1151 # whenever an unsupported (non-HTML) file type is requested. The path
1152 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1153 # and can be used like any other CGI-variable. Servicing binary files
1154 # then becomes supplying the correct Content-type (e.g., print
1155 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1156 # to STDOUT (e.g., using sysread() and syswrite()).
1159 # THE META TAG
1161 # All attributes of a META tag are ignored, except the
1162 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1163 # inside the quotes following the CONTENT= indication (white-space is
1164 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1165 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1166 # text/osshell) and a comma or semicolon.
1167 # The quoted string following CGI= contains a white-space separated list
1168 # of declarations of the CGI (and Environment) values and default values
1169 # used when no CGI values are supplied by the query string.
1171 # If the default value is a longer string containing special characters,
1172 # possibly spanning several lines, the string must be enclosed in quotes.
1173 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1174 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1175 # is different from (...)). The outermost pair will always be used and any
1176 # other quotes inside the string are considered to be part of the string
1177 # value, e.g.,
1179 # $Value = {['this'
1180 # "and" (this)]}
1181 # will result in $Value getting the default value: ['this'
1182 # "and" (this)]
1183 # (NOTE that the newline is part of the default value!).
1185 # Internally, for defining and initializing CGI (ENV) values, the META
1186 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1187 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1188 # These functions can be used inside scripts as
1189 # "CGIscriptor::defineCGIvariable($name, $default)" and
1190 # "CGIscriptor::defineCGIvariableList($name, $default)".
1191 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1193 # The CGI attribute will be processed exactly identical when used inside
1194 # the <SCRIPT> tag. However, this use is not according to the
1195 # HTML 4.0 specifications of the W3C.
1198 # THE DIV/INS TAGS
1200 # There is a problem when constructing html files containing
1201 # server-side perl scripts with standard HTML tools. These
1202 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1203 # tags. This is quite annoying when you want to use large
1204 # HTML templates where you will fill in values.
1206 # For this purpose, CGIscriptor will read the neutral
1207 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1208 # <INS CLASS="ssperl" ID="varname"></INS>
1209 # tag (in Cascading Style Sheet manner) Note that
1210 # "varname" has NO '$' before it, it is a bare name.
1211 # Any text between these <DIV ...></DIV> or
1212 # <INS ...></INS>tags will be assigned to '$varname'
1213 # as is (e.g., as a literal).
1214 # No processing or interpolation will be performed.
1215 # There is also NO nesting possible. Do NOT nest a
1216 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1217 # DIV tags do ensure a block structure in the final
1218 # rendering (i.e., no empty lines).
1220 # Note that <DIV CLASS="ssperl" ID="varname"/>
1221 # is handled the XML way. No content is processed,
1222 # but varname is defined, and any SRC directives are
1223 # processed.
1225 # You can use $varname like any other variable name.
1226 # However, $varname is NOT a CGI variable and will be
1227 # completely internal to your script. There is NO
1228 # interaction between $varname and the outside world.
1230 # To interpolate a DIV derived text, you can use:
1231 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1232 # $varname = eval("qq[$varname]"); # Interpolate all values
1234 # The DIV tags will process IF, UNLESS, CGI and
1235 # SRC attributes. The SRC files will be pre-pended to the
1236 # body text of the tag. SRC blocks are NOT executed.
1238 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1240 # It is often necessary to include code-blocks that should be executed
1241 # conditionally, e.g., only for certain browsers or operating system.
1242 # Furthermore, quite often sanity and security checks are necessary
1243 # before user (form) data can be processed, e.g., with respect to
1244 # email addresses and filenames.
1246 # Checks added to the code are often difficult to find, interpret or
1247 # maintain and in general mess up the code flow. This kind of confussion
1248 # is dangerous.
1249 # Also, for many of the supported "foreign" scripting languages, adding
1250 # these checks is cumbersome or even impossible.
1252 # As a uniform method for asserting the correctness of "context", two
1253 # attributes are added to all supported tags: IF and UNLESS.
1254 # They both evaluate their value and block execution when the
1255 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1256 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1257 # the backslash in the '\>' is removed and only used to differentiate
1258 # this conditional '>' from the tag-closing '>'. For symmetry, the
1259 # backslash in '\<' is also removed. Inside these conditionals,
1260 # ~/ and ./ are expanded to their respective directory root paths.
1262 # For example, the following tag will be ignored when the filename is
1263 # invalid:
1265 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1266 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1267 # ...
1268 # </SCRIPT>
1270 # The IF and UNLESS values must be quoted. The same quotes are supported
1271 # as with the other attributes. The SRC attribute is ignored when IF and
1272 # UNLESS block execution.
1274 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1277 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1279 # The SRC attribute inside tags accepts a list of filenames and URL's
1280 # separated by "," comma's (or ";" semicolons).
1281 # ALL the variable values defined in the CGI attribute are available
1282 # in @ARGV as if the file or block was executed from the command line,
1283 # in the exact order in which they were declared in the preceding CGI
1284 # attribute.
1286 # First, a SRC={}-block will be evaluated as if the code inside the
1287 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1288 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1289 # Only a single block is evaluated. Note that this is processed less
1290 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1291 # depends on the content-type: Perl for text/ssperl and OS shell for
1292 # text/osshell. For other mime types (scripting languages), anything in
1293 # the source block is put in front of the code block "inside" the tag.
1295 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1296 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1297 # That is, you can actually call executables savely from the SRC tag.
1299 # Third, text files that match the file pattern, used by CGIscriptor to
1300 # check whether files should be processed ($FilePattern), are
1301 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1302 # was inserted in the original source file. Recursions, i.e., calling
1303 # a file inside itself, are blocked. If you need them, you have to code
1304 # them explicitely using "main::ProcessFile($file_path)".
1306 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1307 # "do FileName;'';".
1309 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1310 # 'TELNET://', 'WHOIS://' etc.) are loaded
1311 # and printed. The loading and handling of <BASE> and document header
1312 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1313 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1315 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1316 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1317 # content of the tag to appending the code after the content of the tag
1318 # (POSTFIX). The switches are done in the order in which the PREFIX and
1319 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1320 # in any order with the SRC files. Note that the ORDER of file execution
1321 # is determined for prefixed and postfixed files seperately.
1323 # File paths can be preceded by the URL protocol prefix "file://". This
1324 # is simply STRIPPED from the name.
1326 # Example:
1327 # The request
1328 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1329 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1330 # With QUERY_STRING = "positive=8&negative=22"
1332 # on encountering the lines:
1333 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1334 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1335 # </SCRIPT></b><p>"
1337 # This line will be processed as:
1338 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1340 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1341 # This line will end up printed as:
1342 # "<b>p <= 0.0161</b><p>"
1344 # Note that the META tag itself will never be printed, and is invisible to
1345 # the outside world.
1347 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1348 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1349 # need any content, you can use the <DIV...../> format.
1352 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1354 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1355 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1356 # public directories can be accessed without direct reference to the
1357 # actual paths. Filepaths starting with "./" are replaced by
1358 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1360 # Note: this replacement can seriously affect Perl scripts. Watch
1361 # out for constructs like $a =~ s/aap\./noot./g, use
1362 # $a =~ s@aap\.@noot.@g instead.
1364 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1365 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1366 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1367 # executed.
1368 # Values not preceded by $, ~/, or ./ are used as literals
1371 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1373 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1374 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1375 # interpolate the script and replace all user-supplied CGI-variables by
1376 # their ''-quoted values (actually, all variables defined in CGI attributes
1377 # are quoted). Other Perl variables are interpolated in a simple fasion,
1378 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1379 # their name=value pairs. Complex references, e.g., @$variable, are all
1380 # evaluated in a scalar context. Quotes should be used with care.
1381 # NOTE: the results of the shell script evaluation will appear in the
1382 # @CGIscriptorResults stack just as any other result.
1383 # All occurrences of $@% that should NOT be interpolated must be
1384 # preceeded by a "\". Interpolation can be switched off completely by
1385 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1386 # (set to 0 or undef to switch interpolation on again)
1387 # i.e.,
1388 # <SCRIPT TYPE="text/ssperl">
1389 # $CGIscriptor::NoShellScriptInterpolation = 1;
1390 # </SCRIPT>
1393 # RUN TIME TRANSLATION OF INPUT FILES
1395 # Allows general and global conversions of files using Regular Expressions.
1396 # Very handy (but costly) to rewrite legacy pages to a new format.
1397 # Select files to use it on with
1398 # my $TranslationPaths = 'filepattern';
1399 # This is costly. For efficiency, define:
1400 # $TranslationPaths = ''; when not using translations.
1401 # Accepts general regular expressions: [$pattern, $replacement]
1403 # Define:
1404 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1406 # push(@TranslationTable, ['pattern', 'replacement']);
1407 # e.g. (for Ruby Rails):
1408 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1409 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1411 # Runs:
1412 # my $currentRegExp;
1413 # foreach $currentRegExp (@TranslationTable)
1415 # my ($pattern, $replacement) = @$currentRegExp;
1416 # $$text =~ s!$pattern!$replacement!msg;
1417 # };
1420 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1422 # Adding a MIME-type and an interpreter command to
1423 # %ScriptingLanguages automatically will catch any other
1424 # scripting language in the standard
1425 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1426 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1427 # will actually execute the folowing code in an HTML page
1428 # (ignore 'REMOTE_HOST' for the moment):
1429 # <SCRIPT TYPE="text/sspython">
1430 # # A Python script
1431 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1432 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1433 # </SCRIPT>
1435 # The script code is NOT interpolated by perl, EXCEPT for those
1436 # interpreters that cannot handle variables themselves.
1437 # Currently, several interpreters are pre-installed:
1439 # Perl test - "text/testperl" => 'perl',
1440 # Python - "text/sspython" => 'python',
1441 # Ruby - "text/ssruby" => 'ruby',
1442 # Tcl - "text/sstcl" => 'tcl',
1443 # Awk - "text/ssawk" => 'awk -f-',
1444 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1445 # "| egrep -v '> |^rep. |^nil\\\$'",
1446 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1447 # "| egrep -v '> \\\$|^NIL'",
1448 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1449 # M4 macro's- "text/ssm4" => 'm4',
1450 # Born shell- "text/sh" => 'sh',
1451 # Bash - "text/bash" => 'bash',
1452 # C-shell - "text/csh" => 'csh',
1453 # Korn shell- "text/ksh" => 'ksh',
1454 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1455 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1456 # REBOL - "text/ssrebol" =>
1457 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1458 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1459 # (psql)
1461 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1462 # that reads Standard Input and writes to standard output. Any extra
1463 # output of interactive interpreters (banners, echo's, prompts)
1464 # should be removed by piping the output through 'tail', 'grep',
1465 # 'sed', or even 'awk' or 'perl'.
1467 # For access to CGI variables there is a special hashtable:
1468 # %ScriptingCGIvariables.
1469 # CGI variables can be accessed in three ways.
1470 # 1. If the mime type is not present in %ScriptingCGIvariables,
1471 # nothing is done and the script itself should parse the relevant
1472 # environment variables.
1473 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1474 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1475 # the script text is interpolated by perl. That is, all $var, @array,
1476 # %hash, and \-slashes are replaced by their respective values.
1477 # 3. In all other cases, the CGI and environment variables are added
1478 # in front of the script according to the format stored in
1479 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1480 # executed for each CGI- or Environment variable defined in the CGI-tag:
1481 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1483 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1484 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1485 # (note that these definitions are not save, the real ones contain '-quotes).
1487 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1488 # instead.
1490 # The $CGI_VALUE parameters are "shrubed" of all control characters
1491 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1492 # Control characters are replaced by \0<octal ascii value> (the exception
1493 # is \015, the newline, which is replaced by \n) and quotes
1494 # and backslashes by their HTML character
1495 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1496 # For example:
1497 # if a client would supply the string value (in standard perl, e.g.,
1498 # \n means <newline>)
1499 # "/dev/null';\nrm -rf *;\necho '"
1500 # it would be processed as
1501 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1502 # (e.g., sh or bash would process the latter more according to your
1503 # intentions).
1504 # If your intepreter requires different protection measures, you will
1505 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1506 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1508 # Currently, the following definitions are used:
1509 # %ScriptingCGIvariables = (
1510 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1511 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1512 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1513 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1514 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1515 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1516 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1517 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1518 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1519 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1520 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1521 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1522 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1523 # "text/sspraat" => '', # Praat (interpolation)
1524 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1525 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1526 # "text/postgresql" => '', # PostgreSQL (interpolation)
1527 # "" => ""
1528 # );
1530 # Four tables allow fine-tuning of interpreter with code that should be
1531 # added before and after each code block:
1533 # Code added before each script block
1534 # %ScriptingPrefix = (
1535 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1536 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1537 # );
1538 # Code added at the end of each script block
1539 # %ScriptingPostfix = (
1540 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1541 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1542 # );
1543 # Initialization code, inserted directly after opening (NEVER interpolated)
1544 # %ScriptingInitialization = (
1545 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1546 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1547 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1548 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1549 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1550 # );
1551 # Cleanup code, inserted before closing (NEVER interpolated)
1552 # %ScriptingCleanup = (
1553 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1554 # "text/sspraat" => 'Quit',
1555 # "text/ssawk" => '};', # Server Side awk scripts
1556 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1557 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1558 # "text/postgresql" => '\q',
1559 # );
1562 # The SRC attribute is NOT magical for these interpreters. In short,
1563 # all code inside a source file or {} block is written verbattim
1564 # to the interpreter. No (pre-)processing or executional magic is done.
1566 # A serious shortcomming of the described mechanism for handling other
1567 # (scripting) languages, with respect to standard perl scripts
1568 # (i.e., 'text/ssperl'), is that the code is only executed when
1569 # the pipe to the interpreter is closed. So the pipe has to be
1570 # closed at the end of each block. This means that the state of the
1571 # interpreter (e.g., all variable values) is lost after the closing of
1572 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1573 # all values and definitions.
1575 # APPLICATION MIME TYPES
1577 # To ease some important auxilliary functions from within the
1578 # html pages I have added them as MIME types. This uses
1579 # the mechanism that is also used for the evaluation of
1580 # other scripting languages, with interpolation of CGI
1581 # parameters (and perl-variables). Actually, these are
1582 # defined exactly like any other "scripting language".
1584 # text/ssdisplay: display some (HTML) text with interpolated
1585 # variables (uses `cat`).
1586 # text/sslogfile: write (append) the interpolated block to the file
1587 # mentioned on the first, non-empty line
1588 # (the filename can be preceded by 'File: ',
1589 # note the space after the ':',
1590 # uses `awk .... >> <filename>`).
1591 # text/ssmailto: send email directly from within the script block.
1592 # The first line of the body must contain
1593 # To:Name@Valid.Email.Address
1594 # (note: NO space between 'To:' and the email adres)
1595 # For other options see the mailto man pages.
1596 # It works by directly sending the (interpolated)
1597 # content of the text block to a pipe into the
1598 # Linux program 'mailto'.
1600 # In these script blocks, all Perl variables will be
1601 # replaced by their values. All CGI variables are cleaned before
1602 # they are used. These CGI variables must be redefined with a
1603 # CGI attribute to restore their original values.
1604 # In general, this will be more secure than constructing
1605 # e.g., your own email command lines. For instance, Mailto will
1606 # not execute any odd (forged) email addres, but just stops
1607 # when the email address is invalid and awk will construct
1608 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1609 # as a "valid" UNIX filename). Note that it will also gladly
1610 # store this file anywhere (/../../../etc/passwd will work!).
1611 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1612 # filename.
1614 # SHELL SCRIPT PIPING
1616 # If a shell script starts with the UNIX style "#! <shell command> \n"
1617 # line, the rest of the shell script is piped into the indicated command,
1618 # i.e.,
1619 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1621 # In many ways this is equivalent to the MIME-type profiling for
1622 # evaluating other scripting languages as discussed above. The
1623 # difference breaks down to convenience. Shell script piping is a
1624 # "raw" implementation. It allows you to control all aspects of
1625 # execution. Using the MIME-type profiling is easier, but has a
1626 # lot of defaults built in that might get in the way. Another
1627 # difference is that shell script piping uses the SAFEqx() function,
1628 # and MIME-type profiling does not.
1630 # Execution of shell scripts is under the control of the Perl Script blocks
1631 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1632 # blocks can be simulated easily. You can switch to a different shell,
1633 # e.g. tcl, completely by executing the following Perl commands inside
1634 # your document:
1636 # <SCRIPT TYPE="text/ssperl">
1637 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1638 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1639 # $CGIscriptor::NoShellScriptInterpolation = 1;
1640 # </SCRIPT>
1642 # After this script is executed, CGIscriptor will parse scripts of
1643 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1644 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1645 # The crucial function is :
1646 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1647 # After executing this function, all shell scripts AND all
1648 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1649 # of RedirectShellScript is empty, e.g., '', the original (default)
1650 # value is reset.
1652 # The standard output, STDOUT, of any pipe is send to the client.
1653 # Currently, you should be carefull with quotes in such a piped script.
1654 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1655 # As a result, you do not have access to the output of any piped (#!)
1656 # process! If you want such access, execute
1657 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1658 # or
1659 # <SCRIPT TYPE="text/ssperl">
1660 # $resultvar = SAFEqx('echo "script"|command');
1661 # </SCRIPT>.
1663 # Safety is never complete. Although SAFEqx() prevents some of the
1664 # most obvious forms of attacks and security slips, it cannot prevent
1665 # them all. Especially, complex combinations of quotes and intricate
1666 # variable references cannot be handled safely by SAFEqx. So be on
1667 # guard.
1670 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1672 # All PERL scripts are evaluated inside a PERL package. This package
1673 # has a separate name space. This isolated name space protects the
1674 # CGIscriptor.pl program against interference from user code. However,
1675 # some variables, e.g., $_, are global and cannot be protected. You are
1676 # advised NOT to use such global variable names. You CAN write
1677 # directives that directly access the variables in the main program.
1678 # You do so at your own risk (there is definitely enough rope available
1679 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1680 # you change its private variables during run time. The PERL code
1681 # directives are used as in:
1682 # $Result = eval($directive); print $Result;'';
1683 # ($directive contains all text between <SCRIPT></SCRIPT>).
1684 # That is, the <directive> is treated as ''-quoted string and
1685 # the result is treated as a scalar. To prevent the VALUE of the code
1686 # block from appearing on the client's screen, end the directive with
1687 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1688 # eval(), blocks, and subroutines, but only as a scalar.
1690 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1691 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1692 # (e.g., for "my $var;" declarations). This means that values assigned
1693 # to a PERL variable can be used throughout the document unless they
1694 # were declared with "my". The following will actually work as intended
1695 # (note that the ``-quotes in this example are NOT evaluated, but used
1696 # as simple quotes):
1698 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1699 # anything ...
1700 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1701 # anything ...
1702 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1704 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1705 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1706 # construct will print the elements of $String separated by commas, leaving
1707 # out the first element, i.e., $List[0].
1709 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1710 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1711 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1712 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1713 # s@<regexp>.@<replacement>.@g instead.
1716 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1718 # An infrastructure for user acount authorization and file access control
1719 # is available. Each request is matched against a list of URL path patterns.
1720 # If the request matches, a Session Ticket is required to access the URL.
1721 # This Session Ticket should be present as a CGI parameter or Cookie, eg:
1723 # CGI: SESSIONTICKET=&lt;value&gt;
1724 # Cookie: CGIscriptorSESSION=&lt;value&gt;
1726 # The example implementation stores Session Tickets as files in a local
1727 # directory. To create Session Tickets, a Login request must be given
1728 # with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
1729 # password. The user name and (singly hashed) password are stored in a
1730 # PASSWORD ticket with the same name as the user account (name cleaned up
1731 # for security).
1733 # The example session model implements 3 functions:
1734 # - Login
1735 # The password is hashed with the user name and server side salt, and then
1736 # hashed with a random salt. Client and Server both perform these actions
1737 # and the Server only grants access if restults are the same. The server
1738 # side only stores the password hashed with the user name and
1739 # server side salt. Neither the plain password, nor the hashed password is
1740 # ever exchanged. Only values hashed with the one-time salt are exchanged.
1741 # - Session
1742 # For every access to a restricted URL, the Session Ticket is checked before
1743 # access is granted. There are three session modes. The first uses a fixed
1744 # Session Ticket that is stored as a cookie value in the browser (actually,
1745 # as a sessionStorage value). The second uses only the IP address at login
1746 # to authenticate requests. The third
1747 # is a Challenge mode, where the client has to calculate the value of the
1748 # next one-time Session Ticket from a value derived from the password and
1749 # a random string.
1750 # - Password Change
1751 # A new password is hashed with the user name and server side salt, and
1752 # then encrypted (XORed)
1753 # with the old password hashed with the user name and salt. That value is
1754 # exchanged and XORed with the stored old hashed(salt+password+username).
1755 # Again, the stored password value is never exchanged unencrypted.
1757 # Implementation
1759 # The session authentication mechanism is based on the exchange of ticket
1760 # identifiers. A ticket identifier is just a string of characters, a name
1761 # or a random 64 character hexadecimal string. Ticket identifiers should be
1762 # "safe" filenames (except user names). There are four types of tickets:
1763 # PASSWORD: User account descriptors, including a user name and password
1764 # LOGIN: Temporary anonymous tickets used during login
1765 # IPADDRESS: Authetication tokens that allow access based on the IP address of the request
1766 # SESSION: Reusable authetication tokens
1767 # CHALLENGE: One-time authetication tokens
1768 # All tickets can have an expiration date in the form of a time duration
1769 # from creation, in seconds, minutes, hours, or days (+duration[smhd]).
1770 # An absolute time can be given in seconds since the epoch of the server host.
1771 # Note that expiration times of CHALLENGE authetication tokens are calculated
1772 # from the last access time. Accounts can include a maximal lifetime
1773 # for session tickets (MaxLifetime).
1775 # A Login page should create a LOGIN ticket file locally and send a
1776 # server specific salt, a Random salt, and a LOGIN ticket
1777 # identifier. The server side compares the username and hashed password,
1778 # actually hashed(Random salt+hashed(serversalt+password)) from the client with
1779 # the values it calculates from the stored Random salt from the LOGIN
1780 # ticket and the hashed(serversalt+password) from the PASSWORD ticket. If
1781 # successful, a new SESSION ticket is generated as a hash sum of the LOGIN
1782 # ticket and the stored password. This SESSION ticket should also be
1783 # generated by the client and stored as sessionStorage and cookie values
1784 # as needed. The Username, IP address and Path are available as
1785 # $LoginUsername, $LoginIPaddress, and $LoginPath, respectively.
1787 # The CHALLENGE protocol stores the same value as the SESSION tickets.
1788 # However, this value is not exchanged, but kept secret in the JavaScript
1789 # sessionStorage object. Instead, every page returned from the
1790 # server will contain a one-time Challenge value ($CHALLENGETICKET) which
1791 # has to be hashed with the stored value to return the current ticket
1792 # id string.
1794 # In the current example implementation, all random values are created as
1795 # full, 256 bit SHA256 hash values (Hex strings) of 64 bytes read from
1796 # /dev/urandom.
1798 # Security considerations with Session tickets
1800 # For strong security, please use end-to-end encryption. This can be
1801 # achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
1802 # capable server with OpenSSL. The session ticket system of CGIscriptor.pl
1803 # is intended to be used as a simple authentication mechanism WITHOUT
1804 # END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
1805 # simple means to protect the authentication process from eavesdropping.
1806 # For this it uses a secure hash function, SHA256. For all practial purposes,
1807 # it is impossible to "decrypt" a SHA256 sum. But this login scheme is
1808 # only as secure as your browser. Which, in general, is not very secure.
1810 # Humans tend to reuse passwords. A compromise of a site running
1811 # CGIscriptor.pl could therefore lead to a compromise of user accounts at
1812 # other sites. Therefore, plain text passwords are never stored, used, or
1813 # exchanged. Instead, a server site salt value is "encrypted" with
1814 # the plain password and user name. Actually, all are concatenated and hashed
1815 # with a one-way secure hash function (SHA256) into a single string.
1816 # Whenever the word "password" is used, this hash sum is meant. Note that
1817 # the salts are generated from /dev/urandom. You should check whether the
1818 # implementation of /dev/urandom on your platform is secure before
1819 # relying on it. This might be a problem when running CGIscriptor under
1820 # Cygwin on MS Windows.
1821 # Note: not attempt is made to slow down the password hash, so bad
1822 # passwords can be cracked by brute force
1824 # For the authentication and a change of password, the (old) password
1825 # is used to "encrypt" a random one-time token or the new password,
1826 # respectively. For authentication, decryption is not needed, so a secure
1827 # hash function (SHA256) is used to create a one-way hash sum "encryption".
1828 # A new password must be decrypted. New passwords are encryped by XORing
1829 # them with the old password.
1831 # USER EXTENSIONS
1833 # A CGIscriptor package is attached to the bottom of this file. With
1834 # this package you can personalize your version of CGIscriptor by
1835 # including often used perl routines. These subroutines can be
1836 # accessed by prefixing their names with CGIscriptor::, e.g.,
1837 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
1838 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
1839 # </SCRIPT>
1840 # It already contains some useful subroutines for Document Management.
1841 # As it is a separate package, it has its own namespace, isolated from
1842 # both the evaluator and the main program. To access variables from
1843 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
1845 # Currently, the following functions are implemented
1846 # (precede them with CGIscriptor::, see below for more information)
1847 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
1848 # Is used by text/osshell Shell scripts. Protects all CGI
1849 # (client-supplied) values with single quotes before executing the
1850 # commands (one of the few functions that also works WITHOUT CGIscriptor::
1851 # in front)
1852 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
1853 # Is used by the META tag to define and initialize CGI and ENV
1854 # name/value pairs. Tries to obtain an initializing value from (in order):
1855 # $ENV{$name}
1856 # The Query string
1857 # The default value given (if any)
1858 # (one of the few functions that also works WITHOUT CGIscriptor::
1859 # in front)
1860 # - CGIsafeFileName (FileName) -> FileName or ""
1861 # Check a string against the Allowed File Characters (and ../ /..).
1862 # Returns an empty string for unsafe filenames.
1863 # - CGIsafeEmailAddress (Email) -> Email or ""
1864 # Check a string against correct email address pattern.
1865 # Returns an empty string for unsafe addresses.
1866 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
1867 # Open a named PIPE for SAFEqx to receive ALL shell scripts
1868 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
1869 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
1870 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
1871 # Extract the value of a CGI variable from the global or a private
1872 # URL-encoded query (multipart POST raw, NOT decoded)
1873 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
1874 # -> List of decoded values
1875 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
1876 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
1877 # Extract the header of a multipart CGI variable from the global or a private
1878 # URL-encoded query ("" when not a multipart variable or absent)
1879 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
1880 # Decode the complete global URL-encoded query or a private
1881 # URL-encoded query
1882 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
1883 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
1884 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
1885 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
1886 # all documents, e.g., ListDocs("/*", "dl");.
1887 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
1888 # local links starting from a given document, e.g.,
1889 # HTMLdocTree("/Welcome.html", "dl");
1892 # THE RESULTS STACK: @CGISCRIPTORRESULTS
1894 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
1895 # META tag, all subsequent SCRIPT and META results are pushed
1896 # on the @CGIscriptorResults stack. This list is just another
1897 # Perl variable and can be used and manipulated like any other list.
1898 # $CGIscriptorResults[-1] is always the last result.
1899 # This is only of limited use, e.g., to use the results of an OS shell
1900 # script inside a Perl script. Will NOT contain the results of Pipes
1901 # or code from MIME-profiling.
1904 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
1906 # $CGI_HOME - The DocumentRoot directory
1907 # $CGI_Decoded_QS - The complete decoded Query String
1908 # $CGI_Content_Length - The ACTUAL length of the Query String
1909 # $CGI_Date - Current date and time
1910 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
1911 # $CGI_Time - Current Time
1912 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
1913 # GMT Date/Time:
1914 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
1915 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
1918 # USEFULL CGI ENVIRONMENT VARIABLES
1920 # Variables accessible (in APACHE) as $ENV{<name>}
1921 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
1923 # QUERY_STRING - The query part of URL, that is, everything that follows the
1924 # question mark.
1925 # PATH_INFO - Extra path information given after the script name
1926 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
1927 # (This doesn't always make sense.)
1928 # REMOTE_USER - If the server supports user authentication, and the script is
1929 # protected, this is the username they have authenticated as.
1930 # REMOTE_HOST - The hostname making the request. If the server does not have
1931 # this information, it should set REMOTE_ADDR and leave this unset
1932 # REMOTE_ADDR - The IP address of the remote host making the request.
1933 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
1934 # variable will be set to the remote user name retrieved from
1935 # the server. Usage of this variable should be limited to logging
1936 # only.
1937 # AUTH_TYPE - If the server supports user authentication, and the script
1938 # is protected, this is the protocol-specific authentication
1939 # method used to validate the user.
1940 # CONTENT_TYPE - For queries which have attached information, such as HTTP
1941 # POST and PUT, this is the content type of the data.
1942 # CONTENT_LENGTH - The length of the said content as given by the client.
1943 # SERVER_SOFTWARE - The name and version of the information server software
1944 # answering the request (and running the gateway).
1945 # Format: name/version
1946 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
1947 # would appear in self-referencing URLs
1948 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
1949 # server complies. Format: CGI/revision
1950 # SERVER_PROTOCOL - The name and revision of the information protocol this
1951 # request came in with. Format: protocol/revision
1952 # SERVER_PORT - The port number to which the request was sent.
1953 # REQUEST_METHOD - The method with which the request was made. For HTTP,
1954 # this is "GET", "HEAD", "POST", etc.
1955 # SCRIPT_NAME - A virtual path to the script being executed, used for
1956 # self-referencing URLs.
1957 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
1958 # HTTP headers. Other protocols may need to get this
1959 # information from elsewhere. Each item in this list should
1960 # be separated by commas as per the HTTP spec.
1961 # Format: type/subtype, type/subtype
1962 # HTTP_USER_AGENT - The browser the client is using to send the request.
1963 # General format: software/version library/version.
1966 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
1968 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
1969 # a line like the following to your srm.conf file (Apache example):
1971 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
1973 # URL's that refer to http://www.your.address/SHTML/... will now be handled
1974 # by CGIscriptor.pl, which can use a private directory tree (default is the
1975 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
1977 # If your hosting ISP won't let you add ScriptAlias lines you can use
1978 # the following "rewrite"-based "scriptalias" in .htaccess
1979 # (from Gerd Franke)
1981 # RewriteEngine On
1982 # RewriteBase /
1983 # RewriteCond %{REQUEST_FILENAME} .html$
1984 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
1985 # RewriteCond %{REQUEST_FILENAME} -f
1986 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
1988 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
1989 # in the url and where the file "path/filename.html" exists is redirected
1990 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
1991 # The user configuration should get the same path-level as the
1992 # .htaccess-file:
1994 # # Just enter your own directory path here
1995 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
1996 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
1998 # If this .htaccess goes in a specific directory, the path to this
1999 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
2001 # The CGIscriptor file contains all documentation as comments. These
2002 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
2003 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
2004 # CGIscriptor.pl, lacking documentation, most comments, access control,
2005 # example functions etc. (but still with the copyright notice and some
2006 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
2007 # command line with the '-slim' command line argument, e.g.,
2009 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
2011 # CGIscriptor.pl can be run from the command line with <path> and <query> as
2012 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
2013 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
2014 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
2015 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
2016 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
2017 # $ENV{QUERY_STRING}).
2019 # Using the command line execution option, CGIscriptor.pl can be used as a
2020 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
2021 # For example:
2023 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
2025 # This command line will produce a STATIC file with the DYNAMIC content of
2026 # MyDocument.html "interpolated".
2028 # This option would be very dangerous when available over the internet.
2029 # If someone could sneak a 'http://www.your.domain/-' URL past your
2030 # server, CGIscriptor could EXECUTE any POSTED contend.
2031 # Therefore, for security reasons, STDIN will NOT be read
2032 # if ANY of the HTTP server environment variables is set (e.g.,
2033 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
2034 # HTTP_USER_AGENT, REMOTE_ADDR).
2035 # This block on processing STDIN on HTTP requests can be lifted by setting
2036 # $BLOCK_STDIN_HTTP_REQUEST = 0;
2037 # In the security configuration. Butbe carefull when doing this.
2038 # It can be very dangerous.
2040 # Running demo's and more information can be found at
2041 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
2043 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
2044 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
2045 # demonstrates its use.
2048 # PROCESSING NON-FILESYSTEM DATA
2050 # Normally, HTTP (WWW) requests map onto file that can be accessed
2051 # using the perl open() function. That is, the web server runs on top of
2052 # some directory structure. However, we can envission (and put to good
2053 # use) other systems that do not use a normal file system. The whole CGI
2054 # was developed to make dynamic document generation possible.
2056 # A special case is where we want to have it both: A normal web server
2057 # with normal "file data", but not a normal files system. For instance,
2058 # we want or normal Web Site to run directly from a RAM hash table or
2059 # other database, instead of from disk. But we do NOT want to code the
2060 # whole site structure in CGI.
2062 # CGIscriptor can do this. If the web server fills an environment variable
2063 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
2064 # of this variable is processed instead of opening a file. If this environment
2065 # variable has the value '-', the content of another environment variable,
2066 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
2067 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
2068 # and the result is processed as if it was the content of the requested
2069 # file.
2070 # (actually, the names of the environment variables are user configurable,
2071 # they are stored in the local variables $CGI_FILE_CONTENT and
2072 # $CGI_DATA_ACCESS_CODE)
2074 # When using this mechanism, the SRC attribute mechanism will only partially work.
2075 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
2076 # will work, the automagical execution of SRC files won't. (In this case,
2077 # the SRC attribute won't work either for other scripting languages)
2080 # NON-UNIX PLATFORMS
2082 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2083 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2084 # CGIscriptor did run under MacPerl (with command line options). But only
2085 # as an independend script, not as part of a HTTP server. I have used it
2086 # under Apache in Windows XP.
2088 ENDOFHELPTEXT
2089 exit;
2091 ###############################################################################
2093 # SECURITY CONFIGURATION
2095 # Special configurations related to SECURITY
2096 # (i.e., optional, see also environment variables below)
2098 # LOGGING
2099 # Log Clients and the requested paths (Redundant when loging Queries)
2101 $ClientLog = "./Client.log"; # (uncomment for use)
2103 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2104 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2106 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2107 # to log queries. If you log Queries, the loging of Clients is redundant
2108 # (note that queries can be quite long, so this might not be a good idea)
2110 #$QueryLog = "./Query.log"; # (uncomment for use)
2112 # ACCESS CONTROL
2113 # the Access files should contain Hostnames or IP addresses,
2114 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2115 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2116 # Matching is done "domain first". For example ".edu" matches all
2117 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2118 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2119 # (both matchings are done case-insensitive).
2120 # The name special symbol "-" matches ALL clients who do not supply a
2121 # REMOTE_HOST name, "*" matches all clients.
2122 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2123 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2124 # lines are evaluated in the program's own name-space. So DO NOT assign to
2125 # variables.
2127 # Accept the following users (remove comment # and adapt filename)
2128 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2130 # Reject requests from the following users (remove comment # and
2131 # adapt filename, this is only of limited use)
2132 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2134 # Empty lines or comment lines starting with '#' are ignored in both
2135 # $CGI_Accept and $CGI_Reject.
2137 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2138 # Comment this out if you realy want to use STDIN in an on-line web server
2139 $BLOCK_STDIN_HTTP_REQUEST = 1;
2142 # End of security configuration
2144 ##################################################<<<<<<<<<<End Remove
2146 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2148 # The CGI parse commands. These commands extract the values of the
2149 # CGI variables from the URL encoded Query String.
2150 # If you want to use your own CGI decoders, you can call them here
2151 # instead, using your own PATH and commenting/uncommenting the
2152 # appropriate lines
2154 # CGI parse command for individual values
2155 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2156 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2158 my $Name = shift;
2159 my $List = shift || 0;
2160 # Use one of the following by uncommenting
2161 if(!$List) # Simple value
2163 return CGIscriptor::CGIparseValue($Name) ;
2165 elsif($List < 0) # Hash tables
2167 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2169 else # Lists
2171 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2174 # return `/PATH/cgiparse -value $Name`; # Shell commands
2175 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2177 # Complete queries
2178 sub YOUR_CGIQUERYDECODE
2180 # Use one of the following by uncommenting
2181 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2182 # return `/PATH/cgiparse -form`; # Shell commands
2183 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2186 # End of configuration
2188 #######################################################################
2190 # Translating input files.
2191 # Allows general and global conversions of files using Regular Expressions
2192 # Translations are applied in the order of definition.
2194 # Define:
2195 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2197 # push(@TranslationTable, ['pattern', 'replacement']);
2198 # e.g. (for Ruby Rails):
2199 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2200 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2202 # Runs:
2203 # my $currentRegExp;
2204 # foreach $currentRegExp (keys(%TranslationTable))
2206 # my $currentRegExp;
2207 # foreach $currentRegExp (@TranslationTable)
2209 # my ($pattern, $replacement) = @$currentRegExp;
2210 # $$text =~ s!$pattern!$replacement!msg;
2211 # };
2212 # };
2214 # Configuration section
2216 #######################################################################
2218 # The file paths on which to apply the translation
2219 my $TranslationPaths = ''; # NO files
2220 #$TranslationPaths = '.'; # ANY file
2221 # $TranslationPaths = '\.html'; # HTML files
2223 my @TranslationTable = ();
2224 # Some legacy code
2225 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2226 # Ruby Rails?
2227 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2228 push(@TranslationTable, ['%>', '</SCRIPT>']);
2230 sub performTranslation # (\$text)
2232 my $text = shift || return;
2233 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2235 my $currentRegExp;
2236 foreach $currentRegExp (@TranslationTable)
2238 my ($pattern, $replacement) = @$currentRegExp;
2239 $$text =~ s!$pattern!$replacement!msg;
2244 #######################################################################
2246 # Seamless access to other (Scripting) Languages
2247 # TYPE='text/ss<interpreter>'
2249 # Configuration section
2251 #######################################################################
2253 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2254 # Yes, it realy is this simple! (unbelievable, isn't it)
2255 # NOTE: Some interpreters require some filtering to obtain "clean" output
2257 %ScriptingLanguages = (
2258 "text/testperl" => 'perl', # Perl for testing
2259 "text/sspython" => 'python', # Python
2260 "text/ssruby" => 'ruby', # Ruby
2261 "text/sstcl" => 'tcl', # TCL
2262 "text/ssawk" => 'awk -f-', # Awk
2263 "text/sslisp" => # lisp (rep, GNU)
2264 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2265 "text/xlispstat" => # xlispstat
2266 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2267 "text/ssprolog" => # Prolog (GNU)
2268 "gprolog | tail +4 | sed 's/^| ?- //'",
2269 "text/ssm4" => 'm4', # M4 macro's
2270 "text/sh" => 'sh', # Born shell
2271 "text/bash" => 'bash', # Born again shell
2272 "text/csh" => 'csh', # C shell
2273 "text/ksh" => 'ksh', # Korn shell
2274 "text/sspraat" => # Praat (sound/speech analysis)
2275 "praat - | sed 's/Praat > //g'",
2276 "text/ssr" => # R
2277 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2278 "text/ssrebol" => # REBOL
2279 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2280 "text/postgresql" => 'psql 2>/dev/null',
2282 # Not real scripting, but the use of other applications
2283 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2284 "text/ssdisplay" => 'cat', # Display, (interpolation)
2285 "text/sslogfile" => # Log to file, (interpolation)
2286 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2288 "" => ""
2291 # To be able to access the CGI variables in your script, they
2292 # should be passed to the scripting language in a readable form
2293 # Here you can enter how they should be printed (the first %s
2294 # is replaced by the NAME of the CGI variable as it apears in the
2295 # META tag, the second by its VALUE).
2296 # For Perl this would be:
2297 # "text/testperl" => '$%s = "%s";',
2298 # which would be executed as
2299 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2301 # If the hash table value doesn't exist, nothing is done
2302 # (you have to parse the Environment variables yourself).
2303 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2304 # Perl string interpolation of variables (i.e., $var, @array,
2305 # %hash) is performed. This means that $@%\ must be protected
2306 # with a \.
2308 %ScriptingCGIvariables = (
2309 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2310 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2311 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2312 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2313 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2314 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2315 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2316 "text/ssprolog" => '', # Gnu prolog (interpolated)
2317 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2318 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2319 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2320 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2321 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2323 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2324 "text/sspraat" => '', # Praat (interpolation)
2325 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2326 "text/postgresql" => '', # PostgreSQL (interpolation)
2328 # Not real scripting, but the use of other applications
2329 "text/ssmailto" => '', # MAILTO, (interpolation)
2330 "text/ssdisplay" => '', # Display, (interpolation)
2331 "text/sslogfile" => '', # Log to file, (interpolation)
2333 "" => ""
2336 # If you want something added in front or at the back of each script
2337 # block as send to the interpreter add it here.
2338 # mime => "string", e.g., "text/sspython" => "python commands"
2339 %ScriptingPrefix = (
2340 "text/testperl" => "\# Prefix Code;", # Perl script testing
2341 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2343 "" => ""
2345 # If you want something added at the end of each script block
2346 %ScriptingPostfix = (
2347 "text/testperl" => "\# Postfix Code;", # Perl script testing
2348 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2350 "" => ""
2352 # If you need initialization code, directly after opening
2353 %ScriptingInitialization = (
2354 "text/testperl" => "\# Initialization Code;", # Perl script testing
2355 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2356 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2357 "text/xlispstat" => '(prog1 nil ', # xlispstat
2358 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2360 "" => ""
2362 # If you need cleanup code before closing
2363 %ScriptingCleanup = (
2364 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2365 "text/sspraat" => 'Quit',
2366 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2367 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2368 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2369 "text/postgresql" => '\q', # quit psql
2370 "text/ssdisplay" => "", # close cat
2372 "" => ""
2375 # End of configuration for foreign scripting languages
2377 ###############################################################################
2379 # Initialization Code
2382 sub Initialize_Request
2384 ###############################################################################
2386 # ENVIRONMENT VARIABLES
2388 # Use environment variables to configure CGIscriptor on a temporary basis.
2389 # If you define any of the configurable variables as environment variables,
2390 # these are used instead of the "hard coded" values above.
2392 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2393 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2396 # Substitution strings, these are used internally to handle the
2397 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2398 $HOME_SUB = $SS_PUB;
2399 $SCRIPT_SUB = $SS_SCRIPT;
2402 # Make sure all script are reliably loaded
2403 push(@INC, $SS_SCRIPT);
2406 # Add the directory separator to the "home" directories.
2407 # (This is required for ~/ and ./ substitution)
2408 $HOME_SUB .= '/' if $HOME_SUB;
2409 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2411 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2412 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2413 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2414 $default_values{'CGI_HOME'} = $CGI_HOME;
2415 $ENV{'HOME'} = $CGI_HOME;
2416 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2417 # to the scripts)
2418 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2419 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2421 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2422 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2423 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2424 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2425 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2426 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2428 # Parse file names
2429 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2430 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2431 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2432 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2434 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2435 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2436 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2437 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2439 @CGIscriptorResults = (); # A stack of results
2441 # end of Environment variables
2443 #############################################################################
2445 # Define and Store "standard" values
2447 # BEFORE doing ANYTHING check the size of Query String
2448 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2450 # The Translated Query String and the Actual length of the (decoded)
2451 # Query String
2452 if($ENV{'QUERY_STRING'})
2454 # If this can contain '`"-quotes, be carefull to use it QUOTED
2455 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2456 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2459 # Get the current Date and time and store them as default variables
2461 # Get Local Time
2462 $LocalTime = localtime;
2464 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2465 # CGI_Hour CGI_Minutes CGI_Seconds
2467 $default_values{CGI_Date} = $LocalTime;
2468 ($default_values{CGI_WeekDay},
2469 $default_values{CGI_Month},
2470 $default_values{CGI_Day},
2471 $default_values{CGI_Time},
2472 $default_values{CGI_Year}) = split(' ', $LocalTime);
2473 ($default_values{CGI_Hour},
2474 $default_values{CGI_Minutes},
2475 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2477 # GMT:
2478 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2479 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2481 ($default_values{CGI_GMTSeconds},
2482 $default_values{CGI_GMTMinutes},
2483 $default_values{CGI_GMTHour},
2484 $default_values{CGI_GMTDay},
2485 $default_values{CGI_GMTMonth},
2486 $default_values{CGI_GMTYear},
2487 $default_values{CGI_GMTWeekDay},
2488 $default_values{CGI_GMTYearDay},
2489 $default_values{CGI_GMTisdst}) = gmtime;
2493 # End of Initialize Request
2495 ###################################################################
2497 # SECURITY: ACCESS CONTROL
2499 # Check the credentials of each client (use pattern matching, domain first).
2500 # This subroutine will kill-off (die) the current process whenever access
2501 # is denied.
2503 sub Access_Control
2505 # >>>>>>>>>>Start Remove
2507 # ACCEPTED CLIENTS
2509 # Only accept clients which are authorized, reject all unnamed clients
2510 # if REMOTE_HOST is given.
2511 # If file patterns are given, check whether the user is authorized for
2512 # THIS file.
2513 if($CGI_Accept)
2515 # Use local variables, REMOTE_HOST becomes '-' if undefined
2516 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2517 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2518 my $PATH_INFO = $ENV{'PATH_INFO'};
2520 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2521 $NoAccess = 1;
2522 while(<CGI_Accept>)
2524 next unless /\S/; # Skip empty lines
2525 next if /^\s*\#/; # Skip comments
2527 # Full expressions
2528 if(/^\s*-e\s/is)
2530 my $Accept = $'; # Get the expression
2531 $NoAccess &&= eval($Accept); # evaluate the expresion
2533 else
2535 my ($Accept, @FilePatternList) = split;
2536 if($Accept eq '*' # Always match
2537 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2538 || (
2539 $Accept =~ /^[0-9\.]+$/
2540 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2544 if($FilePatternList[0])
2546 foreach $Pattern (@FilePatternList)
2548 # Check whether this patterns is accepted
2549 $NoAccess &&= ($PATH_INFO !~ m@\Q$Pattern\E@is);
2552 else
2554 $NoAccess = 0; # No file patterns -> Accepted
2558 # Blocked
2559 last unless $NoAccess;
2561 close(CGI_Accept);
2562 if($NoAccess){ dieHandler(4, "No Access: $PATH_INFO\n");};
2566 # REJECTED CLIENTS
2568 # Reject named clients, accept all unnamed clients
2569 if($CGI_Reject)
2571 # Use local variables, REMOTE_HOST becomes '-' if undefined
2572 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
2573 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2574 my $PATH_INFO = $ENV{'PATH_INFO'};
2576 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
2577 $NoAccess = 0;
2578 while(<CGI_Reject>)
2580 next unless /\S/; # Skip empty lines
2581 next if /^\s*\#/; # Skip comments
2583 # Full expressions
2584 if(/^-e\s/is)
2586 my $Reject = $'; # Get the expression
2587 $NoAccess ||= eval($Reject); # evaluate the expresion
2589 else
2591 my ($Reject, @FilePatternList) = split;
2592 if($Reject eq '*' # Always match
2593 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
2594 ||($Reject =~ /^[0-9\.]+$/
2595 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
2599 if($FilePatternList[0])
2601 foreach $Pattern (@FilePatternList)
2603 $NoAccess ||= ($PATH_INFO =~ m@\Q$Pattern\E@is);
2606 else
2608 $NoAccess = 1; # No file patterns -> Rejected
2612 last if $NoAccess;
2614 close(CGI_Reject);
2615 if($NoAccess){ dieHandler(6, "Request rejected: $PATH_INFO\n");};
2618 ##########################################################<<<<<<<<<<End Remove
2621 # Get the filename
2623 # Does the filename contain any illegal characters (e.g., |, >, or <)
2624 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
2625 # Does the pathname contain an illegal (blocked) "directory"
2626 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
2627 # Does the pathname contain a direct referencer to BinaryMapFile
2628 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
2630 # SECURITY: Is PATH_INFO allowed?
2631 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
2632 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
2634 # Unsupported file types can be processed by a special raw-file
2635 if($BinaryMapFile)
2637 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
2638 $ENV{'PATH_INFO'} = $BinaryMapFile;
2640 else
2642 dieHandler(10, "Illegal file\n");
2648 # End of Security Access Control
2651 ############################################################################
2653 # Get the POST part of the query and add it to the QUERY_STRING.
2656 sub Get_POST_part_of_query
2659 # If POST, Read data from stdin to QUERY_STRING
2660 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
2662 # SECURITY: Check size of Query String
2663 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
2664 my $QueryRead = 0;
2665 my $SystemRead = $ENV{'CONTENT_LENGTH'};
2666 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
2667 while($SystemRead > 0)
2669 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
2670 $ENV{'QUERY_STRING'} .= $Post;
2671 $SystemRead -= $QueryRead;
2673 # Update decoded Query String
2674 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2675 $default_values{CGI_Content_Length} =
2676 length($default_values{CGI_Decoded_QS});
2680 # End of getting POST part of query
2683 ############################################################################
2685 # Start (HTML) output and logging
2686 # (if there are irregularities, it can kill the current process)
2689 sub Initialize_output
2691 # Construct the REAL file path (except for STDIN on the command line)
2692 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
2693 $file_path =~ s/\?.*$//; # Remove query
2694 # This is only necessary if your server does not catch ../ directives
2695 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
2697 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
2698 if($file_path eq '-')
2700 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
2701 && ($ENV{'SERVER_SOFTWARE'}
2702 || $ENV{'SERVER_NAME'}
2703 || $ENV{'GATEWAY_INTERFACE'}
2704 || $ENV{'SERVER_PROTOCOL'}
2705 || $ENV{'SERVER_PORT'}
2706 || $ENV{'REMOTE_ADDR'}
2707 || $ENV{'HTTP_USER_AGENT'});
2712 if($ClientLog)
2714 open(ClientLog, ">>$ClientLog");
2715 print ClientLog "$LocalTime | ",
2716 ($ENV{REMOTE_USER} || "-"), " ",
2717 ($ENV{REMOTE_IDENT} || "-"), " ",
2718 ($ENV{REMOTE_HOST} || "-"), " ",
2719 $ENV{REMOTE_ADDR}, " ",
2720 $ENV{PATH_INFO}, " ",
2721 $ENV{'CGI_BINARY_FILE'}, " ",
2722 ($default_values{CGI_Content_Length} || "-"),
2723 "\n";
2724 close(ClientLog);
2726 if($QueryLog)
2728 open(QueryLog, ">>$QueryLog");
2729 print QueryLog "$LocalTime\n",
2730 ($ENV{REMOTE_USER} || "-"), " ",
2731 ($ENV{REMOTE_IDENT} || "-"), " ",
2732 ($ENV{REMOTE_HOST} || "-"), " ",
2733 $ENV{REMOTE_ADDR}, ": ",
2734 $ENV{PATH_INFO}, " ",
2735 $ENV{'CGI_BINARY_FILE'}, "\n";
2737 # Write Query to Log file
2738 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
2739 close(QueryLog);
2742 # Return the file path
2743 return $file_path;
2746 # End of Initialize output
2749 ############################################################################
2751 # Handle login access
2753 # Access is based on a valid session ticket.
2754 # Session tickets should be dependend on user name
2755 # and IP address. The patterns of URLs for which a
2756 # session ticket is needed and the login URL are stored in
2757 # %TicketRequiredPatterns as:
2758 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
2761 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
2763 # No patterns, no login
2764 return 0 unless %TicketRequiredPatterns;
2766 # Get and initialize values (watch out for stuff processed by BinaryMap files)
2767 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
2768 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
2769 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2770 return 0 if $REMOTE_ADDR =~ /[^0-9\.]/;
2771 # Extract TICKETs, starting with returned cookies
2772 CGIexecute::defineCGIvariable('LOGINTICKET', "");
2773 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
2774 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
2775 if($ENV{'COOKIE_JAR'})
2777 my $CurrentCookieJar = $ENV{'COOKIE_JAR'};
2778 $CurrentCookieJar =~ s/\w+\=\-\s*(\;\s*|$)//isg;
2779 if($CurrentCookieJar =~ /\s*CGIscriptorLOGIN\=\s*([^\;]+)/)
2781 ${"CGIexecute::LOGINTICKET"} = $1;
2783 if($CurrentCookieJar =~ /\s*CGIscriptorCHALLENGE\=\s*([^\;]+)/ && $1 ne '-')
2785 ${"CGIexecute::CHALLENGETICKET"} = $1;
2787 if($CurrentCookieJar =~ /\s*CGIscriptorSESSION\=\s*([^\;]+)/ && $1 ne '-')
2789 ${"CGIexecute::SESSIONTICKET"} = $1;
2792 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_+.)
2793 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
2794 return 0 if ($LOGINTICKET && $LOGINTICKET =~ /[^\w\.]/isg);
2795 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
2796 return 0 if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
2797 my $CHALLENGETICKET = ${"CGIexecute::CHALLENGETICKET"};
2798 return 0 if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w\.]/isg);
2799 # Look for a LOGOUT message
2800 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
2801 # Username and password
2802 CGIexecute::defineCGIvariable('CGIUSERNAME', "");
2803 my $username = lc(${"CGIexecute::CGIUSERNAME"});
2804 return 0 if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
2805 my $userfile = lc($username);
2806 $userfile =~ s/[^\w]/_/isg;
2807 CGIexecute::defineCGIvariable('PASSWORD', "");
2808 my $password = ${"CGIexecute::PASSWORD"};
2809 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
2810 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
2812 foreach my $pattern (keys(%TicketRequiredPatterns))
2814 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
2815 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
2817 # Fall through a sieve of requirements
2818 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
2819 # If a LOGOUT is present, remove everything
2820 if($LOGOUT && !$LOGINTICKET)
2822 unlink "$SessionPath/$LOGINTICKET" if $LOGINTICKET && (-s "$SessionPath/$LOGINTICKET");
2823 $LOGINTICKET = "";
2824 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
2825 $SESSIONTICKET = "";
2826 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
2827 $CHALLENGETICKET = "";
2828 unlink "$SessionPath/$REMOTE_ADDR" if (-s "$SessionPath/$$REMOTE_ADDR");
2829 $CHALLENGETICKET = "";
2830 goto Login;
2832 # Is there a change password request?
2833 if($newpassword && $LOGINTICKET && $username)
2835 my $tickets_removed = remove_expired_tickets($SessionPath);
2836 goto Login unless (-s "$SessionPath/$LOGINTICKET");
2837 goto Login unless (-s "$PasswordsPath/$userfile");
2838 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
2839 goto Login unless $ticket_valid;
2841 my ($sessiontype, $currentticket) = ("", "");
2842 if($CHALLENGETICKET) {($sessiontype, $currentticket) = ("CHALLENGE", $CHALLENGETICKET);}
2843 elsif($SESSIONTICKET) {($sessiontype, $currentticket) = ("SESSION", $SESSIONTICKET);}
2844 elsif(-s "$SessionPath/$REMOTE_ADDR") {($sessiontype, $currentticket) = ("IPADDRESS", $REMOTE_ADDR);
2846 if($sessiontype)
2848 goto Login unless (-s "$SessionPath/$currentticket");
2849 my $ticket_valid = check_ticket_validity($sessiontype, "$SessionPath/$currentticket", $REMOTE_ADDR, $PATH_INFO);
2850 goto Login unless $ticket_valid;
2852 # Authorize
2853 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$currentticket", "$PasswordsPath/$userfile", $password, $newpassword);
2854 # After a change of password, you have to login again for a CHALLENGE
2855 if($CHALLENGETICKET){$CHALLENGETICKET = "";};
2856 # Ready
2857 return 0;
2859 # Is there a login ticket of this name?
2860 elsif($LOGINTICKET)
2862 my $tickets_removed = remove_expired_tickets($SessionPath);
2863 goto Login unless (-s "$SessionPath/$LOGINTICKET");
2864 goto Login unless (-s "$PasswordsPath/$userfile");
2865 my $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
2866 goto Login unless $ticket_valid;
2867 $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
2868 goto Login unless $ticket_valid;
2870 # Remove any lingering tickets
2871 unlink "$SessionPath/$SESSIONTICKET" if $SESSIONTICKET && (-s "$SessionPath/$SESSIONTICKET");
2872 $SESSIONTICKET = "";
2873 unlink "$SessionPath/$CHALLENGETICKET" if $CHALLENGETICKET && (-s "$SessionPath/$CHALLENGETICKET");
2874 $CHALLENGETICKET = "";
2877 # Authorize
2878 my $TMPTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password, $SessionPath);
2879 if($TMPTICKET)
2881 my $authorization = read_ticket("$PasswordsPath/$userfile");
2882 # Session type is read from the userfile
2883 if($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "CHALLENGE")
2885 # Create New Random CHALLENGETICKET
2886 $CHALLENGETICKET = $TMPTICKET;
2887 create_session_file("$SessionPath/$CHALLENGETICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
2889 elsif($authorization->{"Session"} && $authorization->{"Session"}->[0] eq "IPADDRESS")
2891 create_session_file("$SessionPath/$REMOTE_ADDR", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
2893 else
2895 $SESSIONTICKET = $TMPTICKET;
2896 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
2897 $SETCOOKIELIST{"CGIscriptorSESSION"} = "-";
2900 # Login ticket file has been used, remove it
2901 unlink($loginfile);
2903 # Is there a session ticket of this name?
2904 # CHALLENGE
2905 if($CHALLENGETICKET)
2907 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
2908 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
2909 goto Login unless $ticket_valid;
2911 my $oldchallenge = read_ticket("$SessionPath/$CHALLENGETICKET");
2912 my $userfile = lc($oldchallenge->{"Username"}->[0]);
2913 $userfile =~ s/[^\w]/_/isg;
2914 goto Login unless (-s "$PasswordsPath/$userfile");
2916 $ticket_valid = check_ticket_validity("PASSWORD", "$PasswordsPath/$userfile", $REMOTE_ADDR, $PATH_INFO);
2917 goto Login unless $ticket_valid;
2919 my $NEWCHALLENGETICKET = "";
2920 $NEWCHALLENGETICKET = copy_challenge_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $SessionPath);
2921 # Sessionticket is available to scripts, do NOT set the cookie
2922 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
2923 return 0;
2925 # IPADDRESS
2926 elsif(-s "$SessionPath/$REMOTE_ADDR")
2928 my $ticket_valid = check_ticket_validity("IPADDRESS", "$SessionPath/$REMOTE_ADDR", $REMOTE_ADDR, $PATH_INFO);
2929 goto Login unless $ticket_valid;
2930 return 0;
2932 # SESSION
2933 elsif($SESSIONTICKET)
2935 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
2936 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
2937 goto Login unless $ticket_valid;
2938 # Sessionticket is available to scripts
2939 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
2940 return 0;
2943 goto Login;
2944 return 0;
2947 return 0;
2949 Login:
2950 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
2951 # Note, cookies are set only ONCE
2952 $SETCOOKIELIST{"CGIscriptorLOGIN"} = "-";
2953 return "$YOUR_HTML_FILES/$Login";
2956 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath) => SESSIONTICKET First two arguments are file paths
2958 my $loginfile = shift || "";
2959 my $authorizationfile = shift || "";
2960 my $password = shift || "";
2961 my $SessionPath = shift || "";
2963 # Get Login session ticket
2964 my $loginticket = read_ticket($loginfile);
2965 # Get User credentials for authorization
2966 my $authorization = read_ticket($authorizationfile);
2968 # Get Randomsalt
2969 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
2970 return "" unless $Randomsalt;
2972 my $storedpassword = $authorization->{'Password'}->[0];
2973 return "" unless $storedpassword;
2974 # Without the "bash -c", the 'echo -n' could use sh, which does not recognize the -n option
2975 my $Hashedpassword = `bash -c 'echo -n $Randomsalt$storedpassword| $ENV{"SHASUMCMD"}'`;
2976 chomp($Hashedpassword);
2977 return "" unless $password eq $Hashedpassword;
2979 # Extract Session Ticket
2980 my $loginsession = $loginticket->{'Session'}->[0];
2981 my $sessionticket = `bash -c 'echo -n $loginsession$storedpassword| $ENV{"SHASUMCMD"}'`;
2982 chomp($sessionticket);
2983 $sessionticket = "" if -x "$SessionPath/$sessionticket";
2985 return $sessionticket;
2988 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First two arguments are file paths
2990 my $loginfile = shift || "";
2991 my $sessionfile = shift || "";
2992 my $authorizationfile = shift || "";
2993 my $password = shift || "";
2994 my $newpassword = shift || "";
2995 # Get Login session ticket
2996 my $loginticket = read_ticket($loginfile);
2997 # Login ticket file has been used, remove it
2998 unlink($loginfile);
2999 # Get Randomsalt
3000 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
3002 return "" unless $Randomsalt;
3004 # Get session ticket
3005 my $sessionticket = read_ticket($sessionfile);
3006 # Get User credentials for authorization
3007 my $authorization = read_ticket($authorizationfile);
3008 return "" unless lc($authorization->{'Username'}->[0]) eq lc($sessionticket->{'Username'}->[0]);
3010 my $storedpassword = $authorization->{'Password'}->[0];
3011 # Without the "bash -c", the 'echo -n' could use sh, which does not recognize the -n option
3012 my $Hashedpassword = `bash -c 'echo -n $Randomsalt$storedpassword| $ENV{"SHASUMCMD"}'`;
3013 chomp($Hashedpassword);
3014 return "" unless $password eq $Hashedpassword;
3016 # Decrypt the $newpassword
3017 my $loginticketid = $loginticket->{'Session'}->[0];
3018 my $passwordkey = `bash -c 'echo -n $loginticketid$storedpassword| $ENV{"SHASUMCMD"}'`;
3019 chomp($passwordkey);
3020 my $decryptedPassword = XOR_hex_strings($passwordkey, $newpassword);
3021 return "" unless $decryptedPassword;
3022 # Authorization succeeded, change password
3023 $authorization->{'Password'}->[0] = $decryptedPassword;
3025 open(USERFILE, "<$authorizationfile") || die "<$authorizationfile: $!\n";
3026 my @USERlines = <USERFILE>;
3027 close(USERFILE);
3028 # Change
3029 open(USERFILE, ">$authorizationfile") || die ">$authorizationfile: $!\n";
3030 foreach my $line (@USERlines)
3032 $line =~ s/^Password: ($storedpassword)$/Password: $decryptedPassword/ig;
3033 print USERFILE $line;
3035 close(USERFILE);
3037 return $newpassword;
3040 sub XOR_hex_strings # (hex1, hex2) -> hex
3042 my $hex1 = shift || "";
3043 my $hex2 = shift || "";
3044 my @hex1list = split('', $hex1);
3045 my @hex2list = split('', $hex2);
3046 my @hexresultlist = ();
3047 for(my $i; $i < scalar(@hex1list); ++$i)
3049 my $d1 = hex($hex1list[$i]);
3050 my $d2 = hex($hex2list[$i]);
3051 my $dresult = ($d1 ^ $d2);
3052 $hexresultlist[$i] = sprintf("%x", $dresult);
3054 $hexresult = join('', @hexresultlist);
3055 return $hexresult;
3058 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
3059 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
3061 my $oldchallengefile = shift || "";
3062 my $authorizationfile = shift || "";
3063 my $sessionpath = shift || "";
3064 $sessionpath =~ s!/+$!!g;
3066 # Get Login session ticket
3067 my $oldchallenge = read_ticket($oldchallengefile);
3069 # Get Authorization (user) session file
3070 my $authorization = read_ticket($authorizationfile);
3071 my $storedpassword = $authorization->{'Password'}->[0];
3072 return "" unless $storedpassword;
3073 my $challengekey = $oldchallenge->{'Key'}->[0];
3074 return "" unless $challengekey;
3076 # Create Random Hash Salt
3077 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
3078 my $NEWCHALLENGETICKET = <URANDOM>;
3079 close(URANDOM);
3080 chomp($NEWCHALLENGETICKET);
3081 my $newchallengefile = `bash -c 'echo -n $NEWCHALLENGETICKET$challengekey| $ENV{"SHASUMCMD"}'`;
3082 chomp($newchallengefile);
3083 return "" unless $newchallengefile;
3085 $ENV{'CHALLENGETICKET'} = $NEWCHALLENGETICKET;
3086 CGIexecute::defineCGIvariable('CHALLENGETICKET', "");
3087 ${"CGIexecute::CHALLENGETICKET"} = $NEWCHALLENGETICKET;
3089 # Write Session Ticket
3090 open(OLDCHALLENGE, "<$oldchallengefile") || die "<$oldchallengefile: $!\n";
3091 my @OldChallengeLines = <OLDCHALLENGE>;
3092 close(OLDCHALLENGE);
3093 # Old file should now be removed
3094 unlink($oldchallengefile);
3096 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
3097 foreach $line (@OldChallengeLines)
3099 print SESSION $line;
3101 close(SESSION);
3103 return $NEWCHALLENGETICKET;
3106 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
3108 my $PasswordDir = shift || "";
3109 my $SessionDir = shift || "";
3110 my $IPaddress = shift || "";
3112 # Create Login Ticket
3113 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $!\n";
3114 my $LOGINTICKET= <URANDOM>;
3115 close(URANDOM);
3116 chomp($LOGINTICKET);
3118 # Create Random Hash Salt
3119 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
3120 my $RANDOMSALT= <URANDOM>;
3121 close(URANDOM);
3122 chomp($RANDOMSALT);
3124 # Create SALT file if it does not exist
3125 # Remove this, including test account for life system
3126 unless(-d "$SessionDir")
3128 `mkdir -p "$SessionDir"`;
3130 unless(-d "$PasswordDir")
3132 `mkdir -p "$PasswordDir"`;
3134 # Create SERVERSALT and default test account
3135 my $SERVERSALT = "";
3136 unless(-s "$PasswordDir/SALT")
3138 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $!\n";
3139 $SERVERSALT= <URANDOM>;
3140 chomp($SERVERSALT);
3141 close(URANDOM);
3142 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
3143 print SALTFILE "$SERVERSALT\n";
3144 close(SALTFILE);
3146 # Update test account (should be removed in live system)
3147 my @alltestusers = ("test", "testip", "testchallenge");
3148 foreach my $testuser (@alltestusers)
3150 if(-s "$PasswordDir/$testuser")
3152 my $storedpassword = `bash -c 'echo -n ${SERVERSALT}test${testuser} | $ENV{"SHASUMCMD"}'`;
3153 chomp($storedpassword);
3154 open(USERFILE, "<$PasswordDir/$testuser") || die "</Private/.Passwords/$testuser: $!\n";
3155 @USERlines = <USERFILE>;
3156 close(USERFILE);
3158 open(USERFILE, ">$PasswordDir/$testuser") || die ">/Private/.Passwords/$testuser: $!\n";
3159 # Add Password and Salt
3160 foreach my $line (@USERlines)
3162 $line =~ s/^Password: (.*)$/Password: $storedpassword/ig;
3163 $line =~ s/^Salt: (.*)$/Salt: $SERVERSALT/ig;
3165 print USERFILE $line;
3167 close(USERFILE);
3173 # Read in site Salt
3174 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3175 $SERVERSALT=<SALTFILE>;
3176 close(SALTFILE);
3177 chomp($SERVERSALT);
3179 # Create login session ticket
3180 open(LOGINTICKET, ">$SessionDir/$LOGINTICKET") || die "$SessionDir/$LOGINTICKET: $!\n";
3181 print LOGINTICKET << "ENDOFLOGINTICKET";
3182 Type: LOGIN
3183 IPaddress: $IPaddress
3184 Salt: $SERVERSALT
3185 Session: $LOGINTICKET
3186 Randomsalt: $RANDOMSALT
3187 Expires: +600s
3188 ENDOFLOGINTICKET
3189 close(LOGINTICKET);
3191 # Set global variables
3192 # $SERVERSALT
3193 $ENV{'SERVERSALT'} = $SERVERSALT;
3194 CGIexecute::defineCGIvariable('SERVERSALT', "");
3195 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3197 # $SESSIONTICKET
3198 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3199 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3200 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3202 # $RANDOMSALT
3203 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3204 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3205 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3207 # $LOGINTICKET
3208 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3209 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3210 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3212 return $ENV{'LOGINTICKET'};
3215 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3217 my $sessionfile = shift || "";
3218 my $loginfile = shift || "";
3219 my $authorizationfile = shift || "";
3220 my $path = shift || "";
3222 # Get Login session ticket
3223 my $loginticket = read_ticket($loginfile);
3225 # Get Authorization (user) session file
3226 my $authorization = read_ticket($authorizationfile);
3227 # For a Session or a Challenge, we need a stored key
3228 my $sessionkey = "";
3229 if($authorization->{'Session'} && $authorization->{'Session'}->[0] ne 'IPADDRESS')
3231 my $storedpassword = $authorization->{'Password'}->[0];
3232 my $loginticketid = $loginticket->{'Session'}->[0];
3233 $sessionkey = `bash -c 'echo -n $loginticketid$storedpassword| $ENV{"SHASUMCMD"}'`;
3234 chomp($sessionkey);
3237 my @IPaddress = @{$loginticket->{'IPaddress'}};
3238 my @AllowedPaths = @{$authorization->{'AllowedPaths'}};;
3239 my @DeniedPaths = @{$authorization->{'DeniedPaths'}};;
3240 my @Expires = @{$authorization->{'MaxLifetime'}};
3241 foreach my $pattern (keys(%TicketRequiredPatterns))
3243 if($path =~ m#$pattern#)
3245 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3246 push(@Expires, $validtime);
3250 # Write Session Ticket
3251 open(SESSION, ">$sessionfile") || die "$sessionfile: $!\n";
3252 if($authorization->{'Session'} && $authorization->{'Session'}->[0])
3254 print SESSION "Type: ", $authorization->{'Session'}->[0], "\n";
3256 else
3258 print SESSION "Type: SESSION\n";
3260 foreach my $address (@IPaddress)
3262 print SESSION "IPaddress: $address\n";
3264 foreach my $path (@AllowedPaths)
3266 print SESSION "AllowedPaths: $path\n";
3268 foreach my $path (@DeniedPaths)
3270 print SESSION "DeniedPaths: $path\n";
3272 foreach my $validtime (@Expires)
3274 print SESSION "Expires: $validtime\n";
3276 print SESSION "Username: ", lc($authorization->{'Username'}->[0]), "\n";
3277 print SESSION "Key: $sessionkey\n" if $sessionkey;
3278 close(SESSION);
3280 # Login file should now be removed
3281 return unlink($loginfile);
3284 sub check_ticket_validity # ($type, $ticketfile, $address, $path)
3286 my $type = shift || "SESSION";
3287 my $ticketfile = shift || "";
3288 my $address = shift || "";
3289 my $path = shift || "";
3291 # Is there a session ticket of this name?
3292 return 0 unless -s "$ticketfile";
3294 # There is a session ticket, is it linked to this IP address?
3295 my $ticket = read_ticket($ticketfile);
3297 # Is this the right type of ticket
3298 return unless $ticket->{"Type"}->[0] eq $type;
3300 # Does the IP address match?
3301 $IPmatches = 0;
3302 for my $IPpattern (@{$ticket->{"IPaddress"}})
3304 ++$IPmatches if $address =~ m#^$IPpattern#ig;
3306 return 0 unless !$ticket->{"IPaddress"} || $IPmatches;
3308 # Is the path denied
3309 my $Pathmatches = 0;
3310 foreach my $Pathpattern (@{$ticket->{"DeniedPaths"}})
3312 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3314 return 0 if @{$ticket->{"DeniedPaths"}} && $Pathmatches;
3316 # Is the path allowed
3317 my $Pathmatches = 0;
3318 foreach my $Pathpattern (@{$ticket->{"AllowedPaths"}})
3320 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3322 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
3324 # Is the ticket expired?
3325 my $Expired = 0;
3326 if($ticket->{"Expires"} && @{$ticket->{"Expires"}})
3328 my $CurrentTime = time();
3329 ++$Expired if($CurrentTime > $ticket->{"Expires"}->[0]);
3331 return 0 if $Expired;
3333 # Make login values available
3334 $ENV{"LOGINUSERNAME"} = lc($ticket->{'Username'}->[0]);
3335 $ENV{"LOGINIPADDRESS"} = $address;
3336 $ENV{"LOGINPATH"} = $path;
3337 $ENV{"SESSIONTYPE"} = $type unless $type eq "PASSWORD";
3339 return 1;
3343 sub remove_expired_tickets # ($path) -> number of tickets removed
3345 my $path = shift || "";
3346 return 0 unless $path;
3347 $path =~ s!/+$!!g;
3348 my $removed_tickets = 0;
3349 my @ticketlist = glob("$path/*");
3350 foreach my $ticketfile (@ticketlist)
3352 my $ticket = read_ticket($ticketfile);
3353 if(@{$ticket->{'Expires'}} && $ticket->{'Expires'}->[0] < time)
3355 unlink $ticketfile;
3356 ++$removed_tickets;
3359 return $removed_tickets;
3362 sub read_ticket # ($ticketfile) -> &%ticket
3364 my $ticketfile = shift || "";
3365 my $ticket = {};
3366 if($ticketfile && -s $ticketfile)
3368 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
3369 my @alllines = <TICKETFILE>;
3370 close(TICKETFILE);
3371 foreach my $currentline (@alllines)
3373 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
3375 my $Label = $1;
3376 my $Value = $2;
3377 # Recalculate expire date from relative time
3378 if($Label =~ /^Expires$/ig && $Value =~ /^\+/)
3380 # Get SessionTicket file stats
3381 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
3382 = stat("$ticketfile");
3383 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
3385 $ExpireTime = 24*3600*$1;
3387 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
3389 $ExpireTime = 60*$1;
3391 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
3393 $ExpireTime = 3600*$1;
3395 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
3397 $ExpireTime = $1;
3399 elsif($Value =~ /^\+(\d+)\s*$/)
3401 $ExpireTime = $1;
3404 my $ActualExpireTime = $ExpireTime + $ctime;
3405 $Value = $ActualExpireTime;
3407 $ticket->{$Label} = () unless exists($ticket->{$Label});
3408 push(@{$ticket->{$Label}}, $Value);
3412 if(exists($ticket->{Expires}))
3414 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
3416 return $ticket;
3419 # End of Handle login access
3422 ############################################################################
3424 # Handle foreign interpreters (i.e., scripting languages)
3426 # Insert perl code to execute scripts in foreign scripting languages.
3427 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
3428 # into an interpreter.
3429 # The code presented here is fairly confusing because it
3430 # actually writes perl code code to the output.
3432 # A table with the file handles
3433 %SCRIPTINGINPUT = ();
3435 # A function to clean up Client delivered CGI parameter values
3436 # (i.e., quote all odd characters)
3437 %SHRUBcharacterTR =
3439 "\'" => '&#39;',
3440 "\`" => '&#96;',
3441 "\"" => '&quot;',
3442 '&' => '&amper;',
3443 "\\" => '&#92;'
3446 sub shrubCGIparameter # ($String) -> Cleaned string
3448 my $String = shift || "";
3450 # Change all quotes [`'"] into HTML character entities
3451 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
3453 # Protect &
3454 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
3456 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
3458 next if $Char eq '&';
3459 $String =~ s/\Q$Char\E/$Transcript/isg;
3462 # Replace newlines
3463 $String =~ s/[\n]/\\n/g;
3464 # Replace control characters with their backslashed octal ordinal numbers
3465 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
3466 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
3468 return $String;
3472 # The initial open statements: Open a pipe to the foreign script interpreter
3473 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
3475 my $ContentType = lc(shift) || return "";
3476 my $NewDirective = "";
3478 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
3480 # Construct a unique file handle name
3481 $SCRIPTINGFILEHANDLE = uc($ContentType);
3482 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
3483 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
3484 unless $SCRIPTINGINPUT{$ContentType};
3486 # Create the relevant script: Open the pipe to the interpreter
3487 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
3488 # Open interpreter for '$ContentType'
3489 # Open pipe to interpreter (if it isn't open already)
3490 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
3491 BLOCKCGISCRIPTOROPEN
3493 # Insert Initialization code and CGI variables
3494 $NewDirective .= InitializeForeignScript($ContentType);
3496 # Ready
3497 return $NewDirective;
3501 # The final closing code to stop the interpreter
3502 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
3504 my $ContentType = lc(shift) || return "";
3505 my $NewDirective = "";
3507 # Do nothing unless the pipe realy IS open
3508 return "" unless $SCRIPTINGINPUT{$ContentType};
3510 # Initial comment
3511 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
3514 # Write the Postfix code
3515 $NewDirective .= CleanupForeignScript($ContentType);
3517 # Create the relevant script: Close the pipe to the interpreter
3518 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
3519 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
3520 select(STDOUT); \$|=1;
3522 BLOCKCGISCRIPTORCLOSE
3524 # Remove the file handler of the foreign script
3525 delete($SCRIPTINGINPUT{$ContentType});
3527 return $NewDirective;
3531 # The initialization code for the foreign script interpreter
3532 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
3534 my $ContentType = lc(shift) || return "";
3535 my $NewDirective = "";
3537 # Add initialization code
3538 if($ScriptingInitialization{$ContentType})
3540 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
3541 # Initialization Code for '$ContentType'
3542 # Select relevant output filehandle
3543 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3545 # The Initialization code (if any)
3546 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
3547 $ScriptingInitialization{$ContentType}
3548 ${ContentType}INITIALIZATIONCODE
3550 BLOCKCGISCRIPTORINIT
3553 # Add all CGI variables defined
3554 if(exists($ScriptingCGIvariables{$ContentType}))
3556 # Start writing variable definitions to the Interpreter
3557 if($ScriptingCGIvariables{$ContentType})
3559 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
3560 # CGI variables (from the %default_values table)
3561 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
3562 BLOCKCGISCRIPTORVARDEF
3565 my ($N, $V);
3566 foreach $N (keys(%default_values))
3568 # Determine whether the parameter has been defined
3569 # (the eval is a workaround to get at the variable value)
3570 next unless eval("defined(\$CGIexecute::$N)");
3572 # Get the value from the EXECUTION environment
3573 $V = eval("\$CGIexecute::$N");
3574 # protect control characters (i.e., convert them to \0.. form)
3575 $V = shrubCGIparameter($V);
3577 # Protect interpolated variables
3578 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
3580 # Print the actual declaration for this scripting language
3581 if($ScriptingCGIvariables{$ContentType})
3583 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
3584 $NewDirective .= "\n";
3588 # Stop writing variable definitions to the Interpreter
3589 if($ScriptingCGIvariables{$ContentType})
3591 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
3592 ${ContentType}CGIVARIABLES
3593 BLOCKCGISCRIPTORVARDEFEND
3598 $NewDirective .= << "BLOCKCGISCRIPTOREND";
3600 # Select STDOUT filehandle
3601 select(STDOUT); \$|=1;
3603 BLOCKCGISCRIPTOREND
3605 return $NewDirective;
3609 # The cleanup code for the foreign script interpreter
3610 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
3612 my $ContentType = lc(shift) || return "";
3613 my $NewDirective = "";
3615 # Return if not needed
3616 return $NewDirective unless $ScriptingCleanup{$ContentType};
3618 # Create the relevant script: Open the pipe to the interpreter
3619 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
3620 # Cleanup Code for '$ContentType'
3621 # Select relevant output filehandle
3622 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3623 # Print Cleanup code to foreign script
3624 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
3625 $ScriptingCleanup{$ContentType}
3626 ${ContentType}SCRIPTSTOP
3628 # Select STDOUT filehandle
3629 select(STDOUT); \$|=1;
3630 BLOCKCGISCRIPTORSTOP
3632 return $NewDirective;
3636 # The prefix code for each <script></script> block
3637 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
3639 my $ContentType = lc(shift) || return "";
3640 my $NewDirective = "";
3642 # Return if not needed
3643 return $NewDirective unless $ScriptingPrefix{$ContentType};
3645 my $Quote = "\'";
3646 # If the CGIvariables parameter is defined, but empty, interpolate
3647 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
3648 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
3649 !$ScriptingCGIvariables{$ContentType};
3651 # Add initialization code
3652 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
3653 # Prefix Code for '$ContentType'
3654 # Select relevant output filehandle
3655 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3657 # The block Prefix code (if any)
3658 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
3659 $ScriptingPrefix{$ContentType}
3660 ${ContentType}PREFIXCODE
3661 # Select STDOUT filehandle
3662 select(STDOUT); \$|=1;
3663 BLOCKCGISCRIPTORPREFIX
3665 return $NewDirective;
3669 # The postfix code for each <script></script> block
3670 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
3672 my $ContentType = lc(shift) || return "";
3673 my $NewDirective = "";
3675 # Return if not needed
3676 return $NewDirective unless $ScriptingPostfix{$ContentType};
3678 my $Quote = "\'";
3679 # If the CGIvariables parameter is defined, but empty, interpolate
3680 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
3681 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
3682 !$ScriptingCGIvariables{$ContentType};
3684 # Create the relevant script: Open the pipe to the interpreter
3685 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
3686 # Postfix Code for '$ContentType'
3687 # Select filehandle to interpreter
3688 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3689 # Print postfix code to foreign script
3690 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
3691 $ScriptingPostfix{$ContentType}
3692 ${ContentType}SCRIPTPOSTFIX
3693 # Select STDOUT filehandle
3694 select(STDOUT); \$|=1;
3695 BLOCKCGISCRIPTORPOSTFIX
3697 return $NewDirective;
3700 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
3702 my $ContentType = lc(shift) || return "";
3703 my $directive = shift || return "";
3704 my @SRCfile = @_;
3705 my $NewDirective = "";
3707 my $Quote = "\'";
3708 # If the CGIvariables parameter is defined, but empty, interpolate
3709 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
3710 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
3711 !$ScriptingCGIvariables{$ContentType};
3713 # Create the relevant script
3714 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
3715 # Insert Code for '$ContentType'
3716 # Select filehandle to interpreter
3717 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3718 BLOCKCGISCRIPTORINSERT
3720 # Use SRC feature files
3721 my $ThisSRCfile;
3722 while($ThisSRCfile = shift(@_))
3724 # Handle blocks
3725 if($ThisSRCfile =~ /^\s*\{\s*/)
3727 my $Block = $';
3728 $Block = $` if $Block =~ /\s*\}\s*$/;
3729 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
3730 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
3731 $Block
3732 ${ContentType}SRCBLOCKCODE
3733 BLOCKCGISCRIPTORSRCBLOCK
3735 next;
3738 # Handle files
3739 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
3740 # Read $ThisSRCfile
3741 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
3742 while(<SCRIPTINGSOURCE>)
3744 print $SCRIPTINGINPUT{$ContentType} \$_;
3746 close(SCRIPTINGSOURCE);
3748 BLOCKCGISCRIPTORSRCFILES
3752 # Add the directive
3753 if($directive)
3755 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
3756 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
3757 $directive
3758 ${ContentType}DIRECTIVECODE
3759 BLOCKCGISCRIPTORINSERT
3763 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
3764 # Select STDOUT filehandle
3765 select(STDOUT); \$|=1;
3766 BLOCKCGISCRIPTORSELECT
3768 # Ready
3769 return $NewDirective;
3772 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
3774 my $ContentType;
3775 foreach $ContentType (keys(%SCRIPTINGINPUT))
3777 my $directive = CloseForeignScript($ContentType);
3778 print STDERR "\nDirective $CGI_Date: ", $directive;
3779 CGIexecute->evaluate($directive);
3784 # End of handling foreign (external) scripting languages.
3786 ############################################################################
3788 # A subroutine to handle "nested" quotes, it cuts off the leading
3789 # item or quoted substring
3790 # E.g.,
3791 # ' A_word and more words' -> @('A_word', ' and more words')
3792 # '"quoted string" The rest' -> @('quoted string', ' The rest')
3793 # (this is needed for parsing the <TAGS> and their attributes)
3794 my $SupportedQuotes = "\'\"\`\(\{\[";
3795 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
3796 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
3798 my @Result = ();
3799 my $String = shift || return @Result;
3801 if($String =~ /^\s*([\w\/\-\.]+)/is)
3803 push(@Result, $1, $');
3805 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
3807 my $BackSlash = $1 || "";
3808 my $OpenQuote = $2;
3809 my $CloseQuote = $OpenQuote;
3810 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
3812 if($BackSlash)
3814 $String =~ /^\s*\\\Q$OpenQuote\E/i;
3815 my $Onset = $';
3816 $Onset =~ /\\\Q$CloseQuote\E/i;
3817 my $Rest = $';
3818 my $Item = $`;
3819 push(@Result, $Item, $Rest);
3822 else
3824 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
3825 push(@Result, $1, $');
3828 else
3830 push(@Result, "", $String);
3832 return @Result;
3835 # Now, start with the real work
3837 # Control the output of the Content-type: text/html\n\n message
3838 my $SupressContentType = 0;
3840 # Process a file
3841 sub ProcessFile # ($file_path)
3843 my $file_path = shift || return 0;
3846 # Generate a unique file handle (for recursions)
3847 my @SRClist = ();
3848 my $FileHandle = "file";
3849 my $n = 0;
3850 while(!eof($FileHandle.$n)) {++$n;};
3851 $FileHandle .= $n;
3853 # Start HTML output
3854 # Use the default Content-type if this is NOT a raw file
3855 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
3856 || $SupressContentType)
3858 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
3859 my $ContentType = $ContentTypeTable{$1};
3860 print "Content-type: $ContentType\n";
3861 if(%SETCOOKIELIST && keys(%SETCOOKIELIST))
3863 foreach my $name (keys(%SETCOOKIELIST))
3865 my $value = $SETCOOKIELIST{$name};
3866 print "Set-Cookie: $name=$value\n";
3868 # Cookies are set only ONCE
3869 %SETCOOKIELIST = ();
3871 print "\n";
3872 $SupressContentType = 1; # Content type has been printed
3876 # Get access to the actual data. This can be from RAM (by way of an
3877 # environment variable) or by opening a file.
3879 # Handle the use of RAM images (file-data is stored in the
3880 # $CGI_FILE_CONTENTS environment variable)
3881 # Note that this environment variable will be cleared, i.e., it is strictly for
3882 # single-use only!
3883 if($ENV{$CGI_FILE_CONTENTS})
3885 # File has been read already
3886 $_ = $ENV{$CGI_FILE_CONTENTS};
3887 # Sorry, you have to do the reading yourself (dynamic document creation?)
3888 # NOTE: you must read the whole document at once
3889 if($_ eq '-')
3891 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
3893 else # Clear environment variable
3895 $ENV{$CGI_FILE_CONTENTS} = '-';
3898 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
3899 # THIS IS A SECURITY FEATURE!
3900 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
3902 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
3903 push(@OpenFiles, $file_path);
3904 $_ = <$FileHandle>; # Read first line
3906 else
3908 print "<h2>File not found</h2>\n";
3909 dieHandler(18, "$file_path\n");
3912 $| = 1; # Flush output buffers
3914 # Initialize variables
3915 my $METAarguments = ""; # The CGI arguments from the latest META tag
3916 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
3917 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
3920 # Send document to output
3921 # Process the requested document.
3922 # Do a loop BEFORE reading input again (this catches the RAM/Database
3923 # type of documents).
3924 do {
3927 # Handle translations if needed
3929 performTranslation(\$_) if $TranslationPaths;
3931 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
3932 # There can be more than 1 <SCRIPT> or META tags on a line
3933 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
3935 my $directive = "";
3936 # Store rest of line
3937 my $Before = $`;
3938 my $ScriptTag = $&;
3939 my $After = $';
3940 my $TagType = uc($1);
3941 # The before part can be send to the output
3942 print $Before;
3944 # Read complete Tag from after and/or file
3945 until($After =~ /([^\\])\>/)
3947 $After .= <$FileHandle>;
3948 performTranslation(\$After) if $TranslationPaths;
3951 if($After =~ /([^\\])\>/)
3953 $ScriptTag .= $`.$&; # Keep the Script Tag intact
3954 $After = $';
3956 else
3958 dieHandler(19, "Closing > not found\n");
3961 # The tag could be closed by />, we handle this in the XML way
3962 # and don't process any content (we ignore whitespace)
3963 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
3966 # TYPE or CLASS?
3967 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
3968 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
3970 # Parse <SCRIPT> or <META> directive
3971 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
3972 # send the line to the output and go to the next loop
3973 my $CurrentContentType = "";
3974 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
3976 my ($Type) = ExtractQuotedItem($');
3977 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
3978 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
3979 # CSS classes are aliases of $ServerScriptContentType
3980 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
3982 $CurrentContentType = $ServerScriptContentType;
3987 # Not a known server-side content type, print and continue
3988 unless(($CurrentContentType =~
3989 /$ServerScriptContentType|$ShellScriptContentType/is) ||
3990 $ScriptingLanguages{$CurrentContentType})
3992 print $ScriptTag;
3993 $_ = $After;
3994 next;
3998 # A known server-side content type, evaluate
4000 # First, handle \> and \<
4001 $ScriptTag =~ s/\\\>/\>/isg;
4002 $ScriptTag =~ s/\\\</\</isg;
4004 # Extract the CGI, SRC, ID, IF and UNLESS attributes
4005 my %ScriptTagAttributes = ();
4006 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
4008 my $Attribute = $2;
4009 my $Rest = $';
4010 my $Value = "";
4011 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
4012 $ScriptTagAttributes{uc($Attribute)} = $Value;
4016 # The attribute used to define the CGI variables
4017 # Extract CGI-variables from
4018 # <META CONTENT="text/ssperl; CGI='' SRC=''">
4019 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
4020 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
4021 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
4022 if($ScriptTagAttributes{'CGI'})
4024 @ARGV = (); # Reset ARGV
4025 $ARGC = 0;
4026 $METAarguments = ""; # Reset the META CGI arguments
4027 @METAvalues = ();
4028 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
4030 # Process default values of variables ($<name> = 'default value')
4031 # Allowed quotes are '', "", ``, (), [], and {}
4032 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
4034 my $varType = $2 || '$'; # Variable or list
4035 my $name = $3; # The Name
4036 my $default = "";
4037 $Meta_CGI = $';
4039 if($Meta_CGI =~ /^\s*\=\s*/is)
4041 # Locate (any) default value
4042 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
4044 $RemainingTag = $Meta_CGI;
4047 # Define CGI (or ENV) variable, initalize it from the
4048 # Query string or the default value
4050 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
4051 # scripts to access the CGI arguments defined in the META tag
4052 # (Not for CGI inside <SCRIPT> tags)
4053 if($varType eq '$')
4055 CGIexecute::defineCGIvariable($name, $default)
4056 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
4057 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
4058 # Add value to the @ARGV list
4059 push(@ARGV, ${"CGIexecute::$name"});
4060 ++$ARGC;
4062 elsif($varType eq '@')
4064 CGIexecute::defineCGIvariableList($name, $default)
4065 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
4066 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
4067 # Add value to the @ARGV list
4068 push(@ARGV, @{"CGIexecute::$name"});
4069 $ARGC = scalar(@CGIexecute::ARGV);
4071 elsif($varType eq '%')
4073 CGIexecute::defineCGIvariableHash($name, $default)
4074 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
4075 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
4076 push(@METAvalues, "'".join("'", @PairList)."'");
4077 # Add value to the @ARGV list
4078 push(@ARGV, %{"CGIexecute::$name"});
4079 $ARGC = scalar(@CGIexecute::ARGV);
4082 # Store the values for internal and later use
4083 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
4085 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
4090 # The IF (conditional execution) Attribute
4091 # Evaluate the condition and stop unless it evaluates to true
4092 if($ScriptTagAttributes{'IF'})
4094 my $IFcondition = $ScriptTagAttributes{'IF'};
4096 # Convert SCRIPT calls, ./<script>
4097 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4099 # Convert FILE calls, ~/<file>
4100 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4102 # Block execution if necessary
4103 unless(CGIexecute->evaluate($IFcondition))
4105 %ScriptTagAttributes = ();
4106 $CurrentContentType = "";
4110 # The UNLESS (conditional execution) Attribute
4111 # Evaluate the condition and stop if it evaluates to true
4112 if($ScriptTagAttributes{'UNLESS'})
4114 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
4116 # Convert SCRIPT calls, ./<script>
4117 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4119 # Convert FILE calls, ~/<file>
4120 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4122 # Block execution if necessary
4123 if(CGIexecute->evaluate($UNLESScondition))
4125 %ScriptTagAttributes = ();
4126 $CurrentContentType = "";
4130 # The SRC (Source File) Attribute
4131 # Extract any source script files and add them in
4132 # front of the directive
4133 # The SRC list should be emptied
4134 @SRClist = ();
4135 my $SRCtag = "";
4136 my $Prefix = 1;
4137 my $PrefixDirective = "";
4138 my $PostfixDirective = "";
4139 # There is a SRC attribute
4140 if($ScriptTagAttributes{'SRC'})
4142 $SRCtag = $ScriptTagAttributes{'SRC'};
4143 # Remove "file://" prefixes
4144 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
4145 # Expand script filenames "./Script"
4146 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
4147 # Expand script filenames "~/Script"
4148 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
4151 # File source tags
4152 while($SRCtag =~ /\S/is)
4154 my $SRCdirective = "";
4156 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
4157 # SRC files
4158 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
4160 my $InsertionPlace = $1;
4161 $SRCtag = $2.$';
4163 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
4164 # Go to next round
4165 next;
4167 # {}-blocks are just evaluated by "do"
4168 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
4170 my $SRCblock = $';
4171 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
4173 $SRCblock = $`;
4174 $SRCtag = $1.$';
4175 # SAFEqx shell script blocks
4176 if($CurrentContentType =~ /$ShellScriptContentType/is)
4178 # Handle ''-quotes inside the script
4179 $SRCblock =~ s/[\']/\\$&/gis;
4181 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
4182 $SRCdirective .= $SRCblock."\n";
4184 # do { SRCblocks }
4185 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
4187 $SRCblock = "print do { $SRCblock };'';";
4188 $SRCdirective .= $SRCblock."\n";
4190 else # The interpreter should handle this
4192 push(@SRClist, "{ $SRCblock }");
4196 else
4197 { dieHandler(23, "Closing \} missing\n");};
4199 # Files are processed as Text or Executable files
4200 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
4202 my $SrcFile = $1;
4203 $SRCtag = $';
4205 # We are handling one of the external interpreters
4206 if($ScriptingLanguages{$CurrentContentType})
4208 push(@SRClist, $SrcFile);
4210 # We are at the start of a DIV tag, just load all SRC files and/or URL's
4211 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
4213 # $SrcFile is a URL pointing to an HTTP or FTP server
4214 if($SrcFile =~ m!^([a-z]+)\://!)
4216 my $URLoutput = CGIscriptor::read_url($SrcFile);
4217 $SRCdirective .= $URLoutput;
4219 # SRC file is an existing file
4220 elsif(-e "$SrcFile")
4222 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
4223 my $Content;
4224 while(sysread(DIVSOURCE, $Content, 1024) > 0)
4226 $SRCdirective .= $Content;
4228 close(DIVSOURCE);
4231 # Executable files are executed as
4232 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
4233 elsif(-x "$SrcFile")
4235 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
4237 # Handle 'standard' files, using ProcessFile
4238 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
4239 && $SrcFile =~ m@($FilePattern)$@) # A recursion
4242 # Do not process still open files because it can lead
4243 # to endless recursions
4244 if(grep(/^$SrcFile$/, @OpenFiles))
4245 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
4246 # Prepare meta arguments
4247 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
4248 # Process the file
4249 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
4251 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
4253 $SRCdirective .= GET_URL($SrcFile);
4255 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
4257 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
4258 $SRCdirective .= "do \'$SrcFile\';'';\n";
4260 else # This one could not be resolved (should be handled by BinaryMapFile)
4262 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
4267 # Postfix or Prefix
4268 if($Prefix)
4270 $PrefixDirective .= $SRCdirective;
4272 else
4274 $PostfixDirective .= $SRCdirective;
4277 # The prefix should be handled immediately
4278 $directive .= $PrefixDirective;
4279 $PrefixDirective = "";
4283 # Handle the content of the <SCRIPT></SCRIPT> tags
4284 # Do not process the content of <SCRIPT/>
4285 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
4287 my $EndScriptTag = "";
4289 # Execute SHELL scripts with SAFEqx()
4290 if($CurrentContentType =~ /$ShellScriptContentType/is)
4292 $directive .= "SAFEqx(\'";
4295 # Extract Program
4296 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
4298 $After .= <$FileHandle>;
4299 performTranslation(\$After) if $TranslationPaths;
4302 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
4304 $directive .= $`;
4305 $EndScriptTag = $&;
4306 $After = $';
4308 else
4310 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
4313 # Process only when content should be executed
4314 if($CurrentContentType)
4317 # Remove all comments from Perl scripts
4318 # (NOT from OS shell scripts)
4319 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
4320 if $CurrentContentType =~ /$ServerScriptContentType/i;
4322 # Convert SCRIPT calls, ./<script>
4323 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4325 # Convert FILE calls, ~/<file>
4326 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4328 # Execute SHELL scripts with SAFEqx(), closing bracket
4329 if($CurrentContentType =~ /$ShellScriptContentType/i)
4331 # Handle ''-quotes inside the script
4332 $directive =~ /SAFEqx\(\'/;
4333 $directive = $`.$&;
4334 my $Executable = $';
4335 $Executable =~ s/[\']/\\$&/gs;
4337 $directive .= $Executable."\');"; # Closing bracket
4340 else
4342 $directive = "";
4345 # Handle the content of the <DIV></DIV> tags
4346 # Do not process the content of <DIV/>
4347 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
4349 my $EndScriptTag = "";
4351 # Extract Text
4352 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
4354 $After .= <$FileHandle>;
4355 performTranslation(\$After) if $TranslationPaths;
4358 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
4360 $directive .= $`;
4361 $EndScriptTag = $&;
4362 $After = $';
4364 else
4366 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
4369 # Add the Postfixed directives (but only when it contains something printable)
4370 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
4371 $PostfixDirective = "";
4374 # Process only when content should be handled
4375 if($CurrentContentType)
4378 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
4379 # a valid Perl name). Names should not contain $, but we can handle it.
4380 my $name = $ScriptTagAttributes{'ID'};
4381 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
4382 $name = $1;
4384 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
4385 CGIexecute::defineCGIexecuteVariable($name, $directive);
4386 $directive = "";
4389 # Nothing to execute
4390 $directive = "";
4394 # Handle Foreign scripting languages
4395 if($ScriptingLanguages{$CurrentContentType})
4397 my $newDirective = "";
4398 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
4399 $newDirective .= PrefixForeignScript($CurrentContentType);
4400 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
4401 $newDirective .= PostfixForeignScript($CurrentContentType);
4402 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
4404 $newDirective .= '"";';
4406 $directive = $newDirective;
4410 # Add the Postfixed directives (but only when it contains something printable)
4411 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
4412 $PostfixDirective = "";
4415 # EXECUTE the script and print the results
4417 # Use this to debug the program
4418 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
4420 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
4421 $Result =~ s/\n$//g; # Remove final newline
4423 # Print the Result of evaluating the directive
4424 # (this will handle LARGE, >64 kB output)
4425 my $BytesWritten = 1;
4426 while($Result && $BytesWritten)
4428 $BytesWritten = syswrite(STDOUT, $Result, 64);
4429 $Result = substr($Result, $BytesWritten);
4431 # print $Result; # Could be used instead of above code
4433 # Store result if wanted, i.e., if $CGIscriptorResults has been
4434 # defined in a <META> tag.
4435 push(@CGIexecute::CGIscriptorResults, $Result)
4436 if exists($default_values{'CGIscriptorResults'});
4438 # Process the rest of the input line (this could contain
4439 # another directive)
4440 $_ = $After;
4442 print $_;
4443 } while(<$FileHandle>); # Read and Test AFTER first loop!
4445 close ($FileHandle);
4446 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
4450 ###############################################################################
4452 # Call the whole package
4454 sub Handle_Request
4456 my $file_path = "";
4458 # Initialization Code
4459 Initialize_Request();
4461 # SECURITY: ACCESS CONTROL
4462 Access_Control();
4464 # Read the POST part of the query, if there is one
4465 Get_POST_part_of_query();
4467 # Start (HTML) output and logging
4468 $file_path = Initialize_output();
4470 # Check login access or divert to login procedure
4471 $Use_Login = Log_In_Access();
4472 $file_path = $Use_Login if $Use_Login;
4474 # Record which files are still open (to avoid endless recursions)
4475 my @OpenFiles = ();
4477 # Record whether the default HTML ContentType has already been printed
4478 # but only if the SERVER uses HTTP or some other protocol that might interpret
4479 # a content MIME type.
4481 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
4483 # Process the specified file
4484 ProcessFile($file_path) if $file_path ne $SS_PUB;
4486 # Cleanup all open external (foreign) interpreters
4487 CloseAllForeignScripts();
4490 "" # SUCCESS
4493 # Make a single call to handle an (empty) request
4494 Handle_Request();
4497 # END OF PACKAGE MAIN
4500 ####################################################################################
4502 # The CGIEXECUTE PACKAGE
4504 ####################################################################################
4506 # Isolate the evaluation of directives as PERL code from the rest of the program.
4507 # Remember that each package has its own name space.
4508 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
4509 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
4511 package CGIexecute;
4513 sub evaluate
4515 my $self = shift;
4516 my $directive = shift;
4517 $directive = eval($directive);
4518 warn $@ if $@; # Write an error message to STDERR
4519 $directive; # Return value of directive
4523 # defineCGIexecuteVariable($name [, $value]) -> 0/1
4525 # Define and intialize variables inside CGIexecute
4526 # Does no sanity checking, for internal use only
4528 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
4530 my $name = shift || return 0; # The Name
4531 my $value = shift || ""; # The value
4533 ${$name} = $value;
4535 return 1;
4538 # defineCGIvariable($name [, $default]) -> 0/1
4540 # Define and intialize CGI variables
4541 # Tries (in order) $ENV{$name}, the Query string and the
4542 # default value.
4543 # Removes all '-quotes etc.
4545 sub defineCGIvariable # ($name [, $default]) -> 0/1
4547 my $name = shift || return 0; # The Name
4548 my $default = shift || ""; # The default value
4550 # Remove \-quoted characters
4551 $default =~ s/\\(.)/$1/g;
4552 # Store default values
4553 $::default_values{$name} = $default if $default;
4555 # Process variables
4556 my $temp = undef;
4557 # If there is a user supplied value, it replaces the
4558 # default value.
4560 # Environment values have precedence
4561 if(exists($ENV{$name}))
4563 $temp = $ENV{$name};
4565 # Get name and its value from the query string
4566 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
4568 $temp = ::YOUR_CGIPARSE($name);
4570 # Defined values must exist for security
4571 elsif(!exists($::default_values{$name}))
4573 $::default_values{$name} = undef;
4576 # SECURITY, do not allow '- and `-quotes in
4577 # client values.
4578 # Remove all existing '-quotes
4579 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
4580 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
4581 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
4582 # If $temp is empty, use the default value (if it exists)
4583 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
4585 $temp = $::default_values{$name};
4586 # Remove all existing '-quotes
4587 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
4588 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
4589 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
4591 else # Store current CGI values and remove defaults
4593 $::default_values{$name} = $temp;
4595 # Define the CGI variable and its value (in the execute package)
4596 ${$name} = $temp;
4598 # return SUCCES
4599 return 1;
4602 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
4604 my $name = shift || return 0; # The Name
4605 my $default = shift || ""; # The default value
4607 # Defined values must exist for security
4608 if(!exists($::default_values{$name}))
4610 $::default_values{$name} = $default;
4613 my @temp = ();
4616 # For security:
4617 # Environment values have precedence
4618 if(exists($ENV{$name}))
4620 push(@temp, $ENV{$name});
4622 # Get name and its values from the query string
4623 if($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
4625 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
4627 else
4629 push(@temp, $::default_values{$name});
4633 # SECURITY, do not allow '- and `-quotes in
4634 # client values.
4635 # Remove all existing '-quotes
4636 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
4637 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
4638 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
4640 # Store current CGI values and remove defaults
4641 $::default_values{$name} = $temp[0];
4643 # Define the CGI variable and its value (in the execute package)
4644 @{$name} = @temp;
4646 # return SUCCES
4647 return 1;
4650 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
4652 my $name = shift || return 0; # The Name
4653 my $default = shift || ""; # The default value
4655 # Defined values must exist for security
4656 if(!exists($::default_values{$name}))
4658 $::default_values{$name} = $default;
4661 my %temp = ();
4664 # For security:
4665 # Environment values have precedence
4666 if(exists($ENV{$name}))
4668 $temp{""} = $ENV{$name};
4670 # Get name and its values from the query string
4671 if($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
4673 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
4675 elsif($::default_values{$name} ne "")
4677 $temp{""} = $::default_values{$name};
4681 # SECURITY, do not allow '- and `-quotes in
4682 # client values.
4683 # Remove all existing '-quotes
4684 my $Key;
4685 foreach $Key (keys(%temp))
4687 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
4688 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
4689 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
4692 # Store current CGI values and remove defaults
4693 $::default_values{$name} = $temp{""};
4695 # Define the CGI variable and its value (in the execute package)
4696 %{$name} = ();
4697 my $tempKey;
4698 foreach $tempKey (keys(%temp))
4700 ${$name}{$tempKey} = $temp{$tempKey};
4703 # return SUCCES
4704 return 1;
4708 # SAFEqx('CommandString')
4710 # A special function that is a safe alternative to backtick quotes (and qx//)
4711 # with client-supplied CGI values. All CGI variables are surrounded by
4712 # single ''-quotes (except between existing \'\'-quotes, don't try to be
4713 # too smart). All variables are then interpolated. Simple (@) lists are
4714 # expanded with join(' ', @List), and simple (%) hash tables expanded
4715 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
4716 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
4717 # $@% that should NOT be interpolated must be preceeded by a "\".
4718 # If the first line of the String starts with "#! interpreter", the
4719 # remainder of the string is piped into interpreter (after interpolation), i.e.,
4720 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
4721 # just like in UNIX. There are some problems with quotes. Be carefull in
4722 # using them. You do not have access to the output of any piped (#!)
4723 # process! If you want such access, execute
4724 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
4725 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
4726 # </SCRIPT>.
4728 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
4729 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
4730 sub SAFEqx # ('String') -> result of executing qx/"String"/
4732 my $CommandString = shift;
4733 my $NewCommandString = "";
4735 # Only interpolate when required (check the On/Off switch)
4736 unless($CGIscriptor::NoShellScriptInterpolation)
4739 # Handle existing single quotes around CGI values
4740 while($CommandString =~ /\'[^\']+\'/s)
4742 my $CurrentQuotedString = $&;
4743 $NewCommandString .= $`;
4744 $CommandString = $'; # The remaining string
4745 # Interpolate CGI variables between quotes
4746 # (e.g., '$CGIscriptorResults[-1]')
4747 $CurrentQuotedString =~
4748 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
4749 "$1".eval("$2$3")}else{"$&"}/egs;
4751 # Combine result with previous result
4752 $NewCommandString .= $CurrentQuotedString;
4754 $CommandString = $NewCommandString.$CommandString;
4756 # Select known CGI variables and surround them with single quotes,
4757 # then interpolate all variables
4758 $CommandString =~
4759 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
4760 if($2 eq '$' && exists($main::default_values{$4}))
4761 {"$1\'".eval("\$$3")."\'";}
4762 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
4763 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
4764 keys(%{"$3"});$t}
4765 else{$1.eval("${2}$3");
4766 }/egs;
4768 # Remove backslashed [$@%]
4769 $CommandString =~ s/\\([\$\@\%])/$1/gs;
4772 # Debugging
4773 # return $CommandString;
4775 # Handle UNIX style "#! shell command\n" constructs as
4776 # a pipe into the shell command. The output cannot be tapped.
4777 my $ReturnValue = "";
4778 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
4780 my $ShellScripts = $';
4781 my $ShellCommand = $1;
4782 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
4783 select(INTERPRETER);$| = 1;
4784 print INTERPRETER $ShellScripts;
4785 close(INTERPRETER);
4786 select(STDOUT);$| = 1;
4788 # Shell scripts which are redirected to an existing named pipe.
4789 # The output cannot be tapped.
4790 elsif($CGIscriptor::ShellScriptPIPE)
4792 CGIscriptor::printSAFEqxPIPE($CommandString);
4794 else # Plain ``-backtick execution
4796 # Execute the commands
4797 $ReturnValue = qx/$CommandString/;
4799 return $ReturnValue;
4802 ####################################################################################
4804 # The CGIscriptor PACKAGE
4806 ####################################################################################
4808 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
4809 # "CGIscriptor::"
4811 package CGIscriptor;
4814 # The Interpolation On/Off switch
4815 my $NoShellScriptInterpolation = undef;
4816 # The ShellScript redirection pipe
4817 my $ShellScriptPIPE = undef;
4819 # Open a named PIPE for SAFEqx to receive ALL shell scripts
4820 sub RedirectShellScript # ('CommandString')
4822 my $CommandString = shift || undef;
4824 if($CommandString)
4826 $ShellScriptPIPE = "ShellScriptNamedPipe";
4827 open($ShellScriptPIPE, "|$CommandString")
4828 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
4830 else
4832 close($ShellScriptPIPE);
4833 $ShellScriptPIPE = undef;
4835 return $ShellScriptPIPE;
4838 # Print to redirected shell script pipe
4839 sub printSAFEqxPIPE # ("String") -> print return value
4841 my $String = shift || undef;
4843 select($ShellScriptPIPE); $| = 1;
4844 my $returnvalue = print $ShellScriptPIPE ($String);
4845 select(STDOUT); $| = 1;
4847 return $returnvalue;
4850 # a pointer to CGIexecute::SAFEqx
4851 sub SAFEqx # ('String') -> result of qx/"String"/
4853 my $CommandString = shift;
4854 return CGIexecute::SAFEqx($CommandString);
4858 # a pointer to CGIexecute::defineCGIvariable
4859 sub defineCGIvariable # ($name[, $default]) ->0/1
4861 my $name = shift;
4862 my $default = shift;
4863 return CGIexecute::defineCGIvariable($name, $default);
4867 # Decode URL encoded arguments
4868 sub URLdecode # (URL encoded input) -> string
4870 my $output = "";
4871 my $char;
4872 my $Value;
4873 foreach $Value (@_)
4875 my $EncodedValue = $Value; # Do not change the loop variable
4876 # Convert all "+" to " "
4877 $EncodedValue =~ s/\+/ /g;
4878 # Convert all hexadecimal codes (%FF) to their byte values
4879 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
4881 $output .= $`.chr(hex($1));
4882 $EncodedValue = $';
4884 $output .= $EncodedValue; # The remaining part of $Value
4886 $output;
4889 # Encode arguments as URL codes.
4890 sub URLencode # (input) -> URL encoded string
4892 my $output = "";
4893 my $char;
4894 my $Value;
4895 foreach $Value (@_)
4897 my @CharList = split('', $Value);
4898 foreach $char (@CharList)
4900 if($char =~ /\s/)
4901 { $output .= "+";}
4902 elsif($char =~ /\w\-/)
4903 { $output .= $char;}
4904 else
4906 $output .= uc(sprintf("%%%2.2x", ord($char)));
4910 $output;
4913 # Extract the value of a CGI variable from the URL-encoded $string
4914 # Also extracts the data blocks from a multipart request. Does NOT
4915 # decode the multipart blocks
4916 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
4918 my $ValueName = shift;
4919 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
4920 my $ReturnReference = shift || undef;
4921 my $output = "";
4923 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
4925 $output = URLdecode($2);
4926 $$ReturnReference = $' if ref($ReturnReference);
4928 # Get multipart POST or PUT methods
4929 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
4931 my $MultipartType = $2;
4932 my $BoundaryString = $3;
4933 # Remove the boundary-string
4934 my $temp = $QueryString;
4935 $temp =~ /^\Q--$BoundaryString\E/m;
4936 $temp = $';
4938 # Identify the newline character(s), this is the first character in $temp
4939 my $NewLine = "\r\n"; # Actually, this IS the correct one
4940 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
4942 # Is this correct??? I have to check.
4943 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
4944 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
4945 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
4946 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
4949 # search through all data blocks
4950 while($temp =~ /^\Q--$BoundaryString\E/m)
4952 my $DataBlock = $`;
4953 $temp = $';
4954 # Get the empty line after the header
4955 $DataBlock =~ /$NewLine$NewLine/;
4956 $Header = $`;
4957 $output = $';
4958 my $Header = $`;
4959 $output = $';
4961 # Remove newlines from the header
4962 $Header =~ s/$NewLine/ /g;
4964 # Look whether this block is the one you are looking for
4965 # Require the quotes!
4966 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
4968 my $i;
4969 for($i=length($NewLine); $i; --$i)
4971 chop($output);
4973 # OK, get out
4974 last;
4976 # reinitialize the output
4977 $output = "";
4979 $$ReturnReference = $temp if ref($ReturnReference);
4981 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
4983 return undef;
4984 $$ReturnReference = undef if ref($ReturnReference);
4986 else
4988 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
4990 return $output;
4994 # Get a list of values for the same ValueName. Uses CGIparseValue
4996 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
4998 my $ValueName = shift;
4999 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5000 my @output = ();
5001 my $RestQueryString;
5002 my $Value;
5003 while($QueryString &&
5004 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
5005 || defined($Value)))
5007 push(@output, $Value);
5008 $QueryString = $RestQueryString; # QueryString is consumed!
5010 # ready, return list with values
5011 return @output;
5014 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
5016 my $ValueName = shift;
5017 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5018 my $RestQueryString;
5019 my %output = ();
5020 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
5022 my $Key = $2;
5023 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
5024 $output{$Key} = $Value;
5025 $QueryString = $RestQueryString; # QueryString is consumed!
5027 # ready, return list with values
5028 return %output;
5031 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
5033 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5034 my $output = "";
5036 $QueryString =~ s/\&/\n/g;
5037 $output = URLdecode($QueryString);
5039 $output;
5042 # Extract the header of a multipart CGI variable from the POST input
5043 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
5045 my $ValueName = shift;
5046 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
5047 my $output = "";
5049 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
5051 my $MultipartType = $2;
5052 my $BoundaryString = $3;
5053 # Remove the boundary-string
5054 my $temp = $QueryString;
5055 $temp =~ /^\Q--$BoundaryString\E/m;
5056 $temp = $';
5058 # Identify the newline character(s), this is the first character in $temp
5059 my $NewLine = "\r\n"; # Actually, this IS the correct one
5060 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
5062 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
5063 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
5064 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
5065 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
5068 # search through all data blocks
5069 while($temp =~ /^\Q--$BoundaryString\E/m)
5071 my $DataBlock = $`;
5072 $temp = $';
5073 # Get the empty line after the header
5074 $DataBlock =~ /$NewLine$NewLine/;
5075 $Header = $`;
5076 my $Header = $`;
5078 # Remove newlines from the header
5079 $Header =~ s/$NewLine/ /g;
5081 # Look whether this block is the one you are looking for
5082 # Require the quotes!
5083 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
5085 $output = $Header;
5086 last;
5088 # reinitialize the output
5089 $output = "";
5092 return $output;
5096 # Checking variables for security (e.g., file names and email addresses)
5097 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
5098 sub CGIsafeFileName # FileName -> FileName or ""
5100 my $FileName = shift || "";
5101 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
5102 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
5103 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
5104 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
5105 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
5107 return $FileName;
5110 sub CGIsafeEmailAddress # email -> email or ""
5112 my $Email = shift || "";
5113 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
5114 return $Email;
5117 # Get a URL from the web. Needs main::GET_URL($URL) function
5118 # (i.e., curl, snarf, or wget)
5119 sub read_url # ($URL) -> page/file
5121 my $URL = shift || return "";
5123 # Get the commands to read the URL, do NOT add a print command
5124 my $URL_command = main::GET_URL($URL, 1);
5125 # execute the commands, i.e., actually read it
5126 my $URLcontent = CGIexecute->evaluate($URL_command);
5128 # Ready, return the content.
5129 return $URLcontent;
5132 ################################################>>>>>>>>>>Start Remove
5134 # BrowseAllDirs(Directory, indexfile)
5136 # usage:
5137 # <SCRIPT TYPE='text/ssperl'>
5138 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
5139 # </SCRIPT>
5141 # Allows to browse all directories. Stops at '/'. If the directory contains
5142 # an indexfile, eg, index.html, that file will be used instead. Files must match
5143 # the $Pattern, if it is given. Default is
5144 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
5146 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
5148 my $Directory = shift || '/';
5149 my $indexfile = shift || 'index.html';
5150 my $Pattern = shift || '';
5151 $Directory =~ s!/$!!g;
5153 # If the index directory exists, use that one
5154 if(-s "$::CGI_HOME$Directory/$indexfile")
5156 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
5159 # No indexfile, continue
5160 my @DirectoryList = glob("$::CGI_HOME$Directory");
5161 $CurrentDirectory = shift(@DirectoryList);
5162 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
5163 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
5164 print "<h1>";
5165 print "$CurrentDirectory" if $CurrentDirectory;
5166 print "</h1>\n";
5168 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
5169 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
5171 # Print directories
5172 my $file;
5173 print "<pre><ul TYPE='NONE'>\n";
5174 foreach $file (@AllFiles)
5176 next unless -d "$::CGI_HOME$Directory/$file";
5177 # Check whether this file should be visible
5178 next if $::BlockPathAccess &&
5179 "$Directory/$file/" =~ m@$::BlockPathAccess@;
5180 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
5182 print "</ul></pre>\n";
5184 # Print files
5185 print "<pre><ul TYPE='CIRCLE'>\n";
5186 my $TotalSize = 0;
5187 foreach $file (@AllFiles)
5189 next if $file =~ /^\./;
5190 next if -d "$::CGI_HOME$Directory/$file";
5191 next if -l "$::CGI_HOME$Directory/$file";
5192 # Check whether this file should be visible
5193 next if $::BlockPathAccess &&
5194 "$Directory/$file" =~ m@$::BlockPathAccess@;
5196 if(!$Pattern || $file =~ m@$Pattern@)
5198 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
5199 my $Size = -s "$::CGI_HOME$Directory/$file";
5200 $Size = sprintf("%6.0F kB", $Size/1024);
5201 my $Type = `file $::CGI_HOME$Directory/$file`;
5202 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
5203 chomp($Type);
5205 print "<li>";
5206 print "<a href='$Directory/$file'>";
5207 printf("%-40s", "$file</a>");
5208 print "\t$Size\t$Date\t$Type";
5209 print "</li>\n";
5212 print "</ul></pre>";
5214 return 1;
5218 ################################################
5220 # BrowseDirs(RootDirectory [, Pattern, Start])
5222 # usage:
5223 # <SCRIPT TYPE='text/ssperl'>
5224 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
5225 # </SCRIPT>
5227 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
5228 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
5229 # Only files which fit /$Pattern/ and directories are displayed.
5230 # Directories down or up the directory tree are supplied with a
5231 # GET request with the name of the CGI variable in the fourth argument (default
5232 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
5233 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
5235 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
5237 my $RootDirectory = shift; # || return 0;
5238 my $Pattern = shift || '\S';
5239 my $Start = shift || "";
5240 my $CGIvariable = shift || "BROWSEDIRS";
5241 my $HTTPserver = shift || '';
5243 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
5244 $Start =~ s@//+@/@g;
5245 $Start =~ s@[^/]+/\.\.@@ig;
5246 $Start =~ s@^\.\.@@ig;
5247 $Start =~ s@/\.$@@ig;
5248 $Start =~ s!/+$!!g;
5249 $Start .= "/" if $Start;
5251 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
5252 $CurrentDirectory = shift(@Directory);
5253 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
5254 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
5255 print "<h1>";
5256 print "$CurrentDirectory" if $CurrentDirectory;
5257 print "</h1>\n";
5258 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
5259 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
5261 # Print directories
5262 my $file;
5263 print "<pre><ul TYPE='NONE'>\n";
5264 foreach $file (@AllFiles)
5266 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
5267 # Check whether this file should be visible
5268 next if $::BlockPathAccess &&
5269 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
5271 my $NewURL = $Start ? "$Start$file" : $file;
5272 $NewURL = CGIscriptor::URLencode($NewURL);
5273 print "<dt><a href='";
5274 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
5275 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
5277 print "</ul></pre>\n";
5279 # Print files
5280 print "<pre><ul TYPE='CIRCLE'>\n";
5281 my $TotalSize = 0;
5282 foreach $file (@AllFiles)
5284 next if $file =~ /^\./;
5285 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
5286 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
5287 # Check whether this file should be visible
5288 next if $::BlockPathAccess &&
5289 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
5291 if($file =~ m@$Pattern@)
5293 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
5294 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
5295 $Size = sprintf("%6.0F kB", $Size/1024);
5296 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
5297 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
5298 chomp($Type);
5300 print "<li>";
5301 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
5303 print "<a href='$RootDirectory/$Start$file'>";
5305 elsif($HTTPserver)
5307 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
5309 printf("%-40s", "$file</a>") if $HTTPserver;
5310 printf("%-40s", "$file") unless $HTTPserver;
5311 print "\t$Size\t$Date\t$Type";
5312 print "</li>\n";
5315 print "</ul></pre>";
5317 return 1;
5321 # ListDocs(Pattern [,ListType])
5323 # usage:
5324 # <SCRIPT TYPE=text/ssperl>
5325 # CGIscriptor::ListDocs("/*", "dl");
5326 # </SCRIPT>
5328 # This subroutine is very usefull to manage collections of independent
5329 # documents. The resulting list will display the tree-like directory
5330 # structure. If this routine is too slow for online use, you can
5331 # store the result and use a link to that stored file.
5333 # List HTML and Text files with title and first header (HTML)
5334 # or filename and first meaningfull line (general text files).
5335 # The listing starts at the ServerRoot directory. Directories are
5336 # listed recursively.
5338 # You can change the list type (default is dl).
5339 # e.g.,
5340 # <dt><a href=<file.html>>title</a>
5341 # <dd>First Header
5342 # <dt><a href=<file.txt>>file.txt</a>
5343 # <dd>First meaningfull line of text
5345 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
5347 my $Pattern = shift;
5348 $Pattern =~ /\*/;
5349 my $ListType = shift || "dl";
5350 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
5351 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
5352 my @FileList = glob("$::CGI_HOME$Pattern");
5353 my ($FileName, $Path, $Link);
5355 # Print List markers
5356 print "<$ListType>\n";
5358 # Glob all files
5359 File: foreach $FileName (@FileList)
5361 # Check whether this file should be visible
5362 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
5364 # Recursively list files in all directories
5365 if(-d $FileName)
5367 $FileName =~ m@([^/]*)$@;
5368 my $DirName = $1;
5369 print "<$Prefix>$DirName\n";
5370 $Pattern =~ m@([^/]*)$@;
5371 &ListDocs("$`$DirName/$1", $ListType);
5372 next;
5374 # Use textfiles
5375 elsif(-T "$FileName")
5377 open(TextFile, $FileName) || next;
5379 # Ignore all other file types
5380 else
5381 { next;};
5383 # Get file path for link
5384 $FileName =~ /$::CGI_HOME/;
5385 print "<$Prefix><a href=$URL_root$'>";
5386 # Initialize all variables
5387 my $Line = "";
5388 my $TitleFound = 0;
5389 my $Caption = "";
5390 my $Title = "";
5391 # Read file and step through
5392 while(<TextFile>)
5394 chop $_;
5395 $Line = $_;
5396 # HTML files
5397 if($FileName =~ /\.ht[a-zA-Z]*$/i)
5399 # Catch Title
5400 while(!$Title)
5402 if($Line =~ m@<title>([^<]*)</title>@i)
5404 $Title = $1;
5405 $Line = $';
5407 else
5409 $Line .= <TextFile> || goto Print;
5410 chop $Line;
5413 # Catch First Header
5414 while(!$Caption)
5416 if($Line =~ m@</h1>@i)
5418 $Caption = $`;
5419 $Line = $';
5420 $Caption =~ m@<h1>@i;
5421 $Caption = $';
5422 $Line = $`.$Caption.$Line;
5424 else
5426 $Line .= <TextFile> || goto Print;
5427 chop $Line;
5431 # Other text files
5432 else
5434 # Title equals file name
5435 $FileName =~ /([^\/]+)$/;
5436 $Title = $1;
5437 # Catch equals First Meaningfull line
5438 while(!$Caption)
5440 if($Line =~ /[A-Z]/ &&
5441 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
5442 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
5444 $Line =~ s/\<[^\>]+\>//g;
5445 $Caption = $Line;
5447 else
5449 $Line = <TextFile> || goto Print;
5453 Print: # Print title and subject
5454 print "$Title</a>\n";
5455 print "<dd>$Caption\n" if $ListType eq "dl";
5456 $TitleFound = 0;
5457 $Caption = "";
5458 close TextFile;
5459 next File;
5462 # Print Closing List Marker
5463 print "</$ListType>\n";
5464 ""; # Empty return value
5468 # HTMLdocTree(Pattern [,ListType])
5470 # usage:
5471 # <SCRIPT TYPE=text/ssperl>
5472 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
5473 # </SCRIPT>
5475 # The following subroutine is very usefull for checking large document
5476 # trees. Starting from the root (s), it reads all files and prints out
5477 # a nested list of links to all attached files. Non-existing or misplaced
5478 # files are flagged. This is quite a file-i/o intensive routine
5479 # so you would not like it to be accessible to everyone. If you want to
5480 # use the result, save the whole resulting page to disk and use a link
5481 # to this file.
5483 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
5484 # with links to *local* files (i.e., only links to the local server are
5485 # followed). The list entries are the document titles.
5486 # If the list type is <dl>, the first <H1> header is used too.
5487 # For each file matching the pattern, a list is made recursively of all
5488 # HTML documents that are linked from it and are stored in the same directory
5489 # or a sub-directory. Warnings are given for missing files.
5490 # The listing starts for the ServerRoot directory.
5491 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
5493 %LinkUsed = ();
5495 sub HTMLdocTree # ($Pattern [, listtype])
5496 # e.g., ("/Welcome.html", [, "ul"])
5498 my $Pattern = shift;
5499 my $ListType = shift || "dl";
5500 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
5501 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
5502 my ($Filename, $Path, $Link);
5503 my %LocalLinks = {};
5505 # Read files (glob them for expansion of wildcards)
5506 my @FileList = glob("$::CGI_HOME$Pattern");
5507 foreach $Path (@FileList)
5509 # Get URL_path
5510 $Path =~ /$::CGI_HOME/;
5511 my $URL_path = $';
5512 # Check whether this file should be visible
5513 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
5515 my $Title = $URL_path;
5516 my $Caption = "";
5517 # Current file should not be used again
5518 ++$LinkUsed{$URL_path};
5519 # Open HTML doc
5520 unless(open(TextFile, $Path))
5522 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
5523 next;
5525 while(<TextFile>)
5527 chop $_;
5528 $Line = $_;
5529 # Catch Title
5530 while($Line =~ m@<title>@i)
5532 if($Line =~ m@<title>([^<]*)</title>@i)
5534 $Title = $1;
5535 $Line = $';
5537 else
5539 $Line .= <TextFile>;
5540 chop $Line;
5543 # Catch First Header
5544 while(!$Caption && $Line =~ m@<h1>@i)
5546 if($Line =~ m@</h[1-9]>@i)
5548 $Caption = $`;
5549 $Line = $';
5550 $Caption =~ m@<h1>@i;
5551 $Caption = $';
5552 $Line = $`.$Caption.$Line;
5554 else
5556 $Line .= <TextFile>;
5557 chop $Line;
5560 # Catch and print Links
5561 while($Line =~ m@<a href\=([^>]*)>@i)
5563 $Link = $1;
5564 $Line = $';
5565 # Remove quotes
5566 $Link =~ s/\"//g;
5567 # Remove extras
5568 $Link =~ s/[\#\?].*$//g;
5569 # Remove Servername
5570 if($Link =~ m@(http://|^)@i)
5572 $Link = $';
5573 # Only build tree for current server
5574 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
5575 # Remove server name and port
5576 $Link =~ s@^[^\/]*@@g;
5578 # Store the current link
5579 next if $LinkUsed{$Link} || $Link eq $URL_path;
5580 ++$LinkUsed{$Link};
5581 ++$LocalLinks{$Link};
5585 close TextFile;
5586 print "<$Prefix>";
5587 print "<a href=http://";
5588 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
5589 print "$Title</a>\n";
5590 print "<br>$Caption\n"
5591 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
5592 print "<$ListType>\n";
5593 foreach $Link (keys(%LocalLinks))
5595 &HTMLdocTree($Link, $ListType);
5597 print "</$ListType>\n";
5601 ###########################<<<<<<<<<<End Remove
5603 # Make require happy
5606 =head1 NAME
5608 CGIscriptor -
5610 =head1 DESCRIPTION
5612 A flexible HTML 4 compliant script/module for CGI-aware
5613 embeded Perl, shell-scripts, and other scripting languages,
5614 executed at the server side.
5616 =head1 README
5618 Executes embeded Perl code in HTML pages with easy
5619 access to CGI variables. Also processes embeded shell
5620 scripts and scripts in any other language with an
5621 interactive interpreter (e.g., in-line Python, Tcl,
5622 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
5623 sh, bash, csh, ksh).
5625 CGIscriptor is very flexible and hides all the specifics
5626 and idiosyncrasies of correct output and CGI coding and naming.
5627 CGIscriptor complies with the W3C HTML 4.0 recommendations.
5629 This Perl program will run on any WWW server that runs
5630 Perl scripts, just add a line like the following to your
5631 srm.conf file (Apache example):
5633 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
5635 URL's that refer to http://www.your.address/SHTML/... will
5636 now be handled by CGIscriptor.pl, which can use a private
5637 directory tree (default is the DOCUMENT_ROOT directory tree,
5638 but it can be anywhere).
5640 =head1 PREREQUISITES
5643 =head1 COREQUISITES
5646 =pod OSNAMES
5648 Linux, *BSD, *nix, MS WinXP
5650 =pod SCRIPT CATEGORIES
5652 Servers
5656 =cut