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.

This commit is contained in:
Andrey Grinenko 2015-04-09 02:59:00 +03:00
parent 41f2660136
commit e58427221d
2 changed files with 57 additions and 2 deletions

View File

@ -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)) {

View File

@ -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
*/