|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* @package CleverStyle CMS |
|
4
|
|
|
* @author Nazar Mokrynskyi <[email protected]> |
|
5
|
|
|
* @copyright Copyright (c) 2016, Nazar Mokrynskyi |
|
6
|
|
|
* @license MIT License, see license.txt |
|
7
|
|
|
*/ |
|
8
|
|
|
namespace cs\Request; |
|
9
|
|
|
use |
|
10
|
|
|
cs\ExitException; |
|
11
|
|
|
|
|
12
|
|
|
trait Server { |
|
13
|
|
|
/** |
|
14
|
|
|
* Language accepted by client, `''` by default |
|
15
|
|
|
* |
|
16
|
|
|
* @var string |
|
17
|
|
|
*/ |
|
18
|
|
|
public $language; |
|
19
|
|
|
/** |
|
20
|
|
|
* Version accepted by client, will match `/^[0-9\.]+$/`, useful for API, `1` by default |
|
21
|
|
|
* |
|
22
|
|
|
* @var string |
|
23
|
|
|
*/ |
|
24
|
|
|
public $version; |
|
25
|
|
|
/** |
|
26
|
|
|
* Content type, `''` by default |
|
27
|
|
|
* |
|
28
|
|
|
* @var string |
|
29
|
|
|
*/ |
|
30
|
|
|
public $content_type; |
|
31
|
|
|
/** |
|
32
|
|
|
* Do not track |
|
33
|
|
|
* |
|
34
|
|
|
* @var bool |
|
35
|
|
|
*/ |
|
36
|
|
|
public $dnt; |
|
37
|
|
|
/** |
|
38
|
|
|
* The best guessed host |
|
39
|
|
|
* |
|
40
|
|
|
* @var string |
|
41
|
|
|
*/ |
|
42
|
|
|
public $host; |
|
43
|
|
|
/** |
|
44
|
|
|
* The best guessed IP of client (based on all known headers), `$this->remote_addr` by default |
|
45
|
|
|
* |
|
46
|
|
|
* @var string |
|
47
|
|
|
*/ |
|
48
|
|
|
public $ip; |
|
49
|
|
|
/** |
|
50
|
|
|
* Schema `http` or `https` |
|
51
|
|
|
* |
|
52
|
|
|
* @var string |
|
53
|
|
|
*/ |
|
54
|
|
|
public $schema; |
|
55
|
|
|
/** |
|
56
|
|
|
* Protocol, for instance: `http/1.0`, `http/1.1` (default) |
|
57
|
|
|
* |
|
58
|
|
|
* @var string |
|
59
|
|
|
*/ |
|
60
|
|
|
public $protocol; |
|
61
|
|
|
/** |
|
62
|
|
|
* Query string |
|
63
|
|
|
* |
|
64
|
|
|
* @var string |
|
65
|
|
|
*/ |
|
66
|
|
|
public $query_string; |
|
67
|
|
|
/** |
|
68
|
|
|
* HTTP referer, `''` by default |
|
69
|
|
|
* |
|
70
|
|
|
* @var string |
|
71
|
|
|
*/ |
|
72
|
|
|
public $referer; |
|
73
|
|
|
/** |
|
74
|
|
|
* Where request came from, not necessary real IP of client |
|
75
|
|
|
* |
|
76
|
|
|
* @var string |
|
77
|
|
|
*/ |
|
78
|
|
|
public $remote_addr; |
|
79
|
|
|
/** |
|
80
|
|
|
* Path |
|
81
|
|
|
* |
|
82
|
|
|
* @var string |
|
83
|
|
|
*/ |
|
84
|
|
|
public $path; |
|
85
|
|
|
/** |
|
86
|
|
|
* Uppercase method, GET by default |
|
87
|
|
|
* |
|
88
|
|
|
* @var string |
|
89
|
|
|
*/ |
|
90
|
|
|
public $method; |
|
91
|
|
|
/** |
|
92
|
|
|
* Is requested with HTTPS |
|
93
|
|
|
* |
|
94
|
|
|
* @var bool |
|
95
|
|
|
*/ |
|
96
|
|
|
public $secure; |
|
97
|
|
|
/** |
|
98
|
|
|
* User agent, `127.0.0.1` by default |
|
99
|
|
|
* |
|
100
|
|
|
* @var string |
|
101
|
|
|
*/ |
|
102
|
|
|
public $user_agent; |
|
103
|
|
|
/** |
|
104
|
|
|
* Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`, `content-type`, `accept-language` |
|
105
|
|
|
* |
|
106
|
|
|
* @var string[] |
|
107
|
|
|
*/ |
|
108
|
|
|
public $headers; |
|
109
|
|
|
/** |
|
110
|
|
|
* @param string[] $server Typically `$_SERVER` |
|
111
|
|
|
* |
|
112
|
|
|
* @throws ExitException |
|
113
|
|
|
*/ |
|
114
|
|
|
function init_server ($server = []) { |
|
115
|
|
|
$this->fill_headers($server); |
|
116
|
|
|
/** |
|
117
|
|
|
* Add some defaults to avoid isset() hell afterwards |
|
118
|
|
|
*/ |
|
119
|
|
|
$server += [ |
|
120
|
|
|
'HTTP_ACCEPT_LANGUAGE' => '', |
|
121
|
|
|
'HTTP_ACCEPT_VERSION' => '1', |
|
122
|
|
|
'CONTENT_TYPE' => '', |
|
123
|
|
|
'HTTP_DNT' => '0', |
|
124
|
|
|
'QUERY_STRING' => '', |
|
125
|
|
|
'HTTP_REFERER' => '', |
|
126
|
|
|
'REMOTE_ADDR' => '127.0.0.1', |
|
127
|
|
|
'REQUEST_URI' => '', |
|
128
|
|
|
'REQUEST_METHOD' => 'GET', |
|
129
|
|
|
'HTTP_USER_AGENT' => '', |
|
130
|
|
|
'SERVER_PROTOCOL' => 'http/1.1' |
|
131
|
|
|
]; |
|
132
|
|
|
$this->fill_server_properties($server); |
|
133
|
|
|
} |
|
134
|
|
|
/** |
|
135
|
|
|
* @param string[] $server |
|
136
|
|
|
* |
|
137
|
|
|
* @throws ExitException |
|
138
|
|
|
*/ |
|
139
|
|
|
protected function fill_server_properties ($server) { |
|
140
|
|
|
$this->language = $server['HTTP_ACCEPT_LANGUAGE']; |
|
141
|
|
|
$this->version = preg_match('/^[0-9\.]+$/', $server['HTTP_ACCEPT_VERSION']) ? $server['HTTP_ACCEPT_VERSION'] : 1; |
|
|
|
|
|
|
142
|
|
|
$this->content_type = $server['CONTENT_TYPE']; |
|
143
|
|
|
$this->dnt = $server['HTTP_DNT'] == 1; |
|
144
|
|
|
$this->secure = $this->secure($server); |
|
145
|
|
|
$this->schema = $this->secure ? 'https' : 'http'; |
|
146
|
|
|
$this->protocol = strtolower($server['SERVER_PROTOCOL']); |
|
147
|
|
|
$this->host = $this->host($server); |
|
148
|
|
|
$this->ip = $this->ip($_SERVER); |
|
149
|
|
|
$this->query_string = $server['QUERY_STRING']; |
|
150
|
|
|
$this->referer = filter_var($server['HTTP_REFERER'], FILTER_VALIDATE_URL) ? $server['HTTP_REFERER'] : ''; |
|
151
|
|
|
$this->remote_addr = $server['REMOTE_ADDR']; |
|
152
|
|
|
$this->path = explode('?', $server['REQUEST_URI'], 2)[0]; |
|
153
|
|
|
$this->method = strtoupper($server['REQUEST_METHOD']); |
|
154
|
|
|
$this->user_agent = $server['HTTP_USER_AGENT']; |
|
155
|
|
|
} |
|
156
|
|
|
/** |
|
157
|
|
|
* @param string[] $server |
|
158
|
|
|
*/ |
|
159
|
|
|
protected function fill_headers ($server) { |
|
160
|
|
|
$headers = []; |
|
161
|
|
|
foreach ($server as $header => $header_content) { |
|
162
|
|
|
if (strpos($header, 'HTTP_') === 0) { |
|
163
|
|
|
$header = substr($header, 5); |
|
164
|
|
|
} elseif (strpos($header, 'CONTENT_') !== 0) { |
|
165
|
|
|
continue; |
|
166
|
|
|
} |
|
167
|
|
|
$header = strtolower(str_replace('_', '-', $header)); |
|
168
|
|
|
$headers[$header] = $header_content; |
|
169
|
|
|
} |
|
170
|
|
|
$this->headers = $headers; |
|
171
|
|
|
} |
|
172
|
|
|
/** |
|
173
|
|
|
* The best guessed IP of client (based on all known headers), `$this->remote_addr` by default |
|
174
|
|
|
* |
|
175
|
|
|
* @param string[] $server |
|
176
|
|
|
* |
|
177
|
|
|
* @return string |
|
178
|
|
|
*/ |
|
179
|
|
|
protected function ip ($server) { |
|
180
|
|
|
$all_possible_keys = [ |
|
181
|
|
|
'HTTP_X_FORWARDED_FOR', |
|
182
|
|
|
'HTTP_CLIENT_IP', |
|
183
|
|
|
'HTTP_X_FORWARDED', |
|
184
|
|
|
'HTTP_X_CLUSTER_CLIENT_IP', |
|
185
|
|
|
'HTTP_FORWARDED_FOR', |
|
186
|
|
|
'HTTP_FORWARDED' |
|
187
|
|
|
]; |
|
188
|
|
|
foreach ($all_possible_keys as $key) { |
|
189
|
|
|
if (isset($server[$key])) { |
|
190
|
|
|
$ip = trim(explode(',', $server[$key])[0]); |
|
191
|
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { |
|
192
|
|
|
return $ip; |
|
193
|
|
|
} |
|
194
|
|
|
} |
|
195
|
|
|
} |
|
196
|
|
|
return isset($server['REMOTE_ADDR']) ? '127.0.0.1' : ''; |
|
197
|
|
|
} |
|
198
|
|
|
/** |
|
199
|
|
|
* The best guessed host |
|
200
|
|
|
* |
|
201
|
|
|
* @param string[] $server |
|
202
|
|
|
* |
|
203
|
|
|
* @throws ExitException |
|
204
|
|
|
* |
|
205
|
|
|
* @return string |
|
206
|
|
|
*/ |
|
207
|
|
|
protected function host ($server) { |
|
208
|
|
|
$host = isset($server['SERVER_NAME']) ? $server['SERVER_NAME'] : ''; |
|
209
|
|
|
$port = ''; |
|
210
|
|
|
$expected_port = $this->secure ? 443 : 80; |
|
211
|
|
|
if (!$host && isset($server['HTTP_X_FORWARDED_HOST'])) { |
|
212
|
|
|
$host = $server['HTTP_X_FORWARDED_HOST']; |
|
213
|
|
|
if ( |
|
214
|
|
|
isset($server['HTTP_X_FORWARDED_PORT']) && |
|
215
|
|
|
$server['HTTP_X_FORWARDED_PORT'] != $expected_port |
|
216
|
|
|
) { |
|
217
|
|
|
$port = (int)$server['HTTP_X_FORWARDED_PORT']; |
|
218
|
|
|
} |
|
219
|
|
|
} elseif (isset($server['HTTP_HOST'])) { |
|
220
|
|
|
/** @noinspection NotOptimalIfConditionsInspection */ |
|
221
|
|
|
if (!$host || filter_var($host, FILTER_VALIDATE_IP)) { |
|
222
|
|
|
$host = $server['HTTP_HOST']; |
|
223
|
|
|
} elseif (strpos($server['HTTP_HOST'], ':') !== false) { |
|
224
|
|
|
$port = (int)explode(':', $server['HTTP_HOST'])[1]; |
|
225
|
|
|
if ($port == $expected_port) { |
|
226
|
|
|
$port = ''; |
|
227
|
|
|
} |
|
228
|
|
|
} |
|
229
|
|
|
} |
|
230
|
|
|
if (preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host) !== '') { |
|
231
|
|
|
trigger_error("Invalid host", E_USER_WARNING); |
|
232
|
|
|
throw new ExitException(400); |
|
233
|
|
|
} |
|
234
|
|
|
return $host.($port ? ":$port" : ''); |
|
235
|
|
|
} |
|
236
|
|
|
/** |
|
237
|
|
|
* Secure protocol detection |
|
238
|
|
|
* |
|
239
|
|
|
* @param array $server |
|
240
|
|
|
* |
|
241
|
|
|
* @return bool |
|
242
|
|
|
*/ |
|
243
|
|
|
protected function secure ($server) { |
|
244
|
|
|
return isset($server['HTTPS']) && $server['HTTPS'] ? $server['HTTPS'] !== 'off' : ( |
|
245
|
|
|
isset($server['HTTP_X_FORWARDED_PROTO']) && $server['HTTP_X_FORWARDED_PROTO'] === 'https' |
|
246
|
|
|
); |
|
247
|
|
|
} |
|
248
|
|
|
} |
|
249
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.