Allowing Grav to inline scripts for better CSP support
The Grav codebase currently supports inlining of javascript via the addInlineJs method. A useful feature, but one that presents a problem when generating a suitably secure content security policy for your website - namely the avoidance of 'unsafe-inline'
.
It would be great if this method could (optionally) include the inline javascript within the Asset Manager pipeline. If this was possible then inline scripts could still be included by the author within a web page but would be rendered by Grav as an external script. If all scripts are external then the CSP does not need to include 'unsafe-inline'
within 'script-src'
.
A simple change to the AssetManager adds this functionality. Open the file /src/Grav/Common/Assets.php and navigate to the function public function addInlineJs($asset, $priority = null, $group = null, $attributes = null)
.
We now need to modify this function so that it will honour instructions to add the inline javascript into the pipeline.
The original code block appears as:
$key = md5($asset);
if ($asset && is_string($asset) && !array_key_exists($key, $this->inline_js)) {
$this->inline_js[$key] = $data;
We need to prepend this block with a check for the term 'pipeline' within the $data constructed from the function arguments. If the term 'pipeline' is found then write the inline javascript to a file and include that within the pipeline.
if ($this->js_pipeline && isset($data['pipeline']) && strtolower($data['pipeline']) == 'true') {
$inlineFile = md5($asset) . '.js';
if (!file_exists($this->assets_dir . $inlineFile)) {
file_put_contents($this->assets_dir . $inlineFile, $asset);
}
$relative_path = "{$this->base_url}{$this->assets_url}/{$inlineFile}";
$this->addTo($this->js, $relative_path, $data['priority'], $data['pipeline'], null, $data['group']);
}
else {
$key = md5($asset);
if ($asset && is_string($asset) && !array_key_exists($key, $this->inline_js)) {
$this->inline_js[$key] = $data;
}
}
To include inline javascript in the pipeline provide an option with a name of 'pipeline' and value of 'true' as demonstrated below:
{% do assets.addInlineJs('$.noop();', {'group':'bottom', 'pipeline':'true', 'priority' : '95' }) %}