Просмотр исходного кода

Merge pull request #1711 from PrivateBin/1.7-backport-2.0.3

backport security fixes for 1.7
El RIDO 8 месяцев назад
Родитель
Сommit
0618a9dd74
57 измененных файлов с 1012 добавлено и 435 удалено
  1. 9 5
      .github/workflows/codeql-analysis.yml
  2. 5 1
      .github/workflows/release.yml
  3. 13 3
      .github/workflows/snyk-scan.yml
  4. 1 0
      .github/workflows/test-results.yml
  5. 101 7
      .github/workflows/tests.yml
  6. 1 0
      .gitignore
  7. 2 4
      .scrutinizer.yml
  8. 2 6
      BADGES.md
  9. 10 0
      CHANGELOG.md
  10. 3 2
      bin/configuration-test-generator
  11. 4 0
      codacy-analysis.yml
  12. 1 1
      composer.json
  13. 104 48
      composer.lock
  14. 0 4
      css/bootstrap5/bootstrap-5.3.3.css
  15. 4 0
      css/bootstrap5/bootstrap-5.3.8.css
  16. 0 4
      css/bootstrap5/bootstrap.rtl-5.3.3.css
  17. 4 0
      css/bootstrap5/bootstrap.rtl-5.3.8.css
  18. 14 6
      doc/Installation.md
  19. 3 3
      doc/README.md
  20. 1 1
      doc/Running Unit Tests.md
  21. 54 50
      js/base-x-5.0.1.js
  22. 0 5
      js/bootstrap-5.3.3.js
  23. 5 0
      js/bootstrap-5.3.8.js
  24. 2 2
      js/common.js
  25. 1 0
      js/kjua-0.10.0.js
  26. 0 1
      js/kjua-0.9.0.js
  27. 7 2
      js/legacy.js
  28. 58 55
      js/privatebin.js
  29. 0 1
      js/purify-3.2.6.js
  30. 1 0
      js/purify-3.3.0.js
  31. 2 1
      js/test/Prompt.js
  32. 6 6
      lib/Configuration.php
  33. 8 4
      lib/Controller.php
  34. 6 0
      lib/Data/Filesystem.php
  35. 4 10
      lib/Persistence/TrafficLimiter.php
  36. 14 34
      lib/TemplateSwitcher.php
  37. 8 28
      lib/View.php
  38. 4 4
      tpl/bootstrap.php
  39. 6 6
      tpl/bootstrap5.php
  40. 3 0
      tst/Bootstrap.php
  41. 5 13
      tst/ViewTest.php
  42. 45 51
      vendor/composer/ClassLoader.php
  43. 1 0
      vendor/composer/autoload_classmap.php
  44. 1 0
      vendor/composer/autoload_static.php
  45. 9 9
      vendor/composer/installed.php
  46. 3 2
      vendor/mlocati/ip-lib/src/Address/AddressInterface.php
  47. 14 16
      vendor/mlocati/ip-lib/src/Address/IPv4.php
  48. 15 17
      vendor/mlocati/ip-lib/src/Address/IPv6.php
  49. 15 5
      vendor/mlocati/ip-lib/src/Range/AbstractRange.php
  50. 16 1
      vendor/mlocati/ip-lib/src/Range/Pattern.php
  51. 14 4
      vendor/mlocati/ip-lib/src/Range/RangeInterface.php
  52. 10 0
      vendor/mlocati/ip-lib/src/Range/Single.php
  53. 16 1
      vendor/mlocati/ip-lib/src/Range/Subnet.php
  54. 11 11
      vendor/mlocati/ip-lib/src/Range/Type.php
  55. 112 0
      vendor/mlocati/ip-lib/src/Service/BinaryMath.php
  56. 253 0
      vendor/mlocati/ip-lib/src/Service/NumberInChunks.php
  57. 1 1
      vendor/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php

+ 9 - 5
.github/workflows/codeql-analysis.yml

@@ -18,6 +18,10 @@ on:
   schedule:
   schedule:
     - cron: '28 22 * * 5'
     - cron: '28 22 * * 5'
 
 
+permissions:
+  contents: read
+  security-events: write
+
 jobs:
 jobs:
   analyze:
   analyze:
     name: Analyze
     name: Analyze
@@ -26,18 +30,18 @@ jobs:
     strategy:
     strategy:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:
-        language: [ 'javascript' ]
-        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+        language: [ 'actions', 'javascript' ]
+        # CodeQL supports [ 'actions', 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
         # Learn more:
         # Learn more:
         # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
         # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
 
 
     steps:
     steps:
     - name: Checkout repository
     - name: Checkout repository
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
 
 
     # Initializes the CodeQL tools for scanning.
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v3
+      uses: github/codeql-action/init@v4
       with:
       with:
         languages: ${{ matrix.language }}
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -46,4 +50,4 @@ jobs:
         # queries: ./path/to/local/query, your-org/your-repo/queries@main
         # queries: ./path/to/local/query, your-org/your-repo/queries@main
 
 
     - name: Perform CodeQL Analysis
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v3
+      uses: github/codeql-action/analyze@v4

+ 5 - 1
.github/workflows/release.yml

@@ -4,12 +4,16 @@ on:
   push:
   push:
     tags: '[0-9]+.[0-9]?[0-9]?[0-9]?.?[0-9]+'
     tags: '[0-9]+.[0-9]?[0-9]?[0-9]?.?[0-9]+'
 
 
+permissions: {}
+
 jobs:
 jobs:
   draft:
   draft:
+    permissions:
+      contents: write
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
       - name: Fetch changelog from tag
       - name: Fetch changelog from tag
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
         with:
           sparse-checkout: CHANGELOG.md
           sparse-checkout: CHANGELOG.md
           sparse-checkout-cone-mode: false
           sparse-checkout-cone-mode: false

+ 13 - 3
.github/workflows/snyk-scan.yml

@@ -8,13 +8,23 @@ on:
     branches: [ master ]
     branches: [ master ]
   pull_request:
   pull_request:
     branches: [ master ]
     branches: [ master ]
+
+permissions:
+  contents: read
+  security-events: write
+
 jobs:
 jobs:
   # https://github.com/snyk/actions/tree/master/php
   # https://github.com/snyk/actions/tree/master/php
   snyk-php:
   snyk-php:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
-    if: ${{ github.repository == 'PrivateBin/PrivateBin' }}
+    if: |
+      github.repository == 'PrivateBin/PrivateBin' && (
+      github.event.pull_request.author_association == 'COLLABORATOR' ||
+      github.event.pull_request.author_association == 'CONTRIBUTOR' ||
+      github.event.pull_request.author_association == 'MEMBER' ||
+      github.event.pull_request.author_association == 'OWNER' )
     steps:
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: Install Google Cloud Storage
       - name: Install Google Cloud Storage
         run: composer require --no-update google/cloud-storage && composer update --no-dev
         run: composer require --no-update google/cloud-storage && composer update --no-dev
       - name: Run Snyk to check for vulnerabilities
       - name: Run Snyk to check for vulnerabilities
@@ -25,6 +35,6 @@ jobs:
         with:
         with:
           args: --sarif-file-output=snyk.sarif
           args: --sarif-file-output=snyk.sarif
       - name: Upload result to GitHub Code Scanning
       - name: Upload result to GitHub Code Scanning
-        uses: github/codeql-action/upload-sarif@v3
+        uses: github/codeql-action/upload-sarif@v4
         with:
         with:
           sarif_file: snyk.sarif
           sarif_file: snyk.sarif

+ 1 - 0
.github/workflows/test-results.yml

@@ -5,6 +5,7 @@ on:
     workflows: ["Tests"]
     workflows: ["Tests"]
     types:
     types:
       - completed
       - completed
+
 permissions: {}
 permissions: {}
 
 
 jobs:
 jobs:

+ 101 - 7
.github/workflows/tests.yml

@@ -1,15 +1,18 @@
 name: Tests
 name: Tests
+
 on:
 on:
   push:
   push:
   workflow_dispatch:
   workflow_dispatch:
 
 
+permissions: {}
+
 jobs:
 jobs:
 
 
   Composer:
   Composer:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
     - name: Checkout
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
     - name: Validate composer.json and composer.lock
     - name: Validate composer.json and composer.lock
       run: composer validate
       run: composer validate
     - name: Install dependencies
     - name: Install dependencies
@@ -36,7 +39,7 @@ jobs:
 
 
     # let's get started!
     # let's get started!
     - name: Checkout
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
 
 
     # cache PHP extensions
     # cache PHP extensions
     - name: Setup cache environment
     - name: Setup cache environment
@@ -106,20 +109,111 @@ jobs:
 
 
     - name: Upload Test Results
     - name: Upload Test Results
       if: always()
       if: always()
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v5
       with:
       with:
         name: Test Results (PHP ${{ matrix.php-versions }})
         name: Test Results (PHP ${{ matrix.php-versions }})
         path: tst/results.xml
         path: tst/results.xml
 
 
+  PHPunitConfigCombinations:
+    name: PHP configuration combination unit tests
+    runs-on: ubuntu-latest
+    env:
+      php-version: '8.4'
+      extensions: gd, sqlite3
+      extensions-cache-key-name: phpextensions
+
+    steps:
+
+    # let's get started!
+    - name: Checkout
+      uses: actions/checkout@v5
+
+    # cache PHP extensions
+    - name: Setup cache environment
+      id: extcache
+      uses: shivammathur/cache-extensions@v1
+      with:
+        php-version: ${{ env.php-version }}
+        extensions: ${{ env.extensions }}
+        key: ${{ runner.os }}-${{ env.extensions-cache-key }}
+
+    - name: Cache extensions
+      uses: actions/cache@v4
+      with:
+        path: ${{ steps.extcache.outputs.dir }}
+        key: ${{ steps.extcache.outputs.key }}
+        restore-keys: ${{ runner.os }}-${{ env.extensions-cache-key }}
+
+    - name: Setup PHP
+      uses: shivammathur/setup-php@v2
+      with:
+        php-version: ${{ env.php-version }}
+        extensions: ${{ env.extensions }}
+
+    # Setup GitHub CI PHP problem matchers
+    # https://github.com/shivammathur/setup-php#problem-matchers
+    - name: Setup problem matchers for PHP
+      run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+
+    - name: Setup problem matchers for PHPUnit
+      run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
+    # composer cache
+    - name: Remove composer lock
+      run: rm composer.lock
+
+    - name: Get composer cache directory
+      id: composer-cache
+      run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+    # http://man7.org/linux/man-pages/man1/date.1.html
+    # https://github.com/actions/cache#creating-a-cache-key
+    - name: Get Date
+      id: get-date
+      run: echo "date=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_OUTPUT
+      shell: bash
+
+    - 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 }}-"
+
+    # composer installation
+    - name: Unset platform requirement
+      run: composer config --unset platform
+
+    - name: Setup PHPunit
+      run: composer install -n
+
+    - name: Install Google Cloud Storage
+      run: composer require google/cloud-storage
+
+    # testing
+    - name: Generate configuration combination unit tests
+      run: bin/configuration-test-generator
+
+    - name: Run unit tests
+      run: ../vendor/bin/phpunit --no-coverage --log-junit results.xml ConfigurationCombinationsTest.php
+      working-directory: tst
+
+    - name: Upload Test Results
+      if: always()
+      uses: actions/upload-artifact@v5
+      with:
+        name: Test Results
+        path: tst/results.xml
+
   Mocha:
   Mocha:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
 
 
     - name: Checkout
     - name: Checkout
-      uses: actions/checkout@v4
+      uses: actions/checkout@v5
 
 
     - name: Setup Node
     - name: Setup Node
-      uses: actions/setup-node@v4
+      uses: actions/setup-node@v6
       with:
       with:
         node-version: '18'
         node-version: '18'
         cache: 'npm'
         cache: 'npm'
@@ -138,7 +232,7 @@ jobs:
 
 
     - name: Upload Test Results
     - name: Upload Test Results
       if: always()
       if: always()
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v5
       with:
       with:
         name: Test Results (Mocha)
         name: Test Results (Mocha)
         path: js/mocha-results.xml
         path: js/mocha-results.xml
@@ -148,7 +242,7 @@ jobs:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
     - name: Upload
     - name: Upload
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v5
       with:
       with:
         name: Event File
         name: Event File
         path: "${{ github.event_path }}"
         path: "${{ github.event_path }}"

+ 1 - 0
.gitignore

@@ -25,6 +25,7 @@ vendor/**/tst
 vendor/**/tests
 vendor/**/tests
 vendor/**/build_phar.php
 vendor/**/build_phar.php
 !vendor/**/*.php
 !vendor/**/*.php
+vendor/bin/**
 
 
 # Ignore local node modules, unit testing logs, api docs and IDE project files
 # Ignore local node modules, unit testing logs, api docs and IDE project files
 js/node_modules/
 js/node_modules/

+ 2 - 4
.scrutinizer.yml

@@ -15,14 +15,12 @@ coding_style:
                 additive: false
                 additive: false
                 concatenation: true
                 concatenation: true
 build:
 build:
-    image: default-bionic
     environment:
     environment:
         php:
         php:
-            version: 7.4
+            version: 8.2
     tests:
     tests:
         override:
         override:
-            -
-                command: 'composer require google/cloud-storage && cd tst && ../vendor/bin/phpunit'
+            -   command: 'composer require google/cloud-storage && cd tst && XDEBUG_MODE=coverage ../vendor/bin/phpunit'
                 coverage:
                 coverage:
                     file: 'tst/log/coverage-clover.xml'
                     file: 'tst/log/coverage-clover.xml'
                     format: 'clover'
                     format: 'clover'

+ 2 - 6
BADGES.md

@@ -1,11 +1,7 @@
 # Badges
 # Badges
 
 
-[![Build Status](https://travis-ci.org/PrivateBin/PrivateBin.svg?branch=master)](https://travis-ci.org/PrivateBin/PrivateBin) [![Build Status](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/build.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/build-status/master)  
-[![Codacy Badge](https://api.codacy.com/project/badge/Grade/094500f62abf4c9aa0c8a8a4520e4789)](https://www.codacy.com/app/PrivateBin/PrivateBin)
-[![Code Climate](https://codeclimate.com/github/PrivateBin/PrivateBin/badges/gpa.svg)](https://codeclimate.com/github/PrivateBin/PrivateBin)
+[![Build Status](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/build.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/build-status/master) [![Code Climate](https://codeclimate.com/github/PrivateBin/PrivateBin/badges/gpa.svg)](https://codeclimate.com/github/PrivateBin/PrivateBin)
 [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/?branch=master)
 [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/?branch=master)
-[![SensioLabsInsight](https://insight.sensiolabs.com/projects/57c9e74e-c6f9-4de6-a876-df66ec2ea1ff/mini.png)](https://insight.sensiolabs.com/projects/57c9e74e-c6f9-4de6-a876-df66ec2ea1ff)  
-[![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)
+[![Code Coverage](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PrivateBin/PrivateBin/?branch=master)
 
 
 [![BrowserStack - cross browser testing](img/browserstack.svg)](https://www.browserstack.com/)
 [![BrowserStack - cross browser testing](img/browserstack.svg)](https://www.browserstack.com/)

+ 10 - 0
CHANGELOG.md

@@ -1,5 +1,15 @@
 # PrivateBin version history
 # PrivateBin version history
 
 
+## 1.7.9 (not yet released)
+* CHANGED: Upgrading libraries to: base-x 5.0.1, bootstrap 5.3.8, DOMpurify 3.2.7, ip-lib 1.21.0 & kjua 0.10.0
+* CHANGED: Refactored jQuery DOM element creation into plain JavaScript
+* FIXED: Prevent arbitrary PHP file inclusion when enabling template switching
+* FIXED: Malicious filename can be used for self-XSS / HTML injection locally for users
+* FIXED: Sanitize file name in attachment size hint
+* FIXED: Unable to create a new paste from the cloned one when a JSON file attached (#1585)
+* FIXED: traffic limiter not working when using Filesystem storage and PHP opcache
+* FIXED: Configuration combinations test errors
+
 ## 1.7.8 (2025-06-30)
 ## 1.7.8 (2025-06-30)
 * FIXED: Duplicate attachment for every comment (#1577)
 * FIXED: Duplicate attachment for every comment (#1577)
 * FIXED: Attachments with empty file names (#1577)
 * FIXED: Attachments with empty file names (#1577)

+ 3 - 2
bin/configuration-test-generator

@@ -536,7 +536,8 @@ EOT;
                 break;
                 break;
             case 'Delete':
             case 'Delete':
                 $code .= PHP_EOL . <<<'EOT'
                 $code .= PHP_EOL . <<<'EOT'
-        $this->_model->create(Helper::getPasteId(), Helper::getPaste());
+        $paste = Helper::getPaste();
+        $this->_model->create(Helper::getPasteId(), $paste);
         $this->assertTrue($this->_model->exists(Helper::getPasteId()), 'paste exists before deleting data');
         $this->assertTrue($this->_model->exists(Helper::getPasteId()), 'paste exists before deleting data');
         $_GET['pasteid'] = Helper::getPasteId();
         $_GET['pasteid'] = Helper::getPasteId();
         $_GET['deletetoken'] = hash_hmac('sha256', Helper::getPasteId(), $this->_model->read(Helper::getPasteId())['meta']['salt']);
         $_GET['deletetoken'] = hash_hmac('sha256', Helper::getPasteId(), $this->_model->read(Helper::getPasteId())['meta']['salt']);
@@ -574,7 +575,7 @@ EOT;
                 $code .= <<<'EOT'
                 $code .= <<<'EOT'
 
 
         $this->assertMatchesRegularExpression(
         $this->assertMatchesRegularExpression(
-            '#<div[^>]*id="status"[^>]*>.*Paste was properly deleted[^<]*</div>#s',
+            '#<div[^>]*id="status"[^>]*>.*Paste was properly deleted[^<]*(<button|<\/div>)#s',
             $content,
             $content,
             'outputs deleted status correctly'
             'outputs deleted status correctly'
         );
         );

+ 4 - 0
codacy-analysis.yml

@@ -17,6 +17,10 @@ on:
   schedule:
   schedule:
     - cron: '45 16 * * 1'
     - cron: '45 16 * * 1'
 
 
+permissions:
+  contents: read
+  security-events: write
+
 jobs:
 jobs:
   codacy-security-scan:
   codacy-security-scan:
     name: Codacy Security Scan
     name: Codacy Security Scan

+ 1 - 1
composer.json

@@ -26,7 +26,7 @@
 	"require" : {
 	"require" : {
 		"php": "^7.3 || ^8.0",
 		"php": "^7.3 || ^8.0",
 		"jdenticon/jdenticon": "1.0.2",
 		"jdenticon/jdenticon": "1.0.2",
-		"mlocati/ip-lib": "1.20.0",
+		"mlocati/ip-lib": "1.21.0",
 		"symfony/polyfill-php80": "1.31.0",
 		"symfony/polyfill-php80": "1.31.0",
 		"yzalis/identicon": "2.0.0"
 		"yzalis/identicon": "2.0.0"
 	},
 	},

+ 104 - 48
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
         "This file is @generated automatically"
     ],
     ],
-    "content-hash": "6c7e6dea19e8bfd5641b220cb68c4b65",
+    "content-hash": "a6501ecd3d80fece24f1c78e49df7217",
     "packages": [
     "packages": [
         {
         {
             "name": "jdenticon/jdenticon",
             "name": "jdenticon/jdenticon",
@@ -57,16 +57,16 @@
         },
         },
         {
         {
             "name": "mlocati/ip-lib",
             "name": "mlocati/ip-lib",
-            "version": "1.20.0",
+            "version": "1.21.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/mlocati/ip-lib.git",
                 "url": "https://github.com/mlocati/ip-lib.git",
-                "reference": "fd45fc3bf08ed6c7e665e2e70562082ac954afd4"
+                "reference": "b5d38cdcbfc1516604d821a1f3f4a1638f327267"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/fd45fc3bf08ed6c7e665e2e70562082ac954afd4",
-                "reference": "fd45fc3bf08ed6c7e665e2e70562082ac954afd4",
+                "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/b5d38cdcbfc1516604d821a1f3f4a1638f327267",
+                "reference": "b5d38cdcbfc1516604d821a1f3f4a1638f327267",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -112,7 +112,7 @@
             ],
             ],
             "support": {
             "support": {
                 "issues": "https://github.com/mlocati/ip-lib/issues",
                 "issues": "https://github.com/mlocati/ip-lib/issues",
-                "source": "https://github.com/mlocati/ip-lib/tree/1.20.0"
+                "source": "https://github.com/mlocati/ip-lib/tree/1.21.0"
             },
             },
             "funding": [
             "funding": [
                 {
                 {
@@ -124,7 +124,7 @@
                     "type": "other"
                     "type": "other"
                 }
                 }
             ],
             ],
-            "time": "2025-02-04T17:30:58+00:00"
+            "time": "2025-09-24T13:58:50+00:00"
         },
         },
         {
         {
             "name": "symfony/polyfill-php80",
             "name": "symfony/polyfill-php80",
@@ -337,16 +337,16 @@
         },
         },
         {
         {
             "name": "myclabs/deep-copy",
             "name": "myclabs/deep-copy",
-            "version": "1.13.0",
+            "version": "1.13.4",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/myclabs/DeepCopy.git",
                 "url": "https://github.com/myclabs/DeepCopy.git",
-                "reference": "024473a478be9df5fdaca2c793f2232fe788e414"
+                "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
-                "reference": "024473a478be9df5fdaca2c793f2232fe788e414",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+                "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -385,7 +385,7 @@
             ],
             ],
             "support": {
             "support": {
                 "issues": "https://github.com/myclabs/DeepCopy/issues",
                 "issues": "https://github.com/myclabs/DeepCopy/issues",
-                "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
+                "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
             },
             },
             "funding": [
             "funding": [
                 {
                 {
@@ -393,7 +393,7 @@
                     "type": "tidelift"
                     "type": "tidelift"
                 }
                 }
             ],
             ],
-            "time": "2025-02-12T12:17:51+00:00"
+            "time": "2025-08-01T08:46:24+00:00"
         },
         },
         {
         {
             "name": "nikic/php-parser",
             "name": "nikic/php-parser",
@@ -890,16 +890,16 @@
         },
         },
         {
         {
             "name": "phpunit/phpunit",
             "name": "phpunit/phpunit",
-            "version": "9.6.22",
+            "version": "9.6.29",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c"
+                "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
-                "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3",
+                "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -910,7 +910,7 @@
                 "ext-mbstring": "*",
                 "ext-mbstring": "*",
                 "ext-xml": "*",
                 "ext-xml": "*",
                 "ext-xmlwriter": "*",
                 "ext-xmlwriter": "*",
-                "myclabs/deep-copy": "^1.12.1",
+                "myclabs/deep-copy": "^1.13.4",
                 "phar-io/manifest": "^2.0.4",
                 "phar-io/manifest": "^2.0.4",
                 "phar-io/version": "^3.2.1",
                 "phar-io/version": "^3.2.1",
                 "php": ">=7.3",
                 "php": ">=7.3",
@@ -921,11 +921,11 @@
                 "phpunit/php-timer": "^5.0.3",
                 "phpunit/php-timer": "^5.0.3",
                 "sebastian/cli-parser": "^1.0.2",
                 "sebastian/cli-parser": "^1.0.2",
                 "sebastian/code-unit": "^1.0.8",
                 "sebastian/code-unit": "^1.0.8",
-                "sebastian/comparator": "^4.0.8",
+                "sebastian/comparator": "^4.0.9",
                 "sebastian/diff": "^4.0.6",
                 "sebastian/diff": "^4.0.6",
                 "sebastian/environment": "^5.1.5",
                 "sebastian/environment": "^5.1.5",
-                "sebastian/exporter": "^4.0.6",
-                "sebastian/global-state": "^5.0.7",
+                "sebastian/exporter": "^4.0.8",
+                "sebastian/global-state": "^5.0.8",
                 "sebastian/object-enumerator": "^4.0.4",
                 "sebastian/object-enumerator": "^4.0.4",
                 "sebastian/resource-operations": "^3.0.4",
                 "sebastian/resource-operations": "^3.0.4",
                 "sebastian/type": "^3.2.1",
                 "sebastian/type": "^3.2.1",
@@ -973,7 +973,7 @@
             "support": {
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29"
             },
             },
             "funding": [
             "funding": [
                 {
                 {
@@ -984,12 +984,20 @@
                     "url": "https://github.com/sebastianbergmann",
                     "url": "https://github.com/sebastianbergmann",
                     "type": "github"
                     "type": "github"
                 },
                 },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
                 {
                 {
                     "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
                     "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
                     "type": "tidelift"
                     "type": "tidelift"
                 }
                 }
             ],
             ],
-            "time": "2024-12-05T13:48:26+00:00"
+            "time": "2025-09-24T06:29:11+00:00"
         },
         },
         {
         {
             "name": "sebastian/cli-parser",
             "name": "sebastian/cli-parser",
@@ -1160,16 +1168,16 @@
         },
         },
         {
         {
             "name": "sebastian/comparator",
             "name": "sebastian/comparator",
-            "version": "4.0.8",
+            "version": "4.0.9",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/comparator.git",
                 "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "fa0f136dd2334583309d32b62544682ee972b51a"
+                "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a",
-                "reference": "fa0f136dd2334583309d32b62544682ee972b51a",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
+                "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -1222,15 +1230,27 @@
             ],
             ],
             "support": {
             "support": {
                 "issues": "https://github.com/sebastianbergmann/comparator/issues",
                 "issues": "https://github.com/sebastianbergmann/comparator/issues",
-                "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8"
+                "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9"
             },
             },
             "funding": [
             "funding": [
                 {
                 {
                     "url": "https://github.com/sebastianbergmann",
                     "url": "https://github.com/sebastianbergmann",
                     "type": "github"
                     "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+                    "type": "tidelift"
                 }
                 }
             ],
             ],
-            "time": "2022-09-14T12:41:17+00:00"
+            "time": "2025-08-10T06:51:50+00:00"
         },
         },
         {
         {
             "name": "sebastian/complexity",
             "name": "sebastian/complexity",
@@ -1420,16 +1440,16 @@
         },
         },
         {
         {
             "name": "sebastian/exporter",
             "name": "sebastian/exporter",
-            "version": "4.0.6",
+            "version": "4.0.8",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72"
+                "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72",
-                "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+                "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -1485,28 +1505,40 @@
             ],
             ],
             "support": {
             "support": {
                 "issues": "https://github.com/sebastianbergmann/exporter/issues",
                 "issues": "https://github.com/sebastianbergmann/exporter/issues",
-                "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6"
+                "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8"
             },
             },
             "funding": [
             "funding": [
                 {
                 {
                     "url": "https://github.com/sebastianbergmann",
                     "url": "https://github.com/sebastianbergmann",
                     "type": "github"
                     "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+                    "type": "tidelift"
                 }
                 }
             ],
             ],
-            "time": "2024-03-02T06:33:00+00:00"
+            "time": "2025-09-24T06:03:27+00:00"
         },
         },
         {
         {
             "name": "sebastian/global-state",
             "name": "sebastian/global-state",
-            "version": "5.0.7",
+            "version": "5.0.8",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/global-state.git",
                 "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9"
+                "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
-                "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+                "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -1549,15 +1581,27 @@
             ],
             ],
             "support": {
             "support": {
                 "issues": "https://github.com/sebastianbergmann/global-state/issues",
                 "issues": "https://github.com/sebastianbergmann/global-state/issues",
-                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7"
+                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8"
             },
             },
             "funding": [
             "funding": [
                 {
                 {
                     "url": "https://github.com/sebastianbergmann",
                     "url": "https://github.com/sebastianbergmann",
                     "type": "github"
                     "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
+                    "type": "tidelift"
                 }
                 }
             ],
             ],
-            "time": "2024-03-02T06:35:11+00:00"
+            "time": "2025-08-10T07:10:35+00:00"
         },
         },
         {
         {
             "name": "sebastian/lines-of-code",
             "name": "sebastian/lines-of-code",
@@ -1730,16 +1774,16 @@
         },
         },
         {
         {
             "name": "sebastian/recursion-context",
             "name": "sebastian/recursion-context",
-            "version": "4.0.5",
+            "version": "4.0.6",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/recursion-context.git",
                 "url": "https://github.com/sebastianbergmann/recursion-context.git",
-                "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1"
+                "reference": "539c6691e0623af6dc6f9c20384c120f963465a0"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
-                "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0",
+                "reference": "539c6691e0623af6dc6f9c20384c120f963465a0",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -1781,15 +1825,27 @@
             "homepage": "https://github.com/sebastianbergmann/recursion-context",
             "homepage": "https://github.com/sebastianbergmann/recursion-context",
             "support": {
             "support": {
                 "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
                 "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
-                "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5"
+                "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6"
             },
             },
             "funding": [
             "funding": [
                 {
                 {
                     "url": "https://github.com/sebastianbergmann",
                     "url": "https://github.com/sebastianbergmann",
                     "type": "github"
                     "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
+                    "type": "tidelift"
                 }
                 }
             ],
             ],
-            "time": "2023-02-03T06:07:39+00:00"
+            "time": "2025-08-10T06:57:39+00:00"
         },
         },
         {
         {
             "name": "sebastian/resource-operations",
             "name": "sebastian/resource-operations",
@@ -2017,5 +2073,5 @@
     "platform-overrides": {
     "platform-overrides": {
         "php": "7.3"
         "php": "7.3"
     },
     },
-    "plugin-api-version": "2.3.0"
+    "plugin-api-version": "2.6.0"
 }
 }

Разница между файлами не показана из-за своего большого размера
+ 0 - 4
css/bootstrap5/bootstrap-5.3.3.css


Разница между файлами не показана из-за своего большого размера
+ 4 - 0
css/bootstrap5/bootstrap-5.3.8.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 4
css/bootstrap5/bootstrap.rtl-5.3.3.css


Разница между файлами не показана из-за своего большого размера
+ 4 - 0
css/bootstrap5/bootstrap.rtl-5.3.8.css


+ 14 - 6
doc/Installation.md

@@ -22,12 +22,20 @@ for more information.
 ### Minimal Requirements
 ### Minimal Requirements
 
 
 - PHP version 7.3 or above
 - PHP version 7.3 or above
-- GD extension (when using identicon or vizhash icons, jdenticon works without it)
-- zlib extension
-- some disk space or a database supported by [PDO](https://php.net/manual/book.pdo.php)
-- ability to create files and folders in the installation directory and the PATH
-  defined in index.php
-- A web browser with JavaScript and (optional) WebAssembly support
+- PHP with zlib extension
+- some disk space and the capability to create files and folders in the
+  installation directory and the `PATH` defined in index.php
+- A web browser with JavaScript support enabled
+
+### Optional Requirements
+
+- PHP with GD extension (when using identicon or vizhash icons, jdenticon works
+  without it) and OPcache (for better performance)
+- a database supported by [PHP PDO](https://php.net/manual/book.pdo.php) and the
+  PHP PDO extension (when using database storage)
+- a Ceph cluster with Rados gateway or AWS S3 storage (when using S3 storage)
+- Google Cloud Storage (when using GCP storage)
+- A web browser with WebAssembly support
 
 
 ## Hardening and Security
 ## Hardening and Security
 
 

+ 3 - 3
doc/README.md

@@ -4,7 +4,7 @@
 
 
 Please have a look at these questions *before* opening an issue in this repo.
 Please have a look at these questions *before* opening an issue in this repo.
 
 
-## [Installation guide](https://github.com/PrivateBin/PrivateBin/blob/master/doc/Installation.md#installation)
+## [Installation guide](Installation.md#installation)
 
 
 Minimal requirements, hardening and securing your installation and initial
 Minimal requirements, hardening and securing your installation and initial
 configuration.
 configuration.
@@ -26,12 +26,12 @@ How to help translate PrivateBin and technical background on it's implementation
 
 
 Know how for participating in PrivateBins development.
 Know how for participating in PrivateBins development.
 
 
-### [Generating Source Code Documentation](https://github.com/PrivateBin/PrivateBin/blob/master/doc/Generating%20Source%20Code%20Documentation.md#generating-source-code-documentation)
+### [Generating Source Code Documentation](Generating%20Source%20Code%20Documentation.md#generating-source-code-documentation)
 
 
 How to generate the source code API documentation, as found on the project
 How to generate the source code API documentation, as found on the project
 website for [PHP](https://privatebin.info/codedoc/) and [JS](https://privatebin.info/jsdoc/)
 website for [PHP](https://privatebin.info/codedoc/) and [JS](https://privatebin.info/jsdoc/)
 
 
-### [Running Unit Tests](https://github.com/PrivateBin/PrivateBin/blob/master/doc/Running Unit Tests.md#running-all-unit-tests)
+### [Running Unit Tests](Running%20Unit%20Tests.md#running-all-unit-tests)
 
 
 How to run the PHP & JS unit tests, including a brief introduction to property
 How to run the PHP & JS unit tests, including a brief introduction to property
 based unit testing.
 based unit testing.

+ 1 - 1
doc/Running Unit Tests.md

@@ -18,7 +18,7 @@ The parameters in detail:
   an accidentally destructive test case in it.
   an accidentally destructive test case in it.
 - `--read-only` - This image supports running in read-only mode. Only /tmp
 - `--read-only` - This image supports running in read-only mode. Only /tmp
   may be written into.
   may be written into.
-- `-rm` - Remove the container after the run. This saves you doing a cleanup
+- `--rm` - Remove the container after the run. This saves you doing a cleanup
   on your docker environment, if you run the image frequently.
   on your docker environment, if you run the image frequently.
 
 
 You can also run just the php and javascript test suites instead of both:
 You can also run just the php and javascript test suites instead of both:

+ 54 - 50
js/base-x-4.0.0.js → js/base-x-5.0.1.js

@@ -7,47 +7,47 @@
 (function(){
 (function(){
 this.baseX = function base (ALPHABET) {
 this.baseX = function base (ALPHABET) {
   if (ALPHABET.length >= 255) { throw new TypeError('Alphabet too long') }
   if (ALPHABET.length >= 255) { throw new TypeError('Alphabet too long') }
-  var BASE_MAP = new Uint8Array(256)
-  for (var j = 0; j < BASE_MAP.length; j++) {
+  const BASE_MAP = new Uint8Array(256)
+  for (let j = 0; j < BASE_MAP.length; j++) {
     BASE_MAP[j] = 255
     BASE_MAP[j] = 255
   }
   }
-  for (var i = 0; i < ALPHABET.length; i++) {
-    var x = ALPHABET.charAt(i)
-    var xc = x.charCodeAt(0)
+  for (let i = 0; i < ALPHABET.length; i++) {
+    const x = ALPHABET.charAt(i)
+    const xc = x.charCodeAt(0)
     if (BASE_MAP[xc] !== 255) { throw new TypeError(x + ' is ambiguous') }
     if (BASE_MAP[xc] !== 255) { throw new TypeError(x + ' is ambiguous') }
     BASE_MAP[xc] = i
     BASE_MAP[xc] = i
   }
   }
-  var BASE = ALPHABET.length
-  var LEADER = ALPHABET.charAt(0)
-  var FACTOR = Math.log(BASE) / Math.log(256) // log(BASE) / log(256), rounded up
-  var iFACTOR = Math.log(256) / Math.log(BASE) // log(256) / log(BASE), rounded up
+  const BASE = ALPHABET.length
+  const LEADER = ALPHABET.charAt(0)
+  const FACTOR = Math.log(BASE) / Math.log(256) // log(BASE) / log(256), rounded up
+  const iFACTOR = Math.log(256) / Math.log(BASE) // log(256) / log(BASE), rounded up
   function encode (source) {
   function encode (source) {
-    if (source instanceof Uint8Array) {
-    } else if (ArrayBuffer.isView(source)) {
+    // eslint-disable-next-line no-empty
+    if (source instanceof Uint8Array) { } else if (ArrayBuffer.isView(source)) {
       source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength)
       source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength)
     } else if (Array.isArray(source)) {
     } else if (Array.isArray(source)) {
       source = Uint8Array.from(source)
       source = Uint8Array.from(source)
     }
     }
     if (!(source instanceof Uint8Array)) { throw new TypeError('Expected Uint8Array') }
     if (!(source instanceof Uint8Array)) { throw new TypeError('Expected Uint8Array') }
     if (source.length === 0) { return '' }
     if (source.length === 0) { return '' }
-        // Skip & count leading zeroes.
-    var zeroes = 0
-    var length = 0
-    var pbegin = 0
-    var pend = source.length
+    // Skip & count leading zeroes.
+    let zeroes = 0
+    let length = 0
+    let pbegin = 0
+    const pend = source.length
     while (pbegin !== pend && source[pbegin] === 0) {
     while (pbegin !== pend && source[pbegin] === 0) {
       pbegin++
       pbegin++
       zeroes++
       zeroes++
     }
     }
-        // Allocate enough space in big-endian base58 representation.
-    var size = ((pend - pbegin) * iFACTOR + 1) >>> 0
-    var b58 = new Uint8Array(size)
-        // Process the bytes.
+    // Allocate enough space in big-endian base58 representation.
+    const size = ((pend - pbegin) * iFACTOR + 1) >>> 0
+    const b58 = new Uint8Array(size)
+    // Process the bytes.
     while (pbegin !== pend) {
     while (pbegin !== pend) {
-      var carry = source[pbegin]
-            // Apply "b58 = b58 * 256 + ch".
-      var i = 0
-      for (var it1 = size - 1; (carry !== 0 || i < length) && (it1 !== -1); it1--, i++) {
+      let carry = source[pbegin]
+      // Apply "b58 = b58 * 256 + ch".
+      let i = 0
+      for (let it1 = size - 1; (carry !== 0 || i < length) && (it1 !== -1); it1--, i++) {
         carry += (256 * b58[it1]) >>> 0
         carry += (256 * b58[it1]) >>> 0
         b58[it1] = (carry % BASE) >>> 0
         b58[it1] = (carry % BASE) >>> 0
         carry = (carry / BASE) >>> 0
         carry = (carry / BASE) >>> 0
@@ -56,38 +56,42 @@ this.baseX = function base (ALPHABET) {
       length = i
       length = i
       pbegin++
       pbegin++
     }
     }
-        // Skip leading zeroes in base58 result.
-    var it2 = size - length
+    // Skip leading zeroes in base58 result.
+    let it2 = size - length
     while (it2 !== size && b58[it2] === 0) {
     while (it2 !== size && b58[it2] === 0) {
       it2++
       it2++
     }
     }
-        // Translate the result into a string.
-    var str = LEADER.repeat(zeroes)
+    // Translate the result into a string.
+    let str = LEADER.repeat(zeroes)
     for (; it2 < size; ++it2) { str += ALPHABET.charAt(b58[it2]) }
     for (; it2 < size; ++it2) { str += ALPHABET.charAt(b58[it2]) }
     return str
     return str
   }
   }
   function decodeUnsafe (source) {
   function decodeUnsafe (source) {
     if (typeof source !== 'string') { throw new TypeError('Expected String') }
     if (typeof source !== 'string') { throw new TypeError('Expected String') }
     if (source.length === 0) { return new Uint8Array() }
     if (source.length === 0) { return new Uint8Array() }
-    var psz = 0
-        // Skip and count leading '1's.
-    var zeroes = 0
-    var length = 0
+    let psz = 0
+    // Skip and count leading '1's.
+    let zeroes = 0
+    let length = 0
     while (source[psz] === LEADER) {
     while (source[psz] === LEADER) {
       zeroes++
       zeroes++
       psz++
       psz++
     }
     }
-        // Allocate enough space in big-endian base256 representation.
-    var size = (((source.length - psz) * FACTOR) + 1) >>> 0 // log(58) / log(256), rounded up.
-    var b256 = new Uint8Array(size)
-        // Process the characters.
-    while (source[psz]) {
-            // Decode character
-      var carry = BASE_MAP[source.charCodeAt(psz)]
-            // Invalid character
+    // Allocate enough space in big-endian base256 representation.
+    const size = (((source.length - psz) * FACTOR) + 1) >>> 0 // log(58) / log(256), rounded up.
+    const b256 = new Uint8Array(size)
+    // Process the characters.
+    while (psz < source.length) {
+      // Find code of next character
+      const charCode = source.charCodeAt(psz)
+      // Base map can not be indexed using char code
+      if (charCode > 255) { return }
+      // Decode character
+      let carry = BASE_MAP[charCode]
+      // Invalid character
       if (carry === 255) { return }
       if (carry === 255) { return }
-      var i = 0
-      for (var it3 = size - 1; (carry !== 0 || i < length) && (it3 !== -1); it3--, i++) {
+      let i = 0
+      for (let it3 = size - 1; (carry !== 0 || i < length) && (it3 !== -1); it3--, i++) {
         carry += (BASE * b256[it3]) >>> 0
         carry += (BASE * b256[it3]) >>> 0
         b256[it3] = (carry % 256) >>> 0
         b256[it3] = (carry % 256) >>> 0
         carry = (carry / 256) >>> 0
         carry = (carry / 256) >>> 0
@@ -96,27 +100,27 @@ this.baseX = function base (ALPHABET) {
       length = i
       length = i
       psz++
       psz++
     }
     }
-        // Skip leading zeroes in b256.
-    var it4 = size - length
+    // Skip leading zeroes in b256.
+    let it4 = size - length
     while (it4 !== size && b256[it4] === 0) {
     while (it4 !== size && b256[it4] === 0) {
       it4++
       it4++
     }
     }
-    var vch = new Uint8Array(zeroes + (size - it4))
-    var j = zeroes
+    const vch = new Uint8Array(zeroes + (size - it4))
+    let j = zeroes
     while (it4 !== size) {
     while (it4 !== size) {
       vch[j++] = b256[it4++]
       vch[j++] = b256[it4++]
     }
     }
     return vch
     return vch
   }
   }
   function decode (string) {
   function decode (string) {
-    var buffer = decodeUnsafe(string)
+    const buffer = decodeUnsafe(string)
     if (buffer) { return buffer }
     if (buffer) { return buffer }
     throw new Error('Non-base' + BASE + ' character')
     throw new Error('Non-base' + BASE + ' character')
   }
   }
   return {
   return {
-    encode: encode,
-    decodeUnsafe: decodeUnsafe,
-    decode: decode
+    encode,
+    decodeUnsafe,
+    decode
   }
   }
 }
 }
 }).call(this);
 }).call(this);

Разница между файлами не показана из-за своего большого размера
+ 0 - 5
js/bootstrap-5.3.3.js


Разница между файлами не показана из-за своего большого размера
+ 5 - 0
js/bootstrap-5.3.8.js


+ 2 - 2
js/common.js

@@ -16,8 +16,8 @@ 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-2.1.0');
 global.showdown = require('./showdown-2.1.0');
-global.DOMPurify = require('./purify-3.2.6');
-global.baseX = require('./base-x-4.0.0').baseX;
+global.DOMPurify = require('./purify-3.3.0');
+global.baseX = require('./base-x-5.0.1').baseX;
 global.Legacy = require('./legacy').Legacy;
 global.Legacy = require('./legacy').Legacy;
 require('./bootstrap-3.4.1');
 require('./bootstrap-3.4.1');
 require('./privatebin');
 require('./privatebin');

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
js/kjua-0.10.0.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 1
js/kjua-0.9.0.js


+ 7 - 2
js/legacy.js

@@ -106,8 +106,8 @@
                 return window.isSecureContext;
                 return window.isSecureContext;
             }
             }
 
 
-            // HTTP is obviously insecure
-            if (window.location.protocol !== 'http:') {
+            // HTTPS is considered secure
+            if (window.location.protocol === 'https:') {
                 return true;
                 return true;
             }
             }
 
 
@@ -246,6 +246,11 @@
          */
          */
         me.init = function()
         me.init = function()
         {
         {
+            // prevent early init
+            if (typeof document === 'undefined' || typeof navigator === 'undefined' || typeof window === 'undefined') {
+                return;
+            }
+
             // prevent bots from viewing a paste and potentially deleting data
             // prevent bots from viewing a paste and potentially deleting data
             // when burn-after-reading is set
             // when burn-after-reading is set
             if (isBadBot()) {
             if (isBadBot()) {

+ 58 - 55
js/privatebin.js

@@ -10,7 +10,7 @@
  * @namespace
  * @namespace
  */
  */
 
 
-// global Base64, DOMPurify, FileReader, RawDeflate, history, navigator, prettyPrint, prettyPrintOne, showdown, kjua
+// global Base64, DOMPurify, FileReader, baseX, bootstrap, RawDeflate, history, navigator, prettyPrint, prettyPrintOne, showdown, kjua
 
 
 jQuery.fn.draghover = function() {
 jQuery.fn.draghover = function() {
     'use strict';
     'use strict';
@@ -975,7 +975,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          *
          *
          * @private
          * @private
          */
          */
-        let base58 = new baseX('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
+        const base58 = new baseX('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
 
 
         /**
         /**
          * convert UTF-8 string stored in a DOMString to a standard UTF-16 DOMString
          * convert UTF-8 string stored in a DOMString to a standard UTF-16 DOMString
@@ -3028,7 +3028,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                 attachmentLink.attr('download', fileName);
                 attachmentLink.attr('download', fileName);
 
 
                 const fileSize = Helper.formatBytes(decodedData.length);
                 const fileSize = Helper.formatBytes(decodedData.length);
-                template.append(`(${fileName}, ${fileSize})`);
+                const fileInfo = document.createTextNode(` (${fileName}, ${fileSize})`);
+                template[0].appendChild(fileInfo);
             }
             }
 
 
             // sanitize SVG preview
             // sanitize SVG preview
@@ -3121,10 +3122,15 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          * @name AttachmentViewer.printDragAndDropFileNames
          * @name AttachmentViewer.printDragAndDropFileNames
          * @private
          * @private
          * @function
          * @function
-         * @param {array} fileNames
+         * @param {string[]} fileNames
          */
          */
         function printDragAndDropFileNames(fileNames) {
         function printDragAndDropFileNames(fileNames) {
-            $dragAndDropFileNames.html(fileNames.join("<br>"));
+            $dragAndDropFileNames.empty();
+            fileNames.forEach(fileName => {
+                const name = document.createTextNode(fileName);
+                $dragAndDropFileNames[0].appendChild(name);
+                $dragAndDropFileNames[0].appendChild(document.createElement('br'));
+            });
         }
         }
 
 
         /**
         /**
@@ -3323,44 +3329,38 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             const alreadyIncludesCurrentAttachment = $targetElement.find(`[src='${blobUrl}']`).length > 0;
             const alreadyIncludesCurrentAttachment = $targetElement.find(`[src='${blobUrl}']`).length > 0;
 
 
             if (blobUrl && !alreadyIncludesCurrentAttachment) {
             if (blobUrl && !alreadyIncludesCurrentAttachment) {
-                if (mimeType.match(/^image\//i)) {
-                    $targetElement.append(
-                        $(document.createElement('img'))
-                            .attr('src', blobUrl)
-                            .attr('class', 'img-thumbnail')
-                    );
-                } else if (mimeType.match(/^video\//i)) {
-                    $targetElement.append(
-                        $(document.createElement('video'))
-                            .attr('controls', 'true')
-                            .attr('autoplay', 'true')
-                            .attr('class', 'img-thumbnail')
-
-                            .append($(document.createElement('source'))
-                            .attr('type', mimeType)
-                            .attr('src', blobUrl))
-                    );
-                } else if (mimeType.match(/^audio\//i)) {
-                    $targetElement.append(
-                        $(document.createElement('audio'))
-                            .attr('controls', 'true')
-                            .attr('autoplay', 'true')
-
-                            .append($(document.createElement('source'))
-                            .attr('type', mimeType)
-                            .attr('src', blobUrl))
-                    );
-                } else if (mimeType.match(/\/pdf/i)) {
+                if (mimeType.toLowerCase().startsWith('image/')) {
+                    const image = document.createElement('img');
+                    image.setAttribute('src', blobUrl);
+                    image.setAttribute('class', 'img-thumbnail');
+                    $targetElement[0].appendChild(image);
+                } else if (mimeType.toLowerCase().startsWith('video/')) {
+                    const video = document.createElement('video');
+                    video.setAttribute('controls', 'true');
+                    video.setAttribute('autoplay', 'true');
+                    video.setAttribute('class', 'img-thumbnail');
+                    const source = document.createElement('source');
+                    source.setAttribute('type', mimeType);
+                    source.setAttribute('src', blobUrl);
+                    video.appendChild(source);
+                    $targetElement[0].appendChild(video);
+                } else if (mimeType.toLowerCase().startsWith('audio/')) {
+                    const audio = document.createElement('audio');
+                    audio.setAttribute('controls', 'true');
+                    audio.setAttribute('autoplay', 'true');
+                    const source = document.createElement('source');
+                    source.setAttribute('type', mimeType);
+                    source.setAttribute('src', blobUrl);
+                    audio.appendChild(source);
+                    $targetElement[0].appendChild(audio);
+                } else if (mimeType.toLowerCase().endsWith('/pdf')) {
+                    const embed = document.createElement('embed');
+                    embed.setAttribute('src', blobUrl);
+                    embed.setAttribute('type', 'application/pdf');
+                    embed.setAttribute('class', 'pdfPreview');
                     // Fallback for browsers, that don't support the vh unit
                     // Fallback for browsers, that don't support the vh unit
-                    const clientHeight = $(window).height();
-
-                    $targetElement.append(
-                        $(document.createElement('embed'))
-                            .attr('src', blobUrl)
-                            .attr('type', 'application/pdf')
-                            .attr('class', 'pdfPreview')
-                            .css('height', clientHeight)
-                    );
+                    embed.style.height = window.innerHeight + 'px';
+                    $targetElement[0].appendChild(embed);
                 }
                 }
             }
             }
         };
         };
@@ -3638,8 +3638,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             if (nickname.length > 0) {
             if (nickname.length > 0) {
                 $commentEntry.find('span.nickname').text(nickname);
                 $commentEntry.find('span.nickname').text(nickname);
             } else {
             } else {
-                $commentEntry.find('span.nickname').html('<i></i>');
-                I18n._($commentEntry.find('span.nickname i'), 'Anonymous');
+                const anonCommenter = document.createElement('em');
+                anonCommenter.textContent = I18n._('Anonymous');
+                $commentEntry.find('span.nickname')[0].innerHTML = '';
+                $commentEntry.find('span.nickname')[0].appendChild(anonCommenter);
             }
             }
 
 
             // set date
             // set date
@@ -3652,14 +3654,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             // if an avatar is available, display it
             // if an avatar is available, display it
             const icon = comment.getIcon();
             const icon = comment.getIcon();
             if (icon) {
             if (icon) {
-                $commentEntry.find('span.nickname')
-                             .before(
-                                '<img src="' + icon + '" class="vizhash" /> '
-                             );
-                $(document).on('languageLoaded', function () {
-                    $commentEntry.find('img.vizhash')
-                                 .prop('title', I18n._('Avatar generated from IP address'));
-                });
+                const image = document.createElement('img');
+                image.setAttribute('src', icon);
+                image.setAttribute('class', 'vizhash');
+                $commentEntry.find('span.nickname').prepend(' ').prepend(image);
             }
             }
 
 
             // starting point (default value/fallback)
             // starting point (default value/fallback)
@@ -5268,22 +5266,23 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                 cipherMessage['attachment'] = attachments.map(attachment => attachment[0]);
                 cipherMessage['attachment'] = attachments.map(attachment => attachment[0]);
                 cipherMessage['attachment_name'] = attachments.map(attachment => attachment[1]);
                 cipherMessage['attachment_name'] = attachments.map(attachment => attachment[1]);
 
 
-                cipherMessage['attachment'] = await Promise.all(cipherMessage['attachment'].map(async (attachment) => {
+                cipherMessage['attachment'] = await Promise.all(cipherMessage['attachment'].map(async (attachment, i) => {
                     // we need to retrieve data from blob if browser already parsed it in memory
                     // we need to retrieve data from blob if browser already parsed it in memory
                     if (typeof attachment === 'string' && attachment.startsWith('blob:')) {
                     if (typeof attachment === 'string' && attachment.startsWith('blob:')) {
                         Alert.showStatus(
                         Alert.showStatus(
                             [
                             [
                                 'Retrieving cloned file \'%s\' from memory...',
                                 'Retrieving cloned file \'%s\' from memory...',
-                                attachment[1]
+                                cipherMessage['attachment_name'][i]
                             ],
                             ],
                             'copy'
                             'copy'
                         );
                         );
                         try {
                         try {
                             const blobData = await $.ajax({
                             const blobData = await $.ajax({
                                 type: 'GET',
                                 type: 'GET',
-                                url: `${attachment}`,
+                                url: attachment,
                                 processData: false,
                                 processData: false,
                                 timeout: 10000,
                                 timeout: 10000,
+                                dataType: 'binary',
                                 xhrFields: {
                                 xhrFields: {
                                     withCredentials: false,
                                     withCredentials: false,
                                     responseType: 'blob'
                                     responseType: 'blob'
@@ -5493,6 +5492,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                         plaintexts[i][1]
                         plaintexts[i][1]
                     );
                     );
                 }
                 }
+                $(document).on('languageLoaded', function () {
+                    $('#commentcontainer').find('img.vizhash')
+                        .prop('title', I18n._('Avatar generated from IP address'));
+                });
             });
             });
         }
         }
 
 

Разница между файлами не показана из-за своего большого размера
+ 0 - 1
js/purify-3.2.6.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 0
js/purify-3.3.0.js


+ 2 - 1
js/test/Prompt.js

@@ -24,6 +24,8 @@ describe('Prompt', function () {
                 );
                 );
                 $.PrivateBin.Model.reset();
                 $.PrivateBin.Model.reset();
                 $.PrivateBin.Model.init();
                 $.PrivateBin.Model.init();
+                // eslint-disable-next-line global-require
+                global.bootstrap = require('../bootstrap-5.3.8');
                 $.PrivateBin.Prompt.init();
                 $.PrivateBin.Prompt.init();
                 $.PrivateBin.Prompt.requestPassword();
                 $.PrivateBin.Prompt.requestPassword();
                 $('#passworddecrypt').val(password);
                 $('#passworddecrypt').val(password);
@@ -39,4 +41,3 @@ describe('Prompt', function () {
         );
         );
     });
     });
 });
 });
-

+ 6 - 6
lib/Configuration.php

@@ -110,17 +110,17 @@ class Configuration
         ),
         ),
         // update this array when adding/changing/removing js files
         // update this array when adding/changing/removing js files
         'sri' => array(
         'sri' => array(
-            'js/base-x-4.0.0.js'     => 'sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==',
+            'js/base-x-5.0.1.js'     => 'sha512-FmhlnjIxQyxkkxQmzf0l6IRGsGbgyCdgqPxypFsEtHMF1naRqaLLo6mcyN5rEaT16nKx1PeJ4g7+07D6gnk/Tg==',
             'js/base64-1.7.js'       => 'sha512-JdwsSP3GyHR+jaCkns9CL9NTt4JUJqm/BsODGmYhBcj5EAPKcHYh+OiMfyHbcDLECe17TL0hjXADFkusAqiYgA==',
             'js/base64-1.7.js'       => 'sha512-JdwsSP3GyHR+jaCkns9CL9NTt4JUJqm/BsODGmYhBcj5EAPKcHYh+OiMfyHbcDLECe17TL0hjXADFkusAqiYgA==',
             'js/bootstrap-3.4.1.js'  => 'sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==',
             'js/bootstrap-3.4.1.js'  => 'sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==',
-            'js/bootstrap-5.3.3.js'  => 'sha512-in2rcOpLTdJ7/pw5qjF4LWHFRtgoBDxXCy49H4YGOcVdGiPaQucGIbOqxt1JvmpvOpq3J/C7VTa0FlioakB2gQ==',
+            'js/bootstrap-5.3.8.js'  => 'sha512-BkZvJ5rZ3zbDCod5seWHpRGg+PRd6ZgE8Nua/OMtcxqm8Wtg0PqwhUUXK5bqvl3oclMt5O+3zjRVX0L+L2j7fA==',
             'js/dark-mode-switch.js' => 'sha512-BhY7dNU14aDN5L+muoUmA66x0CkYUWkQT0nxhKBLP/o2d7jE025+dvWJa4OiYffBGEFgmhrD/Sp+QMkxGMTz2g==',
             'js/dark-mode-switch.js' => 'sha512-BhY7dNU14aDN5L+muoUmA66x0CkYUWkQT0nxhKBLP/o2d7jE025+dvWJa4OiYffBGEFgmhrD/Sp+QMkxGMTz2g==',
             'js/jquery-3.7.1.js'     => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==',
             'js/jquery-3.7.1.js'     => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==',
-            'js/kjua-0.9.0.js'       => 'sha512-CVn7af+vTMBd9RjoS4QM5fpLFEOtBCoB0zPtaqIDC7sF4F8qgUSRFQQpIyEDGsr6yrjbuOLzdf20tkHHmpaqwQ==',
-            'js/legacy.js'           => 'sha512-UxW/TOZKon83n6dk/09GsYKIyeO5LeBHokxyIq+r7KFS5KMBeIB/EM7NrkVYIezwZBaovnyNtY2d9tKFicRlXg==',
+            'js/kjua-0.10.0.js'      => 'sha512-BYj4xggowR7QD150VLSTRlzH62YPfhpIM+b/1EUEr7RQpdWAGKulxWnOvjFx1FUlba4m6ihpNYuQab51H6XlYg==',
+            'js/legacy.js'           => 'sha512-iP69buypAHBJOgt7AyDcfaelVxBES9/k3dVfd6hPxTRizVRH2dijEpMWCt1D8OH4FNgytKsDI/J7+9y7IgXPaA==',
             'js/prettify.js'         => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
             'js/prettify.js'         => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
-            'js/privatebin.js'       => 'sha512-i0l0rh+NCY8Oeg9SxzQREHin6egXJ6sdIC84RTEsBpBNhYGObv8QEdRng1dMERZmw4olVeXx2ZCkusTyT1G+SA==',
-            'js/purify-3.2.6.js'     => 'sha512-zqwL4OoBLFx89QPewkz4Lz5CSA2ktU+f31fuECkF0iK3Id5qd3Zpq5dMby8KwHjIEpsUgOqwF58cnmcaNem0EA==',
+            'js/privatebin.js'       => 'sha512-ehY9hXpKGr1xhUaZbD6n2IyfyUvTVHDtcobY/azlnuOnXmdwgH4dHSGUSNxscyeMxHAktS/9K8UTYyCka4Cnlg==',
+            'js/purify-3.3.0.js'     => 'sha512-lsHD5zxs4lu/NDzaaibe27Vd2t7Cy9JQ3qDHUvDfb4oZvKoWDNEhwUY+4bT3R68cGgpgCYp8U1x2ifeVxqurdQ==',
             'js/rawinflate-0.3.js'   => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==',
             'js/rawinflate-0.3.js'   => 'sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==',
             'js/showdown-2.1.0.js'   => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
             'js/showdown-2.1.0.js'   => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
             'js/zlib-1.3.1-1.js'     => 'sha512-5bU9IIP4PgBrOKLZvGWJD4kgfQrkTz8Z3Iqeu058mbQzW3mCumOU6M3UVbVZU9rrVoVwaW4cZK8U8h5xjF88eQ==',
             'js/zlib-1.3.1-1.js'     => 'sha512-5bU9IIP4PgBrOKLZvGWJD4kgfQrkTz8Z3Iqeu058mbQzW3mCumOU6M3UVbVZU9rrVoVwaW4cZK8U8h5xjF88eQ==',

+ 8 - 4
lib/Controller.php

@@ -210,13 +210,17 @@ class Controller
     {
     {
         $templates = $this->_conf->getKey('availabletemplates');
         $templates = $this->_conf->getKey('availabletemplates');
         $template  = $this->_conf->getKey('template');
         $template  = $this->_conf->getKey('template');
+        if (!in_array($template, $templates, true)) {
+            $templates[] = $template;
+        }
         TemplateSwitcher::setAvailableTemplates($templates);
         TemplateSwitcher::setAvailableTemplates($templates);
         TemplateSwitcher::setTemplateFallback($template);
         TemplateSwitcher::setTemplateFallback($template);
 
 
-        // force default template, if template selection is disabled and a default is set
-        if (!$this->_conf->getKey('templateselection') && !empty($template)) {
-            $_COOKIE['template'] = $template;
-            setcookie('template', $template, array('SameSite' => 'Lax', 'Secure' => true));
+        // force default template, if template selection is disabled
+        if (!$this->_conf->getKey('templateselection') && array_key_exists('template', $_COOKIE)) {
+            unset($_COOKIE['template']); // ensure value is not re-used in template switcher
+            $expiredInAllTimezones = time() - 86400;
+            setcookie('template', '', array('expires' => $expiredInAllTimezones, 'SameSite' => 'Lax', 'Secure' => true));
         }
         }
     }
     }
 
 

+ 6 - 0
lib/Data/Filesystem.php

@@ -274,6 +274,9 @@ class Filesystem extends AbstractData
     {
     {
         switch ($namespace) {
         switch ($namespace) {
             case 'purge_limiter':
             case 'purge_limiter':
+                if (function_exists('opcache_invalidate')) {
+                    opcache_invalidate($this->_path . DIRECTORY_SEPARATOR . 'purge_limiter.php');
+                }
                 return $this->_storeString(
                 return $this->_storeString(
                     $this->_path . DIRECTORY_SEPARATOR . 'purge_limiter.php',
                     $this->_path . DIRECTORY_SEPARATOR . 'purge_limiter.php',
                     '<?php' . PHP_EOL . '$GLOBALS[\'purge_limiter\'] = ' . var_export($value, true) . ';'
                     '<?php' . PHP_EOL . '$GLOBALS[\'purge_limiter\'] = ' . var_export($value, true) . ';'
@@ -285,6 +288,9 @@ class Filesystem extends AbstractData
                 );
                 );
             case 'traffic_limiter':
             case 'traffic_limiter':
                 $this->_last_cache[$key] = $value;
                 $this->_last_cache[$key] = $value;
+                if (function_exists('opcache_invalidate')) {
+                    opcache_invalidate($this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php');
+                }
                 return $this->_storeString(
                 return $this->_storeString(
                     $this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php',
                     $this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php',
                     '<?php' . PHP_EOL . '$GLOBALS[\'traffic_limiter\'] = ' . var_export($this->_last_cache, true) . ';'
                     '<?php' . PHP_EOL . '$GLOBALS[\'traffic_limiter\'] = ' . var_export($this->_last_cache, true) . ';'

+ 4 - 10
lib/Persistence/TrafficLimiter.php

@@ -204,16 +204,10 @@ class TrafficLimiter extends AbstractPersistence
         $now  = time();
         $now  = time();
         $tl   = (int) self::$_store->getValue('traffic_limiter', $hash);
         $tl   = (int) self::$_store->getValue('traffic_limiter', $hash);
         self::$_store->purgeValues('traffic_limiter', $now - self::$_limit);
         self::$_store->purgeValues('traffic_limiter', $now - self::$_limit);
-        if ($tl > 0 && ($tl + self::$_limit >= $now)) {
-            $result = false;
-        } else {
-            $tl     = time();
-            $result = true;
-        }
-        if (!self::$_store->setValue((string) $tl, 'traffic_limiter', $hash)) {
-            error_log('failed to store the traffic limiter, it probably contains outdated information');
-        }
-        if ($result) {
+        if ($tl === 0 || ($tl + self::$_limit) < $now) {
+            if (!self::$_store->setValue((string) $now, 'traffic_limiter', $hash)) {
+                error_log('failed to store the traffic limiter, it probably contains outdated information');
+            }
             return true;
             return true;
         }
         }
         throw new Exception(I18n::_(
         throw new Exception(I18n::_(

+ 14 - 34
lib/TemplateSwitcher.php

@@ -59,16 +59,13 @@ class TemplateSwitcher
     {
     {
         if (self::isTemplateAvailable($template)) {
         if (self::isTemplateAvailable($template)) {
             self::$_templateFallback = $template;
             self::$_templateFallback = $template;
-
-            if (!in_array($template, self::getAvailableTemplates())) {
-                // Add custom template to the available templates list
-                self::$_availableTemplates[] = $template;
-            }
+        } else {
+            error_log('failed to set "' . $template . '" as a fallback, it needs to be added to the list of `availabletemplates` in the configuration file');
         }
         }
     }
     }
 
 
     /**
     /**
-     * get currently loaded template
+     * get user selected template or fallback
      *
      *
      * @access public
      * @access public
      * @static
      * @static
@@ -76,8 +73,13 @@ class TemplateSwitcher
      */
      */
     public static function getTemplate(): string
     public static function getTemplate(): string
     {
     {
-        $selectedTemplate = self::getSelectedByUserTemplate();
-        return $selectedTemplate ?? self::$_templateFallback;
+        if (array_key_exists('template', $_COOKIE)) {
+            $template = basename($_COOKIE['template']);
+            if (self::isTemplateAvailable($template)) {
+                return $template;
+            }
+        }
+        return self::$_templateFallback;
     }
     }
 
 
     /**
     /**
@@ -101,32 +103,10 @@ class TemplateSwitcher
      */
      */
     public static function isTemplateAvailable(string $template): bool
     public static function isTemplateAvailable(string $template): bool
     {
     {
-        $available = in_array($template, self::getAvailableTemplates());
-
-        if (!$available && !View::isBootstrapTemplate($template)) {
-            $path      = View::getTemplateFilePath($template);
-            $available = file_exists($path);
-        }
-
-        return $available;
-    }
-
-    /**
-     * get the template selected by user
-     *
-     * @access private
-     * @static
-     * @return string|null
-     */
-    private static function getSelectedByUserTemplate(): ?string
-    {
-        $selectedTemplate    = null;
-        $templateCookieValue = $_COOKIE['template'] ?? '';
-
-        if (self::isTemplateAvailable($templateCookieValue)) {
-            $selectedTemplate = $templateCookieValue;
+        if (in_array($template, self::getAvailableTemplates(), true)) {
+            return true;
         }
         }
-
-        return $selectedTemplate;
+        error_log('template "' . $template . '" is not in the list of `availabletemplates` in the configuration file');
+        return false;
     }
     }
 }
 }

+ 8 - 28
lib/View.php

@@ -49,39 +49,19 @@ class View
      */
      */
     public function draw($template)
     public function draw($template)
     {
     {
-        $path = self::getTemplateFilePath($template);
-        if (!file_exists($path)) {
-            throw new Exception('Template ' . $template . ' not found!', 80);
+        $dir  = PATH . 'tpl' . DIRECTORY_SEPARATOR;
+        $file = substr($template, 0, 10) === 'bootstrap-' ? 'bootstrap' : $template;
+        $path = $dir . $file . '.php';
+        if (!is_file($path)) {
+            throw new Exception('Template ' . $template . ' not found in file ' . $path . '!', 80);
+        }
+        if (!in_array($path, glob($dir . '*.php', GLOB_NOSORT | GLOB_ERR), true)) {
+            throw new Exception('Template ' . $file . '.php not found in ' . $dir . '!', 81);
         }
         }
         extract($this->_variables);
         extract($this->_variables);
         include $path;
         include $path;
     }
     }
 
 
-    /**
-     * Get template file path
-     *
-     * @access public
-     * @param  string $template
-     * @return string
-     */
-    public static function getTemplateFilePath(string $template): string
-    {
-        $file = self::isBootstrapTemplate($template) ? 'bootstrap' : $template;
-        return PATH . 'tpl' . DIRECTORY_SEPARATOR . $file . '.php';
-    }
-
-    /**
-     * Is the template a variation of the bootstrap template
-     *
-     * @access public
-     * @param  string $template
-     * @return bool
-     */
-    public static function isBootstrapTemplate(string $template): bool
-    {
-        return substr($template, 0, 10) === 'bootstrap-';
-    }
-
     /**
     /**
      * echo script tag incl. SRI hash for given script file
      * echo script tag incl. SRI hash for given script file
      *
      *

+ 4 - 4
tpl/bootstrap.php

@@ -46,7 +46,7 @@ endif;
 <?php
 <?php
 if ($QRCODE) :
 if ($QRCODE) :
 ?>
 ?>
-		<?php $this->_scriptTag('js/kjua-0.9.0.js', 'async'); ?>
+		<?php $this->_scriptTag('js/kjua-0.10.0.js', 'async'); ?>
 <?php
 <?php
 endif;
 endif;
 if ($ZEROBINCOMPATIBILITY) :
 if ($ZEROBINCOMPATIBILITY) :
@@ -56,7 +56,7 @@ if ($ZEROBINCOMPATIBILITY) :
 endif;
 endif;
 ?>
 ?>
 		<?php $this->_scriptTag('js/zlib-1.3.1-1.js', 'async'); ?>
 		<?php $this->_scriptTag('js/zlib-1.3.1-1.js', 'async'); ?>
-		<?php $this->_scriptTag('js/base-x-4.0.0.js', 'defer'); ?>
+		<?php $this->_scriptTag('js/base-x-5.0.1.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/rawinflate-0.3.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/rawinflate-0.3.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/bootstrap-3.4.1.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/bootstrap-3.4.1.js', 'defer'); ?>
 <?php
 <?php
@@ -71,8 +71,8 @@ if ($MARKDOWN) :
 <?php
 <?php
 endif;
 endif;
 ?>
 ?>
-		<?php $this->_scriptTag('js/purify-3.2.6.js', 'async'); ?>
-		<?php $this->_scriptTag('js/legacy.js', 'async'); ?>
+		<?php $this->_scriptTag('js/purify-3.3.0.js', 'defer'); ?>
+		<?php $this->_scriptTag('js/legacy.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/privatebin.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/privatebin.js', 'defer'); ?>
 		<!-- icon -->
 		<!-- icon -->
 		<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
 		<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />

+ 6 - 6
tpl/bootstrap5.php

@@ -10,7 +10,7 @@ use PrivateBin\I18n;
 		<meta name="robots" content="noindex" />
 		<meta name="robots" content="noindex" />
 		<meta name="google" content="notranslate">
 		<meta name="google" content="notranslate">
 		<title><?php echo I18n::_($NAME); ?></title>
 		<title><?php echo I18n::_($NAME); ?></title>
-		<link type="text/css" rel="stylesheet" href="css/bootstrap5/bootstrap<?php echo I18n::isRtl() ? '.rtl' : ''; ?>-5.3.3.css" />
+		<link type="text/css" rel="stylesheet" href="css/bootstrap5/bootstrap<?php echo I18n::isRtl() ? '.rtl' : ''; ?>-5.3.8.css" />
 		<link type="text/css" rel="stylesheet" href="css/bootstrap5/privatebin.css?<?php echo rawurlencode($VERSION); ?>" />
 		<link type="text/css" rel="stylesheet" href="css/bootstrap5/privatebin.css?<?php echo rawurlencode($VERSION); ?>" />
 <?php
 <?php
 if ($SYNTAXHIGHLIGHTING) :
 if ($SYNTAXHIGHLIGHTING) :
@@ -29,7 +29,7 @@ endif;
 <?php
 <?php
 if ($QRCODE) :
 if ($QRCODE) :
 ?>
 ?>
-		<?php $this->_scriptTag('js/kjua-0.9.0.js', 'async'); ?>
+		<?php $this->_scriptTag('js/kjua-0.10.0.js', 'async'); ?>
 <?php
 <?php
 endif;
 endif;
 if ($ZEROBINCOMPATIBILITY) :
 if ($ZEROBINCOMPATIBILITY) :
@@ -39,9 +39,9 @@ if ($ZEROBINCOMPATIBILITY) :
 endif;
 endif;
 ?>
 ?>
 		<?php $this->_scriptTag('js/zlib-1.3.1-1.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/zlib-1.3.1-1.js', 'defer'); ?>
-		<?php $this->_scriptTag('js/base-x-4.0.0.js', 'defer'); ?>
+		<?php $this->_scriptTag('js/base-x-5.0.1.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/rawinflate-0.3.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/rawinflate-0.3.js', 'defer'); ?>
-		<?php $this->_scriptTag('js/bootstrap-5.3.3.js', 'async'); ?>
+		<?php $this->_scriptTag('js/bootstrap-5.3.8.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/dark-mode-switch.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/dark-mode-switch.js', 'defer'); ?>
 <?php
 <?php
 if ($SYNTAXHIGHLIGHTING) :
 if ($SYNTAXHIGHLIGHTING) :
@@ -55,8 +55,8 @@ if ($MARKDOWN) :
 <?php
 <?php
 endif;
 endif;
 ?>
 ?>
-		<?php $this->_scriptTag('js/purify-3.2.6.js', 'defer'); ?>
-		<?php $this->_scriptTag('js/legacy.js', 'async'); ?>
+		<?php $this->_scriptTag('js/purify-3.3.0.js', 'defer'); ?>
+		<?php $this->_scriptTag('js/legacy.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/privatebin.js', 'defer'); ?>
 		<?php $this->_scriptTag('js/privatebin.js', 'defer'); ?>
 		<!-- icon -->
 		<!-- icon -->
 		<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
 		<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />

+ 3 - 0
tst/Bootstrap.php

@@ -6,7 +6,9 @@ use Google\Cloud\Storage\Bucket;
 use Google\Cloud\Storage\Connection\ConnectionInterface;
 use Google\Cloud\Storage\Connection\ConnectionInterface;
 use Google\Cloud\Storage\StorageClient;
 use Google\Cloud\Storage\StorageClient;
 use Google\Cloud\Storage\StorageObject;
 use Google\Cloud\Storage\StorageObject;
+use PrivateBin\Configuration;
 use PrivateBin\Persistence\ServerSalt;
 use PrivateBin\Persistence\ServerSalt;
+use PrivateBin\TemplateSwitcher;
 
 
 error_reporting(E_ALL | E_STRICT);
 error_reporting(E_ALL | E_STRICT);
 
 
@@ -26,6 +28,7 @@ if (!defined('CONF_SAMPLE')) {
 
 
 require PATH . 'vendor/autoload.php';
 require PATH . 'vendor/autoload.php';
 Helper::updateSubresourceIntegrity();
 Helper::updateSubresourceIntegrity();
+TemplateSwitcher::setAvailableTemplates(Configuration::getDefaults()['main']['availabletemplates']);
 
 
 /**
 /**
  * Class Helper provides unit tests pastes and comments of various formats
  * Class Helper provides unit tests pastes and comments of various formats

+ 5 - 13
tst/ViewTest.php

@@ -143,19 +143,11 @@ class ViewTest extends TestCase
         $test->draw('123456789 does not exist!');
         $test->draw('123456789 does not exist!');
     }
     }
 
 
-    public function testTemplateFilePath()
+    public function testInvalidTemplate()
     {
     {
-        $template     = 'bootstrap';
-        $templatePath = PATH . 'tpl' . DIRECTORY_SEPARATOR . $template . '.php';
-        $path         = View::getTemplateFilePath($template);
-        $this->assertEquals($templatePath, $path, 'Template file path');
-    }
-
-    public function testIsBootstrapTemplate()
-    {
-        $bootstrapTemplate    = 'bootstrap-dark';
-        $nonBootstrapTemplate = 'page';
-        $this->assertTrue(View::isBootstrapTemplate($bootstrapTemplate), 'Is bootstrap template');
-        $this->assertFalse(View::isBootstrapTemplate($nonBootstrapTemplate), 'Is not bootstrap template');
+        $test = new View;
+        $this->expectException(Exception::class);
+        $this->expectExceptionCode(81);
+        $test->draw('../index');
     }
     }
 }
 }

+ 45 - 51
vendor/composer/ClassLoader.php

@@ -45,35 +45,34 @@ class ClassLoader
     /** @var \Closure(string):void */
     /** @var \Closure(string):void */
     private static $includeFile;
     private static $includeFile;
 
 
-    /** @var ?string */
+    /** @var string|null */
     private $vendorDir;
     private $vendorDir;
 
 
     // PSR-4
     // PSR-4
     /**
     /**
-     * @var array[]
-     * @psalm-var array<string, array<string, int>>
+     * @var array<string, array<string, int>>
      */
      */
     private $prefixLengthsPsr4 = array();
     private $prefixLengthsPsr4 = array();
     /**
     /**
-     * @var array[]
-     * @psalm-var array<string, array<int, string>>
+     * @var array<string, list<string>>
      */
      */
     private $prefixDirsPsr4 = array();
     private $prefixDirsPsr4 = array();
     /**
     /**
-     * @var array[]
-     * @psalm-var array<string, string>
+     * @var list<string>
      */
      */
     private $fallbackDirsPsr4 = array();
     private $fallbackDirsPsr4 = array();
 
 
     // PSR-0
     // PSR-0
     /**
     /**
-     * @var array[]
-     * @psalm-var array<string, array<string, string[]>>
+     * List of PSR-0 prefixes
+     *
+     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+     *
+     * @var array<string, array<string, list<string>>>
      */
      */
     private $prefixesPsr0 = array();
     private $prefixesPsr0 = array();
     /**
     /**
-     * @var array[]
-     * @psalm-var array<string, string>
+     * @var list<string>
      */
      */
     private $fallbackDirsPsr0 = array();
     private $fallbackDirsPsr0 = array();
 
 
@@ -81,8 +80,7 @@ class ClassLoader
     private $useIncludePath = false;
     private $useIncludePath = false;
 
 
     /**
     /**
-     * @var string[]
-     * @psalm-var array<string, string>
+     * @var array<string, string>
      */
      */
     private $classMap = array();
     private $classMap = array();
 
 
@@ -90,21 +88,20 @@ class ClassLoader
     private $classMapAuthoritative = false;
     private $classMapAuthoritative = false;
 
 
     /**
     /**
-     * @var bool[]
-     * @psalm-var array<string, bool>
+     * @var array<string, bool>
      */
      */
     private $missingClasses = array();
     private $missingClasses = array();
 
 
-    /** @var ?string */
+    /** @var string|null */
     private $apcuPrefix;
     private $apcuPrefix;
 
 
     /**
     /**
-     * @var self[]
+     * @var array<string, self>
      */
      */
     private static $registeredLoaders = array();
     private static $registeredLoaders = array();
 
 
     /**
     /**
-     * @param ?string $vendorDir
+     * @param string|null $vendorDir
      */
      */
     public function __construct($vendorDir = null)
     public function __construct($vendorDir = null)
     {
     {
@@ -113,7 +110,7 @@ class ClassLoader
     }
     }
 
 
     /**
     /**
-     * @return string[]
+     * @return array<string, list<string>>
      */
      */
     public function getPrefixes()
     public function getPrefixes()
     {
     {
@@ -125,8 +122,7 @@ class ClassLoader
     }
     }
 
 
     /**
     /**
-     * @return array[]
-     * @psalm-return array<string, array<int, string>>
+     * @return array<string, list<string>>
      */
      */
     public function getPrefixesPsr4()
     public function getPrefixesPsr4()
     {
     {
@@ -134,8 +130,7 @@ class ClassLoader
     }
     }
 
 
     /**
     /**
-     * @return array[]
-     * @psalm-return array<string, string>
+     * @return list<string>
      */
      */
     public function getFallbackDirs()
     public function getFallbackDirs()
     {
     {
@@ -143,8 +138,7 @@ class ClassLoader
     }
     }
 
 
     /**
     /**
-     * @return array[]
-     * @psalm-return array<string, string>
+     * @return list<string>
      */
      */
     public function getFallbackDirsPsr4()
     public function getFallbackDirsPsr4()
     {
     {
@@ -152,8 +146,7 @@ class ClassLoader
     }
     }
 
 
     /**
     /**
-     * @return string[] Array of classname => path
-     * @psalm-return array<string, string>
+     * @return array<string, string> Array of classname => path
      */
      */
     public function getClassMap()
     public function getClassMap()
     {
     {
@@ -161,8 +154,7 @@ class ClassLoader
     }
     }
 
 
     /**
     /**
-     * @param string[] $classMap Class to filename map
-     * @psalm-param array<string, string> $classMap
+     * @param array<string, string> $classMap Class to filename map
      *
      *
      * @return void
      * @return void
      */
      */
@@ -179,24 +171,25 @@ class ClassLoader
      * Registers a set of PSR-0 directories for a given prefix, either
      * Registers a set of PSR-0 directories for a given prefix, either
      * appending or prepending to the ones previously set for this prefix.
      * appending or prepending to the ones previously set for this prefix.
      *
      *
-     * @param string          $prefix  The prefix
-     * @param string[]|string $paths   The PSR-0 root directories
-     * @param bool            $prepend Whether to prepend the directories
+     * @param string              $prefix  The prefix
+     * @param list<string>|string $paths   The PSR-0 root directories
+     * @param bool                $prepend Whether to prepend the directories
      *
      *
      * @return void
      * @return void
      */
      */
     public function add($prefix, $paths, $prepend = false)
     public function add($prefix, $paths, $prepend = false)
     {
     {
+        $paths = (array) $paths;
         if (!$prefix) {
         if (!$prefix) {
             if ($prepend) {
             if ($prepend) {
                 $this->fallbackDirsPsr0 = array_merge(
                 $this->fallbackDirsPsr0 = array_merge(
-                    (array) $paths,
+                    $paths,
                     $this->fallbackDirsPsr0
                     $this->fallbackDirsPsr0
                 );
                 );
             } else {
             } else {
                 $this->fallbackDirsPsr0 = array_merge(
                 $this->fallbackDirsPsr0 = array_merge(
                     $this->fallbackDirsPsr0,
                     $this->fallbackDirsPsr0,
-                    (array) $paths
+                    $paths
                 );
                 );
             }
             }
 
 
@@ -205,19 +198,19 @@ class ClassLoader
 
 
         $first = $prefix[0];
         $first = $prefix[0];
         if (!isset($this->prefixesPsr0[$first][$prefix])) {
         if (!isset($this->prefixesPsr0[$first][$prefix])) {
-            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+            $this->prefixesPsr0[$first][$prefix] = $paths;
 
 
             return;
             return;
         }
         }
         if ($prepend) {
         if ($prepend) {
             $this->prefixesPsr0[$first][$prefix] = array_merge(
             $this->prefixesPsr0[$first][$prefix] = array_merge(
-                (array) $paths,
+                $paths,
                 $this->prefixesPsr0[$first][$prefix]
                 $this->prefixesPsr0[$first][$prefix]
             );
             );
         } else {
         } else {
             $this->prefixesPsr0[$first][$prefix] = array_merge(
             $this->prefixesPsr0[$first][$prefix] = array_merge(
                 $this->prefixesPsr0[$first][$prefix],
                 $this->prefixesPsr0[$first][$prefix],
-                (array) $paths
+                $paths
             );
             );
         }
         }
     }
     }
@@ -226,9 +219,9 @@ class ClassLoader
      * Registers a set of PSR-4 directories for a given namespace, either
      * Registers a set of PSR-4 directories for a given namespace, either
      * appending or prepending to the ones previously set for this namespace.
      * appending or prepending to the ones previously set for this namespace.
      *
      *
-     * @param string          $prefix  The prefix/namespace, with trailing '\\'
-     * @param string[]|string $paths   The PSR-4 base directories
-     * @param bool            $prepend Whether to prepend the directories
+     * @param string              $prefix  The prefix/namespace, with trailing '\\'
+     * @param list<string>|string $paths   The PSR-4 base directories
+     * @param bool                $prepend Whether to prepend the directories
      *
      *
      * @throws \InvalidArgumentException
      * @throws \InvalidArgumentException
      *
      *
@@ -236,17 +229,18 @@ class ClassLoader
      */
      */
     public function addPsr4($prefix, $paths, $prepend = false)
     public function addPsr4($prefix, $paths, $prepend = false)
     {
     {
+        $paths = (array) $paths;
         if (!$prefix) {
         if (!$prefix) {
             // Register directories for the root namespace.
             // Register directories for the root namespace.
             if ($prepend) {
             if ($prepend) {
                 $this->fallbackDirsPsr4 = array_merge(
                 $this->fallbackDirsPsr4 = array_merge(
-                    (array) $paths,
+                    $paths,
                     $this->fallbackDirsPsr4
                     $this->fallbackDirsPsr4
                 );
                 );
             } else {
             } else {
                 $this->fallbackDirsPsr4 = array_merge(
                 $this->fallbackDirsPsr4 = array_merge(
                     $this->fallbackDirsPsr4,
                     $this->fallbackDirsPsr4,
-                    (array) $paths
+                    $paths
                 );
                 );
             }
             }
         } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
         } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
@@ -256,18 +250,18 @@ class ClassLoader
                 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
                 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
             }
             }
             $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
             $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
-            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+            $this->prefixDirsPsr4[$prefix] = $paths;
         } elseif ($prepend) {
         } elseif ($prepend) {
             // Prepend directories for an already registered namespace.
             // Prepend directories for an already registered namespace.
             $this->prefixDirsPsr4[$prefix] = array_merge(
             $this->prefixDirsPsr4[$prefix] = array_merge(
-                (array) $paths,
+                $paths,
                 $this->prefixDirsPsr4[$prefix]
                 $this->prefixDirsPsr4[$prefix]
             );
             );
         } else {
         } else {
             // Append directories for an already registered namespace.
             // Append directories for an already registered namespace.
             $this->prefixDirsPsr4[$prefix] = array_merge(
             $this->prefixDirsPsr4[$prefix] = array_merge(
                 $this->prefixDirsPsr4[$prefix],
                 $this->prefixDirsPsr4[$prefix],
-                (array) $paths
+                $paths
             );
             );
         }
         }
     }
     }
@@ -276,8 +270,8 @@ class ClassLoader
      * Registers a set of PSR-0 directories for a given prefix,
      * Registers a set of PSR-0 directories for a given prefix,
      * replacing any others previously set for this prefix.
      * replacing any others previously set for this prefix.
      *
      *
-     * @param string          $prefix The prefix
-     * @param string[]|string $paths  The PSR-0 base directories
+     * @param string              $prefix The prefix
+     * @param list<string>|string $paths  The PSR-0 base directories
      *
      *
      * @return void
      * @return void
      */
      */
@@ -294,8 +288,8 @@ class ClassLoader
      * Registers a set of PSR-4 directories for a given namespace,
      * Registers a set of PSR-4 directories for a given namespace,
      * replacing any others previously set for this namespace.
      * replacing any others previously set for this namespace.
      *
      *
-     * @param string          $prefix The prefix/namespace, with trailing '\\'
-     * @param string[]|string $paths  The PSR-4 base directories
+     * @param string              $prefix The prefix/namespace, with trailing '\\'
+     * @param list<string>|string $paths  The PSR-4 base directories
      *
      *
      * @throws \InvalidArgumentException
      * @throws \InvalidArgumentException
      *
      *
@@ -481,9 +475,9 @@ class ClassLoader
     }
     }
 
 
     /**
     /**
-     * Returns the currently registered loaders indexed by their corresponding vendor directories.
+     * Returns the currently registered loaders keyed by their corresponding vendor directories.
      *
      *
-     * @return self[]
+     * @return array<string, self>
      */
      */
     public static function getRegisteredLoaders()
     public static function getRegisteredLoaders()
     {
     {

+ 1 - 0
vendor/composer/autoload_classmap.php

@@ -22,6 +22,7 @@ return array(
     'IPLib\\Range\\Subnet' => $vendorDir . '/mlocati/ip-lib/src/Range/Subnet.php',
     'IPLib\\Range\\Subnet' => $vendorDir . '/mlocati/ip-lib/src/Range/Subnet.php',
     'IPLib\\Range\\Type' => $vendorDir . '/mlocati/ip-lib/src/Range/Type.php',
     'IPLib\\Range\\Type' => $vendorDir . '/mlocati/ip-lib/src/Range/Type.php',
     'IPLib\\Service\\BinaryMath' => $vendorDir . '/mlocati/ip-lib/src/Service/BinaryMath.php',
     'IPLib\\Service\\BinaryMath' => $vendorDir . '/mlocati/ip-lib/src/Service/BinaryMath.php',
+    'IPLib\\Service\\NumberInChunks' => $vendorDir . '/mlocati/ip-lib/src/Service/NumberInChunks.php',
     'IPLib\\Service\\RangesFromBoundaryCalculator' => $vendorDir . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
     'IPLib\\Service\\RangesFromBoundaryCalculator' => $vendorDir . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
     'IPLib\\Service\\UnsignedIntegerMath' => $vendorDir . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
     'IPLib\\Service\\UnsignedIntegerMath' => $vendorDir . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
     'Identicon\\Generator\\BaseGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php',
     'Identicon\\Generator\\BaseGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php',

+ 1 - 0
vendor/composer/autoload_static.php

@@ -70,6 +70,7 @@ class ComposerStaticInitDontChange
         'IPLib\\Range\\Subnet' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Subnet.php',
         'IPLib\\Range\\Subnet' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Subnet.php',
         'IPLib\\Range\\Type' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Type.php',
         'IPLib\\Range\\Type' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Type.php',
         'IPLib\\Service\\BinaryMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/BinaryMath.php',
         'IPLib\\Service\\BinaryMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/BinaryMath.php',
+        'IPLib\\Service\\NumberInChunks' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/NumberInChunks.php',
         'IPLib\\Service\\RangesFromBoundaryCalculator' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
         'IPLib\\Service\\RangesFromBoundaryCalculator' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
         'IPLib\\Service\\UnsignedIntegerMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
         'IPLib\\Service\\UnsignedIntegerMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
         'Identicon\\Generator\\BaseGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php',
         'Identicon\\Generator\\BaseGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php',

+ 9 - 9
vendor/composer/installed.php

@@ -1,9 +1,9 @@
 <?php return array(
 <?php return array(
     'root' => array(
     'root' => array(
         'name' => 'privatebin/privatebin',
         'name' => 'privatebin/privatebin',
-        'pretty_version' => 'dev-master',
-        'version' => 'dev-master',
-        'reference' => '7825471d70c39baf6042c52a453c8fe705d9ed75',
+        'pretty_version' => 'dev-1.7-backport-2.0.3',
+        'version' => 'dev-1.7-backport-2.0.3',
+        'reference' => 'eb203e2d2505fd167de6f4c714562fad611b1ffa',
         'type' => 'project',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
         'aliases' => array(),
@@ -20,18 +20,18 @@
             'dev_requirement' => false,
             'dev_requirement' => false,
         ),
         ),
         'mlocati/ip-lib' => array(
         'mlocati/ip-lib' => array(
-            'pretty_version' => '1.20.0',
-            'version' => '1.20.0.0',
-            'reference' => 'fd45fc3bf08ed6c7e665e2e70562082ac954afd4',
+            'pretty_version' => '1.21.0',
+            'version' => '1.21.0.0',
+            'reference' => 'b5d38cdcbfc1516604d821a1f3f4a1638f327267',
             'type' => 'library',
             'type' => 'library',
             'install_path' => __DIR__ . '/../mlocati/ip-lib',
             'install_path' => __DIR__ . '/../mlocati/ip-lib',
             'aliases' => array(),
             'aliases' => array(),
             'dev_requirement' => false,
             'dev_requirement' => false,
         ),
         ),
         'privatebin/privatebin' => array(
         'privatebin/privatebin' => array(
-            'pretty_version' => 'dev-master',
-            'version' => 'dev-master',
-            'reference' => '7825471d70c39baf6042c52a453c8fe705d9ed75',
+            'pretty_version' => 'dev-1.7-backport-2.0.3',
+            'version' => 'dev-1.7-backport-2.0.3',
+            'reference' => 'eb203e2d2505fd167de6f4c714562fad611b1ffa',
             'type' => 'project',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
             'aliases' => array(),

+ 3 - 2
vendor/mlocati/ip-lib/src/Address/AddressInterface.php

@@ -110,11 +110,12 @@ interface AddressInterface
     /**
     /**
      * Get the address at a certain distance from this address.
      * Get the address at a certain distance from this address.
      *
      *
-     * @param int $n the distance of the address (can be negative)
+     * @param int|numeric-string $n the distance of the address (can be negative)
      *
      *
-     * @return \IPLib\Address\AddressInterface|null return NULL if $n is not an integer or if the final address would be invalid
+     * @return \IPLib\Address\AddressInterface|null return NULL if $n is not an integer or NULL if $n is neither an integer nor a string containing a valid integer, or if the final address would be invalid
      *
      *
      * @since 1.15.0
      * @since 1.15.0
+     * @since 1.21.0 $n can also be a numeric string
      *
      *
      * @example passing 1 to the address 127.0.0.1 will result in 127.0.0.2
      * @example passing 1 to the address 127.0.0.1 will result in 127.0.0.2
      * @example passing -1 to the address 127.0.0.1 will result in 127.0.0.0
      * @example passing -1 to the address 127.0.0.1 will result in 127.0.0.0

+ 14 - 16
vendor/mlocati/ip-lib/src/Address/IPv4.php

@@ -6,6 +6,8 @@ use IPLib\ParseStringFlag;
 use IPLib\Range\RangeInterface;
 use IPLib\Range\RangeInterface;
 use IPLib\Range\Subnet;
 use IPLib\Range\Subnet;
 use IPLib\Range\Type as RangeType;
 use IPLib\Range\Type as RangeType;
+use IPLib\Service\BinaryMath;
+use IPLib\Service\NumberInChunks;
 
 
 /**
 /**
  * An IPv4 address.
  * An IPv4 address.
@@ -456,28 +458,24 @@ class IPv4 implements AddressInterface
      */
      */
     public function getAddressAtOffset($n)
     public function getAddressAtOffset($n)
     {
     {
-        if (!is_int($n)) {
+        if (is_int($n)) {
+            $thatChunks = NumberInChunks::fromInteger($n, NumberInChunks::CHUNKSIZE_BYTES);
+        } elseif (($s = BinaryMath::getInstance()->normalizeIntegerString($n)) !== '') {
+            $thatChunks = NumberInChunks::fromNumericString($s, NumberInChunks::CHUNKSIZE_BYTES);
+        } else {
             return null;
             return null;
         }
         }
-
-        $boundary = 256;
-        $mod = $n;
-        $bytes = $this->getBytes();
-        for ($i = count($bytes) - 1; $i >= 0; $i--) {
-            $tmp = ($bytes[$i] + $mod) % $boundary;
-            $mod = (int) floor(($bytes[$i] + $mod) / $boundary);
-            if ($tmp < 0) {
-                $tmp += $boundary;
-            }
-
-            $bytes[$i] = $tmp;
+        $myBytes = $this->getBytes();
+        while (isset($myBytes[1]) && $myBytes[0] === 0) {
+            array_shift($myBytes);
         }
         }
-
-        if ($mod !== 0) {
+        $myChunks = new NumberInChunks(false, $myBytes, NumberInChunks::CHUNKSIZE_BYTES);
+        $result = $myChunks->add($thatChunks);
+        if ($result->negative || count($result->chunks) > 4) {
             return null;
             return null;
         }
         }
 
 
-        return static::fromBytes($bytes);
+        return static::fromBytes(array_pad($result->chunks, -4, 0));
     }
     }
 
 
     /**
     /**

+ 15 - 17
vendor/mlocati/ip-lib/src/Address/IPv6.php

@@ -6,6 +6,8 @@ use IPLib\ParseStringFlag;
 use IPLib\Range\RangeInterface;
 use IPLib\Range\RangeInterface;
 use IPLib\Range\Subnet;
 use IPLib\Range\Subnet;
 use IPLib\Range\Type as RangeType;
 use IPLib\Range\Type as RangeType;
+use IPLib\Service\BinaryMath;
+use IPLib\Service\NumberInChunks;
 
 
 /**
 /**
  * An IPv6 address.
  * An IPv6 address.
@@ -549,28 +551,24 @@ class IPv6 implements AddressInterface
      */
      */
     public function getAddressAtOffset($n)
     public function getAddressAtOffset($n)
     {
     {
-        if (!is_int($n)) {
+        if (is_int($n)) {
+            $thatChunks = NumberInChunks::fromInteger($n, NumberInChunks::CHUNKSIZE_WORDS);
+        } elseif (($s = BinaryMath::getInstance()->normalizeIntegerString($n)) !== '') {
+            $thatChunks = NumberInChunks::fromNumericString($s, NumberInChunks::CHUNKSIZE_WORDS);
+        } else {
             return null;
             return null;
         }
         }
-
-        $boundary = 0x10000;
-        $mod = $n;
-        $words = $this->getWords();
-        for ($i = count($words) - 1; $i >= 0; $i--) {
-            $tmp = ($words[$i] + $mod) % $boundary;
-            $mod = (int) floor(($words[$i] + $mod) / $boundary);
-            if ($tmp < 0) {
-                $tmp += $boundary;
-            }
-
-            $words[$i] = $tmp;
+        $myWords = $this->getWords();
+        while (isset($myWords[1]) && $myWords[0] === 0) {
+            array_shift($myWords);
         }
         }
-
-        if ($mod !== 0) {
+        $myChunks = new NumberInChunks(false, $myWords, NumberInChunks::CHUNKSIZE_WORDS);
+        $result = $myChunks->add($thatChunks);
+        if ($result->negative || count($result->chunks) > 8) {
             return null;
             return null;
         }
         }
 
 
-        return static::fromWords($words);
+        return static::fromWords(array_pad($result->chunks, -8, 0));
     }
     }
 
 
     /**
     /**
@@ -645,7 +643,7 @@ class IPv6 implements AddressInterface
         }
         }
         $myWords = $this->getWords();
         $myWords = $this->getWords();
         $otherWords = $other->getWords();
         $otherWords = $other->getWords();
-        $sum = array_fill(0, 7, 0);
+        $sum = array_fill(0, 8, 0);
         $carry = 0;
         $carry = 0;
         for ($index = 7; $index >= 0; $index--) {
         for ($index = 7; $index >= 0; $index--) {
             $word = $myWords[$index] + $otherWords[$index] + $carry;
             $word = $myWords[$index] + $otherWords[$index] + $carry;

+ 15 - 5
vendor/mlocati/ip-lib/src/Range/AbstractRange.php

@@ -7,6 +7,7 @@ use IPLib\Address\IPv4;
 use IPLib\Address\IPv6;
 use IPLib\Address\IPv6;
 use IPLib\Address\Type as AddressType;
 use IPLib\Address\Type as AddressType;
 use IPLib\Factory;
 use IPLib\Factory;
+use IPLib\Service\BinaryMath;
 use OutOfBoundsException;
 use OutOfBoundsException;
 
 
 /**
 /**
@@ -59,17 +60,26 @@ abstract class AbstractRange implements RangeInterface
      */
      */
     public function getAddressAtOffset($n)
     public function getAddressAtOffset($n)
     {
     {
-        if (!is_int($n)) {
+        if (is_int($n)) {
+            $positive = $n >= 0;
+            if ($positive === false) {
+                $nPlus1 = $n + 1;
+            }
+        } elseif (($s = BinaryMath::getInstance()->normalizeIntegerString($n)) !== '') {
+            $n = $s;
+            $positive = $n[0] !== '-';
+            if ($positive === false) {
+                $nPlus1 = BinaryMath::getInstance()->add1ToIntegerString($n);
+            }
+        } else {
             return null;
             return null;
         }
         }
-
-        $address = null;
-        if ($n >= 0) {
+        if ($positive) {
             $start = Factory::parseAddressString($this->getComparableStartString());
             $start = Factory::parseAddressString($this->getComparableStartString());
             $address = $start->getAddressAtOffset($n);
             $address = $start->getAddressAtOffset($n);
         } else {
         } else {
             $end = Factory::parseAddressString($this->getComparableEndString());
             $end = Factory::parseAddressString($this->getComparableEndString());
-            $address = $end->getAddressAtOffset($n + 1);
+            $address = $end->getAddressAtOffset($nPlus1);
         }
         }
 
 
         if ($address === null) {
         if ($address === null) {

+ 16 - 1
vendor/mlocati/ip-lib/src/Range/Pattern.php

@@ -7,6 +7,7 @@ use IPLib\Address\IPv4;
 use IPLib\Address\IPv6;
 use IPLib\Address\IPv6;
 use IPLib\Address\Type as AddressType;
 use IPLib\Address\Type as AddressType;
 use IPLib\ParseStringFlag;
 use IPLib\ParseStringFlag;
+use IPLib\Service\BinaryMath;
 
 
 /**
 /**
  * Represents an address range in pattern format (only ending asterisks are supported).
  * Represents an address range in pattern format (only ending asterisks are supported).
@@ -304,7 +305,21 @@ class Pattern extends AbstractRange
         $maxPrefix = $fromAddress::getNumberOfBits();
         $maxPrefix = $fromAddress::getNumberOfBits();
         $prefix = $this->getNetworkPrefix();
         $prefix = $this->getNetworkPrefix();
 
 
-        return pow(2, ($maxPrefix - $prefix));
+        return pow(2, $maxPrefix - $prefix);
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @see \IPLib\Range\RangeInterface::getExactSize()
+     */
+    public function getExactSize()
+    {
+        $fromAddress = $this->fromAddress;
+        $maxPrefix = $fromAddress::getNumberOfBits();
+        $prefix = $this->getNetworkPrefix();
+
+        return BinaryMath::getInstance()->pow2string($maxPrefix - $prefix);
     }
     }
 
 
     /**
     /**

+ 14 - 4
vendor/mlocati/ip-lib/src/Range/RangeInterface.php

@@ -46,11 +46,12 @@ interface RangeInterface
     /**
     /**
      * Get the address at a certain offset of this range.
      * Get the address at a certain offset of this range.
      *
      *
-     * @param int $n the offset of the address (support negative offset)
+     * @param int|numeric-string $n the offset of the address (support negative offset)
      *
      *
-     * @return \IPLib\Address\AddressInterface|null return NULL if $n is not an integer or if the offset out of range
+     * @return \IPLib\Address\AddressInterface|null return NULL if $n is neither an integer nor a string containing a valid integer, or if the offset out of range
      *
      *
      * @since 1.15.0
      * @since 1.15.0
+     * @since 1.21.0 $n can also be a numeric string
      *
      *
      * @example passing 256 to the range 127.0.0.0/16 will result in 127.0.1.0
      * @example passing 256 to the range 127.0.0.0/16 will result in 127.0.1.0
      * @example passing -1 to the range 127.0.1.0/16 will result in 127.0.255.255
      * @example passing -1 to the range 127.0.1.0/16 will result in 127.0.255.255
@@ -150,14 +151,23 @@ interface RangeInterface
     public function getReverseDNSLookupName();
     public function getReverseDNSLookupName();
 
 
     /**
     /**
-     * Get the count of addresses this IP range contains.
+     * Get the count of addresses contained in this IP range (possibly approximated).
      *
      *
-     * @return int|float Return float as for huge IPv6 networks, int is not enough
+     * @return int|float If the number of addresses exceeds PHP_INT_MAX a float containing an approximation will be returned
      *
      *
      * @since 1.16.0
      * @since 1.16.0
      */
      */
     public function getSize();
     public function getSize();
 
 
+    /**
+     * Get the exact count of addresses contained in this IP range.
+     *
+     * @return int|numeric-string If the number of addresses exceeds PHP_INT_MAX a string containing the exact number of addresses will be returned
+     *
+     * @since 1.21.0
+     */
+    public function getExactSize();
+
     /**
     /**
      * Get the "network prefix", that is how many bits of the address are dedicated to the network portion.
      * Get the "network prefix", that is how many bits of the address are dedicated to the network portion.
      *
      *

+ 10 - 0
vendor/mlocati/ip-lib/src/Range/Single.php

@@ -237,6 +237,16 @@ class Single extends AbstractRange
         return 1;
         return 1;
     }
     }
 
 
+    /**
+     * {@inheritdoc}
+     *
+     * @see \IPLib\Range\RangeInterface::getExactSize()
+     */
+    public function getExactSize()
+    {
+        return 1;
+    }
+
     /**
     /**
      * {@inheritdoc}
      * {@inheritdoc}
      *
      *

+ 16 - 1
vendor/mlocati/ip-lib/src/Range/Subnet.php

@@ -7,6 +7,7 @@ use IPLib\Address\IPv4;
 use IPLib\Address\Type as AddressType;
 use IPLib\Address\Type as AddressType;
 use IPLib\Factory;
 use IPLib\Factory;
 use IPLib\ParseStringFlag;
 use IPLib\ParseStringFlag;
+use IPLib\Service\BinaryMath;
 
 
 /**
 /**
  * Represents an address range in subnet format (eg CIDR).
  * Represents an address range in subnet format (eg CIDR).
@@ -348,6 +349,20 @@ class Subnet extends AbstractRange
         $maxPrefix = $fromAddress::getNumberOfBits();
         $maxPrefix = $fromAddress::getNumberOfBits();
         $prefix = $this->getNetworkPrefix();
         $prefix = $this->getNetworkPrefix();
 
 
-        return pow(2, ($maxPrefix - $prefix));
+        return pow(2, $maxPrefix - $prefix);
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @see \IPLib\Range\RangeInterface::getExactSize()
+     */
+    public function getExactSize()
+    {
+        $fromAddress = $this->fromAddress;
+        $maxPrefix = $fromAddress::getNumberOfBits();
+        $prefix = $this->getNetworkPrefix();
+
+        return BinaryMath::getInstance()->pow2string($maxPrefix - $prefix);
     }
     }
 }
 }

+ 11 - 11
vendor/mlocati/ip-lib/src/Range/Type.php

@@ -120,29 +120,29 @@ class Type
             case static::T_UNSPECIFIED:
             case static::T_UNSPECIFIED:
                 return 'Unspecified/unknown address';
                 return 'Unspecified/unknown address';
             case static::T_RESERVED:
             case static::T_RESERVED:
-                 return 'Reserved/internal use only';
+                return 'Reserved/internal use only';
             case static::T_THISNETWORK:
             case static::T_THISNETWORK:
-                 return 'Refer to source hosts on "this" network';
+                return 'Refer to source hosts on "this" network';
             case static::T_LOOPBACK:
             case static::T_LOOPBACK:
-                 return 'Internet host loopback address';
+                return 'Internet host loopback address';
             case static::T_ANYCASTRELAY:
             case static::T_ANYCASTRELAY:
-                 return 'Relay anycast address';
+                return 'Relay anycast address';
             case static::T_LIMITEDBROADCAST:
             case static::T_LIMITEDBROADCAST:
-                 return '"Limited broadcast" destination address';
+                return '"Limited broadcast" destination address';
             case static::T_MULTICAST:
             case static::T_MULTICAST:
-                 return 'Multicast address assignments - Indentify a group of interfaces';
+                return 'Multicast address assignments - Indentify a group of interfaces';
             case static::T_LINKLOCAL:
             case static::T_LINKLOCAL:
-                 return '"Link local" address, allocated for communication between hosts on a single link';
+                return '"Link local" address, allocated for communication between hosts on a single link';
             case static::T_LINKLOCAL_UNICAST:
             case static::T_LINKLOCAL_UNICAST:
                 return 'Link local unicast / Linked-scoped unicast';
                 return 'Link local unicast / Linked-scoped unicast';
             case static::T_DISCARDONLY:
             case static::T_DISCARDONLY:
-                 return 'Discard only';
+                return 'Discard only';
             case static::T_DISCARD:
             case static::T_DISCARD:
-                 return 'Discard';
+                return 'Discard';
             case static::T_PRIVATENETWORK:
             case static::T_PRIVATENETWORK:
-                 return 'For use in private networks';
+                return 'For use in private networks';
             case static::T_PUBLIC:
             case static::T_PUBLIC:
-                 return 'Public address';
+                return 'Public address';
             case static::T_CGNAT:
             case static::T_CGNAT:
                 return 'Carrier-grade NAT';
                 return 'Carrier-grade NAT';
             default:
             default:

+ 112 - 0
vendor/mlocati/ip-lib/src/Service/BinaryMath.php

@@ -9,6 +9,20 @@ namespace IPLib\Service;
  */
  */
 class BinaryMath
 class BinaryMath
 {
 {
+    private static $instance;
+
+    /**
+     * @return \IPLib\Service\BinaryMath
+     */
+    public static function getInstance()
+    {
+        if (self::$instance === null) {
+            self::$instance = new self();
+        }
+
+        return self::$instance;
+    }
+
     /**
     /**
      * Trim the leading zeroes from a non-negative integer represented in binary form.
      * Trim the leading zeroes from a non-negative integer represented in binary form.
      *
      *
@@ -97,6 +111,104 @@ class BinaryMath
         return $result;
         return $result;
     }
     }
 
 
+    /**
+     * Compute 2 raised to the given exponent.
+     *
+     * If the result fits into a native PHP integer, an int is returned.
+     * If the result exceeds PHP_INT_MAX, a string containing the exact decimal representation is returned.
+     *
+     * @param int $exponent The non-negative exponent
+     *
+     * @return int|string
+     */
+    public function pow2string($exponent)
+    {
+        if ($exponent < PHP_INT_SIZE * 8 - 1) {
+            return 1 << $exponent;
+        }
+        $digits = array(1);
+        for ($i = 0; $i < $exponent; $i++) {
+            $carry = 0;
+            foreach ($digits as $index => $digit) {
+                $product = $digit * 2 + $carry;
+                $digits[$index] = $product % 10;
+                $carry = (int) ($product / 10);
+            }
+            if ($carry !== 0) {
+                $digits[] = $carry;
+            }
+        }
+
+        return implode('', array_reverse($digits));
+    }
+
+    /**
+     * @param numeric-string|mixed $value
+     *
+     * @return string empty string if $value is not a valid numeric string
+     */
+    public function normalizeIntegerString($value)
+    {
+        if (!is_string($value) || $value === '') {
+            return '';
+        }
+        $sign = $value[0];
+        if ($sign === '-' || $sign === '+') {
+            $value = substr($value, 1);
+        }
+        $matches = null;
+        if (!preg_match('/^0*([0-9]+)$/', $value, $matches)) {
+            return '';
+        }
+
+        return ($sign === '-' && $matches[1] !== '0' ? $sign : '') . $matches[1];
+    }
+
+    /**
+     * @param numeric-string $value a string that has been normalized with normalizeIntegerString()
+     *
+     * @return string
+     */
+    public function add1ToIntegerString($value)
+    {
+        if ($value[0] === '-') {
+            if ($value === '-1') {
+                return '0';
+            }
+            $digits = str_split(substr($value, 1));
+            $i = count($digits) - 1;
+            while ($i >= 0) {
+                if ($digits[$i] !== '0') {
+                    $digits[$i] = (string) ((int) $digits[$i] - 1);
+                    break;
+                }
+                $digits[$i] = '9';
+                $i--;
+            }
+            $imploded = implode('', $digits);
+            if ($imploded[0] === '0') {
+                $imploded = substr($imploded, 1);
+            }
+
+            return '-' . $imploded;
+        }
+        $digits = str_split($value);
+        $carry = 1;
+        for ($i = count($digits) - 1; $i >= 0; $i--) {
+            $sum = (int) $digits[$i] + $carry;
+            $digits[$i] = (string) ($sum % 10);
+            $carry = (int) ($sum / 10);
+            if ($carry === 0) {
+                break;
+            }
+            if ($i === 0) {
+                array_unshift($digits, (string) $carry);
+            }
+        }
+
+        return implode('', $digits);
+    }
+
     /**
     /**
      * Zero-padding of two non-negative integers represented in binary form, so that they have the same length.
      * Zero-padding of two non-negative integers represented in binary form, so that they have the same length.
      *
      *

+ 253 - 0
vendor/mlocati/ip-lib/src/Service/NumberInChunks.php

@@ -0,0 +1,253 @@
+<?php
+
+namespace IPLib\Service;
+
+use InvalidArgumentException;
+
+/**
+ * @internal
+ *
+ * @readonly
+ */
+class NumberInChunks
+{
+    const CHUNKSIZE_BYTES = 8;
+
+    const CHUNKSIZE_WORDS = 16;
+
+    /**
+     * @var bool
+     */
+    public $negative;
+
+    /**
+     * @var int[]
+     */
+    public $chunks;
+
+    /**
+     * @var int
+     */
+    public $chunkSize;
+
+    /**
+     * @param bool $negative
+     * @param int[] $chunks
+     * @param int $chunkSize
+     */
+    public function __construct($negative, array $chunks, $chunkSize)
+    {
+        $this->negative = $negative;
+        $this->chunks = $chunks;
+        $this->chunkSize = $chunkSize;
+    }
+
+    /**
+     * @throws \InvalidArgumentException if $other has a $chunkSize that's not the same as the $chunkSize of this
+     *
+     * @return \IPLib\Service\NumberInChunks
+     */
+    public function negate()
+    {
+        return new self($this->chunks === array(0) ? false : !$this->negative, $this->chunks, $this->chunkSize);
+    }
+
+    /**
+     * @throws \InvalidArgumentException if $other has a $chunkSize that's not the same as the $chunkSize of this
+     *
+     * @return \IPLib\Service\NumberInChunks
+     */
+    public function add(NumberInChunks $that)
+    {
+        if ($this->chunkSize !== $that->chunkSize) {
+            throw new InvalidArgumentException('Incompatible chunk size');
+        }
+        if ($this->negative === $that->negative) {
+            return new self($this->negative, self::addChunks($this->chunks, $that->chunks, $this->chunkSize), $this->chunkSize);
+        }
+        if ($that->negative) {
+            list($negative, $chunks) = self::substractChunks($this->chunks, $that->chunks, $this->chunkSize);
+        } else {
+            list($negative, $chunks) = self::substractChunks($that->chunks, $this->chunks, $this->chunkSize);
+        }
+
+        return new self($negative, $chunks, $this->chunkSize);
+    }
+
+    /**
+     * @param int $int
+     * @param int $chunkSize
+     *
+     * @return \IPLib\Service\NumberInChunks
+     */
+    public static function fromInteger($int, $chunkSize)
+    {
+        if ($int === 0) {
+            return new self(false, array(0), $chunkSize);
+        }
+        $negative = $int < 0;
+        if ($negative) {
+            $positiveInt = -$int;
+            if (is_float($positiveInt)) { // -PHP_INT_MIN is bigger than PHP_INT_MAX
+                return self::fromNumericString((string) $int, $chunkSize);
+            }
+            $int = $positiveInt;
+        }
+        $bitMask = (1 << $chunkSize) - 1;
+        $chunks = array();
+        while ($int !== 0) {
+            $chunks[] = $int & $bitMask;
+            $int >>= $chunkSize;
+        }
+
+        return new self($negative, array_reverse($chunks), $chunkSize);
+    }
+
+    /**
+     * @param string $numericString a string normalized with BinaryMath::normalizeIntegerString()
+     * @param int $chunkSize
+     *
+     * @return \IPLib\Service\NumberInChunks
+     */
+    public static function fromNumericString($numericString, $chunkSize)
+    {
+        if ($numericString === '0') {
+            return new self(false, array(0), $chunkSize);
+        }
+        $negative = $numericString[0] === '-';
+        if ($negative) {
+            $numericString = substr($numericString, 1);
+        }
+        $chunks = array();
+        while ($numericString !== '0') {
+            $chunks[] = self::modulo($numericString, $chunkSize);
+            $numericString = self::divide($numericString, $chunkSize);
+        }
+
+        return new self($negative, array_reverse($chunks), $chunkSize);
+    }
+
+    /**
+     * @param string $numericString
+     * @param int $chunkSize
+     *
+     * @return int
+     */
+    private static function modulo($numericString, $chunkSize)
+    {
+        $divisor = 1 << $chunkSize;
+        $carry = 0;
+        $len = strlen($numericString);
+        for ($i = 0; $i < $len; $i++) {
+            $digit = (int) $numericString[$i];
+            $carry = ($carry * 10 + $digit) % $divisor;
+        }
+
+        return $carry;
+    }
+
+    /**
+     * @param string $numericString
+     * @param int $chunkSize
+     *
+     * @return string
+     */
+    private static function divide($numericString, $chunkSize)
+    {
+        $divisor = 1 << $chunkSize;
+        $quotient = '';
+        $carry = 0;
+        $len = strlen($numericString);
+        for ($i = 0; $i < $len; $i++) {
+            $digit = (int) $numericString[$i];
+            $value = $carry * 10 + $digit;
+            $quotient .= (string) ($value >> $chunkSize);
+            $carry = $value % $divisor;
+        }
+
+        return ltrim($quotient, '0') ?: '0';
+    }
+
+    /**
+     * @param int[] $addend1
+     * @param int[] $addend2
+     * @param int $chunkSize
+     *
+     * @return int[]
+     */
+    private static function addChunks(array $addend1, array $addend2, $chunkSize)
+    {
+        $divisor = 1 << $chunkSize;
+        $result = array();
+        $carry = 0;
+        while ($addend1 !== array() || $addend2 !== array()) {
+            $sum = $carry + (array_pop($addend1) ?: 0) + (array_pop($addend2) ?: 0);
+            $result[] = $sum % $divisor;
+            $carry = $sum >> $chunkSize;
+        }
+        if ($carry !== 0) {
+            $result[] = $carry;
+        }
+
+        return array_reverse($result);
+    }
+
+    /**
+     * @param int[] $minuend
+     * @param int[] $subtrahend
+     * @param int $chunkSize
+     *
+     * @return array
+     */
+    private static function substractChunks(array $minuend, array $subtrahend, $chunkSize)
+    {
+        $minuendCount = count($minuend);
+        $subtrahendCount = count($subtrahend);
+        if ($minuendCount > $subtrahendCount) {
+            $count = $minuendCount;
+            $negative = false;
+        } elseif ($minuendCount < $subtrahendCount) {
+            $count = $subtrahendCount;
+            $negative = true;
+        } else {
+            $count = $minuendCount;
+            $negative = false;
+            for ($i = 0; $i < $count; $i++) {
+                $delta = $minuend[$i] - $subtrahend[$i];
+                if ($delta === 0) {
+                    continue;
+                }
+                if ($delta < 0) {
+                    $negative = true;
+                }
+                break;
+            }
+        }
+        if ($negative) {
+            list($minuend, $subtrahend) = array($subtrahend, $minuend);
+        }
+        $subtrahend = array_pad($subtrahend, -$count, 0);
+        $borrowValue = 1 << $chunkSize;
+        $result = array();
+        $borrow = 0;
+        for ($i = $count - 1; $i >= 0; $i--) {
+            $value = $minuend[$i] - $subtrahend[$i] - $borrow;
+            if ($value < 0) {
+                $value += $borrowValue;
+                $borrow = 1;
+            } else {
+                $borrow = 0;
+            }
+            $result[] = $value;
+        }
+        while (isset($result[1])) {
+            $value = array_pop($result);
+            if ($value !== 0) {
+                $result[] = $value;
+                break;
+            }
+        }
+
+        return array($negative, array_reverse($result));
+    }
+}

+ 1 - 1
vendor/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php

@@ -50,7 +50,7 @@ class RangesFromBoundaryCalculator
      */
      */
     public function __construct($numBits)
     public function __construct($numBits)
     {
     {
-        $this->math = new BinaryMath();
+        $this->math = BinaryMath::getInstance();
         $this->setNumBits($numBits);
         $this->setNumBits($numBits);
     }
     }
 
 

Некоторые файлы не были показаны из-за большого количества измененных файлов