several changes:
- added tests for all 4 cases: output to string or into element vs first param contains link or not - cleaned up logic - skip HTML entity encoding only if we can ensure insertion to text node / when output to string, we always encode - DOMpurify sanitizes gopher, ws & wss links, which we previosly had tested for
This commit is contained in:
parent
fa9d3037ba
commit
685c354d0e
|
@ -36,7 +36,7 @@ var a2zString = ['a','b','c','d','e','f','g','h','i','j','k','l','m',
|
||||||
return c.toUpperCase();
|
return c.toUpperCase();
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
schemas = ['ftp','gopher','http','https','ws','wss'],
|
schemas = ['ftp','http','https'],
|
||||||
supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'],
|
supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'],
|
||||||
mimeTypes = ['image/png', 'application/octet-stream'],
|
mimeTypes = ['image/png', 'application/octet-stream'],
|
||||||
formats = ['plaintext', 'markdown', 'syntaxhighlighting'],
|
formats = ['plaintext', 'markdown', 'syntaxhighlighting'],
|
||||||
|
|
|
@ -631,28 +631,35 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
|
|
||||||
// messageID may contain links, but should be from a trusted source (code or translation JSON files)
|
// messageID may contain links, but should be from a trusted source (code or translation JSON files)
|
||||||
let containsLinks = args[0].indexOf('<a') !== -1;
|
let containsLinks = args[0].indexOf('<a') !== -1;
|
||||||
for (let i = 0; i < args.length; ++i) {
|
|
||||||
// parameters (i > 0) may never contain HTML as they may come from untrusted parties
|
// prevent double encoding, when we insert into a text node
|
||||||
if (i > 0 || !containsLinks) {
|
if (!containsLinks || $element === null) {
|
||||||
args[i] = Helper.htmlEntities(args[i]);
|
for (let i = 0; i < args.length; ++i) {
|
||||||
|
// parameters (i > 0) may never contain HTML as they may come from untrusted parties
|
||||||
|
if (i > 0 || !containsLinks) {
|
||||||
|
args[i] = Helper.htmlEntities(args[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// format string
|
// format string
|
||||||
let output = Helper.sprintf.apply(this, args);
|
let output = Helper.sprintf.apply(this, args);
|
||||||
|
|
||||||
// if $element is given, apply text to element
|
if (containsLinks) {
|
||||||
|
// only allow tags/attributes we actually use in translations
|
||||||
|
output = DOMPurify.sanitize(
|
||||||
|
output, {
|
||||||
|
ALLOWED_TAGS: ['a', 'br', 'i', 'span'],
|
||||||
|
ALLOWED_ATTR: ['href', 'id']
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if $element is given, insert translation
|
||||||
if ($element !== null) {
|
if ($element !== null) {
|
||||||
if (containsLinks) {
|
if (containsLinks) {
|
||||||
// only allow tags/attributes we actually use in our translations
|
$element.html(output);
|
||||||
$element.html(
|
|
||||||
DOMPurify.sanitize(output, {
|
|
||||||
ALLOWED_TAGS: ['a', 'br', 'i', 'span'],
|
|
||||||
ALLOWED_ATTR: ['href', 'id']
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// avoid HTML entity encoding if translation contains no links
|
// text node takes care of entity encoding
|
||||||
$element.text(output);
|
$element.text(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1946,11 +1953,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
*/
|
*/
|
||||||
me.createPasteNotification = function(url, deleteUrl)
|
me.createPasteNotification = function(url, deleteUrl)
|
||||||
{
|
{
|
||||||
$('#pastelink').html(
|
I18n._(
|
||||||
I18n._(
|
$('#pastelink'),
|
||||||
'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>',
|
'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>',
|
||||||
url, url
|
url, url
|
||||||
)
|
|
||||||
);
|
);
|
||||||
// save newly created element
|
// save newly created element
|
||||||
$pasteUrl = $('#pasteurl');
|
$pasteUrl = $('#pasteurl');
|
||||||
|
|
|
@ -88,7 +88,7 @@ describe('AttachmentViewer', function () {
|
||||||
if (prefix.indexOf('<a') === -1 && postfix.indexOf('<a') === -1) {
|
if (prefix.indexOf('<a') === -1 && postfix.indexOf('<a') === -1) {
|
||||||
result = $.PrivateBin.Helper.htmlEntities(prefix + filename + postfix);
|
result = $.PrivateBin.Helper.htmlEntities(prefix + filename + postfix);
|
||||||
} else {
|
} else {
|
||||||
result = $('<div>').html(prefix + $.PrivateBin.Helper.htmlEntities(filename) + postfix).html();
|
result = prefix + $.PrivateBin.Helper.htmlEntities(filename) + postfix;
|
||||||
}
|
}
|
||||||
if (filename.length) {
|
if (filename.length) {
|
||||||
results.push(
|
results.push(
|
||||||
|
|
|
@ -3,6 +3,7 @@ var common = require('../common');
|
||||||
|
|
||||||
describe('I18n', function () {
|
describe('I18n', function () {
|
||||||
describe('translate', function () {
|
describe('translate', function () {
|
||||||
|
this.timeout(30000);
|
||||||
before(function () {
|
before(function () {
|
||||||
$.PrivateBin.I18n.reset();
|
$.PrivateBin.I18n.reset();
|
||||||
});
|
});
|
||||||
|
@ -45,13 +46,13 @@ describe('I18n', function () {
|
||||||
'string',
|
'string',
|
||||||
function (prefix, params, postfix) {
|
function (prefix, params, postfix) {
|
||||||
prefix = prefix.replace(/%(s|d)/g, '%%');
|
prefix = prefix.replace(/%(s|d)/g, '%%');
|
||||||
params[0] = params[0].replace(/%(s|d)/g, '%%').replace(/%<a/g, '');
|
params[0] = params[0].replace(/%(s|d)/g, '%%').replace(/<a/g, '');
|
||||||
postfix = postfix.replace(/%(s|d)/g, '%%');
|
postfix = postfix.replace(/%(s|d)/g, '%%');
|
||||||
var translation = $.PrivateBin.Helper.htmlEntities(prefix + params[0] + postfix);
|
const translation = $.PrivateBin.Helper.htmlEntities(prefix + params[0] + postfix);
|
||||||
params.unshift(prefix + '%s' + postfix);
|
params.unshift(prefix + '%s' + postfix);
|
||||||
var result = $.PrivateBin.I18n.translate.apply(this, params);
|
const result = $.PrivateBin.I18n.translate.apply(this, params);
|
||||||
$.PrivateBin.I18n.reset();
|
$.PrivateBin.I18n.reset();
|
||||||
var alias = $.PrivateBin.I18n._.apply(this, params);
|
const alias = $.PrivateBin.I18n._.apply(this, params);
|
||||||
$.PrivateBin.I18n.reset();
|
$.PrivateBin.I18n.reset();
|
||||||
return translation === result && translation === alias;
|
return translation === result && translation === alias;
|
||||||
}
|
}
|
||||||
|
@ -63,14 +64,81 @@ describe('I18n', function () {
|
||||||
'string',
|
'string',
|
||||||
function (prefix, params, postfix) {
|
function (prefix, params, postfix) {
|
||||||
prefix = prefix.replace(/%(s|d)/g, '%%');
|
prefix = prefix.replace(/%(s|d)/g, '%%');
|
||||||
params[0] = params[0].replace(/%(s|d)/g, '%%') + '<a/>';
|
params[0] = params[0].replace(/%(s|d)/g, '%%');
|
||||||
postfix = postfix.replace(/%(s|d)/g, '%%');
|
postfix = postfix.replace(/%(s|d)/g, '%%');
|
||||||
var translation = $.PrivateBin.Helper.htmlEntities(prefix) + params[0] + $.PrivateBin.Helper.htmlEntities(postfix);
|
const translation = DOMPurify.sanitize(
|
||||||
|
prefix + $.PrivateBin.Helper.htmlEntities(params[0]) + '<a></a>' + postfix, {
|
||||||
|
ALLOWED_TAGS: ['a', 'br', 'i', 'span'],
|
||||||
|
ALLOWED_ATTR: ['href', 'id']
|
||||||
|
}
|
||||||
|
);
|
||||||
|
params.unshift(prefix + '%s<a></a>' + postfix);
|
||||||
|
const result = $.PrivateBin.I18n.translate.apply(this, params);
|
||||||
|
$.PrivateBin.I18n.reset();
|
||||||
|
const alias = $.PrivateBin.I18n._.apply(this, params);
|
||||||
|
$.PrivateBin.I18n.reset();
|
||||||
|
return translation === result && translation === alias;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
jsc.property(
|
||||||
|
'replaces %s in strings with first given parameter into an element, encoding all, when no link is in the messageID',
|
||||||
|
'string',
|
||||||
|
'(small nearray) string',
|
||||||
|
'string',
|
||||||
|
function (prefix, params, postfix) {
|
||||||
|
prefix = prefix.replace(/%(s|d)/g, '%%');
|
||||||
|
params[0] = params[0].replace(/%(s|d)/g, '%%').replace(/<a/g, '');
|
||||||
|
postfix = postfix.replace(/%(s|d)/g, '%%');
|
||||||
|
const translation = $.PrivateBin.Helper.htmlEntities(prefix + params[0] + postfix);
|
||||||
params.unshift(prefix + '%s' + postfix);
|
params.unshift(prefix + '%s' + postfix);
|
||||||
var result = $.PrivateBin.I18n.translate.apply(this, params);
|
let clean = jsdom();
|
||||||
|
$('body').html('<div id="i18n"></div>');
|
||||||
|
params.unshift($('#i18n'));
|
||||||
|
$.PrivateBin.I18n.translate.apply(this, params);
|
||||||
|
const result = $('#i18n').text();
|
||||||
$.PrivateBin.I18n.reset();
|
$.PrivateBin.I18n.reset();
|
||||||
var alias = $.PrivateBin.I18n._.apply(this, params);
|
clean();
|
||||||
|
clean = jsdom();
|
||||||
|
$('body').html('<div id="i18n"></div>');
|
||||||
|
params[0] = $('#i18n');
|
||||||
|
$.PrivateBin.I18n._.apply(this, params);
|
||||||
|
const alias = $('#i18n').text();
|
||||||
$.PrivateBin.I18n.reset();
|
$.PrivateBin.I18n.reset();
|
||||||
|
clean();
|
||||||
|
return translation === result && translation === alias;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
jsc.property(
|
||||||
|
'replaces %s in strings with first given parameter into an element, encoding params only, when a link is part of the messageID inserted',
|
||||||
|
'string',
|
||||||
|
'(small nearray) string',
|
||||||
|
'string',
|
||||||
|
function (prefix, params, postfix) {
|
||||||
|
prefix = prefix.replace(/%(s|d)/g, '%%');
|
||||||
|
params[0] = params[0].replace(/%(s|d)/g, '%%');
|
||||||
|
postfix = postfix.replace(/%(s|d)/g, '%%');
|
||||||
|
const translation = $('<div>').html(DOMPurify.sanitize(
|
||||||
|
prefix + $.PrivateBin.Helper.htmlEntities(params[0]) + '<a></a>' + postfix, {
|
||||||
|
ALLOWED_TAGS: ['a', 'br', 'i', 'span'],
|
||||||
|
ALLOWED_ATTR: ['href', 'id']
|
||||||
|
}
|
||||||
|
)).html();
|
||||||
|
let args = Array.prototype.slice.call(params);
|
||||||
|
args.unshift(prefix + '%s<a></a>' + postfix);
|
||||||
|
let clean = jsdom();
|
||||||
|
$('body').html('<div id="i18n"></div>');
|
||||||
|
args.unshift($('#i18n'));
|
||||||
|
$.PrivateBin.I18n.translate.apply(this, args);
|
||||||
|
const result = $('#i18n').html();
|
||||||
|
$.PrivateBin.I18n.reset();
|
||||||
|
clean();
|
||||||
|
clean = jsdom();
|
||||||
|
$('body').html('<div id="i18n"></div>');
|
||||||
|
args[0] = $('#i18n');
|
||||||
|
$.PrivateBin.I18n._.apply(this, args);
|
||||||
|
const alias = $('#i18n').html();
|
||||||
|
$.PrivateBin.I18n.reset();
|
||||||
|
clean();
|
||||||
return translation === result && translation === alias;
|
return translation === result && translation === alias;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -72,7 +72,7 @@ endif;
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.7.js" integrity="sha512-XjNEK1xwh7SJ/7FouwV4VZcGW9cMySL3SwNpXgrURLBcXXQYtZdqhGoNdEwx9vwLvFjUGDQVNgpOrTsXlSTiQg==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.7.js" integrity="sha512-XjNEK1xwh7SJ/7FouwV4VZcGW9cMySL3SwNpXgrURLBcXXQYtZdqhGoNdEwx9vwLvFjUGDQVNgpOrTsXlSTiQg==" 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-NI8nepXnEpe180qcTGA7ndw76ybURZvHkXTEdgj8+lAPpiVeW3K76DcK2J1YhQSsMBA2olquoJKf7/DEJQE3DA==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-s5eJk4kWlOK8dmXtEGUcWDKj97JAzHtTwFBU8U++rIdLNFBe7D669cec8JzEXWPiQNsHe4PLwshwqTOy/7yfyA==" crossorigin="anonymous"></script>
|
||||||
<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" />
|
||||||
<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
|
<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
|
||||||
|
|
|
@ -50,7 +50,7 @@ endif;
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.7.js" integrity="sha512-XjNEK1xwh7SJ/7FouwV4VZcGW9cMySL3SwNpXgrURLBcXXQYtZdqhGoNdEwx9vwLvFjUGDQVNgpOrTsXlSTiQg==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.7.js" integrity="sha512-XjNEK1xwh7SJ/7FouwV4VZcGW9cMySL3SwNpXgrURLBcXXQYtZdqhGoNdEwx9vwLvFjUGDQVNgpOrTsXlSTiQg==" 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-NI8nepXnEpe180qcTGA7ndw76ybURZvHkXTEdgj8+lAPpiVeW3K76DcK2J1YhQSsMBA2olquoJKf7/DEJQE3DA==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-s5eJk4kWlOK8dmXtEGUcWDKj97JAzHtTwFBU8U++rIdLNFBe7D669cec8JzEXWPiQNsHe4PLwshwqTOy/7yfyA==" crossorigin="anonymous"></script>
|
||||||
<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" />
|
||||||
<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
|
<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
|
||||||
|
|
Loading…
Reference in New Issue