File Coverage

blib/lib/App/Upfiles/FTPlazy.pm
Criterion Covered Total %
statement 26 133 19.5
branch 1 54 1.8
condition 0 45 0.0
subroutine 8 29 27.5
pod 0 23 0.0
total 35 284 12.3


line stmt bran cond sub pod time code
1             # Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2017, 2020, 2023, 2024 Kevin Ryde
2              
3             # This file is part of Upfiles.
4             #
5             # Upfiles is free software; you can redistribute it and/or modify it under
6             # the terms of the GNU General Public License as published by the Free
7             # Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Upfiles is distributed in the hope that it will be useful, but WITHOUT
11             # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12             # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13             # more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Upfiles. If not, see .
17              
18             package App::Upfiles::FTPlazy;
19 1     1   247344 use 5.010;
  1         6  
20 1     1   7 use strict;
  1         2  
  1         39  
21 1     1   6 use warnings;
  1         2  
  1         65  
22 1     1   7 use Carp;
  1         3  
  1         176  
23 1     1   3203 use Locale::TextDomain ('App-Upfiles');
  1         16329  
  1         7  
24              
25             # uncomment this to run the ### lines
26             # use Smart::Comments;
27              
28             our $VERSION = 16;
29              
30             # Have croaks from Net::FTP reported against the caller (App::Upfiles)
31             # rather than here.
32             our @CARP_NOT = ('Net::FTP');
33              
34             sub new {
35 2     2 0 2687 my $class = shift;
36             ### FTPlazy: @_
37 2         10 return bless { verbose => 0,
38             want_dir => '/',
39             have_site_utime => 'unknown',
40             message => '',
41             code => 0,
42             @_ }, $class;
43             }
44              
45             sub message {
46 0     0 0 0 my ($self) = @_;
47 0 0       0 return (defined $self->{'ftp'} ? $self->{'ftp'}->message : $self->{'message'});
48             }
49             sub ok {
50 0     0 0 0 my $self = shift;
51 0         0 my $code = $self->code;
52 0   0     0 return $code >= 0 && $code < 400;
53             }
54             sub code {
55 0     0 0 0 my ($self) = @_;
56 0 0       0 return (defined $self->{'ftp'} ? $self->{'ftp'}->code : $self->{'code'});
57             }
58              
59             sub host {
60 0     0 0 0 my ($self, $host) = @_;
61 0 0       0 if (@_ < 2) { return $self->{'want_host'}; }
  0         0  
62              
63 0         0 $self->{'want_host'} = $host;
64 0         0 return 1;
65             }
66             sub ensure_host {
67 0     0 0 0 my ($self) = @_;
68 0 0       0 if (! defined $self->{'want_host'}) {
69 0         0 $self->{'message'} = 'No host() machine given';
70 0         0 return 0;
71             }
72 0 0 0     0 if (defined $self->{'got_host'}
73             && $self->{'got_host'} eq $self->{'want_host'}) {
74 0         0 return 1;
75             }
76              
77             # Net::FTP without SSL support quietly ignores SSL=>1, don't let that happen
78 0         0 require Net::FTP;
79 0 0 0     0 if ($self->{'use_SSL'} && ! Net::FTP->can('can_ssl')) {
80 0         0 croak "ftps use SSL but Net::FTP (version ",Net::FTP->VERSION,
81             ") does not have can_ssl()";
82             }
83              
84 0 0       0 if ($self->{'verbose'} >= 2) { print "OPEN $self->{'want_host'}\n"; }
  0         0  
85              
86             ### use_SSL: $self->{'use_SSL'}
87             ### use_TLS: $self->{'use_TLS'}
88 0 0       0 if ($self->{'use_SSL'}) {
89 0         0 require IO::Socket::SSL;
90 0 0       0 if (! $IO::Socket::SSL::DEBUG) {
91 0         0 $IO::Socket::SSL::DEBUG = $self->{'verbose'};
92             }
93             }
94              
95             ### open Net FTP ...
96 0         0 require Net::FTP;
97             my $ftp = Net::FTP->new ($self->{'want_host'},
98             Debug => ($self->{'verbose'} >= 2),
99             SSL => $self->{'use_SSL'},
100 0         0 Passive => $self->{'Passive'},
101             );
102 0 0       0 if (! $ftp) {
103 0         0 $self->{'message'} = $@;
104 0         0 $self->{'code'} = 500;
105 0         0 return 0;
106             }
107              
108 0 0       0 if ($self->{'verbose'}) {
109             # IO::Socket::IP method
110 0         0 my ($host, $port) = $ftp->peerhost_service;
111 0         0 print "Connected $host $port\n";
112             }
113              
114 0 0       0 if ($self->{'use_TLS'}) {
115 0 0       0 if ($self->{'verbose'}) { print __("TLS\n"); }
  0         0  
116 0         0 $ftp->starttls;
117             }
118              
119 0         0 undef $self->{'got_username'};
120 0         0 $self->{'ftp'} = $ftp;
121 0         0 $self->{'got_host'} = $self->{'want_host'};
122 0         0 return 1;
123             }
124              
125             sub login {
126 0     0 0 0 my ($self, $username) = @_;
127 0         0 $self->{'want_username'} = $username;
128 0         0 return 1;
129             }
130             sub ensure_login {
131 0     0 0 0 my ($self) = @_;
132 0 0       0 if (! defined $self->{'want_username'}) {
133 0         0 $self->{'message'} = 'No login() username given';
134 0         0 $self->{'code'} = 500;
135 0         0 return 0;
136             }
137 0 0 0     0 if (defined $self->{'got_username'}
138             && $self->{'got_username'} eq $self->{'want_username'}) {
139 0         0 return 1;
140             }
141 0 0       0 if ($self->{'verbose'} >= 2) { print "LOGIN $self->{'want_username'}\n"; }
  0         0  
142 0 0       0 $self->{'ftp'}->login ($self->{'want_username'})
143             or return 0;
144 0         0 undef $self->{'got_binary'};
145 0         0 undef $self->{'got_cwd'};
146 0         0 $self->{'got_username'} = $self->{'want_username'};
147 0         0 return 1;
148             }
149              
150             sub binary {
151 0     0 0 0 my ($self) = @_;
152 0         0 $self->{'want_binary'} = 1;
153 0         0 return 1;
154             }
155             sub ensure_binary {
156 0     0 0 0 my ($self) = @_;
157 0 0       0 if (! defined $self->{'want_binary'}) {
158 0         0 return 1;
159             }
160 0 0 0     0 if (defined $self->{'got_binary'}
161             && $self->{'got_binary'} eq $self->{'want_binary'}) {
162 0         0 return 1;
163             }
164 0 0       0 my $method = ($self->{'want_binary'} ? 'binary' : 'ascii');
165 0 0       0 if ($self->{'verbose'} >= 2) { print "\U$method\E\n"; }
  0         0  
166 0 0       0 $self->{'ftp'}->$method
167             or return 0;
168 0         0 $self->{'got_binary'} = $self->{'want_binary'};
169 0         0 return 1;
170             }
171              
172             sub cwd {
173 4     4 0 2466 my ($self, $dir) = @_;
174              
175             # default root dir same as Net::FTP
176 4 50       30 if (! defined $dir) { $dir = '/'; }
  0         0  
177              
178             # relative to current want_dir
179 4         26 require File::Spec::Unix;
180 4         66 $dir = File::Spec::Unix->rel2abs ($dir, $self->{'want_dir'});
181 4         9 $dir = _collapse_dotdot_parent ($dir);
182              
183 4         7 $self->{'want_dir'} = $dir;
184 4         8 return 1;
185             }
186             sub ensure_cwd {
187 0     0 0 0 my ($self) = @_;
188 0 0 0     0 if (defined $self->{'got_dir'}
189             && $self->{'got_dir'} eq $self->{'want_dir'}) {
190 0         0 return 1;
191             }
192 0 0       0 if ($self->{'verbose'} >= 2) { print "CWD $self->{'want_dir'}\n"; }
  0         0  
193              
194 0 0       0 $self->{'ftp'}->cwd ($self->{'want_dir'})
195             or return 0;
196 0         0 $self->{'got_dir'} = $self->{'want_dir'};
197 0         0 return 1;
198             }
199             # this is wrong if the removed parent is a symlink, but prevents relative
200             # cwd()s accumulating an endlessly longer $self->{'want_path'}
201             sub _collapse_dotdot_parent {
202 4     4   7 my ($path) = @_;
203 4         21 while ($path =~ s{[^/]+/\.\.(/|$)}{}) {}
204 4         9 return File::Spec::Unix->canonpath($path);
205             }
206              
207             sub pwd {
208 0     0 0   my ($self) = @_;
209 0           return $self->{'want_dir'};
210             }
211              
212             sub ensure_all {
213 0     0 0   my ($self) = @_;
214 0   0       return $self->ensure_host
215             && $self->ensure_login
216             && $self->ensure_binary
217             && $self->ensure_cwd;
218             }
219              
220             sub put { # ($self, $local, $remote)
221 0     0 0   my $self = shift;
222             ### FTPlazy put(): @_
223 0   0       return $self->ensure_all && $self->{'ftp'}->put (@_);
224             }
225             sub delete {
226 0     0 0   my $self = shift; # ($self, $remote)
227 0   0       return $self->ensure_all && $self->{'ftp'}->delete (@_);
228             }
229             sub mkdir {
230 0     0 0   my $self = shift;
231 0   0       return $self->ensure_all && $self->{'ftp'}->mkdir (@_);
232             }
233             sub rmdir { # ($self, $remote)
234 0     0 0   my $self = shift;
235 0   0       return $self->ensure_all && $self->{'ftp'}->rmdir (@_);
236             }
237             sub rename { # ($self, $remote_oldname, $remote_newname)
238 0     0 0   my $self = shift;
239 0   0       return $self->ensure_all && $self->{'ftp'}->rename (@_);
240             }
241             sub site {
242 0     0 0   my $self = shift;
243 0   0       return $self->ensure_all && $self->{'ftp'}->site (@_);
244             }
245             sub quot {
246 0     0 0   my $self = shift;
247 0   0       return $self->ensure_all && $self->{'ftp'}->quot (@_);
248             }
249              
250             sub all_ok {
251 0     0 0   my ($self) = @_;
252 0   0       return (! $self->{'ftp'} || $self->{'ftp'}->pwd);
253             }
254              
255             sub mlsd {
256 0     0 0   my $self = shift;
257 0 0         $self->ensure_all || return;
258 0           return $self->{'ftp'}->_list_cmd("MLSD", @_);
259             }
260              
261             # sub mlsd {
262             # my ($self, $remote_dirname, $local_filename) = @_;
263             # $self->ensure_all;
264             # # ### MLSD: $remote_dirname
265             # # my $data = $self->{'ftp'}->_data_cmd("MLSD $remote_dirname")
266             # # or return undef;
267             # #
268             # # require File::Copy;
269             # # File::Copy::copy($data, $local_filename);
270             # # unless ($data->close) {
271             # # croak "Error closing data stream";
272             # # }
273             # #
274             # # ### MLSD message: $self->message
275             # # return undef;
276             # }
277              
278             1;
279             __END__