Merge branch 'hardening'

This commit is contained in:
El RIDO 2022-03-28 19:02:35 +02:00
commit 1dd53a93f4
No known key found for this signature in database
GPG key ID: 0F5C940A6BD81F92
11 changed files with 122 additions and 46 deletions

View file

@ -9,11 +9,15 @@
* ADDED: Google Cloud Storage backend support (#795) * ADDED: Google Cloud Storage backend support (#795)
* ADDED: Oracle database support (#868) * ADDED: Oracle database support (#868)
* ADDED: Configuration option to limit paste creation and commenting to certain IPs (#883) * ADDED: Configuration option to limit paste creation and commenting to certain IPs (#883)
* ADDED: Set CSP also as meta tag, to deal with misconfigured webservers mangling the HTTP header
* ADDED: Sanitize SVG preview, preventing script execution in instance context
* CHANGED: Language selection cookie only transmitted over HTTPS (#472) * CHANGED: Language selection cookie only transmitted over HTTPS (#472)
* CHANGED: Upgrading libraries to: base-x 4.0.0, bootstrap 3.4.1 (JS), DOMpurify 2.3.6, ip-lib 1.18.0, jQuery 3.6.0, random_compat 2.0.21 & Showdown 2.0.0 * CHANGED: Upgrading libraries to: base-x 4.0.0, bootstrap 3.4.1 (JS), DOMpurify 2.3.6, ip-lib 1.18.0, jQuery 3.6.0, random_compat 2.0.21 & Showdown 2.0.3
* CHANGED: Removed automatic `.ini` configuration file migration (#808) * CHANGED: Removed automatic `.ini` configuration file migration (#808)
* CHANGED: Removed configurable `dir` for `traffic` & `purge` limiters (#419) * CHANGED: Removed configurable `dir` for `traffic` & `purge` limiters (#419)
* CHANGED: Server salt, traffic and purge limiter now stored in the storage backend (#419) * CHANGED: Server salt, traffic and purge limiter now stored in the storage backend (#419)
* CHANGED: Drop support for attachment download in IE
* FIXED: Error when attachments are disabled, but paste with attachment gets displayed
* **1.3.5 (2021-04-05)** * **1.3.5 (2021-04-05)**
* ADDED: Translations for Hebrew, Lithuanian, Indonesian and Catalan * ADDED: Translations for Hebrew, Lithuanian, Indonesian and Catalan
* ADDED: Make the project info configurable (#681) * ADDED: Make the project info configurable (#681)

View file

@ -87,7 +87,7 @@ languageselection = false
; async functions and display an error if not and for Chrome to enable ; async functions and display an error if not and for Chrome to enable
; webassembly support (used for zlib compression). You can remove it if Chrome ; webassembly support (used for zlib compression). You can remove it if Chrome
; doesn't need to be supported and old browsers don't need to be warned. ; doesn't need to be supported and old browsers don't need to be warned.
; cspheader = "default-src 'none'; base-uri 'self'; form-action 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval' resource:; style-src 'self'; font-src 'self'; frame-ancestors 'none'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads" ; cspheader = "default-src 'none'; base-uri 'self'; form-action 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval'; style-src 'self'; font-src 'self'; frame-ancestors 'none'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads"
; stay compatible with PrivateBin Alpha 0.19, less secure ; stay compatible with PrivateBin Alpha 0.19, less secure
; if enabled will use base64.js version 1.7 instead of 2.1.9 and sha1 instead of ; if enabled will use base64.js version 1.7 instead of 2.1.9 and sha1 instead of

View file

@ -16,7 +16,7 @@ global.zlib = require('./zlib-1.2.11').zlib;
require('./prettify'); require('./prettify');
global.prettyPrint = window.PR.prettyPrint; global.prettyPrint = window.PR.prettyPrint;
global.prettyPrintOne = window.PR.prettyPrintOne; global.prettyPrintOne = window.PR.prettyPrintOne;
global.showdown = require('./showdown-2.0.0'); global.showdown = require('./showdown-2.0.3');
global.DOMPurify = require('./purify-2.3.6'); global.DOMPurify = require('./purify-2.3.6');
global.baseX = require('./base-x-4.0.0').baseX; global.baseX = require('./base-x-4.0.0').baseX;
global.Legacy = require('./legacy').Legacy; global.Legacy = require('./legacy').Legacy;

View file

@ -52,6 +52,31 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/ */
let z; let z;
/**
* DOMpurify settings for HTML content
*
* @private
*/
const purifyHtmlConfig = {
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
SAFE_FOR_JQUERY: true,
USE_PROFILES: {
html: true
}
};
/**
* DOMpurify settings for SVG content
*
* @private
*/
const purifySvgConfig = {
USE_PROFILES: {
svg: true,
svgFilters: true
}
};
/** /**
* CryptoData class * CryptoData class
* *
@ -409,7 +434,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
element.html().replace( element.html().replace(
/(((https?|ftp):\/\/[\w?!=&.\/-;#@~%+*-]+(?![\w\s?!&.\/;#~%"=-]>))|((magnet):[\w?=&.\/-;#@~%+*-]+))/ig, /(((https?|ftp):\/\/[\w?!=&.\/-;#@~%+*-]+(?![\w\s?!&.\/;#~%"=-]>))|((magnet):[\w?=&.\/-;#@~%+*-]+))/ig,
'<a href="$1" rel="nofollow noopener noreferrer">$1</a>' '<a href="$1" rel="nofollow noopener noreferrer">$1</a>'
) ),
purifyHtmlConfig
) )
); );
}; };
@ -2536,7 +2562,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// let showdown convert the HTML and sanitize HTML *afterwards*! // let showdown convert the HTML and sanitize HTML *afterwards*!
$plainText.html( $plainText.html(
DOMPurify.sanitize( DOMPurify.sanitize(
converter.makeHtml(text) converter.makeHtml(text),
purifyHtmlConfig
) )
); );
// add table classes from bootstrap css // add table classes from bootstrap css
@ -2752,6 +2779,34 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$dropzone; $dropzone;
/** /**
* get blob URL from string data and mime type
*
* @name AttachmentViewer.getBlobUrl
* @private
* @function
* @param {string} data - raw data of attachment
* @param {string} data - mime type of attachment
* @return {string} objectURL
*/
function getBlobUrl(data, mimeType)
{
// Transform into a Blob
const buf = new Uint8Array(data.length);
for (let i = 0; i < data.length; ++i) {
buf[i] = data.charCodeAt(i);
}
const blob = new window.Blob(
[buf],
{
type: mimeType
}
);
// Get blob URL
return window.URL.createObjectURL(blob);
}
/**
* sets the attachment but does not yet show it * sets the attachment but does not yet show it
* *
* @name AttachmentViewer.setAttachment * @name AttachmentViewer.setAttachment
@ -2761,44 +2816,42 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/ */
me.setAttachment = function(attachmentData, fileName) me.setAttachment = function(attachmentData, fileName)
{ {
// data URI format: data:[<mediaType>][;base64],<data> // skip, if attachments got disabled
if (!$attachmentLink || !$attachmentPreview) return;
// data URI format: data:[<mimeType>][;base64],<data>
// position in data URI string of where data begins // position in data URI string of where data begins
const base64Start = attachmentData.indexOf(',') + 1; const base64Start = attachmentData.indexOf(',') + 1;
// position in data URI string of where mediaType ends // position in data URI string of where mimeType ends
const mediaTypeEnd = attachmentData.indexOf(';'); const mimeTypeEnd = attachmentData.indexOf(';');
// extract mediaType // extract mimeType
const mediaType = attachmentData.substring(5, mediaTypeEnd); const mimeType = attachmentData.substring(5, mimeTypeEnd);
// extract data and convert to binary // extract data and convert to binary
const rawData = attachmentData.substring(base64Start); const rawData = attachmentData.substring(base64Start);
const decodedData = rawData.length > 0 ? atob(rawData) : ''; const decodedData = rawData.length > 0 ? atob(rawData) : '';
// Transform into a Blob let blobUrl = getBlobUrl(decodedData, mimeType);
const buf = new Uint8Array(decodedData.length); $attachmentLink.attr('href', blobUrl);
for (let i = 0; i < decodedData.length; ++i) {
buf[i] = decodedData.charCodeAt(i);
}
const blob = new window.Blob([ buf ], { type: mediaType });
// Get Blob URL
const blobUrl = window.URL.createObjectURL(blob);
// IE does not support setting a data URI on an a element
// Using msSaveBlob to download
if (window.Blob && navigator.msSaveBlob) {
$attachmentLink.off('click').on('click', function () {
navigator.msSaveBlob(blob, fileName);
});
} else {
$attachmentLink.attr('href', blobUrl);
}
if (typeof fileName !== 'undefined') { if (typeof fileName !== 'undefined') {
$attachmentLink.attr('download', fileName); $attachmentLink.attr('download', fileName);
} }
me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mediaType); // sanitize SVG preview
// prevents executing embedded scripts when CSP is not set and user
// right-clicks/long-taps and opens the SVG in a new tab - prevented
// in the preview by use of an img tag, which disables scripts, too
if (mimeType.match(/^image\/.*svg/i)) {
const sanitizedData = DOMPurify.sanitize(
decodedData,
purifySvgConfig
);
blobUrl = getBlobUrl(sanitizedData, mimeType);
}
me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mimeType);
}; };
/** /**
@ -2809,6 +2862,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/ */
me.showAttachment = function() me.showAttachment = function()
{ {
// skip, if attachments got disabled
if (!$attachment || !$attachmentPreview) return;
$attachment.removeClass('hidden'); $attachment.removeClass('hidden');
if (attachmentHasPreview) { if (attachmentHasPreview) {
@ -3016,13 +3072,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
me.handleBlobAttachmentPreview = function ($targetElement, blobUrl, mimeType) { me.handleBlobAttachmentPreview = function ($targetElement, blobUrl, mimeType) {
if (blobUrl) { if (blobUrl) {
attachmentHasPreview = true; attachmentHasPreview = true;
if (mimeType.match(/image\//i)) { if (mimeType.match(/^image\//i)) {
$targetElement.html( $targetElement.html(
$(document.createElement('img')) $(document.createElement('img'))
.attr('src', blobUrl) .attr('src', blobUrl)
.attr('class', 'img-thumbnail') .attr('class', 'img-thumbnail')
); );
} else if (mimeType.match(/video\//i)) { } else if (mimeType.match(/^video\//i)) {
$targetElement.html( $targetElement.html(
$(document.createElement('video')) $(document.createElement('video'))
.attr('controls', 'true') .attr('controls', 'true')
@ -3033,7 +3089,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
.attr('type', mimeType) .attr('type', mimeType)
.attr('src', blobUrl)) .attr('src', blobUrl))
); );
} else if (mimeType.match(/audio\//i)) { } else if (mimeType.match(/^audio\//i)) {
$targetElement.html( $targetElement.html(
$(document.createElement('audio')) $(document.createElement('audio'))
.attr('controls', 'true') .attr('controls', 'true')
@ -3665,7 +3721,14 @@ jQuery.PrivateBin = (function($, RawDeflate) {
for (let i = 0; i < $head.length; ++i) { for (let i = 0; i < $head.length; ++i) {
newDoc.write($head[i].outerHTML); newDoc.write($head[i].outerHTML);
} }
newDoc.write('</head><body><pre>' + DOMPurify.sanitize(Helper.htmlEntities(paste)) + '</pre></body></html>'); newDoc.write(
'</head><body><pre>' +
DOMPurify.sanitize(
Helper.htmlEntities(paste),
purifyHtmlConfig
) +
'</pre></body></html>'
);
newDoc.close(); newDoc.close();
} }
@ -5394,11 +5457,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// first load translations // first load translations
I18n.loadTranslations(); I18n.loadTranslations();
DOMPurify.setConfig({
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
SAFE_FOR_JQUERY: true
});
// Add a hook to make all links open a new window // Add a hook to make all links open a new window
DOMPurify.addHook('afterSanitizeAttributes', function(node) { DOMPurify.addHook('afterSanitizeAttributes', function(node) {
// set all elements owning target to target=_blank // set all elements owning target to target=_blank

File diff suppressed because one or more lines are too long

2
js/showdown-2.0.3.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -54,7 +54,7 @@ class Configuration
'urlshortener' => '', 'urlshortener' => '',
'qrcode' => true, 'qrcode' => true,
'icon' => 'identicon', 'icon' => 'identicon',
'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\' resource:; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads', 'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
'zerobincompatibility' => false, 'zerobincompatibility' => false,
'httpwarning' => true, 'httpwarning' => true,
'compression' => 'zlib', 'compression' => 'zlib',

View file

@ -364,6 +364,16 @@ class Controller
setcookie('lang', $languageselection, 0, '', '', true); setcookie('lang', $languageselection, 0, '', '', true);
} }
// strip policies that are unsupported in meta tag
$metacspheader = str_replace(
array(
'frame-ancestors \'none\'; ',
'; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
),
'',
$this->_conf->getKey('cspheader')
);
$page = new View; $page = new View;
$page->assign('NAME', $this->_conf->getKey('name')); $page->assign('NAME', $this->_conf->getKey('name'));
$page->assign('BASEPATH', I18n::_($this->_conf->getKey('basepath'))); $page->assign('BASEPATH', I18n::_($this->_conf->getKey('basepath')));
@ -392,6 +402,7 @@ class Controller
$page->assign('HTTPWARNING', $this->_conf->getKey('httpwarning')); $page->assign('HTTPWARNING', $this->_conf->getKey('httpwarning'));
$page->assign('HTTPSLINK', 'https://' . $this->_request->getHost() . $this->_request->getRequestUri()); $page->assign('HTTPSLINK', 'https://' . $this->_request->getHost() . $this->_request->getRequestUri());
$page->assign('COMPRESSION', $this->_conf->getKey('compression')); $page->assign('COMPRESSION', $this->_conf->getKey('compression'));
$page->assign('CSPHEADER', $metacspheader);
$page->draw($this->_conf->getKey('template')); $page->draw($this->_conf->getKey('template'));
} }

View file

@ -7,6 +7,7 @@ $isPage = substr($template, -5) === '-page';
<html lang="<?php echo I18n::_('en'); ?>"> <html lang="<?php echo I18n::_('en'); ?>">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" content="<?php echo I18n::encode($CSPHEADER); ?>">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
@ -66,13 +67,13 @@ if ($SYNTAXHIGHLIGHTING) :
endif; endif;
if ($MARKDOWN) : if ($MARKDOWN) :
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.0.js" integrity="sha512-UB9jpMTOJLSnVzePuqlSGT34G70wEGqtIWabMeAh+Drnj4/uQ8rFkFn1zkN9vkWp/7nA51U2LmP23H5MJvBXsw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.3.js" integrity="sha512-vcfjvW3UKHD/4vlQx804cqWK88jFmjsWRsZ8/u5YEcyHB1IituxrXDU7TvdqsFVsMnxpE/UIEo25/SYW+puWHw==" crossorigin="anonymous"></script>
<?php <?php
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-EdUms2nI12Cmtv014stIEBlyPjeKMHlkg7NiBJup1b7jJF5amKhev2RwTaldINXK4UaWbZtQ6hGuMPNvvNQZFA==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-kFUoM3Qg6VzRTJGau3wWP2IRZQF2WToL7Dgvazxqp0jgYIKA5cCK9xFy9Zes208XX0nYP64scW2nJIYNk9pL6Q==" crossorigin="anonymous"></script>
<!-- icon --> <!-- icon -->
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" /> <link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" /> <link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />

View file

@ -4,6 +4,7 @@ use PrivateBin\I18n;
<html lang="<?php echo I18n::_('en'); ?>"> <html lang="<?php echo I18n::_('en'); ?>">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" content="<?php echo I18n::encode($CSPHEADER); ?>">
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<meta name="google" content="notranslate"> <meta name="google" content="notranslate">
<title><?php echo I18n::_($NAME); ?></title> <title><?php echo I18n::_($NAME); ?></title>
@ -44,13 +45,13 @@ if ($SYNTAXHIGHLIGHTING):
endif; endif;
if ($MARKDOWN): if ($MARKDOWN):
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.0.js" integrity="sha512-UB9jpMTOJLSnVzePuqlSGT34G70wEGqtIWabMeAh+Drnj4/uQ8rFkFn1zkN9vkWp/7nA51U2LmP23H5MJvBXsw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.3.js" integrity="sha512-vcfjvW3UKHD/4vlQx804cqWK88jFmjsWRsZ8/u5YEcyHB1IituxrXDU7TvdqsFVsMnxpE/UIEo25/SYW+puWHw==" crossorigin="anonymous"></script>
<?php <?php
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-EdUms2nI12Cmtv014stIEBlyPjeKMHlkg7NiBJup1b7jJF5amKhev2RwTaldINXK4UaWbZtQ6hGuMPNvvNQZFA==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-kFUoM3Qg6VzRTJGau3wWP2IRZQF2WToL7Dgvazxqp0jgYIKA5cCK9xFy9Zes208XX0nYP64scW2nJIYNk9pL6Q==" crossorigin="anonymous"></script>
<!-- icon --> <!-- icon -->
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" /> <link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" /> <link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />

View file

@ -60,6 +60,7 @@ class ViewTest extends PHPUnit_Framework_TestCase
$page->assign('HTTPWARNING', true); $page->assign('HTTPWARNING', true);
$page->assign('HTTPSLINK', 'https://example.com/'); $page->assign('HTTPSLINK', 'https://example.com/');
$page->assign('COMPRESSION', 'zlib'); $page->assign('COMPRESSION', 'zlib');
$page->assign('CSPHEADER', 'default-src \'none\'');
$dir = dir(PATH . 'tpl'); $dir = dir(PATH . 'tpl');
while (false !== ($file = $dir->read())) { while (false !== ($file = $dir->read())) {