From 38743404da21c61cc7fa65cf51e10eff21c08e21 Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+github@gmail.com>
Date: Thu, 17 Feb 2022 07:51:00 +0100
Subject: [PATCH] introduce js encodeURI() equivalent in php and use it for
 X-Location header, fixes #684

---
 app/controllers/studip_controller.php      |  2 +-
 lib/functions.php                          | 34 ++++++++++++++++++++++
 resources/assets/javascripts/lib/dialog.js |  2 +-
 tests/unit/lib/FunctionsTest.php           | 18 ++++++++++--
 4 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/app/controllers/studip_controller.php b/app/controllers/studip_controller.php
index 556ff8b6284..7c0991e4806 100644
--- a/app/controllers/studip_controller.php
+++ b/app/controllers/studip_controller.php
@@ -349,7 +349,7 @@ abstract class StudipController extends Trails_Controller
         $to = $this->adjustToArguments(...func_get_args());
 
         if (Request::isDialog()) {
-            $this->response->add_header('X-Location', rawurlencode($to));
+            $this->response->add_header('X-Location', encodeURI($to));
             $this->render_nothing();
         } else {
             parent::redirect($to);
diff --git a/lib/functions.php b/lib/functions.php
index 96dd9551731..bbed6298f50 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -1827,3 +1827,37 @@ function get_default_http_stream_context($url = '')
     }
     return stream_context_get_default($opts);
 }
+
+/**
+ * Encodes an uri just like encodeURI() in Javascript would do.
+ *
+ * encodeURI() escapes all characters except:
+ *
+ *     A-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #
+ *
+ * @param string $uri
+ * @return string
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
+ */
+function encodeURI(string $uri): string
+{
+    $replacements = [
+        '%21' => '!',
+        '%23' => '#',
+        '%24' => '$',
+        '%26' => '&',
+        '%27' => "'",
+        '%28' => '(',
+        '%29' => ')',
+        '%2A' => '*',
+        '%2B' => '+',
+        '%2C' => ',',
+        '%3B' => ';',
+        '%2F' => '/',
+        '%3A' => ':',
+        '%3D' => '=',
+        '%3F' => '?',
+        '%40' => '@',
+    ];
+    return strtr(rawurlencode($uri), $replacements);
+}
diff --git a/resources/assets/javascripts/lib/dialog.js b/resources/assets/javascripts/lib/dialog.js
index de016ed72f0..0ce30c6eef6 100644
--- a/resources/assets/javascripts/lib/dialog.js
+++ b/resources/assets/javascripts/lib/dialog.js
@@ -103,7 +103,7 @@ const Dialog = {
 
 // Handler for HTTP header X-Location: Relocate to another location
 Dialog.handlers.header['X-Location'] = function(location, options) {
-    location = decodeURIComponent(location);
+    location = decodeURI(location);
 
     if (document.location.href === location) {
         document.location.reload(true);
diff --git a/tests/unit/lib/FunctionsTest.php b/tests/unit/lib/FunctionsTest.php
index 2d3c868f3ea..61ad409ed50 100644
--- a/tests/unit/lib/FunctionsTest.php
+++ b/tests/unit/lib/FunctionsTest.php
@@ -42,7 +42,7 @@ class FunctionsTest extends \Codeception\Test\Unit
 
         $this->assertEquals(range(1, 7), array_flatten($array));
     }
-    
+
     function testRelsize()
     {
         // Test basic sizes and suffixed 's' if value is <> 1
@@ -62,7 +62,7 @@ class FunctionsTest extends \Codeception\Test\Unit
         $this->assertEquals('1 Exabyte', relsize(pow(1024, 6)));
         $this->assertEquals('1 Zettabyte', relsize(pow(1024, 7)));
         $this->assertEquals('1 Yottabyte', relsize(pow(1024, 8)));
-        
+
         // Test displayed levels
         $this->assertEquals('1 Megabyte', relsize(1024 * 1024 + 2 * 1024 + 3, true, 1));
         $this->assertEquals('1.5 Megabytes', relsize(1024 * 1024 + 512 * 1024 + 3, true, 1));
@@ -70,4 +70,18 @@ class FunctionsTest extends \Codeception\Test\Unit
         $this->assertEquals('1 Megabyte, 2 Kilobytes, 3 Bytes', relsize(1024 * 1024 + 2 * 1024 + 3, true, 3));
         $this->assertEquals('1 Megabyte, 2 Kilobytes, 3 Bytes', relsize(1024 * 1024 + 2 * 1024 + 3, true, 0));
     }
+
+    public function testEncodeURI()
+    {
+        $input = 'A-Za-z0-9;,/?:@&=+$-_.!~*\'()#';
+        $this->assertEquals($input, encodeURI($input));
+
+        $input = 'https://example.org/?x=шеллы';
+        $output = 'https://example.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B';
+        $this->assertEquals($output, encodeURI($input));
+
+        $input = 'https://mäuschen-hüpft.de/öffnungszeiten?menu=Spaß&page=23';
+        $output = 'https://m%C3%A4uschen-h%C3%BCpft.de/%C3%B6ffnungszeiten?menu=Spa%C3%9F&page=23';
+        $this->assertEquals($output, encodeURI($input));
+    }
 }
-- 
GitLab