Fix regexes display issue

This commit is contained in:
DariusIII
2026-06-03 21:52:05 +02:00
parent 3aee57795e
commit 87bbf22bdf
10 changed files with 104 additions and 17 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')) { if (! function_exists('realDuration')) {
function realDuration(mixed $milliseconds): string function realDuration(mixed $milliseconds): string
@@ -43,7 +43,7 @@
id="group_regex" id="group_regex"
name="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" 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> required>
</div> </div>
<p class="mt-2 text-sm text-gray-500"> <p class="mt-2 text-sm text-gray-500">
@@ -66,7 +66,7 @@
name="regex" 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" 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" rows="4"
required>{{ htmlspecialchars($regex->regex ?? '') }}</textarea> required>{{ regex_display_value($regex->regex ?? '') }}</textarea>
</div> </div>
<p class="mt-2 text-sm text-gray-500"> <p class="mt-2 text-sm text-gray-500">
Regex to use when categorizing releases.<br> Regex to use when categorizing releases.<br>
@@ -87,7 +87,7 @@
<textarea id="description" <textarea id="description"
name="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" 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> </div>
<p class="mt-2 text-sm text-gray-500"> <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. Description for this regex. You can include an example usenet subject this regex would match on.
@@ -94,8 +94,8 @@
</td> </td>
<td class="px-6 py-4 text-sm"> <td class="px-6 py-4 text-sm">
<div class="max-w-[200px] wrap-break-word"> <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) }}"> <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) }}">
{{ htmlspecialchars($row->regex) }} {{ regex_display_value($row->regex) }}
</code> </code>
</div> </div>
</td> </td>
@@ -43,7 +43,7 @@
id="group_regex" id="group_regex"
name="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" 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> required>
</div> </div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400"> <p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
@@ -66,7 +66,7 @@
name="regex" 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" 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" rows="4"
required>{{ htmlspecialchars($regex->regex ?? '') }}</textarea> required>{{ regex_display_value($regex->regex ?? '') }}</textarea>
</div> </div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400"> <p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
Regex to use when grouping binaries into collections.<br> Regex to use when grouping binaries into collections.<br>
@@ -87,7 +87,7 @@
<textarea id="description" <textarea id="description"
name="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" 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> </div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400"> <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. Description for this regex. You can include an example usenet subject this regex would match on.
@@ -93,8 +93,8 @@
</td> </td>
<td class="px-6 py-4 text-sm"> <td class="px-6 py-4 text-sm">
<div class="max-w-[200px] wrap-break-word"> <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) }}"> <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) }}">
{{ htmlspecialchars($row->regex) }} {{ regex_display_value($row->regex) }}
</code> </code>
</div> </div>
</td> </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" 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" rows="4"
required 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"> <p class="mt-2 text-sm text-gray-500">
Enter the regex pattern to test. Include delimiters and flags. Enter the regex pattern to test. Include delimiters and flags.
</p> </p>
@@ -43,7 +43,7 @@
id="group_regex" id="group_regex"
name="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" 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> required>
</div> </div>
<p class="mt-2 text-sm text-gray-500"> <p class="mt-2 text-sm text-gray-500">
@@ -66,7 +66,7 @@
name="regex" 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" 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" rows="4"
required>{{ htmlspecialchars($regex->regex ?? '') }}</textarea> required>{{ regex_display_value($regex->regex ?? '') }}</textarea>
</div> </div>
<p class="mt-2 text-sm text-gray-500"> <p class="mt-2 text-sm text-gray-500">
Regex to use when renaming releases.<br> Regex to use when renaming releases.<br>
@@ -87,7 +87,7 @@
<textarea id="description" <textarea id="description"
name="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" 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> </div>
<p class="mt-2 text-sm text-gray-500"> <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. Description for this regex. You can include an example release name this regex would match on.
@@ -93,8 +93,8 @@
</td> </td>
<td class="px-6 py-4 text-sm"> <td class="px-6 py-4 text-sm">
<div class="max-w-[200px] wrap-break-word"> <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) }}"> <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) }}">
{{ htmlspecialchars($row->regex) }} {{ regex_display_value($row->regex) }}
</code> </code>
</div> </div>
</td> </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" 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" rows="4"
required 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"> <p class="mt-2 text-sm text-gray-500">
Enter the regex pattern to test. Include delimiters and flags. Enter the regex pattern to test. Include delimiters and flags.
</p> </p>
@@ -166,6 +166,63 @@ class AdminRegexesControllerTest extends TestCase
->assertNotFound(); ->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 private function setEnvironmentValue(string $key, ?string $value): void
{ {
if ($value === null) { if ($value === null) {
@@ -373,6 +430,23 @@ class AdminRegexesControllerTest extends TestCase
return $user->fresh(); 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 private function resetGlobalComposerState(): void
{ {
$reflection = new ReflectionClass(GlobalDataComposer::class); $reflection = new ReflectionClass(GlobalDataComposer::class);