]> granicus.if.org Git - php/commitdiff
Add a test for fragmented SSL packets
authorAbyr Valg <valga@abyrga.ru>
Sun, 29 Apr 2018 09:05:59 +0000 (12:05 +0300)
committerJakub Zelenka <bukka@php.net>
Fri, 25 Jan 2019 14:09:55 +0000 (14:09 +0000)
ext/openssl/tests/ServerClientProxyTestCase.inc [new file with mode: 0644]
ext/openssl/tests/non_blocking_eof.phpt [new file with mode: 0644]

diff --git a/ext/openssl/tests/ServerClientProxyTestCase.inc b/ext/openssl/tests/ServerClientProxyTestCase.inc
new file mode 100644 (file)
index 0000000..c55159f
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+
+const WORKER_ARGV_VALUE = 'RUN_WORKER';
+
+function phpt_notify($worker = null)
+{
+       ServerClientProxyTestCase::getInstance()->notify($worker);
+}
+
+function phpt_wait($worker = null)
+{
+       ServerClientProxyTestCase::getInstance()->wait($worker);
+}
+
+/**
+ * This is a singleton to let the wait/notify functions work
+ * I know it's horrible, but it's a means to an end
+ */
+class ServerClientProxyTestCase
+{
+       private $isWorker = false;
+
+       private $workerHandles = [];
+
+       private $workerStdIn = [];
+
+       private $workerStdOut = [];
+
+       private static $instance;
+
+       public static function getInstance($isWorker = false)
+       {
+               if (!isset(self::$instance)) {
+                       self::$instance = new self($isWorker);
+               }
+
+               return self::$instance;
+       }
+
+       public function __construct($isWorker = false)
+       {
+               if (!isset(self::$instance)) {
+                       self::$instance = $this;
+               }
+
+               $this->isWorker = $isWorker;
+       }
+
+       private function spawnWorkerProcess($worker, $code)
+       {
+               if (defined("PHP_WINDOWS_VERSION_MAJOR")) {
+                               $ini = php_ini_loaded_file();
+                               $cmd = sprintf('%s %s "%s" %s', PHP_BINARY, $ini ? "-n -c $ini" : "", __FILE__, WORKER_ARGV_VALUE);
+               } else {
+                               $cmd = sprintf('%s "%s" %s %s', PHP_BINARY, __FILE__, WORKER_ARGV_VALUE, $worker);
+               }
+               $this->workerHandle[$worker] = proc_open($cmd, [['pipe', 'r'], ['pipe', 'w'], STDERR], $pipes);
+               $this->workerStdIn[$worker] = $pipes[0];
+               $this->workerStdOut[$worker] = $pipes[1];
+
+               fwrite($this->workerStdIn[$worker], $code . "\n---\n");
+       }
+
+       private function cleanupWorkerProcess($worker)
+       {
+               fclose($this->workerStdIn[$worker]);
+               fclose($this->workerStdOut[$worker]);
+               proc_close($this->workerHandle[$worker]);
+       }
+
+       private function stripPhpTagsFromCode($code)
+       {
+               return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code);
+       }
+
+       public function runWorker()
+       {
+               $code = '';
+
+               while (1) {
+                       $line = fgets(STDIN);
+
+                       if (trim($line) === "---") {
+                               break;
+                       }
+
+                       $code .= $line;
+               }
+
+               eval($code);
+       }
+
+       public function run($testCode, array $workerCodes)
+       {
+               foreach ($workerCodes as $worker => $code) {
+                       $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code));
+               }
+               eval($this->stripPhpTagsFromCode($testCode));
+               foreach ($workerCodes as $worker => $code) {
+                       $this->cleanupWorkerProcess($worker);
+               }
+       }
+
+       public function wait($worker)
+       {
+               fgets($this->isWorker ? STDIN : $this->workerStdOut[$worker]);
+       }
+
+       public function notify($worker)
+       {
+               fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n");
+       }
+}
+
+if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) {
+       ServerClientProxyTestCase::getInstance(true)->runWorker();
+}
diff --git a/ext/openssl/tests/non_blocking_eof.phpt b/ext/openssl/tests/non_blocking_eof.phpt
new file mode 100644 (file)
index 0000000..86b3815
--- /dev/null
@@ -0,0 +1,99 @@
+--TEST--
+php_stream_eof() should not block on SSL non-blocking streams when packets are fragmented
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+?>
+--FILE--
+<?php
+
+$clientCode = <<<'CODE'
+       $context = stream_context_create(['ssl' => ['verify_peer' => false, 'peer_name' => 'bug54992.local']]);
+
+       phpt_wait('server');
+       phpt_notify('proxy');
+       
+       phpt_wait('proxy');
+       $fp = stream_socket_client("ssl://127.0.0.1:10012", $errornum, $errorstr, 3000, STREAM_CLIENT_CONNECT, $context);
+       stream_set_blocking($fp, false);
+
+       $read = [$fp];
+       $buf = '';
+       while (stream_select($read, $write, $except, 1000)) {
+               $chunk = stream_get_contents($fp, 4096);
+               var_dump($chunk);
+               $buf .= $chunk;
+               if ($buf === 'hello, world') {
+                       break;
+               }
+       }
+       
+       phpt_notify('server');
+       phpt_notify('proxy');
+CODE;
+
+$serverCode = <<<'CODE'
+       $context = stream_context_create(['ssl' => ['local_cert' => __DIR__ . '/bug54992.pem']]);
+
+       $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
+       $fp = stream_socket_server("ssl://127.0.0.1:10011", $errornum, $errorstr, $flags, $context);
+       phpt_notify();
+       
+       $conn = stream_socket_accept($fp);
+       fwrite($conn, 'hello, world');
+       
+       phpt_wait();
+       fclose($conn);
+CODE;
+
+$proxyCode = <<<'CODE'
+       phpt_wait();
+
+       $upstream = stream_socket_client("tcp://127.0.0.1:10011", $errornum, $errorstr, 3000, STREAM_CLIENT_CONNECT);
+       stream_set_blocking($upstream, false);
+
+       $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
+       $server = stream_socket_server("tcp://127.0.0.1:10012", $errornum, $errorstr, $flags);
+       phpt_notify();
+       
+       $conn = stream_socket_accept($server);
+       stream_set_blocking($conn, false);
+       
+       $read = [$upstream, $conn];
+       while (stream_select($read, $write, $except, 1)) {
+               foreach ($read as $fp) {
+                       $data = stream_get_contents($fp);
+                       if ($fp === $conn) {
+                               fwrite($upstream, $data);
+                       } else {
+                               if ($data !== '' && $data[0] === chr(23)) {
+                                       $parts = str_split($data, (int) ceil(strlen($data) / 3));
+                                       foreach ($parts as $part) {
+                                               fwrite($conn, $part);
+                                               usleep(1000);
+                                       }
+                               } else {
+                                       fwrite($conn, $data);
+                               }
+                       }
+               }
+               if (feof($upstream)) {
+                       break;
+               }
+               $read = [$upstream, $conn];
+       }
+       
+       phpt_wait();
+CODE;
+
+include 'ServerClientProxyTestCase.inc';
+ServerClientProxyTestCase::getInstance()->run($clientCode, [
+       'server' => $serverCode,
+       'proxy' => $proxyCode,
+]);
+?>
+--EXPECT--
+string(0) ""
+string(0) ""
+string(12) "hello, world"