Merge remote-tracking branch 'upstream/develop' into develop
diff --git a/system/core/Router.php b/system/core/Router.php
index 01f44bc..76772a0 100644
--- a/system/core/Router.php
+++ b/system/core/Router.php
@@ -111,21 +111,21 @@
 		// since URI segments are more search-engine friendly, but they can optionally be used.
 		// If this feature is enabled, we will gather the directory/class/method a little differently
 		$segments = array();
-		if ($this->config->item('enable_query_strings') === TRUE && isset($_GET[$this->config->item('controller_trigger')]))
+		if ($this->config->item('enable_query_strings') === TRUE
+			&& ! empty($_GET[$this->config->item('controller_trigger')])
+			&& is_string($_GET[$this->config->item('controller_trigger')])
+		)
 		{
-			if (isset($_GET[$this->config->item('directory_trigger')]))
+			if (isset($_GET[$this->config->item('directory_trigger')]) && is_string($_GET[$this->config->item('directory_trigger')]))
 			{
 				$this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')])));
 				$segments[] = $this->fetch_directory();
 			}
 
-			if (isset($_GET[$this->config->item('controller_trigger')]))
-			{
-				$this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')])));
-				$segments[] = $this->fetch_class();
-			}
+			$this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')])));
+			$segments[] = $this->fetch_class();
 
-			if (isset($_GET[$this->config->item('function_trigger')]))
+			if ( ! empty($_GET[$this->config->item('function_trigger')]) && is_string($_GET[$this->config->item('function_trigger')]))
 			{
 				$this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item('function_trigger')])));
 				$segments[] = $this->fetch_method();
@@ -142,7 +142,7 @@
 			include(APPPATH.'config/routes.php');
 		}
 
-		$this->routes = (isset($route) && is_array($route)) ? $route : array();
+		$this->routes = (empty($route) OR ! is_array($route)) ? array() : $route;
 		unset($route);
 
 		// Set the default controller so we can display it in the event
diff --git a/system/core/URI.php b/system/core/URI.php
index 9174025..900472b 100644
--- a/system/core/URI.php
+++ b/system/core/URI.php
@@ -219,7 +219,32 @@
 		}
 
 		// Do some final cleaning of the URI and return it
-		return str_replace(array('//', '../'), '/', trim($uri, '/'));
+		return $this->_remove_relative_directory($uri);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * 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);
 	}
 
 	// --------------------------------------------------------------------
@@ -249,7 +274,7 @@
 
 		parse_str($_SERVER['QUERY_STRING'], $_GET);
 
-		return str_replace(array('//', '../'), '/', trim($uri, '/'));
+		return $this->_remove_relative_directory($uri);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/DB_result.php b/system/database/DB_result.php
index 9d19075..e1ef341 100644
--- a/system/database/DB_result.php
+++ b/system/database/DB_result.php
@@ -203,7 +203,7 @@
 			return $this->custom_result_object[$class_name];
 		}
 
-		$this->_data_seek(0);
+		$this->data_seek(0);
 		$this->custom_result_object[$class_name] = array();
 
 		while ($row = $this->_fetch_object($class_name))
@@ -246,7 +246,7 @@
 			return $this->result_object;
 		}
 
-		$this->_data_seek(0);
+		$this->data_seek(0);
 		while ($row = $this->_fetch_object())
 		{
 			$this->result_object[] = $row;
@@ -287,7 +287,7 @@
 			return $this->result_array;
 		}
 
-		$this->_data_seek(0);
+		$this->data_seek(0);
 		while ($row = $this->_fetch_assoc())
 		{
 			$this->result_array[] = $row;
@@ -617,7 +617,7 @@
 	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return FALSE;
 	}
diff --git a/system/database/drivers/cubrid/cubrid_result.php b/system/database/drivers/cubrid/cubrid_result.php
index 130eea2..793b35b 100644
--- a/system/database/drivers/cubrid/cubrid_result.php
+++ b/system/database/drivers/cubrid/cubrid_result.php
@@ -130,7 +130,7 @@
 	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return cubrid_data_seek($this->result_id, $n);
 	}
diff --git a/system/database/drivers/mssql/mssql_result.php b/system/database/drivers/mssql/mssql_result.php
index a8f850d..ca222ae 100644
--- a/system/database/drivers/mssql/mssql_result.php
+++ b/system/database/drivers/mssql/mssql_result.php
@@ -135,7 +135,7 @@
 	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return mssql_data_seek($this->result_id, $n);
 	}
diff --git a/system/database/drivers/mysql/mysql_result.php b/system/database/drivers/mysql/mysql_result.php
index a6dcde4..293980e 100644
--- a/system/database/drivers/mysql/mysql_result.php
+++ b/system/database/drivers/mysql/mysql_result.php
@@ -149,7 +149,7 @@
 	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return $this->num_rows
 			? @mysql_data_seek($this->result_id, $n)
diff --git a/system/database/drivers/mysqli/mysqli_result.php b/system/database/drivers/mysqli/mysqli_result.php
index d55188e..ac0f1a8 100644
--- a/system/database/drivers/mysqli/mysqli_result.php
+++ b/system/database/drivers/mysqli/mysqli_result.php
@@ -136,7 +136,7 @@
 	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return $this->result_id->data_seek($n);
 	}
diff --git a/system/database/drivers/oci8/oci8_result.php b/system/database/drivers/oci8/oci8_result.php
index 7d5bf51..84d46f8 100644
--- a/system/database/drivers/oci8/oci8_result.php
+++ b/system/database/drivers/oci8/oci8_result.php
@@ -216,64 +216,6 @@
 		return $class_name;
 	}
 
-	// --------------------------------------------------------------------
-
-	/**
-	 * Data Seek
-	 *
-	 * Moves the internal pointer to the desired offset. We call
-	 * this internally before fetching results to make sure the
-	 * result set starts at zero.
-	 *
-	 * Oracle's PHP extension doesn't have an easy way of doing this
-	 * and the only workaround is to (re)execute the statement or cursor
-	 * in order to go to the first (zero) index of the result set.
-	 * Then, we would need to "dummy" fetch ($n - 1) rows to get to the
-	 * right one.
-	 *
-	 * This is as ridiculous as it sounds and it's the reason why every
-	 * other method that is fetching data tries to use an already "cached"
-	 * result set. Keeping this just in case it becomes needed at
-	 * some point in the future, but it will only work for resetting the
-	 * pointer to zero.
-	 *
-	 * @param	int	$n	(ignored)
-	 * @return	bool
-	 */
-	protected function _data_seek($n = 0)
-	{
-		/* The PHP manual says that if OCI_NO_AUTO_COMMIT mode
-		 * is used, and oci_rollback() and/or oci_commit() are
-		 * not subsequently called - this will cause an unnecessary
-		 * rollback to be triggered at the end of the script execution.
-		 *
-		 * Therefore we'll try to avoid using that mode flag
-		 * if we're not currently in the middle of a transaction.
-		 */
-		if ($this->commit_mode !== OCI_COMMIT_ON_SUCCESS)
-		{
-			$result = @oci_execute($this->stmt_id, $this->commit_mode);
-		}
-		else
-		{
-			$result = @oci_execute($this->stmt_id);
-		}
-
-		if ($result && $this->curs_id)
-		{
-			if ($this->commit_mode !== OCI_COMMIT_ON_SUCCESS)
-			{
-				return @oci_execute($this->curs_id, $this->commit_mode);
-			}
-			else
-			{
-				return @oci_execute($this->curs_id);
-			}
-		}
-
-		return $result;
-	}
-
 }
 
 /* End of file oci8_result.php */
diff --git a/system/database/drivers/postgre/postgre_result.php b/system/database/drivers/postgre/postgre_result.php
index 3a4e57c..fdaeaef 100644
--- a/system/database/drivers/postgre/postgre_result.php
+++ b/system/database/drivers/postgre/postgre_result.php
@@ -133,7 +133,7 @@
 	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return pg_result_seek($this->result_id, $n);
 	}
diff --git a/system/database/drivers/sqlite/sqlite_result.php b/system/database/drivers/sqlite/sqlite_result.php
index 24f02a8b..889757d 100644
--- a/system/database/drivers/sqlite/sqlite_result.php
+++ b/system/database/drivers/sqlite/sqlite_result.php
@@ -117,7 +117,7 @@
 	 * @param	int	$n
 	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		return sqlite_seek($this->result_id, $n);
 	}
diff --git a/system/database/drivers/sqlite3/sqlite3_result.php b/system/database/drivers/sqlite3/sqlite3_result.php
index 44fef89..69c4200 100644
--- a/system/database/drivers/sqlite3/sqlite3_result.php
+++ b/system/database/drivers/sqlite3/sqlite3_result.php
@@ -175,10 +175,10 @@
 	 * @param	int	$n	(ignored)
 	 * @return	array
 	 */
-	protected function _data_seek($n = 0)
+	public function data_seek($n = 0)
 	{
 		// Only resetting to the start of the result set is supported
-		return $this->result_id->reset();
+		return ($n > 0) ? FALSE : $this->result_id->reset();
 	}
 
 }
diff --git a/system/helpers/url_helper.php b/system/helpers/url_helper.php
index 14c216a..36ff0ff 100644
--- a/system/helpers/url_helper.php
+++ b/system/helpers/url_helper.php
@@ -152,7 +152,7 @@
 
 		if ( ! is_array($uri))
 		{
-			$site_url = preg_match('!^\w+://! i', $uri) ? $uri : site_url($uri);
+			$site_url = preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri);
 		}
 		else
 		{
@@ -191,7 +191,7 @@
 	function anchor_popup($uri = '', $title = '', $attributes = FALSE)
 	{
 		$title = (string) $title;
-		$site_url = preg_match('!^\w+://! i', $uri) ? $uri : site_url($uri);
+		$site_url = preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri);
 
 		if ($title === '')
 		{
@@ -535,7 +535,7 @@
 	 */
 	function redirect($uri = '', $method = 'auto', $code = NULL)
 	{
-		if ( ! preg_match('#^https?://#i', $uri))
+		if ( ! preg_match('#^(\w+:)?//#i', $uri))
 		{
 			$uri = site_url($uri);
 		}
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 9b0ea53..daf7965 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -73,6 +73,7 @@
 	 - Added JS window name support to the :php:func:`anchor_popup()` function.
 	 - Added support (auto-detection) for HTTP/1.1 response code 303 in :php:func:`redirect()`.
 	 - Changed :php:func:`redirect()` to only choose the **refresh** method only on IIS servers, instead of all servers on Windows (when **auto** is used).
+	 - Changed :php:func:`anchor()`, :php:func:`anchor_popup()`, and :php:func:`redirect()` to support protocol-relative URLs, such as `redirect('//ellislab.com/codeigniter')`.
    -  Added XHTML Basic 1.1 doctype to :doc:`HTML Helper <helpers/html_helper>`.
    -  :doc:`Inflector Helper <helpers/inflector_helper>` changes include:
 	 - Changed :php:func:`humanize()` to allow passing an input separator as its second parameter.
@@ -106,7 +107,6 @@
    -  Added **schema** configuration setting (defaults to *public*) for drivers that might need it (currently used by PostgreSQL and ODBC).
    -  Added subdrivers support (currently only used by PDO).
    -  Added an optional database name parameter to ``db_select()``.
-   -  Added a constructor to the ``DB_result`` class and moved all driver-specific properties and logic out of the base ``DB_driver`` class to allow better abstraction.
    -  Removed ``protect_identifiers()`` and renamed internal method ``_protect_identifiers()`` to it instead - it was just an alias.
    -  Renamed internal method ``_escape_identifiers()`` to ``escape_identifiers()``.
    -  Updated ``escape_identifiers()`` to accept an array of fields as well as strings.
@@ -114,8 +114,7 @@
    -  ``db_set_charset()`` now only requires one parameter (collation was only needed due to legacy support for MySQL versions prior to 5.1).
    -  Replaced the ``_error_message()`` and ``_error_number()`` methods with ``error()``, which returns an array containing the last database error code and message.
    -  Improved ``version()`` implementation so that drivers that have a native function to get the version number don't have to be defined in the core ``DB_driver`` class.
-   -  Added ``unbuffered_row()`` method for getting a row without prefetching whole result (consume less memory).
-   -  Added capability for packages to hold *database.php* config files.
+   -  Added capability for packages to hold *config/database.php* config files.
    -  Added MySQL client compression support.
    -  Added encrypted connections support (for *mysql*, *sqlsrv* and PDO with *sqlsrv*).
    -  Removed :doc:`Loader Class <libraries/loader>` from Database error tracing to better find the likely culprit.
@@ -133,6 +132,10 @@
 	 - Changed ``limit()`` to ignore NULL values instead of always casting to integer.
 	 - Changed ``offset()`` to ignore empty values instead of always casting to integer.
 	 - Methods ``insert_batch()`` and ``update_batch()`` now return an integer representing the number of rows affected by them.
+   -  :doc:`Database Results <database/results>` changes include:
+	 - Added a constructor to the ``DB_result`` class and moved all driver-specific properties and logic out of the base ``DB_driver`` class to allow better abstraction.
+	 - Added method ``unbuffered_row()`` for fetching a row without prefetching the whole result (consume less memory).
+	 - Renamed former method ``_data_seek()`` to ``data_seek()`` and made it public.
    -  Improved support for the MySQLi driver, including:
 	 - OOP style of the PHP extension is now used, instead of the procedural aliases.
 	 - Server version checking is now done via ``mysqli::$server_info`` instead of running an SQL query.
@@ -461,6 +464,7 @@
 -  Fixed a bug (#18) - :doc:`APC Cache <libraries/caching>` driver didn't (un)serialize data, resulting in failure to store objects.
 -  Fixed a bug (#188) - :doc:`Unit Testing Library <libraries/unit_testing>` filled up logs with error messages for non-existing language keys.
 -  Fixed a bug (#113) - :doc:`Form Validation Library <libraries/form_validation>` didn't properly handle empty fields that were specified as an array.
+-  Fixed a bug (#2061) - :doc:`Routing Class <general/routing>` didn't properly sanitize directory, controller and function triggers with **enable_query_strings** set to TRUE.
 
 Version 2.1.3
 =============
diff --git a/user_guide_src/source/database/results.rst b/user_guide_src/source/database/results.rst
index d032f73..e0a87a8 100644
--- a/user_guide_src/source/database/results.rst
+++ b/user_guide_src/source/database/results.rst
@@ -99,7 +99,7 @@
 	echo $row->reverse_name(); // or methods defined on the 'User' class
 
 row_array()
-============
+===========
 
 Identical to the above row() function, except it returns an array.
 Example::
@@ -138,12 +138,12 @@
 
 .. note:: all the functions above will load the whole result into memory (prefetching) use unbuffered_row() for processing large result sets.
 
-unbuffered_row($type)
-=====================
+unbuffered_row()
+================
 
-This function returns a single result row without prefetching the whole result in memory as row() does.
-If your query has more than one row, it returns the current row and moves the internal data pointer ahead. 
-The result is returned as $type could be 'object' (default) or 'array' that will return an associative array.
+This method returns a single result row without prefetching the whole
+result in memory as ``row()`` does. If your query has more than one row,
+it returns the current row and moves the internal data pointer ahead. 
 
 ::
 
@@ -156,12 +156,19 @@
 		echo $row->body;
 	}
 
+You can optionally pass 'object' (default) or 'array' in order to specify
+the returned value's type::
+
+	$query->unbuffered_row();		// object
+	$query->unbuffered_row('object');	// object
+	$query->unbuffered_row('array');	// associative array
+
 ***********************
 Result Helper Functions
 ***********************
 
 $query->num_rows()
-===================
+==================
 
 The number of rows returned by the query. Note: In this example, $query
 is the variable that the query result object is assigned to::
@@ -177,7 +184,7 @@
 	resulting array in order to achieve the same functionality.
 	
 $query->num_fields()
-=====================
+====================
 
 The number of FIELDS (columns) returned by the query. Make sure to call
 the function using your query result object::
@@ -187,7 +194,7 @@
 	echo $query->num_fields();
 
 $query->free_result()
-======================
+=====================
 
 It frees the memory associated with the result and deletes the result
 resource ID. Normally PHP frees its memory automatically at the end of
@@ -209,3 +216,21 @@
 	$row = $query2->row();
 	echo $row->name;
 	$query2->free_result(); // The $query2 result object will no longer be available
+
+data_seek()
+===========
+
+This method sets the internal pointer for the next result row to be
+fetched. It is only useful in combination with ``unbuffered_row()``.
+
+It accepts a positive integer value, which defaults to 0 and returns
+TRUE on success or FALSE on failure.
+
+::
+
+	$query = $this->db->query('SELECT `field_name` FROM `table_name`');
+	$query->data_seek(5); // Skip the first 5 rows
+	$row = $query->unbuffered_row();
+
+.. note:: Not all database drivers support this feature and will return FALSE.
+	Most notably - you won't be able to use it with PDO.
\ No newline at end of file