diff --git a/.gitattributes b/.gitattributes
index daef8b1c..3c395463 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -2,8 +2,6 @@ doc/ export-ignore
tst/ export-ignore
js/.istanbul.yml export-ignore
js/test.js export-ignore
-js/mocha-3.2.0.js export-ignore
-css/mocha-3.2.0.css export-ignore
.codeclimate.yml export-ignore
.csslintrc export-ignore
.dockerignore export-ignore
diff --git a/.styleci.yml b/.styleci.yml
index 0c7ba38a..8a62bd56 100644
--- a/.styleci.yml
+++ b/.styleci.yml
@@ -3,10 +3,11 @@ preset: recommended
risky: false
enabled:
- - no_empty_comment
- align_equals
- - long_array_syntax
- concat_with_spaces
+ - long_array_syntax
+ - no_empty_comment
+ - pre_increment
disabled:
- blank_line_after_opening_tag
@@ -23,6 +24,7 @@ disabled:
- phpdoc_separation
- phpdoc_single_line_var_spacing
- phpdoc_summary
+ - post_increment
- short_array_syntax
- single_line_after_imports
- unalign_equals
diff --git a/js/privatebin.js b/js/privatebin.js
index 0d34603a..20396b2d 100644
--- a/js/privatebin.js
+++ b/js/privatebin.js
@@ -124,7 +124,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
range = document.body.createTextRange();
range.moveToElementText(element);
range.select();
- } else if (window.getSelection){
+ } else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(element);
@@ -302,8 +302,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
* internationalization module
*
* @name I18n
- * @param {object} window
- * @param {object} document
* @class
*/
var I18n = (function () {
@@ -414,7 +412,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
var orgArguments = arguments;
$(document).on(languageLoadedEvent, function () {
// log to show that the previous error could be mitigated
- console.log('Fix missing translation of \'' + messageId + '\' with now loaded language ' + language);
+ console.warn('Fix missing translation of \'' + messageId + '\' with now loaded language ' + language);
// re-execute this function
me.translate.apply(this, orgArguments);
});
@@ -845,11 +843,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
* everything directly UI-related, which fits nowhere else
*
* @name UiHelper
- * @param {object} window
- * @param {object} document
* @class
*/
- var UiHelper = (function (window, document) {
+ var UiHelper = (function () {
var me = {};
/**
@@ -866,7 +862,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
{
var currentLocation = Helper.baseUri();
if (event.originalEvent.state === null && // no state object passed
- event.originalEvent.target.location.href === currentLocation && // target location is home page
+ event.target.location.href === currentLocation && // target location is home page
window.location.href === currentLocation // and we are not already on the home page
) {
// redirect to home page
@@ -958,6 +954,23 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
});
}
+ /**
+ * trigger a history (pop) state change
+ *
+ * used to test the UiHelper.historyChange private function
+ *
+ * @name UiHelper.mockHistoryChange
+ * @function
+ * @param {string} state (optional) state to mock
+ */
+ me.mockHistoryChange = function(state)
+ {
+ if (typeof state === 'undefined') {
+ state = null;
+ }
+ historyChange($.Event('popstate', {originalEvent: new PopStateEvent('popstate', {state: state}), target: window}));
+ }
+
/**
* initialize
*
@@ -973,7 +986,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
}
return me;
- })(window, document);
+ })();
/**
* Alert/error manager
@@ -989,12 +1002,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
$statusMessage,
$remainingTime;
- var currentIcon = [
- 'glyphicon-time', // loading icon
- 'glyphicon-info-sign', // status icon
- '', // resevered for warning, not used yet
- 'glyphicon-alert' // error icon
- ];
+ var currentIcon;
var alertType = [
'loading', // not in bootstrap, but using a good value here
@@ -1090,7 +1098,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.showStatus = function(message, icon, dismissable, autoclose)
{
- console.log('status shown: ', message);
+ console.info('status shown: ', message);
// @TODO: implement dismissable
// @TODO: implement autoclose
@@ -1133,7 +1141,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.showRemaining = function(message)
{
- console.log('remaining message shown: ', message);
+ console.info('remaining message shown: ', message);
handleNotification(1, $remainingTime, message);
}
@@ -1151,7 +1159,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.showLoading = function(message, percentage, icon)
{
if (typeof message !== 'undefined' && message !== null) {
- console.log('status changed: ', message);
+ console.info('status changed: ', message);
}
// default message text
@@ -1238,6 +1246,13 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
$loadingIndicator = $('#loadingindicator');
$statusMessage = $('#status');
$remainingTime = $('#remainingtime');
+
+ currentIcon = [
+ 'glyphicon-time', // loading icon
+ 'glyphicon-info-sign', // status icon
+ '', // reserved for warning, not used yet
+ 'glyphicon-alert' // error icon
+ ];
}
return me;
@@ -1247,10 +1262,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
* handles paste status/result
*
* @name PasteStatus
- * @param {object} window
* @class
*/
- var PasteStatus = (function (window) {
+ var PasteStatus = (function () {
var me = {};
var $pasteSuccess,
@@ -1351,7 +1365,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
];
Alert.showRemaining([expirationLabel, expiration[0]]);
- $remainingTime.removeClass('foryoureyesonly')
+ $remainingTime.removeClass('foryoureyesonly');
} else {
// never expires
return;
@@ -1383,7 +1397,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.init = function()
{
- $pasteSuccess = $('#pasteSuccess');
+ $pasteSuccess = $('#pastesuccess');
// $pasteUrl is saved in me.createPasteNotification() after creation
$remainingTime = $('#remainingtime');
$shortenButton = $('#shortenbutton');
@@ -1393,7 +1407,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
}
return me;
- })(window);
+ })();
/**
* password prompt
@@ -1454,7 +1468,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
throw 'password prompt canceled';
}
if (password.length === 0) {
- // recursive…
+ // recurse…
return me.requestPassword();
}
@@ -1462,7 +1476,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
}
/**
- * getthe cached password
+ * get the cached password
*
* If you do not get a password with this function
* (returns an empty string), use requestPassword.
@@ -1827,7 +1841,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*
* @name PasteViewer.setFormat
* @function
- * @param {string} newFormat the the new format
+ * @param {string} newFormat the new format
*/
me.setFormat = function(newFormat)
{
@@ -1836,7 +1850,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
return;
}
- // needs to update display too, if from or to Markdown is switched
+ // needs to update display too, if we switch from or to Markdown
if (format === 'markdown' || newFormat === 'markdown') {
isDisplayed = false;
}
@@ -1965,6 +1979,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
// get default option from template/HTML or fall back to set value
format = Model.getFormatDefault() || format;
+ text = '';
+ isDisplayed = false;
+ isChanged = true;
}
return me;
@@ -2641,7 +2658,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.showViewButtons = function()
{
if (viewButtonsDisplayed) {
- console.log('showViewButtons: view buttons are already displayed');
+ console.warn('showViewButtons: view buttons are already displayed');
return;
}
@@ -2661,7 +2678,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.hideViewButtons = function()
{
if (!viewButtonsDisplayed) {
- console.log('hideViewButtons: view buttons are already hidden');
+ console.warn('hideViewButtons: view buttons are already hidden');
return;
}
@@ -2693,7 +2710,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.showCreateButtons = function()
{
if (createButtonsDisplayed) {
- console.log('showCreateButtons: create buttons are already displayed');
+ console.warn('showCreateButtons: create buttons are already displayed');
return;
}
@@ -2718,7 +2735,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.hideCreateButtons = function()
{
if (!createButtonsDisplayed) {
- console.log('hideCreateButtons: create buttons are already hidden');
+ console.warn('hideCreateButtons: create buttons are already hidden');
return;
}
@@ -3970,7 +3987,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
Uploader.setUnencryptedData('deletetoken', deleteToken);
Uploader.setFailure(function () {
- Controller.showError(I18n._('Could not delete the paste, it was not stored in burn after reading mode.'));
+ Alert.showError(I18n._('Could not delete the paste, it was not stored in burn after reading mode.'));
})
Uploader.run();
}
diff --git a/js/test.js b/js/test.js
index 807d29dd..4b689718 100644
--- a/js/test.js
+++ b/js/test.js
@@ -22,11 +22,17 @@ global.sjcl = require('./sjcl-1.0.6');
global.Base64 = require('./base64-2.1.9').Base64;
global.RawDeflate = require('./rawdeflate-0.5').RawDeflate;
global.RawDeflate.inflate = require('./rawinflate-0.3').RawDeflate.inflate;
+require('./prettify');
+global.prettyPrint = window.PR.prettyPrint;
+global.prettyPrintOne = window.PR.prettyPrintOne;
+global.showdown = require('./showdown-1.6.1');
+global.DOMPurify = require('./purify.min');
+require('./bootstrap-3.3.7');
require('./privatebin');
// redirect console messages to log file
-console.warn = console.error = function (msg) {
- logFile.write(msg + '\n');
+console.info = console.warn = console.error = function () {
+ logFile.write(Array.prototype.slice.call(arguments).join('') + '\n');
}
describe('Helper', function () {
@@ -182,7 +188,7 @@ describe('Helper', function () {
jsc.array(jsc.elements(queryString)),
'string',
function (prefix, query, postfix) {
- var url = 'magnet:?' + query.join(''),
+ var url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''),
prefix = $.PrivateBin.Helper.htmlEntities(prefix),
postfix = $.PrivateBin.Helper.htmlEntities(postfix),
element = $('
' + prefix + url + ' ' + postfix + '
');
@@ -599,6 +605,82 @@ describe('Model', function () {
);
});
+ describe('getFormatDefault', function () {
+ before(function () {
+ $.PrivateBin.Model.reset();
+ cleanup();
+ });
+
+ jsc.property(
+ 'returns the contents of the element with id "pasteFormatter"',
+ 'array asciinestring',
+ 'string',
+ 'small nat',
+ function (keys, value, key) {
+ keys = keys.map($.PrivateBin.Helper.htmlEntities);
+ value = $.PrivateBin.Helper.htmlEntities(value);
+ var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'),
+ contents = '';
+ $('body').html(contents);
+ var result = $.PrivateBin.Helper.htmlEntities(
+ $.PrivateBin.Model.getFormatDefault()
+ );
+ $.PrivateBin.Model.reset();
+ return content === result;
+ }
+ );
+ });
+
+ describe('hasCipherData', function () {
+ before(function () {
+ $.PrivateBin.Model.reset();
+ cleanup();
+ });
+
+ jsc.property(
+ 'checks if the element with id "cipherdata" contains any data',
+ 'asciistring',
+ function (value) {
+ value = $.PrivateBin.Helper.htmlEntities(value).trim();
+ $('body').html('' + value + '
');
+ $.PrivateBin.Model.init();
+ var result = $.PrivateBin.Model.hasCipherData();
+ $.PrivateBin.Model.reset();
+ return (value.length > 0) === result;
+ }
+ );
+ });
+
+ describe('getCipherData', function () {
+ before(function () {
+ $.PrivateBin.Model.reset();
+ cleanup();
+ });
+
+ jsc.property(
+ 'returns the contents of the element with id "cipherdata"',
+ 'asciistring',
+ function (value) {
+ value = $.PrivateBin.Helper.htmlEntities(value).trim();
+ $('body').html('' + value + '
');
+ $.PrivateBin.Model.init();
+ var result = $.PrivateBin.Helper.htmlEntities(
+ $.PrivateBin.Model.getCipherData()
+ );
+ $.PrivateBin.Model.reset();
+ return value === result;
+ }
+ );
+ });
+
describe('getPasteId', function () {
this.timeout(30000);
before(function () {
@@ -710,4 +792,701 @@ describe('Model', function () {
}
);
});
+
+ describe('getTemplate', function () {
+ before(function () {
+ $.PrivateBin.Model.reset();
+ cleanup();
+ });
+
+ jsc.property(
+ 'returns the contents of the element with id "[name]template"',
+ jsc.nearray(jsc.elements(alnumString)),
+ jsc.nearray(jsc.elements(a2zString)),
+ jsc.nearray(jsc.elements(alnumString)),
+ function (id, element, value) {
+ id = id.join('');
+ element = element.join('');
+ value = value.join('').trim();
+
+ //
,
, and tags can't contain strings,
+ // table tags can't be alone, so test with a instead
+ if (['br', 'col', 'hr', 'img', 'tr', 'td', 'th', 'wbr'].indexOf(element) >= 0) {
+ element = 'p';
+ }
+
+ $('body').html(
+ '
<' + element + ' id="' + id +
+ 'template">' + value + '' + element + '>
'
+ );
+ $.PrivateBin.Model.init();
+ var template = '<' + element + ' id="' + id + '">' + value +
+ '' + element + '>',
+ result = $.PrivateBin.Model.getTemplate(id).wrap('').parent().html();
+ $.PrivateBin.Model.reset();
+ return template === result;
+ }
+ );
+ });
});
+
+describe('UiHelper', function () {
+ // TODO: As per https://github.com/tmpvar/jsdom/issues/1565 there is no navigation support in jsdom, yet.
+ // for now we use a mock function to trigger the event
+ describe('historyChange', function () {
+ this.timeout(30000);
+ before(function () {
+ $.PrivateBin.Helper.reset();
+ });
+
+ jsc.property(
+ 'redirects to home, when the state is null',
+ jsc.elements(schemas),
+ jsc.nearray(jsc.elements(a2zString)),
+ function (schema, address) {
+ var expected = schema + '://' + address.join('') + '/',
+ clean = jsdom('', {url: expected});
+
+ // make window.location.href writable
+ Object.defineProperty(window.location, 'href', {
+ writable: true,
+ value: window.location.href
+ });
+ $.PrivateBin.UiHelper.mockHistoryChange();
+ $.PrivateBin.Helper.reset();
+ var result = window.location.href;
+ clean();
+ return expected === result;
+ }
+ );
+
+ jsc.property(
+ 'does not redirect to home, when a new paste is created',
+ jsc.elements(schemas),
+ jsc.nearray(jsc.elements(a2zString)),
+ jsc.array(jsc.elements(queryString)),
+ jsc.nearray(jsc.elements(base64String)),
+ function (schema, address, query, fragment) {
+ var expected = schema + '://' + address.join('') + '/' + '?' +
+ query.join('') + '#' + fragment.join(''),
+ clean = jsdom('', {url: expected});
+
+ // make window.location.href writable
+ Object.defineProperty(window.location, 'href', {
+ writable: true,
+ value: window.location.href
+ });
+ $.PrivateBin.UiHelper.mockHistoryChange([
+ {type: 'newpaste'}, '', expected
+ ]);
+ $.PrivateBin.Helper.reset();
+ var result = window.location.href;
+ clean();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('reloadHome', function () {
+ this.timeout(30000);
+ before(function () {
+ $.PrivateBin.Helper.reset();
+ });
+
+ jsc.property(
+ 'redirects to home',
+ jsc.elements(schemas),
+ jsc.nearray(jsc.elements(a2zString)),
+ jsc.array(jsc.elements(queryString)),
+ jsc.nearray(jsc.elements(base64String)),
+ function (schema, address, query, fragment) {
+ var expected = schema + '://' + address.join('') + '/',
+ clean = jsdom('', {
+ url: expected + '?' + query.join('') + '#' + fragment.join('')
+ });
+
+ // make window.location.href writable
+ Object.defineProperty(window.location, 'href', {
+ writable: true,
+ value: window.location.href
+ });
+ $.PrivateBin.UiHelper.reloadHome();
+ $.PrivateBin.Helper.reset();
+ var result = window.location.href;
+ clean();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('isVisible', function () {
+ // TODO As per https://github.com/tmpvar/jsdom/issues/1048 there is no layout support in jsdom, yet.
+ // once it is supported or a workaround is found, uncomment the section below
+ /*
+ before(function () {
+ $.PrivateBin.Helper.reset();
+ });
+
+ jsc.property(
+ 'detect visible elements',
+ jsc.nearray(jsc.elements(alnumString)),
+ jsc.nearray(jsc.elements(a2zString)),
+ function (id, element) {
+ id = id.join('');
+ element = element.join('');
+ var clean = jsdom(
+ '<' + element + ' id="' + id + '">' + element + '>'
+ );
+ var result = $.PrivateBin.UiHelper.isVisible($('#' + id));
+ clean();
+ return result;
+ }
+ );
+ */
+ });
+
+ describe('scrollTo', function () {
+ // TODO Did not find a way to test that, see isVisible test above
+ });
+});
+
+describe('Alert', function () {
+ describe('showStatus', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows a status message',
+ jsc.array(jsc.elements(alnumString)),
+ jsc.array(jsc.elements(alnumString)),
+ function (icon, message) {
+ icon = icon.join('');
+ message = message.join('');
+ var expected = ' ' + message + '
';
+ $('body').html(
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.showStatus(message, icon);
+ var result = $('body').html();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('showError', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows an error message',
+ jsc.array(jsc.elements(alnumString)),
+ jsc.array(jsc.elements(alnumString)),
+ function (icon, message) {
+ icon = icon.join('');
+ message = message.join('');
+ var expected = ' ' + message + '
';
+ $('body').html(
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.showError(message, icon);
+ var result = $('body').html();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('showRemaining', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows remaining time',
+ jsc.array(jsc.elements(alnumString)),
+ jsc.array(jsc.elements(alnumString)),
+ 'integer',
+ function (message, string, number) {
+ message = message.join('');
+ string = string.join('');
+ var expected = '' +
+ ' ' + string + message + number + '
';
+ $('body').html(
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.showRemaining(['%s' + message + '%d', string, number]);
+ var result = $('body').html();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('showLoading', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows a loading message',
+ jsc.array(jsc.elements(alnumString)),
+ jsc.array(jsc.elements(alnumString)),
+ 'integer',
+ function (icon, message, number) {
+ icon = icon.join('');
+ message = message.join('');
+ var default_message = 'Loading…';
+ if (message.length == 0) {
+ message = default_message;
+ }
+ var expected = '';
+ $('body').html(
+ '- ' +
+ default_message + '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.showLoading(message, number, icon);
+ var result = $('body').html();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('hideLoading', function () {
+ before(function () {
+ cleanup();
+ });
+
+ it(
+ 'hides the loading message',
+ function() {
+ $('body').html(
+ ''
+ );
+ $('body').addClass('loading');
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.hideLoading();
+ return !$('body').hasClass('loading') &&
+ $('#loadingindicator').hasClass('hidden');
+ }
+ );
+ });
+
+ describe('hideMessages', function () {
+ before(function () {
+ cleanup();
+ });
+
+ it(
+ 'hides all messages',
+ function() {
+ $('body').html(
+ '
' +
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.hideMessages();
+ return $('#statusmessage').hasClass('hidden') &&
+ $('#errormessage').hasClass('hidden');
+ }
+ );
+ });
+
+ describe('setCustomHandler', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'calls a given handler function',
+ 'nat 3',
+ jsc.array(jsc.elements(alnumString)),
+ function (trigger, message) {
+ message = message.join('');
+ var handlerCalled = false,
+ default_message = 'Loading…',
+ functions = [
+ $.PrivateBin.Alert.showStatus,
+ $.PrivateBin.Alert.showError,
+ $.PrivateBin.Alert.showRemaining,
+ $.PrivateBin.Alert.showLoading
+ ];
+ if (message.length == 0) {
+ message = default_message;
+ }
+ $('body').html(
+ '- ' +
+ default_message + '
' +
+ '
' +
+ '
' +
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.setCustomHandler(function(id, $element) {
+ handlerCalled = true;
+ return jsc.random(0, 1) ? true : $element;
+ });
+ functions[trigger](message);
+ return handlerCalled;
+ }
+ );
+ });
+});
+
+describe('PasteStatus', function () {
+ describe('createPasteNotification', function () {
+ this.timeout(30000);
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'creates a notification after a successfull paste upload',
+ jsc.elements(schemas),
+ jsc.nearray(jsc.elements(a2zString)),
+ jsc.array(jsc.elements(queryString)),
+ 'string',
+ jsc.elements(schemas),
+ jsc.nearray(jsc.elements(a2zString)),
+ jsc.array(jsc.elements(queryString)),
+ function (
+ schema1, address1, query1, fragment1,
+ schema2, address2, query2
+ ) {
+ var expected1 = schema1 + '://' + address1.join('') + '/?' +
+ encodeURI(query1.join('').replace(/^&+|&+$/gm,'') + '#' + fragment1),
+ expected2 = schema2 + '://' + address2.join('') + '/?' +
+ encodeURI(query2.join('')),
+ clean = jsdom();
+ $('body').html('');
+ $.PrivateBin.PasteStatus.init();
+ $.PrivateBin.PasteStatus.createPasteNotification(expected1, expected2);
+ var result1 = $('#pasteurl')[0].href,
+ result2 = $('#deletelink a')[0].href;
+ clean();
+ return result1 == expected1 && result2 == expected2;
+ }
+ );
+ });
+
+ describe('showRemainingTime', function () {
+ this.timeout(30000);
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows burn after reading message or remaining time',
+ 'bool',
+ 'nat',
+ jsc.nearray(jsc.elements(a2zString)),
+ jsc.nearray(jsc.elements(a2zString)),
+ jsc.array(jsc.elements(queryString)),
+ 'string',
+ function (
+ burnafterreading, remaining_time,
+ schema, address, query, fragment
+ ) {
+ var clean = jsdom('', {
+ url: schema.join('') + '://' + address.join('') +
+ '/?' + queryString + '#' + fragment
+ });
+ $('body').html('');
+ $.PrivateBin.PasteStatus.init();
+ $.PrivateBin.PasteStatus.showRemainingTime({
+ 'burnafterreading': burnafterreading,
+ 'remaining_time': remaining_time,
+ 'expire_date': remaining_time ? ((new Date()).getTime() / 1000) + remaining_time : 0
+ });
+ if (burnafterreading) {
+ var result = $('#remainingtime').hasClass('foryoureyesonly') &&
+ !$('#remainingtime').hasClass('hidden');
+ } else if (remaining_time) {
+ var result =!$('#remainingtime').hasClass('foryoureyesonly') &&
+ !$('#remainingtime').hasClass('hidden');
+ } else {
+ var result = $('#remainingtime').hasClass('hidden') &&
+ !$('#remainingtime').hasClass('foryoureyesonly');
+ }
+ clean();
+ return result;
+ }
+ );
+ });
+
+ describe('hideMessages', function () {
+ before(function () {
+ cleanup();
+ });
+
+ it(
+ 'hides all messages',
+ function() {
+ $('body').html(
+ ''
+ );
+ $.PrivateBin.PasteStatus.init();
+ $.PrivateBin.PasteStatus.hideMessages();
+ return $('#remainingtime').hasClass('hidden') &&
+ $('#pastesuccess').hasClass('hidden');
+ }
+ );
+ });
+});
+
+describe('Prompt', function () {
+ // TODO: this does not test the prompt() fallback, since that isn't available
+ // in nodejs -> replace the prompt in the "page" template with a modal
+ describe('requestPassword & getPassword', function () {
+ this.timeout(30000);
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'returns the password fed into the dialog',
+ 'string',
+ function (password) {
+ password = password.replace(/\r+/g, '');
+ var clean = jsdom('', {url: 'ftp://example.com/#0'});
+ $('body').html(
+ '{}
'
+ );
+ $.PrivateBin.Model.init();
+ $.PrivateBin.Prompt.init();
+ $.PrivateBin.Prompt.requestPassword();
+ $('#passworddecrypt').val(password);
+ $('#passwordform').submit();
+ var result = $.PrivateBin.Prompt.getPassword();
+ clean();
+ return result == password;
+ }
+ );
+ });
+});
+
+describe('Editor', function () {
+ describe('show, hide, getText, setText & isPreview', function () {
+ this.timeout(30000);
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'returns text fed into the textarea, handles editor tabs',
+ 'string',
+ function (text) {
+ var clean = jsdom(),
+ results = [];
+ $('body').html(
+ '+++ no paste text +++
' +
+ ''
+ );
+ $.PrivateBin.Editor.init();
+ results.push(
+ $('#editorTabs').hasClass('hidden') &&
+ $('#message').hasClass('hidden')
+ );
+ $.PrivateBin.Editor.show();
+ results.push(
+ !$('#editorTabs').hasClass('hidden') &&
+ !$('#message').hasClass('hidden')
+ );
+ $.PrivateBin.Editor.hide();
+ results.push(
+ $('#editorTabs').hasClass('hidden') &&
+ $('#message').hasClass('hidden')
+ );
+ $.PrivateBin.Editor.show();
+ $.PrivateBin.Editor.focusInput();
+ results.push(
+ $.PrivateBin.Editor.getText().length == 0
+ );
+ $.PrivateBin.Editor.setText(text);
+ results.push(
+ $.PrivateBin.Editor.getText() == $('#message').val()
+ );
+ $.PrivateBin.Editor.setText();
+ results.push(
+ !$.PrivateBin.Editor.isPreview() &&
+ !$('#message').hasClass('hidden')
+ );
+ $('#messagepreview').click();
+ results.push(
+ $.PrivateBin.Editor.isPreview() &&
+ $('#message').hasClass('hidden')
+ );
+ $('#messageedit').click();
+ results.push(
+ !$.PrivateBin.Editor.isPreview() &&
+ !$('#message').hasClass('hidden')
+ );
+ clean();
+ return results.every(element => element);
+ }
+ );
+ });
+});
+
+describe('PasteViewer', function () {
+ describe('run, hide, getText, setText, getFormat, setFormat & isPrettyPrinted', function () {
+ this.timeout(30000);
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'displays text according to format',
+ jsc.elements(['plaintext', 'markdown', 'syntaxhighlighting']),
+ 'nestring',
+ function (format, text) {
+ var clean = jsdom(),
+ results = [];
+ $('body').html(
+ '+++ no paste text ' +
+ '+++
'
+ );
+ $.PrivateBin.PasteViewer.init();
+ $.PrivateBin.PasteViewer.setFormat(format);
+ $.PrivateBin.PasteViewer.setText('');
+ results.push(
+ $('#placeholder').hasClass('hidden') &&
+ $('#prettymessage').hasClass('hidden') &&
+ $('#plaintext').hasClass('hidden') &&
+ $.PrivateBin.PasteViewer.getFormat() == format &&
+ $.PrivateBin.PasteViewer.getText() == ''
+ );
+ $.PrivateBin.PasteViewer.run();
+ results.push(
+ !$('#placeholder').hasClass('hidden') &&
+ $('#prettymessage').hasClass('hidden') &&
+ $('#plaintext').hasClass('hidden')
+ );
+ $.PrivateBin.PasteViewer.hide();
+ results.push(
+ $('#placeholder').hasClass('hidden') &&
+ $('#prettymessage').hasClass('hidden') &&
+ $('#plaintext').hasClass('hidden')
+ );
+ $.PrivateBin.PasteViewer.setText(text);
+ $.PrivateBin.PasteViewer.run();
+ results.push(
+ $('#placeholder').hasClass('hidden') &&
+ !$.PrivateBin.PasteViewer.isPrettyPrinted() &&
+ $.PrivateBin.PasteViewer.getText() == text
+ );
+ if (format == 'markdown') {
+ results.push(
+ $('#prettymessage').hasClass('hidden') &&
+ !$('#plaintext').hasClass('hidden')
+ );
+ } else {
+ results.push(
+ !$('#prettymessage').hasClass('hidden') &&
+ $('#plaintext').hasClass('hidden')
+ );
+ }
+ clean();
+ return results.every(element => element);
+ }
+ );
+
+ jsc.property(
+ 'sanitizes XSS',
+ jsc.elements(['plaintext', 'markdown', 'syntaxhighlighting']),
+ 'string',
+ // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
+ jsc.elements([
+ '',
+ '\';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";',
+ 'alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//--',
+ '>">\'>',
+ '\'\';!--"=&{()}',
+ '',
+ '\'">>">|\\>@gmail.com\'-->">">\'">',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'xxs link',
+ 'xxs link',
+ '">',
+ ''
+ // the list goes on…
+ ]),
+ 'string',
+ function (format, prefix, xss, suffix) {
+ var clean = jsdom(),
+ text = prefix + xss + suffix;
+ $('body').html(
+ '+++ no paste text ' +
+ '+++
'
+ );
+ $.PrivateBin.PasteViewer.init();
+ $.PrivateBin.PasteViewer.setFormat(format);
+ $.PrivateBin.PasteViewer.setText(text);
+ $.PrivateBin.PasteViewer.run();
+ var result = $('body').html().indexOf(xss) !== -1;
+ clean();
+ return result;
+ }
+ );
+ });
+});
+
diff --git a/lib/Filter.php b/lib/Filter.php
index 4c0a22e9..302c84c8 100644
--- a/lib/Filter.php
+++ b/lib/Filter.php
@@ -64,7 +64,7 @@ class Filter
$i = 0;
while (($size / 1024) >= 1) {
$size = $size / 1024;
- $i++;
+ ++$i;
}
return number_format($size, ($i ? 2 : 0), '.', ' ') . ' ' . I18n::_($iec[$i]);
}
@@ -82,7 +82,7 @@ class Filter
public static function slowEquals($a, $b)
{
$diff = strlen($a) ^ strlen($b);
- for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
+ for ($i = 0; $i < strlen($a) && $i < strlen($b); ++$i) {
$diff |= ord($a[$i]) ^ ord($b[$i]);
}
return $diff === 0;
diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php
index def90dfb..208ab7d0 100644
--- a/tpl/bootstrap.php
+++ b/tpl/bootstrap.php
@@ -70,7 +70,7 @@ if ($MARKDOWN):
-
+
@@ -231,6 +231,17 @@ if ($isCpct):
?>
Opera,
Chrome…
-
+
diff --git a/tpl/page.php b/tpl/page.php
index 9c1db28d..2ecbb697 100644
--- a/tpl/page.php
+++ b/tpl/page.php
@@ -48,7 +48,7 @@ if ($MARKDOWN):
-
+
@@ -185,7 +185,7 @@ if (strlen($LANGUAGESELECTION)):
endif;
?>
-
+
_options;
+ $options = $this->_options;
Helper::createIniFile(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini.sample', $options);
$options['main']['opendiscussion'] = true;
diff --git a/tst/I18nTest.php b/tst/I18nTest.php
index c7ded0ee..de163490 100644
--- a/tst/I18nTest.php
+++ b/tst/I18nTest.php
@@ -150,8 +150,8 @@ class I18nTest extends PHPUnit_Framework_TestCase
$dir = dir(PATH . 'i18n');
while (false !== ($file = $dir->read())) {
if (strlen($file) === 7) {
- $language = substr($file, 0, 2);
- $languageMessageIds = array_keys(
+ $language = substr($file, 0, 2);
+ $languageMessageIds = array_keys(
json_decode(
file_get_contents(PATH . 'i18n' . DIRECTORY_SEPARATOR . $file),
true
diff --git a/tst/PrivateBinWithDbTest.php b/tst/PrivateBinWithDbTest.php
index a438d4c8..25e6082b 100644
--- a/tst/PrivateBinWithDbTest.php
+++ b/tst/PrivateBinWithDbTest.php
@@ -35,7 +35,7 @@ class PrivateBinWithDbTest extends PrivateBinTest
$options['model'] = array(
'class' => 'Database',
);
- $options['model_options'] = $this->_options;
+ $options['model_options'] = $this->_options;
Helper::createIniFile(CONF, $options);
}
}