Repository: codeigniter4/CodeIgniter4 (PHP)

Issue:
Bug: esc(..., 'raw')  fails on recursive data structures
### PHP Version

8.2

### CodeIgniter4 Version

4.4.6

### CodeIgniter4 Installation Method

Composer (as dependency to an existing project)

### Which operating systems have you tested for this bug?

Linux

### Which server did you use?

apache

### Database

_No response_

### What happened?

In the process of upgrading from CI3 -> 4, I encountered a problem with a `view()` that uses a fairly complex `$data` structure including &references between elements. Even though the `view()` helper calls `setData()` with `$context = 'raw'`, this still ends up in infinite recursion because `esc()` iterates through the fields of the array and calls `esc(..., 'raw')` on them. This whole operation is completely unnecessary because in the end it does not escape anything. However, because of the self-references, this walking of the array ends up in infinite recursion.

The `esc()` function should return `$data` immediately in the case of 'raw'. There's no need to walk the whole array tree just to build it anew.

### Steps to Reproduce

A non-realistic example, just to illustrate the point:
```php
$data = ['key' => []];
$data['key']['recur'] = &$data;
esc($data, 'raw');
```

### Expected Output

The exact same $data array, without iterating its values.

### Anything else?

_No response_


Discussion:
You have a valid point. Can you send a PR?
https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/README.md



Patch:
diff --git a/system/Common.php b/system/Common.php
index 093943effb47..2ad287fb26ea 100644
--- a/system/Common.php
+++ b/system/Common.php
@@ -426,6 +426,15 @@ function env(string $key, $default = null)
      */
     function esc($data, string $context = 'html', ?string $encoding = null)
     {
+        $context = strtolower($context);
+
+        // Provide a way to NOT escape data since
+        // this could be called automatically by
+        // the View library.
+        if ($context === 'raw') {
+            return $data;
+        }
+
         if (is_array($data)) {
             foreach ($data as &$value) {
                 $value = esc($value, $context);
@@ -433,15 +442,6 @@ function esc($data, string $context = 'html', ?string $encoding = null)
         }
 
         if (is_string($data)) {
-            $context = strtolower($context);
-
-            // Provide a way to NOT escape data since
-            // this could be called automatically by
-            // the View library.
-            if ($context === 'raw') {
-                return $data;
-            }
-
             if (! in_array($context, ['html', 'js', 'css', 'url', 'attr'], true)) {
                 throw new InvalidArgumentException('Invalid escape context provided.');
             }


Test patch:
diff --git a/tests/system/CommonFunctionsTest.php b/tests/system/CommonFunctionsTest.php
index a097cb2a81a0..89da3e89c4ad 100644
--- a/tests/system/CommonFunctionsTest.php
+++ b/tests/system/CommonFunctionsTest.php
@@ -247,6 +247,27 @@ public function testEscapeBadContextZero(): void
         esc('<script>', '0');
     }
 
+    public function testEscapeArray(): void
+    {
+        $data = [
+            'a' => [
+                'b' => 'c&',
+            ],
+            'd' => 'e>',
+        ];
+        $expected           = $data;
+        $expected['a']['b'] = 'c&amp;';
+        $expected['d']      = 'e&gt;';
+        $this->assertSame($expected, esc($data));
+    }
+
+    public function testEscapeRecursiveArrayRaw(): void
+    {
+        $data      = ['a' => 'b', 'c' => 'd'];
+        $data['e'] = &$data;
+        $this->assertSame($data, esc($data, 'raw'));
+    }
+
     /**
      * @runInSeparateProcess
      * @preserveGlobalState disabled