Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SRI + XHTML #5742

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/defaults.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,16 @@
// PHP code about the location of asset files in filesystem
$config['assets_dir'] = '';

// Add SRI (Subresource Integrity) tags to assets
// This currently works by reading the files in asset_dir
// during page serve time and appending a corresponding
// SHA-384 hash tag.
// Warning: Because the hash is calculated every time a
// page is served, enabling this option may result in
// decreased performance.
// See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
// for more information on SRI
$config['assets_sri'] = false;

// ----------------------------------
// PLUGINS
Expand Down
64 changes: 60 additions & 4 deletions program/include/rcmail_output_html.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class rcmail_output_html extends rcmail_output
protected $body = '';
protected $base_path = '';
protected $assets_path;
protected $assets_sri = false;
protected $assets_dir = RCUBE_INSTALL_PATH;
protected $devel_mode = false;

Expand Down Expand Up @@ -84,6 +85,7 @@ public function __construct($task = null, $framed = false)
$this->set_env('skin', $skin);

$this->set_assets_path($this->config->get('assets_path'), $this->config->get('assets_dir'));
$this->assets_sri = $this->config->get('assets_sri') && in_array('sha384', hash_algos(), true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to check hash_algos(), but I would check if function_exists('hash_file').


if (!empty($_REQUEST['_extwin']))
$this->set_env('extwin', 1);
Expand Down Expand Up @@ -755,6 +757,29 @@ public function raise_error($code, $message)
exit;
}

/**
* Get the sha384 hash for local assets
*/
public function asset_sri($path)
{
// iframe content can't be in a different domain
// @TODO: check if assests are on a different domain

if (!$this->assets_sri || in_array($path[0], array('?', '/', '.')) || strpos($path, '://')) {
return false;
}

list($file) = explode('?', $path, 2);
if (!empty($file) && is_readable($this->assets_dir . $file)) {
$content = @file_get_contents($this->assets_dir . $file);
if ($content !== false) {
return 'sha384-'.base64_encode(hash('sha384', $content, true));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to use hash_file() instead of hash() and file_get_contents().

}
}

return false;
}

/**
* Modify path by adding URL prefix if configured
*/
Expand Down Expand Up @@ -802,7 +827,7 @@ protected function globals_callback($matches)
protected function fix_paths($output)
{
return preg_replace_callback(
'!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
'!(src|href|background)=(["\']?)([a-z0-9/_.-]+)\2([\s>]?)!i',
array($this, 'file_callback'), $output);
}

Expand All @@ -826,7 +851,34 @@ protected function file_callback($matches)
$file = $this->file_mod($file);
}

return $matches[1] . '=' . $matches[2] . $file . $matches[4];
return $matches[1] . '=' . $matches[2] . $file . $matches[2] . $matches[4];
}

/**
* Add SRI tags to assets
*/
protected function include_assets_sri($output)
{
return preg_replace_callback(
'!(src|href|background)=(["\']?)([a-z0-9/_.?=-]+)\2([\s>])?!i',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

W3C specification talks only about link and script tags. Do we really want to handle all others, e.g. img?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went this route because it was easier to implement for everything and because future browsers may support SRI in other tags. I concur that for performance reasons this might not be the best approach.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A slightly off-topic comment: the current way of implementing these features using templates (i.e., generate a document, then use regexes) seems error-prone and inefficient. I think a DOM-based (or similar) document generation approach could be more reliable and flexible (even though it's hard to say what the effect on performance may be.) Have you considered changing this?

array($this, 'sri_callback'), $output);
}

/**
* Callback function for preg_replace_callback in include_assets_sri()
*
* @return string Parsed string
*/
protected function sri_callback($matches)
{
$attrs = '';
$sri = $this->asset_sri($matches[3]);

if (!empty($sri)) {
$attrs = ' integrity="' . $sri . '" crossorigin="anonymous"';
}

return $matches[1] . '=' . $matches[2] . $matches[3] . $matches[2] . $attrs . $matches[4];
}

/**
Expand All @@ -835,7 +887,7 @@ protected function file_callback($matches)
protected function fix_assets_paths($output)
{
return preg_replace_callback(
'!(src|href|background)=(["\']?)([a-z0-9/_.?=-]+)(["\'\s>])!i',
'!(src|href|background)=(["\']?)([a-z0-9/_.?=-]+)\2([\s>])?!i',
array($this, 'assets_callback'), $output);
}

Expand All @@ -848,7 +900,7 @@ protected function assets_callback($matches)
{
$file = $this->asset_url($matches[3]);

return $matches[1] . '=' . $matches[2] . $file . $matches[4];
return $matches[1] . '=' . $matches[2] . $file . $matches[2] . $matches[4];
}

/**
Expand Down Expand Up @@ -1679,6 +1731,10 @@ protected function _write($templ = '', $base_path = '')

$output = $this->parse_with_globals($this->fix_paths($output));

if ($this->assets_sri) {
$output = $this->include_assets_sri($output);
}

if ($this->assets_path) {
$output = $this->fix_assets_paths($output);
}
Expand Down
3 changes: 2 additions & 1 deletion program/lib/Roundcube/html.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public static function tag($tagname, $attrib = array(), $content = null, $allowe
return '<' . $tagname . self::attrib_string($attrib, $allowed) . '>' . $content . $suffix;
}
else {
return '<' . $tagname . self::attrib_string($attrib, $allowed) . '>' . $suffix;
return '<' . $tagname . self::attrib_string($attrib, $allowed) . '/>' . $suffix;
}
}

Expand All @@ -99,6 +99,7 @@ public static function doctype($type)
{
$doctypes = array(
'html5' => '<!DOCTYPE html>',
'xhtml-html5' => '<!DOCTYPE html>',
'xhtml' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
'xhtml-trans' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
'xhtml-strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
Expand Down