3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\App;
24 use Friendica\Core\Config\Capability\IManageConfigValues;
25 use Friendica\Core\System;
28 * Container for the whole request
30 * @see https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface
32 * @todo future container class for whole requests, currently it's not :-)
37 * A comma separated list of default headers that could contain the client IP in a proxy request
41 const DEFAULT_FORWARD_FOR_HEADER = 'HTTP_X_FORWARDED_FOR';
43 * The default Request-ID header to retrieve the current transaction ID from the HTTP header (if set)
47 const DEFAULT_REQUEST_ID_HEADER = 'HTTP_X_REQUEST_ID';
49 /** @var string The remote IP address of the current request */
50 protected $remoteAddress;
51 /** @var string The request-id of the current request */
55 * @return string The remote IP address of the current request
57 * Do always use this instead of $_SERVER['REMOTE_ADDR']
59 public function getRemoteAddress(): string
61 return $this->remoteAddress;
65 * @return string The request ID of the current request
67 * Do always use this instead of $_SERVER['X_REQUEST_ID']
69 public function getRequestId(): string
71 return $this->requestId;
74 public function __construct(IManageConfigValues $config, array $server = [])
76 $this->remoteAddress = $this->determineRemoteAddress($config, $server);
77 $this->requestId = $server[static::DEFAULT_REQUEST_ID_HEADER] ?? System::createGUID(8, false);
81 * Checks if given $remoteAddress matches given $trustedProxy.
82 * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if
83 * $remoteAddress is an IPv4 address within that IP range.
84 * Otherwise, $remoteAddress will be compared to $trustedProxy literally and the result
87 * @param string $trustedProxy The current, trusted proxy to check
88 * @param string $remoteAddress The current remote IP address
91 * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise
93 protected function matchesTrustedProxy(string $trustedProxy, string $remoteAddress): bool
95 $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/';
97 if (preg_match($cidrre, $trustedProxy, $match)) {
99 $shiftbits = min(32, max(0, 32 - intval($match[2])));
100 $netnum = ip2long($net) >> $shiftbits;
101 $ipnum = ip2long($remoteAddress) >> $shiftbits;
103 return $ipnum === $netnum;
106 return $trustedProxy === $remoteAddress;
110 * Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
111 * For details regarding what "match" means, refer to `matchesTrustedProxy`.
113 * @param string[] $trustedProxies A list of the trusted proxies
114 * @param string $remoteAddress The current remote IP address
116 * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
118 protected function isTrustedProxy(array $trustedProxies, string $remoteAddress): bool
120 foreach ($trustedProxies as $tp) {
121 if ($this->matchesTrustedProxy($tp, $remoteAddress)) {
130 * Determines the remote address, if the connection came from a trusted proxy
131 * and `forwarded_for_headers` has been configured then the IP address
132 * specified in this header will be returned instead.
134 * @param IManageConfigValues $config
135 * @param array $server The $_SERVER array
139 protected function determineRemoteAddress(IManageConfigValues $config, array $server): string
141 $remoteAddress = $server['REMOTE_ADDR'] ?? '0.0.0.0';
142 $trustedProxies = preg_split('/(\s*,*\s*)*,+(\s*,*\s*)*/', $config->get('proxy', 'trusted_proxies', ''));
144 if (\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) {
145 $forwardedForHeaders = preg_split('/(\s*,*\s*)*,+(\s*,*\s*)*/', $config->get('proxy', 'forwarded_for_headers', static::DEFAULT_FORWARD_FOR_HEADER));
147 foreach ($forwardedForHeaders as $header) {
148 if (isset($server[$header])) {
149 foreach (explode(',', $server[$header]) as $IP) {
152 // remove brackets from IPv6 addresses
153 if (strpos($IP, '[') === 0 && substr($IP, -1) === ']') {
154 $IP = substr($IP, 1, -1);
157 // skip trusted proxies in the list itself
158 if ($this->isTrustedProxy($trustedProxies, $IP)) {
162 if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
170 return $remoteAddress;