$addresses = array_reverse($addresses); $address = $addresses[0]; } else $address = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null; return $address; } /** * Returns the base URL from which this request is executed. * * @return KHttpUrl A HttpUrl object */ public function getBaseUrl() { if(!$this->_base_url instanceof KHttpUrl) { $base = $this->getObject('lib:http.url', array('url' => $this->getUrl()->toString(KHttpUrl::AUTHORITY))); $base->setUrl($this->getBasePath()); $this->_base_url = $base; } return $this->_base_url; } /** * Set the base URL for which the request is executed. * * @param string $url * @return KDispatcherRequestAbstract */ public function setBaseUrl($url) { $this->_base_url = $url; return $this; } /** * Returns the base path of the request. * * @param boolean $fqp If TRUE create a fully qualified path. Default FALSE. * @return string */ public function getBasePath($fqp = false) { if(!isset($this->_base_path)) { // PHP-CGI on Apache with "cgi.fix_pathinfo = 0". We don't have user-supplied PATH_INFO in PHP_SELF if (strpos(PHP_SAPI, 'cgi') !== false && !ini_get('cgi.fix_pathinfo') && !empty($_SERVER['REQUEST_URI'])) { $path = $_SERVER['PHP_SELF']; } else { $path = $_SERVER['SCRIPT_NAME']; } $this->_base_path = rtrim(dirname($path), '/\\'); } $path = $fqp ? $_SERVER['DOCUMENT_ROOT'].$this->_base_path : $this->_base_path; // Sanitize the path since we can't trust the server var return $this->getObject('lib:filter.path')->sanitize($path); } /** * Set the base path for which the request is executed. * * @param string $path * @return KDispatcherRequestAbstract */ public function setBasePath($path) { $this->_base_path = !empty($path) ? rtrim($path, '/\\') : $path; return $this; } /** * Gets a list of languages acceptable by the client browser. * * @return array Languages ordered in the user browser preferences */ public function getLanguages() { if (!isset($this->languages)) { $this->_languages = array(); if($this->_headers->has('Accept-Language')) { $languages = $this->getAccept(); foreach (array_keys($languages) as $lang) { if (strstr($lang, '-')) { $codes = explode('-', $lang); if ($codes[0] == 'i') { // Language not listed in ISO 639 that are not variants // of any listed language, which can be registered with the // i-prefix, such as i-cherokee if (count($codes) > 1) { $lang = $codes[1]; } } else { for ($i = 0, $max = count($codes); $i < $max; $i++) { if ($i == 0) { $lang = strtolower($codes[0]); } else { $lang .= '_'.strtoupper($codes[$i]); } } } } $this->_languages[] = $lang; } } } return $this->_languages; } /** * Gets a list of charsets acceptable by the client browser. * * @return array List of charsets in preferable order */ public function getCharsets() { if (!isset($this->_charsets)) { $this->_charsets = array(); if($this->_headers->has('Accept-Charset')) { $charsets = $this->getAccept(); $this->_charsets = array_keys($charsets); } } return $this->_charsets; } /** * Gets the request ranges * * @link : http://tools.ietf.org/html/rfc2616#section-14.35 * * @throws KHttpExceptionRangeNotSatisfied If the range info is not valid or if the start offset is large then the end offset * @return array List of request ranges */ public function getRanges() { if(!isset($this->_ranges)) { $this->_ranges = array(); if($this->_headers->has('Range')) { $range = $this->_headers->get('Range'); if(!preg_match('/^bytes=((\d*-\d*,? ?)+)$/', $range)) { throw new KHttpExceptionRangeNotSatisfied('Invalid range'); } $ranges = explode(',', substr($range, 6)); foreach ($ranges as $key => $range) { $parts = explode('-', $range); $first = $parts[0]; $last = $parts[1]; $ranges[$key] = array('first' => $first, 'last' => $last); } $this->_ranges = $ranges; } } return $this->_ranges; } /** * Gets the etag * * @link https://tools.ietf.org/html/rfc7232#page-14 * * @return string|null The entity tag, or null if the etag header doesn't exist */ public function getETag() { if($result = $this->_headers->get('If-None-Match')) { //Remove the encoding from the etag // //RFC-7232 explicitly states that ETags should be content-coding aware $result = str_replace(['-gzip', '-br'], '', $result); } return $result; } /** * Checks whether the request is secure or not. * * This method can read the client scheme from the "X-Forwarded-Proto" header when the request is proxied and the * proxy is trusted. The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". * * @link http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10#section-5.4 * * @return boolean */ public function isSecure() { if ($this->isProxied() && $this->_headers->has('X-Forwarded-Proto')) { $scheme = $this->_headers->get('X-Forwarded-Proto'); } else { $scheme = isset($_SERVER['HTTPS']) ? strtolower($_SERVER['HTTPS']) : (isset($_SERVER['REQUEST_SCHEME']) ? strtolower($_SERVER['REQUEST_SCHEME']) : 'http'); } return in_array(strtolower($scheme), array('https', 'on', '1')); } /** * Checks whether the request is proxied or not. * * The request is considered to be proxied if the X-Forwarded-For is provided. * * If one or more trusted proxies are defined this method reads the proxy IP from the "X-Forwarded-By" header. * If no "X-Forwarded-By" header can be found, or the header IP address doesn't match the list of trusted proxies * the function will return false. The "X-Forwarded-By" header must contain the proxy IP address and, potentially, * a port number). * * @link http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10#page-7 * * @return boolean Returns TRUE if the request is proxied (and trusted). FALSE otherwise. */ public function isProxied() { if($this->_headers->has('X-Forwarded-For')) { if(!empty($this->_proxies)) { if($this->_headers->has('X-Forwarded-By')) { $ip = $this->_headers->get('X-Forwarded-By'); $proxies = $this->getProxies(); //Validates the proxied IP-address against the list of trusted proxies. foreach ($proxies as $proxy) { if (strpos($proxy, '/') !== false) { list($address, $netmask) = explode('/', $proxy, 2); if ($netmask < 1 || $netmask > 32) { return false; } } else { $address = $proxy; $netmask = 32; } if(substr_compare(sprintf('%032b', ip2long($ip)), sprintf('%032b', ip2long($address)), 0, $netmask) === 0) { return true; } } } return false; } return true; } return false; } /** * Check if the request is downloadable or not. * * A request is downloading if one of the following conditions are met : * * 1. The request query contains a 'force-download' parameter * 2. The request accepts specifies either the application/force-download or application/octet-stream mime types * * @return bool Returns TRUE If the request is downloadable. FALSE otherwise. */ public function isDownload() { $result = $this->query->has('force-download'); if($this->headers->has('Accept')) { $types = $this->getAccept(); //Get the highest quality format $type = key($types); if(in_array($type, array('application/force-download', 'application/octet-stream'))) { return $result = true; } } return $result; } /** * Check if the request is streaming * * Responses that contain a Range header is considered to be streaming. * @link : http://tools.ietf.org/html/rfc2616#section-14.35 * * @return bool */ public function isStreaming() { return $this->_headers->has('Range'); } /** * Implement a virtual 'headers', 'query' and 'data class property to return their respective objects. * * @param string $name The property name. * @return string $value The property value. */ public function __get($name) { if($name == 'cookies') { return $this->getCookies(); } if($name == 'files') { return $this->getFiles(); } return parent::__get($name); } /** * Deep clone of this instance * * @return void */ public function __clone() { parent::__clone(); $this->_cookies = clone $this->_cookies; $this->_files = clone $this->_files; } }