Completed
Push — master ( 32eaad...6cdc53 )
by Nazar
04:10
created

Server   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 209
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 81.54%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
dl 0
loc 209
ccs 53
cts 65
cp 0.8154
rs 9.6
c 2
b 0
f 1
wmc 32
lcom 1
cbo 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A init_server() 0 14 1
A fill_server_properties() 0 16 4
A fill_headers() 0 13 4
A secure() 0 6 3
A header() 0 4 2
A ip() 0 19 4
C host() 0 28 14
1
<?php
2
/**
3
 * @package   CleverStyle Framework
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
	 * Uppercase method, GET by default
13
	 *
14
	 * @var string
15
	 */
16
	public $method;
17
	/**
18
	 * The best guessed host
19
	 *
20
	 * @var string
21
	 */
22
	public $host;
23
	/**
24
	 * Schema `http` or `https`
25
	 *
26
	 * @var string
27
	 */
28
	public $scheme;
29
	/**
30
	 * Is requested with HTTPS
31
	 *
32
	 * @var bool
33
	 */
34
	public $secure;
35
	/**
36
	 * Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
37
	 *
38
	 * @var string
39
	 */
40
	public $protocol;
41
	/**
42
	 * Path
43
	 *
44
	 * @var string
45
	 */
46
	public $path;
47
	/**
48
	 * URI, basically `$path?$query_string` (without `?` is query string is empty), `/` by default
49
	 *
50
	 * @var string
51
	 */
52
	public $uri;
53
	/**
54
	 * Query string
55
	 *
56
	 * @var string
57
	 */
58
	public $query_string;
59
	/**
60
	 * Where request came from, not necessary real IP of client, `127.0.0.1` by default
61
	 *
62
	 * @var string
63
	 */
64
	public $remote_addr;
65
	/**
66
	 * The best guessed IP of client (based on all known headers), `$this->remote_addr` by default
67
	 *
68
	 * @var string
69
	 */
70
	public $ip;
71
	/**
72
	 * Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`, `content-type`, `accept-language`
73
	 *
74
	 * @var string[]
75
	 */
76
	public $headers;
77
	/**
78
	 * @var bool
79
	 */
80
	protected $cli;
81
	/**
82
	 * @param string[] $server Typically `$_SERVER`
83
	 */
84 20
	function init_server ($server = []) {
85 20
		$this->fill_headers($server);
86
		/**
87
		 * Add some defaults to avoid isset() hell afterwards
88
		 */
89
		$server += [
90 20
			'QUERY_STRING'    => '',
91
			'REMOTE_ADDR'     => '127.0.0.1',
92
			'REQUEST_URI'     => '/',
93
			'REQUEST_METHOD'  => 'GET',
94
			'SERVER_PROTOCOL' => 'HTTP/1.1'
95
		];
96 20
		$this->fill_server_properties($server);
97 20
	}
98
	/**
99
	 * @param string[] $server
100
	 */
101 20
	protected function fill_server_properties ($server) {
102 20
		$this->cli          = @$server['CLI'] === true;
103 20
		$this->method       = strtoupper($server['REQUEST_METHOD']);
104 20
		$this->host         = $this->host($server);
105 20
		$this->secure       = $this->secure($server);
106 20
		$this->scheme       = $this->secure ? 'https' : 'http';
107 20
		$this->protocol     = $server['SERVER_PROTOCOL'];
108 20
		$this->query_string = $server['QUERY_STRING'];
109 20
		$this->uri          = null_byte_filter(urldecode($server['REQUEST_URI'])) ?: '/';
110 20
		if (strpos($this->uri, '/index.php') === 0) {
111
			$this->uri = substr($this->uri, 10);
112
		}
113 20
		$this->path         = explode('?', $this->uri, 2)[0];
114 20
		$this->remote_addr  = $server['REMOTE_ADDR'];
115 20
		$this->ip           = $this->ip($server);
116 20
	}
117
	/**
118
	 * @param string[] $server
119
	 */
120 20
	protected function fill_headers ($server) {
121 20
		$headers = [];
122 20
		foreach ($server as $header => $header_content) {
123 20
			if (strpos($header, 'HTTP_') === 0) {
124 20
				$header = substr($header, 5);
125 20
			} elseif (strpos($header, 'CONTENT_') !== 0) {
126 20
				continue;
127
			}
128 20
			$header           = strtolower(str_replace('_', '-', $header));
129 20
			$headers[$header] = $header_content;
130
		}
131 20
		$this->headers = $headers;
132 20
	}
133
	/**
134
	 * The best guessed IP of client (based on all known headers), `127.0.0.1` by default
135
	 *
136
	 * @param string[] $server
137
	 *
138
	 * @return string
139
	 */
140 20
	protected function ip ($server) {
141
		$all_possible_keys = [
142 20
			'HTTP_X_FORWARDED_FOR',
143
			'HTTP_CLIENT_IP',
144
			'HTTP_X_FORWARDED',
145
			'HTTP_X_CLUSTER_CLIENT_IP',
146
			'HTTP_FORWARDED_FOR',
147
			'HTTP_FORWARDED'
148
		];
149 20
		foreach ($all_possible_keys as $key) {
150 20
			if (isset($server[$key])) {
151
				$ip = trim(explode(',', $server[$key])[0]);
152
				if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
153 20
					return $ip;
154
				}
155
			}
156
		}
157 20
		return $this->remote_addr;
158
	}
159
	/**
160
	 * The best guessed host
161
	 *
162
	 * @param string[] $server
163
	 *
164
	 * @return string
165
	 */
166 20
	protected function host ($server) {
167 20
		$host          = @$server['SERVER_NAME'] ?: '';
168 20
		$port          = '';
169 20
		$expected_port = $this->secure ? 443 : 80;
170 20
		if (!$host && isset($server['HTTP_X_FORWARDED_HOST'])) {
171
			$host = $server['HTTP_X_FORWARDED_HOST'];
172
			if (
173
				isset($server['HTTP_X_FORWARDED_PORT']) &&
174
				$server['HTTP_X_FORWARDED_PORT'] != $expected_port
175
			) {
176
				$port = (int)$server['HTTP_X_FORWARDED_PORT'];
177
			}
178 20
		} elseif (isset($server['HTTP_HOST'])) {
179
			/** @noinspection NotOptimalIfConditionsInspection */
180 20
			if (!$host || filter_var($host, FILTER_VALIDATE_IP)) {
181
				$host = $server['HTTP_HOST'];
182 20
			} elseif (strpos($server['HTTP_HOST'], ':') !== false) {
183
				$port = (int)explode(':', $server['HTTP_HOST'])[1];
184
				if ($port == $expected_port) {
185
					$port = '';
186
				}
187
			}
188
		}
189 20
		if (preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host) !== '') {
190
			return '';
191
		}
192 20
		return $host.($port ? ":$port" : '');
193
	}
194
	/**
195
	 * Secure protocol detection
196
	 *
197
	 * @param array $server
198
	 *
199
	 * @return bool
200
	 */
201 20
	protected function secure ($server) {
202 20
		return @$server['HTTPS'] ? $server['HTTPS'] !== 'off' : (
203 20
			@$server['REQUEST_SCHEME'] === 'https' ||
204 20
			@$server['HTTP_X_FORWARDED_PROTO'] === 'https'
205
		);
206
	}
207
	/**
208
	 * Get header by name
209
	 *
210
	 * @param string $name Case-insensitive
211
	 *
212
	 * @return string Header content if exists or empty string otherwise
213
	 */
214 22
	function header ($name) {
215 22
		$name = strtolower($name);
216 22
		return isset($this->headers[$name]) ? $this->headers[$name] : '';
217
	}
218
}
219