=================================================================== RCS file: /cvs/cvsweb/cvsweb.cgi,v retrieving revision 1.1.1.7 retrieving revision 3.13 diff -u -p -r1.1.1.7 -r3.13 --- cvsweb/cvsweb.cgi 2000/09/23 20:23:34 1.1.1.7 +++ cvsweb/cvsweb.cgi 2000/08/15 08:35:51 3.13 @@ -9,7 +9,6 @@ # Ken Coar # Dick Balaska # Akinori MUSHA -# Jens-Uwe Mager # # Based on: # * Bill Fenners cvsweb.cgi revision 1.28 available from: @@ -42,8 +41,8 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $zId: cvsweb.cgi,v 1.103 2000/09/20 17:02:29 jumager Exp $ -# $kId: cvsweb.cgi,v 1.26 2000/09/22 11:13:17 knu Exp $ +# $zId: cvsweb.cgi,v 1.93 2000/07/27 17:42:28 hzeller Exp $ +# $Id: cvsweb.cgi,v 3.13 2000/08/15 08:35:51 knu Exp $ # ### @@ -54,8 +53,8 @@ use vars qw ( %CVSROOT %CVSROOTdescr %MIRRORS %DEFAULTVALUE %ICONS %MTYPES %alltags @tabcolors %fileinfo %tags @branchnames %nameprinted %symrev %revsym @allrevisions %date %author @revdisplayorder - @revisions %state %difflines %log %branchpoint @revorder - $prcgi @prcategories $prcategories $mancgi + @revisions %state %difflines %log %branchpoint @revorder $prcgi + @prcategories $prcategories $checkoutMagic $doCheckout $scriptname $scriptwhere $where $pathinfo $Browser $nofilelinks $maycompress @stickyvars %funcline_regexp $is_mod_perl @@ -64,9 +63,8 @@ use vars qw ( $bylog $byfile $hr_default $logsort $cvstree $cvsroot $mimetype $defaultTextPlain $defaultViewable $allow_compress $GZIPBIN $backicon $diricon $fileicon $fullname $newname - $cvstreedefault $body_tag $body_tag_for_src - $logo $defaulttitle $address - $long_intro $short_instruction $shortLogLen + $cvstreedefault $body_tag $logo $defaulttitle $address + $backcolor $long_intro $short_instruction $shortLogLen $show_author $dirtable $tablepadding $columnHeaderColorDefault $columnHeaderColorSorted $hr_breakable $showfunc $hr_ignwhite $hr_ignkeysubst $diffcolorHeading $diffcolorEmpty $diffcolorRemove @@ -78,21 +76,19 @@ use vars qw ( $navigationHeaderColor $tableBorderColor $markupLogColor $tabstop $state $annTable $sel $curbranch @HideModules $module $use_descriptions %descriptions @mytz $dwhere $moddate - $use_moddate $has_zlib $gzip_open + $use_moddate ); sub printDiffSelect($); sub findLastModifiedSubdirs(@); sub htmlify($;$); -sub spacedHtmlText($;$); +sub spacedHtmlText($); sub link($$); sub revcmp($$); sub fatal($$); sub redirect($); sub safeglob($); sub getMimeTypeFromSuffix($); -sub head($;$); -sub scan_directives(@); sub doAnnotate($$); sub doCheckout($$); sub cvswebMarkup($$$); @@ -146,9 +142,8 @@ $allow_version_select = 1; # These are defined to allow checking with perl -cw %CVSROOT = %MIRRORS = %DEFAULTVALUE = %ICONS = %MTYPES = %tags = %alltags = @tabcolors = (); -$cvstreedefault = $body_tag = $body_tag_for_src = -$logo = $defaulttitle = $address = -$long_intro = $short_instruction = $shortLogLen = +$cvstreedefault = $body_tag = $logo = $defaulttitle = $address = +$backcolor = $long_intro = $short_instruction = $shortLogLen = $show_author = $dirtable = $tablepadding = $columnHeaderColorDefault = $columnHeaderColorSorted = $hr_breakable = $showfunc = $hr_ignwhite = $hr_ignkeysubst = $diffcolorHeading = $diffcolorEmpty = $diffcolorRemove = @@ -158,20 +153,13 @@ $allow_markup = $use_java_script = $open_extern_window $extern_window_width = $extern_window_height = $edit_option_form = $checkout_magic = $show_subdir_lastmod = $show_log_in_markup = $v = $navigationHeaderColor = $tableBorderColor = $markupLogColor = -$tabstop = $use_moddate = $moddate = $gzip_open = undef; +$tabstop = $use_moddate = $moddate = undef; ##### End of configuration variables ##### use Time::Local; use IPC::Open2; -# Check if the zlib C library interface is installed, and if yes -# we can avoid using the extra gzip process. -eval { - require Compress::Zlib; -}; -$has_zlib = !$@; - $verbose = $v; $checkoutMagic = "~checkout~"; $pathinfo = defined($ENV{PATH_INFO}) ? $ENV{PATH_INFO} : ''; @@ -209,13 +197,13 @@ $nofilelinks = $is_textbased; # braindamaged MS-Internet Exploders claim that they # accept gzip .. but don't in fact and # display garbage then :-/ -# Turn off gzip if running under mod_perl and no zlib is available, -# piping does not work as expected inside the server. -$maycompress = (((defined($ENV{HTTP_ACCEPT_ENCODING}) - && $ENV{HTTP_ACCEPT_ENCODING} =~ m`gzip`) +# Turn off gzip if running under mod_perl. piping does +# not work as expected inside the server. One can probably +# achieve the same result using Apache::GZIPFilter. +$maycompress = (($ENV{HTTP_ACCEPT_ENCODING} =~ m`gzip` || $is_mozilla3) && !$is_msie - && !($is_mod_perl && !$has_zlib)); + && !$is_mod_perl); # put here the variables we need in order # to hold our state - they will be added (with @@ -359,7 +347,6 @@ foreach my $k (keys %ICONS) { do "$config-$cvstree" if (-f "$config-$cvstree"); $prcategories = '(?:' . join('|', @prcategories) . ')'; -$prcgi .= '%s' if defined($prcgi) && $prcgi !~ /%s/; $fullname = $cvsroot . '/' . $where; $mimetype = &getMimeTypeFromSuffix ($fullname); @@ -371,7 +358,7 @@ $defaultViewable = $allow_markup && viewable($mimetype # ge get an Internal Server Error if we try to pipe the # output through the nonexistent gzip .. # any more elegant ways to prevent this are welcome! -if ($allow_compress && $maycompress && !$has_zlib) { +if ($allow_compress && $maycompress) { foreach (split(/:/, $ENV{PATH})) { if (-x "$_/gzip") { $GZIPBIN = "$_/gzip"; @@ -722,8 +709,7 @@ elsif (-d $fullname) { foreach my $var (@stickyvars) { print "\n" if (defined($input{$var}) - && (!defined($DEFAULTVALUE{$var}) - || $input{$var} ne $DEFAULTVALUE{$var}) + && $input{$var} ne $DEFAULTVALUE{$var} && $input{$var} ne "" && $var ne "only_with_tag"); } @@ -785,18 +771,15 @@ elsif (-d $fullname) { elsif (-f $fullname . ',v') { if (defined($input{'rev'}) || $doCheckout) { &doCheckout($fullname, $input{'rev'}); - gzipclose(); exit; } if (defined($input{'annotate'}) && $allow_annotate) { &doAnnotate($input{'annotate'}); - gzipclose(); exit; } if (defined($input{'r1'}) && defined($input{'r2'})) { &doDiff($fullname, $input{'r1'}, $input{'tr1'}, $input{'r2'}, $input{'tr2'}, $input{'f'}); - gzipclose(); exit; } print("going to dolog($fullname)\n") if ($verbose); @@ -819,7 +802,6 @@ elsif (-d $fullname) { # e.g. foo.c &doDiff($fullname, $input{'r1'}, $input{'tr1'}, $input{'r2'}, $input{'tr2'}, $input{'f'}); - gzipclose(); exit; } elsif (($newname = $fullname) =~ s|/([^/]+)$|/Attic/$1| && @@ -853,8 +835,6 @@ elsif (-d $fullname) { } &fatal("404 Not Found","$where: no such file or directory"); } - -gzipclose(); ## End MAIN sub printDiffSelect($) { @@ -906,7 +886,7 @@ sub findLastModifiedSubdirs(@) { } sub htmlify($;$) { - my($string, $extra) = @_; + my($string, $pr) = @_; # Special Characters; RFC 1866 $string =~ s/&/&/g; @@ -915,52 +895,48 @@ sub htmlify($;$) { $string =~ s/>/>/g; # get URL's as link .. - $string =~ s`(http|ftp|https)(://[-a-zA-Z0-9%.~:_/]+)([?&]([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+)*`$1$2$3`g; # ` + $string =~ s`(http|ftp|https)(://[-a-zA-Z0-9%.~:_/]+)([?&]([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+)*`$1$2$3`; # get e-mails as link - $string =~ s`([-a-zA-Z0-9_.]+@([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})`$1`g; # ` + $string =~ s`([-a-zA-Z0-9_.]+@([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})`$1`; - if ($extra) { - # get PR #'s as link .. - if (defined($prcgi)) { - 1 while $string =~ s`\b(pr[:#]?\s*(?:#?\d+[,\s]\s*)*#?)(\d+)\b`$1 . &link($2, sprintf($prcgi, $2))`ie; # ` - $string =~ s`\b${prcategories}/(\d+)\b`&link($&, sprintf($prcgi, $1))`igeo; # ` - } - - # get manpage specs as link .. - if (defined($mancgi)) { - $string =~ s`\b([a-zA-Z]\w+)\(([0-9n])\)\B`&link($&, sprintf($mancgi, $2, $1))`ge; # ` - } + # get #PR as link .. + if ($pr && defined($prcgi)) { + 1 while $string =~ s`\b(pr[:#]?\s*(?:#?\d+[,\s]\s*)*#?)(\d+)\b`$1$2`i; + $string =~ s`\b${prcategories}/(\d+)\b`$&`igo; } return $string; } -sub spacedHtmlText($;$) { +sub spacedHtmlText($) { local $_ = $_[0]; - my $ts = $_[1] || $tabstop; - # Cut trailing spaces and tabs - s/[ \t]+$//; + # Cut trailing spaces + s/\s+\n$//; - if (defined($ts)) { - # Expand tabs - 1 while s/\t+/' ' x (length($&) * $ts - length($`) % $ts)/e - } + # Expand tabs + s/\t+/' ' x (length($&) * $tabstop - length($`) % $tabstop)/e + if (defined($tabstop)); # replace and (\001 is to protect us from htmlify) # gzip can make excellent use of this repeating pattern :-) + s/\001/\001%/g; #protect our & substitute if ($hr_breakable) { # make every other space 'breakable' + s/ / \001nbsp; \001nbsp; \001nbsp; \001nbsp;/g; # s/ / \001nbsp;/g; # 2 * # leave single space as it is - } else { + } + else { + s/ /\001nbsp;\001nbsp;\001nbsp;\001nbsp;\001nbsp;\001nbsp;\001nbsp;\001nbsp;/g; s/ /\001nbsp;/g; } $_ = htmlify($_); # unescape - y/\001/&/; + s/\001([^%])/&$1/g; + s/\001%/\001/g; return $_; } @@ -973,10 +949,6 @@ sub link($$) { sub revcmp($$) { my($rev1, $rev2) = @_; - - # make no comparison for a tag or a branch - return 0 if $rev1 =~ /[^\d.]/ || $rev2 =~ /[^\d.]/; - my(@r1) = split(/\./, $rev1); my(@r2) = split(/\./, $rev2); my($a,$b); @@ -1080,40 +1052,6 @@ sub getMimeTypeFromSuffix($) { } ############################### -# read first lines like head(1) -############################### -sub head($;$) { - my $fh = $_[0]; - my $linecount = $_[1] || 10; - - my @buf; - - if ($linecount > 0) { - my $i; - for ($i = 0; !eof($fh) && $i < $linecount; $i++) { - push @buf, scalar <$fh>; - } - } else { - @buf = <$fh>; - } - - @buf; -} - -############################### -# scan vim and Emacs directives -############################### -sub scan_directives(@) { - my $ts = undef; - - for (@_) { - $ts = $1 if /\b(?:ts|tabstop|tab-width)[:=]\s*([1-9]\d*)\b/; - } - - ('tabstop' => $ts); -} - -############################### # show Annotation ############################### sub doAnnotate($$) { @@ -1125,7 +1063,7 @@ sub doAnnotate($$) { # make sure the revisions a wellformed, for security # reasons .. - if ($rev =~ /[^\w.]/) { + if (!($rev =~ /^[\d\.]+$/)) { &fatal("404 Not Found", "Malformed query \"$ENV{QUERY_STRING}\""); } @@ -1214,15 +1152,7 @@ sub doAnnotate($$) { else { print "
";
     }
-
-    # prefetch several lines
-    my @buf = head($reader);
-
-    my %d = scan_directives(@buf);
-
-    while (@buf || !eof($reader)) {
-	$_ = @buf ? shift @buf : <$reader>;
-
+    while (<$reader>) {
 	my @words = split;
 	# Adding one is for the (single) space which follows $words[0].
 	my $rest = substr ($_, length ($words[0]) + 1);
@@ -1240,7 +1170,6 @@ sub doAnnotate($$) {
 	    }
 	    else {
 		$revprint = $lrev; $oldLusr = "";
-		$revprint =~ s`^(\S+)`$1`;	# `		
 	    }
 	    if ($lusr eq $oldLusr) {
 		$usrprint = "         ";
@@ -1256,7 +1185,7 @@ sub doAnnotate($$) {
 
 	    print "" if ($isCurrentRev);
 	    printf ("%8s%s%8s %4d:", $revprint, ($isCurrentRev ? "|" : " "), $usrprint, $lineNr);
-	    print spacedHtmlText($line, $d{'tabstop'});
+	    print spacedHtmlText($line);
 	    print "" if ($isCurrentRev);
 	}
 	elsif ($words[0] eq "ok") {
@@ -1285,13 +1214,9 @@ sub doCheckout($$) {
     my ($mimetype,$revopt);
     my $fh = do {local(*FH);};
 
-    if ($rev eq 'HEAD' || $rev eq '.') {
-	$rev = undef;
-    }
-
     # make sure the revisions a wellformed, for security
     # reasons ..
-    if (defined($rev) && $rev =~ /[^\w.]/) {
+    if (defined($rev) && !($rev =~ /^[\d\.]+$/)) {
 	&fatal("404 Not Found",
 		"Malformed query \"$ENV{QUERY_STRING}\"");
     }
@@ -1327,7 +1252,7 @@ sub doCheckout($$) {
     # Safely for a child process to read from.
     if (! open($fh, "-|")) { # child
       open(STDERR, ">&STDOUT"); # Redirect stderr to stdout
-      exec("cvs", "-Rld", $cvsroot, "co", "-p", $revopt, $where);
+      exec("cvs", "-d", $cvsroot, "co", "-p", $revopt, $where);
     }
 #===================================================================
 #Checking out squid/src/ftp.c
@@ -1399,26 +1324,16 @@ sub cvswebMarkup($$$) {
 	    $input{only_with_tag};
     }
     print "";
+    my @content = <$filehandle>;
     my $url = download_url($fileurl, $revision, $mimetype);
     print "
"; if ($mimetype =~ /^image/) { print "
"; } - elsif ($mimetype =~ m%^application/pdf%) { - print "
"; - } else { print "
";
-
-	# prefetch several lines
-	my @buf = head($filehandle);
-
-	my %d = scan_directives(@buf);
-
-	while (@buf || !eof($filehandle)) {
-	    $_ = @buf ? shift @buf : <$filehandle>;
-
-	    print spacedHtmlText($_, $d{'tabstop'});
+	foreach (@content) {
+	    print spacedHtmlText($_);
 	}
 	print "
"; } @@ -1427,7 +1342,7 @@ sub cvswebMarkup($$$) { sub viewable($) { my ($mimetype) = @_; - $mimetype =~ m%^((text|image)/|application/pdf)% ; + $mimetype =~ m%^(text|image)/%; } ############################### @@ -1454,10 +1369,9 @@ sub doDiff($$$$$$) { $rev2 = $tr2; $sym2 = ""; } - # make sure the revisions a wellformed, for security # reasons .. - if ($rev1 =~ /[^\w.]/ || $rev2 =~ /[^\w.]/) { + if (!($rev1 =~ /^[\d\.]+$/) || !($rev2 =~ /^[\d\.]+$/)) { &fatal("404 Not Found", "Malformed query \"$ENV{QUERY_STRING}\""); } @@ -1499,7 +1413,7 @@ sub doDiff($$$$$$) { # apply special options if ($showfunc) { - push @difftype, '-p' if $f =~ /^[cHhu]$/; + push @difftype, '-p'; my($re1, $re2); @@ -1525,7 +1439,6 @@ sub doDiff($$$$$$) { if ($human_readable) { http_header(); &human_readable_diff($fh, $rev2); - gzipclose(); exit; } else { @@ -2195,42 +2108,40 @@ EOF foreach (@stickyvars) { print "\n" if (defined($input{$_}) - && ((!defined($DEFAULTVALUE{$_}) - || $input{$_} ne $DEFAULTVALUE{$_}) - && $input{$_} ne "")); + && ($input{$_} ne $DEFAULTVALUE{$_} && $input{$_} ne "")); } - print "\n"; - print ""; - print "\n"; - print ""; - print "\n"; + print "\n"; + print "
Type of Diff should be a "; + printDiffSelect(0); + print "\n"; print "\n"; - print "
Diffs between \n"; + print "Diffs between \n"; print "\n"; $diffrev = $revdisplayorder[$#revdisplayorder]; $diffrev = $input{"r1"} if (defined($input{"r1"})); - print "
and \n"; + print "\n"; + print " and \n"; print "\n"; $diffrev = $revdisplayorder[0]; $diffrev = $input{"r2"} if (defined($input{"r2"})); - print "
\n"; print "
\n"; - print ""; - print "\n"; - print ""; - print "\n"; if (@branchnames) { - print ""; - print "\n"; + print "\n"; + print "\n"; + print "\n"; } + print "\n"; + print "\n"; foreach (@stickyvars) { - next if ($_ eq "f"); next if ($_ eq "only_with_tag"); next if ($_ eq "logsort"); print "\n" - if (defined($input{$_}) - && (!defined($DEFAULTVALUE{$_}) - || $input{$_} ne $DEFAULTVALUE{$_}) + if (defined($input{$_}) && $input{$_} ne $DEFAULTVALUE{$_} && $input{$_} ne ""); } - print ""; - print ""; - print ""; + print "\n"; + print "\n"; print "\n"; - print "
Preferred Diff type:"; - printDiffSelect($use_java_script); - print "
View only Branch:"; print "\n"; + print "\n"; + foreach (@stickyvars) { + next if ($_ eq "only_with_tag"); + next if ($_ eq "logsort"); + print "\n" + if (defined($input{$_}) && $input{$_} ne $DEFAULTVALUE{$_} + && $input{$_} ne ""); + } + print "View only Branch: \n"; print "
"; - print "\n"; - print "Sort log by:
"; print &html_footer; print "\n"; } @@ -2308,7 +2217,7 @@ sub flush_diff_rows($$$$) { # human_readable_diff(String revision_to_return_to); ## sub human_readable_diff($){ - my ($difftxt, $where_nd, $filename, $pathname, $scriptwhere_nd); + my ($i,$difftxt, $where_nd, $filename, $pathname, $scriptwhere_nd); my ($fh, $rev) = @_; my ($date1, $date2, $r1d, $r2d, $r1r, $r2r, $rev1, $rev2, $sym1, $sym2); my (@rightCol, @leftCol); @@ -2362,15 +2271,9 @@ sub human_readable_diff($){ # cascading style sheets because we've to set the # font and color for each row. anyone ...? #### + while (<$fh>) { + $difftxt = $_; - # prefetch several lines - my @buf = head($fh); - - my %d = scan_directives(@buf); - - while (@buf || !eof($fh)) { - $difftxt = @buf ? shift @buf : <$fh>; - if ($difftxt =~ /^@@/) { ($oldline,$newline,$funname) = $difftxt =~ /@@ \-([0-9]+).*\+([0-9]+).*@@(.*)/; print ""; @@ -2386,7 +2289,7 @@ sub human_readable_diff($){ } else { ($diffcode,$rest) = $difftxt =~ /^([-+ ])(.*)/; - $_ = spacedHtmlText($rest, $d{'tabstop'}); + $_ = spacedHtmlText ($rest); # Add fontface, size $_ = "$fs $_$fe"; @@ -2470,9 +2373,9 @@ sub navigateHeader($$$$$) { $swhere = urlencode($filename) if ($swhere eq ""); print "<\!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">"; print "\n\n"; - print ''; + print ''; print "\n$path$filename - $title - $rev\n"; - print "$body_tag_for_src\n"; + print "\n"; print ""; print "
"; print "$backicon"; @@ -2771,7 +2674,7 @@ sub http_header(;$) { my $content_type = shift || "text/html"; if (defined($moddate)) { if ($is_mod_perl) { - Apache->request->header_out("Last-Modified" => scalar gmtime($moddate) . " GMT"); + Apache->request->header_out(Last_modified => scalar gmtime($moddate) . " GMT"); } else { print "Last-Modified: " . scalar gmtime($moddate) . " GMT\r\n"; @@ -2784,7 +2687,8 @@ sub http_header(;$) { print "Content-type: $content_type\r\n"; } if ($allow_compress && $maycompress) { - if ($has_zlib || (defined($GZIPBIN) && open(GZIP, "|$GZIPBIN -1 -c"))) { + my $fh = do {local(*FH);}; + if (defined($GZIPBIN) && open($fh, "|$GZIPBIN -1 -c")) { if ($is_mod_perl) { Apache->request->content_encoding("x-gzip"); Apache->request->header_out(Vary => "Accept-Encoding"); @@ -2796,11 +2700,7 @@ sub http_header(;$) { print "\r\n"; # Close headers } $| = 1; $| = 0; # Flush header output - if ($has_zlib) { - tie *GZIP, __PACKAGE__, \*STDOUT; - } - select(GZIP); - $gzip_open = 1; + select ($fh); # print "" if ($content_type eq "text/html"); } else { @@ -2825,7 +2725,7 @@ sub http_header(;$) { sub html_header($) { my ($title) = @_; - my $version = '$zRevision: 1.103 $ $kRevision: 1.26 $'; #' + my $version = '$zRevision: 1.93 $ $Revision: 3.13 $'; #' http_header(); print < Compress::Zlib::Z_BEST_COMPRESSION(), - -WindowBits => -Compress::Zlib::MAX_WBITS()) or return undef; - my ($o) = { - handle => $out, - dh => $d, - crc => 0, - len => 0, - }; - my ($header) = pack("c10", MAGIC1, MAGIC2, Compress::Zlib::Z_DEFLATED(), 0,0,0,0,0,0, OSCODE); - print {$o->{handle}} $header; - return bless($o, $class); -} - -sub PRINT { - my ($o) = shift; - my ($buf) = join(defined $, ? $, : "",@_); - my ($len) = length($buf); - my ($compressed, $status) = $o->{dh}->deflate($buf); - print {$o->{handle}} $compressed if defined($compressed); - $o->{crc} = Compress::Zlib::crc32($buf, $o->{crc}); - $o->{len} += $len; - return $len; -} - -sub PRINTF { - my ($o) = shift; - my ($fmt) = shift; - my ($buf) = sprintf($fmt, @_); - my ($len) = length($buf); - my ($compressed, $status) = $o->{dh}->deflate($buf); - print {$o->{handle}} $compressed if defined($compressed); - $o->{crc} = Compress::Zlib::crc32($buf, $o->{crc}); - $o->{len} += $len; - return $len; -} - -sub WRITE { - my ($o, $buf, $len, $off) = @_; - my ($compressed, $status) = $o->{dh}->deflate(substr($buf, 0, $len)); - print {$o->{handle}} $compressed if defined($compressed); - $o->{crc} = Compress::Zlib::crc32(substr($buf, 0, $len), $o->{crc}); - $o->{len} += $len; - return $len; -} - -sub CLOSE { - my ($o) = @_; - return if !defined( $o->{dh}); - my ($buf) = $o->{dh}->flush(); - $buf .= pack("V V", $o->{crc}, $o->{len}); - print {$o->{handle}} $buf; - undef $o->{dh}; -} - -sub DESTROY { - my ($o) = @_; - CLOSE($o); }