1<?php 2 3const WORKER_ARGV_VALUE = 'RUN_WORKER'; 4 5const WORKER_DEFAULT_NAME = 'server'; 6 7function phpt_notify($worker = WORKER_DEFAULT_NAME) 8{ 9 ServerClientTestCase::getInstance()->notify($worker); 10} 11 12function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null) 13{ 14 ServerClientTestCase::getInstance()->wait($worker, $timeout); 15} 16 17function phpt_has_sslv3() { 18 static $result = null; 19 if (!is_null($result)) { 20 return $result; 21 } 22 $server = @stream_socket_server('sslv3://127.0.0.1:10013'); 23 if ($result = !!$server) { 24 fclose($server); 25 } 26 return $result; 27} 28 29function phpt_extract_tls_records($rawData) { 30 $records = []; 31 $offset = 0; 32 $dataLength = strlen($rawData); 33 34 while ($offset < $dataLength) { 35 // Ensure there's enough data left for the header. 36 if ($offset + 5 > $dataLength) { 37 break; 38 } 39 40 // Extract the length of the current record. 41 $length = unpack("n", substr($rawData, $offset + 3, 2))[1]; 42 43 // Check if the total length is within the bounds of the rawData. 44 if ($offset + 5 + $length > $dataLength) { 45 break; 46 } 47 48 // Extract the record and add it to the records array. 49 $records[] = substr($rawData, $offset, 5 + $length); 50 51 // Move the offset past the current record. 52 $offset += 5 + $length; 53 } 54 55 return $records; 56} 57 58/** 59 * This is a singleton to let the wait/notify functions work 60 * I know it's horrible, but it's a means to an end 61 */ 62class ServerClientTestCase 63{ 64 private $isWorker = false; 65 66 private $workerHandle = []; 67 68 private $workerStdIn = []; 69 70 private $workerStdOut = []; 71 72 private static $instance; 73 74 public static function getInstance($isWorker = false) 75 { 76 if (!isset(self::$instance)) { 77 self::$instance = new self($isWorker); 78 } 79 80 return self::$instance; 81 } 82 83 public function __construct($isWorker = false) 84 { 85 if (!isset(self::$instance)) { 86 self::$instance = $this; 87 } 88 89 $this->isWorker = $isWorker; 90 } 91 92 private function spawnWorkerProcess($worker, $code) 93 { 94 if (defined("PHP_WINDOWS_VERSION_MAJOR")) { 95 $ini = php_ini_loaded_file(); 96 $cmd = sprintf( 97 '%s %s "%s" %s', 98 PHP_BINARY, $ini ? "-n -c $ini" : "", 99 __FILE__, 100 WORKER_ARGV_VALUE 101 ); 102 } else { 103 $cmd = sprintf( 104 '%s %s "%s" %s %s', 105 PHP_BINARY, 106 getenv('TEST_PHP_EXTRA_ARGS'), 107 __FILE__, 108 WORKER_ARGV_VALUE, 109 $worker 110 ); 111 } 112 $this->workerHandle[$worker] = proc_open( 113 $cmd, 114 [['pipe', 'r'], ['pipe', 'w'], STDERR], 115 $pipes 116 ); 117 $this->workerStdIn[$worker] = $pipes[0]; 118 $this->workerStdOut[$worker] = $pipes[1]; 119 120 fwrite($this->workerStdIn[$worker], $code . "\n---\n"); 121 } 122 123 private function cleanupWorkerProcess($worker) 124 { 125 fclose($this->workerStdIn[$worker]); 126 fclose($this->workerStdOut[$worker]); 127 proc_close($this->workerHandle[$worker]); 128 } 129 130 private function stripPhpTagsFromCode($code) 131 { 132 return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code); 133 } 134 135 public function runWorker() 136 { 137 $code = ''; 138 139 while (1) { 140 $line = fgets(STDIN); 141 142 if (trim($line) === "---") { 143 break; 144 } 145 146 $code .= $line; 147 } 148 149 eval($code); 150 } 151 152 public function run($masterCode, $workerCode) 153 { 154 if (!is_array($workerCode)) { 155 $workerCode = [WORKER_DEFAULT_NAME => $workerCode]; 156 } 157 foreach ($workerCode as $worker => $code) { 158 $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code)); 159 } 160 eval($this->stripPhpTagsFromCode($masterCode)); 161 foreach ($workerCode as $worker => $code) { 162 $this->cleanupWorkerProcess($worker); 163 } 164 } 165 166 public function wait($worker, $timeout = null) 167 { 168 $handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker]; 169 if ($timeout === null) { 170 fgets($handle); 171 return true; 172 } 173 174 stream_set_blocking($handle, false); 175 $read = [$handle]; 176 $result = stream_select($read, $write, $except, $timeout); 177 if (!$result) { 178 return false; 179 } 180 181 fgets($handle); 182 stream_set_blocking($handle, true); 183 return true; 184 } 185 186 public function notify($worker) 187 { 188 fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n"); 189 } 190} 191 192if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) { 193 ServerClientTestCase::getInstance(true)->runWorker(); 194} 195