"Fossies" - the Fresh Open Source Software Archive

Member "ApaLogFilter-0.99.010/ApaLogFilter.pl" (7 Sep 2006, 38350 Bytes) of package /linux/www/old/ApaLogFilter-0.99.010.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Perl source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 #!/usr/bin/perl -w
    2 ###############################################################################
    3 ##                                                                           ##
    4 ##   A P A L O G F I L T E R . P L                                           ##
    5 ##                                                                           ##
    6 ## ------------------------------------------------------------------------- ##
    7 ##                                                                           ##
    8 ##  Job .......: Script to filter and transform an existing apache-logfile.  ##
    9 ##                                                                           ##
   10 ## ------------------------------------------------------------------------- ##
   11 ##                                                                           ##
   12 ##  Copyright (C) 2000  Daniel Scheibli                                      ##
   13 ##                                                                           ##
   14 ##  This program is free software; you can redistribute it and/or modify it  ##
   15 ##  under the terms of the GNU General Public License as published by the    ##
   16 ##  Free Software Foundation; either version 2 of the License, or (at your   ##
   17 ##  option) any later version.                                               ##
   18 ##                                                                           ##
   19 ##  This program is distributed in the hope that it will be useful, but      ##
   20 ##  WITHOUT ANY WARRANTY; without even the implied warranty of               ##
   21 ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            ##
   22 ##  GNU General Public License for more details.                             ##
   23 ##                                                                           ##
   24 ##  You should have received a copy of the GNU General Public License along  ##
   25 ##  with this program; if not, write to the Free Software Foundation, Inc.,  ##
   26 ##  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.                 ##
   27 ##                                                                           ##
   28 ## ------------------------------------------------------------------------- ##
   29 ##                                                                           ##
   30 ##  Author .....: Daniel Scheibli <daniel@scheibli.com>                      ##
   31 ##  Date .......: 2000-05-17                                                 ##
   32 ##  Changes ....: ----- ApaLogFilter 0.99.010 ------------------------------ ##
   33 ##                2006-09-03 o Updated code layout and program outputs.      ##
   34 ##                ----- ApaLogFilter 0.99.009 ------------------------------ ##
   35 ##                2002-08-06 o Improved performance by differenciating the   ##
   36 ##                             RequestUrl.n.Value content into cases with    ##
   37 ##                             STRING and with REGEXP value.                 ##
   38 ##                ----- ApaLogFilter 0.99.008 ------------------------------ ##
   39 ##                2001-03-12 o Added prototype for function main().          ##
   40 ##                2001-09-18 o FEATURE: Added new keyword $ALL for           ##
   41 ##                             RequestUrl.n.Title                            ##
   42 ##                ----- ApaLogFilter 0.99.007 ------------------------------ ##
   43 ##                2001-01-22 o FEATURE: Now possible, to use RegExp's for    ##
   44 ##                             Remote Host field.                            ##
   45 ##                ----- ApaLogFilter 0.99.006 ------------------------------ ##
   46 ##                2000-12-30 o FEATURE: Added feature to allow all records   ##
   47 ##                             to go to the outfile when there are no        ##
   48 ##                             RegExp. entry (for matching) are defined      ##
   49 ##                             at all.                                       ##
   50 ##                2000-12-31 o Changed all comments in code to english.      ##
   51 ##                2000-12-31 o Changed the documentation.                    ##
   52 ##                ----- ApaLogFilter 0.99.005 ------------------------------ ##
   53 ##                2000-12-29 o BUGFIX: Negative TZ not gets accepted.        ##
   54 ##                ----- ApaLogFilter 0.99.004 ------------------------------ ##
   55 ##                2000-12-03 o Changed the license to GPL.                   ##
   56 ##                2000-12-03 o Changed the documentation.                    ##
   57 ##                ----- ApaLogFilter 0.99.003 ------------------------------ ##
   58 ##                2000-08-14 o FEATURE: Added the function of replacing      ##
   59 ##                             HTTP statuscodes.                             ##
   60 ##                ----- ApaLogFilter 0.99.002 ------------------------------ ##
   61 ##                2000-06-12 o Changed comments in code.                     ##
   62 ##                2000-06-13 o FEATURE: Added the function of a seperate     ##
   63 ##                             ExceptionFile.                                ##
   64 ##                                                                           ##
   65 ###############################################################################
   66 
   67 
   68 
   69 # Presettings
   70 
   71 use English;
   72 use strict;
   73 
   74 use File::Basename;
   75 use FindBin qw( $Bin );
   76 use IO::File;
   77 
   78 sub TRUE    { 1 }
   79 sub FALSE   { 0 }
   80 sub NULL    { undef }   # DANGER: Never change this!!!
   81 sub OK      { 0 }
   82 sub WARNING { 2 }
   83 sub ERROR   { 4 }
   84 
   85 sub Program_Init_ParseArguments();
   86 sub Program_Init_ConfigRead();
   87 sub Program_Action_ProcessAccessLog();
   88 
   89 my %hGlobalVars = (
   90     "Program.Rc"                      => 0,                         # Return-Code
   91     "Program.Version"                 => "APALOGFILTER 0.99.010",   # Version-Token
   92     "Program.Environment.NewLine"     => "\n",                      # NewLine-Token for the current environment
   93     "Program.Environment.Seperator"   => "/",                       # PathSeperator-Token for the current environment
   94     "Program.Obj.CfgFile.Name"        => "ApaLogFilter.cfg",        # Name of the Configurationfile
   95     "Program.Param.InputFileName"     => "",                        # Name of the Apache-Logfile to process (INPUT)
   96     "Program.Param.OutputFileName"    => "",                        # Name of the Apache-Logfile for the results (OUTPUT)
   97     "Program.Param.ExceptionFileName" => "",                        # Name of the Exception-Logfile for Exception-Rows
   98     "Program.Config.RemoteHost"       => [],                        # Settings for the "Client"-field
   99     "Program.Config.RequestUrl"       => [],                        # Settings for the "URL"-field
  100     "Program.Config.Status"           => [],                        # Settings for the "Status"-field
  101     "Program.Config.ReferredUrl"      => []                         # Settings for the "ReferredURL"-field
  102 );
  103 $hGlobalVars{"Program.Obj.CfgFile.Name"} = $Bin.
  104     $hGlobalVars{"Program.Environment.Seperator"}.
  105     $hGlobalVars{"Program.Obj.CfgFile.Name"};
  106 
  107 
  108 
  109 # Program initialization
  110 
  111 printf( "%s%s", 
  112     $hGlobalVars{"Program.Version"}, 
  113     $hGlobalVars{"Program.Environment.NewLine"} );
  114 
  115 Program_Init_ParseArguments();
  116 Program_Init_ConfigRead();
  117 
  118 printf( "INFO: Number of RequestUrl entries = %s%s", 
  119     scalar@{$hGlobalVars{"Program.Config.RequestUrl"}}, 
  120     $hGlobalVars{"Program.Environment.NewLine"} );
  121 
  122 
  123 
  124 # Process the web server's access log file
  125 
  126 Program_Action_ProcessAccessLog();
  127 
  128 exit( OK );
  129 
  130 
  131 
  132 
  133 
  134 ###############################################################################
  135 ##                                                                           ##
  136 ##   P R O G R A M _ I N I T _ P A R S E A R G U M E N T S                   ##
  137 ##                                                                           ##
  138 ###############################################################################
  139 sub Program_Init_ParseArguments() {
  140     
  141     # Presettings
  142 
  143     my( $sCallRc );
  144 
  145 
  146 
  147     # Process the parameters/arguments given to the program
  148 
  149     my @aParamMarkerDefinitions = (
  150         {
  151             "HashKey"    => "Program.Param.InputFileName",
  152             "ArgToken"   => "-in",
  153             "Definition" => "STRING:Y"
  154         },{
  155             "HashKey"    => "Program.Param.OutputFileName",
  156             "ArgToken"   => "-out",
  157             "Definition" => "STRING:Y"
  158         },{
  159             "HashKey"    => "Program.Param.ExceptionFileName",
  160             "ArgToken"   => "-exf",
  161             "Definition" => "STRING:Y"
  162         }             
  163     );
  164     
  165     $sCallRc = ParseArguments( \@ARGV, \@aParamMarkerDefinitions, \%hGlobalVars );
  166 
  167     if( $sCallRc != OK ) {
  168     
  169         my( @aRows );
  170         
  171         push( @aRows, "===> ERROR: Unknown arguments (Rc: ".$sCallRc.")." );
  172         push( @aRows, "" );
  173         push( @aRows, "Call ........: >>--- ApaLogFilter.pl --- -in FILE --- -out FILE --->" );
  174         push( @aRows, "" );
  175         push( @aRows, "                >--- -exf FILE ---><" );
  176         push( @aRows, "" );
  177         push( @aRows, "Sample ......: ApaLogFilter.pl -in access.log -out access.flg -exf access.err" );
  178         push( @aRows, "" );
  179         push( @aRows, "Arguments ...: -in FILE    Full qualified name of the AccessLogFile" );
  180         push( @aRows, "                           to process." );
  181         push( @aRows, "               -out FILE   Full qualified name of the ResultFile." );
  182         push( @aRows, "               -exf FILE   Full qualified name of the ExceptionFile." );  
  183         push( @aRows, "" );
  184         
  185         foreach( @aRows ) { 
  186             printf( "%s%s", $_, $hGlobalVars{"Program.Environment.NewLine"} );
  187         }
  188 
  189         exit( ERROR );      
  190     }
  191 }
  192 
  193 
  194 
  195 
  196 
  197 ###############################################################################
  198 ##                                                                           ##
  199 ##   P R O G R A M _ I N I T _ C O N F I G R E A D                           ##
  200 ##                                                                           ##
  201 ###############################################################################
  202 sub Program_Init_ConfigRead() {
  203 
  204     # Presettings
  205 
  206     my( $oCfgFile, $rhKeyValue, $sIndex, $sSubIndex );
  207     my( $rhReqExpArray, $rhStringArray );
  208     my( $sCallRc, $sDummy, $I );
  209 
  210 
  211 
  212     # Open and read the ConfigFile
  213 
  214     $rhKeyValue = ParseConfigurationFile( $hGlobalVars{"Program.Obj.CfgFile.Name"} );
  215     if( ! ref( $rhKeyValue ) ) {
  216         printf( "===> ERROR: Unable to open/read the ConfigFile (Rc: %s)%s", $sCallRc, $hGlobalVars{"Program.Environment.NewLine"} );
  217         exit( ERROR );
  218     }
  219 
  220 
  221 
  222     # Process the settings for the "RemoteHost"-field
  223 
  224     for( $sIndex = 1; ; $sIndex++ ) {
  225         
  226         # If there is a RemoteHost.<n> Entry,...
  227         
  228         if( defined( $rhKeyValue->{"REMOTEHOST.".$sIndex.".VALUE"} ) ) {
  229 
  230             # Ensure, that the settings (RemoteHost.<n>.Value) are ok
  231                 
  232             if( $rhKeyValue->{"REMOTEHOST.".$sIndex.".VALUE"} eq "" ) {
  233                 printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} );
  234                 printf( "            The value of REMOTEHOST.%s.VALUE is a nullstring.%s", $sIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  235                 exit( ERROR );
  236             }
  237 
  238             # Ensure, that the settings (RemoteHost.<n>.Action) are ok
  239                 
  240             unless( ( defined( $rhKeyValue->{"REMOTEHOST.".$sIndex.".ACTION"} ) ) &&
  241                 ( $rhKeyValue->{"REMOTEHOST.".$sIndex.".ACTION"} eq "EXCLUDE_RECORD" ) ) {
  242                 printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} );
  243                 printf( "            The value of REMOTEHOST.%s.ACTION is invalid or undefined.%s", $sIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  244                 exit( ERROR );
  245             }
  246     
  247             # Added the settings
  248                 
  249             push( @{$hGlobalVars{"Program.Config.RemoteHost"}},
  250                 {
  251                     "VALUE"  => $rhKeyValue->{"REMOTEHOST.".$sIndex.".VALUE"},
  252                     "ACTION" => $rhKeyValue->{"REMOTEHOST.".$sIndex.".ACTION"}
  253                 }
  254             );
  255         }
  256         else {
  257             last;
  258         }
  259     }  
  260 
  261 
  262 
  263     # Process the settings for the "RequestUrl"-field
  264 
  265     for( $sIndex = 1; ; $sIndex++ ) {
  266         
  267         # If there is a RequestUrl.<n> Entry,...
  268             
  269         if( defined( $rhKeyValue->{"REQUESTURL.".$sIndex.".TITLE"} ) ) {
  270             
  271             # Ensure, that the settings (RequestUrl.<n>.Title) are ok
  272 
  273             if( $rhKeyValue->{"REQUESTURL.".$sIndex.".TITLE"} eq "" ) {
  274                 printf( "===> ERRROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  275                 printf( "             The value of REQUESTURL.%s.TITLE is a nullstring.%s", $sIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  276                 exit( ERROR );
  277             }
  278 
  279             # Walk thrue all possible URL's / ReqExp's
  280                 
  281             $rhReqExpArray = [];
  282             $rhStringArray = [];
  283                 
  284             for( $sSubIndex = 1; ; $sSubIndex++ ) {
  285                     
  286                 # If there is a RequestUrl.<n>.Value.<m> Entry,...
  287                     
  288                 if( ( defined( $rhKeyValue->{"REQUESTURL.".$sIndex.".VALUE.".$sSubIndex} ) ) &&
  289                     ( defined( $rhKeyValue->{"REQUESTURL.".$sIndex.".TYPE.".$sSubIndex} ) ) ) {
  290                         
  291                     # Ensure, that the settings (RequestUrl.<n>.Value.<m>) are ok
  292                         
  293                     if( $rhKeyValue->{"REQUESTURL.".$sIndex.".VALUE.".$sSubIndex} eq "" ) {
  294                         printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  295                         printf( "            The value of REQUESTURL.%s.VALUE.%s is invalid or undefined.%s", $sIndex, $sSubIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  296                         exit( ERROR );
  297                     }
  298                         
  299                     # Ensure, that the settings (RequestUrl.<n>.Type.<m>) are ok     
  300                         
  301                     if( $rhKeyValue->{"REQUESTURL.".$sIndex.".TYPE.".$sSubIndex} eq "" ) {
  302                         printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  303                         printf( "            The value of REQUESTURL.%s.TYPE.%s is invalid or undefined.%s", $sIndex, $sSubIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  304                         exit( ERROR );
  305                     }
  306 
  307                     # Added the settings
  308                     
  309                     if( $rhKeyValue->{"REQUESTURL.".$sIndex.".TYPE.".$sSubIndex} eq "STRING" ) {
  310                         push( @{$rhStringArray}, $rhKeyValue->{"REQUESTURL.".$sIndex.".VALUE.".$sSubIndex} );
  311                     }
  312                     elsif( $rhKeyValue->{"REQUESTURL.".$sIndex.".TYPE.".$sSubIndex} eq "REGEXP" ) {
  313                         push( @{$rhReqExpArray}, $rhKeyValue->{"REQUESTURL.".$sIndex.".VALUE.".$sSubIndex} );
  314                     }
  315                     else {
  316                         printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  317                         printf( "            The value of REQUESTURL.%s.TYPE.%s is invalid or undefined.%s", $sIndex, $sSubIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  318                         exit( ERROR );
  319                     }      
  320                 }
  321                 else {
  322                     last;
  323                 }
  324             }
  325     
  326             # Ensure, that there is a minimum of one URL / ReqExp per RequestUrl.<n>.Title entry
  327                 
  328             if( ( scalar@{$rhStringArray} < 1 ) && ( scalar@{$rhReqExpArray} < 1) ) {
  329                 printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} );
  330                 printf( "            No entry for REQUESTURL.%s.VALUE.<n> found.%s", $sIndex, $sSubIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  331                 exit( ERROR );
  332             }
  333     
  334             # Added the settings
  335             
  336             push( @{$hGlobalVars{"Program.Config.RequestUrl"}},
  337                 {
  338                     "TITLE"  => $rhKeyValue->{"REQUESTURL.".$sIndex.".TITLE"},
  339                     "STRING" => $rhStringArray,
  340                     "REGEXP" => $rhReqExpArray
  341                 }
  342             );    
  343         }
  344         else {
  345             last;
  346         }
  347     }  
  348 
  349 
  350 
  351     # Process the settings for the "Status"-field
  352 
  353     for( $sIndex = 1; ; $sIndex++ ) {
  354 
  355         # If there is a Status.<n> Entry,...
  356             
  357         if( defined( $rhKeyValue->{"STATUS.".$sIndex.".VALUE"} ) ) {
  358             
  359             # Ensure, that the settings (Status.<n>.Value) are ok
  360                 
  361             if( $rhKeyValue->{"STATUS.".$sIndex.".VALUE"} eq "" ) {
  362                 printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} );
  363                 printf( "            The value of STATUS.%s.VALUE is a nullstring.%s", $sIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  364                 exit( ERROR );
  365             }
  366 
  367             # Ensure, that the settings (Status.<n>.Action) are ok
  368                 
  369             unless( ( defined( $rhKeyValue->{"STATUS.".$sIndex.".ACTION"} ) ) &&
  370                 ( ( $rhKeyValue->{"STATUS.".$sIndex.".ACTION"} eq "FORCE_RECORD_IN_EXCEPTION_LIST" ) ||
  371                   ( $rhKeyValue->{"STATUS.".$sIndex.".ACTION"} eq "REPLACE_VALUE" ) ) ) {
  372                 printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  373                 printf( "            The value of STATUS.%s.ACTION is invalid or undefined.%s", $sIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  374                 exit( ERROR );
  375             }
  376 
  377             # Ensure, that the there is an NewValue for the Action "Replace_Value"
  378                 
  379             if( $rhKeyValue->{"STATUS.".$sIndex.".ACTION"} eq "REPLACE_VALUE" ) {
  380                 unless( ( defined( $rhKeyValue->{"STATUS.".$sIndex.".NEWVALUE"} ) ) &&
  381                     ( $rhKeyValue->{"STATUS.".$sIndex.".NEWVALUE"} ne "" ) ) {
  382                     printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  383                     printf( "            The value of STATUS.%s.NEWVALUE is invalid or undefined.%s", $sIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  384                     exit( ERROR );
  385                 }
  386             }
  387     
  388             # Added the settings
  389                 
  390             push( @{$hGlobalVars{"Program.Config.Status"}},
  391                 {
  392                     "VALUE"    => $rhKeyValue->{"STATUS.".$sIndex.".VALUE"},
  393                     "ACTION"   => $rhKeyValue->{"STATUS.".$sIndex.".ACTION"},
  394                     "NEWVALUE" => $rhKeyValue->{"STATUS.".$sIndex.".NEWVALUE"}
  395                 }
  396             );
  397         }
  398         else {
  399             last;
  400         }
  401     }  
  402 
  403 
  404 
  405     # Process the settings for the "ReferredUrl"-field
  406 
  407     for( $sIndex = 1; ; $sIndex++ ) {
  408 
  409         # If there is a ReferredUrl.<n> Entry,...
  410             
  411         if( defined( $rhKeyValue->{"REFERREDURL.".$sIndex.".VALUE"} ) ) {
  412 
  413             # Ensure, that the settings (ReferredUrl.<n>.Value) are ok
  414                 
  415             if( $rhKeyValue->{"REFERREDURL.".$sIndex.".VALUE"} eq "" ) {
  416                 printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  417                 printf( "            The value of REFERREDURL.%s.VALUE is a nullstring.%s", $sIndex, $hGlobalVars{"Program.Environment.NewLine"} );      
  418                 exit( ERROR );
  419             }
  420 
  421             # Ensure, that the settings (ReferredUrl.<n>.Action) are ok
  422 
  423             unless( ( defined( $rhKeyValue->{"REFERREDURL.".$sIndex.".ACTION"} ) ) &&
  424                 ( $rhKeyValue->{"REFERREDURL.".$sIndex.".ACTION"} eq "REPLACE_WITH_BLANK" ) ) {
  425                 printf( "===> ERROR: The ConfigFile contains an invalid entry.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  426                 printf( "            The value of REFERREDURL.%s.ACTION is invalid or undefined.%s", $sIndex, $hGlobalVars{"Program.Environment.NewLine"} );
  427                 exit( ERROR );
  428             }
  429     
  430             # Added the settings
  431                 
  432             push( @{$hGlobalVars{"Program.Config.ReferredUrl"}},
  433                 {
  434                     "VALUE"  => $rhKeyValue->{"REFERREDURL.".$sIndex.".VALUE"},
  435                     "ACTION" => $rhKeyValue->{"REFERREDURL.".$sIndex.".ACTION"}
  436                 }
  437             );
  438         }
  439         else {
  440             last;
  441         }
  442     }  
  443 }
  444 
  445 
  446 
  447 
  448 
  449 ###############################################################################
  450 ##                                                                           ##
  451 ##   P R O G R A M _ A C T I O N _ P R O C E S S A C C E S S L O G           ##
  452 ##                                                                           ##
  453 ###############################################################################
  454 sub Program_Action_ProcessAccessLog() {
  455 
  456     # Presettings
  457     
  458     my(
  459         $sClient,   $sIdentUser, $sAuthUser,
  460         $sDate,     $sTime,      $sTz,
  461         $sMethod,   $sUrl,       $sProtocol,
  462         $sStatus,   $sBytes,
  463         $sReferred, $sBrowser
  464     );
  465     my( $sRowNo, $sRow );    
  466     my( $sFound );
  467     my( $oFhOut, $oFhException, $oFhIn, $oFhDummy );
  468     my( $sCallRc, $sDummy, $I, $J, @aDummy );
  469 
  470 
  471 
  472     # Open output file
  473 
  474     $sDummy = 0;
  475     $oFhOut = new IO::File;
  476     $oFhOut->open( $hGlobalVars{"Program.Param.OutputFileName"}, O_WRONLY | O_CREAT | O_TRUNC ) or $sDummy = 1; 
  477     
  478     if( $sDummy != 0 ) { 
  479         printf( "===> ERROR: Unable to open the ResultFile.%s", $hGlobalVars{"Program.Environment.NewLine"} );
  480         exit( ERROR );       
  481     }
  482     $oFhOut->autoflush( 1 );
  483  
  484 
  485 
  486     # Open exception file
  487         
  488     $sDummy = 0;
  489     $oFhException = new IO::File;
  490     $oFhException->open( $hGlobalVars{"Program.Param.ExceptionFileName"}, O_WRONLY | O_CREAT | O_TRUNC ) or $sDummy = 1; 
  491         
  492     if( $sDummy != 0 ) {
  493         printf( "===> ERROR: Unable to open the ExceptionFile.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  494         exit( ERROR );
  495     }
  496     $oFhException->autoflush(1);
  497 
  498 
  499  
  500     # Open the web server access log file
  501 
  502     $sDummy = 0;
  503     $oFhIn  = new IO::File;
  504     $oFhIn->open( $hGlobalVars{"Program.Param.InputFileName"}, O_RDONLY ) or $sDummy = 1;
  505 
  506     if( $sDummy != 0 ) {
  507         printf( "===> ERROR: Unable to open the AccessLogFile.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  508         exit( ERROR );
  509     }
  510 
  511 
  512 
  513     # Walk through the web server access log files
  514         
  515     READ_LOOP: for(;;) {
  516         
  517         # Presettings
  518         
  519         $sRowNo++;
  520         if( $sRowNo % 500 == 0 ) {
  521             printf( "Processing row number %ld ...%s", $sRowNo, $hGlobalVars{"Program.Environment.NewLine"} );
  522         }
  523         $oFhDummy = $oFhOut;
  524 
  525         # Read next row
  526             
  527         $_ = $oFhIn->getline();
  528         if( defined( $_ ) ) {
  529             chomp( $_ );
  530         }
  531         else {
  532             last READ_LOOP;
  533         }
  534 
  535         # Split of the aktual row into the contained tokens/fields
  536             
  537         ( $sClient,   $sIdentUser, $sAuthUser,
  538             $sDate,     $sTime,      $sTz,
  539             $sMethod,   $sUrl,       $sProtocol,
  540             $sStatus,   $sBytes,
  541             $sReferred, $sBrowser ) = /^(\S+) (\S+) (\S+) \[(\S+):(\d+:\d+:\d+) (\S+)\] "(\S+) (.*?) (\S+)" (\S+) (\S+) "(.*?)" "(.*?)"/;
  542 
  543         # Ensure, that all fields could be reed
  544             
  545         if( ! defined( $sBrowser ) ) {
  546             printf( "===> ERROR: Corrupted Record (RowNo: %i).%s", $sRowNo, $hGlobalVars{"Program.Environment.NewLine"} );
  547             exit( ERROR );
  548         }
  549 
  550         ANALYSE_LOOP: for(;;) {
  551             
  552             # Walk thrue the settings for status field
  553                 
  554             for( $I = 0; $I < scalar@{$hGlobalVars{"Program.Config.Status"}}; $I++ ) {
  555                 
  556                 # If status matches,...
  557                     
  558                 if( $hGlobalVars{"Program.Config.Status"}->[$I]->{"VALUE"} eq $sStatus ) {
  559                     
  560                     # If transfer of row to exception file is wanted,...
  561                         
  562                     if( $hGlobalVars{"Program.Config.Status"}->[$I]->{"ACTION"} eq "FORCE_RECORD_IN_EXCEPTION_LIST" ) {
  563                         $oFhDummy = $oFhException;
  564                         last ANALYSE_LOOP;
  565                     }
  566                         
  567                     # If replacement of the value is wanted,...
  568                         
  569                     if( $hGlobalVars{"Program.Config.Status"}->[$I]->{"ACTION"} eq "REPLACE_VALUE" ) {
  570                         $sStatus = $hGlobalVars{"Program.Config.Status"}->[$I]->{"NEWVALUE"};
  571                     }
  572                 }      
  573             }   
  574 
  575    
  576             # Walk thrue the settings for remote host field (only for EXCLUDE)
  577                 
  578             for( $I = 0; $I < scalar@{$hGlobalVars{"Program.Config.RemoteHost"}}; $I++ ) {
  579                 
  580                 # Presettings
  581                         
  582                 $sDummy = $hGlobalVars{"Program.Config.RemoteHost"}->[$I]->{"VALUE"};
  583                         
  584                 # If remote host matches,...
  585                     
  586                 if( $sClient =~ /^$sDummy$/ ) {
  587                     
  588                     # If EXCLUDE is wanted,...
  589                         
  590                     if( $hGlobalVars{"Program.Config.RemoteHost"}->[$I]->{"ACTION"} eq "EXCLUDE_RECORD" ) {
  591                         next READ_LOOP;
  592                     }
  593                 }
  594             }
  595 
  596             # Walk thrue the settings for request url
  597                 
  598             $sFound = 0;
  599             LOOP3: for( $I = 0; $I < scalar@{$hGlobalVars{"Program.Config.RequestUrl"}}; $I++ ) {
  600                 for( $J = 0; $J < scalar@{$hGlobalVars{"Program.Config.RequestUrl"}->[$I]->{"STRING"}}; $J++ ) {
  601                 
  602                     # Presettings
  603                         
  604                     $sDummy = $hGlobalVars{"Program.Config.RequestUrl"}->[$I]->{"STRING"}->[$J];
  605                             
  606                     # If request url matches,...
  607                             
  608                     if( $sUrl eq $sDummy ) {
  609                             
  610                         $sFound = 1;
  611                         $sDummy = Translate( ' ', '_', undef, $hGlobalVars{"Program.Config.RequestUrl"}->[$I]->{"TITLE"} );
  612                         if( index( $sDummy, '$ALL' ) >= 0 ) {
  613                             $sDummy = $sUrl;
  614                         }
  615                         elsif( index( $sDummy, '$FILE' ) >= 0 ) {
  616                             $sDummy = Replace( '\$FILE', basename( $sUrl ), $sDummy );
  617                         }
  618                         $sUrl = $sDummy;
  619                         last LOOP3;
  620                     }
  621                 }
  622                 for( $J = 0; $J < scalar@{$hGlobalVars{"Program.Config.RequestUrl"}->[$I]->{"REGEXP"}}; $J++ ) {
  623                     
  624                     # Presettings
  625                         
  626                     $sDummy = $hGlobalVars{"Program.Config.RequestUrl"}->[$I]->{"REGEXP"}->[$J];
  627                     
  628                     # If request url matches,...
  629                         
  630                     if( $sUrl =~ /^$sDummy$/ ) {
  631                         
  632                         $sFound = 1;
  633                         $sDummy = Translate( ' ', '_', undef, $hGlobalVars{"Program.Config.RequestUrl"}->[$I]->{"TITLE"} );
  634                         if( index( $sDummy, '$ALL' ) >= 0 ) {
  635                             $sDummy = $sUrl;
  636                         }
  637                         elsif( index( $sDummy, '$FILE' ) >= 0 ) {
  638                             $sDummy = Replace( '\$FILE', basename( $sUrl ), $sDummy );
  639                         }
  640                         $sUrl = $sDummy;
  641                         last LOOP3;
  642                     }
  643                 }
  644             }
  645 
  646             # If current row doesn't match any defined Request-URL,...
  647 
  648             if( ( $sFound == 0 ) && ( scalar@{$hGlobalVars{"Program.Config.RequestUrl"}} > 0 ) ) {
  649                 next READ_LOOP;
  650             }
  651 
  652             # Walk thrue the settings for referred url (for REPLACE)
  653                 
  654             for( $I = 0; $I < scalar@{$hGlobalVars{"Program.Config.ReferredUrl"}}; $I++ ) {
  655                 
  656                 # If referred url matches and replacement is wanted,...
  657                     
  658                 if( ( index( $sReferred, $hGlobalVars{"Program.Config.ReferredUrl"}->[$I]->{"VALUE"} ) == 0 ) &&
  659                     ( $hGlobalVars{"Program.Config.ReferredUrl"}->[$I]->{"ACTION"} eq "REPLACE_WITH_BLANK" ) ) {
  660                     $sReferred = "-";
  661                 }
  662             }
  663                 
  664             last ANALYSE_LOOP;
  665         }
  666 
  667         # Assemble the new row in CLF format
  668             
  669         $sRow = $sClient;
  670         $sRow = $sRow." ".  $sIdentUser;
  671         $sRow = $sRow." ".  $sAuthUser;
  672         $sRow = $sRow." [". $sDate;
  673         $sRow = $sRow.":".  $sTime;
  674         $sRow = $sRow." ".  $sTz;
  675         $sRow = $sRow.'] "'.$sMethod;
  676         $sRow = $sRow." ".  $sUrl;
  677         $sRow = $sRow." ".  $sProtocol;   
  678         $sRow = $sRow.'" '. $sStatus;   
  679         $sRow = $sRow." ".  $sBytes;  
  680         $sRow = $sRow.' "'. $sReferred;
  681         $sRow = $sRow.'" "'.$sBrowser;
  682         $sRow = $sRow.'"';   
  683 
  684         # Write new row to output / exception file
  685             
  686         $sDummy = 0;
  687         $oFhDummy->printf( "%s%s", $sRow, $hGlobalVars{"Program.Environment.NewLine"} ) or $sDummy = 1;
  688             
  689         if( $sDummy != 0 ) {
  690             printf( "===> ERROR: Unable to write into the Result/ExceptionFile.%s", $hGlobalVars{"Program.Environment.NewLine"} ); 
  691             exit( ERROR );
  692         }   
  693     }
  694         
  695     close($oFhIn);
  696     close($oFhException);  
  697     close($oFhOut);
  698 }
  699 
  700 
  701 
  702 
  703 
  704 ###############################################################################
  705 ##                                                                           ##
  706 ##   P A R S E C O N F I G U R A T I O N F I L E                             ##
  707 ##                                                                           ##
  708 ###############################################################################
  709 sub ParseConfigurationFile($) {
  710 
  711     # NOTE: This  code is very basic, but works and allows one to
  712     # parse a file like the one described in the box (the standard
  713     # Perl functions are not sufficient for that).
  714 
  715     # TODO: Have a second look at the available CPAN modules.
  716     
  717     # +------------------------------------------------------------------+
  718     # |  #BEGIN                                                          |
  719     # |  # CommentToken                                                  |
  720     # |  KeyToken1.KeyToken2 = ValueToken1 ValueToken2 \ # CommentToken  |
  721     # |                        ValueToken3               # CommentToken  |
  722     # |  #END                                                            |
  723     # +------------------------------------------------------------------+
  724 
  725 
  726 
  727     # Presettings
  728 
  729     my( $sFileName ) = @_;  
  730     my $oFh = undef;
  731     my $rhKeyValueRows = {};
  732     my $sDummy;
  733 
  734 
  735 
  736     # Open the configuration file
  737 
  738     $sDummy = OK;
  739     $oFh = new IO::File;
  740     $oFh->open( $sFileName, O_RDONLY ) or $sDummy = ERROR;
  741     
  742     if( $sDummy != OK ) {
  743         $oFh = undef;
  744         return( -5 );
  745     }
  746 
  747 
  748  
  749     # Walk thrue all rows in config file
  750 
  751     my( $sBeginRowFlag, $sEndRowFlag, $sRowNo ) = ( FALSE, FALSE, 0 );
  752     my( $sCurrentRowOrg );
  753     my( $sCurrentRowKeyValue, $sCurrentRowComment );
  754     my( $sCurrentRowKey, $sCurrentRowValue );
  755     my( $sCommentPos, $sEqualPos );
  756     my( $I, $sBackSlashesNoOf );
  757     my( $sKey4FollowRow, $sRowNo2Follow ) = ( "", -1 );
  758     
  759     while( defined( $sCurrentRowOrg = $oFh->getline() ) ) {
  760                 
  761         # Presettings
  762             
  763         chomp( $sCurrentRowOrg );
  764         $sCommentPos = index( $sCurrentRowOrg, '#' );
  765         $sRowNo++;   
  766    
  767         # Cut out the comment from row
  768             
  769         if( $sCommentPos < 0 ) {
  770             $sCommentPos = length( $sCurrentRowOrg );
  771         }
  772         $sCurrentRowKeyValue = Strip( substr( $sCurrentRowOrg, 0, $sCommentPos ) );
  773         $sCurrentRowComment  = Strip( Space( 0, ' ', uc( substr( $sCurrentRowOrg, $sCommentPos ) ) ) );
  774 
  775         # Check for pure comment row
  776             
  777         if( ( $sCurrentRowKeyValue eq "" ) && ( $sCurrentRowComment ne "" ) ) {
  778             $sRowNo--;
  779             next;
  780         }
  781             
  782         # Check for key/value or value containing rows
  783             
  784         elsif( $sCurrentRowKeyValue ne "" ) {
  785             
  786             # Presettings
  787                 
  788             $sEqualPos = index( $sCurrentRowKeyValue, '=' );
  789             
  790             # If row is a key/value row,...
  791             
  792             if( $sEqualPos >= 0 ) {
  793                 
  794                 # Split the row into key- and value-part
  795                 
  796                 $sCurrentRowKey   = Strip( uc( substr( $sCurrentRowKeyValue, 0, $sEqualPos ) ) );   
  797                 $sCurrentRowValue = Strip( substr( $sCurrentRowKeyValue, $sEqualPos+1 ) );
  798                     
  799                 # Ensure, that there is a valid key name
  800                     
  801                 if( $sCurrentRowKey eq "" ) {
  802                     return( -3 );
  803                 }
  804                     
  805                 # Get the number of backslashes at the end of row
  806                     
  807                 $sBackSlashesNoOf = 0;
  808                     
  809                 for( $I = length( $sCurrentRowValue ); $I > 0; $I-- ) {
  810                     if( substr( $sCurrentRowValue, $I-1, 1 ) eq '\\' ) {
  811                         $sBackSlashesNoOf++;
  812                     }
  813                     else {
  814                         last;
  815                     }
  816                 }
  817                     
  818                 # Process the indicator for a following row (number of backslashes = 1,3,5,...)
  819                 
  820                 if( $sBackSlashesNoOf % 2 != 0 ) {
  821                     $sCurrentRowValue = Strip( substr( $sCurrentRowValue, 0, length( $sCurrentRowValue ) - 1 ) );
  822                     $sKey4FollowRow   = $sCurrentRowKey;
  823                     $sRowNo2Follow    = $sRowNo;
  824                 }
  825                     
  826                 # Write key/value into the hash (replace duplicated backslashes with single once)
  827                     
  828                 $sCurrentRowValue = Replace( '\\\\\\\\', '\\\\', $sCurrentRowValue );
  829                 $$rhKeyValueRows{$sCurrentRowKey} = $sCurrentRowValue;
  830                 next;
  831             }
  832                 
  833             # Otherwise (row is treated as a value row),...
  834                 
  835             else {
  836                 
  837                 # Ensure, that a following row is wanted
  838                     
  839                 if( ( $sKey4FollowRow eq "" ) || ( ( $sRowNo2Follow + 1 ) != $sRowNo ) ) {
  840                     return( -4 );
  841                 }
  842                     
  843                 # Split row into key- and value- token
  844                     
  845                 $sCurrentRowKey   = "";
  846                 $sCurrentRowValue = $sCurrentRowKeyValue;
  847                     
  848                 # Get number of backslashes
  849                     
  850                 $sBackSlashesNoOf = 0;
  851                     
  852                 for( $I = length( $sCurrentRowValue ); $I > 0; $I-- ) {
  853                     if( substr( $sCurrentRowValue, $I-1, 1 ) eq '\\' ) {
  854                         $sBackSlashesNoOf++;
  855                     }
  856                     else {
  857                         last;
  858                     }
  859                 }
  860                     
  861                 # Process the indication for following row (number of backslashes = 1,3,5,...)
  862                     
  863                 if( $sBackSlashesNoOf % 2 != 0 ) {
  864                     $sCurrentRowValue = Strip( substr( $sCurrentRowValue, 0, length( $sCurrentRowValue ) - 1 ) );
  865                     $sRowNo2Follow    = $sRowNo;
  866                 }
  867                 else {
  868                     $sRowNo2Follow = -1;
  869                 }
  870 
  871                 # Write key/value into hash (replace duplicated bashslashes with single once)
  872                     
  873                 $sCurrentRowValue = Replace( '\\\\\\\\', '\\\\', $sCurrentRowValue );
  874                 $$rhKeyValueRows{$sKey4FollowRow} = $$rhKeyValueRows{$sKey4FollowRow}.$sCurrentRowValue;
  875                 
  876                 next;
  877             }
  878         }
  879             
  880         # Otherwise (empty row),...
  881             
  882         else {
  883             $sRowNo2Follow = -1;
  884             next;
  885         }
  886     }
  887 
  888 
  889 
  890     # Close file when needed and return the key/value pairs
  891 
  892     if( defined( $oFh ) ) {
  893         close( $oFh );
  894     }
  895 
  896     return( $rhKeyValueRows );
  897 } 
  898 
  899 
  900 
  901 
  902 
  903 ###############################################################################
  904 ##                                                                           ##
  905 ##   P A R S E A R G U M E N T S                                             ##
  906 ##                                                                           ##
  907 ###############################################################################
  908 sub ParseArguments($$$) {
  909 
  910     # Presettings
  911 
  912     my( $raArguments, $raMarkerDefinitions, $rhTargetHash ) = @_;
  913     my( $I, $sIndexPos, $sDummy, @aDummy, %hArgTokenIndex );
  914 
  915 
  916 
  917     # Check and extent the given list of parameter markers
  918         
  919     for( $I = 0; $I < scalar@$raMarkerDefinitions; $I++ ) {
  920         
  921         # Check the definition of the parameter markers
  922         
  923         @aDummy = split( ":", $$raMarkerDefinitions[$I]->{"Definition"} );
  924         foreach( @aDummy ) {
  925             $_ = Strip( uc( $_ ) );
  926         }
  927   
  928         # Check for (enough) definitions per parameter marker
  929         
  930         if( ( ! defined( $aDummy[0] ) ) || ( ! defined( $aDummy[1] ) ) ) {
  931             return( -1 );
  932         }
  933             
  934         # Check for valid type of parameter marker
  935         
  936         if( ( $aDummy[0] ne "FLAG" ) && ( $aDummy[0] ne "BOOLEAN" ) && ( $aDummy[0] ne "STRING" ) ) {
  937             return( -2 );
  938         }
  939             
  940         # Check for settings if parameter marker is a must in the list of arguments,...
  941             
  942         if( $aDummy[1] eq "Y" ) {
  943             $aDummy[1] = TRUE;
  944         }
  945         elsif( $aDummy[1] eq "N" ) {
  946             $aDummy[1] = FALSE;
  947         }
  948         elsif( $aDummy[0] eq "FLAG" ) {
  949             $aDummy[1] = FALSE;
  950         }
  951         else {
  952             return( -3 );
  953         }
  954 
  955         # Extent the list of parameter markers
  956         
  957         $$raMarkerDefinitions[$I]->{"FoundCounter"}    = 0;
  958         $$raMarkerDefinitions[$I]->{"Def.Type"}        = $aDummy[0];
  959         $$raMarkerDefinitions[$I]->{"Def.MustBeThere"} = $aDummy[1];
  960             
  961         # Additional Settings
  962             
  963         $hArgTokenIndex{$$raMarkerDefinitions[$I]->{"ArgToken"}} = $I;
  964     }
  965 
  966 
  967 
  968     # Walk thrue the given list of arguments
  969         
  970     for( $I = 0; $I < scalar@$raArguments; $I++ ) {
  971         
  972         # If argument matches one of the parameter markers,...
  973             
  974         if( defined( $hArgTokenIndex{$$raArguments[$I]} ) ) {
  975             
  976             # Presettings
  977                 
  978             $sIndexPos = $hArgTokenIndex{$$raArguments[$I]};
  979             $$raMarkerDefinitions[$sIndexPos]->{"FoundCounter"}++;
  980    
  981             # Do processing depending on the defined type for the parameter marker
  982             
  983             if( $$raMarkerDefinitions[$sIndexPos]->{"Def.Type"} eq "FLAG" ) {
  984                 $$raMarkerDefinitions[$sIndexPos]->{"Value"} = TRUE;
  985             }
  986             elsif( $$raMarkerDefinitions[$sIndexPos]->{"Def.Type"} eq "BOOLEAN" ) {
  987                     
  988                 # Ensure, that a arguments follows which is not a parameter marker
  989                     
  990                 if( ( $I + 1 ) < scalar@$raArguments ) {
  991                     $sDummy = $hArgTokenIndex{$$raArguments[$I+1]};
  992                     if( defined( $sDummy ) ) {
  993                         return( -6 );
  994                     }
  995                 }
  996                 else {
  997                     return( -5 );
  998                 }
  999                     
 1000                 # Process the parameter marker following argument
 1001                 
 1002                 $sDummy = uc( $$raArguments[$I+1] );
 1003                 if( ( $sDummy eq "ON" )  || ( $sDummy eq "YES" ) || ( $sDummy eq "TRUE" ) ) {
 1004                     $$raMarkerDefinitions[$sIndexPos]->{"Value"} = TRUE;
 1005                 }
 1006                 elsif( ( $sDummy eq "OFF" ) || ( $sDummy eq "NO" )  || ( $sDummy eq "FALSE" ) ) {
 1007                     $$raMarkerDefinitions[$sIndexPos]->{"Value"} = FALSE;
 1008                 }
 1009                 else {
 1010                     return( -7 );
 1011                 }
 1012                 
 1013                 # Increase the argument index (to skip the second argument)
 1014                     
 1015                 $I++;
 1016             }
 1017             elsif( $$raMarkerDefinitions[$sIndexPos]->{"Def.Type"} eq "STRING" ) {
 1018             
 1019                 # Ensure, that a arguments follows which is not a parameter marker
 1020                     
 1021                 if( ( $I + 1 ) < scalar@$raArguments ) {
 1022                     $sDummy = $hArgTokenIndex{$$raArguments[$I+1]};
 1023                     if( defined( $sDummy ) ) {
 1024                         return( -8 );
 1025                     }
 1026                 }
 1027                 else {
 1028                     return( -9 );
 1029                 }
 1030                     
 1031                 # Process the parameter marker following argument
 1032                     
 1033                 $$raMarkerDefinitions[$sIndexPos]->{"Value"} = $$raArguments[$I+1];
 1034                     
 1035                 # Increase the argument index (to skip the second argument)
 1036                     
 1037                 $I++;
 1038             }
 1039             else {
 1040                 return( -4 );
 1041             }
 1042         }
 1043         else {
 1044             return( -10 );
 1045         }
 1046     }
 1047 
 1048 
 1049 
 1050     # Ensure, that all "must" parameter markers are found in argument list
 1051         
 1052     for( $I = 0; $I < scalar@$raMarkerDefinitions; $I++ ) {
 1053         
 1054         # Check if every "must" parameter marker was found
 1055             
 1056         if( ( $$raMarkerDefinitions[$I]->{"Def.MustBeThere"} ) &&
 1057             ( $$raMarkerDefinitions[$I]->{"FoundCounter"} <= 0 ) ) {
 1058             return( -11 );
 1059         }
 1060             
 1061         # Check if a "must" parameter marker was found more than one time
 1062             
 1063         if( $$raMarkerDefinitions[$I]->{"FoundCounter"} > 1 ) {
 1064             return( -12 );
 1065         }
 1066     }
 1067 
 1068 
 1069 
 1070     # Write the values/defaults for the defined parameter markers
 1071         
 1072     for( $I = 0; $I < scalar@$raMarkerDefinitions; $I++ ) {
 1073             
 1074         # If parameter marker was found in arguments,...
 1075         
 1076         if( $$raMarkerDefinitions[$I]->{"FoundCounter"} > 0 ) {
 1077             $$rhTargetHash{$$raMarkerDefinitions[$I]->{"HashKey"}} = $$raMarkerDefinitions[$I]->{"Value"};
 1078         }
 1079             
 1080         # If parameter marker wasn't found in arguments (but has a default),...
 1081             
 1082         elsif( exists( $$raMarkerDefinitions[$I]->{"Default"} ) ) {
 1083             $$rhTargetHash{$$raMarkerDefinitions[$I]->{"HashKey"}} = $$raMarkerDefinitions[$I]->{"Default"};
 1084         }
 1085     }    
 1086 
 1087 
 1088     
 1089     return( OK );
 1090 }
 1091 
 1092 
 1093 
 1094 
 1095 
 1096 ###############################################################################
 1097 ##                                                                           ##
 1098 ##   R E P L A C E                                                           ##
 1099 ##                                                                           ##
 1100 ###############################################################################
 1101 sub Replace($$@) {
 1102 
 1103     my( $sFromPattern, $sToPattern, @aRc ) = @_;
 1104     my $sStmt;
 1105 
 1106     for( @aRc ) {
 1107         $sStmt="s/$sFromPattern/$sToPattern/g;";
 1108         eval $sStmt;  
 1109     }
 1110 
 1111     return wantarray ? @aRc : $aRc[0]; 
 1112 }
 1113 
 1114 
 1115 
 1116 
 1117 
 1118 ###############################################################################
 1119 ##                                                                           ##
 1120 ##   S P A C E                                                               ##
 1121 ##                                                                           ##
 1122 ###############################################################################
 1123 sub Space($$@) {
 1124 
 1125     my( $sNoOfSpaces, $sReplaceChar, @aRc ) = @_;
 1126     my $sReplaceToken = ( ( defined( $sReplaceChar ) ) ? $sReplaceChar : " " ) x $sNoOfSpaces;
 1127 
 1128     for( @aRc ) {
 1129         s/\s+/$sReplaceToken/g;
 1130     }
 1131 
 1132     return wantarray ? @aRc : $aRc[0];
 1133 }
 1134 
 1135 
 1136 
 1137 
 1138 
 1139 ###############################################################################
 1140 ##                                                                           ##
 1141 ##   S T R I P                                                               ##
 1142 ##                                                                           ##
 1143 ###############################################################################
 1144 sub Strip(@) {
 1145 
 1146     my @aArray = @_;
 1147 
 1148     foreach( @aArray ) { 
 1149         s/^\s+//;
 1150         s/\s+$//;
 1151     }
 1152 
 1153     return wantarray ? @aArray : $aArray[0];
 1154 }
 1155 
 1156 
 1157 
 1158 
 1159 
 1160 ###############################################################################
 1161 ##                                                                           ##
 1162 ##   T R A N S L A T E                                                       ##
 1163 ##                                                                           ##
 1164 ###############################################################################
 1165 sub Translate($$$@) {
 1166 
 1167     my( $sFromPattern, $sToPattern, $sOption, @aRc ) = @_;
 1168     my $sStmt;
 1169 
 1170     if( !defined( $sOption ) ) {
 1171         $sOption = "";
 1172     }
 1173 
 1174     for( @aRc ) {
 1175         $sStmt = "tr/$sFromPattern/$sToPattern/$sOption;";
 1176         eval $sStmt;
 1177     }
 1178 
 1179     return wantarray ? @aRc : $aRc[0];
 1180 }
 1181 
 1182 
 1183 
 1184