Merge branch '5.2' into 5.3
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / io / RemoteFile.class.php
1 <?php
2 namespace wcf\system\io;
3 use wcf\system\exception\SystemException;
4
5 /**
6 * The RemoteFile class opens a connection to a remote host as a file.
7 *
8 * @author Marcel Werk
9 * @copyright 2001-2019 WoltLab GmbH
10 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
11 * @package WoltLabSuite\Core\System\Io
12 */
13 class RemoteFile extends File {
14 /**
15 * host address
16 * @var string
17 */
18 protected $host = '';
19
20 /**
21 * port
22 * @var integer
23 */
24 protected $port = 0;
25
26 /**
27 * error number
28 * @var integer
29 */
30 protected $errorNumber = 0;
31
32 /**
33 * error description
34 * @var string
35 */
36 protected $errorDesc = '';
37
38 /**
39 * true if PHP supports SSL/TLS
40 * @var boolean
41 */
42 private static $hasSSLSupport = null;
43
44 /** @noinspection PhpMissingParentConstructorInspection */
45 /**
46 * Opens a new connection to a remote host.
47 *
48 * @param string $host
49 * @param integer $port
50 * @param integer $timeout
51 * @param array $options
52 * @throws SystemException
53 */
54 public function __construct($host, $port, $timeout = 30, $options = []) {
55 $this->host = $host;
56 $this->port = $port;
57
58 if (!preg_match('/^[a-z0-9]+:/', $this->host)) $this->host = 'tcp://'.$this->host;
59
60 $context = stream_context_create($options);
61 try {
62 $this->resource = stream_socket_client($this->host.':'.$this->port, $this->errorNumber, $this->errorDesc, $timeout, STREAM_CLIENT_CONNECT, $context);
63 if ($this->resource === false) {
64 throw new \Exception('stream_socket_client returned false: ' . $this->errorDesc, $this->errorNumber);
65 }
66 }
67 catch (\Exception $e) {
68 throw new SystemException('Can not connect to ' . $host, 0, $this->errorDesc, $e);
69 }
70
71 stream_set_timeout($this->resource, $timeout);
72 }
73
74 /**
75 * Returns the error number of the last error.
76 *
77 * @return integer
78 */
79 public function getErrorNumber() {
80 return $this->errorNumber;
81 }
82
83 /**
84 * Returns the error description of the last error.
85 *
86 * @return string
87 */
88 public function getErrorDesc() {
89 return $this->errorDesc;
90 }
91
92 /**
93 * Switches TLS support for this connection.
94 * Usually used in combination with 'STARTTLS'
95 *
96 * @param boolean $enable Whether TLS support should be enabled
97 * @return boolean True on success, false otherwise
98 */
99 public function setTLS($enable) {
100 if (!$this->hasTLSSupport()) return false;
101
102 $cryptoType = STREAM_CRYPTO_METHOD_TLS_CLIENT;
103
104 // PHP 5.6.8+ defines STREAM_CRYPTO_METHOD_TLS_CLIENT as STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT for BC reasons.
105 // STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT was introduced in PHP 5.6.8, but is not exposed to userland. Try to use
106 // it for forward compatibility.
107 //
108 // As of PHP 7.2+ STREAM_CRYPTO_METHOD_TLS_CLIENT is equivalent to STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT.
109 // see: https://wiki.php.net/rfc/improved-tls-constants
110 // see: https://github.com/php/php-src/blob/197cac65fdf712effb19ad3e40688ceb7ebc7f7d/main/streams/php_stream_transport.h#L173-L175
111 if (defined('STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT;
112
113 // Add bits for all known TLS versions for the reasons above.
114 if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
115 if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
116 if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
117 if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
118
119 return stream_socket_enable_crypto($this->resource, $enable, $cryptoType);
120 }
121
122 /**
123 * Returns whether TLS support is available.
124 *
125 * @return boolean
126 */
127 public function hasTLSSupport() {
128 return function_exists('stream_socket_enable_crypto');
129 }
130
131 /**
132 * Returns true if PHP supports SSL/TLS.
133 *
134 * @return boolean
135 */
136 public static function supportsSSL() {
137 if (static::$hasSSLSupport === null) {
138 static::$hasSSLSupport = false;
139
140 $transports = stream_get_transports();
141 foreach ($transports as $transport) {
142 if (preg_match('~^(ssl(v[23])?|tls(v[0-9\.]+)?)$~', $transport)) {
143 static::$hasSSLSupport = true;
144 break;
145 }
146 }
147 }
148
149 return static::$hasSSLSupport;
150 }
151
152 /**
153 * Disables SSL/TLS support on runtime regardless if PHP is theoretically capable of it.
154 */
155 public static function disableSSL() {
156 static::$hasSSLSupport = false;
157 }
158 }