Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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