1
1
# -*- tab-width: 4 -*- ###############################################
3
# $Id: CVS.pm,v 1.17 2002/02/03 08:22:08 mbox Exp $
3
# $Id: CVS.pm,v 1.33 2005/05/04 23:21:09 mbox Exp $
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
11
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
13
# GNU General Public License for more details.
15
15
# You should have received a copy of the GNU General Public License
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
19
package LXR::Files::CVS;
21
$CVSID = '$Id: CVS.pm,v 1.17 2002/02/03 08:22:08 mbox Exp $ ';
21
$CVSID = '$Id: CVS.pm,v 1.33 2005/05/04 23:21:09 mbox Exp $ ';
28
use vars qw(%cvs $cache_filename);
28
use vars qw(%cvs $cache_filename $gnu_diff);
31
33
my ($self, $rootpath) = @_;
33
35
$self = bless({}, $self);
34
36
$self->{'rootpath'} = $rootpath;
35
37
$self->{'rootpath'} =~ s@/*$@/@;
38
$self->{'path'} = $config->cvspath;
40
unless (defined $gnu_diff) {
42
# the rcsdiff command (used in getdiff) uses parameters only supported by GNU diff
43
$ENV{'PATH'} = $self->{'path'};
44
if (`diff --version 2>/dev/null` =~ /GNU/) {
43
57
if ($release =~ /rev_([\d\.]+)/) {
46
elsif ($release =~ /^([\d\.]+)$/) {
59
} elsif ($release =~ /^([\d\.]+)$/) {
50
62
$self->parsecvs($filename);
51
63
return $cvs{'header'}{'symbols'}{$release};
56
68
my ($self, $filename, $release) = @_;
91
103
$self->parsecvs($filename);
93
105
my $rev = $self->filerev($filename, $release);
94
return undef unless defined($rev);
106
return () unless defined($rev);
96
108
my $hrev = $cvs{'header'}{'head'};
99
111
my $headfh = $self->getfilehandle($filename, $release);
100
my @head = $headfh->getlines;
112
my @head = $headfh->getlines;
103
115
if ($rev eq $hrev) {
108
120
$hrev = $cvs{'branch'}{$hrev}{'next'} || last;
110
122
my @diff = $self->getdiff($filename, $lrev, $hrev);
114
126
my $dir = shift(@diff);
116
128
if ($dir =~ /^a(\d+)\s+(\d+)/) {
117
129
splice(@diff, 0, $2);
118
splice(@head, $1-$off, 0, ('') x $2);
130
splice(@head, $1 - $off, 0, ('') x $2);
121
elsif ($dir =~ /^d(\d+)\s+(\d+)/) {
123
$anno[$_] = $lrev if $_ ne '';
124
} splice(@head, $1-$off-1, $2);
132
} elsif ($dir =~ /^d(\d+)\s+(\d+)/) {
133
map { $anno[$_] = $lrev if $_ ne ''; } splice(@head, $1 - $off - 1, $2);
129
137
warn("Oops! Out of sync!");
135
$anno[$_] = $lrev if $_ ne '';
143
map { $anno[$_] = $lrev if $_ ne ''; } @head;
138
# print(STDERR "** Anno: ".scalar(@anno).join("\n", '', @anno, ''));
146
# print(STDERR "** Anno: ".scalar(@anno).join("\n", '', @anno, ''));
156
164
my $rev = $self->filerev($filename, $release);
157
165
return undef unless defined($rev);
159
$fileh = new FileHandle("co -q -p$rev ".
160
$self->toreal($filename, $release).
161
" |"); # FIXME: Exploitable?
162
die("Error execting \"co\", rcs not installed?") unless $fileh;
167
return undef unless defined($self->toreal($filename, $release));
171
my $clean_filename = $self->cleanstring($self->toreal($filename, $release));
172
$clean_filename =~ /(.*)/;
173
$clean_filename = $1; # technically untaint here (cleanstring did the real untainting)
175
$ENV{'PATH'} = $self->{'path'};
176
open($fileh, "-|", "co -q -p$rev $clean_filename");
178
die("Error executing \"co\"; rcs not installed?") unless $fileh;
167
183
my ($self, $filename, $release1, $release2) = @_;
186
return () if $gnu_diff == 0;
170
188
$self->parsecvs($filename);
172
190
my $rev1 = $self->filerev($filename, $release1);
173
return undef unless defined($rev1);
191
return () unless defined($rev1);
175
193
my $rev2 = $self->filerev($filename, $release2);
176
return undef unless defined($rev2);
178
$fileh = new FileHandle("rcsdiff -q -a -n -r$rev1 -r$rev2 ".
179
$self->toreal($filename, $release1).
180
" |"); # FIXME: Exploitable?
181
die("Error execting \"rcsdiff\", rcs not installed?") unless $fileh;
194
return () unless defined($rev2);
196
$rev1 =~ /([\d\.]*)/;
197
$rev1 = $1; # untaint
198
$rev2 =~ /([\d\.]*)/;
199
$rev2 = $1; # untaint
200
my $clean_filename = $self->cleanstring($self->toreal($filename, $release1));
201
$clean_filename =~ /(.*)/;
202
$clean_filename = $1; # technically untaint here (cleanstring did the real untainting)
204
$ENV{'PATH'} = $self->{'path'};
205
open($fileh, "-|", "rcsdiff -q -a -n -r$rev1 -r$rev2 $clean_filename");
207
die("Error executing \"rcsdiff\"; rcs not installed?") unless $fileh;
182
208
return $fileh->getlines;
186
212
my ($self, $filename, $release) = @_;
189
215
$buf = $self->getfile($filename, $release);
190
216
return undef unless defined($buf);
192
$tmp = $config->tmpdir.'/lxrtmp.'.time.'.'.$$.'.'.&LXR::Common::tmpcounter;
218
$tmp = $config->tmpdir . '/lxrtmp.' . time . '.' . $$ . '.' . &LXR::Common::tmpcounter;
193
219
open(TMP, "> $tmp") || return undef;
208
234
next if $node =~ /^\.|~$|\.orig$/;
209
235
next if $node eq 'CVS';
211
if (-d $real.$node) {
212
push(@dirs, $node.'/');
214
elsif ($node =~ /(.*),v$/) {
237
if (-d $real . $node) {
238
push(@dirs, $node . '/');
239
} elsif ($node =~ /(.*),v$/) {
215
240
push(@files, $1);
220
245
foreach $node (@files) {
221
return 0 if $self->filerev($pathname.$node, $release);
246
return 0 if $self->filerev($pathname . $node, $release);
224
249
foreach $node (@dirs) {
225
return 0 unless $self->dirempty($pathname.$node, $release);
250
return 0 unless $self->dirempty($pathname . $node, $release);
234
259
my $real = $self->toreal($pathname, $release);
236
261
opendir($DIRH, $real) || return ();
237
while (defined($node = readdir($DIRH))) {
262
FILE: while (defined($node = readdir($DIRH))) {
238
263
next if $node =~ /^\.|~$|\.orig$/;
239
264
next if $node eq 'CVS';
241
if (-d $real.$node) {
265
if (-d $real . $node) {
266
foreach my $ignoredir ($config->ignoredirs) {
267
next FILE if $node eq $ignoredir;
242
269
if ($node eq 'Attic') {
243
push(@files, $self->getdir($pathname.$node.'/', $release));
246
push(@dirs, $node.'/')
247
unless defined($release)
248
&& $self->dirempty($pathname.$node.'/', $release);
251
elsif ($node =~ /(.*),v$/) {
253
if ! defined($release)
254
|| $self->getfiletime($pathname.$1, $release);
270
push(@files, $self->getdir($pathname . $node . '/', $release));
272
push(@dirs, $node . '/')
273
unless defined($release)
274
&& $self->dirempty($pathname . $node . '/', $release);
276
} elsif ($node =~ /(.*),v$/) {
277
if (!$$LXR::Common::HTTP{'param'}{'showattic'}) {
279
# you can't just check for 'Attic' because for certain versions the file is alive even if in Attic
280
$self->parsecvs($pathname . substr($node, 0, length($node) - 2))
281
; # substr is to remove the ',v'
282
my $rev = $cvs{'header'}{'symbols'}{$release};
283
if ($cvs{'branch'}{$rev}{'state'} eq "dead") {
288
if !defined($release)
289
|| $self->getfiletime($pathname . $1, $release);
263
298
my ($self, $pathname, $release) = @_;
264
my $real = $self->{'rootpath'}.$pathname;
299
my $real = $self->{'rootpath'} . $pathname;
301
# nearly all (if not all) method calls eventually call toreal(), so this is a good place to block file access
302
foreach my $ignoredir ($config->ignoredirs) {
303
return undef if $real =~ m|/$ignoredir/|;
266
306
return $real if -d $real;
267
return $real.',v' if -f $real.',v';
308
if (!$$LXR::Common::HTTP{'param'}{'showattic'}) {
310
# you can't just check for 'Attic' because for certain versions the file is alive even if in Attic
311
$self->parsecvs($pathname);
312
my $rev = $cvs{'header'}{'symbols'}{$release};
313
if ($cvs{'branch'}{$rev}{'state'} eq "dead") {
318
return $real . ',v' if -f $real . ',v';
269
320
$real =~ s|(/[^/]+/?)$|/Attic$1|;
271
return $real if -d $real;
272
return $real.',v' if -f $real.',v';
322
return $real if -d $real;
323
return $real . ',v' if -f $real . ',v';
329
my ($self, $in) = @_;
333
for (split('', $in)) {
334
s/[|&!`;\$%<>[:cntrl:]]// || # drop these in particular
335
/[\w\/,.-_+=]/ || # keep these intact
336
s/([ '"\x20-\x7E])/\\$1/ || # escape these out
337
s/.//; # drop everything else
278
346
my ($self, $pathname, $release) = @_;
300
368
$self->parsecvs($filename);
302
return sort(keys(%{$cvs{'header'}{'symbols'}}));
370
# no header symbols for a directory, so we use the default and the current release
371
if (defined %{ $cvs{'header'}{'symbols'} }) {
372
return sort keys %{ $cvs{'header'}{'symbols'} };
375
push @releases, $$LXR::Common::HTTP{'param'}{'v'} if $$LXR::Common::HTTP{'param'}{'v'};
376
push @releases, $config->vardefault('v');
381
# sort by CVS version
382
# split rev numbers into arrays
383
# compare each array element, returning as soon as we find a difference
385
my @one = split /\./, $a;
386
my @two = split /\./, $b;
387
for (my $i = 0; $i <= $#one; $i++) {
388
my $ret = $one[$i] <=> $two[$i];
392
# if still no difference after we ran through all elements of @one, compare the length of the array
393
return $#one <=> $#two;
305
396
sub allrevisions {
308
399
$self->parsecvs($filename);
310
return sort(keys(%{$cvs{'branch'}}));
401
return sort byrevision keys(%{ $cvs{'branch'} });
314
406
# Actually, these days it just parses the header.
315
407
# RCS tools are much better at parsing RCS files.
319
411
return if $cache_filename eq $filename;
320
412
$cache_filename = $filename;
325
open (CVS, $self->toreal($filename, undef));
417
open(CVS, $self->toreal($filename, undef));
418
close CVS and return if -d CVS; # we can't parse a directory
327
420
if (/^text\s*$/) {
328
422
# stop reading when we hit the text.
335
429
my @cvs = $file =~ /((?:(?:[^\n@]+|@[^@]*@)\n?)+)/gs;
337
$cvs{'header'} = { map { s/@@/@/gs;
338
/^@/s && substr($_, 1, -1) || $_ }
339
shift(@cvs) =~ /(\w+)\s*((?:[^;@]+|@[^@]*@)*);/gs };
341
$cvs{'header'}{'symbols'}
342
= { $cvs{'header'}{'symbols'} =~ /(\S+?):(\S+)/g };
434
/^@/s && substr($_, 1, -1) || $_
435
} shift(@cvs) =~ /(\w+)\s*((?:[^;@]+|@[^@]*@)*);/gs
438
$cvs{'header'}{'symbols'} = { $cvs{'header'}{'symbols'} =~ /(\S+?):(\S+)/g };
344
440
my ($orel, $nrel, $rev);
345
while (($orel, $rev) = each %{$cvs{'header'}{'symbols'}}) {
441
while (($orel, $rev) = each %{ $cvs{'header'}{'symbols'} }) {
346
442
$nrel = $config->cvsversion($orel);
347
443
next unless defined($nrel);
357
453
while (@cvs && $cvs[0] !~ /\s*desc/s) {
358
454
my ($r, $v) = shift(@cvs) =~ /\s*(\S+)\s*(.*)/s;
359
$cvs{'branch'}{$r} = { map { s/@@/@/gs;
360
/^@/s && substr($_, 1, -1) || $_ }
361
$v =~ /(\w+)\s*((?:[^;@]+|@[^@]*@)*);/gs };
455
$cvs{'branch'}{$r} = {
458
/^@/s && substr($_, 1, -1) || $_
459
} $v =~ /(\w+)\s*((?:[^;@]+|@[^@]*@)*);/gs
462
delete $cvs{'branch'}{''}; # somehow an empty branch name gets in; delete it
364
464
$cvs{'desc'} = shift(@cvs) =~ /\s*desc\s+((?:[^\n@]+|@[^@]*@)*)\n/s;
365
465
$cvs{'desc'} =~ s/^@|@($|@)/$1/gs;