Merge branch 'security/entity_decode' into 3.1-stable
diff --git a/system/core/Security.php b/system/core/Security.php
index 3a5da4f..4a69daa 100644
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -669,6 +669,22 @@
 			? ENT_COMPAT | ENT_HTML5
 			: ENT_COMPAT;
 
+		if ( ! isset($_entities))
+		{
+			$_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, $flag, $charset));
+
+			// If we're not on PHP 5.4+, add the possibly dangerous HTML 5
+			// entities to the array manually
+			if ($flag === ENT_COMPAT)
+			{
+				$_entities[':'] = ':';
+				$_entities['('] = '(';
+				$_entities[')'] = ')';
+				$_entities["\n"] = '
';
+				$_entities["\t"] = '	';
+			}
+		}
+
 		do
 		{
 			$str_compare = $str;
@@ -676,22 +692,6 @@
 			// Decode standard entities, avoiding false positives
 			if (preg_match_all('/&[a-z]{2,}(?![a-z;])/i', $str, $matches))
 			{
-				if ( ! isset($_entities))
-				{
-					$_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, $flag, $charset));
-
-					// If we're not on PHP 5.4+, add the possibly dangerous HTML 5
-					// entities to the array manually
-					if ($flag === ENT_COMPAT)
-					{
-						$_entities[':'] = ':';
-						$_entities['('] = '(';
-						$_entities[')'] = ')';
-						$_entities["\n"] = '&newline;';
-						$_entities["\t"] = '&tab;';
-					}
-				}
-
 				$replace = array();
 				$matches = array_unique(array_map('strtolower', $matches[0]));
 				foreach ($matches as &$match)
@@ -702,7 +702,7 @@
 					}
 				}
 
-				$str = str_ireplace(array_keys($replace), array_values($replace), $str);
+				$str = str_replace(array_keys($replace), array_values($replace), $str);
 			}
 
 			// Decode numeric & UTF16 two byte entities
@@ -711,6 +711,11 @@
 				$flag,
 				$charset
 			);
+
+			if ($flag === ENT_COMPAT)
+			{
+				$str = str_replace(array_values($_entities), array_keys($_entities), $str);
+			}
 		}
 		while ($str_compare !== $str);
 		return $str;
diff --git a/tests/codeigniter/core/Security_test.php b/tests/codeigniter/core/Security_test.php
index 8328c37..cbf0285 100644
--- a/tests/codeigniter/core/Security_test.php
+++ b/tests/codeigniter/core/Security_test.php
@@ -270,6 +270,12 @@
 
 		$this->assertEquals('<div>Hello <b>Booya</b></div>', $decoded);
 
+		$this->assertEquals('colon:',    $this->security->entity_decode('colon&colon;'));
+		$this->assertEquals("NewLine\n", $this->security->entity_decode('NewLine&NewLine;'));
+		$this->assertEquals("Tab\t",     $this->security->entity_decode('Tab&Tab;'));
+		$this->assertEquals("lpar(",     $this->security->entity_decode('lpar&lpar;'));
+		$this->assertEquals("rpar)",     $this->security->entity_decode('rpar&rpar;'));
+
 		// Issue #3057 (https://github.com/bcit-ci/CodeIgniter/issues/3057)
 		$this->assertEquals(
 			'&foo should not include a semicolon',
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index d025d52..1e59d4c 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -7,6 +7,10 @@
 
 Release Date: Not Released
 
+-  **Security**
+
+   -  Fixed a flaw in :doc:`Security Library <libraries/security>` method ``entity_decode()`` (used by ``xss_clean()``) that affects HTML 5 entities when using PHP 5.3.
+
 -  General Changes
 
    -  Added ``E_PARSE`` to the list of error levels detected by the shutdown handler.