From eb429159919870eb4c25864ea3eb06e778b699e0 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Thu, 13 Jun 2024 20:22:40 +0200 Subject: [PATCH 1/9] bootstrap5 textarea height relative to viewport height, fixes #1349 --- CHANGELOG.md | 1 + css/bootstrap5/privatebin.css | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0797555..e7e953fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.7.4 (not yet released) * CHANGED: Saving markdown pastes uses `.md` extension instead of `.txt` (#1293) * CHANGED: Enable strict type checking in PHP (#1350) +* CHANGED: Various tweaks of the `bootstrap5` template, suggested by the community * FIXED: Reset password input field on creation of new paste (#1194) * FIXED: Allow database schema upgrade to skip versions (#1343) diff --git a/css/bootstrap5/privatebin.css b/css/bootstrap5/privatebin.css index 426e66d7..b14768a1 100644 --- a/css/bootstrap5/privatebin.css +++ b/css/bootstrap5/privatebin.css @@ -36,6 +36,10 @@ margin-bottom: 10px; } +#message { + height: 70vh; +} + #message, .replymessage { font-family: monospace; resize: vertical; From 2c711e9d3ca21230fc68f5b4dba2a7a0592b963b Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 29 Jun 2024 20:26:09 +0200 Subject: [PATCH 2/9] prevent bypassing YOURLS proxy URL filter, allowing to shorten non-self URLs --- CHANGELOG.md | 1 + lib/YourlsProxy.php | 2 +- tst/YourlsProxyTest.php | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d9485a..7ba2cb97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * FIXED: Reset password input field on creation of new paste (#1194) * FIXED: Allow database schema upgrade to skip versions (#1343) * FIXED: `bootstrap5` dark mode toggle unset on dark browser preference (#1340) +* FIXED: Prevent bypassing YOURLS proxy URL filter, allowing to shorten non-self URLs ## 1.7.3 (2024-05-13) * CHANGED: Various tweaks of the `bootstrap5` template, suggested by the community diff --git a/lib/YourlsProxy.php b/lib/YourlsProxy.php index f616832d..de46a12b 100644 --- a/lib/YourlsProxy.php +++ b/lib/YourlsProxy.php @@ -47,7 +47,7 @@ class YourlsProxy */ public function __construct(Configuration $conf, $link) { - if (strpos($link, $conf->getKey('basepath') . '?') === false) { + if (strpos($link, $conf->getKey('basepath') . '?') !== 0) { $this->_error = 'Trying to shorten a URL that isn\'t pointing at our instance.'; return; } diff --git a/tst/YourlsProxyTest.php b/tst/YourlsProxyTest.php index d6e9cb76..389f510d 100644 --- a/tst/YourlsProxyTest.php +++ b/tst/YourlsProxyTest.php @@ -54,6 +54,13 @@ class YourlsProxyTest extends TestCase $this->assertEquals($yourls->getError(), 'Trying to shorten a URL that isn\'t pointing at our instance.'); } + public function testSneakyForeignUrl() + { + $yourls = new YourlsProxy($this->_conf, 'https://other.example.com/?q=https://example.com/?foo#bar'); + $this->assertTrue($yourls->isError()); + $this->assertEquals($yourls->getError(), 'Trying to shorten a URL that isn\'t pointing at our instance.'); + } + public function testYourlsError() { // when statusCode is not 200, shorturl may not have been set From 8e6e31db5cd7e50cfc6c1eeece8ac414fe37c30d Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 30 Jun 2024 07:45:06 +0200 Subject: [PATCH 3/9] fix test, basepath needs to be set --- tst/JsonApiTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tst/JsonApiTest.php b/tst/JsonApiTest.php index 07f33ad2..f5b69a7c 100644 --- a/tst/JsonApiTest.php +++ b/tst/JsonApiTest.php @@ -325,6 +325,9 @@ class JsonApiTest extends TestCase */ public function testShortenViaYourlsFailure() { + $options = parse_ini_file(CONF, true); + $options['main']['basepath'] = 'https://example.com/path'; // missing slash gets added by Configuration constructor + Helper::createIniFile(CONF, $options); $_SERVER['REQUEST_URI'] = '/path/shortenviayourls?link=https%3A%2F%2Fexample.com%2Fpath%2F%3Ffoo%23bar'; $_GET['link'] = 'https://example.com/path/?foo#bar'; ob_start(); From 17f924118efe0aee134f79103b240d5d9ed38efb Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 7 Jul 2024 14:13:59 +0200 Subject: [PATCH 4/9] address warnings and errors in github actions --- .github/workflows/tests.yml | 19 +++++++++---------- .gitignore | 1 + js/package.json | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70f87abd..43cc58f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,8 +8,8 @@ jobs: Composer: runs-on: ubuntu-latest # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#handling-failures - continue-on-error: ${{ matrix.experimental }} - + continue-on-error: "${{ matrix.experimental }}" + steps: - name: Checkout uses: actions/checkout@v4 @@ -21,7 +21,7 @@ jobs: PHPunit: name: PHP ${{ matrix.php-versions }} unit tests on ${{ matrix.operating-system }} runs-on: ubuntu-latest - continue-on-error: ${{ matrix.experimental }} + continue-on-error: "${{ matrix.experimental }}" strategy: matrix: php-versions: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] @@ -86,9 +86,9 @@ jobs: - name: Cache dependencies uses: actions/cache@v4 with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}- + path: "${{ steps.composer-cache.outputs.dir }}" + key: "${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.json') }}" + restore-keys: "${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-" # composer installation - name: Unset platform requirement @@ -136,14 +136,14 @@ jobs: - name: Run unit tests run: npm run ci-test working-directory: js - + - name: Upload Test Results if: always() uses: actions/upload-artifact@v4 with: name: Test Results (Mocha) path: js/mocha-results.xml - + event_file: name: "Event File" runs-on: ubuntu-latest @@ -152,5 +152,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: Event File - path: ${{ github.event_path }} - + path: "${{ github.event_path }}" diff --git a/.gitignore b/.gitignore index ee344551..07ce26ed 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ vendor/**/build_phar.php # Ignore local node modules, unit testing logs, api docs and IDE project files js/node_modules/ +js/mocha-results.xml js/test.log tst/log/ tst/ConfigurationCombinationsTest.php diff --git a/js/package.json b/js/package.json index 47300ff8..a3534c59 100644 --- a/js/package.json +++ b/js/package.json @@ -16,7 +16,7 @@ }, "scripts": { "test": "mocha", - "ci-test": "mocha --reporter-option output=mocha-results.xml" + "ci-test": "mocha --reporter xunit --reporter-option output=mocha-results.xml" }, "repository": { "type": "git", From 84d4d31c73a179ae3177f2e008f7ef07c5856883 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 7 Jul 2024 14:22:48 +0200 Subject: [PATCH 5/9] composer is not part of the matrix, don't try and process event.json --- .github/workflows/test-results.yml | 5 +---- .github/workflows/tests.yml | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-results.yml b/.github/workflows/test-results.yml index f92fec48..1c196507 100644 --- a/.github/workflows/test-results.yml +++ b/.github/workflows/test-results.yml @@ -36,7 +36,4 @@ jobs: commit: ${{ github.event.workflow_run.head_sha }} event_file: artifacts/Event File/event.json event_name: ${{ github.event.workflow_run.event }} - files: | - artifacts/**/*.xml - artifacts/**/*.trx - artifacts/**/*.json + files: "artifacts/**/*.xml" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 43cc58f8..d668d74e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,9 +7,6 @@ jobs: Composer: runs-on: ubuntu-latest - # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#handling-failures - continue-on-error: "${{ matrix.experimental }}" - steps: - name: Checkout uses: actions/checkout@v4 @@ -21,6 +18,7 @@ jobs: PHPunit: name: PHP ${{ matrix.php-versions }} unit tests on ${{ matrix.operating-system }} runs-on: ubuntu-latest + # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#handling-failures continue-on-error: "${{ matrix.experimental }}" strategy: matrix: From d09763146902770a505285eeff8db1dedf7d3292 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 7 Jul 2024 14:28:29 +0200 Subject: [PATCH 6/9] possibly not necessary? --- .github/workflows/test-results.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test-results.yml b/.github/workflows/test-results.yml index 1c196507..4e20737d 100644 --- a/.github/workflows/test-results.yml +++ b/.github/workflows/test-results.yml @@ -32,7 +32,6 @@ jobs: - name: Publish Test Results uses: EnricoMi/publish-unit-test-result-action@v2 with: - check_name: "Test Results (${{ github.event.workflow_run.event || github.event_name }})" commit: ${{ github.event.workflow_run.head_sha }} event_file: artifacts/Event File/event.json event_name: ${{ github.event.workflow_run.event }} From 3cba170f3255de21bbebb77f6c565519ef33e8c1 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 7 Jul 2024 15:10:17 +0200 Subject: [PATCH 7/9] re-order stubs to the end of the file for easier maintenance --- tst/Bootstrap.php | 840 +++++++++++++++++++++++----------------------- 1 file changed, 420 insertions(+), 420 deletions(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index fba1be14..984f7d50 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -28,94 +28,386 @@ require PATH . 'vendor/autoload.php'; Helper::updateSubresourceIntegrity(); /** - * Class StorageClientStub provides a limited stub for performing the unit test + * Class Helper provides unit tests pastes and comments of various formats */ -class StorageClientStub extends StorageClient +class Helper { - private $_config = null; - private $_connection = null; - private static $_buckets = array(); + /** + * example ID of a paste + * + * @var string + */ + private static $pasteid = '5b65a01b43987bc2'; - public function __construct(array $config = array()) - { - $this->_config = $config; - $this->_connection = new ConnectionInterfaceStub(); - } + /** + * example paste version 1 + * + * @var array + */ + private static $pasteV1 = array( + 'data' => '{"iv":"EN39/wd5Nk8HAiSG2K5AsQ","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"QKN1DBXe5PI","ct":"8hA83xDdXjD7K2qfmw5NdA"}', + 'attachment' => '{"iv":"Pd4pOKWkmDTT9uPwVwd5Ag","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"6nOCU3peNDclDDpFtJEBKA"}', + 'attachmentname' => '{"iv":"76MkAtOGC4oFogX/aSMxRA","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"b6Ae/U1xJdsX/+lATud4sQ"}', + 'meta' => array( + 'formatter' => 'plaintext', + 'postdate' => 1344803344, + 'opendiscussion' => true, + ), + ); - public function bucket($name, $userProject = false) + /** + * example paste version 2 + * + * @var array + */ + private static $pasteV2 = array( + 'adata' => array( + array( + 'gMSNoLOk4z0RnmsYwXZ8mw==', + 'TZO+JWuIuxs=', + 100000, + 256, + 128, + 'aes', + 'gcm', + 'zlib', + ), + 'plaintext', + 1, + 0, + ), + 'meta' => array( + 'expire' => '5min', + ), + 'v' => 2, + 'ct' => 'ME5JF/YBEijp2uYMzLZozbKtWc5wfy6R59NBb7SmRig=', + ); + + /** + * example ID of a comment + * + * @var string + */ + private static $commentid = '5a52eebf11c4c94b'; + + /** + * example comment + * + * @var array + */ + private static $commentV1 = array( + 'data' => '{"iv":"Pd4pOKWkmDTT9uPwVwd5Ag","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"6nOCU3peNDclDDpFtJEBKA"}', + 'meta' => array( + 'nickname' => '{"iv":"76MkAtOGC4oFogX/aSMxRA","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"b6Ae/U1xJdsX/+lATud4sQ"}', + 'vizhash' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGUlEQVQokWOsl5/94983CNKQMjnxaOePf98MeKwPfNjkLZ3AgARab6b9+PeNEVnDj3/ff/z7ZiHnzsDA8Pv7H2TVPJw8EAYLAwb48OaVgIgYKycLsrYv378wMDB8//qdCVMDRA9EKSsnCwRBxNsepaLboMFlyMDAICAi9uHNK24GITQ/MDAwoNhgIGMLtwGrzegaLjw5jMz9+vUdnN17uwDCQDhJgk0O07yvX9+teDX1x79v6DYIsIjgcgMaYGFgYOBg4kJx2JejkAiBxAw+PzAwMNz4dp6wDXDw4MdNNOl0rWYsNkD89OLXI/xmo9sgzatJjAYmBgYGDiauD3/ePP18nVgb4MF89+M5ZX6js293wUMpnr8KTQMAxsCJnJ30apMAAAAASUVORK5CYII=', + 'postdate' => 1344803528, + ), + ); + + /** + * JS files and their SRI hashes + * + * @var array + */ + private static $hashes = array(); + + /** + * get example paste ID + * + * @return string + */ + public static function getPasteId() { - if (!key_exists($name, self::$_buckets)) { - $b = new BucketStub($this->_connection, $name, array(), $this); - self::$_buckets[$name] = $b; - } - return self::$_buckets[$name]; + return self::$pasteid; } /** - * @throws \Google\Cloud\Core\Exception\NotFoundException + * get example paste, as stored on server + * + * @param int $version + * @param array $meta + * @return array */ - public function deleteBucket($name) + public static function getPaste($version = 2, array $meta = array()) { - if (key_exists($name, self::$_buckets)) { - unset(self::$_buckets[$name]); + $example = self::getPasteWithAttachment($version, $meta); + // v1 has the attachment stored in a separate property + if ($version === 1) { + unset($example['attachment'], $example['attachmentname']); + } + return $example; + } + + /** + * get example paste with attachment, as stored on server + * + * @param int $version + * @param array $meta + * @return array + */ + public static function getPasteWithAttachment($version = 2, array $meta = array()) + { + $example = $version === 1 ? self::$pasteV1 : self::$pasteV2; + $example['meta']['salt'] = ServerSalt::generate(); + $example['meta'] = array_merge($example['meta'], $meta); + return $example; + } + + /** + * get example paste, as decoded from POST by the request object + * + * @param int $version + * @param array $meta + * @return array + */ + public static function getPastePost($version = 2, array $meta = array()) + { + $example = self::getPaste($version, $meta); + if ($version == 2) { + $example['meta'] = array('expire' => $example['meta']['expire']); } else { - throw new NotFoundException(); + unset($example['meta']['postdate']); + } + return $example; + } + + /** + * get example paste, as received via POST by the user + * + * @param int $version + * @param array $meta + * @return array + */ + public static function getPasteJson($version = 2, array $meta = array()) + { + return json_encode(self::getPastePost($version, $meta)); + } + + /** + * get example paste ID + * + * @return string + */ + public static function getCommentId() + { + return self::$commentid; + } + + /** + * get example comment, as stored on server + * + * @param int $version + * @param array $meta + * @return array + */ + public static function getComment($version = 2, array $meta = array()) + { + $example = $version === 1 ? self::$commentV1 : self::$pasteV2; + if ($version === 2) { + $example['adata'] = $example['adata'][0]; + $example['pasteid'] = $example['parentid'] = self::getPasteId(); + $example['meta']['created'] = self::$commentV1['meta']['postdate']; + $example['meta']['icon'] = self::$commentV1['meta']['vizhash']; + unset($example['meta']['expire']); + } + $example['meta'] = array_merge($example['meta'], $meta); + return $example; + } + + /** + * get example comment, as decoded from POST by the request object + * + * @param int $version + * @return array + */ + public static function getCommentPost() + { + $example = self::getComment(); + unset($example['meta']); + return $example; + } + + /** + * get example comment, as received via POST by user + * + * @param int $version + * @return array + */ + public static function getCommentJson() + { + return json_encode(self::getCommentPost()); + } + + /** + * delete directory and all its contents recursively + * + * @param string $path + * @throws Exception + */ + public static function rmDir($path) + { + if (is_dir($path)) { + $path .= DIRECTORY_SEPARATOR; + $dir = dir($path); + while (false !== ($file = $dir->read())) { + if ($file != '.' && $file != '..') { + if (is_dir($path . $file)) { + self::rmDir($path . $file); + } elseif (is_file($path . $file)) { + if (!unlink($path . $file)) { + throw new Exception('Error deleting file "' . $path . $file . '".'); + } + } + } + } + $dir->close(); + if (!rmdir($path)) { + throw new Exception('Error deleting directory "' . $path . '".'); + } } } - public function buckets(array $options = array()) + /** + * create a backup of the config file + * + * @return void + */ + public static function confBackup() { - throw new BadMethodCallException('not supported by this stub'); - } - - public function registerStreamWrapper($protocol = null) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function unregisterStreamWrapper($protocol = null) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function signedUrlUploader($uri, $data, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function timestamp(\DateTimeInterface $timestamp, $nanoSeconds = null) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getServiceAccount(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function hmacKeys(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function hmacKey($accessId, $projectId = null, array $metadata = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function createHmacKey($serviceAccountEmail, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function createBucket($name, array $options = array()) - { - if (key_exists($name, self::$_buckets)) { - throw new BadRequestException('already exists'); + if (!is_file(CONF . '.bak') && is_file(CONF)) { + rename(CONF, CONF . '.bak'); + } + if (!is_file(CONF_SAMPLE . '.bak') && is_file(CONF_SAMPLE)) { + copy(CONF_SAMPLE, CONF_SAMPLE . '.bak'); + } + } + + /** + * restor backup of the config file + * + * @return void + */ + public static function confRestore() + { + if (is_file(CONF . '.bak')) { + rename(CONF . '.bak', CONF); + } + if (is_file(CONF_SAMPLE . '.bak')) { + rename(CONF_SAMPLE . '.bak', CONF_SAMPLE); + } + } + + /** + * create ini file + * + * @param string $pathToFile + * @param array $values + */ + public static function createIniFile($pathToFile, array $values) + { + if (count($values)) { + @unlink($pathToFile); + $ini = fopen($pathToFile, 'a'); + foreach ($values as $section => $options) { + fwrite($ini, "[$section]" . PHP_EOL); + foreach ($options as $option => $setting) { + if (is_null($setting)) { + continue; + } elseif (is_string($setting)) { + $setting = '"' . $setting . '"'; + } elseif (is_array($setting)) { + foreach ($setting as $key => $value) { + if (is_null($value)) { + $value = 'null'; + } elseif (is_string($value)) { + $value = '"' . $value . '"'; + } else { + $value = var_export($value, true); + } + fwrite($ini, $option . "[$key] = $value" . PHP_EOL); + } + continue; + } else { + $setting = var_export($setting, true); + } + fwrite($ini, "$option = $setting" . PHP_EOL); + } + fwrite($ini, PHP_EOL); + } + fclose($ini); + } + } + + /** + * a var_export that returns arrays without line breaks + * by linus@flowingcreativity.net via php.net + * + * @param mixed $var + * @param bool $return + * @return void|string + */ + public static function varExportMin($var, $return = false) + { + if (is_array($var)) { + $toImplode = array(); + foreach ($var as $key => $value) { + $toImplode[] = var_export($key, true) . ' => ' . self::varExportMin($value, true); + } + $code = 'array(' . implode(', ', $toImplode) . ')'; + if ($return) { + return $code; + } else { + echo $code; + } + } else { + return var_export($var, $return); + } + } + + /** + * update all templates with the latest SRI hashes for all JS files + * + * @return void + */ + public static function updateSubresourceIntegrity() + { + $dir = dir(PATH . 'js'); + while (false !== ($file = $dir->read())) { + if (substr($file, -3) === '.js') { + self::$hashes[$file] = base64_encode( + hash('sha512', file_get_contents( + PATH . 'js' . DIRECTORY_SEPARATOR . $file + ), true) + ); + } + } + + $dir = dir(PATH . 'tpl'); + while (false !== ($file = $dir->read())) { + if (substr($file, -4) === '.php') { + $content = file_get_contents( + PATH . 'tpl' . DIRECTORY_SEPARATOR . $file + ); + $content = preg_replace_callback( + '##', + function ($matches) { + if (array_key_exists($matches[2], Helper::$hashes)) { + return ''; + } else { + return $matches[0]; + } + }, + $content + ); + file_put_contents( + PATH . 'tpl' . DIRECTORY_SEPARATOR . $file, + $content + ); + } } - $b = new BucketStub($this->_connection, $name, array(), $this); - self::$_buckets[$name] = $b; - return $b; } } @@ -610,385 +902,93 @@ class ConnectionInterfaceStub implements ConnectionInterface } /** - * Class Helper provides unit tests pastes and comments of various formats + * Class StorageClientStub provides a limited stub for performing the unit test */ -class Helper +class StorageClientStub extends StorageClient { - /** - * example ID of a paste - * - * @var string - */ - private static $pasteid = '5b65a01b43987bc2'; + private $_config = null; + private $_connection = null; + private static $_buckets = array(); - /** - * example paste version 1 - * - * @var array - */ - private static $pasteV1 = array( - 'data' => '{"iv":"EN39/wd5Nk8HAiSG2K5AsQ","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"QKN1DBXe5PI","ct":"8hA83xDdXjD7K2qfmw5NdA"}', - 'attachment' => '{"iv":"Pd4pOKWkmDTT9uPwVwd5Ag","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"6nOCU3peNDclDDpFtJEBKA"}', - 'attachmentname' => '{"iv":"76MkAtOGC4oFogX/aSMxRA","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"b6Ae/U1xJdsX/+lATud4sQ"}', - 'meta' => array( - 'formatter' => 'plaintext', - 'postdate' => 1344803344, - 'opendiscussion' => true, - ), - ); - - /** - * example paste version 2 - * - * @var array - */ - private static $pasteV2 = array( - 'adata' => array( - array( - 'gMSNoLOk4z0RnmsYwXZ8mw==', - 'TZO+JWuIuxs=', - 100000, - 256, - 128, - 'aes', - 'gcm', - 'zlib', - ), - 'plaintext', - 1, - 0, - ), - 'meta' => array( - 'expire' => '5min', - ), - 'v' => 2, - 'ct' => 'ME5JF/YBEijp2uYMzLZozbKtWc5wfy6R59NBb7SmRig=', - ); - - /** - * example ID of a comment - * - * @var string - */ - private static $commentid = '5a52eebf11c4c94b'; - - /** - * example comment - * - * @var array - */ - private static $commentV1 = array( - 'data' => '{"iv":"Pd4pOKWkmDTT9uPwVwd5Ag","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"6nOCU3peNDclDDpFtJEBKA"}', - 'meta' => array( - 'nickname' => '{"iv":"76MkAtOGC4oFogX/aSMxRA","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"b6Ae/U1xJdsX/+lATud4sQ"}', - 'vizhash' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGUlEQVQokWOsl5/94983CNKQMjnxaOePf98MeKwPfNjkLZ3AgARab6b9+PeNEVnDj3/ff/z7ZiHnzsDA8Pv7H2TVPJw8EAYLAwb48OaVgIgYKycLsrYv378wMDB8//qdCVMDRA9EKSsnCwRBxNsepaLboMFlyMDAICAi9uHNK24GITQ/MDAwoNhgIGMLtwGrzegaLjw5jMz9+vUdnN17uwDCQDhJgk0O07yvX9+teDX1x79v6DYIsIjgcgMaYGFgYOBg4kJx2JejkAiBxAw+PzAwMNz4dp6wDXDw4MdNNOl0rWYsNkD89OLXI/xmo9sgzatJjAYmBgYGDiauD3/ePP18nVgb4MF89+M5ZX6js293wUMpnr8KTQMAxsCJnJ30apMAAAAASUVORK5CYII=', - 'postdate' => 1344803528, - ), - ); - - /** - * JS files and their SRI hashes - * - * @var array - */ - private static $hashes = array(); - - /** - * get example paste ID - * - * @return string - */ - public static function getPasteId() + public function __construct(array $config = array()) { - return self::$pasteid; + $this->_config = $config; + $this->_connection = new ConnectionInterfaceStub(); } - /** - * get example paste, as stored on server - * - * @param int $version - * @param array $meta - * @return array - */ - public static function getPaste($version = 2, array $meta = array()) + public function bucket($name, $userProject = false) { - $example = self::getPasteWithAttachment($version, $meta); - // v1 has the attachment stored in a separate property - if ($version === 1) { - unset($example['attachment'], $example['attachmentname']); + if (!key_exists($name, self::$_buckets)) { + $b = new BucketStub($this->_connection, $name, array(), $this); + self::$_buckets[$name] = $b; } - return $example; + return self::$_buckets[$name]; } /** - * get example paste with attachment, as stored on server - * - * @param int $version - * @param array $meta - * @return array + * @throws \Google\Cloud\Core\Exception\NotFoundException */ - public static function getPasteWithAttachment($version = 2, array $meta = array()) + public function deleteBucket($name) { - $example = $version === 1 ? self::$pasteV1 : self::$pasteV2; - $example['meta']['salt'] = ServerSalt::generate(); - $example['meta'] = array_merge($example['meta'], $meta); - return $example; - } - - /** - * get example paste, as decoded from POST by the request object - * - * @param int $version - * @param array $meta - * @return array - */ - public static function getPastePost($version = 2, array $meta = array()) - { - $example = self::getPaste($version, $meta); - if ($version == 2) { - $example['meta'] = array('expire' => $example['meta']['expire']); + if (key_exists($name, self::$_buckets)) { + unset(self::$_buckets[$name]); } else { - unset($example['meta']['postdate']); - } - return $example; - } - - /** - * get example paste, as received via POST by the user - * - * @param int $version - * @param array $meta - * @return array - */ - public static function getPasteJson($version = 2, array $meta = array()) - { - return json_encode(self::getPastePost($version, $meta)); - } - - /** - * get example paste ID - * - * @return string - */ - public static function getCommentId() - { - return self::$commentid; - } - - /** - * get example comment, as stored on server - * - * @param int $version - * @param array $meta - * @return array - */ - public static function getComment($version = 2, array $meta = array()) - { - $example = $version === 1 ? self::$commentV1 : self::$pasteV2; - if ($version === 2) { - $example['adata'] = $example['adata'][0]; - $example['pasteid'] = $example['parentid'] = self::getPasteId(); - $example['meta']['created'] = self::$commentV1['meta']['postdate']; - $example['meta']['icon'] = self::$commentV1['meta']['vizhash']; - unset($example['meta']['expire']); - } - $example['meta'] = array_merge($example['meta'], $meta); - return $example; - } - - /** - * get example comment, as decoded from POST by the request object - * - * @param int $version - * @return array - */ - public static function getCommentPost() - { - $example = self::getComment(); - unset($example['meta']); - return $example; - } - - /** - * get example comment, as received via POST by user - * - * @param int $version - * @return array - */ - public static function getCommentJson() - { - return json_encode(self::getCommentPost()); - } - - /** - * delete directory and all its contents recursively - * - * @param string $path - * @throws Exception - */ - public static function rmDir($path) - { - if (is_dir($path)) { - $path .= DIRECTORY_SEPARATOR; - $dir = dir($path); - while (false !== ($file = $dir->read())) { - if ($file != '.' && $file != '..') { - if (is_dir($path . $file)) { - self::rmDir($path . $file); - } elseif (is_file($path . $file)) { - if (!unlink($path . $file)) { - throw new Exception('Error deleting file "' . $path . $file . '".'); - } - } - } - } - $dir->close(); - if (!rmdir($path)) { - throw new Exception('Error deleting directory "' . $path . '".'); - } + throw new NotFoundException(); } } - /** - * create a backup of the config file - * - * @return void - */ - public static function confBackup() + public function buckets(array $options = array()) { - if (!is_file(CONF . '.bak') && is_file(CONF)) { - rename(CONF, CONF . '.bak'); - } - if (!is_file(CONF_SAMPLE . '.bak') && is_file(CONF_SAMPLE)) { - copy(CONF_SAMPLE, CONF_SAMPLE . '.bak'); - } + throw new BadMethodCallException('not supported by this stub'); } - /** - * restor backup of the config file - * - * @return void - */ - public static function confRestore() + public function registerStreamWrapper($protocol = null) { - if (is_file(CONF . '.bak')) { - rename(CONF . '.bak', CONF); - } - if (is_file(CONF_SAMPLE . '.bak')) { - rename(CONF_SAMPLE . '.bak', CONF_SAMPLE); - } + throw new BadMethodCallException('not supported by this stub'); } - /** - * create ini file - * - * @param string $pathToFile - * @param array $values - */ - public static function createIniFile($pathToFile, array $values) + public function unregisterStreamWrapper($protocol = null) { - if (count($values)) { - @unlink($pathToFile); - $ini = fopen($pathToFile, 'a'); - foreach ($values as $section => $options) { - fwrite($ini, "[$section]" . PHP_EOL); - foreach ($options as $option => $setting) { - if (is_null($setting)) { - continue; - } elseif (is_string($setting)) { - $setting = '"' . $setting . '"'; - } elseif (is_array($setting)) { - foreach ($setting as $key => $value) { - if (is_null($value)) { - $value = 'null'; - } elseif (is_string($value)) { - $value = '"' . $value . '"'; - } else { - $value = var_export($value, true); - } - fwrite($ini, $option . "[$key] = $value" . PHP_EOL); - } - continue; - } else { - $setting = var_export($setting, true); - } - fwrite($ini, "$option = $setting" . PHP_EOL); - } - fwrite($ini, PHP_EOL); - } - fclose($ini); - } + throw new BadMethodCallException('not supported by this stub'); } - /** - * a var_export that returns arrays without line breaks - * by linus@flowingcreativity.net via php.net - * - * @param mixed $var - * @param bool $return - * @return void|string - */ - public static function varExportMin($var, $return = false) + public function signedUrlUploader($uri, $data, array $options = array()) { - if (is_array($var)) { - $toImplode = array(); - foreach ($var as $key => $value) { - $toImplode[] = var_export($key, true) . ' => ' . self::varExportMin($value, true); - } - $code = 'array(' . implode(', ', $toImplode) . ')'; - if ($return) { - return $code; - } else { - echo $code; - } - } else { - return var_export($var, $return); - } + throw new BadMethodCallException('not supported by this stub'); } - /** - * update all templates with the latest SRI hashes for all JS files - * - * @return void - */ - public static function updateSubresourceIntegrity() + public function timestamp(\DateTimeInterface $timestamp, $nanoSeconds = null) { - $dir = dir(PATH . 'js'); - while (false !== ($file = $dir->read())) { - if (substr($file, -3) === '.js') { - self::$hashes[$file] = base64_encode( - hash('sha512', file_get_contents( - PATH . 'js' . DIRECTORY_SEPARATOR . $file - ), true) - ); - } - } + throw new BadMethodCallException('not supported by this stub'); + } - $dir = dir(PATH . 'tpl'); - while (false !== ($file = $dir->read())) { - if (substr($file, -4) === '.php') { - $content = file_get_contents( - PATH . 'tpl' . DIRECTORY_SEPARATOR . $file - ); - $content = preg_replace_callback( - '##', - function ($matches) { - if (array_key_exists($matches[2], Helper::$hashes)) { - return ''; - } else { - return $matches[0]; - } - }, - $content - ); - file_put_contents( - PATH . 'tpl' . DIRECTORY_SEPARATOR . $file, - $content - ); - } + public function getServiceAccount(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function hmacKeys(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function hmacKey($accessId, $projectId = null, array $metadata = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function createHmacKey($serviceAccountEmail, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function createBucket($name, array $options = array()) + { + if (key_exists($name, self::$_buckets)) { + throw new BadRequestException('already exists'); } + $b = new BucketStub($this->_connection, $name, array(), $this); + self::$_buckets[$name] = $b; + return $b; } } From 031bcef317a2cea33fa0e384b6804e8da0950793 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 9 Jul 2024 20:35:59 +0200 Subject: [PATCH 8/9] incrementing version --- CHANGELOG.md | 2 +- Makefile | 2 +- README.md | 2 +- SECURITY.md | 4 ++-- doc/Installation.md | 2 +- js/package-lock.json | 4 ++-- js/package.json | 2 +- lib/Controller.php | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb0392a4..c0039127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # PrivateBin version history -## 1.7.4 (not yet released) +## 1.7.4 (2024-07-09) * CHANGED: Saving markdown pastes uses `.md` extension instead of `.txt` (#1293) * CHANGED: Enable strict type checking in PHP (#1350) * CHANGED: Various tweaks of the `bootstrap5` template, suggested by the community diff --git a/Makefile b/Makefile index a94eeb06..66fe387f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: all coverage coverage-js coverage-php doc doc-js doc-php increment sign test test-js test-php help -CURRENT_VERSION = 1.7.3 +CURRENT_VERSION = 1.7.4 VERSION ?= 1.7.4 VERSION_FILES = README.md SECURITY.md doc/Installation.md js/package*.json lib/Controller.php Makefile REGEX_CURRENT_VERSION := $(shell echo $(CURRENT_VERSION) | sed "s/\./\\\./g") diff --git a/README.md b/README.md index c0d699e1..995335ec 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [![PrivateBin](https://cdn.rawgit.com/PrivateBin/assets/master/images/preview/logoSmall.png)](https://privatebin.info/) -*Current version: 1.7.3* +*Current version: 1.7.4* **PrivateBin** is a minimalist, open source online [pastebin](https://en.wikipedia.org/wiki/Pastebin) diff --git a/SECURITY.md b/SECURITY.md index ecad42f7..f78fe952 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,8 +4,8 @@ | Version | Supported | | ------- | ------------------ | -| 1.7.3 | :heavy_check_mark: | -| < 1.7.3 | :x: | +| 1.7.4 | :heavy_check_mark: | +| < 1.7.4 | :x: | ## Reporting a Vulnerability diff --git a/doc/Installation.md b/doc/Installation.md index 3a569588..f7d98c81 100644 --- a/doc/Installation.md +++ b/doc/Installation.md @@ -201,7 +201,7 @@ CREATE INDEX parent ON prefix_comment(pasteid); CREATE TABLE prefix_config ( id CHAR(16) NOT NULL, value TEXT, PRIMARY KEY (id) ); -INSERT INTO prefix_config VALUES('VERSION', '1.7.3'); +INSERT INTO prefix_config VALUES('VERSION', '1.7.4'); ``` In **PostgreSQL**, the `data`, `attachment`, `nickname` and `vizhash` columns diff --git a/js/package-lock.json b/js/package-lock.json index ead24582..f63d2113 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -1,12 +1,12 @@ { "name": "privatebin", - "version": "1.7.3", + "version": "1.7.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "privatebin", - "version": "1.7.3", + "version": "1.7.4", "license": "zlib-acknowledgement", "devDependencies": { "@peculiar/webcrypto": "^1.1.1", diff --git a/js/package.json b/js/package.json index a3534c59..90e501dc 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "privatebin", - "version": "1.7.3", + "version": "1.7.4", "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": { diff --git a/lib/Controller.php b/lib/Controller.php index d518fcc5..c1c4f2ba 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -27,7 +27,7 @@ class Controller * * @const string */ - const VERSION = '1.7.3'; + const VERSION = '1.7.4'; /** * minimal required PHP version From d2f311d246d4d3eccd34e64506f95dab81c51418 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 9 Jul 2024 21:27:49 +0200 Subject: [PATCH 9/9] chore: prepare for next release --- CHANGELOG.md | 2 ++ Makefile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0039127..251ad5a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # PrivateBin version history +## 1.7.5 (not yet released) + ## 1.7.4 (2024-07-09) * CHANGED: Saving markdown pastes uses `.md` extension instead of `.txt` (#1293) * CHANGED: Enable strict type checking in PHP (#1350) diff --git a/Makefile b/Makefile index 66fe387f..99872fd6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: all coverage coverage-js coverage-php doc doc-js doc-php increment sign test test-js test-php help CURRENT_VERSION = 1.7.4 -VERSION ?= 1.7.4 +VERSION ?= 1.7.5 VERSION_FILES = README.md SECURITY.md doc/Installation.md js/package*.json lib/Controller.php Makefile REGEX_CURRENT_VERSION := $(shell echo $(CURRENT_VERSION) | sed "s/\./\\\./g") REGEX_VERSION := $(shell echo $(VERSION) | sed "s/\./\\\./g")