This commit is contained in:
DariusIII
2026-06-04 09:33:02 +02:00
11 changed files with 120 additions and 33 deletions
+13
View File
@@ -304,6 +304,19 @@ if (! function_exists('escapeString')) {
}
}
if (! function_exists('regex_display_value')) {
/**
* Decode entity-encoded regex text at the presentation boundary.
*
* Blade should still render the returned value with escaped {{ }} output so regexes that
* contain HTML-looking text remain safe while displaying named groups and quotes readably.
*/
function regex_display_value(mixed $value): string
{
return html_entity_decode((string) ($value ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
}
if (! function_exists('realDuration')) {
function realDuration(mixed $milliseconds): string
Generated
+16 -16
View File
@@ -2162,16 +2162,16 @@
},
{
"name": "laravel/horizon",
"version": "v5.47.1",
"version": "v5.47.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/horizon.git",
"reference": "356e3ec86321361a4cb8259cb73fdb3406fa3b30"
"reference": "a6ac142293ad02db4d7cccb961dd32f56ef1462d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/horizon/zipball/356e3ec86321361a4cb8259cb73fdb3406fa3b30",
"reference": "356e3ec86321361a4cb8259cb73fdb3406fa3b30",
"url": "https://api.github.com/repos/laravel/horizon/zipball/a6ac142293ad02db4d7cccb961dd32f56ef1462d",
"reference": "a6ac142293ad02db4d7cccb961dd32f56ef1462d",
"shasum": ""
},
"require": {
@@ -2236,9 +2236,9 @@
],
"support": {
"issues": "https://github.com/laravel/horizon/issues",
"source": "https://github.com/laravel/horizon/tree/v5.47.1"
"source": "https://github.com/laravel/horizon/tree/v5.47.2"
},
"time": "2026-05-20T12:12:55+00:00"
"time": "2026-06-03T15:11:37+00:00"
},
{
"name": "laravel/prompts",
@@ -2301,16 +2301,16 @@
},
{
"name": "laravel/pulse",
"version": "v1.7.3",
"version": "v1.7.4",
"source": {
"type": "git",
"url": "https://github.com/laravel/pulse.git",
"reference": "dfd3b8e98cd1ada8d105e528da0afe1bbfe7d990"
"reference": "6daab88ef368c0a1272a4401d90d514b30829225"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pulse/zipball/dfd3b8e98cd1ada8d105e528da0afe1bbfe7d990",
"reference": "dfd3b8e98cd1ada8d105e528da0afe1bbfe7d990",
"url": "https://api.github.com/repos/laravel/pulse/zipball/6daab88ef368c0a1272a4401d90d514b30829225",
"reference": "6daab88ef368c0a1272a4401d90d514b30829225",
"shasum": ""
},
"require": {
@@ -2385,7 +2385,7 @@
"issues": "https://github.com/laravel/pulse/issues",
"source": "https://github.com/laravel/pulse"
},
"time": "2026-03-26T14:27:31+00:00"
"time": "2026-06-03T14:04:01+00:00"
},
{
"name": "laravel/sentinel",
@@ -12845,16 +12845,16 @@
},
{
"name": "laravel/sail",
"version": "v1.61.0",
"version": "v1.62.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/sail.git",
"reference": "68ef35015630fe510432e63e11e21749006df688"
"reference": "3aaeefc979f8ba6586fbc5b6e0b1b3638058f98e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sail/zipball/68ef35015630fe510432e63e11e21749006df688",
"reference": "68ef35015630fe510432e63e11e21749006df688",
"url": "https://api.github.com/repos/laravel/sail/zipball/3aaeefc979f8ba6586fbc5b6e0b1b3638058f98e",
"reference": "3aaeefc979f8ba6586fbc5b6e0b1b3638058f98e",
"shasum": ""
},
"require": {
@@ -12904,7 +12904,7 @@
"issues": "https://github.com/laravel/sail/issues",
"source": "https://github.com/laravel/sail"
},
"time": "2026-05-23T23:33:57+00:00"
"time": "2026-05-27T04:02:01+00:00"
},
{
"name": "localheinz/diff",
@@ -43,7 +43,7 @@
id="group_regex"
name="group_regex"
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value="{{ htmlspecialchars($regex->group_regex ?? '') }}"
value="{{ $regex->group_regex ?? '' }}"
required>
</div>
<p class="mt-2 text-sm text-gray-500">
@@ -66,7 +66,7 @@
name="regex"
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm"
rows="4"
required>{{ htmlspecialchars($regex->regex ?? '') }}</textarea>
required>{{ regex_display_value($regex->regex ?? '') }}</textarea>
</div>
<p class="mt-2 text-sm text-gray-500">
Regex to use when categorizing releases.<br>
@@ -87,7 +87,7 @@
<textarea id="description"
name="description"
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
rows="3">{{ htmlspecialchars($regex->description ?? '') }}</textarea>
rows="3">{{ $regex->description ?? '' }}</textarea>
</div>
<p class="mt-2 text-sm text-gray-500">
Description for this regex. You can include an example usenet subject this regex would match on.
@@ -94,8 +94,8 @@
</td>
<td class="px-6 py-4 text-sm">
<div class="max-w-[200px] wrap-break-word">
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-xs break-all" title="{{ htmlspecialchars($row->regex) }}">
{{ htmlspecialchars($row->regex) }}
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-xs break-all" title="{{ regex_display_value($row->regex) }}">
{{ regex_display_value($row->regex) }}
</code>
</div>
</td>
@@ -43,7 +43,7 @@
id="group_regex"
name="group_regex"
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value="{{ htmlspecialchars($regex->group_regex ?? '') }}"
value="{{ $regex->group_regex ?? '' }}"
required>
</div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
@@ -66,7 +66,7 @@
name="regex"
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm"
rows="4"
required>{{ htmlspecialchars($regex->regex ?? '') }}</textarea>
required>{{ regex_display_value($regex->regex ?? '') }}</textarea>
</div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
Regex to use when grouping binaries into collections.<br>
@@ -87,7 +87,7 @@
<textarea id="description"
name="description"
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
rows="3">{{ htmlspecialchars($regex->description ?? '') }}</textarea>
rows="3">{{ $regex->description ?? '' }}</textarea>
</div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
Description for this regex. You can include an example usenet subject this regex would match on.
@@ -93,8 +93,8 @@
</td>
<td class="px-6 py-4 text-sm">
<div class="max-w-[200px] wrap-break-word">
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-xs break-all" title="{{ htmlspecialchars($row->regex) }}">
{{ htmlspecialchars($row->regex) }}
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-xs break-all" title="{{ regex_display_value($row->regex) }}">
{{ regex_display_value($row->regex) }}
</code>
</div>
</td>
@@ -73,7 +73,7 @@
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm"
rows="4"
required
placeholder="/^(?P<name>.*?)([\. ]S\d{1,3}[\. ]?E\d{1,3})/i">{{ $regex }}</textarea>
placeholder="/^(?P<name>.*?)([\. ]S\d{1,3}[\. ]?E\d{1,3})/i">{{ regex_display_value($regex) }}</textarea>
<p class="mt-2 text-sm text-gray-500">
Enter the regex pattern to test. Include delimiters and flags.
</p>
@@ -43,7 +43,7 @@
id="group_regex"
name="group_regex"
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value="{{ htmlspecialchars($regex->group_regex ?? '') }}"
value="{{ $regex->group_regex ?? '' }}"
required>
</div>
<p class="mt-2 text-sm text-gray-500">
@@ -66,7 +66,7 @@
name="regex"
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm"
rows="4"
required>{{ htmlspecialchars($regex->regex ?? '') }}</textarea>
required>{{ regex_display_value($regex->regex ?? '') }}</textarea>
</div>
<p class="mt-2 text-sm text-gray-500">
Regex to use when renaming releases.<br>
@@ -87,7 +87,7 @@
<textarea id="description"
name="description"
class="pl-10 w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
rows="3">{{ htmlspecialchars($regex->description ?? '') }}</textarea>
rows="3">{{ $regex->description ?? '' }}</textarea>
</div>
<p class="mt-2 text-sm text-gray-500">
Description for this regex. You can include an example release name this regex would match on.
@@ -93,8 +93,8 @@
</td>
<td class="px-6 py-4 text-sm">
<div class="max-w-[200px] wrap-break-word">
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-xs break-all" title="{{ htmlspecialchars($row->regex) }}">
{{ htmlspecialchars($row->regex) }}
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-xs break-all" title="{{ regex_display_value($row->regex) }}">
{{ regex_display_value($row->regex) }}
</code>
</div>
</td>
@@ -90,7 +90,7 @@
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm"
rows="4"
required
placeholder="/^(?P<name>.*?)([\. ]S\d{1,3}[\. ]?E\d{1,3})/i">{{ $regex }}</textarea>
placeholder="/^(?P<name>.*?)([\. ]S\d{1,3}[\. ]?E\d{1,3})/i">{{ regex_display_value($regex) }}</textarea>
<p class="mt-2 text-sm text-gray-500">
Enter the regex pattern to test. Include delimiters and flags.
</p>
@@ -166,6 +166,63 @@ class AdminRegexesControllerTest extends TestCase
->assertNotFound();
}
public function test_admin_regex_list_pages_decode_entity_encoded_regexes_without_double_escaping(): void
{
$admin = $this->createUserWithRole('Admin');
$rawRegex = '/^(?P<name>.+?) - "(?P<title>.+?)"$/i';
$entityEncodedRegex = '/^(?P&lt;name&gt;.+?) - &quot;(?P&lt;title&gt;.+?)&quot;$/i';
$scriptTag = '<'.'script>alert("x")</'.'script>';
$htmlLookingRegex = '/^(?P&lt;name&gt;.+?)'.$scriptTag.'$/i';
foreach (['release_naming_regexes', 'collection_regexes', 'category_regexes'] as $table) {
$this->insertRegexFixture($table, $rawRegex, 'Raw regex fixture');
$this->insertRegexFixture($table, $entityEncodedRegex, 'Entity encoded regex fixture');
$this->insertRegexFixture($table, $htmlLookingRegex, 'HTML-looking regex fixture');
}
foreach ([
route('admin.release_naming_regexes-list'),
route('admin.collection_regexes-list'),
route('admin.category_regexes-list'),
] as $url) {
$response = $this->actingAs($admin)->get($url);
$response->assertOk()
->assertSee(e($rawRegex), false)
->assertSee(e(html_entity_decode($entityEncodedRegex, ENT_QUOTES | ENT_HTML5, 'UTF-8')), false)
->assertSee(e(html_entity_decode($htmlLookingRegex, ENT_QUOTES | ENT_HTML5, 'UTF-8')), false)
->assertDontSee('&amp;quot;', false)
->assertDontSee('&amp;lt;', false)
->assertDontSee('&amp;gt;', false)
->assertDontSee($scriptTag, false);
}
}
public function test_admin_regex_edit_pages_decode_entity_encoded_regexes_without_double_escaping(): void
{
$admin = $this->createUserWithRole('Admin');
$entityEncodedRegex = '/^(?P&lt;name&gt;.+?) - &quot;(?P&lt;title&gt;.+?)&quot;$/i';
$expectedRenderedRegex = e(html_entity_decode($entityEncodedRegex, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
$releaseNamingRegexId = $this->insertRegexFixture('release_naming_regexes', $entityEncodedRegex, 'Release naming encoded regex');
$collectionRegexId = $this->insertRegexFixture('collection_regexes', $entityEncodedRegex, 'Collection encoded regex');
$categoryRegexId = $this->insertRegexFixture('category_regexes', $entityEncodedRegex, 'Category encoded regex');
foreach ([
route('admin.release_naming_regexes-edit', ['id' => (string) $releaseNamingRegexId]),
route('admin.collection_regexes-edit', ['id' => (string) $collectionRegexId]),
route('admin.category_regexes-edit', ['id' => (string) $categoryRegexId]),
] as $url) {
$this->actingAs($admin)
->get($url)
->assertOk()
->assertSee($expectedRenderedRegex, false)
->assertDontSee('&amp;quot;', false)
->assertDontSee('&amp;lt;', false)
->assertDontSee('&amp;gt;', false);
}
}
private function setEnvironmentValue(string $key, ?string $value): void
{
if ($value === null) {
@@ -373,6 +430,23 @@ class AdminRegexesControllerTest extends TestCase
return $user->fresh();
}
private function insertRegexFixture(string $table, string $regex, string $description): int
{
$data = [
'group_regex' => 'alt\\.binaries\\.example',
'regex' => $regex,
'description' => $description,
'ordinal' => 10,
'status' => 1,
];
if ($table === 'category_regexes') {
$data['categories_id'] = 1;
}
return (int) DB::table($table)->insertGetId($data);
}
private function resetGlobalComposerState(): void
{
$reflection = new ReflectionClass(GlobalDataComposer::class);