From e58427221d1762df47478b9c234d0766e05eaf6e Mon Sep 17 00:00:00 2001 From: Andrey Grinenko Date: Thu, 9 Apr 2015 02:59:00 +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 | 23 ++++++++++++-- tests/Functional/Net/SFTPUserStoryTest.php | 36 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index b3363aa8..909b7b20 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -72,6 +72,11 @@ class SFTP extends SSH2 */ // this value isn't really used anymore but i'm keeping it reserved for historical reasons const SOURCE_STRING = 2; + /** + * Reads data from callback: + * function callback($length) returns string to proceed, null for EOF + */ + const SOURCE_CALLBACK = 16; /** * Resumes an upload */ @@ -1718,6 +1723,8 @@ class SFTP extends SSH2 * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how * large $remote_file will be, as well. * + * 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 + * * If $data is a resource then it'll be used as a resource instead. * * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take @@ -1797,7 +1804,12 @@ class SFTP extends SSH2 } // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3 + $callback = false; switch (true) { + case $mode & self::SOURCE_CALLBACK; + $callback = $data; + // do nothing + break; case is_resource($data): $mode = $mode & ~self::SOURCE_LOCAL_FILE; $fp = $data; @@ -1824,6 +1836,8 @@ class SFTP extends SSH2 } else { fseek($fp, $offset); } + } elseif ($callback) { + $size = 0; } else { $size = strlen($data); } @@ -1835,8 +1849,13 @@ class SFTP extends 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 9c9f41b2..76bf163c 100644 --- a/tests/Functional/Net/SFTPUserStoryTest.php +++ b/tests/Functional/Net/SFTPUserStoryTest.php @@ -13,6 +13,7 @@ class Functional_Net_SFTPUserStoryTest extends PhpseclibFunctionalTestCase static protected $scratchDir; static protected $exampleData; static protected $exampleDataLength; + static protected $buffer; static public function setUpBeforeClass() { @@ -159,6 +160,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'), $sftp::SOURCE_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 testPutSizeGetFile */ From b5e80bc176a950b640c163abb91e4052bd604244 Mon Sep 17 00:00:00 2001 From: Andrey Grinenko Date: Sat, 11 Apr 2015 11:34:41 +0300 Subject: [PATCH 2/3] cosmetics --- phpseclib/Net/SFTP.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 909b7b20..55cf52c1 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1807,6 +1807,9 @@ class SFTP extends SSH2 $callback = false; switch (true) { case $mode & self::SOURCE_CALLBACK; + if (!is_callable($data)) { + throw new Exception('if you specify SOURCE_CALLBACK then $data should be callable'); + } $callback = $data; // do nothing break; @@ -1852,7 +1855,9 @@ class SFTP extends SSH2 while ($callback || $sent < $size) { if ($callback) { $temp = call_user_func($callback, $sftp_packet_size); - if (is_null($temp)) break; + if (is_null($temp)) { + break; + } } else { $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size); } From 26086789ef07af167ae31cdbe9fce00482501f29 Mon Sep 17 00:00:00 2001 From: andrey012 Date: Wed, 15 Apr 2015 03:35:08 +0300 Subject: [PATCH 3/3] replace throw with user_error() --- 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 55cf52c1..1d7b6d3e 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1808,7 +1808,7 @@ class SFTP extends SSH2 switch (true) { case $mode & self::SOURCE_CALLBACK; if (!is_callable($data)) { - throw new Exception('if you specify SOURCE_CALLBACK then $data should be callable'); + user_error("\$data should be is_callable() if you specify SOURCE_CALLBACK flag"); } $callback = $data; // do nothing