Compare commits

...

48 commits

Author SHA1 Message Date
El RIDO
a490390d60
incrementing version 2025-11-13 11:10:14 +01:00
El RIDO
0618a9dd74
Merge pull request #1711 from PrivateBin/1.7-backport-2.0.3
backport security fixes for 1.7
2025-11-13 10:59:12 +01:00
Ribas160
c116d30ada
Fix configuration combinations test errors 2025-11-12 11:56:50 +01:00
El RIDO
4563422080
document the changes 2025-11-12 11:44:06 +01:00
El RIDO
777e0e8570
apply StyleCI suggestion 2025-11-12 11:40:11 +01:00
El RIDO
7b1c3ffd40
remove dead code 2025-11-12 11:38:42 +01:00
El RIDO
5da187a496
use more straight forward in_array check
kudos @Ribas160 for the suggestion
2025-11-12 11:38:33 +01:00
El RIDO
125f57c5b4
ensure template cookie cannot be a path 2025-11-12 11:38:20 +01:00
El RIDO
fffa9fb4e9
remove dead code 2025-11-12 11:38:05 +01:00
El RIDO
a1a50ee3a5
do add the configured template to the available ones, if missing 2025-11-12 11:37:57 +01:00
El RIDO
194385e692
don't always set the cookie, having to unset it later
but still unset it, if it currently should not be in use (templateselection = false)
2025-11-12 11:37:48 +01:00
El RIDO
da9e85ecde
simplify logic and improve readability
function was only used in one place and only indirectly tested, so it could be inlined, which also makes the test for null and the extra variable allocation unnecessary
2025-11-12 11:37:38 +01:00
El RIDO
83b5d1fbba
use realpath and validate tpl directory contents
to ensure only php files inside the tpl dir can get used as templates
2025-11-12 11:37:29 +01:00
El RIDO
db251732d2
partially revert #1559
Instead of automatically adding custom templates, we log an error if
that template is missing in the available templates. Still mitigates
arbitrary file inclusion, as the string is now checked against a fixed
allow list.
2025-11-12 11:37:08 +01:00
El RIDO
d1124382bc
belt and braces: reset the template cookie, if function is not enabled 2025-11-12 11:36:07 +01:00
El RIDO
4ac8ffa2a4
prevent use of paths in template names, only file names inside tpl directory are allowed 2025-11-12 11:35:56 +01:00
El RIDO
fd6ba6595f
improve readability of logic 2025-11-12 11:35:47 +01:00
El RIDO
530f360497
make OPcache optional, resolves #1678 2025-11-12 11:34:03 +01:00
El RIDO
ad983ef670
ensure PHP opcache gets invalidated, when storing data in file parsed via PHP require 2025-11-12 11:32:10 +01:00
Mikhail Romanov
8c4b3bb114
Insert file names as break-separated text nodes
Co-authored-by: El RIDO <elrido@gmx.net>
2025-11-12 11:27:44 +01:00
Ribas160
88fd86b994
Use pure JavaScript to create a div element 2025-11-12 11:26:49 +01:00
Ribas160
b14da334f4
Insert drag and drop file names as a text, not html 2025-11-12 11:26:01 +01:00
Ribas160
d03ec380d1
fix: error fetching attachments from blob 2025-11-12 11:24:21 +01:00
El RIDO
41dcdbc41d
ensure there is still a space between commenter icon and name 2025-11-12 11:21:45 +01:00
El RIDO
68972322d9
Refactored jQuery DOM element creation
using plain JavaScript, to ensure text nodes are sanitized
2025-11-12 11:20:32 +01:00
El RIDO
1f5ed30a63
update DOMpurify library from 3.2.7 to 3.3.0 2025-11-12 11:17:51 +01:00
El RIDO
dc3bc8b23d
suppress noise from early initialization during unit tests
the tests still all passed, but the missing browser globals in the node environment could cause misleading messages in the mocha output
2025-11-12 11:11:24 +01:00
rugk
55472df906
Make sure legacy check returns true only on HTTPS (not like ftp or whatever)
I am not sure why it was expressed so convoluted before?

Found that in https://github.com/orgs/PrivateBin/discussions/1657
2025-11-12 11:07:17 +01:00
El RIDO
e3ec9dc963
upgrade kjua to 0.10.0 2025-11-12 11:07:03 +01:00
El RIDO
c7c0420d63
upgrade base-x to 5.0.1 2025-11-12 11:03:12 +01:00
Cél
f35d883a18
Fixed a Typo in Running Unit Tests.md #HSFDPMUW
Fixed a typo for a command. 
I need to add this hashtag at the end because I am contributing in a project at my university.
2025-11-12 10:54:54 +01:00
rugk
61b2783634
Fix links in doc/README.md
* Fixing the last link, which was totally broken
* Updated links in README to use relative paths.
2025-11-12 10:54:30 +01:00
El RIDO
3e3ee8abc5
update bootstrap CSS library from 5.3.7 to 5.3.8 2025-11-12 10:52:07 +01:00
El RIDO
eb72844588
update ip-lib library from 1.20.0 to 1.21.0 2025-11-12 10:43:29 +01:00
El RIDO
eb203e2d25
remove broken & obsolete badges 2025-11-12 10:36:48 +01:00
El RIDO
f622a04425
enable xdebug for coverage in scrutinizer 2025-11-12 10:36:30 +01:00
El RIDO
f55d027baf
attempt to upgrade to PHP 8.2 in scrutinizer 2025-11-12 10:36:30 +01:00
El RIDO
cf039f1d71
attempt to upgrade to PHP 8.3 in scrutinizer 2025-11-12 10:36:30 +01:00
El RIDO
8f55715749
attempt to upgrade to PHP 8.4 in scrutinizer 2025-11-12 10:36:30 +01:00
rugk
c6bccdbfe1
chore: always ignore composer PHP bin dir 2025-11-12 10:35:42 +01:00
dependabot[bot]
c2341032a4
Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-12 10:35:18 +01:00
dependabot[bot]
ec82920a93
Bump actions/setup-node from 5 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-12 10:35:05 +01:00
El RIDO
2894ac430a
unify workflow code styles 2025-11-12 10:28:56 +01:00
El RIDO
aea562a1b4
attempting to make the condition list more readable 2025-11-12 10:27:26 +01:00
El RIDO
86d39434a3
disable running snyk if triggering user doesn't have access to the secret 2025-11-12 10:27:15 +01:00
El RIDO
7eec8caae3
apply explicit permissions as per CodeQL suggestion
as per rule ID actions/missing-workflow-permissions
2025-11-12 10:24:57 +01:00
El RIDO
bab4d50cd4
update codeql actions to release 4 (node 24) and enable github action scanning 2025-11-12 10:24:36 +01:00
dependabot[bot]
d4ebb12828
Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-12 10:23:56 +01:00
62 changed files with 1032 additions and 455 deletions

View file

@ -18,6 +18,10 @@ on:
schedule:
- cron: '28 22 * * 5'
permissions:
contents: read
security-events: write
jobs:
analyze:
name: Analyze
@ -26,18 +30,18 @@ jobs:
strategy:
fail-fast: false
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:
# 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:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# 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
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4

View file

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

View file

@ -8,13 +8,23 @@ on:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read
security-events: write
jobs:
# https://github.com/snyk/actions/tree/master/php
snyk-php:
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:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Google Cloud Storage
run: composer require --no-update google/cloud-storage && composer update --no-dev
- name: Run Snyk to check for vulnerabilities
@ -25,6 +35,6 @@ jobs:
with:
args: --sarif-file-output=snyk.sarif
- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: snyk.sarif

View file

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

View file

@ -1,15 +1,18 @@
name: Tests
on:
push:
workflow_dispatch:
permissions: {}
jobs:
Composer:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Validate composer.json and composer.lock
run: composer validate
- name: Install dependencies
@ -36,7 +39,7 @@ jobs:
# let's get started!
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
# cache PHP extensions
- name: Setup cache environment
@ -106,20 +109,111 @@ jobs:
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Test Results (PHP ${{ matrix.php-versions }})
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:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '18'
cache: 'npm'
@ -138,7 +232,7 @@ jobs:
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Test Results (Mocha)
path: js/mocha-results.xml
@ -148,7 +242,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Event File
path: "${{ github.event_path }}"

1
.gitignore vendored
View file

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

View file

@ -15,14 +15,12 @@ coding_style:
additive: false
concatenation: true
build:
image: default-bionic
environment:
php:
version: 7.4
version: 8.2
tests:
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:
file: 'tst/log/coverage-clover.xml'
format: 'clover'

View file

@ -1,11 +1,7 @@
# 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)
[![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/)

View file

@ -1,5 +1,15 @@
# PrivateBin version history
## 1.7.9 (2025-11-13)
* 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)
* FIXED: Duplicate attachment for every comment (#1577)
* FIXED: Attachments with empty file names (#1577)

View file

@ -1,6 +1,6 @@
.PHONY: all coverage coverage-js coverage-php doc doc-js doc-php increment sign test test-js test-php help
CURRENT_VERSION = 1.7.8
CURRENT_VERSION = 1.7.9
VERSION ?= 2.0.0
VERSION_FILES = README.md SECURITY.md doc/Installation.md js/package*.json lib/Controller.php Makefile
REGEX_CURRENT_VERSION := $(shell echo $(CURRENT_VERSION) | sed "s/\./\\\./g")

View file

@ -1,6 +1,6 @@
# [![PrivateBin](https://cdn.rawgit.com/PrivateBin/assets/master/images/preview/logoSmall.png)](https://privatebin.info/)
*Current version: 1.7.8*
*Current version: 1.7.9*
**PrivateBin** is a minimalist, open source online
[pastebin](https://en.wikipedia.org/wiki/Pastebin)

View file

@ -4,8 +4,8 @@
| Version | Supported |
| ------- | ------------------ |
| 1.7.8 | :heavy_check_mark: |
| < 1.7.8 | :x: |
| 1.7.9 | :heavy_check_mark: |
| < 1.7.9 | :x: |
## Reporting a Vulnerability

View file

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

View file

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

View file

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

152
composer.lock generated
View file

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

File diff suppressed because one or more lines are too long

5
css/bootstrap5/bootstrap-5.3.8.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -22,12 +22,20 @@ for more information.
### Minimal Requirements
- 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
@ -200,7 +208,7 @@ CREATE INDEX parent ON prefix_comment(pasteid);
CREATE TABLE prefix_config (
id CHAR(16) NOT NULL, value TEXT, PRIMARY KEY (id)
);
INSERT INTO prefix_config VALUES('VERSION', '1.7.8');
INSERT INTO prefix_config VALUES('VERSION', '1.7.9');
```
In **PostgreSQL**, the `data`, `attachment`, `nickname` and `vizhash` columns

View file

@ -4,7 +4,7 @@
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
configuration.
@ -26,12 +26,12 @@ How to help translate PrivateBin and technical background on it's implementation
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
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
based unit testing.

View file

@ -18,7 +18,7 @@ The parameters in detail:
an accidentally destructive test case in it.
- `--read-only` - This image supports running in read-only mode. Only /tmp
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.
You can also run just the php and javascript test suites instead of both:

View file

@ -7,47 +7,47 @@
(function(){
this.baseX = function base (ALPHABET) {
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
}
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') }
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) {
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)
} else if (Array.isArray(source)) {
source = Uint8Array.from(source)
}
if (!(source instanceof Uint8Array)) { throw new TypeError('Expected Uint8Array') }
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) {
pbegin++
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) {
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
b58[it1] = (carry % BASE) >>> 0
carry = (carry / BASE) >>> 0
@ -56,38 +56,42 @@ this.baseX = function base (ALPHABET) {
length = i
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) {
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]) }
return str
}
function decodeUnsafe (source) {
if (typeof source !== 'string') { throw new TypeError('Expected String') }
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) {
zeroes++
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 }
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
b256[it3] = (carry % 256) >>> 0
carry = (carry / 256) >>> 0
@ -96,27 +100,27 @@ this.baseX = function base (ALPHABET) {
length = i
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) {
it4++
}
var vch = new Uint8Array(zeroes + (size - it4))
var j = zeroes
const vch = new Uint8Array(zeroes + (size - it4))
let j = zeroes
while (it4 !== size) {
vch[j++] = b256[it4++]
}
return vch
}
function decode (string) {
var buffer = decodeUnsafe(string)
const buffer = decodeUnsafe(string)
if (buffer) { return buffer }
throw new Error('Non-base' + BASE + ' character')
}
return {
encode: encode,
decodeUnsafe: decodeUnsafe,
decode: decode
encode,
decodeUnsafe,
decode
}
}
}).call(this);

File diff suppressed because one or more lines are too long

6
js/bootstrap-5.3.8.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -16,8 +16,8 @@ require('./prettify');
global.prettyPrint = window.PR.prettyPrint;
global.prettyPrintOne = window.PR.prettyPrintOne;
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;
require('./bootstrap-3.4.1');
require('./privatebin');

2
js/kjua-0.10.0.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -106,8 +106,8 @@
return window.isSecureContext;
}
// HTTP is obviously insecure
if (window.location.protocol !== 'http:') {
// HTTPS is considered secure
if (window.location.protocol === 'https:') {
return true;
}
@ -246,6 +246,11 @@
*/
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
// when burn-after-reading is set
if (isBadBot()) {

4
js/package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "privatebin",
"version": "1.7.8",
"version": "1.7.9",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "privatebin",
"version": "1.7.8",
"version": "1.7.9",
"license": "zlib-acknowledgement",
"devDependencies": {
"@peculiar/webcrypto": "^1.5.0",

View file

@ -1,6 +1,6 @@
{
"name": "privatebin",
"version": "1.7.8",
"version": "1.7.9",
"description": "PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted in the browser using 256 bit AES in Galois Counter mode (GCM).",
"main": "privatebin.js",
"directories": {

View file

@ -10,7 +10,7 @@
* @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() {
'use strict';
@ -975,7 +975,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*
* @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
@ -3028,7 +3028,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
attachmentLink.attr('download', fileName);
const fileSize = Helper.formatBytes(decodedData.length);
template.append(`(${fileName}, ${fileSize})`);
const fileInfo = document.createTextNode(` (${fileName}, ${fileSize})`);
template[0].appendChild(fileInfo);
}
// sanitize SVG preview
@ -3121,10 +3122,15 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @name AttachmentViewer.printDragAndDropFileNames
* @private
* @function
* @param {array} fileNames
* @param {string[]} 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;
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
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) {
$commentEntry.find('span.nickname').text(nickname);
} 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
@ -3652,14 +3654,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// if an avatar is available, display it
const icon = comment.getIcon();
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)
@ -5268,22 +5266,23 @@ jQuery.PrivateBin = (function($, RawDeflate) {
cipherMessage['attachment'] = attachments.map(attachment => attachment[0]);
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
if (typeof attachment === 'string' && attachment.startsWith('blob:')) {
Alert.showStatus(
[
'Retrieving cloned file \'%s\' from memory...',
attachment[1]
cipherMessage['attachment_name'][i]
],
'copy'
);
try {
const blobData = await $.ajax({
type: 'GET',
url: `${attachment}`,
url: attachment,
processData: false,
timeout: 10000,
dataType: 'binary',
xhrFields: {
withCredentials: false,
responseType: 'blob'
@ -5493,6 +5492,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
plaintexts[i][1]
);
}
$(document).on('languageLoaded', function () {
$('#commentcontainer').find('img.vizhash')
.prop('title', I18n._('Avatar generated from IP address'));
});
});
}

File diff suppressed because one or more lines are too long

2
js/purify-3.3.0.js Normal file

File diff suppressed because one or more lines are too long

View file

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

View file

@ -110,17 +110,17 @@ class Configuration
),
// update this array when adding/changing/removing js files
'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/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/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/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/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
'js/zlib-1.3.1-1.js' => 'sha512-5bU9IIP4PgBrOKLZvGWJD4kgfQrkTz8Z3Iqeu058mbQzW3mCumOU6M3UVbVZU9rrVoVwaW4cZK8U8h5xjF88eQ==',

View file

@ -27,7 +27,7 @@ class Controller
*
* @const string
*/
const VERSION = '1.7.8';
const VERSION = '1.7.9';
/**
* minimal required PHP version
@ -210,13 +210,17 @@ class Controller
{
$templates = $this->_conf->getKey('availabletemplates');
$template = $this->_conf->getKey('template');
if (!in_array($template, $templates, true)) {
$templates[] = $template;
}
TemplateSwitcher::setAvailableTemplates($templates);
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));
}
}

View file

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

View file

@ -204,16 +204,10 @@ class TrafficLimiter extends AbstractPersistence
$now = time();
$tl = (int) self::$_store->getValue('traffic_limiter', $hash);
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;
}
throw new Exception(I18n::_(

View file

@ -59,16 +59,13 @@ class TemplateSwitcher
{
if (self::isTemplateAvailable($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
* @static
@ -76,8 +73,13 @@ class TemplateSwitcher
*/
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
{
$available = in_array($template, self::getAvailableTemplates());
if (!$available && !View::isBootstrapTemplate($template)) {
$path = View::getTemplateFilePath($template);
$available = file_exists($path);
if (in_array($template, self::getAvailableTemplates(), true)) {
return true;
}
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;
}
return $selectedTemplate;
error_log('template "' . $template . '" is not in the list of `availabletemplates` in the configuration file');
return false;
}
}

View file

@ -49,39 +49,19 @@ class View
*/
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);
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
*

View file

@ -46,7 +46,7 @@ endif;
<?php
if ($QRCODE) :
?>
<?php $this->_scriptTag('js/kjua-0.9.0.js', 'async'); ?>
<?php $this->_scriptTag('js/kjua-0.10.0.js', 'async'); ?>
<?php
endif;
if ($ZEROBINCOMPATIBILITY) :
@ -56,7 +56,7 @@ if ($ZEROBINCOMPATIBILITY) :
endif;
?>
<?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/bootstrap-3.4.1.js', 'defer'); ?>
<?php
@ -71,8 +71,8 @@ if ($MARKDOWN) :
<?php
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'); ?>
<!-- icon -->
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />

View file

@ -10,7 +10,7 @@ use PrivateBin\I18n;
<meta name="robots" content="noindex" />
<meta name="google" content="notranslate">
<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); ?>" />
<?php
if ($SYNTAXHIGHLIGHTING) :
@ -29,7 +29,7 @@ endif;
<?php
if ($QRCODE) :
?>
<?php $this->_scriptTag('js/kjua-0.9.0.js', 'async'); ?>
<?php $this->_scriptTag('js/kjua-0.10.0.js', 'async'); ?>
<?php
endif;
if ($ZEROBINCOMPATIBILITY) :
@ -39,9 +39,9 @@ if ($ZEROBINCOMPATIBILITY) :
endif;
?>
<?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/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
if ($SYNTAXHIGHLIGHTING) :
@ -55,8 +55,8 @@ if ($MARKDOWN) :
<?php
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'); ?>
<!-- icon -->
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />

View file

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

View file

@ -143,19 +143,11 @@ class ViewTest extends TestCase
$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');
}
}

View file

@ -45,35 +45,34 @@ class ClassLoader
/** @var \Closure(string):void */
private static $includeFile;
/** @var ?string */
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// 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();
/**
* @var array[]
* @psalm-var array<string, string>
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
@ -81,8 +80,7 @@ class ClassLoader
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
* @var array<string, string>
*/
private $classMap = array();
@ -90,21 +88,20 @@ class ClassLoader
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
/** @var string|null */
private $apcuPrefix;
/**
* @var self[]
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
@ -113,7 +110,7 @@ class ClassLoader
}
/**
* @return string[]
* @return array<string, list<string>>
*/
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()
{
@ -134,8 +130,7 @@ class ClassLoader
}
/**
* @return array[]
* @psalm-return array<string, string>
* @return list<string>
*/
public function getFallbackDirs()
{
@ -143,8 +138,7 @@ class ClassLoader
}
/**
* @return array[]
* @psalm-return array<string, string>
* @return list<string>
*/
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()
{
@ -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
*/
@ -179,24 +171,25 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given prefix, either
* 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
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
$paths
);
}
@ -205,19 +198,19 @@ class ClassLoader
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$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
* 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
*
@ -236,17 +229,18 @@ class ClassLoader
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
$paths
);
}
} 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.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
$paths
);
}
}
@ -276,8 +270,8 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given 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
*/
@ -294,8 +288,8 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given 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
*
@ -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()
{

View file

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

View file

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

View file

@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'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',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -20,18 +20,18 @@
'dev_requirement' => false,
),
'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',
'install_path' => __DIR__ . '/../mlocati/ip-lib',
'aliases' => array(),
'dev_requirement' => false,
),
'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',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

View file

@ -110,11 +110,12 @@ interface AddressInterface
/**
* 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.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.0

View file

@ -6,6 +6,8 @@ use IPLib\ParseStringFlag;
use IPLib\Range\RangeInterface;
use IPLib\Range\Subnet;
use IPLib\Range\Type as RangeType;
use IPLib\Service\BinaryMath;
use IPLib\Service\NumberInChunks;
/**
* An IPv4 address.
@ -456,28 +458,24 @@ class IPv4 implements AddressInterface
*/
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;
}
$myBytes = $this->getBytes();
while (isset($myBytes[1]) && $myBytes[0] === 0) {
array_shift($myBytes);
}
$myChunks = new NumberInChunks(false, $myBytes, NumberInChunks::CHUNKSIZE_BYTES);
$result = $myChunks->add($thatChunks);
if ($result->negative || count($result->chunks) > 4) {
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;
}
if ($mod !== 0) {
return null;
}
return static::fromBytes($bytes);
return static::fromBytes(array_pad($result->chunks, -4, 0));
}
/**

View file

@ -6,6 +6,8 @@ use IPLib\ParseStringFlag;
use IPLib\Range\RangeInterface;
use IPLib\Range\Subnet;
use IPLib\Range\Type as RangeType;
use IPLib\Service\BinaryMath;
use IPLib\Service\NumberInChunks;
/**
* An IPv6 address.
@ -549,28 +551,24 @@ class IPv6 implements AddressInterface
*/
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;
}
$myWords = $this->getWords();
while (isset($myWords[1]) && $myWords[0] === 0) {
array_shift($myWords);
}
$myChunks = new NumberInChunks(false, $myWords, NumberInChunks::CHUNKSIZE_WORDS);
$result = $myChunks->add($thatChunks);
if ($result->negative || count($result->chunks) > 8) {
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;
}
if ($mod !== 0) {
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();
$otherWords = $other->getWords();
$sum = array_fill(0, 7, 0);
$sum = array_fill(0, 8, 0);
$carry = 0;
for ($index = 7; $index >= 0; $index--) {
$word = $myWords[$index] + $otherWords[$index] + $carry;

View file

@ -7,6 +7,7 @@ use IPLib\Address\IPv4;
use IPLib\Address\IPv6;
use IPLib\Address\Type as AddressType;
use IPLib\Factory;
use IPLib\Service\BinaryMath;
use OutOfBoundsException;
/**
@ -59,17 +60,26 @@ abstract class AbstractRange implements RangeInterface
*/
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;
}
$address = null;
if ($n >= 0) {
if ($positive) {
$start = Factory::parseAddressString($this->getComparableStartString());
$address = $start->getAddressAtOffset($n);
} else {
$end = Factory::parseAddressString($this->getComparableEndString());
$address = $end->getAddressAtOffset($n + 1);
$address = $end->getAddressAtOffset($nPlus1);
}
if ($address === null) {

View file

@ -7,6 +7,7 @@ use IPLib\Address\IPv4;
use IPLib\Address\IPv6;
use IPLib\Address\Type as AddressType;
use IPLib\ParseStringFlag;
use IPLib\Service\BinaryMath;
/**
* Represents an address range in pattern format (only ending asterisks are supported).
@ -304,7 +305,21 @@ class Pattern extends AbstractRange
$maxPrefix = $fromAddress::getNumberOfBits();
$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);
}
/**

View file

@ -46,11 +46,12 @@ interface RangeInterface
/**
* 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.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 -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();
/**
* 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
*/
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.
*

View file

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

View file

@ -7,6 +7,7 @@ use IPLib\Address\IPv4;
use IPLib\Address\Type as AddressType;
use IPLib\Factory;
use IPLib\ParseStringFlag;
use IPLib\Service\BinaryMath;
/**
* Represents an address range in subnet format (eg CIDR).
@ -348,6 +349,20 @@ class Subnet extends AbstractRange
$maxPrefix = $fromAddress::getNumberOfBits();
$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);
}
}

View file

@ -120,29 +120,29 @@ class Type
case static::T_UNSPECIFIED:
return 'Unspecified/unknown address';
case static::T_RESERVED:
return 'Reserved/internal use only';
return 'Reserved/internal use only';
case static::T_THISNETWORK:
return 'Refer to source hosts on "this" network';
return 'Refer to source hosts on "this" network';
case static::T_LOOPBACK:
return 'Internet host loopback address';
return 'Internet host loopback address';
case static::T_ANYCASTRELAY:
return 'Relay anycast address';
return 'Relay anycast address';
case static::T_LIMITEDBROADCAST:
return '"Limited broadcast" destination address';
return '"Limited broadcast" destination address';
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:
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:
return 'Link local unicast / Linked-scoped unicast';
case static::T_DISCARDONLY:
return 'Discard only';
return 'Discard only';
case static::T_DISCARD:
return 'Discard';
return 'Discard';
case static::T_PRIVATENETWORK:
return 'For use in private networks';
return 'For use in private networks';
case static::T_PUBLIC:
return 'Public address';
return 'Public address';
case static::T_CGNAT:
return 'Carrier-grade NAT';
default:

View file

@ -9,6 +9,20 @@ namespace IPLib\Service;
*/
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.
*
@ -97,6 +111,104 @@ class BinaryMath
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.
*

View file

@ -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));
}
}

View file

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