From 62365880b40a1bc3c2b65fe1fb369733a112d825 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 25 Jan 2020 09:07:06 +0100 Subject: [PATCH] implement simplified translation logic, forcing the use of safe application via jQuery element --- js/privatebin.js | 80 +++++++++++++++---------- js/test/I18n.js | 149 ++++++++++++++++++++++------------------------ tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 4 files changed, 122 insertions(+), 111 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 39537cd9..82cc4e16 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -322,19 +322,12 @@ jQuery.PrivateBin = (function($, RawDeflate) { let format = args[0], i = 1; return format.replace(/%(s|d)/g, function (m) { - // m is the matched format, e.g. %s, %d let val = args[i]; - // A switch statement so that the formatter can be extended. - switch (m) - { - case '%d': - val = parseFloat(val); - if (isNaN(val)) { - val = 0; - } - break; - default: - // Default is %s + if (m === '%d') { + val = parseFloat(val); + if (isNaN(val)) { + val = 0; + } } ++i; return val; @@ -547,19 +540,23 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * translate a string * - * Optionally pass a jQuery element as the first parameter, to automatically - * let the text of this element be replaced. In case the (asynchronously + * As the first parameter a jQuery element has to be provided, to let + * the text of this element be replaced. In case the (asynchronously * loaded) language is not downloadet yet, this will make sure the string - * is replaced when it is actually loaded. - * So for easy translations passing the jQuery object to apply it to is - * more save, especially when they are loaded in the beginning. + * is replaced when it is actually loaded. This also handles HTML in + * secure fashion, to avoid XSS. + * The second parameter is the message ID, matching the ones found in + * the translation files under the i18n directory. + * Any additional parameters will get inserted into the message ID in + * place of %s (strings) or %d (digits), applying the appropriate plural + * in case of digits. See also Helper.sprintf(). * * @name I18n.translate * @function - * @param {jQuery} $element - optional + * @param {jQuery} $element * @param {string} messageId * @param {...*} args - one or multiple parameters injected into placeholders - * @return {string} + * @throws {string} */ me.translate = function() { @@ -573,6 +570,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { // optional jQuery element as first parameter $element = args[0]; args.shift(); + } else { + throw 'translation requires a jQuery element to be passed, for secure insertion of messages and to avoid double encoding of HTML entities'; } // extract messageId from arguments @@ -633,10 +632,10 @@ jQuery.PrivateBin = (function($, RawDeflate) { let containsLinks = args[0].indexOf(' 0) may never contain HTML as they may come from untrusted parties - if (i > 0 || !containsLinks) { + if (i > 0) { args[i] = Helper.htmlEntities(args[i]); } } @@ -654,18 +653,37 @@ jQuery.PrivateBin = (function($, RawDeflate) { ); } - // if $element is given, insert translation - if ($element !== null) { - if (containsLinks) { - $element.html(output); - } else { - // text node takes care of entity encoding - $element.text(output); - } - return ''; + if (containsLinks) { + $element.html(output); + } else { + // text node takes care of entity encoding + $element.text(output); } + }; - return output; + /** + * translate a string, outputs the result + * + * This function is identical to I18n.translate, but doesn't require a + * jQuery element as the first parameter, instead it returns the + * translated message as string. + * Avoid using this function, if possible, as it may double encode your + * message's HTML entities. This is done to fail safe, preventing XSS. + * + * @name I18n.translate2string + * @function + * @param {string} messageId + * @param {...*} args - one or multiple parameters injected into placeholders + * @throws {string} + * @return {string} + */ + me.translate2string = function() + { + let args = Array.prototype.slice.call(arguments), + $element = $('