mirror of
https://github.com/PrivateBin/PrivateBin.git
synced 2024-12-25 07:19:31 -05:00
backporting double encoding fixes from #560
This commit is contained in:
parent
2a5f622580
commit
6ccbad612d
@ -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)
|
||||||
|
@ -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'],
|
||||||
|
171
js/privatebin.js
171
js/privatebin.js
@ -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 = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": ''',
|
||||||
|
'/': '/',
|
||||||
|
'`': '`',
|
||||||
|
'=': '='
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
val = parseFloat(val);
|
||||||
{
|
if (isNaN(val)) {
|
||||||
case '%d':
|
val = 0;
|
||||||
val = parseFloat(val);
|
}
|
||||||
if (isNaN(val)) {
|
|
||||||
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 {
|
|
||||||
'"': '"',
|
|
||||||
"'": ''',
|
|
||||||
'/': '/'
|
|
||||||
}[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) {
|
|
||||||
// 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 || containsNoLinks) {
|
if (containsLinks || $element === null) {
|
||||||
args[i] = Helper.htmlEntities(args[i]);
|
for (var i = 0; i < args.length; ++i) {
|
||||||
|
// parameters (i > 0) may never contain HTML as they may come from untrusted parties
|
||||||
|
if ((containsLinks ? i > 1 : i > 0) || !containsLinks) {
|
||||||
|
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) {
|
||||||
|
// only allow tags/attributes we actually use in translations
|
||||||
|
output = DOMPurify.sanitize(
|
||||||
|
output, {
|
||||||
|
ALLOWED_TAGS: ['a', 'i', 'span'],
|
||||||
|
ALLOWED_ATTR: ['href', 'id']
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if $element is given, insert translation
|
||||||
if ($element !== null) {
|
if ($element !== null) {
|
||||||
if (containsNoLinks) {
|
if (containsLinks) {
|
||||||
// avoid HTML entity encoding if translation contains links
|
$element.html(output);
|
||||||
$element.text(output);
|
|
||||||
} else {
|
} else {
|
||||||
// only allow tags/attributes we actually use in our translations
|
// text node takes care of entity encoding
|
||||||
$element.html(
|
$element.text(output);
|
||||||
DOMPurify.sanitize(output, {
|
|
||||||
ALLOWED_TAGS: ['a', 'br', 'i', 'span'],
|
|
||||||
ALLOWED_ATTR: ['href', 'id']
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
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();
|
||||||
|
@ -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/' ||
|
||||||
@ -23,7 +23,7 @@ describe('AttachmentViewer', function () {
|
|||||||
),
|
),
|
||||||
results = [],
|
results = [],
|
||||||
result = '';
|
result = '';
|
||||||
prefix = prefix.replace(/%(s|d)/g, '%%');
|
prefix = prefix.replace(/%(s|d)/g, '%%');
|
||||||
postfix = postfix.replace(/%(s|d)/g, '%%');
|
postfix = postfix.replace(/%(s|d)/g, '%%');
|
||||||
$('body').html(
|
$('body').html(
|
||||||
'<div id="attachment" role="alert" class="hidden alert ' +
|
'<div id="attachment" role="alert" class="hidden alert ' +
|
||||||
@ -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(
|
||||||
|
@ -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. � or &#x
|
// special cases: When the query string and fragment imply the beginning of an HTML entity, eg. � 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;
|
||||||
}
|
}
|
||||||
|
117
js/test/I18n.js
117
js/test/I18n.js
@ -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();
|
||||||
|
|
||||||
messageId = $.PrivateBin.Helper.htmlEntities(messageId);
|
if (messageId.indexOf('<a') === -1) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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]-->
|
||||||
|
@ -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]-->
|
||||||
|
Loading…
Reference in New Issue
Block a user