mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-31 19:03:43 +01:00
Each page builds its table cells and attributes as strings through an esc() of its own; 47 of them used the DOM's innerHTML or jQuery's .text().html(), which leave quotes alone. Inside title="..." or data-x="..." a value's first quote ended the attribute: - Client Logs: a refused viewer's user agent, in the JSON of the row's detail, became attributes in the admin's page; - Users: a reseller's username or notes, the same. Every one now escapes " and ' too, and AdminViewEscapeHelpersTest scans the views so one that does not cannot come back. Two sinks were not esc()'s: the stream probe's table put a codec name from the source's own /probe into HTML as it came, and the guide's tooltip replaced one quote with itself. Both are escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AneTzAHPPkYWPQjwyZ2td9
37 lines
1.6 KiB
PHP
37 lines
1.6 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Every page's own `esc()` (the admin and reseller views build table cells
|
|
* and attributes as strings) escapes quotes too. The DOM's innerHTML and
|
|
* jQuery's .text().html() leave `"` and `'` alone, and inside title="…" or
|
|
* data-x="…" a value's first quote ended the attribute: a viewer's user agent
|
|
* (Client Logs), a reseller's username or notes (Users) became markup in the
|
|
* admin's page.
|
|
*/
|
|
final class AdminViewEscapeHelpersTest extends TestCase {
|
|
public function testEveryEscapeHelperEscapesQuotes(): void {
|
|
$rRoot = MAIN_HOME . 'Public/';
|
|
$rFiles = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($rRoot . 'Views')), '/\.php$/');
|
|
$rBlind = [];
|
|
$rSeen = 0;
|
|
foreach ($rFiles as $rFile) {
|
|
$rSource = (string) file_get_contents((string) $rFile);
|
|
if (!preg_match_all('/(?:(?:var|let|const)\s+esc(?:Html)?\s*=\s*(?:function\s*\(\w*\)|\(?\w*\)?\s*=>)|function\s+esc(?:Html)?\s*\(\w*\))/', $rSource, $rMatches, PREG_OFFSET_CAPTURE)) {
|
|
continue;
|
|
}
|
|
foreach ($rMatches[0] as [, $rAt]) {
|
|
$rSeen++;
|
|
$rBody = substr($rSource, $rAt, 420);
|
|
$rBody = preg_match('/\n\s*\}\s*;?\s*\n/', $rBody, $rEnd, PREG_OFFSET_CAPTURE) ? substr($rBody, 0, $rEnd[0][1]) : strtok($rBody, "\n");
|
|
if (!preg_match('/"|�?39;|"|ESC_MAP/', (string) $rBody)) {
|
|
$rBlind[] = substr((string) $rFile, strlen($rRoot)) . ':' . (substr_count($rSource, "\n", 0, $rAt) + 1);
|
|
}
|
|
}
|
|
}
|
|
$this->assertGreaterThan(40, $rSeen, 'the helpers were found');
|
|
$this->assertSame([], $rBlind, 'esc() helpers that leave quotes alone');
|
|
}
|
|
}
|