From a75d5b8bc17f5d1352ae5db7804df10f26ff751b Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Thu, 22 Feb 2024 11:22:08 +0000
Subject: [PATCH] Sessions: Prevent image urls being part of session URL
 history

To prevent them being considered for redirects.
Includes test to cover.
For #4863
---
 app/Http/Kernel.php                          |  2 +-
 app/Http/Middleware/StartSessionExtended.php | 34 ++++++++++++++++++++
 tests/Uploads/ImageTest.php                  | 23 +++++++++++++
 3 files changed, 58 insertions(+), 1 deletion(-)
 create mode 100644 app/Http/Middleware/StartSessionExtended.php

diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index 1b96ff3db..d23f56a2c 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -28,7 +28,7 @@ class Kernel extends HttpKernel
             \BookStack\Http\Middleware\ApplyCspRules::class,
             \BookStack\Http\Middleware\EncryptCookies::class,
             \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
-            \Illuminate\Session\Middleware\StartSession::class,
+            \BookStack\Http\Middleware\StartSessionExtended::class,
             \Illuminate\View\Middleware\ShareErrorsFromSession::class,
             \BookStack\Http\Middleware\VerifyCsrfToken::class,
             \BookStack\Http\Middleware\CheckEmailConfirmed::class,
diff --git a/app/Http/Middleware/StartSessionExtended.php b/app/Http/Middleware/StartSessionExtended.php
new file mode 100644
index 000000000..26cd250ac
--- /dev/null
+++ b/app/Http/Middleware/StartSessionExtended.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace BookStack\Http\Middleware;
+
+use Illuminate\Http\Request;
+use Illuminate\Session\Middleware\StartSession as Middleware;
+
+/**
+ * An extended version of the default Laravel "StartSession" middleware
+ * with customizations applied as required:
+ *
+ * - Adds filtering for the request URLs stored in session history.
+ */
+class StartSessionExtended extends Middleware
+{
+    protected static array $pathPrefixesExcludedFromHistory = [
+        'uploads/images/'
+    ];
+
+    /**
+     * @inheritdoc
+     */
+    protected function storeCurrentUrl(Request $request, $session): void
+    {
+        $requestPath = strtolower($request->path());
+        foreach (static::$pathPrefixesExcludedFromHistory as $excludedPath) {
+            if (str_starts_with($requestPath, $excludedPath)) {
+                return;
+            }
+        }
+
+        parent::storeCurrentUrl($request, $session);
+    }
+}
diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php
index af249951f..d24b6202b 100644
--- a/tests/Uploads/ImageTest.php
+++ b/tests/Uploads/ImageTest.php
@@ -383,6 +383,29 @@ class ImageTest extends TestCase
         }
     }
 
+    public function test_secure_images_not_tracked_in_session_history()
+    {
+        config()->set('filesystems.images', 'local_secure');
+        $this->asEditor();
+        $page = $this->entities->page();
+        $result = $this->files->uploadGalleryImageToPage($this, $page);
+        $expectedPath = storage_path($result['path']);
+        $this->assertFileExists($expectedPath);
+
+        $this->get('/books');
+        $this->assertEquals(url('/books'), session()->previousUrl());
+
+        $resp = $this->get($result['path']);
+        $resp->assertOk();
+        $resp->assertHeader('Content-Type', 'image/png');
+
+        $this->assertEquals(url('/books'), session()->previousUrl());
+
+        if (file_exists($expectedPath)) {
+            unlink($expectedPath);
+        }
+    }
+
     public function test_system_images_remain_public_with_local_secure_restricted()
     {
         config()->set('filesystems.images', 'local_secure_restricted');