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 | foreach ($options as $optionName => $value) { |
---|
145 | if (array_key_exists($optionName, $this->_options)) { |
---|
146 | $this->_options[$optionName] = $value; |
---|
147 | } |
---|
148 | } |
---|
149 | |
---|
150 | $this->parseUrl($url); |
---|
151 | } |
---|
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 | $method = 'set' . $var; |
---|
166 | if (method_exists($this, $method)) { |
---|
167 | $this->$method($arg); |
---|
168 | } |
---|
169 | } |
---|
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 | $method = 'get' . $var; |
---|
185 | if (method_exists($this, $method)) { |
---|
186 | return $this->$method(); |
---|
187 | } |
---|
188 | |
---|
189 | 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 | 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 | $this->_scheme = $scheme; |
---|
217 | 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 | return $this->_userinfo !== false |
---|
229 | ? preg_replace('@:.*$@', '', $this->_userinfo) |
---|
230 | : 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 | return $this->_userinfo !== false |
---|
244 | ? substr(strstr($this->_userinfo, ':'), 1) |
---|
245 | : 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 | 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 | $this->_userinfo = $userinfo; |
---|
271 | if ($password !== false) { |
---|
272 | $this->_userinfo .= ':' . $password; |
---|
273 | } |
---|
274 | 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 | 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 | $this->_host = $host; |
---|
299 | 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 | 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 | $this->_port = $port; |
---|
324 | 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 | if (!$this->_host) { |
---|
336 | return false; |
---|
337 | } |
---|
338 | |
---|
339 | $authority = ''; |
---|
340 | |
---|
341 | if ($this->_userinfo !== false) { |
---|
342 | $authority .= $this->_userinfo . '@'; |
---|
343 | } |
---|
344 | |
---|
345 | $authority .= $this->_host; |
---|
346 | |
---|
347 | if ($this->_port !== false) { |
---|
348 | $authority .= ':' . $this->_port; |
---|
349 | } |
---|
350 | |
---|
351 | 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 | $this->_userinfo = false; |
---|
367 | $this->_host = false; |
---|
368 | $this->_port = false; |
---|
369 | if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) { |
---|
370 | if ($reg[1]) { |
---|
371 | $this->_userinfo = $reg[2]; |
---|
372 | } |
---|
373 | |
---|
374 | $this->_host = $reg[3]; |
---|
375 | if (isset($reg[5])) { |
---|
376 | $this->_port = $reg[5]; |
---|
377 | } |
---|
378 | } |
---|
379 | 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 | 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 | $this->_path = $path; |
---|
402 | 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 | 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 | $this->_query = $query; |
---|
429 | 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 | 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 | $this->_fragment = $fragment; |
---|
453 | 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 | preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') . |
---|
467 | ']/'; |
---|
468 | $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY); |
---|
469 | $return = array(); |
---|
470 | |
---|
471 | foreach ($parts as $part) { |
---|
472 | if (strpos($part, '=') !== false) { |
---|
473 | list($key, $value) = explode('=', $part, 2); |
---|
474 | } else { |
---|
475 | $key = $part; |
---|
476 | $value = null; |
---|
477 | } |
---|
478 | |
---|
479 | if ($this->getOption(self::OPTION_ENCODE_KEYS)) { |
---|
480 | $key = rawurldecode($key); |
---|
481 | } |
---|
482 | $value = rawurldecode($value); |
---|
483 | |
---|
484 | if ($this->getOption(self::OPTION_USE_BRACKETS) && |
---|
485 | preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) { |
---|
486 | |
---|
487 | $key = $matches[1]; |
---|
488 | $idx = $matches[2]; |
---|
489 | |
---|
490 | // Ensure is an array |
---|
491 | if (empty($return[$key]) || !is_array($return[$key])) { |
---|
492 | $return[$key] = array(); |
---|
493 | } |
---|
494 | |
---|
495 | // Add data |
---|
496 | if ($idx === '') { |
---|
497 | $return[$key][] = $value; |
---|
498 | } else { |
---|
499 | $return[$key][$idx] = $value; |
---|
500 | } |
---|
501 | } elseif (!$this->getOption(self::OPTION_USE_BRACKETS) |
---|
502 | && !empty($return[$key]) |
---|
503 | ) { |
---|
504 | $return[$key] = (array) $return[$key]; |
---|
505 | $return[$key][] = $value; |
---|
506 | } else { |
---|
507 | $return[$key] = $value; |
---|
508 | } |
---|
509 | } |
---|
510 | |
---|
511 | 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 | if (!$array) { |
---|
524 | $this->_query = false; |
---|
525 | } else { |
---|
526 | $this->_query = $this->buildQuery( |
---|
527 | $array, |
---|
528 | $this->getOption(self::OPTION_SEPARATOR_OUTPUT) |
---|
529 | ); |
---|
530 | } |
---|
531 | 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 | $array = $this->getQueryVariables(); |
---|
545 | $array[$name] = $value; |
---|
546 | $this->setQueryVariables($array); |
---|
547 | 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 | $array = $this->getQueryVariables(); |
---|
560 | unset($array[$name]); |
---|
561 | $this->setQueryVariables($array); |
---|
562 | } |
---|
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 | $url = ""; |
---|
573 | |
---|
574 | if ($this->_scheme !== false) { |
---|
575 | $url .= $this->_scheme . ':'; |
---|
576 | } |
---|
577 | |
---|
578 | $authority = $this->getAuthority(); |
---|
579 | if ($authority !== false) { |
---|
580 | $url .= '//' . $authority; |
---|
581 | } |
---|
582 | $url .= $this->_path; |
---|
583 | |
---|
584 | if ($this->_query !== false) { |
---|
585 | $url .= '?' . $this->_query; |
---|
586 | } |
---|
587 | |
---|
588 | if ($this->_fragment !== false) { |
---|
589 | $url .= '#' . $this->_fragment; |
---|
590 | } |
---|
591 | |
---|
592 | 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 | 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 | $url = clone $this; |
---|
615 | $url->normalize(); |
---|
616 | 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 | if ($this->_scheme) { |
---|
630 | $this->_scheme = strtolower($this->_scheme); |
---|
631 | } |
---|
632 | |
---|
633 | // Hostnames are case-insensitive |
---|
634 | if ($this->_host) { |
---|
635 | $this->_host = strtolower($this->_host); |
---|
636 | } |
---|
637 | |
---|
638 | // Remove default port number for known schemes (RFC 3986, section 6.2.3) |
---|
639 | if ($this->_port && |
---|
640 | $this->_scheme && |
---|
641 | $this->_port == getservbyname($this->_scheme, 'tcp')) { |
---|
642 | |
---|
643 | $this->_port = false; |
---|
644 | } |
---|
645 | |
---|
646 | // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1) |
---|
647 | foreach (array('_userinfo', '_host', '_path') as $part) { |
---|
648 | if ($this->$part) { |
---|
649 | $this->$part = preg_replace('/%[0-9a-f]{2}/ie', |
---|
650 | 'strtoupper("\0")', |
---|
651 | $this->$part); |
---|
652 | } |
---|
653 | } |
---|
654 | |
---|
655 | // Path segment normalization (RFC 3986, section 6.2.2.3) |
---|
656 | $this->_path = self::removeDotSegments($this->_path); |
---|
657 | |
---|
658 | // Scheme based normalization (RFC 3986, section 6.2.3) |
---|
659 | if ($this->_host && !$this->_path) { |
---|
660 | $this->_path = '/'; |
---|
661 | } |
---|
662 | } |
---|
663 | |
---|
664 | /** |
---|
665 | * Returns whether this instance represents an absolute URL. |
---|
666 | * |
---|
667 | * @return bool |
---|
668 | */ |
---|
669 | public function isAbsolute() |
---|
670 | { |
---|
671 | 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 | if (!$reference instanceof Net_URL2) { |
---|
685 | $reference = new self($reference); |
---|
686 | } |
---|
687 | if (!$this->isAbsolute()) { |
---|
688 | 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 | if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) { |
---|
694 | $reference->_scheme = false; |
---|
695 | } |
---|
696 | |
---|
697 | $target = new self(''); |
---|
698 | if ($reference->_scheme !== false) { |
---|
699 | $target->_scheme = $reference->_scheme; |
---|
700 | $target->setAuthority($reference->getAuthority()); |
---|
701 | $target->_path = self::removeDotSegments($reference->_path); |
---|
702 | $target->_query = $reference->_query; |
---|
703 | } else { |
---|
704 | $authority = $reference->getAuthority(); |
---|
705 | if ($authority !== false) { |
---|
706 | $target->setAuthority($authority); |
---|
707 | $target->_path = self::removeDotSegments($reference->_path); |
---|
708 | $target->_query = $reference->_query; |
---|
709 | } else { |
---|
710 | if ($reference->_path == '') { |
---|
711 | $target->_path = $this->_path; |
---|
712 | if ($reference->_query !== false) { |
---|
713 | $target->_query = $reference->_query; |
---|
714 | } else { |
---|
715 | $target->_query = $this->_query; |
---|
716 | } |
---|
717 | } else { |
---|
718 | if (substr($reference->_path, 0, 1) == '/') { |
---|
719 | $target->_path = self::removeDotSegments($reference->_path); |
---|
720 | } else { |
---|
721 | // Merge paths (RFC 3986, section 5.2.3) |
---|
722 | if ($this->_host !== false && $this->_path == '') { |
---|
723 | $target->_path = '/' . $this->_path; |
---|
724 | } else { |
---|
725 | $i = strrpos($this->_path, '/'); |
---|
726 | if ($i !== false) { |
---|
727 | $target->_path = substr($this->_path, 0, $i + 1); |
---|
728 | } |
---|
729 | $target->_path .= $reference->_path; |
---|
730 | } |
---|
731 | $target->_path = self::removeDotSegments($target->_path); |
---|
732 | } |
---|
733 | $target->_query = $reference->_query; |
---|
734 | } |
---|
735 | $target->setAuthority($this->getAuthority()); |
---|
736 | } |
---|
737 | $target->_scheme = $this->_scheme; |
---|
738 | } |
---|
739 | |
---|
740 | $target->_fragment = $reference->_fragment; |
---|
741 | |
---|
742 | 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 | $output = ''; |
---|
756 | |
---|
757 | // Make sure not to be trapped in an infinite loop due to a bug in this |
---|
758 | // method |
---|
759 | $j = 0; |
---|
760 | while ($path && $j++ < 100) { |
---|
761 | if (substr($path, 0, 2) == './') { |
---|
762 | // Step 2.A |
---|
763 | $path = substr($path, 2); |
---|
764 | } elseif (substr($path, 0, 3) == '../') { |
---|
765 | // Step 2.A |
---|
766 | $path = substr($path, 3); |
---|
767 | } elseif (substr($path, 0, 3) == '/./' || $path == '/.') { |
---|
768 | // Step 2.B |
---|
769 | $path = '/' . substr($path, 3); |
---|
770 | } elseif (substr($path, 0, 4) == '/../' || $path == '/..') { |
---|
771 | // Step 2.C |
---|
772 | $path = '/' . substr($path, 4); |
---|
773 | $i = strrpos($output, '/'); |
---|
774 | $output = $i === false ? '' : substr($output, 0, $i); |
---|
775 | } elseif ($path == '.' || $path == '..') { |
---|
776 | // Step 2.D |
---|
777 | $path = ''; |
---|
778 | } else { |
---|
779 | // Step 2.E |
---|
780 | $i = strpos($path, '/'); |
---|
781 | if ($i === 0) { |
---|
782 | $i = strpos($path, '/', 1); |
---|
783 | } |
---|
784 | if ($i === false) { |
---|
785 | $i = strlen($path); |
---|
786 | } |
---|
787 | $output .= substr($path, 0, $i); |
---|
788 | $path = substr($path, $i); |
---|
789 | } |
---|
790 | } |
---|
791 | |
---|
792 | 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 | $encoded = rawurlencode($string); |
---|
806 | |
---|
807 | // This is only necessary in PHP < 5.3. |
---|
808 | $encoded = str_replace('%7E', '~', $encoded); |
---|
809 | 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 | if (!isset($_SERVER['REQUEST_METHOD'])) { |
---|
821 | // ALERT - no current URL |
---|
822 | throw new Exception('Script was not called through a webserver'); |
---|
823 | } |
---|
824 | |
---|
825 | // Begin with a relative URL |
---|
826 | $url = new self($_SERVER['PHP_SELF']); |
---|
827 | $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; |
---|
828 | $url->_host = $_SERVER['SERVER_NAME']; |
---|
829 | $port = $_SERVER['SERVER_PORT']; |
---|
830 | if ($url->_scheme == 'http' && $port != 80 || |
---|
831 | $url->_scheme == 'https' && $port != 443) { |
---|
832 | |
---|
833 | $url->_port = $port; |
---|
834 | } |
---|
835 | 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 | 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 | if (!isset($_SERVER['REQUEST_METHOD'])) { |
---|
857 | // ALERT - no current URL |
---|
858 | throw new Exception('Script was not called through a webserver'); |
---|
859 | } |
---|
860 | |
---|
861 | // Begin with a relative URL |
---|
862 | $url = new self($_SERVER['REQUEST_URI']); |
---|
863 | $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; |
---|
864 | // Set host and possibly port |
---|
865 | $url->setAuthority($_SERVER['HTTP_HOST']); |
---|
866 | 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 | return isset($this->_options[$optionName]) |
---|
879 | ? $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 | $query = array(); |
---|
896 | foreach ($data as $name => $value) { |
---|
897 | if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) { |
---|
898 | $name = rawurlencode($name); |
---|
899 | } |
---|
900 | if ($key !== null) { |
---|
901 | if ($this->getOption(self::OPTION_USE_BRACKETS) === true) { |
---|
902 | $name = $key . '[' . $name . ']'; |
---|
903 | } else { |
---|
904 | $name = $key; |
---|
905 | } |
---|
906 | } |
---|
907 | if (is_array($value)) { |
---|
908 | $query[] = $this->buildQuery($value, $separator, $name); |
---|
909 | } else { |
---|
910 | $query[] = $name . '=' . rawurlencode($value); |
---|
911 | } |
---|
912 | } |
---|
913 | 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 | preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', |
---|
931 | $url, |
---|
932 | $matches); |
---|
933 | |
---|
934 | // "path" is always present (possibly as an empty string); the rest |
---|
935 | // are optional. |
---|
936 | $this->_scheme = !empty($matches[1]) ? $matches[2] : false; |
---|
937 | $this->setAuthority(!empty($matches[3]) ? $matches[4] : false); |
---|
938 | $this->_path = $matches[5]; |
---|
939 | $this->_query = !empty($matches[6]) ? $matches[7] : false; |
---|
940 | $this->_fragment = !empty($matches[8]) ? $matches[9] : false; |
---|
941 | } |
---|
942 | } |
---|