Issues (1686)

sources/ElkArte/Server.php (10 issues)

1
<?php
2
3
/**
4
 * This file has the functions "describing" the server.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
namespace ElkArte;
18
19
/**
20
 * Class Server
21
 *
22
 * Wrapper around many common server functions and server information
23
 */
24
class Server extends \ArrayObject
25
{
26
	/** @var array */
27
	public $SERVER_SOFTWARE;
28
29
	/** @var array */
30
	public $SERVER_PORT;
31
32
	/**
33
	 * Server constructor.
34
	 *
35
	 * @param null|array $server
36
	 */
37
	public function __construct($server = null)
38
	{
39
		if (!is_array($server))
40 1
		{
41
			$server = $_SERVER ?? [];
42 1
		}
43
44
		parent::__construct($server, \ArrayObject::ARRAY_AS_PROPS);
45
	}
46
47 1
	/**
48 1
	 * Helper function to set the system memory to a needed value
49
	 *
50
	 * What it does:
51
	 *
52
	 * - If the needed memory is greater than current, will attempt to get more
53
	 * - If in_use is set to true, will also try to take the current memory usage in to account
54
	 *
55
	 * @param string $needed The amount of memory to request, if needed, like 256M
56
	 * @param bool $in_use Set to true to account for current memory usage of the script
57
	 *
58
	 * @return bool true if we have at least the needed memory
59
	 */
60
	public function setMemoryLimit($needed, $in_use = false)
61
	{
62
		// Everything in bytes
63 10
		$memory_current = memoryReturnBytes(ini_get('memory_limit'));
64
		$memory_needed = memoryReturnBytes($needed);
65
66 10
		// Should we account for how much is currently being used?
67 10
		if ($in_use)
68
		{
69
			$memory_needed += memory_get_usage();
70 10
		}
71
72
		// If more is needed, request it
73
		if ($memory_current < $memory_needed)
74
		{
75
			@ini_set('memory_limit', ceil($memory_needed / 1048576) . 'M');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

75
			/** @scrutinizer ignore-unhandled */ @ini_set('memory_limit', ceil($memory_needed / 1048576) . 'M');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
76 10
			$memory_current = memoryReturnBytes(ini_get('memory_limit'));
77
		}
78
79
		$memory_current = max($memory_current, memoryReturnBytes(get_cfg_var('memory_limit')));
0 ignored issues
show
It seems like get_cfg_var('memory_limit') can also be of type array; however, parameter $val of memoryReturnBytes() does only seem to accept boolean|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

79
		$memory_current = max($memory_current, memoryReturnBytes(/** @scrutinizer ignore-type */ get_cfg_var('memory_limit')));
Loading history...
80
81
		// Return success or not
82 10
		return $memory_current >= $memory_needed;
83
	}
84
85 10
	/**
86
	 * Wrapper function for set_time_limit
87
	 *
88
	 * When called, attempts to restart the timeout counter from zero.
89
	 *
90
	 * This sets the maximum time in seconds a script is allowed to run before it is terminated by the parser.
91
	 * You can not change this setting with ini_set() when running in safe mode.
92
	 * Your web server can have other timeout configurations that may also interrupt PHP execution.
93
	 * Apache has a Timeout directive and IIS has a CGI timeout function.
94
	 * Security extension may also disable this function, such as Suhosin
95
	 * Hosts may add this to the disabled_functions list in php.ini
96
	 *
97
	 * If the current time limit is not unlimited it is possible to decrease the
98
	 * total time limit if the sum of the new time limit and the current time spent
99
	 * running the script is inferior to the original time limit. It is inherent to
100
	 * the way set_time_limit() works, it should rather be called with an
101
	 * appropriate value every time you need to allocate a certain amount of time
102
	 * to execute a task than only once at the beginning of the script.
103
	 *
104
	 * Before calling set_time_limit(), we check if this function is available
105
	 *
106
	 * @param int $time_limit The time limit
107
	 * @param bool $server_reset whether to reset the server timer or not
108
	 *
109
	 * @return string
110
	 */
111
	public function setTimeLimit($time_limit, $server_reset = true)
112
	{
113
		// Make sure the function exists, it may be in the ini disable_functions list
114 21
		if (function_exists('set_time_limit'))
115
		{
116
			$current = (int) ini_get('max_execution_time');
117 21
118
			// Do not set a limit if it is currently unlimited.
119 21
			if ($current !== 0)
120
			{
121
				// Set it to the maximum that we can, not more, not less
122 21
				$time_limit = min($current, max($time_limit, $current));
123
124
				// Still need error suppression as some security addons many prevent this action
125
				@set_time_limit($time_limit);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

125
				/** @scrutinizer ignore-unhandled */ @set_time_limit($time_limit);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
126
			}
127
		}
128
129
		// Don't let apache close the connection
130
		if ($server_reset && function_exists('apache_reset_timeout'))
131
		{
132
			@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for apache_reset_timeout(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

132
			/** @scrutinizer ignore-unhandled */ @apache_reset_timeout();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
133 21
		}
134
135
		return ini_get('max_execution_time');
136
	}
137
138 21
	/**
139
	 * Checks the type of software the webserver is functioning under
140
	 *
141
	 * @param $server
142
	 *
143
	 * @return bool
144
	 */
145
	public function is($server)
146
	{
147
		return match ($server)
148 230
		{
149
			'apache' => $this->_is_web_server('Apache'),
150 116
			'cgi' => $this->SERVER_SOFTWARE !== null && strpos(PHP_SAPI, 'cgi') !== false,
151
			'iis' => $this->_is_web_server('Microsoft-IIS'),
152 230
			'lighttpd' => $this->_is_web_server('lighttpd'),
153 1
			'litespeed' => $this->_is_web_server('LiteSpeed'),
154 230
			'nginx' => $this->_is_web_server('nginx'),
155 1
			'windows' => strpos(PHP_OS_FAMILY, 'WIN') === 0,
156 230
			default => false,
157 1
		};
158 230
	}
159 229
160 6
	/**
161 1
	 * Search $_SERVER['SERVER_SOFTWARE'] for a give $type
162 6
	 *
163 1
	 * @param string $type
164 6
	 *
165 1
	 * @return bool
166 6
	 */
167 1
	private function _is_web_server($type)
168 6
	{
169 6
		return $this->SERVER_SOFTWARE !== null && strpos($this->SERVER_SOFTWARE, $type) !== false;
0 ignored issues
show
$this->SERVER_SOFTWARE of type array is incompatible with the type string expected by parameter $haystack of strpos(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
		return $this->SERVER_SOFTWARE !== null && strpos(/** @scrutinizer ignore-type */ $this->SERVER_SOFTWARE, $type) !== false;
Loading history...
170
	}
171
172
	/**
173
	 * Checks if the webserver supports rewrite
174
	 *
175
	 * @return bool
176
	 */
177
	public function supportRewrite()
178
	{
179
		return (!$this->is('cgi') || ini_get('cgi.fix_pathinfo') == 1 || @get_cfg_var('cgi.fix_pathinfo') == 1)
180
			&& ($this->is('apache') || $this->is('nginx') || $this->is('lighttpd') || $this->is('litespeed'));
181
	}
182 1
183
	/**
184 1
	 * Returns if the system supports output compression
185
	 *
186
	 * @return bool
187
	 */
188
	public function outPutCompressionEnabled()
189
	{
190
		return ini_get('zlib.output_compression') >= 1 || ini_get('output_handler') === 'ob_gzhandler';
191
	}
192
193
	/**
194
	 * Returns if the system supports / is using https connections
195
	 *
196
	 * @return bool
197
	 */
198
	public function supportsSSL()
199
	{
200
		return
201
			(isset($this->HTTPS) && ($this->HTTPS === 'on' || (int) $this->HTTPS === 1))
202
			|| (isset($this->REQUEST_SCHEME) && $this->REQUEST_SCHEME === 'https')
203
			|| (isset($this->SERVER_PORT) && (int) $this->SERVER_PORT === 443)
204
			|| (isset($this->HTTP_X_FORWARDED_PROTO) && $this->HTTP_X_FORWARDED_PROTO === 'https');
205
	}
206
207
	/**
208
	 * Get the host of the current request
209
	 *
210
	 * This method retrieves the host of the current request. It first attempts to get the host
211
	 * from the `HTTP_HOST` property of the object. If the `HTTP_HOST` property is empty,
212
	 * it falls back to using the `SERVER_NAME` property. If a port other than 80 or 443 is specified
213
	 * in the `SERVER_PORT` property, it appends the port number to the host.
214
	 *
215
	 * @return string The host of the current request
216
	 */
217
	public function getHost()
218
	{
219
		$host = $this->HTTP_HOST;
0 ignored issues
show
Bug Best Practice introduced by
The property HTTP_HOST does not exist on ElkArte\Server. Did you maybe forget to declare it?
Loading history...
220
		if (!$host)
221
		{
222
			$host = $this->SERVER_NAME;
0 ignored issues
show
The property SERVER_NAME does not exist on ElkArte\Server. Did you mean SERVER_PORT?
Loading history...
223
			$port = (int) $this->SERVER_PORT;
224
			if ($port && $port !== 80 && $port !== 443)
225
			{
226
				$host .= ':' . $port;
227
			}
228
		}
229
230
		return $host;
231
	}
232
233
	/**
234
	 * Try to determine a FQDN for the server
235
	 *
236
	 * Many SMTP servers *require* a fully qualified domain name in the
237
	 * HELO/EHLO command.  This function tries to determine the fully qualified domain name
238
	 * from the OS which often just returns the current host name, like bob, rather than a FQDN.
239
	 *
240
	 * From the rfc:
241
	 * The SMTP client MUST, if possible, ensure that the domain parameter to the EHLO
242
	 * command is a valid principal host name (not a CNAME or MX name) for its host. If
243
	 * this is not possible (e.g., when the client's address is dynamically assigned and
244
	 * the client does not have an obvious name), an address literal SHOULD be substituted
245
	 * for the domain name and supplemental information provided that will assist in
246
	 * identifying the client.
247
	 *
248
	 * @param string $fallback the fallback to use when we fail
249
	 * @return string a FQDN
250
	 */
251
	public function getFQDN($fallback = '[127.0.0.1]')
252
	{
253
		// Try gethostname
254
		if (function_exists('gethostname') && $this->_isValidFQDN(gethostname()))
255
		{
256
			return gethostname();
257
		}
258
259
		// Failing, try php_uname
260
		if (function_exists('php_uname') && $this->_isValidFQDN(php_uname('n')))
261
		{
262
			return php_uname('n');
263
		}
264
265
		// This is likely a sitename vs host
266
		if (!empty($this->SERVER_NAME) && $this->_isValidFQDN($this->SERVER_NAME))
0 ignored issues
show
The property SERVER_NAME does not exist on ElkArte\Server. Did you mean SERVER_PORT?
Loading history...
267
		{
268
			return $this->SERVER_NAME;
269
		}
270
271
		// Try a reverse IP lookup on the server addr
272
		$reverseIP = host_from_ip($this->SERVER_ADDR);
0 ignored issues
show
The property SERVER_ADDR does not exist on ElkArte\Server. Did you mean SERVER_PORT?
Loading history...
273
		if (!empty($this->SERVER_ADDR) && $this->_isValidFQDN($reverseIP))
274
		{
275
			return $reverseIP;
276
		}
277
278
		// Literal it is, but some SMTP servers may not accept this
279
		if (!empty($this->SERVER_ADDR) && $fallback === '[127.0.0.1]')
280
		{
281
			// Set the address literal prefix
282
			$prefix = strpos($this->SERVER_ADDR, ':') !== false ? 'IPv6:' : '';
283
284
			return '[' . $prefix . $this->SERVER_ADDR . ']';
285
		}
286
287
		return $fallback;
288
	}
289
290
	/**
291
	 * Checks if this is a valid FQDN first by basic syntax and then if it has domain records
292
	 *
293
	 * @param string $hostname
294
	 * @return bool
295
	 */
296
	private function _isValidFQDN($hostname)
297
	{
298
		if (empty($hostname) || strpos($hostname, '.') === false)
299
		{
300
			return false;
301
		}
302
303
		if (preg_match('~^(?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$~', $hostname) === 1)
304
		{
305
			// Check for ANY dns records for this name, for simplicity, although we really want A / AAAA
306
			return checkdnsrr($hostname, 'ANY');
307
		}
308
309
		return false;
310
	}
311
312
	/**
313
	 * Determine what HTTP protocol the server is using, if unknown default to HTTP/1.0
314
	 *
315
	 * @return string
316
	 */
317
	public function getProtocol()
318
	{
319
		if (empty($this->SERVER_PROTOCOL))
0 ignored issues
show
The property SERVER_PROTOCOL does not exist on ElkArte\Server. Did you mean SERVER_PORT?
Loading history...
320
		{
321
			return 'HTTP/1.0';
322
		}
323
324
		// HTTP/1.0, HTTP/1.1, HTTP/2, HTTP/2.0 etc
325
		if (preg_match('~^\s*(HTTP\/[123](\.\d)?)\s*$~i', $this->SERVER_PROTOCOL, $matches) === 1)
326
		{
327
			return $matches[1];
328
		}
329
330
		return 'HTTP/1.0';
331
	}
332
}
333