Erebot  latest
A modular IRC bot for PHP 5.3+
URI.php
1 <?php
2 /*
3  This file is part of Erebot, a modular IRC bot written in PHP.
4 
5  Copyright © 2010 François Poirotte
6 
7  Erebot is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  Erebot is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with Erebot. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 namespace Erebot;
22 
49 class URI implements \Erebot\URIInterface
50 {
52  protected $scheme;
54  protected $userinfo;
56  protected $host;
58  protected $port;
60  protected $path;
62  protected $query;
64  protected $fragment;
65 
78  public function __construct($uri = array())
79  {
80  if (is_string($uri)) {
81  $uri = $this->parseURI($uri, false);
82  }
83 
84  if (!is_array($uri)) {
85  throw new \InvalidArgumentException('Invalid URI');
86  }
87 
88  if (!isset($uri['userinfo']) && isset($uri['user'])) {
89  $uri['userinfo'] = $uri['user'];
90  if (isset($uri['pass'])) {
91  $uri['userinfo'] .= ':'.$uri['pass'];
92  }
93  }
94 
95  $components = array(
96  'Scheme',
97  'Host',
98  'Port',
99  'Path',
100  'Query',
101  'Fragment',
102  'UserInfo',
103  );
104 
105  foreach ($components as $component) {
106  $tmp = strtolower($component);
107  $setter = 'set'.$component;
108  if (isset($uri[$tmp])) {
109  $this->$setter($uri[$tmp]);
110  } else {
111  $this->$setter(null);
112  }
113  }
114  }
115 
136  protected function parseURI($uri, $relative)
137  {
138  $result = array();
139 
140  if (!$relative) {
141  // Parse scheme.
142  $pos = strpos($uri, ':');
143  if (!$pos) {
144  // An URI starting with ":" is also invalid.
145  throw new \InvalidArgumentException('No scheme found');
146  }
147 
148  $result['scheme'] = substr($uri, 0, $pos);
149  $uri = (string) substr($uri, $pos + 1);
150  }
151 
152  // Parse fragment.
153  $pos = strpos($uri, '#');
154  if ($pos !== false) {
155  $result['fragment'] = (string) substr($uri, $pos + 1);
156  $uri = (string) substr($uri, 0, $pos);
157  }
158 
159  // Parse query string.
160  $pos = strpos($uri, '?');
161  if ($pos !== false) {
162  $result['query'] = (string) substr($uri, $pos + 1);
163  $uri = (string) substr($uri, 0, $pos);
164  }
165 
166  // Handle "path-empty".
167  if ($uri == '') {
168  $result['path'] = '';
169  return $result;
170  }
171 
172  // Handle "hier-part".
173  if (substr($uri, 0, 2) == '//') {
174  // Remove leftovers from the scheme field.
175  $uri = (string) substr($uri, 2);
176 
177  // Parse path.
178  $result['path'] = '';
179  $pos = strpos($uri, '/');
180  if ($pos !== false) {
181  $result['path'] = substr($uri, $pos);
182  $uri = (string) substr($uri, 0, $pos);
183  }
184 
185  // Parse userinfo.
186  $pos = strpos($uri, '@');
187  if ($pos !== false) {
188  $result['userinfo'] = (string) substr($uri, 0, $pos);
189  $uri = (string) substr($uri, $pos + 1);
190  }
191 
192  // Parse port.
193  $rpos = strcspn(strrev($uri), ':]');
194  $len = strlen($uri);
195  if ($rpos != 0 && $rpos < $len && $uri[$len - $rpos - 1] != "]") {
196  $result['port'] = (string) substr($uri, -1 * $rpos);
197  $uri = (string) substr($uri, 0, -1 * $rpos - 1);
198  }
199 
200  $result['host'] = $uri;
201  return $result;
202  }
203 
204  // Handle "path-absolute" & "path-rootless".
205  $result['path'] = $uri;
206  return $result;
207  }
208 
223  protected static function normalizePercent($data)
224  {
225  return preg_replace_callback(
226  '/%([[:xdigit:]]{2})/',
227  array('self', 'normalizePercentReal'),
228  $data
229  );
230  }
231 
241  public static function normalizePercentReal($hexchr)
242  {
243  // 6.2.2.1. Case Normalization
244  // Percent-encoded characters must use uppercase letters.
245  // 6.2.2.2. Percent-Encoding Normalization
246  $unreserved = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
247  'abcdefghijklmnopqrstuvwxyz'.
248  '-._~';
249 
250  $chr = chr(hexdec($hexchr[1]));
251  if (strpos($unreserved, $chr) !== false) {
252  return $chr;
253  }
254  return '%' . strtoupper($hexchr[1]);
255  }
256 
257  public function toURI($raw = false, $credentials = true)
258  {
259  // 5.3. Component Recomposition
260  $result = "";
261 
262  // In our case, the scheme will always be set
263  // because we only deal with absolute URIs here.
264  // The condition is checked anyway to keep the code
265  // in line with the algorithm described in RFC 3986.
266  if ($this->scheme !== null) {
267  $result .= $this->getScheme($raw).':';
268  }
269 
270  if ($this->host !== null) {
271  $result .= '//';
272  if ($this->userinfo !== null && $credentials) {
273  $result .= $this->getUserInfo($raw)."@";
274  }
275 
276  $result .= $this->getHost($raw);
277  $port = $this->getPort($raw);
278  if ($port !== null) {
279  $result .= ':'.$port;
280  }
281  }
282 
283  $result .= $this->getPath($raw);
284 
285  if ($this->query !== null) {
286  $result .= '?'.$this->getQuery($raw);
287  }
288 
289  if ($this->fragment !== null) {
290  $result .= '#'.$this->getFragment($raw);
291  }
292 
293  return $result;
294  }
295 
296  public function __toString()
297  {
298  return $this->toURI();
299  }
300 
301  public function getScheme($raw = false)
302  {
303  // 6.2.2.1. Case Normalization
304  // Characters must be normalized to use lowercase letters.
305  if ($raw) {
306  return $this->scheme;
307  }
308  return strtolower($this->scheme);
309  }
310 
311  public function setScheme($scheme)
312  {
313  // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
314  if (!preg_match('/^[-[:alpha:][:alnum:]\\+\\.]*$/Di', $scheme)) {
315  throw new \InvalidArgumentException('Invalid scheme');
316  }
317  $this->scheme = $scheme;
318  }
319 
320  public function getUserInfo($raw = false)
321  {
322  if ($raw) {
323  return $this->userinfo;
324  }
325  return ($this->userinfo === null)
326  ? null
327  : $this->normalizePercent($this->userinfo);
328  }
329 
330  public function setUserInfo($userinfo)
331  {
332  /*
333  userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
334  pct-encoded = "%" HEXDIG HEXDIG
335  unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
336  sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
337  / "*" / "+" / "," / ";" / "="
338  */
339  $pattern = '(?:'.
340  '[-[:alnum:]\\._~!\\$&\'\\(\\)\\*\\+,;=:]|'.
341  '%[[:xdigit:]]{2}'.
342  ')*';
343  if ($userinfo !== null && !preg_match('/^'.$pattern.'$/Di', $userinfo)) {
344  throw new \InvalidArgumentException('Invalid user information');
345  }
346  $this->userinfo = $userinfo;
347  }
348 
349  public function getHost($raw = false)
350  {
351  // 6.2.2.1. Case Normalization
352  // Characters must be normalized to use lowercase letters.
353  if ($raw) {
354  return $this->host;
355  }
356  return ($this->host !== null)
357  ? strtolower($this->normalizePercent($this->host))
358  : null;
359  }
360 
361  public function setHost($host)
362  {
363  /*
364  host = IP-literal / IPv4address / reg-name
365  IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]"
366  IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
367  IPv6address = 6( h16 ":" ) ls32
368  / "::" 5( h16 ":" ) ls32
369  / [ h16 ] "::" 4( h16 ":" ) ls32
370  / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
371  / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
372  / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
373  / [ *4( h16 ":" ) h16 ] "::" ls32
374  / [ *5( h16 ":" ) h16 ] "::" h16
375  / [ *6( h16 ":" ) h16 ] "::"
376  h16 = 1*4HEXDIG
377  ls32 = ( h16 ":" h16 ) / IPv4address
378  IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
379  dec-octet = DIGIT ; 0-9
380  / %x31-39 DIGIT ; 10-99
381  / "1" 2DIGIT ; 100-199
382  / "2" %x30-34 DIGIT ; 200-249
383  / "25" %x30-35 ; 250-255
384  reg-name = *( unreserved / pct-encoded / sub-delims )
385  pct-encoded = "%" HEXDIG HEXDIG
386  unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
387  sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
388  / "*" / "+" / "," / ";" / "="
389  ZoneID = 1*( unreserved / pct-encoded )
390  IPv6addrz = IPv6address "%25" ZoneID
391  */
392  $decOctet = '(?:\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])';
393  $ipv4Address = $decOctet.'(?:\\.'.$decOctet.'){3}';
394  $h16 = '[[:xdigit:]]{1,4}';
395  $ls32 = '(?:'.$h16.':'.$h16.'|'.$ipv4Address.')';
396  $ipv6Address =
397  '(?:'.
398  '(?:'.$h16.':){6}'.$ls32.'|'.
399  '::(?:'.$h16.':){5}'.$ls32.'|'.
400  '(?:'.$h16.')?::(?:'.$h16.':){4}'.$ls32.'|'.
401  '(?:(?:'.$h16.':)?'.$h16.')?::(?:'.$h16.':){3}'.$ls32.'|'.
402  '(?:(?:'.$h16.':){0,2}'.$h16.')?::(?:'.$h16.':){2}'.$ls32.'|'.
403  '(?:(?:'.$h16.':){0,3}'.$h16.')?::'.$h16.':'.$ls32.'|'.
404  '(?:(?:'.$h16.':){0,4}'.$h16.')?::'.$ls32.'|'.
405  '(?:(?:'.$h16.':){0,5}'.$h16.')?::'.$h16.'|'.
406  '(?:(?:'.$h16.':){0,6}'.$h16.')?::'.
407  ')';
408  $ipFuture = 'v[[:xdigit:]]+\\.'.
409  '[-[:alnum:]\\._~!\\$&\'\\(\\)*\\+,;=]+';
410  $unreserved = '-[:alnum:]\\._~';
411  $subDelims = '!\\$&\'\\(\\)*\\+,;=';
412  $pctEncoded = '%[[:xdigit:]]{2}';
413  $zoneID = "(?:[$unreserved]|$pctEncoded)+";
414  $ipv6Addrz = "$ipv6Address%25$zoneID";
415  $regName = "(?:[$unreserved$subDelims]|$pctEncoded)*";
416  $ipLiteral = '\\[(?:'.$ipv6Addrz.'|'.$ipv6Address.'|'.$ipFuture.')\\]';
417  $pattern = '(?:'.$ipLiteral.'|'.$ipv4Address.'|'.$regName.')';
418  if ($host !== null && !preg_match('/^'.$pattern.'$/Di', $host)) {
419  throw new \InvalidArgumentException('Invalid host');
420  }
421  $this->host = $host;
422  }
423 
424  public function getPort($raw = false)
425  {
426  // 6.2.3. Scheme-Based Normalization
427  if ($raw) {
428  return $this->port;
429  }
430 
431  if ($this->port == '') {
432  return null;
433  }
434 
435  $port = (int) $this->port;
436 
437  // Try to canonicalize the port.
438  $tcp = getservbyname($this->scheme, 'tcp');
439  $udp = getservbyname($this->scheme, 'udp');
440 
441  if ($tcp == $port && ($udp === false || $udp == $tcp)) {
442  return null;
443  }
444 
445  if ($udp == $port && ($tcp === false || $udp == $tcp)) {
446  return null;
447  }
448 
449  return $port;
450  }
451 
452  public function setPort($port)
453  {
454  // port = *DIGIT
455  if (is_int($port)) {
456  $port = (string) $port;
457  }
458  if ($port !== null && strspn($port, '0123456789') != strlen($port)) {
459  throw new \InvalidArgumentException('Invalid port');
460  }
461  $this->port = $port;
462  }
463 
474  protected function removeDotSegments($path)
475  {
476  if ($path === null) {
477  throw new \InvalidArgumentException('Path not set');
478  }
479 
480  // §5.2.4. Remove Dot Segments
481  $input = $path;
482  $output = '';
483 
484  while ($input != '') {
485  if (substr($input, 0, 3) == '../') {
486  $input = (string) substr($input, 3);
487  } elseif (substr($input, 0, 2) == './') {
488  $input = (string) substr($input, 2);
489  } elseif (substr($input, 0, 3) == '/./') {
490  $input = substr($input, 2);
491  } elseif ($input == '/.') {
492  $input = '/';
493  } elseif (substr($input, 0, 4) == '/../') {
494  $input = substr($input, 3);
495  $pos = strrpos($output, '/');
496  if ($pos === false) {
497  $output = '';
498  } else {
499  $output = substr($output, 0, $pos);
500  }
501  } elseif ($input == '/..') {
502  $input = '/';
503  $pos = strrpos($output, '/');
504  if ($pos === false) {
505  $output = '';
506  } else {
507  $output = substr($output, 0, $pos);
508  }
509  } elseif ($input == '.' || $input == '..') {
510  $input = '';
511  } else {
512  $pos = strpos($input, '/', 1);
513  if ($pos === false) {
514  $output .= $input;
515  $input = '';
516  } else {
517  $output .= substr($input, 0, $pos);
518  $input = substr($input, $pos);
519  }
520  }
521  }
522 
523  return $output;
524  }
525 
539  protected function merge($path)
540  {
541  // 5.2.3. Merge Paths
542  if ($this->host !== null && $this->path == '') {
543  return '/'.$path;
544  }
545 
546  $pos = strrpos($this->path, '/');
547  if ($pos === false) {
548  return $path;
549  }
550  return substr($this->path, 0, $pos + 1).$path;
551  }
552 
553  public function getPath($raw = false)
554  {
555  // 6.2.2.3. Path Segment Normalization
556  if ($raw) {
557  return $this->path;
558  }
559 
560  return $this->removeDotSegments(
561  $this->normalizePercent($this->path)
562  );
563  }
564 
579  protected function validatePath($path, $relative)
580  {
581  /*
582  path = path-abempty ; begins with "/" or is empty
583  / path-absolute ; begins with "/" but not "//"
584  / path-noscheme ; begins with a non-colon segment
585  / path-rootless ; begins with a segment
586  / path-empty ; zero characters
587  path-abempty = *( "/" segment )
588  path-absolute = "/" [ segment-nz *( "/" segment ) ]
589  path-noscheme = segment-nz-nc *( "/" segment )
590  ; only used for a relative URI
591  path-rootless = segment-nz *( "/" segment )
592  ; only used for an absolute URI
593  path-empty = 0<pchar>
594  segment = *pchar
595  segment-nz = 1*pchar
596  segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
597  ; non-zero-length segment without any colon ":"
598  pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
599  unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
600  sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
601  / "*" / "+" / "," / ";" / "="
602  pct-encoded = "%" HEXDIG HEXDIG
603  */
604  $pchar = '(?:'.
605  '[-[:alnum:]\\._~!\\$&\'\\(\\)\\*\\+,;=:@]|'.
606  '%[[:xdigit:]]'.
607  ')';
608  $segment = '(?:'.$pchar.'*)';
609  $segmentNz = '(?:'.$pchar.'+)';
610  $segmentNzNc = '(?:'.
611  '[-[:alnum:]\\._~!\\$&\'\\(\\)\\*\\+,;=@]|'.
612  '%[[:xdigit:]]'.
613  ')+';
614  $pathAbempty = '(?:/'.$segment.')*';
615  $pathAbsolute = '/(?:'.$segmentNz.'(?:/'.$segment.')*)?';
616  $pathNoscheme = $segmentNzNc.'(?:/'.$segment.')*';
617  $pathRootless = $segmentNz.'(?:/'.$segment.')*';
618  $pathEmpty = '(?!'.$pchar.')';
619 
620  $pattern = '(?:' . $pathAbempty.'|'.$pathAbsolute;
621  if ($relative) {
622  $pattern .= '|'.$pathNoscheme;
623  } else {
624  $pattern .= '|'.$pathRootless;
625  }
626  $pattern .= '|'.$pathEmpty . ')';
627 
628  return (bool) preg_match('#^'.$pattern.'$#Di', $path);
629  }
630 
644  protected function realSetPath($path, $relative)
645  {
646  if (!is_string($path) || !$this->validatePath($path, $relative)) {
647  throw new \InvalidArgumentException(
648  'Invalid path; use relative() for relative paths'
649  );
650  }
651  $this->path = $path;
652  }
653 
654  public function setPath($path)
655  {
656  $this->realSetPath($path, false);
657  }
658 
659  public function getQuery($raw = false)
660  {
661  if ($raw) {
662  return $this->query;
663  }
664  return $this->normalizePercent($this->query);
665  }
666 
667  public function setQuery($query)
668  {
669  /*
670  pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
671  query = *( pchar / "/" / "?" )
672  pct-encoded = "%" HEXDIG HEXDIG
673  unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
674  sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
675  / "*" / "+" / "," / ";" / "="
676  */
677  $pattern = '(?:'.
678  '[-[:alnum:]\\._~!\\$&\'\\(\\)\\*\\+,;=/\\?]|'.
679  '%[[:xdigit:]]{2}'.
680  ')*';
681  if ($query !== null && !preg_match('#^'.$pattern.'$#Di', $query)) {
682  throw new \InvalidArgumentException('Invalid query');
683  }
684  $this->query = $query;
685  }
686 
687  public function getFragment($raw = false)
688  {
689  if ($raw) {
690  return $this->fragment;
691  }
692  return $this->normalizePercent($this->fragment);
693  }
694 
695  public function setFragment($fragment)
696  {
697  /*
698  pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
699  fragment = *( pchar / "/" / "?" )
700  pct-encoded = "%" HEXDIG HEXDIG
701  unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
702  sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
703  / "*" / "+" / "," / ";" / "="
704  */
705  $pattern = '(?:'.
706  '[-[:alnum:]\\._~!\\$&\'\\(\\)\\*\\+,;=/\\?]|'.
707  '%[[:xdigit:]]{2}'.
708  ')*';
709  if ($fragment !== null && !preg_match('#^'.$pattern.'$#Di', $fragment)) {
710  throw new \InvalidArgumentException('Invalid fragment');
711  }
712  $this->fragment = $fragment;
713  }
714 
715  public function asParsedURL($component = -1)
716  {
717  $result = array();
718 
719  // Copy some of the fields now and the rest later.
720  // This is done in two passes so that the order of the fields
721  // in the result matches exactly the output of parse_url().
722  foreach (array('scheme', 'host', 'port') as $field) {
723  if ($this->$field !== null) {
724  $result[$field] = $this->$field;
725  }
726  }
727 
728  // Cleanup "port" component.
729  if (isset($result['port'])) {
730  if (strspn($result['port'], '0123456789') != strlen($result['port'])) {
731  unset($result['port']);
732  } else {
733  $result['port'] = (int) $result['port'];
734  }
735  }
736 
737  if ($this->userinfo !== null) {
738  $pos = strpos($this->userinfo, ':');
739  if ($pos !== false) {
740  $result['user'] = (string) substr($this->userinfo, 0, $pos);
741  $result['pass'] = (string) substr($this->userinfo, $pos + 1);
742  } else {
743  $result['user'] = $this->userinfo;
744  }
745  }
746 
747  // We loop on the rest of the fields now.
748  // See the previous loop for an explanation.
749  foreach (array('path', 'query', 'fragment') as $field) {
750  if ($this->$field !== null) {
751  $result[$field] = $this->$field;
752  }
753  }
754 
755  // Detect invalid URIs.
756  if (!isset($result['host']) || $result['host'] === '' ||
757  !isset($result['scheme']) || $result['scheme'] === '') {
758  return false;
759  }
760 
761  // The positions match the values of PHP_URL_SCHEME, PHP_URL_HOST, etc.
762  $fields = array('scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment');
763  switch ($component) {
764  case -1:
765  return $result;
766 
767  case PHP_URL_SCHEME:
768  case PHP_URL_HOST:
769  case PHP_URL_PORT:
770  case PHP_URL_USER:
771  case PHP_URL_PASS:
772  case PHP_URL_PATH:
773  case PHP_URL_QUERY:
774  case PHP_URL_FRAGMENT:
775  return isset($result[$fields[$component]])
776  ? $result[$fields[$component]]
777  :null;
778 
779  default:
780  return null;
781  }
782  }
783 
784  public function relative($reference)
785  {
786  try {
787  $cls = __CLASS__;
788  $result = new $cls($reference);
789  return $result;
790  } catch (\InvalidArgumentException $e) {
791  // Nothing to do. We will try to interpret
792  // the reference as a relative URI instead.
793  }
794 
795  // Use the current (absolute) URI as the base.
796  $result = clone $this;
797 
798  // 5.2.2. Transform References
799  // Our parser is strict.
800  $parsed = $this->parseURI($reference, true);
801 
802  // No need to test the case where the scheme is defined.
803  // This would be an absolute URI and has already been
804  // captured by the previous try..catch block.
805 
806  // Always copy the new fragment.
807  $result->setFragment(
808  isset($parsed['fragment'])
809  ? $parsed['fragment']
810  : null
811  );
812 
813  // "host" == "authority" here, see the grammar
814  // for reasons why this always holds true.
815  if (isset($parsed['host'])) {
816  $result->setHost(isset($parsed['host']) ? $parsed['host'] : null);
817  $result->setPort(isset($parsed['port']) ? $parsed['port'] : null);
818  $result->setUserInfo(
819  isset($parsed['userinfo'])
820  ? $parsed['userinfo']
821  : null
822  );
823  $result->realSetPath($parsed['path'], true);
824  $result->setQuery(
825  isset($parsed['query'])
826  ? $parsed['query']
827  : null
828  );
829  return $result;
830  }
831 
832  // No need to copy path/authority because
833  // $result is already a copy of the base.
834 
835  if ($parsed['path'] == '') {
836  if (isset($parsed['query'])) {
837  $result->setQuery($parsed['query']);
838  }
839  return $result;
840  }
841 
842  if (substr($parsed['path'], 0, 1) == '/') {
843  $result->realSetPath(
844  $result->removeDotSegments($parsed['path']),
845  true
846  );
847  } else {
848  $result->realSetPath(
849  $result->removeDotSegments($result->merge($parsed['path'])),
850  true
851  );
852  }
853  $result->setQuery(isset($parsed['query']) ? $parsed['query'] : null);
854  return $result;
855  }
856 
857  public static function fromAbsPath($abspath, $strict = true)
858  {
859  if (!strncasecmp(PHP_OS, "Win", 3)) {
860  $isUnc = (substr($abspath, 0, 2) == '\\\\');
861  if ($isUnc) {
862  $abspath = ltrim($abspath, '\\');
863  }
864  $parts = explode('\\', $abspath);
865 
866  if ($isUnc && $parts[0] == '?') {
867  // This is actually UNCW -- "Long UNC".
868  array_shift($parts);
869  if (strpos($parts[0], ':') !== false) {
870  $host = 'localhost';
871  $path = implode('\\', $parts);
872  } elseif ($parts[0] != 'UNC') {
873  throw new \InvalidArgumentException('Invalid UNC path');
874  } else {
875  array_shift($parts[0]); // shift the "UNC" token.
876  $host = array_shift($parts[0]); // shift ServerName.
877  $path = implode('\\', $parts);
878  }
879  } elseif ($isUnc) {
880  // Regular UNC path.
881  $host = array_shift($parts[0]); // shift ServerName.
882  $path = implode('\\', $parts);
883  } else {
884  // Regular local path.
885  $host = 'localhost';
886  $path = implode('\\', $parts);
887  }
888 
889  if (!$strict) {
890  $path = str_replace('/', '\\', $path);
891  }
892  $path = str_replace('/', '%2F', $path);
893  $path = str_replace('\\', '/', $path);
894  $path = ltrim($path, '/');
895  } else {
896  $host = 'localhost';
897 
898  if (DIRECTORY_SEPARATOR != '/') {
899  if (!$strict) {
900  $abspath = str_replace('/', DIRECTORY_SEPARATOR, $abspath);
901  }
902  $path = str_replace('/', '%2F', $path);
903  $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
904  }
905  $path = ltrim($abspath, '/');
906  }
907 
908  $host = strtolower(self::normalizePercent($host));
909  $cls = __CLASS__;
910  $url = 'file://' . ($host == 'localhost' ? '' : $host) . '/' . $path;
911  return new $cls($url);
912  }
913 }
Interface for a Uniform Resource Identifier parser/generator compatible with RFC 3986.
$fragment
Fragment component.
Definition: URI.php:64
getUserInfo($raw=false)
Definition: URI.php:320
toURI($raw=false, $credentials=true)
Definition: URI.php:257
getPath($raw=false)
Definition: URI.php:553
Definition: CLI.php:21
removeDotSegments($path)
Definition: URI.php:474
setPath($path)
Definition: URI.php:654
parseURI($uri, $relative)
Definition: URI.php:136
validatePath($path, $relative)
Definition: URI.php:579
asParsedURL($component=-1)
Definition: URI.php:715
setQuery($query)
Definition: URI.php:667
getHost($raw=false)
Definition: URI.php:349
getFragment($raw=false)
Definition: URI.php:687
setHost($host)
Definition: URI.php:361
setScheme($scheme)
Definition: URI.php:311
setFragment($fragment)
Definition: URI.php:695
$path
Path component.
Definition: URI.php:60
getScheme($raw=false)
Definition: URI.php:301
Simple parser/generator for Uniform Resource Identifiers as defined in RFC 3986.
Definition: URI.php:49
static fromAbsPath($abspath, $strict=true)
Definition: URI.php:857
$userinfo
User information component (such as a "username:password" pair).
Definition: URI.php:54
static normalizePercentReal($hexchr)
Definition: URI.php:241
__construct($uri=array())
Definition: URI.php:78
$query
Query component.
Definition: URI.php:62
getQuery($raw=false)
Definition: URI.php:659
setPort($port)
Definition: URI.php:452
getPort($raw=false)
Definition: URI.php:424
$port
Port component.
Definition: URI.php:58
__toString()
Definition: URI.php:296
static normalizePercent($data)
Definition: URI.php:223
$scheme
Scheme component (sometimes also erroneously called a "protocol").
Definition: URI.php:52
relative($reference)
Definition: URI.php:784
setUserInfo($userinfo)
Definition: URI.php:330
merge($path)
Definition: URI.php:539
realSetPath($path, $relative)
Definition: URI.php:644
$host
Host component ("authority", even though "authority" is more general).
Definition: URI.php:56