diff --git a/README.md b/README.md index a0e7f2c..d320cb4 100644 --- a/README.md +++ b/README.md @@ -1 +1,54 @@ -# kirby-sri-hash \ No newline at end of file +# kirby-sri-hash +This plugin is heavily inspired by the great Kirby plugins [cachebuster](https://github.com/getkirby-plugins/cachebuster-plugin) (by Kirby team members [Bastian Allgeier](https://github.com/bastianallgeier) and [Lukas Bestle](https://github.com/lukasbestle)) as well as [fingerprint](https://github.com/iksi/kirby-fingerprint) (by [Iksi](https://github.com/iksi)). + +## What's SRI? +TL;DR: I wanted to add Kirby-side [subresource integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) (SRI) for safer CDN usage - check out [these](https://getkirby.com/docs/cookbook/kirby-loves-cdn) [articles](https://www.keycdn.com/support/kirby-cdn-integration/) for starters. + +**This is still experimental, and may be deleted at some point.** + +## Installation +Use one of these alternatives in order to use install & use `kirby-sri-hash`: + +### 1. Git Submodule + +If you know your way around Git, you can download this plugin as a [submodule](https://github.com/blog/2104-working-with-submodules): + +```text +git submodule add https://github.com/S1SYPHOS/kirby-sri-hash.git site/plugins/kirby-sri-hash +``` + +### 2. Clone or download + +1. [Clone](https://github.com/S1SYPHOS/kirby-sri-hash.git) or [download](https://github.com/S1SYPHOS/kirby-sri-hash/archive/master.zip) this repository. +2. Unzip / Move the folder to `site/plugins`. + +### 3. Activate the plugin +Activate the plugin with the following line in your `config.php`: + +```text +c::set('sri-hash', true); +``` + +#### Apache +Add the following lines to your `.htaccess` (right after `RewriteBase`): + +``` +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.+)\.([0-9]{10})\.(js|css)$ $1.$3 [L] +``` + +#### NGINX +Add the following lines to your virtual host setup: + +``` +location /assets { + if (!-e $request_filename) { + rewrite "^/(.+)\.([0-9]{10})\.(js|css)$" /$1.$3 break; + } +} +``` + +**Note: Subresource integrity & cache-busting are not applied to external URLs!** + +## Special Thanks +I'd like to thank everybody that's making great software - you people are awesome. Also I'm always thankful for feedback and bug reports :) diff --git a/core/css.php b/core/css.php new file mode 100644 index 0000000..cf762d4 --- /dev/null +++ b/core/css.php @@ -0,0 +1,68 @@ +tag($u, $media); + return implode(PHP_EOL, $css) . PHP_EOL; + } + + // auto template css files + if($url == '@auto') { + $file = $this->kirby->site()->page()->template() . '.css'; + $root = $this->kirby->roots()->autocss() . DS . $file; + $url = $this->kirby->urls()->autocss() . '/' . $file; + if(!file_exists($root)) return false; + $url = preg_replace('#^' . $this->kirby->urls()->index() . '/#', null, $url); + } + + $url = ltrim($url, '/'); + + if (file_exists($url)) { + // generate sri hash for css files + $cssInput = (new Asset($url))->content(); + $cssIntegrity = checksum($cssInput); + + // add timestamp for cache-busting + $modified = filemtime($url); + $filename = f::name($url) . '.' . $modified . '.' . f::extension($url); + $dirname = f::dirname($url); + $url = ($dirname === '.') ? $filename : $dirname . '/' . $filename; + } + + // build the array of HTML attributes + $attr = array( + 'rel' => 'stylesheet', + 'href' => url($url), + 'integrity' => $cssIntegrity // inject generated sri hash + ); + + if(is_array($media)) { + // merge array with custom options + $attr = array_merge($attr, $media); + } else if(is_string($media)) { + // if there's no such array, just set media key + $attr['media'] = $media; + } + + // return the proper 'link' tag + return html::tag('link', null, $attr); + } +} diff --git a/core/js.php b/core/js.php new file mode 100644 index 0000000..c88f0bf --- /dev/null +++ b/core/js.php @@ -0,0 +1,67 @@ +tag($s, $async); + return implode(PHP_EOL, $js) . PHP_EOL; + } + + // auto template css files + if($src == '@auto') { + $file = $this->kirby->site()->page()->template() . '.js'; + $root = $this->kirby->roots()->autojs() . DS . $file; + $src = $this->kirby->urls()->autojs() . '/' . $file; + if(!file_exists($root)) return false; + $src = preg_replace('#^' . $this->kirby->urls()->index() . '/#', null, $src); + } + + $src = ltrim($src, '/'); + + if (file_exists($src)) { + // generate sri hash for css files + $jsInput = (new Asset($src))->content(); + $jsIntegrity = checksum($jsInput); + + // add timestamp for cache-busting + $modified = filemtime($src); + $filename = f::name($src) . '.' . $modified . '.' . f::extension($src); + $dirname = f::dirname($src); + $src = ($dirname === '.') ? $filename : $dirname . '/' . $filename; + } + + // build the array of HTML attributes + $attr = array( + 'src' => url($src), + 'integrity' => $jsIntegrity // inject generated sri hash + ); + + if(is_array($async)) { + // merge array with custom options + $attr = array_merge($attr, $async); + } else if($async === true) { + // if there's no such array, just set async key + $attr['async'] = true; + } + + // return the proper 'script' tag + return html::tag('script', '', $attr); + } +} diff --git a/kirby-sri-hash.php b/kirby-sri-hash.php new file mode 100644 index 0000000..835c0df --- /dev/null +++ b/kirby-sri-hash.php @@ -0,0 +1,25 @@ + + */ + +if(!c::get('sri-hash')) return; + +function checksum($input) { + $hash = hash('sha512', $input, true); + $hash_base64 = base64_encode($hash); + + return "sha512-$hash_base64"; +} + +load([ + 's1syphos\\sri\\css' => __DIR__ . DS . 'core' . DS . 'css.php', + 's1syphos\\sri\\js' => __DIR__ . DS . 'core' . DS . 'js.php' +]); + +kirby()->set('component', 'css', 'S1SYPHOS\\SRI\\CSS'); +kirby()->set('component', 'js', 'S1SYPHOS\\SRI\\JS'); diff --git a/package.json b/package.json new file mode 100644 index 0000000..2fccec3 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "sri-hash", + "description": "Kirby SRI Hash & Cache-bust Plugin", + "author": "S1SYPHOS ", + "version": "0.1.0", + "type": "kirby-plugin", + "license": "MIT" +}