1<?php 2/** 3 * Copyright 2017 Facebook, Inc. 4 * 5 * You are hereby granted a non-exclusive, worldwide, royalty-free license to 6 * use, copy, modify, and distribute this software in source code or binary 7 * form for use in connection with the web services and APIs provided by 8 * Facebook. 9 * 10 * As with any software that integrates with the Facebook platform, your use 11 * of this software is subject to the Facebook Developer Principles and 12 * Policies [http://developers.facebook.com/policy/]. This copyright notice 13 * shall be included in all copies or substantial portions of the software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 * 23 */ 24namespace Facebook\Url; 25 26/** 27 * Class FacebookUrlDetectionHandler 28 * 29 * @package Facebook 30 */ 31class FacebookUrlDetectionHandler implements UrlDetectionInterface 32{ 33 /** 34 * @inheritdoc 35 */ 36 public function getCurrentUrl() 37 { 38 return $this->getHttpScheme() . '://' . $this->getHostName() . $this->getServerVar('REQUEST_URI'); 39 } 40 41 /** 42 * Get the currently active URL scheme. 43 * 44 * @return string 45 */ 46 protected function getHttpScheme() 47 { 48 return $this->isBehindSsl() ? 'https' : 'http'; 49 } 50 51 /** 52 * Tries to detect if the server is running behind an SSL. 53 * 54 * @return boolean 55 */ 56 protected function isBehindSsl() 57 { 58 // Check for proxy first 59 $protocol = $this->getHeader('X_FORWARDED_PROTO'); 60 if ($protocol) { 61 return $this->protocolWithActiveSsl($protocol); 62 } 63 64 $protocol = $this->getServerVar('HTTPS'); 65 if ($protocol) { 66 return $this->protocolWithActiveSsl($protocol); 67 } 68 69 return (string)$this->getServerVar('SERVER_PORT') === '443'; 70 } 71 72 /** 73 * Detects an active SSL protocol value. 74 * 75 * @param string $protocol 76 * 77 * @return boolean 78 */ 79 protected function protocolWithActiveSsl($protocol) 80 { 81 $protocol = strtolower((string)$protocol); 82 83 return in_array($protocol, ['on', '1', 'https', 'ssl'], true); 84 } 85 86 /** 87 * Tries to detect the host name of the server. 88 * 89 * Some elements adapted from 90 * 91 * @see https://github.com/symfony/HttpFoundation/blob/master/Request.php 92 * 93 * @return string 94 */ 95 protected function getHostName() 96 { 97 // Check for proxy first 98 $header = $this->getHeader('X_FORWARDED_HOST'); 99 if ($header && $this->isValidForwardedHost($header)) { 100 $elements = explode(',', $header); 101 $host = $elements[count($elements) - 1]; 102 } elseif (!$host = $this->getHeader('HOST')) { 103 if (!$host = $this->getServerVar('SERVER_NAME')) { 104 $host = $this->getServerVar('SERVER_ADDR'); 105 } 106 } 107 108 // trim and remove port number from host 109 // host is lowercase as per RFC 952/2181 110 $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); 111 112 // Port number 113 $scheme = $this->getHttpScheme(); 114 $port = $this->getCurrentPort(); 115 $appendPort = ':' . $port; 116 117 // Don't append port number if a normal port. 118 if (($scheme == 'http' && $port == '80') || ($scheme == 'https' && $port == '443')) { 119 $appendPort = ''; 120 } 121 122 return $host . $appendPort; 123 } 124 125 protected function getCurrentPort() 126 { 127 // Check for proxy first 128 $port = $this->getHeader('X_FORWARDED_PORT'); 129 if ($port) { 130 return (string)$port; 131 } 132 133 $protocol = (string)$this->getHeader('X_FORWARDED_PROTO'); 134 if ($protocol === 'https') { 135 return '443'; 136 } 137 138 return (string)$this->getServerVar('SERVER_PORT'); 139 } 140 141 /** 142 * Returns the a value from the $_SERVER super global. 143 * 144 * @param string $key 145 * 146 * @return string 147 */ 148 protected function getServerVar($key) 149 { 150 return isset($_SERVER[$key]) ? $_SERVER[$key] : ''; 151 } 152 153 /** 154 * Gets a value from the HTTP request headers. 155 * 156 * @param string $key 157 * 158 * @return string 159 */ 160 protected function getHeader($key) 161 { 162 return $this->getServerVar('HTTP_' . $key); 163 } 164 165 /** 166 * Checks if the value in X_FORWARDED_HOST is a valid hostname 167 * Could prevent unintended redirections 168 * 169 * @param string $header 170 * 171 * @return boolean 172 */ 173 protected function isValidForwardedHost($header) 174 { 175 $elements = explode(',', $header); 176 $host = $elements[count($elements) - 1]; 177 178 return preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $host) //valid chars check 179 && 0 < strlen($host) && strlen($host) < 254 //overall length check 180 && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $host); //length of each label 181 } 182} 183