Index: branches/version-2_13-dev/data/module/Net/URL.php
===================================================================
--- branches/version-2_13-dev/data/module/Net/URL.php	(revision 21237)
+++ branches/version-2_13-dev/data/module/Net/URL.php	(revision 23125)
@@ -33,5 +33,5 @@
 // +-----------------------------------------------------------------------+
 //
-// $Id$
+// $Id: URL.php,v 1.49 2007/06/28 14:43:07 davidc Exp $
 //
 // Net_URL Class
Index: branches/version-2_13-dev/data/module/Net/URL2.php
===================================================================
--- branches/version-2_13-dev/data/module/Net/URL2.php	(revision 23125)
+++ branches/version-2_13-dev/data/module/Net/URL2.php	(revision 23125)
@@ -0,0 +1,942 @@
+<?php
+/**
+ * Net_URL2, a class representing a URL as per RFC 3986.
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2007-2009, Peytz & Co. A/S
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the distribution.
+ *   * Neither the name of the Net_URL2 nor the names of its contributors may
+ *     be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category  Networking
+ * @package   Net_URL2
+ * @author    Christian Schmidt <schmidt@php.net>
+ * @copyright 2007-2009 Peytz & Co. A/S
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version   CVS: $Id: URL2.php 309223 2011-03-14 14:26:32Z till $
+ * @link      http://www.rfc-editor.org/rfc/rfc3986.txt
+ */
+
+/**
+ * Represents a URL as per RFC 3986.
+ *
+ * @category  Networking
+ * @package   Net_URL2
+ * @author    Christian Schmidt <schmidt@php.net>
+ * @copyright 2007-2009 Peytz & Co. A/S
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version   Release: @package_version@
+ * @link      http://pear.php.net/package/Net_URL2
+ */
+class Net_URL2
+{
+    /**
+     * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
+     * is true.
+     */
+    const OPTION_STRICT = 'strict';
+
+    /**
+     * Represent arrays in query using PHP's [] notation. Default is true.
+     */
+    const OPTION_USE_BRACKETS = 'use_brackets';
+
+    /**
+     * URL-encode query variable keys. Default is true.
+     */
+    const OPTION_ENCODE_KEYS = 'encode_keys';
+
+    /**
+     * Query variable separators when parsing the query string. Every character
+     * is considered a separator. Default is "&".
+     */
+    const OPTION_SEPARATOR_INPUT = 'input_separator';
+
+    /**
+     * Query variable separator used when generating the query string. Default
+     * is "&".
+     */
+    const OPTION_SEPARATOR_OUTPUT = 'output_separator';
+
+    /**
+     * Default options corresponds to how PHP handles $_GET.
+     */
+    private $_options = array(
+        self::OPTION_STRICT           => true,
+        self::OPTION_USE_BRACKETS     => true,
+        self::OPTION_ENCODE_KEYS      => true,
+        self::OPTION_SEPARATOR_INPUT  => '&',
+        self::OPTION_SEPARATOR_OUTPUT => '&',
+        );
+
+    /**
+     * @var  string|bool
+     */
+    private $_scheme = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $_userinfo = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $_host = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $_port = false;
+
+    /**
+     * @var  string
+     */
+    private $_path = '';
+
+    /**
+     * @var  string|bool
+     */
+    private $_query = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $_fragment = false;
+
+    /**
+     * Constructor.
+     *
+     * @param string $url     an absolute or relative URL
+     * @param array  $options an array of OPTION_xxx constants
+     *
+     * @return $this
+     * @uses   self::parseUrl()
+     */
+    public function __construct($url, array $options = array())
+    {
+        foreach ($options as $optionName => $value) {
+            if (array_key_exists($optionName, $this->_options)) {
+                $this->_options[$optionName] = $value;
+            }
+        }
+
+        $this->parseUrl($url);
+    }
+
+    /**
+     * Magic Setter.
+     *
+     * This method will magically set the value of a private variable ($var)
+     * with the value passed as the args
+     *
+     * @param  string $var      The private variable to set.
+     * @param  mixed  $arg      An argument of any type.
+     * @return void
+     */
+    public function __set($var, $arg)
+    {
+        $method = 'set' . $var;
+        if (method_exists($this, $method)) {
+            $this->$method($arg);
+        }
+    }
+
+    /**
+     * Magic Getter.
+     *
+     * This is the magic get method to retrieve the private variable
+     * that was set by either __set() or it's setter...
+     *
+     * @param  string $var         The property name to retrieve.
+     * @return mixed  $this->$var  Either a boolean false if the
+     *                             property is not set or the value
+     *                             of the private property.
+     */
+    public function __get($var)
+    {
+        $method = 'get' . $var;
+        if (method_exists($this, $method)) {
+            return $this->$method();
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the scheme, e.g. "http" or "urn", or false if there is no
+     * scheme specified, i.e. if this is a relative URL.
+     *
+     * @return  string|bool
+     */
+    public function getScheme()
+    {
+        return $this->_scheme;
+    }
+
+    /**
+     * Sets the scheme, e.g. "http" or "urn". Specify false if there is no
+     * scheme specified, i.e. if this is a relative URL.
+     *
+     * @param string|bool $scheme e.g. "http" or "urn", or false if there is no
+     *                            scheme specified, i.e. if this is a relative
+     *                            URL
+     *
+     * @return $this
+     * @see    getScheme()
+     */
+    public function setScheme($scheme)
+    {
+        $this->_scheme = $scheme;
+        return $this;
+    }
+
+    /**
+     * Returns the user part of the userinfo part (the part preceding the first
+     *  ":"), or false if there is no userinfo part.
+     *
+     * @return  string|bool
+     */
+    public function getUser()
+    {
+        return $this->_userinfo !== false
+            ? preg_replace('@:.*$@', '', $this->_userinfo)
+            : false;
+    }
+
+    /**
+     * Returns the password part of the userinfo part (the part after the first
+     *  ":"), or false if there is no userinfo part (i.e. the URL does not
+     * contain "@" in front of the hostname) or the userinfo part does not
+     * contain ":".
+     *
+     * @return  string|bool
+     */
+    public function getPassword()
+    {
+        return $this->_userinfo !== false
+            ? substr(strstr($this->_userinfo, ':'), 1)
+            : false;
+    }
+
+    /**
+     * Returns the userinfo part, or false if there is none, i.e. if the
+     * authority part does not contain "@".
+     *
+     * @return  string|bool
+     */
+    public function getUserinfo()
+    {
+        return $this->_userinfo;
+    }
+
+    /**
+     * Sets the userinfo part. If two arguments are passed, they are combined
+     * in the userinfo part as username ":" password.
+     *
+     * @param string|bool $userinfo userinfo or username
+     * @param string|bool $password optional password, or false
+     *
+     * @return $this
+     */
+    public function setUserinfo($userinfo, $password = false)
+    {
+        $this->_userinfo = $userinfo;
+        if ($password !== false) {
+            $this->_userinfo .= ':' . $password;
+        }
+        return $this;
+    }
+
+    /**
+     * Returns the host part, or false if there is no authority part, e.g.
+     * relative URLs.
+     *
+     * @return  string|bool a hostname, an IP address, or false
+     */
+    public function getHost()
+    {
+        return $this->_host;
+    }
+
+    /**
+     * Sets the host part. Specify false if there is no authority part, e.g.
+     * relative URLs.
+     *
+     * @param string|bool $host a hostname, an IP address, or false
+     *
+     * @return $this
+     */
+    public function setHost($host)
+    {
+        $this->_host = $host;
+        return $this;
+    }
+
+    /**
+     * Returns the port number, or false if there is no port number specified,
+     * i.e. if the default port is to be used.
+     *
+     * @return  string|bool
+     */
+    public function getPort()
+    {
+        return $this->_port;
+    }
+
+    /**
+     * Sets the port number. Specify false if there is no port number specified,
+     * i.e. if the default port is to be used.
+     *
+     * @param string|bool $port a port number, or false
+     *
+     * @return $this
+     */
+    public function setPort($port)
+    {
+        $this->_port = $port;
+        return $this;
+    }
+
+    /**
+     * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
+     * false if there is no authority.
+     *
+     * @return string|bool
+     */
+    public function getAuthority()
+    {
+        if (!$this->_host) {
+            return false;
+        }
+
+        $authority = '';
+
+        if ($this->_userinfo !== false) {
+            $authority .= $this->_userinfo . '@';
+        }
+
+        $authority .= $this->_host;
+
+        if ($this->_port !== false) {
+            $authority .= ':' . $this->_port;
+        }
+
+        return $authority;
+    }
+
+    /**
+     * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
+     * false if there is no authority.
+     *
+     * @param string|false $authority a hostname or an IP addresse, possibly
+     *                                with userinfo prefixed and port number
+     *                                appended, e.g. "foo:bar@example.org:81".
+     *
+     * @return $this
+     */
+    public function setAuthority($authority)
+    {
+        $this->_userinfo = false;
+        $this->_host     = false;
+        $this->_port     = false;
+        if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
+            if ($reg[1]) {
+                $this->_userinfo = $reg[2];
+            }
+
+            $this->_host = $reg[3];
+            if (isset($reg[5])) {
+                $this->_port = $reg[5];
+            }
+        }
+        return $this;
+    }
+
+    /**
+     * Returns the path part (possibly an empty string).
+     *
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->_path;
+    }
+
+    /**
+     * Sets the path part (possibly an empty string).
+     *
+     * @param string $path a path
+     *
+     * @return $this
+     */
+    public function setPath($path)
+    {
+        $this->_path = $path;
+        return $this;
+    }
+
+    /**
+     * Returns the query string (excluding the leading "?"), or false if "?"
+     * is not present in the URL.
+     *
+     * @return  string|bool
+     * @see     self::getQueryVariables()
+     */
+    public function getQuery()
+    {
+        return $this->_query;
+    }
+
+    /**
+     * Sets the query string (excluding the leading "?"). Specify false if "?"
+     * is not present in the URL.
+     *
+     * @param string|bool $query a query string, e.g. "foo=1&bar=2"
+     *
+     * @return $this
+     * @see    self::setQueryVariables()
+     */
+    public function setQuery($query)
+    {
+        $this->_query = $query;
+        return $this;
+    }
+
+    /**
+     * Returns the fragment name, or false if "#" is not present in the URL.
+     *
+     * @return  string|bool
+     */
+    public function getFragment()
+    {
+        return $this->_fragment;
+    }
+
+    /**
+     * Sets the fragment name. Specify false if "#" is not present in the URL.
+     *
+     * @param string|bool $fragment a fragment excluding the leading "#", or
+     *                              false
+     *
+     * @return $this
+     */
+    public function setFragment($fragment)
+    {
+        $this->_fragment = $fragment;
+        return $this;
+    }
+
+    /**
+     * Returns the query string like an array as the variables would appear in
+     * $_GET in a PHP script. If the URL does not contain a "?", an empty array
+     * is returned.
+     *
+     * @return  array
+     */
+    public function getQueryVariables()
+    {
+        $pattern = '/[' .
+                   preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') .
+                   ']/';
+        $parts   = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY);
+        $return  = array();
+
+        foreach ($parts as $part) {
+            if (strpos($part, '=') !== false) {
+                list($key, $value) = explode('=', $part, 2);
+            } else {
+                $key   = $part;
+                $value = null;
+            }
+
+            if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
+                $key = rawurldecode($key);
+            }
+            $value = rawurldecode($value);
+
+            if ($this->getOption(self::OPTION_USE_BRACKETS) &&
+                preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) {
+
+                $key = $matches[1];
+                $idx = $matches[2];
+
+                // Ensure is an array
+                if (empty($return[$key]) || !is_array($return[$key])) {
+                    $return[$key] = array();
+                }
+
+                // Add data
+                if ($idx === '') {
+                    $return[$key][] = $value;
+                } else {
+                    $return[$key][$idx] = $value;
+                }
+            } elseif (!$this->getOption(self::OPTION_USE_BRACKETS)
+                      && !empty($return[$key])
+            ) {
+                $return[$key]   = (array) $return[$key];
+                $return[$key][] = $value;
+            } else {
+                $return[$key] = $value;
+            }
+        }
+
+        return $return;
+    }
+
+    /**
+     * Sets the query string to the specified variable in the query string.
+     *
+     * @param array $array (name => value) array
+     *
+     * @return $this
+     */
+    public function setQueryVariables(array $array)
+    {
+        if (!$array) {
+            $this->_query = false;
+        } else {
+            $this->_query = $this->buildQuery(
+                $array,
+                $this->getOption(self::OPTION_SEPARATOR_OUTPUT)
+            );
+        }
+        return $this;
+    }
+
+    /**
+     * Sets the specified variable in the query string.
+     *
+     * @param string $name  variable name
+     * @param mixed  $value variable value
+     *
+     * @return $this
+     */
+    public function setQueryVariable($name, $value)
+    {
+        $array = $this->getQueryVariables();
+        $array[$name] = $value;
+        $this->setQueryVariables($array);
+        return $this;
+    }
+
+    /**
+     * Removes the specifed variable from the query string.
+     *
+     * @param string $name a query string variable, e.g. "foo" in "?foo=1"
+     *
+     * @return void
+     */
+    public function unsetQueryVariable($name)
+    {
+        $array = $this->getQueryVariables();
+        unset($array[$name]);
+        $this->setQueryVariables($array);
+    }
+
+    /**
+     * Returns a string representation of this URL.
+     *
+     * @return  string
+     */
+    public function getURL()
+    {
+        // See RFC 3986, section 5.3
+        $url = "";
+
+        if ($this->_scheme !== false) {
+            $url .= $this->_scheme . ':';
+        }
+
+        $authority = $this->getAuthority();
+        if ($authority !== false) {
+            $url .= '//' . $authority;
+        }
+        $url .= $this->_path;
+
+        if ($this->_query !== false) {
+            $url .= '?' . $this->_query;
+        }
+
+        if ($this->_fragment !== false) {
+            $url .= '#' . $this->_fragment;
+        }
+    
+        return $url;
+    }
+
+    /**
+     * Returns a string representation of this URL.
+     *
+     * @return  string
+     * @see toString()
+     */
+    public function __toString()
+    {
+        return $this->getURL();
+    }
+
+    /** 
+     * Returns a normalized string representation of this URL. This is useful
+     * for comparison of URLs.
+     *
+     * @return  string
+     */
+    public function getNormalizedURL()
+    {
+        $url = clone $this;
+        $url->normalize();
+        return $url->getUrl();
+    }
+
+    /** 
+     * Returns a normalized Net_URL2 instance.
+     *
+     * @return  Net_URL2
+     */
+    public function normalize()
+    {
+        // See RFC 3886, section 6
+
+        // Schemes are case-insensitive
+        if ($this->_scheme) {
+            $this->_scheme = strtolower($this->_scheme);
+        }
+
+        // Hostnames are case-insensitive
+        if ($this->_host) {
+            $this->_host = strtolower($this->_host);
+        }
+
+        // Remove default port number for known schemes (RFC 3986, section 6.2.3)
+        if ($this->_port &&
+            $this->_scheme &&
+            $this->_port == getservbyname($this->_scheme, 'tcp')) {
+
+            $this->_port = false;
+        }
+
+        // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
+        foreach (array('_userinfo', '_host', '_path') as $part) {
+            if ($this->$part) {
+                $this->$part = preg_replace('/%[0-9a-f]{2}/ie',
+                                            'strtoupper("\0")',
+                                            $this->$part);
+            }
+        }
+
+        // Path segment normalization (RFC 3986, section 6.2.2.3)
+        $this->_path = self::removeDotSegments($this->_path);
+
+        // Scheme based normalization (RFC 3986, section 6.2.3)
+        if ($this->_host && !$this->_path) {
+            $this->_path = '/';
+        }
+    }
+
+    /**
+     * Returns whether this instance represents an absolute URL.
+     *
+     * @return  bool
+     */
+    public function isAbsolute()
+    {
+        return (bool) $this->_scheme;
+    }
+
+    /**
+     * Returns an Net_URL2 instance representing an absolute URL relative to
+     * this URL.
+     *
+     * @param Net_URL2|string $reference relative URL
+     *
+     * @return Net_URL2
+     */
+    public function resolve($reference)
+    {
+        if (!$reference instanceof Net_URL2) {
+            $reference = new self($reference);
+        }
+        if (!$this->isAbsolute()) {
+            throw new Exception('Base-URL must be absolute');
+        }
+
+        // A non-strict parser may ignore a scheme in the reference if it is
+        // identical to the base URI's scheme.
+        if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) {
+            $reference->_scheme = false;
+        }
+
+        $target = new self('');
+        if ($reference->_scheme !== false) {
+            $target->_scheme = $reference->_scheme;
+            $target->setAuthority($reference->getAuthority());
+            $target->_path  = self::removeDotSegments($reference->_path);
+            $target->_query = $reference->_query;
+        } else {
+            $authority = $reference->getAuthority();
+            if ($authority !== false) {
+                $target->setAuthority($authority);
+                $target->_path  = self::removeDotSegments($reference->_path);
+                $target->_query = $reference->_query;
+            } else {
+                if ($reference->_path == '') {
+                    $target->_path = $this->_path;
+                    if ($reference->_query !== false) {
+                        $target->_query = $reference->_query;
+                    } else {
+                        $target->_query = $this->_query;
+                    }
+                } else {
+                    if (substr($reference->_path, 0, 1) == '/') {
+                        $target->_path = self::removeDotSegments($reference->_path);
+                    } else {
+                        // Merge paths (RFC 3986, section 5.2.3)
+                        if ($this->_host !== false && $this->_path == '') {
+                            $target->_path = '/' . $this->_path;
+                        } else {
+                            $i = strrpos($this->_path, '/');
+                            if ($i !== false) {
+                                $target->_path = substr($this->_path, 0, $i + 1);
+                            }
+                            $target->_path .= $reference->_path;
+                        }
+                        $target->_path = self::removeDotSegments($target->_path);
+                    }
+                    $target->_query = $reference->_query;
+                }
+                $target->setAuthority($this->getAuthority());
+            }
+            $target->_scheme = $this->_scheme;
+        }
+
+        $target->_fragment = $reference->_fragment;
+
+        return $target;
+    }
+
+    /**
+     * Removes dots as described in RFC 3986, section 5.2.4, e.g.
+     * "/foo/../bar/baz" => "/bar/baz"
+     *
+     * @param string $path a path
+     *
+     * @return string a path
+     */
+    public static function removeDotSegments($path)
+    {
+        $output = '';
+
+        // Make sure not to be trapped in an infinite loop due to a bug in this
+        // method
+        $j = 0;
+        while ($path && $j++ < 100) {
+            if (substr($path, 0, 2) == './') {
+                // Step 2.A
+                $path = substr($path, 2);
+            } elseif (substr($path, 0, 3) == '../') {
+                // Step 2.A
+                $path = substr($path, 3);
+            } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
+                // Step 2.B
+                $path = '/' . substr($path, 3);
+            } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
+                // Step 2.C
+                $path   = '/' . substr($path, 4);
+                $i      = strrpos($output, '/');
+                $output = $i === false ? '' : substr($output, 0, $i);
+            } elseif ($path == '.' || $path == '..') {
+                // Step 2.D
+                $path = '';
+            } else {
+                // Step 2.E
+                $i = strpos($path, '/');
+                if ($i === 0) {
+                    $i = strpos($path, '/', 1);
+                }
+                if ($i === false) {
+                    $i = strlen($path);
+                }
+                $output .= substr($path, 0, $i);
+                $path = substr($path, $i);
+            }
+        }
+
+        return $output;
+    }
+
+    /**
+     * Percent-encodes all non-alphanumeric characters except these: _ . - ~
+     * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
+     * 5.2.x and earlier.
+     *
+     * @param  $raw the string to encode
+     * @return string
+     */
+    public static function urlencode($string)
+    {
+    	$encoded = rawurlencode($string);
+
+        // This is only necessary in PHP < 5.3.
+        $encoded = str_replace('%7E', '~', $encoded);
+        return $encoded;
+    }
+
+    /**
+     * Returns a Net_URL2 instance representing the canonical URL of the
+     * currently executing PHP script.
+     *
+     * @return  string
+     */
+    public static function getCanonical()
+    {
+        if (!isset($_SERVER['REQUEST_METHOD'])) {
+            // ALERT - no current URL
+            throw new Exception('Script was not called through a webserver');
+        }
+
+        // Begin with a relative URL
+        $url = new self($_SERVER['PHP_SELF']);
+        $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+        $url->_host   = $_SERVER['SERVER_NAME'];
+        $port = $_SERVER['SERVER_PORT'];
+        if ($url->_scheme == 'http' && $port != 80 ||
+            $url->_scheme == 'https' && $port != 443) {
+
+            $url->_port = $port;
+        }
+        return $url;
+    }
+
+    /**
+     * Returns the URL used to retrieve the current request.
+     *
+     * @return  string
+     */
+    public static function getRequestedURL()
+    {
+        return self::getRequested()->getUrl();
+    }
+
+    /**
+     * Returns a Net_URL2 instance representing the URL used to retrieve the
+     * current request.
+     *
+     * @return  Net_URL2
+     */
+    public static function getRequested()
+    {
+        if (!isset($_SERVER['REQUEST_METHOD'])) {
+            // ALERT - no current URL
+            throw new Exception('Script was not called through a webserver');
+        }
+
+        // Begin with a relative URL
+        $url = new self($_SERVER['REQUEST_URI']);
+        $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+        // Set host and possibly port
+        $url->setAuthority($_SERVER['HTTP_HOST']);
+        return $url;
+    }
+
+    /**
+     * Returns the value of the specified option.
+     *
+     * @param string $optionName The name of the option to retrieve
+     *
+     * @return  mixed
+     */
+    public function getOption($optionName)
+    {
+        return isset($this->_options[$optionName])
+            ? $this->_options[$optionName] : false;
+    }
+
+    /**
+     * A simple version of http_build_query in userland. The encoded string is
+     * percentage encoded according to RFC 3986.
+     *
+     * @param array  $data      An array, which has to be converted into
+     *                          QUERY_STRING. Anything is possible.
+     * @param string $seperator See {@link self::OPTION_SEPARATOR_OUTPUT}
+     * @param string $key       For stacked values (arrays in an array).
+     *
+     * @return string
+     */
+    protected function buildQuery(array $data, $separator, $key = null)
+    {
+        $query = array();
+        foreach ($data as $name => $value) {
+            if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) {
+                $name = rawurlencode($name);
+            }
+            if ($key !== null) {
+                if ($this->getOption(self::OPTION_USE_BRACKETS) === true) {
+                    $name = $key . '[' . $name . ']';
+                } else {
+                    $name = $key;
+                }
+            }
+            if (is_array($value)) {
+                $query[] = $this->buildQuery($value, $separator, $name);
+            } else {
+                $query[] = $name . '=' . rawurlencode($value);
+            }
+        }
+        return implode($separator, $query);
+    }
+
+    /**
+     * This method uses a funky regex to parse the url into the designated parts.
+     *
+     * @param string $url
+     *
+     * @return void
+     * @uses   self::$_scheme, self::setAuthority(), self::$_path, self::$_query,
+     *         self::$_fragment
+     * @see    self::__construct()
+     */
+    protected function parseUrl($url)
+    {
+        // The regular expression is copied verbatim from RFC 3986, appendix B.
+        // The expression does not validate the URL but matches any string.
+        preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!',
+                   $url,
+                   $matches);
+
+        // "path" is always present (possibly as an empty string); the rest
+        // are optional.
+        $this->_scheme   = !empty($matches[1]) ? $matches[2] : false;
+        $this->setAuthority(!empty($matches[3]) ? $matches[4] : false);
+        $this->_path     = $matches[5];
+        $this->_query    = !empty($matches[6]) ? $matches[7] : false;
+        $this->_fragment = !empty($matches[8]) ? $matches[9] : false;
+    }
+}
Index: branches/version-2_13-dev/data/module/Net/Socket.php
===================================================================
--- branches/version-2_13-dev/data/module/Net/Socket.php	(revision 20998)
+++ branches/version-2_13-dev/data/module/Net/Socket.php	(revision 23125)
@@ -5,5 +5,5 @@
  * PHP Version 4
  *
- * Copyright (c) 1997-2003 The PHP Group
+ * Copyright (c) 1997-2013 The PHP Group
  *
  * This source file is subject to version 2.0 of the PHP license,
@@ -24,5 +24,4 @@
  * @copyright 1997-2003 The PHP Group
  * @license   http://www.php.net/license/2_02.txt PHP 2.02
- * @version   CVS: $Id$
  * @link      http://pear.php.net/packages/Net_Socket
  */
@@ -78,9 +77,9 @@
 
     /**
-     * Number of seconds to wait on socket connections before assuming
+     * Number of seconds to wait on socket operations before assuming
      * there's no more data. Defaults to no timeout.
-     * @var integer $timeout
-     */
-    var $timeout = false;
+     * @var integer|float $timeout
+     */
+    var $timeout = null;
 
     /**
@@ -101,15 +100,15 @@
      * already connected, it disconnects and connects again.
      *
-     * @param string  $addr       IP address or host name.
+     * @param string  $addr       IP address or host name (may be with protocol prefix).
      * @param integer $port       TCP port number.
      * @param boolean $persistent (optional) Whether the connection is
      *                            persistent (kept open between requests
      *                            by the web server).
-     * @param integer $timeout    (optional) How long to wait for data.
+     * @param integer $timeout    (optional) Connection socket timeout.
      * @param array   $options    See options for stream_context_create.
      *
      * @access public
      *
-     * @return boolean | PEAR_Error  True on success or a PEAR_Error on failure.
+     * @return boolean|PEAR_Error  True on success or a PEAR_Error on failure.
      */
     function connect($addr, $port = 0, $persistent = null,
@@ -123,9 +122,8 @@
         if (!$addr) {
             return $this->raiseError('$addr cannot be empty');
-        } elseif (strspn($addr, '.0123456789') == strlen($addr) ||
-                  strstr($addr, '/') !== false) {
+        } else if (strspn($addr, ':.0123456789') == strlen($addr)) {
+            $this->addr = strpos($addr, ':') !== false ? '['.$addr.']' : $addr;
+        } else {
             $this->addr = $addr;
-        } else {
-            $this->addr = @gethostbyname($addr);
         }
 
@@ -134,8 +132,4 @@
         if ($persistent !== null) {
             $this->persistent = $persistent;
-        }
-
-        if ($timeout !== null) {
-            $this->timeout = $timeout;
         }
 
@@ -146,10 +140,9 @@
         $old_track_errors = @ini_set('track_errors', 1);
 
+        if ($timeout <= 0) {
+            $timeout = @ini_get('default_socket_timeout');
+        }
+
         if ($options && function_exists('stream_context_create')) {
-            if ($this->timeout) {
-                $timeout = $this->timeout;
-            } else {
-                $timeout = 0;
-            }
             $context = stream_context_create($options);
 
@@ -170,10 +163,5 @@
             }
         } else {
-            if ($this->timeout) {
-                $fp = @$openfunc($this->addr, $this->port, $errno,
-                                 $errstr, $this->timeout);
-            } else {
-                $fp = @$openfunc($this->addr, $this->port, $errno, $errstr);
-            }
+            $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout);
         }
 
@@ -188,5 +176,5 @@
         @ini_set('track_errors', $old_track_errors);
         $this->fp = $fp;
-
+        $this->setTimeout();
         return $this->setBlocking($this->blocking);
     }
@@ -259,16 +247,29 @@
      *
      * @param integer $seconds      Seconds.
-     * @param integer $microseconds Microseconds.
-     *
-     * @access public
-     * @return mixed true on success or a PEAR_Error instance otherwise
-     */
-    function setTimeout($seconds, $microseconds)
-    {
-        if (!is_resource($this->fp)) {
-            return $this->raiseError('not connected');
-        }
-
-        return socket_set_timeout($this->fp, $seconds, $microseconds);
+     * @param integer $microseconds Microseconds, optional.
+     *
+     * @access public
+     * @return mixed True on success or false on failure or
+     *               a PEAR_Error instance when not connected
+     */
+    function setTimeout($seconds = null, $microseconds = null)
+    {
+        if (!is_resource($this->fp)) {
+            return $this->raiseError('not connected');
+        }
+
+        if ($seconds === null && $microseconds === null) {
+            $seconds      = (int) $this->timeout;
+            $microseconds = (int) (($this->timeout - $seconds) * 1000000);
+        } else {
+            $this->timeout = $seconds + $microseconds/1000000;
+        }
+
+        if ($this->timeout > 0) {
+            return stream_set_timeout($this->fp, (int) $seconds, (int) $microseconds);
+        }
+        else {
+            return false;
+        }
     }
 
@@ -316,5 +317,5 @@
         }
 
-        return socket_get_status($this->fp);
+        return stream_get_meta_data($this->fp);
     }
 
@@ -322,9 +323,12 @@
      * Get a specified line of data
      *
-     * @param int $size ??
-     *
-     * @access public
-     * @return $size bytes of data from the socket, or a PEAR_Error if
-     *         not connected.
+     * @param int $size Reading ends when size - 1 bytes have been read,
+     *                  or a newline or an EOF (whichever comes first).
+     *                  If no size is specified, it will keep reading from
+     *                  the stream until it reaches the end of the line.
+     *
+     * @access public
+     * @return mixed $size bytes of data from the socket, or a PEAR_Error if
+     *         not connected. If an error occurs, FALSE is returned.
      */
     function gets($size = null)
@@ -371,7 +375,8 @@
      * @access public
      * @return mixed If the socket is not connected, returns an instance of
-     *               PEAR_Error
-     *               If the write succeeds, returns the number of bytes written
+     *               PEAR_Error.
+     *               If the write succeeds, returns the number of bytes written.
      *               If the write fails, returns false.
+     *               If the socket times out, returns an instance of PEAR_Error.
      */
     function write($data, $blocksize = null)
@@ -382,5 +387,20 @@
 
         if (is_null($blocksize) && !OS_WINDOWS) {
-            return @fwrite($this->fp, $data);
+            $written = @fwrite($this->fp, $data);
+
+            // Check for timeout or lost connection
+            if (!$written) {
+                $meta_data = $this->getStatus();
+
+                if (!is_array($meta_data)) {
+                    return $meta_data; // PEAR_Error
+                }
+
+                if (!empty($meta_data['timed_out'])) {
+                    return $this->raiseError('timed out');
+                }
+            }
+
+            return $written;
         } else {
             if (is_null($blocksize)) {
@@ -392,7 +412,20 @@
             while ($pos < $size) {
                 $written = @fwrite($this->fp, substr($data, $pos, $blocksize));
+
+                // Check for timeout or lost connection
                 if (!$written) {
+                    $meta_data = $this->getStatus();
+
+                    if (!is_array($meta_data)) {
+                        return $meta_data; // PEAR_Error
+                    }
+
+                    if (!empty($meta_data['timed_out'])) {
+                        return $this->raiseError('timed out');
+                    }
+
                     return $written;
                 }
+
                 $pos += $written;
             }
@@ -408,5 +441,5 @@
      *
      * @access public
-     * @return mixed fputs result, or an error
+     * @return mixed fwrite() result, or PEAR_Error when not connected
      */
     function writeLine($data)
