1<?php 2namespace go\core\http; 3 4use Exception; 5use go\core\Singleton; 6use go\core\util\StringUtil; 7use stdClass; 8 9 10/** 11 * The HTTP request class. 12 * 13 * <p>Example:</p> 14 * ``````````````````````````````````````````````````````````````````````````` 15 * $var = IFW::app()->request()->queryParams['someVar']; 16 * 17 * //Get the JSON or XML data 18 * $var = IFW::app()->request()->payload['somevar']; 19 * ``````````````````````````````````````````````````````````````````````````` 20 * 21 * 22 * @copyright (c) 2014, Intermesh BV http://www.intermesh.nl 23 * @author Merijn Schering <mschering@intermesh.nl> 24 * @license http://www.gnu.org/licenses/agpl-3.0.html AGPLv3 25 */ 26class Request extends Singleton{ 27 28 /** 29 * The body of the request. Only JSON is supported at the moment. 30 * 31 * @var mixed[] 32 */ 33 private $body; 34 35 36 /** 37 * The request headers 38 * 39 * @var string[] 40 */ 41 private $headers; 42 43 /** 44 * Get all query parameters of this request 45 * 46 * @return array ['paramName' => 'value'] 47 */ 48 public function getQueryParams() { 49 return $_GET; 50 } 51 52 53 /** 54 * Get a query parameter by name 55 * 56 * @return string|bool false if not set. 57 */ 58 public function getQueryParam($name) { 59 return $_GET[$name] ?? false; 60 } 61 62 /** 63 * Get the values of the Accept header in lower case 64 * 65 * @param string[] 66 * @return array 67 */ 68 public function getAccept() { 69 70 if(empty($_SERVER['HTTP_ACCEPT'])) { 71 return []; 72 } 73 74 $accept = explode(',', strtolower($_SERVER['HTTP_ACCEPT'])); 75 return array_map('trim', $accept); 76 } 77 78 /** 79 * Get the accepted languages sent by the request in lower case 80 * 81 * @return string[] 82 */ 83 public function getAcceptLanguages() { 84 if(empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 85 return []; 86 } 87 88 $accept = explode(',', strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE'])); 89 return array_map('trim', $accept); 90 } 91 92 /** 93 * Get the request headers as a key value array. The header names are in lower case. 94 * 95 * Example: 96 * 97 * ``````````````````````````````````````````````````````````````````````````` 98 * [ 99 * 'accept' => 'application/json', 100 * 'accept-language' => 'en-us' 101 * ] 102 * ``````````````````````````````````````````````````````````````````````````` 103 * 104 * @return array 105 */ 106 public function getHeaders() { 107 108 if (!isset($this->headers)) { 109 if(!function_exists('apache_request_headers')) 110 { 111 $this->headers = $this->getNonApacheHeaders(); 112 } else{ 113 $this->headers = array_change_key_case(apache_request_headers(),CASE_LOWER); 114 } 115 } 116 return $this->headers; 117 } 118 119 private function getNonApacheHeaders() { 120 $headers = array(); 121 $copy_server = array( 122 'CONTENT_TYPE' => 'content-type', 123 'CONTENT_LENGTH' => 'content-length', 124 'CONTENT_MD5' => 'content-md5', 125 ); 126 foreach ($_SERVER as $key => $value) { 127 if (substr($key, 0, 5) === 'HTTP_') { 128 $key = substr($key, 5); 129 if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { 130 $key = str_replace(' ', '-', strtolower(str_replace('_', ' ', $key))); 131 $headers[$key] = $value; 132 } 133 } elseif (isset($copy_server[$key])) { 134 $headers[$copy_server[$key]] = $value; 135 } 136 } 137 if (!isset($headers['authorization'])) { 138 if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { 139 $headers['authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; 140 } elseif (isset($_SERVER['PHP_AUTH_USER'])) { 141 $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; 142 $headers['authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); 143 } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { 144 $headers['authorization'] = $_SERVER['PHP_AUTH_DIGEST']; 145 } 146 } 147 return $headers; 148 } 149 150 /** 151 * Get request header value 152 * 153 * @param string $name 154 * @param string 155 */ 156 public function getHeader($name) { 157 $name = strtolower($name); 158 $headers = $this->getHeaders(); 159 return isset($headers[$name]) ? $headers[$name] : null; 160 } 161 162 /** 163 * Get the request payload 164 * 165 * The data send in the body of the request. 166 * We support: 167 * 168 * #application/x-www-form-urlencoded 169 * #multipart/form-data 170 * #application/json 171 * #application/xml or text/xml 172 * 173 * @return stdClass 174 */ 175 public function getBody() { 176 if (!isset($this->body)) { 177 //If it's a form post (application/x-www-form-urlencoded or multipart/form-data) with HTML then PHP already built the data 178 if(!empty($_POST)) { 179 $this->body = $_POST; 180 }else if($this->isJson()) 181 { 182 if(empty($this->getRawBody())) { 183 $this->body = []; 184 }else { 185 $this->body = json_decode($this->getRawBody(), true); 186 187 // Check if the post is filled with an array. Otherwise make it an empty array. 188 if(!isset($this->body)){ 189 throw new Exception("JSON decoding error: '".json_last_error_msg()."'.\n\nJSON data from client: \n\n".var_export($this->getRawBody(), true)); 190 } 191 } 192 } 193 } 194 195 return $this->body; 196 } 197 198 /** 199 * Get raw request body as string. 200 * 201 * @return string 202 */ 203 public function getRawBody() { 204 if(!isset($this->rawBody)) { 205 $this->rawBody = file_get_contents('php://input'); 206 } 207 208 return $this->rawBody; 209 } 210 211 /** 212 * Get's the content type header 213 * 214 * @preturn string 215 */ 216 public function getContentType() { 217 return isset($_SERVER["CONTENT_TYPE"]) ? $_SERVER["CONTENT_TYPE"] : ''; 218 } 219 220 /** 221 * Get the request method in upper case 222 * 223 * @return string PUT, POST, DELETE, GET, PATCH, HEAD 224 */ 225 public function getMethod() { 226 return strtoupper($_SERVER['REQUEST_METHOD']); 227 } 228 229 /** 230 * Check if the request posted a JSON body 231 * 232 * @return boolean 233 */ 234 public function isJson() { 235 return strpos($this->getContentType(), 'application/json') !== false; 236 } 237 238 /** 239 * Check if the request posted a JSON body 240 * 241 * @return boolean 242 */ 243 private function isXml() { 244 return strpos($this->getContentType(), '/xml') !== false; 245 } 246 247 /** 248 * Check if this request SSL secured 249 * 250 * @return boolean 251 */ 252 public function isHttps() { 253 if(!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') { 254 return true; 255 } 256 257 if(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') { 258 return true; 259 } 260 261 return false; 262 } 263 264 private $host; 265 /** 266 * Get the host name of the request. 267 * 268 * @param bool $stripPort remove :1234 from result 269 * @return string eg. localhost:6480 270 */ 271 public function getHost($stripPort = true) { 272 273 if(!isset($this->host)) { 274 $possibleHostSources = array('HTTP_X_FORWARDED_HOST', 'HTTP_HOST', 'SERVER_NAME', 'SERVER_ADDR'); 275 $sourceTransformations = array( 276 "HTTP_X_FORWARDED_HOST" => function ($value) { 277 $elements = explode(',', $value); 278 return trim(end($elements)); 279 } 280 ); 281 $this->host = ''; 282 foreach ($possibleHostSources as $source) { 283 if (!empty($this->host)) break; 284 if (empty($_SERVER[$source])) continue; 285 $this->host = $_SERVER[$source]; 286 if (array_key_exists($source, $sourceTransformations)) { 287 $this->host = $sourceTransformations[$source]($this->host); 288 } 289 } 290 291 $this->host = trim($this->host); 292 } 293 294 // Remove port number from host 295 if($stripPort) { 296 return preg_replace('/:\d+$/', '', $this->host); 297 } 298 299 return $this->host; 300 } 301 302// /** 303// * Get port number of request 304// * 305// * @return int| false 306// */ 307// public function getPort() { 308// $possibleHostSources = array('HTTP_X_FORWARDED_HOST', 'HTTP_HOST'); 309// $host = ''; 310// foreach ($possibleHostSources as $source) 311// { 312// if (!empty($host)) break; 313// if (empty($_SERVER[$source])) continue; 314// $host = $_SERVER[$source]; 315// if (array_key_exists($source, $sourceTransformations)) 316// { 317// $host = $sourceTransformations[$source]($host); 318// } 319// } 320// 321// $pos = strpos($host, ':'); 322// 323// if($pos === false) { 324// return false; 325// } 326// 327// return (int) substr($host, $pos); 328// 329// } 330 331 /** 332 * Get the IP address of the user's client 333 * 334 * @return string 335 */ 336 public function getRemoteIpAddress() { 337 if(!empty($_SERVER['HTTP_CLIENT_IP'])){ 338 //ip from share internet 339 return $_SERVER['HTTP_CLIENT_IP']; 340 }elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){ 341 //ip pass from proxy 342 return $_SERVER['HTTP_X_FORWARDED_FOR']; 343 }else{ 344 return $_SERVER['REMOTE_ADDR'] ?? null; 345 } 346 } 347 348 /** 349 * Check if this request is an XMLHttpRequest 350 * * 351 * @return boolean 352 */ 353 public function isXHR() { 354 return isset($_SERVER["HTTP_X_REQUESTED_WITH"]) && $_SERVER["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"; 355 } 356 357 358 /** 359 * Decode a HTTP header to UTF-8 360 * 361 * @link https://tools.ietf.org/html/rfc5987 362 * @param $string 363 * @return bool|string 364 */ 365 public static function headerDecode($string) { 366 $pos = strpos($string, "''"); 367 if($pos == false || $pos > 64) { 368 return false; 369 } 370 //eg. iso-8859-1''%66%6F%73%73%2D%69%74%2D%73%6D%61%6C%6C%2E%67%69%66 371 $charset = substr($string, 0, $pos); 372 373 $string = rawurldecode(substr($string, $pos + 2)); 374 375 return StringUtil::cleanUtf8($string, $charset); 376 } 377} 378