diff --git a/.codeclimate.yml b/.codeclimate.yml index 01766cda..b5301348 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -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/" + diff --git a/.eslintignore b/.eslintignore index 96212a35..f3c9e2a4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ -**/*{.,-}min.js +js/*.js +!js/privatebin.js diff --git a/.eslintrc b/.eslintrc index cee9820d..1f7106fa 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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 diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..fabd7e6d --- /dev/null +++ b/.jshintrc @@ -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 + } +} diff --git a/.nsprc b/.nsprc new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/.nsprc @@ -0,0 +1 @@ +{} diff --git a/.travis.yml b/.travis.yml index 084a76d1..7e3a1443 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/Dockerfile b/Dockerfile index b0121340..7bc9e127 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index 4db8a33e..db600d5f 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -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 diff --git a/composer.json b/composer.json index 2a363a66..cd542b61 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/css/bootstrap/privatebin.css b/css/bootstrap/privatebin.css index d2ba47ca..e0dd81f2 100644 --- a/css/bootstrap/privatebin.css +++ b/css/bootstrap/privatebin.css @@ -76,6 +76,16 @@ body.loading { #deletelink { float: right; + margin-left: 5px; +} + +#qrcodemodalClose { + float: right; +} +#qrcode-display { + width: 200px; + height: 200px; + margin: auto; } #pastelink { diff --git a/css/privatebin.css b/css/privatebin.css index 077e8bae..a1d3b23e 100644 --- a/css/privatebin.css +++ b/css/privatebin.css @@ -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; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..31432216 --- /dev/null +++ b/docker-compose.yml @@ -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: + diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 00000000..124f2ea4 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,4 @@ +#! /bin/sh + +chown -R www-data /var/www/html/data +apache2-foreground diff --git a/img/icon_qr.png b/img/icon_qr.png new file mode 100644 index 00000000..28d10ca7 Binary files /dev/null and b/img/icon_qr.png differ diff --git a/js/common.js b/js/common.js new file mode 100644 index 00000000..c60a7064 --- /dev/null +++ b/js/common.js @@ -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 = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }, + 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); +}; + diff --git a/js/kjua-0.1.2.js b/js/kjua-0.1.2.js new file mode 100644 index 00000000..93257876 --- /dev/null +++ b/js/kjua-0.1.2.js @@ -0,0 +1,2 @@ +/*! kjua v0.1.2 - https://larsjung.de/kjua/ */ +!function(r,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.kjua=t():r.kjua=t()}(this,function(){return function(r){function t(n){if(e[n])return e[n].exports;var o=e[n]={exports:{},id:n,loaded:!1};return r[n].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var e={};return t.m=r,t.c=e,t.p="",t(0)}([function(r,t,e){"use strict";var n=e(1),o=n.createCanvas,i=n.canvasToImg,a=n.dpr,u=e(2),f=e(3),c=e(4);r.exports=function(r){var t=Object.assign({},u,r),e=f(t.text,t.ecLevel,t.minVersion,t.quiet),n=t.ratio||a,l=o(t.size,n),s=l.getContext("2d");return s.scale(n,n),c(e,s,t),"image"===t.render?i(l):l}},function(r,t){"use strict";var e=window,n=e.document,o=e.devicePixelRatio||1,i=function(r){return n.createElement(r)},a=function(r,t){return r.getAttribute(t)},u=function(r,t,e){return r.setAttribute(t,e)},f=function(r,t){var e=i("canvas");return u(e,"width",r*t),u(e,"height",r*t),e.style.width=r+"px",e.style.height=r+"px",e},c=function(r){var t=i("img");return u(t,"crossorigin","anonymous"),u(t,"src",r.toDataURL("image/png")),u(t,"width",a(r,"width")),u(t,"height",a(r,"height")),t.style.width=r.style.width,t.style.height=r.style.height,t};r.exports={createCanvas:f,canvasToImg:c,dpr:o}},function(r,t){"use strict";r.exports={render:"image",crisp:!0,minVersion:1,ecLevel:"L",size:200,ratio:null,fill:"#333",back:"#fff",text:"no text",rounded:0,quiet:0,mode:"plain",mSize:30,mPosX:50,mPosY:50,label:"no label",fontname:"sans",fontcolor:"#333",image:null}},function(r,t){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(r){return typeof r}:function(r){return r&&"function"==typeof Symbol&&r.constructor===Symbol&&r!==Symbol.prototype?"symbol":typeof r},n=/code length overflow/i,o=function(){var e=function(){function r(t,e){if("undefined"==typeof t.length)throw new Error(t.length+"/"+e);var n=function(){for(var r=0;r=7&&T(r),null==d&&(d=x(l,s,w)),b(d,t)},m=function(r,t){for(var e=-1;e<=7;e+=1)if(!(r+e<=-1||h<=r+e))for(var n=-1;n<=7;n+=1)t+n<=-1||h<=t+n||(0<=e&&e<=6&&(0==n||6==n)||0<=n&&n<=6&&(0==e||6==e)||2<=e&&e<=4&&2<=n&&n<=4?g[r+e][t+n]=!0:g[r+e][t+n]=!1)},A=function(){for(var r=0,t=0,e=0;e<8;e+=1){p(!0,e);var n=i.getLostPoint(y);(0==e||r>n)&&(r=n,t=e)}return t},B=function(){for(var r=8;r>e&1);g[Math.floor(e/3)][e%3+h-8-3]=n}for(var e=0;e<18;e+=1){var n=!r&&1==(t>>e&1);g[e%3+h-8-3][Math.floor(e/3)]=n}},M=function(r,t){for(var e=s<<3|t,n=i.getBCHTypeInfo(e),o=0;o<15;o+=1){var a=!r&&1==(n>>o&1);o<6?g[o][8]=a:o<8?g[o+1][8]=a:g[h-15+o][8]=a}for(var o=0;o<15;o+=1){var a=!r&&1==(n>>o&1);o<8?g[8][h-o-1]=a:o<9?g[8][15-o-1+1]=a:g[8][15-o-1]=a}g[h-8][8]=!r},b=function(r,t){for(var e=-1,n=h-1,o=7,a=0,u=i.getMaskFunction(t),f=h-1;f>0;f-=2)for(6==f&&(f-=1);;){for(var c=0;c<2;c+=1)if(null==g[n][f-c]){var l=!1;a>>o&1));var s=u(n,f-c);s&&(l=!l),g[n][f-c]=l,o-=1,o==-1&&(a+=1,o=7)}if(n+=e,n<0||h<=n){n-=e,e=-e;break}}},k=function(t,e){for(var n=0,o=0,a=0,u=new Array(e.length),f=new Array(e.length),c=0;c=0?d.getAt(w):0}}for(var y=0,g=0;g8*g)throw new Error("code length overflow. ("+c.getLengthInBits()+">"+8*g+")");for(c.getLengthInBits()+4<=8*g&&c.put(0,4);c.getLengthInBits()%8!=0;)c.putBit(!1);for(;;){if(c.getLengthInBits()>=8*g)break;if(c.put(o,8),c.getLengthInBits()>=8*g)break;c.put(a,8)}return k(c,n)};return y.addData=function(r){var t=c(r);w.push(t),d=null},y.isDark=function(r,t){if(r<0||h<=r||t<0||h<=t)throw new Error(r+","+t);return g[r][t]},y.getModuleCount=function(){return h},y.make=function(){p(!1,A())},y.createTableTag=function(r,t){r=r||2,t="undefined"==typeof t?4*r:t;var e="";e+='";for(var o=0;o';e+=""}return e+="",e+="
"},y.createImgTag=function(r,t){r=r||2,t="undefined"==typeof t?4*r:t;var e=y.getModuleCount()*r+2*t,n=t,o=e-t;return v(e,e,function(t,e){if(n<=t&&t>>8),t.push(255&a)):t.push(n)}}return t}};var e={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},n={L:1,M:0,Q:3,H:2},o={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},i=function(){var t=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],n=1335,i=7973,u=21522,f={},c=function(r){for(var t=0;0!=r;)t+=1,r>>>=1;return t};return f.getBCHTypeInfo=function(r){for(var t=r<<10;c(t)-c(n)>=0;)t^=n<=0;)t^=i<5&&(e+=3+i-5)}for(var n=0;n=256;)t-=255;return r[t]},n}(),u=function(){var r=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],t=function(r,t){var e={};return e.totalCount=r,e.dataCount=t,e},e={},o=function(t,e){switch(e){case n.L:return r[4*(t-1)+0];case n.M:return r[4*(t-1)+1];case n.Q:return r[4*(t-1)+2];case n.H:return r[4*(t-1)+3];default:return}};return e.getRSBlocks=function(r,e){var n=o(r,e);if("undefined"==typeof n)throw new Error("bad rs block @ typeNumber:"+r+"/errorCorrectLevel:"+e);for(var i=n.length/3,a=new Array,u=0;u>>7-t%8&1)},e.put=function(r,t){for(var n=0;n>>t-n-1&1))},e.getLengthInBits=function(){return t},e.putBit=function(e){var n=Math.floor(t/8);r.length<=n&&r.push(0),e&&(r[n]|=128>>>t%8),t+=1},e},c=function(r){var n=e.MODE_8BIT_BYTE,o=t.stringToBytes(r),i={};return i.getMode=function(){return n},i.getLength=function(r){return o.length},i.write=function(r){for(var t=0;t>>8)},t.writeBytes=function(r,e,n){e=e||0,n=n||r.length;for(var o=0;o0&&(t+=","),t+=r[e];return t+="]"},t},s=function(){var r=0,t=0,e=0,n="",o={},i=function(r){n+=String.fromCharCode(a(63&r))},a=function(r){if(r<0);else{if(r<26)return 65+r;if(r<52)return 97+(r-26);if(r<62)return 48+(r-52);if(62==r)return 43;if(63==r)return 47}throw new Error("n:"+r)};return o.writeByte=function(n){for(r=r<<8|255&n,t+=8,e+=1;t>=6;)i(r>>>t-6),t-=6},o.flush=function(){if(t>0&&(i(r<<6-t),r=0,t=0),e%3!=0)for(var o=3-e%3,a=0;a=t.length){if(0==o)return-1;throw new Error("unexpected end of file./"+o)}var r=t.charAt(e);if(e+=1,"="==r)return o=0,-1;r.match(/^\s$/)||(n=n<<6|a(r.charCodeAt(0)),o+=6)}var i=n>>>o-8&255;return o-=8,i};var a=function(r){if(65<=r&&r<=90)return r-65;if(97<=r&&r<=122)return r-97+26;if(48<=r&&r<=57)return r-48+52;if(43==r)return 62;if(47==r)return 63;throw new Error("c:"+r)};return i},h=function(r,t){var e=r,n=t,o=new Array(r*t),i={};i.setPixel=function(r,t,n){o[t*e+r]=n},i.write=function(r){r.writeString("GIF87a"),r.writeShort(e),r.writeShort(n),r.writeByte(128),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(255),r.writeByte(255),r.writeByte(255),r.writeString(","),r.writeShort(0),r.writeShort(0),r.writeShort(e),r.writeShort(n),r.writeByte(0);var t=2,o=u(t);r.writeByte(t);for(var i=0;o.length-i>255;)r.writeByte(255),r.writeBytes(o,i,255),i+=255;r.writeByte(o.length-i),r.writeBytes(o,i,o.length-i),r.writeByte(0),r.writeString(";")};var a=function(r){var t=r,e=0,n=0,o={};return o.write=function(r,o){if(r>>>o!=0)throw new Error("length over");for(;e+o>=8;)t.writeByte(255&(r<>>=8-e,n=0,e=0;n|=r<0&&t.writeByte(n)},o},u=function(r){for(var t=1<>6,128|63&n):n<55296||n>=57344?t.push(224|n>>12,128|n>>6&63,128|63&n):(e++,n=65536+((1023&n)<<10|1023&r.charCodeAt(e)),t.push(240|n>>18,128|n>>12&63,128|n>>6&63,128|63&n))}return t}return t(r)}}(e),e}(),i=function(r,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;i=Math.max(1,i);for(var a=i;a<=40;a+=1)try{var u=function(){var e=o(a,t);e.addData(r),e.make();var n=e.getModuleCount(),i=function(r,t){return r>=0&&r=0&&t0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"L",e=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,o=i(r,t,e);if(o){var a=o.isDark;o.moduleCount+=2*n,o.isDark=function(r,t){return a(r-n,t-n)}}return o};r.exports=a},function(r,t,e){"use strict";var n=e(5),o=e(6),i=function(r,t){r.fillStyle=t.back,r.fillRect(0,0,t.size,t.size)},a=function(r,t,e,n,o,i){r.isDark(o,i)&&t.rect(i*n,o*n,n,n)},u=function(r,t,e){if(r){var o=e.rounded>0&&e.rounded<=100?n:a,i=r.moduleCount,u=e.size/i,f=0;e.crisp&&(u=Math.floor(u),f=Math.floor((e.size-u*i)/2)),t.translate(f,f),t.beginPath();for(var c=0;c))|((magnet):[\w?=&.\/-;#@~%+*-]+))/ig, '$1' ); - } + }; /** * minimal sprintf emulation for %s and %d formats @@ -172,7 +174,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { ++i; return val; }); - } + }; /** * get value of cookie, if it was set, empty string otherwise @@ -198,7 +200,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } } return ''; - } + }; /** * get the current location (without search or hash part of the URL), @@ -217,7 +219,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { baseUri = window.location.origin + window.location.pathname; return baseUri; - } + }; /** * resets state, used for unit testing @@ -228,7 +230,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.reset = function() { baseUri = null; - } + }; return me; })(); @@ -293,7 +295,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me._ = function() { return me.translate.apply(this, arguments); - } + }; /** * translate a string @@ -330,7 +332,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var usesPlurals = $.isArray(args[0]); if (usesPlurals) { // use the first plural form as messageId, otherwise the singular - messageId = (args[0].length > 1 ? args[0][1] : args[0][0]); + messageId = args[0].length > 1 ? args[0][1] : args[0][0]; } else { messageId = args[0]; } @@ -397,7 +399,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } return output; - } + }; /** * per language functions to use to determine the plural form @@ -414,18 +416,18 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { case 'fr': case 'oc': case 'zh': - return (n > 1 ? 1 : 0); + return n > 1 ? 1 : 0; case 'pl': - return (n === 1 ? 0 : (n % 10 >= 2 && n %10 <=4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)); + return n === 1 ? 0 : (n % 10 >= 2 && n %10 <=4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); case 'ru': - return (n % 10 === 1 && n % 100 !== 11 ? 0 : (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)); + return n % 10 === 1 && n % 100 !== 11 ? 0 : (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); case 'sl': - return (n % 100 === 1 ? 1 : (n % 100 === 2 ? 2 : (n % 100 === 3 || n % 100 === 4 ? 3 : 0))); + return n % 100 === 1 ? 1 : (n % 100 === 2 ? 2 : (n % 100 === 3 || n % 100 === 4 ? 3 : 0)); // de, en, es, it, no, pt default: - return (n !== 1 ? 1 : 0); + return n !== 1 ? 1 : 0; } - } + }; /** * load translations into cache @@ -469,7 +471,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { console.error('Language \'%s\' could not be loaded (%s: %s). Translation failed, fallback to English.', newLanguage, textStatus, errorMsg); language = 'en'; }); - } + }; /** * resets state, used for unit testing @@ -481,7 +483,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { language = mockLanguage || null; translations = mockTranslations || {}; - } + }; return me; })(); @@ -546,7 +548,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { return sjcl.encrypt(key, compress(message), options); } return sjcl.encrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), compress(message), options); - } + }; /** * decrypt message with key, then decompress @@ -556,7 +558,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @param {string} key * @param {string} password * @param {string} data - JSON with encrypted data - * @return {string} decrypted message + * @return {string} decrypted message, empty if decryption failed */ me.decipher = function(key, password, data) { @@ -567,12 +569,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { try { return decompress(sjcl.decrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), data)); } catch(e) { - // ignore error, because ????? @TODO + return ''; } } } - return ''; - } + }; /** * checks whether the crypt tool has collected enough entropy @@ -584,7 +585,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.isEntropyReady = function() { return sjcl.random.isReady(); - } + }; /** * add a listener function, triggered when enough entropy is available @@ -596,7 +597,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.addEntropySeedListener = function(func) { sjcl.random.addEventListener('seeded', func); - } + }; /** * returns a random symmetric key @@ -608,7 +609,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getSymmetricKey = function() { return sjcl.codec.base64.fromBits(sjcl.random.randomWords(8, 0), 0); - } + }; return me; })(); @@ -633,12 +634,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @name Model.getExpirationDefault * @function * @return string - * @TODO the template can be simplified as #pasteExpiration is no longer modified (only default value) */ me.getExpirationDefault = function() { return $('#pasteExpiration').val(); - } + }; /** * returns the format set in the HTML @@ -646,12 +646,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @name Model.getFormatDefault * @function * @return string - * @TODO the template can be simplified as #pasteFormatter is no longer modified (only default value) */ me.getFormatDefault = function() { return $('#pasteFormatter').val(); - } + }; /** * check if cipher data was supplied @@ -662,8 +661,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ me.hasCipherData = function() { - return (me.getCipherData().length > 0); - } + return me.getCipherData().length > 0; + }; /** * returns the cipher data @@ -675,7 +674,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getCipherData = function() { return $cipherData.text(); - } + }; /** * get the pastes unique identifier from the URL, @@ -697,7 +696,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } return id; - } + }; /** * return the deciphering key stored in anchor part of the URL @@ -726,7 +725,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } return symmetricKey; - } + }; /** * returns a jQuery copy of the HTML template @@ -742,7 +741,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var $element = $templates.find('#' + name + 'template').clone(true); // change ID to avoid collisions (one ID should really be unique) return $element.prop('id', name); - } + }; /** * resets state, used for unit testing @@ -753,7 +752,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.reset = function() { $cipherData = $templates = id = symmetricKey = null; - } + }; /** * init navigation manager @@ -767,7 +766,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { $cipherData = $('#cipherdata'); $templates = $('#templates'); - } + }; return me; })(); @@ -816,7 +815,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.reloadHome = function() { window.location.href = Helper.baseUri(); - } + }; /** * checks whether the element is currently visible in the viewport (so @@ -833,8 +832,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var viewportTop = $(window).scrollTop(); var viewportBottom = viewportTop + $(window).height(); - return (elementTop > viewportTop && elementTop < viewportBottom); - } + return elementTop > viewportTop && elementTop < viewportBottom; + }; /** * scrolls to a specific element @@ -887,7 +886,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } next(); }); - } + }; /** * trigger a history (pop) state change @@ -904,7 +903,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { state = null; } historyChange($.Event('popstate', {originalEvent: new PopStateEvent('popstate', {state: state}), target: window})); - } + }; /** * initialize @@ -918,7 +917,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $('.reloadlink').prop('href', Helper.baseUri()); $(window).on('popstate', historyChange); - } + }; return me; })(); @@ -976,7 +975,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { if (typeof customHandler === 'function') { var handlerResult = customHandler(alertType[id], $element, args, icon); if (handlerResult === true) { - // if it returs true, skip own handler + // if it returns true, skip own handler return; } if (handlerResult instanceof jQuery) { @@ -1025,20 +1024,12 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @param {string|array} message string, use an array for %s/%d options * @param {string|null} icon optional, the icon to show, * default: leave previous icon - * @param {bool} dismissable optional, whether the notification - * can be dismissed (closed), default: false - * @param {bool|int} autoclose optional, after how many seconds the - * notification should be hidden automatically; - * default: disabled (0); use true for default value */ - me.showStatus = function(message, icon, dismissable, autoclose) + me.showStatus = function(message, icon) { console.info('status shown: ', message); - // @TODO: implement dismissable - // @TODO: implement autoclose - handleNotification(1, $statusMessage, message, icon); - } + }; /** * display an error message @@ -1050,20 +1041,12 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @param {string|array} message string, use an array for %s/%d options * @param {string|null} icon optional, the icon to show, default: * leave previous icon - * @param {bool} dismissable optional, whether the notification - * can be dismissed (closed), default: false - * @param {bool|int} autoclose optional, after how many seconds the - * notification should be hidden automatically; - * default: disabled (0); use true for default value */ - me.showError = function(message, icon, dismissable, autoclose) + me.showError = function(message, icon) { console.error('error message shown: ', message); - // @TODO: implement dismissable (bootstrap add-on has it) - // @TODO: implement autoclose - handleNotification(3, $errorMessage, message, icon); - } + }; /** * display remaining message @@ -1078,7 +1061,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { console.info('remaining message shown: ', message); handleNotification(1, $remainingTime, message); - } + }; /** * shows a loading message, optionally with a percentage @@ -1088,10 +1071,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @name Alert.showLoading * @function * @param {string|array|null} message optional, use an array for %s/%d options, default: 'Loading…' - * @param {int} percentage optional, default: null * @param {string|null} icon optional, the icon to show, default: leave previous icon */ - me.showLoading = function(message, percentage, icon) + me.showLoading = function(message, icon) { if (typeof message !== 'undefined' && message !== null) { console.info('status changed: ', message); @@ -1102,14 +1084,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { message = 'Loading…'; } - // currently percentage parameter is ignored - // // @TODO handle it here… - handleNotification(0, $loadingIndicator, message, icon); // show loading status (cursor) $('body').addClass('loading'); - } + }; /** * hides the loading message @@ -1123,7 +1102,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // hide loading cursor $('body').removeClass('loading'); - } + }; /** * hides any status/error messages @@ -1138,7 +1117,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // also possible: $('.statusmessage').addClass('hidden'); $statusMessage.addClass('hidden'); $errorMessage.addClass('hidden'); - } + }; /** * set a custom handler, which gets all notifications. @@ -1161,7 +1140,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.setCustomHandler = function(newHandler) { customHandler = newHandler; - } + }; /** * init status manager @@ -1188,7 +1167,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { '', // reserved for warning, not used yet 'glyphicon-alert' // error icon ]; - } + }; return me; })(); @@ -1213,12 +1192,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @name PasteStatus.sendToShortener * @private * @function - * @param {Event} event */ - function sendToShortener(event) + function sendToShortener() { - window.location.href = $shortenButton.data('shortener') - + encodeURIComponent($pasteUrl.attr('href')); + window.location.href = $shortenButton.data('shortener') + + encodeURIComponent($pasteUrl.attr('href')); } /** @@ -1229,9 +1207,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * * @name PasteStatus.pasteLinkClick * @function - * @param {Event} event */ - function pasteLinkClick(event) + function pasteLinkClick() { // check if location is (already) shown in URL bar if (window.location.href === $pasteUrl.attr('href')) { @@ -1268,7 +1245,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $pasteSuccess.removeClass('hidden'); // we pre-select the link so that the user only has to [Ctrl]+[c] the link Helper.selectText($pasteUrl[0]); - } + }; /** * shows the remaining time @@ -1308,7 +1285,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // in the end, display notification $remainingTime.removeClass('hidden'); - } + }; /** * hides the remaining time and successful upload notification @@ -1320,7 +1297,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { $remainingTime.addClass('hidden'); $pasteSuccess.addClass('hidden'); - } + }; /** * init status manager @@ -1339,7 +1316,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // bind elements $shortenButton.click(sendToShortener); - } + }; return me; })(); @@ -1408,7 +1385,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } password = newPassword; - } + }; /** * get the cached password @@ -1423,7 +1400,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getPassword = function() { return password; - } + }; /** * init status manager @@ -1447,7 +1424,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { }); // handle Model password submission $passwordForm.submit(submitPasswordModal); - } + }; return me; })(); @@ -1563,7 +1540,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.isPreview = function() { return isPreview; - } + }; /** * reset the Editor view @@ -1580,7 +1557,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // clear content $message.val(''); - } + }; /** * shows the Editor @@ -1592,7 +1569,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { $message.removeClass('hidden'); $editorTabs.removeClass('hidden'); - } + }; /** * hides the Editor @@ -1604,7 +1581,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { $message.addClass('hidden'); $editorTabs.addClass('hidden'); - } + }; /** * focuses the message input @@ -1615,7 +1592,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.focusInput = function() { $message.focus(); - } + }; /** * sets a new text @@ -1627,7 +1604,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.setText = function(newText) { $message.val(newText); - } + }; /** * returns the current text @@ -1638,8 +1615,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ me.getText = function() { - return $message.val() - } + return $message.val(); + }; /** * init status manager @@ -1661,7 +1638,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // (li) $messageEdit = $('#messageedit').click(viewEditor).parent(); $messagePreview = $('#messagepreview').click(viewPreview).parent(); - } + }; return me; })(); @@ -1699,8 +1676,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { return; } - // set sanitized and linked text - var sanitizedLinkedText = DOMPurify.sanitize(Helper.urls2links(text), {SAFE_FOR_JQUERY: true}); + // escape HTML entities, link URLs, sanitize + var escapedLinkedText = Helper.urls2links( + $('
').text(text).html() + ), + sanitizedLinkedText = DOMPurify.sanitize(escapedLinkedText); $plainText.html(sanitizedLinkedText); $prettyPrint.html(sanitizedLinkedText); @@ -1713,7 +1693,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { }); // let showdown convert the HTML and sanitize HTML *afterwards*! $plainText.html( - DOMPurify.sanitize(converter.makeHtml(text), {SAFE_FOR_JQUERY: true}) + DOMPurify.sanitize(converter.makeHtml(text)) ); // add table classes from bootstrap css $plainText.find('table').addClass('table-condensed table-bordered'); @@ -1727,8 +1707,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $prettyPrint.html( DOMPurify.sanitize( - prettyPrintOne(Helper.urls2links(text), null, true), - {SAFE_FOR_JQUERY: true} + prettyPrintOne(escapedLinkedText, null, true) ) ); // fall through, as the rest is the same @@ -1750,11 +1729,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { // instead of "nothing" better display a placeholder if (text === '') { - $placeholder.removeClass('hidden') + $placeholder.removeClass('hidden'); return; } // otherwise hide the placeholder - $placeholder.addClass('hidden') + $placeholder.addClass('hidden'); switch (format) { case 'markdown': @@ -1789,7 +1768,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { format = newFormat; isChanged = true; - } + }; /** * returns the current format @@ -1801,7 +1780,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getFormat = function() { return format; - } + }; /** * returns whether the current view is pretty printed @@ -1813,7 +1792,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.isPrettyPrinted = function() { return $prettyPrint.hasClass('prettyprinted'); - } + }; /** * sets the text to show @@ -1828,7 +1807,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { text = newText; isChanged = true; } - } + }; /** * gets the current cached text @@ -1840,7 +1819,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getText = function() { return text; - } + }; /** * show/update the parsed text (preview) @@ -1859,7 +1838,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { showPaste(); isDisplayed = true; } - } + }; /** * hide parsed text (preview) @@ -1878,7 +1857,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $placeholder.addClass('hidden'); isDisplayed = false; - } + }; /** * init status manager @@ -1914,7 +1893,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { text = ''; isDisplayed = false; isChanged = true; - } + }; return me; })(); @@ -1923,11 +1902,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * (view) Show attachment and preview if possible * * @name AttachmentViewer - * @param {object} window - * @param {object} document * @class */ - var AttachmentViewer = (function (window, document) { + var AttachmentViewer = (function () { var me = {}; var $attachmentLink, @@ -1962,7 +1939,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { ); attachmentHasPreview = true; } - } + }; /** * displays the attachment @@ -1977,7 +1954,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { if (attachmentHasPreview) { $attachmentPreview.removeClass('hidden'); } - } + }; /** * removes the attachment @@ -1995,7 +1972,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $attachmentLink.prop('href', ''); $attachmentLink.prop('download', ''); $attachmentPreview.html(''); - } + }; /** * hides the attachment @@ -2010,7 +1987,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.hideAttachment = function() { $attachment.addClass('hidden'); - } + }; /** * hides the attachment preview @@ -2021,7 +1998,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.hideAttachmentPreview = function() { $attachmentPreview.addClass('hidden'); - } + }; /** * checks if there is an attachment @@ -2032,8 +2009,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.hasAttachment = function() { var link = $attachmentLink.prop('href'); - return (typeof link !== 'undefined' && link !== '') - } + return typeof link !== 'undefined' && link !== ''; + }; /** * return the attachment @@ -2048,7 +2025,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $attachmentLink.prop('href'), $attachmentLink.prop('download') ]; - } + }; /** * moves the attachment link to another element @@ -2067,7 +2044,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // update text I18n._($attachmentLink, label, $attachmentLink.attr('download')); - } + }; /** * initiate @@ -2082,20 +2059,19 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $attachment = $('#attachment'); $attachmentLink = $('#attachment a'); $attachmentPreview = $('#attachmentPreview'); - } + attachmentHasPreview = false; + }; return me; - })(window, document); + })(); /** * (view) Shows discussion thread and handles replies * * @name DiscussionViewer - * @param {object} window - * @param {object} document * @class */ - var DiscussionViewer = (function (window, document) { + var DiscussionViewer = (function () { var me = {}; var $commentTail, @@ -2161,12 +2137,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @name DiscussionViewer.handleNotification * @function * @param {string} alertType - * @param {jQuery} $element - * @param {string|array} args - * @param {string|null} icon * @return {bool|jQuery} */ - me.handleNotification = function(alertType, $element, args, icon) + me.handleNotification = function(alertType) { // ignore loading messages if (alertType === 'loading') { @@ -2186,7 +2159,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } return $replyStatus; - } + }; /** * adds another comment @@ -2195,22 +2168,10 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @function * @param {object} comment * @param {string} commentText - * @param {jQuery} $place - optional, tries to find the best position otherwise + * @param {string} nickname */ - me.addComment = function(comment, commentText, nickname, $place) + me.addComment = function(comment, commentText, nickname) { - if (typeof $place === 'undefined') { - // starting point (default value/fallback) - $place = $commentContainer; - - // if parent comment exists - var $parentComment = $('#comment_' + comment.parentid); - if ($parentComment.length) { - // use parent as position for noew comment, so it shifted - // to the right - $place = $parentComment; - } - } if (commentText === '') { commentText = 'comment decryption failed'; } @@ -2223,8 +2184,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // set & parse text $commentEntryData.html( DOMPurify.sanitize( - Helper.urls2links(commentText), - {SAFE_FOR_JQUERY: true} + Helper.urls2links(commentText) ) ); @@ -2253,9 +2213,20 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { }); } + // starting point (default value/fallback) + var $place = $commentContainer; + + // if parent comment exists + var $parentComment = $('#comment_' + comment.parentid); + if ($parentComment.length) { + // use parent as position for new comment, so it is shifted + // to the right + $place = $parentComment; + } + // finally append comment $place.append($commentEntry); - } + }; /** * finishes the discussion area after last comment @@ -2270,49 +2241,59 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // show discussions $discussion.removeClass('hidden'); - } - - /** - * shows the discussion area - * - * @name DiscussionViewer.showDiscussion - * @function - */ - me.showDiscussion = function() - { - $discussion.removeClass('hidden'); - } + }; /** * removes the old discussion and prepares everything for creating a new * one. * - * @name DiscussionViewer.prepareNewDisucssion + * @name DiscussionViewer.prepareNewDiscussion * @function */ - me.prepareNewDisucssion = function() + me.prepareNewDiscussion = function() { $commentContainer.html(''); $discussion.addClass('hidden'); // (re-)init templates initTemplates(); - } + }; /** - * returns the user put into the reply form + * returns the users message from the reply form * - * @name DiscussionViewer.getReplyData + * @name DiscussionViewer.getReplyMessage * @function - * @return {array} + * @return {String} */ - me.getReplyData = function() + me.getReplyMessage = function() { - return [ - $replyMessage.val(), - $replyNickname.val() - ]; - } + return $replyMessage.val(); + }; + + /** + * returns the users nickname (if any) from the reply form + * + * @name DiscussionViewer.getReplyNickname + * @function + * @return {String} + */ + me.getReplyNickname = function() + { + return $replyNickname.val(); + }; + + /** + * returns the id of the parent comment the user is replying to + * + * @name DiscussionViewer.getReplyCommentId + * @function + * @return {int|undefined} + */ + me.getReplyCommentId = function() + { + return replyCommentId; + }; /** * highlights a specific comment and scrolls to it if necessary @@ -2337,26 +2318,14 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $comment.removeClass('highlight'); }, 300); } - } + }; if (UiHelper.isVisible($comment)) { return highlightComment(); } UiHelper.scrollTo($comment, 100, 'swing', highlightComment); - } - - /** - * returns the id of the parent comment the user is replying to - * - * @name DiscussionViewer.getReplyCommentId - * @function - * @return {int|undefined} - */ - me.getReplyCommentId = function() - { - return replyCommentId; - } + }; /** * initiate @@ -2374,10 +2343,10 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $commentContainer = $('#commentcontainer'); $discussion = $('#discussion'); - } + }; return me; - })(window, document); + })(); /** * Manage top (navigation) bar @@ -2408,6 +2377,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $password, $passwordInput, $rawTextButton, + $qrCodeLink, $sendButton; var pasteExpiration = '1week'; @@ -2504,12 +2474,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @name TopNav.rawText * @private * @function - * @param {Event} event */ - function rawText(event) + function rawText() { TopNav.hideAllButtons(); - Alert.showLoading('Showing raw text…', 0, 'time'); + Alert.showLoading('Showing raw text…', 'time'); var paste = PasteViewer.getText(); // push a new state to allow back navigation with browser back button @@ -2529,7 +2498,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { for (var i = 0; i < $head.length; i++) { newDoc.write($head[i].outerHTML); } - newDoc.write('
' + DOMPurify.sanitize(paste, {SAFE_FOR_JQUERY: true}) + '
'); + newDoc.write('
' + DOMPurify.sanitize(paste) + '
'); newDoc.close(); } @@ -2553,9 +2522,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @name TopNav.clickNewPaste * @private * @function - * @param {Event} event */ - function clickNewPaste(event) + function clickNewPaste() { Controller.hideStatusMessages(); Controller.newPaste(); @@ -2585,6 +2553,21 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { event.preventDefault(); } + /** + * Shows the QR code of the current paste (URL). + * + * @name TopNav.displayQrCode + * @function + */ + function displayQrCode() + { + var qrCanvas = kjua({ + render: 'canvas', + text: window.location.href + }); + $('#qrcode-display').html(qrCanvas); + } + /** * Shows all elements belonging to viwing an existing pastes * @@ -2601,9 +2584,10 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $newButton.removeClass('hidden'); $cloneButton.removeClass('hidden'); $rawTextButton.removeClass('hidden'); + $qrCodeLink.removeClass('hidden'); viewButtonsDisplayed = true; - } + }; /** * Hides all elements belonging to existing pastes @@ -2621,9 +2605,10 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $newButton.addClass('hidden'); $cloneButton.addClass('hidden'); $rawTextButton.addClass('hidden'); + $qrCodeLink.addClass('hidden'); viewButtonsDisplayed = false; - } + }; /** * Hides all elements belonging to existing pastes @@ -2635,7 +2620,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { me.hideViewButtons(); me.hideCreateButtons(); - } + }; /** * shows all elements needed when creating a new paste @@ -2660,7 +2645,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $attach.removeClass('hidden'); createButtonsDisplayed = true; - } + }; /** * shows all elements needed when creating a new paste @@ -2685,7 +2670,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $attach.addClass('hidden'); createButtonsDisplayed = false; - } + }; /** * only shows the "new paste" button @@ -2696,7 +2681,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.showNewPasteButton = function() { $newButton.removeClass('hidden'); - } + }; /** * only hides the clone button @@ -2707,7 +2692,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.hideCloneButton = function() { $cloneButton.addClass('hidden'); - } + }; /** * only hides the raw text button @@ -2718,7 +2703,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.hideRawButton = function() { $rawTextButton.addClass('hidden'); - } + }; /** * hides the file selector in attachment @@ -2729,7 +2714,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.hideFileSelector = function() { $fileWrap.addClass('hidden'); - } + }; /** @@ -2741,7 +2726,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.showCustomAttachment = function() { $customAttachment.removeClass('hidden'); - } + }; /** * collapses the navigation bar if nedded @@ -2758,7 +2743,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // if so, toggle it $bar.click(); } - } + }; /** * returns the currently set expiration time @@ -2770,7 +2755,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getExpiration = function() { return pasteExpiration; - } + }; /** * returns the currently selected file(s) @@ -2787,13 +2772,14 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { if (!$file.length || !$file[0].files.length) { return null; } - // @TODO is this really necessary + + // ensure the selected file is still accessible if (!($file[0].files && $file[0].files[0])) { return null; } return $file[0].files; - } + }; /** * returns the state of the burn after reading checkbox @@ -2805,7 +2791,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getBurnAfterReading = function() { return $burnAfterReading.is(':checked'); - } + }; /** * returns the state of the discussion checkbox @@ -2817,7 +2803,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getOpenDiscussion = function() { return $openDiscussion.is(':checked'); - } + }; /** * returns the entered password @@ -2829,7 +2815,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getPassword = function() { return $passwordInput.val(); - } + }; /** * returns the element where custom attachments can be placed @@ -2843,7 +2829,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.getCustomAttachment = function() { return $customAttachment; - } + }; /** * init navigation manager @@ -2871,6 +2857,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $passwordInput = $('#passwordinput'); $rawTextButton = $('#rawtextbutton'); $sendButton = $('#sendbutton'); + $qrCodeLink = $('#qrcodelink'); // bootstrap template drop down $('#language ul.dropdown-menu li a').click(setLanguage); @@ -2885,6 +2872,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $cloneButton.click(Controller.clonePaste); $rawTextButton.click(rawText); $fileRemoveButton.click(removeAttachment); + $qrCodeLink.click(displayQrCode); // bootstrap template drop downs $('ul.dropdown-menu li a', $('#expiration').parent()).click(updateExpiration); @@ -2896,7 +2884,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // get default value from template or fall back to set value pasteExpiration = Model.getExpirationDefault() || pasteExpiration; - } + }; return me; })(window, document); @@ -3038,7 +3026,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { console.error(textStatus, errorThrown); fail(3, jqXHR); }); - } + }; /** * set success function @@ -3050,7 +3038,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.setUrl = function(newUrl) { url = newUrl; - } + }; /** * sets the password to use (first value) and optionally also the @@ -3070,7 +3058,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { if (typeof newKey !== 'undefined') { symmetricKey = newKey; } - } + }; /** * set success function @@ -3082,7 +3070,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.setSuccess = function(func) { successFunc = func; - } + }; /** * set failure function @@ -3094,7 +3082,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.setFailure = function(func) { failureFunc = func; - } + }; /** * prepares a new upload @@ -3122,7 +3110,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { failureFunc = null; url = Helper.baseUri(); data = {}; - } + }; /** * encrypts and sets the data @@ -3136,7 +3124,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { checkCryptParameters(); data[index] = CryptTool.cipher(symmetricKey, password, element); - } + }; /** * set the additional metadata to send unencrypted @@ -3149,7 +3137,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.setUnencryptedData = function(index, element) { data[index] = element; - } + }; /** * set the additional metadata to send unencrypted passed at once @@ -3161,7 +3149,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.setUnencryptedBulkData = function(newData) { $.extend(data, newData); - } + }; /** * Helper, which parses shows a general error message based on the result of the Uploader @@ -3177,13 +3165,13 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var errorArray; switch (status) { - case me.error['custom']: + case me.error.custom: errorArray = ['Could not ' + doThisThing + ': %s', data.message]; break; - case me.error['unknown']: + case me.error.unknown: errorArray = ['Could not ' + doThisThing + ': %s', I18n._('unknown status')]; break; - case me.error['serverError']: + case me.error.serverError: errorArray = ['Could not ' + doThisThing + ': %s', I18n._('server error or not responding')]; break; default: @@ -3192,7 +3180,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } return errorArray; - } + }; /** * init Uploader @@ -3203,7 +3191,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.init = function() { // nothing yet - } + }; return me; })(); @@ -3267,7 +3255,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { Alert.hideMessages(); // show notification - PasteStatus.createPasteNotification(url, deleteUrl) + PasteStatus.createPasteNotification(url, deleteUrl); // show new URL in browser bar history.pushState({type: 'newpaste'}, document.title, url); @@ -3292,7 +3280,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ function showUploadedComment(status, data) { // show success message - // Alert.showStatus('Comment posted.'); + Alert.showStatus('Comment posted.'); // reload paste Controller.refreshPaste(function () { @@ -3330,7 +3318,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // run callback return callback(); - } + }; // actually read first file reader.readAsDataURL(file); @@ -3360,12 +3348,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // UI loading state TopNav.hideAllButtons(); - Alert.showLoading('Sending comment…', 0, 'cloud-upload'); + Alert.showLoading('Sending comment…', 'cloud-upload'); - // get data, note that "var [x, y] = " structures aren't supported in all JS environments - var replyData = DiscussionViewer.getReplyData(), - plainText = replyData[0], - nickname = replyData[1], + // get data + var plainText = DiscussionViewer.getReplyMessage(), + nickname = DiscussionViewer.getReplyNickname(), parentid = DiscussionViewer.getReplyCommentId(); // do not send if there is no data @@ -3383,7 +3370,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { })) { return; // to prevent multiple executions } - Alert.showLoading(null, 10); // prepare Uploader Uploader.prepare(); @@ -3397,7 +3383,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { TopNav.showViewButtons(); // show error message - Alert.showError(Uploader.parseUploadError(status, data, 'post comment')); + Alert.showError( + Uploader.parseUploadError(status, data, 'post comment') + ); // reset error handler Alert.setCustomHandler(null); @@ -3407,7 +3395,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { Uploader.setUnencryptedData('pasteid', Model.getPasteId()); if (typeof parentid === 'undefined') { // if parent id is not set, this is the top-most comment, so use - // paste id as parent @TODO is this really good? + // paste id as parent, as the root element of the discussion tree Uploader.setUnencryptedData('parentid', Model.getPasteId()); } else { Uploader.setUnencryptedData('parentid', parentid); @@ -3421,7 +3409,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } Uploader.run(); - } + }; /** * sends a new paste to server @@ -3436,7 +3424,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // UI loading state TopNav.hideAllButtons(); - Alert.showLoading('Sending paste…', 0, 'cloud-upload'); + Alert.showLoading('Sending paste…', 'cloud-upload'); TopNav.collapseBar(); // get data @@ -3452,8 +3440,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { return; } - Alert.showLoading(null, 10); - // check entropy if (!checkRequirements(function () { me.sendPaste(); @@ -3473,7 +3459,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { TopNav.showCreateButtons(); // show error message - Alert.showError(Uploader.parseUploadError(status, data, 'create paste')); + Alert.showError( + Uploader.parseUploadError(status, data, 'create paste') + ); }); // fill it with unencrypted submitted options @@ -3499,7 +3487,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { Uploader.run(); } ); - } + }; /** * initialize @@ -3510,7 +3498,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.init = function() { // nothing yet - } + }; return me; })(); @@ -3582,14 +3570,14 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ function decryptPaste(paste, key, password, ignoreError) { - var plaintext + var plaintext; if (ignoreError === true) { plaintext = CryptTool.decipher(key, password, paste.data); } else { try { plaintext = decryptOrPromptPassword(key, password, paste.data); } catch (err) { - throw 'failed to decipher paste text: ' + err + throw 'failed to decipher paste text: ' + err; } if (plaintext === false) { return false; @@ -3619,23 +3607,24 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ function decryptAttachment(paste, key, password) { + var attachment, attachmentName; + // decrypt attachment try { - var attachment = decryptOrPromptPassword(key, password, paste.attachment); + attachment = decryptOrPromptPassword(key, password, paste.attachment); } catch (err) { - throw 'failed to decipher attachment: ' + err + throw 'failed to decipher attachment: ' + err; } if (attachment === false) { return false; } // decrypt attachment name - var attachmentName; if (paste.attachmentname) { try { attachmentName = decryptOrPromptPassword(key, password, paste.attachmentname); } catch (err) { - throw 'failed to decipher attachment name: ' + err + throw 'failed to decipher attachment name: ' + err; } if (attachmentName === false) { return false; @@ -3662,7 +3651,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { function decryptComments(paste, key, password) { // remove potentially previous discussion - DiscussionViewer.prepareNewDisucssion(); + DiscussionViewer.prepareNewDiscussion(); // iterate over comments for (var i = 0; i < paste.comments.length; ++i) { @@ -3676,7 +3665,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } DiscussionViewer.finishDiscussion(); - DiscussionViewer.showDiscussion(); return true; } @@ -3690,7 +3678,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.run = function(paste) { Alert.hideMessages(); - Alert.showLoading('Decrypting paste…', 0, 'cloud-download'); // @TODO icon maybe rotation-lock, but needs full Glyphicons + Alert.showLoading('Decrypting paste…', 'cloud-download'); if (typeof paste === 'undefined') { paste = $.parseJSON(Model.getCipherData()); @@ -3700,7 +3688,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { password = Prompt.getPassword(); if (PasteViewer.isPrettyPrinted()) { - console.error('Too pretty! (don\'t know why this check)'); //@TODO + // don't decrypt twice return; } @@ -3737,7 +3725,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { console.error(err); Alert.showError('Could not decrypt data (Wrong key?)'); } - } + }; /** * initialize @@ -3748,7 +3736,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.init = function() { // nothing yet - } + }; return me; })(); @@ -3774,7 +3762,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { { PasteStatus.hideMessages(); Alert.hideMessages(); - } + }; /** * creates a new paste @@ -3787,7 +3775,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // Important: This *must not* run Alert.hideMessages() as previous // errors from viewing a paste should be shown. TopNav.hideAllButtons(); - Alert.showLoading('Preparing new paste…', 0, 'time'); + Alert.showLoading('Preparing new paste…', 'time'); PasteStatus.hideMessages(); PasteViewer.hide(); @@ -3797,7 +3785,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { TopNav.showCreateButtons(); Alert.hideLoading(); - } + }; /** * shows the loaded paste @@ -3816,14 +3804,13 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // missing decryption key (or paste ID) in URL? if (window.location.hash.length === 0) { Alert.showError('Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)'); - // @TODO adjust error message as it is less specific now, probably include thrown exception for a detailed error return; } } // show proper elements on screen PasteDecrypter.run(); - } + }; /** * refreshes the loaded paste to show potential new data @@ -3846,8 +3833,10 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { TopNav.showViewButtons(); // show error message - Alert.showError(Uploader.parseUploadError(status, data, 'refresh display')); - }) + Alert.showError( + Uploader.parseUploadError(status, data, 'refresh display') + ); + }); Uploader.setSuccess(function (status, data) { PasteDecrypter.run(data); @@ -3855,22 +3844,21 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { window.scrollTo(0, orgPosition); callback(); - }) + }); Uploader.run(); - } + }; /** * clone the current paste * * @name Controller.clonePaste * @function - * @param {Event} event */ - me.clonePaste = function(event) + me.clonePaste = function() { TopNav.collapseBar(); TopNav.hideAllButtons(); - Alert.showLoading('Cloning paste…', 0, 'transfer'); + Alert.showLoading('Cloning paste…', 'transfer'); // hide messages from previous paste me.hideStatusMessages(); @@ -3896,16 +3884,18 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { [ 'The cloned file \'%s\' was attached to this paste.', AttachmentViewer.getAttachment()[1] - ], 'copy', true, true); + ], + 'copy' + ); } - Editor.setText(PasteViewer.getText()) + Editor.setText(PasteViewer.getText()); PasteViewer.hide(); Editor.show(); Alert.hideLoading(); TopNav.showCreateButtons(); - } + }; /** * removes a saved paste @@ -3923,10 +3913,12 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { Uploader.setUnencryptedData('deletetoken', deleteToken); Uploader.setFailure(function () { - Alert.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(); - } + }; /** * application start @@ -3939,10 +3931,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // first load translations I18n.loadTranslations(); + DOMPurify.setConfig({SAFE_FOR_JQUERY: true}); + // initialize other modules/"classes" Alert.init(); Model.init(); - AttachmentViewer.init(); DiscussionViewer.init(); Editor.init(); @@ -3962,7 +3955,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // otherwise create a new paste me.newPaste(); - } + }; return me; })(window, document); @@ -3986,4 +3979,4 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { PasteDecrypter: PasteDecrypter, Controller: Controller }; -}(jQuery, sjcl, Base64, RawDeflate); +})(jQuery, sjcl, Base64, RawDeflate); diff --git a/js/purify-1.0.3.js b/js/purify-1.0.3.js new file mode 100644 index 00000000..b5368e39 --- /dev/null +++ b/js/purify-1.0.3.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.DOMPurify=t()}(this,function(){"use strict";function e(e,t){for(var n=t.length;n--;)"string"==typeof t[n]&&(t[n]=t[n].toLowerCase()),e[t[n]]=!0;return e}function t(e){var t={},n=void 0;for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t}function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:A(),S=function(e){return o(e)};if(S.version="1.1.1",S.removed=[],!x||!x.document||9!==x.document.nodeType)return S.isSupported=!1,S;var k=x.document,w=!1,E=!1,O=x.document,L=x.DocumentFragment,M=x.HTMLTemplateElement,N=x.Node,_=x.NodeFilter,D=x.NamedNodeMap,R=void 0===D?x.NamedNodeMap||x.MozNamedAttrMap:D,C=x.Text,F=x.Comment,z=x.DOMParser,H=x.XMLHttpRequest,I=void 0===H?x.XMLHttpRequest:H,j=x.encodeURI,U=void 0===j?x.encodeURI:j;if("function"==typeof M){var W=O.createElement("template");W.content&&W.content.ownerDocument&&(O=W.content.ownerDocument)}var q=O,G=q.implementation,P=q.createNodeIterator,B=q.getElementsByTagName,X=q.createDocumentFragment,V=k.importNode,Y={};S.isSupported=G&&void 0!==G.createHTMLDocument&&9!==O.documentMode;var K=p,$=f,J=h,Q=g,Z=v,ee=b,te=y,ne=null,oe=e({},[].concat(n(r),n(i),n(a),n(l),n(s))),re=null,ie=e({},[].concat(n(c),n(d),n(u),n(m))),ae=null,le=null,se=!0,ce=!0,de=!1,ue=!1,me=!1,pe=!1,fe=!1,he=!1,ge=!1,ye=!1,ve=!1,be=!0,Te=!0,Ae={},xe=e({},["audio","head","math","script","style","template","svg","video"]),Se=e({},["audio","video","img","source","image"]),ke=e({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),we=null,Ee=O.createElement("form"),Oe=function(o){"object"!==(void 0===o?"undefined":T(o))&&(o={}),ne="ALLOWED_TAGS"in o?e({},o.ALLOWED_TAGS):oe,re="ALLOWED_ATTR"in o?e({},o.ALLOWED_ATTR):ie,ae="FORBID_TAGS"in o?e({},o.FORBID_TAGS):{},le="FORBID_ATTR"in o?e({},o.FORBID_ATTR):{},Ae="USE_PROFILES"in o&&o.USE_PROFILES,se=!1!==o.ALLOW_ARIA_ATTR,ce=!1!==o.ALLOW_DATA_ATTR,de=o.ALLOW_UNKNOWN_PROTOCOLS||!1,ue=o.SAFE_FOR_JQUERY||!1,me=o.SAFE_FOR_TEMPLATES||!1,pe=o.WHOLE_DOCUMENT||!1,ge=o.RETURN_DOM||!1,ye=o.RETURN_DOM_FRAGMENT||!1,ve=o.RETURN_DOM_IMPORT||!1,he=o.FORCE_BODY||!1,be=!1!==o.SANITIZE_DOM,Te=!1!==o.KEEP_CONTENT,te=o.ALLOWED_URI_REGEXP||te,me&&(ce=!1),ye&&(ge=!0),Ae&&(ne=e({},[].concat(n(s))),re=[],!0===Ae.html&&(e(ne,r),e(re,c)),!0===Ae.svg&&(e(ne,i),e(re,d),e(re,m)),!0===Ae.svgFilters&&(e(ne,a),e(re,d),e(re,m)),!0===Ae.mathMl&&(e(ne,l),e(re,u),e(re,m))),o.ADD_TAGS&&(ne===oe&&(ne=t(ne)),e(ne,o.ADD_TAGS)),o.ADD_ATTR&&(re===ie&&(re=t(re)),e(re,o.ADD_ATTR)),o.ADD_URI_SAFE_ATTR&&e(ke,o.ADD_URI_SAFE_ATTR),Te&&(ne["#text"]=!0),Object&&"freeze"in Object&&Object.freeze(o),we=o},Le=function(e){S.removed.push({element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=""}},Me=function(e,t){S.removed.push({attribute:t.getAttributeNode(e),from:t}),t.removeAttribute(e)},Ne=function(e){var t=void 0,n=void 0;if(he&&(e=""+e),E){try{e=U(e)}catch(e){}var o=new I;o.responseType="document",o.open("GET","data:text/html;charset=utf-8,"+e,!1),o.send(null),t=o.response}if(w)try{t=(new z).parseFromString(e,"text/html")}catch(e){}return t&&t.documentElement||((n=(t=G.createHTMLDocument("")).body).parentNode.removeChild(n.parentNode.firstElementChild),n.outerHTML=e),B.call(t,pe?"html":"body")[0]};S.isSupported&&function(){var e=Ne('');e.querySelector("svg")||(E=!0);try{(e=Ne('

')).querySelector("svg img")&&(w=!0)}catch(e){}}();var _e=function(e){return P.call(e.ownerDocument||e,e,_.SHOW_ELEMENT|_.SHOW_COMMENT|_.SHOW_TEXT,function(){return _.FILTER_ACCEPT},!1)},De=function(e){return!(e instanceof C||e instanceof F)&&!("string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof R&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute)},Re=function(e){return"object"===(void 0===N?"undefined":T(N))?e instanceof N:e&&"object"===(void 0===e?"undefined":T(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},Ce=function(e,t,n){Y[e]&&Y[e].forEach(function(e){e.call(S,t,n,we)})},Fe=function(e){var t=void 0;if(Ce("beforeSanitizeElements",e,null),De(e))return Le(e),!0;var n=e.nodeName.toLowerCase();if(Ce("uponSanitizeElement",e,{tagName:n,allowedTags:ne}),!ne[n]||ae[n]){if(Te&&!xe[n]&&"function"==typeof e.insertAdjacentHTML)try{e.insertAdjacentHTML("AfterEnd",e.innerHTML)}catch(e){}return Le(e),!0}return!ue||e.firstElementChild||e.content&&e.content.firstElementChild||!/l&&e.setAttribute("id",i.value);else{if("INPUT"===e.nodeName&&"type"===r&&"file"===o&&(re[r]||!le[r]))continue;"id"===n&&e.setAttribute(n,""),Me(n,e)}if(s.keepAttr&&(!be||"id"!==r&&"name"!==r||!(o in O||o in Ee))){if(me&&(o=(o=o.replace(K," ")).replace($," ")),ce&&J.test(r));else if(se&&Q.test(r));else{if(!re[r]||le[r])continue;if(ke[r]);else if(te.test(o.replace(ee,"")));else if("src"!==r&&"xlink:href"!==r||0!==o.indexOf("data:")||!Se[e.nodeName.toLowerCase()]){if(de&&!Z.test(o.replace(ee,"")));else if(o)continue}else;}try{e.setAttribute(n,o),S.removed.pop()}catch(e){}}}Ce("afterSanitizeAttributes",e,null)}},He=function e(t){var n=void 0,o=_e(t);for(Ce("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)Ce("uponSanitizeShadowNode",n,null),Fe(n)||(n.content instanceof L&&e(n.content),ze(n));Ce("afterSanitizeShadowDOM",t,null)};return S.sanitize=function(e,t){var n=void 0,o=void 0,r=void 0,i=void 0,a=void 0;if(e||(e="\x3c!--\x3e"),"string"!=typeof e&&!Re(e)){if("function"!=typeof e.toString)throw new TypeError("toString is not a function");if("string"!=typeof(e=e.toString()))throw new TypeError("dirty is not a string, aborting")}if(!S.isSupported){if("object"===T(x.toStaticHTML)||"function"==typeof x.toStaticHTML){if("string"==typeof e)return x.toStaticHTML(e);if(Re(e))return x.toStaticHTML(e.outerHTML)}return e}if(fe||Oe(t),S.removed=[],e instanceof N)1===(o=(n=Ne("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===o.nodeName?n=o:n.appendChild(o);else{if(!ge&&!pe&&-1===e.indexOf("<"))return e;if(!(n=Ne(e)))return ge?null:""}he&&Le(n.firstChild);for(var l=_e(n);r=l.nextNode();)3===r.nodeType&&r===i||Fe(r)||(r.content instanceof L&&He(r.content),ze(r),i=r);if(ge){if(ye)for(a=X.call(n.ownerDocument);n.firstChild;)a.appendChild(n.firstChild);else a=n;return ve&&(a=V.call(k,a,!0)),a}return pe?n.outerHTML:n.innerHTML},S.setConfig=function(e){Oe(e),fe=!0},S.clearConfig=function(){we=null,fe=!1},S.addHook=function(e,t){"function"==typeof t&&(Y[e]=Y[e]||[],Y[e].push(t))},S.removeHook=function(e){Y[e]&&Y[e].pop()},S.removeHooks=function(e){Y[e]&&(Y[e]=[])},S.removeAllHooks=function(){Y={}},S}var r=["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"],i=["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","audio","canvas","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","video","view","vkern"],a=["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence"],l=["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmuliscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mpspace","msqrt","mystyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover"],s=["#text"],c=["accept","action","align","alt","autocomplete","background","bgcolor","border","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","coords","crossorigin","datetime","default","dir","disabled","download","enctype","face","for","headers","height","hidden","high","href","hreflang","id","integrity","ismap","label","lang","list","loop","low","max","maxlength","media","method","min","multiple","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","type","usemap","valign","value","width","xmlns"],d=["accent-height","accumulate","additivive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","tabindex","targetx","targety","transform","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"],u=["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"],m=["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"],p=/\{\{[\s\S]*|[\s\S]*\}\}/gm,f=/<%[\s\S]*|[\s\S]*%>/gm,h=/^data-[\-\w.\u00B7-\uFFFF]/,g=/^aria-[\-\w]+$/,y=/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,v=/^(?:\w+script|data):/i,b=/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g,T="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},A=function(){return"undefined"==typeof window?null:window};return o()}); diff --git a/js/purify.min.js b/js/purify.min.js deleted file mode 100644 index 5fe41d20..00000000 --- a/js/purify.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.DOMPurify=t()}(this,function(){"use strict";function e(e,t){for(var n=t.length;n--;)"string"==typeof t[n]&&(t[n]=t[n].toLowerCase()),e[t[n]]=!0;return e}function t(e){var t={},n=void 0;for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t}function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:p(),g=function(e){return o(e)};if(g.version="1.0.2",g.removed=[],!h||!h.document||9!==h.document.nodeType)return g.isSupported=!1,g;var y=h.document,v=!1,b=!1,T=h.document,A=h.DocumentFragment,x=h.HTMLTemplateElement,S=h.Node,k=h.NodeFilter,w=h.NamedNodeMap,E=void 0===w?h.NamedNodeMap||h.MozNamedAttrMap:w,O=h.Text,M=h.Comment,N=h.DOMParser,L=h.XMLHttpRequest,D=void 0===L?h.XMLHttpRequest:L,_=h.encodeURI,R=void 0===_?h.encodeURI:_;if("function"==typeof x){var C=T.createElement("template");C.content&&C.content.ownerDocument&&(T=C.content.ownerDocument)}var F=T,z=F.implementation,H=F.createNodeIterator,I=F.getElementsByTagName,j=F.createDocumentFragment,U=y.importNode,q={};g.isSupported=z&&void 0!==z.createHTMLDocument&&9!==T.documentMode;var W=null,B=e({},[].concat(n(r),n(i),n(a),n(l),n(s))),G=null,P=e({},[].concat(n(c),n(d),n(u),n(m))),V=null,X=null,Y=!0,K=!0,$=!1,J=!1,Q=!1,Z=/\{\{[\s\S]*|[\s\S]*\}\}/gm,ee=/<%[\s\S]*|[\s\S]*%>/gm,te=!1,ne=!1,oe=!1,re=!1,ie=!1,ae=!1,le=!0,se=!0,ce={},de=e({},["audio","head","math","script","style","template","svg","video"]),ue=e({},["audio","video","img","source","image"]),me=e({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),fe=null,pe=T.createElement("form"),he=function(o){"object"!==(void 0===o?"undefined":f(o))&&(o={}),W="ALLOWED_TAGS"in o?e({},o.ALLOWED_TAGS):B,G="ALLOWED_ATTR"in o?e({},o.ALLOWED_ATTR):P,V="FORBID_TAGS"in o?e({},o.FORBID_TAGS):{},X="FORBID_ATTR"in o?e({},o.FORBID_ATTR):{},ce="USE_PROFILES"in o&&o.USE_PROFILES,Y=!1!==o.ALLOW_ARIA_ATTR,K=!1!==o.ALLOW_DATA_ATTR,$=o.ALLOW_UNKNOWN_PROTOCOLS||!1,J=o.SAFE_FOR_JQUERY||!1,Q=o.SAFE_FOR_TEMPLATES||!1,te=o.WHOLE_DOCUMENT||!1,re=o.RETURN_DOM||!1,ie=o.RETURN_DOM_FRAGMENT||!1,ae=o.RETURN_DOM_IMPORT||!1,oe=o.FORCE_BODY||!1,le=!1!==o.SANITIZE_DOM,se=!1!==o.KEEP_CONTENT,Q&&(K=!1),ie&&(re=!0),ce&&(W=e({},[].concat(n(s))),G=[],!0===ce.html&&(e(W,r),e(G,c)),!0===ce.svg&&(e(W,i),e(G,d),e(G,m)),!0===ce.svgFilters&&(e(W,a),e(G,d),e(G,m)),!0===ce.mathMl&&(e(W,l),e(G,u),e(G,m))),o.ADD_TAGS&&(W===B&&(W=t(W)),e(W,o.ADD_TAGS)),o.ADD_ATTR&&(G===P&&(G=t(G)),e(G,o.ADD_ATTR)),o.ADD_URI_SAFE_ATTR&&e(me,o.ADD_URI_SAFE_ATTR),se&&(W["#text"]=!0),Object&&"freeze"in Object&&Object.freeze(o),fe=o},ge=function(e){g.removed.push({element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=""}},ye=function(e,t){g.removed.push({attribute:t.getAttributeNode(e),from:t}),t.removeAttribute(e)},ve=function(e){var t=void 0,n=void 0;if(oe&&(e=""+e),b){try{e=R(e)}catch(e){}var o=new D;o.responseType="document",o.open("GET","data:text/html;charset=utf-8,"+e,!1),o.send(null),t=o.response}if(v)try{t=(new N).parseFromString(e,"text/html")}catch(e){}return t&&t.documentElement||((n=(t=z.createHTMLDocument("")).body).parentNode.removeChild(n.parentNode.firstElementChild),n.outerHTML=e),I.call(t,te?"html":"body")[0]};g.isSupported&&function(){var e=ve('');e.querySelector("svg")||(b=!0);try{(e=ve('

')).querySelector("svg img")&&(v=!0)}catch(e){}}();var be=function(e){return H.call(e.ownerDocument||e,e,k.SHOW_ELEMENT|k.SHOW_COMMENT|k.SHOW_TEXT,function(){return k.FILTER_ACCEPT},!1)},Te=function(e){return!(e instanceof O||e instanceof M)&&!("string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof E&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute)},Ae=function(e){return"object"===(void 0===S?"undefined":f(S))?e instanceof S:e&&"object"===(void 0===e?"undefined":f(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},xe=function(e,t,n){q[e]&&q[e].forEach(function(e){e.call(g,t,n,fe)})},Se=function(e){var t=void 0;if(xe("beforeSanitizeElements",e,null),Te(e))return ge(e),!0;var n=e.nodeName.toLowerCase();if(xe("uponSanitizeElement",e,{tagName:n,allowedTags:W}),!W[n]||V[n]){if(se&&!de[n]&&"function"==typeof e.insertAdjacentHTML)try{e.insertAdjacentHTML("AfterEnd",e.innerHTML)}catch(e){}return ge(e),!0}return!J||e.firstElementChild||e.content&&e.content.firstElementChild||!/l&&e.setAttribute("id",i.value);else{if("INPUT"===e.nodeName&&"type"===r&&"file"===o&&(G[r]||!X[r]))continue;"id"===n&&e.setAttribute(n,""),ye(n,e)}if(s.keepAttr&&(!le||"id"!==r&&"name"!==r||!(o in h||o in T||o in pe))){if(Q&&(o=(o=o.replace(Z," ")).replace(ee," ")),K&&ke.test(r));else if(Y&&we.test(r));else{if(!G[r]||X[r])continue;if(me[r]);else if(Ee.test(o.replace(Me,"")));else if("src"!==r&&"xlink:href"!==r||0!==o.indexOf("data:")||!ue[e.nodeName.toLowerCase()]){if($&&!Oe.test(o.replace(Me,"")));else if(o)continue}else;}try{e.setAttribute(n,o),g.removed.pop()}catch(e){}}}xe("afterSanitizeAttributes",e,null)}},Le=function e(t){var n=void 0,o=be(t);for(xe("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)xe("uponSanitizeShadowNode",n,null),Se(n)||(n.content instanceof A&&e(n.content),Ne(n));xe("afterSanitizeShadowDOM",t,null)};return g.sanitize=function(e,t){var n=void 0,o=void 0,r=void 0,i=void 0,a=void 0;if(e||(e="\x3c!--\x3e"),"string"!=typeof e&&!Ae(e)){if("function"!=typeof e.toString)throw new TypeError("toString is not a function");e=e.toString()}if(!g.isSupported){if("object"===f(h.toStaticHTML)||"function"==typeof h.toStaticHTML){if("string"==typeof e)return h.toStaticHTML(e);if(Ae(e))return h.toStaticHTML(e.outerHTML)}return e}if(ne||he(t),g.removed=[],e instanceof S)1===(o=(n=ve("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===o.nodeName?n=o:n.appendChild(o);else{if(!re&&!te&&-1===e.indexOf("<"))return e;if(!(n=ve(e)))return re?null:""}oe&&ge(n.firstChild);for(var l=be(n);r=l.nextNode();)3===r.nodeType&&r===i||Se(r)||(r.content instanceof A&&Le(r.content),Ne(r),i=r);if(re){if(ie)for(a=j.call(n.ownerDocument);n.firstChild;)a.appendChild(n.firstChild);else a=n;return ae&&(a=U.call(y,a,!0)),a}return te?n.outerHTML:n.innerHTML},g.setConfig=function(e){he(e),ne=!0},g.clearConfig=function(){fe=null,ne=!1},g.addHook=function(e,t){"function"==typeof t&&(q[e]=q[e]||[],q[e].push(t))},g.removeHook=function(e){q[e]&&q[e].pop()},g.removeHooks=function(e){q[e]&&(q[e]=[])},g.removeAllHooks=function(){q={}},g}var r=["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"],i=["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","audio","canvas","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","video","view","vkern"],a=["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence"],l=["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmuliscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mpspace","msqrt","mystyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover"],s=["#text"],c=["accept","action","align","alt","autocomplete","background","bgcolor","border","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","coords","datetime","default","dir","disabled","download","enctype","face","for","headers","height","hidden","high","href","hreflang","id","ismap","label","lang","list","loop","low","max","maxlength","media","method","min","multiple","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","span","srclang","start","src","step","style","summary","tabindex","title","type","usemap","valign","value","width","xmlns"],d=["accent-height","accumulate","additivive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","tabindex","targetx","targety","transform","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"],u=["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"],m=["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"],f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},p=function(){return"undefined"==typeof window?null:window};return o()}); -//# sourceMappingURL=purify.min.js.map diff --git a/js/test.js b/js/test.js deleted file mode 100644 index bf89e906..00000000 --- a/js/test.js +++ /dev/null @@ -1,1522 +0,0 @@ -'use strict'; -var jsc = require('jsverify'), - jsdom = require('jsdom-global'), - cleanup = jsdom(), - - 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 supported by the whatwg-url library - schemas = ['ftp','gopher','http','https','ws','wss'], - supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'], - - /** - * character to HTML entity lookup table - * - * @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60} - */ - entityMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/', - '`': '`', - '=': '=' - }, - logFile = require('fs').createWriteStream('test.log'); - -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.min'); -require('./bootstrap-3.3.7'); -require('./privatebin'); - -// redirect console messages to log file -console.info = console.warn = console.error = function () { - logFile.write(Array.prototype.slice.call(arguments).join('') + '\n'); -} - -/** - * 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 - */ -function htmlEntities(str) { - return String(str).replace( - /[&<>"'`=\/]/g, function(s) { - return entityMap[s]; - }); -} - -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(jsc.elements(alnumString))), - 'nearray string', - function (ids, contents) { - var html = '', - result = true; - ids.forEach(function(item, i) { - html += '

' + htmlEntities(contents[i] || contents[0]) + '
'; - }); - var clean = jsdom(html); - ids.forEach(function(item, i) { - $.PrivateBin.Helper.selectText(item.join('')); - // TODO: As per https://github.com/tmpvar/jsdom/issues/321 there is no getSelection in jsdom, yet. - // Once there is one, uncomment the line below to actually check the result. - //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(jsc.elements(a2zString)), - jsc.array(jsc.elements(queryString)), - jsc.array(jsc.elements(queryString)), - 'string', - function (prefix, schema, address, query, fragment, postfix) { - var query = query.join(''), - fragment = fragment.join(''), - url = schema + '://' + address.join('') + '/?' + query + '#' + fragment, - prefix = htmlEntities(prefix), - postfix = ' ' + htmlEntities(postfix); - - // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. � 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 + '' + url + '' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + postfix); - } - ); - jsc.property( - 'replaces magnet links with anchors', - 'string', - jsc.array(jsc.elements(queryString)), - 'string', - function (prefix, query, postfix) { - var url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''), - prefix = htmlEntities(prefix), - postfix = htmlEntities(postfix); - return prefix + '' + url + ' ' + 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 = [], - count = 0; - 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', - jsc.elements(schemas), - jsc.nearray(jsc.elements(a2zString)), - jsc.array(jsc.elements(queryString)), - '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 = htmlEntities(string); - return !(/[<>"'`=\/]/.test(result)) && !(string.indexOf('&') > -1 && !(/&/.test(result))); - } - ); - }); -}); - -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 p_result = $.PrivateBin.I18n.translate(plurals); - $.PrivateBin.I18n.reset(); - - var p_alias = $.PrivateBin.I18n._(plurals); - $.PrivateBin.I18n.reset(); - - var f_result = $.PrivateBin.I18n.translate(fake); - $.PrivateBin.I18n.reset(); - - var f_alias = $.PrivateBin.I18n._(fake); - $.PrivateBin.I18n.reset(); - - return messageId === result && messageId === alias && - messageId === p_result && messageId === p_alias && - messageId === f_result && messageId === f_alias; - } - ); - 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', - jsc.elements(supportedLanguages), - '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', - jsc.elements(supportedLanguages), - 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; - } - ); - }); -}); - -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","salt":"u0lQvePq6L0=","ct":"fGPUVrDyaVr1ZDGb+kqQ3CPEW8x4YKGfzHDmA0Vjkh250aWNe7Cnigkps9aaFVMX9AaerrTp3yZbojJtNqVGMfLdUTu+53xmZHqRKxCCqSfDNSNoW4Oxk5OVgAtRyuG4bXHDsWTXDNz2xceqzVFqhkwTwlUchrV7uuFK/XUKTNjPFM744moivIcBbfM2FOeKlIFs8RYPYuvqQhp2rMLlNGwwKh//4kykQsHMQDeSDuJl8stMQzgWR/btUBZuwNZEydkMH6IPpTdf5WTSrZ+wC2OK0GutCm4UaEe6txzaTMfu+WRVu4PN6q+N+2zljWJ1XdpVcN/i0Sv4QVMym0Xa6y0eccEhj/69o47PmExmMMeEwExImPalMNT9JUSiZdOZJ/GdzwrwoIuq1mdQR6vSH+XJ/8jXJQ7bjjJVJYXTcT0Di5jixArI2Kpp1GGlGVFbLgPugwU1wczg+byqeDOAECXRRnQcogeaJtVcRwXwfy4j3ORFcblYMilxyHqKBewcYPRVBGtBs50cVjSIkAfR84rnc1nfvnxK/Gmm+4VBNHI6ODWNpRolVMCzXjbKYnV3Are5AgSpsTqaGl41VJGpcco6cAwi4K0Bys1seKR+bLSdUgqRrkEqSRSdu3/VTu9HhEk8an0rjTE4CBB5/LMn16p0TGLoOb32odKFIEtpanVvLjeyiVMvSxcgYLNnTi/5FiaAC4pJxRD+AZHedU1FICUeEXxIcac/4E5qjkHjX9SpQtLl80QLIVnjNliZm7QLB/nKu7W8Jb0+/CiTdV3Q9LhxlH4ciprnX+W0B00BKYFHnL9jRVzKdXhf1EHydbXMAfpCjHAXIVCkFakJinQBDIIw/SC6Yig0u0ddEID2B7LYAP1iE4RZwzTrxCB+ke2jQr8c20Jj6u6ShFOPC9DCw9XupZ4HAalVG00kSgjus+b8zrVji3/LKEhb4EBzp1ctBJCFTeXwej8ZETLoXTylev5dlwZSYAbuBPPcbFR/xAIPx3uDabd1E1gTqUc68ICIGhd197Mb2eRWiSvHr5SPsASerMxId6XA6+iQlRiI+NDR+TGVNmCnfxSlyPFMOHGTmslXOGIqGfBR8l4ft8YVZ70lCwmwTuViGc75ULSf9mM57/LmRzQFMYQtvI8IFK9JaQEMY5xz0HLtR4iyQUUdwR9e0ytBNdWF2a2WPDEnJuY/QJo4GzTlgv4QUxMXI5htsn2rf0HxCFu7Po8DNYLxTS+67hYjDIYWYaEIc8LXWMLyDm9C5fARPJ4F2BIWgzgzkNj+dVjusft2XnziamWdbS5u3kuRlVuz5LQj+R5imnqQAincdZTkTT1nYx+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","salt":"jN6CjbQMJCM=","ct":"kYYMo5DFG1+w0UHiYXT5pdV0IUuXxzOlslkW/c3DRCbGFROCVkAskHce7HoRczee1N9c5MhHjVMJUIZE02qIS8UyHdJ/GqcPVidTUcj9rnDNWsTXkjVv8jCwHS/cwmAjDTWpwp5ThECN+ov/wNp/NdtTj8Qj7f/T3rfZIOCWfwLH9s4Des35UNcUidfPTNQ1l0Gm0X+r98CCUSYZjQxkZc6hRZBLPQ8EaNVooUwd5eP4GiYlmSDNA0wOSA+5isPYxomVCt+kFf58VBlNhpfNi7BLYAUTPpXT4SfH5drR9+C7NTeZ+tTCYjbU94PzYItOpu8vgnB1/a6BAM5h3m9w+giUb0df4hgTWeZnZxLjo5BN8WV+kdTXMj3/Vv0gw0DQrDcCuX/cBAjpy3lQGwlAN1vXoOIyZJUjMpQRrOLdKvLB+zcmVNtGDbgnfP2IYBzk9NtodpUa27ne0T0ZpwOPlVwevsIVZO224WLa+iQmmHOWDFFpVDlS0t0fLfOk7Hcb2xFsTxiCIiyKMho/IME1Du3X4e6BVa3hobSSZv0rRtNgY1KcyYPrUPW2fxZ+oik3y9SgGvb7XpjVIta8DWlDWRfZ9kzoweWEYqz9IA8Xd373RefpyuWI25zlHoX3nwljzsZU6dC//h/Dt2DNr+IAvKO3+u23cWoB9kgcZJ2FJuqjLvVfCF+OWcig7zs2pTYJW6Rg6lqbBCxiUUlae6xJrjfv0pzD2VYCLY7v1bVTagppwKzNI3WaluCOrdDYUCxUSe56yd1oAoLPRVbYvomRboUO6cjQhEknERyvt45og2kORJOEJayHW+jZgR0Y0jM3Nk17ubpij2gHxNx9kiLDOiCGSV5mn9mV7qd3HHcOMSykiBgbyzjobi96LT2dIGLeDXTIdPOog8wyobO4jWq0GGs0vBB8oSYXhHvixZLcSjX2KQuHmEoWzmJcr3DavdoXZmAurGWLKjzEdJc5dSD/eNr99gjHX7wphJ6umKMM+fn6PcbYJkhDh2GlJL5COXjXfm/5aj/vuyaRRWZMZtmnYpGAtAPg7AUG"}' - ); - - 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","salt":"u0lQvePq6L0=","ct":"A3nBTvICZtYy6xqbIJE0c8Veored5lMJUGgGUm4581wjrPFlU0Q0tUZSf+RUUoZj2jqDa4kiyyZ5YNMe30hNMV0oVSalNhRgD9svVMnPuF162IbyhVCwr7ULjT981CHxVlGNqGqmIU6L/XixgdArxAA8x1GCrfAkBWWGeq8Qw5vJPG/RCHpwR4Wy3azrluqeyERBzmaOQjO/kM35TiI6IrLYFyYyL7upYlxAaxS0XBMZvN8QU8Lnerwvh5JVC6OkkKrhogajTJIKozCF79yI78c50LUh7tTuI3Yoh7+fXxhoODvQdYFmoiUlrutN7Y5ZMRdITvVu8fTYtX9c7Fiufmcq5icEimiHp2g1bvfpOaGOsFT+XNFgC9215jcp5mpBdN852xs7bUtw+nDrf+LsDEX6iRpRZ+PYgLDN5xQT1ByEtYbeP+tO38pnx72oZdIB3cj8UkOxnxdNiZM5YB5egn4jUj1fHot1I69WoTiUJipZ5PIATv7ScymRB+AYzjxjurQ9lVfX9QtAbEH2dhdmoUo3IDRSXpWNCe9RC1aUIyWfZO7oI7FEohNscHNTLEcT+wFnFUPByLlXmjNZ7FKeNpvUm3jTY4t4sbZH8o2dUl624PAw1INcJ6FKqWGWwoFT2j1MYC+YV/LkLTdjuWfayvwLMh27G/FfKCRbW36vqinegqpPDylsx9+3oFkEw3y5Z8+44oN91rE/4Md7JhPJeRVlFC9TNCj4dA+EVhbbQqscvSnIH2uHkMw7mNNo7xba/YT9KoPDaniqnYqb+q2pX1WNWE7dLS2wfroMAS3kh8P22DAV37AeiNoD2PcI6ZcHbRdPa+XRrRcJhSPPW7UQ0z4OvBfjdu/w390QxAxSxvZewoh49fKKB6hTsRnZb4tpHkjlww=="}' - ), - paste2 = $.PrivateBin.CryptTool.decipher( - 's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=', - '', // no password - '{"iv":"Z7lAZQbkrqGMvruxoSm6Pw==","v":1,"iter":10000,"ks":256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","salt":"jN6CjbQMJCM=","ct":"PuOPWB3i2FPcreSrLYeQf84LdE8RHjsc+MGtiOr4b7doNyWKYtkNorbRadxaPnEee2/Utrp1MIIfY5juJSy8RGwEPX5ciWcYe6EzsXWznsnvhmpKNj9B7eIIrfSbxfy8E2e/g7xav1nive+ljToka3WT1DZ8ILQd/NbnJeHWaoSEOfvz8+d8QJPb1tNZvs7zEY95DumQwbyOsIMKAvcZHJ9OJNpujXzdMyt6DpcFcqlldWBZ/8q5rAUTw0HNx/rCgbhAxRYfNoTLIcMM4L0cXbPSgCjwf5FuO3EdE13mgEDhcClW79m0QvcnIh8xgzYoxLbp0+AwvC/MbZM8savN/0ieWr2EKkZ04ggiOIEyvfCUuNprQBYO+y8kKduNEN6by0Yf4LRCPfmwN+GezDLuzTnZIMhPbGqUAdgV6ExqK2ULEEIrQEMoOuQIxfoMhqLlzG79vXGt2O+BY+4IiYfvmuRLks4UXfyHqxPXTJg48IYbGs0j4TtJPUgp3523EyYLwEGyVTAuWhYAmVIwd/hoV7d7tmfcF73w9dufDFI3LNca2KxzBnWNPYvIZKBwWbq8ncxkb191dP6mjEi7NnhqVk5A6vIBbu4AC5PZf76l6yep4xsoy/QtdDxCMocCXeAML9MQ9uPQbuspOKrBvMfN5igA1kBqasnxI472KBNXsdZnaDddSVUuvhTcETM="}' - ); - - 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', - 'nat', - function(n) { - 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; - } - ); - }); -}); - -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(htmlEntities); - value = htmlEntities(value); - var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), - contents = ''; - $('body').html(contents); - var result = 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(htmlEntities); - value = htmlEntities(value); - var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), - contents = ''; - $('body').html(contents); - var result = 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 = 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 = htmlEntities(value).trim(); - $('body').html('
' + value + '
'); - $.PrivateBin.Model.init(); - var result = 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(jsc.elements(a2zString)), - jsc.nearray(jsc.elements(a2zString)), - jsc.nearray(jsc.elements(queryString)), - '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(jsc.elements(a2zString)), - jsc.nearray(jsc.elements(a2zString)), - '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(jsc.elements(a2zString)), - jsc.nearray(jsc.elements(a2zString)), - jsc.array(jsc.elements(queryString)), - jsc.nearray(jsc.elements(base64String)), - 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(jsc.elements(a2zString)), - jsc.nearray(jsc.elements(a2zString)), - jsc.array(jsc.elements(queryString)), - jsc.nearray(jsc.elements(base64String)), - jsc.array(jsc.elements(queryString)), - 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(jsc.elements(a2zString)), - jsc.nearray(jsc.elements(a2zString)), - jsc.array(jsc.elements(queryString)), - 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(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 + '
' - ); - $.PrivateBin.Model.init(); - var template = '<' + element + ' id="' + id + '">' + value + - '', - 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 + '">' - ); - 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 = '

'; - $('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 = ''; - $('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 = ''; - $('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( - '' - ); - $.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( - '' + - '' + - '' + - '' - ); - $.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( - '' + - '

' - ); - $.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( - '' - ); - $.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([ - '', - '></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; - } - ); - }); -}); diff --git a/js/test/Alert.js b/js/test/Alert.js new file mode 100644 index 00000000..2f0a2df8 --- /dev/null +++ b/js/test/Alert.js @@ -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; + } + ); + }); +}); + diff --git a/js/test/AttachmentViewer.js b/js/test/AttachmentViewer.js new file mode 100644 index 00000000..989f942e --- /dev/null +++ b/js/test/AttachmentViewer.js @@ -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); + } + ); + }); +}); + diff --git a/js/test/CryptTool.js b/js/test/CryptTool.js new file mode 100644 index 00000000..76951cbf --- /dev/null +++ b/js/test/CryptTool.js @@ -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; + } + ); + }); +}); + diff --git a/js/test/DiscussionViewer.js b/js/test/DiscussionViewer.js new file mode 100644 index 00000000..ff06c01f --- /dev/null +++ b/js/test/DiscussionViewer.js @@ -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); + } + ); + }); +}); + diff --git a/js/test/Editor.js b/js/test/Editor.js new file mode 100644 index 00000000..86a9d9f2 --- /dev/null +++ b/js/test/Editor.js @@ -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); + } + ); + }); +}); + diff --git a/js/test/Helper.js b/js/test/Helper.js new file mode 100644 index 00000000..e4141f5c --- /dev/null +++ b/js/test/Helper.js @@ -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))); + } + ); + }); +}); + diff --git a/js/test/I18n.js b/js/test/I18n.js new file mode 100644 index 00000000..0d2fc084 --- /dev/null +++ b/js/test/I18n.js @@ -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; + } + ); + }); +}); + diff --git a/js/test/Model.js b/js/test/Model.js new file mode 100644 index 00000000..3763d931 --- /dev/null +++ b/js/test/Model.js @@ -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; + } + ); + }); +}); + diff --git a/js/test/PasteStatus.js b/js/test/PasteStatus.js new file mode 100644 index 00000000..1c31813d --- /dev/null +++ b/js/test/PasteStatus.js @@ -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'); + } + ); + }); +}); + diff --git a/js/test/PasteViewer.js b/js/test/PasteViewer.js new file mode 100644 index 00000000..64e2120a --- /dev/null +++ b/js/test/PasteViewer.js @@ -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; + } + ); + }); +}); + diff --git a/js/test/Prompt.js b/js/test/Prompt.js new file mode 100644 index 00000000..2b55ae71 --- /dev/null +++ b/js/test/Prompt.js @@ -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; + } + ); + }); +}); + diff --git a/js/test/UiHelper.js b/js/test/UiHelper.js new file mode 100644 index 00000000..e669e609 --- /dev/null +++ b/js/test/UiHelper.js @@ -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 + }); +}); + diff --git a/lib/Configuration.php b/lib/Configuration.php index 274743ed..173ae21f 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -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( diff --git a/lib/PrivateBin.php b/lib/PrivateBin.php index 7b53fa1d..39fb2db3 100644 --- a/lib/PrivateBin.php +++ b/lib/PrivateBin.php @@ -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')); } diff --git a/lib/Request.php b/lib/Request.php index f6daa508..3fb35e32 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -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']) : '/'; } /** diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 203fbcba..a8868752 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -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> diff --git a/tpl/page.php b/tpl/page.php index 867f8ed0..d1e2d906 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -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 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-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]--> @@ -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> diff --git a/tst/ViewTest.php b/tst/ViewTest.php index e2e014b4..5d89f24d 100644 --- a/tst/ViewTest.php +++ b/tst/ViewTest.php @@ -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())) {