diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index a2034550..ce7605b9 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 @@ -1798,7 +1805,15 @@ 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; + if (!is_callable($data)) { + user_error("\$data should be is_callable() if you specify SOURCE_CALLBACK flag"); + } + $callback = $data; + // do nothing + break; case is_resource($data): $mode = $mode & ~self::SOURCE_LOCAL_FILE; $fp = $data; @@ -1825,6 +1840,8 @@ class SFTP extends SSH2 } else { fseek($fp, $offset); } + } elseif ($callback) { + $size = 0; } else { $size = strlen($data); } @@ -1836,8 +1853,15 @@ 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 1b7e1f71..3f989ed2 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 */