1<?php 2 3namespace OAuth\Common\Http\Uri; 4 5use InvalidArgumentException; 6 7/** 8 * Standards-compliant URI class. 9 */ 10class Uri implements UriInterface 11{ 12 /** 13 * @var string 14 */ 15 private $scheme = 'http'; 16 17 /** 18 * @var string 19 */ 20 private $userInfo = ''; 21 22 /** 23 * @var string 24 */ 25 private $rawUserInfo = ''; 26 27 /** 28 * @var string 29 */ 30 private $host; 31 32 /** 33 * @var int 34 */ 35 private $port = 80; 36 37 /** 38 * @var string 39 */ 40 private $path = '/'; 41 42 /** 43 * @var string 44 */ 45 private $query = ''; 46 47 /** 48 * @var string 49 */ 50 private $fragment = ''; 51 52 /** 53 * @var bool 54 */ 55 private $explicitPortSpecified = false; 56 57 /** 58 * @var bool 59 */ 60 private $explicitTrailingHostSlash = false; 61 62 /** 63 * @param string $uri 64 */ 65 public function __construct($uri = null) 66 { 67 if (null !== $uri) { 68 $this->parseUri($uri); 69 } 70 } 71 72 /** 73 * @param string $uri 74 * 75 * @throws \InvalidArgumentException 76 */ 77 protected function parseUri($uri) 78 { 79 if (false === ($uriParts = parse_url($uri))) { 80 // congratulations if you've managed to get parse_url to fail, 81 // it seems to always return some semblance of a parsed url no matter what 82 throw new InvalidArgumentException("Invalid URI: $uri"); 83 } 84 85 if (!isset($uriParts['scheme'])) { 86 throw new InvalidArgumentException('Invalid URI: http|https scheme required'); 87 } 88 89 $this->scheme = $uriParts['scheme']; 90 $this->host = $uriParts['host']; 91 92 if (isset($uriParts['port'])) { 93 $this->port = $uriParts['port']; 94 $this->explicitPortSpecified = true; 95 } else { 96 $this->port = strcmp('https', $uriParts['scheme']) ? 80 : 443; 97 $this->explicitPortSpecified = false; 98 } 99 100 if (isset($uriParts['path'])) { 101 $this->path = $uriParts['path']; 102 if ('/' === $uriParts['path']) { 103 $this->explicitTrailingHostSlash = true; 104 } 105 } else { 106 $this->path = '/'; 107 } 108 109 $this->query = isset($uriParts['query']) ? $uriParts['query'] : ''; 110 $this->fragment = isset($uriParts['fragment']) ? $uriParts['fragment'] : ''; 111 112 $userInfo = ''; 113 if (!empty($uriParts['user'])) { 114 $userInfo .= $uriParts['user']; 115 } 116 if ($userInfo && !empty($uriParts['pass'])) { 117 $userInfo .= ':' . $uriParts['pass']; 118 } 119 120 $this->setUserInfo($userInfo); 121 } 122 123 /** 124 * @param string $rawUserInfo 125 * 126 * @return string 127 */ 128 protected function protectUserInfo($rawUserInfo) 129 { 130 $colonPos = strpos($rawUserInfo, ':'); 131 132 // rfc3986-3.2.1 | http://tools.ietf.org/html/rfc3986#section-3.2 133 // "Applications should not render as clear text any data 134 // after the first colon (":") character found within a userinfo 135 // subcomponent unless the data after the colon is the empty string 136 // (indicating no password)" 137 if ($colonPos !== false && strlen($rawUserInfo)-1 > $colonPos) { 138 return substr($rawUserInfo, 0, $colonPos) . ':********'; 139 } else { 140 return $rawUserInfo; 141 } 142 } 143 144 /** 145 * @return string 146 */ 147 public function getScheme() 148 { 149 return $this->scheme; 150 } 151 152 /** 153 * @return string 154 */ 155 public function getUserInfo() 156 { 157 return $this->userInfo; 158 } 159 160 /** 161 * @return string 162 */ 163 public function getRawUserInfo() 164 { 165 return $this->rawUserInfo; 166 } 167 168 /** 169 * @return string 170 */ 171 public function getHost() 172 { 173 return $this->host; 174 } 175 176 /** 177 * @return int 178 */ 179 public function getPort() 180 { 181 return $this->port; 182 } 183 184 /** 185 * @return string 186 */ 187 public function getPath() 188 { 189 return $this->path; 190 } 191 192 /** 193 * @return string 194 */ 195 public function getQuery() 196 { 197 return $this->query; 198 } 199 200 /** 201 * @return string 202 */ 203 public function getFragment() 204 { 205 return $this->fragment; 206 } 207 208 /** 209 * Uses protected user info by default as per rfc3986-3.2.1 210 * Uri::getRawAuthority() is available if plain-text password information is desirable. 211 * 212 * @return string 213 */ 214 public function getAuthority() 215 { 216 $authority = $this->userInfo ? $this->userInfo.'@' : ''; 217 $authority .= $this->host; 218 219 if ($this->explicitPortSpecified) { 220 $authority .= ":{$this->port}"; 221 } 222 223 return $authority; 224 } 225 226 /** 227 * @return string 228 */ 229 public function getRawAuthority() 230 { 231 $authority = $this->rawUserInfo ? $this->rawUserInfo.'@' : ''; 232 $authority .= $this->host; 233 234 if ($this->explicitPortSpecified) { 235 $authority .= ":{$this->port}"; 236 } 237 238 return $authority; 239 } 240 241 /** 242 * @return string 243 */ 244 public function getAbsoluteUri() 245 { 246 $uri = $this->scheme . '://' . $this->getRawAuthority(); 247 248 if ('/' === $this->path) { 249 $uri .= $this->explicitTrailingHostSlash ? '/' : ''; 250 } else { 251 $uri .= $this->path; 252 } 253 254 if (!empty($this->query)) { 255 $uri .= "?{$this->query}"; 256 } 257 258 if (!empty($this->fragment)) { 259 $uri .= "#{$this->fragment}"; 260 } 261 262 return $uri; 263 } 264 265 /** 266 * @return string 267 */ 268 public function getRelativeUri() 269 { 270 $uri = ''; 271 272 if ('/' === $this->path) { 273 $uri .= $this->explicitTrailingHostSlash ? '/' : ''; 274 } else { 275 $uri .= $this->path; 276 } 277 278 return $uri; 279 } 280 281 /** 282 * Uses protected user info by default as per rfc3986-3.2.1 283 * Uri::getAbsoluteUri() is available if plain-text password information is desirable. 284 * 285 * @return string 286 */ 287 public function __toString() 288 { 289 $uri = $this->scheme . '://' . $this->getAuthority(); 290 291 if ('/' === $this->path) { 292 $uri .= $this->explicitTrailingHostSlash ? '/' : ''; 293 } else { 294 $uri .= $this->path; 295 } 296 297 if (!empty($this->query)) { 298 $uri .= "?{$this->query}"; 299 } 300 301 if (!empty($this->fragment)) { 302 $uri .= "#{$this->fragment}"; 303 } 304 305 return $uri; 306 } 307 308 /** 309 * @param $path 310 */ 311 public function setPath($path) 312 { 313 if (empty($path)) { 314 $this->path = '/'; 315 $this->explicitTrailingHostSlash = false; 316 } else { 317 $this->path = $path; 318 if ('/' === $this->path) { 319 $this->explicitTrailingHostSlash = true; 320 } 321 } 322 } 323 324 /** 325 * @param string $query 326 */ 327 public function setQuery($query) 328 { 329 $this->query = $query; 330 } 331 332 /** 333 * @param string $var 334 * @param string $val 335 */ 336 public function addToQuery($var, $val) 337 { 338 if (strlen($this->query) > 0) { 339 $this->query .= '&'; 340 } 341 $this->query .= http_build_query(array($var => $val), '', '&'); 342 } 343 344 /** 345 * @param string $fragment 346 */ 347 public function setFragment($fragment) 348 { 349 $this->fragment = $fragment; 350 } 351 352 /** 353 * @param string $scheme 354 */ 355 public function setScheme($scheme) 356 { 357 $this->scheme = $scheme; 358 } 359 360 361 /** 362 * @param string $userInfo 363 */ 364 public function setUserInfo($userInfo) 365 { 366 $this->userInfo = $userInfo ? $this->protectUserInfo($userInfo) : ''; 367 $this->rawUserInfo = $userInfo; 368 } 369 370 371 /** 372 * @param int $port 373 */ 374 public function setPort($port) 375 { 376 $this->port = intval($port); 377 378 if (('https' === $this->scheme && $this->port === 443) || ('http' === $this->scheme && $this->port === 80)) { 379 $this->explicitPortSpecified = false; 380 } else { 381 $this->explicitPortSpecified = true; 382 } 383 } 384 385 /** 386 * @param string $host 387 */ 388 public function setHost($host) 389 { 390 $this->host = $host; 391 } 392 393 /** 394 * @return bool 395 */ 396 public function hasExplicitTrailingHostSlash() 397 { 398 return $this->explicitTrailingHostSlash; 399 } 400 401 /** 402 * @return bool 403 */ 404 public function hasExplicitPortSpecified() 405 { 406 return $this->explicitPortSpecified; 407 } 408} 409