Working on login and ticket system
[CGIscriptor.git] / CGIscriptor.pl
blobd2f3be3a4720813d880d204f871f40ddd87be11c
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 # 31 May 2012 - Session ticket system added for handling login sessions.
64 # 29 May 2012 - CGIsafeFileName does not accept filenames starting with '.'
65 # 29 May 2012 - Added CGIscriptor::BrowseAllDirs to handle browsing directories
66 # correctly.
67 # 22 May 2012 - Added Access control with Session Tickets linked to
68 # IP Address and PATH_INFO.
69 # 21 May 2012 - Corrected the links generated by CGIscriptor::BrowseDirs
70 # Will link to current base URL when the HTTP server is '.' or '~'
71 # 29 Oct 2009 - Adapted David A. Wheeler's suggestion about filenames:
72 # CGIsafeFileName does not accept filenames starting with '-'
73 # (http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html)
74 # 08 Oct 2009 - Some corrections in the README.txt file, eg, new email address
75 # 28 Jan 2005 - Added a file selector to performTranslation.
76 # Changed %TranslationTable to @TranslationTable
77 # and patterns to lists.
78 # 27 Jan 2005 - Added a %TranslationTable with associated
79 # performTranslation(\$text) function to allow
80 # run changes in the web pages. Say, to translate
81 # legacy pages with <%=...%> delimiters to the new
82 # <SCRIPT TYPE=..></SCRIPT> format.
83 # 27 Jan 2005 - Small bug of extra '\n' in output removed from the
84 # Other Languages Code.
85 # 10 May 2004 - Belated upload of latest version (2.3) to CPAN
86 # 07 Oct 2003 - Corrected error '\s' -> '\\s' in rebol scripting
87 # language call
88 # 07 Oct 2003 - Corrected omitted INS tags in <DIV><INS> handling
89 # 20 May 2003 - Added a --help switch to print the manual.
90 # 06 Mar 2003 - Adapted the blurb at the end of the file.
91 # 03 Mar 2003 - Added a user definable dieHandler function to catch all
92 # "die" calls. Also "enhanced" the STDERR printout.
93 # 10 Feb 2003 - Split off the reading of the POST part of a query
94 # from Initialize_output. This was suggested by Gerd Franke
95 # to allow for the catching of the file_path using a
96 # POST based lookup. That is, he needed the POST part
97 # to change the file_path.
98 # 03 Feb 2003 - %{$name}; => %{$name} = (); in defineCGIvariableHash.
99 # 03 Feb 2003 - \1 better written as $1 in
100 # $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
101 # 29 Jan 2003 - This makes "CLASS="ssperl" CSS-compatible Gerd Franke
102 # added:
103 # $ServerScriptContentClass = "ssperl";
104 # changed in ProcessFile():
105 # unless(($CurrentContentType =~
106 # 28 Jan 2003 - Added 'INS' Tag! Gerd Franke
107 # 20 Dec 2002 - Removed useless $Directoryseparator variable.
108 # Update comments and documentation.
109 # 18 Dec 2002 - Corrected bug in Accept/Reject processing.
110 # Files didn't work.
111 # 24 Jul 2002 - Added .htaccess documentation (from Gerd Franke)
112 # Also added a note that RawFilePattern can be a
113 # complete file name.
114 # 19 Mar 2002 - Added SRC pseudo-files PREFIX and POSTFIX. These
115 # switch to prepending or to appending the content
116 # of the SRC attribute. Default is prefixing. You
117 # can add as many of these switches as you like.
118 # 13 Mar 2002 - Do not search for tag content if a tag closes with
119 # />, i.e., <DIV ... /> will be handled the XML/XHTML way.
120 # 25 Jan 2002 - Added 'curl' and 'snarf' to SRC attribute URL handling
121 # (replaces wget).
122 # 25 Jan 2002 - Found a bug in SAFEqx, now executes qx() in a scalar context
123 # (i.o. a list context). This is necessary for binary results.
124 # 24 Jan 2002 - Disambiguated -T $SRCfile to -T "$SRCfile" (and -e) and
125 # changed the order of if/elsif to allow removing these
126 # conditions in systems with broken -T functions.
127 # (I also removed a spurious ')' bracket)
128 # 17 Jan 2002 - Changed DIV tag SRC from <SOURCE> to sysread(SOURCE,...)
129 # to support binary files.
130 # 17 Jan 2002 - Removed WhiteSpace from $FileAllowedCharacters.
131 # 17 Jan 2002 - Allow "file://" prefix in SRC attribute. It is simply
132 # stipped from the path.
133 # 15 Jan 2002 - Version 2.2
134 # 15 Jan 2002 - Debugged and completed URL support (including
135 # CGIscriptor::read_url() function)
136 # 07 Jan 2002 - Added automatic (magic) URL support to the SRC attribute
137 # with the main::GET_URL function. Uses wget -O underlying.
138 # 04 Jan 2002 - Added initialization of $NewDirective in InsertForeignScript
139 # (i.e., my $NewDirective = "";) to clear old output
140 # (this was a realy anoying bug).
141 # 03 Jan 2002 - Added a <DIV CLASS='text/ssperl' ID='varname'></DIV>
142 # tags that assign the body text as-is (literally)
143 # to $varname. Allows standard HTML-tools to handle
144 # Cascading Style Sheet templates. This implements a
145 # design by Gerd Franke (franke@roo.de).
146 # 03 Jan 2002 - I finaly gave in and allowed SRC files to expand ~/.
147 # 12 Oct 2001 - Normalized spelling of "CGIsafFileName" in documentation.
148 # 09 Oct 2001 - Added $ENV{'CGI_BINARY_FILE'} to log files to
149 # detect unwanted indexing of TAR files by webcrawlers.
150 # 10 Sep 2001 - Added $YOUR_SCRIPTS directory to @INC for 'require'.
151 # 22 Aug 2001 - Added .txt (Content-type: text/plain) as a default
152 # processed file type. Was processed via BinaryMapFile.
153 # 31 May 2001 - Changed =~ inside CGIsafeEmailAddress that was buggy.
154 # 29 May 2001 - Updated $CGI_HOME to point to $ENV{DOCUMENT_ROOT} io
155 # the root of PATH_TRANSLATED. DOCUMENT_ROOT can now
156 # be manipulated to achieve a "Sub Root".
157 # NOTE: you can have $YOUR_HTML_FILES != DOCUMENT_ROOT
158 # 28 May 2001 - Changed CGIscriptor::BrowsDirs function for security
159 # and debugging (it now works).
160 # 21 May 2001 - defineCGIvariableHash will ADD values to existing
161 # hashes,instead of replacing existing hashes.
162 # 17 May 2001 - Interjected a '&' when pasting POST to GET data
163 # 24 Apr 2001 - Blocked direct requests for BinaryMapFile.
164 # 16 Aug 2000 - Added hash table extraction for CGI parameters with
165 # CGIparseValueHash (used with structured parameters).
166 # Use: CGI='%<CGI-partial-name>' (fill in your name in <>)
167 # Will collect all <CGI-partial-name><key>=value pairs in
168 # $<CGI-partial-name>{<key>} = value;
169 # 16 Aug 2000 - Adapted SAFEqx to protect @PARAMETER values.
170 # 09 Aug 2000 - Added support for non-filesystem input by way of
171 # the CGI_FILE_CONTENTS and CGI_DATA_ACCESS_CODE
172 # environment variables.
173 # 26 Jul 2000 - On the command-line, file-path '-' indicates STDIN.
174 # This allows CGIscriptor to be used in pipes.
175 # Default, $BLOCK_STDIN_HTTP_REQUEST=1 will block this
176 # in an HTTP request (i.e., in a web server).
177 # 26 Jul 2000 - Blocked 'Content-type: text/html' if the SERVER_PROTOCOL
178 # is not HTTP or another protocol. Changed the default
179 # source directory to DOCUMENT_ROOT (i.o. the incorrect
180 # SERVER_ROOT).
181 # 24 Jul 2000 - -slim Command-line argument added to remove all
182 # comments, security, etc.. Updated documentation.
183 # 05 Jul 2000 - Added IF and UNLESS attributes to make the
184 # execution of all <META> and <SCRIPT> code
185 # conditional.
186 # 05 Jul 2000 - Rewrote and isolated the code for extracting
187 # quoted items from CGI and SRC attributes.
188 # Now all attributes expect the same set of
189 # quotes: '', "", ``, (), {}, [] and the same
190 # preceded by a \, e.g., "\((aap)\)" will be
191 # extracted as "(aap)".
192 # 17 Jun 2000 - Construct @ARGV list directly in CGIexecute
193 # name-space (i.o. by evaluation) from
194 # CGI attributes to prevent interference with
195 # the processing for non perl scripts.
196 # Changed CGIparseValueList to prevent runaway
197 # loops.
198 # 16 Jun 2000 - Added a direct (interpolated) display mode
199 # (text/ssdisplay) and a user log mode
200 # (text/sslogfile).
201 # 06 Jun 2000 - Replace "print $Result" with a syswrite loop to
202 # allow large string output.
203 # 02 Jun 2000 - Corrected shrubCGIparameter($CGI_VALUE) to realy
204 # remove all control characters. Changed Interpreter
205 # initialization to shrub interpolated CGI parameters.
206 # Added 'text/ssmailto' interpreter script.
207 # 22 May 2000 - Changed some of the comments
208 # 09 May 2000 - Added list extraction for CGI parameters with
209 # CGIparseValueList (used with multiple selections).
210 # Use: CGI='@<CGI-parameter>' (fill in your name in <>)
211 # 09 May 2000 - Added a 'Not Present' condition to CGIparseValue.
212 # 27 Apr 2000 - Updated documentation to reflect changes.
213 # 27 Apr 2000 - SRC attribute "cleaned". Supported for external
214 # interpreters.
215 # 27 Apr 2000 - CGI attribute can be used in <SCRIPT> tag.
216 # 27 Apr 2000 - Gprolog, M4 support added.
217 # 26 Apr 2000 - Lisp (rep) support added.
218 # 20 Apr 2000 - Use of external interpreters now functional.
219 # 20 Apr 2000 - Removed bug from extracting Content types (RegExp)
220 # 10 Mar 2000 - Qualified unconditional removal of '#' that preclude
221 # the use of $#foo, i.e., I changed
222 # s/[^\\]\#[^\n\f\r]*([\n\f\r])/\1/g
223 # to
224 # s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/\1/g
225 # 03 Mar 2000 - Added a '$BlockPathAccess' variable to "hide"
226 # things like, e.g., CVS information in CVS subtrees
227 # 10 Feb 2000 - URLencode/URLdecode have been made case-insensitive
228 # 10 Feb 2000 - Added a BrowseDirs function (CGIscriptor package)
229 # 01 Feb 2000 - A BinaryMapFile in the ~/ directory has precedence
230 # over a "burried" BinaryMapFile.
231 # 04 Oct 1999 - Added two functions to check file names and email addresses
232 # (CGIscriptor::CGIsafeFileName and
233 # CGIscriptor::CGIsafeEmailAddress)
234 # 28 Sept 1999 - Corrected bug in sysread call for reading POST method
235 # to allow LONG posts.
236 # 28 Sept 1999 - Changed CGIparseValue to handle multipart/form-data.
237 # 29 July 1999 - Refer to BinaryMapFile from CGIscriptor directory, if
238 # this directory exists.
239 # 07 June 1999 - Limit file-pattern matching to LAST extension
240 # 04 June 1999 - Default text/html content type is printed only once.
241 # 18 May 1999 - Bug in replacement of ~/ and ./ removed.
242 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
243 # 15 May 1999 - Changed the name of the execute package to CGIexecute.
244 # Changed the processing of the Accept and Reject file.
245 # Added a full expression evaluation to Access Control.
246 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
247 # 27 Apr 1999 - Brought CGIscriptor under the GNU GPL. Made CGIscriptor
248 # Version 1.1 a module that can be called with 'require "CGIscriptor.pl"'.
249 # Requests are serviced by "Handle_Request()". CGIscriptor
250 # can still be called as a isolated perl script and a shell
251 # command.
252 # Changed the "factory default setting" so that it will run
253 # from the DOCUMENT_ROOT directory.
254 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
255 # 29 Mar 1999 - Remove second debugging STDERR switch. Moved most code
256 # to subroutines to change CGIscriptor into a module.
257 # Added mapping to process unsupported file types (e.g., binary
258 # pictures). See $BinaryMapFile.
259 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
260 # 24 Sept 1998 - Changed text of license (Rob van Son, R.J.J.H.vanSon@uva.nl)
261 # Removed a double setting of filepatterns and maximum query
262 # size. Changed email address. Removed some typos from the
263 # comments.
264 # 02 June 1998 - Bug fixed in URLdecode. Changing the foreach loop variable
265 # caused quiting CGIscriptor.(Rob van Son, R.J.J.H.vanSon@uva.nl)
266 # 02 June 1998 - $SS_PUB and $SS_SCRIPT inserted an extra /, removed.
267 # (Rob van Son, R.J.J.H.vanSon@uva.nl)
270 # Known Bugs:
272 # 23 Mar 2000
273 # It is not possible to use operators or variables to construct variable names,
274 # e.g., $bar = \@{$foo}; won't work. However, eval('$bar = \@{'.$foo.'};');
275 # will indeed work. If someone could tell me why, I would be obliged.
278 ############################################################################
280 # OBLIGATORY USER CONFIGURATION
282 # Configure the directories where all user files can be found (this
283 # is the equivalent of the server root directory of a WWW-server).
284 # These directories can be located ANYWHERE. For security reasons, it is
285 # better to locate them outside the WWW-tree of your HTTP server, unless
286 # CGIscripter handles ALL requests.
288 # For convenience, the defaults are set to the root of the WWW server.
289 # However, this might not be safe!
291 # ~/ text files
292 # $YOUR_HTML_FILES = "/usr/pub/WWW/SHTML"; # or SS_PUB as environment var
293 # (patch to use the parent directory of CGIscriptor as document root, should be removed)
294 if($ENV{'SCRIPT_FILENAME'}) # && $ENV{'SCRIPT_FILENAME'} !~ /\Q$ENV{'DOCUMENT_ROOT'}\E/)
296 $ENV{'DOCUMENT_ROOT'} = $ENV{'SCRIPT_FILENAME'};
297 $ENV{'DOCUMENT_ROOT'} =~ s@/CGIscriptor.*$@@ig;
300 # Just enter your own directory path here
301 $YOUR_HTML_FILES = $ENV{'DOCUMENT_ROOT'}; # default is the DOCUMENT_ROOT
303 # ./ script files (recommended to be different from the previous)
304 # $YOUR_SCRIPTS = "/usr/pub/WWW/scripts"; # or SS_SCRIPT as environment var
305 $YOUR_SCRIPTS = $YOUR_HTML_FILES; # This might be a SECURITY RISK
307 # End of obligatory user configuration
308 # (note: there is more non-essential user configuration below)
310 ############################################################################
312 # OPTIONAL USER CONFIGURATION (all values are used CASE INSENSITIVE)
314 # Script content-types: TYPE="Content-type" (user defined mime-type)
315 $ServerScriptContentType = "text/ssperl"; # Server Side Perl scripts
316 # CSS require a simple class
317 $ServerScriptContentClass = $ServerScriptContentType =~ m!/! ?
318 $' : "ssperl"; # Server Side Perl CSS classes
320 $ShellScriptContentType = "text/osshell"; # OS shell scripts
321 # # (Server Side perl ``-execution)
323 # Accessible file patterns, block any request that doesn't match.
324 # Matches any file with the extension .(s)htm(l), .txt, or .xmr
325 # (\. is used in regexp)
326 # Note: die unless $PATH_INFO =~ m@($FilePattern)$@is;
327 $FilePattern = ".shtml|.htm|.html|.xml|.xmr|.txt";
329 # The table with the content type MIME types
330 # (allows to differentiate MIME types, if needed)
331 %ContentTypeTable =
333 '.html' => 'text/html',
334 '.shtml' => 'text/html',
335 '.htm' => 'text/html',
336 '.xml' => 'text/xml',
337 '.txt' => 'text/plain'
341 # File pattern post-processing
342 $FilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
344 # SHAsum command needed for Authorization and Login
345 # (note, these have to be accessible in the HTML pages, ie, the CGIexecute environment)
346 my $shasum = qx{uname} =~ /Darwin/ ? "shasum-5.12" : "shasum";
347 my $SHASUMCMD = $shasum.' |cut -f 1 -d" "';
348 $ENV{"SHASUMCMD"} = $SHASUMCMD;
349 my $RANDOMHASHCMD = 'dd bs=32 count=1 if=/dev/urandom 2>/dev/null | '.$shasum.' -b |cut -f 1 -d" "';
350 $ENV{"RANDOMHASHCMD"} = $RANDOMHASHCMD;
352 # File patterns of files which are handled by session tickets.
353 %TicketRequiredPatterns = (
354 '^/Private(/|$)' => "Private/.Sessions\tPrivate/.Passwords\t/Private/Login.html\t+36000"
356 # Session Ticket Directory: Private/.Sessions
357 # Password Directory: Private/.Passwords
358 # Login page (url path): /Private/Login.html
359 # Expiration time (s): +3600
360 # +<seconds> = relative time <seconds> is absolute date-time
362 # Raw files must contain their own Content-type (xmr <- x-multipart-replace).
363 # THIS IS A SUBSET OF THE FILES DEFINED IN $FilePattern
364 $RawFilePattern = ".xmr";
365 # (In principle, this could contain a full file specification, e.g.,
366 # ".xmr|relocated.html")
368 # Raw File pattern post-processing
369 $RawFilePattern =~ s/([@.])/\\$1/g; # Convert . and @ to \. and \@
371 # Server protocols for which "Content-type: text/html\n\n" should be printed
372 # (you should not bother with these, except for HTTP, they are mostly imaginary)
373 $ContentTypeServerProtocols = 'HTTP|MAIL|MIME';
375 # Block access to all (sub-) paths and directories that match the
376 # following (URL) path (is used as:
377 # 'die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;' )
378 $BlockPathAccess = '/(CVS|\.git)/'; # Protect CVS and .git information
380 # All (blocked) other file-types can be mapped to a single "binary-file"
381 # processor (a kind of pseudo-file path). This can either be an error
382 # message (e.g., "illegal file") or contain a script that serves binary
383 # files.
384 # Note: the real file path wil be stored in $ENV{CGI_BINARY_FILE}.
385 $BinaryMapFile = "/BinaryMapFile.xmr";
386 # Allow for the addition of a CGIscriptor directory
387 # Note that a BinaryMapFile in the root "~/" directory has precedence
388 $BinaryMapFile = "/CGIscriptor".$BinaryMapFile
389 if ! -e "$YOUR_HTML_FILES".$BinaryMapFile
390 && -e "$YOUR_HTML_FILES/CGIscriptor".$BinaryMapFile;
393 # List of all characters that are allowed in file names and paths.
394 # All requests containing illegal characters are blocked. This
395 # blocks most tricks (e.g., adding "\000", "\n", or other control
396 # characters, also blocks URI's using %FF)
397 # THIS IS A SECURITY FEATURE
398 # (this is also used to parse filenames in SRC= features, note the
399 # '-quotes, they are essential)
400 $FileAllowedChars = '\w\.\~\/\:\*\?\-'; # Covers Unix and Mac, but NO spaces
402 # Maximum size of the Query (number of characters clients can send
403 # covers both GET & POST combined)
404 $MaximumQuerySize = 2**20 - 1; # = 2**14 - 1
407 # Embeded URL get function used in SRC attributes and CGIscriptor::read_url
408 # (returns a string with the PERL code to transfer the URL contents, e.g.,
409 # "SAFEqx(\'curl \"http://www.fon.hum.uva.nl\"\')")
410 # "SAFEqx(\'wget --quiet --output-document=- \"http://www.fon.hum.uva.nl\"\')")
411 # Be sure to handle <BASE HREF='URL'> and allow BOTH
412 # direct printing GET_URL($URL [, 0]) and extracting the content of
413 # the $URL for post-processing GET_URL($URL, 1).
414 # You get the WHOLE file, including HTML header.
415 # The shell command Use $URL where the URL should go
416 # ('wget', 'snarf' or 'curl', uncomment the one you would like to use)
417 my $GET_URL_shell_command = 'wget --quiet --output-document=- $URL';
418 #my $GET_URL_shell_command = 'snarf $URL -';
419 #my $GET_URL_shell_command = 'curl $URL';
421 sub GET_URL # ($URL, $ValueNotPrint) -> content_of_url
423 my $URL = shift || return;
424 my $ValueNotPrint = shift || 0;
426 # Check URL for illegal characters
427 return "print '<h1>Illegal URL<h1>'\"\n\";" if $URL =~ /[^$FileAllowedChars\%]/;
429 # Include URL in final command
430 my $CurrentCommand = $GET_URL_shell_command;
431 $CurrentCommand =~ s/\$URL/$URL/g;
433 # Print to STDOUT or return a value
434 my $BlockPrint = "print STDOUT ";
435 $BlockPrint = "" if $ValueNotPrint;
437 my $Commands = <<"GETURLCODE";
438 # Get URL
440 my \$Page = "";
442 # Simple, using shell command
443 \$Page = SAFEqx('$CurrentCommand');
445 # Add a BASE tage to the header
446 \$Page =~ s!\\</head!\\<base href='$URL'\\>\\</head!ig unless \$Page =~ m!\\<base!;
448 # Print the URL value, or return it as a value
449 $BlockPrint\$Page;
451 GETURLCODE
452 return $Commands;
455 # As files can get rather large (and binary), you might want to use
456 # some more intelligent reading procedure, e.g.,
457 # Direct Perl
458 # # open(URLHANDLE, '/usr/bin/wget --quiet --output-document=- "$URL"|') || die "wget: \$!";
459 # #open(URLHANDLE, '/usr/bin/snarf "$URL" -|') || die "snarf: \$!";
460 # open(URLHANDLE, '/usr/bin/curl "$URL"|') || die "curl: \$!";
461 # my \$text = "";
462 # while(sysread(URLHANDLE,\$text, 1024) > 0)
464 # \$Page .= \$text;
465 # };
466 # close(URLHANDLE) || die "\$!";
467 # However, this doesn't work with the CGIexecute->evaluate() function.
468 # You get an error: 'No child processes at (eval 16) line 15, <file0> line 8.'
470 # You can forget the next two variables, they are only needed when
471 # you don't want to use a regular file system (i.e., with open)
472 # but use some kind of database/RAM image for accessing (generating)
473 # the data.
475 # Name of the environment variable that contains the file contents
476 # when reading directly from Database/RAM. When this environment variable,
477 # $ENV{$CGI_FILE_CONTENTS}, is not false, no real file will be read.
478 $CGI_FILE_CONTENTS = 'CGI_FILE_CONTENTS';
479 # Uncomment the following if you want to force the use of the data access code
480 # $ENV{$CGI_FILE_CONTENTS} = '-'; # Force use of $ENV{$CGI_DATA_ACCESS_CODE}
482 # Name of the environment variable that contains the RAM access perl
483 # code needed to read additional "files", i.e.,
484 # $ENV{$CGI_FILE_CONTENTS} = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
485 # When $ENV{$CGI_FILE_CONTENTS} eq '-', this code is executed to generate the data.
486 $CGI_DATA_ACCESS_CODE = 'CGI_DATA_ACCESS_CODE';
488 # You can, of course, fill this yourself, e.g.,
489 # $ENV{$CGI_DATA_ACCESS_CODE} =
490 # 'open(INPUT, "<$_[0]"); while(<INPUT>){print;};close(INPUT);'
493 # DEBUGGING
495 # Suppress error messages, this can be changed for debugging or error-logging
496 #open(STDERR, "/dev/null"); # (comment out for use in debugging)
498 # SPECIAL: Remove Comments, security, etc. if the command line is
499 # '>CGIscriptor.pl -slim >slimCGIscriptor.pl'
500 $TrimDownCGIscriptor = 1 if $ARGV[0] =~ /^\-slim/i;
502 # If CGIscriptor is used from the command line, the command line
503 # arguments are interpreted as the file (1st) and the Query String (rest).
504 # Get the arguments
505 $ENV{'PATH_INFO'} = shift(@ARGV) unless exists($ENV{'PATH_INFO'}) || grep(/\-\-help/i, @ARGV);
506 $ENV{'QUERY_STRING'} = join("&", @ARGV) unless exists($ENV{'QUERY_STRING'});
509 # Handle bail-outs in a user definable way.
510 # Catch Die and replace it with your own function.
511 # Ends with a call to "die $_[0];"
513 sub dieHandler # ($ErrorCode, "Message", @_) -> DEAD
515 my $ErrorCode = shift;
516 my $ErrorMessage = shift;
518 # Place your own reporting functions here
520 # Now, kill everything (default)
521 print STDERR "$ErrorCode: $ErrorMessage\n";
522 die $ErrorMessage;
526 # End of optional user configuration
527 # (note: there is more non-essential user configuration below)
529 if(grep(/\-\-help/i, @ARGV))
531 print << 'ENDOFPREHELPTEXT2';
533 ###############################################################################
535 # Author and Copyright (c):
536 # Rob van Son, © 1995,1996,1997,1998,1999,2000,2001,2002-2012
537 # NKI-AVL Amsterdam
538 # r.v.son@nki.nl
539 # Institute of Phonetic Sciences & IFOTT/ACLS
540 # University of Amsterdam
541 # Email: R.J.J.H.vanSon@gmail.com
542 # Email: R.J.J.H.vanSon@uva.nl
543 # WWW : http://www.fon.hum.uva.nl/rob/
545 # License for use and disclaimers
547 # CGIscriptor merges plain ASCII HTML files transparantly
548 # with CGI variables, in-line PERL code, shell commands,
549 # and executable scripts in other scripting languages.
551 # This program is free software; you can redistribute it and/or
552 # modify it under the terms of the GNU General Public License
553 # as published by the Free Software Foundation; either version 2
554 # of the License, or (at your option) any later version.
556 # This program is distributed in the hope that it will be useful,
557 # but WITHOUT ANY WARRANTY; without even the implied warranty of
558 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
559 # GNU General Public License for more details.
561 # You should have received a copy of the GNU General Public License
562 # along with this program; if not, write to the Free Software
563 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
566 # Contributors:
567 # Rob van Son (R.J.J.H.vanSon@uva.nl)
568 # Gerd Franke franke@roo.de (designed the <DIV> behaviour)
570 #######################################################
571 ENDOFPREHELPTEXT2
573 #######################################################>>>>>>>>>>Start Remove
575 # You can skip the following code, it is an auto-splice
576 # procedure.
578 # Construct a slimmed down version of CGIscriptor
579 # (i.e., CGIscriptor.pl -slim > slimCGIscriptor.pl)
581 if($TrimDownCGIscriptor)
583 open(CGISCRIPTOR, "<CGIscriptor.pl")
584 || dieHandler(1, "<CGIscriptor.pl not slimmed down: $!\n");
585 my $SKIPtext = 0;
586 my $SKIPComments = 0;
588 while(<CGISCRIPTOR>)
590 my $SKIPline = 0;
592 ++$LineCount;
594 # Start of SKIP text
595 $SKIPtext = 1 if /[\>]{10}Start Remove/;
596 $SKIPComments = 1 if $SKIPtext == 1;
598 # Skip this line?
599 $SKIPline = 1 if $SKIPtext || ($SKIPComments && /^\s*\#/);
601 ++$PrintCount unless $SKIPline;
603 print STDOUT $_ unless $SKIPline;
605 # End of SKIP text ?
606 $SKIPtext = 0 if /[\<]{10}End Remove/;
608 # Ready!
609 print STDERR "\# Printed $PrintCount out of $LineCount lines\n";
610 exit;
613 #######################################################
615 if(grep(/\-\-help/i, @ARGV))
617 print << 'ENDOFHELPTEXT';
619 # HYPE
621 # CGIscriptor merges plain ASCII HTML files transparantly and safely
622 # with CGI variables, in-line PERL code, shell commands, and executable
623 # scripts in many languages (on-line and real-time). It combines the
624 # "ease of use" of HTML files with the versatillity of specialized
625 # scripts and PERL programs. It hides all the specifics and
626 # idiosyncrasies of correct output and CGI coding and naming. Scripts
627 # do not have to be aware of HTML, HTTP, or CGI conventions just as HTML
628 # files can be ignorant of scripts and the associated values. CGIscriptor
629 # complies with the W3C HTML 4.0 recommendations.
630 # In addition to its use as a WWW embeded CGI processor, it can
631 # be used as a command-line document preprocessor (text-filter).
633 # THIS IS HOW IT WORKS
635 # The aim of CGIscriptor is to execute "plain" scripts inside a text file
636 # using any required CGIparameters and environment variables. It
637 # is optimized to transparantly process HTML files inside a WWW server.
638 # The native language is Perl, but many other scripting languages
639 # can be used.
641 # CGIscriptor reads text files from the requested input file (i.e., from
642 # $YOUR_HTML_FILES$PATH_INFO) and writes them to <STDOUT> (i.e., the
643 # client requesting the service) preceded by the obligatory
644 # "Content-type: text/html\n\n" or "Content-type: text/plain\n\n" string
645 # (except for "raw" files which supply their own Content-type message
646 # and only if the SERVER_PROTOCOL supports HTTP, MAIL, or MIME).
648 # When CGIscriptor encounters an embedded script, indicated by an HTML4 tag
650 # <SCRIPT TYPE="text/ssperl" [CGI="$VAR='default value'"] [SRC="ScriptSource"]>
651 # PERL script
652 # </SCRIPT>
654 # or
656 # <SCRIPT TYPE="text/osshell" [CGI="$name='default value'"] [SRC="ScriptSource"]>
657 # OS Shell script
658 # </SCRIPT>
660 # construct (anything between []-brackets is optional, other MIME-types
661 # and scripting languages are supported), the embedded script is removed
662 # and both the contents of the source file (i.e., "do 'ScriptSource'")
663 # AND the script are evaluated as a PERL program (i.e., by eval()),
664 # shell script (i.e., by a "safe" version of `Command`, qx) or an external
665 # interpreter. The output of the eval() function takes the place of the
666 # original <SCRIPT></SCRIPT> construct in the output string. Any CGI
667 # parameters declared by the CGI attribute are available as simple perl
668 # variables, and can subsequently be made available as variables to other
669 # scripting languages (e.g., bash, python, or lisp).
671 # Example: printing "Hello World"
672 # <HTML><HEAD><TITLE>Hello World</TITLE>
673 # <BODY>
674 # <H1><SCRIPT TYPE="text/ssperl">"Hello World"</SCRIPT></H1>
675 # </BODY></HTML>
677 # Save this in a file, hello.html, in the directory you indicated with
678 # $YOUR_HTML_FILES and access http://your_server/SHTML/hello.html
679 # (or to whatever name you use as an alias for CGIscriptor.pl).
680 # This is realy ALL you need to do to get going.
682 # You can use any values that are delivered in CGI-compliant form (i.e.,
683 # the "?name=value" type URL additions) transparently as "$name" variables
684 # in your scripts IFF you have declared them in the CGI attribute of
685 # a META or SCRIPT tag before e.g.:
686 # <META CONTENT="text/ssperl; CGI='$name = `default value`'
687 # [SRC='ScriptSource']">
688 # or
689 # <SCRIPT TYPE="text/ssperl" CGI="$name = 'default value'"
690 # [SRC='ScriptSource']>
691 # After such a 'CGI' attribute, you can use $name as an ordinary PERL variable
692 # (the ScriptSource file is immediately evaluated with "do 'ScriptSource'").
693 # The CGIscriptor script allows you to write ordinary HTML files which will
694 # include dynamic CGI aware (run time) features, such as on-line answers
695 # to specific CGI requests, queries, or the results of calculations.
697 # For example, if you wanted to answer questions of clients, you could write
698 # a Perl program called "Answer.pl" with a function "AnswerQuestion()"
699 # that prints out the answer to requests given as arguments. You then write
700 # an HTML page "Respond.html" containing the following fragment:
702 # <center>
703 # The Answer to your question
704 # <META CONTENT="text/ssperl; CGI='$Question'">
705 # <h3><SCRIPT TYPE="text/ssperl">$Question</SCRIPT></h3>
706 # is
707 # <h3><SCRIPT TYPE="text/ssperl" SRC="./PATH/Answer.pl">
708 # AnswerQuestion($Question);
709 # </SCRIPT></h3>
710 # </center>
711 # <FORM ACTION=Respond.html METHOD=GET>
712 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
713 # <INPUT TYPE=SUBMIT VALUE="Ask">
714 # </FORM>
716 # The output could look like the following (in HTML-speak):
718 # <CENTER>
719 # The Answer to your question
720 # <h3>What is the capital of the Netherlands?</h3>
721 # is
722 # <h3>Amsterdam</h3>
723 # </CENTER>
724 # <FORM ACTION=Respond.html METHOD=GET>
725 # Next question: <INPUT NAME="Question" TYPE=TEXT SIZE=40><br>
726 # <INPUT TYPE=SUBMIT VALUE="Ask">
728 # Note that the function "Answer.pl" does know nothing about CGI or HTML,
729 # it just prints out answers to arguments. Likewise, the text has no
730 # provisions for scripts or CGI like constructs. Also, it is completely
731 # trivial to extend this "program" to use the "Answer" later in the page
732 # to call up other information or pictures/sounds. The final text never
733 # shows any cue as to what the original "source" looked like, i.e.,
734 # where you store your scripts and how they are called.
736 # There are some extra's. The argument of the files called in a SRC= tag
737 # can access the CGI variables declared in the preceding META tag from
738 # the @ARGV array. Executable files are called as:
739 # `file '$ARGV[0]' ... ` (e.g., `Answer.pl \'$Question\'`;)
740 # The files called from SRC can even be (CGIscriptor) html files which are
741 # processed in-line. Furthermore, the SRC= tag can contain a perl block
742 # that is evaluated. That is,
743 # <META CONTENT="text/ssperl; CGI='$Question' SRC='{$Question}'">
744 # will result in the evaluation of "print do {$Question};" and the VALUE
745 # of $Question will be printed. Note that these "SRC-blocks" can be
746 # preceded and followed by other file names, but only a single block is
747 # allowed in a SRC= tag.
749 # One of the major hassles of dynamic WWW pages is the fact that several
750 # mutually incompatible browsers and platforms must be supported. For example,
751 # the way sound is played automatically is different for Netscape and
752 # Internet Explorer, and for each browser it is different again on
753 # Unix, MacOS, and Windows. Realy dangerous is processing user-supplied
754 # (form-) values to construct email addresses, file names, or database
755 # queries. All Apache WWW-server exploits reported in the media are
756 # based on faulty CGI-scripts that didn't check their user-data properly.
758 # There is no panacee for these problems, but a lot of work and problems
759 # can be saved by allowing easy and transparent control over which
760 # <SCRIPT></SCRIPT> blocks are executed on what CGI-data. CGIscriptor
761 # supplies such a method in the form of a pair of attributes:
762 # IF='...condition..' and UNLESS='...condition...'. When added to a
763 # script tag, the whole block (including the SRC attribute) will be
764 # ignored if the condition is false (IF) or true (UNLESS).
765 # For example, the following block will NOT be evaluated if the value
766 # of the CGI variable FILENAME is NOT a valid filename:
768 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
769 # IF='CGIscriptor::CGIsafeFileName($FILENAME)'>
770 # .....
771 # </SCRIPT>
773 # (the function CGIsafeFileName(String) returns an empty string ("")
774 # if the String argument is not a valid filename).
775 # The UNLESS attribute is the mirror image of IF.
777 # A user manual follows the HTML 4 and security paragraphs below.
779 ##########################################################################
781 # HTML 4 compliance
783 # In general, CGIscriptor.pl complies with the HTML 4 recommendations of
784 # the W3C. This means that any software to manage Web sites will be able
785 # to handle CGIscriptor files, as will web agents.
787 # All script code should be placed between <SCRIPT></SCRIPT> tags, the
788 # script type is indicated with TYPE="mime-type", the LANGUAGE
789 # feature is ignored, and a SRC feature is implemented. All CGI specific
790 # features are delegated to the CGI attribute.
792 # However, the behavior deviates from the W3C recommendations at some
793 # points. Most notably:
794 # 0- The scripts are executed at the server side, invissible to the
795 # client (i.e., the browser)
796 # 1- The mime-types are personal and idiosyncratic, but can be adapted.
797 # 2- Code in the body of a <SCRIPT></SCRIPT> tag-pair is still evaluated
798 # when a SRC feature is present.
799 # 3- The SRC attribute reads a list of files.
800 # 4- The files in a SRC attribute are processed according to file type.
801 # 5- The SRC attribute evaluates inline Perl code.
802 # 6- Processed META, DIV, INS tags are removed from the output
803 # document.
804 # 7- All attributes of the processed META tags, except CONTENT, are ignored
805 # (i.e., deleted from the output).
806 # 8- META tags can be placed ANYWHERE in the document.
807 # 9- Through the SRC feature, META tags can have visible output in the
808 # document.
809 # 10- The CGI attribute that declares CGI parameters, can be used
810 # inside the <SCRIPT> tag.
811 # 11- Use of an extended quote set, i.e., '', "", ``, (), {}, []
812 # and their \-slashed combinations: \'\', \"\", \`\`, \(\),
813 # \{\}, \[\].
814 # 12- IF and UNLESS attributes to <SCRIPT>, <META>, <DIV>, <INS> tags.
815 # 13- <DIV> tags cannot be nested, DIV tags are not
816 # rendered with new-lines.
817 # 14- The XML style <TAG .... /> is recognized and handled correctly.
818 # (i.e., no content is processed)
820 # The reasons for these choices are:
821 # You can still write completely HTML4 compliant documents. CGIscriptor
822 # will not force you to write "deviant" code. However, it allows you to
823 # do so (which is, in fact, just as bad). The prime design principle
824 # was to allow users to include plain Perl code. The code itself should
825 # be "enhancement free". Therefore, extra features were needed to
826 # supply easy access to CGI and Web site components. For security
827 # reasons these have to be declared explicitly. The SRC feature
828 # transparently manages access to external files, especially the safe
829 # use of executable files.
830 # The CGI attribute handles the declarations of external (CGI) variables
831 # in the SCRIPT and META tag's.
832 # EVERYTHING THE CGI ATTRIBUTE AND THE META TAG DO CAN BE DONE INSIDE
833 # A <SCRIPT></SCRIPT> TAG CONSTRUCT.
835 # The reason for the IF, UNLESS, and SRC attributes (and their Perl code
836 # evaluation) were build into the META and SCRIPT tags is part laziness,
837 # part security. The SRC blocks allows more compact documents and easier
838 # debugging. The values of the CGI variables can be immediately screened
839 # for security by IF or UNLESS conditions, and even SRC attributes (e.g.,
840 # email addresses and file names), and a few commands can be called
841 # without having to add another Perl TAG pair. This is especially important
842 # for documents that require the use of other (more restricted) "scripting"
843 # languages and facilities that lag transparent control structures.
845 ##########################################################################
847 # SECURITY
849 # Your WWW site is a few keystrokes away from a few hundred million internet
850 # users. A fair percentage of these users knows more about your computer
851 # than you do. And some of these just might have bad intentions.
853 # To ensure uncompromized operation of your server and platform, several
854 # features are incorporated in CGIscriptor.pl to enhance security.
855 # First of all, you should check the source of this program. No security
856 # measures will help you when you download programs from anonymous sources.
857 # If you want to use THIS file, please make sure that it is uncompromized.
858 # The best way to do this is to contact the source and try to determine
859 # whether s/he is reliable (and accountable).
861 # BE AWARE THAT ANY PROGRAMMER CAN CHANGE THIS PROGRAM IN SUCH A WAY THAT
862 # IT WILL SET THE DOORS TO YOUR SYSTEM WIDE OPEN
864 # I would like to ask any user who finds bugs that could compromise
865 # security to report them to me (and any other bug too,
866 # Email: R.J.J.H.vanSon@uva.nl or ifa@hum.uva.nl).
868 # Security features
870 # 1 Invisibility
871 # The inner workings of the HTML source files are completely hidden
872 # from the client. Only the HTTP header and the ever changing content
873 # of the output distinguish it from the output of a plain, fixed HTML
874 # file. Names, structures, and arguments of the "embedded" scripts
875 # are invisible to the client. Error output is suppressed except
876 # during debugging (user configurable).
878 # 2 Separate directory trees
879 # Directories containing Inline text and script files can reside on
880 # separate trees, distinct from those of the HTTP server. This means
881 # that NEITHER the text files, NOR the script files can be read by
882 # clients other than through CGIscriptor.pl, UNLESS they are
883 # EXPLICITELY made available.
885 # 3 Requests are NEVER "evaluated"
886 # All client supplied values are used as literal values (''-quoted).
887 # Client supplied ''-quotes are ALWAYS removed. Therefore, as long as the
888 # embedded scripts do NOT themselves evaluate these values, clients CANNOT
889 # supply executable commands. Be sure to AVOID scripts like:
891 # <META CONTENT="text/ssperl; CGI='$UserValue'">
892 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 $UserValue`;</SCRIPT>
894 # These are a recipe for disaster. However, the following quoted
895 # form should be save (but is still not adviced):
897 # <SCRIPT TYPE="text/ssperl">$dir = `ls -1 \'$UserValue\'`;</SCRIPT>
899 # A special function, SAFEqx(), will automatically do exactly this,
900 # e.g., SAFEqx('ls -1 $UserValue') will execute `ls -1 \'$UserValue\'`
901 # with $UserValue interpolated. I recommend to use SAFEqx() instead
902 # of backticks whenever you can. The OS shell scripts inside
904 # <SCRIPT TYPE="text/osshell">ls -1 $UserValue</SCRIPT>
906 # are handeld by SAFEqx and automatically ''-quoted.
908 # 4 Logging of requests
909 # All requests can be logged separate from the Host server. The level of
910 # detail is user configurable: Including or excluding the actual queries.
911 # This allows for the inspection of (im-) proper use.
913 # 5 Access control: Clients
914 # The Remote addresses can be checked against a list of authorized
915 # (i.e., accepted) or non-authorized (i.e., rejected) clients. Both
916 # REMOTE_HOST and REMOTE_ADDR are tested so clients without a proper
917 # HOST name can be (in-) excluded by their IP-address. Client patterns
918 # containing all numbers and dots are considered IP-addresses, all others
919 # domain names. No wild-cards or regexp's are allowed, only partial
920 # addresses.
921 # Matching of names is done from the back to the front (domain first,
922 # i.e., $REMOTE_HOST =~ /\Q$pattern\E$/is), so including ".edu" will
923 # accept or reject all clients from the domain EDU. Matching of
924 # IP-addresses is done from the front to the back (domain first, i.e.,
925 # $REMOTE_ADDR =~ /^\Q$pattern\E/is), so including "128." will (in-)
926 # exclude all clients whose IP-address starts with 128.
927 # There are two special symbols: "-" matches HOSTs with no name and "*"
928 # matches ALL HOSTS/clients.
929 # For those needing more expressional power, lines starting with
930 # "-e" are evaluated by the perl eval() function. E.g.,
931 # '-e $REMOTE_HOST =~ /\.edu$/is;' will accept/reject clients from the
932 # domain '.edu'.
934 # 6 Access control: Files
935 # In principle, CGIscriptor could read ANY file in the directory
936 # tree as discussed in 1. However, for security reasons this is
937 # restricted to text files. It can be made more restricted by entering
938 # a global file pattern (e.g., ".html"). This is done by default.
939 # For each client requesting access, the file pattern(s) can be made
940 # more restrictive than the global pattern by entering client specific
941 # file patterns in the Access Control files (see 5).
942 # For example: if the ACCEPT file contained the lines
943 # * DEMO
944 # .hum.uva.nl LET
945 # 145.18.230.
946 # Then all clients could request paths containing "DEMO" or "demo", e.g.
947 # "/my/demo/file.html" ($PATH_INFO =~ /\Q$pattern\E/), Clients from
948 # *.hum.uva.nl could also request paths containing "LET or "let", e.g.
949 # "/my/let/file.html", and clients from the local cluster
950 # 145.18.230.[0-9]+ could access ALL files.
951 # Again, for those needing more expressional power, lines starting with
952 # "-e" are evaluated. For instance:
953 # '-e $REMOTE_HOST =~ /\.edu$/is && $PATH_INFO =~ m@/DEMO/@is;'
954 # will accept/reject requests for files from the directory "/demo/" from
955 # clients from the domain '.edu'.
957 # 7 Access control: Server side session tickets
958 # Specific paths can be controlled by Session Tickets which must be
959 # present as a SESSIONTICKET=<value> CGI variable in the request. These paths
960 # are defined in %TicketRequiredPatterns as pairs of:
961 # ('regexp' => 'SessionPath\tPasswordPath\tLogin.html\tExpiration').
962 # Session Tickets are stored in a separate directory (SessionPath, e.g.,
963 # "Private/.Session") as files with the exact same name of the SESSIONTICKET
964 # CGI. The following is an example:
965 # Type: SESSION
966 # IPaddress: 127.0.0.1
967 # AllowedPaths: ^/Private/Name/
968 # Expires: 3600
969 # Username: test
970 # ...
971 # Other content can follow.
973 # It is adviced that Session Tickets should be deleted
974 # after some (idle) time. The IP address should be the IP number at login, and
975 # the SESSIONTICKET will be rejected if it is presented from another IP address.
976 # AllowedPaths is a perl regexp. Be careful how they match. Make sure to delimit
977 # the names to prevent access to overlapping names, eg, "^/Private/Rob" will also
978 # match "^/Private/Robert", however, "^/Private/Rob/" will not. Expires is the
979 # time the ticket will remain valid after creation (file ctime). Time can be given
980 # in s[econds] (default), m[inutes], h[hours], or d[ays], eg, "24h" means 24 hours.
981 # None of these need be present, but the Ticket must have a non-zero size.
983 # Next to Session Tickets, there are two other type of ticket files:
984 # - LOGIN tickets store information about a current login request
985 # - PASSWORD ticket store account information to authorize login requests
987 # 8 Query length limiting
988 # The length of the Query string can be limited. If CONTENT_LENGTH is larger
989 # than this limit, the request is rejected. The combined length of the
990 # Query string and the POST input is checked before any processing is done.
991 # This will prevent clients from overloading the scripts.
992 # The actual, combined, Query Size is accessible as a variable through
993 # $CGI_Content_Length.
995 # 9 Illegal filenames, paths, and protected directories
996 # One of the primary security concerns in handling CGI-scripts is the
997 # use of "funny" characters in the requests that con scripts in executing
998 # malicious commands. Examples are inserting ';', null bytes, or <newline>
999 # characters in URL's and filenames, followed by executable commands. A
1000 # special variable $FileAllowedChars stores a string of all allowed
1001 # characters. Any request that translates to a filename with a character
1002 # OUTSIDE this set will be rejected.
1003 # In general, all (readable files) in the DocumentRoot tree are accessible.
1004 # This might not be what you want. For instance, your DocumentRoot directory
1005 # might be the working directory of a CVS project and contain sensitive
1006 # information (e.g., the password to get to the repository). You can block
1007 # access to these subdirectories by adding the corresponding patterns to
1008 # the $BlockPathAccess variable. For instance, $BlockPathAccess = '/CVS/'
1009 # will block any request that contains '/CVS/' or:
1010 # die if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@;
1012 #10 The execution of code blocks can be controlled in a transparent way
1013 # by adding IF or UNLESS conditions in the tags themselves. That is,
1014 # a simple check of the validity of filenames or email addresses can
1015 # be done before any code is executed.
1017 ###############################################################################
1019 # USER MANUAL (sort of)
1021 # CGIscriptor removes embedded scripts, indicated by an HTML 4 type
1022 # <SCRIPT TYPE='text/ssperl'> </SCRIPT> or <SCRIPT TYPE='text/osshell'>
1023 # </SCRIPT> constructs. CGIscriptor also recognizes XML-type
1024 # <SCRIPT TYPE='text/ssperl'/> constructs. These are usefull when
1025 # the necessary code is already available in the TAG itself (e.g.,
1026 # using external files). The contents of the directive are executed by
1027 # the PERL eval() and `` functions (in a separate name space). The
1028 # result of the eval() function replaces the <SCRIPT> </SCRIPT> construct
1029 # in the output file. You can use the values that are delivered in
1030 # CGI-compliant form (i.e., the "?name=value&.." type URL additions)
1031 # transparently as "$name" variables in your directives after they are
1032 # defined in a <META> or <SCRIPT> tag.
1033 # If you define the variable "$CGIscriptorResults" in a CGI attribute, all
1034 # subsequent <SCRIPT> and <META> results (including the defining
1035 # tag) will also be pushed onto a stack: @CGIscriptorResults. This list
1036 # behaves like any other, ordinary list and can be manipulated.
1038 # Both GET and POST requests are accepted. These two methods are treated
1039 # equal. Variables, i.e., those values that are determined when a file is
1040 # processed, are indicated in the CGI attribute by $<name> or $<name>=<default>
1041 # in which <name> is the name of the variable and <default> is the value
1042 # used when there is NO current CGI value for <name> (you can use
1043 # white-spaces in $<name>=<default> but really DO make sure that the
1044 # default value is followed by white space or is quoted). Names can contain
1045 # any alphanumeric characters and _ (i.e., names match /[\w]+/).
1046 # If the Content-type: is 'multipart/*', the input is treated as a
1047 # MIME multipart message and automatically delimited. CGI variables get
1048 # the "raw" (i.e., undecoded) body of the corresponding message part.
1050 # Variables can be CGI variables, i.e., those from the QUERY_STRING,
1051 # environment variables, e.g., REMOTE_USER, REMOTE_HOST, or REMOTE_ADDR,
1052 # or predefined values, e.g., CGI_Decoded_QS (The complete, decoded,
1053 # query string), CGI_Content_Length (the length of the decoded query
1054 # string), CGI_Year, CGI_Month, CGI_Time, and CGI_Hour (the current
1055 # date and time).
1057 # All these are available when defined in a CGI attribute. All environment
1058 # variables are accessible as $ENV{'name'}. So, to access the REMOTE_HOST
1059 # and the REMOTE_USER, use, e.g.:
1061 # <SCRIPT TYPE='text/ssperl'>
1062 # ($ENV{'REMOTE_HOST'}||"-")." $ENV{'REMOTE_USER'}"
1063 # </SCRIPT>
1065 # (This will print a "-" if REMOTE_HOST is not known)
1066 # Another way to do this is:
1068 # <META CONTENT="text/ssperl; CGI='$REMOTE_HOST = - $REMOTE_USER'">
1069 # <SCRIPT TYPE='text/ssperl'>"$REMOTE_HOST $REMOTE_USER"</SCRIPT>
1070 # or
1071 # <META CONTENT='text/ssperl; CGI="$REMOTE_HOST = - $REMOTE_USER"
1072 # SRC={"$REMOTE_HOST $REMOTE_USER\n"}'>
1074 # This is possible because ALL environment variables are available as
1075 # CGI variables. The environment variables take precedence over CGI
1076 # names in case of a "name clash". For instance:
1077 # <META CONTENT="text/ssperl; CGI='$HOME' SRC={$HOME}">
1078 # Will print the current HOME directory (environment) irrespective whether
1079 # there is a CGI variable from the query
1080 # (e.g., Where do you live? <INPUT TYPE="TEXT" NAME="HOME">)
1081 # THIS IS A SECURITY FEATURE. It prevents clients from changing
1082 # the values of defined environment variables (e.g., by supplying
1083 # a bogus $REMOTE_ADDR). Although $ENV{} is not changed by the META tags,
1084 # it would make the use of declared variables insecure. You can still
1085 # access CGI variables after a name clash with
1086 # CGIscriptor::CGIparseValue(<name>).
1088 # Some CGI variables are present several times in the query string
1089 # (e.g., from multiple selections). These should be defined as
1090 # @VARIABLENAME=default in the CGI attribute. The list @VARIABLENAME
1091 # will contain ALL VARIABLENAME values from the query, or a single
1092 # default value. If there is an ENVIRONMENT variable of the
1093 # same name, it will be used instead of the default AND the query
1094 # values. The corresponding function is
1095 # CGIscriptor::CGIparseValueList(<name>)
1097 # CGI variables collected in a @VARIABLENAME list are unordered.
1098 # When more structured variables are needed, a hash table can be used.
1099 # A variable defined as %VARIABLE=default will collect all
1100 # CGI-parameters whose name start with 'VARIABLE' in a hash table with
1101 # the remainder of the name as a key. For instance, %PERSON will
1102 # collect PERSONname='John Doe', PERSONbirthdate='01 Jan 00', and
1103 # PERSONspouse='Alice' into a hash table %PERSON such that $PERSON{'spouse'}
1104 # equals 'Alice'. Any default value or environment value will be stored
1105 # under the "" key. If there is an ENVIRONMENT variable of the same name,
1106 # it will be used instead of the default AND the query values. The
1107 # corresponding function is CGIscriptor::CGIparseValueHash(<name>)
1109 # This method of first declaring your environment and CGI variables
1110 # before being able to use them in the scripts might seem somewhat
1111 # clumsy, but it protects you from inadvertedly printing out the values of
1112 # system environment variables when their names coincide with those used
1113 # in the CGI forms. It also prevents "clients" from supplying CGI
1114 # parameter values for your private variables.
1115 # THIS IS A SECURITY FEATURE!
1118 # NON-HTML CONTENT TYPES
1120 # Normally, CGIscriptor prints the standard "Content-type: text/html\n\n"
1121 # message before anything is printed. This has been extended to include
1122 # plain text (.txt) files, for which the Content-type (MIME type)
1123 # 'text/plain' is printed. In all other respects, text files are treated
1124 # as HTML files (this can be switched off by removing '.txt' from the
1125 # $FilePattern variable) . When the content type should be something else,
1126 # e.g., with multipart files, use the $RawFilePattern (.xmr, see also next
1127 # item). CGIscriptor will not print a Content-type message for this file
1128 # type (which must supply its OWN Content-type message). Raw files must
1129 # still conform to the <SCRIPT></SCRIPT> and <META> tag specifications.
1132 # NON-HTML FILES
1134 # CGIscriptor is intended to process HTML and text files only. You can
1135 # create documents of any mime-type on-the-fly using "raw" text files,
1136 # e.g., with the .xmr extension. However, CGIscriptor will not process
1137 # binary files of any type, e.g., pictures or sounds. Given the sheer
1138 # number of formats, I do not have any intention to do so. However,
1139 # an escape route has been provided. You can construct a genuine raw
1140 # (.xmr) text file that contains the perl code to service any file type
1141 # you want. If the global $BinaryMapFile variable contains the path to
1142 # this file (e.g., /BinaryMapFile.xmr), this file will be called
1143 # whenever an unsupported (non-HTML) file type is requested. The path
1144 # to the requested binary file is stored in $ENV('CGI_BINARY_FILE')
1145 # and can be used like any other CGI-variable. Servicing binary files
1146 # then becomes supplying the correct Content-type (e.g., print
1147 # "Content-type: image/jpeg\n\n";) and reading the file and writing it
1148 # to STDOUT (e.g., using sysread() and syswrite()).
1151 # THE META TAG
1153 # All attributes of a META tag are ignored, except the
1154 # CONTENT='text/ssperl; CGI=" ... " [SRC=" ... "]' attribute. The string
1155 # inside the quotes following the CONTENT= indication (white-space is
1156 # ignored, "" '' `` (){}[]-quote pairs are allowed, plus their \ versions)
1157 # MUST start with any of the CGIscriptor mime-types (e.g.: text/ssperl or
1158 # text/osshell) and a comma or semicolon.
1159 # The quoted string following CGI= contains a white-space separated list
1160 # of declarations of the CGI (and Environment) values and default values
1161 # used when no CGI values are supplied by the query string.
1163 # If the default value is a longer string containing special characters,
1164 # possibly spanning several lines, the string must be enclosed in quotes.
1165 # You may use any pair of quotes or brackets from the list '', "", ``, (),
1166 # [], or {} to distinguish default values (or preceded by \, e.g., \(...\)
1167 # is different from (...)). The outermost pair will always be used and any
1168 # other quotes inside the string are considered to be part of the string
1169 # value, e.g.,
1171 # $Value = {['this'
1172 # "and" (this)]}
1173 # will result in $Value getting the default value: ['this'
1174 # "and" (this)]
1175 # (NOTE that the newline is part of the default value!).
1177 # Internally, for defining and initializing CGI (ENV) values, the META
1178 # and SCRIPT tags use the functions "defineCGIvariable($name, $default)"
1179 # (scalars) and "defineCGIvariableList($name, $default)" (lists).
1180 # These functions can be used inside scripts as
1181 # "CGIscriptor::defineCGIvariable($name, $default)" and
1182 # "CGIscriptor::defineCGIvariableList($name, $default)".
1183 # "CGIscriptor::defineCGIvariableHash($name, $default)".
1185 # The CGI attribute will be processed exactly identical when used inside
1186 # the <SCRIPT> tag. However, this use is not according to the
1187 # HTML 4.0 specifications of the W3C.
1190 # THE DIV/INS TAGS
1192 # There is a problem when constructing html files containing
1193 # server-side perl scripts with standard HTML tools. These
1194 # tools will refuse to process any text between <SCRIPT></SCRIPT>
1195 # tags. This is quite annoying when you want to use large
1196 # HTML templates where you will fill in values.
1198 # For this purpose, CGIscriptor will read the neutral
1199 # <DIV CLASS="ssperl" ID="varname"></DIV> or
1200 # <INS CLASS="ssperl" ID="varname"></INS>
1201 # tag (in Cascading Style Sheet manner) Note that
1202 # "varname" has NO '$' before it, it is a bare name.
1203 # Any text between these <DIV ...></DIV> or
1204 # <INS ...></INS>tags will be assigned to '$varname'
1205 # as is (e.g., as a literal).
1206 # No processing or interpolation will be performed.
1207 # There is also NO nesting possible. Do NOT nest a
1208 # </DIV> inside a <DIV></DIV>! Moreover, neither INS nor
1209 # DIV tags do ensure a block structure in the final
1210 # rendering (i.e., no empty lines).
1212 # Note that <DIV CLASS="ssperl" ID="varname"/>
1213 # is handled the XML way. No content is processed,
1214 # but varname is defined, and any SRC directives are
1215 # processed.
1217 # You can use $varname like any other variable name.
1218 # However, $varname is NOT a CGI variable and will be
1219 # completely internal to your script. There is NO
1220 # interaction between $varname and the outside world.
1222 # To interpolate a DIV derived text, you can use:
1223 # $varname =~ s/([\]])/\\\1/g; # Mark ']'-quotes
1224 # $varname = eval("qq[$varname]"); # Interpolate all values
1226 # The DIV tags will process IF, UNLESS, CGI and
1227 # SRC attributes. The SRC files will be pre-pended to the
1228 # body text of the tag. SRC blocks are NOT executed.
1230 # CONDITIONAL PROCESSING: THE 'IF' AND 'UNLESS' ATTRIBUTES
1232 # It is often necessary to include code-blocks that should be executed
1233 # conditionally, e.g., only for certain browsers or operating system.
1234 # Furthermore, quite often sanity and security checks are necessary
1235 # before user (form) data can be processed, e.g., with respect to
1236 # email addresses and filenames.
1238 # Checks added to the code are often difficult to find, interpret or
1239 # maintain and in general mess up the code flow. This kind of confussion
1240 # is dangerous.
1241 # Also, for many of the supported "foreign" scripting languages, adding
1242 # these checks is cumbersome or even impossible.
1244 # As a uniform method for asserting the correctness of "context", two
1245 # attributes are added to all supported tags: IF and UNLESS.
1246 # They both evaluate their value and block execution when the
1247 # result is <FALSE> (IF) or <TRUE> (UNLESS) in Perl, e.g.,
1248 # UNLESS='$NUMBER \> 100;' blocks execution if $NUMBER <= 100. Note that
1249 # the backslash in the '\>' is removed and only used to differentiate
1250 # this conditional '>' from the tag-closing '>'. For symmetry, the
1251 # backslash in '\<' is also removed. Inside these conditionals,
1252 # ~/ and ./ are expanded to their respective directory root paths.
1254 # For example, the following tag will be ignored when the filename is
1255 # invalid:
1257 # <SCRIPT TYPE='text/ssperl' CGI='$FILENAME'
1258 # IF='CGIscriptor::CGIsafeFileName($FILENAME);'>
1259 # ...
1260 # </SCRIPT>
1262 # The IF and UNLESS values must be quoted. The same quotes are supported
1263 # as with the other attributes. The SRC attribute is ignored when IF and
1264 # UNLESS block execution.
1266 # NOTE: 'IF' and 'UNLESS' always evaluate perl code.
1269 # THE MAGIC SOURCE ATTRIBUTE (SRC=)
1271 # The SRC attribute inside tags accepts a list of filenames and URL's
1272 # separated by "," comma's (or ";" semicolons).
1273 # ALL the variable values defined in the CGI attribute are available
1274 # in @ARGV as if the file or block was executed from the command line,
1275 # in the exact order in which they were declared in the preceding CGI
1276 # attribute.
1278 # First, a SRC={}-block will be evaluated as if the code inside the
1279 # block was part of a <SCRIPT></SCRIPT> construct, i.e.,
1280 # "print do { code };'';" or `code` (i.e., SAFEqx('code)).
1281 # Only a single block is evaluated. Note that this is processed less
1282 # efficiently than <SCRIPT> </SCRIPT> blocks. Type of evaluation
1283 # depends on the content-type: Perl for text/ssperl and OS shell for
1284 # text/osshell. For other mime types (scripting languages), anything in
1285 # the source block is put in front of the code block "inside" the tag.
1287 # Second, executable files (i.e., -x filename != 0) are evaluated as:
1288 # print `filename \'$ARGV[0]\' \'$ARGV[1]\' ...`
1289 # That is, you can actually call executables savely from the SRC tag.
1291 # Third, text files that match the file pattern, used by CGIscriptor to
1292 # check whether files should be processed ($FilePattern), are
1293 # processed in-line (i.e., recursively) by CGIscriptor as if the code
1294 # was inserted in the original source file. Recursions, i.e., calling
1295 # a file inside itself, are blocked. If you need them, you have to code
1296 # them explicitely using "main::ProcessFile($file_path)".
1298 # Fourth, Perl text files (i.e., -T filename != 0) are evaluated as:
1299 # "do FileName;'';".
1301 # Last, URL's (i.e., starting with 'HTTP://', 'FTP://', 'GOPHER://',
1302 # 'TELNET://', 'WHOIS://' etc.) are loaded
1303 # and printed. The loading and handling of <BASE> and document header
1304 # is done by a command generated by main::GET_URL($URL [, 0]). You can enter your
1305 # own code (default is curl, wget, or snarf and some post-processing to add a <BASE> tag).
1307 # There are two pseudo-file names: PREFIX and POSTFIX. These implement
1308 # a switch from prefixing the SRC code/files (PREFIX, default) before the
1309 # content of the tag to appending the code after the content of the tag
1310 # (POSTFIX). The switches are done in the order in which the PREFIX and
1311 # POSTFIX labels are encountered. You can mix PREFIX and POSTFIX labels
1312 # in any order with the SRC files. Note that the ORDER of file execution
1313 # is determined for prefixed and postfixed files seperately.
1315 # File paths can be preceded by the URL protocol prefix "file://". This
1316 # is simply STRIPPED from the name.
1318 # Example:
1319 # The request
1320 # "http://cgi-bin/Action_Forms.pl/Statistics/Sign_Test.html?positive=8&negative=22
1321 # will result in printing "${SS_PUB}/Statistics/Sign_Test.html"
1322 # With QUERY_STRING = "positive=8&negative=22"
1324 # on encountering the lines:
1325 # <META CONTENT="text/osshell; CGI='$positive=11 $negative=3'">
1326 # <b><SCRIPT LANGUAGE=PERL TYPE="text/ssperl" SRC="./Statistics/SignTest.pl">
1327 # </SCRIPT></b><p>"
1329 # This line will be processed as:
1330 # "<b>`${SS_SCRIPT}/Statistics/SignTest.pl '8' '22'`</b><p>"
1332 # In which "${SS_SCRIPT}/Statistics/SignTest.pl" is an executable script,
1333 # This line will end up printed as:
1334 # "<b>p <= 0.0161</b><p>"
1336 # Note that the META tag itself will never be printed, and is invisible to
1337 # the outside world.
1339 # The SRC files in a DIV or INS tag will be added (pre-pended) to the body
1340 # of the <DIV></DIV> tag. Blocks are NOT executed! If you do not
1341 # need any content, you can use the <DIV...../> format.
1344 # THE CGISCRIPTOR ROOT DIRECTORIES ~/ AND ./
1346 # Inside <SCRIPT></SCRIPT> tags, filepaths starting
1347 # with "~/" are replaced by "$YOUR_HTML_FILES/", this way files in the
1348 # public directories can be accessed without direct reference to the
1349 # actual paths. Filepaths starting with "./" are replaced by
1350 # "$YOUR_SCRIPTS/" and this should only be used for scripts.
1352 # Note: this replacement can seriously affect Perl scripts. Watch
1353 # out for constructs like $a =~ s/aap\./noot./g, use
1354 # $a =~ s@aap\.@noot.@g instead.
1356 # CGIscriptor.pl will assign the values of $SS_PUB and $SS_SCRIPT
1357 # (i.e., $YOUR_HTML_FILES and $YOUR_SCRIPTS) to the environment variables
1358 # $SS_PUB and $SS_SCRIPT. These can be accessed by the scripts that are
1359 # executed.
1360 # Values not preceded by $, ~/, or ./ are used as literals
1363 # OS SHELL SCRIPT EVALUATION (CONTENT-TYPE=TEXT/OSSHELL)
1365 # OS scripts are executed by a "safe" version of the `` operator (i.e.,
1366 # SAFEqx(), see also below) and any output is printed. CGIscriptor will
1367 # interpolate the script and replace all user-supplied CGI-variables by
1368 # their ''-quoted values (actually, all variables defined in CGI attributes
1369 # are quoted). Other Perl variables are interpolated in a simple fasion,
1370 # i.e., $scalar by their value, @list by join(' ', @list), and %hash by
1371 # their name=value pairs. Complex references, e.g., @$variable, are all
1372 # evaluated in a scalar context. Quotes should be used with care.
1373 # NOTE: the results of the shell script evaluation will appear in the
1374 # @CGIscriptorResults stack just as any other result.
1375 # All occurrences of $@% that should NOT be interpolated must be
1376 # preceeded by a "\". Interpolation can be switched off completely by
1377 # setting $CGIscriptor::NoShellScriptInterpolation = 1
1378 # (set to 0 or undef to switch interpolation on again)
1379 # i.e.,
1380 # <SCRIPT TYPE="text/ssperl">
1381 # $CGIscriptor::NoShellScriptInterpolation = 1;
1382 # </SCRIPT>
1385 # RUN TIME TRANSLATION OF INPUT FILES
1387 # Allows general and global conversions of files using Regular Expressions.
1388 # Very handy (but costly) to rewrite legacy pages to a new format.
1389 # Select files to use it on with
1390 # my $TranslationPaths = 'filepattern';
1391 # This is costly. For efficiency, define:
1392 # $TranslationPaths = ''; when not using translations.
1393 # Accepts general regular expressions: [$pattern, $replacement]
1395 # Define:
1396 # my $TranslationPaths = 'filepattern'; # Pattern matching PATH_INFO
1398 # push(@TranslationTable, ['pattern', 'replacement']);
1399 # e.g. (for Ruby Rails):
1400 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
1401 # push(@TranslationTable, ['%>', '</SCRIPT>']);
1403 # Runs:
1404 # my $currentRegExp;
1405 # foreach $currentRegExp (@TranslationTable)
1407 # my ($pattern, $replacement) = @$currentRegExp;
1408 # $$text =~ s!$pattern!$replacement!msg;
1409 # };
1412 # EVALUATION OF OTHER SCRIPTING LANGUAGES
1414 # Adding a MIME-type and an interpreter command to
1415 # %ScriptingLanguages automatically will catch any other
1416 # scripting language in the standard
1417 # <SCRIPT TYPE="[mime]"></SCRIPT> manner.
1418 # E.g., adding: $ScriptingLanguages{'text/sspython'} = 'python';
1419 # will actually execute the folowing code in an HTML page
1420 # (ignore 'REMOTE_HOST' for the moment):
1421 # <SCRIPT TYPE="text/sspython">
1422 # # A Python script
1423 # x = ["A","real","python","script","Hello","World","and", REMOTE_HOST]
1424 # print x[4:8] # Prints the list ["Hello","World","and", REMOTE_HOST]
1425 # </SCRIPT>
1427 # The script code is NOT interpolated by perl, EXCEPT for those
1428 # interpreters that cannot handle variables themselves.
1429 # Currently, several interpreters are pre-installed:
1431 # Perl test - "text/testperl" => 'perl',
1432 # Python - "text/sspython" => 'python',
1433 # Ruby - "text/ssruby" => 'ruby',
1434 # Tcl - "text/sstcl" => 'tcl',
1435 # Awk - "text/ssawk" => 'awk -f-',
1436 # Gnu Lisp - "text/sslisp" => 'rep | tail +5 '.
1437 # "| egrep -v '> |^rep. |^nil\\\$'",
1438 # XLispstat - "text/xlispstat" => 'xlispstat | tail +7 '.
1439 # "| egrep -v '> \\\$|^NIL'",
1440 # Gnu Prolog- "text/ssprolog" => 'gprolog',
1441 # M4 macro's- "text/ssm4" => 'm4',
1442 # Born shell- "text/sh" => 'sh',
1443 # Bash - "text/bash" => 'bash',
1444 # C-shell - "text/csh" => 'csh',
1445 # Korn shell- "text/ksh" => 'ksh',
1446 # Praat - "text/sspraat" => "praat - | sed 's/Praat > //g'",
1447 # R - "text/ssr" => "R --vanilla --slave | sed 's/^[\[0-9\]*] //g'",
1448 # REBOL - "text/ssrebol" =>
1449 # "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\s*\[> \]* //g'",
1450 # PostgreSQL- "text/postgresql" => 'psql 2>/dev/null',
1451 # (psql)
1453 # Note that the "value" of $ScriptingLanguages{mime} must be a command
1454 # that reads Standard Input and writes to standard output. Any extra
1455 # output of interactive interpreters (banners, echo's, prompts)
1456 # should be removed by piping the output through 'tail', 'grep',
1457 # 'sed', or even 'awk' or 'perl'.
1459 # For access to CGI variables there is a special hashtable:
1460 # %ScriptingCGIvariables.
1461 # CGI variables can be accessed in three ways.
1462 # 1. If the mime type is not present in %ScriptingCGIvariables,
1463 # nothing is done and the script itself should parse the relevant
1464 # environment variables.
1465 # 2. If the mime type IS present in %ScriptingCGIvariables, but it's
1466 # value is empty, e.g., $ScriptingCGIvariables{"text/sspraat"} = '';,
1467 # the script text is interpolated by perl. That is, all $var, @array,
1468 # %hash, and \-slashes are replaced by their respective values.
1469 # 3. In all other cases, the CGI and environment variables are added
1470 # in front of the script according to the format stored in
1471 # %ScriptingCGIvariables. That is, the following (pseudo-)code is
1472 # executed for each CGI- or Environment variable defined in the CGI-tag:
1473 # printf(INTERPRETER, $ScriptingCGIvariables{$mime}, $CGI_NAME, $CGI_VALUE);
1475 # For instance, "text/testperl" => '$%s = "%s";' defines variable
1476 # definitions for Perl, and "text/sspython" => '%s = "%s"' for Python
1477 # (note that these definitions are not save, the real ones contain '-quotes).
1479 # THIS WILL NOT WORK FOR @VARIABLES, the (empty) $VARIABLES will be used
1480 # instead.
1482 # The $CGI_VALUE parameters are "shrubed" of all control characters
1483 # and quotes (by &shrubCGIparameter($CGI_VALUE)) for the options 2 and 3.
1484 # Control characters are replaced by \0<octal ascii value> (the exception
1485 # is \015, the newline, which is replaced by \n) and quotes
1486 # and backslashes by their HTML character
1487 # value (' -> &#39; ` -> &#96; " -> &quot; \ -> &#92; & -> &amper;).
1488 # For example:
1489 # if a client would supply the string value (in standard perl, e.g.,
1490 # \n means <newline>)
1491 # "/dev/null';\nrm -rf *;\necho '"
1492 # it would be processed as
1493 # '/dev/null&#39;;\nrm -rf *;\necho &#39;'
1494 # (e.g., sh or bash would process the latter more according to your
1495 # intentions).
1496 # If your intepreter requires different protection measures, you will
1497 # have to supply these in %main::SHRUBcharacterTR (string => translation),
1498 # e.g., $SHRUBcharacterTR{"\'"} = "&#39;";
1500 # Currently, the following definitions are used:
1501 # %ScriptingCGIvariables = (
1502 # "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value' (for testing)
1503 # "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
1504 # "text/ssruby" => '@%s = "%s"', # Ruby @VAR = "value"
1505 # "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
1506 # "text/ssawk" => '%s = "%s";', # Awk VAR = "value";
1507 # "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
1508 # "text/xlispstat" => '(setq %s "%s")', # Xlispstat (setq VAR "value")
1509 # "text/ssprolog" => '', # Gnu prolog (interpolated)
1510 # "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
1511 # "text/sh" => "\%s='\%s';", # Born shell VAR='value';
1512 # "text/bash" => "\%s='\%s';", # Born again shell VAR='value';
1513 # "text/csh" => "\$\%s = '\%s';", # C shell $VAR = 'value';
1514 # "text/ksh" => "\$\%s = '\%s';", # Korn shell $VAR = 'value';
1515 # "text/sspraat" => '', # Praat (interpolation)
1516 # "text/ssr" => '%s <- "%s";', # R VAR <- "value";
1517 # "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
1518 # "text/postgresql" => '', # PostgreSQL (interpolation)
1519 # "" => ""
1520 # );
1522 # Four tables allow fine-tuning of interpreter with code that should be
1523 # added before and after each code block:
1525 # Code added before each script block
1526 # %ScriptingPrefix = (
1527 # "text/testperl" => "\# Prefix Code;", # Perl script testing
1528 # "text/ssm4" => 'divert(0)' # M4 macro's (open STDOUT)
1529 # );
1530 # Code added at the end of each script block
1531 # %ScriptingPostfix = (
1532 # "text/testperl" => "\# Postfix Code;", # Perl script testing
1533 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1534 # );
1535 # Initialization code, inserted directly after opening (NEVER interpolated)
1536 # %ScriptingInitialization = (
1537 # "text/testperl" => "\# Initialization Code;", # Perl script testing
1538 # "text/ssawk" => 'BEGIN {', # Server Side awk scripts
1539 # "text/sslisp" => '(prog1 nil ', # Lisp (rep)
1540 # "text/xlispstat" => '(prog1 nil ', # xlispstat
1541 # "text/ssm4" => 'divert(-1)' # M4 macro's (block STDOUT)
1542 # );
1543 # Cleanup code, inserted before closing (NEVER interpolated)
1544 # %ScriptingCleanup = (
1545 # "text/testperl" => "\# Cleanup Code;", # Perl script testing
1546 # "text/sspraat" => 'Quit',
1547 # "text/ssawk" => '};', # Server Side awk scripts
1548 # "text/sslisp" => '(princ "\n" standard-output)).' # Closing print to rep
1549 # "text/xlispstat" => '(print "" *standard-output*)).' # Closing print to xlispstat
1550 # "text/postgresql" => '\q',
1551 # );
1554 # The SRC attribute is NOT magical for these interpreters. In short,
1555 # all code inside a source file or {} block is written verbattim
1556 # to the interpreter. No (pre-)processing or executional magic is done.
1558 # A serious shortcomming of the described mechanism for handling other
1559 # (scripting) languages, with respect to standard perl scripts
1560 # (i.e., 'text/ssperl'), is that the code is only executed when
1561 # the pipe to the interpreter is closed. So the pipe has to be
1562 # closed at the end of each block. This means that the state of the
1563 # interpreter (e.g., all variable values) is lost after the closing of
1564 # the next </SCRIPT> tag. The standard 'text/ssperl' scripts retain
1565 # all values and definitions.
1567 # APPLICATION MIME TYPES
1569 # To ease some important auxilliary functions from within the
1570 # html pages I have added them as MIME types. This uses
1571 # the mechanism that is also used for the evaluation of
1572 # other scripting languages, with interpolation of CGI
1573 # parameters (and perl-variables). Actually, these are
1574 # defined exactly like any other "scripting language".
1576 # text/ssdisplay: display some (HTML) text with interpolated
1577 # variables (uses `cat`).
1578 # text/sslogfile: write (append) the interpolated block to the file
1579 # mentioned on the first, non-empty line
1580 # (the filename can be preceded by 'File: ',
1581 # note the space after the ':',
1582 # uses `awk .... >> <filename>`).
1583 # text/ssmailto: send email directly from within the script block.
1584 # The first line of the body must contain
1585 # To:Name@Valid.Email.Address
1586 # (note: NO space between 'To:' and the email adres)
1587 # For other options see the mailto man pages.
1588 # It works by directly sending the (interpolated)
1589 # content of the text block to a pipe into the
1590 # Linux program 'mailto'.
1592 # In these script blocks, all Perl variables will be
1593 # replaced by their values. All CGI variables are cleaned before
1594 # they are used. These CGI variables must be redefined with a
1595 # CGI attribute to restore their original values.
1596 # In general, this will be more secure than constructing
1597 # e.g., your own email command lines. For instance, Mailto will
1598 # not execute any odd (forged) email addres, but just stops
1599 # when the email address is invalid and awk will construct
1600 # any filename you give it (e.g. '<File;rm\\\040-f' would end up
1601 # as a "valid" UNIX filename). Note that it will also gladly
1602 # store this file anywhere (/../../../etc/passwd will work!).
1603 # Use the CGIscriptor::CGIsafeFileName() function to clean the
1604 # filename.
1606 # SHELL SCRIPT PIPING
1608 # If a shell script starts with the UNIX style "#! <shell command> \n"
1609 # line, the rest of the shell script is piped into the indicated command,
1610 # i.e.,
1611 # open(COMMAND, "| command");print COMMAND $RestOfScript;
1613 # In many ways this is equivalent to the MIME-type profiling for
1614 # evaluating other scripting languages as discussed above. The
1615 # difference breaks down to convenience. Shell script piping is a
1616 # "raw" implementation. It allows you to control all aspects of
1617 # execution. Using the MIME-type profiling is easier, but has a
1618 # lot of defaults built in that might get in the way. Another
1619 # difference is that shell script piping uses the SAFEqx() function,
1620 # and MIME-type profiling does not.
1622 # Execution of shell scripts is under the control of the Perl Script blocks
1623 # in the document. The MIME-type triggered execution of <SCRIPT></SCRIPT>
1624 # blocks can be simulated easily. You can switch to a different shell,
1625 # e.g. tcl, completely by executing the following Perl commands inside
1626 # your document:
1628 # <SCRIPT TYPE="text/ssperl">
1629 # $main::ShellScriptContentType = "text/ssTcl"; # Yes, you can do this
1630 # CGIscriptor::RedirectShellScript('/usr/bin/tcl'); # Pipe to Tcl
1631 # $CGIscriptor::NoShellScriptInterpolation = 1;
1632 # </SCRIPT>
1634 # After this script is executed, CGIscriptor will parse scripts of
1635 # TYPE="text/ssTcl" and pipe their contents into '|/usr/bin/tcl'
1636 # WITHOUT interpolation (i.e., NO substitution of Perl variables).
1637 # The crucial function is :
1638 # CGIscriptor::RedirectShellScript('/usr/bin/tcl')
1639 # After executing this function, all shell scripts AND all
1640 # calls to SAFEqx()) are piped into '|/usr/bin/tcl'. If the argument
1641 # of RedirectShellScript is empty, e.g., '', the original (default)
1642 # value is reset.
1644 # The standard output, STDOUT, of any pipe is send to the client.
1645 # Currently, you should be carefull with quotes in such a piped script.
1646 # The results of a pipe is NOT put on the @CGIscriptorResults stack.
1647 # As a result, you do not have access to the output of any piped (#!)
1648 # process! If you want such access, execute
1649 # <SCRIPT TYPE="text/osshell">echo "script"|command</SCRIPT>
1650 # or
1651 # <SCRIPT TYPE="text/ssperl">
1652 # $resultvar = SAFEqx('echo "script"|command');
1653 # </SCRIPT>.
1655 # Safety is never complete. Although SAFEqx() prevents some of the
1656 # most obvious forms of attacks and security slips, it cannot prevent
1657 # them all. Especially, complex combinations of quotes and intricate
1658 # variable references cannot be handled safely by SAFEqx. So be on
1659 # guard.
1662 # PERL CODE EVALUATION (CONTENT-TYPE=TEXT/SSPERL)
1664 # All PERL scripts are evaluated inside a PERL package. This package
1665 # has a separate name space. This isolated name space protects the
1666 # CGIscriptor.pl program against interference from user code. However,
1667 # some variables, e.g., $_, are global and cannot be protected. You are
1668 # advised NOT to use such global variable names. You CAN write
1669 # directives that directly access the variables in the main program.
1670 # You do so at your own risk (there is definitely enough rope available
1671 # to hang yourself). The behavior of CGIscriptor becomes undefined if
1672 # you change its private variables during run time. The PERL code
1673 # directives are used as in:
1674 # $Result = eval($directive); print $Result;'';
1675 # ($directive contains all text between <SCRIPT></SCRIPT>).
1676 # That is, the <directive> is treated as ''-quoted string and
1677 # the result is treated as a scalar. To prevent the VALUE of the code
1678 # block from appearing on the client's screen, end the directive with
1679 # ';""</SCRIPT>'. Evaluated directives return the last value, just as
1680 # eval(), blocks, and subroutines, but only as a scalar.
1682 # IMPORTANT: All PERL variables defined are persistent. Each <SCRIPT>
1683 # </SCRIPT> construct is evaluated as a {}-block with associated scope
1684 # (e.g., for "my $var;" declarations). This means that values assigned
1685 # to a PERL variable can be used throughout the document unless they
1686 # were declared with "my". The following will actually work as intended
1687 # (note that the ``-quotes in this example are NOT evaluated, but used
1688 # as simple quotes):
1690 # <META CONTENT="text/ssperl; CGI=`$String='abcdefg'`">
1691 # anything ...
1692 # <SCRIPT TYPE=text/ssperl>@List = split('', $String);</SCRIPT>
1693 # anything ...
1694 # <SCRIPT TYPE=text/ssperl>join(", ", @List[1..$#List]);</SCRIPT>
1696 # The first <SCRIPT TYPE=text/ssperl></SCRIPT> construct will return the
1697 # value scalar(@List), the second <SCRIPT TYPE=text/ssperl></SCRIPT>
1698 # construct will print the elements of $String separated by commas, leaving
1699 # out the first element, i.e., $List[0].
1701 # Another warning: './' and '~/' are ALWAYS replaced by the values of
1702 # $YOUR_SCRIPTS and $YOUR_HTML_FILES, respectively . This can interfere
1703 # with pattern matching, e.g., $a =~ s/aap\./noot\./g will result in the
1704 # evaluations of $a =~ s/aap\\${YOUR_SCRIPTS}noot\\${YOUR_SCRIPTS}g. Use
1705 # s@<regexp>.@<replacement>.@g instead.
1708 # SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)
1710 # An infrastructure for user acount authorization and file access control
1711 # is available. Each request is matched against a list of URL path patterns.
1712 # If the request matches, a Session Ticket is required to access the URL.
1713 # This Session Ticket should be present as a CGI parameter:
1714 # SESSIONTICKET=<value>
1715 # The example implementation stores Session Tickets as files in a local
1716 # directory. To create Session Tickets, a Login request must be given
1717 # with a LOGIN=<value> CGI parameter, a user name and a (doubly hashed)
1718 # password. The user name and (singly hashed) password are stored in a
1719 # PASSWORD ticket with the same name as the user account (name cleaned up
1720 # for security).
1722 # A Login page should create a LOGIN ticket file localy and send a
1723 # server specific SALT, a Random salt, and both the LOGIN and SESSION ticket
1724 # identifiers. The server side compares the username and hashed password,
1725 # actually hashed(Random salt+hashed(SALT+password)) from the client with
1726 # the values it calculates from the stored Random salt from the LOGIN
1727 # ticket and the hashed(SALT+password) from the PASSWORD ticket. If
1728 # successful, a new SESSION ticket is generated. The SESSION ticket
1729 # identifier is available as $SESSIONTICKET, the Username, IP address
1730 # and Path as $LOGINUSERNAME, $LOGINIPADDRESS, and $LOGINPATH, respectively.
1732 # In the current example implementation, all random values are created as
1733 # a full SHA1 hash (Hex) of a 512 byte block read from /dev/urandom.
1735 # The example session model implements 3 functions:
1736 # 1 Login
1737 # The password is hashed with the server side salt, and then hashed with
1738 # a Random salt. The server side only stores the password hashed with the
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 # 2 Session
1742 # For every access to a restricted URL, the Session Ticket is checked
1743 # before access is granted.
1744 # 3 Password Change
1745 # A new password is hashed with the server side salt, and then XORed
1746 # with the old password hashed with the salt. That value is exchanged
1747 # and XORed with the stored old hashed(salt+password). Again, the
1748 # stored password value is never exchanged unencrypted.
1750 # USER EXTENSIONS
1752 # A CGIscriptor package is attached to the bottom of this file. With
1753 # this package you can personalize your version of CGIscriptor by
1754 # including often used perl routines. These subroutines can be
1755 # accessed by prefixing their names with CGIscriptor::, e.g.,
1756 # <SCRIPT LANGUAGE=PERL TYPE=text/ssperl>
1757 # CGIscriptor::ListDocs("/Books/*") # List all documents in /Books
1758 # </SCRIPT>
1759 # It already contains some useful subroutines for Document Management.
1760 # As it is a separate package, it has its own namespace, isolated from
1761 # both the evaluator and the main program. To access variables from
1762 # the document <SCRIPT></SCRIPT> blocks, use $CGIexecute::<var>.
1764 # Currently, the following functions are implemented
1765 # (precede them with CGIscriptor::, see below for more information)
1766 # - SAFEqx ('String') -> result of qx/"String"/ # Safe application of ``-quotes
1767 # Is used by text/osshell Shell scripts. Protects all CGI
1768 # (client-supplied) values with single quotes before executing the
1769 # commands (one of the few functions that also works WITHOUT CGIscriptor::
1770 # in front)
1771 # - defineCGIvariable ($name[, $default) -> 0/1 (i.e., failure/success)
1772 # Is used by the META tag to define and initialize CGI and ENV
1773 # name/value pairs. Tries to obtain an initializing value from (in order):
1774 # $ENV{$name}
1775 # The Query string
1776 # The default value given (if any)
1777 # (one of the few functions that also works WITHOUT CGIscriptor::
1778 # in front)
1779 # - CGIsafeFileName (FileName) -> FileName or ""
1780 # Check a string against the Allowed File Characters (and ../ /..).
1781 # Returns an empty string for unsafe filenames.
1782 # - CGIsafeEmailAddress (Email) -> Email or ""
1783 # Check a string against correct email address pattern.
1784 # Returns an empty string for unsafe addresses.
1785 # - RedirectShellScript ('CommandString') -> FILEHANDLER or undef
1786 # Open a named PIPE for SAFEqx to receive ALL shell scripts
1787 # - URLdecode (URL encoded string) -> plain string # Decode URL encoded argument
1788 # - URLencode (plain string) -> URL encoded string # Encode argument as URL code
1789 # - CGIparseValue (ValueName [, URL_encoded_QueryString]) -> Decoded value
1790 # Extract the value of a CGI variable from the global or a private
1791 # URL-encoded query (multipart POST raw, NOT decoded)
1792 # - CGIparseValueList (ValueName [, URL_encoded_QueryString])
1793 # -> List of decoded values
1794 # As CGIparseValue, but now assembles ALL values of ValueName into a list.
1795 # - CGIparseHeader (ValueName [, URL_encoded_QueryString]) -> Header
1796 # Extract the header of a multipart CGI variable from the global or a private
1797 # URL-encoded query ("" when not a multipart variable or absent)
1798 # - CGIparseForm ([URL_encoded_QueryString]) -> Decoded Form
1799 # Decode the complete global URL-encoded query or a private
1800 # URL-encoded query
1801 # - read_url(URL) # Returns the page from URL (with added base tag, both FTP and HTTP)
1802 # Uses main::GET_URL(URL, 1) to get at the command to read the URL.
1803 # - BrowseDirs(RootDirectory [, Pattern, Startdir, CGIname]) # print browsable directories
1804 # - ListDocs(Pattern [,ListType]) # Prints a nested HTML directory listing of
1805 # all documents, e.g., ListDocs("/*", "dl");.
1806 # - HTMLdocTree(Pattern [,ListType]) # Prints a nested HTML listing of all
1807 # local links starting from a given document, e.g.,
1808 # HTMLdocTree("/Welcome.html", "dl");
1811 # THE RESULTS STACK: @CGISCRIPTORRESULTS
1813 # If the pseudo-variable "$CGIscriptorResults" has been defined in a
1814 # META tag, all subsequent SCRIPT and META results are pushed
1815 # on the @CGIscriptorResults stack. This list is just another
1816 # Perl variable and can be used and manipulated like any other list.
1817 # $CGIscriptorResults[-1] is always the last result.
1818 # This is only of limited use, e.g., to use the results of an OS shell
1819 # script inside a Perl script. Will NOT contain the results of Pipes
1820 # or code from MIME-profiling.
1823 # USEFULL CGI PREDEFINED VARIABLES (DO NOT ASSIGN TO THESE)
1825 # $CGI_HOME - The DocumentRoot directory
1826 # $CGI_Decoded_QS - The complete decoded Query String
1827 # $CGI_Content_Length - The ACTUAL length of the Query String
1828 # $CGI_Date - Current date and time
1829 # $CGI_Year $CGI_Month $CGI_Day $CGI_WeekDay - Current Date
1830 # $CGI_Time - Current Time
1831 # $CGI_Hour $CGI_Minutes $CGI_Seconds - Current Time, split
1832 # GMT Date/Time:
1833 # $CGI_GMTYear $CGI_GMTMonth $CGI_GMTDay $CGI_GMTWeekDay $CGI_GMTYearDay
1834 # $CGI_GMTHour $CGI_GMTMinutes $CGI_GMTSeconds $CGI_GMTisdst
1837 # USEFULL CGI ENVIRONMENT VARIABLES
1839 # Variables accessible (in APACHE) as $ENV{<name>}
1840 # (see: "http://hoohoo.ncsa.uiuc.edu/cgi/env.html"):
1842 # QUERY_STRING - The query part of URL, that is, everything that follows the
1843 # question mark.
1844 # PATH_INFO - Extra path information given after the script name
1845 # PATH_TRANSLATED - Extra pathinfo translated through the rule system.
1846 # (This doesn't always make sense.)
1847 # REMOTE_USER - If the server supports user authentication, and the script is
1848 # protected, this is the username they have authenticated as.
1849 # REMOTE_HOST - The hostname making the request. If the server does not have
1850 # this information, it should set REMOTE_ADDR and leave this unset
1851 # REMOTE_ADDR - The IP address of the remote host making the request.
1852 # REMOTE_IDENT - If the HTTP server supports RFC 931 identification, then this
1853 # variable will be set to the remote user name retrieved from
1854 # the server. Usage of this variable should be limited to logging
1855 # only.
1856 # AUTH_TYPE - If the server supports user authentication, and the script
1857 # is protected, this is the protocol-specific authentication
1858 # method used to validate the user.
1859 # CONTENT_TYPE - For queries which have attached information, such as HTTP
1860 # POST and PUT, this is the content type of the data.
1861 # CONTENT_LENGTH - The length of the said content as given by the client.
1862 # SERVER_SOFTWARE - The name and version of the information server software
1863 # answering the request (and running the gateway).
1864 # Format: name/version
1865 # SERVER_NAME - The server's hostname, DNS alias, or IP address as it
1866 # would appear in self-referencing URLs
1867 # GATEWAY_INTERFACE - The revision of the CGI specification to which this
1868 # server complies. Format: CGI/revision
1869 # SERVER_PROTOCOL - The name and revision of the information protocol this
1870 # request came in with. Format: protocol/revision
1871 # SERVER_PORT - The port number to which the request was sent.
1872 # REQUEST_METHOD - The method with which the request was made. For HTTP,
1873 # this is "GET", "HEAD", "POST", etc.
1874 # SCRIPT_NAME - A virtual path to the script being executed, used for
1875 # self-referencing URLs.
1876 # HTTP_ACCEPT - The MIME types which the client will accept, as given by
1877 # HTTP headers. Other protocols may need to get this
1878 # information from elsewhere. Each item in this list should
1879 # be separated by commas as per the HTTP spec.
1880 # Format: type/subtype, type/subtype
1881 # HTTP_USER_AGENT - The browser the client is using to send the request.
1882 # General format: software/version library/version.
1885 # INSTRUCTIONS FOR RUNNING CGIscriptor ON UNIX
1887 # CGIscriptor.pl will run on any WWW server that runs Perl scripts, just add
1888 # a line like the following to your srm.conf file (Apache example):
1890 # ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
1892 # URL's that refer to http://www.your.address/SHTML/... will now be handled
1893 # by CGIscriptor.pl, which can use a private directory tree (default is the
1894 # DOCUMENT_ROOT directory tree, but it can be anywhere, see manual).
1896 # If your hosting ISP won't let you add ScriptAlias lines you can use
1897 # the following "rewrite"-based "scriptalias" in .htaccess
1898 # (from Gerd Franke)
1900 # RewriteEngine On
1901 # RewriteBase /
1902 # RewriteCond %{REQUEST_FILENAME} .html$
1903 # RewriteCond %{SCRIPT_FILENAME} !cgiscriptor.pl$
1904 # RewriteCond %{REQUEST_FILENAME} -f
1905 # RewriteRule ^(.*)$ /cgi-bin/cgiscriptor.pl/$1?&%{QUERY_STRING}
1907 # Everthing with the extension ".html" and not including "cgiscriptor.pl"
1908 # in the url and where the file "path/filename.html" exists is redirected
1909 # to "/cgi.bin/cgiscriptor.pl/path/filename.html?query".
1910 # The user configuration should get the same path-level as the
1911 # .htaccess-file:
1913 # # Just enter your own directory path here
1914 # $YOUR_HTML_FILES = "$ENV{'DOCUMENT_ROOT'}";
1915 # # use DOCUMENT_ROOT only, if .htaccess lies in the root-directory.
1917 # If this .htaccess goes in a specific directory, the path to this
1918 # directory must be added to $ENV{'DOCUMENT_ROOT'}.
1920 # The CGIscriptor file contains all documentation as comments. These
1921 # comments can be removed to speed up loading (e.g., `egrep -v '^#'
1922 # CGIscriptor.pl` > leanScriptor.pl). A bare bones version of
1923 # CGIscriptor.pl, lacking documentation, most comments, access control,
1924 # example functions etc. (but still with the copyright notice and some
1925 # minimal documentation) can be obtained by calling CGIscriptor.pl on the
1926 # command line with the '-slim' command line argument, e.g.,
1928 # >CGIscriptor.pl -slim > slimCGIscriptor.pl
1930 # CGIscriptor.pl can be run from the command line with <path> and <query> as
1931 # arguments, as `CGIscriptor.pl <path> <query>`, inside a perl script
1932 # with 'do CGIscriptor.pl' after setting $ENV{PATH_INFO}
1933 # and $ENV{QUERY_STRING}, or CGIscriptor.pl can be loaded with 'require
1934 # "/real-path/CGIscriptor.pl"'. In the latter case, requests are processed
1935 # by 'Handle_Request();' (again after setting $ENV{PATH_INFO} and
1936 # $ENV{QUERY_STRING}).
1938 # Using the command line execution option, CGIscriptor.pl can be used as a
1939 # document (meta-)preprocessor. If the first argument is '-', STDIN will be read.
1940 # For example:
1942 # > cat MyDynamicDocument.html | CGIscriptor.pl - '[QueryString]' > MyStaticFile.html
1944 # This command line will produce a STATIC file with the DYNAMIC content of
1945 # MyDocument.html "interpolated".
1947 # This option would be very dangerous when available over the internet.
1948 # If someone could sneak a 'http://www.your.domain/-' URL past your
1949 # server, CGIscriptor could EXECUTE any POSTED contend.
1950 # Therefore, for security reasons, STDIN will NOT be read
1951 # if ANY of the HTTP server environment variables is set (e.g.,
1952 # SERVER_PORT, SERVER_PROTOCOL, SERVER_NAME, SERVER_SOFTWARE,
1953 # HTTP_USER_AGENT, REMOTE_ADDR).
1954 # This block on processing STDIN on HTTP requests can be lifted by setting
1955 # $BLOCK_STDIN_HTTP_REQUEST = 0;
1956 # In the security configuration. Butbe carefull when doing this.
1957 # It can be very dangerous.
1959 # Running demo's and more information can be found at
1960 # http://www.fon.hum.uva.nl/~rob/OSS/OSS.html
1962 # A pocket-size HTTP daemon, CGIservlet.pl, is available from my web site or
1963 # CPAN that can use CGIscriptor.pl as the base of a µWWW server and
1964 # demonstrates its use.
1967 # PROCESSING NON-FILESYSTEM DATA
1969 # Normally, HTTP (WWW) requests map onto file that can be accessed
1970 # using the perl open() function. That is, the web server runs on top of
1971 # some directory structure. However, we can envission (and put to good
1972 # use) other systems that do not use a normal file system. The whole CGI
1973 # was developed to make dynamic document generation possible.
1975 # A special case is where we want to have it both: A normal web server
1976 # with normal "file data", but not a normal files system. For instance,
1977 # we want or normal Web Site to run directly from a RAM hash table or
1978 # other database, instead of from disk. But we do NOT want to code the
1979 # whole site structure in CGI.
1981 # CGIscriptor can do this. If the web server fills an environment variable
1982 # $ENV{'CGI_FILE_CONTENT'} with the content of the "file", then the content
1983 # of this variable is processed instead of opening a file. If this environment
1984 # variable has the value '-', the content of another environment variable,
1985 # $ENV{'CGI_DATA_ACCESS_CODE'} is executed as:
1986 # eval("\@_ = ($file_path); do {$ENV{'CGI_DATA_ACCESS_CODE'}};")
1987 # and the result is processed as if it was the content of the requested
1988 # file.
1989 # (actually, the names of the environment variables are user configurable,
1990 # they are stored in the local variables $CGI_FILE_CONTENT and
1991 # $CGI_DATA_ACCESS_CODE)
1993 # When using this mechanism, the SRC attribute mechanism will only partially work.
1994 # Only the "recursive" calls to CGIscriptor (the ProcessFile() function)
1995 # will work, the automagical execution of SRC files won't. (In this case,
1996 # the SRC attribute won't work either for other scripting languages)
1999 # NON-UNIX PLATFORMS
2001 # CGIscriptor.pl was mainly developed and tested on UNIX. However, as I
2002 # coded part of the time on an Apple Macintosh under MacPerl, I made sure
2003 # CGIscriptor did run under MacPerl (with command line options). But only
2004 # as an independend script, not as part of a HTTP server. I have used it
2005 # under Apache in Windows XP.
2007 ENDOFHELPTEXT
2008 exit;
2010 ###############################################################################
2012 # SECURITY CONFIGURATION
2014 # Special configurations related to SECURITY
2015 # (i.e., optional, see also environment variables below)
2017 # LOGGING
2018 # Log Clients and the requested paths (Redundant when loging Queries)
2020 $ClientLog = "./Client.log"; # (uncomment for use)
2022 # Format: Localtime | REMOTE_USER REMOTE_IDENT REMOTE_HOST REMOTE_ADDRESS \
2023 # PATH_INFO CONTENT_LENGTH (actually, the real query+post length)
2025 # Log Clients and the queries, the CGIQUERYDECODE is required if you want
2026 # to log queries. If you log Queries, the loging of Clients is redundant
2027 # (note that queries can be quite long, so this might not be a good idea)
2029 #$QueryLog = "./Query.log"; # (uncomment for use)
2031 # ACCESS CONTROL
2032 # the Access files should contain Hostnames or IP addresses,
2033 # i.e. REMOTE_HOST or REMOTE_ADDR, each on a separate line
2034 # optionally followed by one ore more file patterns, e.g., "edu /DEMO".
2035 # Matching is done "domain first". For example ".edu" matches all
2036 # clients whose "name" ends in ".edu" or ".EDU". The file pattern
2037 # "/DEMO" matches all paths that contain the strings "/DEMO" or "/demo"
2038 # (both matchings are done case-insensitive).
2039 # The name special symbol "-" matches ALL clients who do not supply a
2040 # REMOTE_HOST name, "*" matches all clients.
2041 # Lines starting with '-e' are evaluated. A non-zero return value indicates
2042 # a match. You can use $REMOTE_HOST, $REMOTE_ADDR, and $PATH_INFO. These
2043 # lines are evaluated in the program's own name-space. So DO NOT assign to
2044 # variables.
2046 # Accept the following users (remove comment # and adapt filename)
2047 $CGI_Accept = -s "$YOUR_SCRIPTS/ACCEPT.lis" ? "$YOUR_SCRIPTS/ACCEPT.lis" : ''; # (uncomment for use)
2049 # Reject requests from the following users (remove comment # and
2050 # adapt filename, this is only of limited use)
2051 $CGI_Reject = -s "$YOUR_SCRIPTS/REJECT.lis" ? "$YOUR_SCRIPTS/REJECT.lis" : ''; # (uncomment for use)
2053 # Empty lines or comment lines starting with '#' are ignored in both
2054 # $CGI_Accept and $CGI_Reject.
2056 # Block STDIN (i.e., '-') requests when servicing an HTTP request
2057 # Comment this out if you realy want to use STDIN in an on-line web server
2058 $BLOCK_STDIN_HTTP_REQUEST = 1;
2061 # End of security configuration
2063 ##################################################<<<<<<<<<<End Remove
2065 # PARSING CGI VALUES FROM THE QUERY STRING (USER CONFIGURABLE)
2067 # The CGI parse commands. These commands extract the values of the
2068 # CGI variables from the URL encoded Query String.
2069 # If you want to use your own CGI decoders, you can call them here
2070 # instead, using your own PATH and commenting/uncommenting the
2071 # appropriate lines
2073 # CGI parse command for individual values
2074 # (if $List > 0, returns a list value, if $List < 0, a hash table, this is optional)
2075 sub YOUR_CGIPARSE # ($Name [, $List]) -> Decoded value
2077 my $Name = shift;
2078 my $List = shift || 0;
2079 # Use one of the following by uncommenting
2080 if(!$List) # Simple value
2082 return CGIscriptor::CGIparseValue($Name) ;
2084 elsif($List < 0) # Hash tables
2086 return CGIscriptor::CGIparseValueHash($Name); # Defined in CGIscriptor below
2088 else # Lists
2090 return CGIscriptor::CGIparseValueList($Name); # Defined in CGIscriptor below
2093 # return `/PATH/cgiparse -value $Name`; # Shell commands
2094 # require "/PATH/cgiparse.pl"; return cgivalue($Name); # Library
2096 # Complete queries
2097 sub YOUR_CGIQUERYDECODE
2099 # Use one of the following by uncommenting
2100 return CGIscriptor::CGIparseForm(); # Defined in CGIscriptor below
2101 # return `/PATH/cgiparse -form`; # Shell commands
2102 # require "/PATH/cgiparse.pl"; return cgiform(); # Library
2105 # End of configuration
2107 #######################################################################
2109 # Translating input files.
2110 # Allows general and global conversions of files using Regular Expressions
2111 # Translations are applied in the order of definition.
2113 # Define:
2114 # my $TranslationPaths = 'pattern'; # Pattern matching PATH_INFO
2116 # push(@TranslationTable, ['pattern', 'replacement']);
2117 # e.g. (for Ruby Rails):
2118 # push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2119 # push(@TranslationTable, ['%>', '</SCRIPT>']);
2121 # Runs:
2122 # my $currentRegExp;
2123 # foreach $currentRegExp (keys(%TranslationTable))
2125 # my $currentRegExp;
2126 # foreach $currentRegExp (@TranslationTable)
2128 # my ($pattern, $replacement) = @$currentRegExp;
2129 # $$text =~ s!$pattern!$replacement!msg;
2130 # };
2131 # };
2133 # Configuration section
2135 #######################################################################
2137 # The file paths on which to apply the translation
2138 my $TranslationPaths = ''; # NO files
2139 #$TranslationPaths = '.'; # ANY file
2140 # $TranslationPaths = '\.html'; # HTML files
2142 my @TranslationTable = ();
2143 # Some legacy code
2144 push(@TranslationTable, ['\<\s*CGI\s+([^\>])*\>', '\<SCRIPT TYPE=\"text/ssperl\"\>$1\<\/SCRIPT>']);
2145 # Ruby Rails?
2146 push(@TranslationTable, ['<%=', '<SCRIPT TYPE="text/ssruby">']);
2147 push(@TranslationTable, ['%>', '</SCRIPT>']);
2149 sub performTranslation # (\$text)
2151 my $text = shift || return;
2152 if(@TranslationTable && $TranslationPaths && $ENV{'PATH_INFO'} =~ m!$TranslationPaths!)
2154 my $currentRegExp;
2155 foreach $currentRegExp (@TranslationTable)
2157 my ($pattern, $replacement) = @$currentRegExp;
2158 $$text =~ s!$pattern!$replacement!msg;
2163 #######################################################################
2165 # Seamless access to other (Scripting) Languages
2166 # TYPE='text/ss<interpreter>'
2168 # Configuration section
2170 #######################################################################
2172 # OTHER SCRIPTING LANGUAGES AT THE SERVER SIDE (MIME => OScommand)
2173 # Yes, it realy is this simple! (unbelievable, isn't it)
2174 # NOTE: Some interpreters require some filtering to obtain "clean" output
2176 %ScriptingLanguages = (
2177 "text/testperl" => 'perl', # Perl for testing
2178 "text/sspython" => 'python', # Python
2179 "text/ssruby" => 'ruby', # Ruby
2180 "text/sstcl" => 'tcl', # TCL
2181 "text/ssawk" => 'awk -f-', # Awk
2182 "text/sslisp" => # lisp (rep, GNU)
2183 'rep | tail +4 '."| egrep -v '> |^rep. |^nil\\\$'",
2184 "text/xlispstat" => # xlispstat
2185 'xlispstat | tail +7 ' ."| egrep -v '> \\\$|^NIL'",
2186 "text/ssprolog" => # Prolog (GNU)
2187 "gprolog | tail +4 | sed 's/^| ?- //'",
2188 "text/ssm4" => 'm4', # M4 macro's
2189 "text/sh" => 'sh', # Born shell
2190 "text/bash" => 'bash', # Born again shell
2191 "text/csh" => 'csh', # C shell
2192 "text/ksh" => 'ksh', # Korn shell
2193 "text/sspraat" => # Praat (sound/speech analysis)
2194 "praat - | sed 's/Praat > //g'",
2195 "text/ssr" => # R
2196 "R --vanilla --slave | sed 's/^[\[0-9\]*] //'",
2197 "text/ssrebol" => # REBOL
2198 "rebol --quiet|egrep -v '^[> ]* == '|sed 's/^\\s*\[> \]* //'",
2199 "text/postgresql" => 'psql 2>/dev/null',
2201 # Not real scripting, but the use of other applications
2202 "text/ssmailto" => "awk 'NF||F{F=1;print \\\$0;}'|mailto >/dev/null", # Send mail from server
2203 "text/ssdisplay" => 'cat', # Display, (interpolation)
2204 "text/sslogfile" => # Log to file, (interpolation)
2205 "awk 'NF||L {if(!L){L=tolower(\\\$1)~/^file:\\\$/ ? \\\$2 : \\\$1;}else{print \\\$0 >> L;};}'",
2207 "" => ""
2210 # To be able to access the CGI variables in your script, they
2211 # should be passed to the scripting language in a readable form
2212 # Here you can enter how they should be printed (the first %s
2213 # is replaced by the NAME of the CGI variable as it apears in the
2214 # META tag, the second by its VALUE).
2215 # For Perl this would be:
2216 # "text/testperl" => '$%s = "%s";',
2217 # which would be executed as
2218 # printf('$%s = "%s";', $CGI_NAME, $CGI_VALUE);
2220 # If the hash table value doesn't exist, nothing is done
2221 # (you have to parse the Environment variables yourself).
2222 # If it DOES exist but is empty (e.g., "text/sspraat" => '',)
2223 # Perl string interpolation of variables (i.e., $var, @array,
2224 # %hash) is performed. This means that $@%\ must be protected
2225 # with a \.
2227 %ScriptingCGIvariables = (
2228 "text/testperl" => "\$\%s = '\%s';", # Perl $VAR = 'value'; (for testing)
2229 "text/sspython" => "\%s = '\%s'", # Python VAR = 'value'
2230 "text/ssruby" => '@%s = "%s"', # Ruby @VAR = 'value'
2231 "text/sstcl" => 'set %s "%s"', # TCL set VAR "value"
2232 "text/ssawk" => '%s = "%s";', # Awk VAR = 'value';
2233 "text/sslisp" => '(setq %s "%s")', # Gnu lisp (rep) (setq VAR "value")
2234 "text/xlispstat" => '(setq %s "%s")', # xlispstat (setq VAR "value")
2235 "text/ssprolog" => '', # Gnu prolog (interpolated)
2236 "text/ssm4" => "define(`\%s', `\%s')", # M4 macro's define(`VAR', `value')
2237 "text/sh" => "\%s='\%s'", # Born shell VAR='value'
2238 "text/bash" => "\%s='\%s'", # Born again shell VAR='value'
2239 "text/csh" => "\$\%s='\%s';", # C shell $VAR = 'value';
2240 "text/ksh" => "\$\%s='\%s';", # Korn shell $VAR = 'value';
2242 "text/ssrebol" => '%s: copy "%s"', # REBOL VAR: copy "value"
2243 "text/sspraat" => '', # Praat (interpolation)
2244 "text/ssr" => '%s <- "%s";', # R VAR <- "value";
2245 "text/postgresql" => '', # PostgreSQL (interpolation)
2247 # Not real scripting, but the use of other applications
2248 "text/ssmailto" => '', # MAILTO, (interpolation)
2249 "text/ssdisplay" => '', # Display, (interpolation)
2250 "text/sslogfile" => '', # Log to file, (interpolation)
2252 "" => ""
2255 # If you want something added in front or at the back of each script
2256 # block as send to the interpreter add it here.
2257 # mime => "string", e.g., "text/sspython" => "python commands"
2258 %ScriptingPrefix = (
2259 "text/testperl" => "\# Prefix Code;", # Perl script testing
2260 "text/ssm4" => 'divert(0)', # M4 macro's (open STDOUT)
2262 "" => ""
2264 # If you want something added at the end of each script block
2265 %ScriptingPostfix = (
2266 "text/testperl" => "\# Postfix Code;", # Perl script testing
2267 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2269 "" => ""
2271 # If you need initialization code, directly after opening
2272 %ScriptingInitialization = (
2273 "text/testperl" => "\# Initialization Code;", # Perl script testing
2274 "text/ssawk" => 'BEGIN {', # Server Side awk scripts (VAR = "value")
2275 "text/sslisp" => '(prog1 nil ', # Lisp (rep)
2276 "text/xlispstat" => '(prog1 nil ', # xlispstat
2277 "text/ssm4" => 'divert(-1)', # M4 macro's (block STDOUT)
2279 "" => ""
2281 # If you need cleanup code before closing
2282 %ScriptingCleanup = (
2283 "text/testperl" => "\# Cleanup Code;", # Perl script testing
2284 "text/sspraat" => 'Quit',
2285 "text/ssawk" => '};', # Server Side awk scripts (VAR = "value")
2286 "text/sslisp" => '(princ "\n" standard-output)).', # Closing print to rep
2287 "text/xlispstat" => '(print ""))', # Closing print to xlispstat
2288 "text/postgresql" => '\q', # quit psql
2289 "text/ssdisplay" => "", # close cat
2291 "" => ""
2294 # End of configuration for foreign scripting languages
2296 ###############################################################################
2298 # Initialization Code
2301 sub Initialize_Request
2303 ###############################################################################
2305 # ENVIRONMENT VARIABLES
2307 # Use environment variables to configure CGIscriptor on a temporary basis.
2308 # If you define any of the configurable variables as environment variables,
2309 # these are used instead of the "hard coded" values above.
2311 $SS_PUB = $ENV{'SS_PUB'} || $YOUR_HTML_FILES;
2312 $SS_SCRIPT = $ENV{'SS_SCRIPT'} || $YOUR_SCRIPTS;
2315 # Substitution strings, these are used internally to handle the
2316 # directory separator strings, e.g., '~/' -> 'SS_PUB:' (Mac)
2317 $HOME_SUB = $SS_PUB;
2318 $SCRIPT_SUB = $SS_SCRIPT;
2321 # Make sure all script are reliably loaded
2322 push(@INC, $SS_SCRIPT);
2325 # Add the directory separator to the "home" directories.
2326 # (This is required for ~/ and ./ substitution)
2327 $HOME_SUB .= '/' if $HOME_SUB;
2328 $SCRIPT_SUB .= '/' if $SCRIPT_SUB;
2330 $CGI_HOME = $ENV{'DOCUMENT_ROOT'};
2331 $ENV{'PATH_TRANSLATED'} =~ /$ENV{'PATH_INFO'}/is;
2332 $CGI_HOME = $` unless $ENV{'DOCUMENT_ROOT'}; # Get the DOCUMENT_ROOT directory
2333 $default_values{'CGI_HOME'} = $CGI_HOME;
2334 $ENV{'HOME'} = $CGI_HOME;
2335 # Set SS_PUB and SS_SCRIPT as Environment variables (make them available
2336 # to the scripts)
2337 $ENV{'SS_PUB'} = $SS_PUB unless $ENV{'SS_PUB'};
2338 $ENV{'SS_SCRIPT'} = $SS_SCRIPT unless $ENV{'SS_SCRIPT'};
2340 $FilePattern = $ENV{'FilePattern'} || $FilePattern;
2341 $MaximumQuerySize = $ENV{'MaximumQuerySize'} || $MaximumQuerySize;
2342 $ClientLog = $ENV{'ClientLog'} || $ClientLog;
2343 $QueryLog = $ENV{'QueryLog'} || $QueryLog;
2344 $CGI_Accept = $ENV{'CGI_Accept'} || $CGI_Accept;
2345 $CGI_Reject = $ENV{'CGI_Reject'} || $CGI_Reject;
2347 # Parse file names
2348 $CGI_Accept =~ s@^\~/@$HOME_SUB@g if $CGI_Accept;
2349 $CGI_Reject =~ s@^\~/@$HOME_SUB@g if $CGI_Reject;
2350 $ClientLog =~ s@^\~/@$HOME_SUB@g if $ClientLog;
2351 $QueryLog =~ s@^\~/@$HOME_SUB@g if $QueryLog;
2353 $CGI_Accept =~ s@^\./@$SCRIPT_SUB@g if $CGI_Accept;
2354 $CGI_Reject =~ s@^\./@$SCRIPT_SUB@g if $CGI_Reject;
2355 $ClientLog =~ s@^\./@$SCRIPT_SUB@g if $ClientLog;
2356 $QueryLog =~ s@^\./@$SCRIPT_SUB@g if $QueryLog;
2358 @CGIscriptorResults = (); # A stack of results
2360 # end of Environment variables
2362 #############################################################################
2364 # Define and Store "standard" values
2366 # BEFORE doing ANYTHING check the size of Query String
2367 length($ENV{'QUERY_STRING'}) <= $MaximumQuerySize || dieHandler(2, "QUERY TOO LONG\n");
2369 # The Translated Query String and the Actual length of the (decoded)
2370 # Query String
2371 if($ENV{'QUERY_STRING'})
2373 # If this can contain '`"-quotes, be carefull to use it QUOTED
2374 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2375 $default_values{CGI_Content_Length} = length($default_values{CGI_Decoded_QS});
2378 # Get the current Date and time and store them as default variables
2380 # Get Local Time
2381 $LocalTime = localtime;
2383 # CGI_Year CGI_Month CGI_Day CGI_WeekDay CGI_Time
2384 # CGI_Hour CGI_Minutes CGI_Seconds
2386 $default_values{CGI_Date} = $LocalTime;
2387 ($default_values{CGI_WeekDay},
2388 $default_values{CGI_Month},
2389 $default_values{CGI_Day},
2390 $default_values{CGI_Time},
2391 $default_values{CGI_Year}) = split(' ', $LocalTime);
2392 ($default_values{CGI_Hour},
2393 $default_values{CGI_Minutes},
2394 $default_values{CGI_Seconds}) = split(':', $default_values{CGI_Time});
2396 # GMT:
2397 # CGI_GMTYear CGI_GMTMonth CGI_GMTDay CGI_GMTWeekDay CGI_GMTYearDay
2398 # CGI_GMTHour CGI_GMTMinutes CGI_GMTSeconds CGI_GMTisdst
2400 ($default_values{CGI_GMTSeconds},
2401 $default_values{CGI_GMTMinutes},
2402 $default_values{CGI_GMTHour},
2403 $default_values{CGI_GMTDay},
2404 $default_values{CGI_GMTMonth},
2405 $default_values{CGI_GMTYear},
2406 $default_values{CGI_GMTWeekDay},
2407 $default_values{CGI_GMTYearDay},
2408 $default_values{CGI_GMTisdst}) = gmtime;
2412 # End of Initialize Request
2414 ###################################################################
2416 # SECURITY: ACCESS CONTROL
2418 # Check the credentials of each client (use pattern matching, domain first).
2419 # This subroutine will kill-off (die) the current process whenever access
2420 # is denied.
2422 sub Access_Control
2424 # >>>>>>>>>>Start Remove
2426 # ACCEPTED CLIENTS
2428 # Only accept clients which are authorized, reject all unnamed clients
2429 # if REMOTE_HOST is given.
2430 # If file patterns are given, check whether the user is authorized for
2431 # THIS file.
2432 if($CGI_Accept)
2434 # Use local variables, REMOTE_HOST becomes '-' if undefined
2435 my $REMOTE_HOST = $ENV{REMOTE_HOST} || '-';
2436 my $REMOTE_ADDR = $ENV{REMOTE_ADDR};
2437 my $PATH_INFO = $ENV{'PATH_INFO'};
2439 open(CGI_Accept, "<$CGI_Accept") || dieHandler(3, "$CGI_Accept: $!\n");
2440 $NoAccess = 1;
2441 while(<CGI_Accept>)
2443 next unless /\S/; # Skip empty lines
2444 next if /^\s*\#/; # Skip comments
2446 # Full expressions
2447 if(/^\s*-e\s/is)
2449 my $Accept = $'; # Get the expression
2450 $NoAccess &&= eval($Accept); # evaluate the expresion
2452 else
2454 my ($Accept, @FilePatternList) = split;
2455 if($Accept eq '*' # Always match
2456 ||$REMOTE_HOST =~ /\Q$Accept\E$/is # REMOTE_HOST matches
2457 || (
2458 $Accept =~ /^[0-9\.]+$/
2459 && $REMOTE_ADDR =~ /^\Q$Accept\E/ # IP address matches
2463 if($FilePatternList[0])
2465 foreach $Pattern (@FilePatternList)
2467 # Check whether this patterns is accepted
2468 $NoAccess &&= ($PATH_INFO !~ m@\Q$Pattern\E@is);
2471 else
2473 $NoAccess = 0; # No file patterns -> Accepted
2477 # Blocked
2478 last unless $NoAccess;
2480 close(CGI_Accept);
2481 if($NoAccess){ dieHandler(4, "No Access: $PATH_INFO\n");};
2485 # REJECTED CLIENTS
2487 # Reject named clients, accept all unnamed clients
2488 if($CGI_Reject)
2490 # Use local variables, REMOTE_HOST becomes '-' if undefined
2491 my $REMOTE_HOST = $ENV{'REMOTE_HOST'} || '-';
2492 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2493 my $PATH_INFO = $ENV{'PATH_INFO'};
2495 open(CGI_Reject, "<$CGI_Reject") || dieHandler(5, "$CGI_Reject: $!\n");
2496 $NoAccess = 0;
2497 while(<CGI_Reject>)
2499 next unless /\S/; # Skip empty lines
2500 next if /^\s*\#/; # Skip comments
2502 # Full expressions
2503 if(/^-e\s/is)
2505 my $Reject = $'; # Get the expression
2506 $NoAccess ||= eval($Reject); # evaluate the expresion
2508 else
2510 my ($Reject, @FilePatternList) = split;
2511 if($Reject eq '*' # Always match
2512 ||$REMOTE_HOST =~ /\Q$Reject\E$/is # REMOTE_HOST matches
2513 ||($Reject =~ /^[0-9\.]+$/
2514 && $REMOTE_ADDR =~ /^\Q$Reject\E/is # IP address matches
2518 if($FilePatternList[0])
2520 foreach $Pattern (@FilePatternList)
2522 $NoAccess ||= ($PATH_INFO =~ m@\Q$Pattern\E@is);
2525 else
2527 $NoAccess = 1; # No file patterns -> Rejected
2531 last if $NoAccess;
2533 close(CGI_Reject);
2534 if($NoAccess){ dieHandler(6, "Request rejected: $PATH_INFO\n");};
2537 ##########################################################<<<<<<<<<<End Remove
2540 # Get the filename
2542 # Does the filename contain any illegal characters (e.g., |, >, or <)
2543 dieHandler(7, "Illegal request: $ENV{'PATH_INFO'}\n") if $ENV{'PATH_INFO'} =~ /[^$FileAllowedChars]/;
2544 # Does the pathname contain an illegal (blocked) "directory"
2545 dieHandler(8, "Illegal request: $ENV{'PATH_INFO'}\n") if $BlockPathAccess && $ENV{'PATH_INFO'} =~ m@$BlockPathAccess@; # Access is blocked
2546 # Does the pathname contain a direct referencer to BinaryMapFile
2547 dieHandler(9, "Illegal request: $ENV{'PATH_INFO'}\n") if $BinaryMapFile && $ENV{'PATH_INFO'} =~ m@\Q$BinaryMapFile\E@; # Access is blocked
2549 # SECURITY: Is PATH_INFO allowed?
2550 if($FilePattern && $ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} ne '-' &&
2551 ($ENV{'PATH_INFO'} !~ m@($FilePattern)$@is))
2553 # Unsupported file types can be processed by a special raw-file
2554 if($BinaryMapFile)
2556 $ENV{'CGI_BINARY_FILE'} = $ENV{'PATH_INFO'};
2557 $ENV{'PATH_INFO'} = $BinaryMapFile;
2559 else
2561 dieHandler(10, "Illegal file\n");
2567 # End of Security Access Control
2570 ############################################################################
2572 # Get the POST part of the query and add it to the QUERY_STRING.
2575 sub Get_POST_part_of_query
2578 # If POST, Read data from stdin to QUERY_STRING
2579 if($ENV{'REQUEST_METHOD'} =~ /POST/is)
2581 # SECURITY: Check size of Query String
2582 $ENV{'CONTENT_LENGTH'} <= $MaximumQuerySize || dieHandler(11, "Query too long: $ENV{'CONTENT_LENGTH'}\n"); # Query too long
2583 my $QueryRead = 0;
2584 my $SystemRead = $ENV{'CONTENT_LENGTH'};
2585 $ENV{'QUERY_STRING'} .= '&' if length($ENV{'QUERY_STRING'}) > 0;
2586 while($SystemRead > 0)
2588 $QueryRead = sysread(STDIN, $Post, $SystemRead); # Limit length
2589 $ENV{'QUERY_STRING'} .= $Post;
2590 $SystemRead -= $QueryRead;
2592 # Update decoded Query String
2593 $default_values{CGI_Decoded_QS} = YOUR_CGIQUERYDECODE();
2594 $default_values{CGI_Content_Length} =
2595 length($default_values{CGI_Decoded_QS});
2599 # End of getting POST part of query
2602 ############################################################################
2604 # Start (HTML) output and logging
2605 # (if there are irregularities, it can kill the current process)
2608 sub Initialize_output
2610 # Construct the REAL file path (except for STDIN on the command line)
2611 my $file_path = $ENV{'PATH_INFO'} ne '-' ? $SS_PUB . $ENV{'PATH_INFO'} : '-';
2612 $file_path =~ s/\?.*$//; # Remove query
2613 # This is only necessary if your server does not catch ../ directives
2614 $file_path !~ m@\.\./@ || dieHandler(12, "Illegal ../ Construct\n"); # SECURITY: Do not allow ../ constructs
2616 # Block STDIN use (-) if CGIscriptor is servicing a HTTP request
2617 if($file_path eq '-')
2619 dieHandler(13, "STDIN request in On Line system\n") if $BLOCK_STDIN_HTTP_REQUEST
2620 && ($ENV{'SERVER_SOFTWARE'}
2621 || $ENV{'SERVER_NAME'}
2622 || $ENV{'GATEWAY_INTERFACE'}
2623 || $ENV{'SERVER_PROTOCOL'}
2624 || $ENV{'SERVER_PORT'}
2625 || $ENV{'REMOTE_ADDR'}
2626 || $ENV{'HTTP_USER_AGENT'});
2631 if($ClientLog)
2633 open(ClientLog, ">>$ClientLog");
2634 print ClientLog "$LocalTime | ",
2635 ($ENV{REMOTE_USER} || "-"), " ",
2636 ($ENV{REMOTE_IDENT} || "-"), " ",
2637 ($ENV{REMOTE_HOST} || "-"), " ",
2638 $ENV{REMOTE_ADDR}, " ",
2639 $ENV{PATH_INFO}, " ",
2640 $ENV{'CGI_BINARY_FILE'}, " ",
2641 ($default_values{CGI_Content_Length} || "-"),
2642 "\n";
2643 close(ClientLog);
2645 if($QueryLog)
2647 open(QueryLog, ">>$QueryLog");
2648 print QueryLog "$LocalTime\n",
2649 ($ENV{REMOTE_USER} || "-"), " ",
2650 ($ENV{REMOTE_IDENT} || "-"), " ",
2651 ($ENV{REMOTE_HOST} || "-"), " ",
2652 $ENV{REMOTE_ADDR}, ": ",
2653 $ENV{PATH_INFO}, " ",
2654 $ENV{'CGI_BINARY_FILE'}, "\n";
2656 # Write Query to Log file
2657 print QueryLog $default_values{CGI_Decoded_QS}, "\n\n";
2658 close(QueryLog);
2661 # Return the file path
2662 return $file_path;
2665 # End of Initialize output
2668 ############################################################################
2670 # Handle login access
2672 # Access is based on a valid session ticket.
2673 # Session tickets should be dependend on user name
2674 # and IP address. The patterns of URLs for which a
2675 # session ticket is needed and the login URL are stored in
2676 # %TicketRequiredPatterns as:
2677 # 'RegEx pattern' -> 'SessionPath\tPasswordPath\tLogin URL\tExpiration'
2680 sub Log_In_Access # () -> 0 = Access Allowed, Login page if access is not allowed
2682 # No patterns, no login
2683 return 0 unless %TicketRequiredPatterns;
2685 # Get and initialize values (watch out for stuff processed by BinaryMap files)
2686 my ($SessionPath, $PasswordsPath, $Login, $valid_duration) = ("", "", "", 0);
2687 my $PATH_INFO = $ENV{'CGI_BINARY_FILE'} ? $ENV{'CGI_BINARY_FILE'} : $ENV{'PATH_INFO'};
2688 my $REMOTE_ADDR = $ENV{'REMOTE_ADDR'};
2690 # Get and check the tickets. Tickets are restricted to word-characters (alphanumeric+_)
2691 CGIexecute::defineCGIvariable('LOGINTICKET', "");
2692 my $LOGINTICKET = ${"CGIexecute::LOGINTICKET"};
2693 return 0 if ($LOGINTICKET && $LOGINTICKET =~ /[^\w]/isg);
2694 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
2695 my $SESSIONTICKET = ${"CGIexecute::SESSIONTICKET"};
2696 return 0 if ($SESSIONTICKET && $SESSIONTICKET =~ /[^\w]/isg);
2697 # Look for a LOGOUT message
2698 my $LOGOUT = $ENV{QUERY_STRING} =~ /(^|\&)LOGOUT([\=\&]|$)/;
2700 # Username and password
2701 CGIexecute::defineCGIvariable('USERNAME', "");
2702 my $username = ${"CGIexecute::USERNAME"};
2703 return 0 if $username =~ m!^[^\w]!isg || $username =~ m![^\w \-]!isg;
2704 my $userfile = lc($username);
2705 $userfile =~ s/[^\w]/_/isg;
2706 CGIexecute::defineCGIvariable('PASSWORD', "");
2707 my $password = ${"CGIexecute::PASSWORD"};
2708 CGIexecute::defineCGIvariable('NEWPASSWORD', "");
2709 my $newpassword = ${"CGIexecute::NEWPASSWORD"};
2711 foreach my $pattern (keys(%TicketRequiredPatterns))
2713 # Check BOTH the real PATH_INFO and the CGI_BINARY_FILE variable
2714 if($ENV{'PATH_INFO'} =~ m#$pattern# || $ENV{'CGI_BINARY_FILE'} =~ m#$pattern#)
2716 # Fall through a sieve of requirements
2717 ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
2719 # Is there a change password request?
2720 if($newpassword && $LOGINTICKET && $SESSIONTICKET)
2722 my $tickets_removed = remove_expired_tickets($SessionPath);
2723 goto Login unless (-s "$SessionPath/$LOGINTICKET");
2724 goto Login unless (-s "$PasswordsPath/$userfile");
2725 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
2726 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
2727 goto Login unless $ticket_valid;
2728 # Sessionticket is available to scripts
2729 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
2730 # Authorize
2731 change_password("$SessionPath/$LOGINTICKET", "$SessionPath/$SESSIONTICKET", "$PasswordsPath/$userfile", $password, $newpassword);
2732 # Ready
2733 return 0;
2735 # Is there a login ticket of this name?
2736 elsif($LOGINTICKET)
2738 my $tickets_removed = remove_expired_tickets($SessionPath);
2739 goto Login unless (-s "$SessionPath/$LOGINTICKET");
2740 goto Login unless (-s "$PasswordsPath/$userfile");
2741 my $ticket_valid = check_ticket_validity("LOGIN", "$SessionPath/$LOGINTICKET", $REMOTE_ADDR, ".");
2742 goto Login unless $ticket_valid;
2744 # Authorize
2745 $SESSIONTICKET = authorize_login("$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $password);
2746 if($SESSIONTICKET)
2748 create_session_file("$SessionPath/$SESSIONTICKET", "$SessionPath/$LOGINTICKET", "$PasswordsPath/$userfile", $PATH_INFO);
2749 # Sessionticket is available to scripts
2750 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
2753 # Is there a session ticket of this name?
2754 if($CHALLENGETICKET)
2756 goto Login unless (-s "$SessionPath/$CHALLENGETICKET");
2757 my $ticket_valid = check_ticket_validity("CHALLENGE", "$SessionPath/$CHALLENGETICKET", $REMOTE_ADDR, $PATH_INFO);
2758 goto Login unless $ticket_valid;
2759 if(!$LOGINTICKET && $LOGOUT)
2761 unlink "$SessionPath/$CHALLENGETICKET";
2762 goto Login;
2765 my $NEWCHALLENGETICKET = "";
2766 $NEWCHALLENGETICKET = create_session_file("$SessionPath/$CHALLENGETICKET", "$PasswordsPath/$userfile", $PATH_INFO);
2768 # Sessionticket is available to scripts
2769 $ENV{'CHALLENGETICKET'} = $CHALLENGETICKET;
2770 return 0;
2772 elsif($SESSIONTICKET)
2774 goto Login unless (-s "$SessionPath/$SESSIONTICKET");
2775 my $ticket_valid = check_ticket_validity("SESSION", "$SessionPath/$SESSIONTICKET", $REMOTE_ADDR, $PATH_INFO);
2776 goto Login unless $ticket_valid;
2777 if(!$LOGINTICKET && $LOGOUT)
2779 unlink "$SessionPath/$SESSIONTICKET";
2780 goto Login;
2782 # Sessionticket is available to scripts
2783 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
2784 return 0;
2787 goto Login;
2788 return 0;
2791 return 0;
2793 Login:
2794 create_login_file($PasswordsPath, $SessionPath, $REMOTE_ADDR);
2795 return "$YOUR_HTML_FILES/$Login";
2798 sub authorize_login # ($loginfile, $authorizationfile, $password, $SessionPath) => SESSIONTICKET First two arguments are file paths
2800 my $loginfile = shift || "";
2801 my $authorizationfile = shift || "";
2802 my $password = shift || "";
2803 my $SessionPath = shift || "";
2805 # Get Login session ticket
2806 my $loginticket = read_ticket($loginfile);
2807 # Get User credentials for authorization
2808 my $authorization = read_ticket($authorizationfile);
2810 # Get Randomsalt
2811 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
2813 return "" unless $Randomsalt;
2815 my $storedpassword = $authorization->{'Password'}->[0];
2816 return "" unless $storedpassword;
2817 # Without the "bash -c", the 'echo -n' could use sh, which does not recognize the -n option
2818 my $Hashedpassword = `bash -c 'echo -n $Randomsalt$storedpassword| $ENV{"SHASUMCMD"}'`;
2819 chomp($Hashedpassword);
2820 return "" unless $password eq $Hashedpassword;
2822 # Extract Session Ticket
2823 my $sessionticket = $loginticket->{'Session'}->[0];
2824 $sessionticket = "" if -x "$SessionPath/$sessionticket";
2826 return $sessionticket;
2829 sub change_password # ($loginfile, $sessionfile, $authorizationfile, $password, $newpassword) First two arguments are file paths
2831 my $loginfile = shift || "";
2832 my $sessionfile = shift || "";
2833 my $authorizationfile = shift || "";
2834 my $password = shift || "";
2835 my $newpassword = shift || "";
2836 # Get Login session ticket
2837 my $loginticket = read_ticket($loginfile);
2838 # Get Randomsalt
2839 my $Randomsalt = $loginticket->{'Randomsalt'}->[0];
2840 # Login ticket file has been used, remove it
2841 unlink($loginfile);
2843 return "" unless $Randomsalt;
2845 # Get session ticket
2846 my $sessionticket = read_ticket($sessionfile);
2847 # Get User credentials for authorization
2848 my $authorization = read_ticket($authorizationfile);
2849 return "" unless $authorization->{'Username'}->[0] eq $sessionticket->{'Username'}->[0];
2851 my $storedpassword = $authorization->{'Password'}->[0];
2852 # Without the "bash -c", the 'echo -n' could use sh, which does not recognize the -n option
2853 my $Hashedpassword = `bash -c 'echo -n $Randomsalt$storedpassword| $ENV{"SHASUMCMD"}'`;
2854 chomp($Hashedpassword);
2856 return "" unless $password eq $Hashedpassword;
2858 # Decrypt the $newpassword
2859 my $decryptedPassword = XOR_hex_strings($storedpassword, $newpassword);
2861 # Authorization succeeded, change password
2862 $authorization->{'Password'}->[0] = $decryptedPassword;
2864 open(USERFILE, "<$authorizationfile") || die "<$authorizationfile: $!\n";
2865 my @USERlines = <USERFILE>;
2866 close(USERFILE);
2867 # Change
2868 open(USERFILE, ">$authorizationfile") || die ">$authorizationfile: $!\n";
2869 foreach my $line (@USERlines)
2871 $line =~ s/^Password: ($storedpassword)$/Password: $decryptedPassword/ig;
2872 print USERFILE $line;
2874 close(USERFILE);
2876 return $newpassword;
2879 sub XOR_hex_strings # (hex1, hex2) -> hex
2881 my $hex1 = shift || "";
2882 my $hex2 = shift || "";
2883 my @hex1list = split('', $hex1);
2884 my @hex2list = split('', $hex2);
2885 my @hexresultlist = ();
2886 for(my $i; $i < scalar(@hex1list); ++$i)
2888 my $d1 = hex($hex1list[$i]);
2889 my $d2 = hex($hex2list[$i]);
2890 my $dresult = ($d1 ^ $d2);
2891 $hexresultlist[$i] = sprintf("%x", $dresult);
2893 $hexresult = join('', @hexresultlist);
2894 return $hexresult;
2897 # Copy a Challenge ticket file to a new name which is the hash of the new $CHALLENGETICKET and the password
2898 sub copy_challenge_file #($oldchallengefile, $authorizationfile, $sessionpath) -> $CHALLENGETICKET
2900 my $oldchallengefile = shift || "";
2901 my $authorizationfile = shift || "";
2902 my $sessionpath = shift || "";
2903 $sessionpath =~ s!/+$!!g;
2905 # Get Login session ticket
2906 my $oldchallenge = read_ticket($oldchallengefile);
2907 # Get Authorization (user) session file
2908 my $authorization = read_ticket($authorizationfile);
2909 my $storedpassword = $authorization->{'Password'}->[0];
2910 return "" unless $storedpassword;
2912 # Create Random Hash Salt
2913 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
2914 my $NEWCHALLENGETICKET = <URANDOM>;
2915 close(URANDOM);
2916 chomp($NEWCHALLENGETICKET);
2917 my $newchallengefile = `bash -c 'echo -n $NEWCHALLENGETICKET$storedpassword| $ENV{"SHASUMCMD"}'`;
2918 return "" unless $newchallengefile;
2920 $ENV{'CHALLENGETICKET'} = $CHALLENGETICKET;
2921 CGIexecute::defineCGIvariable('CHALLENGETICKET', "CHALLENGETICKET");
2922 ${"CGIexecute::CHALLENGETICKET"} = $CHALLENGETICKET;
2924 # Write Session Ticket
2925 open(OLDCHALLENGE, "<$oldchallengefile") || die "$oldchallengefile: $!\n";
2926 my @OldChallengeLines = <OLDCHALLENGE>;
2928 open(SESSION, ">$sessionpath/$newchallengefile") || die "$sessionpath/$newchallengefile: $!\n";
2929 foreach $line (@OldChallengeLines)
2931 print SESSION $line;
2933 close(SESSION);
2935 # Old file should now be removed
2936 unlink($oldchallengefile);
2938 return $CHALLENGETICKET;
2941 sub create_login_file #($PasswordDir, $SessionDir, $IPaddress)
2943 my $PasswordDir = shift || "";
2944 my $SessionDir = shift || "";
2945 my $IPaddress = shift || "";
2947 # Create Session Ticket
2948 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
2949 my $SESSIONTICKET = <URANDOM>;
2950 close(URANDOM);
2951 chomp($SESSIONTICKET);
2953 # Create Login Ticket
2954 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $!\n";
2955 my $LOGINTICKET= <URANDOM>;
2956 close(URANDOM);
2957 chomp($LOGINTICKET);
2959 # Create Random Hash Salt
2960 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $RANDOMHASHCMD | $!\n";
2961 my $RANDOMSALT= <URANDOM>;
2962 close(URANDOM);
2963 chomp($RANDOMSALT);
2965 # Create SALT file if it does not exist
2966 # Remove this, including test account for life system
2967 unless(-d "$SessionDir")
2969 `mkdir -p "$SessionDir"`;
2971 unless(-d "$PasswordDir")
2973 `mkdir -p "$PasswordDir"`;
2975 # Create SERVERSALT and default test account
2976 my $SERVERSALT = "";
2977 unless(-s "$PasswordDir/SALT")
2979 open(URANDOM, "$RANDOMHASHCMD |") || die "URANDOM; $!\n";
2980 $SERVERSALT= <URANDOM>;
2981 chomp($SERVERSALT);
2982 close(URANDOM);
2983 open(SALTFILE, ">$PasswordDir/SALT") || die ">$PasswordDir/SALT: $!\n";
2984 print SALTFILE "$SERVERSALT\n";
2985 close(SALTFILE);
2987 # Update test account (should be removed in live system)
2988 if(-s "$PasswordDir/test")
2990 my $storedpassword = `bash -c 'echo -n ${SERVERSALT}test | $ENV{"SHASUMCMD"}'`;
2991 chomp($storedpassword);
2992 open(USERFILE, "<$PasswordDir/test") || die "</Private/.Passwords/test: $!\n";
2993 @USERlines = <USERFILE>;
2994 close(USERFILE);
2996 open(USERFILE, ">$PasswordDir/test") || die ">/Private/.Passwords/test: $!\n";
2997 # Add Password and Salt
2998 foreach my $line (@USERlines)
3000 $line =~ s/^Password: (.*)$/Password: $storedpassword/ig;
3001 $line =~ s/^Salt: (.*)$/Salt: $SERVERSALT/ig;
3003 print USERFILE $line;
3005 close(USERFILE);
3010 # Read in site Salt
3011 open(SALTFILE, "<$PasswordDir/SALT") || die "$PasswordDir/SALT: $!\n";
3012 $SERVERSALT=<SALTFILE>;
3013 close(SALTFILE);
3014 chomp($SERVERSALT);
3016 # Create login session ticket
3017 open(LOGINTICKET, ">$SessionDir/$LOGINTICKET") || die "$SessionDir/$LOGINTICKET: $!\n";
3018 print LOGINTICKET << "ENDOFLOGINTICKET";
3019 Type: LOGIN
3020 IPaddress: $IPaddress
3021 Salt: $SERVERSALT
3022 Session: $SESSIONTICKET
3023 Randomsalt: $RANDOMSALT
3024 Expires: +600s
3025 ENDOFLOGINTICKET
3026 close(LOGINTICKET);
3028 # Set global variables
3029 # $SERVERSALT
3030 $ENV{'SERVERSALT'} = $SERVERSALT;
3031 CGIexecute::defineCGIvariable('SERVERSALT', "");
3032 ${"CGIexecute::SERVERSALT"} = $SERVERSALT;
3034 # $SESSIONTICKET
3035 $ENV{'SESSIONTICKET'} = $SESSIONTICKET;
3036 CGIexecute::defineCGIvariable('SESSIONTICKET', "");
3037 ${"CGIexecute::SESSIONTICKET"} = $SESSIONTICKET;
3039 # $RANDOMSALT
3040 $ENV{'RANDOMSALT'} = $RANDOMSALT;
3041 CGIexecute::defineCGIvariable('RANDOMSALT', "");
3042 ${"CGIexecute::RANDOMSALT"} = $RANDOMSALT;
3044 # $LOGINTICKET
3045 $ENV{'LOGINTICKET'} = $LOGINTICKET;
3046 CGIexecute::defineCGIvariable('LOGINTICKET', "");
3047 ${"CGIexecute::LOGINTICKET"} = $LOGINTICKET;
3049 return $ENV{'LOGINTICKET'};
3052 sub create_session_file #($sessionfile, $loginfile, $authorizationfile, $path) -> Is $loginfile deleted? 0/1
3054 my $sessionfile = shift || "";
3055 my $loginfile = shift || "";
3056 my $authorizationfile = shift || "";
3057 my $path = shift || "";
3059 # Get Login session ticket
3060 my $loginticket = read_ticket($loginfile);
3061 # Get Authorization (user) session file
3062 my $authorization = read_ticket($authorizationfile);
3064 my @IPaddress = @{$loginticket->{'IPaddress'}};
3065 my @AllowedPaths = @{$authorization->{'AllowedPaths'}};;
3066 my @Expires = ();
3067 foreach my $pattern (keys(%TicketRequiredPatterns))
3069 if($path =~ m#$pattern#)
3071 my ($SessionPath, $PasswordsPath, $Login, $validtime) = split(/\t/, $TicketRequiredPatterns{$pattern});
3072 push(@Expires, $validtime);
3075 # Write Session Ticket
3076 open(SESSION, ">$sessionfile") || die "$sessionfile: $!\n";
3077 if($authorization->{'Session'} && $authorization->{'Session'}->[0])
3079 print SESSION "Type: ", $authorization->{'Session'}->[0], "\n";
3081 else
3083 print SESSION "Type: SESSION\n";
3085 foreach my $address (@IPaddress)
3087 print SESSION "IPaddress: $address\n";
3089 foreach my $path (@AllowedPaths)
3091 print SESSION "AllowedPaths: $path\n";
3093 foreach my $validtime (@Expires)
3095 print SESSION "Expires: $validtime\n";
3097 print SESSION "Username: ", $authorization->{'Username'}->[0], "\n";
3098 close(SESSION);
3100 # Login file should now be removed
3101 return unlink($loginfile);
3104 sub check_ticket_validity # ($type, $ticketfile, $address, $path)
3106 my $type = shift || "SESSION";
3107 my $ticketfile = shift || "";
3108 my $address = shift || "";
3109 my $path = shift || "";
3111 # Is there a session ticket of this name?
3112 return 0 unless -s "$ticketfile";
3114 # There is a session ticket, is it linked to this IP address?
3115 my $ticket = read_ticket($ticketfile);
3117 # Is this the right type of ticket
3118 return unless $ticket->{"Type"}->[0] eq $type;
3120 # Does the IP address match?
3121 $IPmatches = 0;
3122 for my $IPpattern (@{$ticket->{"IPaddress"}})
3124 ++$IPmatches if $address =~ m#^$IPpattern#ig;
3126 return 0 unless !$ticket->{"IPaddress"} || $IPmatches;
3128 # Is the path allowed
3129 my $Pathmatches = 0;
3130 foreach my $Pathpattern (@{$ticket->{"AllowedPaths"}})
3132 ++$Pathmatches if $path =~ m#$Pathpattern#ig;
3134 return 0 unless !@{$ticket->{"AllowedPaths"}} || $Pathmatches;
3136 # Is the ticket expired?
3137 my $Expired = 0;
3138 if($ticket->{"Expires"} && @{$ticket->{"Expires"}})
3140 my $CurrentTime = time();
3141 ++$Expired if($CurrentTime > $ticket->{"Expires"}->[0]);
3143 return 0 if $Expired;
3145 # Make login values available
3146 $ENV{"LOGINUSERNAME"} = $ticket->{'Username'}->[0];
3147 $ENV{"LOGINIPADDRESS"} = $address;
3148 $ENV{"LOGINPATH"} = $path;
3150 return 1;
3154 sub remove_expired_tickets # ($path) -> number of tickets removed
3156 my $path = shift || "";
3157 return 0 unless $path;
3158 $path =~ s!/+$!!g;
3159 my $removed_tickets = 0;
3160 my @ticketlist = glob("$path/*");
3161 foreach my $ticketfile (@ticketlist)
3163 my $ticket = read_ticket($ticketfile);
3164 if(@{$ticket->{'Expires'}} && $ticket->{'Expires'}->[0] < time)
3166 unlink $ticketfile;
3167 ++$removed_tickets;
3170 return $removed_tickets;
3173 sub read_ticket # ($ticketfile) -> &%ticket
3175 my $ticketfile = shift || "";
3176 my $ticket = {};
3177 if($ticketfile && -s $ticketfile)
3179 open(TICKETFILE, "<$ticketfile") || die "$ticketfile: $!\n";
3180 my @alllines = <TICKETFILE>;
3181 close(TICKETFILE);
3182 foreach my $currentline (@alllines)
3184 if($currentline =~ /^\s*(\S[^\:]+)\:\s+(.*)\s*$/)
3186 my $Label = $1;
3187 my $Value = $2;
3188 # Recalculate expire date from relative time
3189 if($Label =~ /^Expires$/ig && $Value =~ /^\+/)
3191 # Get SessionTicket file stats
3192 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)
3193 = stat("$ticketfile");
3194 if($Value =~ /^\+(\d+)\s*d(ays)?\s*$/)
3196 $ExpireTime = 24*3600*$1;
3198 elsif($Value =~ /^\+(\d+)\s*m(inutes)?\s*$/)
3200 $ExpireTime = 60*$1;
3202 elsif($Value =~ /^\+(\d+)\s*h(ours)?\s*$/)
3204 $ExpireTime = 3600*$1;
3206 elsif($Value =~ /^\+(\d+)\s*s(econds)?\s*$/)
3208 $ExpireTime = $1;
3210 elsif($Value =~ /^\+(\d+)\s*$/)
3212 $ExpireTime = $1;
3215 my $ActualExpireTime = $ExpireTime + $ctime;
3216 $Value = $ActualExpireTime;
3218 $ticket->{$Label} = () unless exists($ticket->{$Label});
3219 push(@{$ticket->{$Label}}, $Value);
3223 if(exists($ticket->{Expires}))
3225 @{$ticket->{Expires}} = sort(@{$ticket->{Expires}});
3227 return $ticket;
3230 # End of Handle login access
3233 ############################################################################
3235 # Handle foreign interpreters (i.e., scripting languages)
3237 # Insert perl code to execute scripts in foreign scripting languages.
3238 # Actually, the scripts inside the <SCRIPT></SCRIPT> blocks are piped
3239 # into an interpreter.
3240 # The code presented here is fairly confusing because it
3241 # actually writes perl code code to the output.
3243 # A table with the file handles
3244 %SCRIPTINGINPUT = ();
3246 # A function to clean up Client delivered CGI parameter values
3247 # (i.e., quote all odd characters)
3248 %SHRUBcharacterTR =
3250 "\'" => '&#39;',
3251 "\`" => '&#96;',
3252 "\"" => '&quot;',
3253 '&' => '&amper;',
3254 "\\" => '&#92;'
3257 sub shrubCGIparameter # ($String) -> Cleaned string
3259 my $String = shift || "";
3261 # Change all quotes [`'"] into HTML character entities
3262 my ($Char, $Transcript) = ('&', $SHRUBcharacterTR{'&'});
3264 # Protect &
3265 $String =~ s/\Q$Char\E/$Transcript/isg if $Transcript;
3267 while( ($Char, $Transcript) = each %SHRUBcharacterTR)
3269 next if $Char eq '&';
3270 $String =~ s/\Q$Char\E/$Transcript/isg;
3273 # Replace newlines
3274 $String =~ s/[\n]/\\n/g;
3275 # Replace control characters with their backslashed octal ordinal numbers
3276 $String =~ s/([^\S \t])/(sprintf("\\0%o", ord($1)))/eisg; #
3277 $String =~ s/([\x00-\x08\x0A-\x1F])/(sprintf("\\0%o", ord($1)))/eisg; #
3279 return $String;
3283 # The initial open statements: Open a pipe to the foreign script interpreter
3284 sub OpenForeignScript # ($ContentType) -> $DirectivePrefix
3286 my $ContentType = lc(shift) || return "";
3287 my $NewDirective = "";
3289 return $NewDirective if($SCRIPTINGINPUT{$ContentType});
3291 # Construct a unique file handle name
3292 $SCRIPTINGFILEHANDLE = uc($ContentType);
3293 $SCRIPTINGFILEHANDLE =~ s/\W/\_/isg;
3294 $SCRIPTINGINPUT{$ContentType} = $SCRIPTINGFILEHANDLE
3295 unless $SCRIPTINGINPUT{$ContentType};
3297 # Create the relevant script: Open the pipe to the interpreter
3298 $NewDirective .= <<"BLOCKCGISCRIPTOROPEN";
3299 # Open interpreter for '$ContentType'
3300 # Open pipe to interpreter (if it isn't open already)
3301 open($SCRIPTINGINPUT{$ContentType}, "|$ScriptingLanguages{$ContentType}") || main::dieHandler(14, "$ContentType: \$!\\n");
3302 BLOCKCGISCRIPTOROPEN
3304 # Insert Initialization code and CGI variables
3305 $NewDirective .= InitializeForeignScript($ContentType);
3307 # Ready
3308 return $NewDirective;
3312 # The final closing code to stop the interpreter
3313 sub CloseForeignScript # ($ContentType) -> $DirectivePrefix
3315 my $ContentType = lc(shift) || return "";
3316 my $NewDirective = "";
3318 # Do nothing unless the pipe realy IS open
3319 return "" unless $SCRIPTINGINPUT{$ContentType};
3321 # Initial comment
3322 $NewDirective .= "\# Close interpreter for '$ContentType'\n";
3325 # Write the Postfix code
3326 $NewDirective .= CleanupForeignScript($ContentType);
3328 # Create the relevant script: Close the pipe to the interpreter
3329 $NewDirective .= <<"BLOCKCGISCRIPTORCLOSE";
3330 close($SCRIPTINGINPUT{$ContentType}) || main::dieHandler(15, \"$ContentType: \$!\\n\");
3331 select(STDOUT); \$|=1;
3333 BLOCKCGISCRIPTORCLOSE
3335 # Remove the file handler of the foreign script
3336 delete($SCRIPTINGINPUT{$ContentType});
3338 return $NewDirective;
3342 # The initialization code for the foreign script interpreter
3343 sub InitializeForeignScript # ($ContentType) -> $DirectivePrefix
3345 my $ContentType = lc(shift) || return "";
3346 my $NewDirective = "";
3348 # Add initialization code
3349 if($ScriptingInitialization{$ContentType})
3351 $NewDirective .= <<"BLOCKCGISCRIPTORINIT";
3352 # Initialization Code for '$ContentType'
3353 # Select relevant output filehandle
3354 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3356 # The Initialization code (if any)
3357 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}INITIALIZATIONCODE';
3358 $ScriptingInitialization{$ContentType}
3359 ${ContentType}INITIALIZATIONCODE
3361 BLOCKCGISCRIPTORINIT
3364 # Add all CGI variables defined
3365 if(exists($ScriptingCGIvariables{$ContentType}))
3367 # Start writing variable definitions to the Interpreter
3368 if($ScriptingCGIvariables{$ContentType})
3370 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEF";
3371 # CGI variables (from the %default_values table)
3372 print $SCRIPTINGINPUT{$ContentType} << '${ContentType}CGIVARIABLES';
3373 BLOCKCGISCRIPTORVARDEF
3376 my ($N, $V);
3377 foreach $N (keys(%default_values))
3379 # Determine whether the parameter has been defined
3380 # (the eval is a workaround to get at the variable value)
3381 next unless eval("defined(\$CGIexecute::$N)");
3383 # Get the value from the EXECUTION environment
3384 $V = eval("\$CGIexecute::$N");
3385 # protect control characters (i.e., convert them to \0.. form)
3386 $V = shrubCGIparameter($V);
3388 # Protect interpolated variables
3389 eval("\$CGIexecute::$N = '$V';") unless $ScriptingCGIvariables{$ContentType};
3391 # Print the actual declaration for this scripting language
3392 if($ScriptingCGIvariables{$ContentType})
3394 $NewDirective .= sprintf($ScriptingCGIvariables{$ContentType}, $N, $V);
3395 $NewDirective .= "\n";
3399 # Stop writing variable definitions to the Interpreter
3400 if($ScriptingCGIvariables{$ContentType})
3402 $NewDirective .= <<"BLOCKCGISCRIPTORVARDEFEND";
3403 ${ContentType}CGIVARIABLES
3404 BLOCKCGISCRIPTORVARDEFEND
3409 $NewDirective .= << "BLOCKCGISCRIPTOREND";
3411 # Select STDOUT filehandle
3412 select(STDOUT); \$|=1;
3414 BLOCKCGISCRIPTOREND
3416 return $NewDirective;
3420 # The cleanup code for the foreign script interpreter
3421 sub CleanupForeignScript # ($ContentType) -> $DirectivePrefix
3423 my $ContentType = lc(shift) || return "";
3424 my $NewDirective = "";
3426 # Return if not needed
3427 return $NewDirective unless $ScriptingCleanup{$ContentType};
3429 # Create the relevant script: Open the pipe to the interpreter
3430 $NewDirective .= <<"BLOCKCGISCRIPTORSTOP";
3431 # Cleanup Code for '$ContentType'
3432 # Select relevant output filehandle
3433 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3434 # Print Cleanup code to foreign script
3435 print $SCRIPTINGINPUT{$ContentType} <<'${ContentType}SCRIPTSTOP';
3436 $ScriptingCleanup{$ContentType}
3437 ${ContentType}SCRIPTSTOP
3439 # Select STDOUT filehandle
3440 select(STDOUT); \$|=1;
3441 BLOCKCGISCRIPTORSTOP
3443 return $NewDirective;
3447 # The prefix code for each <script></script> block
3448 sub PrefixForeignScript # ($ContentType) -> $DirectivePrefix
3450 my $ContentType = lc(shift) || return "";
3451 my $NewDirective = "";
3453 # Return if not needed
3454 return $NewDirective unless $ScriptingPrefix{$ContentType};
3456 my $Quote = "\'";
3457 # If the CGIvariables parameter is defined, but empty, interpolate
3458 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
3459 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
3460 !$ScriptingCGIvariables{$ContentType};
3462 # Add initialization code
3463 $NewDirective .= <<"BLOCKCGISCRIPTORPREFIX";
3464 # Prefix Code for '$ContentType'
3465 # Select relevant output filehandle
3466 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3468 # The block Prefix code (if any)
3469 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}PREFIXCODE$Quote;
3470 $ScriptingPrefix{$ContentType}
3471 ${ContentType}PREFIXCODE
3472 # Select STDOUT filehandle
3473 select(STDOUT); \$|=1;
3474 BLOCKCGISCRIPTORPREFIX
3476 return $NewDirective;
3480 # The postfix code for each <script></script> block
3481 sub PostfixForeignScript # ($ContentType) -> $DirectivePrefix
3483 my $ContentType = lc(shift) || return "";
3484 my $NewDirective = "";
3486 # Return if not needed
3487 return $NewDirective unless $ScriptingPostfix{$ContentType};
3489 my $Quote = "\'";
3490 # If the CGIvariables parameter is defined, but empty, interpolate
3491 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
3492 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
3493 !$ScriptingCGIvariables{$ContentType};
3495 # Create the relevant script: Open the pipe to the interpreter
3496 $NewDirective .= <<"BLOCKCGISCRIPTORPOSTFIX";
3497 # Postfix Code for '$ContentType'
3498 # Select filehandle to interpreter
3499 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3500 # Print postfix code to foreign script
3501 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SCRIPTPOSTFIX$Quote;
3502 $ScriptingPostfix{$ContentType}
3503 ${ContentType}SCRIPTPOSTFIX
3504 # Select STDOUT filehandle
3505 select(STDOUT); \$|=1;
3506 BLOCKCGISCRIPTORPOSTFIX
3508 return $NewDirective;
3511 sub InsertForeignScript # ($ContentType, $directive, @SRCfile) -> $NewDirective
3513 my $ContentType = lc(shift) || return "";
3514 my $directive = shift || return "";
3515 my @SRCfile = @_;
3516 my $NewDirective = "";
3518 my $Quote = "\'";
3519 # If the CGIvariables parameter is defined, but empty, interpolate
3520 # code string (i.e., $var .= << "END" i.s.o. $var .= << 'END')
3521 $Quote = '"' if exists($ScriptingCGIvariables{$ContentType}) &&
3522 !$ScriptingCGIvariables{$ContentType};
3524 # Create the relevant script
3525 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
3526 # Insert Code for '$ContentType'
3527 # Select filehandle to interpreter
3528 select($SCRIPTINGINPUT{$ContentType}); \$|=1;
3529 BLOCKCGISCRIPTORINSERT
3531 # Use SRC feature files
3532 my $ThisSRCfile;
3533 while($ThisSRCfile = shift(@_))
3535 # Handle blocks
3536 if($ThisSRCfile =~ /^\s*\{\s*/)
3538 my $Block = $';
3539 $Block = $` if $Block =~ /\s*\}\s*$/;
3540 $NewDirective .= <<"BLOCKCGISCRIPTORSRCBLOCK";
3541 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}SRCBLOCKCODE$Quote;
3542 $Block
3543 ${ContentType}SRCBLOCKCODE
3544 BLOCKCGISCRIPTORSRCBLOCK
3546 next;
3549 # Handle files
3550 $NewDirective .= <<"BLOCKCGISCRIPTORSRCFILES";
3551 # Read $ThisSRCfile
3552 open(SCRIPTINGSOURCE, "<$ThisSRCfile") || main::dieHandler(16, "$ThisSRCfILE: \$!");
3553 while(<SCRIPTINGSOURCE>)
3555 print $SCRIPTINGINPUT{$ContentType} \$_;
3557 close(SCRIPTINGSOURCE);
3559 BLOCKCGISCRIPTORSRCFILES
3563 # Add the directive
3564 if($directive)
3566 $NewDirective .= <<"BLOCKCGISCRIPTORINSERT";
3567 print $SCRIPTINGINPUT{$ContentType} <<$Quote${ContentType}DIRECTIVECODE$Quote;
3568 $directive
3569 ${ContentType}DIRECTIVECODE
3570 BLOCKCGISCRIPTORINSERT
3574 $NewDirective .= <<"BLOCKCGISCRIPTORSELECT";
3575 # Select STDOUT filehandle
3576 select(STDOUT); \$|=1;
3577 BLOCKCGISCRIPTORSELECT
3579 # Ready
3580 return $NewDirective;
3583 sub CloseAllForeignScripts # Call CloseForeignScript on all open scripts
3585 my $ContentType;
3586 foreach $ContentType (keys(%SCRIPTINGINPUT))
3588 my $directive = CloseForeignScript($ContentType);
3589 print STDERR "\nDirective $CGI_Date: ", $directive;
3590 CGIexecute->evaluate($directive);
3595 # End of handling foreign (external) scripting languages.
3597 ############################################################################
3599 # A subroutine to handle "nested" quotes, it cuts off the leading
3600 # item or quoted substring
3601 # E.g.,
3602 # ' A_word and more words' -> @('A_word', ' and more words')
3603 # '"quoted string" The rest' -> @('quoted string', ' The rest')
3604 # (this is needed for parsing the <TAGS> and their attributes)
3605 my $SupportedQuotes = "\'\"\`\(\{\[";
3606 my %QuotePairs = ('('=>')','['=>']','{'=>'}'); # Brackets
3607 sub ExtractQuotedItem # ($String) -> @($QuotedString, $RestOfString)
3609 my @Result = ();
3610 my $String = shift || return @Result;
3612 if($String =~ /^\s*([\w\/\-\.]+)/is)
3614 push(@Result, $1, $');
3616 elsif($String =~ /^\s*(\\?)([\Q$SupportedQuotes\E])/is)
3618 my $BackSlash = $1 || "";
3619 my $OpenQuote = $2;
3620 my $CloseQuote = $OpenQuote;
3621 $CloseQuote = $QuotePairs{$OpenQuote} if $QuotePairs{$OpenQuote};
3623 if($BackSlash)
3625 $String =~ /^\s*\\\Q$OpenQuote\E/i;
3626 my $Onset = $';
3627 $Onset =~ /\\\Q$CloseQuote\E/i;
3628 my $Rest = $';
3629 my $Item = $`;
3630 push(@Result, $Item, $Rest);
3633 else
3635 $String =~ /^\s*\Q$OpenQuote\E([^\Q$CloseQuote\E]*)\Q$CloseQuote\E/i;
3636 push(@Result, $1, $');
3639 else
3641 push(@Result, "", $String);
3643 return @Result;
3646 # Now, start with the real work
3648 # Control the output of the Content-type: text/html\n\n message
3649 my $SupressContentType = 0;
3651 # Process a file
3652 sub ProcessFile # ($file_path)
3654 my $file_path = shift || return 0;
3657 # Generate a unique file handle (for recursions)
3658 my @SRClist = ();
3659 my $FileHandle = "file";
3660 my $n = 0;
3661 while(!eof($FileHandle.$n)) {++$n;};
3662 $FileHandle .= $n;
3664 # Start HTML output
3665 # Use the default Content-type if this is NOT a raw file
3666 unless(($RawFilePattern && $ENV{'PATH_INFO'} =~ m@($RawFilePattern)$@i)
3667 || $SupressContentType)
3669 $ENV{'PATH_INFO'} =~ m@($FilePattern)$@i;
3670 my $ContentType = $ContentTypeTable{$1};
3671 print "Content-type: $ContentType\n";
3672 print "\n";
3673 $SupressContentType = 1; # Content type has been printed
3677 # Get access to the actual data. This can be from RAM (by way of an
3678 # environment variable) or by opening a file.
3680 # Handle the use of RAM images (file-data is stored in the
3681 # $CGI_FILE_CONTENTS environment variable)
3682 # Note that this environment variable will be cleared, i.e., it is strictly for
3683 # single-use only!
3684 if($ENV{$CGI_FILE_CONTENTS})
3686 # File has been read already
3687 $_ = $ENV{$CGI_FILE_CONTENTS};
3688 # Sorry, you have to do the reading yourself (dynamic document creation?)
3689 # NOTE: you must read the whole document at once
3690 if($_ eq '-')
3692 $_ = eval("\@_=('$file_path'); do{$ENV{$CGI_DATA_ACCESS_CODE}}");
3694 else # Clear environment variable
3696 $ENV{$CGI_FILE_CONTENTS} = '-';
3699 # Open Only PLAIN TEXT files (or STDIN) and NO executable files (i.e., scripts).
3700 # THIS IS A SECURITY FEATURE!
3701 elsif($file_path eq '-' || (-e "$file_path" && -r _ && -T _ && -f _ && ! (-x _ || -X _) ))
3703 open($FileHandle, $file_path) || dieHandler(17, "<h2>File not found</h2>\n");
3704 push(@OpenFiles, $file_path);
3705 $_ = <$FileHandle>; # Read first line
3707 else
3709 print "<h2>File not found</h2>\n";
3710 dieHandler(18, "$file_path\n");
3713 $| = 1; # Flush output buffers
3715 # Initialize variables
3716 my $METAarguments = ""; # The CGI arguments from the latest META tag
3717 my @METAvalues = (); # The ''-quoted CGI values from the latest META tag
3718 my $ClosedTag = 0; # <TAG> </TAG> versus <TAG/>
3721 # Send document to output
3722 # Process the requested document.
3723 # Do a loop BEFORE reading input again (this catches the RAM/Database
3724 # type of documents).
3725 do {
3728 # Handle translations if needed
3730 performTranslation(\$_) if $TranslationPaths;
3732 # Catch <SCRIPT LANGUAGE="PERL" TYPE="text/ssperl" > directives in $_
3733 # There can be more than 1 <SCRIPT> or META tags on a line
3734 while(/\<\s*(SCRIPT|META|DIV|INS)\s/is)
3736 my $directive = "";
3737 # Store rest of line
3738 my $Before = $`;
3739 my $ScriptTag = $&;
3740 my $After = $';
3741 my $TagType = uc($1);
3742 # The before part can be send to the output
3743 print $Before;
3745 # Read complete Tag from after and/or file
3746 until($After =~ /([^\\])\>/)
3748 $After .= <$FileHandle>;
3749 performTranslation(\$After) if $TranslationPaths;
3752 if($After =~ /([^\\])\>/)
3754 $ScriptTag .= $`.$&; # Keep the Script Tag intact
3755 $After = $';
3757 else
3759 dieHandler(19, "Closing > not found\n");
3762 # The tag could be closed by />, we handle this in the XML way
3763 # and don't process any content (we ignore whitespace)
3764 $ClosedTag = ($ScriptTag =~ m@[^\\]/\s*\>\s*$@) ? 1 : 0;
3767 # TYPE or CLASS?
3768 my $TypeName = ($TagType =~ /META/is) ? "CONTENT" : "TYPE";
3769 $TypeName = "CLASS" if $TagType eq 'DIV' || $TagType eq 'INS';
3771 # Parse <SCRIPT> or <META> directive
3772 # If NOT (TYPE|CONTENT)="text/ssperl" (i.e., $ServerScriptContentType),
3773 # send the line to the output and go to the next loop
3774 my $CurrentContentType = "";
3775 if($ScriptTag =~ /(^|\s)$TypeName\s*=\s*/is)
3777 my ($Type) = ExtractQuotedItem($');
3778 $Type =~ /^\s*([\w\/\-]+)\s*[\,\;]?/;
3779 $CurrentContentType = lc($1); # Note: mime-types are "case-less"
3780 # CSS classes are aliases of $ServerScriptContentType
3781 if($TypeName eq "CLASS" && $CurrentContentType eq $ServerScriptContentClass)
3783 $CurrentContentType = $ServerScriptContentType;
3788 # Not a known server-side content type, print and continue
3789 unless(($CurrentContentType =~
3790 /$ServerScriptContentType|$ShellScriptContentType/is) ||
3791 $ScriptingLanguages{$CurrentContentType})
3793 print $ScriptTag;
3794 $_ = $After;
3795 next;
3799 # A known server-side content type, evaluate
3801 # First, handle \> and \<
3802 $ScriptTag =~ s/\\\>/\>/isg;
3803 $ScriptTag =~ s/\\\</\</isg;
3805 # Extract the CGI, SRC, ID, IF and UNLESS attributes
3806 my %ScriptTagAttributes = ();
3807 while($ScriptTag =~ /(^|\s)(CGI|IF|UNLESS|SRC|ID)\s*=\s*/is)
3809 my $Attribute = $2;
3810 my $Rest = $';
3811 my $Value = "";
3812 ($Value, $ScriptTag) = ExtractQuotedItem($Rest);
3813 $ScriptTagAttributes{uc($Attribute)} = $Value;
3817 # The attribute used to define the CGI variables
3818 # Extract CGI-variables from
3819 # <META CONTENT="text/ssperl; CGI='' SRC=''">
3820 # <SCRIPT TYPE='text/ssperl' CGI='' SRC=''>
3821 # <DIV CLASS='ssperl' CGI='' SRC='' ID=""> tags
3822 # <INS CLASS='ssperl' CGI='' SRC='' ID=""> tags
3823 if($ScriptTagAttributes{'CGI'})
3825 @ARGV = (); # Reset ARGV
3826 $ARGC = 0;
3827 $METAarguments = ""; # Reset the META CGI arguments
3828 @METAvalues = ();
3829 my $Meta_CGI = $ScriptTagAttributes{'CGI'};
3831 # Process default values of variables ($<name> = 'default value')
3832 # Allowed quotes are '', "", ``, (), [], and {}
3833 while($Meta_CGI =~ /(^\s*|[^\\])([\$\@\%]?)([\w\-]+)\s*/is)
3835 my $varType = $2 || '$'; # Variable or list
3836 my $name = $3; # The Name
3837 my $default = "";
3838 $Meta_CGI = $';
3840 if($Meta_CGI =~ /^\s*\=\s*/is)
3842 # Locate (any) default value
3843 ($default, $Meta_CGI) = ExtractQuotedItem($'); # Cut the parameter from the CGI
3845 $RemainingTag = $Meta_CGI;
3848 # Define CGI (or ENV) variable, initalize it from the
3849 # Query string or the default value
3851 # Also construct the @ARGV and @_ arrays. This allows other (SRC=) Perl
3852 # scripts to access the CGI arguments defined in the META tag
3853 # (Not for CGI inside <SCRIPT> tags)
3854 if($varType eq '$')
3856 CGIexecute::defineCGIvariable($name, $default)
3857 || dieHandler(20, "INVALID CGI name/value pair ($name, $default)\n");
3858 push(@METAvalues, "'".${"CGIexecute::$name"}."'");
3859 # Add value to the @ARGV list
3860 push(@ARGV, ${"CGIexecute::$name"});
3861 ++$ARGC;
3863 elsif($varType eq '@')
3865 CGIexecute::defineCGIvariableList($name, $default)
3866 || dieHandler(21, "INVALID CGI name/value list pair ($name, $default)\n");
3867 push(@METAvalues, "'".join("'", @{"CGIexecute::$name"})."'");
3868 # Add value to the @ARGV list
3869 push(@ARGV, @{"CGIexecute::$name"});
3870 $ARGC = scalar(@CGIexecute::ARGV);
3872 elsif($varType eq '%')
3874 CGIexecute::defineCGIvariableHash($name, $default)
3875 || dieHandler(22, "INVALID CGI name/value hash pair ($name, $default)\n");
3876 my @PairList = map {"$_ => ".${"CGIexecute::$name"}{$_}} keys(%{"CGIexecute::$name"});
3877 push(@METAvalues, "'".join("'", @PairList)."'");
3878 # Add value to the @ARGV list
3879 push(@ARGV, %{"CGIexecute::$name"});
3880 $ARGC = scalar(@CGIexecute::ARGV);
3883 # Store the values for internal and later use
3884 $METAarguments .= "$varType".$name.","; # A string of CGI variable names
3886 push(@METAvalues, "\'".eval("\"$varType\{CGIexecute::$name\}\"")."\'"); # ALWAYS add '-quotes around values
3891 # The IF (conditional execution) Attribute
3892 # Evaluate the condition and stop unless it evaluates to true
3893 if($ScriptTagAttributes{'IF'})
3895 my $IFcondition = $ScriptTagAttributes{'IF'};
3897 # Convert SCRIPT calls, ./<script>
3898 $IFcondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
3900 # Convert FILE calls, ~/<file>
3901 $IFcondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
3903 # Block execution if necessary
3904 unless(CGIexecute->evaluate($IFcondition))
3906 %ScriptTagAttributes = ();
3907 $CurrentContentType = "";
3911 # The UNLESS (conditional execution) Attribute
3912 # Evaluate the condition and stop if it evaluates to true
3913 if($ScriptTagAttributes{'UNLESS'})
3915 my $UNLESScondition = $ScriptTagAttributes{'UNLESS'};
3917 # Convert SCRIPT calls, ./<script>
3918 $UNLESScondition =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
3920 # Convert FILE calls, ~/<file>
3921 $UNLESScondition =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
3923 # Block execution if necessary
3924 if(CGIexecute->evaluate($UNLESScondition))
3926 %ScriptTagAttributes = ();
3927 $CurrentContentType = "";
3931 # The SRC (Source File) Attribute
3932 # Extract any source script files and add them in
3933 # front of the directive
3934 # The SRC list should be emptied
3935 @SRClist = ();
3936 my $SRCtag = "";
3937 my $Prefix = 1;
3938 my $PrefixDirective = "";
3939 my $PostfixDirective = "";
3940 # There is a SRC attribute
3941 if($ScriptTagAttributes{'SRC'})
3943 $SRCtag = $ScriptTagAttributes{'SRC'};
3944 # Remove "file://" prefixes
3945 $SRCtag =~ s@([^\w\/\\]|^)file\://([^\s\/\@\=])@$1$2@gis;
3946 # Expand script filenames "./Script"
3947 $SRCtag =~ s@([^\w\/\\]|^)\./([^\s\/\@\=])@$1$SCRIPT_SUB/$2@gis;
3948 # Expand script filenames "~/Script"
3949 $SRCtag =~ s@([^\w\/\\]|^)\~/([^\s\/\@\=])@$1$HOME_SUB/$2@gis;
3952 # File source tags
3953 while($SRCtag =~ /\S/is)
3955 my $SRCdirective = "";
3957 # Pseudo file, just a switch to go from PREFIXING to POSTFIXING
3958 # SRC files
3959 if($SRCtag =~ /^[\s\;\,]*(POSTFIX|PREFIX)([^$FileAllowedChars]|$)/is)
3961 my $InsertionPlace = $1;
3962 $SRCtag = $2.$';
3964 $Prefix = $InsertionPlace =~ /POSTFIX/i ? 0 : 1;
3965 # Go to next round
3966 next;
3968 # {}-blocks are just evaluated by "do"
3969 elsif($SRCtag =~ /^[\s\;\,]*\{/is)
3971 my $SRCblock = $';
3972 if($SRCblock =~ /\}[\s\;\,]*([^\}]*)$/is)
3974 $SRCblock = $`;
3975 $SRCtag = $1.$';
3976 # SAFEqx shell script blocks
3977 if($CurrentContentType =~ /$ShellScriptContentType/is)
3979 # Handle ''-quotes inside the script
3980 $SRCblock =~ s/[\']/\\$&/gis;
3982 $SRCblock = "print do { SAFEqx(\'".$SRCblock."\'); };'';";
3983 $SRCdirective .= $SRCblock."\n";
3985 # do { SRCblocks }
3986 elsif($CurrentContentType =~ /$ServerScriptContentType/is)
3988 $SRCblock = "print do { $SRCblock };'';";
3989 $SRCdirective .= $SRCblock."\n";
3991 else # The interpreter should handle this
3993 push(@SRClist, "{ $SRCblock }");
3997 else
3998 { dieHandler(23, "Closing \} missing\n");};
4000 # Files are processed as Text or Executable files
4001 elsif($SRCtag =~ /[\s\;\,]*([$FileAllowedChars]+)[\;\,\s]*/is)
4003 my $SrcFile = $1;
4004 $SRCtag = $';
4006 # We are handling one of the external interpreters
4007 if($ScriptingLanguages{$CurrentContentType})
4009 push(@SRClist, $SrcFile);
4011 # We are at the start of a DIV tag, just load all SRC files and/or URL's
4012 elsif($TagType eq 'DIV' || $TagType eq 'INS') # All files are prepended in DIV's
4014 # $SrcFile is a URL pointing to an HTTP or FTP server
4015 if($SrcFile =~ m!^([a-z]+)\://!)
4017 my $URLoutput = CGIscriptor::read_url($SrcFile);
4018 $SRCdirective .= $URLoutput;
4020 # SRC file is an existing file
4021 elsif(-e "$SrcFile")
4023 open(DIVSOURCE, "<$SrcFile") || dieHandler(24, "<$SrcFile: $!\n");
4024 my $Content;
4025 while(sysread(DIVSOURCE, $Content, 1024) > 0)
4027 $SRCdirective .= $Content;
4029 close(DIVSOURCE);
4032 # Executable files are executed as
4033 # `$SrcFile 'ARGV[0]' 'ARGV[1]'`
4034 elsif(-x "$SrcFile")
4036 $SRCdirective .= "print \`$SrcFile @METAvalues\`;'';\n";
4038 # Handle 'standard' files, using ProcessFile
4039 elsif((-T "$SrcFile" || $ENV{$CGI_FILE_CONTENTS})
4040 && $SrcFile =~ m@($FilePattern)$@) # A recursion
4043 # Do not process still open files because it can lead
4044 # to endless recursions
4045 if(grep(/^$SrcFile$/, @OpenFiles))
4046 { dieHandler(25, "$SrcFile allready opened (endless recursion)\n")};
4047 # Prepare meta arguments
4048 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
4049 # Process the file
4050 $SRCdirective .= "main::ProcessFile(\'$SrcFile\');'';\n";
4052 elsif($SrcFile =~ m!^([a-z]+)\://!) # URL's are loaded and printed
4054 $SRCdirective .= GET_URL($SrcFile);
4056 elsif(-T "$SrcFile") # Textfiles are "do"-ed (Perl execution)
4058 $SRCdirective .= '@ARGV = (' .$METAarguments.");\n" if $METAarguments;
4059 $SRCdirective .= "do \'$SrcFile\';'';\n";
4061 else # This one could not be resolved (should be handled by BinaryMapFile)
4063 $SRCdirective .= 'print "'.$SrcFile.' cannot be used"'."\n";
4068 # Postfix or Prefix
4069 if($Prefix)
4071 $PrefixDirective .= $SRCdirective;
4073 else
4075 $PostfixDirective .= $SRCdirective;
4078 # The prefix should be handled immediately
4079 $directive .= $PrefixDirective;
4080 $PrefixDirective = "";
4084 # Handle the content of the <SCRIPT></SCRIPT> tags
4085 # Do not process the content of <SCRIPT/>
4086 if($TagType =~ /SCRIPT/is && !$ClosedTag) # The <SCRIPT> TAG
4088 my $EndScriptTag = "";
4090 # Execute SHELL scripts with SAFEqx()
4091 if($CurrentContentType =~ /$ShellScriptContentType/is)
4093 $directive .= "SAFEqx(\'";
4096 # Extract Program
4097 while($After !~ /\<\s*\/SCRIPT[^\>]*\>/is && !eof($FileHandle))
4099 $After .= <$FileHandle>;
4100 performTranslation(\$After) if $TranslationPaths;
4103 if($After =~ /\<\s*\/SCRIPT[^\>]*\>/is)
4105 $directive .= $`;
4106 $EndScriptTag = $&;
4107 $After = $';
4109 else
4111 dieHandler(26, "Missing </SCRIPT> end tag in $ENV{'PATH_INFO'}\n");
4114 # Process only when content should be executed
4115 if($CurrentContentType)
4118 # Remove all comments from Perl scripts
4119 # (NOT from OS shell scripts)
4120 $directive =~ s/[^\\\$]\#[^\n\f\r]*([\n\f\r])/$1/g
4121 if $CurrentContentType =~ /$ServerScriptContentType/i;
4123 # Convert SCRIPT calls, ./<script>
4124 $directive =~ s@([\W]|^)\./([\S])@$1$SCRIPT_SUB$2@g;
4126 # Convert FILE calls, ~/<file>
4127 $directive =~ s@([\W])\~/([\S])@$1$HOME_SUB$2@g;
4129 # Execute SHELL scripts with SAFEqx(), closing bracket
4130 if($CurrentContentType =~ /$ShellScriptContentType/i)
4132 # Handle ''-quotes inside the script
4133 $directive =~ /SAFEqx\(\'/;
4134 $directive = $`.$&;
4135 my $Executable = $';
4136 $Executable =~ s/[\']/\\$&/gs;
4138 $directive .= $Executable."\');"; # Closing bracket
4141 else
4143 $directive = "";
4146 # Handle the content of the <DIV></DIV> tags
4147 # Do not process the content of <DIV/>
4148 elsif(($TagType eq 'DIV' || $TagType eq 'INS') && !$ClosedTag) # The <DIV> TAGs
4150 my $EndScriptTag = "";
4152 # Extract Text
4153 while($After !~ /\<\s*\/$TagType[^\>]*\>/is && !eof($FileHandle))
4155 $After .= <$FileHandle>;
4156 performTranslation(\$After) if $TranslationPaths;
4159 if($After =~ /\<\s*\/$TagType[^\>]*\>/is)
4161 $directive .= $`;
4162 $EndScriptTag = $&;
4163 $After = $';
4165 else
4167 dieHandler(27, "Missing </$TagType> end tag in $ENV{'PATH_INFO'}\n");
4170 # Add the Postfixed directives (but only when it contains something printable)
4171 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
4172 $PostfixDirective = "";
4175 # Process only when content should be handled
4176 if($CurrentContentType)
4179 # Get the name (ID), and clean it (i.e., remove anything that is NOT part of
4180 # a valid Perl name). Names should not contain $, but we can handle it.
4181 my $name = $ScriptTagAttributes{'ID'};
4182 $name =~ /^\s*[\$\@\%]?([\w\-]+)/;
4183 $name = $1;
4185 # Assign DIV contents to $NAME value OUTSIDE the CGI values!
4186 CGIexecute::defineCGIexecuteVariable($name, $directive);
4187 $directive = "";
4190 # Nothing to execute
4191 $directive = "";
4195 # Handle Foreign scripting languages
4196 if($ScriptingLanguages{$CurrentContentType})
4198 my $newDirective = "";
4199 $newDirective .= OpenForeignScript($CurrentContentType); # Only if not already done
4200 $newDirective .= PrefixForeignScript($CurrentContentType);
4201 $newDirective .= InsertForeignScript($CurrentContentType, $directive, @SRClist);
4202 $newDirective .= PostfixForeignScript($CurrentContentType);
4203 $newDirective .= CloseForeignScript($CurrentContentType); # This shouldn't be necessary
4205 $newDirective .= '"";';
4207 $directive = $newDirective;
4211 # Add the Postfixed directives (but only when it contains something printable)
4212 $directive .= "\n".$PostfixDirective if $PostfixDirective =~ /\S/;
4213 $PostfixDirective = "";
4216 # EXECUTE the script and print the results
4218 # Use this to debug the program
4219 # print STDERR "Directive $CGI_Date: \n", $directive, "\n\n";
4221 my $Result = CGIexecute->evaluate($directive) if $directive; # Evaluate as PERL code
4222 $Result =~ s/\n$//g; # Remove final newline
4224 # Print the Result of evaluating the directive
4225 # (this will handle LARGE, >64 kB output)
4226 my $BytesWritten = 1;
4227 while($Result && $BytesWritten)
4229 $BytesWritten = syswrite(STDOUT, $Result, 64);
4230 $Result = substr($Result, $BytesWritten);
4232 # print $Result; # Could be used instead of above code
4234 # Store result if wanted, i.e., if $CGIscriptorResults has been
4235 # defined in a <META> tag.
4236 push(@CGIexecute::CGIscriptorResults, $Result)
4237 if exists($default_values{'CGIscriptorResults'});
4239 # Process the rest of the input line (this could contain
4240 # another directive)
4241 $_ = $After;
4243 print $_;
4244 } while(<$FileHandle>); # Read and Test AFTER first loop!
4246 close ($FileHandle);
4247 dieHandler(28, "Error in recursion\n") unless pop(@OpenFiles) == $file_path;
4251 ###############################################################################
4253 # Call the whole package
4255 sub Handle_Request
4257 my $file_path = "";
4259 # Initialization Code
4260 Initialize_Request();
4262 # SECURITY: ACCESS CONTROL
4263 Access_Control();
4265 # Read the POST part of the query, if there is one
4266 Get_POST_part_of_query();
4268 # Start (HTML) output and logging
4269 $file_path = Initialize_output();
4271 # Check login access or divert to login procedure
4272 $Use_Login = Log_In_Access();
4273 $file_path = $Use_Login if $Use_Login;
4275 # Record which files are still open (to avoid endless recursions)
4276 my @OpenFiles = ();
4278 # Record whether the default HTML ContentType has already been printed
4279 # but only if the SERVER uses HTTP or some other protocol that might interpret
4280 # a content MIME type.
4282 $SupressContentType = !("$ENV{'SERVER_PROTOCOL'}" =~ /($ContentTypeServerProtocols)/i);
4284 # Process the specified file
4285 ProcessFile($file_path) if $file_path ne $SS_PUB;
4287 # Cleanup all open external (foreign) interpreters
4288 CloseAllForeignScripts();
4291 "" # SUCCESS
4294 # Make a single call to handle an (empty) request
4295 Handle_Request();
4298 # END OF PACKAGE MAIN
4301 ####################################################################################
4303 # The CGIEXECUTE PACKAGE
4305 ####################################################################################
4307 # Isolate the evaluation of directives as PERL code from the rest of the program.
4308 # Remember that each package has its own name space.
4309 # Note that only the FIRST argument of execute->evaluate is actually evaluated,
4310 # all other arguments are accessible inside the first argument as $_[0] to $_[$#_].
4312 package CGIexecute;
4314 sub evaluate
4316 my $self = shift;
4317 my $directive = shift;
4318 $directive = eval($directive);
4319 warn $@ if $@; # Write an error message to STDERR
4320 $directive; # Return value of directive
4324 # defineCGIexecuteVariable($name [, $value]) -> 0/1
4326 # Define and intialize variables inside CGIexecute
4327 # Does no sanity checking, for internal use only
4329 sub defineCGIexecuteVariable # ($name [, $value]) -> 0/1
4331 my $name = shift || return 0; # The Name
4332 my $value = shift || ""; # The value
4334 ${$name} = $value;
4336 return 1;
4339 # defineCGIvariable($name [, $default]) -> 0/1
4341 # Define and intialize CGI variables
4342 # Tries (in order) $ENV{$name}, the Query string and the
4343 # default value.
4344 # Removes all '-quotes etc.
4346 sub defineCGIvariable # ($name [, $default]) -> 0/1
4348 my $name = shift || return 0; # The Name
4349 my $default = shift || ""; # The default value
4351 # Remove \-quoted characters
4352 $default =~ s/\\(.)/$1/g;
4353 # Store default values
4354 $::default_values{$name} = $default if $default;
4356 # Process variables
4357 my $temp = undef;
4358 # If there is a user supplied value, it replaces the
4359 # default value.
4361 # Environment values have precedence
4362 if(exists($ENV{$name}))
4364 $temp = $ENV{$name};
4366 # Get name and its value from the query string
4367 elsif($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
4369 $temp = ::YOUR_CGIPARSE($name);
4371 # Defined values must exist for security
4372 elsif(!exists($::default_values{$name}))
4374 $::default_values{$name} = undef;
4377 # SECURITY, do not allow '- and `-quotes in
4378 # client values.
4379 # Remove all existing '-quotes
4380 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
4381 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
4382 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
4383 # If $temp is empty, use the default value (if it exists)
4384 unless($temp =~ /\S/ || length($temp) > 0) # I.e., $temp is empty
4386 $temp = $::default_values{$name};
4387 # Remove all existing '-quotes
4388 $temp =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
4389 $temp =~ s/[\']/&#8217;/igs; # Remove all single quotes
4390 $temp =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
4392 else # Store current CGI values and remove defaults
4394 $::default_values{$name} = $temp;
4396 # Define the CGI variable and its value (in the execute package)
4397 ${$name} = $temp;
4399 # return SUCCES
4400 return 1;
4403 sub defineCGIvariableList # ($name [, $default]) -> 0/1)
4405 my $name = shift || return 0; # The Name
4406 my $default = shift || ""; # The default value
4408 # Defined values must exist for security
4409 if(!exists($::default_values{$name}))
4411 $::default_values{$name} = $default;
4414 my @temp = ();
4417 # For security:
4418 # Environment values have precedence
4419 if(exists($ENV{$name}))
4421 push(@temp, $ENV{$name});
4423 # Get name and its values from the query string
4424 if($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
4426 push(@temp, ::YOUR_CGIPARSE($name, 1)); # Extract LIST
4428 else
4430 push(@temp, $::default_values{$name});
4434 # SECURITY, do not allow '- and `-quotes in
4435 # client values.
4436 # Remove all existing '-quotes
4437 @temp = map {s/([\r\f]+\n)/\n/g; $_} @temp; # Only \n is allowed
4438 @temp = map {s/[\']/&#8217;/igs; $_} @temp; # Remove all single quotes
4439 @temp = map {s/[\`]/&#8216;/igs; $_} @temp; # Remove all backtick quotes
4441 # Store current CGI values and remove defaults
4442 $::default_values{$name} = $temp[0];
4444 # Define the CGI variable and its value (in the execute package)
4445 @{$name} = @temp;
4447 # return SUCCES
4448 return 1;
4451 sub defineCGIvariableHash # ($name [, $default]) -> 0/1) Note: '$name{""} = $default';
4453 my $name = shift || return 0; # The Name
4454 my $default = shift || ""; # The default value
4456 # Defined values must exist for security
4457 if(!exists($::default_values{$name}))
4459 $::default_values{$name} = $default;
4462 my %temp = ();
4465 # For security:
4466 # Environment values have precedence
4467 if(exists($ENV{$name}))
4469 $temp{""} = $ENV{$name};
4471 # Get name and its values from the query string
4472 if($ENV{QUERY_STRING} =~ /$name/) # $name is in the query string
4474 %temp = ::YOUR_CGIPARSE($name, -1); # Extract HASH table
4476 elsif($::default_values{$name} ne "")
4478 $temp{""} = $::default_values{$name};
4482 # SECURITY, do not allow '- and `-quotes in
4483 # client values.
4484 # Remove all existing '-quotes
4485 my $Key;
4486 foreach $Key (keys(%temp))
4488 $temp{$Key} =~ s/([\r\f]+\n)/\n/g; # Only \n is allowed
4489 $temp{$Key} =~ s/[\']/&#8217;/igs; # Remove all single quotes
4490 $temp{$Key} =~ s/[\`]/&#8216;/igs; # Remove all backtick quotes
4493 # Store current CGI values and remove defaults
4494 $::default_values{$name} = $temp{""};
4496 # Define the CGI variable and its value (in the execute package)
4497 %{$name} = ();
4498 my $tempKey;
4499 foreach $tempKey (keys(%temp))
4501 ${$name}{$tempKey} = $temp{$tempKey};
4504 # return SUCCES
4505 return 1;
4509 # SAFEqx('CommandString')
4511 # A special function that is a safe alternative to backtick quotes (and qx//)
4512 # with client-supplied CGI values. All CGI variables are surrounded by
4513 # single ''-quotes (except between existing \'\'-quotes, don't try to be
4514 # too smart). All variables are then interpolated. Simple (@) lists are
4515 # expanded with join(' ', @List), and simple (%) hash tables expanded
4516 # as a list of "key=value" pairs. Complex variables, e.g., @$var, are
4517 # evaluated in a scalar context (e.g., as scalar(@$var)). All occurrences of
4518 # $@% that should NOT be interpolated must be preceeded by a "\".
4519 # If the first line of the String starts with "#! interpreter", the
4520 # remainder of the string is piped into interpreter (after interpolation), i.e.,
4521 # open(INTERPRETER, "|interpreter");print INTERPRETER remainder;
4522 # just like in UNIX. There are some problems with quotes. Be carefull in
4523 # using them. You do not have access to the output of any piped (#!)
4524 # process! If you want such access, execute
4525 # <SCRIPT TYPE="text/osshell">echo "script"|interpreter</SCRIPT> or
4526 # <SCRIPT TYPE="text/ssperl">$resultvar = SAFEqx('echo "script"|interpreter');
4527 # </SCRIPT>.
4529 # SAFEqx ONLY WORKS WHEN THE STRING ITSELF IS SURROUNDED BY SINGLE QUOTES
4530 # (SO THAT IT IS NOT INTERPOLATED BEFORE IT CAN BE PROTECTED)
4531 sub SAFEqx # ('String') -> result of executing qx/"String"/
4533 my $CommandString = shift;
4534 my $NewCommandString = "";
4536 # Only interpolate when required (check the On/Off switch)
4537 unless($CGIscriptor::NoShellScriptInterpolation)
4540 # Handle existing single quotes around CGI values
4541 while($CommandString =~ /\'[^\']+\'/s)
4543 my $CurrentQuotedString = $&;
4544 $NewCommandString .= $`;
4545 $CommandString = $'; # The remaining string
4546 # Interpolate CGI variables between quotes
4547 # (e.g., '$CGIscriptorResults[-1]')
4548 $CurrentQuotedString =~
4549 s/(^|[^\\])([\$\@])((\w*)([\{\[][\$\@\%]?[\:\w\-]+[\}\]])*)/if(exists($main::default_values{$4})){
4550 "$1".eval("$2$3")}else{"$&"}/egs;
4552 # Combine result with previous result
4553 $NewCommandString .= $CurrentQuotedString;
4555 $CommandString = $NewCommandString.$CommandString;
4557 # Select known CGI variables and surround them with single quotes,
4558 # then interpolate all variables
4559 $CommandString =~
4560 s/(^|[^\\])([\$\@\%]+)((\w*)([\{\[][\w\:\$\"\-]+[\}\]])*)/
4561 if($2 eq '$' && exists($main::default_values{$4}))
4562 {"$1\'".eval("\$$3")."\'";}
4563 elsif($2 eq '@'){$1.join(' ', @{"$3"});}
4564 elsif($2 eq '%'){my $t=$1;map {$t.=" $_=".${"$3"}{$_}}
4565 keys(%{"$3"});$t}
4566 else{$1.eval("${2}$3");
4567 }/egs;
4569 # Remove backslashed [$@%]
4570 $CommandString =~ s/\\([\$\@\%])/$1/gs;
4573 # Debugging
4574 # return $CommandString;
4576 # Handle UNIX style "#! shell command\n" constructs as
4577 # a pipe into the shell command. The output cannot be tapped.
4578 my $ReturnValue = "";
4579 if($CommandString =~ /^\s*\#\!([^\f\n\r]+)[\f\n\r]/is)
4581 my $ShellScripts = $';
4582 my $ShellCommand = $1;
4583 open(INTERPRETER, "|$ShellCommand") || dieHandler(29, "\'$ShellCommand\' PIPE not opened: &!\n");
4584 select(INTERPRETER);$| = 1;
4585 print INTERPRETER $ShellScripts;
4586 close(INTERPRETER);
4587 select(STDOUT);$| = 1;
4589 # Shell scripts which are redirected to an existing named pipe.
4590 # The output cannot be tapped.
4591 elsif($CGIscriptor::ShellScriptPIPE)
4593 CGIscriptor::printSAFEqxPIPE($CommandString);
4595 else # Plain ``-backtick execution
4597 # Execute the commands
4598 $ReturnValue = qx/$CommandString/;
4600 return $ReturnValue;
4603 ####################################################################################
4605 # The CGIscriptor PACKAGE
4607 ####################################################################################
4609 # Isolate the evaluation of CGIscriptor functions, i.e., those prefixed with
4610 # "CGIscriptor::"
4612 package CGIscriptor;
4615 # The Interpolation On/Off switch
4616 my $NoShellScriptInterpolation = undef;
4617 # The ShellScript redirection pipe
4618 my $ShellScriptPIPE = undef;
4620 # Open a named PIPE for SAFEqx to receive ALL shell scripts
4621 sub RedirectShellScript # ('CommandString')
4623 my $CommandString = shift || undef;
4625 if($CommandString)
4627 $ShellScriptPIPE = "ShellScriptNamedPipe";
4628 open($ShellScriptPIPE, "|$CommandString")
4629 || main::dieHandler(30, "\'|$CommandString\' PIPE open failed: $!\n");
4631 else
4633 close($ShellScriptPIPE);
4634 $ShellScriptPIPE = undef;
4636 return $ShellScriptPIPE;
4639 # Print to redirected shell script pipe
4640 sub printSAFEqxPIPE # ("String") -> print return value
4642 my $String = shift || undef;
4644 select($ShellScriptPIPE); $| = 1;
4645 my $returnvalue = print $ShellScriptPIPE ($String);
4646 select(STDOUT); $| = 1;
4648 return $returnvalue;
4651 # a pointer to CGIexecute::SAFEqx
4652 sub SAFEqx # ('String') -> result of qx/"String"/
4654 my $CommandString = shift;
4655 return CGIexecute::SAFEqx($CommandString);
4659 # a pointer to CGIexecute::defineCGIvariable
4660 sub defineCGIvariable # ($name[, $default]) ->0/1
4662 my $name = shift;
4663 my $default = shift;
4664 return CGIexecute::defineCGIvariable($name, $default);
4668 # Decode URL encoded arguments
4669 sub URLdecode # (URL encoded input) -> string
4671 my $output = "";
4672 my $char;
4673 my $Value;
4674 foreach $Value (@_)
4676 my $EncodedValue = $Value; # Do not change the loop variable
4677 # Convert all "+" to " "
4678 $EncodedValue =~ s/\+/ /g;
4679 # Convert all hexadecimal codes (%FF) to their byte values
4680 while($EncodedValue =~ /\%([0-9A-F]{2})/i)
4682 $output .= $`.chr(hex($1));
4683 $EncodedValue = $';
4685 $output .= $EncodedValue; # The remaining part of $Value
4687 $output;
4690 # Encode arguments as URL codes.
4691 sub URLencode # (input) -> URL encoded string
4693 my $output = "";
4694 my $char;
4695 my $Value;
4696 foreach $Value (@_)
4698 my @CharList = split('', $Value);
4699 foreach $char (@CharList)
4701 if($char =~ /\s/)
4702 { $output .= "+";}
4703 elsif($char =~ /\w\-/)
4704 { $output .= $char;}
4705 else
4707 $output .= uc(sprintf("%%%2.2x", ord($char)));
4711 $output;
4714 # Extract the value of a CGI variable from the URL-encoded $string
4715 # Also extracts the data blocks from a multipart request. Does NOT
4716 # decode the multipart blocks
4717 sub CGIparseValue # (ValueName [, URL_encoded_QueryString [, \$QueryReturnReference]]) -> Decoded value
4719 my $ValueName = shift;
4720 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
4721 my $ReturnReference = shift || undef;
4722 my $output = "";
4724 if($QueryString =~ /(^|\&)$ValueName\=([^\&]*)(\&|$)/)
4726 $output = URLdecode($2);
4727 $$ReturnReference = $' if ref($ReturnReference);
4729 # Get multipart POST or PUT methods
4730 elsif($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
4732 my $MultipartType = $2;
4733 my $BoundaryString = $3;
4734 # Remove the boundary-string
4735 my $temp = $QueryString;
4736 $temp =~ /^\Q--$BoundaryString\E/m;
4737 $temp = $';
4739 # Identify the newline character(s), this is the first character in $temp
4740 my $NewLine = "\r\n"; # Actually, this IS the correct one
4741 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
4743 # Is this correct??? I have to check.
4744 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
4745 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
4746 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
4747 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
4750 # search through all data blocks
4751 while($temp =~ /^\Q--$BoundaryString\E/m)
4753 my $DataBlock = $`;
4754 $temp = $';
4755 # Get the empty line after the header
4756 $DataBlock =~ /$NewLine$NewLine/;
4757 $Header = $`;
4758 $output = $';
4759 my $Header = $`;
4760 $output = $';
4762 # Remove newlines from the header
4763 $Header =~ s/$NewLine/ /g;
4765 # Look whether this block is the one you are looking for
4766 # Require the quotes!
4767 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
4769 my $i;
4770 for($i=length($NewLine); $i; --$i)
4772 chop($output);
4774 # OK, get out
4775 last;
4777 # reinitialize the output
4778 $output = "";
4780 $$ReturnReference = $temp if ref($ReturnReference);
4782 elsif($QueryString !~ /(^|\&)$ValueName\=/) # The value simply isn't there
4784 return undef;
4785 $$ReturnReference = undef if ref($ReturnReference);
4787 else
4789 print "ERROR: $ValueName $main::ENV{'CONTENT_TYPE'}\n";
4791 return $output;
4795 # Get a list of values for the same ValueName. Uses CGIparseValue
4797 sub CGIparseValueList # (ValueName [, URL_encoded_QueryString]) -> List of decoded values
4799 my $ValueName = shift;
4800 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
4801 my @output = ();
4802 my $RestQueryString;
4803 my $Value;
4804 while($QueryString &&
4805 (($Value = CGIparseValue($ValueName, $QueryString, \$RestQueryString))
4806 || defined($Value)))
4808 push(@output, $Value);
4809 $QueryString = $RestQueryString; # QueryString is consumed!
4811 # ready, return list with values
4812 return @output;
4815 sub CGIparseValueHash # (ValueName [, URL_encoded_QueryString]) -> Hash table of decoded values
4817 my $ValueName = shift;
4818 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
4819 my $RestQueryString;
4820 my %output = ();
4821 while($QueryString && $QueryString =~ /(^|\&)$ValueName([\w]*)\=/)
4823 my $Key = $2;
4824 my $Value = CGIparseValue("$ValueName$Key", $QueryString, \$RestQueryString);
4825 $output{$Key} = $Value;
4826 $QueryString = $RestQueryString; # QueryString is consumed!
4828 # ready, return list with values
4829 return %output;
4832 sub CGIparseForm # ([URL_encoded_QueryString]) -> Decoded Form (NO multipart)
4834 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
4835 my $output = "";
4837 $QueryString =~ s/\&/\n/g;
4838 $output = URLdecode($QueryString);
4840 $output;
4843 # Extract the header of a multipart CGI variable from the POST input
4844 sub CGIparseHeader # (ValueName [, URL_encoded_QueryString]) -> Decoded value
4846 my $ValueName = shift;
4847 my $QueryString = shift || $main::ENV{'QUERY_STRING'};
4848 my $output = "";
4850 if($main::ENV{'CONTENT_TYPE'} =~ m@(multipart/([\w\-]+)\;\s+boundary\=([\S]+))@i)
4852 my $MultipartType = $2;
4853 my $BoundaryString = $3;
4854 # Remove the boundary-string
4855 my $temp = $QueryString;
4856 $temp =~ /^\Q--$BoundaryString\E/m;
4857 $temp = $';
4859 # Identify the newline character(s), this is the first character in $temp
4860 my $NewLine = "\r\n"; # Actually, this IS the correct one
4861 unless($temp =~ /^(\-\-|\r\n)/) # However, you never realy can be sure
4863 $NewLine = "\n" if $temp =~ /^([\n])/; # Single Line Feed
4864 $NewLine = "\r" if $temp =~ /^([\r])/; # Single Return
4865 $NewLine = "\r\n" if $temp =~ /^(\r\n)/; # Double (CRLF, the correct one)
4866 $NewLine = "\n\r" if $temp =~ /^(\n\r)/; # Double
4869 # search through all data blocks
4870 while($temp =~ /^\Q--$BoundaryString\E/m)
4872 my $DataBlock = $`;
4873 $temp = $';
4874 # Get the empty line after the header
4875 $DataBlock =~ /$NewLine$NewLine/;
4876 $Header = $`;
4877 my $Header = $`;
4879 # Remove newlines from the header
4880 $Header =~ s/$NewLine/ /g;
4882 # Look whether this block is the one you are looking for
4883 # Require the quotes!
4884 if($Header =~ /name\s*=\s*[\"\']$ValueName[\"\']/m)
4886 $output = $Header;
4887 last;
4889 # reinitialize the output
4890 $output = "";
4893 return $output;
4897 # Checking variables for security (e.g., file names and email addresses)
4898 # File names are tested against the $::FileAllowedChars and $::BlockPathAccess variables
4899 sub CGIsafeFileName # FileName -> FileName or ""
4901 my $FileName = shift || "";
4902 return "" if $FileName =~ m?[^$::FileAllowedChars]?;
4903 return "" if $FileName =~ m!(^|/|\:)[\-\.]!;
4904 return "" if $FileName =~ m@\.\.\Q$::DirectorySeparator\E@; # Higher directory not allowed
4905 return "" if $FileName =~ m@\Q$::DirectorySeparator\E\.\.@; # Higher directory not allowed
4906 return "" if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@; # Invisible (blocked) file
4908 return $FileName;
4911 sub CGIsafeEmailAddress # email -> email or ""
4913 my $Email = shift || "";
4914 return "" unless $Email =~ m/^[\w\.\-]+[\@][\w\.\-\:]+$/;
4915 return $Email;
4918 # Get a URL from the web. Needs main::GET_URL($URL) function
4919 # (i.e., curl, snarf, or wget)
4920 sub read_url # ($URL) -> page/file
4922 my $URL = shift || return "";
4924 # Get the commands to read the URL, do NOT add a print command
4925 my $URL_command = main::GET_URL($URL, 1);
4926 # execute the commands, i.e., actually read it
4927 my $URLcontent = CGIexecute->evaluate($URL_command);
4929 # Ready, return the content.
4930 return $URLcontent;
4933 ################################################>>>>>>>>>>Start Remove
4935 # BrowseAllDirs(Directory, indexfile)
4937 # usage:
4938 # <SCRIPT TYPE='text/ssperl'>
4939 # CGIscriptor::BrowseAllDirs('Sounds', 'index.html', '\.wav$')
4940 # </SCRIPT>
4942 # Allows to browse all directories. Stops at '/'. If the directory contains
4943 # an indexfile, eg, index.html, that file will be used instead. Files must match
4944 # the $Pattern, if it is given. Default is
4945 # CGIscriptor::BrowseAllDirs('/', 'index.html', '')
4947 sub BrowseAllDirs # (Directory, indexfile, $Pattern) -> Print HTML code
4949 my $Directory = shift || '/';
4950 my $indexfile = shift || 'index.html';
4951 my $Pattern = shift || '';
4952 $Directory =~ s!/$!!g;
4954 # If the index directory exists, use that one
4955 if(-s "$::CGI_HOME$Directory/$indexfile")
4957 return main::ProcessFile("$::CGI_HOME$Directory/$indexfile");
4960 # No indexfile, continue
4961 my @DirectoryList = glob("$::CGI_HOME$Directory");
4962 $CurrentDirectory = shift(@DirectoryList);
4963 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
4964 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
4965 print "<h1>";
4966 print "$CurrentDirectory" if $CurrentDirectory;
4967 print "</h1>\n";
4969 opendir(BROWSE, "$::CGI_HOME$Directory") || main::dieHandler(31, "$::CGI_HOME$Directory $!");
4970 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
4972 # Print directories
4973 my $file;
4974 print "<pre><ul TYPE='NONE'>\n";
4975 foreach $file (@AllFiles)
4977 next unless -d "$::CGI_HOME$Directory/$file";
4978 # Check whether this file should be visible
4979 next if $::BlockPathAccess &&
4980 "$Directory/$file/" =~ m@$::BlockPathAccess@;
4981 print "<dt><a href='$Directory/$file'>$file</a></dt>\n";
4983 print "</ul></pre>\n";
4985 # Print files
4986 print "<pre><ul TYPE='CIRCLE'>\n";
4987 my $TotalSize = 0;
4988 foreach $file (@AllFiles)
4990 next if $file =~ /^\./;
4991 next if -d "$::CGI_HOME$Directory/$file";
4992 next if -l "$::CGI_HOME$Directory/$file";
4993 # Check whether this file should be visible
4994 next if $::BlockPathAccess &&
4995 "$Directory/$file" =~ m@$::BlockPathAccess@;
4997 if(!$Pattern || $file =~ m@$Pattern@)
4999 my $Date = localtime($^T - (-M "$::CGI_HOME$Directory/$file")*3600*24);
5000 my $Size = -s "$::CGI_HOME$Directory/$file";
5001 $Size = sprintf("%6.0F kB", $Size/1024);
5002 my $Type = `file $::CGI_HOME$Directory/$file`;
5003 $Type =~ s@\s*$::CGI_HOME$Directory/$file\s*\:\s*@@ig;
5004 chomp($Type);
5006 print "<li>";
5007 print "<a href='$Directory/$file'>";
5008 printf("%-40s", "$file</a>");
5009 print "\t$Size\t$Date\t$Type";
5010 print "</li>\n";
5013 print "</ul></pre>";
5015 return 1;
5019 ################################################
5021 # BrowseDirs(RootDirectory [, Pattern, Start])
5023 # usage:
5024 # <SCRIPT TYPE='text/ssperl'>
5025 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', 'Speech', 'DIRECTORY')
5026 # </SCRIPT>
5028 # Allows to browse subdirectories. Start should be relative to the RootDirectory,
5029 # e.g., the full path of the directory 'Speech' is '~/Sounds/Speech'.
5030 # Only files which fit /$Pattern/ and directories are displayed.
5031 # Directories down or up the directory tree are supplied with a
5032 # GET request with the name of the CGI variable in the fourth argument (default
5033 # is 'BROWSEDIRS'). So the correct call for a subdirectory could be:
5034 # CGIscriptor::BrowseDirs('Sounds', '\.aifc$', $DIRECTORY, 'DIRECTORY')
5036 sub BrowseDirs # (RootDirectory [, Pattern, Start, CGIvariable, HTTPserver]) -> Print HTML code
5038 my $RootDirectory = shift; # || return 0;
5039 my $Pattern = shift || '\S';
5040 my $Start = shift || "";
5041 my $CGIvariable = shift || "BROWSEDIRS";
5042 my $HTTPserver = shift || '';
5044 $Start = CGIscriptor::URLdecode($Start); # Sometimes, too much has been encoded
5045 $Start =~ s@//+@/@g;
5046 $Start =~ s@[^/]+/\.\.@@ig;
5047 $Start =~ s@^\.\.@@ig;
5048 $Start =~ s@/\.$@@ig;
5049 $Start =~ s!/+$!!g;
5050 $Start .= "/" if $Start;
5052 my @Directory = glob("$::CGI_HOME/$RootDirectory/$Start");
5053 $CurrentDirectory = shift(@Directory);
5054 $CurrentDirectory = $' if $CurrentDirectory =~ m@(/\.\./)+@;
5055 $CurrentDirectory =~ s@^$::CGI_HOME@@g;
5056 print "<h1>";
5057 print "$CurrentDirectory" if $CurrentDirectory;
5058 print "</h1>\n";
5059 opendir(BROWSE, "$::CGI_HOME/$RootDirectory/$Start") || main::dieHandler(31, "$::CGI_HOME/$RootDirectory/$Start $!");
5060 my @AllFiles = sort grep(!/^([\.]+[^\.]|\~)/, readdir(BROWSE));
5062 # Print directories
5063 my $file;
5064 print "<pre><ul TYPE='NONE'>\n";
5065 foreach $file (@AllFiles)
5067 next unless -d "$::CGI_HOME/$RootDirectory/$Start$file";
5068 # Check whether this file should be visible
5069 next if $::BlockPathAccess &&
5070 "/$RootDirectory/$Start$file/" =~ m@$::BlockPathAccess@;
5072 my $NewURL = $Start ? "$Start$file" : $file;
5073 $NewURL = CGIscriptor::URLencode($NewURL);
5074 print "<dt><a href='";
5075 print "$ENV{SCRIPT_NAME}" if $ENV{SCRIPT_NAME} !~ m@[^\w+\-/]@;
5076 print "$ENV{PATH_INFO}?$CGIvariable=$NewURL'>$file</a></dt>\n";
5078 print "</ul></pre>\n";
5080 # Print files
5081 print "<pre><ul TYPE='CIRCLE'>\n";
5082 my $TotalSize = 0;
5083 foreach $file (@AllFiles)
5085 next if $file =~ /^\./;
5086 next if -d "$::CGI_HOME/$RootDirectory/$Start$file";
5087 next if -l "$::CGI_HOME/$RootDirectory/$Start$file";
5088 # Check whether this file should be visible
5089 next if $::BlockPathAccess &&
5090 "$::CGI_HOME/$RootDirectory/$Start$file" =~ m@$::BlockPathAccess@;
5092 if($file =~ m@$Pattern@)
5094 my $Date = localtime($^T - (-M "$::CGI_HOME/$RootDirectory/$Start$file")*3600*24);
5095 my $Size = -s "$::CGI_HOME/$RootDirectory/$Start$file";
5096 $Size = sprintf("%6.0F kB", $Size/1024);
5097 my $Type = `file $::CGI_HOME/$RootDirectory/$Start$file`;
5098 $Type =~ s@\s*$::CGI_HOME/$RootDirectory/$Start$file\s*\:\s*@@ig;
5099 chomp($Type);
5101 print "<li>";
5102 if($HTTPserver =~ /^\s*[\.\~]\s*$/)
5104 print "<a href='$RootDirectory/$Start$file'>";
5106 elsif($HTTPserver)
5108 print "<a href='$HTTPserver/$RootDirectory/$Start$file'>";
5110 printf("%-40s", "$file</a>") if $HTTPserver;
5111 printf("%-40s", "$file") unless $HTTPserver;
5112 print "\t$Size\t$Date\t$Type";
5113 print "</li>\n";
5116 print "</ul></pre>";
5118 return 1;
5122 # ListDocs(Pattern [,ListType])
5124 # usage:
5125 # <SCRIPT TYPE=text/ssperl>
5126 # CGIscriptor::ListDocs("/*", "dl");
5127 # </SCRIPT>
5129 # This subroutine is very usefull to manage collections of independent
5130 # documents. The resulting list will display the tree-like directory
5131 # structure. If this routine is too slow for online use, you can
5132 # store the result and use a link to that stored file.
5134 # List HTML and Text files with title and first header (HTML)
5135 # or filename and first meaningfull line (general text files).
5136 # The listing starts at the ServerRoot directory. Directories are
5137 # listed recursively.
5139 # You can change the list type (default is dl).
5140 # e.g.,
5141 # <dt><a href=<file.html>>title</a>
5142 # <dd>First Header
5143 # <dt><a href=<file.txt>>file.txt</a>
5144 # <dd>First meaningfull line of text
5146 sub ListDocs # ($Pattern [, prefix]) e.g., ("/Books/*", [, "dl"])
5148 my $Pattern = shift;
5149 $Pattern =~ /\*/;
5150 my $ListType = shift || "dl";
5151 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
5152 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
5153 my @FileList = glob("$::CGI_HOME$Pattern");
5154 my ($FileName, $Path, $Link);
5156 # Print List markers
5157 print "<$ListType>\n";
5159 # Glob all files
5160 File: foreach $FileName (@FileList)
5162 # Check whether this file should be visible
5163 next if $::BlockPathAccess && $FileName =~ m@$::BlockPathAccess@;
5165 # Recursively list files in all directories
5166 if(-d $FileName)
5168 $FileName =~ m@([^/]*)$@;
5169 my $DirName = $1;
5170 print "<$Prefix>$DirName\n";
5171 $Pattern =~ m@([^/]*)$@;
5172 &ListDocs("$`$DirName/$1", $ListType);
5173 next;
5175 # Use textfiles
5176 elsif(-T "$FileName")
5178 open(TextFile, $FileName) || next;
5180 # Ignore all other file types
5181 else
5182 { next;};
5184 # Get file path for link
5185 $FileName =~ /$::CGI_HOME/;
5186 print "<$Prefix><a href=$URL_root$'>";
5187 # Initialize all variables
5188 my $Line = "";
5189 my $TitleFound = 0;
5190 my $Caption = "";
5191 my $Title = "";
5192 # Read file and step through
5193 while(<TextFile>)
5195 chop $_;
5196 $Line = $_;
5197 # HTML files
5198 if($FileName =~ /\.ht[a-zA-Z]*$/i)
5200 # Catch Title
5201 while(!$Title)
5203 if($Line =~ m@<title>([^<]*)</title>@i)
5205 $Title = $1;
5206 $Line = $';
5208 else
5210 $Line .= <TextFile> || goto Print;
5211 chop $Line;
5214 # Catch First Header
5215 while(!$Caption)
5217 if($Line =~ m@</h1>@i)
5219 $Caption = $`;
5220 $Line = $';
5221 $Caption =~ m@<h1>@i;
5222 $Caption = $';
5223 $Line = $`.$Caption.$Line;
5225 else
5227 $Line .= <TextFile> || goto Print;
5228 chop $Line;
5232 # Other text files
5233 else
5235 # Title equals file name
5236 $FileName =~ /([^\/]+)$/;
5237 $Title = $1;
5238 # Catch equals First Meaningfull line
5239 while(!$Caption)
5241 if($Line =~ /[A-Z]/ &&
5242 ($Line =~ /subject|title/i || $Line =~ /^[\w,\.\s\?\:]+$/)
5243 && $Line !~ /Newsgroup/ && $Line !~ /\:\s*$/)
5245 $Line =~ s/\<[^\>]+\>//g;
5246 $Caption = $Line;
5248 else
5250 $Line = <TextFile> || goto Print;
5254 Print: # Print title and subject
5255 print "$Title</a>\n";
5256 print "<dd>$Caption\n" if $ListType eq "dl";
5257 $TitleFound = 0;
5258 $Caption = "";
5259 close TextFile;
5260 next File;
5263 # Print Closing List Marker
5264 print "</$ListType>\n";
5265 ""; # Empty return value
5269 # HTMLdocTree(Pattern [,ListType])
5271 # usage:
5272 # <SCRIPT TYPE=text/ssperl>
5273 # CGIscriptor::HTMLdocTree("/Welcome.html", "dl");
5274 # </SCRIPT>
5276 # The following subroutine is very usefull for checking large document
5277 # trees. Starting from the root (s), it reads all files and prints out
5278 # a nested list of links to all attached files. Non-existing or misplaced
5279 # files are flagged. This is quite a file-i/o intensive routine
5280 # so you would not like it to be accessible to everyone. If you want to
5281 # use the result, save the whole resulting page to disk and use a link
5282 # to this file.
5284 # HTMLdocTree takes an HTML file or file pattern and constructs nested lists
5285 # with links to *local* files (i.e., only links to the local server are
5286 # followed). The list entries are the document titles.
5287 # If the list type is <dl>, the first <H1> header is used too.
5288 # For each file matching the pattern, a list is made recursively of all
5289 # HTML documents that are linked from it and are stored in the same directory
5290 # or a sub-directory. Warnings are given for missing files.
5291 # The listing starts for the ServerRoot directory.
5292 # You can change the default list type <dl> (<dl>, <ul>, <ol>).
5294 %LinkUsed = ();
5296 sub HTMLdocTree # ($Pattern [, listtype])
5297 # e.g., ("/Welcome.html", [, "ul"])
5299 my $Pattern = shift;
5300 my $ListType = shift || "dl";
5301 my $Prefix = lc($ListType) eq "dl" ? "dt" : "li";
5302 my $URL_root = "http://$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}";
5303 my ($Filename, $Path, $Link);
5304 my %LocalLinks = {};
5306 # Read files (glob them for expansion of wildcards)
5307 my @FileList = glob("$::CGI_HOME$Pattern");
5308 foreach $Path (@FileList)
5310 # Get URL_path
5311 $Path =~ /$::CGI_HOME/;
5312 my $URL_path = $';
5313 # Check whether this file should be visible
5314 next if $::BlockPathAccess && $URL_path =~ m@$::BlockPathAccess@;
5316 my $Title = $URL_path;
5317 my $Caption = "";
5318 # Current file should not be used again
5319 ++$LinkUsed{$URL_path};
5320 # Open HTML doc
5321 unless(open(TextFile, $Path))
5323 print "<$Prefix>$Title <blink>(not found)</blink><br>\n";
5324 next;
5326 while(<TextFile>)
5328 chop $_;
5329 $Line = $_;
5330 # Catch Title
5331 while($Line =~ m@<title>@i)
5333 if($Line =~ m@<title>([^<]*)</title>@i)
5335 $Title = $1;
5336 $Line = $';
5338 else
5340 $Line .= <TextFile>;
5341 chop $Line;
5344 # Catch First Header
5345 while(!$Caption && $Line =~ m@<h1>@i)
5347 if($Line =~ m@</h[1-9]>@i)
5349 $Caption = $`;
5350 $Line = $';
5351 $Caption =~ m@<h1>@i;
5352 $Caption = $';
5353 $Line = $`.$Caption.$Line;
5355 else
5357 $Line .= <TextFile>;
5358 chop $Line;
5361 # Catch and print Links
5362 while($Line =~ m@<a href\=([^>]*)>@i)
5364 $Link = $1;
5365 $Line = $';
5366 # Remove quotes
5367 $Link =~ s/\"//g;
5368 # Remove extras
5369 $Link =~ s/[\#\?].*$//g;
5370 # Remove Servername
5371 if($Link =~ m@(http://|^)@i)
5373 $Link = $';
5374 # Only build tree for current server
5375 next unless $Link =~ m@$::ENV{'SERVER_NAME'}|^/@;
5376 # Remove server name and port
5377 $Link =~ s@^[^\/]*@@g;
5379 # Store the current link
5380 next if $LinkUsed{$Link} || $Link eq $URL_path;
5381 ++$LinkUsed{$Link};
5382 ++$LocalLinks{$Link};
5386 close TextFile;
5387 print "<$Prefix>";
5388 print "<a href=http://";
5389 print "$::ENV{'SERVER_NAME'}\:$::ENV{'SERVER_PORT'}$URL_path>";
5390 print "$Title</a>\n";
5391 print "<br>$Caption\n"
5392 if $Caption && $Caption ne $Title && $ListType =~ /dl/i;
5393 print "<$ListType>\n";
5394 foreach $Link (keys(%LocalLinks))
5396 &HTMLdocTree($Link, $ListType);
5398 print "</$ListType>\n";
5402 ###########################<<<<<<<<<<End Remove
5404 # Make require happy
5407 =head1 NAME
5409 CGIscriptor -
5411 =head1 DESCRIPTION
5413 A flexible HTML 4 compliant script/module for CGI-aware
5414 embeded Perl, shell-scripts, and other scripting languages,
5415 executed at the server side.
5417 =head1 README
5419 Executes embeded Perl code in HTML pages with easy
5420 access to CGI variables. Also processes embeded shell
5421 scripts and scripts in any other language with an
5422 interactive interpreter (e.g., in-line Python, Tcl,
5423 Ruby, Awk, Lisp, Xlispstat, Prolog, M4, R, REBOL, Praat,
5424 sh, bash, csh, ksh).
5426 CGIscriptor is very flexible and hides all the specifics
5427 and idiosyncrasies of correct output and CGI coding and naming.
5428 CGIscriptor complies with the W3C HTML 4.0 recommendations.
5430 This Perl program will run on any WWW server that runs
5431 Perl scripts, just add a line like the following to your
5432 srm.conf file (Apache example):
5434 ScriptAlias /SHTML/ /real-path/CGIscriptor.pl/
5436 URL's that refer to http://www.your.address/SHTML/... will
5437 now be handled by CGIscriptor.pl, which can use a private
5438 directory tree (default is the DOCUMENT_ROOT directory tree,
5439 but it can be anywhere).
5441 =head1 PREREQUISITES
5444 =head1 COREQUISITES
5447 =pod OSNAMES
5449 Linux, *BSD, *nix, MS WinXP
5451 =pod SCRIPT CATEGORIES
5453 Servers
5457 =cut