3 * @see https://github.com/zendframework/zend-uri for the canonical source repository
4 * @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (https://www.zend.com)
5 * @license https://github.com/zendframework/zend-uri/blob/master/LICENSE.md New BSD License
10 use Zend\Escaper\Escaper
;
16 class Uri
implements UriInterface
19 * Character classes defined in RFC-3986
21 const CHAR_UNRESERVED
= 'a-zA-Z0-9_\-\.~';
22 const CHAR_GEN_DELIMS
= ':\/\?#\[\]@';
23 const CHAR_SUB_DELIMS
= '!\$&\'\(\)\*\+,;=';
24 const CHAR_RESERVED
= ':\/\?#\[\]@!\$&\'\(\)\*\+,;=';
26 * Not in the spec - those characters have special meaning in urlencoded query parameters
28 const CHAR_QUERY_DELIMS
= '!\$\'\(\)\*\,';
31 * Host part types represented as binary masks
32 * The binary mask consists of 5 bits in the following order:
33 * <RegName> | <DNS> | <IPvFuture> | <IPv6> | <IPv4>
34 * Place 1 or 0 in the different positions for enable or disable the part.
35 * Finally use a hexadecimal representation.
37 const HOST_IPV4
= 0x01; //00001
38 const HOST_IPV6
= 0x02; //00010
39 const HOST_IPVFUTURE
= 0x04; //00100
40 const HOST_IPVANY
= 0x07; //00111
41 const HOST_DNS
= 0x08; //01000
42 const HOST_DNS_OR_IPV4
= 0x09; //01001
43 const HOST_DNS_OR_IPV6
= 0x0A; //01010
44 const HOST_DNS_OR_IPV4_OR_IPV6
= 0x0B; //01011
45 const HOST_DNS_OR_IPVANY
= 0x0F; //01111
46 const HOST_REGNAME
= 0x10; //10000
47 const HOST_DNS_OR_IPV4_OR_IPV6_OR_REGNAME
= 0x1B; //11011
48 const HOST_ALL
= 0x1F; //11111
58 * URI userInfo part (usually user:password in HTTP URLs)
100 * Which host part types are valid for this URI?
104 protected $validHostTypes = self
::HOST_ALL
;
107 * Array of valid schemes.
109 * Subclasses of this class that only accept specific schemes may set the
110 * list of accepted schemes here. If not empty, when setScheme() is called
111 * it will only accept the schemes listed here.
115 protected static $validSchemes = [];
118 * List of default ports per scheme
120 * Inheriting URI classes may set this, and the normalization methods will
121 * automatically remove the port if it is equal to the default port for the
126 protected static $defaultPorts = [];
131 protected static $escaper;
134 * Create a new URI object
136 * @param Uri|string|null $uri
137 * @throws Exception\InvalidArgumentException
139 public function __construct($uri = null)
141 if (is_string($uri)) {
143 } elseif ($uri instanceof UriInterface
) {
145 $this->setScheme($uri->getScheme());
146 $this->setUserInfo($uri->getUserInfo());
147 $this->setHost($uri->getHost());
148 $this->setPort($uri->getPort());
149 $this->setPath($uri->getPath());
150 $this->setQuery($uri->getQuery());
151 $this->setFragment($uri->getFragment());
152 } elseif ($uri !== null) {
153 throw new Exception\
InvalidArgumentException(sprintf(
154 'Expecting a string or a URI object, received "%s"',
155 (is_object($uri) ?
get_class($uri) : gettype($uri))
161 * Set Escaper instance
163 * @param Escaper $escaper
165 public static function setEscaper(Escaper
$escaper)
167 static::$escaper = $escaper;
171 * Retrieve Escaper instance
173 * Lazy-loads one if none provided
177 public static function getEscaper()
179 if (null === static::$escaper) {
180 static::setEscaper(new Escaper());
182 return static::$escaper;
186 * Check if the URI is valid
188 * Note that a relative URI may still be valid
192 public function isValid()
195 if (strlen($this->path
) > 0 && substr($this->path
, 0, 1) != '/') {
201 if ($this->userInfo ||
$this->port
) {
206 // Check path-only (no host) URI
207 if (substr($this->path
, 0, 2) == '//') {
213 if (! ($this->query ||
$this->fragment
)) {
214 // No host, path, query or fragment - this is not a valid URI
222 * Check if the URI is a valid relative URI
226 public function isValidRelative()
228 if ($this->scheme ||
$this->host ||
$this->userInfo ||
$this->port
) {
233 // Check path-only (no host) URI
234 if (substr($this->path
, 0, 2) == '//') {
240 if (! ($this->query ||
$this->fragment
)) {
241 // No host, path, query or fragment - this is not a valid URI
249 * Check if the URI is an absolute or relative URI
253 public function isAbsolute()
255 return ($this->scheme
!== null);
261 protected function reset()
263 $this->setScheme(null);
264 $this->setPort(null);
265 $this->setUserInfo(null);
266 $this->setHost(null);
267 $this->setPath(null);
268 $this->setFragment(null);
269 $this->setQuery(null);
278 public function parse($uri)
283 if (($scheme = self
::parseScheme($uri)) !== null) {
284 $this->setScheme($scheme);
285 $uri = substr($uri, strlen($scheme) +
1) ?
: '';
288 // Capture authority part
289 if (preg_match('|^//([^/\?#]*)|', $uri, $match)) {
290 $authority = $match[1];
291 $uri = substr($uri, strlen($match[0]));
293 // Split authority into userInfo and host
294 if (strpos($authority, '@') !== false) {
295 // The userInfo can also contain '@' symbols; split $authority
296 // into segments, and set it to the last segment.
297 $segments = explode('@', $authority);
298 $authority = array_pop($segments);
299 $userInfo = implode('@', $segments);
301 $this->setUserInfo($userInfo);
304 $nMatches = preg_match('/:[\d]{1,5}$/', $authority, $matches);
305 if ($nMatches === 1) {
306 $portLength = strlen($matches[0]);
307 $port = substr($matches[0], 1);
309 $this->setPort((int) $port);
310 $authority = substr($authority, 0, -$portLength);
313 $this->setHost($authority);
321 if (preg_match('|^[^\?#]*|', $uri, $match)) {
322 $this->setPath($match[0]);
323 $uri = substr($uri, strlen($match[0]));
331 if (preg_match('|^\?([^#]*)|', $uri, $match)) {
332 $this->setQuery($match[1]);
333 $uri = substr($uri, strlen($match[0]));
339 // All that's left is the fragment
340 if ($uri && substr($uri, 0, 1) == '#') {
341 $this->setFragment(substr($uri, 1));
348 * Compose the URI into a string
351 * @throws Exception\InvalidUriException
353 public function toString()
355 if (! $this->isValid()) {
356 if ($this->isAbsolute() ||
! $this->isValidRelative()) {
357 throw new Exception\
InvalidUriException(
358 'URI is not valid and cannot be converted into a string'
366 $uri .= $this->scheme
. ':';
369 if ($this->host
!== null) {
371 if ($this->userInfo
) {
372 $uri .= $this->userInfo
. '@';
376 $uri .= ':' . $this->port
;
381 $uri .= static::encodePath($this->path
);
382 } elseif ($this->host
&& ($this->query ||
$this->fragment
)) {
387 $uri .= "?" . static::encodeQueryFragment($this->query
);
390 if ($this->fragment
) {
391 $uri .= "#" . static::encodeQueryFragment($this->fragment
);
400 * Normalizing a URI includes removing any redundant parent directory or
401 * current directory references from the path (e.g. foo/bar/../baz becomes
402 * foo/baz), normalizing the scheme case, decoding any over-encoded
405 * Eventually, two normalized URLs pointing to the same resource should be
406 * equal even if they were originally represented by two different strings
410 public function normalize()
413 $this->scheme
= static::normalizeScheme($this->scheme
);
417 $this->host
= static::normalizeHost($this->host
);
421 $this->port
= static::normalizePort($this->port
, $this->scheme
);
425 $this->path
= static::normalizePath($this->path
);
429 $this->query
= static::normalizeQuery($this->query
);
432 if ($this->fragment
) {
433 $this->fragment
= static::normalizeFragment($this->fragment
);
436 // If path is empty (and we have a host), path should be '/'
437 // Isn't this valid ONLY for HTTP-URI?
438 if ($this->host
&& empty($this->path
)) {
446 * Convert a relative URI into an absolute URI using a base absolute URI as
449 * This is similar to merge() - only it uses the supplied URI as the
450 * base reference instead of using the current URI as the base reference.
452 * Merging algorithm is adapted from RFC-3986 section 5.2
453 * (@link http://tools.ietf.org/html/rfc3986#section-5.2)
455 * @param Uri|string $baseUri
456 * @throws Exception\InvalidArgumentException
459 public function resolve($baseUri)
461 // Ignore if URI is absolute
462 if ($this->isAbsolute()) {
466 if (is_string($baseUri)) {
467 $baseUri = new static($baseUri);
468 } elseif (! $baseUri instanceof Uri
) {
469 throw new Exception\
InvalidArgumentException(
470 'Provided base URI must be a string or a Uri object'
474 // Merging starts here...
475 if ($this->getHost()) {
476 $this->setPath(static::removePathDotSegments($this->getPath()));
478 $basePath = $baseUri->getPath();
479 $relPath = $this->getPath();
481 $this->setPath($basePath);
482 if (! $this->getQuery()) {
483 $this->setQuery($baseUri->getQuery());
486 if (substr($relPath, 0, 1) == '/') {
487 $this->setPath(static::removePathDotSegments($relPath));
489 if ($baseUri->getHost() && ! $basePath) {
492 $mergedPath = substr($basePath, 0, strrpos($basePath, '/') +
1);
494 $this->setPath(static::removePathDotSegments($mergedPath . $relPath));
498 // Set the authority part
499 $this->setUserInfo($baseUri->getUserInfo());
500 $this->setHost($baseUri->getHost());
501 $this->setPort($baseUri->getPort());
504 $this->setScheme($baseUri->getScheme());
509 * Convert the link to a relative link by substracting a base URI
511 * This is the opposite of resolving a relative link - i.e. creating a
512 * relative reference link from an original URI and a base URI.
514 * If the two URIs do not intersect (e.g. the original URI is not in any
515 * way related to the base URI) the URI will not be modified.
517 * @param Uri|string $baseUri
520 public function makeRelative($baseUri)
522 // Copy base URI, we should not modify it
523 $baseUri = new static($baseUri);
526 $baseUri->normalize();
528 $host = $this->getHost();
529 $baseHost = $baseUri->getHost();
530 if ($host && $baseHost && ($host != $baseHost)) {
531 // Not the same hostname
535 $port = $this->getPort();
536 $basePort = $baseUri->getPort();
537 if ($port && $basePort && ($port != $basePort)) {
542 $scheme = $this->getScheme();
543 $baseScheme = $baseUri->getScheme();
544 if ($scheme && $baseScheme && ($scheme != $baseScheme)) {
545 // Not the same scheme (e.g. HTTP vs. HTTPS)
549 // Remove host, port and scheme
555 if ($this->getPath() == $baseUri->getPath()) {
560 $pathParts = preg_split('|(/)|', $this->getPath(), null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
);
561 $baseParts = preg_split('|(/)|', $baseUri->getPath(), null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
);
563 // Get the intersection of existing path parts and those from the
565 $matchingParts = array_intersect_assoc($pathParts, $baseParts);
567 // Loop through the matches
568 foreach ($matchingParts as $index => $segment) {
569 // If we skip an index at any point, we have parent traversal, and
570 // need to prepend the path accordingly
571 if ($index && ! isset($matchingParts[$index - 1])) {
572 array_unshift($pathParts, '../');
576 // Otherwise, we simply unset the given path segment
577 unset($pathParts[$index]);
580 // Reset the path by imploding path segments
581 $this->setPath(implode($pathParts));
587 * Get the scheme part of the URI
589 * @return string|null
591 public function getScheme()
593 return $this->scheme
;
597 * Get the User-info (usually user:password) part
599 * @return string|null
601 public function getUserInfo()
603 return $this->userInfo
;
609 * @return string|null
611 public function getHost()
621 public function getPort()
629 * @return string|null
631 public function getPath()
639 * @return string|null
641 public function getQuery()
647 * Return the query string as an associative array of key => value pairs
649 * This is an extension to RFC-3986 but is quite useful when working with
650 * most common URI types
654 public function getQueryAsArray()
658 parse_str($this->query
, $query);
665 * Get the URI fragment
667 * @return string|null
669 public function getFragment()
671 return $this->fragment
;
677 * If the scheme is not valid according to the generic scheme syntax or
678 * is not acceptable by the specific URI class (e.g. 'http' or 'https' are
679 * the only acceptable schemes for the Zend\Uri\Http class) an exception
682 * You can check if a scheme is valid before setting it using the
683 * validateScheme() method.
685 * @param string $scheme
686 * @throws Exception\InvalidUriPartException
689 public function setScheme($scheme)
691 if (($scheme !== null) && (! self
::validateScheme($scheme))) {
692 throw new Exception\
InvalidUriPartException(sprintf(
693 'Scheme "%s" is not valid or is not accepted by %s',
696 ), Exception\InvalidUriPartException
::INVALID_SCHEME
);
699 $this->scheme
= $scheme;
704 * Set the URI User-info part (usually user:password)
706 * @param string $userInfo
708 * @throws Exception\InvalidUriPartException If the schema definition
709 * does not have this part
711 public function setUserInfo($userInfo)
713 $this->userInfo
= $userInfo;
720 * Note that the generic syntax for URIs allows using host names which
721 * are not necessarily IPv4 addresses or valid DNS host names. For example,
722 * IPv6 addresses are allowed as well, and also an abstract "registered name"
723 * which may be any name composed of a valid set of characters, including,
724 * for example, tilda (~) and underscore (_) which are not allowed in DNS
727 * Subclasses of Uri may impose more strict validation of host names - for
728 * example the HTTP RFC clearly states that only IPv4 and valid DNS names
729 * are allowed in HTTP URIs.
731 * @param string $host
732 * @throws Exception\InvalidUriPartException
735 public function setHost($host)
739 && ! self
::validateHost($host, $this->validHostTypes
)
741 throw new Exception\
InvalidUriPartException(sprintf(
742 'Host "%s" is not valid or is not accepted by %s',
745 ), Exception\InvalidUriPartException
::INVALID_HOSTNAME
);
753 * Set the port part of the URI
758 public function setPort($port)
767 * @param string $path
770 public function setPath($path)
777 * Set the query string
779 * If an array is provided, will encode this array of parameters into a
780 * query string. Array values will be represented in the query string using
781 * PHP's common square bracket notation.
783 * @param string|array $query
786 public function setQuery($query)
788 if (is_array($query)) {
789 // We replace the + used for spaces by http_build_query with the
790 // more standard %20.
791 $query = str_replace('+', '%20', http_build_query($query));
794 $this->query
= $query;
799 * Set the URI fragment part
801 * @param string $fragment
803 * @throws Exception\InvalidUriPartException If the schema definition
804 * does not have this part
806 public function setFragment($fragment)
808 $this->fragment
= $fragment;
813 * Magic method to convert the URI to a string
817 public function __toString()
820 return $this->toString();
821 } catch (\Exception
$e) {
827 * Encoding and Validation Methods
831 * Check if a scheme is valid or not
833 * Will check $scheme to be valid against the generic scheme syntax defined
834 * in RFC-3986. If the class also defines specific acceptable schemes, will
835 * also check that $scheme is one of them.
837 * @param string $scheme
840 public static function validateScheme($scheme)
842 if (! empty(static::$validSchemes)
843 && ! in_array(strtolower($scheme), static::$validSchemes)
848 return (bool) preg_match('/^[A-Za-z][A-Za-z0-9\-\.+]*$/', $scheme);
852 * Check that the userInfo part of a URI is valid
854 * @param string $userInfo
857 public static function validateUserInfo($userInfo)
859 $regex = '/^(?:[' . self
::CHAR_UNRESERVED
. self
::CHAR_SUB_DELIMS
. ':]+|%[A-Fa-f0-9]{2})*$/';
860 return (bool) preg_match($regex, $userInfo);
864 * Validate the host part
866 * Users may control which host types to allow by passing a second parameter
867 * with a bitmask of HOST_* constants which are allowed. If not specified,
868 * all address types will be allowed.
870 * Note that the generic URI syntax allows different host representations,
871 * including IPv4 addresses, IPv6 addresses and future IP address formats
872 * enclosed in square brackets, and registered names which may be DNS names
873 * or even more complex names. This is different (and is much more loose)
874 * from what is commonly accepted as valid HTTP URLs for example.
876 * @param string $host
877 * @param int $allowed bitmask of allowed host types
880 public static function validateHost($host, $allowed = self
::HOST_ALL
)
883 * "first-match-wins" algorithm (RFC 3986):
884 * If host matches the rule for IPv4address, then it should be
885 * considered an IPv4 address literal and not a reg-name
887 if ($allowed & self
::HOST_IPVANY
) {
888 if (static::isValidIpAddress($host, $allowed)) {
893 if ($allowed & self
::HOST_REGNAME
) {
894 if (static::isValidRegName($host)) {
899 if ($allowed & self
::HOST_DNS
) {
900 if (static::isValidDnsHostname($host)) {
911 * Valid values include numbers between 1 and 65535, and empty values
916 public static function validatePort($port)
924 if ($port < 1 ||
$port > 0xffff) {
935 * @param string $path
938 public static function validatePath($path)
940 $pchar = '(?:[' . self
::CHAR_UNRESERVED
. ':@&=\+\$,]+|%[A-Fa-f0-9]{2})*';
941 $segment = $pchar . "(?:;{$pchar})*";
942 $regex = "/^{$segment}(?:\/{$segment})*$/";
943 return (bool) preg_match($regex, $path);
947 * Check if a URI query or fragment part is valid or not
949 * Query and Fragment parts are both restricted by the same syntax rules,
950 * so the same validation method can be used for both.
952 * You can encode a query or fragment part to ensure it is valid by passing
953 * it through the encodeQueryFragment() method.
955 * @param string $input
958 public static function validateQueryFragment($input)
960 $regex = '/^(?:[' . self
::CHAR_UNRESERVED
. self
::CHAR_SUB_DELIMS
. ':@\/\?]+|%[A-Fa-f0-9]{2})*$/';
961 return (bool) preg_match($regex, $input);
965 * URL-encode the user info part of a URI
967 * @param string $userInfo
969 * @throws Exception\InvalidArgumentException
971 public static function encodeUserInfo($userInfo)
973 if (! is_string($userInfo)) {
974 throw new Exception\
InvalidArgumentException(sprintf(
975 'Expecting a string, got %s',
976 (is_object($userInfo) ?
get_class($userInfo) : gettype($userInfo))
980 $regex = '/(?:[^' . self
::CHAR_UNRESERVED
. self
::CHAR_SUB_DELIMS
. '%:]|%(?![A-Fa-f0-9]{2}))/';
981 $escaper = static::getEscaper();
982 $replace = function ($match) use ($escaper) {
983 return $escaper->escapeUrl($match[0]);
986 return preg_replace_callback($regex, $replace, $userInfo);
992 * Will replace all characters which are not strictly allowed in the path
993 * part with percent-encoded representation
995 * @param string $path
996 * @throws Exception\InvalidArgumentException
999 public static function encodePath($path)
1001 if (! is_string($path)) {
1002 throw new Exception\
InvalidArgumentException(sprintf(
1003 'Expecting a string, got %s',
1004 (is_object($path) ?
get_class($path) : gettype($path))
1008 $regex = '/(?:[^' . self
::CHAR_UNRESERVED
. ')(:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/';
1009 $escaper = static::getEscaper();
1010 $replace = function ($match) use ($escaper) {
1011 return $escaper->escapeUrl($match[0]);
1014 return preg_replace_callback($regex, $replace, $path);
1018 * URL-encode a query string or fragment based on RFC-3986 guidelines.
1020 * Note that query and fragment encoding allows more unencoded characters
1021 * than the usual rawurlencode() function would usually return - for example
1022 * '/' and ':' are allowed as literals.
1024 * @param string $input
1026 * @throws Exception\InvalidArgumentException
1028 public static function encodeQueryFragment($input)
1030 if (! is_string($input)) {
1031 throw new Exception\
InvalidArgumentException(sprintf(
1032 'Expecting a string, got %s',
1033 (is_object($input) ?
get_class($input) : gettype($input))
1037 $regex = '/(?:[^' . self
::CHAR_UNRESERVED
. self
::CHAR_SUB_DELIMS
. '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/';
1038 $escaper = static::getEscaper();
1039 $replace = function ($match) use ($escaper) {
1040 return $escaper->escapeUrl($match[0]);
1043 return preg_replace_callback($regex, $replace, $input);
1047 * Extract only the scheme part out of a URI string.
1049 * This is used by the parse() method, but is useful as a standalone public
1050 * method if one wants to test a URI string for it's scheme before doing
1053 * Will return the scheme if found, or NULL if no scheme found (URI may
1054 * still be valid, but not full)
1056 * @param string $uriString
1057 * @throws Exception\InvalidArgumentException
1058 * @return string|null
1060 public static function parseScheme($uriString)
1062 if (! is_string($uriString)) {
1063 throw new Exception\
InvalidArgumentException(sprintf(
1064 'Expecting a string, got %s',
1065 (is_object($uriString) ?
get_class($uriString) : gettype($uriString))
1069 if (preg_match('/^([A-Za-z][A-Za-z0-9\.\+\-]*):/', $uriString, $match)) {
1077 * Remove any extra dot segments (/../, /./) from a path
1079 * Algorithm is adapted from RFC-3986 section 5.2.4
1080 * (@link http://tools.ietf.org/html/rfc3986#section-5.2.4)
1082 * @todo consider optimizing
1084 * @param string $path
1087 public static function removePathDotSegments($path)
1092 if ($path == '..' ||
$path == '.') {
1097 case ($path == '/.'):
1100 case ($path == '/..'):
1102 $lastSlashPos = strrpos($output, '/', -1);
1103 if (false === $lastSlashPos) {
1106 $output = substr($output, 0, $lastSlashPos);
1108 case (substr($path, 0, 4) == '/../'):
1109 $path = '/' . substr($path, 4);
1110 $lastSlashPos = strrpos($output, '/', -1);
1111 if (false === $lastSlashPos) {
1114 $output = substr($output, 0, $lastSlashPos);
1116 case (substr($path, 0, 3) == '/./'):
1117 $path = substr($path, 2);
1119 case (substr($path, 0, 2) == './'):
1120 $path = substr($path, 2);
1122 case (substr($path, 0, 3) == '../'):
1123 $path = substr($path, 3);
1126 $slash = strpos($path, '/', 1);
1127 if ($slash === false) {
1130 $seg = substr($path, 0, $slash);
1134 $path = substr($path, strlen($seg));
1143 * Merge a base URI and a relative URI into a new URI object
1145 * This convenience method wraps ::resolve() to allow users to quickly
1146 * create new absolute URLs without the need to instantiate and clone
1149 * If objects are passed in, none of the passed objects will be modified.
1151 * @param Uri|string $baseUri
1152 * @param Uri|string $relativeUri
1155 public static function merge($baseUri, $relativeUri)
1157 $uri = new static($relativeUri);
1158 return $uri->resolve($baseUri);
1162 * Check if a host name is a valid IP address, depending on allowed IP address types
1164 * @param string $host
1165 * @param int $allowed allowed address types
1168 protected static function isValidIpAddress($host, $allowed)
1170 $validatorParams = [
1171 'allowipv4' => (bool) ($allowed & self
::HOST_IPV4
),
1172 'allowipv6' => false,
1173 'allowipvfuture' => false,
1174 'allowliteral' => false,
1178 $validator = new Validator\
Ip($validatorParams);
1179 $return = $validator->isValid($host);
1184 // IPv6 & IPvLiteral must be in literal format
1185 $validatorParams = [
1186 'allowipv4' => false,
1187 'allowipv6' => (bool) ($allowed & self
::HOST_IPV6
),
1188 'allowipvfuture' => (bool) ($allowed & self
::HOST_IPVFUTURE
),
1189 'allowliteral' => true,
1191 static $regex = '/^\[.*\]$/';
1192 $validator->setOptions($validatorParams);
1193 return (preg_match($regex, $host) && $validator->isValid($host));
1197 * Check if an address is a valid DNS hostname
1199 * @param string $host
1202 protected static function isValidDnsHostname($host)
1204 $validator = new Validator\
Hostname([
1205 'allow' => Validator\Hostname
::ALLOW_DNS | Validator\Hostname
::ALLOW_LOCAL
,
1208 return $validator->isValid($host);
1212 * Check if an address is a valid registered name (as defined by RFC-3986) address
1214 * @param string $host
1217 protected static function isValidRegName($host)
1219 $regex = '/^(?:[' . self
::CHAR_UNRESERVED
. self
::CHAR_SUB_DELIMS
. ':@\/\?]+|%[A-Fa-f0-9]{2})+$/';
1220 return (bool) preg_match($regex, $host);
1224 * Part normalization methods
1226 * These are called by normalize() using static::_normalize*() so they may
1227 * be extended or overridden by extending classes to implement additional
1228 * scheme specific normalization rules
1232 * Normalize the scheme
1234 * Usually this means simply converting the scheme to lower case
1236 * @param string $scheme
1239 protected static function normalizeScheme($scheme)
1241 return strtolower($scheme);
1245 * Normalize the host part
1247 * By default this converts host names to lower case
1249 * @param string $host
1252 protected static function normalizeHost($host)
1254 return strtolower($host);
1258 * Normalize the port
1260 * If the class defines a default port for the current scheme, and the
1261 * current port is default, it will be unset.
1264 * @param string $scheme
1267 protected static function normalizePort($port, $scheme = null)
1270 && isset(static::$defaultPorts[$scheme])
1271 && ($port == static::$defaultPorts[$scheme])
1280 * Normalize the path
1282 * This involves removing redundant dot segments, decoding any over-encoded
1283 * characters and encoding everything that needs to be encoded and is not
1285 * @param string $path
1288 protected static function normalizePath($path)
1290 $path = self
::encodePath(
1291 self
::decodeUrlEncodedChars(
1292 self
::removePathDotSegments($path),
1293 '/[' . self
::CHAR_UNRESERVED
. ':@&=\+\$,\/;%]/'
1301 * Normalize the query part
1303 * This involves decoding everything that doesn't need to be encoded, and
1304 * encoding everything else
1306 * @param string $query
1309 protected static function normalizeQuery($query)
1311 $query = self
::encodeQueryFragment(
1312 self
::decodeUrlEncodedChars(
1314 '/[' . self
::CHAR_UNRESERVED
. self
::CHAR_QUERY_DELIMS
. ':@\/\?]/'
1322 * Normalize the fragment part
1324 * Currently this is exactly the same as normalizeQuery().
1326 * @param string $fragment
1329 protected static function normalizeFragment($fragment)
1331 $fragment = self
::encodeQueryFragment(
1332 self
::decodeUrlEncodedChars(
1334 '/[' . self
::CHAR_UNRESERVED
. self
::CHAR_SUB_DELIMS
. '%:@\/\?]/'
1342 * Decode all percent encoded characters which are allowed to be represented literally
1344 * Will not decode any characters which are not listed in the 'allowed' list
1346 * @param string $input
1347 * @param string $allowed Pattern of allowed characters
1350 protected static function decodeUrlEncodedChars($input, $allowed = '')
1352 $decodeCb = function ($match) use ($allowed) {
1353 $char = rawurldecode($match[0]);
1354 if (preg_match($allowed, $char)) {
1357 return strtoupper($match[0]);
1360 return preg_replace_callback('/%[A-Fa-f0-9]{2}/', $decodeCb, $input);