1<?php 2 3namespace React\Socket; 4 5use Evenement\EventEmitter; 6use React\EventLoop\LoopInterface; 7use React\Stream\DuplexResourceStream; 8use React\Stream\Util; 9use React\Stream\WritableResourceStream; 10use React\Stream\WritableStreamInterface; 11 12/** 13 * The actual connection implementation for ConnectionInterface 14 * 15 * This class should only be used internally, see ConnectionInterface instead. 16 * 17 * @see ConnectionInterface 18 * @internal 19 */ 20class Connection extends EventEmitter implements ConnectionInterface 21{ 22 /** 23 * Internal flag whether this is a Unix domain socket (UDS) connection 24 * 25 * @internal 26 */ 27 public $unix = false; 28 29 /** 30 * Internal flag whether encryption has been enabled on this connection 31 * 32 * Mostly used by internal StreamEncryption so that connection returns 33 * `tls://` scheme for encrypted connections instead of `tcp://`. 34 * 35 * @internal 36 */ 37 public $encryptionEnabled = false; 38 39 /** @internal */ 40 public $stream; 41 42 private $input; 43 44 public function __construct($resource, LoopInterface $loop) 45 { 46 // PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() might 47 // block with 100% CPU usage on fragmented TLS records. 48 // We try to work around this by always consuming the complete receive 49 // buffer at once to avoid stale data in TLS buffers. This is known to 50 // work around high CPU usage for well-behaving peers, but this may 51 // cause very large data chunks for high throughput scenarios. The buggy 52 // behavior can still be triggered due to network I/O buffers or 53 // malicious peers on affected versions, upgrading is highly recommended. 54 // @link https://bugs.php.net/bug.php?id=77390 55 $clearCompleteBuffer = \PHP_VERSION_ID < 70215 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70303); 56 57 // PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big 58 // chunks of data over TLS streams at once. 59 // We try to work around this by limiting the write chunk size to 8192 60 // bytes for older PHP versions only. 61 // This is only a work-around and has a noticable performance penalty on 62 // affected versions. Please update your PHP version. 63 // This applies to all streams because TLS may be enabled later on. 64 // See https://github.com/reactphp/socket/issues/105 65 $limitWriteChunks = (\PHP_VERSION_ID < 70018 || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70104)); 66 67 $this->input = new DuplexResourceStream( 68 $resource, 69 $loop, 70 $clearCompleteBuffer ? -1 : null, 71 new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null) 72 ); 73 74 $this->stream = $resource; 75 76 Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain')); 77 78 $this->input->on('close', array($this, 'close')); 79 } 80 81 public function isReadable() 82 { 83 return $this->input->isReadable(); 84 } 85 86 public function isWritable() 87 { 88 return $this->input->isWritable(); 89 } 90 91 public function pause() 92 { 93 $this->input->pause(); 94 } 95 96 public function resume() 97 { 98 $this->input->resume(); 99 } 100 101 public function pipe(WritableStreamInterface $dest, array $options = array()) 102 { 103 return $this->input->pipe($dest, $options); 104 } 105 106 public function write($data) 107 { 108 return $this->input->write($data); 109 } 110 111 public function end($data = null) 112 { 113 $this->input->end($data); 114 } 115 116 public function close() 117 { 118 $this->input->close(); 119 $this->handleClose(); 120 $this->removeAllListeners(); 121 } 122 123 public function handleClose() 124 { 125 if (!\is_resource($this->stream)) { 126 return; 127 } 128 129 // Try to cleanly shut down socket and ignore any errors in case other 130 // side already closed. Shutting down may return to blocking mode on 131 // some legacy versions, so reset to non-blocking just in case before 132 // continuing to close the socket resource. 133 // Underlying Stream implementation will take care of closing file 134 // handle, so we otherwise keep this open here. 135 @\stream_socket_shutdown($this->stream, \STREAM_SHUT_RDWR); 136 \stream_set_blocking($this->stream, false); 137 } 138 139 public function getRemoteAddress() 140 { 141 if (!\is_resource($this->stream)) { 142 return null; 143 } 144 145 return $this->parseAddress(\stream_socket_get_name($this->stream, true)); 146 } 147 148 public function getLocalAddress() 149 { 150 if (!\is_resource($this->stream)) { 151 return null; 152 } 153 154 return $this->parseAddress(\stream_socket_get_name($this->stream, false)); 155 } 156 157 private function parseAddress($address) 158 { 159 if ($address === false) { 160 return null; 161 } 162 163 if ($this->unix) { 164 // remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo 165 // note that technically ":" is a valid address, so keep this in place otherwise 166 if (\substr($address, -1) === ':' && \defined('HHVM_VERSION_ID') && \HHVM_VERSION_ID < 31900) { 167 $address = (string)\substr($address, 0, -1); // @codeCoverageIgnore 168 } 169 170 // work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556 171 // PHP uses "\0" string and HHVM uses empty string (colon removed above) 172 if ($address === '' || $address[0] === "\x00" ) { 173 return null; // @codeCoverageIgnore 174 } 175 176 return 'unix://' . $address; 177 } 178 179 // check if this is an IPv6 address which includes multiple colons but no square brackets 180 $pos = \strrpos($address, ':'); 181 if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') { 182 $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore 183 } 184 185 return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address; 186 } 187} 188