From a905dd01d97c8858327cc958b83d976911a4dc5a Mon Sep 17 00:00:00 2001 From: Victor Boctor Date: Tue, 16 Aug 2016 23:25:40 -0700 Subject: [PATCH] Add API for Content-Security-Policy Add APIs to allow plugins to change the Content-Security-Policy header. Fixes #21263 --- core/http_api.php | 66 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/core/http_api.php b/core/http_api.php index 658a26af02..37aeda8189 100644 --- a/core/http_api.php +++ b/core/http_api.php @@ -29,6 +29,12 @@ require_api( 'config_api.php' ); +/** + * The Content-Security-Policy settings array. Use http_csp_add() to update it. + * @var array + */ +$g_csp = array(); + /** * Checks to see if script was queried through the HTTPS protocol * @return boolean True if protocol is HTTPS @@ -138,6 +144,43 @@ function http_content_headers() { } } +/** + * Add a Content-Security-Policy directive. + * + * @param string $p_type The directive type, e.g. style-src, script-src. + * @param string $p_value The directive value, e.g. 'self', https://ajax.googleapis.com + * @return void + */ +function http_csp_add( $p_type, $p_value ) { + global $g_csp; + + if ( isset( $g_csp[$p_type] ) ) { + if ( !in_array( $p_value, $g_csp[$p_type] ) ) { + $g_csp[$p_type][] = $p_value; + } + } else { + $g_csp[$p_type] = array( $p_value ); + } +} + +/** + * Constructs the value of the CSP header. + * @return string CSP header value. + */ +function http_csp_value() { + global $g_csp; + + $t_csp_value = ''; + + foreach ( $g_csp as $t_key => $t_values ) { + $t_csp_value .= $t_key . ' ' . implode( ' ', $t_values ) . '; '; + } + + $t_csp_value = trim( $t_csp_value, '; ' ); + + return $t_csp_value; +} + /** * Set security headers (frame busting, clickjacking/XSS/CSRF protection). * @return void @@ -147,32 +190,27 @@ function http_security_headers() { header( 'X-Frame-Options: DENY' ); # Define Content Security Policy - $t_csp = array( - "default-src 'self'", - "frame-ancestors 'none'", - ); - - $t_style_src = "style-src 'self'"; - $t_script_src = "script-src 'self'"; + http_csp_add( 'default-src', "'self'" ); + http_csp_add( 'frame-ancestors', "'none'" ); + http_csp_add( 'style-src', "'self'" ); + http_csp_add( 'script-src', "'self'" ); + http_csp_add( 'img-src', "'self'" ); # White list the CDN urls (if enabled) if ( config_get_global( 'cdn_enabled' ) == ON ) { $t_cdn_url = 'https://ajax.googleapis.com'; - $t_style_src .= " $t_cdn_url"; - $t_script_src .= " $t_cdn_url"; + http_csp_add( 'style-src', $t_cdn_url ); + http_csp_add( 'script-src', $t_cdn_url ); } # Relaxing policy for roadmap page to allow inline styles # This is a workaround to fix the broken progress bars (see #19501) if( 'roadmap_page.php' == basename( $_SERVER['SCRIPT_NAME'] ) ) { - $t_style_src .= " 'unsafe-inline'"; + http_csp_add( 'style-src', "'unsafe-inline'" ); } - $t_csp[] = $t_style_src; - $t_csp[] = $t_script_src; - # Set CSP header - header( 'Content-Security-Policy: ' . implode('; ', $t_csp) ); + header( 'Content-Security-Policy: ' . http_csp_value() ); if( http_is_protocol_https() ) { header( 'Strict-Transport-Security: max-age=7776000' );