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
29/**
30 * This is a singleton to let the wait/notify functions work
31 * I know it's horrible, but it's a means to an end
32 */
33class ServerClientTestCase
34{
35    private $isWorker = false;
36
37    private $workerHandle = [];
38
39    private $workerStdIn = [];
40
41    private $workerStdOut = [];
42
43    private static $instance;
44
45    public static function getInstance($isWorker = false)
46    {
47        if (!isset(self::$instance)) {
48            self::$instance = new self($isWorker);
49        }
50
51        return self::$instance;
52    }
53
54    public function __construct($isWorker = false)
55    {
56        if (!isset(self::$instance)) {
57            self::$instance = $this;
58        }
59
60        $this->isWorker = $isWorker;
61    }
62
63    private function spawnWorkerProcess($worker, $code)
64    {
65        if (defined("PHP_WINDOWS_VERSION_MAJOR")) {
66            $ini = php_ini_loaded_file();
67            $cmd = sprintf(
68                '%s %s "%s" %s',
69                PHP_BINARY, $ini ? "-n -c $ini" : "",
70                __FILE__,
71                WORKER_ARGV_VALUE
72            );
73        } else {
74            $cmd = sprintf(
75                '%s "%s" %s %s',
76                PHP_BINARY,
77                __FILE__,
78                WORKER_ARGV_VALUE,
79                $worker
80            );
81        }
82        $this->workerHandle[$worker] = proc_open(
83            $cmd,
84            [['pipe', 'r'], ['pipe', 'w'], STDERR],
85            $pipes
86        );
87        $this->workerStdIn[$worker] = $pipes[0];
88        $this->workerStdOut[$worker] = $pipes[1];
89
90        fwrite($this->workerStdIn[$worker], $code . "\n---\n");
91    }
92
93    private function cleanupWorkerProcess($worker)
94    {
95        fclose($this->workerStdIn[$worker]);
96        fclose($this->workerStdOut[$worker]);
97        proc_close($this->workerHandle[$worker]);
98    }
99
100    private function stripPhpTagsFromCode($code)
101    {
102        return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code);
103    }
104
105    public function runWorker()
106    {
107        $code = '';
108
109        while (1) {
110            $line = fgets(STDIN);
111
112            if (trim($line) === "---") {
113                break;
114            }
115
116            $code .= $line;
117        }
118
119        eval($code);
120    }
121
122    public function run($masterCode, $workerCode)
123    {
124        if (!is_array($workerCode)) {
125            $workerCode = [WORKER_DEFAULT_NAME => $workerCode];
126        }
127        foreach ($workerCode as $worker => $code) {
128            $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code));
129        }
130        eval($this->stripPhpTagsFromCode($masterCode));
131        foreach ($workerCode as $worker => $code) {
132            $this->cleanupWorkerProcess($worker);
133        }
134    }
135
136    public function wait($worker, $timeout = null)
137    {
138        $handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker];
139        if ($timeout === null) {
140            fgets($handle);
141            return true;
142        }
143
144        stream_set_blocking($handle, false);
145        $read = [$handle];
146        $result = stream_select($read, $write, $except, $timeout);
147        if (!$result) {
148            return false;
149        }
150
151        fgets($handle);
152        stream_set_blocking($handle, true);
153        return true;
154    }
155
156    public function notify($worker)
157    {
158        fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n");
159    }
160}
161
162if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) {
163    ServerClientTestCase::getInstance(true)->runWorker();
164}
165