File Coverage

blib/lib/Net/FTP.pm
Criterion Covered Total %
statement 55 726 7.5
branch 2 410 0.4
condition 4 238 1.6
subroutine 18 129 13.9
pod 56 61 91.8
total 135 1564 8.6


line stmt bran cond sub pod time code
1             # Net::FTP.pm
2             #
3             # Copyright (C) 1995-2004 Graham Barr. All rights reserved.
4             # Copyright (C) 2013-2017, 2020, 2022 Steve Hay. All rights reserved.
5             # This module is free software; you can redistribute it and/or modify it under
6             # the same terms as Perl itself, i.e. under the terms of either the GNU General
7             # Public License or the Artistic License, as specified in the F file.
8             #
9             # Documentation (at end) improved 1996 by Nathan Torkington .
10              
11             package Net::FTP;
12              
13 2     2   1111 use 5.008001;
  2         7  
14              
15 2     2   8 use strict;
  2         4  
  2         33  
16 2     2   8 use warnings;
  2         3  
  2         37  
17              
18 2     2   8 use Carp;
  2         3  
  2         100  
19 2     2   9 use Fcntl qw(O_WRONLY O_RDONLY O_APPEND O_CREAT O_TRUNC);
  2         3  
  2         80  
20 2     2   831 use IO::Socket;
  2         21829  
  2         46  
21 2     2   1179 use Net::Cmd;
  2         3  
  2         98  
22 2     2   12 use Net::Config;
  2         11  
  2         132  
23 2     2   10 use Socket;
  2         3  
  2         811  
24 2     2   853 use Time::Local;
  2         3741  
  2         157  
25              
26             our $VERSION = '3.14';
27              
28             our $IOCLASS;
29             my $family_key;
30             BEGIN {
31             # Code for detecting if we can use SSL
32 2   50 2   8 my $ssl_class = eval {
33             require IO::Socket::SSL;
34             # first version with default CA on most platforms
35 2     2   12 no warnings 'numeric';
  2         2  
  2         152  
36             IO::Socket::SSL->VERSION(2.007);
37             } && 'IO::Socket::SSL';
38              
39 2   50     11 my $nossl_warn = !$ssl_class &&
40             'To use SSL please install IO::Socket::SSL with version>=2.007';
41              
42             # Code for detecting if we can use IPv6
43             my $inet6_class = eval {
44             require IO::Socket::IP;
45 2     2   11 no warnings 'numeric';
  2         3  
  2         105  
46             IO::Socket::IP->VERSION(0.25);
47 2   33     6 } && 'IO::Socket::IP' || eval {
48             require IO::Socket::INET6;
49 2     2   11 no warnings 'numeric';
  2         4  
  2         226  
50             IO::Socket::INET6->VERSION(2.62);
51             } && 'IO::Socket::INET6';
52              
53 0     0 1   sub can_ssl { $ssl_class };
54 0     0 1   sub can_inet6 { $inet6_class };
55              
56 2   50     10 $IOCLASS = $ssl_class || $inet6_class || 'IO::Socket::INET';
57 2 50 0     14 $family_key =
    50          
58             ( $ssl_class ? $ssl_class->can_ipv6 : $inet6_class || '' )
59             eq 'IO::Socket::IP'
60             ? 'Family' : 'Domain';
61             }
62              
63             our @ISA = ('Exporter','Net::Cmd',$IOCLASS);
64              
65 2     2   114 use constant TELNET_IAC => 255;
  2         3  
  2         98  
66 2     2   10 use constant TELNET_IP => 244;
  2         4  
  2         73  
67 2     2   9 use constant TELNET_DM => 242;
  2         4  
  2         79  
68              
69 2     2   10 use constant EBCDIC => ord 'A' == 193;
  2         3  
  2         15728  
70              
71             sub new {
72 0     0 1   my $pkg = shift;
73 0           my ($peer, %arg);
74 0 0         if (@_ % 2) {
75 0           $peer = shift;
76 0           %arg = @_;
77             }
78             else {
79 0           %arg = @_;
80 0           $peer = delete $arg{Host};
81             }
82              
83 0           my $host = $peer;
84 0           my $fire = undef;
85 0           my $fire_type = undef;
86              
87 0 0 0       if (exists($arg{Firewall}) || Net::Config->requires_firewall($peer)) {
88             $fire = $arg{Firewall}
89             || $ENV{FTP_FIREWALL}
90             || $NetConfig{ftp_firewall}
91 0   0       || undef;
92              
93 0 0         if (defined $fire) {
94 0           $peer = $fire;
95 0           delete $arg{Port};
96             $fire_type = $arg{FirewallType}
97             || $ENV{FTP_FIREWALL_TYPE}
98             || $NetConfig{firewall_type}
99 0   0       || undef;
100             }
101             }
102              
103 0           my %tlsargs;
104 0 0         if (can_ssl()) {
    0          
105             # for name verification strip port from domain:port, ipv4:port, [ipv6]:port
106 0           (my $hostname = $host) =~s{(?
107 0 0         %tlsargs = (
108             SSL_verifycn_scheme => 'ftp',
109             SSL_verifycn_name => $hostname,
110             # use SNI if supported by IO::Socket::SSL
111             $pkg->can_client_sni ? (SSL_hostname => $hostname):(),
112             # reuse SSL session of control connection in data connections
113             SSL_session_cache_size => 10,
114             SSL_session_key => $hostname,
115             );
116             # user defined SSL arg
117 0           $tlsargs{$_} = $arg{$_} for(grep { m{^SSL_} } keys %arg);
  0            
118 0 0         $tlsargs{SSL_reuse_ctx} = IO::Socket::SSL::SSL_Context->new(%tlsargs)
119             or return;
120              
121             } elsif ($arg{SSL}) {
122 0           croak("IO::Socket::SSL >= 2.007 needed for SSL support");
123             }
124              
125             my $ftp = $pkg->SUPER::new(
126             PeerAddr => $peer,
127             PeerPort => $arg{Port} || ($arg{SSL} ? 'ftps(990)' : 'ftp(21)'),
128             LocalAddr => $arg{'LocalAddr'},
129             $family_key => $arg{Domain} || $arg{Family},
130             Proto => 'tcp',
131             Timeout => defined $arg{Timeout} ? $arg{Timeout} : 120,
132             %tlsargs,
133 0 0 0       $arg{SSL} ? ():( SSL_startHandshake => 0 ),
    0 0        
    0          
134             ) or return;
135              
136 0           ${*$ftp}{'net_ftp_host'} = $host; # Remote hostname
  0            
137 0           ${*$ftp}{'net_ftp_type'} = 'A'; # ASCII/binary/etc mode
  0            
138 0   0       ${*$ftp}{'net_ftp_blksize'} = abs($arg{'BlockSize'} || 10240);
  0            
139              
140 0           ${*$ftp}{'net_ftp_localaddr'} = $arg{'LocalAddr'};
  0            
141 0   0       ${*$ftp}{'net_ftp_domain'} = $arg{Domain} || $arg{Family};
  0            
142              
143 0 0         ${*$ftp}{'net_ftp_firewall'} = $fire
  0            
144             if (defined $fire);
145 0 0         ${*$ftp}{'net_ftp_firewall_type'} = $fire_type
  0            
146             if (defined $fire_type);
147              
148 0           ${*$ftp}{'net_ftp_passive'} =
149             int exists $arg{Passive} ? $arg{Passive}
150             : exists $ENV{FTP_PASSIVE} ? $ENV{FTP_PASSIVE}
151             : defined $fire ? $NetConfig{ftp_ext_passive}
152 0 0         : $NetConfig{ftp_int_passive}; # Whew! :-)
    0          
    0          
153              
154 0 0         ${*$ftp}{net_ftp_tlsargs} = \%tlsargs if %tlsargs;
  0            
155 0 0         if ($arg{SSL}) {
156 0           ${*$ftp}{net_ftp_tlsprot} = 'P';
  0            
157 0           ${*$ftp}{net_ftp_tlsdirect} = 1;
  0            
158             }
159              
160 0 0         $ftp->hash(exists $arg{Hash} ? $arg{Hash} : 0, 1024);
161              
162 0           $ftp->autoflush(1);
163              
164 0 0         $ftp->debug(exists $arg{Debug} ? $arg{Debug} : undef);
165              
166 0 0         unless ($ftp->response() == CMD_OK) {
167 0           $ftp->close();
168             # keep @$ if no message. Happens, when response did not start with a code.
169 0   0       $@ = $ftp->message || $@;
170 0           undef $ftp;
171             }
172              
173 0           $ftp;
174             }
175              
176             ##
177             ## User interface methods
178             ##
179              
180              
181             sub host {
182 0     0 1   my $me = shift;
183 0           ${*$me}{'net_ftp_host'};
  0            
184             }
185              
186             sub passive {
187 0     0 1   my $ftp = shift;
188 0 0         return ${*$ftp}{'net_ftp_passive'} unless @_;
  0            
189 0           ${*$ftp}{'net_ftp_passive'} = shift;
  0            
190             }
191              
192              
193             sub hash {
194 0     0 1   my $ftp = shift; # self
195              
196 0           my ($h, $b) = @_;
197 0 0         unless ($h) {
198 0           delete ${*$ftp}{'net_ftp_hash'};
  0            
199 0           return [\*STDERR, 0];
200             }
201 0 0 0       ($h, $b) = (ref($h) ? $h : \*STDERR, $b || 1024);
202 0           select((select($h), $| = 1)[0]);
203 0 0         $b = 512 if $b < 512;
204 0           ${*$ftp}{'net_ftp_hash'} = [$h, $b];
  0            
205             }
206              
207              
208             sub quit {
209 0     0 1   my $ftp = shift;
210              
211 0           $ftp->_QUIT;
212 0           $ftp->close;
213             }
214              
215              
216       0     sub DESTROY { }
217              
218              
219 0     0 1   sub ascii { shift->type('A', @_); }
220 0     0 1   sub binary { shift->type('I', @_); }
221              
222              
223             sub ebcdic {
224 0     0 0   carp "TYPE E is unsupported, shall default to I";
225 0           shift->type('E', @_);
226             }
227              
228              
229             sub byte {
230 0     0 0   carp "TYPE L is unsupported, shall default to I";
231 0           shift->type('L', @_);
232             }
233              
234             # Allow the user to send a command directly, BE CAREFUL !!
235              
236              
237             sub quot {
238 0     0 1   my $ftp = shift;
239 0           my $cmd = shift;
240              
241 0           $ftp->command(uc $cmd, @_);
242 0           $ftp->response();
243             }
244              
245              
246             sub site {
247 0     0 1   my $ftp = shift;
248              
249 0           $ftp->command("SITE", @_);
250 0           $ftp->response();
251             }
252              
253              
254             sub mdtm {
255 0     0 1   my $ftp = shift;
256 0           my $file = shift;
257              
258             # Server Y2K bug workaround
259             #
260             # sigh; some idiotic FTP servers use ("19%d",tm.tm_year) instead of
261             # ("%d",tm.tm_year+1900). This results in an extra digit in the
262             # string returned. To account for this we allow an optional extra
263             # digit in the year. Then if the first two digits are 19 we use the
264             # remainder, otherwise we subtract 1900 from the whole year.
265              
266 0 0 0       $ftp->_MDTM($file)
    0          
267             && $ftp->message =~ /((\d\d)(\d\d\d?))(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/
268             ? timegm($8, $7, $6, $5, $4 - 1, $2 eq '19' ? ($3 + 1900) : $1)
269             : undef;
270             }
271              
272              
273             sub size {
274 0     0 1   my $ftp = shift;
275 0           my $file = shift;
276 0           my $io;
277 0 0         if ($ftp->supported("SIZE")) {
    0          
278 0 0         return $ftp->_SIZE($file)
279             ? ($ftp->message =~ /(\d+)\s*(bytes?\s*)?$/)[0]
280             : undef;
281             }
282             elsif ($ftp->supported("STAT")) {
283 0           my @msg;
284             return
285 0 0 0       unless $ftp->_STAT($file) && (@msg = $ftp->message) == 3;
286 0           foreach my $line (@msg) {
287 0 0         return (split(/\s+/, $line))[4]
288             if $line =~ /^[-rwxSsTt]{10}/;
289             }
290             }
291             else {
292 0           my @files = $ftp->dir($file);
293 0 0         if (@files) {
294 0 0         return (split(/\s+/, $1))[4]
295             if $files[0] =~ /^([-rwxSsTt]{10}.*)$/;
296             }
297             }
298 0           undef;
299             }
300              
301              
302             sub starttls {
303 0     0 1   my $ftp = shift;
304 0 0         can_ssl() or croak("IO::Socket::SSL >= 2.007 needed for SSL support");
305 0 0         $ftp->is_SSL and croak("called starttls within SSL session");
306 0 0         $ftp->_AUTH('TLS') == CMD_OK or return;
307              
308 0 0         $ftp->connect_SSL or return;
309 0           $ftp->prot('P');
310 0           return 1;
311             }
312              
313             sub prot {
314 0     0 1   my ($ftp,$prot) = @_;
315 0 0 0       $prot eq 'C' or $prot eq 'P' or croak("prot must by C or P");
316 0 0         $ftp->_PBSZ(0) or return;
317 0 0         $ftp->_PROT($prot) or return;
318 0           ${*$ftp}{net_ftp_tlsprot} = $prot;
  0            
319 0           return 1;
320             }
321              
322             sub stoptls {
323 0     0 1   my $ftp = shift;
324 0 0         $ftp->is_SSL or croak("called stoptls outside SSL session");
325 0 0         ${*$ftp}{net_ftp_tlsdirect} and croak("cannot stoptls direct SSL session");
  0            
326 0 0         $ftp->_CCC() or return;
327 0           $ftp->stop_SSL();
328 0           return 1;
329             }
330              
331             sub login {
332 0     0 1   my ($ftp, $user, $pass, $acct) = @_;
333 0           my ($ok, $ruser, $fwtype);
334              
335 0 0         unless (defined $user) {
336 0           require Net::Netrc;
337              
338 0           my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'});
  0            
339              
340 0 0         ($user, $pass, $acct) = $rc->lpa()
341             if ($rc);
342             }
343              
344 0   0       $user ||= "anonymous";
345 0           $ruser = $user;
346              
347             $fwtype = ${*$ftp}{'net_ftp_firewall_type'}
348 0   0       || $NetConfig{'ftp_firewall_type'}
349             || 0;
350              
351 0 0 0       if ($fwtype && defined ${*$ftp}{'net_ftp_firewall'}) {
  0            
352 0 0 0       if ($fwtype == 1 || $fwtype == 7) {
353 0           $user .= '@' . ${*$ftp}{'net_ftp_host'};
  0            
354             }
355             else {
356 0           require Net::Netrc;
357              
358 0           my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'});
  0            
359              
360 0 0         my ($fwuser, $fwpass, $fwacct) = $rc ? $rc->lpa() : ();
361              
362 0 0         if ($fwtype == 5) {
363 0           $user = join('@', $user, $fwuser, ${*$ftp}{'net_ftp_host'});
  0            
364 0           $pass = $pass . '@' . $fwpass;
365             }
366             else {
367 0 0         if ($fwtype == 2) {
    0          
368 0           $user .= '@' . ${*$ftp}{'net_ftp_host'};
  0            
369             }
370             elsif ($fwtype == 6) {
371 0           $fwuser .= '@' . ${*$ftp}{'net_ftp_host'};
  0            
372             }
373              
374 0           $ok = $ftp->_USER($fwuser);
375              
376 0 0 0       return 0 unless $ok == CMD_OK || $ok == CMD_MORE;
377              
378 0   0       $ok = $ftp->_PASS($fwpass || "");
379              
380 0 0 0       return 0 unless $ok == CMD_OK || $ok == CMD_MORE;
381              
382 0 0         $ok = $ftp->_ACCT($fwacct)
383             if defined($fwacct);
384              
385 0 0         if ($fwtype == 3) {
    0          
386 0           $ok = $ftp->command("SITE", ${*$ftp}{'net_ftp_host'})->response;
  0            
387             }
388             elsif ($fwtype == 4) {
389 0           $ok = $ftp->command("OPEN", ${*$ftp}{'net_ftp_host'})->response;
  0            
390             }
391              
392 0 0 0       return 0 unless $ok == CMD_OK || $ok == CMD_MORE;
393             }
394             }
395             }
396              
397 0           $ok = $ftp->_USER($user);
398              
399             # Some dumb firewalls don't prefix the connection messages
400 0 0 0       $ok = $ftp->response()
      0        
401             if ($ok == CMD_OK && $ftp->code == 220 && $user =~ /\@/);
402              
403 0 0         if ($ok == CMD_MORE) {
404 0 0         unless (defined $pass) {
405 0           require Net::Netrc;
406              
407 0           my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'}, $ruser);
  0            
408              
409 0 0         ($ruser, $pass, $acct) = $rc->lpa()
410             if ($rc);
411              
412 0 0 0       $pass = '-anonymous@'
      0        
413             if (!defined $pass && (!defined($ruser) || $ruser =~ /^anonymous/o));
414             }
415              
416 0   0       $ok = $ftp->_PASS($pass || "");
417             }
418              
419 0 0 0       $ok = $ftp->_ACCT($acct)
      0        
420             if (defined($acct) && ($ok == CMD_MORE || $ok == CMD_OK));
421              
422 0 0 0       if ($fwtype == 7 && $ok == CMD_OK && defined ${*$ftp}{'net_ftp_firewall'}) {
  0   0        
423 0           my ($f, $auth, $resp) = _auth_id($ftp);
424 0 0         $ftp->authorize($auth, $resp) if defined($resp);
425             }
426              
427 0           $ok == CMD_OK;
428             }
429              
430              
431             sub account {
432 0 0   0 1   @_ == 2 or croak 'usage: $ftp->account($acct)';
433 0           my $ftp = shift;
434 0           my $acct = shift;
435 0           $ftp->_ACCT($acct) == CMD_OK;
436             }
437              
438              
439             sub _auth_id {
440 0     0     my ($ftp, $auth, $resp) = @_;
441              
442 0 0         unless (defined $resp) {
443 0           require Net::Netrc;
444              
445 0   0       $auth ||= eval { (getpwuid($>))[0] } || $ENV{NAME};
      0        
446              
447             my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}, $auth)
448 0   0       || Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'});
449              
450 0 0         ($auth, $resp) = $rc->lpa()
451             if ($rc);
452             }
453 0           ($ftp, $auth, $resp);
454             }
455              
456              
457             sub authorize {
458 0 0 0 0 1   @_ >= 1 || @_ <= 3 or croak 'usage: $ftp->authorize([$auth[, $resp]])';
459              
460 0           my ($ftp, $auth, $resp) = &_auth_id;
461              
462 0   0       my $ok = $ftp->_AUTH($auth || "");
463              
464 0 0 0       return $ftp->_RESP($resp || "")
465             if ($ok == CMD_MORE);
466              
467 0           $ok == CMD_OK;
468             }
469              
470              
471             sub rename {
472 0 0   0 1   @_ == 3 or croak 'usage: $ftp->rename($oldname, $newname)';
473              
474 0           my ($ftp, $oldname, $newname) = @_;
475              
476 0 0         $ftp->_RNFR($oldname)
477             && $ftp->_RNTO($newname);
478             }
479              
480              
481             sub type {
482 0     0 1   my $ftp = shift;
483 0           my $type = shift;
484 0           my $oldval = ${*$ftp}{'net_ftp_type'};
  0            
485              
486 0 0         return $oldval
487             unless (defined $type);
488              
489             return
490 0 0         unless ($ftp->_TYPE($type, @_));
491              
492 0           ${*$ftp}{'net_ftp_type'} = join(" ", $type, @_);
  0            
493              
494 0           $oldval;
495             }
496              
497              
498             sub alloc {
499 0     0 1   my $ftp = shift;
500 0           my $size = shift;
501 0           my $oldval = ${*$ftp}{'net_ftp_allo'};
  0            
502              
503 0 0         return $oldval
504             unless (defined $size);
505              
506             return
507 0 0 0       unless ($ftp->supported("ALLO") and $ftp->_ALLO($size, @_));
508              
509 0           ${*$ftp}{'net_ftp_allo'} = join(" ", $size, @_);
  0            
510              
511 0           $oldval;
512             }
513              
514              
515             sub abort {
516 0     0 1   my $ftp = shift;
517              
518 0           send($ftp, pack("CCC", TELNET_IAC, TELNET_IP, TELNET_IAC), MSG_OOB);
519              
520 0           $ftp->command(pack("C", TELNET_DM) . "ABOR");
521              
522 0           ${*$ftp}{'net_ftp_dataconn'}->close()
523 0 0         if defined ${*$ftp}{'net_ftp_dataconn'};
  0            
524              
525 0           $ftp->response();
526              
527 0           $ftp->status == CMD_OK;
528             }
529              
530              
531             sub get {
532 0     0 1   my ($ftp, $remote, $local, $where) = @_;
533              
534 0           my ($loc, $len, $buf, $resp, $data);
535 0           local *FD;
536              
537 0   0       my $localfd = ref($local) || ref(\$local) eq "GLOB";
538              
539 0 0         ($local = $remote) =~ s#^.*/##
540             unless (defined $local);
541              
542 0 0         croak("Bad remote filename '$remote'\n")
543             if $remote =~ /[\r\n]/s;
544              
545 0 0         ${*$ftp}{'net_ftp_rest'} = $where if defined $where;
  0            
546 0           my $rest = ${*$ftp}{'net_ftp_rest'};
  0            
547              
548 0           delete ${*$ftp}{'net_ftp_port'};
  0            
549 0           delete ${*$ftp}{'net_ftp_pasv'};
  0            
550              
551 0 0         $data = $ftp->retr($remote)
552             or return;
553              
554 0 0         if ($localfd) {
555 0           $loc = $local;
556             }
557             else {
558 0           $loc = \*FD;
559              
560 0 0         unless (sysopen($loc, $local, O_CREAT | O_WRONLY | ($rest ? O_APPEND: O_TRUNC))) {
    0          
561 0           carp "Cannot open Local file $local: $!\n";
562 0           $data->abort;
563 0           return;
564             }
565             }
566              
567 0 0 0       if ($ftp->type eq 'I' && !binmode($loc)) {
568 0           carp "Cannot binmode Local file $local: $!\n";
569 0           $data->abort;
570 0 0         close($loc) unless $localfd;
571 0           return;
572             }
573              
574 0           $buf = '';
575 0           my ($count, $hashh, $hashb, $ref) = (0);
576              
577             ($hashh, $hashb) = @$ref
578 0 0         if ($ref = ${*$ftp}{'net_ftp_hash'});
  0            
579              
580 0           my $blksize = ${*$ftp}{'net_ftp_blksize'};
  0            
581 0           local $\; # Just in case
582              
583 0           while (1) {
584 0 0         last unless $len = $data->read($buf, $blksize);
585              
586 0           if (EBCDIC && $ftp->type ne 'I') {
587             $buf = $ftp->toebcdic($buf);
588             $len = length($buf);
589             }
590              
591 0 0         if ($hashh) {
592 0           $count += $len;
593 0           print $hashh "#" x (int($count / $hashb));
594 0           $count %= $hashb;
595             }
596 0 0         unless (print $loc $buf) {
597 0           carp "Cannot write to Local file $local: $!\n";
598 0           $data->abort;
599 0 0         close($loc)
600             unless $localfd;
601 0           return;
602             }
603             }
604              
605 0 0         print $hashh "\n" if $hashh;
606              
607 0 0         unless ($localfd) {
608 0 0         unless (close($loc)) {
609 0           carp "Cannot close file $local (perhaps disk space) $!\n";
610 0           return;
611             }
612             }
613              
614 0 0         unless ($data->close()) # implied $ftp->response
615             {
616 0           carp "Unable to close datastream";
617 0           return;
618             }
619              
620 0           return $local;
621             }
622              
623              
624             sub cwd {
625 0 0 0 0 1   @_ == 1 || @_ == 2 or croak 'usage: $ftp->cwd([$dir])';
626              
627 0           my ($ftp, $dir) = @_;
628              
629 0 0 0       $dir = "/" unless defined($dir) && $dir =~ /\S/;
630              
631 0 0         $dir eq ".."
632             ? $ftp->_CDUP()
633             : $ftp->_CWD($dir);
634             }
635              
636              
637             sub cdup {
638 0 0   0 1   @_ == 1 or croak 'usage: $ftp->cdup()';
639 0           $_[0]->_CDUP;
640             }
641              
642              
643             sub pwd {
644 0 0   0 1   @_ == 1 || croak 'usage: $ftp->pwd()';
645 0           my $ftp = shift;
646              
647 0           $ftp->_PWD();
648 0           $ftp->_extract_path;
649             }
650              
651             # rmdir( $ftp, $dir, [ $recurse ] )
652             #
653             # Removes $dir on remote host via FTP.
654             # $ftp is handle for remote host
655             #
656             # If $recurse is TRUE, the directory and deleted recursively.
657             # This means all of its contents and subdirectories.
658             #
659             # Initial version contributed by Dinkum Software
660             #
661             sub rmdir {
662 0 0 0 0 1   @_ == 2 || @_ == 3 or croak('usage: $ftp->rmdir($dir[, $recurse])');
663              
664             # Pick off the args
665 0           my ($ftp, $dir, $recurse) = @_;
666 0           my $ok;
667              
668 0 0 0       return $ok
669             if $ok = $ftp->_RMD($dir)
670             or !$recurse;
671              
672             # Try to delete the contents
673             # Get a list of all the files in the directory, excluding the current and parent directories
674 0 0         my @filelist = map { /^(?:\S+;)+ (.+)$/ ? ($1) : () } grep { !/^(?:\S+;)*type=[cp]dir;/i } $ftp->_list_cmd("MLSD", $dir);
  0            
  0            
675              
676             # Fallback to using the less well-defined NLST command if MLSD fails
677 0 0         @filelist = grep { !/^\.{1,2}$/ } $ftp->ls($dir)
  0            
678             unless @filelist;
679              
680             return
681 0 0         unless @filelist; # failed, it is probably not a directory
682              
683 0 0 0       return $ftp->delete($dir)
684             if @filelist == 1 and $dir eq $filelist[0];
685              
686             # Go thru and delete each file or the directory
687 0 0         foreach my $file (map { m,/, ? $_ : "$dir/$_" } @filelist) {
  0            
688             next # successfully deleted the file
689 0 0         if $ftp->delete($file);
690              
691             # Failed to delete it, assume its a directory
692             # Recurse and ignore errors, the final rmdir() will
693             # fail on any errors here
694 0 0         return $ok
695             unless $ok = $ftp->rmdir($file, 1);
696             }
697              
698             # Directory should be empty
699             # Try to remove the directory again
700             # Pass results directly to caller
701             # If any of the prior deletes failed, this
702             # rmdir() will fail because directory is not empty
703 0           return $ftp->_RMD($dir);
704             }
705              
706              
707             sub restart {
708 0 0   0 1   @_ == 2 || croak 'usage: $ftp->restart($where)';
709              
710 0           my ($ftp, $where) = @_;
711              
712 0           ${*$ftp}{'net_ftp_rest'} = $where;
  0            
713              
714 0           return;
715             }
716              
717              
718             sub mkdir {
719 0 0 0 0 1   @_ == 2 || @_ == 3 or croak 'usage: $ftp->mkdir($dir[, $recurse])';
720              
721 0           my ($ftp, $dir, $recurse) = @_;
722              
723 0 0 0       $ftp->_MKD($dir) || $recurse
724             or return;
725              
726 0           my $path = $dir;
727              
728 0 0         unless ($ftp->ok) {
729 0           my @path = split(m#(?=/+)#, $dir);
730              
731 0           $path = "";
732              
733 0           while (@path) {
734 0           $path .= shift @path;
735              
736 0           $ftp->_MKD($path);
737              
738 0           $path = $ftp->_extract_path($path);
739             }
740              
741             # If the creation of the last element was not successful, see if we
742             # can cd to it, if so then return path
743              
744 0 0         unless ($ftp->ok) {
745 0           my ($status, $message) = ($ftp->status, $ftp->message);
746 0           my $pwd = $ftp->pwd;
747              
748 0 0 0       if ($pwd && $ftp->cwd($dir)) {
749 0           $path = $dir;
750 0           $ftp->cwd($pwd);
751             }
752             else {
753 0           undef $path;
754             }
755 0           $ftp->set_status($status, $message);
756             }
757             }
758              
759 0           $path;
760             }
761              
762              
763             sub delete {
764 0 0   0 1   @_ == 2 || croak 'usage: $ftp->delete($filename)';
765              
766 0           $_[0]->_DELE($_[1]);
767             }
768              
769              
770 0     0 1   sub put { shift->_store_cmd("stor", @_) }
771 0     0 1   sub put_unique { shift->_store_cmd("stou", @_) }
772 0     0 1   sub append { shift->_store_cmd("appe", @_) }
773              
774              
775 0     0 1   sub nlst { shift->_data_cmd("NLST", @_) }
776 0     0 1   sub list { shift->_data_cmd("LIST", @_) }
777 0     0 1   sub retr { shift->_data_cmd("RETR", @_) }
778 0     0 1   sub stor { shift->_data_cmd("STOR", @_) }
779 0     0 1   sub stou { shift->_data_cmd("STOU", @_) }
780 0     0 1   sub appe { shift->_data_cmd("APPE", @_) }
781              
782              
783             sub _store_cmd {
784 0     0     my ($ftp, $cmd, $local, $remote) = @_;
785 0           my ($loc, $sock, $len, $buf);
786 0           local *FD;
787              
788 0   0       my $localfd = ref($local) || ref(\$local) eq "GLOB";
789              
790 0 0 0       if (!defined($remote) and 'STOU' ne uc($cmd)) {
791 0 0         croak 'Must specify remote filename with stream input'
792             if $localfd;
793              
794 0           require File::Basename;
795 0           $remote = File::Basename::basename($local);
796             }
797 0 0         if (defined ${*$ftp}{'net_ftp_allo'}) {
  0            
798 0           delete ${*$ftp}{'net_ftp_allo'};
  0            
799             }
800             else {
801              
802             # if the user hasn't already invoked the alloc method since the last
803             # _store_cmd call, figure out if the local file is a regular file(not
804             # a pipe, or device) and if so get the file size from stat, and send
805             # an ALLO command before sending the STOR, STOU, or APPE command.
806 0 0         my $size = do { local $^W; -f $local && -s _ }; # no ALLO if sending data from a pipe
  0            
  0            
807 0 0         ${*$ftp}{'net_ftp_allo'} = $size if $size;
  0            
808             }
809 0 0 0       croak("Bad remote filename '$remote'\n")
810             if defined($remote) and $remote =~ /[\r\n]/s;
811              
812 0 0         if ($localfd) {
813 0           $loc = $local;
814             }
815             else {
816 0           $loc = \*FD;
817              
818 0 0         unless (sysopen($loc, $local, O_RDONLY)) {
819 0           carp "Cannot open Local file $local: $!\n";
820 0           return;
821             }
822             }
823              
824 0 0 0       if ($ftp->type eq 'I' && !binmode($loc)) {
825 0           carp "Cannot binmode Local file $local: $!\n";
826 0           return;
827             }
828              
829 0           delete ${*$ftp}{'net_ftp_port'};
  0            
830 0           delete ${*$ftp}{'net_ftp_pasv'};
  0            
831              
832 0 0         $sock = $ftp->_data_cmd($cmd, grep { defined } $remote)
  0            
833             or return;
834              
835 0 0         $remote = ($ftp->message =~ /\w+\s*:\s*(.*)/)[0]
836             if 'STOU' eq uc $cmd;
837              
838 0           my $blksize = ${*$ftp}{'net_ftp_blksize'};
  0            
839              
840 0           my ($count, $hashh, $hashb, $ref) = (0);
841              
842             ($hashh, $hashb) = @$ref
843 0 0         if ($ref = ${*$ftp}{'net_ftp_hash'});
  0            
844              
845 0           while (1) {
846 0 0         last unless $len = read($loc, $buf = "", $blksize);
847              
848 0           if (EBCDIC && $ftp->type ne 'I') {
849             $buf = $ftp->toascii($buf);
850             $len = length($buf);
851             }
852              
853 0 0         if ($hashh) {
854 0           $count += $len;
855 0           print $hashh "#" x (int($count / $hashb));
856 0           $count %= $hashb;
857             }
858              
859 0           my $wlen;
860 0 0 0       unless (defined($wlen = $sock->write($buf, $len)) && $wlen == $len) {
861 0           $sock->abort;
862 0 0         close($loc)
863             unless $localfd;
864 0 0         print $hashh "\n" if $hashh;
865 0           return;
866             }
867             }
868              
869 0 0         print $hashh "\n" if $hashh;
870              
871 0 0         close($loc)
872             unless $localfd;
873              
874 0 0         $sock->close()
875             or return;
876              
877 0 0 0       if ('STOU' eq uc $cmd and $ftp->message =~ m/unique\s+file\s*name\s*:\s*(.*)\)|"(.*)"/) {
878 0           require File::Basename;
879 0           $remote = File::Basename::basename($+);
880             }
881              
882 0           return $remote;
883             }
884              
885              
886             sub port {
887 0 0 0 0 1   @_ == 1 || @_ == 2 or croak 'usage: $self->port([$port])';
888 0           return _eprt('PORT',@_);
889             }
890              
891             sub eprt {
892 0 0 0 0 1   @_ == 1 || @_ == 2 or croak 'usage: $self->eprt([$port])';
893 0           return _eprt('EPRT',@_);
894             }
895              
896             sub _eprt {
897 0     0     my ($cmd,$ftp,$port) = @_;
898 0           delete ${*$ftp}{net_ftp_intern_port};
  0            
899 0 0 0       unless ($port) {
900 0           my $listen = ${*$ftp}{net_ftp_listen} ||= $IOCLASS->new(
901             Listen => 1,
902             Timeout => $ftp->timeout,
903             LocalAddr => $ftp->sockhost,
904             $family_key => $ftp->sockdomain,
905             can_ssl() ? (
906 0 0 0       %{ ${*$ftp}{net_ftp_tlsargs} },
  0            
  0            
907             SSL_startHandshake => 0,
908             ):(),
909             );
910 0           ${*$ftp}{net_ftp_intern_port} = 1;
  0            
911 0 0         my $fam = ($listen->sockdomain == AF_INET) ? 1:2;
912 0 0 0       if ( $cmd eq 'EPRT' || $fam == 2 ) {
913 0           $port = "|$fam|".$listen->sockhost."|".$listen->sockport."|";
914 0           $cmd = 'EPRT';
915             } else {
916 0           my $p = $listen->sockport;
917 0           $port = join(',',split(m{\.},$listen->sockhost),$p >> 8,$p & 0xff);
918             }
919             } elsif (ref($port) eq 'ARRAY') {
920             $port = join(',',split(m{\.},@$port[0]),@$port[1] >> 8,@$port[1] & 0xff);
921             }
922 0 0         my $ok = $cmd eq 'EPRT' ? $ftp->_EPRT($port) : $ftp->_PORT($port);
923 0 0         ${*$ftp}{net_ftp_port} = $port if $ok;
  0            
924 0           return $ok;
925             }
926              
927              
928 0     0 1   sub ls { shift->_list_cmd("NLST", @_); }
929 0     0 1   sub dir { shift->_list_cmd("LIST", @_); }
930              
931              
932             sub pasv {
933 0     0 1   my $ftp = shift;
934 0 0         @_ and croak 'usage: $ftp->port()';
935 0 0         return $ftp->epsv if $ftp->sockdomain != AF_INET;
936 0           delete ${*$ftp}{net_ftp_intern_port};
  0            
937              
938 0 0 0       if ( $ftp->_PASV &&
939             $ftp->message =~ m{(\d+,\d+,\d+,\d+),(\d+),(\d+)} ) {
940 0           my $port = 256 * $2 + $3;
941 0           ( my $ip = $1 ) =~s{,}{.}g;
942 0           return ${*$ftp}{net_ftp_pasv} = [ $ip,$port ];
  0            
943             }
944 0           return;
945             }
946              
947             sub epsv {
948 0     0 1   my $ftp = shift;
949 0 0         @_ and croak 'usage: $ftp->epsv()';
950 0           delete ${*$ftp}{net_ftp_intern_port};
  0            
951              
952             $ftp->_EPSV && $ftp->message =~ m{\(([\x33-\x7e])\1\1(\d+)\1\)}
953 0 0 0       ? ${*$ftp}{net_ftp_pasv} = [ $ftp->peerhost, $2 ]
  0            
954             : undef;
955             }
956              
957              
958             sub unique_name {
959 0     0 1   my $ftp = shift;
960 0 0         ${*$ftp}{'net_ftp_unique'} || undef;
  0            
961             }
962              
963              
964             sub supported {
965 0 0   0 1   @_ == 2 or croak 'usage: $ftp->supported($cmd)';
966 0           my $ftp = shift;
967 0           my $cmd = uc shift;
968 0   0       my $hash = ${*$ftp}{'net_ftp_supported'} ||= {};
  0            
969              
970             return $hash->{$cmd}
971 0 0         if exists $hash->{$cmd};
972              
973 0 0         return $hash->{$cmd} = 1
974             if $ftp->feature($cmd);
975              
976 0 0         return $hash->{$cmd} = 0
977             unless $ftp->_HELP($cmd);
978              
979 0           my $text = $ftp->message;
980 0 0         if ($text =~ /following.+commands/i) {
981 0           $text =~ s/^.*\n//;
982 0           while ($text =~ /(\*?)(\w+)(\*?)/sg) {
983 0           $hash->{"\U$2"} = !length("$1$3");
984             }
985             }
986             else {
987 0           $hash->{$cmd} = $text !~ /unimplemented/i;
988             }
989              
990 0   0       $hash->{$cmd} ||= 0;
991             }
992              
993             ##
994             ## Deprecated methods
995             ##
996              
997              
998             sub lsl {
999 0 0   0 0   carp "Use of Net::FTP::lsl deprecated, use 'dir'"
1000             if $^W;
1001 0           goto &dir;
1002             }
1003              
1004              
1005             sub authorise {
1006 0 0   0 0   carp "Use of Net::FTP::authorise deprecated, use 'authorize'"
1007             if $^W;
1008 0           goto &authorize;
1009             }
1010              
1011              
1012             ##
1013             ## Private methods
1014             ##
1015              
1016              
1017             sub _extract_path {
1018 0     0     my ($ftp, $path) = @_;
1019              
1020             # This tries to work both with and without the quote doubling
1021             # convention (RFC 959 requires it, but the first 3 servers I checked
1022             # didn't implement it). It will fail on a server which uses a quote in
1023             # the message which isn't a part of or surrounding the path.
1024 0 0 0       $ftp->ok
1025             && $ftp->message =~ /(?:^|\s)\"(.*)\"(?:$|\s)/
1026             && ($path = $1) =~ s/\"\"/\"/g;
1027              
1028 0           $path;
1029             }
1030              
1031             ##
1032             ## Communication methods
1033             ##
1034              
1035              
1036             sub _dataconn {
1037 0     0     my $ftp = shift;
1038 0           my $pkg = "Net::FTP::" . $ftp->type;
1039 0 0         eval "require " . $pkg ## no critic (BuiltinFunctions::ProhibitStringyEval)
1040             or croak("cannot load $pkg required for type ".$ftp->type);
1041 0           $pkg =~ s/ /_/g;
1042 0           delete ${*$ftp}{net_ftp_dataconn};
  0            
1043              
1044 0           my $conn;
1045 0           my $pasv = ${*$ftp}{net_ftp_pasv};
  0            
1046 0 0         if ($pasv) {
    0          
1047             $conn = $pkg->new(
1048             PeerAddr => $pasv->[0],
1049             PeerPort => $pasv->[1],
1050 0           LocalAddr => ${*$ftp}{net_ftp_localaddr},
1051 0           $family_key => ${*$ftp}{net_ftp_domain},
1052             Timeout => $ftp->timeout,
1053             can_ssl() ? (
1054             SSL_startHandshake => 0,
1055 0 0         %{${*$ftp}{net_ftp_tlsargs}},
  0 0          
  0            
1056             ):(),
1057             ) or return;
1058 0           } elsif (my $listen = delete ${*$ftp}{net_ftp_listen}) {
1059 0 0         $conn = $listen->accept($pkg) or return;
1060 0           $conn->timeout($ftp->timeout);
1061 0           close($listen);
1062             } else {
1063 0           croak("no listener in active mode");
1064             }
1065              
1066 0 0 0       if (( ${*$ftp}{net_ftp_tlsprot} || '') eq 'P') {
1067 0 0         if ($conn->connect_SSL) {
1068             # SSL handshake ok
1069             } else {
1070 0           carp("failed to ssl upgrade dataconn: $IO::Socket::SSL::SSL_ERROR");
1071 0           return;
1072             }
1073             }
1074              
1075 0           ${*$ftp}{net_ftp_dataconn} = $conn;
  0            
1076 0           ${*$conn} = "";
  0            
1077 0           ${*$conn}{net_ftp_cmd} = $ftp;
  0            
1078 0           ${*$conn}{net_ftp_blksize} = ${*$ftp}{net_ftp_blksize};
  0            
  0            
1079 0           return $conn;
1080             }
1081              
1082              
1083             sub _list_cmd {
1084 0     0     my $ftp = shift;
1085 0           my $cmd = uc shift;
1086              
1087 0           delete ${*$ftp}{'net_ftp_port'};
  0            
1088 0           delete ${*$ftp}{'net_ftp_pasv'};
  0            
1089              
1090 0           my $data = $ftp->_data_cmd($cmd, @_);
1091              
1092             return
1093 0 0         unless (defined $data);
1094              
1095 0           require Net::FTP::A;
1096 0           bless $data, "Net::FTP::A"; # Force ASCII mode
1097              
1098 0           my $databuf = '';
1099 0           my $buf = '';
1100 0           my $blksize = ${*$ftp}{'net_ftp_blksize'};
  0            
1101              
1102 0           while ($data->read($databuf, $blksize)) {
1103 0           $buf .= $databuf;
1104             }
1105              
1106 0           my $list = [split(/\n/, $buf)];
1107              
1108 0           $data->close();
1109              
1110 0           if (EBCDIC) {
1111             for (@$list) { $_ = $ftp->toebcdic($_) }
1112             }
1113              
1114             wantarray
1115 0 0         ? @{$list}
  0            
1116             : $list;
1117             }
1118              
1119              
1120             sub _data_cmd {
1121 0     0     my $ftp = shift;
1122 0           my $cmd = uc shift;
1123 0           my $ok = 1;
1124 0   0       my $where = delete ${*$ftp}{'net_ftp_rest'} || 0;
1125 0           my $arg;
1126              
1127 0           for my $arg (@_) {
1128 0 0         croak("Bad argument '$arg'\n")
1129             if $arg =~ /[\r\n]/s;
1130             }
1131              
1132 0 0 0       if ( ${*$ftp}{'net_ftp_passive'}
  0   0        
1133 0           && !defined ${*$ftp}{'net_ftp_pasv'}
1134 0           && !defined ${*$ftp}{'net_ftp_port'})
1135             {
1136 0 0         return unless defined $ftp->pasv;
1137              
1138 0 0 0       if ($where and !$ftp->_REST($where)) {
1139 0           my ($status, $message) = ($ftp->status, $ftp->message);
1140 0           $ftp->abort;
1141 0           $ftp->set_status($status, $message);
1142 0           return;
1143             }
1144              
1145             # first send command, then open data connection
1146             # otherwise the peer might not do a full accept (with SSL
1147             # handshake if PROT P)
1148 0           $ftp->command($cmd, @_);
1149 0           my $data = $ftp->_dataconn();
1150 0 0         if (CMD_INFO == $ftp->response()) {
1151 0 0 0       $data->reading
1152             if $data && $cmd =~ /RETR|LIST|NLST|MLSD/;
1153 0           return $data;
1154             }
1155 0 0         $data->_close if $data;
1156              
1157 0           return;
1158             }
1159              
1160             $ok = $ftp->port
1161 0           unless (defined ${*$ftp}{'net_ftp_port'}
1162 0 0 0       || defined ${*$ftp}{'net_ftp_pasv'});
  0            
1163              
1164 0 0 0       $ok = $ftp->_REST($where)
1165             if $ok && $where;
1166              
1167             return
1168 0 0         unless $ok;
1169              
1170 0 0 0       if ($cmd =~ /(STOR|APPE|STOU)/ and exists ${*$ftp}{net_ftp_allo} and
  0   0        
1171             $ftp->supported("ALLO"))
1172             {
1173 0           $ftp->_ALLO(delete ${*$ftp}{net_ftp_allo})
1174 0 0         or return;
1175             }
1176              
1177 0           $ftp->command($cmd, @_);
1178              
1179             return 1
1180 0 0         if (defined ${*$ftp}{'net_ftp_pasv'});
  0            
1181              
1182 0           $ok = CMD_INFO == $ftp->response();
1183              
1184             return $ok
1185 0 0         unless exists ${*$ftp}{'net_ftp_intern_port'};
  0            
1186              
1187 0 0         if ($ok) {
1188 0           my $data = $ftp->_dataconn();
1189              
1190 0 0 0       $data->reading
1191             if $data && $cmd =~ /RETR|LIST|NLST|MLSD/;
1192              
1193 0           return $data;
1194             }
1195              
1196              
1197 0           close(delete ${*$ftp}{'net_ftp_listen'});
  0            
1198              
1199 0           return;
1200             }
1201              
1202             ##
1203             ## Over-ride methods (Net::Cmd)
1204             ##
1205              
1206              
1207 0 0   0 1   sub debug_text { $_[2] =~ /^(pass|resp|acct)/i ? "$1 ....\n" : $_[2]; }
1208              
1209              
1210             sub command {
1211 0     0 1   my $ftp = shift;
1212              
1213 0           delete ${*$ftp}{'net_ftp_port'};
  0            
1214 0           $ftp->SUPER::command(@_);
1215             }
1216              
1217              
1218             sub response {
1219 0     0 1   my $ftp = shift;
1220 0   0       my $code = $ftp->SUPER::response() || 5; # assume 500 if undef
1221              
1222 0 0 0       delete ${*$ftp}{'net_ftp_pasv'}
  0            
1223             if ($code != CMD_MORE && $code != CMD_INFO);
1224              
1225 0           $code;
1226             }
1227              
1228              
1229             sub parse_response {
1230 0 0   0 1   return ($1, $2 eq "-")
1231             if $_[1] =~ s/^(\d\d\d)([- ]?)//o;
1232              
1233 0           my $ftp = shift;
1234              
1235             # Darn MS FTP server is a load of CRAP !!!!
1236             # Expect to see undef here.
1237             return ()
1238 0 0 0       unless 0 + (${*$ftp}{'net_cmd_code'} || 0);
1239              
1240 0           (${*$ftp}{'net_cmd_code'}, 1);
  0            
1241             }
1242              
1243             ##
1244             ## Allow 2 servers to talk directly
1245             ##
1246              
1247              
1248             sub pasv_xfer_unique {
1249 0     0 1   my ($sftp, $sfile, $dftp, $dfile) = @_;
1250 0           $sftp->pasv_xfer($sfile, $dftp, $dfile, 1);
1251             }
1252              
1253              
1254             sub pasv_xfer {
1255 0     0 1   my ($sftp, $sfile, $dftp, $dfile, $unique) = @_;
1256              
1257 0 0         ($dfile = $sfile) =~ s#.*/##
1258             unless (defined $dfile);
1259              
1260 0 0         my $port = $sftp->pasv
1261             or return;
1262              
1263 0 0         $dftp->port($port)
1264             or return;
1265              
1266             return
1267 0 0         unless ($unique ? $dftp->stou($dfile) : $dftp->stor($dfile));
    0          
1268              
1269 0 0 0       unless ($sftp->retr($sfile) && $sftp->response == CMD_INFO) {
1270 0           $sftp->retr($sfile);
1271 0           $dftp->abort;
1272 0           $dftp->response();
1273 0           return;
1274             }
1275              
1276 0           $dftp->pasv_wait($sftp);
1277             }
1278              
1279              
1280             sub pasv_wait {
1281 0 0   0 1   @_ == 2 or croak 'usage: $ftp->pasv_wait($non_pasv_server)';
1282              
1283 0           my ($ftp, $non_pasv_server) = @_;
1284 0           my ($file, $rin, $rout);
1285              
1286 0           vec($rin = '', fileno($ftp), 1) = 1;
1287 0           select($rout = $rin, undef, undef, undef);
1288              
1289 0           my $dres = $ftp->response();
1290 0           my $sres = $non_pasv_server->response();
1291              
1292             return
1293 0 0 0       unless $dres == CMD_OK && $sres == CMD_OK;
1294              
1295             return
1296 0 0 0       unless $ftp->ok() && $non_pasv_server->ok();
1297              
1298 0 0         return $1
1299             if $ftp->message =~ /unique file name:\s*(\S*)\s*\)/;
1300              
1301 0 0         return $1
1302             if $non_pasv_server->message =~ /unique file name:\s*(\S*)\s*\)/;
1303              
1304 0           return 1;
1305             }
1306              
1307              
1308             sub feature {
1309 0 0   0 1   @_ == 2 or croak 'usage: $ftp->feature($name)';
1310 0           my ($ftp, $name) = @_;
1311              
1312 0   0       my $feature = ${*$ftp}{net_ftp_feature} ||= do {
  0            
1313 0           my @feat;
1314              
1315             # Example response
1316             # 211-Features:
1317             # MDTM
1318             # REST STREAM
1319             # SIZE
1320             # 211 End
1321              
1322 0 0         @feat = map { /^\s+(.*\S)/ } $ftp->message
  0            
1323             if $ftp->_FEAT;
1324              
1325 0           \@feat;
1326             };
1327              
1328 0           return grep { /^\Q$name\E\b/i } @$feature;
  0            
1329             }
1330              
1331              
1332 0     0 0   sub cmd { shift->command(@_)->response() }
1333              
1334             ########################################
1335             #
1336             # RFC959 + RFC2428 + RFC4217 commands
1337             #
1338              
1339              
1340 0     0     sub _ABOR { shift->command("ABOR")->response() == CMD_OK }
1341 0     0     sub _ALLO { shift->command("ALLO", @_)->response() == CMD_OK }
1342 0     0     sub _CDUP { shift->command("CDUP")->response() == CMD_OK }
1343 0     0     sub _NOOP { shift->command("NOOP")->response() == CMD_OK }
1344 0     0     sub _PASV { shift->command("PASV")->response() == CMD_OK }
1345 0     0     sub _QUIT { shift->command("QUIT")->response() == CMD_OK }
1346 0     0     sub _DELE { shift->command("DELE", @_)->response() == CMD_OK }
1347 0     0     sub _CWD { shift->command("CWD", @_)->response() == CMD_OK }
1348 0     0     sub _PORT { shift->command("PORT", @_)->response() == CMD_OK }
1349 0     0     sub _RMD { shift->command("RMD", @_)->response() == CMD_OK }
1350 0     0     sub _MKD { shift->command("MKD", @_)->response() == CMD_OK }
1351 0     0     sub _PWD { shift->command("PWD", @_)->response() == CMD_OK }
1352 0     0     sub _TYPE { shift->command("TYPE", @_)->response() == CMD_OK }
1353 0     0     sub _RNTO { shift->command("RNTO", @_)->response() == CMD_OK }
1354 0     0     sub _RESP { shift->command("RESP", @_)->response() == CMD_OK }
1355 0     0     sub _MDTM { shift->command("MDTM", @_)->response() == CMD_OK }
1356 0     0     sub _SIZE { shift->command("SIZE", @_)->response() == CMD_OK }
1357 0     0     sub _HELP { shift->command("HELP", @_)->response() == CMD_OK }
1358 0     0     sub _STAT { shift->command("STAT", @_)->response() == CMD_OK }
1359 0     0     sub _FEAT { shift->command("FEAT", @_)->response() == CMD_OK }
1360 0     0     sub _PBSZ { shift->command("PBSZ", @_)->response() == CMD_OK }
1361 0     0     sub _PROT { shift->command("PROT", @_)->response() == CMD_OK }
1362 0     0     sub _CCC { shift->command("CCC", @_)->response() == CMD_OK }
1363 0     0     sub _EPRT { shift->command("EPRT", @_)->response() == CMD_OK }
1364 0     0     sub _EPSV { shift->command("EPSV", @_)->response() == CMD_OK }
1365 0     0     sub _APPE { shift->command("APPE", @_)->response() == CMD_INFO }
1366 0     0     sub _LIST { shift->command("LIST", @_)->response() == CMD_INFO }
1367 0     0     sub _NLST { shift->command("NLST", @_)->response() == CMD_INFO }
1368 0     0     sub _RETR { shift->command("RETR", @_)->response() == CMD_INFO }
1369 0     0     sub _STOR { shift->command("STOR", @_)->response() == CMD_INFO }
1370 0     0     sub _STOU { shift->command("STOU", @_)->response() == CMD_INFO }
1371 0     0     sub _RNFR { shift->command("RNFR", @_)->response() == CMD_MORE }
1372 0     0     sub _REST { shift->command("REST", @_)->response() == CMD_MORE }
1373 0     0     sub _PASS { shift->command("PASS", @_)->response() }
1374 0     0     sub _ACCT { shift->command("ACCT", @_)->response() }
1375 0     0     sub _AUTH { shift->command("AUTH", @_)->response() }
1376              
1377              
1378             sub _USER {
1379 0     0     my $ftp = shift;
1380 0           my $ok = $ftp->command("USER", @_)->response();
1381              
1382             # A certain brain dead firewall :-)
1383 0 0 0       $ok = $ftp->command("user", @_)->response()
1384             unless $ok == CMD_MORE or $ok == CMD_OK;
1385              
1386 0           $ok;
1387             }
1388              
1389              
1390 0     0     sub _SMNT { shift->unsupported(@_) }
1391 0     0     sub _MODE { shift->unsupported(@_) }
1392 0     0     sub _SYST { shift->unsupported(@_) }
1393 0     0     sub _STRU { shift->unsupported(@_) }
1394 0     0     sub _REIN { shift->unsupported(@_) }
1395              
1396              
1397             1;
1398              
1399             __END__