From 8b3b16be4442ba2415c67c02336ac8b8078a872b Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 7 Jul 2024 16:36:52 +0200 Subject: [PATCH 01/10] SRI hashes are now configurable, no longer hardcoded in templates - addresses #1365 - should make upgrades easier for those using custom templates - if the JS files got customized, the default SRI hashes can be replaced in the conf.php file, added commented section in conf.sample.php --- CHANGELOG.md | 1 + cfg/conf.sample.php | 6 ++++++ lib/Configuration.php | 17 +++++++++++++++++ lib/Controller.php | 1 + lib/View.php | 18 ++++++++++++++++++ tpl/bootstrap.php | 24 ++++++++++++------------ tpl/bootstrap5.php | 26 +++++++++++++------------- tpl/page.php | 22 +++++++++++----------- tst/Bootstrap.php | 41 +++++++++++++++-------------------------- tst/ViewTest.php | 1 + 10 files changed, 95 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d9485a..9ed1e3a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.7.4 (not yet released) * CHANGED: Saving markdown pastes uses `.md` extension instead of `.txt` (#1293) * CHANGED: Enable strict type checking in PHP (#1350) +* CHANGED: SRI hashes are now configurable, no longer hardcoded in templates (#1365) * FIXED: Reset password input field on creation of new paste (#1194) * FIXED: Allow database schema upgrade to skip versions (#1343) * FIXED: `bootstrap5` dark mode toggle unset on dark browser preference (#1340) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index f1db2644..f91c5e89 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -275,3 +275,9 @@ dir = PATH "data" ; signature = "" ; (optional) the URL of the YOURLS API, called to shorten a PrivateBin URL ; apiurl = "https://yourls.example.com/yourls-api.php" + +;[sri] +; Subresource integrity (SRI) hashes used in template files. Uncomment and set +; these for all js files used. See: +; https://github.com/PrivateBin/PrivateBin/wiki/FAQ#user-content-how-to-make-privatebin-work-when-i-have-changed-some-javascript-files +;privatebin.js = sha512-[…] diff --git a/lib/Configuration.php b/lib/Configuration.php index 59722d15..e441f05f 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -98,6 +98,23 @@ class Configuration 'signature' => '', 'apiurl' => '', ), + // update this array when adding/changing/removing js files + 'sri' => array( + 'js/base-x-4.0.0.js' => 'sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==', + 'js/base64-1.7.js' => 'sha512-JdwsSP3GyHR+jaCkns9CL9NTt4JUJqm/BsODGmYhBcj5EAPKcHYh+OiMfyHbcDLECe17TL0hjXADFkusAqiYgA==', + 'js/bootstrap-3.4.1.js' => 'sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==', + 'js/bootstrap-5.3.3.js' => 'sha512-in2rcOpLTdJ7/pw5qjF4LWHFRtgoBDxXCy49H4YGOcVdGiPaQucGIbOqxt1JvmpvOpq3J/C7VTa0FlioakB2gQ==', + 'js/dark-mode-switch.js' => 'sha512-CCbdHdeWDbDO7aqFFmhgnvFESzaILHbUYmbhNjTpcjyO/XYdouQ9Pw8W9rpV8oJT1TsK5FbwSHU1oazmnb7BWA==', + 'js/jquery-3.7.1.js' => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==', + 'js/kjua-0.9.0.js' => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==', + 'js/legacy.js' => 'sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==', + 'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==', + 'js/privatebin.js' => 'sha512-cbmXvtZ/5gZPFjQDzP3IEhUAIhFPAoM31gw2kRYCT5xOh8wv9gXeDqI/t798luRW1xdC4gaYodjEFCzrsZR4mA==', + 'js/purify-3.1.3.js' => 'sha512-t/FKG/ucQVMWTWVouSMABSEx1r+uSyAI9eNDq0KEr9mPhkgxpJztHI/E72JIpv/+VwPs/Q4husxj14TE9Ps/wg==', + 'js/rawinflate-0.3.js' => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==', + 'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==', + 'js/zlib-1.3.1.js' => 'sha512-Z90oppVx/mn0DG2k9airjFVQuliELlXLeT3SRiO6MLiUSbhGlAq+UFwmYbG4i9mwW87dkG8fgJPapGwnUq7Osg==', + ), ); /** diff --git a/lib/Controller.php b/lib/Controller.php index d518fcc5..20a83a13 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -436,6 +436,7 @@ class Controller $page->assign('HTTPWARNING', $this->_conf->getKey('httpwarning')); $page->assign('HTTPSLINK', 'https://' . $this->_request->getHost() . $this->_request->getRequestUri()); $page->assign('COMPRESSION', $this->_conf->getKey('compression')); + $page->assign('SRI', $this->_conf->getSection('sri')); $page->draw($this->_conf->getKey('template')); } diff --git a/lib/View.php b/lib/View.php index 395ac86d..8c21f341 100644 --- a/lib/View.php +++ b/lib/View.php @@ -57,4 +57,22 @@ class View extract($this->_variables); include $path; } + + /** + * echo script tag incl. SRI hash for given script file + * + * @access private + * @param string $file + * @param bool $async should it execute ASAP or only after HTML got parsed + */ + private function _scriptTag($file, $async = true) + { + $sri = array_key_exists($file, $this->_variables['SRI']) ? + ' integrity="' . $this->_variables['SRI'][$file] . '"' : ''; + $suffix = preg_match('#\d.js$#', $file) == 0 ? + '?' . rawurlencode($this->_variables['VERSION']) : ''; + echo '', PHP_EOL; + } } diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index e9a15ef1..2ae57582 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -42,38 +42,38 @@ if ($SYNTAXHIGHLIGHTING) : endif; ?> - + _scriptTag('js/jquery-3.7.1.js', false); ?> - + _scriptTag('js/kjua-0.9.0.js'); ?> - + _scriptTag('js/base64-1.7.js'); ?> - - - - + _scriptTag('js/zlib-1.3.1.js'); ?> + _scriptTag('js/base-x-4.0.0.js'); ?> + _scriptTag('js/rawinflate-0.3.js'); ?> + _scriptTag('js/bootstrap-3.4.1.js', false); ?> - + _scriptTag('js/prettify.js'); ?> - + _scriptTag('js/showdown-2.1.0.js'); ?> - - - + _scriptTag('js/purify-3.1.3.js'); ?> + _scriptTag('js/legacy.js'); ?> + _scriptTag('js/privatebin.js', false); ?> diff --git a/tpl/bootstrap5.php b/tpl/bootstrap5.php index 9bf7feb0..3700bd98 100644 --- a/tpl/bootstrap5.php +++ b/tpl/bootstrap5.php @@ -25,39 +25,39 @@ if ($SYNTAXHIGHLIGHTING) : endif; ?> - + _scriptTag('js/jquery-3.7.1.js', false); ?> - + _scriptTag('js/kjua-0.9.0.js'); ?> - + _scriptTag('js/base64-1.7.js'); ?> - - - - - + _scriptTag('js/zlib-1.3.1.js'); ?> + _scriptTag('js/base-x-4.0.0.js'); ?> + _scriptTag('js/rawinflate-0.3.js'); ?> + _scriptTag('js/bootstrap-5.3.3.js'); ?> + _scriptTag('js/dark-mode-switch.js'); ?> - + _scriptTag('js/prettify.js'); ?> - + _scriptTag('js/showdown-2.1.0.js'); ?> - - - + _scriptTag('js/purify-3.1.3.js'); ?> + _scriptTag('js/legacy.js'); ?> + _scriptTag('js/privatebin.js', false); ?> diff --git a/tpl/page.php b/tpl/page.php index 47c55203..8a83c9cd 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -21,37 +21,37 @@ if ($SYNTAXHIGHLIGHTING): endif; endif; ?> - + _scriptTag('js/jquery-3.7.1.js', false); ?> - + _scriptTag('js/kjua-0.9.0.js'); ?> - + _scriptTag('js/base64-1.7.js'); ?> - - - + _scriptTag('js/zlib-1.3.1.js'); ?> + _scriptTag('js/base-x-4.0.0.js'); ?> + _scriptTag('js/rawinflate-0.3.js'); ?> - + _scriptTag('js/prettify.js'); ?> - + _scriptTag('js/showdown-2.1.0.js'); ?> - - - + _scriptTag('js/purify-3.1.3.js'); ?> + _scriptTag('js/legacy.js'); ?> + _scriptTag('js/privatebin.js', false); ?> diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index 984f7d50..584a8d75 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -382,32 +382,21 @@ class Helper } } - $dir = dir(PATH . 'tpl'); - while (false !== ($file = $dir->read())) { - if (substr($file, -4) === '.php') { - $content = file_get_contents( - PATH . 'tpl' . DIRECTORY_SEPARATOR . $file - ); - $content = preg_replace_callback( - '##', - function ($matches) { - if (array_key_exists($matches[2], Helper::$hashes)) { - return ''; - } else { - return $matches[0]; - } - }, - $content - ); - file_put_contents( - PATH . 'tpl' . DIRECTORY_SEPARATOR . $file, - $content - ); - } - } + $file = PATH . 'lib' . DIRECTORY_SEPARATOR . 'Configuration.php'; + $content = preg_replace_callback( + '#\'js/([a-z0-9.-]+.js)\' =\> \'([^\']*)\',#', + function ($matches) { + if (array_key_exists($matches[1], Helper::$hashes)) { + return '\'js/' . $matches[1] . '\' => \'sha512-' . + Helper::$hashes[$matches[1]] . + '\','; + } else { + return $matches[0]; + } + }, + file_get_contents($file) + ); + file_put_contents($file, $content); } } diff --git a/tst/ViewTest.php b/tst/ViewTest.php index 2a1c7e47..65b252de 100644 --- a/tst/ViewTest.php +++ b/tst/ViewTest.php @@ -63,6 +63,7 @@ class ViewTest extends TestCase $page->assign('HTTPSLINK', 'https://example.com/'); $page->assign('COMPRESSION', 'zlib'); $page->assign('CSPHEADER', 'default-src \'none\''); + $page->assign('SRI', array()); $dir = dir(PATH . 'tpl'); while (false !== ($file = $dir->read())) { From d27e7e8ee396facadb9dd92861ba800274492569 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 9 Jul 2024 21:38:41 +0200 Subject: [PATCH 02/10] make scriptTag parameter a string for flexibility and clarity --- lib/View.php | 6 +++--- tpl/bootstrap.php | 24 ++++++++++++------------ tpl/bootstrap5.php | 26 +++++++++++++------------- tpl/page.php | 4 ++-- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/View.php b/lib/View.php index 8c21f341..5e253580 100644 --- a/lib/View.php +++ b/lib/View.php @@ -63,15 +63,15 @@ class View * * @access private * @param string $file - * @param bool $async should it execute ASAP or only after HTML got parsed + * @param string $attributes additional attributes to add into the script tag */ - private function _scriptTag($file, $async = true) + private function _scriptTag($file, $attributes = '') { $sri = array_key_exists($file, $this->_variables['SRI']) ? ' integrity="' . $this->_variables['SRI'][$file] . '"' : ''; $suffix = preg_match('#\d.js$#', $file) == 0 ? '?' . rawurlencode($this->_variables['VERSION']) : ''; - echo '', PHP_EOL; } diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 2ae57582..9f266615 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -42,38 +42,38 @@ if ($SYNTAXHIGHLIGHTING) : endif; ?> - _scriptTag('js/jquery-3.7.1.js', false); ?> + _scriptTag('js/jquery-3.7.1.js', 'async'); ?> - _scriptTag('js/kjua-0.9.0.js'); ?> + _scriptTag('js/kjua-0.9.0.js', 'async'); ?> - _scriptTag('js/base64-1.7.js'); ?> + _scriptTag('js/base64-1.7.js', 'async'); ?> - _scriptTag('js/zlib-1.3.1.js'); ?> - _scriptTag('js/base-x-4.0.0.js'); ?> - _scriptTag('js/rawinflate-0.3.js'); ?> - _scriptTag('js/bootstrap-3.4.1.js', false); ?> + _scriptTag('js/zlib-1.3.1.js', 'async'); ?> + _scriptTag('js/base-x-4.0.0.js', 'async'); ?> + _scriptTag('js/rawinflate-0.3.js', 'async'); ?> + _scriptTag('js/bootstrap-3.4.1.js', 'defer'); ?> - _scriptTag('js/prettify.js'); ?> + _scriptTag('js/prettify.js', 'async'); ?> - _scriptTag('js/showdown-2.1.0.js'); ?> + _scriptTag('js/showdown-2.1.0.js', 'async'); ?> - _scriptTag('js/purify-3.1.3.js'); ?> - _scriptTag('js/legacy.js'); ?> - _scriptTag('js/privatebin.js', false); ?> + _scriptTag('js/purify-3.1.3.js', 'async'); ?> + _scriptTag('js/legacy.js', 'async'); ?> + _scriptTag('js/privatebin.js', 'defer'); ?> diff --git a/tpl/bootstrap5.php b/tpl/bootstrap5.php index 3700bd98..64bb9677 100644 --- a/tpl/bootstrap5.php +++ b/tpl/bootstrap5.php @@ -25,39 +25,39 @@ if ($SYNTAXHIGHLIGHTING) : endif; ?> - _scriptTag('js/jquery-3.7.1.js', false); ?> + _scriptTag('js/jquery-3.7.1.js', 'defer'); ?> - _scriptTag('js/kjua-0.9.0.js'); ?> + _scriptTag('js/kjua-0.9.0.js', 'async'); ?> - _scriptTag('js/base64-1.7.js'); ?> + _scriptTag('js/base64-1.7.js', 'async'); ?> - _scriptTag('js/zlib-1.3.1.js'); ?> - _scriptTag('js/base-x-4.0.0.js'); ?> - _scriptTag('js/rawinflate-0.3.js'); ?> - _scriptTag('js/bootstrap-5.3.3.js'); ?> - _scriptTag('js/dark-mode-switch.js'); ?> + _scriptTag('js/zlib-1.3.1.js', 'async'); ?> + _scriptTag('js/base-x-4.0.0.js', 'async'); ?> + _scriptTag('js/rawinflate-0.3.js', 'async'); ?> + _scriptTag('js/bootstrap-5.3.3.js', 'async'); ?> + _scriptTag('js/dark-mode-switch.js', 'async'); ?> - _scriptTag('js/prettify.js'); ?> + _scriptTag('js/prettify.js', 'async'); ?> - _scriptTag('js/showdown-2.1.0.js'); ?> + _scriptTag('js/showdown-2.1.0.js', 'async'); ?> - _scriptTag('js/purify-3.1.3.js'); ?> - _scriptTag('js/legacy.js'); ?> - _scriptTag('js/privatebin.js', false); ?> + _scriptTag('js/purify-3.1.3.js', 'async'); ?> + _scriptTag('js/legacy.js', 'async'); ?> + _scriptTag('js/privatebin.js', 'defer'); ?> diff --git a/tpl/page.php b/tpl/page.php index 8a83c9cd..18e3b9a7 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -21,7 +21,7 @@ if ($SYNTAXHIGHLIGHTING): endif; endif; ?> - _scriptTag('js/jquery-3.7.1.js', false); ?> + _scriptTag('js/jquery-3.7.1.js', 'defer'); ?> @@ -51,7 +51,7 @@ endif; ?> _scriptTag('js/purify-3.1.3.js'); ?> _scriptTag('js/legacy.js'); ?> - _scriptTag('js/privatebin.js', false); ?> + _scriptTag('js/privatebin.js', 'defer'); ?> From e051cde3174af7e7acc41a4d4fcdd3bea4b1396d Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 9 Jul 2024 21:48:40 +0200 Subject: [PATCH 03/10] apply StyleCI recommendation --- lib/Configuration.php | 26 +++++++++++++------------- lib/View.php | 4 ++-- tst/Bootstrap.php | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/Configuration.php b/lib/Configuration.php index e441f05f..2659217f 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -100,20 +100,20 @@ class Configuration ), // update this array when adding/changing/removing js files 'sri' => array( - 'js/base-x-4.0.0.js' => 'sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==', - 'js/base64-1.7.js' => 'sha512-JdwsSP3GyHR+jaCkns9CL9NTt4JUJqm/BsODGmYhBcj5EAPKcHYh+OiMfyHbcDLECe17TL0hjXADFkusAqiYgA==', - 'js/bootstrap-3.4.1.js' => 'sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==', - 'js/bootstrap-5.3.3.js' => 'sha512-in2rcOpLTdJ7/pw5qjF4LWHFRtgoBDxXCy49H4YGOcVdGiPaQucGIbOqxt1JvmpvOpq3J/C7VTa0FlioakB2gQ==', + 'js/base-x-4.0.0.js' => 'sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==', + 'js/base64-1.7.js' => 'sha512-JdwsSP3GyHR+jaCkns9CL9NTt4JUJqm/BsODGmYhBcj5EAPKcHYh+OiMfyHbcDLECe17TL0hjXADFkusAqiYgA==', + 'js/bootstrap-3.4.1.js' => 'sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==', + 'js/bootstrap-5.3.3.js' => 'sha512-in2rcOpLTdJ7/pw5qjF4LWHFRtgoBDxXCy49H4YGOcVdGiPaQucGIbOqxt1JvmpvOpq3J/C7VTa0FlioakB2gQ==', 'js/dark-mode-switch.js' => 'sha512-CCbdHdeWDbDO7aqFFmhgnvFESzaILHbUYmbhNjTpcjyO/XYdouQ9Pw8W9rpV8oJT1TsK5FbwSHU1oazmnb7BWA==', - 'js/jquery-3.7.1.js' => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==', - 'js/kjua-0.9.0.js' => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==', - 'js/legacy.js' => 'sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==', - 'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==', - 'js/privatebin.js' => 'sha512-cbmXvtZ/5gZPFjQDzP3IEhUAIhFPAoM31gw2kRYCT5xOh8wv9gXeDqI/t798luRW1xdC4gaYodjEFCzrsZR4mA==', - 'js/purify-3.1.3.js' => 'sha512-t/FKG/ucQVMWTWVouSMABSEx1r+uSyAI9eNDq0KEr9mPhkgxpJztHI/E72JIpv/+VwPs/Q4husxj14TE9Ps/wg==', - 'js/rawinflate-0.3.js' => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==', - 'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==', - 'js/zlib-1.3.1.js' => 'sha512-Z90oppVx/mn0DG2k9airjFVQuliELlXLeT3SRiO6MLiUSbhGlAq+UFwmYbG4i9mwW87dkG8fgJPapGwnUq7Osg==', + 'js/jquery-3.7.1.js' => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==', + 'js/kjua-0.9.0.js' => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==', + 'js/legacy.js' => 'sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==', + 'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==', + 'js/privatebin.js' => 'sha512-cbmXvtZ/5gZPFjQDzP3IEhUAIhFPAoM31gw2kRYCT5xOh8wv9gXeDqI/t798luRW1xdC4gaYodjEFCzrsZR4mA==', + 'js/purify-3.1.3.js' => 'sha512-t/FKG/ucQVMWTWVouSMABSEx1r+uSyAI9eNDq0KEr9mPhkgxpJztHI/E72JIpv/+VwPs/Q4husxj14TE9Ps/wg==', + 'js/rawinflate-0.3.js' => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==', + 'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==', + 'js/zlib-1.3.1.js' => 'sha512-Z90oppVx/mn0DG2k9airjFVQuliELlXLeT3SRiO6MLiUSbhGlAq+UFwmYbG4i9mwW87dkG8fgJPapGwnUq7Osg==', ), ); diff --git a/lib/View.php b/lib/View.php index 5e253580..0caa911d 100644 --- a/lib/View.php +++ b/lib/View.php @@ -72,7 +72,7 @@ class View $suffix = preg_match('#\d.js$#', $file) == 0 ? '?' . rawurlencode($this->_variables['VERSION']) : ''; echo '', PHP_EOL; + ' type="text/javascript" data-cfasync="false" src="', $file, + $suffix, '"', $sri, ' crossorigin="anonymous">', PHP_EOL; } } diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index 584a8d75..5dba9480 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -382,7 +382,7 @@ class Helper } } - $file = PATH . 'lib' . DIRECTORY_SEPARATOR . 'Configuration.php'; + $file = PATH . 'lib' . DIRECTORY_SEPARATOR . 'Configuration.php'; $content = preg_replace_callback( '#\'js/([a-z0-9.-]+.js)\' =\> \'([^\']*)\',#', function ($matches) { From 118c91966320ce876b485b0f09b428707d9364d3 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 9 Jul 2024 21:51:19 +0200 Subject: [PATCH 04/10] adjust regex due to StyleCI spacing change --- tst/Bootstrap.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index 5dba9480..59fb4060 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -384,12 +384,11 @@ class Helper $file = PATH . 'lib' . DIRECTORY_SEPARATOR . 'Configuration.php'; $content = preg_replace_callback( - '#\'js/([a-z0-9.-]+.js)\' =\> \'([^\']*)\',#', + '#\'js/([a-z0-9.-]+.js)(\' +)=\> \'[^\']*\',#', function ($matches) { if (array_key_exists($matches[1], Helper::$hashes)) { - return '\'js/' . $matches[1] . '\' => \'sha512-' . - Helper::$hashes[$matches[1]] . - '\','; + return '\'js/' . $matches[1] . $matches[2] . + '=> \'sha512-' . Helper::$hashes[$matches[1]] . '\','; } else { return $matches[0]; } From 6261c94fc98e2e48f8a6145a733d39b116c58739 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 9 Jul 2024 22:20:08 +0200 Subject: [PATCH 05/10] break unit tests if mismatch between JS files and SRI configuration array is detected --- tst/Bootstrap.php | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index 59fb4060..bee35847 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -1,5 +1,7 @@ read())) { - if (substr($file, -3) === '.js') { - self::$hashes[$file] = base64_encode( - hash('sha512', file_get_contents( - PATH . 'js' . DIRECTORY_SEPARATOR . $file - ), true) - ); + foreach (new GlobIterator(PATH . 'js' . DIRECTORY_SEPARATOR . '*.js') as $file) { + if ($file->getBasename() == 'common.js') { + continue; // ignore JS unit test bootstrap } + self::$hashes[$file->getBasename()] = base64_encode( + hash('sha512', file_get_contents($file->getPathname()), true) + ); } + $counter = 0; $file = PATH . 'lib' . DIRECTORY_SEPARATOR . 'Configuration.php'; $content = preg_replace_callback( '#\'js/([a-z0-9.-]+.js)(\' +)=\> \'[^\']*\',#', - function ($matches) { + function ($matches) use (&$counter) { if (array_key_exists($matches[1], Helper::$hashes)) { + $counter++; return '\'js/' . $matches[1] . $matches[2] . '=> \'sha512-' . Helper::$hashes[$matches[1]] . '\','; } else { - return $matches[0]; + throw new Exception('SRI hash for file js/' . $matches[1] . ' not found, please add the missing file or remove it from lib/Configuration.php.'); } }, file_get_contents($file) ); file_put_contents($file, $content); + if ($counter != count(self::$hashes)) { + throw new Exception('Mismatch between ' . count(self::$hashes) . ' found js files and ' . $counter . ' SRI hashes in lib/Configuration.php, please update lib/Configuration.php to match the list of js files.'); + } } } From 3c6df4573e23d602c9e6720d8cc3c79965bdcecc Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 9 Jul 2024 22:21:01 +0200 Subject: [PATCH 06/10] apply StyleCI recommendation --- tst/Bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index bee35847..c43e2b25 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -388,7 +388,7 @@ class Helper '#\'js/([a-z0-9.-]+.js)(\' +)=\> \'[^\']*\',#', function ($matches) use (&$counter) { if (array_key_exists($matches[1], Helper::$hashes)) { - $counter++; + ++$counter; return '\'js/' . $matches[1] . $matches[2] . '=> \'sha512-' . Helper::$hashes[$matches[1]] . '\','; } else { From 4b6c8356f5544774488385df582c77a8df599dd0 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 9 Jul 2024 22:35:21 +0200 Subject: [PATCH 07/10] clarify use of cache buster, avoid using regex --- lib/View.php | 7 ++++--- tst/Bootstrap.php | 2 -- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/View.php b/lib/View.php index 0caa911d..958be833 100644 --- a/lib/View.php +++ b/lib/View.php @@ -69,10 +69,11 @@ class View { $sri = array_key_exists($file, $this->_variables['SRI']) ? ' integrity="' . $this->_variables['SRI'][$file] . '"' : ''; - $suffix = preg_match('#\d.js$#', $file) == 0 ? - '?' . rawurlencode($this->_variables['VERSION']) : ''; + // if the file isn't versioned (ends in a digit), add our own version + $cacheBuster = ctype_digit(substr($file, -4, 1)) ? + '' : '?' . rawurlencode($this->_variables['VERSION']); echo '', PHP_EOL; + $cacheBuster, '"', $sri, ' crossorigin="anonymous">', PHP_EOL; } } diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index c43e2b25..28b56422 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -1,7 +1,5 @@ Date: Tue, 9 Jul 2024 22:47:15 +0200 Subject: [PATCH 08/10] re-add missing attribute --- tpl/page.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tpl/page.php b/tpl/page.php index 18e3b9a7..79bf482f 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -25,32 +25,32 @@ endif; - _scriptTag('js/kjua-0.9.0.js'); ?> + _scriptTag('js/kjua-0.9.0.js', 'async'); ?> - _scriptTag('js/base64-1.7.js'); ?> + _scriptTag('js/base64-1.7.js', 'async'); ?> - _scriptTag('js/zlib-1.3.1.js'); ?> - _scriptTag('js/base-x-4.0.0.js'); ?> - _scriptTag('js/rawinflate-0.3.js'); ?> + _scriptTag('js/zlib-1.3.1.js', 'async'); ?> + _scriptTag('js/base-x-4.0.0.js', 'async'); ?> + _scriptTag('js/rawinflate-0.3.js', 'async'); ?> - _scriptTag('js/prettify.js'); ?> + _scriptTag('js/prettify.js', 'async'); ?> - _scriptTag('js/showdown-2.1.0.js'); ?> + _scriptTag('js/showdown-2.1.0.js', 'async'); ?> - _scriptTag('js/purify-3.1.3.js'); ?> - _scriptTag('js/legacy.js'); ?> + _scriptTag('js/purify-3.1.3.js', 'async'); ?> + _scriptTag('js/legacy.js', 'async'); ?> _scriptTag('js/privatebin.js', 'defer'); ?> From 2b768985461ea7c9b3e3efa4a0fe0baba2bfa7c7 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Fri, 19 Jul 2024 08:05:40 +0200 Subject: [PATCH 09/10] preserve configured SRI hashes --- lib/Configuration.php | 4 ++++ tst/ConfigurationTest.php | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lib/Configuration.php b/lib/Configuration.php index 2659217f..3e014dc8 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -210,6 +210,10 @@ class Configuration } // check for missing keys and set defaults if necessary else { + // preserve configured SRI hashes + if ($section == 'sri' && array_key_exists($section, $config)) { + $this->_configuration[$section] = $config[$section]; + } foreach ($values as $key => $val) { if ($key == 'dir') { $val = PATH . $val; diff --git a/tst/ConfigurationTest.php b/tst/ConfigurationTest.php index 27a31f73..317c9dc9 100644 --- a/tst/ConfigurationTest.php +++ b/tst/ConfigurationTest.php @@ -120,9 +120,14 @@ class ConfigurationTest extends TestCase unset($options['expire_options']['1week']); unset($options['expire_options']['1year']); unset($options['expire_options']['never']); + $valid_sri = $options['sri'][array_key_first($options['sri'])]; + $options['sri'][array_key_first($options['sri'])] = ''; // empty string should get replaced with default + $options['sri']['js/example.js'] = 'some invalid SRI hash'; Helper::createIniFile(CONF, $options); $conf = new Configuration; + // restore expected results $options['expire']['default'] = '5min'; + $options['sri'][array_key_first($options['sri'])] = $valid_sri; $this->assertEquals($options, $conf->get(), 'not overriding "missing" subkeys'); } From 93bc1220860b6430593cd8d83f88bcfb983fb141 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Fri, 19 Jul 2024 08:18:10 +0200 Subject: [PATCH 10/10] apply StyleCI recommendation --- tst/ConfigurationTest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tst/ConfigurationTest.php b/tst/ConfigurationTest.php index 317c9dc9..d3e87387 100644 --- a/tst/ConfigurationTest.php +++ b/tst/ConfigurationTest.php @@ -120,14 +120,15 @@ class ConfigurationTest extends TestCase unset($options['expire_options']['1week']); unset($options['expire_options']['1year']); unset($options['expire_options']['never']); - $valid_sri = $options['sri'][array_key_first($options['sri'])]; - $options['sri'][array_key_first($options['sri'])] = ''; // empty string should get replaced with default + $sri_key = array_key_first($options['sri']); + $valid_sri = $options['sri'][$sri_key]; + $options['sri'][$sri_key] = ''; // empty string should get replaced with default $options['sri']['js/example.js'] = 'some invalid SRI hash'; Helper::createIniFile(CONF, $options); $conf = new Configuration; // restore expected results $options['expire']['default'] = '5min'; - $options['sri'][array_key_first($options['sri'])] = $valid_sri; + $options['sri'][$sri_key] = $valid_sri; $this->assertEquals($options, $conf->get(), 'not overriding "missing" subkeys'); }