Altered the parsing of custom head to prevent htmlentities on content

Was causing things like emjoi within script content to be somewhat
mangled. Instead we force UTF8 only parsing via XML declaration.

Added test to cover.

For #2923
This commit is contained in:
Dan Brown 2021-09-12 16:19:17 +01:00
parent fb80bb5d58
commit ef459ca4c4
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
3 changed files with 40 additions and 3 deletions

View file

@ -12,7 +12,7 @@ class CspService
public function __construct(string $nonce = '')
{
$this->nonce = $nonce ?: Str::random(16);
$this->nonce = $nonce ?: Str::random(24);
}
/**

View file

@ -21,10 +21,10 @@ class HtmlNonceApplicator
return $html;
}
$html = '<body>' . $html . '</body>';
$html = '<?xml encoding="utf-8" ?><body>' . $html . '</body>';
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_SCHEMA_CREATE);
$doc->loadHTML($html, LIBXML_SCHEMA_CREATE);
$xPath = new DOMXPath($doc);
// Apply to scripts

View file

@ -2,6 +2,7 @@
namespace Tests\Settings;
use BookStack\Util\CspService;
use Tests\TestCase;
class CustomHeadContentTest extends TestCase
@ -26,4 +27,40 @@ class CustomHeadContentTest extends TestCase
$resp = $this->get('/login');
$resp->assertSee('<div id="hello">cat</div>');
}
public function test_nonce_application_handles_edge_cases()
{
$mockCSP = $this->mock(CspService::class);
$mockCSP->shouldReceive('getNonce')->andReturn('abc123');
$content = trim('
<script>console.log("cat");</script>
<script type="text/html"><\script>const a = `<div></div>`<\/\script></script>
<script >const a = `<div></div>`;</script>
<script type="<script text>test">const c = `<div></div>`;</script>
<script
type="text/html"
>
const a = `<\script><\/script>`;
const b = `<script`;
</script>
<SCRIPT>const b = `↗️£`;</SCRIPT>
');
$expectedOutput = trim('
<script nonce="abc123">console.log("cat");</script>
<script type="text/html" nonce="abc123"><\script>const a = `<div></div>`<\/\script></script>
<script nonce="abc123">const a = `<div></div>`;</script>
<script type="&lt;script text&gt;test" nonce="abc123">const c = `<div></div>`;</script>
<script type="text/html" nonce="abc123">
const a = `<\script><\/script>`;
const b = `<script`;
</script>
<script nonce="abc123">const b = `↗️£`;</script>
');
$this->setSettings(['app-custom-head' => $content]);
$resp = $this->get('/login');
$resp->assertSee($expectedOutput);
}
}