diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index af45ee78..126be4a3 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. * @@ -1837,7 +1846,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; @@ -1864,6 +1881,8 @@ class Net_SFTP extends Net_SSH2 } else { fseek($fp, $offset); } + } elseif ($callback) { + $size = 0; } else { $size = strlen($data); } @@ -1875,8 +1894,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 cdd8301c..687ef4c6 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 */