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')) {
function realDuration(mixed $milliseconds): string
@@ -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);