File Coverage

blib/lib/Net/SFTP.pm
Criterion Covered Total %
statement 38 372 10.2
branch 0 128 0.0
condition 0 21 0.0
subroutine 13 50 26.0
pod 16 26 61.5
total 67 597 11.2


line stmt bran cond sub pod time code
1             # $Id: SFTP.pm,v 1.35 2005/10/05 06:19:36 dbrobins Exp $
2              
3             package Net::SFTP;
4 2     2   802 use strict;
  2         5  
  2         56  
5              
6 2     2   597 use Net::SFTP::Constants qw( :fxp :flags :status :att SSH2_FILEXFER_VERSION );
  2         5  
  2         10  
7 2     2   614 use Net::SFTP::Util qw( fx2txt );
  2         4  
  2         85  
8 2     2   629 use Net::SFTP::Attributes;
  2         13  
  2         47  
9 2     2   10 use Net::SFTP::Buffer;
  2         4  
  2         32  
10 2     2   801 use Net::SSH::Perl::Constants qw( :msg2 );
  2         3114  
  2         13  
11 2     2   1460 use Net::SSH::Perl 2.12;
  2         111045  
  2         59  
12 2     2   15 use Math::Int64 qw( :native_if_available uint64 );
  2         15  
  2         15  
13              
14 2     2   264 use Carp qw( carp croak );
  2         4  
  2         79  
15              
16 2     2   11 use vars qw( $VERSION );
  2         4  
  2         68  
17             $VERSION = '0.12';
18              
19 2     2   9 use constant COPY_SIZE => 65536;
  2         4  
  2         3245  
20              
21             sub new {
22 0     0 1 0 my $class = shift;
23 0         0 my $sftp = bless { }, $class;
24 0         0 $sftp->{host} = shift;
25 0         0 $sftp->init(@_);
26             }
27              
28             # call the warning handler with the object and message
29             sub warn {
30 0     0 1 0 my ($sftp,$msg,$status) = @_;
31 0 0       0 $msg .= ': '.fx2txt($status) if defined $status;
32 0   0     0 $sftp->{status} = $status || SSH2_FX_OK;
33 0         0 $sftp->{warn_h}->($sftp,$msg);
34             }
35              
36             # returns last SSH error, or SSH2_FX_OK (only useful after failure)
37             sub status {
38 0     0 1 0 my $sftp = shift;
39 0         0 my $status = $sftp->{status};
40 0 0       0 wantarray ? ($status,fx2txt($status)) : $status
41             }
42              
43             # returns the new object
44             sub init {
45 0     0 0 0 my $sftp = shift;
46 0         0 my %param = @_;
47 0         0 $sftp->{debug} = delete $param{debug};
48 0         0 $sftp->{status} = SSH2_FX_OK;
49              
50 0   0     0 $param{ssh_args} ||= [];
51 0         0 $param{ssh_args} = [%{$param{ssh_args}}]
52 0 0       0 if UNIVERSAL::isa($param{ssh_args},'HASH');
53              
54 0 0       0 $param{warn} = 1 if not defined $param{warn}; # default
55 0   0 0   0 $sftp->{warn_h} = delete $param{warn} || sub {}; # false => ignore
56 0     0   0 $sftp->{warn_h} = sub { carp $_[1] } # true => emit warning
57 0 0 0     0 if $sftp->{warn_h} and not ref $sftp->{warn_h};
58              
59 0         0 $sftp->{_msg_id} = 0;
60              
61             my $ssh = Net::SSH::Perl->new($sftp->{host}, protocol => 2,
62 0         0 debug => $sftp->{debug}, @{ $param{ssh_args} });
  0         0  
63 0         0 $ssh->login($param{user}, $param{password}, 'supress_shell');
64 0         0 $sftp->{ssh} = $ssh;
65              
66 0         0 my $channel = $sftp->_open_channel;
67 0         0 $sftp->{channel} = $channel;
68              
69 0         0 $sftp->do_init;
70              
71 0         0 $sftp;
72             }
73              
74             # returns the new channel object
75             sub _open_channel {
76 0     0   0 my $sftp = shift;
77 0         0 my $ssh = $sftp->{ssh};
78              
79 0         0 my $channel = $ssh->_session_channel;
80 0         0 $channel->open;
81              
82             $channel->register_handler(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, sub {
83 0     0   0 my($c, $packet) = @_;
84 0         0 $c->{ssh}->debug("Sending subsystem: sftp");
85 0         0 my $r_packet = $c->request_start("subsystem", 1);
86 0         0 $r_packet->put_str("sftp");
87 0         0 $r_packet->send;
88 0         0 });
89              
90             my $subsystem_reply = sub {
91 0     0   0 my($c, $packet) = @_;
92 0         0 my $id = $packet->get_int32;
93 0 0       0 if ($packet->type == SSH2_MSG_CHANNEL_FAILURE) {
94 0         0 $c->{ssh}->fatal_disconnect("Request for " .
95             "subsystem 'sftp' failed on channel '$id'");
96             }
97 0         0 $c->{ssh}->break_client_loop;
98 0         0 };
99              
100 0         0 my $cmgr = $ssh->channel_mgr;
101 0         0 $cmgr->register_handler(SSH2_MSG_CHANNEL_FAILURE, $subsystem_reply);
102 0         0 $cmgr->register_handler(SSH2_MSG_CHANNEL_SUCCESS, $subsystem_reply);
103              
104 0         0 $sftp->{incoming} = Net::SFTP::Buffer->new;
105 0         0 my $incoming = $sftp->{incoming};
106             $channel->register_handler("_output_buffer", sub {
107 0     0   0 my($c, $buffer) = @_;
108 0         0 $incoming->append($buffer->bytes);
109 0         0 $c->{ssh}->break_client_loop;
110 0         0 });
111              
112             ## Get channel confirmation, etc. Break once we get a response
113             ## to subsystem execution.
114 0         0 $ssh->client_loop;
115              
116 0         0 $channel;
117             }
118              
119             sub do_init {
120 0     0 0 0 my $sftp = shift;
121 0         0 my $ssh = $sftp->{ssh};
122              
123 0         0 $sftp->debug("Sending SSH2_FXP_INIT");
124 0         0 my $msg = $sftp->new_msg(SSH2_FXP_INIT);
125 0         0 $msg->put_int32(SSH2_FILEXFER_VERSION);
126 0         0 $sftp->send_msg($msg);
127              
128 0         0 $msg = $sftp->get_msg;
129 0         0 my $type = $msg->get_int8;
130 0 0       0 if ($type != SSH2_FXP_VERSION) {
131 0         0 croak "Invalid packet back from SSH2_FXP_INIT (type $type)";
132             }
133 0         0 my $version = $msg->get_int32;
134 0         0 $sftp->debug("Remote version: $version");
135              
136             ## XXX Check for extensions.
137             }
138              
139             sub debug {
140 0     0 1 0 my $sftp = shift;
141 0 0       0 if ($sftp->{debug}) {
142 0         0 $sftp->{ssh}->debug("sftp: @_");
143             }
144             }
145              
146             ## Server -> client methods.
147              
148             # reads SSH2_FXP_STATUS packet and returns Net::SFTP::Attributes object or undef
149             sub get_attrs {
150 0     0 0 0 my $sftp = shift;
151 0         0 my($expected_id) = @_;
152 0         0 my $msg = $sftp->get_msg;
153 0         0 my $type = $msg->get_int8;
154 0         0 my $id = $msg->get_int32;
155 0         0 $sftp->debug("Received stat reply T:$type I:$id");
156 0 0       0 croak "ID mismatch ($id != $expected_id)" unless $id == $expected_id;
157 0 0       0 if ($type == SSH2_FXP_STATUS) {
    0          
158 0         0 my $status = $msg->get_int32;
159 0         0 $sftp->warn("Couldn't stat remote file",$status);
160 0         0 return;
161             }
162             elsif ($type != SSH2_FXP_ATTRS) {
163 0         0 croak "Expected SSH2_FXP_ATTRS packet, got $type";
164             }
165 0         0 $msg->get_attributes;
166             }
167              
168             # reads SSH2_FXP_STATUS packet and returns SFTP status value
169             sub get_status {
170 0     0 0 0 my $sftp = shift;
171 0         0 my($expected_id) = @_;
172 0         0 my $msg = $sftp->get_msg;
173 0         0 my $type = $msg->get_int8;
174 0         0 my $id = $msg->get_int32;
175              
176 0 0       0 croak "ID mismatch ($id != $expected_id)" unless $id == $expected_id;
177 0 0       0 if ($type != SSH2_FXP_STATUS) {
178 0         0 croak "Expected SSH2_FXP_STATUS packet, got $type";
179             }
180              
181 0         0 $msg->get_int32;
182             }
183              
184             # reads SSH2_FXP_HANDLE packet and returns handle, or undef on failure
185             sub get_handle {
186 0     0 0 0 my $sftp = shift;
187 0         0 my($expected_id) = @_;
188              
189 0         0 my $msg = $sftp->get_msg;
190 0         0 my $type = $msg->get_int8;
191 0         0 my $id = $msg->get_int32;
192              
193 0 0       0 croak "ID mismatch ($id != $expected_id)" unless $id == $expected_id;
194 0 0       0 if ($type == SSH2_FXP_STATUS) {
    0          
195 0         0 my $status = $msg->get_int32;
196 0         0 $sftp->warn("Couldn't get handle",$status);
197 0         0 return;
198             }
199             elsif ($type != SSH2_FXP_HANDLE) {
200 0         0 croak "Expected SSH2_FXP_HANDLE packet, got $type";
201             }
202              
203 0         0 $msg->get_str;
204             }
205              
206             ## Client -> server methods.
207              
208             sub _send_str_request {
209 0     0   0 my $sftp = shift;
210 0         0 my($code, $str) = @_;
211 0         0 my($msg, $id) = $sftp->new_msg_w_id($code);
212 0         0 $msg->put_str($str);
213 0         0 $sftp->send_msg($msg);
214 0         0 $sftp->debug("Sent message T:$code I:$id");
215 0         0 $id;
216             }
217              
218             sub _send_str_attrs_request {
219 0     0   0 my $sftp = shift;
220 0         0 my($code, $str, $a) = @_;
221 0         0 my($msg, $id) = $sftp->new_msg_w_id($code);
222 0         0 $msg->put_str($str);
223 0         0 $msg->put_attributes($a);
224 0         0 $sftp->send_msg($msg);
225 0         0 $sftp->debug("Sent message T:$code I:$id");
226 0         0 $id;
227             }
228              
229             sub _check_ok_status {
230 0     0   0 my $status = $_[0]->get_status($_[1]);
231 0 0       0 $_[0]->warn("Couldn't $_[2]",$status) unless $status == SSH2_FX_OK;
232 0         0 $status;
233             }
234              
235             ## SSH2_FXP_OPEN (3)
236             # returns handle on success, undef on failure
237             sub do_open {
238 0     0 1 0 my $sftp = shift;
239 0         0 my($path, $flags, $a) = @_;
240 0   0     0 $a ||= Net::SFTP::Attributes->new;
241 0         0 my($msg, $id) = $sftp->new_msg_w_id(SSH2_FXP_OPEN);
242 0         0 $msg->put_str($path);
243 0         0 $msg->put_int32($flags);
244 0         0 $msg->put_attributes($a);
245 0         0 $sftp->send_msg($msg);
246 0         0 $sftp->debug("Sent SSH2_FXP_OPEN I:$id P:$path");
247 0         0 $sftp->get_handle($id);
248             }
249              
250             ## SSH2_FXP_READ (4)
251             # returns data on success, (undef,$status) on failure
252             sub do_read {
253 0     0 1 0 my $sftp = shift;
254 0         0 my($handle, $offset, $size) = @_;
255 0   0     0 $size ||= COPY_SIZE;
256 0         0 my($msg, $expected_id) = $sftp->new_msg_w_id(SSH2_FXP_READ);
257 0         0 $msg->put_str($handle);
258 0         0 $msg->put_int64($offset);
259 0         0 $msg->put_int32($size);
260 0         0 $sftp->send_msg($msg);
261 0         0 $sftp->debug("Sent message SSH2_FXP_READ I:$expected_id O:$offset");
262 0         0 $msg = $sftp->get_msg;
263 0         0 my $type = $msg->get_int8;
264 0         0 my $id = $msg->get_int32;
265 0         0 $sftp->debug("Received reply T:$type I:$id");
266 0 0       0 croak "ID mismatch ($id != $expected_id)" unless $id == $expected_id;
267 0 0       0 if ($type == SSH2_FXP_STATUS) {
    0          
268 0         0 my $status = $msg->get_int32;
269 0 0       0 if ($status != SSH2_FX_EOF) {
270 0         0 $sftp->warn("Couldn't read from remote file",$status);
271 0         0 $sftp->do_close($handle);
272             }
273 0         0 return(undef, $status);
274             }
275             elsif ($type != SSH2_FXP_DATA) {
276 0         0 croak "Expected SSH2_FXP_DATA packet, got $type";
277             }
278 0         0 $msg->get_str;
279             }
280              
281             ## SSH2_FXP_WRITE (6)
282             # returns status (SSH2_FX_OK on success)
283             sub do_write {
284 0     0 1 0 my $sftp = shift;
285 0         0 my($handle, $offset, $data) = @_;
286 0         0 my($msg, $id) = $sftp->new_msg_w_id(SSH2_FXP_WRITE);
287 0         0 $msg->put_str($handle);
288 0         0 $msg->put_int64($offset);
289 0         0 $msg->put_str($data);
290 0         0 $sftp->send_msg($msg);
291 0         0 $sftp->debug("Sent message SSH2_FXP_WRITE I:$id O:$offset");
292 0         0 my $status = $sftp->_check_ok_status($id,'write to remote file');
293 0 0       0 $sftp->do_close($handle) unless $status == SSH2_FX_OK;
294 0         0 return $status;
295             }
296              
297             ## SSH2_FXP_LSTAT (7), SSH2_FXP_FSTAT (8), SSH2_FXP_STAT (17)
298             # these all return a Net::SFTP::Attributes object on success, undef on failure
299 0     0 1 0 sub do_lstat { $_[0]->_do_stat(SSH2_FXP_LSTAT, $_[1]) }
300 0     0 1 0 sub do_fstat { $_[0]->_do_stat(SSH2_FXP_FSTAT, $_[1]) }
301 0     0 1 0 sub do_stat { $_[0]->_do_stat(SSH2_FXP_STAT , $_[1]) }
302             sub _do_stat {
303 0     0   0 my $sftp = shift;
304 0         0 my $id = $sftp->_send_str_request(@_);
305 0         0 $sftp->get_attrs($id);
306             }
307              
308             ## SSH2_FXP_OPENDIR (11)
309             sub do_opendir {
310 0     0 1 0 my $sftp = shift;
311 0         0 my $id = $sftp->_send_str_request(SSH2_FXP_OPENDIR, @_);
312 0         0 $sftp->get_handle($id);
313             }
314              
315             ## SSH2_FXP_CLOSE (4), SSH2_FXP_REMOVE (13),
316             ## SSH2_FXP_MKDIR (14), SSH2_FXP_RMDIR (15),
317             ## SSH2_FXP_SETSTAT (9), SSH2_FXP_FSETSTAT (10)
318             # all of these return a status (SSH2_FX_OK on success)
319             {
320 2     2   15 no strict 'refs';
  2         4  
  2         2871  
321             *do_close = _gen_simple_method(SSH2_FXP_CLOSE, 'close file');
322             *do_remove = _gen_simple_method(SSH2_FXP_REMOVE, 'delete file');
323             *do_mkdir = _gen_simple_method(SSH2_FXP_MKDIR, 'create directory');
324             *do_rmdir = _gen_simple_method(SSH2_FXP_RMDIR, 'remove directory');
325             *do_setstat = _gen_simple_method(SSH2_FXP_SETSTAT , 'setstat');
326             *do_fsetstat = _gen_simple_method(SSH2_FXP_FSETSTAT , 'fsetstat');
327             }
328              
329             sub _gen_simple_method {
330 12     12   23 my($code, $msg) = @_;
331             sub {
332 0     0     my $sftp = shift;
333 0 0         my $id = @_ > 1 ?
334             $sftp->_send_str_attrs_request($code, @_) :
335             $sftp->_send_str_request($code, @_);
336 0           $sftp->_check_ok_status($id, $msg);
337 12         36 };
338             }
339              
340             ## SSH2_FXP_REALPATH (16)
341             sub do_realpath {
342 0     0 1   my $sftp = shift;
343 0           my($path) = @_;
344 0           my $expected_id = $sftp->_send_str_request(SSH2_FXP_REALPATH, $path);
345 0           my $msg = $sftp->get_msg;
346 0           my $type = $msg->get_int8;
347 0           my $id = $msg->get_int32;
348 0 0         croak "ID mismatch ($id != $expected_id)" unless $id == $expected_id;
349 0 0         if ($type == SSH2_FXP_STATUS) {
    0          
350 0           my $status = $msg->get_int32;
351 0           $sftp->warn("Couldn't canonicalise $path",$status);
352 0           return;
353             }
354             elsif ($type != SSH2_FXP_NAME) {
355 0           croak "Expected SSH2_FXP_NAME packet, got $type";
356             }
357 0           my $count = $msg->get_int32;
358 0 0         croak "Got multiple names ($count) from SSH2_FXP_REALPATH"
359             unless $count == 1;
360 0           $msg->get_str; ## Filename.
361             }
362              
363             ## SSH2_FXP_RENAME (18)
364             sub do_rename {
365 0     0 1   my $sftp = shift;
366 0           my($old, $new) = @_;
367 0           my($msg, $id) = $sftp->new_msg_w_id(SSH2_FXP_RENAME);
368 0           $msg->put_str($old);
369 0           $msg->put_str($new);
370 0           $sftp->send_msg($msg);
371 0           $sftp->debug("Sent message SSH2_FXP_RENAME '$old' => '$new'");
372 0           $sftp->_check_ok_status($id, "rename '$old' to '$new'");
373             }
374              
375             ## High-level client -> server methods.
376              
377             # always returns undef on failure
378             # if local filename is provided, returns '' on success, else file contents
379             sub get {
380 0     0 1   my $sftp = shift;
381 0           my($remote, $local, $cb) = @_;
382 0           my $ssh = $sftp->{ssh};
383 0 0         my $want = defined wantarray ? 1 : 0;
384              
385 0 0         my $a = $sftp->do_stat($remote) or return;
386 0           my $handle = $sftp->do_open($remote, SSH2_FXF_READ);
387 0 0         return unless defined $handle;
388              
389 0           local *FH;
390 0 0         if ($local) {
391 0 0         open FH, ">$local" or
392             $sftp->do_close($handle), croak "Can't open $local: $!";
393 0 0         binmode FH or
394             $sftp->do_close($handle), croak "Can't binmode FH: $!";
395             }
396              
397 0           my $offset = uint64(0);
398 0           my $ret = '';
399 0           while (1) {
400 0           my($data, $status) = $sftp->do_read($handle, $offset, COPY_SIZE);
401 0 0 0       last if defined $status && $status == SSH2_FX_EOF;
402 0 0         return unless $data;
403 0           my $len = length($data);
404 0 0         croak "Received more data than asked for $len > " . COPY_SIZE
405             if $len > COPY_SIZE;
406 0           $sftp->debug("In read loop, got $len offset $offset");
407 0 0         $cb->($sftp, $data, $offset, $a->size) if defined $cb;
408 0 0         if ($local) {
    0          
409 0           print FH $data;
410             }
411             elsif ($want) {
412 0           $ret .= $data;
413             }
414 0           $offset += $len;
415             }
416 0           $sftp->do_close($handle);
417              
418 0 0         if ($local) {
419 0           close FH;
420 0           my $flags = $a->flags;
421 0 0         my $mode = $flags & SSH2_FILEXFER_ATTR_PERMISSIONS ?
422             $a->perm & 0777 : 0666;
423 0 0         chmod $mode, $local or croak "Can't chmod $local: $!";
424              
425 0 0         if ($flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
426 0 0         utime $a->atime, $a->mtime, $local or
427             croak "Can't utime $local: $!";
428             }
429             }
430 0           $ret;
431             }
432              
433             sub put {
434 0     0 1   my $sftp = shift;
435 0           my($local, $remote, $cb) = @_;
436 0           my $ssh = $sftp->{ssh};
437              
438 0 0         my @stat = stat $local or croak "Can't stat local $local: $!";
439 0           my $size = $stat[7];
440 0           my $a = Net::SFTP::Attributes->new(Stat => \@stat);
441 0           my $flags = $a->flags;
442 0           $flags &= ~SSH2_FILEXFER_ATTR_SIZE;
443 0           $flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
444 0           $a->flags($flags);
445 0           $a->perm( $a->perm & 0777 );
446              
447 0           local *FH;
448 0 0         open FH, $local or croak "Can't open local file $local: $!";
449 0 0         binmode FH or croak "Can't binmode FH: $!";
450              
451 0           my $handle = $sftp->do_open($remote, SSH2_FXF_WRITE | SSH2_FXF_CREAT |
452             SSH2_FXF_TRUNC, $a); # check status for info
453 0 0         return unless defined $handle;
454              
455 0           my $offset = uint64(0);
456 0           while (1) {
457 0           my($len, $data, $msg, $id);
458 0           $len = read FH, $data, 8192;
459 0 0         last unless $len;
460 0 0         $cb->($sftp, $data, $offset, $size) if defined $cb;
461 0           my $status = $sftp->do_write($handle, $offset, $data);
462 0 0         if ($status != SSH2_FX_OK) {
463 0           close FH;
464 0           return;
465             }
466 0           $sftp->debug("In write loop, got $len offset $offset");
467 0           $offset += $len;
468             }
469              
470 0 0         close FH or $sftp->warn("Can't close local file $local: $!");
471              
472             # ignore failures here, the transmission is the important part
473 0           $sftp->do_fsetstat($handle, $a);
474 0           $sftp->do_close($handle);
475 0           return 1;
476             }
477              
478             # returns ()/undef on error, directory list/reference to same otherwise
479             sub ls {
480 0     0 1   my $sftp = shift;
481 0           my($remote, $code) = @_;
482 0           my @dir;
483 0           my $handle = $sftp->do_opendir($remote);
484 0 0         return unless defined $handle;
485              
486 0           while (1) {
487 0           my $expected_id = $sftp->_send_str_request(SSH2_FXP_READDIR, $handle);
488 0           my $msg = $sftp->get_msg;
489 0           my $type = $msg->get_int8;
490 0           my $id = $msg->get_int32;
491 0           $sftp->debug("Received reply T:$type I:$id");
492              
493 0 0         croak "ID mismatch ($id != $expected_id)" unless $id == $expected_id;
494 0 0         if ($type == SSH2_FXP_STATUS) {
    0          
495 0           my $status = $msg->get_int32;
496 0           $sftp->debug("Received SSH2_FXP_STATUS $status");
497 0 0         if ($status == SSH2_FX_EOF) {
498 0           last;
499             }
500             else {
501 0           $sftp->warn("Couldn't read directory",$status);
502 0           $sftp->do_close($handle);
503 0           return;
504             }
505             }
506             elsif ($type != SSH2_FXP_NAME) {
507 0           croak "Expected SSH2_FXP_NAME packet, got $type";
508             }
509              
510 0           my $count = $msg->get_int32;
511 0 0         last unless $count;
512 0           $sftp->debug("Received $count SSH2_FXP_NAME responses");
513 0           for my $i (0..$count-1) {
514 0           my $fname = $msg->get_str;
515 0           my $lname = $msg->get_str;
516 0           my $a = $msg->get_attributes;
517 0           my $rec = {
518             filename => $fname,
519             longname => $lname,
520             a => $a,
521             };
522 0 0 0       if ($code && ref($code) eq "CODE") {
523 0           $code->($rec);
524             }
525             else {
526 0           push @dir, $rec;
527             }
528             }
529             }
530 0           $sftp->do_close($handle);
531 0 0         wantarray ? @dir : \@dir;
532             }
533              
534             ## Messaging methods--messages are essentially sub-packets.
535              
536 0     0 0   sub msg_id { $_[0]->{_msg_id}++ }
537              
538             sub new_msg {
539 0     0 0   my $sftp = shift;
540 0           my($code) = @_;
541 0           my $msg = Net::SFTP::Buffer->new;
542 0           $msg->put_int8($code);
543 0           $msg;
544             }
545              
546             sub new_msg_w_id {
547 0     0 0   my $sftp = shift;
548 0           my($code, $sid) = @_;
549 0           my $msg = $sftp->new_msg($code);
550 0 0         my $id = defined $sid ? $sid : $sftp->msg_id;
551 0           $msg->put_int32($id);
552 0           ($msg, $id);
553             }
554              
555             sub send_msg {
556 0     0 0   my $sftp = shift;
557 0           my($buf) = @_;
558 0           my $b = Net::SFTP::Buffer->new;
559 0           $b->put_int32($buf->length);
560 0           $b->append($buf->bytes);
561 0           $sftp->{channel}->send_data($b->bytes);
562             }
563              
564             sub get_msg {
565 0     0 0   my $sftp = shift;
566 0           my $buf = $sftp->{incoming};
567 0           my $len;
568 0 0         unless ($buf->length > 4) {
569 0           $sftp->{ssh}->client_loop;
570 0 0         croak "Connection closed" unless $buf->length > 4;
571 0           $len = unpack "N", $buf->bytes(0, 4, '');
572 0 0         croak "Received message too long $len" if $len > 256 * 1024;
573 0           while ($buf->length < $len) {
574 0           $sftp->{ssh}->client_loop;
575             }
576             }
577 0           my $b = Net::SFTP::Buffer->new;
578 0           $b->append( $buf->bytes(0, $len, '') );
579 0           $b;
580             }
581              
582             1;
583             __END__