friendship ended with social-app. php is my new best friend
1<?php 2 3namespace React\Socket; 4 5use React\EventLoop\LoopInterface; 6use React\Promise\Deferred; 7use RuntimeException; 8use UnexpectedValueException; 9 10/** 11 * This class is considered internal and its API should not be relied upon 12 * outside of Socket. 13 * 14 * @internal 15 */ 16class StreamEncryption 17{ 18 private $loop; 19 private $method; 20 private $server; 21 22 public function __construct(LoopInterface $loop, $server = true) 23 { 24 $this->loop = $loop; 25 $this->server = $server; 26 27 // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3. 28 // As of PHP 7.2+ the main crypto method constant includes all TLS versions. 29 // As of PHP 5.6+ the crypto method is a bitmask, so we explicitly include all TLS versions. 30 // For legacy PHP < 5.6 the crypto method is a single value only and this constant includes all TLS versions. 31 // @link https://3v4l.org/9PSST 32 if ($server) { 33 $this->method = \STREAM_CRYPTO_METHOD_TLS_SERVER; 34 35 if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) { 36 $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; // @codeCoverageIgnore 37 } 38 } else { 39 $this->method = \STREAM_CRYPTO_METHOD_TLS_CLIENT; 40 41 if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) { 42 $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; // @codeCoverageIgnore 43 } 44 } 45 } 46 47 /** 48 * @param Connection $stream 49 * @return \React\Promise\PromiseInterface<Connection> 50 */ 51 public function enable(Connection $stream) 52 { 53 return $this->toggle($stream, true); 54 } 55 56 /** 57 * @param Connection $stream 58 * @param bool $toggle 59 * @return \React\Promise\PromiseInterface<Connection> 60 */ 61 public function toggle(Connection $stream, $toggle) 62 { 63 // pause actual stream instance to continue operation on raw stream socket 64 $stream->pause(); 65 66 // TODO: add write() event to make sure we're not sending any excessive data 67 68 // cancelling this leaves this stream in an inconsistent state… 69 $deferred = new Deferred(function () { 70 throw new \RuntimeException(); 71 }); 72 73 // get actual stream socket from stream instance 74 $socket = $stream->stream; 75 76 // get crypto method from context options or use global setting from constructor 77 $method = $this->method; 78 $context = \stream_context_get_options($socket); 79 if (isset($context['ssl']['crypto_method'])) { 80 $method = $context['ssl']['crypto_method']; 81 } 82 83 $that = $this; 84 $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) { 85 $that->toggleCrypto($socket, $deferred, $toggle, $method); 86 }; 87 88 $this->loop->addReadStream($socket, $toggleCrypto); 89 90 if (!$this->server) { 91 $toggleCrypto(); 92 } 93 94 $loop = $this->loop; 95 96 return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) { 97 $loop->removeReadStream($socket); 98 99 $stream->encryptionEnabled = $toggle; 100 $stream->resume(); 101 102 return $stream; 103 }, function($error) use ($stream, $socket, $loop) { 104 $loop->removeReadStream($socket); 105 $stream->resume(); 106 throw $error; 107 }); 108 } 109 110 /** 111 * @internal 112 * @param resource $socket 113 * @param Deferred<null> $deferred 114 * @param bool $toggle 115 * @param int $method 116 * @return void 117 */ 118 public function toggleCrypto($socket, Deferred $deferred, $toggle, $method) 119 { 120 $error = null; 121 \set_error_handler(function ($_, $errstr) use (&$error) { 122 $error = \str_replace(array("\r", "\n"), ' ', $errstr); 123 124 // remove useless function name from error message 125 if (($pos = \strpos($error, "): ")) !== false) { 126 $error = \substr($error, $pos + 3); 127 } 128 }); 129 130 $result = \stream_socket_enable_crypto($socket, $toggle, $method); 131 132 \restore_error_handler(); 133 134 if (true === $result) { 135 $deferred->resolve(null); 136 } else if (false === $result) { 137 // overwrite callback arguments for PHP7+ only, so they do not show 138 // up in the Exception trace and do not cause a possible cyclic reference. 139 $d = $deferred; 140 $deferred = null; 141 142 if (\feof($socket) || $error === null) { 143 // EOF or failed without error => connection closed during handshake 144 $d->reject(new \UnexpectedValueException( 145 'Connection lost during TLS handshake (ECONNRESET)', 146 \defined('SOCKET_ECONNRESET') ? \SOCKET_ECONNRESET : 104 147 )); 148 } else { 149 // handshake failed with error message 150 $d->reject(new \UnexpectedValueException( 151 $error 152 )); 153 } 154 } else { 155 // need more data, will retry 156 } 157 } 158}