Compare commits

...

25 commits

Author SHA1 Message Date
El RIDO
91e5038242
incrementing version 2020-02-16 10:46:43 +01:00
El RIDO
dd12fbf3a3
upgrade DOMpurify to 2.0.8 2020-02-16 09:13:36 +01:00
El RIDO
d2d471278c
fix FAQ links, tweaking the subtext display, add link to modern browser FAQ, fixes #577 2020-02-16 09:08:52 +01:00
El RIDO
4058558399
document backport of server side HTML encoding fixes 2020-02-16 08:59:57 +01:00
El RIDO
ba2363d66b
apply StyleCI recommendation 2020-02-16 08:57:32 +01:00
El RIDO
35be3aabf8
ensuring consistent use of php side encoding, testing all encoding cases, correctly report the language in the <html> tag 2020-02-16 08:57:28 +01:00
El RIDO
d3082c36d7
add HTML entity encoding to PHP translation logic, remove exception to allow <br/> tags in DOMpurify by eliminating the single case that made use of it 2020-02-16 08:49:47 +01:00
El RIDO
79d30c9410
adding test that expects parameters of php translation to get HTML entities to get encoded 2020-02-16 08:42:57 +01:00
rugk
e56edb6c6b
Feature FAQ link in Readme & remove legacy things
* remove old ZeroBin 0.19 guide, this is so old already, few people will benefit from a direct link in the Readme. It stays in the wiki for those, who need it.
* add direct link to the FAQ - it's one of our best documentation/sources, so it's a shame it is not featured more prominently 😉
2020-02-16 08:42:00 +01:00
El RIDO
6ccbad612d
backporting double encoding fixes from #560 2020-02-16 08:37:33 +01:00
El RIDO
2a5f622580
fixing travis CI builds for php 5.5, which require trusty 2020-01-11 13:30:14 +01:00
El RIDO
1b966b35fc
Merge branch 'master' into webcrypto, fix nvm 2020-01-11 13:28:48 +01:00
El RIDO
c28b134067
implementing web crypto API for encryption 2020-01-11 13:24:16 +01:00
El RIDO
a6d5254662
incrementing version 2020-01-08 19:19:12 +01:00
El RIDO
7c66ba9de6
documenting changes for 1.2.2 2020-01-07 21:23:41 +01:00
El RIDO
1a77f25000
upgrading SJCL to 1.0.8 2020-01-07 21:22:34 +01:00
El RIDO
71029f7d3d
upgrading showdown to released 1.9.1 version 2020-01-07 21:14:06 +01:00
El RIDO
1f5d237806
address new fixer in StyleCI causing false positives in templates 2020-01-07 21:08:48 +01:00
El RIDO
2caddf985f
more general solution addressing #554, kudos @rugk for the suggestions 2020-01-07 21:08:11 +01:00
El RIDO
6a3a8a395a
updating DOMpurify library, fixes #523 2020-01-07 20:31:44 +01:00
El RIDO
b21d0a6cb7
fixing font paths 2020-01-07 20:27:40 +01:00
El RIDO
f70ffe3864
updated kjua library 2020-01-07 20:27:22 +01:00
El RIDO
9acddb530f
revert bootstraps JS to 3.3.7 as 3.4.1 breaks the navbar toggle 2020-01-07 20:26:27 +01:00
El RIDO
85d2cea504
upgrade jQuery library 2020-01-07 20:23:59 +01:00
El RIDO
1935dee6b7
upgrading bootstrap CSS 2020-01-07 20:19:35 +01:00
73 changed files with 640 additions and 342 deletions

View file

@ -19,6 +19,7 @@ disabled:
- heredoc_to_nowdoc - heredoc_to_nowdoc
- method_argument_space - method_argument_space
- new_with_braces - new_with_braces
- no_alternative_syntax
- phpdoc_align - phpdoc_align
- phpdoc_no_access - phpdoc_no_access
- phpdoc_separation - phpdoc_separation

View file

@ -1,5 +1,7 @@
language: php language: php
sudo: false sudo: false
# only needed for PHP 5.5 support as of 2019-07
dist: trusty
php: php:
- '5.4' - '5.4'
- '5.5' - '5.5'
@ -7,16 +9,17 @@ php:
- '7.0' - '7.0'
- '7.1' - '7.1'
- '7.2' - '7.2'
- '7.3'
# as this is a php project, node.js v4 (for JS unit testing) isn't installed # as this is a php project, node.js (for JS unit testing) isn't installed
install: install:
- 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 - if [ ! -d "$HOME/.nvm" ]; then mkdir -p $HOME/.nvm && curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | NVM_METHOD=script bash; fi
- source ~/.nvm/nvm.sh && nvm install 4 - source ~/.nvm/nvm.sh && nvm install --lts
before_script: before_script:
- composer install -n - composer install -n
- npm install -g mocha - npm install -g mocha
- cd js && npm install jsverify jsdom@9 jsdom-global@2 mime-types - cd js && npm install
script: script:
- mocha - mocha

View file

@ -1,6 +1,12 @@
# PrivateBin version history # PrivateBin version history
* **1.3 (not yet released)** * **1.2.3 (2020-02-16)**
* CHANGED: Upgrading libraries to: DOMpurify 2.0.8
* CHANGED: Introduce HTML entity encoding on server side (#581)
* FIXED: HTML entity double encoding issues introduced in 1.3.2 (#560)
* **1.2.2 (2020-01-11)**
* CHANGED: Upgrading libraries to: bootstrap 3.4.1, DOMpurify 2.0.7, jQuery 3.4.1, kjua 0.6.0, Showdown 1.9.1 & SJCL 1.0.8
* FIXED: HTML injection via unescaped attachment filename (#554)
* **1.2.1 (2018-08-11)** * **1.2.1 (2018-08-11)**
* ADDED: Add support for mega.nz links in pastes and comments (#331) * ADDED: Add support for mega.nz links in pastes and comments (#331)
* CHANGED: Added some missing Russian translations (#348) * CHANGED: Added some missing Russian translations (#348)

View file

@ -165,7 +165,7 @@ CREATE INDEX parent ON prefix_comment(pasteid);
CREATE TABLE prefix_config ( CREATE TABLE prefix_config (
id CHAR(16) NOT NULL, value TEXT, PRIMARY KEY (id) id CHAR(16) NOT NULL, value TEXT, PRIMARY KEY (id)
); );
INSERT INTO prefix_config VALUES('VERSION', '1.2.1'); INSERT INTO prefix_config VALUES('VERSION', '1.2.3');
``` ```
In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to be TEXT and not BLOB or MEDIUMBLOB. In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to be TEXT and not BLOB or MEDIUMBLOB.

View file

@ -7,7 +7,7 @@
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/094500f62abf4c9aa0c8a8a4520e4789)](https://www.codacy.com/app/PrivateBin/PrivateBin) [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/094500f62abf4c9aa0c8a8a4520e4789)](https://www.codacy.com/app/PrivateBin/PrivateBin)
[![Test Coverage](https://codeclimate.com/github/PrivateBin/PrivateBin/badges/coverage.svg)](https://codeclimate.com/github/PrivateBin/PrivateBin/coverage) [![Code Coverage](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/?branch=master) [![Test Coverage](https://codeclimate.com/github/PrivateBin/PrivateBin/badges/coverage.svg)](https://codeclimate.com/github/PrivateBin/PrivateBin/coverage) [![Code Coverage](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/?branch=master)
*Current version: 1.2.1* *Current version: 1.2.3*
**PrivateBin** is a minimalist, open source online [pastebin](https://en.wikipedia.org/wiki/Pastebin) **PrivateBin** is a minimalist, open source online [pastebin](https://en.wikipedia.org/wiki/Pastebin)
where the server has zero knowledge of pasted data. where the server has zero knowledge of pasted data.
@ -102,9 +102,9 @@ file](https://github.com/PrivateBin/PrivateBin/wiki/Configuration):
## Further resources ## Further resources
* [Installation guide](https://github.com/PrivateBin/PrivateBin/blob/master/INSTALL.md#installation) * [FAQ](https://github.com/PrivateBin/PrivateBin/wiki/FAQ)
* [Upgrading from ZeroBin 0.19 Alpha](https://github.com/PrivateBin/PrivateBin/wiki/Upgrading-from-ZeroBin-0.19-Alpha) * [Installation guide](https://github.com/PrivateBin/PrivateBin/blob/master/INSTALL.md#installation)
* [Configuration guide](https://github.com/PrivateBin/PrivateBin/wiki/Configuration) * [Configuration guide](https://github.com/PrivateBin/PrivateBin/wiki/Configuration)

View file

@ -3,13 +3,13 @@
"description": "PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted in the browser using 256 bit AES in Galois Counter mode (GCM).", "description": "PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted in the browser using 256 bit AES in Galois Counter mode (GCM).",
"type": "project", "type": "project",
"keywords": ["private", "secure", "end-to-end-encrypted", "e2e", "paste", "pastebin", "zero", "zero-knowledge", "encryption", "encrypted", "AES"], "keywords": ["private", "secure", "end-to-end-encrypted", "e2e", "paste", "pastebin", "zero", "zero-knowledge", "encryption", "encrypted", "AES"],
"homepage": "https://github.com/PrivateBin", "homepage": "https://privatebin.info/",
"license":"zlib-acknowledgement", "license":"zlib-acknowledgement",
"support": { "support": {
"issues": "https://github.com/PrivateBin/PrivateBin/issues", "issues": "https://github.com/PrivateBin/PrivateBin/issues",
"wiki": "https://github.com/PrivateBin/PrivateBin/wiki", "wiki": "https://github.com/PrivateBin/PrivateBin/wiki",
"source": "https://github.com/PrivateBin/PrivateBin", "source": "https://github.com/PrivateBin/PrivateBin",
"docs": "https://zerobin.dssr.ch/documentation/" "docs": "https://privatebin.info/codedoc/"
}, },
"require": { "require": {
"php": "^5.4.0 || ^7.0", "php": "^5.4.0 || ^7.0",

File diff suppressed because one or more lines are too long

5
css/bootstrap/bootstrap-3.4.1.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -315,8 +315,8 @@ th {
} }
@font-face { @font-face {
font-family: 'Glyphicons Halflings'; font-family: 'Glyphicons Halflings';
src: url(fonts/../fonts/glyphicons-halflings-regular.eot?1445975532); src: url(fonts/glyphicons-halflings-regular.eot?1445975532);
src: url(fonts/../fonts/glyphicons-halflings-regular.eot?&1445975532#iefix) format("embedded-opentype"), url(fonts/../fonts/glyphicons-halflings-regular.woff2?1445975532) format("woff2"), url(fonts/../fonts/glyphicons-halflings-regular.woff?1445975532) format("woff"), url(fonts/../fonts/glyphicons-halflings-regular.ttf?1445975532) format("truetype"), url(fonts/../fonts/glyphicons-halflings-regular.svg?1445975532#glyphicons_halflingsregular) format("svg"); src: url(fonts/glyphicons-halflings-regular.eot?&1445975532#iefix) format("embedded-opentype"), url(fonts/glyphicons-halflings-regular.woff2?1445975532) format("woff2"), url(fonts/glyphicons-halflings-regular.woff?1445975532) format("woff"), url(fonts/glyphicons-halflings-regular.ttf?1445975532) format("truetype"), url(fonts/glyphicons-halflings-regular.svg?1445975532#glyphicons_halflingsregular) format("svg");
} }
.glyphicon { .glyphicon {
position: relative; position: relative;

View file

@ -6,7 +6,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
body { body {

View file

@ -6,7 +6,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
/* When there is no script at all other */ /* When there is no script at all other */

View file

@ -6,7 +6,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
/* CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved. /* CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved.

View file

@ -31,8 +31,8 @@
"Falscher Lösch-Code. Text wurde nicht gelöscht.", "Falscher Lösch-Code. Text wurde nicht gelöscht.",
"Paste was properly deleted.": "Paste was properly deleted.":
"Text wurde erfolgreich gelöscht.", "Text wurde erfolgreich gelöscht.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"JavaScript ist eine Voraussetzung, um %s zu nutzen.<br />Bitte entschuldige die Unannehmlichkeiten.", "JavaScript ist eine Voraussetzung, um %s zu nutzen. Bitte entschuldige die Unannehmlichkeiten.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s setzt einen modernen Browser voraus, um funktionieren zu können.", "%s setzt einen modernen Browser voraus, um funktionieren zu können.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,8 +31,8 @@
"Token de eliminación erróneo. El texto no fue eliminado.", "Token de eliminación erróneo. El texto no fue eliminado.",
"Paste was properly deleted.": "Paste was properly deleted.":
"El texto se ha eliminado correctamente.", "El texto se ha eliminado correctamente.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"JavaScript es necesario para que %s funcione.<br />Sentimos los inconvenientes ocasionados.", "JavaScript es necesario para que %s funcione. Sentimos los inconvenientes ocasionados.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s requiere un navegador moderno para funcionar.", "%s requiere un navegador moderno para funcionar.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,8 +31,8 @@
"Jeton de suppression incorrect. Le paste n'a pas été supprimé.", "Jeton de suppression incorrect. Le paste n'a pas été supprimé.",
"Paste was properly deleted.": "Paste was properly deleted.":
"Le paste a été correctement supprimé.", "Le paste a été correctement supprimé.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"JavaScript est requis pour faire fonctionner %s. <br />Désolé pour cet inconvénient.", "JavaScript est requis pour faire fonctionner %s. Désolé pour cet inconvénient.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s nécessite un navigateur moderne pour fonctionner.", "%s nécessite un navigateur moderne pour fonctionner.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,7 +31,7 @@
"Hibás törlési azonosító. A bejegyzés nem lett törölve.", "Hibás törlési azonosító. A bejegyzés nem lett törölve.",
"Paste was properly deleted.": "Paste was properly deleted.":
"A bejegyzés sikeresen törölve.", "A bejegyzés sikeresen törölve.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"JavaScript szükséges a %s működéséhez. Elnézést a fennakadásért.", "JavaScript szükséges a %s működéséhez. Elnézést a fennakadásért.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"A %s működéséhez a jelenleginél újabb böngészőre van szükség.", "A %s működéséhez a jelenleginél újabb böngészőre van szükség.",

View file

@ -31,8 +31,8 @@
"Codice cancellazione errato. Il messaggio NON è stato cancellato.", "Codice cancellazione errato. Il messaggio NON è stato cancellato.",
"Paste was properly deleted.": "Paste was properly deleted.":
"Il messaggio è stato correttamente cancellato.", "Il messaggio è stato correttamente cancellato.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"%s funziona solo con JavaScript attivo.<br />Ci dispiace per l'inconveniente.", "%s funziona solo con JavaScript attivo. Ci dispiace per l'inconveniente.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s richiede un browser moderno e aggiornato per funzionare.", "%s richiede un browser moderno e aggiornato per funzionare.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,8 +31,8 @@
"Foutieve verwijdercode. Geplakte tekst is niet verwijderd.", "Foutieve verwijdercode. Geplakte tekst is niet verwijderd.",
"Paste was properly deleted.": "Paste was properly deleted.":
"Geplakte tekst is correct verwijderd.", "Geplakte tekst is correct verwijderd.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"JavaScript vereist om %s te laten werken.<br />Sorry voor het ongemak.", "JavaScript vereist om %s te laten werken. Sorry voor het ongemak.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s vereist een moderne browser om te kunnen werken ", "%s vereist een moderne browser om te kunnen werken ",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,8 +31,8 @@
"Feil slettingsnøkkel. Innlegg ble ikke fjernet.", "Feil slettingsnøkkel. Innlegg ble ikke fjernet.",
"Paste was properly deleted.": "Paste was properly deleted.":
"Innlegget er slettet.", "Innlegget er slettet.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"Javascript kreves for at %s skal fungere<br />Beklager.", "Javascript kreves for at %s skal fungere. Beklager.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s krever en moderne nettleser for å fungere.", "%s krever en moderne nettleser for å fungere.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,8 +31,8 @@
"Geton de supression incorrècte. Lo tèxte es pas estat suprimit.", "Geton de supression incorrècte. Lo tèxte es pas estat suprimit.",
"Paste was properly deleted.": "Paste was properly deleted.":
"Lo tèxte es estat correctament suprimit.", "Lo tèxte es estat correctament suprimit.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"JavaScript es requesit per far foncionar %s. <br />O planhèm per linconvenient.", "JavaScript es requesit per far foncionar %s. O planhèm per linconvenient.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s necessita un navigator modèrn per foncionar.", "%s necessita un navigator modèrn per foncionar.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,7 +31,7 @@
"Nieprawidłowy token usuwania. Wklejka nie została usunięta.", "Nieprawidłowy token usuwania. Wklejka nie została usunięta.",
"Paste was properly deleted.": "Paste was properly deleted.":
"Wklejka usunięta poprawnie.", "Wklejka usunięta poprawnie.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"Do działania %sa jest wymagany JavaScript. Przepraszamy za tę niedogodność.", "Do działania %sa jest wymagany JavaScript. Przepraszamy za tę niedogodność.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s wymaga do działania nowoczesnej przeglądarki.", "%s wymaga do działania nowoczesnej przeglądarki.",

View file

@ -31,8 +31,8 @@
"Token de remoção inválido. A cópia não foi excluída.", "Token de remoção inválido. A cópia não foi excluída.",
"Paste was properly deleted.": "Paste was properly deleted.":
"A cópia foi devidamente excluída.", "A cópia foi devidamente excluída.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"JavaScript é necessário para que %s funcione.<br />Pedimos desculpas pela inconveniência.", "JavaScript é necessário para que %s funcione. Pedimos desculpas pela inconveniência.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s requer um navegador moderno para funcionar.", "%s requer um navegador moderno para funcionar.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,8 +31,8 @@
"Неверный ключ удаления записи. Запись не удалена", "Неверный ключ удаления записи. Запись не удалена",
"Paste was properly deleted.": "Paste was properly deleted.":
"Запись была успешно удалена.", "Запись была успешно удалена.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"Для работы %s требуется включенный JavaScript.<br />Приносим извинения за неудобства.", "Для работы %s требуется включенный JavaScript. Приносим извинения за неудобства.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"Для работы %s требуется более современный браузер.", "Для работы %s требуется более современный браузер.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,8 +31,8 @@
"Napačen token za izbris. Prilepek ni bil izbrisan..", "Napačen token za izbris. Prilepek ni bil izbrisan..",
"Paste was properly deleted.": "Paste was properly deleted.":
"Prilepek je uspešno izbrisan.", "Prilepek je uspešno izbrisan.",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"Da %s deluje, moraš vklopiti JavaScript.<br />Oprosti za povročene nevšečnosti.", "Da %s deluje, moraš vklopiti JavaScript. Oprosti za povročene nevšečnosti.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s za svoje delovanje potrebuje moderen brskalnik.", "%s za svoje delovanje potrebuje moderen brskalnik.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -31,8 +31,8 @@
"错误的删除token粘贴没有被删除。", "错误的删除token粘贴没有被删除。",
"Paste was properly deleted.": "Paste was properly deleted.":
"粘贴已被正确删除。", "粘贴已被正确删除。",
"JavaScript is required for %s to work.<br />Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.":
"%s需要JavaScript来进行加解密。<br />带来的不便敬请谅解。", "%s需要JavaScript来进行加解密。 给你带来的不便敬请谅解。",
"%s requires a modern browser to work.": "%s requires a modern browser to work.":
"%s需要工作于现代化的浏览器。", "%s需要工作于现代化的浏览器。",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:": "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
// change this, if your php files and data is outside of your webservers document root // change this, if your php files and data is outside of your webservers document root

View file

@ -8,16 +8,16 @@ global.cleanup = global.jsdom();
global.fs = require('fs'); global.fs = require('fs');
// application libraries to test // application libraries to test
global.$ = global.jQuery = require('./jquery-3.3.1'); global.$ = global.jQuery = require('./jquery-3.4.1');
global.sjcl = require('./sjcl-1.0.7'); global.sjcl = require('./sjcl-1.0.8');
global.Base64 = require('./base64-2.4.5').Base64; global.Base64 = require('./base64-2.4.5').Base64;
global.RawDeflate = require('./rawdeflate-0.5').RawDeflate; global.RawDeflate = require('./rawdeflate-0.5').RawDeflate;
global.RawDeflate.inflate = require('./rawinflate-0.3').RawDeflate.inflate; global.RawDeflate.inflate = require('./rawinflate-0.3').RawDeflate.inflate;
require('./prettify'); require('./prettify');
global.prettyPrint = window.PR.prettyPrint; global.prettyPrint = window.PR.prettyPrint;
global.prettyPrintOne = window.PR.prettyPrintOne; global.prettyPrintOne = window.PR.prettyPrintOne;
global.showdown = require('./showdown-1.8.6'); global.showdown = require('./showdown-1.9.1');
global.DOMPurify = require('./purify-1.0.7'); global.DOMPurify = require('./purify-2.0.8');
require('./bootstrap-3.3.7'); require('./bootstrap-3.3.7');
require('./privatebin'); require('./privatebin');
@ -32,25 +32,10 @@ var a2zString = ['a','b','c','d','e','f','g','h','i','j','k','l','m',
return c.toUpperCase(); return c.toUpperCase();
}) })
), ),
schemas = ['ftp','gopher','http','https','ws','wss'], schemas = ['ftp','http','https'],
supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'], supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'],
mimeTypes = ['image/png', 'application/octet-stream'], mimeTypes = ['image/png', 'application/octet-stream'],
formats = ['plaintext', 'markdown', 'syntaxhighlighting'], formats = ['plaintext', 'markdown', 'syntaxhighlighting'],
/**
* character to HTML entity lookup table
*
* @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60}
*/
entityMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;',
'`': '&#x60;',
'=': '&#x3D;'
},
logFile = fs.createWriteStream('test.log'), logFile = fs.createWriteStream('test.log'),
mimeFile = fs.createReadStream('/etc/mime.types'), mimeFile = fs.createReadStream('/etc/mime.types'),
mimeLine = ''; mimeLine = '';
@ -97,22 +82,6 @@ function parseMime(line) {
// common testing helper functions // 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 // provides random lowercase characters from a to z
exports.jscA2zString = function() { exports.jscA2zString = function() {
return jsc.elements(a2zString); return jsc.elements(a2zString);

2
js/jquery-3.3.1.js vendored

File diff suppressed because one or more lines are too long

2
js/jquery-3.4.1.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
js/kjua-0.6.0.js Normal file

File diff suppressed because one or more lines are too long

43
js/package.json Normal file
View file

@ -0,0 +1,43 @@
{
"name": "privatebin",
"version": "1.2.1",
"description": "PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted in the browser using 256 bit AES in Galois Counter mode (GCM).",
"main": "privatebin.js",
"directories": {
"test": "test"
},
"dependencies": {},
"devDependencies": {
"jsdom": "^9.12.0",
"jsdom-global": "^2.1.1",
"jsverify": "^0.8.3",
"mime-types": "^2.1.20",
"node-webcrypto-ossl": "^1.0.37"
},
"scripts": {
"test": "mocha"
},
"repository": {
"type": "git",
"url": "git+https://github.com/PrivateBin/PrivateBin.git"
},
"keywords": [
"private",
"secure",
"end-to-end-encrypted",
"e2e",
"paste",
"pastebin",
"zero",
"zero-knowledge",
"encryption",
"encrypted",
"AES"
],
"author": "",
"license": "zlib-acknowledgement",
"bugs": {
"url": "https://github.com/PrivateBin/PrivateBin/issues"
},
"homepage": "https://privatebin.info/"
}

View file

@ -6,7 +6,7 @@
* @see {@link https://github.com/PrivateBin/PrivateBin} * @see {@link https://github.com/PrivateBin/PrivateBin}
* @copyright 2012 Sébastien SAUVAGE ({@link http://sebsauvage.net}) * @copyright 2012 Sébastien SAUVAGE ({@link http://sebsauvage.net})
* @license {@link https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License} * @license {@link https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License}
* @version 1.2.1 * @version 1.2.3
* @name PrivateBin * @name PrivateBin
* @namespace * @namespace
*/ */
@ -68,6 +68,26 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
*/ */
var baseUri = null; var baseUri = null;
/**
* character to HTML entity lookup table
*
* @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60}
* @name Helper.entityMap
* @private
* @enum {Object}
* @readonly
*/
var entityMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;',
'`': '&#x60;',
'=': '&#x3D;'
};
/** /**
* converts a duration (in seconds) into human friendly approximation * converts a duration (in seconds) into human friendly approximation
* *
@ -171,19 +191,12 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
var format = args[0], var format = args[0],
i = 1; i = 1;
return format.replace(/%(s|d)/g, function (m) { return format.replace(/%(s|d)/g, function (m) {
// m is the matched format, e.g. %s, %d
var val = args[i]; var val = args[i];
// A switch statement so that the formatter can be extended. if (m === '%d') {
switch (m) val = parseFloat(val);
{ if (isNaN(val)) {
case '%d': val = 0;
val = parseFloat(val); }
if (isNaN(val)) {
val = 0;
}
break;
default:
// Default is %s
} }
++i; ++i;
return val; return val;
@ -237,15 +250,21 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
}; };
/** /**
* resets state, used for unit testing * convert all applicable characters to HTML entities
* *
* @name Helper.reset * @see {@link https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html}
* @name Helper.htmlEntities
* @function * @function
* @param {string} str
* @return {string} escaped HTML
*/ */
me.reset = function() me.htmlEntities = function(str) {
{ return String(str).replace(
baseUri = null; /[&<>"'`=\/]/g, function(s) {
}; return entityMap[s];
}
);
}
/** /**
* checks whether this is a bot we dislike * checks whether this is a bot we dislike
@ -267,6 +286,17 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
return false; return false;
} }
/**
* resets state, used for unit testing
*
* @name Helper.reset
* @function
*/
me.reset = function()
{
baseUri = null;
};
return me; return me;
})(); })();
@ -337,10 +367,14 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* *
* Optionally pass a jQuery element as the first parameter, to automatically * Optionally pass a jQuery element as the first parameter, to automatically
* let the text of this element be replaced. In case the (asynchronously * let the text of this element be replaced. In case the (asynchronously
* loaded) language is not downloadet yet, this will make sure the string * loaded) language is not downloaded yet, this will make sure the string
* is replaced when it is actually loaded. * is replaced when it eventually gets loaded. Using this is both simpler
* So for easy translations passing the jQuery object to apply it to is * and more secure, as it avoids potential XSS when inserting text.
* more save, especially when they are loaded in the beginning. * The next parameter is the message ID, matching the ones found in
* the translation files under the i18n directory.
* Any additional parameters will get inserted into the message ID in
* place of %s (strings) or %d (digits), applying the appropriate plural
* in case of digits. See also Helper.sprintf().
* *
* @name I18n.translate * @name I18n.translate
* @function * @function
@ -419,18 +453,40 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
args[0] = translations[messageId]; args[0] = translations[messageId];
} }
// messageID may contain links, but should be from a trusted source (code or translation JSON files)
var containsLinks = args[0].indexOf('<a') !== -1;
// prevent double encoding, when we insert into a text node
if (containsLinks || $element === null) {
for (var i = 0; i < args.length; ++i) {
// parameters (i > 0) may never contain HTML as they may come from untrusted parties
if ((containsLinks ? i > 1 : i > 0) || !containsLinks) {
args[i] = Helper.htmlEntities(args[i]);
}
}
}
// format string // format string
var output = Helper.sprintf.apply(this, args); var output = Helper.sprintf.apply(this, args);
// if $element is given, apply text to element if (containsLinks) {
// only allow tags/attributes we actually use in translations
output = DOMPurify.sanitize(
output, {
ALLOWED_TAGS: ['a', 'i', 'span'],
ALLOWED_ATTR: ['href', 'id']
}
);
}
// if $element is given, insert translation
if ($element !== null) { if ($element !== null) {
// get last text node of element if (containsLinks) {
var content = $element.contents(); $element.html(output);
if (content.length > 1) {
content[content.length - 1].nodeValue = ' ' + output;
} else { } else {
// text node takes care of entity encoding
$element.text(output); $element.text(output);
} }
return '';
} }
return output; return output;
@ -1052,28 +1108,35 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
icon = null; // icons not supported in this case icon = null; // icons not supported in this case
} }
} }
var $translationTarget = $element;
// handle icon // handle icon, if template uses one
if (icon !== null && // icon was passed var $glyphIcon = $element.find(':first');
icon !== currentIcon[id] // and it differs from current icon if ($glyphIcon.length) {
) { // if there is an icon, we need to provide an inner element
var $glyphIcon = $element.find(':first'); // to translate the message into, instead of the parent
$translationTarget = $('<span>');
$element.html(' ').prepend($glyphIcon).append($translationTarget);
// remove (previous) icon if (icon !== null && // icon was passed
$glyphIcon.removeClass(currentIcon[id]); icon !== currentIcon[id] // and it differs from current icon
) {
// remove (previous) icon
$glyphIcon.removeClass(currentIcon[id]);
// any other thing as a string (e.g. 'null') (only) removes the icon // any other thing as a string (e.g. 'null') (only) removes the icon
if (typeof icon === 'string') { if (typeof icon === 'string') {
// set new icon // set new icon
currentIcon[id] = 'glyphicon-' + icon; currentIcon[id] = 'glyphicon-' + icon;
$glyphIcon.addClass(currentIcon[id]); $glyphIcon.addClass(currentIcon[id]);
}
} }
} }
// show text // show text
if (args !== null) { if (args !== null) {
// add jQuery object to it as first parameter // add jQuery object to it as first parameter
args.unshift($element); args.unshift($translationTarget);
// pass it to I18n // pass it to I18n
I18n._.apply(this, args); I18n._.apply(this, args);
} }
@ -1295,11 +1358,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
*/ */
me.createPasteNotification = function(url, deleteUrl) me.createPasteNotification = function(url, deleteUrl)
{ {
$('#pastelink').html( I18n._(
I18n._( $('#pastelink'),
'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>', 'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>',
url, url url, url
)
); );
// save newly created element // save newly created element
$pasteUrl = $('#pasteurl'); $pasteUrl = $('#pasteurl');
@ -1307,7 +1369,8 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
$pasteUrl.click(pasteLinkClick); $pasteUrl.click(pasteLinkClick);
// shorten button // shorten button
$('#deletelink').html('<a href="' + deleteUrl + '">' + I18n._('Delete data') + '</a>'); $('#deletelink').html('<a href="' + deleteUrl + '"></a>');
I18n._($('#deletelink a').first(), 'Delete data');
// show result // show result
$pasteSuccess.removeClass('hidden'); $pasteSuccess.removeClass('hidden');
@ -1763,10 +1826,13 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
} }
// escape HTML entities, link URLs, sanitize // escape HTML entities, link URLs, sanitize
var escapedLinkedText = Helper.urls2links( var escapedLinkedText = Helper.urls2links(text),
$('<div />').text(text).html() sanitizedLinkedText = DOMPurify.sanitize(
), escapedLinkedText, {
sanitizedLinkedText = DOMPurify.sanitize(escapedLinkedText); ALLOWED_TAGS: ['a'],
ALLOWED_ATTR: ['href', 'rel']
}
);
$plainText.html(sanitizedLinkedText); $plainText.html(sanitizedLinkedText);
$prettyPrint.html(sanitizedLinkedText); $prettyPrint.html(sanitizedLinkedText);
@ -2578,7 +2644,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// set & parse text // set & parse text
$commentEntryData.html( $commentEntryData.html(
DOMPurify.sanitize( DOMPurify.sanitize(
Helper.urls2links(commentText) Helper.urls2links(commentText), {
ALLOWED_TAGS: ['a'],
ALLOWED_ATTR: ['href', 'rel']
}
) )
); );
@ -2894,7 +2963,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
for (var i = 0; i < $head.length; i++) { for (var i = 0; i < $head.length; i++) {
newDoc.write($head[i].outerHTML); newDoc.write($head[i].outerHTML);
} }
newDoc.write('</head><body><pre>' + DOMPurify.sanitize(paste) + '</pre></body></html>'); newDoc.write('</head><body><pre>' + DOMPurify.sanitize(Helper.htmlEntities(paste)) + '</pre></body></html>');
newDoc.close(); newDoc.close();
} }
@ -4371,9 +4440,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
Uploader.setUnencryptedData('deletetoken', deleteToken); Uploader.setUnencryptedData('deletetoken', deleteToken);
Uploader.setFailure(function () { Uploader.setFailure(function () {
Alert.showError( Alert.showError('Could not delete the paste, it was not stored in burn after reading mode.');
I18n._('Could not delete the paste, it was not stored in burn after reading mode.')
);
}); });
Uploader.run(); Uploader.run();
}; };
@ -4389,7 +4456,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// first load translations // first load translations
I18n.loadTranslations(); I18n.loadTranslations();
DOMPurify.setConfig({SAFE_FOR_JQUERY: true}); DOMPurify.setConfig({
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
SAFE_FOR_JQUERY: true
});
// initialize other modules/"classes" // initialize other modules/"classes"
Alert.init(); Alert.init();

File diff suppressed because one or more lines are too long

1
js/purify-2.0.8.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
js/showdown-1.9.1.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -3,21 +3,56 @@ var common = require('../common');
describe('Alert', function () { describe('Alert', function () {
describe('showStatus', function () { describe('showStatus', function () {
before(function () {
cleanup();
});
jsc.property( jsc.property(
'shows a status message', 'shows a status message',
jsc.array(common.jscAlnumString()), jsc.array(common.jscAlnumString()),
jsc.array(common.jscAlnumString()), jsc.array(common.jscAlnumString()),
function (icon, message) {
icon = icon.join('');
message = message.join('');
var expected = '<div id="status">' + message + '</div>';
$('body').html(
'<div id="status"></div>'
);
$.PrivateBin.Alert.init();
$.PrivateBin.Alert.showStatus(message, icon);
var result = $('body').html();
return expected === result;
}
);
jsc.property(
'shows a status message (bootstrap)',
jsc.array(common.jscAlnumString()),
function (message) {
message = message.join('');
var expected = '<div id="status" role="alert" ' +
'class="statusmessage alert alert-info"><span ' +
'class="glyphicon glyphicon-info-sign" ' +
'aria-hidden="true"></span> <span>' + message + '</span></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);
var result = $('body').html();
return expected === result;
}
);
jsc.property(
'shows a status message (bootstrap, custom icon)',
jsc.array(common.jscAlnumString()),
jsc.array(common.jscAlnumString()),
function (icon, message) { function (icon, message) {
icon = icon.join(''); icon = icon.join('');
message = message.join(''); message = message.join('');
var expected = '<div id="status" role="alert" ' + var expected = '<div id="status" role="alert" ' +
'class="statusmessage alert alert-info"><span ' + 'class="statusmessage alert alert-info"><span ' +
'class="glyphicon glyphicon-' + icon + 'class="glyphicon glyphicon-' + icon +
'" aria-hidden="true"></span> ' + message + '</div>'; '" aria-hidden="true"></span> <span>' + message + '</span></div>';
$('body').html( $('body').html(
'<div id="status" role="alert" class="statusmessage ' + '<div id="status" role="alert" class="statusmessage ' +
'alert alert-info hidden"><span class="glyphicon ' + 'alert alert-info hidden"><span class="glyphicon ' +
@ -32,12 +67,48 @@ describe('Alert', function () {
}); });
describe('showError', function () { describe('showError', function () {
before(function () { jsc.property(
cleanup(); 'shows an error message (basic)',
}); jsc.array(common.jscAlnumString()),
jsc.array(common.jscAlnumString()),
function (icon, message) {
icon = icon.join('');
message = message.join('');
var expected = '<div id="errormessage">' + message + '</div>';
$('body').html(
'<div id="errormessage"></div>'
);
$.PrivateBin.Alert.init();
$.PrivateBin.Alert.showError(message, icon);
var result = $('body').html();
return expected === result;
}
);
jsc.property( jsc.property(
'shows an error message', 'shows an error message (bootstrap)',
jsc.array(common.jscAlnumString()),
jsc.array(common.jscAlnumString()),
function (icon, message) {
message = message.join('');
var expected = '<div id="errormessage" role="alert" ' +
'class="statusmessage alert alert-danger"><span ' +
'class="glyphicon glyphicon-alert" ' +
'aria-hidden="true"></span> <span>' + message + '</span></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);
var result = $('body').html();
return expected === result;
}
);
jsc.property(
'shows an error message (bootstrap, custom icon)',
jsc.array(common.jscAlnumString()), jsc.array(common.jscAlnumString()),
jsc.array(common.jscAlnumString()), jsc.array(common.jscAlnumString()),
function (icon, message) { function (icon, message) {
@ -46,7 +117,7 @@ describe('Alert', function () {
var expected = '<div id="errormessage" role="alert" ' + var expected = '<div id="errormessage" role="alert" ' +
'class="statusmessage alert alert-danger"><span ' + 'class="statusmessage alert alert-danger"><span ' +
'class="glyphicon glyphicon-' + icon + 'class="glyphicon glyphicon-' + icon +
'" aria-hidden="true"></span> ' + message + '</div>'; '" aria-hidden="true"></span> <span>' + message + '</span></div>';
$('body').html( $('body').html(
'<div id="errormessage" role="alert" class="statusmessage ' + '<div id="errormessage" role="alert" class="statusmessage ' +
'alert alert-danger hidden"><span class="glyphicon ' + 'alert alert-danger hidden"><span class="glyphicon ' +
@ -61,12 +132,27 @@ describe('Alert', function () {
}); });
describe('showRemaining', function () { describe('showRemaining', function () {
before(function () { jsc.property(
cleanup(); 'shows remaining time (basic)',
}); jsc.array(common.jscAlnumString()),
jsc.array(common.jscAlnumString()),
'integer',
function (message, string, number) {
message = message.join('');
string = string.join('');
var expected = '<div id="remainingtime" class="">' + string + message + number + '</div>';
$('body').html(
'<div id="remainingtime" class="hidden"></div>'
);
$.PrivateBin.Alert.init();
$.PrivateBin.Alert.showRemaining(['%s' + message + '%d', string, number]);
var result = $('body').html();
return expected === result;
}
);
jsc.property( jsc.property(
'shows remaining time', 'shows remaining time (bootstrap)',
jsc.array(common.jscAlnumString()), jsc.array(common.jscAlnumString()),
jsc.array(common.jscAlnumString()), jsc.array(common.jscAlnumString()),
'integer', 'integer',
@ -76,7 +162,7 @@ describe('Alert', function () {
var expected = '<div id="remainingtime" role="alert" ' + var expected = '<div id="remainingtime" role="alert" ' +
'class="alert alert-info"><span ' + 'class="alert alert-info"><span ' +
'class="glyphicon glyphicon-fire" aria-hidden="true">' + 'class="glyphicon glyphicon-fire" aria-hidden="true">' +
'</span> ' + string + message + number + '</div>'; '</span> <span>' + string + message + number + '</span></div>';
$('body').html( $('body').html(
'<div id="remainingtime" role="alert" class="hidden ' + '<div id="remainingtime" role="alert" class="hidden ' +
'alert alert-info"><span class="glyphicon ' + 'alert alert-info"><span class="glyphicon ' +
@ -91,12 +177,30 @@ describe('Alert', function () {
}); });
describe('showLoading', function () { describe('showLoading', function () {
before(function () { jsc.property(
cleanup(); 'shows a loading message (basic)',
}); 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 = '<div id="loadingindicator" class="">' + message + '</div>';
$('body').html(
'<div id="loadingindicator" class="hidden">' + defaultMessage + '</div>'
);
$.PrivateBin.Alert.init();
$.PrivateBin.Alert.showLoading(message, icon);
var result = $('body').html();
return expected === result;
}
);
jsc.property( jsc.property(
'shows a loading message', 'shows a loading message (bootstrap)',
jsc.array(common.jscAlnumString()), jsc.array(common.jscAlnumString()),
jsc.array(common.jscAlnumString()), jsc.array(common.jscAlnumString()),
function (message, icon) { function (message, icon) {
@ -109,7 +213,7 @@ describe('Alert', function () {
var expected = '<ul class="nav navbar-nav"><li ' + var expected = '<ul class="nav navbar-nav"><li ' +
'id="loadingindicator" class="navbar-text"><span ' + 'id="loadingindicator" class="navbar-text"><span ' +
'class="glyphicon glyphicon-' + icon + 'class="glyphicon glyphicon-' + icon +
'" aria-hidden="true"></span> ' + message + '</li></ul>'; '" aria-hidden="true"></span> <span>' + message + '</span></li></ul>';
$('body').html( $('body').html(
'<ul class="nav navbar-nav"><li id="loadingindicator" ' + '<ul class="nav navbar-nav"><li id="loadingindicator" ' +
'class="navbar-text hidden"><span class="glyphicon ' + 'class="navbar-text hidden"><span class="glyphicon ' +
@ -125,10 +229,6 @@ describe('Alert', function () {
}); });
describe('hideLoading', function () { describe('hideLoading', function () {
before(function () {
cleanup();
});
it( it(
'hides the loading message', 'hides the loading message',
function() { function() {
@ -150,10 +250,6 @@ describe('Alert', function () {
}); });
describe('hideMessages', function () { describe('hideMessages', function () {
before(function () {
cleanup();
});
it( it(
'hides all messages', 'hides all messages',
function() { function() {
@ -176,10 +272,6 @@ describe('Alert', function () {
}); });
describe('setCustomHandler', function () { describe('setCustomHandler', function () {
before(function () {
cleanup();
});
jsc.property( jsc.property(
'calls a given handler function', 'calls a given handler function',
'nat 3', 'nat 3',

View file

@ -4,28 +4,26 @@ var common = require('../common');
describe('AttachmentViewer', function () { describe('AttachmentViewer', function () {
describe('setAttachment, showAttachment, removeAttachment, hideAttachment, hideAttachmentPreview, hasAttachment, getAttachment & moveAttachmentTo', function () { describe('setAttachment, showAttachment, removeAttachment, hideAttachment, hideAttachmentPreview, hasAttachment, getAttachment & moveAttachmentTo', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
cleanup();
});
jsc.property( jsc.property(
'displays & hides data as requested', 'displays & hides data as requested',
common.jscMimeTypes(), common.jscMimeTypes(),
jsc.nearray(common.jscBase64String()),
'string', 'string',
'string', 'string',
'string', 'string',
function (mimeType, base64, filename, prefix, postfix) { 'string',
var clean = jsdom(), function (mimeType, rawdata, filename, prefix, postfix) {
data = 'data:' + mimeType + ';base64,' + base64.join(''), let clean = jsdom(),
data = 'data:' + mimeType + ';base64,' + btoa(rawdata),
previewSupported = ( previewSupported = (
mimeType.substring(0, 6) === 'image/' || mimeType.substring(0, 6) === 'image/' ||
mimeType.substring(0, 6) === 'audio/' || mimeType.substring(0, 6) === 'audio/' ||
mimeType.substring(0, 6) === 'video/' || mimeType.substring(0, 6) === 'video/' ||
mimeType.match(/\/pdf/i) mimeType.match(/\/pdf/i)
), ),
results = []; results = [],
prefix = prefix.replace(/%(s|d)/g, '%%'); result = '';
prefix = prefix.replace(/%(s|d)/g, '%%');
postfix = postfix.replace(/%(s|d)/g, '%%'); postfix = postfix.replace(/%(s|d)/g, '%%');
$('body').html( $('body').html(
'<div id="attachment" role="alert" class="hidden alert ' + '<div id="attachment" role="alert" class="hidden alert ' +
@ -45,7 +43,7 @@ describe('AttachmentViewer', function () {
} else { } else {
$.PrivateBin.AttachmentViewer.setAttachment(data); $.PrivateBin.AttachmentViewer.setAttachment(data);
} }
var attachment = $.PrivateBin.AttachmentViewer.getAttachment(); const attachment = $.PrivateBin.AttachmentViewer.getAttachment();
results.push( results.push(
$.PrivateBin.AttachmentViewer.hasAttachment() && $.PrivateBin.AttachmentViewer.hasAttachment() &&
$('#attachment').hasClass('hidden') && $('#attachment').hasClass('hidden') &&
@ -72,13 +70,24 @@ describe('AttachmentViewer', function () {
!$('#attachment').hasClass('hidden') && !$('#attachment').hasClass('hidden') &&
(previewSupported ? !$('#attachmentPreview').hasClass('hidden') : $('#attachmentPreview').hasClass('hidden')) (previewSupported ? !$('#attachmentPreview').hasClass('hidden') : $('#attachmentPreview').hasClass('hidden'))
); );
var element = $('<div></div>'); let element = $('<div>');
$.PrivateBin.AttachmentViewer.moveAttachmentTo(element, prefix + '%s' + postfix); $.PrivateBin.AttachmentViewer.moveAttachmentTo(element, prefix + '%s' + postfix);
// messageIDs with links get a relaxed treatment
if (prefix.indexOf('<a') === -1 && postfix.indexOf('<a') === -1) {
result = $('<textarea>').text((prefix + filename + postfix)).text();
} else {
result = DOMPurify.sanitize(
prefix + $.PrivateBin.Helper.htmlEntities(filename) + postfix, {
ALLOWED_TAGS: ['a', 'i', 'span'],
ALLOWED_ATTR: ['href', 'id']
}
);
}
if (filename.length) { if (filename.length) {
results.push( results.push(
element.children()[0].href === data && element.children()[0].href === data &&
element.children()[0].getAttribute('download') === filename && element.children()[0].getAttribute('download') === filename &&
element.children()[0].text === prefix + filename + postfix element.children()[0].text === result
); );
} else { } else {
results.push(element.children()[0].href === data); results.push(element.children()[0].href === data);

View file

@ -4,9 +4,6 @@ var common = require('../common');
describe('DiscussionViewer', function () { describe('DiscussionViewer', function () {
describe('handleNotification, prepareNewDiscussion, addComment, finishDiscussion, getReplyMessage, getReplyNickname, getReplyCommentId & highlightComment', function () { describe('handleNotification, prepareNewDiscussion, addComment, finishDiscussion, getReplyMessage, getReplyNickname, getReplyCommentId & highlightComment', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
cleanup();
});
jsc.property( jsc.property(
'displays & hides comments as requested', 'displays & hides comments as requested',

View file

@ -4,9 +4,6 @@ require('../common');
describe('Editor', function () { describe('Editor', function () {
describe('show, hide, getText, setText & isPreview', function () { describe('show, hide, getText, setText & isPreview', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
cleanup();
});
jsc.property( jsc.property(
'returns text fed into the textarea, handles editor tabs', 'returns text fed into the textarea, handles editor tabs',

View file

@ -3,10 +3,6 @@ var common = require('../common');
describe('Helper', function () { describe('Helper', function () {
describe('secondsToHuman', function () { describe('secondsToHuman', function () {
after(function () {
cleanup();
});
jsc.property('returns an array with a number and a word', 'integer', function (number) { jsc.property('returns an array with a number and a word', 'integer', function (number) {
var result = $.PrivateBin.Helper.secondsToHuman(number); var result = $.PrivateBin.Helper.secondsToHuman(number);
return Array.isArray(result) && return Array.isArray(result) &&
@ -57,11 +53,11 @@ describe('Helper', function () {
'nearray string', 'nearray string',
function (ids, contents) { function (ids, contents) {
var html = '', var html = '',
result = true; result = true,
clean = jsdom(html);
ids.forEach(function(item, i) { ids.forEach(function(item, i) {
html += '<div id="' + item.join('') + '">' + common.htmlEntities(contents[i] || contents[0]) + '</div>'; html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.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. // 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. // Once there is one, uncomment the block below to actually check the result.
/* /*
@ -77,8 +73,8 @@ describe('Helper', function () {
}); });
describe('urls2links', function () { describe('urls2links', function () {
after(function () { before(function () {
cleanup(); cleanup = jsdom();
}); });
jsc.property( jsc.property(
@ -97,11 +93,11 @@ describe('Helper', function () {
jsc.array(common.jscHashString()), jsc.array(common.jscHashString()),
'string', 'string',
function (prefix, schema, address, query, fragment, postfix) { function (prefix, schema, address, query, fragment, postfix) {
var query = query.join(''), query = query.join('');
fragment = fragment.join(''), fragment = fragment.join('');
url = schema + '://' + address.join('') + '/?' + query + '#' + fragment, prefix = $.PrivateBin.Helper.htmlEntities(prefix);
prefix = common.htmlEntities(prefix), postfix = ' ' + $.PrivateBin.Helper.htmlEntities(postfix);
postfix = ' ' + common.htmlEntities(postfix); let url = schema + '://' + address.join('') + '/?' + query + '#' + fragment;
// special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x
if ( if (
@ -122,19 +118,15 @@ describe('Helper', function () {
jsc.array(common.jscQueryString()), jsc.array(common.jscQueryString()),
'string', 'string',
function (prefix, query, postfix) { function (prefix, query, postfix) {
var url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''), prefix = $.PrivateBin.Helper.htmlEntities(prefix);
prefix = common.htmlEntities(prefix), postfix = $.PrivateBin.Helper.htmlEntities(postfix);
postfix = common.htmlEntities(postfix); let url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,'');
return prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + ' ' + postfix); return prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + ' ' + postfix);
} }
); );
}); });
describe('sprintf', function () { describe('sprintf', function () {
after(function () {
cleanup();
});
jsc.property( jsc.property(
'replaces %s in strings with first given parameter', 'replaces %s in strings with first given parameter',
'string', 'string',
@ -183,9 +175,9 @@ describe('Helper', function () {
'string', 'string',
'string', 'string',
function (prefix, uint, middle, string, postfix) { function (prefix, uint, middle, string, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%'); prefix = prefix.replace(/%(s|d)/g, '');
middle = middle.replace(/%(s|d)/g, '%%'); middle = middle.replace(/%(s|d)/g, '');
postfix = postfix.replace(/%(s|d)/g, '%%'); postfix = postfix.replace(/%(s|d)/g, '');
var params = [prefix + '%d' + middle + '%s' + postfix, uint, string], var params = [prefix + '%d' + middle + '%s' + postfix, uint, string],
result = prefix + uint + middle + string + postfix; result = prefix + uint + middle + string + postfix;
return result === $.PrivateBin.Helper.sprintf.apply(this, params); return result === $.PrivateBin.Helper.sprintf.apply(this, params);
@ -199,9 +191,9 @@ describe('Helper', function () {
'string', 'string',
'string', 'string',
function (prefix, uint, middle, string, postfix) { function (prefix, uint, middle, string, postfix) {
prefix = prefix.replace(/%(s|d)/g, '%%'); prefix = prefix.replace(/%(s|d)/g, '');
middle = middle.replace(/%(s|d)/g, '%%'); middle = middle.replace(/%(s|d)/g, '');
postfix = postfix.replace(/%(s|d)/g, '%%'); postfix = postfix.replace(/%(s|d)/g, '');
var params = [prefix + '%s' + middle + '%d' + postfix, string, uint], var params = [prefix + '%s' + middle + '%d' + postfix, string, uint],
result = prefix + string + middle + uint + postfix; result = prefix + string + middle + uint + postfix;
return result === $.PrivateBin.Helper.sprintf.apply(this, params); return result === $.PrivateBin.Helper.sprintf.apply(this, params);
@ -211,17 +203,20 @@ describe('Helper', function () {
describe('getCookie', function () { describe('getCookie', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
cleanup();
});
jsc.property( jsc.property(
'returns the requested cookie', 'returns the requested cookie',
'nearray asciinestring', jsc.nearray(jsc.nearray(common.jscAlnumString())),
'nearray asciistring', jsc.nearray(jsc.nearray(common.jscAlnumString())),
function (labels, values) { function (labels, values) {
var selectedKey = '', selectedValue = '', var selectedKey = '', selectedValue = '',
cookieArray = []; cookieArray = [];
labels.forEach(function(item, i) { labels.forEach(function(item, i) {
// deliberatly using a non-ascii key for replacing invalid characters var key = item.join(''),
var key = item.replace(/[\s;,=]/g, Array(i+2).join('£')), value = (values[i] || values[0]).join('');
value = (values[i] || values[0]).replace(/[\s;,=]/g, '');
cookieArray.push(key + '=' + value); cookieArray.push(key + '=' + value);
if (Math.random() < 1 / i || selectedKey === key) if (Math.random() < 1 / i || selectedKey === key)
{ {
@ -231,6 +226,7 @@ describe('Helper', function () {
}); });
var clean = jsdom('', {cookie: cookieArray}), var clean = jsdom('', {cookie: cookieArray}),
result = $.PrivateBin.Helper.getCookie(selectedKey); result = $.PrivateBin.Helper.getCookie(selectedKey);
$.PrivateBin.Helper.reset();
clean(); clean();
return result === selectedValue; return result === selectedValue;
} }
@ -239,21 +235,19 @@ describe('Helper', function () {
describe('baseUri', function () { describe('baseUri', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
$.PrivateBin.Helper.reset();
});
jsc.property( jsc.property(
'returns the URL without query & fragment', 'returns the URL without query & fragment',
common.jscSchemas(), jsc.elements(['http', 'https']),
jsc.nearray(common.jscA2zString()), jsc.nearray(common.jscA2zString()),
jsc.array(common.jscA2zString()),
jsc.array(common.jscQueryString()), jsc.array(common.jscQueryString()),
'string', 'string',
function (schema, address, query, fragment) { function (schema, address, path, query, fragment) {
var expected = schema + '://' + address.join('') + '/', $.PrivateBin.Helper.reset();
var path = path.join('') + (path.length > 0 ? '/' : ''),
expected = schema + '://' + address.join('') + '/' + path,
clean = jsdom('', {url: expected + '?' + query.join('') + '#' + fragment}), clean = jsdom('', {url: expected + '?' + query.join('') + '#' + fragment}),
result = $.PrivateBin.Helper.baseUri(); result = $.PrivateBin.Helper.baseUri();
$.PrivateBin.Helper.reset();
clean(); clean();
return expected === result; return expected === result;
} }
@ -261,16 +255,16 @@ describe('Helper', function () {
}); });
describe('htmlEntities', function () { describe('htmlEntities', function () {
after(function () { before(function () {
cleanup(); cleanup = jsdom();
}); });
jsc.property( jsc.property(
'removes all HTML entities from any given string', 'removes all HTML entities from any given string',
'string', 'string',
function (string) { function (string) {
var result = common.htmlEntities(string); var result = $.PrivateBin.Helper.htmlEntities(string);
return !(/[<>"'`=\/]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result))); return !(/[<>]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result)));
} }
); );
}); });

View file

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

View file

@ -5,18 +5,18 @@ describe('Model', function () {
describe('getExpirationDefault', function () { describe('getExpirationDefault', function () {
before(function () { before(function () {
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
cleanup(); cleanup = jsdom();
}); });
jsc.property( jsc.property(
'returns the contents of the element with id "pasteExpiration"', 'returns the contents of the element with id "pasteExpiration"',
'array asciinestring', 'nearray asciinestring',
'string', 'string',
'small nat', 'small nat',
function (keys, value, key) { function (keys, value, key) {
keys = keys.map(common.htmlEntities); keys = keys.map($.PrivateBin.Helper.htmlEntities);
value = common.htmlEntities(value); value = $.PrivateBin.Helper.htmlEntities(value);
var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), var content = keys.length > key ? keys[key] : keys[0],
contents = '<select id="pasteExpiration" name="pasteExpiration">'; contents = '<select id="pasteExpiration" name="pasteExpiration">';
keys.forEach(function(item) { keys.forEach(function(item) {
contents += '<option value="' + item + '"'; contents += '<option value="' + item + '"';
@ -27,7 +27,7 @@ describe('Model', function () {
}); });
contents += '</select>'; contents += '</select>';
$('body').html(contents); $('body').html(contents);
var result = common.htmlEntities( var result = $.PrivateBin.Helper.htmlEntities(
$.PrivateBin.Model.getExpirationDefault() $.PrivateBin.Model.getExpirationDefault()
); );
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
@ -39,18 +39,20 @@ describe('Model', function () {
describe('getFormatDefault', function () { describe('getFormatDefault', function () {
before(function () { before(function () {
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
});
after(function () {
cleanup(); cleanup();
}); });
jsc.property( jsc.property(
'returns the contents of the element with id "pasteFormatter"', 'returns the contents of the element with id "pasteFormatter"',
'array asciinestring', 'nearray asciinestring',
'string', 'string',
'small nat', 'small nat',
function (keys, value, key) { function (keys, value, key) {
keys = keys.map(common.htmlEntities); keys = keys.map($.PrivateBin.Helper.htmlEntities);
value = common.htmlEntities(value); value = $.PrivateBin.Helper.htmlEntities(value);
var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), var content = keys.length > key ? keys[key] : keys[0],
contents = '<select id="pasteFormatter" name="pasteFormatter">'; contents = '<select id="pasteFormatter" name="pasteFormatter">';
keys.forEach(function(item) { keys.forEach(function(item) {
contents += '<option value="' + item + '"'; contents += '<option value="' + item + '"';
@ -61,7 +63,7 @@ describe('Model', function () {
}); });
contents += '</select>'; contents += '</select>';
$('body').html(contents); $('body').html(contents);
var result = common.htmlEntities( var result = $.PrivateBin.Helper.htmlEntities(
$.PrivateBin.Model.getFormatDefault() $.PrivateBin.Model.getFormatDefault()
); );
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
@ -74,7 +76,6 @@ describe('Model', function () {
this.timeout(30000); this.timeout(30000);
before(function () { before(function () {
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
cleanup();
}); });
jsc.property( jsc.property(
@ -185,7 +186,6 @@ describe('Model', function () {
describe('getTemplate', function () { describe('getTemplate', function () {
before(function () { before(function () {
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
cleanup();
}); });
jsc.property( jsc.property(

View file

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

View file

@ -4,9 +4,6 @@ var common = require('../common');
describe('PasteViewer', function () { describe('PasteViewer', function () {
describe('run, hide, getText, setText, getFormat, setFormat & isPrettyPrinted', function () { describe('run, hide, getText, setText, getFormat, setFormat & isPrettyPrinted', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
cleanup();
});
jsc.property( jsc.property(
'displays text according to format', 'displays text according to format',

View file

@ -6,10 +6,6 @@ describe('Prompt', function () {
// in nodejs -> replace the prompt in the "page" template with a modal // in nodejs -> replace the prompt in the "page" template with a modal
describe('requestPassword & getPassword', function () { describe('requestPassword & getPassword', function () {
this.timeout(30000); this.timeout(30000);
before(function () {
$.PrivateBin.Model.reset();
cleanup();
});
jsc.property( jsc.property(
'returns the password fed into the dialog', 'returns the password fed into the dialog',
@ -26,6 +22,7 @@ describe('Prompt', function () {
'password"></div><button type="submit">Decrypt</button>' + 'password"></div><button type="submit">Decrypt</button>' +
'</form></div></div></div></div>' '</form></div></div></div></div>'
); );
$.PrivateBin.Model.reset();
$.PrivateBin.Model.init(); $.PrivateBin.Model.init();
$.PrivateBin.Prompt.init(); $.PrivateBin.Prompt.init();
$.PrivateBin.Prompt.requestPassword(); $.PrivateBin.Prompt.requestPassword();

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;
@ -28,7 +28,7 @@ class Controller
* *
* @const string * @const string
*/ */
const VERSION = '1.2.1'; const VERSION = '1.2.3';
/** /**
* minimal required PHP version * minimal required PHP version

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Data; namespace PrivateBin\Data;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Data; namespace PrivateBin\Data;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Data; namespace PrivateBin\Data;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;
@ -125,9 +125,31 @@ class I18n
} else { } else {
$args[0] = self::$_translations[$messageId]; $args[0] = self::$_translations[$messageId];
} }
// encode any non-integer arguments and the message ID, if it doesn't contain a link
$argsCount = count($args);
if ($argsCount > 1) {
for ($i = 0; $i < $argsCount; ++$i) {
if (($i > 0 && !is_int($args[$i])) || strpos($args[0], '<a') === false) {
$args[$i] = self::encode($args[$i]);
}
}
}
return call_user_func_array('sprintf', $args); return call_user_func_array('sprintf', $args);
} }
/**
* encode HTML entities for output into an HTML5 document
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function encode($string)
{
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5 | ENT_DISALLOWED, 'UTF-8', false);
}
/** /**
* loads translations * loads translations
* *

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Model; namespace PrivateBin\Model;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Model; namespace PrivateBin\Model;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Model; namespace PrivateBin\Model;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Persistence; namespace PrivateBin\Persistence;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Persistence; namespace PrivateBin\Persistence;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Persistence; namespace PrivateBin\Persistence;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin\Persistence; namespace PrivateBin\Persistence;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;

View file

@ -7,7 +7,7 @@
* @link https://github.com/PrivateBin/PrivateBin * @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.2.1 * @version 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;

View file

@ -8,7 +8,7 @@
* @link http://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd * @link http://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 0.0.5 beta PrivateBin 1.2.1 * @version 0.0.5 beta PrivateBin 1.2.3
*/ */
namespace PrivateBin; namespace PrivateBin;

View file

@ -4,7 +4,7 @@ $isCpct = substr($template, 9, 8) === '-compact';
$isDark = substr($template, 9, 5) === '-dark'; $isDark = substr($template, 9, 5) === '-dark';
$isPage = substr($template, -5) === '-page'; $isPage = substr($template, -5) === '-page';
?><!DOCTYPE html> ?><!DOCTYPE html>
<html> <html lang="<?php echo I18n::_('en'); ?>">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -15,11 +15,11 @@ $isPage = substr($template, -5) === '-page';
<?php <?php
if (!$isDark): if (!$isDark):
?> ?>
<link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-3.3.5.css" /> <link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-3.4.1.css" />
<?php <?php
endif; endif;
?> ?>
<link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-theme-3.3.5.css" /> <link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-theme-3.4.1.css" />
<?php <?php
if ($isDark): if ($isDark):
?> ?>
@ -41,12 +41,12 @@ if ($SYNTAXHIGHLIGHTING):
endif; endif;
?> ?>
<noscript><link type="text/css" rel="stylesheet" href="css/noscript.css" /></noscript> <noscript><link type="text/css" rel="stylesheet" href="css/noscript.css" /></noscript>
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.3.1.js" integrity="sha512-+NqPlbbtM1QqiK8ZAo4Yrj2c4lNQoGv8P79DPtKzj++l5jnN39rHA/xsqn8zE9l0uSoxaCdrOgFs6yjyfbBxSg==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/jquery-3.4.1.js" integrity="sha512-bnIvzh6FU75ZKxp0GXLH9bewza/OIw6dLVh9ICg0gogclmYGguQJWl8U30WpbsGTqbIiAwxTsbe76DErLq5EDQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/sjcl-1.0.7.js" integrity="sha512-J2eNenPwyfXkMVNMFz9Q54kKfYi5AA3mQWpNgtjSJzsKHtpbhUt/7bvcjGwwmzE8ZUVWMI/ndagIX1lG+SfxGA==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/sjcl-1.0.8.js" integrity="sha512-J2eNenPwyfXkMVNMFz9Q54kKfYi5AA3mQWpNgtjSJzsKHtpbhUt/7bvcjGwwmzE8ZUVWMI/ndagIX1lG+SfxGA==" crossorigin="anonymous"></script>
<?php <?php
if ($QRCODE): if ($QRCODE):
?> ?>
<script async type="text/javascript" data-cfasync="false" src="js/kjua-0.1.2.js" integrity="sha512-hmvfOhcr4J8bjQ2GuNVzfSbuulv72wgQCJpgnXc2+cCHKqvYo8pK2nc0Q4Esem2973zo1radyIMTEkt+xJlhBA==" crossorigin="anonymous"></script> <script async type="text/javascript" data-cfasync="false" src="js/kjua-0.6.0.js" integrity="sha512-GEEIHvphDt1NmaxzX8X1ZkBiGKXCv+Ofzwi8SMEH5wQVWqdGIvBO/fnxxKZ90RU1bVp6srS68nHIpZo6iVcG9g==" crossorigin="anonymous"></script>
<?php <?php
endif; endif;
if ($ZEROBINCOMPATIBILITY): if ($ZEROBINCOMPATIBILITY):
@ -70,12 +70,12 @@ if ($SYNTAXHIGHLIGHTING):
endif; endif;
if ($MARKDOWN): if ($MARKDOWN):
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/showdown-1.8.6.js" integrity="sha512-YFg2sBCGT00I6X5KzgCLP4VqRlmPMRhkVvJS9oJKk5LxiUzzcjzV5m4fNf6mQMctLrhgS5LFKiFF3vzIuXbjAw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/showdown-1.9.1.js" integrity="sha512-nRri7kqh3iRLdHbhtjfe8w9eAQPmt+ubH5U88UZyKbz6O9Q0q4haaXF0krOUclKmRJou/kKZYulgBHvHXPqOvg==" crossorigin="anonymous"></script>
<?php <?php
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-1.0.7.js" integrity="sha512-VnKJHLosO8z2ojNvWk9BEKYqnhZyWK9rM90FgZUUEp/PRnUqR5OLLKE0a3BkVmn7YgB7LXRrjHgFHQYKd6DAIA==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-2.0.8.js" integrity="sha512-QwcEKGuEmKtMguCO9pqNtUtZqq9b/tJ8gNr5qhY8hykq3zKTlDOvpZAmf6Rs8yH35Bz1ZdctUjj2qEWxT5aXCg==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-5xT6WHyurvC0LFESbkycBMAjhsi0KL/Xhx2oU+d0bqiJPkWK6ZAZgZ7I02oQiXaLtFSLf7UqpBo5XEePlfhH7Q==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LqJqykq4XPBKgf0fiUu/4NmxGI7oioFgQFeU2wTF9IHOdQ7wUlsSHw9L5vr40rGj0UBjJaX/u5dF62FSF+GNvg==" crossorigin="anonymous"></script>
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]--> <![endif]-->
@ -422,7 +422,7 @@ if (strlen($NOTICE)):
?> ?>
<div role="alert" class="alert alert-info"> <div role="alert" class="alert alert-info">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> <span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
<?php echo htmlspecialchars($NOTICE), PHP_EOL; ?> <?php echo I18n::encode($NOTICE), PHP_EOL; ?>
</div> </div>
<?php <?php
endif; endif;
@ -442,11 +442,11 @@ endif;
?> ?>
<div id="status" role="alert" class="statusmessage alert alert-info<?php echo empty($STATUS) ? ' hidden' : '' ?>"> <div id="status" role="alert" class="statusmessage alert alert-info<?php echo empty($STATUS) ? ' hidden' : '' ?>">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> <span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
<?php echo htmlspecialchars($STATUS), PHP_EOL; ?> <?php echo I18n::encode($STATUS), PHP_EOL; ?>
</div> </div>
<div id="errormessage" role="alert" class="statusmessage<?php echo empty($ERROR) ? ' hidden' : '' ?> alert alert-danger"> <div id="errormessage" role="alert" class="statusmessage<?php echo empty($ERROR) ? ' hidden' : '' ?> alert alert-danger">
<span class="glyphicon glyphicon-alert" aria-hidden="true"></span> <span class="glyphicon glyphicon-alert" aria-hidden="true"></span>
<?php echo htmlspecialchars($ERROR), PHP_EOL; ?> <?php echo I18n::encode($ERROR), PHP_EOL; ?>
</div> </div>
<noscript> <noscript>
<div id="noscript" role="alert" class="nonworking alert alert-<?php echo $isDark ? 'error' : 'warning'; ?>"> <div id="noscript" role="alert" class="nonworking alert alert-<?php echo $isDark ? 'error' : 'warning'; ?>">
@ -463,7 +463,8 @@ endif;
<?php echo I18n::_('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:'), PHP_EOL; ?> <?php echo I18n::_('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:'), PHP_EOL; ?>
<a href="https://www.mozilla.org/firefox/">Firefox</a>, <a href="https://www.mozilla.org/firefox/">Firefox</a>,
<a href="https://www.opera.com/">Opera</a>, <a href="https://www.opera.com/">Opera</a>,
<a href="https://www.google.com/chrome">Chrome</a> <a href="https://www.google.com/chrome">Chrome</a><br />
<span class="small"><?php echo I18n::_('For more information <a href="%s">see this FAQ entry</a>.', 'https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-it-show-me-the-error-privatebin-requires-a-modern-browser-to-work'); ?></span>
</div> </div>
<div id="pastesuccess" role="alert" class="hidden alert alert-success"> <div id="pastesuccess" role="alert" class="hidden alert alert-success">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> <span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
@ -472,7 +473,7 @@ endif;
<?php <?php
if (strlen($URLSHORTENER)): if (strlen($URLSHORTENER)):
?> ?>
<button id="shortenbutton" data-shortener="<?php echo htmlspecialchars($URLSHORTENER); ?>" type="button" class="btn btn-<?php echo $isDark ? 'warning' : 'primary'; ?>"> <button id="shortenbutton" data-shortener="<?php echo I18n::encode($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; ?> <span class="glyphicon glyphicon-send" aria-hidden="true"></span> <?php echo I18n::_('Shorten URL'), PHP_EOL; ?>
</button> </button>
<?php <?php
@ -505,7 +506,7 @@ endif;
<div id="noscript" role="alert" class="nonworking alert alert-info noscript-hide"> <div id="noscript" role="alert" class="nonworking alert alert-info noscript-hide">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<?php echo I18n::_('Loading…'); ?><br /> <?php echo I18n::_('Loading…'); ?><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> <span class="small"><?php echo I18n::_('In case this message never disappears please have a look at <a href="%s">this FAQ for information to troubleshoot</a>.', 'https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-the-loading-message-not-go-away'); ?></span>
</div> </div>
</section> </section>
<footer class="container"> <footer class="container">

View file

@ -1,7 +1,7 @@
<?php <?php
use PrivateBin\I18n; use PrivateBin\I18n;
?><!DOCTYPE html> ?><!DOCTYPE html>
<html lang="en"> <html lang="<?php echo I18n::_('en'); ?>">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
@ -20,12 +20,12 @@ if ($SYNTAXHIGHLIGHTING):
endif; endif;
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.3.1.js" integrity="sha512-+NqPlbbtM1QqiK8ZAo4Yrj2c4lNQoGv8P79DPtKzj++l5jnN39rHA/xsqn8zE9l0uSoxaCdrOgFs6yjyfbBxSg==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/jquery-3.4.1.js" integrity="sha512-bnIvzh6FU75ZKxp0GXLH9bewza/OIw6dLVh9ICg0gogclmYGguQJWl8U30WpbsGTqbIiAwxTsbe76DErLq5EDQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/sjcl-1.0.7.js" integrity="sha512-J2eNenPwyfXkMVNMFz9Q54kKfYi5AA3mQWpNgtjSJzsKHtpbhUt/7bvcjGwwmzE8ZUVWMI/ndagIX1lG+SfxGA==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/sjcl-1.0.8.js" integrity="sha512-J2eNenPwyfXkMVNMFz9Q54kKfYi5AA3mQWpNgtjSJzsKHtpbhUt/7bvcjGwwmzE8ZUVWMI/ndagIX1lG+SfxGA==" crossorigin="anonymous"></script>
<?php <?php
if ($QRCODE): if ($QRCODE):
?> ?>
<script async type="text/javascript" data-cfasync="false" src="js/kjua-0.1.2.js" integrity="sha512-hmvfOhcr4J8bjQ2GuNVzfSbuulv72wgQCJpgnXc2+cCHKqvYo8pK2nc0Q4Esem2973zo1radyIMTEkt+xJlhBA==" crossorigin="anonymous"></script> <script async type="text/javascript" data-cfasync="false" src="js/kjua-0.6.0.js" integrity="sha512-GEEIHvphDt1NmaxzX8X1ZkBiGKXCv+Ofzwi8SMEH5wQVWqdGIvBO/fnxxKZ90RU1bVp6srS68nHIpZo6iVcG9g==" crossorigin="anonymous"></script>
<?php <?php
endif; endif;
if ($ZEROBINCOMPATIBILITY): if ($ZEROBINCOMPATIBILITY):
@ -48,12 +48,12 @@ if ($SYNTAXHIGHLIGHTING):
endif; endif;
if ($MARKDOWN): if ($MARKDOWN):
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/showdown-1.8.6.js" integrity="sha512-YFg2sBCGT00I6X5KzgCLP4VqRlmPMRhkVvJS9oJKk5LxiUzzcjzV5m4fNf6mQMctLrhgS5LFKiFF3vzIuXbjAw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/showdown-1.9.1.js" integrity="sha512-nRri7kqh3iRLdHbhtjfe8w9eAQPmt+ubH5U88UZyKbz6O9Q0q4haaXF0krOUclKmRJou/kKZYulgBHvHXPqOvg==" crossorigin="anonymous"></script>
<?php <?php
endif; endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-1.0.7.js" integrity="sha512-VnKJHLosO8z2ojNvWk9BEKYqnhZyWK9rM90FgZUUEp/PRnUqR5OLLKE0a3BkVmn7YgB7LXRrjHgFHQYKd6DAIA==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-2.0.8.js" integrity="sha512-QwcEKGuEmKtMguCO9pqNtUtZqq9b/tJ8gNr5qhY8hykq3zKTlDOvpZAmf6Rs8yH35Bz1ZdctUjj2qEWxT5aXCg==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-5xT6WHyurvC0LFESbkycBMAjhsi0KL/Xhx2oU+d0bqiJPkWK6ZAZgZ7I02oQiXaLtFSLf7UqpBo5XEePlfhH7Q==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LqJqykq4XPBKgf0fiUu/4NmxGI7oioFgQFeU2wTF9IHOdQ7wUlsSHw9L5vr40rGj0UBjJaX/u5dF62FSF+GNvg==" crossorigin="anonymous"></script>
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
<![endif]--> <![endif]-->
@ -73,26 +73,28 @@ endif;
<?php <?php
if (strlen($NOTICE)): if (strlen($NOTICE)):
?> ?>
<span class="blink"></span> <?php echo htmlspecialchars($NOTICE); <span class="blink"></span> <?php echo I18n::encode($NOTICE);
endif; endif;
?> ?>
</div> </div>
<h1 class="title reloadlink"><?php echo I18n::_($NAME); ?></h1><br /> <h1 class="title reloadlink"><?php echo I18n::_($NAME); ?></h1><br />
<h2 class="title"><?php echo I18n::_('Because ignorance is bliss'); ?></h2><br /> <h2 class="title"><?php echo I18n::_('Because ignorance is bliss'); ?></h2><br />
<h3 class="title"><?php echo $VERSION; ?></h3> <h3 class="title"><?php echo $VERSION; ?></h3>
<noscript><div id="noscript" class="nonworking"><?php echo I18n::_('JavaScript is required for %s to work.<br />Sorry for the inconvenience.', I18n::_($NAME)); ?></div></noscript> <noscript><div id="noscript" class="nonworking"><?php echo I18n::_('JavaScript is required for %s to work. Sorry for the inconvenience.', I18n::_($NAME)); ?></div></noscript>
<div id="oldienotice" class="nonworking"><?php echo I18n::_('%s requires a modern browser to work.', I18n::_($NAME)); ?></div> <div id="oldienotice" class="nonworking"><?php echo I18n::_('%s requires a modern browser to work.', I18n::_($NAME)); ?></div>
<div id="ienotice"><?php echo I18n::_('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:'), PHP_EOL; ?> <div id="ienotice">
<?php echo I18n::_('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:'), PHP_EOL; ?>
<a href="https://www.mozilla.org/firefox/">Firefox</a>, <a href="https://www.mozilla.org/firefox/">Firefox</a>,
<a href="https://www.opera.com/">Opera</a>, <a href="https://www.opera.com/">Opera</a>,
<a href="https://www.google.com/chrome">Chrome</a> <a href="https://www.google.com/chrome">Chrome</a><br />
<span class="small"><?php echo I18n::_('For more information <a href="%s">see this FAQ entry</a>.', 'https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-it-show-me-the-error-privatebin-requires-a-modern-browser-to-work'); ?></span>
</div> </div>
</header> </header>
<section> <section>
<article> <article>
<div id="loadingindicator" class="hidden"><?php echo I18n::_('Loading…'); ?></div> <div id="loadingindicator" class="hidden"><?php echo I18n::_('Loading…'); ?></div>
<div id="status"><?php echo htmlspecialchars($STATUS); ?></div> <div id="status"><?php echo I18n::encode($STATUS); ?></div>
<div id="errormessage" class="hidden"><?php echo htmlspecialchars($ERROR); ?></div> <div id="errormessage" class="hidden"><?php echo I18n::encode($ERROR); ?></div>
<div id="toolbar"> <div id="toolbar">
<button id="newbutton" class="reloadlink hidden"><img src="img/icon_new.png" width="11" height="15" alt="" /><?php echo I18n::_('New'); ?></button> <button id="newbutton" class="reloadlink hidden"><img src="img/icon_new.png" width="11" height="15" alt="" /><?php echo I18n::_('New'); ?></button>
<button id="retrybutton" class="reloadlink hidden"><?php echo I18n::_('Retry'), PHP_EOL; ?></button> <button id="retrybutton" class="reloadlink hidden"><?php echo I18n::_('Retry'), PHP_EOL; ?></button>
@ -200,7 +202,7 @@ endif;
<?php <?php
if (strlen($URLSHORTENER)): 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 I18n::encode($URLSHORTENER); ?>"><img src="img/icon_shorten.png" width="13" height="15" /><?php echo I18n::_('Shorten URL'); ?></button>
<?php <?php
endif; endif;
?> ?>
@ -252,7 +254,7 @@ endif;
<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"> <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> <?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> <span class="small"><?php echo I18n::_('In case this message never disappears please have a look at <a href="%s">this FAQ for information to troubleshoot</a>.', 'https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-the-loading-message-not-go-away'); ?></span>
</div> </div>
</section> </section>
</body> </body>

View file

@ -143,6 +143,17 @@ class I18nTest extends PHPUnit_Framework_TestCase
$this->assertEquals('some string + 1', I18n::_('some %s + %d', 'string', 1), 'browser language en'); $this->assertEquals('some string + 1', I18n::_('some %s + %d', 'string', 1), 'browser language en');
} }
public function testHtmlEntityEncoding()
{
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'foobar';
I18n::loadTranslations();
$input = '&<>"\'/`=';
$result = htmlspecialchars($input, ENT_QUOTES | ENT_HTML5 | ENT_DISALLOWED, 'UTF-8', false);
$this->assertEquals($result, I18n::encode($input), 'encodes HTML entities');
$this->assertEquals('<a>some ' . $result . ' + 1</a>', I18n::_('<a>some %s + %d</a>', $input, 1), 'encodes parameters in translations');
$this->assertEquals($result . $result, I18n::_($input . '%s', $input), 'encodes message ID as well, when no link');
}
public function testMessageIdsExistInAllLanguages() public function testMessageIdsExistInAllLanguages()
{ {
$messageIds = array(); $messageIds = array();

View file

@ -69,6 +69,12 @@ $ npm install jsverify jsdom@9 jsdom-global@2 mime-types
Note: If you use a distribution that provides nodeJS >= 6, then you can install Note: If you use a distribution that provides nodeJS >= 6, then you can install
the latest jsdom and jsdom-global packages and don't need to use @9 and @2. the latest jsdom and jsdom-global packages and don't need to use @9 and @2.
Note: When running Ubuntu 18.04, there is [a bug](https://bugs.launchpad.net/ubuntu/+source/nodejs/+bug/1779863)
due to the mismatch of nodejs 8 and OpenSSL 1.1 library it was compiled against.
Until this is solved, you may have to use [a PPA of nodejs, compiled against
OpenSSL 1.0](https://launchpad.net/~ddstreet/+archive/ubuntu/lp1779863) or use
nodejs 10 or later from a different source.
To run the tests, just change into the `js` directory and run istanbul: To run the tests, just change into the `js` directory and run istanbul:
```console ```console
$ cd PrivateBin/js $ cd PrivateBin/js