URI Routing overhaul

 - Allow multiple levels of controller directories (supersedes PRs #390, #2439)
 - Add support for per-directory 'defaul_controller' and '404_override' (resolves issue #2611; supersedes PR #939)
 - Fixed a bug where default_controller was called instead of triggering 404 if the current route is inside a directory
 - Removed a few calls from CI_Router to CI_URI that made a necessity for otherwise internal CI_URI methods to be public:

    - Removed CI_URI::_fetch_uri_string() and moved its logic into CI_URI::__construct()
    - Removed CI_URI::_remove_url_suffix, CI_URI::_explode_segments() and moved their logic into CI_URI::_set_uri_string()
    - Removed CI_URI::_reindex_segments() altogether ( doesn't need further manipulation, while  is
      public anyway and can be properly (and more effectively) replaced on the spot)
diff --git a/system/core/URI.php b/system/core/URI.php
index c83b7a7..80cf0b4 100644
--- a/system/core/URI.php
+++ b/system/core/URI.php
@@ -44,21 +44,21 @@
 	 *
 	 * @var	array
 	 */
-	public $keyval =	array();
+	public $keyval = array();
 
 	/**
 	 * Current URI string
 	 *
 	 * @var	string
 	 */
-	public $uri_string;
+	public $uri_string = '';
 
 	/**
 	 * List of URI segments
 	 *
 	 * @var	array
 	 */
-	public $segments =	array();
+	public $segments = array();
 
 	/**
 	 * Re-indexed list of URI segments
@@ -67,7 +67,7 @@
 	 *
 	 * @var	array
 	 */
-	public $rsegments =	array();
+	public $rsegments = array();
 
 	/**
 	 * Permitted URI chars
@@ -81,19 +81,50 @@
 	/**
 	 * Class constructor
 	 *
-	 * Simply globalizes the $RTR object. The front
-	 * loads the Router class early on so it's not available
-	 * normally as other classes are.
-	 *
 	 * @return	void
 	 */
 	public function __construct()
 	{
 		$this->config =& load_class('Config', 'core');
 
-		if ($this->config->item('enable_query_strings') !== TRUE OR is_cli())
+		// If query strings are enabled, we don't need to parse any segments.
+		// However, they don't make sense under CLI.
+		if (is_cli() OR $this->config->item('enable_query_strings') !== TRUE)
 		{
 			$this->_permitted_uri_chars = $this->config->item('permitted_uri_chars');
+
+			// If it's a CLI request, ignore the configuration
+			if (is_cli() OR ($protocol = strtoupper($this->config->item('uri_protocol')) === 'CLI'))
+			{
+				$this->_set_uri_string($this->_parse_argv());
+			}
+			elseif ($protocol === 'AUTO')
+			{
+				// Is there a PATH_INFO variable? This should be the easiest solution.
+				if (isset($_SERVER['PATH_INFO']))
+				{
+					$this->_set_uri_string($_SERVER['PATH_INFO']);
+				}
+				// No PATH_INFO? Let's try REQUST_URI or QUERY_STRING then
+				elseif (($uri = $this->_parse_request_uri()) !== '' OR ($uri = $this->_parse_query_string()) !== '')
+				{
+					$this->_set_uri_string($uri);
+				}
+				// As a last ditch effor, let's try using the $_GET array
+				elseif (is_array($_GET) && count($_GET) === 1 && trim(key($_GET), '/') !== '')
+				{
+					$this->_set_uri_string(key($_GET));
+				}
+			}
+			elseif (method_exists($this, ($method = '_parse_'.strtolower($protocol))))
+			{
+				$this->_set_uri_string($this->$method());
+			}
+			else
+			{
+				$uri = isset($_SERVER[$protocol]) ? $_SERVER[$protocol] : @getenv($protocol);
+				$this->_set_uri_string($uri);
+			}
 		}
 
 		log_message('debug', 'URI Class Initialized');
@@ -102,75 +133,6 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Fetch URI String
-	 *
-	 * @used-by	CI_Router
-	 * @return	void
-	 */
-	public function _fetch_uri_string()
-	{
-		$protocol = strtoupper($this->config->item('uri_protocol'));
-
-		if ($protocol === 'AUTO')
-		{
-			// Is the request coming from the command line?
-			if (is_cli())
-			{
-				$this->_set_uri_string($this->_parse_argv());
-				return;
-			}
-
-			// Is there a PATH_INFO variable? This should be the easiest solution.
-			if (isset($_SERVER['PATH_INFO']))
-			{
-				$this->_set_uri_string($_SERVER['PATH_INFO']);
-				return;
-			}
-
-			// Let's try REQUEST_URI then, this will work in most situations
-			if (($uri = $this->_parse_request_uri()) !== '')
-			{
-				$this->_set_uri_string($uri);
-				return;
-			}
-
-			// No REQUEST_URI either?... What about QUERY_STRING?
-			if (($uri = $this->_parse_query_string()) !== '')
-			{
-				$this->_set_uri_string($uri);
-				return;
-			}
-
-			// As a last ditch effort let's try using the $_GET array
-			if (is_array($_GET) && count($_GET) === 1 && trim(key($_GET), '/') !== '')
-			{
-				$this->_set_uri_string(key($_GET));
-				return;
-			}
-
-			// We've exhausted all our options...
-			$this->uri_string = '';
-			return;
-		}
-
-		if ($protocol === 'CLI')
-		{
-			$this->_set_uri_string($this->_parse_argv());
-			return;
-		}
-		elseif (method_exists($this, ($method = '_parse_'.strtolower($protocol))))
-		{
-			$this->_set_uri_string($this->$method());
-			return;
-		}
-
-		$uri = isset($_SERVER[$protocol]) ? $_SERVER[$protocol] : @getenv($protocol);
-		$this->_set_uri_string($uri);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Set URI String
 	 *
 	 * @param 	string	$str
@@ -180,6 +142,32 @@
 	{
 		// Filter out control characters and trim slashes
 		$this->uri_string = trim(remove_invisible_characters($str, FALSE), '/');
+
+		if ($this->uri_string !== '')
+		{
+			// Remove the URL suffix, if present
+			if (($suffix = (string) $this->config->item('url_suffix')) !== '')
+			{
+				$slen = strlen($suffix);
+
+				if (substr($this->uri_string, -$slen) === $suffix)
+				{
+					$this->uri_string = substr($this->uri_string, 0, -$slen);
+				}
+			}
+
+			// Populate the segments array
+			foreach (explode('/', preg_replace('|/*(.+?)/*$|', '\\1', $this->uri_string)) as $val)
+			{
+				// Filter segments for security
+				$val = trim($this->filter_uri($val));
+
+				if ($val !== '')
+				{
+					$this->segments[] = $val;
+				}
+			}
+		}
 	}
 
 	// --------------------------------------------------------------------
@@ -240,36 +228,10 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Remove relative directory (../) and multi slashes (///)
-	 *
-	 * Do some final cleaning of the URI and return it, currently only used in self::_parse_request_uri()
-	 *
-	 * @param	string	$url
-	 * @return	string
-	 */
-	protected function _remove_relative_directory($uri)
-	{
-		$uris = array();
-		$tok = strtok($uri, '/');
-		while ($tok !== FALSE)
-		{
-			if (( ! empty($tok) OR $tok === '0') && $tok !== '..')
-			{
-				$uris[] = $tok;
-			}
-			$tok = strtok('/');
-		}
-		return implode('/', $uris);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Parse QUERY_STRING
 	 *
 	 * Will parse QUERY_STRING and automatically detect the URI from it.
 	 *
-	 * @used-by	CI_URI::_fetch_uri_string()
 	 * @return	string
 	 */
 	protected function _parse_query_string()
@@ -310,11 +272,36 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Remove relative directory (../) and multi slashes (///)
+	 *
+	 * Do some final cleaning of the URI and return it, currently only used in self::_parse_request_uri()
+	 *
+	 * @param	string	$url
+	 * @return	string
+	 */
+	protected function _remove_relative_directory($uri)
+	{
+		$uris = array();
+		$tok = strtok($uri, '/');
+		while ($tok !== FALSE)
+		{
+			if (( ! empty($tok) OR $tok === '0') && $tok !== '..')
+			{
+				$uris[] = $tok;
+			}
+			$tok = strtok('/');
+		}
+
+		return implode('/', $uris);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Filter URI
 	 *
 	 * Filters segments for malicious characters.
 	 *
-	 * @used-by	CI_Router
 	 * @param	string	$str
 	 * @return	string
 	 */
@@ -336,79 +323,6 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Remove URL suffix
-	 *
-	 * Removes the suffix from the URL if needed.
-	 *
-	 * @used-by	CI_Router
-	 * @return	void
-	 */
-	public function _remove_url_suffix()
-	{
-		$suffix = (string) $this->config->item('url_suffix');
-
-		if ($suffix === '')
-		{
-			return;
-		}
-
-		$slen = strlen($suffix);
-
-		if (substr($this->uri_string, -$slen) === $suffix)
-		{
-			$this->uri_string = substr($this->uri_string, 0, -$slen);
-		}
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Explode URI segments
-	 *
-	 * The individual segments will be stored in the $this->segments array.
-	 *
-	 * @see		CI_URI::$segments
-	 * @used-by	CI_Router
-	 * @return	void
-	 */
-	public function _explode_segments()
-	{
-		foreach (explode('/', preg_replace('|/*(.+?)/*$|', '\\1', $this->uri_string)) as $val)
-		{
-			// Filter segments for security
-			$val = trim($this->filter_uri($val));
-
-			if ($val !== '')
-			{
-				$this->segments[] = $val;
-			}
-		}
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Re-index Segments
-	 *
-	 * Re-indexes the CI_URI::$segment array so that it starts at 1 rather
-	 * than 0. Doing so makes it simpler to use methods like
-	 * CI_URI::segment(n) since there is a 1:1 relationship between the
-	 * segment array and the actual segments.
-	 *
-	 * @used-by	CI_Router
-	 * @return	void
-	 */
-	public function _reindex_segments()
-	{
-		array_unshift($this->segments, NULL);
-		array_unshift($this->rsegments, NULL);
-		unset($this->segments[0]);
-		unset($this->rsegments[0]);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Fetch URI Segment
 	 *
 	 * @see		CI_URI::$segments
@@ -714,7 +628,7 @@
 	{
 		global $RTR;
 
-		return ltrim($RTR->directory, '/').implode('/', $this->rsegment_array());
+		return ltrim($RTR->directory, '/').implode('/', $this->rsegments);
 	}
 
 }