Merge branch 'develop' into 'feature/user-guide-cleanup'
diff --git a/application/language/index.html b/application/language/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/application/language/index.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/readme.rst b/readme.rst
index b4984ea..aa07d6e 100644
--- a/readme.rst
+++ b/readme.rst
@@ -35,7 +35,7 @@
 Installation
 ************
 
-Please see the `installation section <http://codeigniter.com/user_guide/installation/index.html>`_
+Please see the `installation section <http://ellislab.com/codeigniter/user-guide/installation/index.html>`_
 of the CodeIgniter User Guide.
 
 *******
@@ -43,7 +43,7 @@
 *******
 
 Please see the `license
-agreement <http://codeigniter.com/user_guide/license.html>`_
+agreement <http://ellislab.com/codeigniter/user-guide/license.html>`_
 
 *********
 Resources
diff --git a/system/core/Common.php b/system/core/Common.php
index cfc63c2..07f0c6d 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -598,14 +598,14 @@
 			return;
 		}
 
+		$_error->log_exception($severity, $message, $filepath, $line);
+
 		// Should we display the error?
 		if ((bool) ini_get('display_errors') === TRUE)
 		{
 			$_error->show_php_error($severity, $message, $filepath, $line);
 		}
 
-		$_error->log_exception($severity, $message, $filepath, $line);
-
 		// If the error is fatal, the execution of the script should be stopped because
 		// errors can't be recovered from. Halting the script conforms with PHP's
 		// default error handling. See http://www.php.net/manual/en/errorfunc.constants.php
@@ -756,6 +756,11 @@
 	 * *suhosin.executor.disable_eval*. These settings will just
 	 * terminate script execution if a disabled function is executed.
 	 *
+	 * The above described behavior turned out to be a bug in Suhosin,
+	 * but even though a fix was commited for 0.9.34 on 2012-02-12,
+	 * that version is yet to be released. This function will therefore
+	 * be just temporary, but would probably be kept for a few years.
+	 *
 	 * @link	http://www.hardened-php.net/suhosin/
 	 * @param	string	$function_name	Function to check for
 	 * @return	bool	TRUE if the function exists and is safe to call,
diff --git a/system/core/Security.php b/system/core/Security.php
index 49e5ab4..cbff38b 100644
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -433,6 +433,12 @@
 		 * We used to do some version comparisons and use of stripos for PHP5,
 		 * but it is dog slow compared to these simplified non-capturing
 		 * preg_match(), especially if the pattern exists in the string
+		 *
+		 * Note: It was reported that not only space characters, but all in
+		 * the following pattern can be parsed as separators between a tag name
+		 * and its attributes: [\d\s"\'`;,\/\=\(\x00\x0B\x09\x0C]
+		 * ... however, remove_invisible_characters() above already strips the
+		 * hex-encoded ones, so we'll skip them below.
 		 */
 		do
 		{
@@ -440,12 +446,12 @@
 
 			if (preg_match('/<a/i', $str))
 			{
-				$str = preg_replace_callback('#<a\s+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
+				$str = preg_replace_callback('#<a[\s\d"\'`;/=,\(]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
 			}
 
 			if (preg_match('/<img/i', $str))
 			{
-				$str = preg_replace_callback('#<img\s+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str);
+				$str = preg_replace_callback('#<img[\s\d"\'`;/=,\(]+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str);
 			}
 
 			if (preg_match('/script|xss/i', $str))
@@ -469,7 +475,7 @@
 		 * So this: <blink>
 		 * Becomes: &lt;blink&gt;
 		 */
-		$naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|button|select|isindex|layer|link|meta|keygen|object|plaintext|style|script|textarea|title|video|svg|xml|xss';
+		$naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|button|select|isindex|layer|link|meta|keygen|object|plaintext|style|script|textarea|title|math|video|svg|xml|xss';
 		$str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);
 
 		/*
@@ -661,8 +667,7 @@
 	 */
 	protected function _remove_evil_attributes($str, $is_image)
 	{
-		// Formaction, style, and xmlns
-		$evil_attributes = array('style', 'xmlns', 'formaction', 'form', 'xlink:href');
+		$evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction', 'form', 'xlink:href');
 
 		if ($is_image === TRUE)
 		{
@@ -678,7 +683,7 @@
 			$attribs = array();
 
 			// find occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes)
-			preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER);
+			preg_match_all('/(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER);
 
 			foreach ($matches as $attr)
 			{
@@ -686,7 +691,7 @@
 			}
 
 			// find occurrences of illegal attribute strings without quotes
-			preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER);
+			preg_match_all('/(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER);
 
 			foreach ($matches as $attr)
 			{
diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php
index 8223baa..ef69009 100644
--- a/system/database/DB_query_builder.php
+++ b/system/database/DB_query_builder.php
@@ -263,7 +263,7 @@
 			$select = explode(',', $select);
 		}
 
-		// If the escape value was not set will will base it on the global setting
+		// If the escape value was not set, we will base it on the global setting
 		is_bool($escape) OR $escape = $this->_protect_identifiers;
 
 		foreach ($select as $val)
diff --git a/system/database/drivers/odbc/odbc_driver.php b/system/database/drivers/odbc/odbc_driver.php
index 45e91cb..6f635bd 100644
--- a/system/database/drivers/odbc/odbc_driver.php
+++ b/system/database/drivers/odbc/odbc_driver.php
@@ -222,7 +222,7 @@
 	 */
 	public function affected_rows()
 	{
-		return @odbc_num_rows($this->conn_id);
+		return @odbc_num_rows($this->result_id);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/pdo/pdo_driver.php b/system/database/drivers/pdo/pdo_driver.php
index 184a8df..3f4275f 100644
--- a/system/database/drivers/pdo/pdo_driver.php
+++ b/system/database/drivers/pdo/pdo_driver.php
@@ -92,7 +92,7 @@
 		{
 			$this->subdriver = '4d';
 		}
-		elseif ( ! in_array($this->subdriver, array('4d', 'cubrid', 'dblib', 'firebird', 'ibm', 'informix', 'mysql', 'oci', 'odbc', 'sqlite', 'sqlsrv'), TRUE))
+		elseif ( ! in_array($this->subdriver, array('4d', 'cubrid', 'dblib', 'firebird', 'ibm', 'informix', 'mysql', 'oci', 'odbc', 'pgsql', 'sqlite', 'sqlsrv'), TRUE))
 		{
 			log_message('error', 'PDO: Invalid or non-existent subdriver');
 
@@ -117,7 +117,6 @@
 	{
 		$this->options[PDO::ATTR_PERSISTENT] = $persistent;
 
-		// Connecting...
 		try
 		{
 			return @new PDO($this->dsn, $this->username, $this->password, $this->options);
diff --git a/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
index 6ee327b..507abda 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
@@ -84,6 +84,12 @@
 
 			empty($this->port) OR $this->dsn .= ';port='.$this->port;
 			empty($this->database) OR $this->dsn .= ';dbname='.$this->database;
+
+			if ( ! empty($this->username))
+			{
+				$this->dsn .= ';username='.$this->username;
+				empty($this->password) OR $this->dsn .= ';password='.$this->password;
+			}
 		}
 	}
 
diff --git a/system/helpers/captcha_helper.php b/system/helpers/captcha_helper.php
index 24cd535..b61b2d5 100644
--- a/system/helpers/captcha_helper.php
+++ b/system/helpers/captcha_helper.php
@@ -82,7 +82,7 @@
 		}
 
 		if ($img_path === '' OR $img_url === ''
-			OR ! @is_dir($img_path) OR ! is_writeable($img_path)
+			OR ! @is_dir($img_path) OR ! is_really_writable($img_path)
 			OR ! extension_loaded('gd'))
 		{
 			return FALSE;
diff --git a/system/helpers/download_helper.php b/system/helpers/download_helper.php
index 4fe6a0e..9a6f684 100644
--- a/system/helpers/download_helper.php
+++ b/system/helpers/download_helper.php
@@ -120,7 +120,7 @@
 		// Clean output buffer
 		if (ob_get_level() !== 0 && @ob_end_clean() === FALSE)
 		{
-			ob_clean();
+			@ob_clean();
 		}
 
 		// Generate the server headers
diff --git a/system/helpers/file_helper.php b/system/helpers/file_helper.php
index 0587740..ae3db58 100644
--- a/system/helpers/file_helper.php
+++ b/system/helpers/file_helper.php
@@ -298,8 +298,7 @@
 					$fileinfo['readable'] = is_readable($file);
 					break;
 				case 'writable':
-					// There are known problems using is_weritable on IIS.  It may not be reliable - consider fileperms()
-					$fileinfo['writable'] = is_writable($file);
+					$fileinfo['writable'] = is_really_writable($file);
 					break;
 				case 'executable':
 					$fileinfo['executable'] = is_executable($file);
diff --git a/system/libraries/Session/drivers/Session_cookie.php b/system/libraries/Session/drivers/Session_cookie.php
index 971dfea..c8dfad6 100644
--- a/system/libraries/Session/drivers/Session_cookie.php
+++ b/system/libraries/Session/drivers/Session_cookie.php
@@ -395,7 +395,15 @@
 		$hmac	 = substr($session, $len);
 		$session = substr($session, 0, $len);
 
-		if ($hmac !== hash_hmac('sha1', $session, $this->encryption_key))
+		// Time-attack-safe comparison
+		$hmac_check = hash_hmac('sha1', $session, $this->encryption_key);
+		$diff = 0;
+		for ($i = 0; $i < 40; $i++)
+		{
+			$diff |= ord($hmac[$i]) ^ ord($hmac_check[$i]);
+		}
+
+		if ($diff !== 0)
 		{
 			log_message('error', 'The session cookie data did not match what was expected.');
 			$this->sess_destroy();
diff --git a/system/libraries/Zip.php b/system/libraries/Zip.php
index b10b0bb..58f0645 100644
--- a/system/libraries/Zip.php
+++ b/system/libraries/Zip.php
@@ -294,7 +294,7 @@
 			{
 				$name = str_replace('\\', '/', $path);
 
-				if ($preserve_filepath === FALSE)
+				if ($archive_filepath === FALSE)
 				{
 					$name = preg_replace('|.*/(.+)|', '\\1', $name);
 				}
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index ca22856..6854c9f 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -449,8 +449,8 @@
       -  Changed ``_exception_handler()`` to respect php.ini *display_errors* setting.
       -  Added function :func:`is_https()` to check if a secure connection is used.
       -  Added function :func:`is_cli()` to replace the ``CI_Input::is_cli_request()`` method.
-      -  Added function :func:`function_usable()` to check if a function exists and is not disabled by `Suhosin <http://www.hardened-php.net/suhosin/>`.
-      -  Removed the third (`$php_error`) from function :func:`log_message()`.
+      -  Added function :func:`function_usable()` to work around a bug in `Suhosin <http://www.hardened-php.net/suhosin/>`.
+      -  Removed the third (`$php_error`) argument from function :func:`log_message()`.
 
    -  :doc:`Output Library <libraries/output>` changes include:
 
@@ -519,7 +519,7 @@
 -  Fixed a bug (#177) - ``CI_Form_validation::set_value()`` didn't set the default value if POST data is NULL.
 -  Fixed a bug (#68, #414) - Oracle's escape_str() didn't properly escape LIKE wild characters.
 -  Fixed a bug (#81) - ODBC's list_fields() and field_data() methods skipped the first column due to odbc_field_*() functions' index starting at 1 instead of 0.
--  Fixed a bug (#129) - ODBC's num_rows() returned -1 in some cases, due to not all subdrivers supporting the odbc_num_rows() function.
+-  Fixed a bug (#129) - ODBC's ``num_rows()`` method returned -1 in some cases, due to not all subdrivers supporting the ``odbc_num_rows()`` function.
 -  Fixed a bug (#153) - E_NOTICE being generated by getimagesize() in the :doc:`File Uploading Library <libraries/file_uploading>`.
 -  Fixed a bug (#611) - SQLSRV's error handling methods used to issue warnings when there's no actual error.
 -  Fixed a bug (#1036) - ``is_write_type()`` method in the :doc:`Database Library <database/index>` didn't return TRUE for RENAME queries.
@@ -684,6 +684,7 @@
 -  Fixed a bug (#2729) - ``CI_Security::_validate_entities()`` used overly-intrusive ``preg_replace()`` patterns that produced false-positives.
 -  Fixed a bug (#2771) - ``CI_Security::xss_clean()`` didn't take into account HTML5 entities.
 -  Fixed a bug in the :doc:`Session Library <libraries/sessions>` 'cookie' driver where authentication was not performed for encrypted cookies.
+-  Fixed a bug (#2856) - ODBC method ``affected_rows()`` passed an incorrect value to ``odbc_num_rows()``.
 
 Version 2.1.4
 =============
diff --git a/user_guide_src/source/contributing/index.rst b/user_guide_src/source/contributing/index.rst
index 0771a41..4d3fe6e 100644
--- a/user_guide_src/source/contributing/index.rst
+++ b/user_guide_src/source/contributing/index.rst
@@ -33,7 +33,7 @@
 =========
 
 All code must meet the `Style Guide
-<http://codeigniter.com/user_guide/general/styleguide.html>`_, which is
+<http://ellislab.com/codeigniter/user-guide/general/styleguide.html>`_, which is
 essentially the `Allman indent style
 <http://en.wikipedia.org/wiki/Indent_style#Allman_style>`_, underscores and
 readable operators. This makes certain that all code is the same format as the
diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst
index e2536de..c466ca0 100644
--- a/user_guide_src/source/general/common_functions.rst
+++ b/user_guide_src/source/general/common_functions.rst
@@ -171,4 +171,9 @@
 
 	It is useful if you want to check for the availability of functions
 	such as ``eval()`` and ``exec()``, which are dangerous and might be
-	disabled on servers with highly restrictive security policies.
\ No newline at end of file
+	disabled on servers with highly restrictive security policies.
+
+	.. note:: This function was introduced because Suhosin terminated
+		script execution, but this turned out to be a bug. A fix
+		has been available for some time (version 0.9.34), but is
+		unfortunately not released yet.
\ No newline at end of file
diff --git a/user_guide_src/source/overview/at_a_glance.rst b/user_guide_src/source/overview/at_a_glance.rst
index 6dcfdbb..da323b9 100644
--- a/user_guide_src/source/overview/at_a_glance.rst
+++ b/user_guide_src/source/overview/at_a_glance.rst
@@ -111,4 +111,4 @@
 =============================================
 
 Our growing community of users can be seen actively participating in our
-`Community Forums <http://codeigniter.com/forums/>`_.
+`Community Forums <http://ellislab.com/forums/>`_.
diff --git a/user_guide_src/source/overview/getting_started.rst b/user_guide_src/source/overview/getting_started.rst
index 5157d48..feaad59 100644
--- a/user_guide_src/source/overview/getting_started.rst
+++ b/user_guide_src/source/overview/getting_started.rst
@@ -19,6 +19,6 @@
 native libraries and helper files.
 
 Feel free to take advantage of our `Community
-Forums <http://codeigniter.com/forums/>`_ if you have questions or
-problems, and our `Wiki <http://codeigniter.com/wiki/>`_ to see code
+Forums <http://ellislab.com/forums/>`_ if you have questions or
+problems, and our `Wiki <https://github.com/EllisLab/CodeIgniter/wiki>`_ to see code
 examples posted by other users.
diff --git a/user_guide_src/source/tutorial/conclusion.rst b/user_guide_src/source/tutorial/conclusion.rst
index 48fbdcc..a5f69b4 100644
--- a/user_guide_src/source/tutorial/conclusion.rst
+++ b/user_guide_src/source/tutorial/conclusion.rst
@@ -20,7 +20,7 @@
 If you still have questions about the framework or your own CodeIgniter
 code, you can:
 
--  Check out our `forums <http://codeigniter.com/forums>`_
--  Visit our `IRC chatroom <http://codeigniter.com/wiki/IRC>`_
--  Explore the `Wiki <http://codeigniter.com/wiki/>`_
+-  Check out our `forums <http://ellislab.com/forums>`_
+-  Visit our `IRC chatroom <https://github.com/EllisLab/CodeIgniter/wiki/IRC>`_
+-  Explore the `Wiki <https://github.com/EllisLab/CodeIgniter/wiki/>`_