1: <?php
2: /*
3: You may not change or alter any portion of this comment or credits
4: of supporting developers from this source code or any supporting source code
5: which is considered copyrighted (c) material of the original comment or credit authors.
6:
7: This program is distributed in the hope that it will be useful,
8: but WITHOUT ANY WARRANTY; without even the implied warranty of
9: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12: namespace Xmf;
13:
14: /**
15: * ProxyCheck
16: *
17: * @category Xmf\ProxyCheck
18: * @package Xmf
19: * @author Richard Griffith <richard@geekwright.com>
20: * @copyright 2019-2020 XOOPS Project (https://xoops.org)
21: * @license GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html)
22: */
23: class ProxyCheck
24: {
25: const PROXY_ENVIRONMENT_VARIABLE = 'proxy_env';
26:
27: const FORWARDED = 'HTTP_FORWARDED';
28:
29: /** @var string|false header name determines how to process */
30: protected $proxyHeaderName = false;
31:
32: /** @var string|false header data to process */
33: protected $proxyHeader = false;
34:
35: /**
36: * ProxyCheck constructor.
37: */
38: public function __construct()
39: {
40: /* must declare expected proxy in $xoopsConfig['proxy_env'] */
41: $this->proxyHeaderName = $this->getProxyEnvConfig();
42: $this->proxyHeader = $this->getProxyHeader();
43: }
44:
45: /**
46: * Get IP address from proxy header specified in $xoopsConfig['proxy_env']
47: *
48: * Returns proxy revealed valid client address, or false if such address was
49: * not found.
50: *
51: * @return string|false
52: */
53: public function get()
54: {
55: if (false===$this->proxyHeaderName || false===$this->proxyHeader) {
56: return false;
57: }
58: $proxyVars = $this->splitOnComma($this->proxyHeader);
59: // only consider the first (left most) value
60: $header = reset($proxyVars);
61: $ip = false;
62: switch ($this->proxyHeaderName) {
63: case static::FORWARDED:
64: $ip = $this->getFor($header);
65: break;
66: default:
67: $ip = $this->getXForwardedFor($header);
68: break;
69: }
70:
71: return $ip;
72: }
73:
74: /**
75: * Split comma delimited string
76: *
77: * @param string $header
78: *
79: * @return string[]
80: */
81: protected function splitOnComma($header)
82: {
83: $parts = explode(',', $header);
84: return array_map('trim', $parts);
85: }
86:
87: /**
88: * get configured proxy environment variable
89: *
90: * @return string|bool
91: */
92: protected function getProxyEnvConfig()
93: {
94: global $xoopsConfig;
95:
96: /* must declare expected proxy in $xoopsConfig['proxy_env'] */
97: if (!isset($xoopsConfig[static::PROXY_ENVIRONMENT_VARIABLE])
98: || empty($xoopsConfig[static::PROXY_ENVIRONMENT_VARIABLE])) {
99: return false;
100: }
101: return trim($xoopsConfig[static::PROXY_ENVIRONMENT_VARIABLE]);
102: }
103:
104: /**
105: * get the configured proxy header
106: *
107: * @return string|false
108: */
109: protected function getProxyHeader()
110: {
111: if (false === $this->proxyHeaderName || empty($_SERVER[$this->proxyHeaderName])) {
112: return false;
113: }
114:
115: // Use PHP 5.3 compatible type casting
116: return (string)$_SERVER[$this->proxyHeaderName];
117: }
118:
119: /**
120: * Extract 'for' IP address in FORWARDED header as in RFC 7239
121: *
122: * @param string $header
123: *
124: * @return string|false IP address, or false if invalid
125: */
126: protected function getFor($header)
127: {
128: $start = strpos($header, 'for=');
129: if ($start === false) {
130: return false;
131: }
132: $ip = substr($header, $start+4);
133: $end = strpos($ip, ';');
134: if ($end !== false) {
135: $ip = substr($ip, 0, $end);
136: }
137: $ip = trim($ip, '"[] ');
138:
139: return $this->validateRoutableIP($ip);
140: }
141:
142: /**
143: * Process an X-Forwarded-For or Client-IP style header
144: *
145: * @param string $ip expected to be an IP address
146: *
147: * @return string|false IP address, or false if invalid
148: */
149: protected function getXForwardedFor($ip)
150: {
151: return $this->validateRoutableIP($ip);
152: }
153:
154: /**
155: * Validate that an IP address is routable
156: *
157: * @param string $ip an IP address to validate
158: *
159: * @return string|false IP address or false if invalid
160: */
161: protected function validateRoutableIP($ip)
162: {
163: if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
164: return false;
165: }
166: return $ip;
167: }
168: }
169: