1 : <?php
2 : /**
3 : * Net_URL2, a class representing a URL as per RFC 3986.
4 : *
5 : * PHP version 5
6 : *
7 : * LICENSE:
8 : *
9 : * Copyright (c) 2007-2009, Peytz & Co. A/S
10 : * All rights reserved.
11 : *
12 : * Redistribution and use in source and binary forms, with or without
13 : * modification, are permitted provided that the following conditions
14 : * are met:
15 : *
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.
24 : *
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.
36 : *
37 : * @category Networking
38 : * @package Net_URL2
39 : * @author Christian Schmidt <schmidt@php.net>
40 : * @copyright 2007-2009 Peytz & Co. A/S
41 : * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
42 : * @version CVS: $Id: URL2.php 309223 2011-03-14 14:26:32Z till $
43 : * @link http://www.rfc-editor.org/rfc/rfc3986.txt
44 : */
45 :
46 : /**
47 : * Represents a URL as per RFC 3986.
48 : *
49 : * @category Networking
50 : * @package Net_URL2
51 : * @author Christian Schmidt <schmidt@php.net>
52 : * @copyright 2007-2009 Peytz & Co. A/S
53 : * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
54 : * @version Release: @package_version@
55 : * @link http://pear.php.net/package/Net_URL2
56 : */
57 : class Net_URL2
58 : {
59 : /**
60 : * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
61 : * is true.
62 : */
63 : const OPTION_STRICT = 'strict';
64 :
65 : /**
66 : * Represent arrays in query using PHP's [] notation. Default is true.
67 : */
68 : const OPTION_USE_BRACKETS = 'use_brackets';
69 :
70 : /**
71 : * URL-encode query variable keys. Default is true.
72 : */
73 : const OPTION_ENCODE_KEYS = 'encode_keys';
74 :
75 : /**
76 : * Query variable separators when parsing the query string. Every character
77 : * is considered a separator. Default is "&".
78 : */
79 : const OPTION_SEPARATOR_INPUT = 'input_separator';
80 :
81 : /**
82 : * Query variable separator used when generating the query string. Default
83 : * is "&".
84 : */
85 : const OPTION_SEPARATOR_OUTPUT = 'output_separator';
86 :
87 : /**
88 : * Default options corresponds to how PHP handles $_GET.
89 : */
90 : private $_options = array(
91 : self::OPTION_STRICT => true,
92 : self::OPTION_USE_BRACKETS => true,
93 : self::OPTION_ENCODE_KEYS => true,
94 : self::OPTION_SEPARATOR_INPUT => '&',
95 : self::OPTION_SEPARATOR_OUTPUT => '&',
96 : );
97 :
98 : /**
99 : * @var string|bool
100 : */
101 : private $_scheme = false;
102 :
103 : /**
104 : * @var string|bool
105 : */
106 : private $_userinfo = false;
107 :
108 : /**
109 : * @var string|bool
110 : */
111 : private $_host = false;
112 :
113 : /**
114 : * @var string|bool
115 : */
116 : private $_port = false;
117 :
118 : /**
119 : * @var string
120 : */
121 : private $_path = '';
122 :
123 : /**
124 : * @var string|bool
125 : */
126 : private $_query = false;
127 :
128 : /**
129 : * @var string|bool
130 : */
131 : private $_fragment = false;
132 :
133 : /**
134 : * Constructor.
135 : *
136 : * @param string $url an absolute or relative URL
137 : * @param array $options an array of OPTION_xxx constants
138 : *
139 : * @return $this
140 : * @uses self::parseUrl()
141 : */
142 : public function __construct($url, array $options = array())
143 : {
144 10 : foreach ($options as $optionName => $value) {
145 10 : if (array_key_exists($optionName, $this->_options)) {
146 10 : $this->_options[$optionName] = $value;
147 10 : }
148 10 : }
149 :
150 10 : $this->parseUrl($url);
151 10 : }
152 :
153 : /**
154 : * Magic Setter.
155 : *
156 : * This method will magically set the value of a private variable ($var)
157 : * with the value passed as the args
158 : *
159 : * @param string $var The private variable to set.
160 : * @param mixed $arg An argument of any type.
161 : * @return void
162 : */
163 : public function __set($var, $arg)
164 : {
165 0 : $method = 'set' . $var;
166 0 : if (method_exists($this, $method)) {
167 0 : $this->$method($arg);
168 0 : }
169 0 : }
170 :
171 : /**
172 : * Magic Getter.
173 : *
174 : * This is the magic get method to retrieve the private variable
175 : * that was set by either __set() or it's setter...
176 : *
177 : * @param string $var The property name to retrieve.
178 : * @return mixed $this->$var Either a boolean false if the
179 : * property is not set or the value
180 : * of the private property.
181 : */
182 : public function __get($var)
183 : {
184 0 : $method = 'get' . $var;
185 0 : if (method_exists($this, $method)) {
186 0 : return $this->$method();
187 : }
188 :
189 0 : return false;
190 : }
191 :
192 : /**
193 : * Returns the scheme, e.g. "http" or "urn", or false if there is no
194 : * scheme specified, i.e. if this is a relative URL.
195 : *
196 : * @return string|bool
197 : */
198 : public function getScheme()
199 : {
200 10 : return $this->_scheme;
201 : }
202 :
203 : /**
204 : * Sets the scheme, e.g. "http" or "urn". Specify false if there is no
205 : * scheme specified, i.e. if this is a relative URL.
206 : *
207 : * @param string|bool $scheme e.g. "http" or "urn", or false if there is no
208 : * scheme specified, i.e. if this is a relative
209 : * URL
210 : *
211 : * @return $this
212 : * @see getScheme()
213 : */
214 : public function setScheme($scheme)
215 : {
216 0 : $this->_scheme = $scheme;
217 0 : return $this;
218 : }
219 :
220 : /**
221 : * Returns the user part of the userinfo part (the part preceding the first
222 : * ":"), or false if there is no userinfo part.
223 : *
224 : * @return string|bool
225 : */
226 : public function getUser()
227 : {
228 0 : return $this->_userinfo !== false
229 0 : ? preg_replace('@:.*$@', '', $this->_userinfo)
230 0 : : false;
231 : }
232 :
233 : /**
234 : * Returns the password part of the userinfo part (the part after the first
235 : * ":"), or false if there is no userinfo part (i.e. the URL does not
236 : * contain "@" in front of the hostname) or the userinfo part does not
237 : * contain ":".
238 : *
239 : * @return string|bool
240 : */
241 : public function getPassword()
242 : {
243 0 : return $this->_userinfo !== false
244 0 : ? substr(strstr($this->_userinfo, ':'), 1)
245 0 : : false;
246 : }
247 :
248 : /**
249 : * Returns the userinfo part, or false if there is none, i.e. if the
250 : * authority part does not contain "@".
251 : *
252 : * @return string|bool
253 : */
254 : public function getUserinfo()
255 : {
256 10 : return $this->_userinfo;
257 : }
258 :
259 : /**
260 : * Sets the userinfo part. If two arguments are passed, they are combined
261 : * in the userinfo part as username ":" password.
262 : *
263 : * @param string|bool $userinfo userinfo or username
264 : * @param string|bool $password optional password, or false
265 : *
266 : * @return $this
267 : */
268 : public function setUserinfo($userinfo, $password = false)
269 : {
270 0 : $this->_userinfo = $userinfo;
271 0 : if ($password !== false) {
272 0 : $this->_userinfo .= ':' . $password;
273 0 : }
274 0 : return $this;
275 : }
276 :
277 : /**
278 : * Returns the host part, or false if there is no authority part, e.g.
279 : * relative URLs.
280 : *
281 : * @return string|bool a hostname, an IP address, or false
282 : */
283 : public function getHost()
284 : {
285 0 : return $this->_host;
286 : }
287 :
288 : /**
289 : * Sets the host part. Specify false if there is no authority part, e.g.
290 : * relative URLs.
291 : *
292 : * @param string|bool $host a hostname, an IP address, or false
293 : *
294 : * @return $this
295 : */
296 : public function setHost($host)
297 : {
298 0 : $this->_host = $host;
299 0 : return $this;
300 : }
301 :
302 : /**
303 : * Returns the port number, or false if there is no port number specified,
304 : * i.e. if the default port is to be used.
305 : *
306 : * @return string|bool
307 : */
308 : public function getPort()
309 : {
310 0 : return $this->_port;
311 : }
312 :
313 : /**
314 : * Sets the port number. Specify false if there is no port number specified,
315 : * i.e. if the default port is to be used.
316 : *
317 : * @param string|bool $port a port number, or false
318 : *
319 : * @return $this
320 : */
321 : public function setPort($port)
322 : {
323 0 : $this->_port = $port;
324 0 : return $this;
325 : }
326 :
327 : /**
328 : * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
329 : * false if there is no authority.
330 : *
331 : * @return string|bool
332 : */
333 : public function getAuthority()
334 : {
335 0 : if (!$this->_host) {
336 0 : return false;
337 : }
338 :
339 0 : $authority = '';
340 :
341 0 : if ($this->_userinfo !== false) {
342 0 : $authority .= $this->_userinfo . '@';
343 0 : }
344 :
345 0 : $authority .= $this->_host;
346 :
347 0 : if ($this->_port !== false) {
348 0 : $authority .= ':' . $this->_port;
349 0 : }
350 :
351 0 : return $authority;
352 : }
353 :
354 : /**
355 : * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
356 : * false if there is no authority.
357 : *
358 : * @param string|false $authority a hostname or an IP addresse, possibly
359 : * with userinfo prefixed and port number
360 : * appended, e.g. "foo:bar@example.org:81".
361 : *
362 : * @return $this
363 : */
364 : public function setAuthority($authority)
365 : {
366 10 : $this->_userinfo = false;
367 10 : $this->_host = false;
368 10 : $this->_port = false;
369 10 : if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
370 10 : if ($reg[1]) {
371 0 : $this->_userinfo = $reg[2];
372 0 : }
373 :
374 10 : $this->_host = $reg[3];
375 10 : if (isset($reg[5])) {
376 0 : $this->_port = $reg[5];
377 0 : }
378 10 : }
379 10 : return $this;
380 : }
381 :
382 : /**
383 : * Returns the path part (possibly an empty string).
384 : *
385 : * @return string
386 : */
387 : public function getPath()
388 : {
389 10 : return $this->_path;
390 : }
391 :
392 : /**
393 : * Sets the path part (possibly an empty string).
394 : *
395 : * @param string $path a path
396 : *
397 : * @return $this
398 : */
399 : public function setPath($path)
400 : {
401 0 : $this->_path = $path;
402 0 : return $this;
403 : }
404 :
405 : /**
406 : * Returns the query string (excluding the leading "?"), or false if "?"
407 : * is not present in the URL.
408 : *
409 : * @return string|bool
410 : * @see self::getQueryVariables()
411 : */
412 : public function getQuery()
413 : {
414 0 : return $this->_query;
415 : }
416 :
417 : /**
418 : * Sets the query string (excluding the leading "?"). Specify false if "?"
419 : * is not present in the URL.
420 : *
421 : * @param string|bool $query a query string, e.g. "foo=1&bar=2"
422 : *
423 : * @return $this
424 : * @see self::setQueryVariables()
425 : */
426 : public function setQuery($query)
427 : {
428 0 : $this->_query = $query;
429 0 : return $this;
430 : }
431 :
432 : /**
433 : * Returns the fragment name, or false if "#" is not present in the URL.
434 : *
435 : * @return string|bool
436 : */
437 : public function getFragment()
438 : {
439 0 : return $this->_fragment;
440 : }
441 :
442 : /**
443 : * Sets the fragment name. Specify false if "#" is not present in the URL.
444 : *
445 : * @param string|bool $fragment a fragment excluding the leading "#", or
446 : * false
447 : *
448 : * @return $this
449 : */
450 : public function setFragment($fragment)
451 : {
452 0 : $this->_fragment = $fragment;
453 0 : return $this;
454 : }
455 :
456 : /**
457 : * Returns the query string like an array as the variables would appear in
458 : * $_GET in a PHP script. If the URL does not contain a "?", an empty array
459 : * is returned.
460 : *
461 : * @return array
462 : */
463 : public function getQueryVariables()
464 : {
465 : $pattern = '/[' .
466 0 : preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') .
467 0 : ']/';
468 0 : $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY);
469 0 : $return = array();
470 :
471 0 : foreach ($parts as $part) {
472 0 : if (strpos($part, '=') !== false) {
473 0 : list($key, $value) = explode('=', $part, 2);
474 0 : } else {
475 0 : $key = $part;
476 0 : $value = null;
477 : }
478 :
479 0 : if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
480 0 : $key = rawurldecode($key);
481 0 : }
482 0 : $value = rawurldecode($value);
483 :
484 0 : if ($this->getOption(self::OPTION_USE_BRACKETS) &&
485 0 : preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) {
486 :
487 0 : $key = $matches[1];
488 0 : $idx = $matches[2];
489 :
490 : // Ensure is an array
491 0 : if (empty($return[$key]) || !is_array($return[$key])) {
492 0 : $return[$key] = array();
493 0 : }
494 :
495 : // Add data
496 0 : if ($idx === '') {
497 0 : $return[$key][] = $value;
498 0 : } else {
499 0 : $return[$key][$idx] = $value;
500 : }
501 0 : } elseif (!$this->getOption(self::OPTION_USE_BRACKETS)
502 0 : && !empty($return[$key])
503 0 : ) {
504 0 : $return[$key] = (array) $return[$key];
505 0 : $return[$key][] = $value;
506 0 : } else {
507 0 : $return[$key] = $value;
508 : }
509 0 : }
510 :
511 0 : return $return;
512 : }
513 :
514 : /**
515 : * Sets the query string to the specified variable in the query string.
516 : *
517 : * @param array $array (name => value) array
518 : *
519 : * @return $this
520 : */
521 : public function setQueryVariables(array $array)
522 : {
523 0 : if (!$array) {
524 0 : $this->_query = false;
525 0 : } else {
526 0 : $this->_query = $this->buildQuery(
527 0 : $array,
528 0 : $this->getOption(self::OPTION_SEPARATOR_OUTPUT)
529 0 : );
530 : }
531 0 : return $this;
532 : }
533 :
534 : /**
535 : * Sets the specified variable in the query string.
536 : *
537 : * @param string $name variable name
538 : * @param mixed $value variable value
539 : *
540 : * @return $this
541 : */
542 : public function setQueryVariable($name, $value)
543 : {
544 0 : $array = $this->getQueryVariables();
545 0 : $array[$name] = $value;
546 0 : $this->setQueryVariables($array);
547 0 : return $this;
548 : }
549 :
550 : /**
551 : * Removes the specifed variable from the query string.
552 : *
553 : * @param string $name a query string variable, e.g. "foo" in "?foo=1"
554 : *
555 : * @return void
556 : */
557 : public function unsetQueryVariable($name)
558 : {
559 0 : $array = $this->getQueryVariables();
560 0 : unset($array[$name]);
561 0 : $this->setQueryVariables($array);
562 0 : }
563 :
564 : /**
565 : * Returns a string representation of this URL.
566 : *
567 : * @return string
568 : */
569 : public function getURL()
570 : {
571 : // See RFC 3986, section 5.3
572 0 : $url = "";
573 :
574 0 : if ($this->_scheme !== false) {
575 0 : $url .= $this->_scheme . ':';
576 0 : }
577 :
578 0 : $authority = $this->getAuthority();
579 0 : if ($authority !== false) {
580 0 : $url .= '//' . $authority;
581 0 : }
582 0 : $url .= $this->_path;
583 :
584 0 : if ($this->_query !== false) {
585 0 : $url .= '?' . $this->_query;
586 0 : }
587 :
588 0 : if ($this->_fragment !== false) {
589 0 : $url .= '#' . $this->_fragment;
590 0 : }
591 :
592 0 : return $url;
593 : }
594 :
595 : /**
596 : * Returns a string representation of this URL.
597 : *
598 : * @return string
599 : * @see toString()
600 : */
601 : public function __toString()
602 : {
603 0 : return $this->getURL();
604 : }
605 :
606 : /**
607 : * Returns a normalized string representation of this URL. This is useful
608 : * for comparison of URLs.
609 : *
610 : * @return string
611 : */
612 : public function getNormalizedURL()
613 : {
614 0 : $url = clone $this;
615 0 : $url->normalize();
616 0 : return $url->getUrl();
617 : }
618 :
619 : /**
620 : * Returns a normalized Net_URL2 instance.
621 : *
622 : * @return Net_URL2
623 : */
624 : public function normalize()
625 : {
626 : // See RFC 3886, section 6
627 :
628 : // Schemes are case-insensitive
629 0 : if ($this->_scheme) {
630 0 : $this->_scheme = strtolower($this->_scheme);
631 0 : }
632 :
633 : // Hostnames are case-insensitive
634 0 : if ($this->_host) {
635 0 : $this->_host = strtolower($this->_host);
636 0 : }
637 :
638 : // Remove default port number for known schemes (RFC 3986, section 6.2.3)
639 0 : if ($this->_port &&
640 0 : $this->_scheme &&
641 0 : $this->_port == getservbyname($this->_scheme, 'tcp')) {
642 :
643 0 : $this->_port = false;
644 0 : }
645 :
646 : // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
647 0 : foreach (array('_userinfo', '_host', '_path') as $part) {
648 0 : if ($this->$part) {
649 0 : $this->$part = preg_replace('/%[0-9a-f]{2}/ie',
650 0 : 'strtoupper("\0")',
651 0 : $this->$part);
652 0 : }
653 0 : }
654 :
655 : // Path segment normalization (RFC 3986, section 6.2.2.3)
656 0 : $this->_path = self::removeDotSegments($this->_path);
657 :
658 : // Scheme based normalization (RFC 3986, section 6.2.3)
659 0 : if ($this->_host && !$this->_path) {
660 0 : $this->_path = '/';
661 0 : }
662 0 : }
663 :
664 : /**
665 : * Returns whether this instance represents an absolute URL.
666 : *
667 : * @return bool
668 : */
669 : public function isAbsolute()
670 : {
671 10 : return (bool) $this->_scheme;
672 : }
673 :
674 : /**
675 : * Returns an Net_URL2 instance representing an absolute URL relative to
676 : * this URL.
677 : *
678 : * @param Net_URL2|string $reference relative URL
679 : *
680 : * @return Net_URL2
681 : */
682 : public function resolve($reference)
683 : {
684 0 : if (!$reference instanceof Net_URL2) {
685 0 : $reference = new self($reference);
686 0 : }
687 0 : if (!$this->isAbsolute()) {
688 0 : throw new Exception('Base-URL must be absolute');
689 : }
690 :
691 : // A non-strict parser may ignore a scheme in the reference if it is
692 : // identical to the base URI's scheme.
693 0 : if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) {
694 0 : $reference->_scheme = false;
695 0 : }
696 :
697 0 : $target = new self('');
698 0 : if ($reference->_scheme !== false) {
699 0 : $target->_scheme = $reference->_scheme;
700 0 : $target->setAuthority($reference->getAuthority());
701 0 : $target->_path = self::removeDotSegments($reference->_path);
702 0 : $target->_query = $reference->_query;
703 0 : } else {
704 0 : $authority = $reference->getAuthority();
705 0 : if ($authority !== false) {
706 0 : $target->setAuthority($authority);
707 0 : $target->_path = self::removeDotSegments($reference->_path);
708 0 : $target->_query = $reference->_query;
709 0 : } else {
710 0 : if ($reference->_path == '') {
711 0 : $target->_path = $this->_path;
712 0 : if ($reference->_query !== false) {
713 0 : $target->_query = $reference->_query;
714 0 : } else {
715 0 : $target->_query = $this->_query;
716 : }
717 0 : } else {
718 0 : if (substr($reference->_path, 0, 1) == '/') {
719 0 : $target->_path = self::removeDotSegments($reference->_path);
720 0 : } else {
721 : // Merge paths (RFC 3986, section 5.2.3)
722 0 : if ($this->_host !== false && $this->_path == '') {
723 0 : $target->_path = '/' . $this->_path;
724 0 : } else {
725 0 : $i = strrpos($this->_path, '/');
726 0 : if ($i !== false) {
727 0 : $target->_path = substr($this->_path, 0, $i + 1);
728 0 : }
729 0 : $target->_path .= $reference->_path;
730 : }
731 0 : $target->_path = self::removeDotSegments($target->_path);
732 : }
733 0 : $target->_query = $reference->_query;
734 : }
735 0 : $target->setAuthority($this->getAuthority());
736 : }
737 0 : $target->_scheme = $this->_scheme;
738 : }
739 :
740 0 : $target->_fragment = $reference->_fragment;
741 :
742 0 : return $target;
743 : }
744 :
745 : /**
746 : * Removes dots as described in RFC 3986, section 5.2.4, e.g.
747 : * "/foo/../bar/baz" => "/bar/baz"
748 : *
749 : * @param string $path a path
750 : *
751 : * @return string a path
752 : */
753 : public static function removeDotSegments($path)
754 : {
755 0 : $output = '';
756 :
757 : // Make sure not to be trapped in an infinite loop due to a bug in this
758 : // method
759 0 : $j = 0;
760 0 : while ($path && $j++ < 100) {
761 0 : if (substr($path, 0, 2) == './') {
762 : // Step 2.A
763 0 : $path = substr($path, 2);
764 0 : } elseif (substr($path, 0, 3) == '../') {
765 : // Step 2.A
766 0 : $path = substr($path, 3);
767 0 : } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
768 : // Step 2.B
769 0 : $path = '/' . substr($path, 3);
770 0 : } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
771 : // Step 2.C
772 0 : $path = '/' . substr($path, 4);
773 0 : $i = strrpos($output, '/');
774 0 : $output = $i === false ? '' : substr($output, 0, $i);
775 0 : } elseif ($path == '.' || $path == '..') {
776 : // Step 2.D
777 0 : $path = '';
778 0 : } else {
779 : // Step 2.E
780 0 : $i = strpos($path, '/');
781 0 : if ($i === 0) {
782 0 : $i = strpos($path, '/', 1);
783 0 : }
784 0 : if ($i === false) {
785 0 : $i = strlen($path);
786 0 : }
787 0 : $output .= substr($path, 0, $i);
788 0 : $path = substr($path, $i);
789 : }
790 0 : }
791 :
792 0 : return $output;
793 : }
794 :
795 : /**
796 : * Percent-encodes all non-alphanumeric characters except these: _ . - ~
797 : * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
798 : * 5.2.x and earlier.
799 : *
800 : * @param $raw the string to encode
801 : * @return string
802 : */
803 : public static function urlencode($string)
804 : {
805 0 : $encoded = rawurlencode($string);
806 :
807 : // This is only necessary in PHP < 5.3.
808 0 : $encoded = str_replace('%7E', '~', $encoded);
809 0 : return $encoded;
810 : }
811 :
812 : /**
813 : * Returns a Net_URL2 instance representing the canonical URL of the
814 : * currently executing PHP script.
815 : *
816 : * @return string
817 : */
818 : public static function getCanonical()
819 : {
820 0 : if (!isset($_SERVER['REQUEST_METHOD'])) {
821 : // ALERT - no current URL
822 0 : throw new Exception('Script was not called through a webserver');
823 : }
824 :
825 : // Begin with a relative URL
826 0 : $url = new self($_SERVER['PHP_SELF']);
827 0 : $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
828 0 : $url->_host = $_SERVER['SERVER_NAME'];
829 0 : $port = $_SERVER['SERVER_PORT'];
830 0 : if ($url->_scheme == 'http' && $port != 80 ||
831 0 : $url->_scheme == 'https' && $port != 443) {
832 :
833 0 : $url->_port = $port;
834 0 : }
835 0 : return $url;
836 : }
837 :
838 : /**
839 : * Returns the URL used to retrieve the current request.
840 : *
841 : * @return string
842 : */
843 : public static function getRequestedURL()
844 : {
845 0 : return self::getRequested()->getUrl();
846 : }
847 :
848 : /**
849 : * Returns a Net_URL2 instance representing the URL used to retrieve the
850 : * current request.
851 : *
852 : * @return Net_URL2
853 : */
854 : public static function getRequested()
855 : {
856 0 : if (!isset($_SERVER['REQUEST_METHOD'])) {
857 : // ALERT - no current URL
858 0 : throw new Exception('Script was not called through a webserver');
859 : }
860 :
861 : // Begin with a relative URL
862 0 : $url = new self($_SERVER['REQUEST_URI']);
863 0 : $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
864 : // Set host and possibly port
865 0 : $url->setAuthority($_SERVER['HTTP_HOST']);
866 0 : return $url;
867 : }
868 :
869 : /**
870 : * Returns the value of the specified option.
871 : *
872 : * @param string $optionName The name of the option to retrieve
873 : *
874 : * @return mixed
875 : */
876 : public function getOption($optionName)
877 : {
878 0 : return isset($this->_options[$optionName])
879 0 : ? $this->_options[$optionName] : false;
880 : }
881 :
882 : /**
883 : * A simple version of http_build_query in userland. The encoded string is
884 : * percentage encoded according to RFC 3986.
885 : *
886 : * @param array $data An array, which has to be converted into
887 : * QUERY_STRING. Anything is possible.
888 : * @param string $seperator See {@link self::OPTION_SEPARATOR_OUTPUT}
889 : * @param string $key For stacked values (arrays in an array).
890 : *
891 : * @return string
892 : */
893 : protected function buildQuery(array $data, $separator, $key = null)
894 : {
895 0 : $query = array();
896 0 : foreach ($data as $name => $value) {
897 0 : if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) {
898 0 : $name = rawurlencode($name);
899 0 : }
900 0 : if ($key !== null) {
901 0 : if ($this->getOption(self::OPTION_USE_BRACKETS) === true) {
902 0 : $name = $key . '[' . $name . ']';
903 0 : } else {
904 0 : $name = $key;
905 : }
906 0 : }
907 0 : if (is_array($value)) {
908 0 : $query[] = $this->buildQuery($value, $separator, $name);
909 0 : } else {
910 0 : $query[] = $name . '=' . rawurlencode($value);
911 : }
912 0 : }
913 0 : return implode($separator, $query);
914 : }
915 :
916 : /**
917 : * This method uses a funky regex to parse the url into the designated parts.
918 : *
919 : * @param string $url
920 : *
921 : * @return void
922 : * @uses self::$_scheme, self::setAuthority(), self::$_path, self::$_query,
923 : * self::$_fragment
924 : * @see self::__construct()
925 : */
926 : protected function parseUrl($url)
927 : {
928 : // The regular expression is copied verbatim from RFC 3986, appendix B.
929 : // The expression does not validate the URL but matches any string.
930 10 : preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!',
931 10 : $url,
932 10 : $matches);
933 :
934 : // "path" is always present (possibly as an empty string); the rest
935 : // are optional.
936 10 : $this->_scheme = !empty($matches[1]) ? $matches[2] : false;
937 10 : $this->setAuthority(!empty($matches[3]) ? $matches[4] : false);
938 10 : $this->_path = $matches[5];
939 10 : $this->_query = !empty($matches[6]) ? $matches[7] : false;
940 10 : $this->_fragment = !empty($matches[8]) ? $matches[9] : false;
941 10 : }
942 : }
|