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   688033 use strict;
  12         1915  
  12         395  
4 12     12   64 use warnings;
  12         28  
  12         290  
5 12     12   285 use 5.010;
  12         47  
6 12     12   2873 use Moo;
  12         40461  
  12         94  
7 12     12   12801 use File::chdir;
  12         29398  
  12         1550  
8 12     12   5834 use File::ShareDir::Dist qw( dist_share );
  12         11859  
  12         58  
9 12     12   6547 use File::Which qw( which );
  12         12585  
  12         807  
10 12     12   3043 use File::Temp qw( tempfile );
  12         78297  
  12         883  
11 12     12   7345 use Capture::Tiny qw( capture );
  12         79476  
  12         2394  
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.18'; # VERSION
17              
18              
19             with 'AnyEvent::FTP::Server::Role::TransferPrep';
20              
21              
22             sub _layer
23             {
24 46 100   46   633 $_[0]->type eq 'A' ? $_[0]->ascii_layer : ':raw';
25             }
26              
27 2     2 0 10 sub help_retr { 'RETR pathname' }
28              
29             sub cmd_retr
30             {
31 21     21 0 75 my($self, $con, $req) = @_;
32              
33 21         65 my $fn = $req->args;
34              
35 21 50       93 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         40 eval {
42 12     12   5849 use autodie;
  12         152559  
  12         73  
43 21         133 local $CWD = $self->cwd;
44              
45 21 50 0     1694 if(-f $fn)
    0          
46             {
47             # TODO: re-write so that this does not blocks
48 21 100       203 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
49 21         235 my $size = -s $fn;
50 21         213 $con->send_response(150 => "Opening $type mode data connection for $fn ($size bytes)");
51 21         243 open my $fh, '<', $fn;
52 21         7581 binmode $fh, $self->_layer;
53 21 100       4021 seek $fh, $self->restart_offset, 0 if $self->restart_offset;
54 21         1372 $self->data->push_write(do { local $/; <$fh> });
  21         104  
  21         1448  
55 21         1441 close $fh;
56 21         3551 $self->data->push_shutdown;
57 21         758 $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       726 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         131 $self->clear_data;
77 21         115 $self->done;
78             }
79              
80              
81 2     2 0 8 sub help_nlst { 'NLST [ (pathname)]' }
82              
83             sub cmd_nlst
84             {
85 11     11 0 41 my($self, $con, $req) = @_;
86              
87 11   100     41 my $dir = $req->args || '.';
88              
89 11 50       57 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         24 eval {
96 12     12   101765 use autodie;
  12         37  
  12         64  
97 11         48 local $CWD = $self->cwd;
98              
99 11         711 $con->send_response(150 => "Opening ASCII mode data connection for file list");
100 11         28 my $dh;
101 11         79 opendir $dh, $dir;
102             my @list =
103 11 100       3749 map { $req->args ? join('/', $dir, $_) : $_ }
  21         79  
104             sort
105             grep !/^\.\.?$/,
106             readdir $dh;
107 11         66 closedir $dh;
108 11         1949 $self->data->push_write(join '', map { $_ . "\015\012" } @list);
  21         123  
109 11         583 $self->data->push_shutdown;
110 11         382 $con->send_response(226 => 'Transfer complete');
111             };
112 11 50       385 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         141 $self->clear_data;
121 11         58 $self->done;
122             }
123              
124              
125 2     2 0 10 sub help_list { 'LIST [ pathname]' }
126              
127             sub cmd_list
128             {
129 8     8 0 43 my($self, $con, $req) = @_;
130              
131 8   100     38 my $dir = $req->args || '.';
132 8 50       42 $dir = '.' if $dir eq '-l';
133              
134 8 50       45 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         24 eval {
141 12     12   81270 use autodie;
  12         30  
  12         73  
142              
143 8         148 my @cmd = _shared_cmd('ls', '-l', $dir);
144              
145 8         82 local $CWD = $self->cwd;
146              
147 8         714 $con->send_response(150 => "Opening ASCII mode data connection for file list");
148 8         26 my $dh;
149 8         101 opendir $dh, $dir;
150              
151 8     8   2509 $self->data->push_write(join "\015\012", split /\n/, scalar capture { system @cmd });
  8         91014  
152 8         16002 closedir $dh;
153 8         3430 $self->data->push_write("\015\012");
154 8         659 $self->data->push_shutdown;
155 8         753 $con->send_response(226 => 'Transfer complete');
156             };
157 8 50       774 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         197 $self->clear_data;
166 8         245 $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 72 my($self, $con, $req) = @_;
175              
176 15         63 my $fn = $req->args;
177              
178 15 50       75 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         38 eval {
185 12     12   80600 use autodie;
  12         32  
  12         73  
186 15         89 local $CWD = $self->cwd;
187              
188 15 100       1308 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
189 15         487 $con->send_response(150 => "Opening $type mode data connection for $fn");
190              
191 15         140 open my $fh, '>', $fn;
192 15         7558 binmode $fh, $self->_layer;
193             $self->data->on_read(sub {
194             $self->data->push_read(sub {
195 30         1788 print $fh $_[0]{rbuf};
196 30         173 $_[0]{rbuf} = '';
197 15     15   2035 });
198 15         4202 });
199             $self->data->on_error(sub {
200 15     15   507 close $fh;
201 15         4325 $self->data->push_shutdown;
202 15         1054 $con->send_response(226 => 'Transfer complete');
203 15         145 $self->clear_data;
204 15         72 $self->done;
205 15         1043 });
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 55 my($self, $con, $req) = @_;
225              
226 7         40 my $fn = $req->args;
227              
228 7 50       69 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         25 eval {
235 12     12   84388 use autodie;
  12         31  
  12         72  
236 7         45 local $CWD = $self->cwd;
237              
238 7 100       602 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
239 7         109 $con->send_response(150 => "Opening $type mode data connection for $fn");
240              
241 7         84 open my $fh, '>>', $fn;
242 7         5360 binmode $fh, $self->_layer;
243             $self->data->on_read(sub {
244             $self->data->push_read(sub {
245 16         968 print $fh $_[0]{rbuf};
246 16         88 $_[0]{rbuf} = '';
247 7     7   848 });
248 7         2525 });
249             $self->data->on_error(sub {
250 7     7   234 close $fh;
251 7         2779 $self->data->push_shutdown;
252 7         463 $con->send_response(226 => 'Transfer complete');
253 7         57 $self->clear_data;
254 7         46 $self->done;
255 7         462 });
256             };
257 7 50       338 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 11 sub help_stou { 'STOU (store unique filename)' }
271              
272             sub cmd_stou
273             {
274 3     3 0 21 my($self, $con, $req) = @_;
275              
276 3         23 my $fn = $req->args;
277              
278 3 50       30 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         12 eval {
285 12     12   81798 use autodie;
  12         33  
  12         71  
286 3         18 local $CWD = $self->cwd;
287              
288 3         212 my $fh;
289              
290 3 50 33     31 if($fn && ! -e $fn)
291             {
292 0         0 open $fh, '>', $fn;
293             }
294             else
295             {
296 3         34 ($fh,$fn) = tempfile( "aefXXXXXX", TMPDIR => 0 )
297             }
298              
299 3 100       1501 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
300 3         37 $con->send_response(150 => "FILE: $fn");
301              
302 3         28 binmode $fh, $self->_layer;
303             $self->data->on_read(sub {
304             $self->data->push_read(sub {
305 6         341 print $fh $_[0]{rbuf};
306 6         44 $_[0]{rbuf} = '';
307 3     3   456 });
308 3         3004 });
309             $self->data->on_error(sub {
310 3     3   92 close $fh;
311 3         2428 $self->data->push_shutdown;
312 3         307 $con->send_response(226 => 'Transfer complete');
313 3         25 $self->clear_data;
314 3         23 $self->done;
315 3         212 });
316             };
317 3 50       195 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   181854 my ($cmd, @args) = @_;
335              
336 9 100       54 unless (defined $shared{$cmd}) {
337 2         16 my $which = which $cmd;
338 2 50 33     785 if ($which && !$always_use_bundled_cmd) {
339 2         14 $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         25 return @{ $shared{$cmd} }, @args;
  9         64  
350             }
351             }
352              
353             1;
354              
355             __END__