From e58427221d1762df47478b9c234d0766e05eaf6e Mon Sep 17 00:00:00 2001 From: Andrey Grinenko Date: Thu, 9 Apr 2015 02:59:00 +0300 Subject: [PATCH] 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 */