Completed
Push — master ( 12b05e...7485cd )
by Nazar
04:15
created

Server::init_server()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 20
rs 9.4285
cc 1
eloc 15
nc 1
nop 1
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
10
trait Server {
11
	/**
12
	 * Language accepted by client, `''` by default
13
	 *
14
	 * @var string
15
	 */
16
	public $language;
17
	/**
18
	 * Version accepted by client, will match `/^[0-9\.]+$/`, useful for API, `1` by default
19
	 *
20
	 * @var string
21
	 */
22
	public $version;
23
	/**
24
	 * Content type, `''` by default
25
	 *
26
	 * @var string
27
	 */
28
	public $content_type;
29
	/**
30
	 * Do not track
31
	 *
32
	 * @var bool
33
	 */
34
	public $dnt;
35
	/**
36
	 * The best guessed host
37
	 *
38
	 * @var string
39
	 */
40
	public $host;
41
	/**
42
	 * The best guessed IP of client (based on all known headers), `$this->remote_addr` by default
43
	 *
44
	 * @var string
45
	 */
46
	public $ip;
47
	/**
48
	 * Schema `http` or `https`
49
	 *
50
	 * @var string
51
	 */
52
	public $schema;
53
	/**
54
	 * Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
55
	 *
56
	 * @var string
57
	 */
58
	public $protocol;
59
	/**
60
	 * Query string
61
	 *
62
	 * @var string
63
	 */
64
	public $query_string;
65
	/**
66
	 * HTTP referer, `''` by default
67
	 *
68
	 * @var string
69
	 */
70
	public $referer;
71
	/**
72
	 * Where request came from, not necessary real IP of client, `127.0.0.1` by default
73
	 *
74
	 * @var string
75
	 */
76
	public $remote_addr;
77
	/**
78
	 * Path
79
	 *
80
	 * @var string
81
	 */
82
	public $path;
83
	/**
84
	 * URI
85
	 *
86
	 * @var string
87
	 */
88
	public $uri;
89
	/**
90
	 * Uppercase method, GET by default
91
	 *
92
	 * @var string
93
	 */
94
	public $method;
95
	/**
96
	 * Is requested with HTTPS
97
	 *
98
	 * @var bool
99
	 */
100
	public $secure;
101
	/**
102
	 * User agent
103
	 *
104
	 * @var string
105
	 */
106
	public $user_agent;
107
	/**
108
	 * Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`, `content-type`, `accept-language`
109
	 *
110
	 * @var string[]
111
	 */
112
	public $headers;
113
	/**
114
	 * @param string[] $server Typically `$_SERVER`
115
	 */
116
	function init_server ($server = []) {
117
		$this->fill_headers($server);
118
		/**
119
		 * Add some defaults to avoid isset() hell afterwards
120
		 */
121
		$server += [
122
			'HTTP_ACCEPT_LANGUAGE' => '',
123
			'HTTP_ACCEPT_VERSION'  => '1',
124
			'CONTENT_TYPE'         => '',
125
			'HTTP_DNT'             => '0',
126
			'QUERY_STRING'         => '',
127
			'HTTP_REFERER'         => '',
128
			'REMOTE_ADDR'          => '127.0.0.1',
129
			'REQUEST_URI'          => '',
130
			'REQUEST_METHOD'       => 'GET',
131
			'HTTP_USER_AGENT'      => '',
132
			'SERVER_PROTOCOL'      => 'HTTP/1.1'
133
		];
134
		$this->fill_server_properties($server);
135
	}
136
	/**
137
	 * @param string[] $server
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     = $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->uri          = $server['REQUEST_URI'];
154
		$this->method       = strtoupper($server['REQUEST_METHOD']);
155
		$this->user_agent   = $server['HTTP_USER_AGENT'];
156
	}
157
	/**
158
	 * @param string[] $server
159
	 */
160
	protected function fill_headers ($server) {
161
		$headers = [];
162
		foreach ($server as $header => $header_content) {
163
			if (strpos($header, 'HTTP_') === 0) {
164
				$header = substr($header, 5);
165
			} elseif (strpos($header, 'CONTENT_') !== 0) {
166
				continue;
167
			}
168
			$header           = strtolower(str_replace('_', '-', $header));
169
			$headers[$header] = $header_content;
170
		}
171
		$this->headers = $headers;
172
	}
173
	/**
174
	 * The best guessed IP of client (based on all known headers), `127.0.0.1` by default
175
	 *
176
	 * @param string[] $server
177
	 *
178
	 * @return string
179
	 */
180
	protected function ip ($server) {
181
		$all_possible_keys = [
182
			'HTTP_X_FORWARDED_FOR',
183
			'HTTP_CLIENT_IP',
184
			'HTTP_X_FORWARDED',
185
			'HTTP_X_CLUSTER_CLIENT_IP',
186
			'HTTP_FORWARDED_FOR',
187
			'HTTP_FORWARDED'
188
		];
189
		foreach ($all_possible_keys as $key) {
190
			if (isset($server[$key])) {
191
				$ip = trim(explode(',', $server[$key])[0]);
192
				if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
193
					return $ip;
194
				}
195
			}
196
		}
197
		return isset($server['REMOTE_ADDR']) ? '127.0.0.1' : '';
198
	}
199
	/**
200
	 * The best guessed host
201
	 *
202
	 * @param string[] $server
203
	 *
204
	 * @return string
205
	 */
206
	protected function host ($server) {
207
		$host          = isset($server['SERVER_NAME']) ? $server['SERVER_NAME'] : '';
208
		$port          = '';
209
		$expected_port = $this->secure ? 443 : 80;
210
		if (!$host && isset($server['HTTP_X_FORWARDED_HOST'])) {
211
			$host = $server['HTTP_X_FORWARDED_HOST'];
212
			if (
213
				isset($server['HTTP_X_FORWARDED_PORT']) &&
214
				$server['HTTP_X_FORWARDED_PORT'] != $expected_port
215
			) {
216
				$port = (int)$server['HTTP_X_FORWARDED_PORT'];
217
			}
218
		} elseif (isset($server['HTTP_HOST'])) {
219
			/** @noinspection NotOptimalIfConditionsInspection */
220
			if (!$host || filter_var($host, FILTER_VALIDATE_IP)) {
221
				$host = $server['HTTP_HOST'];
222
			} elseif (strpos($server['HTTP_HOST'], ':') !== false) {
223
				$port = (int)explode(':', $server['HTTP_HOST'])[1];
224
				if ($port == $expected_port) {
225
					$port = '';
226
				}
227
			}
228
		}
229
		if (preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host) !== '') {
230
			return '';
231
		}
232
		return $host.($port ? ":$port" : '');
233
	}
234
	/**
235
	 * Secure protocol detection
236
	 *
237
	 * @param array $server
238
	 *
239
	 * @return bool
240
	 */
241
	protected function secure ($server) {
242
		return isset($server['HTTPS']) && $server['HTTPS'] ? $server['HTTPS'] !== 'off' : (
243
			isset($server['HTTP_X_FORWARDED_PROTO']) && $server['HTTP_X_FORWARDED_PROTO'] === 'https'
244
		);
245
	}
246
	/**
247
	 * Get header by name
248
	 *
249
	 * @param string $name
250
	 *
251
	 * @return false|string Header content if exists or `false` otherwise
252
	 */
253
	function header ($name) {
254
		return isset($this->headers[$name]) ? $this->headers[$name] : false;
255
	}
256
}
257