File Coverage

blib/lib/Apache/WeSQL.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


";
line stmt bran cond sub pod time code
1             package Apache::WeSQL;
2              
3 1     1   13158 use 5.006;
  1         4  
  1         48  
4 1     1   5 use strict;
  1         3  
  1         32  
5 1     1   5 use warnings;
  1         14  
  1         37  
6 1     1   1217 use lib(".");
  1         860  
  1         6  
7 1     1   130 use lib("./WeSQL/");
  1         1  
  1         5  
8              
9 1     1   1018 use POSIX qw(strftime);
  1         7817  
  1         6  
10              
11 1     1   1642 use Apache::WeSQL::SqlFunc qw( :all );
  0            
  0            
12             use Apache::WeSQL::Journalled qw( :all );
13             use Apache::WeSQL::Display qw( :all );
14             use Apache::WeSQL::Auth qw( :all );
15              
16             use Apache::Constants qw(:common);
17             require Exporter;
18              
19             # We have to define $VERSION as follows instead of a simpler 'our $VERSION', because perl 5.005_03
20             # can not cope with this in MakeMaker (the perl Makefile.PL doesn't execute)
21             use vars qw($VERSION);
22              
23             our @ISA = qw(Exporter);
24              
25             # Items to export into callers namespace by default. Note: do not export
26             # names by default without a very good reason. Use EXPORT_OK instead.
27             # Do not simply export all your public functions/methods/constants.
28              
29             # This allows declaration use Apache::WeSQL ':all';
30             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
31             # will save memory.
32             # $dbh $r
33             our %EXPORT_TAGS = ( 'all' => [ qw(
34             %params %cookies redirect error readLayoutFile DEBUG
35             ) ] );
36              
37             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
38              
39             our @EXPORT = qw(
40              
41             );
42              
43             $VERSION = '0.53';
44              
45             our $DEBUGTYPE = 'apache';
46              
47             # Preloaded methods go here.
48              
49             # Some global variables
50              
51             our ($r);
52             our ($dbh, %params, %cookies, $errorcode); #CHECK IF NECESSARY!!
53             our $DEBUG = 1;
54              
55             ############################################################
56             # log_error
57             # Log an error, in different ways depending on the value of $DEBUGTYPE
58             ############################################################
59             sub log_error {
60             my $message = shift;
61             if ($DEBUGTYPE eq 'text') {
62             print STDERR "$message\n";
63             } else { # Apache style is default!
64             my $r = Apache->request;
65             $r->log_error($message);
66             }
67             }
68              
69             ############################################################
70             # display
71             # Process a WeSQL file
72             ############################################################
73             sub display {
74             $dbh = shift;
75             $r = shift;
76             my $authsuperuserdir = shift;
77             my $cookieheader = shift;
78             my @commandlist = @_;
79              
80             # These requests are special, and might want to use a HTTP redirect, so we can't
81             # print the standard HTTP header just yet! They will have to take care of that
82             # themselves.
83             my ($result,$errorcode) = ("",0);
84             my $uri = $r->uri;
85              
86             # Note that the next line will make requests ending in a slash look for a index.wsql file...
87             # So if you want to link to a index.html file, you'll have to put the file name in the request!
88             $uri .= "index.wsql" if ($uri =~ /\/$/);
89              
90             # Multi-language support
91             $uri =~ s/\.[\w\-]{2,5}\.wsql$/\.wsql/;
92              
93             for ($uri) {
94             /\/jadd.wsql$/ && do { ($result,$errorcode) = &jAddPrepare($dbh,$cookieheader); last; };
95             /\/jupdate.wsql$/ && do { ($result,$errorcode) = &jUpdatePrepare($dbh,$cookieheader); last; };
96             /\/jdelete.wsql$/ && do { ($result,$errorcode) = &jDeletePrepare($dbh,$cookieheader); last; };
97             /\/jform.wsql$/ && do { ($result,$errorcode) = &jForm($dbh,$cookieheader); last; };
98             /\/jdeleteform.wsql$/ && do { ($result,$errorcode) = &jDetails($dbh,$cookieheader); last; };
99             /\/jdetails.wsql$/ && do { ($result,$errorcode) = &jDetails($dbh,$cookieheader); last; };
100             /\/jlist.wsql$/ && do { ($result,$errorcode) = &jList($dbh,0,$cookieheader); last; };
101             /\/jloginform.wsql$/ && do { ($result,$errorcode) = &jLoginForm($dbh,$cookieheader); last; };
102             /\/jlogin.wsql$/ && do { ($result,$errorcode) = &jLogin($dbh,$authsuperuserdir); last; };
103             /\/jlogout.wsql$/ && do { ($result,$errorcode) = &jLogout($dbh); last; };
104             }
105              
106             if ($result eq "") {
107             my $dd = localtime();
108             unless ($uri =~ /redirect\.wsql$/) {
109             # Print a proper HTTP header
110             print <
111             HTTP/1.1 200 OK
112             Date: $dd
113             Server: Apache
114             EOF
115             print "$cookieheader\r\n" if (defined($cookieheader) && ($cookieheader ne ''));
116             print <
117             Connection: close
118             Content-type: text/html
119              
120             EOF
121             }
122             my $doc_root = $r->document_root;
123              
124             $result = &readWeSQLFile($doc_root . $uri);
125             }
126              
127             # Now only parse files as WeSQL if they have a .wsql suffix!! This allows easy mixing of HTML and WeSQL files...
128             ($result,$errorcode) = &dolayout($result,@commandlist) if ($uri =~ /\.wsql$/);
129              
130             if ($errorcode) {
131             print "An error has occured: errorcode $errorcode. Please contact the webmaster!!";
132             } else {
133             print $result;
134             }
135             }
136              
137             ############################################################
138             # error
139             # Builds & logs an error message
140             ############################################################
141             sub error {
142             my ($message,$logmessage) = @_;
143             &log_error("$$: WeSQL.pm: $logmessage");
144             my $dd = localtime();
145             return <<"EOF";
146            
147             Error
148            
149            

Error

150             $message
151            
152             This page was dynamically generated by WeSQL $VERSION
153            
154            
155             EOF
156             }
157              
158             ########################################################
159             sub readWeSQLFile {
160             my ($layoutfile) = shift;
161             unless (defined(open(LAYOUTFILE,$layoutfile))) {
162             print &error("File not found.","$$: WeSQL.pm: dolayout: file '$layoutfile' not found!");
163             exit;
164             }
165             my $body = join("",);
166             close(LAYOUTFILE);
167             my @stat = stat($layoutfile);
168             $ENV{FILE_SIZE} = $stat[7];
169             $ENV{FILE_LAST_MODIFIED} = $stat[9];
170             return ($body);
171             }
172              
173             sub dolayout {
174             my $body = shift;
175             my $errorcode = 0;
176             my (@commandlist) = @_;
177             foreach (@commandlist) {
178             &log_error("$$: WeSQL.pm: dolayout: executing $_") if ($DEBUG > 1);
179             $body = eval($_);
180             # The following will log errors from the eval()
181             &log_error("$$: WeSQL.pm: dolayout: eval error: " . $@) if $@;
182             };
183             return ($body,$errorcode);
184             }
185              
186             ############################################################
187             # dolist, printlist & printlist_inline deal with the
188             # ... tag
189             ############################################################
190              
191             sub dolist {
192             my $body = shift;
193             my $dbh = shift;
194             $body =~ s/(.*?)/&printlist($dbh,$1,$2,$3,$4)/sieg;
195             return $body;
196             }
197              
198             sub printlist {
199             my $dbh = shift;
200             my $returnval = "";
201             my $prefix = shift;
202             $prefix .= "_" if ($prefix ne "");
203              
204             my $query = shift;
205             my $layout = shift;
206             my $nomatchtext = shift;
207              
208             my $c = sqlSelectMany($dbh,$query);
209              
210             my %data;
211             my $colnameref = $c->{NAME_uc};
212             foreach (@{$colnameref}) {
213             $data{$_} = "";
214             }
215              
216             if (defined($c) && ($query =~ /^(SELECT|SHOW|DESC)/i)) {
217             # Non-select queries will result in a defined $c, but cause a typical
218             # "fetch() without execute()' warning in the logs, hence the checking for
219             # SELECT, SHOW and DESC above!
220             while(my $data=$c->fetchrow_hashref()) { #This is a bit less efficient than fetchrow_arrayref...
221             foreach(sort keys %{$data}) { #Make column-names case-insensitive
222             $data->{lc($_)} = $data->{$_};
223             }
224             my $output = $layout;
225             $output =~ s/([^\w]*?)$prefix([A-Z()\[\]0-9\._]*)\|\[(.*?)(?
226             $output =~ s/([^\w]*?)$prefix([A-Z()0-9\._]*)/(defined($data->{lc($2)})?"$1$data->{lc($2)}":(exists $data->{lc($2)}?"$1NULL":"$1$prefix$2"))/eg;
227             $returnval .= $output;
228             }
229              
230             if ($returnval eq "") {
231             $returnval = "
Your query returned no results.
232             $returnval = $nomatchtext unless $nomatchtext eq "";
233             # if nomatchtext is the html equivalent of a space, just throw it away:
234             # The user doesn't want anything in the html.
235             $returnval = "" if $nomatchtext eq " ";
236             };
237             $c->finish();
238             $errorcode = 0;
239             #Recursively parse the rest of the file!
240             $returnval =~ s/(.*?)/&printlist($dbh,$1,$2,$3,$4)/sieg;
241             } elsif (!defined($c)) {
242             #upon error ($c is not defined), errorcode will be set to 1, and $returnval will stay empty
243             # -> no more recursive invocations of this sub. $errorcode is a global variable (aargh, I know!)
244             #that is initialised and checked in dolayout, our caller sub!
245             &log_error("$$: c not defined in printlist!");
246             $errorcode = 1;
247             }
248            
249             return $returnval;
250             }
251              
252             sub printlist_inline {
253             my ($pre, $alt, $value, %data2) = @_;
254             $alt =~ s/\\\]/\]/g;
255             if (defined($value)) {
256             if ($data2{lc($value)} eq "") {
257             return "$pre$alt";
258             } else {
259             return "$pre$value";
260             }
261             } else {
262             if (exists($data2{lc($value)})) {
263             return "$pre\LNULL";
264             } else {
265             return "$pre$alt";
266             }
267             }
268             }
269              
270             ############################################################
271             # end of
272             # ... tag code
273             ############################################################
274              
275             ############################################################
276             # readLayoutFile
277             # Reads the layout information from the file 'layout.cf'
278             ############################################################
279             sub readLayoutFile {
280             my $file = shift;
281             my $nestlevel = shift;
282              
283             $nestlevel ||= 0;
284              
285             my %aliases;
286             my $r = Apache->request;
287             # Get the 'base-uri' from the request: for instance, for /admin/jlist.wsql that would be /admin/, and for /jlist.wsql that would just be /
288             my $uri = $r->uri;
289             my ($baseuri) = ($uri =~ /^(.+)\//);
290              
291             my $doc_root = $r->document_root;
292              
293             $baseuri .= '/';
294             if (!defined(open(LAYOUT,$doc_root . $baseuri . "$file"))) {
295             &log_error("$$: WeSQL.pm: readLayoutFile: file '" . $doc_root . $baseuri . "$file' not found!");
296             &jErrorMessage("Configuration file not found! Please contact the webmaster.","Can't read $file!",0);
297             exit;
298             }
299             my $layoutinfo = join("",);
300             close(LAYOUT);
301              
302             if ($layoutinfo =~ /^inherit:(.*?)\n/) {
303             if ($nestlevel < 10) { # Protect people from eternal loops...
304             &log_error("$$: WeSQL.pm: readLayoutFile: detected inheritance level " . ++$nestlevel . ", reading $1!");
305             %aliases = &readLayoutFile($1,$nestlevel);
306             } else {
307             &log_error("$$: WeSQL.pm: readLayoutFile: detected 10 levels of inheritance, aborting inheritance here!");
308             }
309             }
310              
311             my @aliases = split(/\n\n/,$layoutinfo);
312             foreach (@aliases) {
313             my @lines = split(/\n/,$_);
314             my $name = "";
315             while (($name eq "") && ($#lines > -1)) {
316             $name = shift @lines; #First line should contain nothing but the name of the layoutalias
317             }
318             $aliases{$name} = join("\n",@lines);
319             }
320             &log_error("$$: WeSQL.pm: readLayoutFile: file '$file' succesfully read") if ($DEBUG);
321             return %aliases;
322             }
323              
324             ############################################################
325             # dolayouttags deals with the tag
326             ############################################################
327              
328             sub dolayouttags {
329             my $body = shift;
330             my %layout = ();
331              
332             if ($cookies{WeSQL_language} ne '') {
333             %layout = &readLayoutFile("layout.$cookies{WeSQL_language}.cf");
334             } else {
335             %layout = &readLayoutFile("layout.cf");
336             }
337              
338             $body =~ s//&log_error("$$: WeSQL.pm: dolayouttags: no layout key '$1' found") if (!defined($layout{$1}));$layout{$1}||="";$layout{$1}/sieg;
339             return $body;
340             }
341              
342             ############################################################
343             # end of tag code
344             ############################################################
345              
346             ############################################################
347             # dolanguages deals with the text tag
348             ############################################################
349             sub dolanguages {
350             my $body = shift;
351              
352             my $uri = $r->uri;
353             my ($baseuri) = ($uri =~ /^(.+)\//);
354             my $doc_root = $r->document_root;
355             $baseuri .= '/';
356              
357             opendir(DIR, $doc_root . $baseuri) || die "can't opendir $doc_root . $baseuri: $!";
358             my @layoutfiles = grep { /^layout\..*?\.cf$/ && -f $doc_root . "$baseuri/$_" } readdir(DIR);
359             closedir DIR;
360              
361             # Now deal with the language tags, that could look like stuff or stuff
362             foreach (@layoutfiles) {
363             my ($lang) = (/^layout\.(.*?)\.cf$/);
364             $body =~ s/<$lang( *\d*|)>(.*?)<\/$lang>/($lang eq $cookies{WeSQL_language})?$2:''/sieg;
365             }
366              
367             return $body;
368             }
369             ############################################################
370             # end of text tag code
371             ############################################################
372              
373             ############################################################
374             # doinsert & insertfile deal with the tag
375             ############################################################
376              
377             sub doinsert {
378             my $body = shift;
379             $body =~ s//&dosubst(&dosubst(&dosubst(&insertfile($1,$2),"PR_",%params),"ENV_",%ENV),"COOKIE_",%cookies)/sieg;
380             return $body;
381             }
382              
383             sub insertfile {
384             my $prefix = shift;
385             my $file = shift;
386              
387             # Setting PREFIX in the INCLUDE tag to "" was the old trick to have no prefix
388             # Depreciated. Now you can just omit the PREFIX parameter.
389             if ($prefix eq "\"\"") { $prefix = ""; }
390              
391             # Allow shorthand when no prefix is necessary for the included file:
392             #
393             if (!defined($file) || ($file eq "")) {
394             $file = $prefix;
395             $prefix = "";
396             }
397              
398             unless(defined(open(LFILE,$r->document_root . "/$file"))) {
399             &log_error("$$: insertfile: file '$file' not found!");
400             return "
File not found !
";
401             }
402             my $body = join("",);
403             close(LFILE);
404              
405             #Now make sure that this inserted file has its separate set of parameters.
406             #First make sure that references in perl code get the right values by rewriting the
407             #getparams call
408             if ($prefix ne "") {
409             $body =~ s/WeSQL\:\:getparams\(\)/WeSQL\:\:getparams\(\$dbh,\"$prefix\"\)/g;
410             #Secondly rewrite all the PR_ references to include the prefix
411             $body =~ s/PR_([A-Z()\[\]0-9\._]*)/PR_$prefix$1/g;
412             }
413             return $body;
414             }
415              
416             ############################################################
417             # end of tag code
418             ############################################################
419              
420             ############################################################
421             # dosubst & dosubst_inline deal with the PREFIX style parameters (e.g. PR_PARAM1)
422             ############################################################
423              
424             sub dosubst {
425             my $body = shift;
426             my $prefix = shift;
427             my %hash = @_;
428             my %uchash;
429             foreach (keys %hash) {
430             $uchash{uc($_)}= $hash{$_};
431             };
432             #First match occurrences with alternative (after a | character)
433             # Example: [PR_WHAT|super] will result in the value of the 'what' parameter if defined,
434             # or else in the word 'super'. You can escape ] and | with a backslash.
435             $body =~ s/\[(.*?)$prefix([A-Z()\[\]0-9\._]*)(.*?)(?
436              
437             #Then match the ones without!
438             $body =~ s/([^\w]*)$prefix([A-Z()\[\]0-9\._]*)/(defined($uchash{$2})?"$1$uchash{$2}":"$1$prefix$2")/eg;
439              
440             return $body;
441             }
442              
443             sub dosubst_inline {
444             my ($pre, $value, $post, $alt,%uchash) = @_;
445             if (defined($uchash{$value})) {
446             $post =~ s/\\\|/\|/g;
447             return "$pre$uchash{$value}$post";
448             } else {
449             $alt =~ s/\\\]/\]/g;
450             return "$pre$alt";
451             }
452             }
453              
454             ############################################################
455             # end of parameter substitution code
456             ############################################################
457              
458             ############################################################
459             # doeval deals with the tag
460             ############################################################
461              
462             # We need evalinline to be able to trap the eval errors...
463             # $@ gets lost in the s//eval()/ statement in doeval.
464             sub evalinline {
465             my $eval = eval($_[0]);
466             &log_error("$$: DOEVAL EVAL ERROR: " . $@) if ($@ || !defined($eval)); #This will log errors from the eval()
467             return $eval;
468             }
469              
470             sub doeval {
471             my $body = shift;
472             my $param = shift;
473             # First do the single-line style evals (e.g. )
474             $body =~ s//&evalinline($1)/emg;
475             # And then the multi-line evals
476             $body =~ s//&evalinline($1)/esmg;
477             return $body;
478             }
479              
480             ############################################################
481             # end tag code
482             ############################################################
483              
484             ############################################################
485             # docutcheck deals with the tag
486             ############################################################
487              
488             sub docutcheck {
489             my $body = shift;
490             # $body =~ s/(.*?)^\n.*/$1/sm;
491             #If you run perl 5.6.0, you will find that the above re is ridiculously slow.
492             #This is a Perl bug, fixed in 5.6.1. You can enable the above line if you run
493             #Perl 5.6.1.
494             $body =~ s/(.*?)^\n.*/$1/sm;
495             return $body;
496             }
497              
498             ############################################################
499             # end tag code
500             ############################################################
501              
502             ############################################################
503             # doparamcheck deals with the tag
504             ############################################################
505              
506             # SYNTAX example (new from version 0.50)
507             #
515             #
516             #
517             # /PARAMCHECK -->
518              
519             sub doparamcheck {
520             my $body = shift;
521             $body =~ s//¶mcheck($1)/esmg;
522             return $body;
523             }
524              
525             sub paramcheck {
526             my $body = shift;
527             my $headertext = "
Below are the problems that have been encountered:
528             ($headertext) = ($body =~ /(.*?)<\/paramcheckhead>/ism);
529             my $footertext = "\n\n";
530             ($footertext) = ($body =~ /(.*?)<\/paramcheckfoot>/ism);
531             $body =~ s/.*?<\/paramcheckhead>\n//ismg;
532             $body =~ s/.*?<\/paramcheckfoot>\n//ismg;
533             $body =~ s/^\n//smg; #Remove any empty lines!
534             $body =~ s/^(.*?)\s+(\/|!\/)(.*?)(?
535             $body = $headertext . $body . "\n" . $footertext if ($body ne '');
536             return $body;
537             }
538              
539             sub check_one_param {
540             my $param = shift;
541             my $negation = shift;
542             my $regexp = shift;
543             my $wrong_string = shift;
544             $param = '' if (!defined($param));
545             $negation = '' if (!defined($negation));
546             $regexp = '' if (!defined($regexp));
547             $wrong_string = '' if (!defined($wrong_string));
548             chop($negation);
549             if ($negation =~ /^!/) {
550             if ($regexp eq '') { # !// means that the parameter should be defined
551             if (!($param =~ /PR_(.*)/)) { return "$wrong_string\n"; }
552             }
553             if ($param =~ /$regexp/) {
554             return "$wrong_string\n";
555             }
556             } else { #if parameter is defined, complain if it does not match the condition
557             if ($regexp eq '') {
558             if ($param =~ /PR_(.*)/) { return "$wrong_string\n"; }
559             return "";
560             }
561             if ((!($param =~ /PR_(.*)/)) && (!($param =~ /$regexp/))) {
562             return "$wrong_string\n";
563             }
564             }
565             return "";
566             }
567              
568             ############################################################
569             # end tag code
570             ############################################################
571              
572             ############################################################
573             # getparams
574             # Prepare & secure the parameters & cookies passed to us from the user
575             # These parameters & cookies are available in the rest of the module
576             # as the global hashes %params and %cookies
577             ############################################################
578              
579             sub getparams {
580             # Sometimes we only want to see a subset of the parameters!
581             # (used for the INSERT statement)
582             # For this we need a 'prefix' parameter
583             my $dbh = shift;
584             my $prefix = shift;
585             my $cookieheader = shift;
586             my $defaultlanguage = shift;
587             undef $r if $r;
588             undef %params if %params;
589             undef %cookies if %cookies;
590             $r = Apache->request;
591             require CGI;
592             my $q = new CGI;
593              
594             &log_error("$$: WeSQL.pm: getparams: entering!") if ($DEBUG);
595              
596             # Set our %cookies hash
597             foreach ($q->cookie) {
598             $cookies{$_} = $q->cookie($_);
599             $cookies{$_} =~ s/\'/\\\'/sg;
600             $cookies{$_} =~ s/\"/\\\"/sg;
601             # The NULL character terminates strings in C. Hence all sorts of nasty things can happen when a NULL is passed to a C program like MySQL...
602             # The ; character terminates sql statements. Let's nuke that one too.
603             $cookies{$_} =~ s/#0|%0|%3B//sg;
604             &log_error("$$: WeSQL.pm: getparams: cookie: $_ -> " . $q->cookie($_)) if ($DEBUG);
605             }
606              
607             # $cookieheader is used to pass values of cookies that have been set while processing this page, and hence
608             # are not passed by the browser yet! Used from AppHandler.pm, to pass the session hash when that is first
609             # set.
610             if (defined($cookieheader) && ($cookieheader ne '')) {
611             if ($cookieheader =~ /Set-Cookie: (.*?)=(.*)/) {
612             $cookies{$1} = $2;
613             &log_error("$$: WeSQL.pm: getparams: cookie: $1 -> $2") if ($DEBUG);
614             }
615             }
616              
617             undef($cookies{su}) if (defined($cookies{su})); #Nonono, this cookie should NEVER be on your hard-drive :-)
618             if (defined($cookies{id}) && defined($cookies{hash})) { #This is - probably - a logged in user
619             # my @sucheck = sqlSelect("superuser","users","uid='$WeSQL::cookies{id}' and status='1'");
620             # Lookup the hash on the users hard-drive. If this hash matches a super-user login, set the su cookie to the id of that superuser
621             # We don't touch the id cookie, thus allowing superusers to 'cloak' as someone else, while maintaining their superuser powers!
622             my @sucheck = sqlSelect($dbh,"select u.superuser,u.id from users as u,logins as l where u.id=l.uid and l.hash='$cookies{hash}' and u.status='1' and l.status='1'");
623             if (defined($sucheck[0]) && ($sucheck[0] > 0)) {
624             $cookies{su} = $sucheck[1];
625             &log_error("$$: WeSQL.pm: getparams: updated cookie: su -> $cookies{su}") if ($DEBUG);
626             }
627             }
628             # $q->param should not be used beyond this sub, instead use the %params hash that does away
629             # with all sorts of dangerous input!
630             foreach ($q->param) {
631             my $tmp = $_;
632             if (defined($prefix) && ($prefix ne '')) {
633             next if (!($_ =~ /^$prefix/));
634             $tmp =~ s/^$prefix//g;
635             }
636             # If multiple parameters have the same name, append them together, separated by a pipe symbol
637             $params{$tmp} = join("|",$q->param($_));
638             # The NULL character terminates strings in C. Hence all sorts of nasty things can happen when a NULL is passed to a C program like MySQL...
639             # The ; character terminates sql statements. Let's nuke that one too.
640             $params{$tmp} =~ s/#0|%0|%3B//sg;
641              
642             # The following lines are CRUCIAL FOR SECURITY - REMOVE AT YOUR OWN RISK!
643             # Anyone trying to insert a WeSQL-style command (e.g.