backporting double encoding fixes from #560

This commit is contained in:
El RIDO 2020-02-16 08:37:33 +01:00
parent 2a5f622580
commit 6ccbad612d
No known key found for this signature in database
GPG Key ID: 0F5C940A6BD81F92
9 changed files with 249 additions and 125 deletions

View File

@ -1,5 +1,7 @@
# PrivateBin version history # PrivateBin version history
* **1.2.3 (2020-02-16)**
* FIXED: HTML entity double encoding issues introduced in 1.3.2 (#560)
* **1.2.2 (2020-01-11)** * **1.2.2 (2020-01-11)**
* CHANGED: Upgrading libraries to: bootstrap 3.4.1, DOMpurify 2.0.7, jQuery 3.4.1, kjua 0.6.0, Showdown 1.9.1 & SJCL 1.0.8 * CHANGED: Upgrading libraries to: bootstrap 3.4.1, DOMpurify 2.0.7, jQuery 3.4.1, kjua 0.6.0, Showdown 1.9.1 & SJCL 1.0.8
* FIXED: HTML injection via unescaped attachment filename (#554) * FIXED: HTML injection via unescaped attachment filename (#554)

View File

@ -32,7 +32,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'],

View File

@ -68,6 +68,26 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
*/ */
var baseUri = null; var baseUri = null;
/**
* character to HTML entity lookup table
*
* @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60}
* @name Helper.entityMap
* @private
* @enum {Object}
* @readonly
*/
var entityMap = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;',
'`': '&#x60;',
'=': '&#x3D;'
};
/** /**
* converts a duration (in seconds) into human friendly approximation * converts a duration (in seconds) into human friendly approximation
* *
@ -171,19 +191,12 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
var format = args[0], var format = args[0],
i = 1; i = 1;
return format.replace(/%(s|d)/g, function (m) { return format.replace(/%(s|d)/g, function (m) {
// m is the matched format, e.g. %s, %d
var val = args[i]; var val = args[i];
// A switch statement so that the formatter can be extended. if (m === '%d') {
switch (m)
{
case '%d':
val = parseFloat(val); val = parseFloat(val);
if (isNaN(val)) { if (isNaN(val)) {
val = 0; val = 0;
} }
break;
default:
// Default is %s
} }
++i; ++i;
return val; return val;
@ -237,15 +250,21 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
}; };
/** /**
* resets state, used for unit testing * convert all applicable characters to HTML entities
* *
* @name Helper.reset * @see {@link https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html}
* @name Helper.htmlEntities
* @function * @function
* @param {string} str
* @return {string} escaped HTML
*/ */
me.reset = function() me.htmlEntities = function(str) {
{ return String(str).replace(
baseUri = null; /[&<>"'`=\/]/g, function(s) {
}; return entityMap[s];
}
);
}
/** /**
* checks whether this is a bot we dislike * checks whether this is a bot we dislike
@ -268,29 +287,14 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
} }
/** /**
* encode all applicable characters to HTML entities * resets state, used for unit testing
* *
* @see {@link https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html} * @name Helper.reset
*
* @name Helper.htmlEntities
* @function * @function
* @param string str
* @return string escaped HTML
*/ */
me.htmlEntities = function(str) { me.reset = function()
// using textarea, since other tags may allow and execute scripts, even when detached from DOM {
let holder = document.createElement('textarea'); baseUri = null;
holder.textContent = str;
// as per OWASP recommendation, also encoding quotes and slash
return holder.innerHTML.replace(
/["'\/]/g,
function(s) {
return {
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;'
}[s];
});
}; };
return me; return me;
@ -363,10 +367,14 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* *
* Optionally pass a jQuery element as the first parameter, to automatically * Optionally pass a jQuery element as the first parameter, to automatically
* let the text of this element be replaced. In case the (asynchronously * let the text of this element be replaced. In case the (asynchronously
* loaded) language is not downloadet yet, this will make sure the string * loaded) language is not downloaded yet, this will make sure the string
* is replaced when it is actually loaded. * is replaced when it eventually gets loaded. Using this is both simpler
* So for easy translations passing the jQuery object to apply it to is * and more secure, as it avoids potential XSS when inserting text.
* more save, especially when they are loaded in the beginning. * The next 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 * @name I18n.translate
* @function * @function
@ -446,31 +454,39 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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 containsNoLinks = args[0].indexOf('<a') === -1; var containsLinks = args[0].indexOf('<a') !== -1;
for (let i = 0; i < args.length; ++i) {
// prevent double encoding, when we insert into a text node
if (containsLinks || $element === null) {
for (var i = 0; i < args.length; ++i) {
// parameters (i > 0) may never contain HTML as they may come from untrusted parties // parameters (i > 0) may never contain HTML as they may come from untrusted parties
if (i > 0 || containsNoLinks) { if ((containsLinks ? i > 1 : i > 0) || !containsLinks) {
args[i] = Helper.htmlEntities(args[i]); args[i] = Helper.htmlEntities(args[i]);
} }
} }
}
// format string // format string
var output = Helper.sprintf.apply(this, args); var output = Helper.sprintf.apply(this, args);
// if $element is given, apply text to element if (containsLinks) {
if ($element !== null) { // only allow tags/attributes we actually use in translations
if (containsNoLinks) { output = DOMPurify.sanitize(
// avoid HTML entity encoding if translation contains links output, {
$element.text(output); ALLOWED_TAGS: ['a', 'i', 'span'],
} else {
// only allow tags/attributes we actually use in our translations
$element.html(
DOMPurify.sanitize(output, {
ALLOWED_TAGS: ['a', 'br', 'i', 'span'],
ALLOWED_ATTR: ['href', 'id'] ALLOWED_ATTR: ['href', 'id']
}) }
); );
} }
// 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 '';
} }
return output; return output;
@ -1342,11 +1358,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, 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');
@ -1354,7 +1369,8 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
$pasteUrl.click(pasteLinkClick); $pasteUrl.click(pasteLinkClick);
// shorten button // shorten button
$('#deletelink').html('<a href="' + deleteUrl + '">' + I18n._('Delete data') + '</a>'); $('#deletelink').html('<a href="' + deleteUrl + '"></a>');
I18n._($('#deletelink a').first(), 'Delete data');
// show result // show result
$pasteSuccess.removeClass('hidden'); $pasteSuccess.removeClass('hidden');
@ -1810,10 +1826,13 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
} }
// escape HTML entities, link URLs, sanitize // escape HTML entities, link URLs, sanitize
var escapedLinkedText = Helper.urls2links( var escapedLinkedText = Helper.urls2links(text),
Helper.htmlEntities(text) sanitizedLinkedText = DOMPurify.sanitize(
), escapedLinkedText, {
sanitizedLinkedText = DOMPurify.sanitize(escapedLinkedText); ALLOWED_TAGS: ['a'],
ALLOWED_ATTR: ['href', 'rel']
}
);
$plainText.html(sanitizedLinkedText); $plainText.html(sanitizedLinkedText);
$prettyPrint.html(sanitizedLinkedText); $prettyPrint.html(sanitizedLinkedText);
@ -2625,7 +2644,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// set & parse text // set & parse text
$commentEntryData.html( $commentEntryData.html(
DOMPurify.sanitize( DOMPurify.sanitize(
Helper.urls2links(commentText) Helper.urls2links(commentText), {
ALLOWED_TAGS: ['a'],
ALLOWED_ATTR: ['href', 'rel']
}
) )
); );
@ -4418,9 +4440,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
Uploader.setUnencryptedData('deletetoken', deleteToken); Uploader.setUnencryptedData('deletetoken', deleteToken);
Uploader.setFailure(function () { Uploader.setFailure(function () {
Alert.showError( Alert.showError('Could not delete the paste, it was not stored in burn after reading mode.');
I18n._('Could not delete the paste, it was not stored in burn after reading mode.')
);
}); });
Uploader.run(); Uploader.run();
}; };
@ -4436,7 +4456,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// first load translations // first load translations
I18n.loadTranslations(); I18n.loadTranslations();
DOMPurify.setConfig({SAFE_FOR_JQUERY: true}); DOMPurify.setConfig({
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
SAFE_FOR_JQUERY: true
});
// initialize other modules/"classes" // initialize other modules/"classes"
Alert.init(); Alert.init();

View File

@ -8,13 +8,13 @@ describe('AttachmentViewer', function () {
jsc.property( jsc.property(
'displays & hides data as requested', 'displays & hides data as requested',
common.jscMimeTypes(), common.jscMimeTypes(),
jsc.nearray(common.jscBase64String()),
'string', 'string',
'string', 'string',
'string', 'string',
function (mimeType, base64, filename, prefix, postfix) { 'string',
var clean = jsdom(), function (mimeType, rawdata, filename, prefix, postfix) {
data = 'data:' + mimeType + ';base64,' + base64.join(''), let clean = jsdom(),
data = 'data:' + mimeType + ';base64,' + btoa(rawdata),
previewSupported = ( previewSupported = (
mimeType.substring(0, 6) === 'image/' || mimeType.substring(0, 6) === 'image/' ||
mimeType.substring(0, 6) === 'audio/' || mimeType.substring(0, 6) === 'audio/' ||
@ -43,7 +43,7 @@ describe('AttachmentViewer', function () {
} else { } else {
$.PrivateBin.AttachmentViewer.setAttachment(data); $.PrivateBin.AttachmentViewer.setAttachment(data);
} }
var attachment = $.PrivateBin.AttachmentViewer.getAttachment(); const attachment = $.PrivateBin.AttachmentViewer.getAttachment();
results.push( results.push(
$.PrivateBin.AttachmentViewer.hasAttachment() && $.PrivateBin.AttachmentViewer.hasAttachment() &&
$('#attachment').hasClass('hidden') && $('#attachment').hasClass('hidden') &&
@ -74,9 +74,14 @@ describe('AttachmentViewer', function () {
$.PrivateBin.AttachmentViewer.moveAttachmentTo(element, prefix + '%s' + postfix); $.PrivateBin.AttachmentViewer.moveAttachmentTo(element, prefix + '%s' + postfix);
// messageIDs with links get a relaxed treatment // messageIDs with links get a relaxed treatment
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 = $('<textarea>').text((prefix + filename + postfix)).text();
} else { } else {
result = $('<div>').html(prefix + $.PrivateBin.Helper.htmlEntities(filename) + postfix).html(); result = DOMPurify.sanitize(
prefix + $.PrivateBin.Helper.htmlEntities(filename) + postfix, {
ALLOWED_TAGS: ['a', 'i', 'span'],
ALLOWED_ATTR: ['href', 'id']
}
);
} }
if (filename.length) { if (filename.length) {
results.push( results.push(

View File

@ -93,11 +93,11 @@ describe('Helper', function () {
jsc.array(common.jscHashString()), jsc.array(common.jscHashString()),
'string', 'string',
function (prefix, schema, address, query, fragment, postfix) { function (prefix, schema, address, query, fragment, postfix) {
var query = query.join(''), query = query.join('');
fragment = fragment.join(''), fragment = fragment.join('');
url = schema + '://' + address.join('') + '/?' + query + '#' + fragment, prefix = $.PrivateBin.Helper.htmlEntities(prefix);
prefix = $.PrivateBin.Helper.htmlEntities(prefix),
postfix = ' ' + $.PrivateBin.Helper.htmlEntities(postfix); postfix = ' ' + $.PrivateBin.Helper.htmlEntities(postfix);
let url = schema + '://' + address.join('') + '/?' + query + '#' + fragment;
// special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x
if ( if (
@ -118,9 +118,9 @@ describe('Helper', function () {
jsc.array(common.jscQueryString()), jsc.array(common.jscQueryString()),
'string', 'string',
function (prefix, query, postfix) { function (prefix, query, postfix) {
var url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''), prefix = $.PrivateBin.Helper.htmlEntities(prefix);
prefix = $.PrivateBin.Helper.htmlEntities(prefix),
postfix = $.PrivateBin.Helper.htmlEntities(postfix); postfix = $.PrivateBin.Helper.htmlEntities(postfix);
let url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,'');
return prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + ' ' + postfix); return prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + ' ' + postfix);
} }
); );
@ -175,9 +175,9 @@ describe('Helper', function () {
'string', 'string',
'string', 'string',
function (prefix, uint, middle, string, postfix) { function (prefix, uint, middle, string, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%'); prefix = prefix.replace(/%(s|d)/g, '');
middle = middle.replace(/%(s|d)/g, '%%'); middle = middle.replace(/%(s|d)/g, '');
postfix = postfix.replace(/%(s|d)/g, '%%'); postfix = postfix.replace(/%(s|d)/g, '');
var params = [prefix + '%d' + middle + '%s' + postfix, uint, string], var params = [prefix + '%d' + middle + '%s' + postfix, uint, string],
result = prefix + uint + middle + string + postfix; result = prefix + uint + middle + string + postfix;
return result === $.PrivateBin.Helper.sprintf.apply(this, params); return result === $.PrivateBin.Helper.sprintf.apply(this, params);
@ -191,9 +191,9 @@ describe('Helper', function () {
'string', 'string',
'string', 'string',
function (prefix, uint, middle, string, postfix) { function (prefix, uint, middle, string, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%'); prefix = prefix.replace(/%(s|d)/g, '');
middle = middle.replace(/%(s|d)/g, '%%'); middle = middle.replace(/%(s|d)/g, '');
postfix = postfix.replace(/%(s|d)/g, '%%'); postfix = postfix.replace(/%(s|d)/g, '');
var params = [prefix + '%s' + middle + '%d' + postfix, string, uint], var params = [prefix + '%s' + middle + '%d' + postfix, string, uint],
result = prefix + string + middle + uint + postfix; result = prefix + string + middle + uint + postfix;
return result === $.PrivateBin.Helper.sprintf.apply(this, params); return result === $.PrivateBin.Helper.sprintf.apply(this, params);
@ -209,15 +209,14 @@ describe('Helper', function () {
jsc.property( jsc.property(
'returns the requested cookie', 'returns the requested cookie',
'nearray asciinestring', jsc.nearray(jsc.nearray(common.jscAlnumString())),
'nearray asciistring', jsc.nearray(jsc.nearray(common.jscAlnumString())),
function (labels, values) { function (labels, values) {
var selectedKey = '', selectedValue = '', var selectedKey = '', selectedValue = '',
cookieArray = []; cookieArray = [];
labels.forEach(function(item, i) { labels.forEach(function(item, i) {
// deliberatly using a non-ascii key for replacing invalid characters var key = item.join(''),
var key = item.replace(/[\s;,=]/g, Array(i+2).join('£')), value = (values[i] || values[0]).join('');
value = (values[i] || values[0]).replace(/[\s;,=]/g, '');
cookieArray.push(key + '=' + value); cookieArray.push(key + '=' + value);
if (Math.random() < 1 / i || selectedKey === key) if (Math.random() < 1 / i || selectedKey === key)
{ {
@ -227,6 +226,7 @@ describe('Helper', function () {
}); });
var clean = jsdom('', {cookie: cookieArray}), var clean = jsdom('', {cookie: cookieArray}),
result = $.PrivateBin.Helper.getCookie(selectedKey); result = $.PrivateBin.Helper.getCookie(selectedKey);
$.PrivateBin.Helper.reset();
clean(); clean();
return result === selectedValue; return result === selectedValue;
} }
@ -235,21 +235,19 @@ describe('Helper', function () {
describe('baseUri', function () { describe('baseUri', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
$.PrivateBin.Helper.reset();
});
jsc.property( jsc.property(
'returns the URL without query & fragment', 'returns the URL without query & fragment',
common.jscSchemas(), jsc.elements(['http', 'https']),
jsc.nearray(common.jscA2zString()), jsc.nearray(common.jscA2zString()),
jsc.array(common.jscA2zString()),
jsc.array(common.jscQueryString()), jsc.array(common.jscQueryString()),
'string', 'string',
function (schema, address, query, fragment) { function (schema, address, path, query, fragment) {
var expected = schema + '://' + address.join('') + '/', $.PrivateBin.Helper.reset();
var path = path.join('') + (path.length > 0 ? '/' : ''),
expected = schema + '://' + address.join('') + '/' + path,
clean = jsdom('', {url: expected + '?' + query.join('') + '#' + fragment}), clean = jsdom('', {url: expected + '?' + query.join('') + '#' + fragment}),
result = $.PrivateBin.Helper.baseUri(); result = $.PrivateBin.Helper.baseUri();
$.PrivateBin.Helper.reset();
clean(); clean();
return expected === result; return expected === result;
} }

View File

@ -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();
}); });
@ -32,14 +33,41 @@ describe('I18n', function () {
var fakeAlias = $.PrivateBin.I18n._(fake); var fakeAlias = $.PrivateBin.I18n._(fake);
$.PrivateBin.I18n.reset(); $.PrivateBin.I18n.reset();
if (messageId.indexOf('<a') === -1) {
messageId = $.PrivateBin.Helper.htmlEntities(messageId); messageId = $.PrivateBin.Helper.htmlEntities(messageId);
} else {
messageId = DOMPurify.sanitize(
messageId, {
ALLOWED_TAGS: ['a', 'i', 'span'],
ALLOWED_ATTR: ['href', 'id']
}
);
}
return messageId === result && messageId === alias && return messageId === result && messageId === alias &&
messageId === pluralResult && messageId === pluralAlias && messageId === pluralResult && messageId === pluralAlias &&
messageId === fakeResult && messageId === fakeAlias; messageId === fakeResult && messageId === fakeAlias;
} }
); );
jsc.property( jsc.property(
'replaces %s in strings with first given parameter', 'replaces %s in strings with first given parameter, encoding all, when no link is in the messageID',
'string',
'(small nearray) string',
'string',
function (prefix, params, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%').replace(/<a/g, '');
params[0] = params[0].replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%').replace(/<a/g, '');
const translation = $.PrivateBin.Helper.htmlEntities(prefix + params[0] + postfix);
params.unshift(prefix + '%s' + 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, encoding params only, when a link is part of the messageID',
'string', 'string',
'(small nearray) string', '(small nearray) string',
'string', 'string',
@ -47,15 +75,83 @@ describe('I18n', function () {
prefix = prefix.replace(/%(s|d)/g, '%%'); prefix = prefix.replace(/%(s|d)/g, '%%');
params[0] = params[0].replace(/%(s|d)/g, '%%'); 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] + postfix); const translation = DOMPurify.sanitize(
params.unshift(prefix + '%s' + postfix); prefix + '<a href="' + params[0] + '"></a>' + postfix, {
var result = $.PrivateBin.I18n.translate.apply(this, params); ALLOWED_TAGS: ['a', 'i', 'span'],
ALLOWED_ATTR: ['href', 'id']
}
);
params.unshift(prefix + '<a href="%s"></a>' + postfix);
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;
} }
); );
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, '%%').replace(/<a/g, '');
params[0] = params[0].replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%').replace(/<a/g, '');
const translation = $('<textarea>').text((prefix + params[0] + postfix)).text();
let args = Array.prototype.slice.call(params);
args.unshift(prefix + '%s' + postfix);
let clean = jsdom();
$('body').html('<div id="i18n"></div>');
args.unshift($('#i18n'));
$.PrivateBin.I18n.translate.apply(this, args);
const result = $('#i18n').text();
$.PrivateBin.I18n.reset();
clean();
clean = jsdom();
$('body').html('<div id="i18n"></div>');
args[0] = $('#i18n');
$.PrivateBin.I18n._.apply(this, args);
const alias = $('#i18n').text();
$.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, '%%').trim();
params[0] = params[0].replace(/%(s|d)/g, '%%').trim();
postfix = postfix.replace(/%(s|d)/g, '%%').trim();
const translation = DOMPurify.sanitize(
prefix + '<a href="' + params[0] + '"></a>' + postfix, {
ALLOWED_TAGS: ['a', 'i', 'span'],
ALLOWED_ATTR: ['href', 'id']
}
);
let args = Array.prototype.slice.call(params);
args.unshift(prefix + '<a href="%s"></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;
}
);
}); });
describe('getPluralForm', function () { describe('getPluralForm', function () {
@ -88,14 +184,17 @@ describe('I18n', function () {
'downloads and handles any supported language', 'downloads and handles any supported language',
common.jscSupportedLanguages(), common.jscSupportedLanguages(),
function(language) { function(language) {
var clean = jsdom('', {url: 'https://privatebin.net/', cookie: ['lang=' + language]}); // cleanup
var clean = jsdom('', {cookie: ['lang=en']});
$.PrivateBin.I18n.reset('en'); $.PrivateBin.I18n.reset('en');
$.PrivateBin.I18n.loadTranslations(); $.PrivateBin.I18n.loadTranslations();
clean();
// mock
clean = jsdom('', {cookie: ['lang=' + language]});
$.PrivateBin.I18n.reset(language, require('../../i18n/' + language + '.json')); $.PrivateBin.I18n.reset(language, require('../../i18n/' + language + '.json'));
var result = $.PrivateBin.I18n.translate('en'), var result = $.PrivateBin.I18n.translate('en'),
alias = $.PrivateBin.I18n._('en'); alias = $.PrivateBin.I18n._('en');
clean(); clean();
return language === result && language === alias; return language === result && language === alias;
} }

View File

@ -4,9 +4,6 @@ var common = require('../common');
describe('PasteStatus', function () { describe('PasteStatus', function () {
describe('createPasteNotification', function () { describe('createPasteNotification', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
cleanup();
});
jsc.property( jsc.property(
'creates a notification after a successfull paste upload', 'creates a notification after a successfull paste upload',
@ -24,7 +21,7 @@ describe('PasteStatus', function () {
var expected1 = schema1 + '://' + address1.join('') + '/?' + var expected1 = schema1 + '://' + address1.join('') + '/?' +
encodeURI(query1.join('').replace(/^&+|&+$/gm,'') + '#' + fragment1), encodeURI(query1.join('').replace(/^&+|&+$/gm,'') + '#' + fragment1),
expected2 = schema2 + '://' + address2.join('') + '/?' + expected2 = schema2 + '://' + address2.join('') + '/?' +
encodeURI(query2.join('')), encodeURI(query2.join('').replace(/^&+|&+$/gm,'')),
clean = jsdom(); clean = jsdom();
$('body').html('<div><div id="deletelink"></div><div id="pastelink"></div></div>'); $('body').html('<div><div id="deletelink"></div><div id="pastelink"></div></div>');
$.PrivateBin.PasteStatus.init(); $.PrivateBin.PasteStatus.init();

View File

@ -75,7 +75,7 @@ if ($MARKDOWN):
endif; 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/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-WMxduWsKcxVaSvyn4rTakNI+62QCAsrT9z67wR12yoLMCnLHV8JOVdisvjlpJNw5pWoMBmLcEpZkENq5/cVfDQ==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-0P2MKUF7ltPzb3r7M6Un13dcY+X+dTVB0N/5d0dubjlLyCJuMJZ0bNfWumCOjESPaY/d3T0lp1TPwar3qpAs8Q==" crossorigin="anonymous"></script>
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]--> <![endif]-->

View File

@ -53,7 +53,7 @@ if ($MARKDOWN):
endif; 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/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-WMxduWsKcxVaSvyn4rTakNI+62QCAsrT9z67wR12yoLMCnLHV8JOVdisvjlpJNw5pWoMBmLcEpZkENq5/cVfDQ==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-0P2MKUF7ltPzb3r7M6Un13dcY+X+dTVB0N/5d0dubjlLyCJuMJZ0bNfWumCOjESPaY/d3T0lp1TPwar3qpAs8Q==" crossorigin="anonymous"></script>
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]--> <![endif]-->