From 1ddb38ecb7ac168ed11c74ce4f96203823fa5c31 Mon Sep 17 00:00:00 2001 From: yannik131 Date: Wed, 20 May 2026 18:08:33 +0200 Subject: [PATCH 1/5] Added additional escaping for filename controller --- .../Volumes/Filters/FilenameController.php | 4 +++ resources/assets/js/volumes/cloneForm.vue | 2 +- resources/assets/js/volumes/stores/filters.js | 2 +- routes/api.php | 2 +- .../Filters/FilenameControllerTest.php | 26 ++++++++++++++++--- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php b/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php index 84ff1576b..14e3ab4b2 100644 --- a/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php +++ b/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php @@ -30,6 +30,10 @@ public function index($id, $pattern) $volume = Volume::findOrFail($id); $this->authorize('access', $volume); + // Decode percent-encoded characters, example: + // Frontend turns / into %2F, we turn it back to / + $pattern = rawurldecode($pattern); + // Escape trailing backslashes, else there would be an error with ilike. $pattern = preg_replace('/\\\\$/', '\\\\\\\\', $pattern); diff --git a/resources/assets/js/volumes/cloneForm.vue b/resources/assets/js/volumes/cloneForm.vue index cdf39cb80..ff0db8121 100644 --- a/resources/assets/js/volumes/cloneForm.vue +++ b/resources/assets/js/volumes/cloneForm.vue @@ -99,7 +99,7 @@ export default { let promise1 = VolumeApi.queryFilenames({id: this.id}); let promise2 = VolumeApi.queryFilesWithFilename({ id: this.id, - pattern: this.filePattern + pattern: encodeURIComponent(this.filePattern) }); Promise.all([promise1, promise2]) diff --git a/resources/assets/js/volumes/stores/filters.js b/resources/assets/js/volumes/stores/filters.js index eb5792cff..9e4333db2 100644 --- a/resources/assets/js/volumes/stores/filters.js +++ b/resources/assets/js/volumes/stores/filters.js @@ -205,7 +205,7 @@ let filenameFilter = { getSequence(volumeId, pattern) { return VolumesApi.queryFilesWithFilename({ id: volumeId, - pattern: pattern, + pattern: encodeURIComponent(pattern), }); }, }; diff --git a/routes/api.php b/routes/api.php index 626a70cae..58db1204a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -432,7 +432,7 @@ $router->get('{id}/files/filter/filename/{pattern}', [ 'uses' => 'Filters\FilenameController@index', - ]); + ])->where('pattern', '.*'); $router->get('{id}/file-labels', [ 'uses' => 'UsedFileLabelsController@index', diff --git a/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php b/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php index b437f8a74..b5eede3d2 100644 --- a/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php +++ b/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php @@ -62,14 +62,32 @@ public function testIndexEscape() { $vid = $this->volume()->id; - $image = ImageTest::create([ + $image1 = ImageTest::create([ 'volume_id' => $vid, 'filename' => 'abcde.jpg', ]); + $image2 = ImageTest::create([ + 'volume_id' => $vid, + 'filename' => '/.jpg' + ]); + $image3 = ImageTest::create([ + 'volume_id' => $vid, + 'filename' => '%2F.jpg' // encodeURIComponent("/") yields "%2F" + ]); + + $expectedResults = [ + '*cde*%5C' => [], + '%2F.jpg' => [$image2->id], // "/.jpg" + '%2F*' => [$image2->id], // "/*" + '%25252F.jpg' => [$image3->id], // "%2F.jpg" + ]; + $this->beGuest(); - $response = $this->json('GET', "/api/v1/volumes/{$vid}/files/filter/filename/*cde*%5C") - ->assertExactJson([]); - $response->assertStatus(200); + foreach ($expectedResults as $pattern => $expectedIds) { + $response = $this->json('GET', "/api/v1/volumes/{$vid}/files/filter/filename/{$pattern}") + ->assertExactJson($expectedIds); + $response->assertStatus(200); + } } public function testIndexVideo() From 9448a4162a63de5a2c266638059ed7cadcc127da Mon Sep 17 00:00:00 2001 From: yannik131 Date: Thu, 21 May 2026 11:38:25 +0200 Subject: [PATCH 2/5] Removed trailing whitespace, formatting --- .../Api/Volumes/Filters/FilenameControllerTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php b/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php index b5eede3d2..31b14c8f8 100644 --- a/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php +++ b/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php @@ -74,18 +74,18 @@ public function testIndexEscape() 'volume_id' => $vid, 'filename' => '%2F.jpg' // encodeURIComponent("/") yields "%2F" ]); - + $expectedResults = [ '*cde*%5C' => [], '%2F.jpg' => [$image2->id], // "/.jpg" '%2F*' => [$image2->id], // "/*" '%25252F.jpg' => [$image3->id], // "%2F.jpg" ]; - + $this->beGuest(); foreach ($expectedResults as $pattern => $expectedIds) { $response = $this->json('GET', "/api/v1/volumes/{$vid}/files/filter/filename/{$pattern}") - ->assertExactJson($expectedIds); + ->assertExactJson($expectedIds); $response->assertStatus(200); } } From ed957be40eae2f5b7b8d5175802684b4baedd16b Mon Sep 17 00:00:00 2001 From: Yannik Schroeder Date: Thu, 21 May 2026 12:13:04 +0200 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Api/Volumes/Filters/FilenameControllerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php b/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php index 31b14c8f8..55df62e3e 100644 --- a/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php +++ b/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php @@ -68,11 +68,11 @@ public function testIndexEscape() ]); $image2 = ImageTest::create([ 'volume_id' => $vid, - 'filename' => '/.jpg' + 'filename' => '/.jpg', ]); $image3 = ImageTest::create([ 'volume_id' => $vid, - 'filename' => '%2F.jpg' // encodeURIComponent("/") yields "%2F" + 'filename' => '%2F.jpg', // encodeURIComponent("/") yields "%2F" ]); $expectedResults = [ From fe6f98aa162bc916b60061b27fef07410e8cfb3c Mon Sep 17 00:00:00 2001 From: yannik131 Date: Fri, 22 May 2026 19:05:43 +0200 Subject: [PATCH 4/5] Remove manual encoding/decoding, unravel test loop --- .../Volumes/Filters/FilenameController.php | 2 +- resources/assets/js/volumes/cloneForm.vue | 2 +- resources/assets/js/volumes/stores/filters.js | 2 +- .../Filters/FilenameControllerTest.php | 26 ++++++++++--------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php b/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php index 14e3ab4b2..37ec51932 100644 --- a/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php +++ b/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php @@ -32,7 +32,7 @@ public function index($id, $pattern) // Decode percent-encoded characters, example: // Frontend turns / into %2F, we turn it back to / - $pattern = rawurldecode($pattern); + // $pattern = rawurldecode($pattern); // Escape trailing backslashes, else there would be an error with ilike. $pattern = preg_replace('/\\\\$/', '\\\\\\\\', $pattern); diff --git a/resources/assets/js/volumes/cloneForm.vue b/resources/assets/js/volumes/cloneForm.vue index ff0db8121..cdf39cb80 100644 --- a/resources/assets/js/volumes/cloneForm.vue +++ b/resources/assets/js/volumes/cloneForm.vue @@ -99,7 +99,7 @@ export default { let promise1 = VolumeApi.queryFilenames({id: this.id}); let promise2 = VolumeApi.queryFilesWithFilename({ id: this.id, - pattern: encodeURIComponent(this.filePattern) + pattern: this.filePattern }); Promise.all([promise1, promise2]) diff --git a/resources/assets/js/volumes/stores/filters.js b/resources/assets/js/volumes/stores/filters.js index 9e4333db2..eb5792cff 100644 --- a/resources/assets/js/volumes/stores/filters.js +++ b/resources/assets/js/volumes/stores/filters.js @@ -205,7 +205,7 @@ let filenameFilter = { getSequence(volumeId, pattern) { return VolumesApi.queryFilesWithFilename({ id: volumeId, - pattern: encodeURIComponent(pattern), + pattern: pattern, }); }, }; diff --git a/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php b/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php index 55df62e3e..8d3a46634 100644 --- a/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php +++ b/tests/php/Http/Controllers/Api/Volumes/Filters/FilenameControllerTest.php @@ -75,19 +75,21 @@ public function testIndexEscape() 'filename' => '%2F.jpg', // encodeURIComponent("/") yields "%2F" ]); - $expectedResults = [ - '*cde*%5C' => [], - '%2F.jpg' => [$image2->id], // "/.jpg" - '%2F*' => [$image2->id], // "/*" - '%25252F.jpg' => [$image3->id], // "%2F.jpg" - ]; - $this->beGuest(); - foreach ($expectedResults as $pattern => $expectedIds) { - $response = $this->json('GET', "/api/v1/volumes/{$vid}/files/filter/filename/{$pattern}") - ->assertExactJson($expectedIds); - $response->assertStatus(200); - } + + $response = $this->json('GET', "/api/v1/volumes/{$vid}/files/filter/filename/*cde*%5C") + ->assertExactJson([]); + $response->assertStatus(200); + + $pattern = rawurlencode("/.jpg"); + $response = $this->json('GET', "/api/v1/volumes/{$vid}/files/filter/filename/$pattern") + ->assertExactJson([$image2->id]); + $response->assertStatus(200); + + $pattern = rawurlencode("%2F.jpg"); + $response = $this->json('GET', "/api/v1/volumes/{$vid}/files/filter/filename/$pattern") + ->assertExactJson([$image3->id]); + $response->assertStatus(200); } public function testIndexVideo() From 5c3d72e61a7ae32648979de4c3abc43ddcaa4150 Mon Sep 17 00:00:00 2001 From: yannik131 Date: Sun, 24 May 2026 13:36:38 +0200 Subject: [PATCH 5/5] Removed unused code --- .../Controllers/Api/Volumes/Filters/FilenameController.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php b/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php index 37ec51932..84ff1576b 100644 --- a/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php +++ b/app/Http/Controllers/Api/Volumes/Filters/FilenameController.php @@ -30,10 +30,6 @@ public function index($id, $pattern) $volume = Volume::findOrFail($id); $this->authorize('access', $volume); - // Decode percent-encoded characters, example: - // Frontend turns / into %2F, we turn it back to / - // $pattern = rawurldecode($pattern); - // Escape trailing backslashes, else there would be an error with ilike. $pattern = preg_replace('/\\\\$/', '\\\\\\\\', $pattern);