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:
andrey012 2015-04-15 03:29:01 +03:00
parent c0370ee91d
commit 16430d4d2e
2 changed files with 64 additions and 2 deletions

View File

@ -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.
*
@ -1836,7 +1845,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;
@ -1863,6 +1880,8 @@ class Net_SFTP extends Net_SSH2
} else {
fseek($fp, $offset);
}
} elseif ($callback) {
$size = 0;
} else {
$size = strlen($data);
}
@ -1874,8 +1893,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)) {

View File

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