File Coverage

blib/lib/HTML/EP/Glimpse.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # HTML::EP::Glimpse - A simple search engine using Glimpse
4             #
5             #
6             # Copyright (C) 1998 Jochen Wiedmann
7             # Am Eisteich 9
8             # 72555 Metzingen
9             # Germany
10             #
11             # Phone: +49 7123 14887
12             # Email: joe@ispsoft.de
13             #
14             # All rights reserved.
15             #
16             # You may distribute this module under the terms of either
17             # the GNU General Public License or the Artistic License, as
18             # specified in the Perl README file.
19             #
20             ############################################################################
21              
22             require 5.005;
23 1     1   638 use strict;
  1         1  
  1         37  
24              
25 1     1   381 use HTML::EP ();
  0            
  0            
26             use HTML::EP::Locale ();
27             use HTML::EP::Glimpse::Config ();
28              
29             package HTML::EP::Glimpse;
30              
31             $HTML::EP::Glimpse::VERSION = '0.05';
32             @HTML::EP::Glimpse::ISA = qw(HTML::EP::Locale HTML::EP);
33              
34              
35             sub _prefs {
36             my $self = shift; my $attr = shift; my $prefs = shift;
37             $self->{'glimpse_config'} ||= $HTML::EP::Glimpse::Config::config;
38             my $config = $self->{'glimpse_config'};
39             my $vardir = $config->{'vardir'};
40             die "A directory $vardir does not exist. Please create it, with write "
41             . " permissions for the web server, or modify the value of "
42             . " vardir in $INC{'HTML/EP/Glimpse/Config.pm'}."
43             unless -d $vardir;
44             my $prefs_file = "$vardir/prefs";
45             if (!$prefs) {
46             # Load Prefs
47             require Safe;
48             my $cpt = Safe->new();
49             $prefs = $self->{'prefs'} = $cpt->rdo($prefs_file) || {};
50              
51             $prefs->{'rootdir'} = $ENV{'DOCUMENT_ROOT'}
52             unless exists($prefs->{'rootdir'});
53             $prefs->{'dirs'} = "/"
54             unless exists($prefs->{'dirs'});
55             $prefs->{'dirs_ignored'} =
56             (($ENV{'PATH_INFO'} =~ /(.*)\//) ? $1 : "")
57             unless exists($prefs->{'dirs_ignored'});
58             $prefs->{'suffix'} = ".html .htm"
59             unless exists($prefs->{'suffix'});
60             } else {
61             # Save Prefs
62             require Data::Dumper;
63             my $d = Data::Dumper->new([$prefs])->Indent(1)->Terse(1)->Dump();
64             require Symbol;
65             my $fh = Symbol::gensym();
66             if ($self->{'debug'}) {
67             print "Saving Preferences to $prefs_file.\n";
68             $self->print("Saving data:\n$d\n");
69             }
70             die "Could not save data into $prefs_file: $!. Please verify whether"
71             . " the web server has write permissions in $vardir and on"
72             . " $prefs_file."
73             unless open($fh, ">$prefs_file") and (print $fh "$d\n")
74             and close($fh);
75             }
76             $self->{'glimpse_prefs'} = $prefs;
77             }
78              
79              
80             sub _ep_glimpse_load {
81             my $self = shift; my $attr = shift;
82             my $cgi = $self->{'cgi'};
83             my $prefs = $self->_prefs($attr);
84              
85             if ($cgi->param('modify')) {
86             my $modified = 0;
87             foreach my $p ($cgi->param()) {
88             if ($p =~ /^glimpse_prefs_(.*)/) {
89             my $sp = $1;
90             my $old = $prefs->{$sp};
91             my $new = $cgi->param($p);
92             if (!defined($old)) {
93             if (defined($new)) {
94             $modified = 1;
95             $prefs->{$sp} = $new;
96             }
97             } elsif (!defined($new)) {
98             $modified = 1;
99             $prefs->{$sp} = $new;
100             } else {
101             $modified = ($new ne $old);
102             $prefs->{$sp} = $new;
103             }
104             }
105             }
106             if ($self->{'debug'}) {
107             $self->print("Modifications detected.\n");
108             }
109             $self->_prefs($attr, $prefs);
110             }
111             '';
112             }
113              
114              
115             sub _ep_glimpse_create {
116             my $self = shift; my $attr = shift;
117             my $prefs = $self->_prefs($attr);
118             my $vardir = $self->{'glimpse_config'}->{'vardir'};
119             my $debug = $self->{'debug'};
120             my $cfg = $self->{'glimpse_config'};
121              
122             my $rootdir = $prefs->{'rootdir'};
123             my $dirlist = $prefs->{'dirs'};
124             $dirlist =~ s/\s+/ /sg;
125             $dirlist =~ s/^\s+//;
126             $dirlist =~ s/\s+$//;
127             my @dirs = map { "$rootdir/$_" } split(/ /, $dirlist);
128             $dirlist = $prefs->{'dirs_ignored'};
129             $dirlist =~ s/\s+/ /sg;
130             $dirlist =~ s/^\s+//;
131             $dirlist =~ s/\s+$//;
132             my @dirs_ignored = map { "$rootdir/$_" } split(/ /, $dirlist);
133              
134             my $matchesDirsIgnored;
135             if (@dirs_ignored) {
136             my $dirsIgnoredRe = join("|", map { "\\Q$_\\E" } @dirs_ignored);
137             my $func = "sub { shift() =~ m[^(?:$dirsIgnoredRe)] }";
138             $matchesDirsIgnored = eval $func;
139             $self->print("Making function for directory match: $func",
140             " ($matchesDirsIgnored))\n") if $debug;
141             } else {
142             $matchesDirsIgnored = sub { 0 }
143             }
144             my $suffixList = $prefs->{'suffix'};
145             $suffixList =~ s/\s+/ /sg;
146             $suffixList =~ s/^\s+//;
147             $suffixList =~ s/\s+$//;
148             my @suffix = split(/ /, $suffixList);
149             my $matchesSuffix;
150             if (@suffix) {
151             my $suffixRe = join("|", map { "\\Q$_\\E" } @suffix);
152             my $func = "sub { shift() =~ m[(?:$suffixRe)\$] }";
153             $matchesSuffix = eval $func;
154             $self->print("Making function for suffix match: $func",
155             "($matchesSuffix)\n") if $debug;
156             } else {
157             $matchesSuffix = sub { 1 }
158             }
159              
160             my $fileList = '';
161             require File::Find;
162             File::Find::find
163             (sub {
164             if (&$matchesDirsIgnored($File::Find::dir)) {
165             $self->print("Skipping directory $File::Find::dir.\n")
166             if $debug;
167             $File::Find::prune = 1;
168             } else {
169             my $f = $File::Find::name;
170             my $ok = ((-f $f) and &$matchesSuffix($f));
171             $self->print(" $f: $ok\n") if $debug;
172             $fileList .= "$f\n" if $ok;
173             }
174             }, @dirs);
175              
176             die "No files found" unless $fileList;
177              
178             my $fh = Symbol::gensym();
179             my $cmd = "$cfg->{'glimpseindex_path'} -b -F -H $vardir -X";
180             $self->print("Creating pipe to command $cmd\n") if $debug;
181             die "Error while creating index: $!"
182             unless (open($fh, "| $cmd >$vardir/.glimpse_output 2>&1") and
183             (print $fh $fileList) and close($fh));
184              
185             $fileList;
186             }
187              
188              
189             sub _ep_glimpse_matchline {
190             my $self = shift; my $attr = shift;
191             my $template = defined($attr->{'template'}) ?
192             $attr->{'template'} : return undef;
193             $self->print("Setting matchline template to $template\n")
194             if $self->{'debug'};
195             $self->{'line_template'} = $template;
196             '';
197             }
198              
199             sub _format_MATCHLINE {
200             my $self = shift; my $f = shift;
201             my $debug = $self->{'debug'};
202             my $template = $self->{'line_template'};
203             my $lines = $f->{'lines'};
204             $self->print("MATCHLINE: f = $f, lines = $lines (", @$lines, ")\n",
205             "line_template = $template\n") if $debug;
206             my $output = $self->_ep_list({'items' => $lines,
207             'item' => 'l',
208             'template' => $template});
209             $self->print("output = ", (defined($output) ? $output : "undef"), "\n")
210             if $debug;
211             $output;
212             }
213              
214             sub _ep_glimpse_search {
215             my $self = shift; my $attr = shift;
216             my $prefs = $self->_prefs($attr);
217             my $vardir = $self->{'glimpse_config'}->{'vardir'};
218             my $cgi = $self->{'cgi'};
219             my $debug = $self->{'debug'};
220             my $start = ($cgi->param('start') or 0);
221             my $max = ($cgi->param('max') or $attr->{'max'} or 20);
222             my @opts = ($self->{'glimpse_config'}->{'glimpse_path'}, '-UOnbqy', '-L',
223             "0:" . ($start+$max), '-H', $vardir);
224             my $case_sensitive = $cgi->param('opt_case_sensitive') ? 1 : 0;
225             push(@opts, '-i') unless $case_sensitive;
226             my $word_boundary = $cgi->param('word_boundary') ? 1 : 0;
227             push(@opts, '-w') if $word_boundary;
228             my $whole_file = $cgi->param('opt_whole_file') ? 1 : 0;
229             push(@opts, '-W') unless $whole_file;
230             my $opt_regex = $cgi->param('opt_regex') ? 1 : 0;
231             push(@opts, $opt_regex ? '-e' : '-k');
232             my $opt_or = $cgi->param('opt_or') ? 1 : 0;
233              
234             # Now for the hard part: Split the search string into words
235             my $search = $cgi->param('search');
236             $self->{'link_opts'} = $self->{'env'}->{'PATH_INFO'} . "?"
237             . join("&", "search=" . CGI->escape($search),
238             "max=$max", "opt_case_sensitive=$case_sensitive",
239             "word_boundary=$word_boundary", "opt_whole_file=$whole_file",
240             "opt_regex=$opt_regex", "opt_or=$opt_or");
241             my @words;
242             while (length($search)) {
243             $search =~ s/^\s+//s;
244             if ($search =~ /^"/s) {
245             if ($search =~ /"(.*?)"\s+(.*)/s) {
246             push(@words, $1);
247             $search = $2;
248             } else {
249             $search =~ s/^"//s;
250             $search =~ s/"$//s;
251             push(@words, $search);
252             last;
253             }
254             } else {
255             $search =~ s/^(\S+)//s;
256             push(@words, $1) if $1;
257             }
258             }
259             if (!@words) {
260             my $language = $self->{'_ep_language'};
261             my $msg;
262             if ($language eq 'de') {
263             $msg = "Keine Suchbegriffe gefunden";
264             } else {
265             $msg = "No search strings found";
266             }
267             $self->_ep_error({'type' => 'user', 'msg' => $msg});
268             }
269             my $sep = $opt_or ? ';' : ',';
270              
271             push(@opts, join($sep, @words));
272              
273             # First try using fork() and system() for security reasons.
274             my $ok;
275             my $tmpnam;
276             my $fh = eval {
277             my $infh = Symbol::gensym();
278             my $outfh = Symbol::gensym();
279             pipe ($infh, $outfh) or die "Failed to create pipe: $!";
280             my $pid = fork();
281             die "Failed to fork: $!" unless defined($pid);
282             if (!$pid) {
283             # This is the child
284             close $infh;
285             open(STDOUT, ">&=" . fileno($outfh))
286             or die "Failed to reopen STDOUT: $!";
287             exec @opts;
288             exit 0;
289             }
290             close $outfh;
291             $self->printf("Forked command %s\n", join(" ", @opts)) if $debug;
292             $infh;
293             } || eval {
294             # Rats, doesn't work. :-( Run glimpse by storing the output in
295             # a file and read from that file. We need to be aware of shell
296             # metacharacters and the like.
297             require POSIX;
298             $tmpnam = "$vardir/" . POSIX::tmpnam();
299             my $command = join(" ", map{ quotemeta $_ } @opts). " >$tmpnam";
300             $self->print("Running command $command\n") if $debug;
301             system $command or die "system() failed: $!";
302             my $infh = Symbol::gensym();
303             open($infh, "<$tmpnam")
304             or die "Failed to open $tmpnam: $!";
305             $infh;
306             };
307             $self->print("fh = $fh\n") if $debug;
308             eval {
309             my $blank_seen;
310             my (@files, @lines, $file, $title, $lineNum, $byteOffset, $offsetStart,
311             $offsetEnd);
312             my $fileNum = $start;
313             my $ignoreFiles = $start;
314             while (defined(my $line = <$fh>)) {
315             #$self->print("Glimpse output: $line") if $debug;
316             if ($line =~ /^\s*$/) {
317             $blank_seen = 1;
318             if ($file) {
319             if ($ignoreFiles) {
320             --$ignoreFiles
321             } else {
322             push(@files, {'file' => $file,
323             'fileNum' => ++$fileNum,
324             'title' => $title,
325             'lines' => [@lines]})
326             }
327             }
328             undef $file;
329             undef $lineNum;
330             @lines = ();
331             #$self->print("Blank line detected\n") if $debug;
332             } elsif ($blank_seen) {
333             $blank_seen = 0;
334             if ($line =~ /^(\S+)\s+(\S.*?)\s+$/) {
335             $file = $1;
336             $title = $2;
337             #$self->print("New file detected: $file, $title\n")
338             # if $debug;
339             } elsif ($line =~ /^(\S+)\:\s*$/) {
340             $file = $title = $1;
341             } else {
342             $self->print("Cannot parse file line: $line") if $debug;
343             }
344             } elsif ($file) {
345             if ($lineNum) {
346             push(@lines, {'line' => $line,
347             'lineNum' => $lineNum,
348             'byteOffset' => $byteOffset,
349             'offsetStart' => $offsetStart,
350             'offsetEnd' => $offsetEnd});
351             #$self->print("Match line detected: $lineNum, $line\n")
352             # if $debug;
353             undef $lineNum;
354             } elsif ($line =~ /^(\d+)\:\s+(\d+)\=\s+\@(\d+)\{(\d+)\}/) {
355             $lineNum = $1;
356             $byteOffset = $2;
357             $offsetStart = $3;
358             $offsetEnd = $4;
359             } else {
360             $self->print("Cannot parse line: $line\n") if $debug;
361             }
362             } else {
363             $self->print("Unexpected line: $line\n") if $debug;
364             }
365             }
366             if ($file) {
367             if ($ignoreFiles) {
368             --$ignoreFiles
369             } else {
370             push(@files, {'file' => $file,
371             'fileNum' => ++$fileNum,
372             'title' => $title,
373             'lines' => [@lines]})
374             }
375             }
376             $self->print("Found " . scalar(@files) . " files\n") if $debug;
377             foreach my $file (@files) {
378             my $url = $file->{'file'};
379             $url =~ s/^\Q$prefs->{'rootdir'}\E//;
380             $url =~ s/^\/+/\//;
381             $file->{'url'} = $url;
382             }
383             $self->{'files'} = \@files;
384             if (@files == $max) {
385             $self->{'next'} = $start + $max;
386             }
387             $self->{'prev'} = $start ? $start - $max : -1;
388             } unless $@;
389             close $fh if $fh;
390             undef $fh;
391             unlink $tmpnam if $tmpnam;
392             '';
393             }
394              
395              
396             1;
397              
398              
399             __END__