File Coverage

blib/lib/AnyEvent/FTP/Server/Context/FSRW.pm
Criterion Covered Total %
statement 178 230 77.3
branch 32 62 51.6
condition 6 15 40.0
subroutine 36 36 100.0
pod 0 12 0.0
total 252 355 70.9


line stmt bran cond sub pod time code
1             package AnyEvent::FTP::Server::Context::FSRW;
2              
3 12     12   737194 use strict;
  12         54  
  12         397  
4 12     12   77 use warnings;
  12         30  
  12         323  
5 12     12   279 use 5.010;
  12         42  
6 12     12   1970 use Moo;
  12         38003  
  12         77  
7 12     12   15092 use File::chdir;
  12         30385  
  12         1600  
8 12     12   6772 use File::ShareDir::Dist qw( dist_share );
  12         12608  
  12         57  
9 12     12   7137 use File::Which qw( which );
  12         12786  
  12         750  
10 12     12   3202 use File::Temp qw( tempfile );
  12         79741  
  12         873  
11 12     12   6984 use Capture::Tiny qw( capture );
  12         80394  
  12         2429  
12              
13             extends 'AnyEvent::FTP::Server::Context::FS';
14              
15             # ABSTRACT: FTP Server client context class with full read/write access
16             our $VERSION = '0.17'; # VERSION
17              
18              
19             with 'AnyEvent::FTP::Server::Role::TransferPrep';
20              
21              
22             sub _layer
23             {
24 46 100   46   581 $_[0]->type eq 'A' ? $_[0]->ascii_layer : ':raw';
25             }
26              
27 2     2 0 9 sub help_retr { 'RETR pathname' }
28              
29             sub cmd_retr
30             {
31 21     21 0 78 my($self, $con, $req) = @_;
32              
33 21         75 my $fn = $req->args;
34              
35 21 50       163 unless(defined $self->data)
36             {
37 0         0 $con->send_response(425 => 'Unable to build data connection');
38 0         0 return;
39             }
40              
41 21         57 eval {
42 12     12   5975 use autodie;
  12         147200  
  12         75  
43 21         185 local $CWD = $self->cwd;
44              
45 21 50 0     1992 if(-f $fn)
    0          
46             {
47             # TODO: re-write so that this does not blocks
48 21 100       236 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
49 21         304 my $size = -s $fn;
50 21         252 $con->send_response(150 => "Opening $type mode data connection for $fn ($size bytes)");
51 21         224 open my $fh, '<', $fn;
52 21         7984 binmode $fh, $self->_layer;
53 21 100       3476 seek $fh, $self->restart_offset, 0 if $self->restart_offset;
54 21         1482 $self->data->push_write(do { local $/; <$fh> });
  21         106  
  21         1244  
55 21         1561 close $fh;
56 21         3681 $self->data->push_shutdown;
57 21         923 $con->send_response(226 => 'Transfer complete');
58             }
59             elsif(-e $fn && !-d $fn)
60             {
61 0         0 $con->send_response(550 => 'Permission denied');
62             }
63             else
64             {
65 0         0 $con->send_response(550 => 'No such file');
66             }
67             };
68 21 50       791 if(my $error = $@)
69             {
70 0         0 warn $error;
71 0 0       0 if(eval { $error->can('errno') })
  0         0  
72 0         0 { $con->send_response(550 => $error->errno) }
73             else
74 0         0 { $con->send_response(550 => 'Internal error') }
75             };
76 21         117 $self->clear_data;
77 21         101 $self->done;
78             }
79              
80              
81 2     2 0 11 sub help_nlst { 'NLST [ (pathname)]' }
82              
83             sub cmd_nlst
84             {
85 11     11 0 48 my($self, $con, $req) = @_;
86              
87 11   100     101 my $dir = $req->args || '.';
88              
89 11 50       64 unless(defined $self->data)
90             {
91 0         0 $con->send_response(425 => 'Unable to build data connection');
92 0         0 return;
93             }
94              
95 11         30 eval {
96 12     12   103418 use autodie;
  12         50  
  12         71  
97 11         57 local $CWD = $self->cwd;
98              
99 11         840 $con->send_response(150 => "Opening ASCII mode data connection for file list");
100 11         145 my $dh;
101 11         74 opendir $dh, $dir;
102             my @list =
103 11 100       4040 map { $req->args ? join('/', $dir, $_) : $_ }
  21         97  
104             sort
105             grep !/^\.\.?$/,
106             readdir $dh;
107 11         73 closedir $dh;
108 11         2115 $self->data->push_write(join '', map { $_ . "\015\012" } @list);
  21         147  
109 11         755 $self->data->push_shutdown;
110 11         509 $con->send_response(226 => 'Transfer complete');
111             };
112 11 50       504 if(my $error = $@)
113             {
114 0         0 warn $error;
115 0 0       0 if(eval { $error->can('errno') })
  0         0  
116 0         0 { $con->send_response(550 => $error->errno) }
117             else
118 0         0 { $con->send_response(550 => 'Internal error') }
119             };
120 11         87 $self->clear_data;
121 11         64 $self->done;
122             }
123              
124              
125 2     2 0 22 sub help_list { 'LIST [ pathname]' }
126              
127             sub cmd_list
128             {
129 8     8 0 24 my($self, $con, $req) = @_;
130              
131 8   100     26 my $dir = $req->args || '.';
132 8 50       48 $dir = '.' if $dir eq '-l';
133              
134 8 50       40 unless(defined $self->data)
135             {
136 0         0 $con->send_response(425 => 'Unable to build data connection');
137 0         0 return;
138             }
139              
140 8         17 eval {
141 12     12   83393 use autodie;
  12         33  
  12         83  
142              
143 8         110 my @cmd = _shared_cmd('ls', '-l', $dir);
144              
145 8         63 local $CWD = $self->cwd;
146              
147 8         585 $con->send_response(150 => "Opening ASCII mode data connection for file list");
148 8         22 my $dh;
149 8         47 opendir $dh, $dir;
150              
151 8     8   1920 $self->data->push_write(join "\015\012", split /\n/, scalar capture { system @cmd });
  8         65876  
152 8         15476 closedir $dh;
153 8         2702 $self->data->push_write("\015\012");
154 8         707 $self->data->push_shutdown;
155 8         726 $con->send_response(226 => 'Transfer complete');
156             };
157 8 50       891 if(my $error = $@)
158             {
159 0         0 warn $error;
160 0 0       0 if(eval { $error->can('errno') })
  0         0  
161 0         0 { $con->send_response(550 => $error->errno) }
162             else
163 0         0 { $con->send_response(550 => 'Internal error') }
164             };
165 8         258 $self->clear_data;
166 8         254 $self->done;
167             }
168              
169              
170 2     2 0 10 sub help_stor { 'STOR pathname' }
171              
172             sub cmd_stor
173             {
174 15     15 0 58 my($self, $con, $req) = @_;
175              
176 15         71 my $fn = $req->args;
177              
178 15 50       83 unless(defined $self->data)
179             {
180 0         0 $con->send_response(425 => 'Unable to build data connection');
181 0         0 return;
182             }
183              
184 15         57 eval {
185 12     12   82802 use autodie;
  12         34  
  12         80  
186 15         66 local $CWD = $self->cwd;
187              
188 15 100       1041 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
189 15         129 $con->send_response(150 => "Opening $type mode data connection for $fn");
190              
191 15         115 open my $fh, '>', $fn;
192 15         7101 binmode $fh, $self->_layer;
193             $self->data->on_read(sub {
194             $self->data->push_read(sub {
195 30         1925 print $fh $_[0]{rbuf};
196 30         140 $_[0]{rbuf} = '';
197 15     15   2229 });
198 15         4220 });
199             $self->data->on_error(sub {
200 15     15   432 close $fh;
201 15         3859 $self->data->push_shutdown;
202 15         1120 $con->send_response(226 => 'Transfer complete');
203 15         77 $self->clear_data;
204 15         59 $self->done;
205 15         915 });
206             };
207 15 50       751 if(my $error = $@)
208             {
209 0         0 warn $error;
210 0 0       0 if(eval { $error->can('errno') })
  0         0  
211 0         0 { $con->send_response(550 => $error->errno) }
212             else
213 0         0 { $con->send_response(550 => 'Internal error') }
214 0         0 $self->clear_data;
215 0         0 $self->done;
216             };
217             }
218              
219              
220 2     2 0 9 sub help_appe { 'APPE pathname' }
221              
222             sub cmd_appe
223             {
224 7     7 0 56 my($self, $con, $req) = @_;
225              
226 7         38 my $fn = $req->args;
227              
228 7 50       45 unless(defined $self->data)
229             {
230 0         0 $con->send_response(425 => 'Unable to build data connection');
231 0         0 return;
232             }
233              
234 7         24 eval {
235 12     12   83402 use autodie;
  12         53  
  12         87  
236 7         48 local $CWD = $self->cwd;
237              
238 7 100       495 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
239 7         91 $con->send_response(150 => "Opening $type mode data connection for $fn");
240              
241 7         84 open my $fh, '>>', $fn;
242 7         5430 binmode $fh, $self->_layer;
243             $self->data->on_read(sub {
244             $self->data->push_read(sub {
245 16         1008 print $fh $_[0]{rbuf};
246 16         85 $_[0]{rbuf} = '';
247 7     7   883 });
248 7         2518 });
249             $self->data->on_error(sub {
250 7     7   207 close $fh;
251 7         2869 $self->data->push_shutdown;
252 7         516 $con->send_response(226 => 'Transfer complete');
253 7         45 $self->clear_data;
254 7         27 $self->done;
255 7         444 });
256             };
257 7 50       361 if(my $error = $@)
258             {
259 0         0 warn $error;
260 0 0       0 if(eval { $error->can('errno') })
  0         0  
261 0         0 { $con->send_response(550 => $error->errno) }
262             else
263 0         0 { $con->send_response(550 => 'Internal error') }
264 0         0 $self->clear_data;
265 0         0 $self->done;
266             };
267             }
268              
269              
270 2     2 0 10 sub help_stou { 'STOU (store unique filename)' }
271              
272             sub cmd_stou
273             {
274 3     3 0 28 my($self, $con, $req) = @_;
275              
276 3         23 my $fn = $req->args;
277              
278 3 50       33 unless(defined $self->data)
279             {
280 0         0 $con->send_response(425 => 'Unable to build data connection');
281 0         0 return;
282             }
283              
284 3         13 eval {
285 12     12   85422 use autodie;
  12         47  
  12         75  
286 3         24 local $CWD = $self->cwd;
287              
288 3         169 my $fh;
289              
290 3 50 33     25 if($fn && ! -e $fn)
291             {
292 0         0 open $fh, '>', $fn;
293             }
294             else
295             {
296 3         32 ($fh,$fn) = tempfile( "aefXXXXXX", TMPDIR => 0 )
297             }
298              
299 3 100       1240 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
300 3         37 $con->send_response(150 => "FILE: $fn");
301              
302 3         23 binmode $fh, $self->_layer;
303             $self->data->on_read(sub {
304             $self->data->push_read(sub {
305 6         340 print $fh $_[0]{rbuf};
306 6         27 $_[0]{rbuf} = '';
307 3     3   452 });
308 3         2803 });
309             $self->data->on_error(sub {
310 3     3   73 close $fh;
311 3         2325 $self->data->push_shutdown;
312 3         220 $con->send_response(226 => 'Transfer complete');
313 3         30 $self->clear_data;
314 3         16 $self->done;
315 3         176 });
316             };
317 3 50       149 if(my $error = $@)
318             {
319 0         0 warn $error;
320 0 0       0 if(eval { $error->can('errno') })
  0         0  
321 0         0 { $con->send_response(550 => $error->errno) }
322             else
323 0         0 { $con->send_response(550 => 'Internal error') }
324 0         0 $self->clear_data;
325 0         0 $self->done;
326             };
327             }
328              
329             {
330             state $always_use_bundled_cmd = $ENV{ANYEVENT_FTP_BUNDLED_CMD};
331             my %shared;
332             sub _shared_cmd
333             {
334 9     9   182744 my ($cmd, @args) = @_;
335              
336 9 100       45 unless (defined $shared{$cmd}) {
337 2         14 my $which = which $cmd;
338 2 50 33     667 if ($which && !$always_use_bundled_cmd) {
339 2         12 $shared{$cmd} = [ $which ];
340             }
341             else {
342 0   0     0 $shared{$cmd} = [
343             $^X, # use the same Perl
344             File::Spec->catfile((dist_share('AnyEvent-FTP') or die "unable to find share directory") , 'ppt', "$cmd.pl"),
345             ];
346             }
347             }
348              
349 9         17 return @{ $shared{$cmd} }, @args;
  9         51  
350             }
351             }
352              
353             1;
354              
355             __END__