mirror of
https://github.com/PrivateBin/PrivateBin.git
synced 2026-07-18 17:05:30 +00:00
Compare commits
13 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a77305838 | ||
|
|
abd1418bc8 | ||
|
|
e80efda7c2 | ||
|
|
886d4419d7 | ||
|
|
2f17c5f1d6 | ||
|
|
360f2adeca | ||
|
|
b6f4278e07 | ||
|
|
4a50fed678 | ||
|
|
bb8b77d714 | ||
|
|
a0c0d154ee | ||
|
|
8504c3157c | ||
|
|
c03cd8f278 | ||
|
|
f71fd1be95 |
16 changed files with 111 additions and 18 deletions
2
.github/workflows/eslint.yml
vendored
2
.github/workflows/eslint.yml
vendored
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
|
|
|
|||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -224,7 +224,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
# PrivateBin version history
|
||||
|
||||
## 2.0.6 (not yet released)
|
||||
* CHANGED: Upgrading libraries to: DOMpurify 3.4.12
|
||||
* FIXED: Gracefully handle YOURLS replies with a 200 status code but no shorturl, instead of raising a TypeError
|
||||
* FIXED: Return "Invalid data." instead of HTTP 500 on malformed v2 JSON payloads (#1883)
|
||||
|
||||
## 2.0.5 (2026-07-11)
|
||||
* CHANGED: Show OS-specific copy hotkey hint (Cmd+c on Mac, Ctrl+c on others) (#1506)
|
||||
* FIXED: Prevent browsers from rendering unsafe attachments like HTML in a new tab
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ require('./prettify');
|
|||
global.prettyPrint = window.PR.prettyPrint;
|
||||
global.prettyPrintOne = window.PR.prettyPrintOne;
|
||||
global.showdown = require('./showdown-2.1.0');
|
||||
global.DOMPurify = require('./purify-3.4.1');
|
||||
global.DOMPurify = require('./purify-3.4.12');
|
||||
global.baseX = require('./base-x-5.0.1').baseX;
|
||||
global.Legacy = require('./legacy').Legacy;
|
||||
require('./privatebin');
|
||||
|
|
|
|||
11
js/package-lock.json
generated
11
js/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "privatebin",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "privatebin",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"license": "zlib-acknowledgement",
|
||||
"devDependencies": {
|
||||
"@peculiar/webcrypto": "^1.5.0",
|
||||
|
|
@ -3993,10 +3993,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.20.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
|
||||
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
|
||||
"version": "8.21.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
2
js/purify-3.4.12.js
Normal file
2
js/purify-3.4.12.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -123,7 +123,7 @@ class Configuration
|
|||
'js/legacy.js' => 'sha512-RQEo1hxpNc37i+jz/D9/JiAZhG8GFx3+SNxjYnI7jUgirDIqrCSj6QPAAZeaidditcWzsJ3jxfEj5lVm7ZwTRQ==',
|
||||
'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
|
||||
'js/privatebin.js' => 'sha512-g58rPgrKnvxBDnT6lzYazdEfgY3XhRh7Yt7i1jkJw4oSvN0k/VPZ4zBSL/dCGfXGIOHqlqmX8DO5UFv2XYqsXg==',
|
||||
'js/purify-3.4.1.js' => 'sha512-280a/Vb6fVFsYaeRrkuDp4EDmdYlt2XS+dlDEO/U9qljPrAraA2bIzHTNmP+9dpwPDDwTML+RS+h5iaagPwTzA==',
|
||||
'js/purify-3.4.12.js' => 'sha512-Akf6HnAJZm0sWWWI4gp2GYff0NDnHUB02XJE5S7Hdq/Z5xtMjkuFacsDA8ZtViv1gi+onBxMhEMIaGyQeGxBng==',
|
||||
'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
|
||||
'js/zlib-1.3.2.js' => 'sha512-RAhJgxg9siMIA8ky4c10Rc2zUgnK80olHB8Tt1IOYWY4Eh1WmrviQkDn+sgBlb38ZHq3tzufGC41kP360gmosQ==',
|
||||
'js/zlib.js' => 'sha512-QOaEwssHqHRRcWJ2Un3Kl2Zhyprzl7T8zmsKN2FppFxW3VR+8UChYOx2iuL0HbXK42fuBWJm5PNQJxufulrt/w==',
|
||||
|
|
|
|||
|
|
@ -56,7 +56,21 @@ class FormatV2
|
|||
return false;
|
||||
}
|
||||
|
||||
$cipherParams = $isComment ? $message['adata'] : $message['adata'][0];
|
||||
$cipherParams = $isComment ? $message['adata'] : ($message['adata'][0] ?? null);
|
||||
|
||||
// Make sure the cipher parameters are a properly sized array.
|
||||
if (!is_array($cipherParams) || count($cipherParams) < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the ciphertext and the cipher parameters used in the
|
||||
// string operations below are actually strings, so that malformed
|
||||
// input yields "Invalid data." instead of a fatal type error.
|
||||
if (!is_string($message['ct']) ||
|
||||
!is_string($cipherParams[0] ?? null) ||
|
||||
!is_string($cipherParams[1] ?? null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure some fields are base64 data:
|
||||
// - initialization vector
|
||||
|
|
@ -119,6 +133,7 @@ class FormatV2
|
|||
|
||||
// require only the key 'expire' in the metadata of pastes
|
||||
if (!$isComment && (
|
||||
!is_array($message['meta']) ||
|
||||
count($message['meta']) === 0 ||
|
||||
!array_key_exists('expire', $message['meta']) ||
|
||||
count($message['meta']) > 1
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class YourlsProxy extends AbstractProxy
|
|||
protected function _extractShortUrl(array $data): ?string
|
||||
{
|
||||
if ((int) ($data['statusCode'] ?? 0) === 200) {
|
||||
return $data['shorturl'] ?? 0;
|
||||
return $data['shorturl'] ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,11 @@ class Request
|
|||
try {
|
||||
$data = file_get_contents(self::$_inputStream);
|
||||
$this->_params = Json::decode($data);
|
||||
// a valid JSON scalar (number, bool or string) decodes
|
||||
// without error, but is not a usable set of parameters
|
||||
if (!is_array($this->_params)) {
|
||||
$this->_params = array();
|
||||
}
|
||||
} catch (JsonException $e) {
|
||||
// ignore error, $this->_params will remain empty
|
||||
}
|
||||
|
|
@ -189,8 +194,6 @@ class Request
|
|||
foreach ($required_keys as $key) {
|
||||
$data[$key] = $this->getParam($key, $key === 'v' ? 1 : '');
|
||||
}
|
||||
// forcing a cast to int or float
|
||||
$data['v'] = $data['v'] + 0;
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ if ($MARKDOWN) :
|
|||
<?php
|
||||
endif;
|
||||
?>
|
||||
<?php $this->_scriptTag('js/purify-3.4.1.js', 'defer'); ?>
|
||||
<?php $this->_scriptTag('js/purify-3.4.12.js', 'defer'); ?>
|
||||
<?php $this->_scriptTag('js/legacy.js', 'defer'); ?>
|
||||
<?php $this->_scriptTag('js/privatebin.js', 'defer'); ?>
|
||||
<!-- icon -->
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ if ($MARKDOWN) :
|
|||
<?php
|
||||
endif;
|
||||
?>
|
||||
<?php $this->_scriptTag('js/purify-3.4.1.js', 'defer'); ?>
|
||||
<?php $this->_scriptTag('js/purify-3.4.12.js', 'defer'); ?>
|
||||
<?php $this->_scriptTag('js/legacy.js', 'defer'); ?>
|
||||
<?php $this->_scriptTag('js/privatebin.js', 'defer'); ?>
|
||||
<!-- icon -->
|
||||
|
|
|
|||
|
|
@ -72,4 +72,41 @@ class FormatV2Test extends TestCase
|
|||
$paste = Helper::getPaste();
|
||||
$this->assertFalse(FormatV2::isValid($paste), 'invalid meta key');
|
||||
}
|
||||
|
||||
public function testFormatV2ValidatorRejectsMalformedTypes()
|
||||
{
|
||||
// malformed input must yield "false" (Invalid data) rather than a
|
||||
// fatal type error under strict_types
|
||||
$paste = Helper::getPastePost();
|
||||
$paste['v'] = 'abc';
|
||||
$this->assertFalse(FormatV2::isValid($paste), 'non-numeric version');
|
||||
|
||||
$paste = Helper::getPastePost();
|
||||
$paste['v'] = array();
|
||||
$this->assertFalse(FormatV2::isValid($paste), 'array version');
|
||||
|
||||
$paste = Helper::getPastePost();
|
||||
$paste['ct'] = array('not', 'a', 'string');
|
||||
$this->assertFalse(FormatV2::isValid($paste), 'non-string ciphertext');
|
||||
|
||||
$paste = Helper::getPastePost();
|
||||
$paste['meta'] = 'not-an-array';
|
||||
$this->assertFalse(FormatV2::isValid($paste), 'non-array meta');
|
||||
|
||||
$paste = Helper::getPastePost();
|
||||
$paste['adata'][0] = 'not-an-array';
|
||||
$this->assertFalse(FormatV2::isValid($paste), 'non-array cipher parameters');
|
||||
|
||||
$paste = Helper::getPastePost();
|
||||
$paste['adata'][0] = array();
|
||||
$this->assertFalse(FormatV2::isValid($paste), 'empty cipher parameters');
|
||||
|
||||
$paste = Helper::getPastePost();
|
||||
$paste['adata'][0][0] = array();
|
||||
$this->assertFalse(FormatV2::isValid($paste), 'non-string iv');
|
||||
|
||||
$comment = Helper::getCommentPost();
|
||||
$comment['ct'] = 42;
|
||||
$this->assertFalse(FormatV2::isValid($comment, true), 'non-string comment ciphertext');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,26 @@ class RequestTest extends TestCase
|
|||
$this->assertEquals('create', $request->getOperation());
|
||||
}
|
||||
|
||||
public function testPostScalarJson()
|
||||
{
|
||||
// a valid JSON scalar body (number, boolean or quoted string) must not
|
||||
// be treated as a set of parameters, so that it is rejected cleanly
|
||||
// instead of triggering a type error further down
|
||||
foreach (array('1', '0.0', 'true', 'false', '"example"') as $scalar) {
|
||||
$this->reset();
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$file = Helper::createTempFile();
|
||||
file_put_contents($file, $scalar);
|
||||
Request::setInputStream($file);
|
||||
$request = new Request;
|
||||
unlink($file);
|
||||
$this->assertTrue($request->isJsonApiCall(), 'is JSON API call');
|
||||
$this->assertEquals('create', $request->getOperation());
|
||||
$this->assertEquals('', $request->getParam('ct'), "scalar body {$scalar} yields no parameters");
|
||||
}
|
||||
}
|
||||
|
||||
public function testReadWithNegotiation()
|
||||
{
|
||||
$this->reset();
|
||||
|
|
|
|||
|
|
@ -135,6 +135,19 @@ class YourlsProxyTest extends TestCase
|
|||
$this->assertEquals($yourls->getError(), 'Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.');
|
||||
}
|
||||
|
||||
public function testYourlsSuccessWithoutShortUrl()
|
||||
{
|
||||
// YOURLS may reply with statusCode 200 but without a shorturl field;
|
||||
// this must be handled gracefully as an error instead of raising a
|
||||
// TypeError (the method is declared to return ?string). YOURLS returns
|
||||
// the status code as a string, so mirror that here.
|
||||
file_put_contents($this->_mock_yourls_service, '{"statusCode":"200"}');
|
||||
|
||||
$yourls = new YourlsProxy($this->_conf, 'https://example.com/?foo#bar');
|
||||
$this->assertTrue($yourls->isError());
|
||||
$this->assertEquals($yourls->getError(), 'Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.');
|
||||
}
|
||||
|
||||
public function testServerError()
|
||||
{
|
||||
// simulate some other server error that results in a non-JSON reply
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue