From 16430d4d2e77e257c26b3a00542faefa56b2454e Mon Sep 17 00:00:00 2001 From: andrey012 Date: Wed, 15 Apr 2015 03:29:01 +0300 Subject: [PATCH 1/3] support for callback function for SFTP::put function - in order to pipe data directly to remote server without putting it into file or keeping in memory. This can be useful particularly for dumping big databases directly to remote server. --- phpseclib/Net/SFTP.php | 30 ++++++++++++++++-- tests/Functional/Net/SFTPUserStoryTest.php | 36 ++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 5f7d3666..167f9828 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -102,6 +102,11 @@ define('NET_SFTP_LOCAL_FILE', 1); */ // this value isn't really used anymore but i'm keeping it reserved for historical reasons define('NET_SFTP_STRING', 2); +/** + * Reads data from callback: + * function callback($length) returns string to proceed, null for EOF + */ +define('NET_SFTP_CALLBACK', 16); /** * Resumes an upload */ @@ -1759,6 +1764,10 @@ class Net_SFTP extends Net_SSH2 * * If $data is a resource then it'll be used as a resource instead. * + * + * Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number + * of bytes to return, and returns a string if there is some data or null if there is no more data + * * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take * care of that, yourself. * @@ -1836,7 +1845,15 @@ class Net_SFTP extends Net_SSH2 } // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3 + $callback = false; switch (true) { + case $mode & NET_SFTP_CALLBACK; + if (!is_callable($data)) { + user_error("\$data should be is_callable if you set NET_SFTP_CALLBACK flag"); + } + $callback = $data; + // do nothing + break; case is_resource($data): $mode = $mode & ~NET_SFTP_LOCAL_FILE; $fp = $data; @@ -1863,6 +1880,8 @@ class Net_SFTP extends Net_SSH2 } else { fseek($fp, $offset); } + } elseif ($callback) { + $size = 0; } else { $size = strlen($data); } @@ -1874,8 +1893,15 @@ class Net_SFTP extends Net_SSH2 // make the SFTP packet be exactly 4096 bytes by including the bytes in the NET_SFTP_WRITE packets "header" $sftp_packet_size-= strlen($handle) + 25; $i = 0; - while ($sent < $size) { - $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size); + while ($callback || ($sent < $size)) { + if ($callback) { + $temp = call_user_func($callback, $sftp_packet_size); + if (is_null($temp)) { + break; + } + } else { + $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size); + } $subtemp = $offset + $sent; $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp); if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) { diff --git a/tests/Functional/Net/SFTPUserStoryTest.php b/tests/Functional/Net/SFTPUserStoryTest.php index b5c57de1..822a269f 100644 --- a/tests/Functional/Net/SFTPUserStoryTest.php +++ b/tests/Functional/Net/SFTPUserStoryTest.php @@ -11,6 +11,7 @@ class Functional_Net_SFTPUserStoryTest extends PhpseclibFunctionalTestCase static protected $scratchDir; static protected $exampleData; static protected $exampleDataLength; + static protected $buffer; static public function setUpBeforeClass() { @@ -132,6 +133,41 @@ class Functional_Net_SFTPUserStoryTest extends PhpseclibFunctionalTestCase return $sftp; } + static function callback($length) + { + $r = substr(self::$buffer, 0, $length); + self::$buffer = substr(self::$buffer, $length); + if (strlen($r)) return $r; + return null; + } + + /** + * @depends testStatOnDir + */ + public function testPutSizeGetFileCallback($sftp) + { + self::$buffer = self::$exampleData; + $this->assertTrue( + $sftp->put('file1.txt', array(__CLASS__, 'callback'), NET_SFTP_CALLBACK), + 'Failed asserting that example data could be successfully put().' + ); + + $this->assertSame( + self::$exampleDataLength, + $sftp->size('file1.txt'), + 'Failed asserting that put example data has the expected length' + ); + + $this->assertSame( + self::$exampleData, + $sftp->get('file1.txt'), + 'Failed asserting that get() returns expected example data.' + ); + + return $sftp; + } + + /** * @depends testStatOnDir */ From 0cc6125f8795f577aa9e01535c1a58545e290866 Mon Sep 17 00:00:00 2001 From: andrey012 Date: Sat, 2 May 2015 14:28:19 +0300 Subject: [PATCH 2/3] documentation fix --- phpseclib/Net/SFTP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 167f9828..5a2168ba 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1765,7 +1765,7 @@ class Net_SFTP extends Net_SSH2 * If $data is a resource then it'll be used as a resource instead. * * - * Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number + * Setting $mode to NET_SFTP_CALLBACK will use $data as callback function, which gets only one parameter -- number * of bytes to return, and returns a string if there is some data or null if there is no more data * * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take From e7719f765c22b4cdce55a8ba806df7664bce1fb6 Mon Sep 17 00:00:00 2001 From: andrey012 Date: Sat, 2 May 2015 15:27:57 +0300 Subject: [PATCH 3/3] callbacks - two PRs conflict sorted out. First one: e9b698fd65075273ec11a1ffae3ebeccaad6e086, fba6894474b767fcacb2dc25cae1508107b417be, 1cd286642ef6ceb5a0c2405799f68798eb20470e Second one: 16430d4d2e77e257c26b3a00542faefa56b2454e, 0cc6125f8795f577aa9e01535c1a58545e290866 --- phpseclib/Net/SFTP.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index ca1f794a..5c71ee04 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1793,12 +1793,12 @@ class Net_SFTP extends Net_SSH2 * @param optional Integer $mode * @param optional Integer $start * @param optional Integer $local_start - * @param optional callable|null $callback + * @param optional callable|null $progressCallback * @return Boolean * @access public * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode(). */ - function put($remote_file, $data, $mode = NET_SFTP_STRING, $start = -1, $local_start = -1, $callback = null) + function put($remote_file, $data, $mode = NET_SFTP_STRING, $start = -1, $local_start = -1, $progressCallback = null) { if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { return false; @@ -1846,13 +1846,13 @@ class Net_SFTP extends Net_SSH2 } // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3 - $callback = false; + $dataCallback = false; switch (true) { case $mode & NET_SFTP_CALLBACK; if (!is_callable($data)) { user_error("\$data should be is_callable if you set NET_SFTP_CALLBACK flag"); } - $callback = $data; + $dataCallback = $data; // do nothing break; case is_resource($data): @@ -1881,7 +1881,7 @@ class Net_SFTP extends Net_SSH2 } else { fseek($fp, $offset); } - } elseif ($callback) { + } elseif ($dataCallback) { $size = 0; } else { $size = strlen($data); @@ -1894,9 +1894,9 @@ class Net_SFTP extends Net_SSH2 // make the SFTP packet be exactly 4096 bytes by including the bytes in the NET_SFTP_WRITE packets "header" $sftp_packet_size-= strlen($handle) + 25; $i = 0; - while ($callback || ($sent < $size)) { - if ($callback) { - $temp = call_user_func($callback, $sftp_packet_size); + while ($dataCallback || ($sent < $size)) { + if ($dataCallback) { + $temp = call_user_func($dataCallback, $sftp_packet_size); if (is_null($temp)) { break; } @@ -1912,8 +1912,8 @@ class Net_SFTP extends Net_SSH2 return false; } $sent+= strlen($temp); - if (is_callable($callback)) { - call_user_func($callback, $sent); + if (is_callable($progressCallback)) { + call_user_func($progressCallback, $sent); } $i++;