似乎 phpseclib 的 scp get 函数在文件末尾返回了一个额外的 NULL,这不是原来的。
我写了一个脚本来演示这个问题。此脚本将使用 phpseclib scp->put() 将文件作为 /tmp/somefile 放入 user@localhost。然后它将使用 scp->get() 返回该文件。 (scp->get() 在有本地文件名和没有本地文件名的情况下都运行,以演示问题可能出在哪里。)代码如下:
#!/usr/bin/env php
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . '/www/cgi-bin/phpseclib') ;
require_once('Crypt/RSA.php');
require_once('Net/SSH2.php');
require_once('Net/SCP.php');
function executeSSHCommand($ssh, $command) {
$result = $ssh->exec($command) ;
printf(" cmd: %s (%s)\n", $command, ($result === false ? 'ERROR' : 'OK') ) ;
if (is_string($result) && strlen($result) > 0) {
$responseLines = explode("\n", $result) ;
foreach ($responseLines as $line) {
if (strlen(trim($line)) > 0)
printf(" > %s", $line ) ;
}
}
}
function toHumanPerms($perms) {
$rtnVal = '' ;
$workPerms = $perms ;
foreach ([ 'o', 'g', 'u' ] as $type) {
$thisPerms = ($workPerms & 0x07) ;
$workPerms >>= 3 ;
$r = ($thisPerms & 0x04) ? 'r' : '' ;
$w = ($thisPerms & 0x02) ? 'w' : '' ;
$x = ($thisPerms & 0x01) ? 'x' : '' ;
if (strlen($rtnVal) > 0) $rtnVal .= ',' ;
$rtnVal .= $type . '=' . $r . $w . $x ;
}
return $rtnVal ;
}
$home = getenv('HOME') ;
$system = 'localhost' ;
$user = getenv('LOGNAME') ;
$hostString = $user . '@' . $system ;
$key = new Crypt_RSA() ;
if ( ! $key->loadKey(file_get_contents($home . DIRECTORY_SEPARATOR . '.ssh/id_rsa'))) {
die("Could not load private key.\n") ;
}
$ssh = new Net_SSH2($system) ;
if ($ssh) {
if ($ssh->login($user, $key)) {
$scp = new Net_SCP($ssh) ;
$localFile = (count($argv) > 1 ? $argv[1] : $home . DIRECTORY_SEPARATOR . '.bashrc' ) ; // If not specified in $argv then copy /etc/hosts
$remoteFile = '/tmp/' . preg_replace('%[/]%','-',$localFile) ; // For example /tmp/-etc-hosts
$copyBack = $localFile . '-copiedBack' ;
$fileSize = filesize($localFile) ;
$success = $scp->put($remoteFile , $localFile, NET_SCP_LOCAL_FILE) ;
printf(" put: %s -> %s: %s\n", $localFile, $remoteFile, ($success === true ? 'OK' : '*ERROR*') ) ;
if ($success) {
$permsString = toHumanPerms(fileperms($localFile)) ;
$command = 'chmod ' . $permsString . ' ' . escapeshellarg($remoteFile) ;
executeSSHCommand($ssh, $command) ;
$fileModTime = date('YmdHi.s', filemtime($localFile)) ;
$command = sprintf('touch -t %s %s', $fileModTime, escapeshellarg($remoteFile)) ;
executeSSHCommand($ssh, $command) ; // Execute but do not write to terminal. (Write to log, though)
$remoteData = $scp->get($remoteFile) ; // copy file to local variable
$success = $scp->get($remoteFile, $copyBack) ; // copy file to filesystem
printf(" get: %s -> %s: %s\n\n", $remoteFile, $copyBack, $success === true ? 'OK' : '*ERROR*' ) ;
printf("Original file size was %d, returned data size is %d, returned file size is %d\n", $fileSize, strlen($remoteData), filesize($copyBack)) ;
}
}
else {
printf("**** Unable to authenticate user %s at host %s. ****\n", $user, $system ) ;
}
}
else {
printf("**** Unable to connect to server %s. ****\n", $system) ;
}
如果传递参数,则该参数将用作源文件名。目标文件名将是 /tmp/ 加上源文件名,但斜杠将被破折号代替。例如:
testscp /path/to/file
目标文件名将是 /tmp/-path-to-file
如果没有传递文件名,那么将使用 $HOME/.bashrc(和 /tmp/-home-username-.bashrc)。
当我使用任何常规文件(文本、二进制文件等)执行此操作时,get(..., $filename) 返回的文件和 get(...) 返回的字符串比原始文件长一个字节,在每种情况下我都尝试过,除了一个:原始文件名是空的。空文件的情况下工作正常,但在所有其他情况下,似乎在 get() 的最后抛出了一个额外的 NULL 字符。
put($filename, $targetname, , NET_SCP_LOCAL_FILE) 写入的文件在任何情况下都是原始文件的正确副本(通过 diff 证明)。
我无法确定造成这种情况的原因。由于它似乎一直是错误的,我会假设这个问题是众所周知的,或者我做的不正确。希望是后者。
很容易在 scp->get() 的用法之后进行操作,如果存在则从末尾删除 null ...但可能存在 null 应该存在的情况(其他情况下不存在额外的 NULL发送)。所以那不好。
读过这篇文章的人是否知道我可以做些什么来解决这个问题,或者如果是这样的话报告问题的方法?
谢谢。
更新:目前,我修改了 phpseclib/Net/SCP.php 如下:
在函数 get() 中,就在这两行之后(我的副本中的第 284 和 285 行):
while ($size < $info['size']) {
$data = $this->_receive();
我添加了以下内容:
// Following added by Lovelady, 6-May-2023 because scp->get() always returned an extra byte for non-zero get.
$expected = $info['size'] - $size ; // Lovelady
if (strlen($data) == $expected + 1) // Lovelady (If exactly 1 byte too many...)
$data = substr($data, 0, $expected) ; // Lovelady
// Above added by Lovelady, 6-May-2023 because scp->get() always returned an extra byte for non-empty file.
在某些时候,除非其他人这样做,否则我可能会回去尝试找出为什么 $this->receive() 最后返回额外的 NULL。
我认为我目前的答案不太理想,但相对安全,因为例程已经知道它应该接收多少字节,并且正好超过了 1.