3 * Net_URL2, a class representing a URL as per RFC 3986.
9 * Copyright (c) 2007-2009, Peytz & Co. A/S
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
16 * * Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * * Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in
20 * the documentation and/or other materials provided with the distribution.
21 * * Neither the name of the Net_URL2 nor the names of its contributors may
22 * be used to endorse or promote products derived from this software
23 * without specific prior written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
26 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
27 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
31 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
32 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
33 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 * @category Networking
39 * @author Christian Schmidt <schmidt@php.net>
40 * @copyright 2007-2009 Peytz & Co. A/S
41 * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause
43 * @link https://tools.ietf.org/html/rfc3986
47 * Represents a URL as per RFC 3986.
49 * @category Networking
51 * @author Christian Schmidt <schmidt@php.net>
52 * @copyright 2007-2009 Peytz & Co. A/S
53 * @license https://spdx.org/licenses/BSD-3-Clause BSD-3-Clause
54 * @version Release: 2.1.2
55 * @link https://pear.php.net/package/Net_URL2
60 * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
63 const OPTION_STRICT = 'strict';
66 * Represent arrays in query using PHP's [] notation. Default is true.
68 const OPTION_USE_BRACKETS = 'use_brackets';
71 * Drop zero-based integer sequences in query using PHP's [] notation. Default
74 const OPTION_DROP_SEQUENCE = 'drop_sequence';
77 * URL-encode query variable keys. Default is true.
79 const OPTION_ENCODE_KEYS = 'encode_keys';
82 * Query variable separators when parsing the query string. Every character
83 * is considered a separator. Default is "&".
85 const OPTION_SEPARATOR_INPUT = 'input_separator';
88 * Query variable separator used when generating the query string. Default
91 const OPTION_SEPARATOR_OUTPUT = 'output_separator';
94 * Default options corresponds to how PHP handles $_GET.
96 private $_options = array(
97 self::OPTION_STRICT => true,
98 self::OPTION_USE_BRACKETS => true,
99 self::OPTION_DROP_SEQUENCE => true,
100 self::OPTION_ENCODE_KEYS => true,
101 self::OPTION_SEPARATOR_INPUT => '&',
102 self::OPTION_SEPARATOR_OUTPUT => '&',
108 private $_scheme = false;
113 private $_userinfo = false;
118 private $_host = false;
123 private $_port = false;
133 private $_query = false;
138 private $_fragment = false;
143 * @param string $url an absolute or relative URL
144 * @param array $options an array of OPTION_xxx constants
146 * @uses self::parseUrl()
148 public function __construct($url, array $options = array())
150 foreach ($options as $optionName => $value) {
151 if (array_key_exists($optionName, $this->_options)) {
152 $this->_options[$optionName] = $value;
156 $this->parseUrl($url);
162 * This method will magically set the value of a private variable ($var)
163 * with the value passed as the args
165 * @param string $var The private variable to set.
166 * @param mixed $arg An argument of any type.
170 public function __set($var, $arg)
172 $method = 'set' . $var;
173 if (method_exists($this, $method)) {
174 $this->$method($arg);
181 * This is the magic get method to retrieve the private variable
182 * that was set by either __set() or it's setter...
184 * @param string $var The property name to retrieve.
186 * @return mixed $this->$var Either a boolean false if the
187 * property is not set or the value
188 * of the private property.
190 public function __get($var)
192 $method = 'get' . $var;
193 if (method_exists($this, $method)) {
194 return $this->$method();
201 * Returns the scheme, e.g. "http" or "urn", or false if there is no
202 * scheme specified, i.e. if this is a relative URL.
204 * @return string|bool
206 public function getScheme()
208 return $this->_scheme;
212 * Sets the scheme, e.g. "http" or "urn". Specify false if there is no
213 * scheme specified, i.e. if this is a relative URL.
215 * @param string|bool $scheme e.g. "http" or "urn", or false if there is no
216 * scheme specified, i.e. if this is a relative
222 public function setScheme($scheme)
224 $this->_scheme = $scheme;
229 * Returns the user part of the userinfo part (the part preceding the first
230 * ":"), or false if there is no userinfo part.
232 * @return string|bool
234 public function getUser()
236 return $this->_userinfo !== false
237 ? preg_replace('(:.*$)', '', $this->_userinfo)
242 * Returns the password part of the userinfo part (the part after the first
243 * ":"), or false if there is no userinfo part (i.e. the URL does not
244 * contain "@" in front of the hostname) or the userinfo part does not
247 * @return string|bool
249 public function getPassword()
251 return $this->_userinfo !== false
252 ? substr(strstr($this->_userinfo, ':'), 1)
257 * Returns the userinfo part, or false if there is none, i.e. if the
258 * authority part does not contain "@".
260 * @return string|bool
262 public function getUserinfo()
264 return $this->_userinfo;
268 * Sets the userinfo part. If two arguments are passed, they are combined
269 * in the userinfo part as username ":" password.
271 * @param string|bool $userinfo userinfo or username
272 * @param string|bool $password optional password, or false
276 public function setUserinfo($userinfo, $password = false)
278 if ($password !== false) {
279 $userinfo .= ':' . $password;
282 if ($userinfo !== false) {
283 $userinfo = $this->_encodeData($userinfo);
286 $this->_userinfo = $userinfo;
291 * Returns the host part, or false if there is no authority part, e.g.
294 * @return string|bool a hostname, an IP address, or false
296 public function getHost()
302 * Sets the host part. Specify false if there is no authority part, e.g.
305 * @param string|bool $host a hostname, an IP address, or false
309 public function setHost($host)
311 $this->_host = $host;
316 * Returns the port number, or false if there is no port number specified,
317 * i.e. if the default port is to be used.
319 * @return string|bool
321 public function getPort()
327 * Sets the port number. Specify false if there is no port number specified,
328 * i.e. if the default port is to be used.
330 * @param string|bool $port a port number, or false
334 public function setPort($port)
336 $this->_port = $port;
341 * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
342 * false if there is no authority.
344 * @return string|bool
346 public function getAuthority()
348 if (false === $this->_host) {
354 if (strlen($this->_userinfo)) {
355 $authority .= $this->_userinfo . '@';
358 $authority .= $this->_host;
360 if ($this->_port !== false) {
361 $authority .= ':' . $this->_port;
368 * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
369 * false if there is no authority.
371 * @param string|bool $authority a hostname or an IP address, possibly
372 * with userinfo prefixed and port number
373 * appended, e.g. "foo:bar@example.org:81".
377 public function setAuthority($authority)
379 $this->_userinfo = false;
380 $this->_host = false;
381 $this->_port = false;
383 if ('' === $authority) {
384 $this->_host = $authority;
388 if (!preg_match('(^(([^@]*)@)?(.+?)(:(\d*))?$)', $authority, $matches)) {
393 $this->_userinfo = $this->_encodeData($matches[2]);
396 $this->_host = $matches[3];
398 if (isset($matches[5]) && strlen($matches[5])) {
399 $this->_port = $matches[5];
405 * Returns the path part (possibly an empty string).
409 public function getPath()
415 * Sets the path part (possibly an empty string).
417 * @param string $path a path
421 public function setPath($path)
423 $this->_path = $path;
428 * Returns the query string (excluding the leading "?"), or false if "?"
429 * is not present in the URL.
431 * @return string|bool
432 * @see getQueryVariables
434 public function getQuery()
436 return $this->_query;
440 * Sets the query string (excluding the leading "?"). Specify false if "?"
441 * is not present in the URL.
443 * @param string|bool $query a query string, e.g. "foo=1&bar=2"
446 * @see setQueryVariables
448 public function setQuery($query)
450 $this->_query = $query;
455 * Returns the fragment name, or false if "#" is not present in the URL.
457 * @return string|bool
459 public function getFragment()
461 return $this->_fragment;
465 * Sets the fragment name. Specify false if "#" is not present in the URL.
467 * @param string|bool $fragment a fragment excluding the leading "#", or
472 public function setFragment($fragment)
474 $this->_fragment = $fragment;
479 * Returns the query string like an array as the variables would appear in
480 * $_GET in a PHP script. If the URL does not contain a "?", an empty array
485 public function getQueryVariables()
487 $separator = $this->getOption(self::OPTION_SEPARATOR_INPUT);
488 $encodeKeys = $this->getOption(self::OPTION_ENCODE_KEYS);
489 $useBrackets = $this->getOption(self::OPTION_USE_BRACKETS);
493 for ($part = strtok($this->_query, $separator);
495 $part = strtok($separator)
497 list($key, $value) = explode('=', $part, 2) + array(1 => '');
500 $key = rawurldecode($key);
502 $value = rawurldecode($value);
505 $return = $this->_queryArrayByKey($key, $value, $return);
507 if (isset($return[$key])) {
508 $return[$key] = (array) $return[$key];
509 $return[$key][] = $value;
511 $return[$key] = $value;
520 * Parse a single query key=value pair into an existing php array
522 * @param string $key query-key
523 * @param string $value query-value
524 * @param array $array of existing query variables (if any)
528 private function _queryArrayByKey($key, $value, array $array = array())
534 $offset = $this->_queryKeyBracketOffset($key);
535 if ($offset === false) {
538 $name = substr($key, 0, $offset);
541 if (!strlen($name)) {
547 $array[$name] = $value;
550 $brackets = substr($key, $offset);
551 if (!isset($array[$name])) {
552 $array[$name] = null;
554 $array[$name] = $this->_queryArrayByBrackets(
555 $brackets, $value, $array[$name]
563 * Parse a key-buffer to place value in array
565 * @param string $buffer to consume all keys from
566 * @param string $value to be set/add
567 * @param array $array to traverse and set/add value in
572 private function _queryArrayByBrackets($buffer, $value, array $array = null)
576 for ($iteration = 0; strlen($buffer); $iteration++) {
577 $open = $this->_queryKeyBracketOffset($buffer);
579 // Opening bracket [ must exist at offset 0, if not, there is
580 // no bracket to parse and the value dropped.
581 // if this happens in the first iteration, this is flawed, see
582 // as well the second exception below.
586 // @codeCoverageIgnoreStart
588 'Net_URL2 Internal Error: '. __METHOD__ .'(): ' .
589 'Opening bracket [ must exist at offset 0'
591 // @codeCoverageIgnoreEnd
594 $close = strpos($buffer, ']', 1);
596 // this error condition should never be reached as this is a
597 // private method and bracket pairs are checked beforehand.
598 // See as well the first exception for the opening bracket.
599 // @codeCoverageIgnoreStart
601 'Net_URL2 Internal Error: '. __METHOD__ .'(): ' .
602 'Closing bracket ] must exist, not found'
604 // @codeCoverageIgnoreEnd
607 $index = substr($buffer, 1, $close - 1);
608 if (strlen($index)) {
609 $entry = &$entry[$index];
611 if (!is_array($entry)) {
618 $buffer = substr($buffer, $close + 1);
627 * Query-key has brackets ("...[]")
629 * @param string $key query-key
631 * @return bool|int offset of opening bracket, false if no brackets
633 private function _queryKeyBracketOffset($key)
635 if (false !== $open = strpos($key, '[')
636 and false === strpos($key, ']', $open + 1)
645 * Sets the query string to the specified variable in the query string.
647 * @param array $array (name => value) array
651 public function setQueryVariables(array $array)
654 $this->_query = false;
656 $this->_query = $this->buildQuery(
658 $this->getOption(self::OPTION_SEPARATOR_OUTPUT)
665 * Sets the specified variable in the query string.
667 * @param string $name variable name
668 * @param mixed $value variable value
672 public function setQueryVariable($name, $value)
674 $array = $this->getQueryVariables();
675 $array[$name] = $value;
676 $this->setQueryVariables($array);
681 * Removes the specified variable from the query string.
683 * @param string $name a query string variable, e.g. "foo" in "?foo=1"
687 public function unsetQueryVariable($name)
689 $array = $this->getQueryVariables();
690 unset($array[$name]);
691 $this->setQueryVariables($array);
695 * Returns a string representation of this URL.
699 public function getURL()
701 // See RFC 3986, section 5.3
704 if ($this->_scheme !== false) {
705 $url .= $this->_scheme . ':';
708 $authority = $this->getAuthority();
709 if ($authority === false && strtolower($this->_scheme) === 'file') {
713 $url .= $this->_buildAuthorityAndPath($authority, $this->_path);
715 if ($this->_query !== false) {
716 $url .= '?' . $this->_query;
719 if ($this->_fragment !== false) {
720 $url .= '#' . $this->_fragment;
727 * Put authority and path together, wrapping authority
728 * into proper separators/terminators.
730 * @param string|bool $authority authority
731 * @param string $path path
735 private function _buildAuthorityAndPath($authority, $path)
737 if ($authority === false) {
741 $terminator = ($path !== '' && $path[0] !== '/') ? '/' : '';
743 return '//' . $authority . $terminator . $path;
747 * Returns a string representation of this URL.
750 * @link https://php.net/language.oop5.magic#object.tostring
752 public function __toString()
754 return $this->getURL();
758 * Returns a normalized string representation of this URL. This is useful
759 * for comparison of URLs.
763 public function getNormalizedURL()
767 return $url->getURL();
773 * See RFC 3986, Section 6. Normalization and Comparison
775 * @link https://tools.ietf.org/html/rfc3986#section-6
779 public function normalize()
781 // See RFC 3986, section 6
783 // Scheme is case-insensitive
784 if ($this->_scheme) {
785 $this->_scheme = strtolower($this->_scheme);
788 // Hostname is case-insensitive
790 $this->_host = strtolower($this->_host);
793 // Remove default port number for known schemes (RFC 3986, section 6.2.3)
794 if ('' === $this->_port
797 && $this->_port == getservbyname($this->_scheme, 'tcp')
799 $this->_port = false;
802 // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
803 // Normalize percentage-encoded unreserved characters (section 6.2.2.2)
804 $fields = array(&$this->_userinfo, &$this->_host, &$this->_path,
805 &$this->_query, &$this->_fragment);
806 foreach ($fields as &$field) {
807 if ($field !== false) {
808 $field = $this->_normalize("$field");
813 // Path segment normalization (RFC 3986, section 6.2.2.3)
814 $this->_path = self::removeDotSegments($this->_path);
816 // Scheme based normalization (RFC 3986, section 6.2.3)
817 if (false !== $this->_host && '' === $this->_path) {
821 // path should start with '/' if there is authority (section 3.3.)
822 if (strlen($this->getAuthority())
823 && strlen($this->_path)
824 && $this->_path[0] !== '/'
826 $this->_path = '/' . $this->_path;
831 * Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
832 * Normalize percentage-encoded unreserved characters (section 6.2.2.2)
834 * @param string|array $mixed string or array of strings to normalize
836 * @return string|array
838 * @see _normalizeCallback()
840 private function _normalize($mixed)
842 return preg_replace_callback(
843 '((?:%[0-9a-fA-Z]{2})+)', array($this, '_normalizeCallback'),
849 * Callback for _normalize() of %XX percentage-encodings
851 * @param array $matches as by preg_replace_callback
856 * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
858 private function _normalizeCallback($matches)
860 return self::urlencode(urldecode($matches[0]));
864 * Returns whether this instance represents an absolute URL.
868 public function isAbsolute()
870 return (bool) $this->_scheme;
874 * Returns an Net_URL2 instance representing an absolute URL relative to
877 * @param Net_URL2|string $reference relative URL
882 public function resolve($reference)
884 if (!$reference instanceof Net_URL2) {
885 $reference = new self($reference);
887 if (!$reference->_isFragmentOnly() && !$this->isAbsolute()) {
889 'Base-URL must be absolute if reference is not fragment-only'
893 // A non-strict parser may ignore a scheme in the reference if it is
894 // identical to the base URI's scheme.
895 if (!$this->getOption(self::OPTION_STRICT)
896 && $reference->_scheme == $this->_scheme
898 $reference->_scheme = false;
901 $target = new self('');
902 if ($reference->_scheme !== false) {
903 $target->_scheme = $reference->_scheme;
904 $target->setAuthority($reference->getAuthority());
905 $target->_path = self::removeDotSegments($reference->_path);
906 $target->_query = $reference->_query;
908 $authority = $reference->getAuthority();
909 if ($authority !== false) {
910 $target->setAuthority($authority);
911 $target->_path = self::removeDotSegments($reference->_path);
912 $target->_query = $reference->_query;
914 if ($reference->_path == '') {
915 $target->_path = $this->_path;
916 if ($reference->_query !== false) {
917 $target->_query = $reference->_query;
919 $target->_query = $this->_query;
922 if (substr($reference->_path, 0, 1) == '/') {
923 $target->_path = self::removeDotSegments($reference->_path);
925 // Merge paths (RFC 3986, section 5.2.3)
926 if ($this->_host !== false && $this->_path == '') {
927 $target->_path = '/' . $reference->_path;
929 $i = strrpos($this->_path, '/');
931 $target->_path = substr($this->_path, 0, $i + 1);
933 $target->_path .= $reference->_path;
935 $target->_path = self::removeDotSegments($target->_path);
937 $target->_query = $reference->_query;
939 $target->setAuthority($this->getAuthority());
941 $target->_scheme = $this->_scheme;
944 $target->_fragment = $reference->_fragment;
950 * URL is fragment-only
952 * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
955 private function _isFragmentOnly()
958 $this->_fragment !== false
959 && $this->_query === false
960 && $this->_path === ''
961 && $this->_port === false
962 && $this->_host === false
963 && $this->_userinfo === false
964 && $this->_scheme === false
969 * Removes dots as described in RFC 3986, section 5.2.4, e.g.
970 * "/foo/../bar/baz" => "/bar/baz"
972 * @param string $path a path
974 * @return string a path
976 public static function removeDotSegments($path)
978 $path = (string) $path;
981 // Make sure not to be trapped in an infinite loop due to a bug in this
985 while ('' !== $path && $j++ < $loopLimit) {
986 if (substr($path, 0, 2) === './') {
988 $path = substr($path, 2);
989 } elseif (substr($path, 0, 3) === '../') {
991 $path = substr($path, 3);
992 } elseif (substr($path, 0, 3) === '/./' || $path === '/.') {
994 $path = '/' . substr($path, 3);
995 } elseif (substr($path, 0, 4) === '/../' || $path === '/..') {
997 $path = '/' . substr($path, 4);
998 $i = strrpos($output, '/');
999 $output = $i === false ? '' : substr($output, 0, $i);
1000 } elseif ($path === '.' || $path === '..') {
1005 $i = strpos($path, '/', $path[0] === '/');
1011 $output .= substr($path, 0, $i);
1012 $path = substr($path, $i);
1018 'Unable to remove dot segments; hit loop limit %d (left: %s)',
1019 $j, var_export($path, true)
1021 trigger_error($message, E_USER_WARNING);
1028 * Percent-encodes all non-alphanumeric characters except these: _ . - ~
1029 * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
1030 * 5.2.x and earlier.
1032 * @param string $string string to encode
1036 public static function urlencode($string)
1038 $encoded = rawurlencode($string);
1040 // This is only necessary in PHP < 5.3.
1041 $encoded = str_replace('%7E', '~', $encoded);
1046 * Returns a Net_URL2 instance representing the canonical URL of the
1047 * currently executing PHP script.
1052 public static function getCanonical()
1054 if (!isset($_SERVER['REQUEST_METHOD'])) {
1055 // ALERT - no current URL
1056 throw new Exception('Script was not called through a webserver');
1059 // Begin with a relative URL
1060 $url = new self($_SERVER['PHP_SELF']);
1061 $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
1062 $url->_host = $_SERVER['SERVER_NAME'];
1063 $port = $_SERVER['SERVER_PORT'];
1064 if ($url->_scheme == 'http' && $port != 80
1065 || $url->_scheme == 'https' && $port != 443
1067 $url->_port = $port;
1073 * Returns the URL used to retrieve the current request.
1077 public static function getRequestedURL()
1079 return self::getRequested()->getUrl();
1083 * Returns a Net_URL2 instance representing the URL used to retrieve the
1089 public static function getRequested()
1091 if (!isset($_SERVER['REQUEST_METHOD'])) {
1092 // ALERT - no current URL
1093 throw new Exception('Script was not called through a webserver');
1096 // Begin with a relative URL
1097 $url = new self($_SERVER['REQUEST_URI']);
1098 $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
1099 // Set host and possibly port
1100 $url->setAuthority($_SERVER['HTTP_HOST']);
1105 * Returns the value of the specified option.
1107 * @param string $optionName The name of the option to retrieve
1111 public function getOption($optionName)
1113 return isset($this->_options[$optionName])
1114 ? $this->_options[$optionName] : false;
1118 * A simple version of http_build_query in userland. The encoded string is
1119 * percentage encoded according to RFC 3986.
1121 * @param array $data An array, which has to be converted into
1122 * QUERY_STRING. Anything is possible.
1123 * @param string $separator Separator {@link self::OPTION_SEPARATOR_OUTPUT}
1124 * @param string $key For stacked values (arrays in an array).
1128 protected function buildQuery(array $data, $separator, $key = null)
1132 $this->_options[self::OPTION_DROP_SEQUENCE] === true
1133 && array_keys($data) === array_keys(array_values($data))
1135 foreach ($data as $name => $value) {
1136 if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) {
1137 $name = rawurlencode($name);
1139 if ($key !== null) {
1140 if ($this->getOption(self::OPTION_USE_BRACKETS) === true) {
1141 $drop_names && $name = '';
1142 $name = $key . '[' . $name . ']';
1147 if (is_array($value)) {
1148 $query[] = $this->buildQuery($value, $separator, $name);
1150 $query[] = $name . '=' . rawurlencode($value);
1153 return implode($separator, $query);
1157 * This method uses a regex to parse the url into the designated parts.
1159 * @param string $url URL
1162 * @uses self::$_scheme, self::setAuthority(), self::$_path, self::$_query,
1166 protected function parseUrl($url)
1168 // The regular expression is copied verbatim from RFC 3986, appendix B.
1169 // The expression does not validate the URL but matches any string.
1171 '(^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)',
1175 // "path" is always present (possibly as an empty string); the rest
1177 $this->_scheme = !empty($matches[1]) ? $matches[2] : false;
1178 $this->setAuthority(!empty($matches[3]) ? $matches[4] : false);
1179 $this->_path = $this->_encodeData($matches[5]);
1180 $this->_query = !empty($matches[6])
1181 ? $this->_encodeData($matches[7])
1184 $this->_fragment = !empty($matches[8]) ? $matches[9] : false;
1188 * Encode characters that might have been forgotten to encode when passing
1189 * in an URL. Applied onto Userinfo, Path and Query.
1191 * @param string $url URL
1196 * @link https://pear.php.net/bugs/bug.php?id=20425
1198 private function _encodeData($url)
1200 return preg_replace_callback(
1201 '([\x-\x20\x22\x3C\x3E\x7F-\xFF]+)',
1202 array($this, '_encodeCallback'), $url
1207 * callback for encoding character data
1209 * @param array $matches Matches
1213 * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
1215 private function _encodeCallback(array $matches)
1217 return rawurlencode($matches[0]);