friendship ended with social-app. php is my new best friend
1<?php 2 3namespace React\Socket; 4 5use Evenement\EventEmitter; 6use React\EventLoop\Loop; 7use React\EventLoop\LoopInterface; 8use BadMethodCallException; 9use UnexpectedValueException; 10 11/** 12 * The `SecureServer` class implements the `ServerInterface` and is responsible 13 * for providing a secure TLS (formerly known as SSL) server. 14 * 15 * It does so by wrapping a `TcpServer` instance which waits for plaintext 16 * TCP/IP connections and then performs a TLS handshake for each connection. 17 * 18 * ```php 19 * $server = new React\Socket\TcpServer(8000); 20 * $server = new React\Socket\SecureServer($server, null, array( 21 * // tls context options here… 22 * )); 23 * ``` 24 * 25 * Whenever a client completes the TLS handshake, it will emit a `connection` event 26 * with a connection instance implementing [`ConnectionInterface`](#connectioninterface): 27 * 28 * ```php 29 * $server->on('connection', function (React\Socket\ConnectionInterface $connection) { 30 * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL; 31 * 32 * $connection->write('hello there!' . PHP_EOL); 33 * … 34 * }); 35 * ``` 36 * 37 * Whenever a client fails to perform a successful TLS handshake, it will emit an 38 * `error` event and then close the underlying TCP/IP connection: 39 * 40 * ```php 41 * $server->on('error', function (Exception $e) { 42 * echo 'Error' . $e->getMessage() . PHP_EOL; 43 * }); 44 * ``` 45 * 46 * See also the `ServerInterface` for more details. 47 * 48 * Note that the `SecureServer` class is a concrete implementation for TLS sockets. 49 * If you want to typehint in your higher-level protocol implementation, you SHOULD 50 * use the generic `ServerInterface` instead. 51 * 52 * @see ServerInterface 53 * @see ConnectionInterface 54 */ 55final class SecureServer extends EventEmitter implements ServerInterface 56{ 57 private $tcp; 58 private $encryption; 59 private $context; 60 61 /** 62 * Creates a secure TLS server and starts waiting for incoming connections 63 * 64 * It does so by wrapping a `TcpServer` instance which waits for plaintext 65 * TCP/IP connections and then performs a TLS handshake for each connection. 66 * It thus requires valid [TLS context options], 67 * which in its most basic form may look something like this if you're using a 68 * PEM encoded certificate file: 69 * 70 * ```php 71 * $server = new React\Socket\TcpServer(8000); 72 * $server = new React\Socket\SecureServer($server, null, array( 73 * 'local_cert' => 'server.pem' 74 * )); 75 * ``` 76 * 77 * Note that the certificate file will not be loaded on instantiation but when an 78 * incoming connection initializes its TLS context. 79 * This implies that any invalid certificate file paths or contents will only cause 80 * an `error` event at a later time. 81 * 82 * If your private key is encrypted with a passphrase, you have to specify it 83 * like this: 84 * 85 * ```php 86 * $server = new React\Socket\TcpServer(8000); 87 * $server = new React\Socket\SecureServer($server, null, array( 88 * 'local_cert' => 'server.pem', 89 * 'passphrase' => 'secret' 90 * )); 91 * ``` 92 * 93 * Note that available [TLS context options], 94 * their defaults and effects of changing these may vary depending on your system 95 * and/or PHP version. 96 * Passing unknown context options has no effect. 97 * 98 * This class takes an optional `LoopInterface|null $loop` parameter that can be used to 99 * pass the event loop instance to use for this object. You can use a `null` value 100 * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). 101 * This value SHOULD NOT be given unless you're sure you want to explicitly use a 102 * given event loop instance. 103 * 104 * Advanced usage: Despite allowing any `ServerInterface` as first parameter, 105 * you SHOULD pass a `TcpServer` instance as first parameter, unless you 106 * know what you're doing. 107 * Internally, the `SecureServer` has to set the required TLS context options on 108 * the underlying stream resources. 109 * These resources are not exposed through any of the interfaces defined in this 110 * package, but only through the internal `Connection` class. 111 * The `TcpServer` class is guaranteed to emit connections that implement 112 * the `ConnectionInterface` and uses the internal `Connection` class in order to 113 * expose these underlying resources. 114 * If you use a custom `ServerInterface` and its `connection` event does not 115 * meet this requirement, the `SecureServer` will emit an `error` event and 116 * then close the underlying connection. 117 * 118 * @param ServerInterface|TcpServer $tcp 119 * @param ?LoopInterface $loop 120 * @param array $context 121 * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support 122 * @see TcpServer 123 * @link https://www.php.net/manual/en/context.ssl.php for TLS context options 124 */ 125 public function __construct(ServerInterface $tcp, $loop = null, array $context = array()) 126 { 127 if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 128 throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); 129 } 130 131 if (!\function_exists('stream_socket_enable_crypto')) { 132 throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore 133 } 134 135 // default to empty passphrase to suppress blocking passphrase prompt 136 $context += array( 137 'passphrase' => '' 138 ); 139 140 $this->tcp = $tcp; 141 $this->encryption = new StreamEncryption($loop ?: Loop::get()); 142 $this->context = $context; 143 144 $that = $this; 145 $this->tcp->on('connection', function ($connection) use ($that) { 146 $that->handleConnection($connection); 147 }); 148 $this->tcp->on('error', function ($error) use ($that) { 149 $that->emit('error', array($error)); 150 }); 151 } 152 153 public function getAddress() 154 { 155 $address = $this->tcp->getAddress(); 156 if ($address === null) { 157 return null; 158 } 159 160 return \str_replace('tcp://' , 'tls://', $address); 161 } 162 163 public function pause() 164 { 165 $this->tcp->pause(); 166 } 167 168 public function resume() 169 { 170 $this->tcp->resume(); 171 } 172 173 public function close() 174 { 175 return $this->tcp->close(); 176 } 177 178 /** @internal */ 179 public function handleConnection(ConnectionInterface $connection) 180 { 181 if (!$connection instanceof Connection) { 182 $this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource'))); 183 $connection->close(); 184 return; 185 } 186 187 foreach ($this->context as $name => $value) { 188 \stream_context_set_option($connection->stream, 'ssl', $name, $value); 189 } 190 191 // get remote address before starting TLS handshake in case connection closes during handshake 192 $remote = $connection->getRemoteAddress(); 193 $that = $this; 194 195 $this->encryption->enable($connection)->then( 196 function ($conn) use ($that) { 197 $that->emit('connection', array($conn)); 198 }, 199 function ($error) use ($that, $connection, $remote) { 200 $error = new \RuntimeException( 201 'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(), 202 $error->getCode() 203 ); 204 205 $that->emit('error', array($error)); 206 $connection->close(); 207 } 208 ); 209 } 210}