8192)return false; // фактическая граница REG.RU, старая реализация падает if(self::$dropInbox&&str_contains($command,'/inbox-'))return false; if(self::$withoutZlib)$command=preg_replace('/^php /','php -d disable_functions=gzinflate ',$command); $process=proc_open($command,[0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']],$pipes); fclose($pipes[0]);$output=stream_get_contents($pipes[1]);$error=stream_get_contents($pipes[2]);fclose($pipes[1]);fclose($pipes[2]);proc_close($process); if($error!=='')throw new \RuntimeException($error); return $output; } } class SFTP extends SSH2 { public static array $uploads=[]; public function put(string $path,string $bytes): bool {self::$uploads[]=$path;return file_put_contents($path,$bytes)!==false;} public function chmod(int $mode,string $path): bool {return chmod($path,$mode);} public function delete(string $path): bool {return !file_exists($path)||unlink($path);} } } namespace { require dirname(__DIR__).'/bootstrap.php'; use PrelandAdmin\{Store,Transport,Problem}; use phpseclib3\Net\{SSH2,SFTP}; function ensure(bool $condition,string $message): void {if(!$condition)throw new RuntimeException($message);echo 'PASS: '.$message."\n";} $fixture=sys_get_temp_dir().'/preland22-command-'.bin2hex(random_bytes(6));mkdir($fixture);mkdir($fixture.'/site'); $site=$fixture.'/site';file_put_contents($site.'/config.json','{"host":"initial.example","utm":{}}'); $store=new Store($fixture.'/state'); $target=['id'=>'limit','type'=>'sftp','host'=>'fixture','port'=>22,'user'=>'fixture','secret'=>'fixture','host_key'=>'ssh-ed25519 '.base64_encode('fixture-key'),'root'=>$site]; $transport=new Transport($store,$target); try { $transport->test();$before=$transport->readConfig(); ensure($before['config']->host==='initial.example','compressed helper reads under SSH 8KiB limit'); $transport->writeConfig($before['version'],'{"host":"small.example","utm":{}}'); ensure($transport->readConfig()['config']->host==='small.example','compressed RPC performs CAS write/read'); $large=json_encode(['host'=>'large.example','utm'=>(object)[],'padding'=>str_repeat('abcdefgh',3000)],JSON_THROW_ON_ERROR); $before=$transport->readConfig();$transport->writeConfig($before['version'],$large); ensure($transport->readConfig()['content']===$large,'large payload uses inbox and preserves bytes'); ensure(count(SFTP::$uploads)===1,'large payload uploaded through SFTP exactly once'); foreach(SFTP::$uploads as $path)ensure(!file_exists($path),'SFTP inbox removed after RPC'); $maximum=max(array_map('strlen',SSH2::$commands)); ensure($maximum<=7000,'all final shell commands stay within 7000 bytes (maximum '.$maximum.')'); $commandCount=count(SSH2::$commands);$uploads=count(SFTP::$uploads); // Inline test может оказаться длиннее inbox runner из-за base64 длинного // root, даже когда сам inbox помещается: этот preflight нельзя рекурсировать. $longRootTarget=$target;$longRootTarget['root']='/'.str_repeat('a',1800); try{(new Transport($store,$longRootTarget))->test();throw new RuntimeException('oversized test recursed');} catch(Problem $error){ensure(str_contains($error->getMessage(),'SSH bootstrap'),'oversized test RPC rejected before inbox/preflight recursion');} ensure(count(SSH2::$commands)===$commandCount&&count(SFTP::$uploads)===$uploads,'oversized test performs no exec/upload'); $bootstrap=new ReflectionMethod(Transport::class,'inboxCommand'); try{$bootstrap->invoke(null,'/*'.base64_encode(random_bytes(16000)).'*/',$site.'/inbox-test');throw new RuntimeException('oversized bootstrap accepted');} catch(Problem $error){ensure(str_contains($error->getMessage(),'bootstrap'),'oversized helper rejected before recursive preflight');} ensure(count(SSH2::$commands)===$commandCount&&count(SFTP::$uploads)===$uploads,'oversized bootstrap performs no remote call/upload'); $before=$transport->readConfig();SSH2::$dropInbox=true; try{$transport->writeConfig($before['version'],$large);throw new RuntimeException('dropped exec accepted');} catch(Problem $error){ensure(str_contains($error->getMessage(),'Неизвестный результат'),'dropped inbox exec remains unknown result');} SSH2::$dropInbox=false; foreach(SFTP::$uploads as $path)ensure(!file_exists($path),'inbox cleaned even when exec is interrupted'); SSH2::$withoutZlib=true; try{$transport->test();throw new RuntimeException('missing zlib accepted');}catch(Problem $error){ensure(str_contains($error->getMessage(),'zlib'),'missing gzinflate returns clear structured error');} SSH2::$withoutZlib=false; } finally { $iterator=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixture,FilesystemIterator::SKIP_DOTS),RecursiveIteratorIterator::CHILD_FIRST); foreach($iterator as $file)$file->isDir()?rmdir($file->getPathname()):unlink($file->getPathname());rmdir($fixture); } }