1<?php 2/** 3 * DokuWiki Content Security Policy (CSP) plugin 4 * 5 * Configure via config manager 6 * 7 * host-expr examples: http://*.foo.com, mail.foo.com:443, https://store.foo.com 8 * Besides FQDNs there are some keywords which are allowed 'self', 'none' or data:-URIs 9 * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP 10 * 11 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 12 * @author Matthias Schulte <post@lupo49.de> 13 * @link https://www.dokuwiki.org/plugin:cspheader 14 */ 15class action_plugin_cspheader extends DokuWiki_Action_Plugin 16{ 17 /** 18 * CSP HTTP Header 19 */ 20 const CSP_HEADER = 'Content-Security-Policy:'; 21 22 /** 23 * @var array CSP directives names 24 * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives 25 */ 26 const DIRECTIVES = [ 27 'base-uri', 28 //'block-all-mixed-content', // this is a yes/no field and should be handled separately 29 'child-src', 30 'connect-src', 31 'default-src', 32 'font-src', 33 'form-action', 34 'frame-ancestors', 35 'frame-src', 36 'img-src', 37 'manifest-src', 38 'media-src', 39 'navigate-to', 40 'object-src', 41 'plugin-types', 42 'prefetch-src', 43 //'referrer', // deprecated 44 //'report-to', // this one isn't widely supported and expects a more complicated setup, skip for now 45 'report-uri', 46 //'require-sri-for', // obsolete 47 'sandbox', 48 'script-src', 49 'script-src-attr', 50 'script-src-elem', 51 'style-src', 52 'style-src-attr', 53 'style-src-elem', 54 'trusted-types', 55 //'upgrade-insecure-requests', // this is a yes/no field and should be handled separately 56 'worker-src', 57 ]; 58 59 public function register(Doku_Event_Handler $controller) 60 { 61 $controller->register_hook('ACTION_HEADERS_SEND', 'BEFORE', $this, 'handleHeadersSend'); 62 } 63 64 /** 65 * Handler for the ACTION_HEADERS_SEND event 66 * 67 * @param Doku_Event $event 68 * @param $params 69 * 70 * @noinspection PhpUnused, PhpUnusedParameterInspection 71 */ 72 public function handleHeadersSend(Doku_Event $event, $params) 73 { 74 $policies = []; 75 foreach (self::DIRECTIVES as $directive) { 76 $option = str_replace('-', '', $directive) . 'Value'; 77 $values = $this->getConf($option); 78 $values = explode("\n", $values); 79 $values = array_map('trim', $values); 80 $values = array_unique($values); 81 $values = array_filter($values); 82 if (!count($values)) continue; 83 84 $policies[$directive] = join(' ', $values); 85 } 86 87 $cspheader = self::CSP_HEADER; 88 foreach ($policies as $directive => $value) { 89 $cspheader .= " $directive $value;"; 90 } 91 92 // create nonce for inline scripts and replace placeholder in header 93 $nonce = bin2hex(random_bytes(16)); 94 putenv("NONCE=$nonce"); 95 $cspheader = str_replace('NONCE', $nonce, $cspheader); 96 97 array_push($event->data, $cspheader); 98 } 99} 100