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   643658 use strict;
  12         63  
  12         526  
4 12     12   65 use warnings;
  12         24  
  12         636  
5 12     12   214 use 5.010;
  12         42  
6 12     12   1968 use Moo;
  12         23312  
  12         73  
7 12     12   12260 use File::chdir;
  12         27719  
  12         1701  
8 12     12   7312 use File::ShareDir::Dist qw( dist_share );
  12         14010  
  12         58  
9 12     12   7019 use File::Which qw( which );
  12         16761  
  12         934  
10 12     12   3580 use File::Temp qw( tempfile );
  12         97275  
  12         1270  
11 12     12   6709 use Capture::Tiny qw( capture );
  12         69326  
  12         2554  
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.20'; # VERSION
17              
18              
19             with 'AnyEvent::FTP::Server::Role::TransferPrep';
20              
21              
22             sub _layer
23             {
24 46 100   46   617 $_[0]->type eq 'A' ? $_[0]->ascii_layer : ':raw';
25             }
26              
27 2     2 0 14 sub help_retr { 'RETR pathname' }
28              
29             sub cmd_retr
30             {
31 21     21 0 85 my($self, $con, $req) = @_;
32              
33 21         82 my $fn = $req->args;
34              
35 21 50       98 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         56 eval {
42 12     12   5135 use autodie;
  12         166491  
  12         70  
43 21         189 local $CWD = $self->cwd;
44              
45 21 50 0     1839 if(-f $fn)
    0          
46             {
47             # TODO: re-write so that this does not blocks
48 21 100       207 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
49 21         246 my $size = -s $fn;
50 21         203 $con->send_response(150 => "Opening $type mode data connection for $fn ($size bytes)");
51 21         177 open my $fh, '<', $fn;
52 21         7303 binmode $fh, $self->_layer;
53 21 100       3539 seek $fh, $self->restart_offset, 0 if $self->restart_offset;
54 21         1154 $self->data->push_write(do { local $/; <$fh> });
  21         103  
  21         1279  
55 21         1474 close $fh;
56 21         3793 $self->data->push_shutdown;
57 21         888 $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       971 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         135 $self->clear_data;
77 21         113 $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     54 my $dir = $req->args || '.';
88              
89 11 50       76 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   121470 use autodie;
  12         30  
  12         63  
97 11         75 local $CWD = $self->cwd;
98              
99 11         782 $con->send_response(150 => "Opening ASCII mode data connection for file list");
100 11         29 my $dh;
101 11         121 opendir $dh, $dir;
102             my @list =
103 11 100       3081 map { $req->args ? join('/', $dir, $_) : $_ }
  21         74  
104             sort
105             grep !/^\.\.?$/,
106             readdir $dh;
107 11         69 closedir $dh;
108 11         1673 $self->data->push_write(join '', map { $_ . "\015\012" } @list);
  21         135  
109 11         671 $self->data->push_shutdown;
110 11         465 $con->send_response(226 => 'Transfer complete');
111             };
112 11 50       464 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         102 $self->clear_data;
121 11         59 $self->done;
122             }
123              
124              
125 2     2 0 11 sub help_list { 'LIST [ pathname]' }
126              
127             sub cmd_list
128             {
129 8     8 0 30 my($self, $con, $req) = @_;
130              
131 8   100     32 my $dir = $req->args || '.';
132 8 50       49 $dir = '.' if $dir eq '-l';
133              
134 8 50       51 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         18 eval {
141 12     12   96542 use autodie;
  12         30  
  12         75  
142              
143 8         93 my @cmd = _shared_cmd('ls', '-l', $dir);
144              
145 8         84 local $CWD = $self->cwd;
146              
147 8         606 $con->send_response(150 => "Opening ASCII mode data connection for file list");
148 8         17 my $dh;
149 8         50 opendir $dh, $dir;
150              
151 8     8   1988 $self->data->push_write(join "\015\012", split /\n/, scalar capture { system @cmd });
  8         98561  
152 8         15087 closedir $dh;
153 8         2527 $self->data->push_write("\015\012");
154 8         564 $self->data->push_shutdown;
155 8         642 $con->send_response(226 => 'Transfer complete');
156             };
157 8 50       724 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         177 $self->clear_data;
166 8         196 $self->done;
167             }
168              
169              
170 2     2 0 11 sub help_stor { 'STOR pathname' }
171              
172             sub cmd_stor
173             {
174 15     15 0 62 my($self, $con, $req) = @_;
175              
176 15         92 my $fn = $req->args;
177              
178 15 50       79 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         35 eval {
185 12     12   78624 use autodie;
  12         29  
  12         65  
186 15         193 local $CWD = $self->cwd;
187              
188 15 100       1275 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
189 15         167 $con->send_response(150 => "Opening $type mode data connection for $fn");
190              
191 15         126 open my $fh, '>', $fn;
192 15         8600 binmode $fh, $self->_layer;
193             $self->data->on_read(sub {
194             $self->data->push_read(sub {
195 30         1744 print $fh $_[0]{rbuf};
196 30         174 $_[0]{rbuf} = '';
197 15     15   2458 });
198 15         4190 });
199             $self->data->on_error(sub {
200 15     15   503 close $fh;
201 15         4245 $self->data->push_shutdown;
202 15         1418 $con->send_response(226 => 'Transfer complete');
203 15         110 $self->clear_data;
204 15         87 $self->done;
205 15         1113 });
206             };
207 15 50       19143 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 11 sub help_appe { 'APPE pathname' }
221              
222             sub cmd_appe
223             {
224 7     7 0 42 my($self, $con, $req) = @_;
225              
226 7         50 my $fn = $req->args;
227              
228 7 50       44 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         17 eval {
235 12     12   79861 use autodie;
  12         25  
  12         105  
236 7         50 local $CWD = $self->cwd;
237              
238 7 100       437 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
239 7         88 $con->send_response(150 => "Opening $type mode data connection for $fn");
240              
241 7         72 open my $fh, '>>', $fn;
242 7         5692 binmode $fh, $self->_layer;
243             $self->data->on_read(sub {
244             $self->data->push_read(sub {
245 16         899 print $fh $_[0]{rbuf};
246 16         61 $_[0]{rbuf} = '';
247 7     7   1016 });
248 7         2605 });
249             $self->data->on_error(sub {
250 7     7   215 close $fh;
251 7         3040 $self->data->push_shutdown;
252 7         611 $con->send_response(226 => 'Transfer complete');
253 7         51 $self->clear_data;
254 7         44 $self->done;
255 7         642 });
256             };
257 7 50       334 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 12 sub help_stou { 'STOU (store unique filename)' }
271              
272             sub cmd_stou
273             {
274 3     3 0 30 my($self, $con, $req) = @_;
275              
276 3         11 my $fn = $req->args;
277              
278 3 50       18 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         8 eval {
285 12     12   81595 use autodie;
  12         28  
  12         73  
286 3         57 local $CWD = $self->cwd;
287              
288 3         186 my $fh;
289              
290 3 50 33     16 if($fn && ! -e $fn)
291             {
292 0         0 open $fh, '>', $fn;
293             }
294             else
295             {
296 3         22 ($fh,$fn) = tempfile( "aefXXXXXX", TMPDIR => 0 )
297             }
298              
299 3 100       1404 my $type = $self->type eq 'A' ? 'ASCII' : 'Binary';
300 3         24 $con->send_response(150 => "FILE: $fn");
301              
302 3         51 binmode $fh, $self->_layer;
303             $self->data->on_read(sub {
304             $self->data->push_read(sub {
305 6         255 print $fh $_[0]{rbuf};
306 6         20 $_[0]{rbuf} = '';
307 3     3   394 });
308 3         2301 });
309             $self->data->on_error(sub {
310 3     3   71 close $fh;
311 3         2032 $self->data->push_shutdown;
312 3         211 $con->send_response(226 => 'Transfer complete');
313 3         19 $self->clear_data;
314 3         14 $self->done;
315 3         195 });
316             };
317 3 50       126 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   645475 my ($cmd, @args) = @_;
335              
336 9 100       64 unless (defined $shared{$cmd}) {
337 2         15 my $which = which $cmd;
338 2 50 33     679 if ($which && !$always_use_bundled_cmd) {
339 2         13 $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         18 return @{ $shared{$cmd} }, @args;
  9         47  
350             }
351             }
352              
353             1;
354              
355             __END__