# NHSE Repository in a Box (RIB) # # The authors of this software are Paul McMahan and Jeff Horner. # Copyright (c) 1997 by the University of Tennessee. # Permission to use, copy, modify, and distribute this software for any # purpose without fee is hereby granted, provided that this entire notice # is included in all copies of any software which is or includes a copy # or modification of this software and in all copies of the supporting # documentation for such software. # THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED # WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR UNIVERSITY OF TENNESSEE # MAKE ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE # MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. # # this package contains various utilities used by the RIB # $Id: Util.pm.DIST,v 1.5 1998/05/13 02:19:40 rib Exp $ # # $Log: Util.pm.DIST,v $ # Revision 1.5 1998/05/13 02:19:40 rib # reverted version number back to 1.2 since the changes were only bug fixes # # Revision 1.4 1998/05/13 01:26:14 rib # changed version number to 1.3 # # Revision 1.3 1998/01/22 03:17:13 rib # updated version number in Util.pm.DIST # # Revision 1.2 1997/12/18 18:54:34 rib # *** empty log message *** # # Revision 1.1.1.1 1997/12/10 15:59:29 jhorner # RIB pre 1.0 # # Revision 1.3 1997/05/26 15:09:03 jhorner # Changed BackToTop to use RIBDIR/cgi-bin/admin # # Revision 1.2 1997/05/26 14:59:01 jhorner # Changed GetRepoName to now look in RIBDIR/cgi-bin/admin # # Revision 1.1 1997/05/06 19:03:11 jhorner # Initial revision # package RIB::Util; use strict; use Cwd; use Fcntl; use Data::Dumper (); use LWP::UserAgent (); use HTTP::Request (); use RIB::BIDMParser (); use RIB::ConfigParser (); use Digest::MD5; use vars qw($VERSION); $VERSION = "1.2"; sub new { my $self = shift; my $obj = {}; $obj->{'ribdir'} = $self->GetRibDir(); return bless $obj, $self; } # this string is printed at the bottom of most of the web pages # that are created by the RIB cgi-scripts sub BackToTop { my ($class, $repository) = @_; if ($repository) { return "


" . "Back to $repository administration page
\n"; } else { return "


Back to top RIB " . "administration page
\n"; } } # print out an html error message and exit sub ErrorMessage { my $self = shift; my ($message) = @_; print "Error\n"; print "\n"; print "

\n"; print "

Error

\n"; print "

\n"; print "

\n"; print "Error Message:

\n"; print "$message\n"; print $self->BackToTop; print "\n"; exit(1); } # print out an html error message without head and body tags and exit sub HtmlEmbeddedCroak { my $self = shift; my ($message) = @_; print "


Error

\n"; print "

\n"; print "Error Message:

\n"; print "$message


\n"; exit(1); } # print out an html error message without head and body tags and exit sub HtmlCroak { my $self = shift; my ($message) = @_; print "

Error

\n"; print "

\n"; print "Error Message:

\n"; print "$message\n"; print $self->BackToTop; exit(1); } # convert special html characters into their encoded form sub HtmlFix { my $class = shift; my $string = shift; $string =~ s/&/&/g; # gotta do this first!! $string =~ s/"/"/g; $string =~ s//>/g; $string =~ s/\n/ /mg; $string =~ s/\s+/ /mg; $string =~ s/^\s*//; $string =~ s/\s*$//m; return $string; } # get the input to a cgi-script from the http server. # values are hashed in %in sub ReadParse { my ($i, $loc, $key, $val,$in,@in,%in); if ($ENV{'REQUEST_METHOD'} eq "GET") { $in = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { $in = ''; for ($i = 0; $i < $ENV{'CONTENT_LENGTH'}; $i++) { $in .= getc; } } @in = split(/&/,$in); foreach $i (0 .. $#in) { # Convert plus's to spaces $in[$i] =~ s/\+/ /g; # Convert %XX from hex numbers to alphanumeric $in[$i] =~ s/%(..)/pack("c",hex($1))/ge; # Split into key and value. $loc = index($in[$i],"="); $key = substr($in[$i],0,$loc); $val = substr($in[$i],$loc+1); #$in{$key} .= "\0" if (defined($in{$key})); # \0 is the multiple separator #$in{$key} .= $val; $in{$key} = $val; } return %in; } sub PrintHeader { print "Content-type: text/html\n"; if ( defined $_[1]){ print "Pragma: no-cache\n"; } print "\n"; } sub GetRibDir { '/var/www/html/rib'; } sub GetRibUrl { 'http://w3.pppl.gov/rib'; } sub GetRibIindexPath { '/usr/bin'; } sub GetRibIsearchCgiPath { '/usr/bin'; } # extract the repository name from the current working directory. # this method assumes that the current working directory is # beneath &GetRibDir()."/cgi-bin/repositories/", which of # course it should be if this method is expected to be useful. sub GetRepoName { my $dir = Cwd::cwd(); my $path_to = "/cgi-bin/admin/repositories/"; $dir =~ m|$path_to([^/]+)/?$|; return $1; # my $dir = Cwd::cwd(); # print "

Cwd $dir


" if $DEBUG; # print "

\$0 $0

" if $DEBUG; # my $path_to = &GetRibDir() . "/cgi-bin/repositories/"; # #$dir =~ /^\Q$path_to\E(.+)/; # $0 =~ /^\Q$path_to\E(.+)\/.+/; # return $1; } # # The policy for the file locking methods is simply this: # # if LockFile($file) returns true, then you have locked the file # # if LockFile($file) returns false, then you haven't locked the file # # UnlockFile($file) returns nothing, so you don't know if the # unlock succeeded or not, but for simplicity you will assume it has. # # a file is locked if there is a filename called 'filename.lock' # # If available, the file is opened and created atomically. otherwise # the file is opened and created in two steps. # # This policy is NOT 100% effective. The authors understand that # a race condition can still arise in the presence of networked # file systems. The authors also agreed NOT to use the flock() # function (operator) since it is not implemented on ALL platforms. # # # For both methods, $file is the absolute path to the file. '.lock' # will be appended for the locking mechanism. # sub LockFile { my ($self,$file,$time) = @_; my $lock = "$file.lock"; my $times = (defined $time)? $time : 3 ; my $flag = 0; foreach ( 0..$times){ eval { $flag = sysopen(LOCK,$lock, O_WRONLY|O_EXCL|O_CREAT,0644); }; if ($@ or !$flag){ # Failed because either the system messed up, the system # doesn't support sysopen with the Fcntl flags, or the lock # file exists. To somewhat recover from the 'system messed # up, or unsupported Fcntl flags', try to open the regular # (but insecure) two-step way... unless(-e $lock){ if (open(LOCK,"+>$lock")){ close LOCK; return 1; } } } else { close LOCK; return 1; } } return 0; } sub UnlockFile { my ($self,$file) = @_; # We don't care if the next statement works or not. We'll # let the RIB maintainer worry about removing flagrant lock # files. eval { unlink $file.'.lock' }; } # InitNonLocal # # Locks the .nonlocal file for repository and initializes # the nonlocal data structure. # # Will not call unlockfile, because that's a job for CommitNonLocal # # Any code between InitNonLocal and CommitNonLocal should be thought # of as a critical section, so make computing short and sweet. Only # do stuff like monkey with .nonlocal. # # If it succeeds # # $repo is the name of the repository, and $buf if defined is a reference # to a scalar that will include the error message explaining why # InitNonLocal failed. sub InitNonLocal { my ($self,$repo,$buf) = @_; my $nonlocal = $self->GetRibDir ."/repositories/$repo/catalog/.nonlocal"; # return if we can't lock file unless($self->LockFile($nonlocal)){ return 0; } # If it already exists and has nonzero size, then there's # already links in it. Open it, and initialize $self->{'nonlocal'}. if(-e $nonlocal and -s $nonlocal){ # open read/write unless(open(N,"+<$nonlocal")){ # open failed for some reason so give up the lock and return. $self->UnlockFile($nonlocal); return 0; } # Begin Block { local($/) = undef; my $tmp = ; no strict 'vars'; $self->{'nonlocal'} = eval "my $tmp"; if ($@){ close(N); # we haven't written to it yet, so it # should be in tact. $self->UnlockFile($nonlocal); $self->ErrorMessage("An error occured while trying to eval" ." $nonlocal: $@. Please contact your RIB administrator!"); } } # End Block # Store file descriptor for later use. $self->{'fd'} = \*N; } else { # NonLocal doesn't exist or is zero length. Create it and # store descriptor # for later use. unless(open(N,"+>$nonlocal")){ $self->UnlockFile($nonlocal); return 0; } $self->{'nonlocal'} = []; $self->{'fd'} = \*N; } # Initialize other usefile stuff $self->{'file'} = $nonlocal; $self->{'repo'} = $repo; $self->{'touched'} = 0; # flag to see if changed return 1; } # # NonLocalLink provides two tasks # # If called with no args it returns the array # of hashes for each link, where the keys of # the hashs are URL FILE and LM. # # If called with a url arg, then it returns 1 # if the url is in the list, and 0 if not. # sub NonLocalLink { my ($self,$url) = @_; if (defined $url and $url ne ''){ foreach ( @{$self->{'nonlocal'}} ){ return $_ if ($url eq ${_}->{URL}); #return 1 if ($url eq ${_}->{URL}); } return 0; } else { return @{$self->{'nonlocal'}}; } } # Adds a link to the file nonlocal and creates # a catalog entry for it. # # ARGS are $url # # this function also initializes a class variable # cp. # # Nonlocal is actually a list of hashes # and each hash has keys URL, LM, and FILE # AddNonLocalLink only initializes URL and # LM if available. Creating a filename will # probably be left up to generate_catalog.pl sub AddNonLocalLink { my ($self,$url) = @_; my $filepath = $self->GetRibDir . '/repositories/' . $self->GetRepoName . '/catalog/Asset/'; return 0 unless ($url =~ m|^http:\/\/|); my $ua = LWP::UserAgent->new("RIB/0.9.2"); my $req = HTTP::Request->new('GET',$url); my $res = $ua->request($req); if ($res->is_success){ # Create the catalog entry my $bp = RIB::BIDMParser->new(); my $file; if ($bp->parse($res->content)){ # Initialize ConfigParser for self unless(exists $self->{'cp'}) {$self->InitCP;} my $ce = $self->{'cp'}->InstanceOf($url,'Asset',$bp); my $context = MD5->new ; $context->add($res->content); $file = $context->hexdigest() . '.html'; if(open(F,">$filepath/$file")){ my $buf; unless($ce->AsHtml(\$buf,$self->GetRepoName,$url,$self->{'cp'})){ $self->ErrorMessage("There was a problem loading $file.
" . "Reason: ".$ce->ErrorMsg."
Please Contact your" . " RIB administrator!"); } print F $self->ClassHeader('Asset'); print F $buf; print F $self->ClassFooter('Asset'); close(F); } else { $self->ErrorMessage("Could not open $file: Reason: $!
" . "Please contact your RIB administrator!"); } } else { $self->ErrorMessage("There was a problem parsing the content" . " of $url: Reason: ".$bp->ErrorMsg."
Pease contact your" . " RIB administrator.\n"); } my $lm = $res->header('Last-Modified'); push @{$self->{'nonlocal'}}, { URL => $url, LM => $lm, FILE => $file }; $self->{'touched'} = 1; } else { return 0; } } # # UpdateNonLocal # # this method will update a catalog # entry specified by a link in the nonlocal # file. Since the filename of the catalog entry # is the md5 digest of the meta data, it will have # to be deleted and a new filename created. Of course # the last modified date will be updated as well. # # sub UpdateNonLocal { my ($self,$link,$content,$lm,$a,$cp,$err) = @_; my $filepath = $self->GetRibDir.'/repositories/'.$self->GetRepoName.'/catalog/Asset'; my $file; my $context = MD5->new ; $context->add($content); $file = $context->hexdigest() . '.html'; if (open(NEW,">$filepath/$file")){ my $buf; unless($a->AsHtml(\$buf,$self->GetRepoName,$link->{URL},$cp)){ $$err = 'Reason: '. $a->ErrorMsg; return 0; } print NEW $self->ClassHeader('Asset'); print NEW $buf; print NEW $self->ClassFooter('Asset'); close(NEW); } else { $$err = "Could not open $filepath/$file: Reason: $!"; return 0; } if ($link->{FILE} ne $file ){ my $tmp = $link->{FILE}; $link->{FILE} = $file; if ($tmp ne "" && -e $tmp){ unless( unlink "$filepath/".$link->{FILE} ){ print 'Could not delete '. $link->{FILE}.": Reason: $!\n" . 'Please contact your RIB administrator!'; } } } $link->{LM} = $lm; $self->{'touched'} = 1; return 1; } #This function was added to allow AddNonLocalLink #to use it's own configparser sub InitCP { my $self = shift; my $repo = $self->GetRepoName; my $ribdir = $self->GetRibDir; $self->{'cp'} = RIB::ConfigParser->new(); unless($self->{'cp'}->load_config("$ribdir/repositories/$repo/conf/BIDM.conf")){ $self->ErrorMessage("There is a problem with $repo\'s ". "configuration file. Problem: " . $self->{'cp'}->ErrorMsg() . ". Please ". "contact your RIB administrator"); } } sub RemoveNonLocalLink { my ($self,$url) = @_; my $offset = 0; # go through the list of foreign urls to find $url foreach ( $self->NonLocalLink ){ if ($url eq ${_}->{URL}){ # delete the catalog entry if file is present if (defined ${_}->{FILE} and ${_}->{FILE} ne ''){ my $filepath = $self->GetRibDir . "/repositories/" . $self->{'repo'}; unlink "$filepath/catalog/Asset/".${_}->{FILE}; unlink "$filepath/objects/Asset/".${_}->{FILE}; } # now delete the array entry splice(@{$self->{'nonlocal'}},$offset,1); $self->{'touched'} = 1; last; } $offset++; } } # Print to $nonlocal,close it,unlock it, and return sub CommitNonLocal { my $self = shift; if ($self->{'touched'}){ # Does the nonlocal file still have links in it # index will be greater than -1 if ($#{$self->{'nonlocal'}} > -1 ){ $Data::Dumper::Indent = 0; seek $self->{'fd'}, 0, 0; truncate $self->{'fd'}, 0; # funny, we don't need the curlies here. # Note the curlys. they are needed to return the right value print {$self->{'fd'}} Data::Dumper::Dumper($self->{'nonlocal'}); } else { # No more links, so unlink nonlocal unless(unlink $self->{'file'}){ $self->ErrorMessage("Could not unlink ". $self->{'file'} . " Reason: $!
.Please contact your RIB administrator."); } } } close($self->{'fd'}); # funny, we don't need the curlies here. $self->UnlockFile($self->{'file'}); undef $self->{'nonlocal'}; undef $self->{'fd'}; undef $self->{'file'}; undef $self->{'repo'}; undef $self->{'touched'}; undef $self->{'cp'}; } sub ClassHeader { my ($self,$class,$repo) = @_; my $assetheader; unless (defined $repo){ $repo = &GetRepoName; } my $file = &GetRibDir . "/repositories/$repo/conf/$class.header"; if (-f $file and -s $file and open (H, "<$file")){ { local($/) = undef; $assetheader = ; close(H); } } else { close(H); $assetheader = "$class Located in " . &GetRepoName ." Software Repository\n"; unless(open(H, "+>$file")){ $self->ErrorMessage("Couldn't open the file "" . "$file". " . "Reason: $!
Please contact your RIB " . "administrator!"); } print H $assetheader; close(H); } return $assetheader; } sub ClassFooter { my ($self,$class,$repo) = @_; my $assetfooter; unless (defined $repo){ $repo = &GetRepoName; } my $file = &GetRibDir . "/repositories/$repo/conf/$class.footer"; if (-f $file and -s $file and open (H, "<$file")){ { local($/) = undef; $assetfooter = ; close(H); } } else { close(H); $assetfooter = ""; unless(open(H, "+>$file")){ $self->ErrorMessage("Couldn't open the file "" . "$file". " . "Reason: $!
Please contact your RIB " . "administrator!"); } print H $assetfooter; close(H); } return $assetfooter; } sub Date { my $class = shift; my $time = shift; $time ||= time; my ($sec,$min,$hour,$mday,$mon,$year,$wday) = localtime($time); my @num2mon = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); my @num2wday = ("Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat"); $year = 1900 + $year; foreach (\$hour,\$min,\$sec) { $$_ = "0". $$_ if length $$_ == 1; } return "$num2wday[$wday] $num2mon[$mon] $mday $hour:$min:$sec $year"; } sub GetUserName { my $class = shift; return (getpwuid($<))[0]; } sub InsertNewlines { my $class = shift; my $string = shift; my $count = 0; my @chars = split(//,$string); my $newstring = ''; my $char; foreach $char (@chars) { if ((++$count > 50 and $char =~ /\s/) or ($char eq "\n")) { $newstring .= "\n"; $count=0; } else { $newstring .= $char; } } return $newstring; } 1;