Upgrade to libnet 1.21
[p5sagit/p5-mst-13.2.git] / lib / Net / FTP.pm
index 99057af..08c3dc3 100644 (file)
@@ -20,110 +20,98 @@ use Time::Local;
 use Net::Cmd;
 use Net::Config;
 use Fcntl qw(O_WRONLY O_RDONLY O_APPEND O_CREAT O_TRUNC);
-# use AutoLoader qw(AUTOLOAD);
 
-$VERSION = "2.77_01";
+$VERSION = '2.77';
 @ISA     = qw(Exporter Net::Cmd IO::Socket::INET);
 
 # Someday I will "use constant", when I am not bothered to much about
 # compatability with older releases of perl
 
 use vars qw($TELNET_IAC $TELNET_IP $TELNET_DM);
-($TELNET_IAC,$TELNET_IP,$TELNET_DM) = (255,244,242);
+($TELNET_IAC, $TELNET_IP, $TELNET_DM) = (255, 244, 242);
 
-# Name is too long for AutoLoad, it clashes with pasv_xfer
-sub pasv_xfer_unique {
-    my($sftp,$sfile,$dftp,$dfile) = @_;
-    $sftp->pasv_xfer($sfile,$dftp,$dfile,1);
-}
 
 BEGIN {
+
   # make a constant so code is fast'ish
   my $is_os390 = $^O eq 'os390';
-  *trEBCDIC = sub () { $is_os390 }
+  *trEBCDIC = sub () {$is_os390}
 }
 
-1;
-# Having problems with AutoLoader
-#__END__
-
-sub new
-{
- my $pkg  = shift;
- my ($peer,%arg);
- if (@_ % 2) {
-   $peer = shift ;
-   %arg  = @_;
- } else {
-   %arg = @_;
-   $peer=delete $arg{Host};
- }
-
- my $host = $peer;
- my $fire = undef;
- my $fire_type = undef;
-
- if(exists($arg{Firewall}) || Net::Config->requires_firewall($peer))
-  {
-   $fire = $arg{Firewall}
-       || $ENV{FTP_FIREWALL}
-       || $NetConfig{ftp_firewall}
-       || undef;
-
-   if(defined $fire)
-    {
-     $peer = $fire;
-     delete $arg{Port};
-        $fire_type = $arg{FirewallType}
-        || $ENV{FTP_FIREWALL_TYPE}
-        || $NetConfig{firewall_type}
-        || undef;
+
+sub new {
+  my $pkg = shift;
+  my ($peer, %arg);
+  if (@_ % 2) {
+    $peer = shift;
+    %arg  = @_;
+  }
+  else {
+    %arg  = @_;
+    $peer = delete $arg{Host};
+  }
+
+  my $host      = $peer;
+  my $fire      = undef;
+  my $fire_type = undef;
+
+  if (exists($arg{Firewall}) || Net::Config->requires_firewall($peer)) {
+         $fire = $arg{Firewall}
+      || $ENV{FTP_FIREWALL}
+      || $NetConfig{ftp_firewall}
+      || undef;
+
+    if (defined $fire) {
+      $peer = $fire;
+      delete $arg{Port};
+           $fire_type = $arg{FirewallType}
+        || $ENV{FTP_FIREWALL_TYPE}
+        || $NetConfig{firewall_type}
+        || undef;
     }
   }
 
- my $ftp = $pkg->SUPER::new(PeerAddr => $peer, 
-                           PeerPort => $arg{Port} || 'ftp(21)',
-                           LocalAddr => $arg{'LocalAddr'},
-                           Proto    => 'tcp',
-                           Timeout  => defined $arg{Timeout}
-                                               ? $arg{Timeout}
-                                               : 120
-                          ) or return undef;
+  my $ftp = $pkg->SUPER::new(
+    PeerAddr  => $peer,
+    PeerPort  => $arg{Port} || 'ftp(21)',
+    LocalAddr => $arg{'LocalAddr'},
+    Proto     => 'tcp',
+    Timeout   => defined $arg{Timeout}
+    ? $arg{Timeout}
+    : 120
+    )
+    or return undef;
 
- ${*$ftp}{'net_ftp_host'}     = $host;         # Remote hostname
- ${*$ftp}{'net_ftp_type'}     = 'A';           # ASCII/binary/etc mode
- ${*$ftp}{'net_ftp_blksize'}  = abs($arg{'BlockSize'} || 10240);
+  ${*$ftp}{'net_ftp_host'}    = $host;                             # Remote hostname
+  ${*$ftp}{'net_ftp_type'}    = 'A';                               # ASCII/binary/etc mode
+  ${*$ftp}{'net_ftp_blksize'} = abs($arg{'BlockSize'} || 10240);
 
- ${*$ftp}{'net_ftp_localaddr'} = $arg{'LocalAddr'};
+  ${*$ftp}{'net_ftp_localaddr'} = $arg{'LocalAddr'};
 
- ${*$ftp}{'net_ftp_firewall'} = $fire
-       if(defined $fire);
- ${*$ftp}{'net_ftp_firewall_type'} = $fire_type
-       if(defined $fire_type);
+  ${*$ftp}{'net_ftp_firewall'} = $fire
+    if (defined $fire);
+  ${*$ftp}{'net_ftp_firewall_type'} = $fire_type
+    if (defined $fire_type);
 
- ${*$ftp}{'net_ftp_passive'} = int
-       exists $arg{Passive}
-           ? $arg{Passive}
-           : exists $ENV{FTP_PASSIVE}
-               ? $ENV{FTP_PASSIVE}
-               : defined $fire
-                   ? $NetConfig{ftp_ext_passive}
-                   : $NetConfig{ftp_int_passive};      # Whew! :-)
+  ${*$ftp}{'net_ftp_passive'} =
+      int exists $arg{Passive} ? $arg{Passive}
+    : exists $ENV{FTP_PASSIVE} ? $ENV{FTP_PASSIVE}
+    : defined $fire            ? $NetConfig{ftp_ext_passive}
+    : $NetConfig{ftp_int_passive};    # Whew! :-)
 
- $ftp->hash(exists $arg{Hash} ? $arg{Hash} : 0, 1024);
+  $ftp->hash(exists $arg{Hash} ? $arg{Hash} : 0, 1024);
 
- $ftp->autoflush(1);
+  $ftp->autoflush(1);
 
- $ftp->debug(exists $arg{Debug} ? $arg{Debug} : undef);
+  $ftp->debug(exists $arg{Debug} ? $arg{Debug} : undef);
 
- unless ($ftp->response() == CMD_OK)
-  {
-   $ftp->close();
-   $@ = $ftp->message;
-   undef $ftp;
+  unless ($ftp->response() == CMD_OK) {
+    $ftp->close();
+    $@ = $ftp->message;
+    undef $ftp;
   }
 
- $ftp;
+  $ftp;
 }
 
 ##
@@ -132,135 +120,140 @@ sub new
 
 
 sub host {
- my $me = shift;
- ${*$me}{'net_ftp_host'};
+  my $me = shift;
+  ${*$me}{'net_ftp_host'};
 }
 
 
 sub hash {
-    my $ftp = shift;           # self
+  my $ftp = shift;    # self
 
-    my($h,$b) = @_;
-    unless($h) {
-      delete ${*$ftp}{'net_ftp_hash'};
-      return [\*STDERR,0];
-    }
-    ($h,$b) = (ref($h)? $h : \*STDERR, $b || 1024);
-    select((select($h), $|=1)[0]);
-    $b = 512 if $b < 512;
-    ${*$ftp}{'net_ftp_hash'} = [$h, $b];
-}        
-
-sub quit
-{
- my $ftp = shift;
-
- $ftp->_QUIT;
- $ftp->close;
+  my ($h, $b) = @_;
+  unless ($h) {
+    delete ${*$ftp}{'net_ftp_hash'};
+    return [\*STDERR, 0];
+  }
+  ($h, $b) = (ref($h) ? $h : \*STDERR, $b || 1024);
+  select((select($h), $| = 1)[0]);
+  $b = 512 if $b < 512;
+  ${*$ftp}{'net_ftp_hash'} = [$h, $b];
 }
 
-sub DESTROY {}
 
-sub ascii  { shift->type('A',@_); }
-sub binary { shift->type('I',@_); }
+sub quit {
+  my $ftp = shift;
 
-sub ebcdic
-{
- carp "TYPE E is unsupported, shall default to I";
- shift->type('E',@_);
+  $ftp->_QUIT;
+  $ftp->close;
 }
 
-sub byte
-{
- carp "TYPE L is unsupported, shall default to I";
- shift->type('L',@_);
+
+sub DESTROY { }
+
+
+sub ascii  { shift->type('A', @_); }
+sub binary { shift->type('I', @_); }
+
+
+sub ebcdic {
+  carp "TYPE E is unsupported, shall default to I";
+  shift->type('E', @_);
+}
+
+
+sub byte {
+  carp "TYPE L is unsupported, shall default to I";
+  shift->type('L', @_);
 }
 
 # Allow the user to send a command directly, BE CAREFUL !!
 
-sub quot
-{ 
- my $ftp = shift;
- my $cmd = shift;
 
- $ftp->command( uc $cmd, @_);
- $ftp->response();
+sub quot {
+  my $ftp = shift;
+  my $cmd = shift;
+
+  $ftp->command(uc $cmd, @_);
+  $ftp->response();
 }
 
-sub site
-{
- my $ftp = shift;
 
- $ftp->command("SITE", @_);
- $ftp->response();
+sub site {
+  my $ftp = shift;
+
+  $ftp->command("SITE", @_);
+  $ftp->response();
 }
 
-sub mdtm
-{
- my $ftp  = shift;
- my $file = shift;
-
- # Server Y2K bug workaround
- #
- # sigh; some idiotic FTP servers use ("19%d",tm.tm_year) instead of 
- # ("%d",tm.tm_year+1900).  This results in an extra digit in the
- # string returned. To account for this we allow an optional extra
- # digit in the year. Then if the first two digits are 19 we use the
- # remainder, otherwise we subtract 1900 from the whole year.
-
- $ftp->_MDTM($file) && $ftp->message =~ /((\d\d)(\d\d\d?))(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/
-    ? timegm($8,$7,$6,$5,$4-1,$2 eq '19' ? $3 : ($1-1900))
+
+sub mdtm {
+  my $ftp  = shift;
+  my $file = shift;
+
+  # Server Y2K bug workaround
+  #
+  # sigh; some idiotic FTP servers use ("19%d",tm.tm_year) instead of
+  # ("%d",tm.tm_year+1900).  This results in an extra digit in the
+  # string returned. To account for this we allow an optional extra
+  # digit in the year. Then if the first two digits are 19 we use the
+  # remainder, otherwise we subtract 1900 from the whole year.
+
+  $ftp->_MDTM($file)
+    && $ftp->message =~ /((\d\d)(\d\d\d?))(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/
+    ? timegm($8, $7, $6, $5, $4 - 1, $2 eq '19' ? $3 : ($1 - 1900))
     : undef;
 }
 
+
 sub size {
   my $ftp  = shift;
   my $file = shift;
   my $io;
-  if($ftp->supported("SIZE")) {
+  if ($ftp->supported("SIZE")) {
     return $ftp->_SIZE($file)
-       ? ($ftp->message =~ /(\d+)\s*(bytes?\s*)?$/)[0]
-       : undef;
- }
- elsif($ftp->supported("STAT")) {
-   my @msg;
-   return undef
-       unless $ftp->_STAT($file) && (@msg = $ftp->message) == 3;
-   my $line;
-   foreach $line (@msg) {
-     return (split(/\s+/,$line))[4]
-        if $line =~ /^[-rwxSsTt]{10}/
-   }
- }
- else {
-   my @files = $ftp->dir($file);
-   if(@files) {
-     return (split(/\s+/,$1))[4]
-        if $files[0] =~ /^([-rwxSsTt]{10}.*)$/;
-   }
- }
- undef;
+      ? ($ftp->message =~ /(\d+)\s*(bytes?\s*)?$/)[0]
+      : undef;
+  }
+  elsif ($ftp->supported("STAT")) {
+    my @msg;
+    return undef
+      unless $ftp->_STAT($file) && (@msg = $ftp->message) == 3;
+    my $line;
+    foreach $line (@msg) {
+      return (split(/\s+/, $line))[4]
+        if $line =~ /^[-rwxSsTt]{10}/;
+    }
+  }
+  else {
+    my @files = $ftp->dir($file);
+    if (@files) {
+      return (split(/\s+/, $1))[4]
+        if $files[0] =~ /^([-rwxSsTt]{10}.*)$/;
+    }
+  }
+  undef;
 }
 
+
 sub login {
-  my($ftp,$user,$pass,$acct) = @_;
-  my($ok,$ruser,$fwtype);
+  my ($ftp, $user, $pass, $acct) = @_;
+  my ($ok, $ruser, $fwtype);
 
   unless (defined $user) {
     require Net::Netrc;
 
     my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'});
 
-    ($user,$pass,$acct) = $rc->lpa()
-        if ($rc);
-   }
+    ($user, $pass, $acct) = $rc->lpa()
+      if ($rc);
+  }
 
   $user ||= "anonymous";
   $ruser = $user;
 
   $fwtype = ${*$ftp}{'net_ftp_firewall_type'}
-  || $NetConfig{'ftp_firewall_type'}
-  || 0;
+    || $NetConfig{'ftp_firewall_type'}
+    || 0;
 
   if ($fwtype && defined ${*$ftp}{'net_ftp_firewall'}) {
     if ($fwtype == 1 || $fwtype == 7) {
@@ -271,39 +264,39 @@ sub login {
 
       my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'});
 
-      my($fwuser,$fwpass,$fwacct) = $rc ? $rc->lpa() : ();
+      my ($fwuser, $fwpass, $fwacct) = $rc ? $rc->lpa() : ();
 
       if ($fwtype == 5) {
-       $user = join('@',$user,$fwuser,${*$ftp}{'net_ftp_host'});
-       $pass = $pass . '@' . $fwpass;
+        $user = join('@', $user, $fwuser, ${*$ftp}{'net_ftp_host'});
+        $pass = $pass . '@' . $fwpass;
       }
       else {
-       if ($fwtype == 2) {
-         $user .= '@' . ${*$ftp}{'net_ftp_host'};
-       }
-       elsif ($fwtype == 6) {
-         $fwuser .= '@' . ${*$ftp}{'net_ftp_host'};
-       }
+        if ($fwtype == 2) {
+          $user .= '@' . ${*$ftp}{'net_ftp_host'};
+        }
+        elsif ($fwtype == 6) {
+          $fwuser .= '@' . ${*$ftp}{'net_ftp_host'};
+        }
 
-       $ok = $ftp->_USER($fwuser);
+        $ok = $ftp->_USER($fwuser);
 
-       return 0 unless $ok == CMD_OK || $ok == CMD_MORE;
+        return 0 unless $ok == CMD_OK || $ok == CMD_MORE;
 
-       $ok = $ftp->_PASS($fwpass || "");
+        $ok = $ftp->_PASS($fwpass || "");
 
-       return 0 unless $ok == CMD_OK || $ok == CMD_MORE;
+        return 0 unless $ok == CMD_OK || $ok == CMD_MORE;
 
-       $ok = $ftp->_ACCT($fwacct)
-         if defined($fwacct);
+        $ok = $ftp->_ACCT($fwacct)
+          if defined($fwacct);
 
-       if ($fwtype == 3) {
-          $ok = $ftp->command("SITE",${*$ftp}{'net_ftp_host'})->response;
-       }
-       elsif ($fwtype == 4) {
-          $ok = $ftp->command("OPEN",${*$ftp}{'net_ftp_host'})->response;
-       }
+        if ($fwtype == 3) {
+          $ok = $ftp->command("SITE", ${*$ftp}{'net_ftp_host'})->response;
+        }
+        elsif ($fwtype == 4) {
+          $ok = $ftp->command("OPEN", ${*$ftp}{'net_ftp_host'})->response;
+        }
 
-       return 0 unless $ok == CMD_OK || $ok == CMD_MORE;
+        return 0 unless $ok == CMD_OK || $ok == CMD_MORE;
       }
     }
   }
@@ -312,263 +305,254 @@ sub login {
 
   # Some dumb firewalls don't prefix the connection messages
   $ok = $ftp->response()
-        if ($ok == CMD_OK && $ftp->code == 220 && $user =~ /\@/);
+    if ($ok == CMD_OK && $ftp->code == 220 && $user =~ /\@/);
 
   if ($ok == CMD_MORE) {
-    unless(defined $pass) {
+    unless (defined $pass) {
       require Net::Netrc;
 
       my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'}, $ruser);
 
-      ($ruser,$pass,$acct) = $rc->lpa()
-        if ($rc);
+      ($ruser, $pass, $acct) = $rc->lpa()
+        if ($rc);
 
       $pass = '-anonymous@'
-         if (!defined $pass && (!defined($ruser) || $ruser =~ /^anonymous/o));
+        if (!defined $pass && (!defined($ruser) || $ruser =~ /^anonymous/o));
     }
 
     $ok = $ftp->_PASS($pass || "");
   }
 
   $ok = $ftp->_ACCT($acct)
-        if (defined($acct) && ($ok == CMD_MORE || $ok == CMD_OK));
+    if (defined($acct) && ($ok == CMD_MORE || $ok == CMD_OK));
 
   if ($fwtype == 7 && $ok == CMD_OK && defined ${*$ftp}{'net_ftp_firewall'}) {
-    my($f,$auth,$resp) = _auth_id($ftp);
-    $ftp->authorize($auth,$resp) if defined($resp);
+    my ($f, $auth, $resp) = _auth_id($ftp);
+    $ftp->authorize($auth, $resp) if defined($resp);
   }
 
   $ok == CMD_OK;
 }
 
-sub account
-{
- @_ == 2 or croak 'usage: $ftp->account( ACCT )';
- my $ftp = shift;
- my $acct = shift;
- $ftp->_ACCT($acct) == CMD_OK;
+
+sub account {
+  @_ == 2 or croak 'usage: $ftp->account( ACCT )';
+  my $ftp  = shift;
+  my $acct = shift;
+  $ftp->_ACCT($acct) == CMD_OK;
 }
 
+
 sub _auth_id {
- my($ftp,$auth,$resp) = @_;
+  my ($ftp, $auth, $resp) = @_;
 
- unless(defined $resp)
-  {
-   require Net::Netrc;
+  unless (defined $resp) {
+    require Net::Netrc;
 
-   $auth ||= eval { (getpwuid($>))[0] } || $ENV{NAME};
+    $auth ||= eval { (getpwuid($>))[0] } || $ENV{NAME};
 
-   my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}, $auth)
-        || Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'});
+    my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}, $auth)
+      || Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'});
 
-   ($auth,$resp) = $rc->lpa()
-     if ($rc);
+    ($auth, $resp) = $rc->lpa()
+      if ($rc);
   }
-  ($ftp,$auth,$resp);
+  ($ftp, $auth, $resp);
 }
 
-sub authorize
-{
- @_ >= 1 || @_ <= 3 or croak 'usage: $ftp->authorize( [AUTH [, RESP]])';
 
- my($ftp,$auth,$resp) = &_auth_id;
+sub authorize {
+  @_ >= 1 || @_ <= 3 or croak 'usage: $ftp->authorize( [AUTH [, RESP]])';
+
+  my ($ftp, $auth, $resp) = &_auth_id;
 
- my $ok = $ftp->_AUTH($auth || "");
+  my $ok = $ftp->_AUTH($auth || "");
 
- $ok = $ftp->_RESP($resp || "")
-       if ($ok == CMD_MORE);
+  $ok = $ftp->_RESP($resp || "")
+    if ($ok == CMD_MORE);
 
- $ok == CMD_OK;
+  $ok == CMD_OK;
 }
 
-sub rename
-{
- @_ == 3 or croak 'usage: $ftp->rename(FROM, TO)';
 
- my($ftp,$from,$to) = @_;
+sub rename {
+  @_ == 3 or croak 'usage: $ftp->rename(FROM, TO)';
 
- $ftp->_RNFR($from)
+  my ($ftp, $from, $to) = @_;
+
+  $ftp->_RNFR($from)
     && $ftp->_RNTO($to);
 }
 
-sub type
-{
- my $ftp = shift;
- my $type = shift;
- my $oldval = ${*$ftp}{'net_ftp_type'};
 
- return $oldval
-       unless (defined $type);
+sub type {
+  my $ftp    = shift;
+  my $type   = shift;
+  my $oldval = ${*$ftp}{'net_ftp_type'};
+
+  return $oldval
+    unless (defined $type);
 
- return undef
-       unless ($ftp->_TYPE($type,@_));
+  return undef
+    unless ($ftp->_TYPE($type, @_));
 
- ${*$ftp}{'net_ftp_type'} = join(" ",$type,@_);
+  ${*$ftp}{'net_ftp_type'} = join(" ", $type, @_);
 
- $oldval;
+  $oldval;
 }
 
-sub alloc
-{
- my $ftp = shift;
- my $size = shift;
- my $oldval = ${*$ftp}{'net_ftp_allo'};
 
- return $oldval
-       unless (defined $size);
+sub alloc {
+  my $ftp    = shift;
+  my $size   = shift;
+  my $oldval = ${*$ftp}{'net_ftp_allo'};
+
+  return $oldval
+    unless (defined $size);
 
- return undef
-       unless ($ftp->_ALLO($size,@_));
+  return undef
+    unless ($ftp->_ALLO($size, @_));
 
- ${*$ftp}{'net_ftp_allo'} = join(" ",$size,@_);
+  ${*$ftp}{'net_ftp_allo'} = join(" ", $size, @_);
 
- $oldval;
+  $oldval;
 }
 
-sub abort
-{
- my $ftp = shift;
 
- send($ftp,pack("CCC", $TELNET_IAC, $TELNET_IP, $TELNET_IAC),MSG_OOB);
+sub abort {
+  my $ftp = shift;
+
+  send($ftp, pack("CCC", $TELNET_IAC, $TELNET_IP, $TELNET_IAC), MSG_OOB);
 
- $ftp->command(pack("C",$TELNET_DM) . "ABOR");
+  $ftp->command(pack("C", $TELNET_DM) . "ABOR");
 
- ${*$ftp}{'net_ftp_dataconn'}->close()
+  ${*$ftp}{'net_ftp_dataconn'}->close()
     if defined ${*$ftp}{'net_ftp_dataconn'};
 
- $ftp->response();
+  $ftp->response();
 
- $ftp->status == CMD_OK;
+  $ftp->status == CMD_OK;
 }
 
-sub get
-{
- my($ftp,$remote,$local,$where) = @_;
 
- my($loc,$len,$buf,$resp,$data);
- local *FD;
+sub get {
+  my ($ftp, $remote, $local, $where) = @_;
 
- my $localfd = ref($local) || ref(\$local) eq "GLOB";
+  my ($loc, $len, $buf, $resp, $data);
+  local *FD;
 
- ($local = $remote) =~ s#^.*/##
-       unless(defined $local);
+  my $localfd = ref($local) || ref(\$local) eq "GLOB";
 
- croak("Bad remote filename '$remote'\n")
-       if $remote =~ /[\r\n]/s;
+  ($local = $remote) =~ s#^.*/##
+    unless (defined $local);
 
- ${*$ftp}{'net_ftp_rest'} = $where if defined $where;
+  croak("Bad remote filename '$remote'\n")
+    if $remote =~ /[\r\n]/s;
+
+  ${*$ftp}{'net_ftp_rest'} = $where if defined $where;
   my $rest = ${*$ftp}{'net_ftp_rest'};
 
- delete ${*$ftp}{'net_ftp_port'};
- delete ${*$ftp}{'net_ftp_pasv'};
+  delete ${*$ftp}{'net_ftp_port'};
+  delete ${*$ftp}{'net_ftp_pasv'};
 
- $data = $ftp->retr($remote) or
-       return undef;
+  $data = $ftp->retr($remote)
+    or return undef;
 
- if($localfd)
-  {
-   $loc = $local;
+  if ($localfd) {
+    $loc = $local;
   }
- else
-  {
-   $loc = \*FD;
+  else {
+    $loc = \*FD;
 
-   unless(sysopen($loc, $local, O_CREAT | O_WRONLY | ($rest ? O_APPEND : O_TRUNC)))
-    {
-     carp "Cannot open Local file $local: $!\n";
-     $data->abort;
-     return undef;
+    unless (sysopen($loc, $local, O_CREAT | O_WRONLY | ($rest ? O_APPEND: O_TRUNC))) {
+      carp "Cannot open Local file $local: $!\n";
+      $data->abort;
+      return undef;
     }
   }
 
- if($ftp->type eq 'I' && !binmode($loc))
-  {
-   carp "Cannot binmode Local file $local: $!\n";
-   $data->abort;
-   close($loc) unless $localfd;
-   return undef;
+  if ($ftp->type eq 'I' && !binmode($loc)) {
+    carp "Cannot binmode Local file $local: $!\n";
+    $data->abort;
+    close($loc) unless $localfd;
+    return undef;
   }
 
- $buf = '';
- my($count,$hashh,$hashb,$ref) = (0);
+  $buf = '';
+  my ($count, $hashh, $hashb, $ref) = (0);
 
- ($hashh,$hashb) = @$ref
-   if($ref = ${*$ftp}{'net_ftp_hash'});
+  ($hashh, $hashb) = @$ref
+    if ($ref = ${*$ftp}{'net_ftp_hash'});
 
- my $blksize = ${*$ftp}{'net_ftp_blksize'};
- local $\; # Just in case
+  my $blksize = ${*$ftp}{'net_ftp_blksize'};
+  local $\;    # Just in case
 
- while(1)
-  {
-   last unless $len = $data->read($buf,$blksize);
+  while (1) {
+    last unless $len = $data->read($buf, $blksize);
 
-   if (trEBCDIC && $ftp->type ne 'I')
-    {
-     $buf = $ftp->toebcdic($buf);
-     $len = length($buf);
+    if (trEBCDIC && $ftp->type ne 'I') {
+      $buf = $ftp->toebcdic($buf);
+      $len = length($buf);
     }
 
-   if($hashh) {
-    $count += $len;
-    print $hashh "#" x (int($count / $hashb));
-    $count %= $hashb;
-   }
-   unless(print $loc $buf)
-    {
-     carp "Cannot write to Local file $local: $!\n";
-     $data->abort;
-     close($loc)
+    if ($hashh) {
+      $count += $len;
+      print $hashh "#" x (int($count / $hashb));
+      $count %= $hashb;
+    }
+    unless (print $loc $buf) {
+      carp "Cannot write to Local file $local: $!\n";
+      $data->abort;
+      close($loc)
         unless $localfd;
-     return undef;
+      return undef;
     }
   }
 
- print $hashh "\n" if $hashh;
+  print $hashh "\n" if $hashh;
 
- unless ($localfd)
-  {
-   unless (close($loc))
-    {
-     carp "Cannot close file $local (perhaps disk space) $!\n";
-     return undef;
+  unless ($localfd) {
+    unless (close($loc)) {
+      carp "Cannot close file $local (perhaps disk space) $!\n";
+      return undef;
     }
   }
 
- unless ($data->close()) # implied $ftp->response
+  unless ($data->close())    # implied $ftp->response
   {
-   carp "Unable to close datastream";
-   return undef;
+    carp "Unable to close datastream";
+    return undef;
   }
 
- return $local;
+  return $local;
 }
 
-sub cwd
-{
- @_ == 1 || @_ == 2 or croak 'usage: $ftp->cwd( [ DIR ] )';
 
- my($ftp,$dir) = @_;
+sub cwd {
+  @_ == 1 || @_ == 2 or croak 'usage: $ftp->cwd( [ DIR ] )';
+
+  my ($ftp, $dir) = @_;
 
- $dir = "/" unless defined($dir) && $dir =~ /\S/;
+  $dir = "/" unless defined($dir) && $dir =~ /\S/;
 
- $dir eq ".."
+  $dir eq ".."
     ? $ftp->_CDUP()
     : $ftp->_CWD($dir);
 }
 
-sub cdup
-{
- @_ == 1 or croak 'usage: $ftp->cdup()';
- $_[0]->_CDUP;
+
+sub cdup {
+  @_ == 1 or croak 'usage: $ftp->cdup()';
+  $_[0]->_CDUP;
 }
 
-sub pwd
-{
- @_ == 1 || croak 'usage: $ftp->pwd()';
- my $ftp = shift;
 
- $ftp->_PWD();
- $ftp->_extract_path;
+sub pwd {
+  @_ == 1 || croak 'usage: $ftp->pwd()';
+  my $ftp = shift;
+
+  $ftp->_PWD();
+  $ftp->_extract_path;
 }
 
 # rmdir( $ftp, $dir, [ $recurse ] )
@@ -581,51 +565,50 @@ sub pwd
 #
 # Initial version contributed by Dinkum Software
 #
-sub rmdir
-{
-    @_ == 2 || @_ == 3 or croak('usage: $ftp->rmdir( DIR [, RECURSE ] )');
+sub rmdir {
+  @_ == 2 || @_ == 3 or croak('usage: $ftp->rmdir( DIR [, RECURSE ] )');
 
-    # Pick off the args
-    my ($ftp, $dir, $recurse) = @_ ;
-    my $ok;
+  # Pick off the args
+  my ($ftp, $dir, $recurse) = @_;
+  my $ok;
 
-    return $ok
-       if $ok = $ftp->_RMD( $dir ) or !$recurse;
+  return $ok
+    if $ok = $ftp->_RMD($dir)
+    or !$recurse;
 
-    # Try to delete the contents
-    # Get a list of all the files in the directory
-    my @filelist = grep { !/^\.{1,2}$/ } $ftp->ls($dir);
+  # Try to delete the contents
+  # Get a list of all the files in the directory
+  my @filelist = grep { !/^\.{1,2}$/ } $ftp->ls($dir);
 
-    return undef
-       unless @filelist; # failed, it is probably not a directory
-
-    # Go thru and delete each file or the directory
-    my $file;
-    foreach $file (map { m,/, ? $_ : "$dir/$_" } @filelist)
-    {
-       next  # successfully deleted the file
-           if $ftp->delete($file);
-
-       # Failed to delete it, assume its a directory
-       # Recurse and ignore errors, the final rmdir() will
-       # fail on any errors here
-       return $ok
-           unless $ok = $ftp->rmdir($file, 1) ;
-    }
+  return undef
+    unless @filelist;    # failed, it is probably not a directory
 
-    # Directory should be empty
-    # Try to remove the directory again
-    # Pass results directly to caller
-    # If any of the prior deletes failed, this
-    # rmdir() will fail because directory is not empty
-    return $ftp->_RMD($dir) ;
+  # Go thru and delete each file or the directory
+  my $file;
+  foreach $file (map { m,/, ? $_ : "$dir/$_" } @filelist) {
+    next                 # successfully deleted the file
+      if $ftp->delete($file);
+
+    # Failed to delete it, assume its a directory
+    # Recurse and ignore errors, the final rmdir() will
+    # fail on any errors here
+    return $ok
+      unless $ok = $ftp->rmdir($file, 1);
+  }
+
+  # Directory should be empty
+  # Try to remove the directory again
+  # Pass results directly to caller
+  # If any of the prior deletes failed, this
+  # rmdir() will fail because directory is not empty
+  return $ftp->_RMD($dir);
 }
 
-sub restart
-{
+
+sub restart {
   @_ == 2 || croak 'usage: $ftp->restart( BYTE_OFFSET )';
 
-  my($ftp,$where) = @_;
+  my ($ftp, $where) = @_;
 
   ${*$ftp}{'net_ftp_rest'} = $where;
 
@@ -633,285 +616,275 @@ sub restart
 }
 
 
-sub mkdir
-{
- @_ == 2 || @_ == 3 or croak 'usage: $ftp->mkdir( DIR [, RECURSE ] )';
+sub mkdir {
+  @_ == 2 || @_ == 3 or croak 'usage: $ftp->mkdir( DIR [, RECURSE ] )';
 
- my($ftp,$dir,$recurse) = @_;
+  my ($ftp, $dir, $recurse) = @_;
 
- $ftp->_MKD($dir) || $recurse or
-    return undef;
+  $ftp->_MKD($dir) || $recurse
+    or return undef;
 
- my $path = $dir;
+  my $path = $dir;
 
- unless($ftp->ok)
-  {
-   my @path = split(m#(?=/+)#, $dir);
+  unless ($ftp->ok) {
+    my @path = split(m#(?=/+)#, $dir);
 
-   $path = "";
+    $path = "";
 
-   while(@path)
-    {
-     $path .= shift @path;
+    while (@path) {
+      $path .= shift @path;
 
-     $ftp->_MKD($path);
+      $ftp->_MKD($path);
 
-     $path = $ftp->_extract_path($path);
+      $path = $ftp->_extract_path($path);
     }
 
-   # If the creation of the last element was not successful, see if we
-   # can cd to it, if so then return path
+    # If the creation of the last element was not successful, see if we
+    # can cd to it, if so then return path
 
-   unless($ftp->ok)
-    {
-     my($status,$message) = ($ftp->status,$ftp->message);
-     my $pwd = $ftp->pwd;
+    unless ($ftp->ok) {
+      my ($status, $message) = ($ftp->status, $ftp->message);
+      my $pwd = $ftp->pwd;
 
-     if($pwd && $ftp->cwd($dir))
-      {
-       $path = $dir;
-       $ftp->cwd($pwd);
+      if ($pwd && $ftp->cwd($dir)) {
+        $path = $dir;
+        $ftp->cwd($pwd);
       }
-     else
-      {
-       undef $path;
+      else {
+        undef $path;
       }
-     $ftp->set_status($status,$message);
+      $ftp->set_status($status, $message);
     }
   }
 
- $path;
+  $path;
 }
 
-sub delete
-{
- @_ == 2 || croak 'usage: $ftp->delete( FILENAME )';
 
- $_[0]->_DELE($_[1]);
+sub delete {
+  @_ == 2 || croak 'usage: $ftp->delete( FILENAME )';
+
+  $_[0]->_DELE($_[1]);
 }
 
-sub put        { shift->_store_cmd("stor",@_) }
-sub put_unique { shift->_store_cmd("stou",@_) }
-sub append     { shift->_store_cmd("appe",@_) }
 
-sub nlst { shift->_data_cmd("NLST",@_) }
-sub list { shift->_data_cmd("LIST",@_) }
-sub retr { shift->_data_cmd("RETR",@_) }
-sub stor { shift->_data_cmd("STOR",@_) }
-sub stou { shift->_data_cmd("STOU",@_) }
-sub appe { shift->_data_cmd("APPE",@_) }
+sub put        { shift->_store_cmd("stor", @_) }
+sub put_unique { shift->_store_cmd("stou", @_) }
+sub append     { shift->_store_cmd("appe", @_) }
 
-sub _store_cmd 
-{
- my($ftp,$cmd,$local,$remote) = @_;
- my($loc,$sock,$len,$buf);
- local *FD;
 
- my $localfd = ref($local) || ref(\$local) eq "GLOB";
+sub nlst { shift->_data_cmd("NLST", @_) }
+sub list { shift->_data_cmd("LIST", @_) }
+sub retr { shift->_data_cmd("RETR", @_) }
+sub stor { shift->_data_cmd("STOR", @_) }
+sub stou { shift->_data_cmd("STOU", @_) }
+sub appe { shift->_data_cmd("APPE", @_) }
 
- unless(defined $remote)
-  {
-   croak 'Must specify remote filename with stream input'
-       if $localfd;
 
-   require File::Basename;
-   $remote = File::Basename::basename($local);
+sub _store_cmd {
+  my ($ftp, $cmd, $local, $remote) = @_;
+  my ($loc, $sock, $len, $buf);
+  local *FD;
+
+  my $localfd = ref($local) || ref(\$local) eq "GLOB";
+
+  unless (defined $remote) {
+    croak 'Must specify remote filename with stream input'
+      if $localfd;
+
+    require File::Basename;
+    $remote = File::Basename::basename($local);
   }
- if( defined ${*$ftp}{'net_ftp_allo'} ) 
-  {
-   delete ${*$ftp}{'net_ftp_allo'};
-  } else 
-  {
-   # if the user hasn't already invoked the alloc method since the last 
-   # _store_cmd call, figure out if the local file is a regular file(not
-   # a pipe, or device) and if so get the file size from stat, and send
-   # an ALLO command before sending the STOR, STOU, or APPE command.
-   my $size = do { local $^W; -f $local && -s _ }; # no ALLO if sending data from a pipe
-   $ftp->_ALLO($size) if $size;
+  if (defined ${*$ftp}{'net_ftp_allo'}) {
+    delete ${*$ftp}{'net_ftp_allo'};
   }
- croak("Bad remote filename '$remote'\n")
-       if $remote =~ /[\r\n]/s;
+  else {
+
+    # if the user hasn't already invoked the alloc method since the last
+    # _store_cmd call, figure out if the local file is a regular file(not
+    # a pipe, or device) and if so get the file size from stat, and send
+    # an ALLO command before sending the STOR, STOU, or APPE command.
+    my $size = do { local $^W; -f $local && -s _ };    # no ALLO if sending data from a pipe
+    $ftp->_ALLO($size) if $size;
+  }
+  croak("Bad remote filename '$remote'\n")
+    if $remote =~ /[\r\n]/s;
 
- if($localfd)
-  {
-   $loc = $local;
+  if ($localfd) {
+    $loc = $local;
   }
- else
-  {
-   $loc = \*FD;
+  else {
+    $loc = \*FD;
 
-   unless(sysopen($loc, $local, O_RDONLY))
-    {
-     carp "Cannot open Local file $local: $!\n";
-     return undef;
+    unless (sysopen($loc, $local, O_RDONLY)) {
+      carp "Cannot open Local file $local: $!\n";
+      return undef;
     }
   }
 
- if($ftp->type eq 'I' && !binmode($loc))
-  {
-   carp "Cannot binmode Local file $local: $!\n";
-   return undef;
+  if ($ftp->type eq 'I' && !binmode($loc)) {
+    carp "Cannot binmode Local file $local: $!\n";
+    return undef;
   }
 
- delete ${*$ftp}{'net_ftp_port'};
- delete ${*$ftp}{'net_ftp_pasv'};
+  delete ${*$ftp}{'net_ftp_port'};
+  delete ${*$ftp}{'net_ftp_pasv'};
 
- $sock = $ftp->_data_cmd($cmd, $remote) or 
-       return undef;
+  $sock = $ftp->_data_cmd($cmd, $remote)
+    or return undef;
 
- $remote = ($ftp->message =~ /FILE:\s*(.*)/)[0]
-   if 'STOU' eq uc $cmd;
+  $remote = ($ftp->message =~ /FILE:\s*(.*)/)[0]
+    if 'STOU' eq uc $cmd;
 
- my $blksize = ${*$ftp}{'net_ftp_blksize'};
+  my $blksize = ${*$ftp}{'net_ftp_blksize'};
 
- my($count,$hashh,$hashb,$ref) = (0);
+  my ($count, $hashh, $hashb, $ref) = (0);
 
- ($hashh,$hashb) = @$ref
-   if($ref = ${*$ftp}{'net_ftp_hash'});
+  ($hashh, $hashb) = @$ref
+    if ($ref = ${*$ftp}{'net_ftp_hash'});
 
- while(1)
-  {
-   last unless $len = read($loc,$buf="",$blksize);
+  while (1) {
+    last unless $len = read($loc, $buf = "", $blksize);
 
-   if (trEBCDIC && $ftp->type ne 'I')
-    {
-     $buf = $ftp->toascii($buf); 
-     $len = length($buf);
+    if (trEBCDIC && $ftp->type ne 'I') {
+      $buf = $ftp->toascii($buf);
+      $len = length($buf);
     }
 
-   if($hashh) {
-    $count += $len;
-    print $hashh "#" x (int($count / $hashb));
-    $count %= $hashb;
-   }
-
-   my $wlen;
-   unless(defined($wlen = $sock->write($buf,$len)) && $wlen == $len)
-    {
-     $sock->abort;
-     close($loc)
-       unless $localfd;
-     print $hashh "\n" if $hashh;
-     return undef;
+    if ($hashh) {
+      $count += $len;
+      print $hashh "#" x (int($count / $hashb));
+      $count %= $hashb;
+    }
+
+    my $wlen;
+    unless (defined($wlen = $sock->write($buf, $len)) && $wlen == $len) {
+      $sock->abort;
+      close($loc)
+        unless $localfd;
+      print $hashh "\n" if $hashh;
+      return undef;
     }
   }
 
- print $hashh "\n" if $hashh;
+  print $hashh "\n" if $hashh;
 
- close($loc)
-       unless $localfd;
+  close($loc)
+    unless $localfd;
 
- $sock->close() or
-       return undef;
+  $sock->close()
+    or return undef;
 
- if ('STOU' eq uc $cmd and $ftp->message =~ m/unique\s+file\s*name\s*:\s*(.*)\)|"(.*)"/)
-  {
-   require File::Basename;
-   $remote = File::Basename::basename($+) 
+  if ('STOU' eq uc $cmd and $ftp->message =~ m/unique\s+file\s*name\s*:\s*(.*)\)|"(.*)"/) {
+    require File::Basename;
+    $remote = File::Basename::basename($+);
   }
 
- return $remote;
+  return $remote;
 }
 
-sub port
-{
- @_ == 1 || @_ == 2 or croak 'usage: $ftp->port([PORT])';
 
- my($ftp,$port) = @_;
- my $ok;
+sub port {
+  @_ == 1 || @_ == 2 or croak 'usage: $ftp->port([PORT])';
 
- delete ${*$ftp}{'net_ftp_intern_port'};
+  my ($ftp, $port) = @_;
+  my $ok;
 
- unless(defined $port)
-  {
-   # create a Listen socket at same address as the command socket
+  delete ${*$ftp}{'net_ftp_intern_port'};
 
-   ${*$ftp}{'net_ftp_listen'} ||= IO::Socket::INET->new(Listen    => 5,
-                                                       Proto     => 'tcp',
-                                                       Timeout   => $ftp->timeout,
-                                                       LocalAddr => $ftp->sockhost,
-                                                      );
+  unless (defined $port) {
 
-   my $listen = ${*$ftp}{'net_ftp_listen'};
+    # create a Listen socket at same address as the command socket
 
-   my($myport, @myaddr) = ($listen->sockport, split(/\./,$listen->sockhost));
+    ${*$ftp}{'net_ftp_listen'} ||= IO::Socket::INET->new(
+      Listen    => 5,
+      Proto     => 'tcp',
+      Timeout   => $ftp->timeout,
+      LocalAddr => $ftp->sockhost,
+    );
 
-   $port = join(',', @myaddr, $myport >> 8, $myport & 0xff);
+    my $listen = ${*$ftp}{'net_ftp_listen'};
 
-   ${*$ftp}{'net_ftp_intern_port'} = 1;
+    my ($myport, @myaddr) = ($listen->sockport, split(/\./, $listen->sockhost));
+
+    $port = join(',', @myaddr, $myport >> 8, $myport & 0xff);
+
+    ${*$ftp}{'net_ftp_intern_port'} = 1;
   }
 
- $ok = $ftp->_PORT($port);
+  $ok = $ftp->_PORT($port);
 
- ${*$ftp}{'net_ftp_port'} = $port;
+  ${*$ftp}{'net_ftp_port'} = $port;
 
- $ok;
+  $ok;
 }
 
-sub ls  { shift->_list_cmd("NLST",@_); }
-sub dir { shift->_list_cmd("LIST",@_); }
 
-sub pasv
-{
- @_ == 1 or croak 'usage: $ftp->pasv()';
+sub ls  { shift->_list_cmd("NLST", @_); }
+sub dir { shift->_list_cmd("LIST", @_); }
+
+
+sub pasv {
+  @_ == 1 or croak 'usage: $ftp->pasv()';
 
- my $ftp = shift;
+  my $ftp = shift;
 
- delete ${*$ftp}{'net_ftp_intern_port'};
+  delete ${*$ftp}{'net_ftp_intern_port'};
 
- $ftp->_PASV && $ftp->message =~ /(\d+(,\d+)+)/
+  $ftp->_PASV && $ftp->message =~ /(\d+(,\d+)+)/
     ? ${*$ftp}{'net_ftp_pasv'} = $1
-    : undef;    
+    : undef;
 }
 
-sub unique_name
-{
- my $ftp = shift;
- ${*$ftp}{'net_ftp_unique'} || undef;
+
+sub unique_name {
+  my $ftp = shift;
+  ${*$ftp}{'net_ftp_unique'} || undef;
 }
 
+
 sub supported {
-    @_ == 2 or croak 'usage: $ftp->supported( CMD )';
-    my $ftp = shift;
-    my $cmd = uc shift;
-    my $hash = ${*$ftp}{'net_ftp_supported'} ||= {};
-
-    return $hash->{$cmd}
-        if exists $hash->{$cmd};
-
-    return $hash->{$cmd} = 0
-       unless $ftp->_HELP($cmd);
-
-    my $text = $ftp->message;
-    if($text =~ /following\s+commands/i) {
-       $text =~ s/^.*\n//;
-        while($text =~ /(\*?)(\w+)(\*?)/sg) {
-            $hash->{"\U$2"} = !length("$1$3");
-        }
-    }
-    else {
-       $hash->{$cmd} = $text !~ /unimplemented/i;
+  @_ == 2 or croak 'usage: $ftp->supported( CMD )';
+  my $ftp  = shift;
+  my $cmd  = uc shift;
+  my $hash = ${*$ftp}{'net_ftp_supported'} ||= {};
+
+  return $hash->{$cmd}
+    if exists $hash->{$cmd};
+
+  return $hash->{$cmd} = 0
+    unless $ftp->_HELP($cmd);
+
+  my $text = $ftp->message;
+  if ($text =~ /following\s+commands/i) {
+    $text =~ s/^.*\n//;
+    while ($text =~ /(\*?)(\w+)(\*?)/sg) {
+      $hash->{"\U$2"} = !length("$1$3");
     }
+  }
+  else {
+    $hash->{$cmd} = $text !~ /unimplemented/i;
+  }
 
-    $hash->{$cmd} ||= 0;
+  $hash->{$cmd} ||= 0;
 }
 
 ##
 ## Deprecated methods
 ##
 
-sub lsl
-{
- carp "Use of Net::FTP::lsl deprecated, use 'dir'"
+
+sub lsl {
+  carp "Use of Net::FTP::lsl deprecated, use 'dir'"
     if $^W;
- goto &dir;
+  goto &dir;
 }
 
-sub authorise
-{
- carp "Use of Net::FTP::authorise deprecated, use 'authorize'"
+
+sub authorise {
+  carp "Use of Net::FTP::authorise deprecated, use 'authorize'"
     if $^W;
- goto &authorize;
+  goto &authorize;
 }
 
 
@@ -919,270 +892,299 @@ sub authorise
 ## Private methods
 ##
 
-sub _extract_path
-{
- my($ftp, $path) = @_;
 
- # This tries to work both with and without the quote doubling
- # convention (RFC 959 requires it, but the first 3 servers I checked
- # didn't implement it).  It will fail on a server which uses a quote in
- # the message which isn't a part of or surrounding the path.
- $ftp->ok &&
-    $ftp->message =~ /(?:^|\s)\"(.*)\"(?:$|\s)/ &&
-    ($path = $1) =~ s/\"\"/\"/g;
+sub _extract_path {
+  my ($ftp, $path) = @_;
 
- $path;
+  # This tries to work both with and without the quote doubling
+  # convention (RFC 959 requires it, but the first 3 servers I checked
+  # didn't implement it).  It will fail on a server which uses a quote in
+  # the message which isn't a part of or surrounding the path.
+  $ftp->ok
+    && $ftp->message =~ /(?:^|\s)\"(.*)\"(?:$|\s)/
+    && ($path = $1) =~ s/\"\"/\"/g;
+
+  $path;
 }
 
 ##
 ## Communication methods
 ##
 
-sub _dataconn
-{
- my $ftp = shift;
- my $data = undef;
- my $pkg = "Net::FTP::" . $ftp->type;
 
- eval "require " . $pkg;
+sub _dataconn {
+  my $ftp  = shift;
+  my $data = undef;
+  my $pkg  = "Net::FTP::" . $ftp->type;
 
- $pkg =~ s/ /_/g;
+  eval "require " . $pkg;
 
- delete ${*$ftp}{'net_ftp_dataconn'};
+  $pkg =~ s/ /_/g;
 
- if(defined ${*$ftp}{'net_ftp_pasv'})
-  {
-   my @port = map { 0+$_ } split(/,/,${*$ftp}{'net_ftp_pasv'});
+  delete ${*$ftp}{'net_ftp_dataconn'};
 
-   $data = $pkg->new(PeerAddr => join(".",@port[0..3]),
-                    PeerPort => $port[4] * 256 + $port[5],
-                    LocalAddr => ${*$ftp}{'net_ftp_localaddr'},
-                    Proto    => 'tcp'
-                   );
+  if (defined ${*$ftp}{'net_ftp_pasv'}) {
+    my @port = map { 0 + $_ } split(/,/, ${*$ftp}{'net_ftp_pasv'});
+
+    $data = $pkg->new(
+      PeerAddr  => join(".", @port[0 .. 3]),
+      PeerPort  => $port[4] * 256 + $port[5],
+      LocalAddr => ${*$ftp}{'net_ftp_localaddr'},
+      Proto     => 'tcp'
+    );
   }
- elsif(defined ${*$ftp}{'net_ftp_listen'})
-  {
-   $data = ${*$ftp}{'net_ftp_listen'}->accept($pkg);
-   close(delete ${*$ftp}{'net_ftp_listen'});
+  elsif (defined ${*$ftp}{'net_ftp_listen'}) {
+    $data = ${*$ftp}{'net_ftp_listen'}->accept($pkg);
+    close(delete ${*$ftp}{'net_ftp_listen'});
   }
 
- if($data)
-  {
-   ${*$data} = "";
-   $data->timeout($ftp->timeout);
-   ${*$ftp}{'net_ftp_dataconn'} = $data;
-   ${*$data}{'net_ftp_cmd'} = $ftp;
-   ${*$data}{'net_ftp_blksize'} = ${*$ftp}{'net_ftp_blksize'};
+  if ($data) {
+    ${*$data} = "";
+    $data->timeout($ftp->timeout);
+    ${*$ftp}{'net_ftp_dataconn'} = $data;
+    ${*$data}{'net_ftp_cmd'}     = $ftp;
+    ${*$data}{'net_ftp_blksize'} = ${*$ftp}{'net_ftp_blksize'};
   }
 
- $data;
+  $data;
 }
 
-sub _list_cmd
-{
- my $ftp = shift;
- my $cmd = uc shift;
 
- delete ${*$ftp}{'net_ftp_port'};
- delete ${*$ftp}{'net_ftp_pasv'};
+sub _list_cmd {
+  my $ftp = shift;
+  my $cmd = uc shift;
 
- my $data = $ftp->_data_cmd($cmd,@_);
+  delete ${*$ftp}{'net_ftp_port'};
+  delete ${*$ftp}{'net_ftp_pasv'};
 
- return
-       unless(defined $data);
+  my $data = $ftp->_data_cmd($cmd, @_);
 
- require Net::FTP::A;
- bless $data, "Net::FTP::A"; # Force ASCII mode
+  return
+    unless (defined $data);
 
- my $databuf = '';
- my $buf = '';
- my $blksize = ${*$ftp}{'net_ftp_blksize'};
+  require Net::FTP::A;
+  bless $data, "Net::FTP::A";    # Force ASCII mode
 
- while($data->read($databuf,$blksize)) {
-   $buf .= $databuf;
- }
+  my $databuf = '';
+  my $buf     = '';
+  my $blksize = ${*$ftp}{'net_ftp_blksize'};
 
- my $list = [ split(/\n/,$buf) ];
+  while ($data->read($databuf, $blksize)) {
+    $buf .= $databuf;
+  }
 
- $data->close();
+  my $list = [split(/\n/, $buf)];
 
- if (trEBCDIC)
-  {
-   for (@$list) { $_ = $ftp->toebcdic($_) }
+  $data->close();
+
+  if (trEBCDIC) {
+    for (@$list) { $_ = $ftp->toebcdic($_) }
   }
 
- wantarray ? @{$list}
-           : $list;
+  wantarray
+    ? @{$list}
+    : $list;
 }
 
-sub _data_cmd
-{
- my $ftp = shift;
- my $cmd = uc shift;
- my $ok = 1;
- my $where = delete ${*$ftp}{'net_ftp_rest'} || 0;
- my $arg;
-
- for $arg (@_) {
-   croak("Bad argument '$arg'\n")
-       if $arg =~ /[\r\n]/s;
- }
-
- if(${*$ftp}{'net_ftp_passive'} &&
-     !defined ${*$ftp}{'net_ftp_pasv'} &&
-     !defined ${*$ftp}{'net_ftp_port'})
+
+sub _data_cmd {
+  my $ftp   = shift;
+  my $cmd   = uc shift;
+  my $ok    = 1;
+  my $where = delete ${*$ftp}{'net_ftp_rest'} || 0;
+  my $arg;
+
+  for $arg (@_) {
+    croak("Bad argument '$arg'\n")
+      if $arg =~ /[\r\n]/s;
+  }
+
+  if ( ${*$ftp}{'net_ftp_passive'}
+    && !defined ${*$ftp}{'net_ftp_pasv'}
+    && !defined ${*$ftp}{'net_ftp_port'})
   {
-   my $data = undef;
-
-   $ok = defined $ftp->pasv;
-   $ok = $ftp->_REST($where)
-       if $ok && $where;
-
-   if($ok)
-    {
-     $ftp->command($cmd,@_);
-     $data = $ftp->_dataconn();
-     $ok = CMD_INFO == $ftp->response();
-     if($ok) 
-      {
-       $data->reading
-         if $data && $cmd =~ /RETR|LIST|NLST/;
-       return $data
+    my $data = undef;
+
+    $ok = defined $ftp->pasv;
+    $ok = $ftp->_REST($where)
+      if $ok && $where;
+
+    if ($ok) {
+      $ftp->command($cmd, @_);
+      $data = $ftp->_dataconn();
+      $ok   = CMD_INFO == $ftp->response();
+      if ($ok) {
+        $data->reading
+          if $data && $cmd =~ /RETR|LIST|NLST/;
+        return $data;
       }
-     $data->_close
-       if $data;
+      $data->_close
+        if $data;
     }
-   return undef;
+    return undef;
   }
 
- $ok = $ftp->port
-    unless (defined ${*$ftp}{'net_ftp_port'} ||
-            defined ${*$ftp}{'net_ftp_pasv'});
+  $ok = $ftp->port
+    unless (defined ${*$ftp}{'net_ftp_port'}
+    || defined ${*$ftp}{'net_ftp_pasv'});
 
- $ok = $ftp->_REST($where)
+  $ok = $ftp->_REST($where)
     if $ok && $where;
 
- return undef
+  return undef
     unless $ok;
 
- $ftp->command($cmd,@_);
+  $ftp->command($cmd, @_);
 
- return 1
-    if(defined ${*$ftp}{'net_ftp_pasv'});
+  return 1
+    if (defined ${*$ftp}{'net_ftp_pasv'});
 
- $ok = CMD_INFO == $ftp->response();
+  $ok = CMD_INFO == $ftp->response();
 
- return $ok 
+  return $ok
     unless exists ${*$ftp}{'net_ftp_intern_port'};
 
- if($ok) {
-   my $data = $ftp->_dataconn();
+  if ($ok) {
+    my $data = $ftp->_dataconn();
 
-   $data->reading
-         if $data && $cmd =~ /RETR|LIST|NLST/;
+    $data->reading
+      if $data && $cmd =~ /RETR|LIST|NLST/;
 
-   return $data;
- }
+    return $data;
+  }
 
 
- close(delete ${*$ftp}{'net_ftp_listen'});
+  close(delete ${*$ftp}{'net_ftp_listen'});
 
- return undef;
+  return undef;
 }
 
 ##
 ## Over-ride methods (Net::Cmd)
 ##
 
+
 sub debug_text { $_[2] =~ /^(pass|resp|acct)/i ? "$1 ....\n" : $_[2]; }
 
-sub command
-{
- my $ftp = shift;
 
- delete ${*$ftp}{'net_ftp_port'};
- $ftp->SUPER::command(@_);
+sub command {
+  my $ftp = shift;
+
+  delete ${*$ftp}{'net_ftp_port'};
+  $ftp->SUPER::command(@_);
 }
 
-sub response
-{
- my $ftp = shift;
- my $code = $ftp->SUPER::response();
 
- delete ${*$ftp}{'net_ftp_pasv'}
+sub response {
+  my $ftp  = shift;
+  my $code = $ftp->SUPER::response();
+
+  delete ${*$ftp}{'net_ftp_pasv'}
     if ($code != CMD_MORE && $code != CMD_INFO);
 
- $code;
+  $code;
 }
 
-sub parse_response
-{
- return ($1, $2 eq "-")
+
+sub parse_response {
+  return ($1, $2 eq "-")
     if $_[1] =~ s/^(\d\d\d)([- ]?)//o;
 
- my $ftp = shift;
+  my $ftp = shift;
 
- # Darn MS FTP server is a load of CRAP !!!!
- return ()
-       unless ${*$ftp}{'net_cmd_code'} + 0;
+  # Darn MS FTP server is a load of CRAP !!!!
+  return ()
+    unless ${*$ftp}{'net_cmd_code'} + 0;
 
- (${*$ftp}{'net_cmd_code'},1);
+  (${*$ftp}{'net_cmd_code'}, 1);
 }
 
 ##
 ## Allow 2 servers to talk directly
 ##
 
+
+sub pasv_xfer_unique {
+  my ($sftp, $sfile, $dftp, $dfile) = @_;
+  $sftp->pasv_xfer($sfile, $dftp, $dfile, 1);
+}
+
+
 sub pasv_xfer {
-    my($sftp,$sfile,$dftp,$dfile,$unique) = @_;
+  my ($sftp, $sfile, $dftp, $dfile, $unique) = @_;
 
-    ($dfile = $sfile) =~ s#.*/##
-       unless(defined $dfile);
+  ($dfile = $sfile) =~ s#.*/##
+    unless (defined $dfile);
 
-    my $port = $sftp->pasv or
-       return undef;
+  my $port = $sftp->pasv
+    or return undef;
 
-    $dftp->port($port) or
-       return undef;
+  $dftp->port($port)
+    or return undef;
 
-    return undef
-       unless($unique ? $dftp->stou($dfile) : $dftp->stor($dfile));
+  return undef
+    unless ($unique ? $dftp->stou($dfile) : $dftp->stor($dfile));
 
-    unless($sftp->retr($sfile) && $sftp->response == CMD_INFO) {
-       $sftp->retr($sfile);
-       $dftp->abort;
-       $dftp->response();
-       return undef;
-    }
+  unless ($sftp->retr($sfile) && $sftp->response == CMD_INFO) {
+    $sftp->retr($sfile);
+    $dftp->abort;
+    $dftp->response();
+    return undef;
+  }
 
-    $dftp->pasv_wait($sftp);
+  $dftp->pasv_wait($sftp);
 }
 
-sub pasv_wait
-{
- @_ == 2 or croak 'usage: $ftp->pasv_wait(NON_PASV_FTP)';
 
- my($ftp, $non_pasv) = @_;
- my($file,$rin,$rout);
+sub pasv_wait {
+  @_ == 2 or croak 'usage: $ftp->pasv_wait(NON_PASV_FTP)';
+
+  my ($ftp, $non_pasv) = @_;
+  my ($file, $rin, $rout);
+
+  vec($rin = '', fileno($ftp), 1) = 1;
+  select($rout = $rin, undef, undef, undef);
+
+  $ftp->response();
+  $non_pasv->response();
+
+  return undef
+    unless $ftp->ok() && $non_pasv->ok();
+
+  return $1
+    if $ftp->message =~ /unique file name:\s*(\S*)\s*\)/;
+
+  return $1
+    if $non_pasv->message =~ /unique file name:\s*(\S*)\s*\)/;
+
+  return 1;
+}
+
 
- vec($rin='',fileno($ftp),1) = 1;
- select($rout=$rin, undef, undef, undef);
+sub feature {
+  @_ == 2 or croak 'usage: $ftp->feature( NAME )';
+  my ($ftp, $feat) = @_;
 
- $ftp->response();
- $non_pasv->response();
+  my $feature = ${*$ftp}{net_ftp_feature} ||= do {
+    my @feat;
 
- return undef
-       unless $ftp->ok() && $non_pasv->ok();
+    # Example response
+    # 211-Features:
+    #  MDTM
+    #  REST STREAM
+    #  SIZE
+    # 211 End
 
- return $1
-       if $ftp->message =~ /unique file name:\s*(\S*)\s*\)/;
+    @feat = map { /^\s+(.*\S)/ } $ftp->message
+      if $ftp->_FEAT;
 
- return $1
-       if $non_pasv->message =~ /unique file name:\s*(\S*)\s*\)/;
+    \@feat;
+  };
 
- return 1;
+  return grep { /^\Q$feat\E\b/i } @$feature;
 }
 
+
 sub cmd { shift->command(@_)->response() }
 
 ########################################
@@ -1190,48 +1192,52 @@ sub cmd { shift->command(@_)->response() }
 # RFC959 commands
 #
 
-sub _ABOR { shift->command("ABOR")->response()  == CMD_OK }
-sub _ALLO { shift->command("ALLO",@_)->response() == CMD_OK}
-sub _CDUP { shift->command("CDUP")->response()  == CMD_OK }
-sub _NOOP { shift->command("NOOP")->response()  == CMD_OK }
-sub _PASV { shift->command("PASV")->response()  == CMD_OK }
-sub _QUIT { shift->command("QUIT")->response()  == CMD_OK }
-sub _DELE { shift->command("DELE",@_)->response() == CMD_OK }
+
+sub _ABOR { shift->command("ABOR")->response() == CMD_OK }
+sub _ALLO { shift->command("ALLO", @_)->response() == CMD_OK }
+sub _CDUP { shift->command("CDUP")->response() == CMD_OK }
+sub _NOOP { shift->command("NOOP")->response() == CMD_OK }
+sub _PASV { shift->command("PASV")->response() == CMD_OK }
+sub _QUIT { shift->command("QUIT")->response() == CMD_OK }
+sub _DELE { shift->command("DELE", @_)->response() == CMD_OK }
 sub _CWD  { shift->command("CWD", @_)->response() == CMD_OK }
-sub _PORT { shift->command("PORT",@_)->response() == CMD_OK }
+sub _PORT { shift->command("PORT", @_)->response() == CMD_OK }
 sub _RMD  { shift->command("RMD", @_)->response() == CMD_OK }
 sub _MKD  { shift->command("MKD", @_)->response() == CMD_OK }
 sub _PWD  { shift->command("PWD", @_)->response() == CMD_OK }
-sub _TYPE { shift->command("TYPE",@_)->response() == CMD_OK }
-sub _RNTO { shift->command("RNTO",@_)->response() == CMD_OK }
-sub _RESP { shift->command("RESP",@_)->response() == CMD_OK }
-sub _MDTM { shift->command("MDTM",@_)->response() == CMD_OK }
-sub _SIZE { shift->command("SIZE",@_)->response() == CMD_OK }
-sub _HELP { shift->command("HELP",@_)->response() == CMD_OK }
-sub _STAT { shift->command("STAT",@_)->response() == CMD_OK }
-sub _APPE { shift->command("APPE",@_)->response() == CMD_INFO }
-sub _LIST { shift->command("LIST",@_)->response() == CMD_INFO }
-sub _NLST { shift->command("NLST",@_)->response() == CMD_INFO }
-sub _RETR { shift->command("RETR",@_)->response() == CMD_INFO }
-sub _STOR { shift->command("STOR",@_)->response() == CMD_INFO }
-sub _STOU { shift->command("STOU",@_)->response() == CMD_INFO }
-sub _RNFR { shift->command("RNFR",@_)->response() == CMD_MORE }
-sub _REST { shift->command("REST",@_)->response() == CMD_MORE }
-sub _PASS { shift->command("PASS",@_)->response() }
-sub _ACCT { shift->command("ACCT",@_)->response() }
-sub _AUTH { shift->command("AUTH",@_)->response() }
+sub _TYPE { shift->command("TYPE", @_)->response() == CMD_OK }
+sub _RNTO { shift->command("RNTO", @_)->response() == CMD_OK }
+sub _RESP { shift->command("RESP", @_)->response() == CMD_OK }
+sub _MDTM { shift->command("MDTM", @_)->response() == CMD_OK }
+sub _SIZE { shift->command("SIZE", @_)->response() == CMD_OK }
+sub _HELP { shift->command("HELP", @_)->response() == CMD_OK }
+sub _STAT { shift->command("STAT", @_)->response() == CMD_OK }
+sub _FEAT { shift->command("FEAT", @_)->response() == CMD_OK }
+sub _APPE { shift->command("APPE", @_)->response() == CMD_INFO }
+sub _LIST { shift->command("LIST", @_)->response() == CMD_INFO }
+sub _NLST { shift->command("NLST", @_)->response() == CMD_INFO }
+sub _RETR { shift->command("RETR", @_)->response() == CMD_INFO }
+sub _STOR { shift->command("STOR", @_)->response() == CMD_INFO }
+sub _STOU { shift->command("STOU", @_)->response() == CMD_INFO }
+sub _RNFR { shift->command("RNFR", @_)->response() == CMD_MORE }
+sub _REST { shift->command("REST", @_)->response() == CMD_MORE }
+sub _PASS { shift->command("PASS", @_)->response() }
+sub _ACCT { shift->command("ACCT", @_)->response() }
+sub _AUTH { shift->command("AUTH", @_)->response() }
+
 
 sub _USER {
   my $ftp = shift;
-  my $ok = $ftp->command("USER",@_)->response();
+  my $ok  = $ftp->command("USER", @_)->response();
 
   # A certain brain dead firewall :-)
-  $ok = $ftp->command("user",@_)->response()
+  $ok = $ftp->command("user", @_)->response()
     unless $ok == CMD_MORE or $ok == CMD_OK;
 
   $ok;
 }
 
+
 sub _SMNT { shift->unsupported(@_) }
 sub _MODE { shift->unsupported(@_) }
 sub _SYST { shift->unsupported(@_) }
@@ -1556,6 +1562,21 @@ of bytes per hash mark printed, and defaults to 1024.  In all cases the
 return value is a reference to an array of two:  the filehandle glob reference
 and the bytes per hash mark.
 
+=item feature ( NAME )
+
+Determine if the server supports the specified feature. The return
+value is a list of lines the server responded with to describe the
+options that it supports for the given feature. If the feature is
+unsupported then the empty list is returned.
+
+  if ($ftp->feature( 'MDTM' )) {
+    # Do something
+  }
+
+  if (grep { /\bTLS\b/ } $ftp->feature('AUTH')) {
+    # Server supports TLS
+  }
+
 =back
 
 The following methods can return different results depending on