Merge branch 'master' into empty-paste

empty-paste
El RIDO 6 years ago
commit b40e6305ca
No known key found for this signature in database
GPG Key ID: 0F5C940A6BD81F92

@ -1,5 +1,19 @@
---
engines:
version: "2"
checks:
file-lines:
config:
threshold: 2000
method-complexity:
config:
threshold: 550
method-count:
config:
threshold: 50
method-lines:
config:
threshold: 250
plugins:
csslint:
enabled: true
duplication:
@ -12,6 +26,8 @@ engines:
enabled: true
fixme:
enabled: true
nodesecurity:
enabled: true
phpmd:
enabled: true
checks:
@ -29,11 +45,20 @@ engines:
enabled: false
CleanCode/StaticAccess:
enabled: false
ratings:
paths:
- "css/privatebin.css"
- "css/bootstrap/privatebin.css"
- "js/privatebin.js"
- "lib/**.php"
- "index.php"
exclude_paths: []
sonar-php:
enabled: true
config:
tests_patterns:
- tst/**
exclude_patterns:
- "cfg/"
- "css/"
- "!css/privatebin.css"
- "!css/noscript.css"
- "!css/bootstrap/privatebin.css"
- "js/"
- "!js/privatebin.js"
- "!js/common.js"
- "!js/test/"
- "vendor/"

@ -1 +1,2 @@
**/*{.,-}min.js
js/*.js
!js/privatebin.js

@ -12,6 +12,14 @@ env:
globals:
sjcl: false
DOMPurify: false
after: true
before: true
cleanup: true
describe: false
it: false
jsc: false
jsdom: true
kjua: true
# http://eslint.org/docs/rules/
rules:
@ -67,7 +75,6 @@ rules:
no-case-declarations: 2
no-div-regex: 2
no-else-return: 0
no-empty-label: 2
no-empty-pattern: 2
no-eq-null: 2
no-eval: 2
@ -92,7 +99,7 @@ rules:
no-octal-escape: 2
no-octal: 2
no-proto: 2
no-redeclare: 2
no-redeclare: 0
no-return-assign: 2
no-script-url: 2
no-self-compare: 2
@ -188,7 +195,9 @@ rules:
operator-linebreak: 0
padded-blocks: 0
quote-props: 0
quotes: 0
quotes:
- error
- single
require-jsdoc: 0
semi-spacing: 0
semi: 0

@ -0,0 +1,46 @@
{
"bitwise": true,
"curly": true,
"eqeqeq": true,
"esversion": 5,
"forin": true,
"freeze": true,
"futurehostile": true,
"latedef": "nofunc",
"maxcomplexity": 25,
"maxdepth": 3,
"maxparams": 4,
"maxstatements": 100,
"noarg": true,
"nonbsp": true,
"nonew": true,
"quotmark": "single",
"singleGroups": true,
"strict": true,
"undef": true,
"unused": true,
"jquery": true,
"browser": true,
"predef": {
"after": true,
"before": true,
"cleanup": true,
"console": true,
"describe": false,
"document": true,
"fs": false,
"global": true,
"exports": true,
"it": false,
"jsc": false,
"jsdom": true,
"require": false,
"setTimeout": false,
"window": true
},
"globals": {
"sjcl": true,
"DOMPurify": true,
"kjua": true
}
}

@ -0,0 +1 @@
{}

@ -6,24 +6,30 @@ php:
- '5.6'
- '7.0'
- '7.1'
- '7.2'
# as this is a php project, node.js v4 (for JS unit testing) isn't installed
install:
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install 4
- if [ ! -d "$HOME/.nvm" ]; then mkdir -p $HOME/.nvm && curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | NVM_METHOD=script bash; fi
- source ~/.nvm/nvm.sh && nvm install 4
before_script:
- composer config -g github-oauth.github.com "$GITHUB_TOKEN"
- composer install -n
- npm install -g mocha
- cd js
- npm install jsverify jsdom@9 jsdom-global@2
- cd ..
- cd js && npm install jsverify jsdom@9 jsdom-global@2
script:
- cd tst && ../vendor/bin/phpunit
- cd ../js && mocha
- mocha
- cd ../tst && ../vendor/bin/phpunit
after_script:
- cd ..
- vendor/bin/codacycoverage clover tst/log/coverage-clover.xml
- vendor/bin/test-reporter --coverage-report tst/log/coverage-clover.xml
- ../vendor/bin/test-reporter --coverage-report log/coverage-clover.xml
- cd .. && vendor/bin/codacycoverage clover tst/log/coverage-clover.xml
cache:
directories:
- $HOME/.composer/cache/files
- $HOME/.composer/cache/vcs
- $HOME/.nvm
- $HOME/.npm
- js/node_modules

@ -3,15 +3,24 @@ FROM php:apache
RUN apt-get update && apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng12-dev \
libpng-dev \
wget \
zip \
unzip; \
unzip && \
# We install and enable php-gd
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/; \
docker-php-ext-install -j$(nproc) gd; \
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ &&\
docker-php-ext-install -j$(nproc) gd && \
# We enable Apache's mod_rewrite
a2enmod rewrite
COPY . .
# Copy app content
COPY . /var/www/html
# Copy start script
RUN mv /var/www/html/docker/entrypoint.sh / && \
rm -r /var/www/html/docker
VOLUME /var/www/html/data
CMD /entrypoint.sh

@ -56,6 +56,10 @@ languageselection = false
; the pastes encryption key
; urlshortener = "https://shortener.example.com/api?link="
; (optional) Let users create a QR code for sharing the paste URL with one click.
; It works both when a new paste is created and when you view a paste.
; qrcode = true
; (optional) IP based icons are a weak mechanism to detect if a comment was from
; a different user when the same username was used in a comment. It might be
; used to get the IP of a non anonymous comment poster if the server salt is
@ -69,7 +73,7 @@ languageselection = false
; scripts or run your site behind certain DDoS-protection services.
; Check the documentation at https://content-security-policy.com/
; Note: If you use a bootstrap theme, you can remove the allow-popups from the sandbox restrictions.
; cspheader = "default-src 'none'; manifest-src 'self'; connect-src *; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:; referrer no-referrer; sandbox allow-same-origin allow-scripts allow-forms allow-popups"
; cspheader = "default-src 'none'; manifest-src 'self'; connect-src *; form-action 'none'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:; referrer no-referrer; sandbox allow-same-origin allow-scripts allow-forms allow-popups"
; stay compatible with PrivateBin Alpha 0.19, less secure
; if enabled will use base64.js version 1.7 instead of 2.1.9 and sha1 instead of

@ -4,19 +4,13 @@
"type": "project",
"keywords": ["private", "secure", "end-to-end-encrypted", "e2e", "paste", "pastebin", "zero", "zero-knowledge", "encryption", "encrypted", "AES"],
"homepage": "https://github.com/PrivateBin",
"license":"zlib",
"license":"zlib-acknowledgement",
"support": {
"issues": "https://github.com/PrivateBin/PrivateBin/issues",
"wiki": "https://github.com/PrivateBin/PrivateBin/wiki",
"source": "https://github.com/PrivateBin/PrivateBin",
"docs": "https://zerobin.dssr.ch/documentation/"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/PrivateBin/PrivateBin"
}
],
"require": {
"php": "^5.4.0 || ^7.0",
"paragonie/random_compat": "2.0.4",

@ -76,6 +76,16 @@ body.loading {
#deletelink {
float: right;
margin-left: 5px;
}
#qrcodemodalClose {
float: right;
}
#qrcode-display {
width: 200px;
height: 200px;
margin: auto;
}
#pastelink {

@ -72,13 +72,13 @@ h3.title {
bottom: 8px;
}
#aboutbox {
color: #94a3b4;
#aboutbox {
color: #94a3b4;
padding: 4px 8px 4px 16px;
position: relative;
position: relative;
top: 10px;
border-left: 2px solid #94a3b4;
float: right;
float: right;
width: 60%;
}
@ -109,12 +109,12 @@ h3.title {
height: auto;
}
#status {
#status {
clear: both;
padding: 5px 10px;
}
#pasteresult {
#pasteresult {
background-color: #1F2833;
color: #fff;
padding: 4px 12px;
@ -132,7 +132,7 @@ h3.title {
#toolbar, #status { margin-bottom: 5px; }
#copyhint { color: #666; font-size: 0.85em; }
#copyhint { color: #666; font-size: 0.85em }
button, .button {
color: #fff;

@ -0,0 +1,15 @@
version: '3'
services:
privatebin:
build: .
ports:
- "3000:80"
volumes:
- data:/var/www/html/data
# Optionally mount a custom config file
#- /srv/docker/privatebin/conf.php:/var/www/html/cfg/conf.php
volumes:
data:

@ -0,0 +1,4 @@
#! /bin/sh
chown -R www-data /var/www/html/data
apache2-foreground

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

@ -0,0 +1,153 @@
'use strict';
// testing prerequisites
global.jsc = require('jsverify');
global.jsdom = require('jsdom-global');
global.cleanup = global.jsdom();
global.fs = require('fs');
// application libraries to test
global.$ = global.jQuery = require('./jquery-3.1.1');
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-1.0.3');
require('./bootstrap-3.3.7');
require('./privatebin');
// internal variables
var a2zString = ['a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z'],
alnumString = a2zString.concat(['0','1','2','3','4','5','6','7','8','9']),
queryString = alnumString.concat(['+','%','&','.','*','-','_']),
base64String = alnumString.concat(['+','/','=']).concat(
a2zString.map(function(c) {
return c.toUpperCase();
})
),
schemas = ['ftp','gopher','http','https','ws','wss'],
supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'],
mimeTypes = ['image/png', 'application/octet-stream'],
formats = ['plaintext', 'markdown', 'syntaxhighlighting'],
/**
* character to HTML entity lookup table
*
* @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60}
*/
entityMap = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;',
'`': '&#x60;',
'=': '&#x3D;'
},
logFile = fs.createWriteStream('test.log'),
mimeFile = fs.createReadStream('/etc/mime.types'),
mimeLine = '';
// redirect console messages to log file
console.info = console.warn = console.error = function () {
logFile.write(Array.prototype.slice.call(arguments).join('') + '\n');
};
// populate mime types from environment
mimeFile.on('data', function(data) {
mimeLine += data;
var index = mimeLine.indexOf('\n');
while (index > -1) {
var line = mimeLine.substring(0, index);
mimeLine = mimeLine.substring(index + 1);
parseMime(line);
index = mimeLine.indexOf('\n');
}
});
mimeFile.on('end', function() {
if (mimeLine.length > 0) {
parseMime(mimeLine);
}
});
function parseMime(line) {
// ignore comments
var index = line.indexOf('#');
if (index > -1) {
line = line.substring(0, index);
}
// ignore bits after tabs
index = line.indexOf('\t');
if (index > -1) {
line = line.substring(0, index);
}
if (line.length > 0) {
mimeTypes.push(line);
}
}
// common testing helper functions
/**
* convert all applicable characters to HTML entities
*
* @see {@link https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content}
* @name htmlEntities
* @function
* @param {string} str
* @return {string} escaped HTML
*/
exports.htmlEntities = function(str) {
return String(str).replace(
/[&<>"'`=\/]/g, function(s) {
return entityMap[s];
});
};
// provides random lowercase characters from a to z
exports.jscA2zString = function() {
return jsc.elements(a2zString);
};
// provides random lowercase alpha numeric characters (a to z and 0 to 9)
exports.jscAlnumString = function() {
return jsc.elements(alnumString);
};
// provides random characters allowed in GET queries
exports.jscQueryString = function() {
return jsc.elements(queryString);
};
// provides random characters allowed in base64 encoded strings
exports.jscBase64String = function() {
return jsc.elements(base64String);
};
// provides a random URL schema supported by the whatwg-url library
exports.jscSchemas = function() {
return jsc.elements(schemas);
};
// provides a random supported language string
exports.jscSupportedLanguages = function() {
return jsc.elements(supportedLanguages);
};
// provides a random mime type
exports.jscMimeTypes = function() {
return jsc.elements(mimeTypes);
};
// provides a random PrivateBin paste formatter
exports.jscFormats = function() {
return jsc.elements(formats);
};

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2
js/purify.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -0,0 +1,222 @@
'use strict';
var common = require('../common');
describe('Alert', function () {
describe('showStatus', function () {
before(function () {
cleanup();
});
jsc.property(
'shows a status message',
jsc.array(common.jscAlnumString()),
jsc.array(common.jscAlnumString()),
function (icon, message) {
icon = icon.join('');
message = message.join('');
var expected = '<div id="status" role="alert" ' +
'class="statusmessage alert alert-info"><span ' +
'class="glyphicon glyphicon-' + icon +
'" aria-hidden="true"></span> ' + message + '</div>';
$('body').html(
'<div id="status" role="alert" class="statusmessage ' +
'alert alert-info hidden"><span class="glyphicon ' +
'glyphicon-info-sign" aria-hidden="true"></span> </div>'
);
$.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(common.jscAlnumString()),
jsc.array(common.jscAlnumString()),
function (icon, message) {
icon = icon.join('');
message = message.join('');
var expected = '<div id="errormessage" role="alert" ' +
'class="statusmessage alert alert-danger"><span ' +
'class="glyphicon glyphicon-' + icon +
'" aria-hidden="true"></span> ' + message + '</div>';
$('body').html(
'<div id="errormessage" role="alert" class="statusmessage ' +
'alert alert-danger hidden"><span class="glyphicon ' +
'glyphicon-alert" aria-hidden="true"></span> </div>'
);
$.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(common.jscAlnumString()),
jsc.array(common.jscAlnumString()),
'integer',
function (message, string, number) {
message = message.join('');
string = string.join('');
var expected = '<div id="remainingtime" role="alert" ' +
'class="alert alert-info"><span ' +
'class="glyphicon glyphicon-fire" aria-hidden="true">' +
'</span> ' + string + message + number + '</div>';
$('body').html(
'<div id="remainingtime" role="alert" class="hidden ' +
'alert alert-info"><span class="glyphicon ' +
'glyphicon-fire" aria-hidden="true"></span> </div>'
);
$.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(common.jscAlnumString()),
jsc.array(common.jscAlnumString()),
function (message, icon) {
message = message.join('');
icon = icon.join('');
var defaultMessage = 'Loading…';
if (message.length === 0) {
message = defaultMessage;
}
var expected = '<ul class="nav navbar-nav"><li ' +
'id="loadingindicator" class="navbar-text"><span ' +
'class="glyphicon glyphicon-' + icon +
'" aria-hidden="true"></span> ' + message + '</li></ul>';
$('body').html(
'<ul class="nav navbar-nav"><li id="loadingindicator" ' +
'class="navbar-text hidden"><span class="glyphicon ' +
'glyphicon-time" aria-hidden="true"></span> ' +
defaultMessage + '</li></ul>'
);
$.PrivateBin.Alert.init();
$.PrivateBin.Alert.showLoading(message, icon);
var result = $('body').html();
return expected === result;
}
);
});
describe('hideLoading', function () {
before(function () {
cleanup();
});
it(
'hides the loading message',
function() {
$('body').html(
'<ul class="nav navbar-nav"><li id="loadingindicator" ' +
'class="navbar-text"><span class="glyphicon ' +
'glyphicon-time" aria-hidden="true"></span> ' +
'Loading…</li></ul>'
);
$('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(
'<div id="status" role="alert" class="statusmessage ' +
'alert alert-info"><span class="glyphicon ' +
'glyphicon-info-sign" aria-hidden="true"></span> </div>' +
'<div id="errormessage" role="alert" class="statusmessage ' +
'alert alert-danger"><span class="glyphicon ' +
'glyphicon-alert" aria-hidden="true"></span> </div>'
);
$.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(common.jscAlnumString()),
function (trigger, message) {
message = message.join('');
var handlerCalled = false,
defaultMessage = 'Loading…',
functions = [
$.PrivateBin.Alert.showStatus,
$.PrivateBin.Alert.showError,
$.PrivateBin.Alert.showRemaining,
$.PrivateBin.Alert.showLoading
];
if (message.length === 0) {
message = defaultMessage;
}
$('body').html(
'<ul class="nav navbar-nav"><li id="loadingindicator" ' +
'class="navbar-text hidden"><span class="glyphicon ' +
'glyphicon-time" aria-hidden="true"></span> ' +
defaultMessage + '</li></ul>' +
'<div id="remainingtime" role="alert" class="hidden ' +
'alert alert-info"><span class="glyphicon ' +
'glyphicon-fire" aria-hidden="true"></span> </div>' +
'<div id="status" role="alert" class="statusmessage ' +
'alert alert-info"><span class="glyphicon ' +
'glyphicon-info-sign" aria-hidden="true"></span> </div>' +
'<div id="errormessage" role="alert" class="statusmessage ' +
'alert alert-danger"><span class="glyphicon ' +
'glyphicon-alert" aria-hidden="true"></span> </div>'
);
$.PrivateBin.Alert.init();
$.PrivateBin.Alert.setCustomHandler(function(id, $element) {
handlerCalled = true;
return jsc.random(0, 1) ? true : $element;
});
functions[trigger](message);
return handlerCalled;
}
);
});
});

@ -0,0 +1,92 @@
'use strict';
var common = require('../common');
describe('AttachmentViewer', function () {
describe('setAttachment, showAttachment, removeAttachment, hideAttachment, hideAttachmentPreview, hasAttachment, getAttachment & moveAttachmentTo', function () {
this.timeout(30000);
before(function () {
cleanup();
});
jsc.property(
'displays & hides data as requested',
common.jscMimeTypes(),
jsc.nearray(common.jscBase64String()),
'string',
'string',
'string',
function (mimeType, base64, filename, prefix, postfix) {
var clean = jsdom(),
data = 'data:' + mimeType + ';base64,' + base64.join(''),
isImage = mimeType.substring(0, 6) === 'image/',
results = [];
prefix = prefix.replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%');
$('body').html(
'<div id="attachment" role="alert" class="hidden alert ' +
'alert-info"><span class="glyphicon glyphicon-download-' +
'alt" aria-hidden="true"></span> <a class="alert-link">' +
'Download attachment</a></div><div id="attachmentPrevie' +
'w" class="hidden"></div>'
);
$.PrivateBin.AttachmentViewer.init();
results.push(
!$.PrivateBin.AttachmentViewer.hasAttachment() &&
$('#attachment').hasClass('hidden') &&
$('#attachmentPreview').hasClass('hidden')
);
if (filename.length) {
$.PrivateBin.AttachmentViewer.setAttachment(data, filename);
} else {
$.PrivateBin.AttachmentViewer.setAttachment(data);
}
var attachement = $.PrivateBin.AttachmentViewer.getAttachment();
results.push(
$.PrivateBin.AttachmentViewer.hasAttachment() &&
$('#attachment').hasClass('hidden') &&
$('#attachmentPreview').hasClass('hidden') &&
attachement[0] === data &&
attachement[1] === filename
);
$.PrivateBin.AttachmentViewer.showAttachment();
results.push(
!$('#attachment').hasClass('hidden') &&
(isImage ? !$('#attachmentPreview').hasClass('hidden') : $('#attachmentPreview').hasClass('hidden'))
);
$.PrivateBin.AttachmentViewer.hideAttachment();
results.push(
$('#attachment').hasClass('hidden') &&
(isImage ? !$('#attachmentPreview').hasClass('hidden') : $('#attachmentPreview').hasClass('hidden'))
);
if (isImage) {
$.PrivateBin.AttachmentViewer.hideAttachmentPreview();
results.push($('#attachmentPreview').hasClass('hidden'));
}
$.PrivateBin.AttachmentViewer.showAttachment();
results.push(
!$('#attachment').hasClass('hidden') &&
(isImage ? !$('#attachmentPreview').hasClass('hidden') : $('#attachmentPreview').hasClass('hidden'))
);
var element = $('<div></div>');
$.PrivateBin.AttachmentViewer.moveAttachmentTo(element, prefix + '%s' + postfix);
if (filename.length) {
results.push(
element.children()[0].href === data &&
element.children()[0].getAttribute('download') === filename &&
element.children()[0].text === prefix + filename + postfix
);
} else {
results.push(element.children()[0].href === data);
}
$.PrivateBin.AttachmentViewer.removeAttachment();
results.push(
$('#attachment').hasClass('hidden') &&
$('#attachmentPreview').hasClass('hidden')
);
clean();
return results.every(element => element);
}
);
});
});

@ -0,0 +1,238 @@
'use strict';
require('../common');
describe('CryptTool', function () {
describe('cipher & decipher', function () {
this.timeout(30000);
it('can en- and decrypt any message', function () {
jsc.check(jsc.forall(
'string',
'string',
'string',
function (key, password, message) {
return message === $.PrivateBin.CryptTool.decipher(
key,
password,
$.PrivateBin.CryptTool.cipher(key, password, message)
);
}
),
// reducing amount of checks as running 100 takes about 5 minutes
{tests: 5, quiet: true});
});
it('can decrypt a particular message (#260)', function () {
var message = `
1 subgoal
inv : Assert
expr : Expr
sBody : Instr
deduction : (|- [|inv /\ assertOfExpr expr|] sBody [|inv|])%assert
IHdeduction : (|= [|inv /\ assertOfExpr expr |] sBody [|inv|])%assert
mem : Mem
preInMem : inv mem
m : Mem
n : nat
interpRel : interp (nth_iterate sBody n) (MemElem mem) = CpoElem Mem m
lastIter : interp (nth_iterate sBody n) (MemElem mem) |=e expr_neg expr
notLastIter : forall p : nat,
p < n -> interp (nth_iterate sBody p) (MemElem mem) |=e expr
isWhile : interp (while expr sBody) (MemElem mem) =
interp (nth_iterate sBody n) (MemElem mem)
======================== ( 1 / 1 )
conseq_or_bottom inv (interp (nth_iterate sBody n) (MemElem mem))
`;
if (message !== $.PrivateBin.CryptTool.decipher(
'y+4So8y7GYliFc+LcyFhXYSyMW/v1CdGqnSND+MPtNw=',
'', // no password
'{"iv":"LwfPcuKXYo2f6gjrtVRbcg==","v":1,"iter":1000,"ks":256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","salt":"gw7Pe+7WGGI=","ct":"Mk6jTCNQjJUTnOQtFGtNqxTtzwnbDEWNmPd0teSJn5PW2IczTcE5aSvevONSOEpP476aNUA0JfPuK8v8zLqK2rmk8ESmm9wqkEdWWdMY2kvzU8mxo1yp6DBs5aXmy9y585GvB4kaCyh6nH2YFDQczUDZ4AQlGC8T11YMPO4sHM\/SOewS8vCnZ3tTiSuLjV0LC6k+xZ3jTg\/yH+V2cH5vfvj2eQMhUaMOyzjSQF34Ab7+pApuVVHXZ\/0lo86btt7iWo7yOHV59Te9AjpxzWgBI2gzTBBsk\/4WeYYVK3l2lTLy08GS9D8D1AbSsTrp5tSH84StAr+kMnEIsiR6FIbJ\/AP+6v9MQ2ryyUXGOj5HQLUZDsle3QQvtB7F6mqPDUvKtx\/Pxx0OHgNW5ttA581Hn1XWreUF6KzoWfcA6XdDEH4eylNiFrAFX+H1Mxfnxwz3aVOiRlP4+zrtmNcR\/XV87nzuDz2fqScrjFsPQ+FV\/784qe\/ZYs3Kp0Q+kVAnXm31vVwc6GU0b\/1bTZfknts0fKoIjCcH1gLivQfrj87QlTUa4l6TVzqgLLapB4EgW4CxcZ4PBhyexSuw+ZmUw\/kqyXZWP3R\/IzElI5Lt9GyLIzpyI9EvWLpVTn8iN8XOFZuEhHfTGb7Wdl+\/\/la4gsvhEvAx+ADqjjPgX0h4lFbyMZXHU3yN0QJr1jiZhIdbWL0QEyUkuWk6PK6E0ziHu558+8+WEjeYkElPosZwKtCHE4Ogfk6taZJhcV3rQu8U\/icqd1gAzbBFXp0="}'
)) {
throw Error('a particular message (#260) could not be deciphered');
}
});
// The below static unit tests are included to ensure deciphering of "classic"
// SJCL based pastes still works
it(
'supports PrivateBin v1 ciphertext (SJCL & Base64 2.1.9)',
function () {
// Of course you can easily decipher the following texts, if you like.
// Bonus points for finding their sources and hidden meanings.
var paste1 = $.PrivateBin.CryptTool.decipher(
'6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=',
// -- "That's amazing. I've got the same combination on my luggage."
Array.apply(0, Array(6)).map(function(_,b) { return b + 1; }).join(''),
'{"iv":"4HNFIl7eYbCh6HuShctTIA==","v":1,"iter":10000,"ks"' +
':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' +
'lt":"u0lQvePq6L0=","ct":"fGPUVrDyaVr1ZDGb+kqQ3CPEW8x4YKG' +
'fzHDmA0Vjkh250aWNe7Cnigkps9aaFVMX9AaerrTp3yZbojJtNqVGMfL' +
'dUTu+53xmZHqRKxCCqSfDNSNoW4Oxk5OVgAtRyuG4bXHDsWTXDNz2xce' +
'qzVFqhkwTwlUchrV7uuFK/XUKTNjPFM744moivIcBbfM2FOeKlIFs8RY' +
'PYuvqQhp2rMLlNGwwKh//4kykQsHMQDeSDuJl8stMQzgWR/btUBZuwNZ' +
'EydkMH6IPpTdf5WTSrZ+wC2OK0GutCm4UaEe6txzaTMfu+WRVu4PN6q+' +
'N+2zljWJ1XdpVcN/i0Sv4QVMym0Xa6y0eccEhj/69o47PmExmMMeEwEx' +
'ImPalMNT9JUSiZdOZJ/GdzwrwoIuq1mdQR6vSH+XJ/8jXJQ7bjjJVJYX' +
'TcT0Di5jixArI2Kpp1GGlGVFbLgPugwU1wczg+byqeDOAECXRRnQcoge' +
'aJtVcRwXwfy4j3ORFcblYMilxyHqKBewcYPRVBGtBs50cVjSIkAfR84r' +
'nc1nfvnxK/Gmm+4VBNHI6ODWNpRolVMCzXjbKYnV3Are5AgSpsTqaGl4' +
'1VJGpcco6cAwi4K0Bys1seKR+bLSdUgqRrkEqSRSdu3/VTu9HhEk8an0' +
'rjTE4CBB5/LMn16p0TGLoOb32odKFIEtpanVvLjeyiVMvSxcgYLNnTi/' +
'5FiaAC4pJxRD+AZHedU1FICUeEXxIcac/4E5qjkHjX9SpQtLl80QLIVn' +
'jNliZm7QLB/nKu7W8Jb0+/CiTdV3Q9LhxlH4ciprnX+W0B00BKYFHnL9' +
'jRVzKdXhf1EHydbXMAfpCjHAXIVCkFakJinQBDIIw/SC6Yig0u0ddEID' +
'2B7LYAP1iE4RZwzTrxCB+ke2jQr8c20Jj6u6ShFOPC9DCw9XupZ4HAal' +
'VG00kSgjus+b8zrVji3/LKEhb4EBzp1ctBJCFTeXwej8ZETLoXTylev5' +
'dlwZSYAbuBPPcbFR/xAIPx3uDabd1E1gTqUc68ICIGhd197Mb2eRWiSv' +
'Hr5SPsASerMxId6XA6+iQlRiI+NDR+TGVNmCnfxSlyPFMOHGTmslXOGI' +
'qGfBR8l4ft8YVZ70lCwmwTuViGc75ULSf9mM57/LmRzQFMYQtvI8IFK9' +
'JaQEMY5xz0HLtR4iyQUUdwR9e0ytBNdWF2a2WPDEnJuY/QJo4GzTlgv4' +
'QUxMXI5htsn2rf0HxCFu7Po8DNYLxTS+67hYjDIYWYaEIc8LXWMLyDm9' +
'C5fARPJ4F2BIWgzgzkNj+dVjusft2XnziamWdbS5u3kuRlVuz5LQj+R5' +
'imnqQAincdZTkTT1nYx+DatlOLllCYIHffpI="}'
),
paste2 = $.PrivateBin.CryptTool.decipher(
's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=',
'', // no password
'{"iv":"WA42mdxIVXUwBqZu7JYNiw==","v":1,"iter":10000,"ks"' +
':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' +
'lt":"jN6CjbQMJCM=","ct":"kYYMo5DFG1+w0UHiYXT5pdV0IUuXxzO' +
'lslkW/c3DRCbGFROCVkAskHce7HoRczee1N9c5MhHjVMJUIZE02qIS8U' +
'yHdJ/GqcPVidTUcj9rnDNWsTXkjVv8jCwHS/cwmAjDTWpwp5ThECN+ov' +
'/wNp/NdtTj8Qj7f/T3rfZIOCWfwLH9s4Des35UNcUidfPTNQ1l0Gm0X+' +
'r98CCUSYZjQxkZc6hRZBLPQ8EaNVooUwd5eP4GiYlmSDNA0wOSA+5isP' +
'YxomVCt+kFf58VBlNhpfNi7BLYAUTPpXT4SfH5drR9+C7NTeZ+tTCYjb' +
'U94PzYItOpu8vgnB1/a6BAM5h3m9w+giUb0df4hgTWeZnZxLjo5BN8WV' +
'+kdTXMj3/Vv0gw0DQrDcCuX/cBAjpy3lQGwlAN1vXoOIyZJUjMpQRrOL' +
'dKvLB+zcmVNtGDbgnfP2IYBzk9NtodpUa27ne0T0ZpwOPlVwevsIVZO2' +
'24WLa+iQmmHOWDFFpVDlS0t0fLfOk7Hcb2xFsTxiCIiyKMho/IME1Du3' +
'X4e6BVa3hobSSZv0rRtNgY1KcyYPrUPW2fxZ+oik3y9SgGvb7XpjVIta' +
'8DWlDWRfZ9kzoweWEYqz9IA8Xd373RefpyuWI25zlHoX3nwljzsZU6dC' +
'//h/Dt2DNr+IAvKO3+u23cWoB9kgcZJ2FJuqjLvVfCF+OWcig7zs2pTY' +
'JW6Rg6lqbBCxiUUlae6xJrjfv0pzD2VYCLY7v1bVTagppwKzNI3WaluC' +
'OrdDYUCxUSe56yd1oAoLPRVbYvomRboUO6cjQhEknERyvt45og2kORJO' +
'EJayHW+jZgR0Y0jM3Nk17ubpij2gHxNx9kiLDOiCGSV5mn9mV7qd3HHc' +
'OMSykiBgbyzjobi96LT2dIGLeDXTIdPOog8wyobO4jWq0GGs0vBB8oSY' +
'XhHvixZLcSjX2KQuHmEoWzmJcr3DavdoXZmAurGWLKjzEdJc5dSD/eNr' +
'99gjHX7wphJ6umKMM+fn6PcbYJkhDh2GlJL5COXjXfm/5aj/vuyaRRWZ' +
'MZtmnYpGAtAPg7AUG"}'
);
if (!paste1.includes('securely packed in iron') || !paste2.includes('Sol is right')) {
throw Error('v1 (SJCL based) pastes could not be deciphered');
}
}
);
it(
'supports ZeroBin ciphertext (SJCL & Base64 1.7)',
function () {
var newBase64 = global.Base64;
global.Base64 = require('../base64-1.7').Base64;
jsdom();
delete require.cache[require.resolve('../privatebin')];
require('../privatebin');
// Of course you can easily decipher the following texts, if you like.
// Bonus points for finding their sources and hidden meanings.
var paste1 = $.PrivateBin.CryptTool.decipher(
'6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=',
// -- "That's amazing. I've got the same combination on my luggage."
Array.apply(0, Array(6)).map(function(_,b) { return b + 1; }).join(''),
'{"iv":"aTnR2qBL1CAmLX8FdWe3VA==","v":1,"iter":10000,"ks"' +
':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' +
'lt":"u0lQvePq6L0=","ct":"A3nBTvICZtYy6xqbIJE0c8Veored5lM' +
'JUGgGUm4581wjrPFlU0Q0tUZSf+RUUoZj2jqDa4kiyyZ5YNMe30hNMV0' +
'oVSalNhRgD9svVMnPuF162IbyhVCwr7ULjT981CHxVlGNqGqmIU6L/Xi' +
'xgdArxAA8x1GCrfAkBWWGeq8Qw5vJPG/RCHpwR4Wy3azrluqeyERBzma' +
'OQjO/kM35TiI6IrLYFyYyL7upYlxAaxS0XBMZvN8QU8Lnerwvh5JVC6O' +
'kkKrhogajTJIKozCF79yI78c50LUh7tTuI3Yoh7+fXxhoODvQdYFmoiU' +
'lrutN7Y5ZMRdITvVu8fTYtX9c7Fiufmcq5icEimiHp2g1bvfpOaGOsFT' +
'+XNFgC9215jcp5mpBdN852xs7bUtw+nDrf+LsDEX6iRpRZ+PYgLDN5xQ' +
'T1ByEtYbeP+tO38pnx72oZdIB3cj8UkOxnxdNiZM5YB5egn4jUj1fHot' +
'1I69WoTiUJipZ5PIATv7ScymRB+AYzjxjurQ9lVfX9QtAbEH2dhdmoUo' +
'3IDRSXpWNCe9RC1aUIyWfZO7oI7FEohNscHNTLEcT+wFnFUPByLlXmjN' +
'Z7FKeNpvUm3jTY4t4sbZH8o2dUl624PAw1INcJ6FKqWGWwoFT2j1MYC+' +
'YV/LkLTdjuWfayvwLMh27G/FfKCRbW36vqinegqpPDylsx9+3oFkEw3y' +
'5Z8+44oN91rE/4Md7JhPJeRVlFC9TNCj4dA+EVhbbQqscvSnIH2uHkMw' +
'7mNNo7xba/YT9KoPDaniqnYqb+q2pX1WNWE7dLS2wfroMAS3kh8P22DA' +
'V37AeiNoD2PcI6ZcHbRdPa+XRrRcJhSPPW7UQ0z4OvBfjdu/w390QxAx' +
'SxvZewoh49fKKB6hTsRnZb4tpHkjlww=="}'
),
paste2 = $.PrivateBin.CryptTool.decipher(
's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=',
'', // no password
'{"iv":"Z7lAZQbkrqGMvruxoSm6Pw==","v":1,"iter":10000,"ks"' +
':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' +
'lt":"jN6CjbQMJCM=","ct":"PuOPWB3i2FPcreSrLYeQf84LdE8RHjs' +
'c+MGtiOr4b7doNyWKYtkNorbRadxaPnEee2/Utrp1MIIfY5juJSy8RGw' +
'EPX5ciWcYe6EzsXWznsnvhmpKNj9B7eIIrfSbxfy8E2e/g7xav1nive+' +
'ljToka3WT1DZ8ILQd/NbnJeHWaoSEOfvz8+d8QJPb1tNZvs7zEY95Dum' +
'QwbyOsIMKAvcZHJ9OJNpujXzdMyt6DpcFcqlldWBZ/8q5rAUTw0HNx/r' +
'CgbhAxRYfNoTLIcMM4L0cXbPSgCjwf5FuO3EdE13mgEDhcClW79m0Qvc' +
'nIh8xgzYoxLbp0+AwvC/MbZM8savN/0ieWr2EKkZ04ggiOIEyvfCUuNp' +
'rQBYO+y8kKduNEN6by0Yf4LRCPfmwN+GezDLuzTnZIMhPbGqUAdgV6Ex' +
'qK2ULEEIrQEMoOuQIxfoMhqLlzG79vXGt2O+BY+4IiYfvmuRLks4UXfy' +
'HqxPXTJg48IYbGs0j4TtJPUgp3523EyYLwEGyVTAuWhYAmVIwd/hoV7d' +
'7tmfcF73w9dufDFI3LNca2KxzBnWNPYvIZKBwWbq8ncxkb191dP6mjEi' +
'7NnhqVk5A6vIBbu4AC5PZf76l6yep4xsoy/QtdDxCMocCXeAML9MQ9uP' +
'QbuspOKrBvMfN5igA1kBqasnxI472KBNXsdZnaDddSVUuvhTcETM="}'
);
global.Base64 = newBase64;
jsdom();
delete require.cache[require.resolve('../privatebin')];
require('../privatebin');
if (!paste1.includes('securely packed in iron') || !paste2.includes('Sol is right')) {
throw Error('v1 (SJCL based) pastes could not be deciphered');
}
}
);
});
describe('isEntropyReady & addEntropySeedListener', function () {
it(
'lets us know that enough entropy is collected or make us wait for it',
function(done) {
if ($.PrivateBin.CryptTool.isEntropyReady()) {
done();
} else {
$.PrivateBin.CryptTool.addEntropySeedListener(function() {
done();
});
}
}
);
});
describe('getSymmetricKey', function () {
var keys = [];
// the parameter is used to ensure the test is run more then one time
jsc.property(
'returns random, non-empty keys',
function() {
var key = $.PrivateBin.CryptTool.getSymmetricKey(),
result = (key !== '' && keys.indexOf(key) === -1);
keys.push(key);
return result;
}
);
});
describe('Base64.js vs SJCL.js vs abab.js', function () {
jsc.property(
'these all return the same base64 string',
'string',
function(string) {
var base64 = Base64.toBase64(string),
sjcl = global.sjcl.codec.base64.fromBits(global.sjcl.codec.utf8String.toBits(string)),
abab = window.btoa(Base64.utob(string));
return base64 === sjcl && sjcl === abab;
}
);
});
});

@ -0,0 +1,116 @@
'use strict';
var common = require('../common');
describe('DiscussionViewer', function () {
describe('handleNotification, prepareNewDiscussion, addComment, finishDiscussion, getReplyMessage, getReplyNickname, getReplyCommentId & highlightComment', function () {
this.timeout(30000);
before(function () {
cleanup();
});
jsc.property(
'displays & hides comments as requested',
jsc.array(
jsc.record({
idArray: jsc.nearray(common.jscAlnumString()),
parentidArray: jsc.nearray(common.jscAlnumString()),
data: jsc.string,
meta: jsc.record({
nickname: jsc.string,
postdate: jsc.nat,
vizhash: jsc.string
})
})
),
'nat',
'bool',
'string',
'string',
jsc.elements(['loading', 'danger', 'other']),
'nestring',
function (comments, commentKey, fadeOut, nickname, message, alertType, alert) {
var clean = jsdom(),
results = [];
$('body').html(
'<div id="discussion"><h4>Discussion</h4>' +
'<div id="commentcontainer"></div></div><div id="templates">' +
'<article id="commenttemplate" class="comment">' +
'<div class="commentmeta"><span class="nickname">name</span>' +
'<span class="commentdate">0000-00-00</span></div>' +
'<div class="commentdata">c</div>' +
'<button class="btn btn-default btn-sm">Reply</button>' +
'</article><p id="commenttailtemplate" class="comment">' +
'<button class="btn btn-default btn-sm">Add comment</button>' +
'</p><div id="replytemplate" class="reply hidden">' +
'<input type="text" id="nickname" class="form-control" ' +
'title="Optional nickname…" placeholder="Optional ' +
'nickname…" /><textarea id="replymessage" ' +
'class="replymessage form-control" cols="80" rows="7">' +
'</textarea><br /><div id="replystatus" role="alert" ' +
'class="statusmessage hidden alert"><span class="glyphicon" ' +
'aria-hidden="true"></span> </div><button id="replybutton" ' +
'class="btn btn-default btn-sm">Post comment</button></div></div>'
);
$.PrivateBin.Model.init();
$.PrivateBin.DiscussionViewer.init();
results.push(
!$('#discussion').hasClass('hidden')
);
$.PrivateBin.DiscussionViewer.prepareNewDiscussion();
results.push(
$('#discussion').hasClass('hidden')
);
comments.forEach(function (comment) {
comment.id = comment.idArray.join('');
comment.parentid = comment.parentidArray.join('');
$.PrivateBin.DiscussionViewer.addComment(comment, comment.data, comment.meta.nickname);
});
results.push(
$('#discussion').hasClass('hidden')
);
$.PrivateBin.DiscussionViewer.finishDiscussion();
results.push(
!$('#discussion').hasClass('hidden') &&
comments.length + 1 >= $('#commentcontainer').children().length
);
if (comments.length > 0) {
if (commentKey >= comments.length) {
commentKey = commentKey % comments.length;
}
$.PrivateBin.DiscussionViewer.highlightComment(comments[commentKey].id, fadeOut);
results.push(
$('#comment_' + comments[commentKey].id).hasClass('highlight')
);
}
$('#commentcontainer').find('button')[0].click();
results.push(
!$('#reply').hasClass('hidden')
);
$('#reply #nickname').val(nickname);
$('#reply #replymessage').val(message);
$.PrivateBin.DiscussionViewer.getReplyCommentId();
results.push(
$.PrivateBin.DiscussionViewer.getReplyNickname() === $('#reply #nickname').val() &&
$.PrivateBin.DiscussionViewer.getReplyMessage() === $('#reply #replymessage').val()
);
var notificationResult = $.PrivateBin.DiscussionViewer.handleNotification(alertType === 'other' ? alert : alertType);
if (alertType === 'loading') {
results.push(notificationResult === false);
} else {
results.push(
alertType === 'danger' ? (
notificationResult.hasClass('alert-danger') &&
!notificationResult.hasClass('alert-info')
) : (
!notificationResult.hasClass('alert-danger') &&
notificationResult.hasClass('alert-info')
)
);
}
clean();
return results.every(element => element);
}
);
});
});

@ -0,0 +1,74 @@
'use strict';
require('../common');
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(
'<ul id="editorTabs" class="nav nav-tabs hidden"><li ' +
'role="presentation" class="active"><a id="messageedit" ' +
'href="#">Editor</a></li><li role="presentation"><a ' +
'id="messagepreview" href="#">Preview</a></li></ul><div ' +
'id="placeholder" class="hidden">+++ no paste text +++</div>' +
'<div id="prettymessage" class="hidden"><pre id="prettyprint" ' +
'class="prettyprint linenums:1"></pre></div><div ' +
'id="plaintext" class="hidden"></div><p><textarea ' +
'id="message" name="message" cols="80" rows="25" ' +
'class="form-control hidden"></textarea></p>'
);
$.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);
}
);
});
});

@ -0,0 +1,278 @@
'use strict';
var common = require('../common');
describe('Helper', function () {
describe('secondsToHuman', function () {
after(function () {
cleanup();
});
jsc.property('returns an array with a number and a word', 'integer', function (number) {
var result = $.PrivateBin.Helper.secondsToHuman(number);
return Array.isArray(result) &&
result.length === 2 &&
result[0] === parseInt(result[0], 10) &&
typeof result[1] === 'string';
});
jsc.property('returns seconds on the first array position', 'integer 59', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[0] === number;
});
jsc.property('returns seconds on the second array position', 'integer 59', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'second';
});
jsc.property('returns minutes on the first array position', 'integer 60 3599', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[0] === Math.floor(number / 60);
});
jsc.property('returns minutes on the second array position', 'integer 60 3599', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'minute';
});
jsc.property('returns hours on the first array position', 'integer 3600 86399', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[0] === Math.floor(number / (60 * 60));
});
jsc.property('returns hours on the second array position', 'integer 3600 86399', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'hour';
});
jsc.property('returns days on the first array position', 'integer 86400 5184000', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[0] === Math.floor(number / (60 * 60 * 24));
});
jsc.property('returns days on the second array position', 'integer 86400 5184000', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'day';
});
// max safe integer as per http://ecma262-5.com/ELS5_HTML.htm#Section_8.5
jsc.property('returns months on the first array position', 'integer 5184000 9007199254740991', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[0] === Math.floor(number / (60 * 60 * 24 * 30));
});
jsc.property('returns months on the second array position', 'integer 5184000 9007199254740991', function (number) {
return $.PrivateBin.Helper.secondsToHuman(number)[1] === 'month';
});
});
// this test is not yet meaningful using jsdom, as it does not contain getSelection support.
// TODO: This needs to be tested using a browser.
describe('selectText', function () {
this.timeout(30000);
jsc.property(
'selection contains content of given ID',
jsc.nearray(jsc.nearray(common.jscAlnumString())),
'nearray string',
function (ids, contents) {
var html = '',
result = true;
ids.forEach(function(item, i) {
html += '<div id="' + item.join('') + '">' + common.htmlEntities(contents[i] || contents[0]) + '</div>';
});
var clean = jsdom(html);
// TODO: As per https://github.com/tmpvar/jsdom/issues/321 there is no getSelection in jsdom, yet.
// Once there is one, uncomment the block below to actually check the result.
/*
ids.forEach(function(item, i) {
$.PrivateBin.Helper.selectText(item.join(''));
result *= (contents[i] || contents[0]) === window.getSelection().toString();
});
*/
clean();
return Boolean(result);
}
);
});
describe('urls2links', function () {
after(function () {
cleanup();
});
jsc.property(
'ignores non-URL content',
'string',
function (content) {
return content === $.PrivateBin.Helper.urls2links(content);
}
);
jsc.property(
'replaces URLs with anchors',
'string',
jsc.elements(['http', 'https', 'ftp']),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
jsc.array(common.jscQueryString()),
'string',
function (prefix, schema, address, query, fragment, postfix) {
var query = query.join(''),
fragment = fragment.join(''),
url = schema + '://' + address.join('') + '/?' + query + '#' + fragment,
prefix = common.htmlEntities(prefix),
postfix = ' ' + common.htmlEntities(postfix);
// special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x
if (
query.slice(-1) === '&' &&
(parseInt(fragment.substring(0, 1), 10) >= 0 || fragment.charAt(0) === 'x' )
)
{
url = schema + '://' + address.join('') + '/?' + query.substring(0, query.length - 1);
postfix = '';
}
return prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a>' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + postfix);
}
);
jsc.property(
'replaces magnet links with anchors',
'string',
jsc.array(common.jscQueryString()),
'string',
function (prefix, query, postfix) {
var url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''),
prefix = common.htmlEntities(prefix),
postfix = common.htmlEntities(postfix);
return prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + ' ' + postfix);
}
);
});
describe('sprintf', function () {
after(function () {
cleanup();
});
jsc.property(
'replaces %s in strings with first given parameter',
'string',
'(small nearray) string',
'string',
function (prefix, params, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%');
params[0] = params[0].replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%');
var result = prefix + params[0] + postfix;
params.unshift(prefix + '%s' + postfix);
return result === $.PrivateBin.Helper.sprintf.apply(this, params);
}
);
jsc.property(
'replaces %d in strings with first given parameter',
'string',
'(small nearray) nat',
'string',
function (prefix, params, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%');
var result = prefix + params[0] + postfix;
params.unshift(prefix + '%d' + postfix);
return result === $.PrivateBin.Helper.sprintf.apply(this, params);
}
);
jsc.property(
'replaces %d in strings with 0 if first parameter is not a number',
'string',
'(small nearray) falsy',
'string',
function (prefix, params, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%');
var result = prefix + '0' + postfix;
params.unshift(prefix + '%d' + postfix);
return result === $.PrivateBin.Helper.sprintf.apply(this, params);
}
);
jsc.property(
'replaces %d and %s in strings in order',
'string',
'nat',
'string',
'string',
'string',
function (prefix, uint, middle, string, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%');
middle = middle.replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%');
var params = [prefix + '%d' + middle + '%s' + postfix, uint, string],
result = prefix + uint + middle + string + postfix;
return result === $.PrivateBin.Helper.sprintf.apply(this, params);
}
);
jsc.property(
'replaces %d and %s in strings in reverse order',
'string',
'nat',
'string',
'string',
'string',
function (prefix, uint, middle, string, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%');
middle = middle.replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%');
var params = [prefix + '%s' + middle + '%d' + postfix, string, uint],
result = prefix + string + middle + uint + postfix;
return result === $.PrivateBin.Helper.sprintf.apply(this, params);
}
);
});
describe('getCookie', function () {
this.timeout(30000);
jsc.property(
'returns the requested cookie',
'nearray asciinestring',
'nearray asciistring',
function (labels, values) {
var selectedKey = '', selectedValue = '',
cookieArray = [];
labels.forEach(function(item, i) {
// deliberatly using a non-ascii key for replacing invalid characters
var key = item.replace(/[\s;,=]/g, Array(i+2).join('£')),
value = (values[i] || values[0]).replace(/[\s;,=]/g, '');
cookieArray.push(key + '=' + value);
if (Math.random() < 1 / i || selectedKey === key)
{
selectedKey = key;
selectedValue = value;
}
});
var clean = jsdom('', {cookie: cookieArray}),
result = $.PrivateBin.Helper.getCookie(selectedKey);
clean();
return result === selectedValue;
}
);
});
describe('baseUri', function () {
this.timeout(30000);
before(function () {
$.PrivateBin.Helper.reset();
});
jsc.property(
'returns the URL without query & fragment',
common.jscSchemas(),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
'string',
function (schema, address, query, fragment) {
var expected = schema + '://' + address.join('') + '/',
clean = jsdom('', {url: expected + '?' + query.join('') + '#' + fragment}),
result = $.PrivateBin.Helper.baseUri();
$.PrivateBin.Helper.reset();
clean();
return expected === result;
}
);
});
describe('htmlEntities', function () {
after(function () {
cleanup();
});
jsc.property(
'removes all HTML entities from any given string',
'string',
function (string) {
var result = common.htmlEntities(string);
return !(/[<>"'`=\/]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result)));
}
);
});
});

@ -0,0 +1,104 @@
'use strict';
var common = require('../common');
describe('I18n', function () {
describe('translate', function () {
before(function () {
$.PrivateBin.I18n.reset();
});
jsc.property(
'returns message ID unchanged if no translation found',
'string',
function (messageId) {
messageId = messageId.replace(/%(s|d)/g, '%%');
var plurals = [messageId, messageId + 's'],
fake = [messageId],
result = $.PrivateBin.I18n.translate(messageId);
$.PrivateBin.I18n.reset();
var alias = $.PrivateBin.I18n._(messageId);
$.PrivateBin.I18n.reset();
var pluralResult = $.PrivateBin.I18n.translate(plurals);
$.PrivateBin.I18n.reset();
var pluralAlias = $.PrivateBin.I18n._(plurals);
$.PrivateBin.I18n.reset();
var fakeResult = $.PrivateBin.I18n.translate(fake);
$.PrivateBin.I18n.reset();
var fakeAlias = $.PrivateBin.I18n._(fake);
$.PrivateBin.I18n.reset();
return messageId === result && messageId === alias &&
messageId === pluralResult && messageId === pluralAlias &&
messageId === fakeResult && messageId === fakeAlias;
}
);
jsc.property(
'replaces %s in strings with first given parameter',
'string',
'(small nearray) string',
'string',
function (prefix, params, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%');
params[0] = params[0].replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%');
var translation = prefix + params[0] + postfix;
params.unshift(prefix + '%s' + postfix);
var result = $.PrivateBin.I18n.translate.apply(this, params);
$.PrivateBin.I18n.reset();
var alias = $.PrivateBin.I18n._.apply(this, params);
$.PrivateBin.I18n.reset();
return translation === result && translation === alias;
}
);
});
describe('getPluralForm', function () {
before(function () {
$.PrivateBin.I18n.reset();
});
jsc.property(
'returns valid key for plural form',
common.jscSupportedLanguages(),
'integer',
function(language, n) {
$.PrivateBin.I18n.reset(language);
var result = $.PrivateBin.I18n.getPluralForm(n);
// arabic seems to have the highest plural count with 6 forms
return result >= 0 && result <= 5;
}
);
});
// loading of JSON via AJAX needs to be tested in the browser, this just mocks it
// TODO: This needs to be tested using a browser.
describe('loadTranslations', function () {
this.timeout(30000);
before(function () {
$.PrivateBin.I18n.reset();
});
jsc.property(
'downloads and handles any supported language',
common.jscSupportedLanguages(),
function(language) {
var clean = jsdom('', {url: 'https://privatebin.net/', cookie: ['lang=' + language]});
$.PrivateBin.I18n.reset('en');
$.PrivateBin.I18n.loadTranslations();
$.PrivateBin.I18n.reset(language, require('../../i18n/' + language + '.json'));
var result = $.PrivateBin.I18n.translate('en'),
alias = $.PrivateBin.I18n._('en');
clean();
return language === result && language === alias;
}
);
});
});

@ -0,0 +1,263 @@
'use strict';
var common = require('../common');
describe('Model', function () {
describe('getExpirationDefault', function () {
before(function () {
$.PrivateBin.Model.reset();
cleanup();
});
jsc.property(
'returns the contents of the element with id "pasteExpiration"',
'array asciinestring',
'string',
'small nat',
function (keys, value, key) {
keys = keys.map(common.htmlEntities);
value = common.htmlEntities(value);
var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'),
contents = '<select id="pasteExpiration" name="pasteExpiration">';
keys.forEach(function(item) {
contents += '<option value="' + item + '"';
if (item === content) {
contents += ' selected="selected"';
}
contents += '>' + value + '</option>';
});
contents += '</select>';
$('body').html(contents);
var result = common.htmlEntities(
$.PrivateBin.Model.getExpirationDefault()
);
$.PrivateBin.Model.reset();
return content === result;
}
);
});
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(common.htmlEntities);
value = common.htmlEntities(value);
var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'),
contents = '<select id="pasteFormatter" name="pasteFormatter">';
keys.forEach(function(item) {
contents += '<option value="' + item + '"';
if (item === content) {
contents += ' selected="selected"';
}
contents += '>' + value + '</option>';
});
contents += '</select>';
$('body').html(contents);
var result = common.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 = common.htmlEntities(value).trim();
$('body').html('<div id="cipherdata">' + value + '</div>');
$.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 = common.htmlEntities(value).trim();
$('body').html('<div id="cipherdata">' + value + '</div>');
$.PrivateBin.Model.init();
var result = common.htmlEntities(
$.PrivateBin.Model.getCipherData()
);
$.PrivateBin.Model.reset();
return value === result;
}
);
});
describe('getPasteId', function () {
this.timeout(30000);
before(function () {
$.PrivateBin.Model.reset();
cleanup();
});
jsc.property(
'returns the query string without separator, if any',
jsc.nearray(common.jscA2zString()),
jsc.nearray(common.jscA2zString()),
jsc.nearray(common.jscQueryString()),
'string',
function (schema, address, query, fragment) {
var queryString = query.join(''),
clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/?' + queryString + '#' + fragment
}),
result = $.PrivateBin.Model.getPasteId();
$.PrivateBin.Model.reset();
clean();
return queryString === result;
}
);
jsc.property(
'throws exception on empty query string',
jsc.nearray(common.jscA2zString()),
jsc.nearray(common.jscA2zString()),
'string',
function (schema, address, fragment) {
var clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/#' + fragment
}),
result = false;
try {
$.PrivateBin.Model.getPasteId();
}
catch(err) {
result = true;
}
$.PrivateBin.Model.reset();
clean();
return result;
}
);
});
describe('getPasteKey', function () {
this.timeout(30000);
jsc.property(
'returns the fragment of the URL',
jsc.nearray(common.jscA2zString()),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
jsc.nearray(common.jscBase64String()),
function (schema, address, query, fragment) {
var fragmentString = fragment.join(''),
clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/?' + query.join('') + '#' + fragmentString
}),
result = $.PrivateBin.Model.getPasteKey();
$.PrivateBin.Model.reset();
clean();
return fragmentString === result;
}
);
jsc.property(
'returns the fragment stripped of trailing query parts',
jsc.nearray(common.jscA2zString()),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
jsc.nearray(common.jscBase64String()),
jsc.array(common.jscQueryString()),
function (schema, address, query, fragment, trail) {
var fragmentString = fragment.join(''),
clean = jsdom('', {
url: schema.join('') + '://' + address.join('') + '/?' +
query.join('') + '#' + fragmentString + '&' + trail.join('')
}),
result = $.PrivateBin.Model.getPasteKey();
$.PrivateBin.Model.reset();
clean();
return fragmentString === result;
}
);
jsc.property(
'throws exception on empty fragment of the URL',
jsc.nearray(common.jscA2zString()),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
function (schema, address, query) {
var clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/?' + query.join('')
}),
result = false;
try {
$.PrivateBin.Model.getPasteKey();
}
catch(err) {
result = true;
}
$.PrivateBin.Model.reset();
clean();
return result;
}
);
});
describe('getTemplate', function () {
before(function () {
$.PrivateBin.Model.reset();
cleanup();
});
jsc.property(
'returns the contents of the element with id "[name]template"',
jsc.nearray(common.jscAlnumString()),
jsc.nearray(common.jscA2zString()),
jsc.nearray(common.jscAlnumString()),
function (id, element, value) {
id = id.join('');
element = element.join('');
value = value.join('').trim();
// <br>, <hr>, <img> and <wbr> tags can't contain strings,
// table tags can't be alone, so test with a <p> instead
if (['br', 'col', 'hr', 'img', 'tr', 'td', 'th', 'wbr'].indexOf(element) >= 0) {
element = 'p';
}
$('body').html(
'<div id="templates"><' + element + ' id="' + id +
'template">' + value + '</' + element + '></div>'
);
$.PrivateBin.Model.init();
var template = '<' + element + ' id="' + id + '">' + value +
'</' + element + '>',
result = $.PrivateBin.Model.getTemplate(id).wrap('<p/>').parent().html();
$.PrivateBin.Model.reset();
return template === result;
}
);
});
});

@ -0,0 +1,105 @@
'use strict';
var common = require('../common');
describe('PasteStatus', function () {
describe('createPasteNotification', function () {
this.timeout(30000);
before(function () {
cleanup();
});
jsc.property(
'creates a notification after a successfull paste upload',
common.jscSchemas(),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
'string',
common.jscSchemas(),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
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('<div><div id="deletelink"></div><div id="pastelink"></div></div>');
$.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(common.jscA2zString()),
jsc.nearray(common.jscA2zString()),
jsc.nearray(common.jscQueryString()),
'string',
function (
burnafterreading, remainingTime,
schema, address, query, fragment
) {
var clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/?' + query.join('') + '#' + fragment
}),
result;
$('body').html('<div id="remainingtime" class="hidden"></div>');
$.PrivateBin.PasteStatus.init();
$.PrivateBin.PasteStatus.showRemainingTime({
'burnafterreading': burnafterreading,
'remaining_time': remainingTime,
'expire_date': remainingTime ? ((new Date()).getTime() / 1000) + remainingTime : 0
});
if (burnafterreading) {
result = $('#remainingtime').hasClass('foryoureyesonly') &&
!$('#remainingtime').hasClass('hidden');
} else if (remainingTime) {
result =!$('#remainingtime').hasClass('foryoureyesonly') &&
!$('#remainingtime').hasClass('hidden');
} else {
result = $('#remainingtime').hasClass('hidden') &&
!$('#remainingtime').hasClass('foryoureyesonly');
}
clean();
return result;
}
);
});
describe('hideMessages', function () {
before(function () {
cleanup();
});
it(
'hides all messages',
function() {
$('body').html(
'<div id="remainingtime"></div><div id="pastesuccess"></div>'
);
$.PrivateBin.PasteStatus.init();
$.PrivateBin.PasteStatus.hideMessages();
return $('#remainingtime').hasClass('hidden') &&
$('#pastesuccess').hasClass('hidden');
}
);
});
});

@ -0,0 +1,121 @@
'use strict';
var common = require('../common');
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',
common.jscFormats(),
'nestring',
function (format, text) {
var clean = jsdom(),
results = [];
$('body').html(
'<div id="placeholder" class="hidden">+++ no paste text ' +
'+++</div><div id="prettymessage" class="hidden"><pre ' +
'id="prettyprint" class="prettyprint linenums:1"></pre>' +
'</div><div id="plaintext" class="hidden"></div>'
);
$.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',
common.jscFormats(),
'string',
// @see {@link https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet}
jsc.elements([
'<PLAINTEXT>',
'></SCRIPT>">\'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>',
'\'\';!--"<XSS>=&{()}',
'<SCRIPT SRC=http://example.com/xss.js></SCRIPT>',
'\'">><marquee><img src=x onerror=confirm(1)></marquee>">' +
'</plaintext\\></|\\><plaintext/onmouseover=prompt(1)>' +
'<script>prompt(1)</script>@gmail.com<isindex formaction=' +
'javascript:alert(/XSS/) type=submit>\'-->"></script>' +
'<script>alert(document.cookie)</script>"><img/id="confirm' +
'&lpar;1)"/alt="/"src="/"onerror=eval(id)>\'">',
'<IMG SRC="javascript:alert(\'XSS\');">',
'<IMG SRC=javascript:alert(\'XSS\')>',
'<IMG SRC=JaVaScRiPt:alert(\'XSS\')>',
'<IMG SRC=javascript:alert(&quot;XSS&quot;)>',
'<IMG SRC=`javascript:alert("RSnake says, \'XSS\'")`>',
'<a onmouseover="alert(document.cookie)">xxs link</a>',
'<a onmouseover=alert(document.cookie)>xxs link</a>',
'<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
'<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>',
'<IMG STYLE="xss:expr/*XSS*/ession(alert(\'XSS\'))">',
'<FRAMESET><FRAME SRC="javascript:alert(\'XSS\');"></FRAMESET>',
'<TABLE BACKGROUND="javascript:alert(\'XSS\')">',
'<TABLE><TD BACKGROUND="javascript:alert(\'XSS\')">',
'<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="httx://xss.rocks/xss.js"></SCRIPT>'
]),
'string',
function (format, prefix, xss, suffix) {
var clean = jsdom(),
text = prefix + xss + suffix;
$('body').html(
'<div id="placeholder" class="hidden">+++ no paste text ' +
'+++</div><div id="prettymessage" class="hidden"><pre ' +
'id="prettyprint" class="prettyprint linenums:1"></pre>' +
'</div><div id="plaintext" class="hidden"></div>'
);
$.PrivateBin.PasteViewer.init();
$.PrivateBin.PasteViewer.setFormat(format);
$.PrivateBin.PasteViewer.setText(text);
$.PrivateBin.PasteViewer.run();
var result = $('body').html().indexOf(xss) === -1;
clean();
return result;
}
);
});
});

@ -0,0 +1,40 @@
'use strict';
require('../common');
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(
'<div id="passwordmodal" class="modal fade" role="dialog">' +
'<div class="modal-dialog"><div class="modal-content">' +
'<div class="modal-body"><form id="passwordform" role="form">' +
'<div class="form-group"><input id="passworddecrypt" ' +
'type="password" class="form-control" placeholder="Enter ' +
'password"></div><button type="submit">Decrypt</button>' +
'</form></div></div></div></div><div id="cipherdata">{}</div>'
);
$.PrivateBin.Model.init();
$.PrivateBin.Prompt.init();
$.PrivateBin.Prompt.requestPassword();
$('#passworddecrypt').val(password);
$('#passwordform').submit();
var result = $.PrivateBin.Prompt.getPassword();
clean();
return result === password;
}
);
});
});

@ -0,0 +1,124 @@
'use strict';
var common = require('../common');
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();
cleanup();
});
jsc.property(
'redirects to home, when the state is null',
common.jscSchemas(),
jsc.nearray(common.jscA2zString()),
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',
common.jscSchemas(),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
jsc.nearray(common.jscBase64String()),
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',
common.jscSchemas(),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
jsc.nearray(common.jscBase64String()),
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(common.jscAlnumString()),
jsc.nearray(common.jscA2zString()),
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
});
});

@ -52,8 +52,9 @@ class Configuration
'languageselection' => false,
'languagedefault' => '',
'urlshortener' => '',
'qrcode' => true,
'icon' => 'identicon',
'cspheader' => 'default-src \'none\'; manifest-src \'self\'; connect-src *; script-src \'self\'; style-src \'self\'; font-src \'self\'; img-src \'self\' data:; referrer no-referrer; sandbox allow-same-origin allow-scripts allow-forms allow-popups',
'cspheader' => 'default-src \'none\'; manifest-src \'self\'; connect-src *; form-action \'none\'; script-src \'self\'; style-src \'self\'; font-src \'self\'; img-src \'self\' data:; referrer no-referrer; sandbox allow-same-origin allow-scripts allow-forms allow-popups',
'zerobincompatibility' => false,
),
'expire' => array(

@ -176,8 +176,7 @@ class PrivateBin
$this->_conf = new Configuration;
$this->_model = new Model($this->_conf);
$this->_request = new Request;
$this->_urlBase = array_key_exists('REQUEST_URI', $_SERVER) ?
htmlspecialchars($_SERVER['REQUEST_URI']) : '/';
$this->_urlBase = $this->_request->getRequestUri();
ServerSalt::setPath($this->_conf->getKey('dir', 'traffic'));
// set default language
@ -448,6 +447,7 @@ class PrivateBin
$page->assign('EXPIREDEFAULT', $this->_conf->getKey('default', 'expire'));
$page->assign('EXPIRECLONE', !$this->_doesExpire || ($this->_doesExpire && $this->_conf->getKey('clone', 'expire')));
$page->assign('URLSHORTENER', $this->_conf->getKey('urlshortener'));
$page->assign('QRCODE', $this->_conf->getKey('qrcode'));
$page->draw($this->_conf->getKey('template'));
}

@ -141,7 +141,20 @@ class Request
*/
public function getParam($param, $default = '')
{
return array_key_exists($param, $this->_params) ? $this->_params[$param] : $default;
return array_key_exists($param, $this->_params) ?
$this->_params[$param] : $default;
}
/**
* Get request URI
*
* @access public
* @return string
*/
public function getRequestUri()
{
return array_key_exists('REQUEST_URI', $_SERVER) ?
htmlspecialchars($_SERVER['REQUEST_URI']) : '/';
}
/**

@ -44,6 +44,11 @@ endif;
<script type="text/javascript" src="js/jquery-3.1.1.js" integrity="sha512-U6K1YLIFUWcvuw5ucmMtT9HH4t0uz3M366qrF5y4vnyH6dgDzndlcGvH/Lz5k8NFh80SN95aJ5rqGZEdaQZ7ZQ==" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/sjcl-1.0.6.js" integrity="sha512-DsyxLV/uBoQlRTJmW5Gb2SxXUXB+aYeZ6zk+NuXy8LuLyi8oGti9AGn6He5fUY2DtgQ2//RjfaZog8exFuunUQ==" crossorigin="anonymous"></script>
<?php
if ($QRCODE):
?>
<script async type="text/javascript" src="js/kjua-0.1.2.js" integrity="sha512-hmvfOhcr4J8bjQ2GuNVzfSbuulv72wgQCJpgnXc2+cCHKqvYo8pK2nc0Q4Esem2973zo1radyIMTEkt+xJlhBA==" crossorigin="anonymous"></script>
<?php
endif;
if ($ZEROBINCOMPATIBILITY):
?>
<script type="text/javascript" src="js/base64-1.7.js" integrity="sha512-JdwsSP3GyHR+jaCkns9CL9NTt4JUJqm/BsODGmYhBcj5EAPKcHYh+OiMfyHbcDLECe17TL0hjXADFkusAqiYgA==" crossorigin="anonymous"></script>
@ -66,11 +71,11 @@ endif;
if ($MARKDOWN):
?>
<script type="text/javascript" src="js/showdown-1.6.1.js" integrity="sha512-e6kAsBTgFnTBnEQXrq8BV6+XFwxb3kyWHeEPOl+KhxaWt3xImE2zAW2+yP3E2CQ7F9yoJl1poVU9qxkOEtVsTQ==" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/purify.min.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-jJuy143F5Oy7oS3VkjzeJGBxIUuQ1H0eSjuvLGD3FiQzeu8Pwp5vI/jQ2dxlxSrzejmNMicdLHnIqH7R8Ft0lQ==" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/purify-1.0.3.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-uhzhZJSgc+XJoaxCOjiuRzQaf5klPlSSVKGw69+zT72hhfLbVwB4jbwI+f7NRucuRz6u0aFGMeZ+0PnGh73iBQ==" crossorigin="anonymous"></script>
<?php
endif;
?>
<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-EvNAh1GXOoUiGZ/W8iPtzsce06bvVHy6+ajJztmfSgdQcKMPoj0dB8j1FC90MEChl7MOeR4xozvDymH/6HwIlA==" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-9HcFkJcGWfvpGHD7tTGYzBtx4TbVfR9z7oujlX2WZ2dYWVv/2QIW5eMSjpvfxUVTJVF+DHD7Ps/80qR8GcQsIg==" crossorigin="anonymous"></script>
<!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]-->
@ -88,8 +93,8 @@ if ($isCpct):
?> class="navbar-spacing"<?php
endif;
?>>
<div id="passwordmodal" class="modal fade" role="dialog">
<div class="modal-dialog">
<div id="passwordmodal" tabindex="-1" class="modal fade" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<form id="passwordform" role="form">
@ -103,6 +108,22 @@ endif;
</div>
</div>
</div>
<?php
if ($QRCODE):
?>
<div id="qrcodemodal" tabindex="-1" class="modal fade" aria-labelledby="qrcodemodalTitle" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="mx-auto" id="qrcode-display"></div>
</div>
<button type="button" class="btn btn-primary btn-block" data-dismiss="modal"><?php echo I18n::_('Close') ?></button>
</div>
</div>
</div>
<?php
endif;
?>
<nav class="navbar navbar-<?php echo $isDark ? 'inverse' : 'default'; ?> navbar-<?php echo $isCpct ? 'fixed' : 'static'; ?>-top"><?php
if ($isCpct):
?><div class="container"><?php
@ -150,6 +171,15 @@ endif;
<button id="rawtextbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> <?php echo I18n::_('Raw text'), PHP_EOL; ?>
</button>
<?php
if ($QRCODE):
?>
<button id="qrcodelink" type="button" data-toggle="modal" data-target="#qrcodemodal" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
<span class="glyphicon glyphicon-qrcode" aria-hidden="true"></span> <?php echo I18n::_('QR code'), PHP_EOL; ?>
</button>
<?php
endif;
?>
</li>
<li class="dropdown">
<select id="pasteExpiration" name="pasteExpiration" class="hidden">
@ -271,7 +301,7 @@ else:
endif;
?> />
<?php echo I18n::_('Open discussion'), PHP_EOL; ?>
</label>
</label>
</div>
</li>
<?php
@ -438,17 +468,16 @@ endif;
<div id="pastesuccess" role="alert" class="hidden alert alert-success">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
<div id="deletelink"></div>
<div id="pastelink">
<div id="pastelink"></div>
<?php
if (strlen($URLSHORTENER)):
?>
<button id="shortenbutton" data-shortener="<?php echo htmlspecialchars($URLSHORTENER); ?>" type="button" class="btn btn-<?php echo $isDark ? 'warning' : 'primary'; ?>">
<span class="glyphicon glyphicon-send" aria-hidden="true"></span> <?php echo I18n::_('Shorten URL'), PHP_EOL; ?>
</button>
<button id="shortenbutton" data-shortener="<?php echo htmlspecialchars($URLSHORTENER); ?>" type="button" class="btn btn-<?php echo $isDark ? 'warning' : 'primary'; ?>">
<span class="glyphicon glyphicon-send" aria-hidden="true"></span> <?php echo I18n::_('Shorten URL'), PHP_EOL; ?>
</button>
<?php
endif;
?>
</div>
</div>
<ul id="editorTabs" class="nav nav-tabs hidden">
<li role="presentation" class="active"><a id="messageedit" href="#"><?php echo I18n::_('Editor'); ?></a></li>
@ -495,7 +524,6 @@ endif;
if ($DISCUSSION):
?>
<div id="templates">
<!-- @TODO: when I intend/structure this corrrectly Firefox adds whitespaces everywhere which completly destroy the layout. (same possible when you remove the template data below and show this area in the browser) -->
<article id="commenttemplate" class="comment"><div class="commentmeta"><span class="nickname">name</span><span class="commentdate">0000-00-00</span></div><div class="commentdata">c</div><button class="btn btn-default btn-sm"><?php echo I18n::_('Reply'); ?></button></article>
<p id="commenttailtemplate" class="comment"><button class="btn btn-default btn-sm"><?php echo I18n::_('Add comment'); ?></button></p>
<div id="replytemplate" class="reply hidden"><input type="text" id="nickname" class="form-control" title="<?php echo I18n::_('Optional nickname…'); ?>" placeholder="<?php echo I18n::_('Optional nickname…'); ?>" /><textarea id="replymessage" class="replymessage form-control" cols="80" rows="7"></textarea><br /><div id="replystatus" role="alert" class="statusmessage hidden alert"><span class="glyphicon" aria-hidden="true"></span> </div><button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button></div>

@ -22,6 +22,7 @@ endif;
?>
<script type="text/javascript" src="js/jquery-3.1.1.js" integrity="sha512-U6K1YLIFUWcvuw5ucmMtT9HH4t0uz3M366qrF5y4vnyH6dgDzndlcGvH/Lz5k8NFh80SN95aJ5rqGZEdaQZ7ZQ==" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/sjcl-1.0.6.js" integrity="sha512-DsyxLV/uBoQlRTJmW5Gb2SxXUXB+aYeZ6zk+NuXy8LuLyi8oGti9AGn6He5fUY2DtgQ2//RjfaZog8exFuunUQ==" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/kjua.min.js" integrity="sha512-hmvfOhcr4J8bjQ2GuNVzfSbuulv72wgQCJpgnXc2+cCHKqvYo8pK2nc0Q4Esem2973zo1radyIMTEkt+xJlhBA==" crossorigin="anonymous"></script>
<?php
if ($ZEROBINCOMPATIBILITY):
?>
@ -44,11 +45,16 @@ endif;
if ($MARKDOWN):
?>
<script type="text/javascript" src="js/showdown-1.6.1.js" integrity="sha512-e6kAsBTgFnTBnEQXrq8BV6+XFwxb3kyWHeEPOl+KhxaWt3xImE2zAW2+yP3E2CQ7F9yoJl1poVU9qxkOEtVsTQ==" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/purify.min.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-jJuy143F5Oy7oS3VkjzeJGBxIUuQ1H0eSjuvLGD3FiQzeu8Pwp5vI/jQ2dxlxSrzejmNMicdLHnIqH7R8Ft0lQ==" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/purify-1.0.3.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-uhzhZJSgc+XJoaxCOjiuRzQaf5klPlSSVKGw69+zT72hhfLbVwB4jbwI+f7NRucuRz6u0aFGMeZ+0PnGh73iBQ==" crossorigin="anonymous"></script>
<?php
endif;
if ($QRCODE):
?>
<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-EvNAh1GXOoUiGZ/W8iPtzsce06bvVHy6+ajJztmfSgdQcKMPoj0dB8j1FC90MEChl7MOeR4xozvDymH/6HwIlA==" crossorigin="anonymous"></script>
<script async type="text/javascript" src="js/kjua-0.1.2.js" integrity="sha512-hmvfOhcr4J8bjQ2GuNVzfSbuulv72wgQCJpgnXc2+cCHKqvYo8pK2nc0Q4Esem2973zo1radyIMTEkt+xJlhBA==" crossorigin="anonymous"></script>
<?php
endif;
?>
<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-9HcFkJcGWfvpGHD7tTGYzBtx4TbVfR9z7oujlX2WZ2dYWVv/2QIW5eMSjpvfxUVTJVF+DHD7Ps/80qR8GcQsIg==" crossorigin="anonymous"></script>
<!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]-->
@ -99,6 +105,13 @@ if ($EXPIRECLONE):
endif;
?>
<button id="rawtextbutton" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" /><?php echo I18n::_('Raw text'); ?></button>
<?php
if ($QRCODE):
?>
<button id="qrcodelink" class="hidden"><img src="img/icon_qr.png" width="15" height="15" alt="" /><?php echo I18n::_('QR code'); ?></button>
<?php
endif;
?>
<div id="expiration" class="hidden button"><?php echo I18n::_('Expires'); ?>:
<select id="pasteExpiration" name="pasteExpiration">
<?php
@ -185,17 +198,22 @@ if (strlen($LANGUAGESELECTION)):
endif;
?>
</div>
<div id="pastesuccess" class="hidden">
<?php
if ($QRCODE):
?>
<div id="qrcode-display"></div>
<?php
endif;
?> <div id="pastesuccess" class="hidden">
<div id="deletelink"></div>
<div id="pastelink">
<div id="pastelink"></div>
<?php
if (strlen($URLSHORTENER)):
?>
<button id="shortenbutton" data-shortener="<?php echo htmlspecialchars($URLSHORTENER); ?>"><img src="img/icon_shorten.png" width="13" height="15" /><?php echo I18n::_('Shorten URL'); ?></button>
<button id="shortenbutton" data-shortener="<?php echo htmlspecialchars($URLSHORTENER); ?>"><img src="img/icon_shorten.png" width="13" height="15" /><?php echo I18n::_('Shorten URL'); ?></button>
<?php
endif;
?>
</div>
</div>
<?php
if ($FILEUPLOAD):
@ -233,7 +251,6 @@ endif;
if ($DISCUSSION):
?>
<div id="templates">
<!-- @TODO: when I intend/structure this corrrectly Firefox adds whitespaces everywhere which completly destroy the layout. (same possible when you remove the template data below and show this area in the browser) -->
<article id="commenttemplate" class="comment"><div class="commentmeta"><span class="nickname">name</span><span class="commentdate">0000-00-00</span></div><div class="commentdata">c</div><button class="btn btn-default btn-sm"><?php echo I18n::_('Reply'); ?></button></article>
<div id="commenttailtemplate" class="comment"><button class="btn btn-default btn-sm"><?php echo I18n::_('Add comment'); ?></button></div>
<div id="replytemplate" class="reply hidden"><input type="text" id="nickname" class="form-control" title="<?php echo I18n::_('Optional nickname…'); ?>" placeholder="<?php echo I18n::_('Optional nickname…'); ?>" /><textarea id="replymessage" class="replymessage form-control" cols="80" rows="7"></textarea><br /><div id="replystatus" role="alert" class="statusmessage hidden alert"><span class="glyphicon" aria-hidden="true"></span> </div><button id="replybutton" class="btn btn-default btn-sm"><?php echo I18n::_('Post comment'); ?></button></div>
@ -242,7 +259,7 @@ if ($DISCUSSION):
endif;
?>
</div>
<section class="container">
<section class="container">
<div id="noscript" role="alert" class="nonworking alert alert-info noscript-hide"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true">
<span> <?php echo I18n::_('Loading…'); ?></span><br>
<span class="small"><?php echo I18n::_('In case this message never disappears please have a look at <a href="https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-the-loading-message-go-away">this FAQ for information to troubleshoot</a>.'); ?></span>

@ -56,6 +56,7 @@ class ViewTest extends PHPUnit_Framework_TestCase
$page->assign('EXPIREDEFAULT', self::$expire_default);
$page->assign('EXPIRECLONE', true);
$page->assign('URLSHORTENER', '');
$page->assign('QRCODE', true);
$dir = dir(PATH . 'tpl');
while (false !== ($file = $dir->read())) {

Loading…
Cancel
Save