Compare commits
206 commits
libre-serv
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
3666db3b9a | ||
|
5ba8266eb8 | ||
|
86c5dc9db9 | ||
|
af852927a9 | ||
|
0fb104b102 | ||
|
f6421c9c7c | ||
|
2d1552a345 | ||
|
8085604385 | ||
|
31cdfc7be6 | ||
|
307443dac3 | ||
|
c5fd6028b5 | ||
|
c7cd450f9b | ||
|
a988be7431 | ||
|
981304848f | ||
|
51a590c3c7 | ||
|
f4e68fcc04 | ||
|
f43a41c117 | ||
|
ab11fbeb47 | ||
|
5f4fe52eab | ||
|
b80732f8e2 | ||
|
a372ee92e9 | ||
|
e2ae0da4e1 | ||
|
3f7bceb862 | ||
|
507a10adc5 | ||
|
a8f7840d25 | ||
|
3ba6483bf3 | ||
|
fba8384ac3 | ||
|
def58480b3 | ||
|
cb5ba022ba | ||
|
c5c3a0e743 | ||
|
3c068cd6c3 | ||
|
82f1431440 | ||
|
df2f5931cd | ||
|
ff3b668958 | ||
|
eb10d4d35e | ||
|
18972ae0fa | ||
|
e7fb9ac54c | ||
|
08816a1d71 | ||
|
ea663f7491 | ||
|
5f28acf629 | ||
|
5f2daa5cd6 | ||
|
1fd998f325 | ||
|
9c09018e6e | ||
|
be164bb6a9 | ||
|
fd08d991fe | ||
|
3d9ba10fcb | ||
|
ae1e4e3edb | ||
|
af54e70359 | ||
|
3327645fd4 | ||
|
b72994f2e0 | ||
|
b4c75b541b | ||
|
9357f122b7 | ||
|
d0248d55d3 | ||
|
078c5785dd | ||
|
68b097087d | ||
|
fa4fe2852d | ||
|
bbcf57de0e | ||
|
f04043a399 | ||
|
1f2dddd9d8 | ||
|
93135e0abf | ||
|
e294145a2b | ||
|
cc6fb1c0c3 | ||
|
1b88eef356 | ||
|
5af069b4f0 | ||
|
1232717334 | ||
|
7b2f0ff302 | ||
|
a203e6322b | ||
|
7901ec74a7 | ||
|
b5a6ce323e | ||
|
3429d293d3 | ||
|
ae486d651b | ||
|
f46221e7c3 | ||
|
55efc858b5 | ||
|
7bdcc2ae15 | ||
|
1a7d0799c0 | ||
|
de8f40ac1a | ||
|
c758eca0a4 | ||
|
9beb176874 | ||
|
9b19a88bb9 | ||
|
d699c41e26 | ||
|
2bc54caa07 | ||
|
a2ffbafa13 | ||
|
197c4a34e8 | ||
|
7a3a306ddc | ||
|
cbdcaf4c30 | ||
|
f635881bd4 | ||
|
371dca1986 | ||
|
7311e9ce12 | ||
|
abb2b90e9b | ||
|
edb8e5e078 | ||
|
ffe48092fe | ||
|
8bc97517fb | ||
|
3df6b62d22 | ||
|
93138cbbae | ||
|
fc5e380ccc | ||
|
33587d54e4 | ||
|
d355bb87e3 | ||
|
52b9b257c3 | ||
|
b939b64778 | ||
|
de4bada695 | ||
|
5c5ae967a8 | ||
|
342270d6dd | ||
|
dae093bbfa | ||
|
7de12d64d5 | ||
|
b6460616ba | ||
|
7a1de52e05 | ||
|
91c8f9f23c | ||
|
84771d7167 | ||
|
3dd01b1f70 | ||
|
89f6f0051d | ||
|
194b27e685 | ||
|
e32e3979a8 | ||
|
af5a14afc3 | ||
|
5812a6bb68 | ||
|
f339c0b1c0 | ||
|
502bb5fa15 | ||
|
89bdc92451 | ||
|
63d6816c7c | ||
|
e572e9c79c | ||
|
2b0ebdb6c7 | ||
|
3a06d5a745 | ||
|
12aa325494 | ||
|
a806a6455e | ||
|
4296b43832 | ||
|
c3ad4a4b4d | ||
|
805eb288d9 | ||
|
b21efd8336 | ||
|
7d82c82fd9 | ||
|
3f92d4c038 | ||
|
377d7d565b | ||
|
e6def62581 | ||
|
17c1284ccf | ||
|
0a9cd05453 | ||
|
2b8534d49e | ||
|
472bf520d8 | ||
|
4d3a2ae946 | ||
|
4c329be95f | ||
|
b47b8cf050 | ||
|
1b8351fef9 | ||
|
d0c6ab224f | ||
|
53e23b7422 | ||
|
010f9db274 | ||
|
c2c0980c57 | ||
|
fcb6422663 | ||
|
993abd746e | ||
|
30228cc33c | ||
|
14ff704b28 | ||
|
cd1b0e0a50 | ||
|
4a73afa057 | ||
|
63d20330b4 | ||
|
982a4f957c | ||
|
67fd327df4 | ||
|
db0db4ebff | ||
|
4514f1f3a4 | ||
|
926fab30e9 | ||
|
492cdc9926 | ||
|
6b5e7c1b49 | ||
|
2bc7e8e38f | ||
|
48916d5df7 | ||
|
0887f567ab | ||
|
3e4def2069 | ||
|
39867d8151 | ||
|
c7a86ebd5c | ||
|
56d993ca82 | ||
|
45b3ec4ac6 | ||
|
9bd04c55c9 | ||
|
dd4633ff8f | ||
|
c0207d00a2 | ||
|
bd83415c82 | ||
|
478f806e9c | ||
|
db402baa14 | ||
|
dac5bd1d93 | ||
|
4b2f2920a2 | ||
|
83620d7eb5 | ||
|
de4abad748 | ||
|
3ca01024fd | ||
|
5809a7cfa7 | ||
|
0e78534e48 | ||
|
b68ae363ec | ||
|
3181cfe58a | ||
|
bc11452259 | ||
|
853a4f386f | ||
|
9683c591bb | ||
|
47029fb04e | ||
|
735a77b783 | ||
|
5f4200c721 | ||
|
9b893f09d7 | ||
|
3b9b6c948f | ||
|
7b7a32c0a7 | ||
|
fd7d05e862 | ||
|
8232dce395 | ||
|
6f3bb25b09 | ||
|
1dc8b24665 | ||
|
ed66351337 | ||
|
9e6eb50ced | ||
|
d727837324 | ||
|
175d14224e | ||
|
51f1f67fe8 | ||
|
ab250d8686 | ||
|
1ff8637c23 | ||
|
727166e945 | ||
|
e50f3eb311 | ||
|
f5fa37b5f2 | ||
|
587822838a | ||
|
553417194c | ||
|
8a08a2167b |
101 changed files with 5891 additions and 760 deletions
3
.gitattributes
vendored
3
.gitattributes
vendored
|
@ -16,8 +16,11 @@ js/test/ export-ignore
|
|||
.jshintrc export-ignore
|
||||
.nsprc export-ignore
|
||||
.php_cs export-ignore
|
||||
.scrutinizer.yml export-ignore
|
||||
.styleci.yml export-ignore
|
||||
.travis.yml export-ignore
|
||||
codacy-analysis.yml export-ignore
|
||||
crowdin.yml export-ignore
|
||||
composer.json export-ignore
|
||||
composer.lock export-ignore
|
||||
BADGES.md export-ignore
|
||||
|
|
32
.github/workflows/refresh-php8.yml
vendored
Normal file
32
.github/workflows/refresh-php8.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
name: Refresh PHP 8 branch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '42 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout php8 branch
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# directly checkout the php8 branch
|
||||
ref: php8
|
||||
# Number of commits to fetch. 0 indicates all history for all branches and tags.
|
||||
# Default: 1
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Merge master changes into php8
|
||||
run: git merge origin/master
|
||||
|
||||
- name: Push new changes
|
||||
uses: github-actions-x/commit@v2.8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
push-branch: 'php8'
|
||||
|
29
.github/workflows/snyk-scan.yml
vendored
Normal file
29
.github/workflows/snyk-scan.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Snyk scan
|
||||
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the master branch
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
jobs:
|
||||
# https://github.com/snyk/actions/tree/master/php
|
||||
snyk-php:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Google Cloud Storage
|
||||
run: composer require --no-update google/cloud-storage && composer update --no-dev
|
||||
- name: Run Snyk to check for vulnerabilities
|
||||
uses: snyk/actions/php@master
|
||||
continue-on-error: true # To make sure that SARIF upload gets called
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
args: --sarif-file-output=snyk.sarif
|
||||
- name: Upload result to GitHub Code Scanning
|
||||
uses: github/codeql-action/upload-sarif@v1
|
||||
with:
|
||||
sarif_file: snyk.sarif
|
75
.github/workflows/tests.yml
vendored
75
.github/workflows/tests.yml
vendored
|
@ -2,6 +2,7 @@ name: Tests
|
|||
on: [push]
|
||||
|
||||
jobs:
|
||||
|
||||
Composer:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
@ -10,42 +11,110 @@ jobs:
|
|||
- name: Validate composer.json and composer.lock
|
||||
run: composer validate
|
||||
- name: Install dependencies
|
||||
run: /usr/bin/php7.4 $(which composer) install --prefer-dist --no-suggest
|
||||
run: composer install --prefer-dist --no-dev
|
||||
|
||||
PHPunit:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4']
|
||||
name: PHP ${{ matrix.php-versions }} unit tests on ${{ matrix.operating-system }}
|
||||
env:
|
||||
extensions: gd, sqlite3
|
||||
extensions-cache-key-name: phpextensions
|
||||
|
||||
steps:
|
||||
|
||||
# let's get started!
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# cache PHP extensions
|
||||
- name: Setup cache environment
|
||||
id: extcache
|
||||
uses: shivammathur/cache-extensions@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: ${{ env.extensions }}
|
||||
key: ${{ runner.os }}-${{ env.extensions-cache-key }}
|
||||
|
||||
- name: Cache extensions
|
||||
uses: actions/cache@v2
|
||||
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: ${{ matrix.php-versions }}
|
||||
extensions: gd, sqlite3
|
||||
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 "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
# 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 "::set-output name=date::$(/bin/date -u "+%Y%m%d")"
|
||||
shell: bash
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
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: Setup PHPunit
|
||||
run: composer install -n
|
||||
|
||||
- name: Install Google Cloud Storage
|
||||
run: composer require google/cloud-storage
|
||||
|
||||
# testing
|
||||
- name: Run unit tests
|
||||
run: ../vendor/bin/phpunit --no-coverage
|
||||
working-directory: tst
|
||||
|
||||
Mocha:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'js/package.json'
|
||||
|
||||
- name: Setup Mocha
|
||||
run: npm install -g mocha
|
||||
|
||||
- name: Setup Node modules
|
||||
run: npm install
|
||||
working-directory: js
|
||||
|
||||
- name: Run unit tests
|
||||
run: mocha
|
||||
working-directory: js
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -6,7 +6,7 @@ cfg/*
|
|||
!cfg/.htaccess
|
||||
|
||||
# Ignore data/
|
||||
data/
|
||||
/data/
|
||||
|
||||
# Ignore PhpDoc
|
||||
doc/*
|
||||
|
@ -36,3 +36,5 @@ tst/ConfigurationCombinationsTest.php
|
|||
.project
|
||||
.externalToolBuilders
|
||||
.c9
|
||||
/.idea/
|
||||
*.iml
|
||||
|
|
36
.scrutinizer.yml
Normal file
36
.scrutinizer.yml
Normal file
|
@ -0,0 +1,36 @@
|
|||
checks:
|
||||
php: true
|
||||
javascript: true
|
||||
filter:
|
||||
paths:
|
||||
- "css/privatebin.css"
|
||||
- "css/bootstrap/privatebin.css"
|
||||
- "js/privatebin.js"
|
||||
- "lib/*.php"
|
||||
- "index.php"
|
||||
coding_style:
|
||||
php:
|
||||
spaces:
|
||||
around_operators:
|
||||
additive: false
|
||||
concatenation: true
|
||||
build:
|
||||
environment:
|
||||
php:
|
||||
version: '7.2'
|
||||
tests:
|
||||
override:
|
||||
-
|
||||
command: 'composer require google/cloud-storage && cd tst && ../vendor/bin/phpunit'
|
||||
coverage:
|
||||
file: 'tst/log/coverage-clover.xml'
|
||||
format: 'clover'
|
||||
nodes:
|
||||
tests: true
|
||||
analysis:
|
||||
tests:
|
||||
override:
|
||||
-
|
||||
command: phpcs-run
|
||||
use_website_config: true
|
||||
- php-scrutinizer-run
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,6 +1,17 @@
|
|||
# PrivateBin version history
|
||||
|
||||
* **1.4 (not yet released)**
|
||||
* ADDED: Translation for Estonian
|
||||
* ADDED: new HTTP headers improving security (#765)
|
||||
* ADDED: Download button for paste text (#774)
|
||||
* ADDED: Opt-out of federated learning of cohorts (FLoC) (#776)
|
||||
* ADDED: Configuration option to exempt IPs from the rate-limiter (#787)
|
||||
* ADDED: Google Cloud Storage backend support (#795)
|
||||
* CHANGED: Language selection cookie only transmitted over HTTPS (#472)
|
||||
* CHANGED: Upgrading libraries to: random_compat 2.0.20
|
||||
* CHANGED: Removed automatic `.ini` configuration file migration (#808)
|
||||
* CHANGED: Removed configurable `dir` for `traffic` & `purge` limiters (#419)
|
||||
* CHANGED: Server salt, traffic and purge limiter now stored in the storage backend (#419)
|
||||
* **1.3.5 (2021-04-05)**
|
||||
* ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan
|
||||
* ADDED: Make the project info configurable (#681)
|
||||
|
|
|
@ -13,7 +13,7 @@ Sébastien Sauvage - original idea and main developer
|
|||
* Alexey Gladkov - syntax highlighting
|
||||
* Greg Knaddison - robots.txt
|
||||
* MrKooky - HTML5 markup, CSS cleanup
|
||||
* Simon Rupf - WebCrypto, unit tests, current docker containers, MVC, configuration, i18n
|
||||
* Simon Rupf - WebCrypto, unit tests, containers images, database backend, MVC, configuration, i18n
|
||||
* Hexalyse - Password protection
|
||||
* Viktor Stanchev - File upload support
|
||||
* azlux - Tab character input support
|
||||
|
@ -27,6 +27,8 @@ Sébastien Sauvage - original idea and main developer
|
|||
* Harald Leithner - base58 encoding of key
|
||||
* Haocen - lots of bugfixes and UI improvements
|
||||
* Lucas Savva - configurable config file location, NixOS packaging
|
||||
* rodehoed - option to exempt ips from the rate-limiter
|
||||
* Mark van Holsteijn - Google Cloud Storage backend
|
||||
|
||||
## Translations
|
||||
* Hexalyse - French
|
||||
|
@ -50,3 +52,4 @@ Sébastien Sauvage - original idea and main developer
|
|||
* Moo - Lithuanian
|
||||
* whenwesober - Indonesian
|
||||
* retiolus - Catalan
|
||||
* sarnane - Estonian
|
||||
|
|
21
INSTALL.md
21
INSTALL.md
|
@ -1,7 +1,7 @@
|
|||
# Installation
|
||||
|
||||
**TL;DR:** Download the
|
||||
[latest release archive](https://github.com/PrivateBin/PrivateBin/releases/latest)
|
||||
[latest release archive](https://github.com/PrivateBin/PrivateBin/releases/latest) (with the link labelled as „Source code (…)“)
|
||||
and extract it in your web hosts folder where you want to install your PrivateBin
|
||||
instance. We try to provide a mostly safe default configuration, but we urge you to
|
||||
check the [security section](#hardening-and-security) below and the [configuration
|
||||
|
@ -190,4 +190,21 @@ CREATE TABLE prefix_config (
|
|||
INSERT INTO prefix_config VALUES('VERSION', '1.3.5');
|
||||
```
|
||||
|
||||
In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to be TEXT and not BLOB or MEDIUMBLOB.
|
||||
In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to
|
||||
be TEXT and not BLOB or MEDIUMBLOB.
|
||||
|
||||
### Using Google Cloud Storage
|
||||
If you want to deploy PrivateBin in a serverless manner in the Google Cloud, you
|
||||
can choose the `GoogleCloudStorage` as backend. To use this backend, you create
|
||||
a GCS bucket and specify the name as the model option `bucket`. Alternatively,
|
||||
you can set the name through the environment variable PASTEBIN_GCS_BUCKET.
|
||||
|
||||
The default prefix for pastes stored in the bucket is `pastes`. To change the
|
||||
prefix, specify the option `prefix`.
|
||||
|
||||
Google Cloud Storage buckets may be significantly slower than a `FileSystem` or
|
||||
`Database` backend. The big advantage is that the deployment on Google Cloud
|
||||
Platform using Google Cloud Run is easy and cheap.
|
||||
|
||||
To use the Google Cloud Storage backend you have to install the suggested
|
||||
library using the command `composer require google/cloud-storage`.
|
||||
|
|
2
Makefile
2
Makefile
|
@ -2,7 +2,7 @@
|
|||
|
||||
CURRENT_VERSION = 1.3.5
|
||||
VERSION ?= 1.3.6
|
||||
VERSION_FILES = index.php cfg/ *.md css/ i18n/ img/ js/privatebin.js lib/ Makefile tpl/ tst/
|
||||
VERSION_FILES = index.php cfg/ *.md css/ i18n/ img/ js/package.json js/privatebin.js lib/ Makefile tpl/ tst/
|
||||
REGEX_CURRENT_VERSION := $(shell echo $(CURRENT_VERSION) | sed "s/\./\\\./g")
|
||||
REGEX_VERSION := $(shell echo $(VERSION) | sed "s/\./\\\./g")
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ languageselection = false
|
|||
; async functions and display an error if not and for Chrome to enable
|
||||
; webassembly support (used for zlib compression). You can remove it if Chrome
|
||||
; doesn't need to be supported and old browsers don't need to be warned.
|
||||
; cspheader = "default-src 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval' resource:; style-src 'self'; font-src 'self'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads"
|
||||
; cspheader = "default-src 'none'; base-uri 'self'; form-action 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval' resource:; style-src 'self'; font-src 'self'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads"
|
||||
|
||||
; stay compatible with PrivateBin Alpha 0.19, less secure
|
||||
; if enabled will use base64.js version 1.7 instead of 2.1.9 and sha1 instead of
|
||||
|
@ -135,13 +135,14 @@ markdown = "Markdown"
|
|||
; Set this to 0 to disable rate limiting.
|
||||
limit = 10
|
||||
|
||||
; Set ips (v4|v6) which should be exempted for the rate-limit. CIDR also supported. Needed to be comma separated.
|
||||
; Unset for enabling and invalid values will be ignored
|
||||
; eg: exemptedIp = '1.2.3.4,10.10.10/24'
|
||||
|
||||
; (optional) if your website runs behind a reverse proxy or load balancer,
|
||||
; set the HTTP header containing the visitors IP address, i.e. X_FORWARDED_FOR
|
||||
; header = "X_FORWARDED_FOR"
|
||||
|
||||
; directory to store the traffic limits in
|
||||
dir = PATH "data"
|
||||
|
||||
[purge]
|
||||
; minimum time limit between two purgings of expired pastes, it is only
|
||||
; triggered when pastes are created
|
||||
|
@ -153,9 +154,6 @@ limit = 300
|
|||
; site
|
||||
batchsize = 10
|
||||
|
||||
; directory to store the purge limit in
|
||||
dir = PATH "data"
|
||||
|
||||
[model]
|
||||
; name of data model class to load and directory for storage
|
||||
; the default model "Filesystem" stores everything in the filesystem
|
||||
|
@ -163,6 +161,13 @@ class = Filesystem
|
|||
[model_options]
|
||||
dir = PATH "data"
|
||||
|
||||
;[model]
|
||||
; example of a Google Cloud Storage configuration
|
||||
;class = GoogleCloudStorage
|
||||
;[model_options]
|
||||
;bucket = "my-private-bin"
|
||||
;prefix = "pastes"
|
||||
|
||||
;[model]
|
||||
; example of DB configuration for MySQL
|
||||
;class = Database
|
||||
|
|
|
@ -25,8 +25,12 @@
|
|||
},
|
||||
"require" : {
|
||||
"php" : "^5.6.0 || ^7.0 || ^8.0",
|
||||
"paragonie/random_compat" : "2.0.19",
|
||||
"yzalis/identicon" : "2.0.0"
|
||||
"paragonie/random_compat" : "2.0.20",
|
||||
"yzalis/identicon" : "2.0.0",
|
||||
"mlocati/ip-lib" : "1.14.0"
|
||||
},
|
||||
"suggest" : {
|
||||
"google/cloud-storage" : "1.23.1"
|
||||
},
|
||||
"require-dev" : {
|
||||
"phpunit/phpunit" : "^4.6 || ^5.0"
|
||||
|
|
106
composer.lock
generated
106
composer.lock
generated
|
@ -4,20 +4,88 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9d110873bf15a6abd66734e8a818134c",
|
||||
"content-hash": "217f0ba9bdac1014a332a8ba390be949",
|
||||
"packages": [
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.19",
|
||||
"name": "mlocati/ip-lib",
|
||||
"version": "1.14.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "446fc9faa5c2a9ddf65eb7121c0af7e857295241"
|
||||
"url": "https://github.com/mlocati/ip-lib.git",
|
||||
"reference": "882bc0e115970a536b13bcfa59f312783fce08c8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/446fc9faa5c2a9ddf65eb7121c0af7e857295241",
|
||||
"reference": "446fc9faa5c2a9ddf65eb7121c0af7e857295241",
|
||||
"url": "https://api.github.com/repos/mlocati/ip-lib/zipball/882bc0e115970a536b13bcfa59f312783fce08c8",
|
||||
"reference": "882bc0e115970a536b13bcfa59f312783fce08c8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-pdo_sqlite": "*",
|
||||
"phpunit/dbunit": "^1.4 || ^2 || ^3 || ^4",
|
||||
"phpunit/phpunit": "^4.8 || ^5.7 || ^6.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"IPLib\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michele Locati",
|
||||
"email": "mlocati@gmail.com",
|
||||
"homepage": "https://github.com/mlocati",
|
||||
"role": "Author"
|
||||
}
|
||||
],
|
||||
"description": "Handle IPv4, IPv6 addresses and ranges",
|
||||
"homepage": "https://github.com/mlocati/ip-lib",
|
||||
"keywords": [
|
||||
"IP",
|
||||
"address",
|
||||
"addresses",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
"manage",
|
||||
"managing",
|
||||
"matching",
|
||||
"network",
|
||||
"networking",
|
||||
"range",
|
||||
"subnet"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/mlocati",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://paypal.me/mlocati",
|
||||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2020-12-31T11:30:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.20",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
|
||||
"reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -53,7 +121,7 @@
|
|||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"time": "2020-10-15T10:06:57+00:00"
|
||||
"time": "2021-04-17T09:33:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "yzalis/identicon",
|
||||
|
@ -1351,16 +1419,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.22.1",
|
||||
"version": "v1.23.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "c6c942b1ac76c82448322025e084cadc56048b4e"
|
||||
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e",
|
||||
"reference": "c6c942b1ac76c82448322025e084cadc56048b4e",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
|
||||
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1372,7 +1440,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.22-dev"
|
||||
"dev-main": "1.23-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
|
@ -1423,20 +1491,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-01-07T16:49:33+00:00"
|
||||
"time": "2021-02-19T12:13:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v4.4.21",
|
||||
"version": "v4.4.24",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "3871c720871029f008928244e56cf43497da7e9d"
|
||||
"reference": "8b6d1b97521e2f125039b3fcb4747584c6dfa0ef"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/3871c720871029f008928244e56cf43497da7e9d",
|
||||
"reference": "3871c720871029f008928244e56cf43497da7e9d",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/8b6d1b97521e2f125039b3fcb4747584c6dfa0ef",
|
||||
"reference": "8b6d1b97521e2f125039b3fcb4747584c6dfa0ef",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1491,7 +1559,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-03-05T17:58:50+00:00"
|
||||
"time": "2021-05-16T09:52:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
|
|
|
@ -249,6 +249,10 @@ button img {
|
|||
padding: 1px 0 1px 0;
|
||||
}
|
||||
|
||||
#downloadtextbutton img {
|
||||
padding: 1px 0 1px 0;
|
||||
}
|
||||
|
||||
#remainingtime, #password {
|
||||
color: #94a3b4;
|
||||
display: inline;
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
47
i18n/ca.json
47
i18n/ca.json
|
@ -8,32 +8,32 @@
|
|||
"%s requires php %s or above to work. Sorry.": "%s requereix php %s o superior per funcionar. Ho sento.",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s requereix que la secció de configuració [%s] sigui present al fitxer de configuració.",
|
||||
"Please wait %d seconds between each post.": [
|
||||
"Please wait %d second between each post. (singular)",
|
||||
"Please wait %d seconds between each post. (1st plural)",
|
||||
"Please wait %d seconds between each post. (2nd plural)",
|
||||
"Please wait %d seconds between each post. (3rd plural)"
|
||||
"Espereu %d segon entre cada entrada. (singular)",
|
||||
"Espereu %d segons entre cada entrada. (1r plural)",
|
||||
"Espereu %d segons entre cada entrada. (2n plural)",
|
||||
"Please wait %d seconds between each post. (3er plural)"
|
||||
],
|
||||
"Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.",
|
||||
"Invalid data.": "Invalid data.",
|
||||
"You are unlucky. Try again.": "You are unlucky. Try again.",
|
||||
"Error saving comment. Sorry.": "Error saving comment. Sorry.",
|
||||
"Error saving paste. Sorry.": "Error saving paste. Sorry.",
|
||||
"Invalid paste ID.": "Invalid paste ID.",
|
||||
"Paste is limited to %s of encrypted data.": "L'enganxat està limitat a %s de dades encriptades.",
|
||||
"Invalid data.": "Dades no vàlides.",
|
||||
"You are unlucky. Try again.": "Mala sort. Torna-ho a provar.",
|
||||
"Error saving comment. Sorry.": "S'ha produït un error en desar el comentari. Ho sento.",
|
||||
"Error saving paste. Sorry.": "S'ha produït un error en desar l'enganxat. Ho sento.",
|
||||
"Invalid paste ID.": "Identificador d'enganxament no vàlid.",
|
||||
"Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.",
|
||||
"Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.",
|
||||
"Paste was properly deleted.": "Paste was properly deleted.",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.",
|
||||
"%s requires a modern browser to work.": "%s requires a modern browser to work.",
|
||||
"New": "New",
|
||||
"Send": "Send",
|
||||
"Clone": "Clone",
|
||||
"Raw text": "Raw text",
|
||||
"Expires": "Expires",
|
||||
"Burn after reading": "Burn after reading",
|
||||
"Open discussion": "Open discussion",
|
||||
"Password (recommended)": "Password (recommended)",
|
||||
"Discussion": "Discussion",
|
||||
"Toggle navigation": "Toggle navigation",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "Cal JavaScript perquè %s funcioni. Em sap greu les molèsties.",
|
||||
"%s requires a modern browser to work.": "%s requereix un navegador modern per funcionar.",
|
||||
"New": "Nou",
|
||||
"Send": "Enviar",
|
||||
"Clone": "Clona",
|
||||
"Raw text": "Text sense processar",
|
||||
"Expires": "Caducitat",
|
||||
"Burn after reading": "Esborra després de ser llegit",
|
||||
"Open discussion": "Discussió oberta",
|
||||
"Password (recommended)": "Contrasenya (recomanat)",
|
||||
"Discussion": "Discussió",
|
||||
"Toggle navigation": "Alternar navegació",
|
||||
"%d seconds": [
|
||||
"%d second (singular)",
|
||||
"%d seconds (1st plural)",
|
||||
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
89
i18n/cs.json
89
i18n/cs.json
|
@ -6,7 +6,7 @@
|
|||
"en": "cs",
|
||||
"Paste does not exist, has expired or has been deleted.": "Vložený text neexistuje, expiroval nebo byl odstraněn.",
|
||||
"%s requires php %s or above to work. Sorry.": "%s vyžaduje php %s nebo vyšší. Lituji.",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s vyžaduje, aby byla v konfiguračním souboru přítomna sekce [%s].",
|
||||
"Please wait %d seconds between each post.": [
|
||||
"Počet sekund do dalšího příspěvku: %d.",
|
||||
"Počet sekund do dalšího příspěvku: %d.",
|
||||
|
@ -19,10 +19,10 @@
|
|||
"Error saving comment. Sorry.": "Chyba při ukládání komentáře.",
|
||||
"Error saving paste. Sorry.": "Chyba při ukládání příspěvku.",
|
||||
"Invalid paste ID.": "Chybně vložené ID.",
|
||||
"Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.",
|
||||
"Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.",
|
||||
"Paste was properly deleted.": "Paste was properly deleted.",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.",
|
||||
"Paste is not of burn-after-reading type.": "Příspěvek není nastaven na smazaní po přečtení.",
|
||||
"Wrong deletion token. Paste was not deleted.": "Chybný token pro odstranění. Příspěvek nebyl smazán.",
|
||||
"Paste was properly deleted.": "Příspěvek byl řádně smazán.",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "Pro fungování %s je vyžadován JavaScript. Omlouváme se za nepříjemnosti.",
|
||||
"%s requires a modern browser to work.": "%%s requires a modern browser to work.",
|
||||
"New": "Nový",
|
||||
"Send": "Odeslat",
|
||||
|
@ -33,7 +33,7 @@
|
|||
"Open discussion": "Povolit komentáře",
|
||||
"Password (recommended)": "Heslo (doporučeno)",
|
||||
"Discussion": "Komentáře",
|
||||
"Toggle navigation": "Toggle navigation",
|
||||
"Toggle navigation": "Přepnout navigaci",
|
||||
"%d seconds": [
|
||||
"%d sekuda",
|
||||
"%d sekundy",
|
||||
|
@ -77,7 +77,7 @@
|
|||
"%d years (3rd plural)"
|
||||
],
|
||||
"Never": "Nikdy",
|
||||
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.",
|
||||
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Poznámka: Tato služba slouží k vyzkoušení: Data mohou být kdykoliv smazána. Při zneužití této služby zemřou koťátka.",
|
||||
"This document will expire in %d seconds.": [
|
||||
"Tento dokument expiruje za %d sekundu.",
|
||||
"Tento dokument expiruje za %d sekundy.",
|
||||
|
@ -109,19 +109,19 @@
|
|||
"Tento dokument expiruje za %d měsíců."
|
||||
],
|
||||
"Please enter the password for this paste:": "Zadejte prosím heslo:",
|
||||
"Could not decrypt data (Wrong key?)": "Could not decrypt data (Wrong key?)",
|
||||
"Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.",
|
||||
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.",
|
||||
"Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?",
|
||||
"Reply": "Reply",
|
||||
"Could not decrypt data (Wrong key?)": "Nepodařilo se dešifrovat data (Špatný klíč?)",
|
||||
"Could not delete the paste, it was not stored in burn after reading mode.": "Nepodařilo se odstranit příspěvek, nebyl uložen v režimu smazání po přečtení.",
|
||||
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "POUZE PRO VAŠE OČI. Nezavírejte toto okno, tuto zprávu nelze znovu zobrazit.",
|
||||
"Could not decrypt comment; Wrong key?": "Nepodařilo se dešifrovat komentář; Špatný klíč?",
|
||||
"Reply": "Odpovědět",
|
||||
"Anonymous": "Anonym",
|
||||
"Avatar generated from IP address": "Avatar generated from IP address",
|
||||
"Avatar generated from IP address": "Avatar vygenerován z IP adresy",
|
||||
"Add comment": "Přidat komentář",
|
||||
"Optional nickname…": "Volitelný nickname…",
|
||||
"Post comment": "Odeslat komentář",
|
||||
"Sending comment…": "Odesílání komentáře…",
|
||||
"Comment posted.": "Komentář odeslán.",
|
||||
"Could not refresh display: %s": "Could not refresh display: %s",
|
||||
"Could not refresh display: %s": "Nepodařilo se obnovit zobrazení: %s",
|
||||
"unknown status": "neznámý stav",
|
||||
"server error or not responding": "Chyba na serveru nebo server neodpovídá",
|
||||
"Could not post comment: %s": "Nelze odeslat komentář: %s",
|
||||
|
@ -145,44 +145,45 @@
|
|||
"Markdown": "Markdown",
|
||||
"Download attachment": "Stáhnout přílohu",
|
||||
"Cloned: '%s'": "Klonováno: '%s'",
|
||||
"The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.",
|
||||
"The cloned file '%s' was attached to this paste.": "Naklonovaný soubor '%s' byl připojen k tomuto příspěvku.",
|
||||
"Attach a file": "Připojit soubor",
|
||||
"alternatively drag & drop a file or paste an image from the clipboard": "alternatively drag & drop a file or paste an image from the clipboard",
|
||||
"alternatively drag & drop a file or paste an image from the clipboard": "alternativně přetáhněte soubor nebo vložte obrázek ze schránky",
|
||||
"File too large, to display a preview. Please download the attachment.": "Soubor je příliš velký pro zobrazení náhledu. Stáhněte si přílohu.",
|
||||
"Remove attachment": "Odstranit přílohu",
|
||||
"Your browser does not support uploading encrypted files. Please use a newer browser.": "Váš prohlížeč nepodporuje nahrávání šifrovaných souborů. Použijte modernější verzi prohlížeče.",
|
||||
"Invalid attachment.": "Chybná příloha.",
|
||||
"Options": "Volby",
|
||||
"Shorten URL": "Shorten URL",
|
||||
"Shorten URL": "Zkrátit URL",
|
||||
"Editor": "Editor",
|
||||
"Preview": "Náhled",
|
||||
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.",
|
||||
"Decrypt": "Decrypt",
|
||||
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s vyžaduje, aby PATH skončil s \"%s\". Aktualizujte PATH ve vašem souboru index.php.",
|
||||
"Decrypt": "Dešifrovat",
|
||||
"Enter password": "Zadejte heslo",
|
||||
"Loading…": "Loading…",
|
||||
"Decrypting paste…": "Decrypting paste…",
|
||||
"Preparing new paste…": "Preparing new paste…",
|
||||
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.",
|
||||
"Loading…": "Načítání…",
|
||||
"Decrypting paste…": "Dešifruji příspěvek…",
|
||||
"Preparing new paste…": "Připravuji nový příspěvek…",
|
||||
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "V případě, že tato zpráva nezmizí, se podívejte na <a href=\"%s\">tyto často kladené otázky pro řešení</a>.",
|
||||
"+++ no paste text +++": "+++ žádný vložený text +++",
|
||||
"Could not get paste data: %s": "Could not get paste data: %s",
|
||||
"QR code": "QR code",
|
||||
"This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.",
|
||||
"For more information <a href=\"%s\">see this FAQ entry</a>.": "For more information <a href=\"%s\">see this FAQ entry</a>.",
|
||||
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
|
||||
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.",
|
||||
"waiting on user to provide a password": "waiting on user to provide a password",
|
||||
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.",
|
||||
"Retry": "Retry",
|
||||
"Showing raw text…": "Showing raw text…",
|
||||
"Notice:": "Notice:",
|
||||
"This link will expire after %s.": "This link will expire after %s.",
|
||||
"This link can only be accessed once, do not use back or refresh button in your browser.": "This link can only be accessed once, do not use back or refresh button in your browser.",
|
||||
"Link:": "Link:",
|
||||
"Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?",
|
||||
"Use Current Timezone": "Use Current Timezone",
|
||||
"Convert To UTC": "Convert To UTC",
|
||||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"Could not get paste data: %s": "Nepodařilo se získat data příspěvku: %s",
|
||||
"QR code": "QR kód",
|
||||
"This website is using an insecure HTTP connection! Please use it only for testing.": "Tato stránka používá nezabezpečený připojení HTTP! Použijte ji prosím jen pro testování.",
|
||||
"For more information <a href=\"%s\">see this FAQ entry</a>.": "Více informací naleznete <a href=\"%s\">v této položce FAQ</a>.",
|
||||
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Váš prohlížeč může vyžadovat připojení HTTPS pro podporu WebCrypto API. Zkuste <a href=\"%s\">přepnout na HTTPS</a>.",
|
||||
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Váš prohlížeč nepodporuje WebAssembly, který se používá pro zlib kompresi. Můžete vytvořit nekomprimované dokumenty, ale nebudete moct číst ty komprimované.",
|
||||
"waiting on user to provide a password": "čekám na zadání hesla",
|
||||
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Nepodařilo se dešifrovat data. Zadali jste špatné heslo? Zkuste to znovu pomocí tlačítka nahoře.",
|
||||
"Retry": "Opakovat",
|
||||
"Showing raw text…": "Zobrazuji surový text…",
|
||||
"Notice:": "Upozornění:",
|
||||
"This link will expire after %s.": "Tento odkaz vyprší za %s.",
|
||||
"This link can only be accessed once, do not use back or refresh button in your browser.": "Tento odkaz je přístupný pouze jednou, nepoužívejte tlačítko zpět ani neobnovujte tuto stránku ve vašem prohlížeči.",
|
||||
"Link:": "Odkaz:",
|
||||
"Recipient may become aware of your timezone, convert time to UTC?": "Příjemce se může dozvědět o vašem časovém pásmu, převést čas na UTC?",
|
||||
"Use Current Timezone": "Použít aktuální časové pásmo",
|
||||
"Convert To UTC": "Převést na UTC",
|
||||
"Close": "Zavřít",
|
||||
"Encrypted note on PrivateBin": "Šifrovaná poznámka ve službě PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Navštivte tento odkaz pro zobrazení poznámky. Přeposláním URL umožníte také jiným lidem přístup.",
|
||||
"URL shortener may expose your decrypt key in URL.": "Zkracovač URL může odhalit váš dešifrovací klíč v URL.",
|
||||
"Save paste": "Uložit příspěvek"
|
||||
}
|
||||
|
|
11
i18n/de.json
11
i18n/de.json
|
@ -175,14 +175,15 @@
|
|||
"Retry": "Wiederholen",
|
||||
"Showing raw text…": "Rohtext wird angezeigt…",
|
||||
"Notice:": "Hinweis:",
|
||||
"This link will expire after %s.": "Diese Verknüpfung wird in %s ablaufen.",
|
||||
"This link can only be accessed once, do not use back or refresh button in your browser.": "Diese Verknüpfung kann nur einmal geöffnet werden, verwende nicht den Zurück- oder Neu-laden-Knopf Deines Browsers.",
|
||||
"Link:": "Verknüpfung:",
|
||||
"This link will expire after %s.": "Dieser Link wird am %s ablaufen.",
|
||||
"This link can only be accessed once, do not use back or refresh button in your browser.": "Dieser Link kann nur einmal geöffnet werden, verwende nicht den Zurück- oder Neu-laden-Knopf Deines Browsers.",
|
||||
"Link:": "Link:",
|
||||
"Recipient may become aware of your timezone, convert time to UTC?": "Der Empfänger könnte Deine Zeitzone erfahren, möchtest Du die Zeit in UTC umwandeln?",
|
||||
"Use Current Timezone": "Aktuelle Zeitzone verwenden",
|
||||
"Convert To UTC": "In UTC umwandeln",
|
||||
"Close": "Schliessen",
|
||||
"Encrypted note on PrivateBin": "Verschlüsselte Notiz auf PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besuche diese Verknüpfung um das Dokument zu sehen. Wird die URL an eine andere Person gegeben, so kann diese Person ebenfalls auf dieses Dokument zugreifen.",
|
||||
"URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen."
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besuche diesen Link um das Dokument zu sehen. Wird die URL an eine andere Person gegeben, so kann diese Person ebenfalls auf dieses Dokument zugreifen.",
|
||||
"URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen.",
|
||||
"Save paste": "Text speichern"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Cerrar",
|
||||
"Encrypted note on PrivateBin": "Nota cifrada en PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite este enlace para ver la nota. Dar la URL a cualquier persona también les permite acceder a la nota.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "El acortador de URL puede exponer su clave de descifrado en el URL.",
|
||||
"Save paste": "Guardar \"paste\""
|
||||
}
|
||||
|
|
189
i18n/et.json
Normal file
189
i18n/et.json
Normal file
|
@ -0,0 +1,189 @@
|
|||
{
|
||||
"PrivateBin": "PrivateBin",
|
||||
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s on minimalistlik, avatud lähtekoodiga online pastebin, kus serveril pole kleebitud andmete kohta teadmist. Andmed krüpteeritakse/dekrüpteeritakse %sbrauseris%s kasutades 256-bitist AES-i.",
|
||||
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Lisateave <a href=\"https://privatebin.info/\">projekti lehel</a>.",
|
||||
"Because ignorance is bliss": "Kuna teadmatus on õndsus",
|
||||
"en": "et",
|
||||
"Paste does not exist, has expired or has been deleted.": "Kleebet ei eksisteeri, on aegunud või on kustutatud.",
|
||||
"%s requires php %s or above to work. Sorry.": "%s vajab, et oleks php %s või kõrgem, et töötada. Vabandame.",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s vajab, et [%s] seadistamise jaotis oleks olemas konfiguratsioonifailis.",
|
||||
"Please wait %d seconds between each post.": [
|
||||
"Palun oota %d sekund iga postituse vahel.",
|
||||
"Palun oota %d sekundit iga postituse vahel.",
|
||||
"Palun oota %d sekundit iga postituse vahel.",
|
||||
"Palun oota %d sekundit iga postituse vahel."
|
||||
],
|
||||
"Paste is limited to %s of encrypted data.": "Kleepe limiit on %s krüpteeritud andmeid.",
|
||||
"Invalid data.": "Valed andmed.",
|
||||
"You are unlucky. Try again.": "Sul ei vea. Proovi uuesti.",
|
||||
"Error saving comment. Sorry.": "Viga kommentaari salvestamisel. Vabandame.",
|
||||
"Error saving paste. Sorry.": "Viga kleepe salvestamisel. Vabandame.",
|
||||
"Invalid paste ID.": "Vale kleepe ID.",
|
||||
"Paste is not of burn-after-reading type.": "Kleebe ei ole põleta-pärast-lugemist tüüpi.",
|
||||
"Wrong deletion token. Paste was not deleted.": "Vale kustutamiskood. Kleebet ei kustutatud.",
|
||||
"Paste was properly deleted.": "Kleebe kustutati korralikult.",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript on vajalik %s'i töötamiseks. Vabandame ebamugavuste pärast.",
|
||||
"%s requires a modern browser to work.": "%s vajab töötamiseks kaasaegset brauserit.",
|
||||
"New": "Uus",
|
||||
"Send": "Saada",
|
||||
"Clone": "Klooni",
|
||||
"Raw text": "Lähtetekst",
|
||||
"Expires": "Aegub",
|
||||
"Burn after reading": "Põleta pärast lugemist",
|
||||
"Open discussion": "Avatud arutelu",
|
||||
"Password (recommended)": "Parool (soovitatav)",
|
||||
"Discussion": "Arutelu",
|
||||
"Toggle navigation": "Näita menüüd",
|
||||
"%d seconds": [
|
||||
"%d sekund",
|
||||
"%d sekundit",
|
||||
"%d sekundit",
|
||||
"%d sekundit"
|
||||
],
|
||||
"%d minutes": [
|
||||
"%d minut",
|
||||
"%d minutit",
|
||||
"%d minutit",
|
||||
"%d minutit"
|
||||
],
|
||||
"%d hours": [
|
||||
"%d tund",
|
||||
"%d tundi",
|
||||
"%d tundi",
|
||||
"%d tundi"
|
||||
],
|
||||
"%d days": [
|
||||
"%d päev",
|
||||
"%d päeva",
|
||||
"%d päeva",
|
||||
"%d päeva"
|
||||
],
|
||||
"%d weeks": [
|
||||
"%d nädal",
|
||||
"%d nädalat",
|
||||
"%d nädalat",
|
||||
"%d nädalat"
|
||||
],
|
||||
"%d months": [
|
||||
"%d kuu",
|
||||
"%d kuud",
|
||||
"%d kuud",
|
||||
"%d kuud"
|
||||
],
|
||||
"%d years": [
|
||||
"%d aasta",
|
||||
"%d aastat",
|
||||
"%d aastat",
|
||||
"%d aastat"
|
||||
],
|
||||
"Never": "Mitte kunagi",
|
||||
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Märge: See on testimisteenus: Andmeid võidakse igal ajal kustutada. Kiisupojad hukuvad, kui seda teenust kuritarvitad.",
|
||||
"This document will expire in %d seconds.": [
|
||||
"See dokument aegub %d sekundi pärast.",
|
||||
"See dokument aegub %d sekundi pärast.",
|
||||
"See dokument aegub %d sekundi pärast.",
|
||||
"See dokument aegub %d sekundi pärast."
|
||||
],
|
||||
"This document will expire in %d minutes.": [
|
||||
"See dokument aegub %d minuti pärast.",
|
||||
"See dokument aegub %d minuti pärast.",
|
||||
"See dokument aegub %d minuti pärast.",
|
||||
"See dokument aegub %d minuti pärast."
|
||||
],
|
||||
"This document will expire in %d hours.": [
|
||||
"See dokument aegub %d tunni pärast.",
|
||||
"See dokument aegub %d tunni pärast.",
|
||||
"See dokument aegub %d tunni pärast.",
|
||||
"See dokument aegub %d tunni pärast."
|
||||
],
|
||||
"This document will expire in %d days.": [
|
||||
"See dokument aegub %d päeva pärast.",
|
||||
"See dokument aegub %d päeva pärast.",
|
||||
"See dokument aegub %d päeva pärast.",
|
||||
"See dokument aegub %d päeva pärast."
|
||||
],
|
||||
"This document will expire in %d months.": [
|
||||
"See dokument aegub %d kuu pärast.",
|
||||
"See dokument aegub %d kuu pärast.",
|
||||
"See dokument aegub %d kuu pärast.",
|
||||
"See dokument aegub %d kuu pärast."
|
||||
],
|
||||
"Please enter the password for this paste:": "Palun sisesta selle kleepe parool:",
|
||||
"Could not decrypt data (Wrong key?)": "Ei suutnud andmeid dekrüpteerida (Vale võti?)",
|
||||
"Could not delete the paste, it was not stored in burn after reading mode.": "Ei suutnud kleebet kustutada, seda ei salvestatud põleta pärast lugemist režiimis.",
|
||||
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "AINULT SINU SILMADELE. Ära sulge seda akent, seda sõnumit ei saa enam kuvada.",
|
||||
"Could not decrypt comment; Wrong key?": "Ei suutnud kommentaari dekrüpteerida; Vale võti?",
|
||||
"Reply": "Vasta",
|
||||
"Anonymous": "Anonüümne",
|
||||
"Avatar generated from IP address": "Avatar genereeritud IP aadressi põhjal",
|
||||
"Add comment": "Lisa kommentaar",
|
||||
"Optional nickname…": "Valikuline hüüdnimi…",
|
||||
"Post comment": "Postita kommentaar",
|
||||
"Sending comment…": "Kommentaari saatmine…",
|
||||
"Comment posted.": "Kommentaar postitatud.",
|
||||
"Could not refresh display: %s": "Ei suutnud kuva värskendada: %s",
|
||||
"unknown status": "tundmatu staatus",
|
||||
"server error or not responding": "serveri viga või ei vasta",
|
||||
"Could not post comment: %s": "Ei suutnud kommentaari postitada: %s",
|
||||
"Sending paste…": "Kleepe saatmine…",
|
||||
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Sinu kleebe on <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Kopeerimiseks vajuta [Ctrl]+[c])</span>",
|
||||
"Delete data": "Kustuta andmed",
|
||||
"Could not create paste: %s": "Ei suutnud kleebet luua: %s",
|
||||
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Ei suutnud kleebet dekrüpteerida: Dekrüpteerimisvõti on URL-ist puudu (Kas kasutasid ümbersuunajat või URL-i lühendajat, mis eemaldab osa URL-ist?)",
|
||||
"B": "B",
|
||||
"KiB": "KiB",
|
||||
"MiB": "MiB",
|
||||
"GiB": "GiB",
|
||||
"TiB": "TiB",
|
||||
"PiB": "PiB",
|
||||
"EiB": "EiB",
|
||||
"ZiB": "ZiB",
|
||||
"YiB": "YiB",
|
||||
"Format": "Formaat",
|
||||
"Plain Text": "Lihttekst",
|
||||
"Source Code": "Lähtekood",
|
||||
"Markdown": "Markdown",
|
||||
"Download attachment": "Laadi manus alla",
|
||||
"Cloned: '%s'": "Kloonitud: '%s'",
|
||||
"The cloned file '%s' was attached to this paste.": "Kloonitud fail '%s' manustati sellele kleepele.",
|
||||
"Attach a file": "Manusta fail",
|
||||
"alternatively drag & drop a file or paste an image from the clipboard": "teise võimalusena lohista fail või kleebi pilt lõikelaualt",
|
||||
"File too large, to display a preview. Please download the attachment.": "Fail on eelvaate kuvamiseks liiga suur. Palun laadi manus alla.",
|
||||
"Remove attachment": "Eemalda manus",
|
||||
"Your browser does not support uploading encrypted files. Please use a newer browser.": "Sinu brauser ei toeta krüpteeritud failide üleslaadimist. Palun kasuta uuemat brauserit.",
|
||||
"Invalid attachment.": "Sobimatu manus.",
|
||||
"Options": "Valikud",
|
||||
"Shorten URL": "Lühenda URL",
|
||||
"Editor": "Toimetaja",
|
||||
"Preview": "Eelvaade",
|
||||
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s vajab, et PATH lõppeks järgmisega: \"%s\". Palun uuenda PATH-i oma index.php failis.",
|
||||
"Decrypt": "Dekrüpteeri",
|
||||
"Enter password": "Sisesta parool",
|
||||
"Loading…": "Laadimine…",
|
||||
"Decrypting paste…": "Kleepe dekrüpteerimine…",
|
||||
"Preparing new paste…": "Uue kleepe ettevalmistamine…",
|
||||
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "Kui see sõnum ei kao, palun vaata <a href=\"%s\">seda KKK-d, et saada tõrkeotsinguks teavet.</a>.",
|
||||
"+++ no paste text +++": "+++ kleepe tekst puudub +++",
|
||||
"Could not get paste data: %s": "Ei suutnud saada kleepe andmeid: %s",
|
||||
"QR code": "QR kood",
|
||||
"This website is using an insecure HTTP connection! Please use it only for testing.": "See veebisait kasutab ebaturvalist HTTP ühendust! Palun kasuta seda ainult katsetamiseks.",
|
||||
"For more information <a href=\"%s\">see this FAQ entry</a>.": "Lisateabe saamiseks <a href=\"%s\">vaata seda KKK sissekannet</a>.",
|
||||
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Sinu brauser võib vajada HTTPS ühendust, et toetada WebCrypto API-d. Proovi <a href=\"%s\">üle minna HTTPS-ile</a>.",
|
||||
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Sinu brauser ei toeta WebAssembly't, mida kasutatakse zlib tihendamiseks. Sa saad luua tihendamata dokumente, kuid ei saa lugeda tihendatuid.",
|
||||
"waiting on user to provide a password": "ootan parooli sisestamist kasutajalt",
|
||||
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Ei suutnud andmeid dekrüpteerida. Kas sisestasid vale parooli? Proovi uuesti üleval asuva nupuga.",
|
||||
"Retry": "Proovi uuesti",
|
||||
"Showing raw text…": "Lähteteksti näitamine…",
|
||||
"Notice:": "Teade:",
|
||||
"This link will expire after %s.": "See link aegub: %s.",
|
||||
"This link can only be accessed once, do not use back or refresh button in your browser.": "Sellele lingile saab vaid üks kord ligi pääseda, ära kasuta tagasi või värskenda nuppe sinu brauseris.",
|
||||
"Link:": "Link:",
|
||||
"Recipient may become aware of your timezone, convert time to UTC?": "Saaja võib saada teada sinu ajavööndi, kas teisendada aeg UTC-ks?",
|
||||
"Use Current Timezone": "Kasuta praegust ajavööndit",
|
||||
"Convert To UTC": "Teisenda UTC-ks",
|
||||
"Close": "Sulge",
|
||||
"Encrypted note on PrivateBin": "Krüpteeritud kiri PrivateBin-is",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Kirja nägemiseks külasta seda linki. Teistele URL-i andmine lubab ka neil ligi pääseda kirjale.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL-i lühendaja võib paljastada sinu dekrüpteerimisvõtme URL-is.",
|
||||
"Save paste": "Salvesta kleebe"
|
||||
}
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Fermer",
|
||||
"Encrypted note on PrivateBin": "Message chiffré sur PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visiter ce lien pour voir la note. Donner l'URL à une autre personne lui permet également d'accéder à la note.",
|
||||
"URL shortener may expose your decrypt key in URL.": "Raccourcir l'URL peut exposer votre clé de déchiffrement dans l'URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "Raccourcir l'URL peut exposer votre clé de déchiffrement dans l'URL.",
|
||||
"Save paste": "Sauver le paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "סגירה",
|
||||
"Encrypted note on PrivateBin": "הערה מוצפנת ב־PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "נא לבקר בקישור כדי לצפות בהערה. מסירת הקישור לאנשים כלשהם תאפשר גם להם לגשת להערה.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Bezárás",
|
||||
"Encrypted note on PrivateBin": "Titkosított jegyzet a PrivateBinen",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Látogasd meg ezt a hivatkozást a bejegyzés megtekintéséhez. Ha mások számára is megadod ezt a linket, azzal hozzáférnek ők is.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
45
i18n/id.json
45
i18n/id.json
|
@ -8,10 +8,10 @@
|
|||
"%s requires php %s or above to work. Sorry.": "%s memerlukan php %s atau versi diatasnya untuk dapat dijalankan. Maaf.",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s membutuhkan bagian konfigurasi [%s] untuk ada di file konfigurasi.",
|
||||
"Please wait %d seconds between each post.": [
|
||||
"Silahkan menunggu %d detik antara masing-masing postingan. (tunggal)",
|
||||
"Silahkan menunggu %d detik antara masing-masing postingan. (jamak ke-1)",
|
||||
"Silahkan menunggu %d detik antara masing-masing postingan. (jamak ke-2)",
|
||||
"Silahkan menunggu %d detik antara masing-masing postingan. (jamak ke-3)"
|
||||
"Silahkan menunggu %d detik antara masing-masing postingan.",
|
||||
"Silahkan menunggu %d detik antara masing-masing postingan.",
|
||||
"Silahkan menunggu %d detik antara masing-masing postingan.",
|
||||
"Silahkan menunggu %d detik antara masing-masing postingan."
|
||||
],
|
||||
"Paste is limited to %s of encrypted data.": "Paste dibatasi sampai %s dari data yang dienskripsi.",
|
||||
"Invalid data.": "Data tidak valid.",
|
||||
|
@ -35,31 +35,31 @@
|
|||
"Discussion": "Diskusi",
|
||||
"Toggle navigation": "Alihkan navigasi",
|
||||
"%d seconds": [
|
||||
"%d detik (tunggal)",
|
||||
"%d detik (jamak ke-1)",
|
||||
"%d detik (jamak ke-2)",
|
||||
"%d detik (jamak ke-3)"
|
||||
"%d detik",
|
||||
"%d detik",
|
||||
"%d detik",
|
||||
"%d detik"
|
||||
],
|
||||
"%d minutes": [
|
||||
"%d menit (tunggal)",
|
||||
"%d menit (jamak ke-1)",
|
||||
"%d menit (jamak ke-2)",
|
||||
"%d menit (jamak ke-3)"
|
||||
"%d menit",
|
||||
"%d menit",
|
||||
"%d menit",
|
||||
"%d menit"
|
||||
],
|
||||
"%d hours": [
|
||||
"%d jam (tunggal)",
|
||||
"%d jam (jamak ke-1)",
|
||||
"%d jam (jamak ke-2)",
|
||||
"%d jam (jamak ke-3)"
|
||||
"%d jam",
|
||||
"%d jam",
|
||||
"%d jam",
|
||||
"%d jam"
|
||||
],
|
||||
"%d days": [
|
||||
"%d hari (tunggal)",
|
||||
"%d hari (jamak ke-1)",
|
||||
"%d hari (jamak ke-2)",
|
||||
"%d hari (jamak ke-3)"
|
||||
"%d hari",
|
||||
"%d hari",
|
||||
"%d hari",
|
||||
"%d hari"
|
||||
],
|
||||
"%d weeks": [
|
||||
"%d minggu (tunggal)",
|
||||
"%d minggu",
|
||||
"%d minggu",
|
||||
"%d minggu",
|
||||
"%d minggu"
|
||||
|
@ -184,5 +184,6 @@
|
|||
"Close": "Tutup",
|
||||
"Encrypted note on PrivateBin": "Catatan ter-ekrip di PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Kunjungi tautan ini untuk melihat catatan. Memberikan alamat URL pada siapapun juga, akan mengizinkan mereka untuk mengakses catatan, so pasti gitu loh Kaka.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "Pemendek URL mungkin akan menampakkan kunci dekrip Anda dalam URL.",
|
||||
"Save paste": "Simpan paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Chiudi",
|
||||
"Encrypted note on PrivateBin": "Nota crittografata su PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visita questo collegamento per vedere la nota. Dare l'URL a chiunque consente anche a loro di accedere alla nota.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener può esporre la tua chiave decrittografata nell'URL.",
|
||||
"Save paste": "Salva il messagio"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -63,10 +63,10 @@
|
|||
"ho": ["Hiri Motu", "Hiri Motu"],
|
||||
"hu": ["magyar", "Hungarian"],
|
||||
"ia": ["Interlingua", "Interlingua"],
|
||||
"id": ["bahasa Indonesia","Indonesian"],
|
||||
"ie": ["Interlingue", "Interlingue"],
|
||||
"ga": ["Gaeilge", "Irish"],
|
||||
"ig": ["Asụsụ Igbo", "Igbo"],
|
||||
"in": ["bahasa Indonesia","Indonesian"],
|
||||
"ik": ["Iñupiaq", "Inupiaq"],
|
||||
"io": ["Ido", "Ido"],
|
||||
"is": ["Íslenska", "Icelandic"],
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"PrivateBin": "PrivateBin",
|
||||
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s yra minimalistinis, atvirojo kodo internetinis įdėjimų dėklas, kurį naudojant, serveris nieko nenutuokia apie įdėtus duomenis. Duomenys yra šifruojami/iššifruojami %snaršyklėje%s naudojant 256 bitų AES.",
|
||||
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Daugiau informacijos rasite <a href=\"https://privatebin.info/\">projeketo puslapyje</a>.",
|
||||
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Daugiau informacijos rasite <a href=\"https://privatebin.info/\">projekto puslapyje</a>.",
|
||||
"Because ignorance is bliss": "Nes nežinojimas yra palaima",
|
||||
"en": "lt",
|
||||
"Paste does not exist, has expired or has been deleted.": "Įdėjimo nėra, jis nebegalioja arba buvo ištrintas.",
|
||||
|
@ -184,5 +184,6 @@
|
|||
"Close": "Užverti",
|
||||
"Encrypted note on PrivateBin": "Šifruoti užrašai ties PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Norėdami matyti užrašus, aplankykite šį tinklalapį. Pasidalinus šiuo URL adresu su kitais žmonėmis, jiems taip pat bus leidžiama prieiga prie šių užrašų.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL trumpinimo įrankis gali atskleisti URL adrese jūsų iššifravimo raktą.",
|
||||
"Save paste": "Įrašyti įdėjimą"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Steng",
|
||||
"Encrypted note on PrivateBin": "Kryptert notat på PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besøk denne lenken for å se notatet. Hvis lenken deles med andre, vil de også kunne se notatet.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL forkorter kan avsløre dekrypteringsnøkkelen.",
|
||||
"Save paste": "Lagre utklipp"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Tampar",
|
||||
"Encrypted note on PrivateBin": "Nòtas chifradas sus PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visitatz aqueste ligam per veire la nòta. Fornir lo ligam a qualqu’un mai li permet tanben d’accedir a la nòta.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "Los espleches d’acorchiment d’URL pòdon expausar la clau de deschiframent dins l’URL.",
|
||||
"Save paste": "Enregistrar lo tèxt"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Fechar",
|
||||
"Encrypted note on PrivateBin": "Nota criptografada no PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite esse link para ver a nota. Dar a URL para qualquer um permite que eles também acessem a nota.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
"%d лет"
|
||||
],
|
||||
"Never": "Никогда",
|
||||
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Примечание: Этот сервис тестовый: Данные могут быть удалены в любое время. Котята умрут, если вы будете злоупотреблять серсисом.",
|
||||
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Примечание: Этот сервис тестовый: Данные могут быть удалены в любое время. Котята умрут, если вы будете злоупотреблять сервисом.",
|
||||
"This document will expire in %d seconds.": [
|
||||
"Документ будет удален через %d секунду.",
|
||||
"Документ будет удален через %d секунды.",
|
||||
|
@ -184,5 +184,6 @@
|
|||
"Close": "Закрыть",
|
||||
"Encrypted note on PrivateBin": "Зашифрованная запись на PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Посетите эту ссылку чтобы просмотреть запись. Передача ссылки кому либо позволит им получить доступ к записи тоже.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "Сервис сокращения ссылок может получить ваш ключ расшифровки из ссылки.",
|
||||
"Save paste": "Сохранить запись"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
57
i18n/tr.json
57
i18n/tr.json
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"PrivateBin": "PrivateBin",
|
||||
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.",
|
||||
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "More information on the <a href=\"https://privatebin.info/\">project page</a>.",
|
||||
"Because ignorance is bliss": "Because ignorance is bliss",
|
||||
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Daha fazla bilgi için <a href=\"https://privatebin.info/\">proje sayfası</a>'na göz atabilirsiniz.",
|
||||
"Because ignorance is bliss": "Çünkü, cehalet mutluluktur",
|
||||
"en": "tr",
|
||||
"Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.",
|
||||
"%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.",
|
||||
|
@ -14,8 +14,8 @@
|
|||
"Please wait %d seconds between each post. (3rd plural)"
|
||||
],
|
||||
"Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.",
|
||||
"Invalid data.": "Invalid data.",
|
||||
"You are unlucky. Try again.": "You are unlucky. Try again.",
|
||||
"Invalid data.": "Geçersiz veri.",
|
||||
"You are unlucky. Try again.": "Lütfen tekrar deneyiniz.",
|
||||
"Error saving comment. Sorry.": "Error saving comment. Sorry.",
|
||||
"Error saving paste. Sorry.": "Error saving paste. Sorry.",
|
||||
"Invalid paste ID.": "Invalid paste ID.",
|
||||
|
@ -24,16 +24,16 @@
|
|||
"Paste was properly deleted.": "Paste was properly deleted.",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.",
|
||||
"%s requires a modern browser to work.": "%s requires a modern browser to work.",
|
||||
"New": "New",
|
||||
"Send": "Send",
|
||||
"Clone": "Clone",
|
||||
"New": "Yeni",
|
||||
"Send": "Gönder",
|
||||
"Clone": "Kopyala",
|
||||
"Raw text": "Raw text",
|
||||
"Expires": "Expires",
|
||||
"Expires": "Süre Sonu",
|
||||
"Burn after reading": "Burn after reading",
|
||||
"Open discussion": "Open discussion",
|
||||
"Open discussion": "Açık Tartışmalar",
|
||||
"Password (recommended)": "Password (recommended)",
|
||||
"Discussion": "Discussion",
|
||||
"Toggle navigation": "Toggle navigation",
|
||||
"Discussion": "Tartışma",
|
||||
"Toggle navigation": "Gezinmeyi değiştir",
|
||||
"%d seconds": [
|
||||
"%d second (singular)",
|
||||
"%d seconds (1st plural)",
|
||||
|
@ -59,8 +59,8 @@
|
|||
"%d days (3rd plural)"
|
||||
],
|
||||
"%d weeks": [
|
||||
"%d week (singular)",
|
||||
"%d weeks (1st plural)",
|
||||
"%d hafta (tekil)",
|
||||
"%d haftalar (çoğul)",
|
||||
"%d weeks (2nd plural)",
|
||||
"%d weeks (3rd plural)"
|
||||
],
|
||||
|
@ -113,21 +113,21 @@
|
|||
"Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.",
|
||||
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.",
|
||||
"Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?",
|
||||
"Reply": "Reply",
|
||||
"Anonymous": "Anonymous",
|
||||
"Reply": "Cevapla",
|
||||
"Anonymous": "Anonim",
|
||||
"Avatar generated from IP address": "Avatar generated from IP address",
|
||||
"Add comment": "Add comment",
|
||||
"Add comment": "Yorum ekle",
|
||||
"Optional nickname…": "Optional nickname…",
|
||||
"Post comment": "Post comment",
|
||||
"Post comment": "Yorumu gönder",
|
||||
"Sending comment…": "Sending comment…",
|
||||
"Comment posted.": "Comment posted.",
|
||||
"Comment posted.": "Yorum gönderildi.",
|
||||
"Could not refresh display: %s": "Could not refresh display: %s",
|
||||
"unknown status": "unknown status",
|
||||
"server error or not responding": "server error or not responding",
|
||||
"Could not post comment: %s": "Could not post comment: %s",
|
||||
"Sending paste…": "Sending paste…",
|
||||
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>",
|
||||
"Delete data": "Delete data",
|
||||
"Delete data": "Veriyi sil",
|
||||
"Could not create paste: %s": "Could not create paste: %s",
|
||||
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)",
|
||||
"B": "B",
|
||||
|
@ -155,34 +155,35 @@
|
|||
"Options": "Options",
|
||||
"Shorten URL": "Shorten URL",
|
||||
"Editor": "Editor",
|
||||
"Preview": "Preview",
|
||||
"Preview": "Ön izleme",
|
||||
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.",
|
||||
"Decrypt": "Decrypt",
|
||||
"Enter password": "Enter password",
|
||||
"Loading…": "Loading…",
|
||||
"Enter password": "Şifreyi girin",
|
||||
"Loading…": "Yükleniyor…",
|
||||
"Decrypting paste…": "Decrypting paste…",
|
||||
"Preparing new paste…": "Preparing new paste…",
|
||||
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.",
|
||||
"+++ no paste text +++": "+++ no paste text +++",
|
||||
"Could not get paste data: %s": "Could not get paste data: %s",
|
||||
"QR code": "QR code",
|
||||
"QR code": "QR kodu",
|
||||
"This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.",
|
||||
"For more information <a href=\"%s\">see this FAQ entry</a>.": "For more information <a href=\"%s\">see this FAQ entry</a>.",
|
||||
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
|
||||
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.",
|
||||
"waiting on user to provide a password": "waiting on user to provide a password",
|
||||
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.",
|
||||
"Retry": "Retry",
|
||||
"Retry": "Yeniden Dene",
|
||||
"Showing raw text…": "Showing raw text…",
|
||||
"Notice:": "Notice:",
|
||||
"Notice:": "Bildirim:",
|
||||
"This link will expire after %s.": "This link will expire after %s.",
|
||||
"This link can only be accessed once, do not use back or refresh button in your browser.": "This link can only be accessed once, do not use back or refresh button in your browser.",
|
||||
"Link:": "Link:",
|
||||
"Link:": "Bağlantı:",
|
||||
"Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?",
|
||||
"Use Current Timezone": "Use Current Timezone",
|
||||
"Convert To UTC": "Convert To UTC",
|
||||
"Close": "Close",
|
||||
"Close": "Kapat",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@
|
|||
"Close": "Close",
|
||||
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
|
||||
"Save paste": "Save paste"
|
||||
}
|
||||
|
|
45
i18n/zh.json
45
i18n/zh.json
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
"PrivateBin": "PrivateBin",
|
||||
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s是一个极简、开源、对粘贴内容毫不知情的在线粘贴板,数据%s在浏览器内%s进行AES-256加密。",
|
||||
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s 是一个极简、开源、对粘贴内容毫不知情的在线粘贴板,数据%s在浏览器内%s进行 AES-256 加密和解密。",
|
||||
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "更多信息请查看<a href=\"https://privatebin.info/\">项目主页</a>。",
|
||||
"Because ignorance is bliss": "因为无知是福",
|
||||
"en": "zh",
|
||||
"Paste does not exist, has expired or has been deleted.": "粘贴内容不存在,已过期或已被删除。",
|
||||
"%s requires php %s or above to work. Sorry.": "%s需要PHP %s及以上版本来工作,抱歉。",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s需要设置配置文件中 [%s] 部分。",
|
||||
"Paste does not exist, has expired or has been deleted.": "粘贴内容不存在、已过期或已被删除。",
|
||||
"%s requires php %s or above to work. Sorry.": "抱歉,%s 需要 PHP %s 及以上版本才能运行。",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s 需要设置配置文件中的 [%s] 部分。",
|
||||
"Please wait %d seconds between each post.": [
|
||||
"每 %d 秒只能粘贴一次。",
|
||||
"每 %d 秒只能粘贴一次。",
|
||||
"每 %d 秒只能粘贴一次。",
|
||||
"每 %d 秒只能粘贴一次。"
|
||||
],
|
||||
"Paste is limited to %s of encrypted data.": "粘贴受限于 %s 加密数据。",
|
||||
"Paste is limited to %s of encrypted data.": "对于加密数据,上限为 %s。",
|
||||
"Invalid data.": "无效的数据。",
|
||||
"You are unlucky. Try again.": "请再试一次。",
|
||||
"Error saving comment. Sorry.": "保存评论时出现错误,抱歉。",
|
||||
|
@ -111,8 +111,8 @@
|
|||
"Please enter the password for this paste:": "请输入这份粘贴内容的密码:",
|
||||
"Could not decrypt data (Wrong key?)": "无法解密数据(密钥错误?)",
|
||||
"Could not delete the paste, it was not stored in burn after reading mode.": "无法删除此粘贴内容,它没有以阅后即焚模式保存。",
|
||||
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "注意啦!!!不要关闭窗口,否则你再也见不到这条消息了。",
|
||||
"Could not decrypt comment; Wrong key?": "无法解密评论; 密钥错误?",
|
||||
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "看!仔!细!了!不要关闭窗口,否则你再也见不到这条消息了。",
|
||||
"Could not decrypt comment; Wrong key?": "无法解密评论;密钥错误?",
|
||||
"Reply": "回复",
|
||||
"Anonymous": "匿名",
|
||||
"Avatar generated from IP address": "由IP生成的头像",
|
||||
|
@ -126,7 +126,7 @@
|
|||
"server error or not responding": "服务器错误或无回应",
|
||||
"Could not post comment: %s": "无法发送评论: %s",
|
||||
"Sending paste…": "粘贴内容提交中…",
|
||||
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "您粘贴内容的链接是<a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(按下 [Ctrl]+[c] 以复制)</span>",
|
||||
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "您粘贴内容的链接是 <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(按下 [Ctrl]+[C] 以复制)</span>",
|
||||
"Delete data": "删除数据",
|
||||
"Could not create paste: %s": "无法创建粘贴:%s",
|
||||
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "无法解密粘贴:URL中缺失解密密钥(是否使用了重定向或者短链接导致密钥丢失?)",
|
||||
|
@ -144,45 +144,46 @@
|
|||
"Source Code": "源代码",
|
||||
"Markdown": "Markdown",
|
||||
"Download attachment": "下载附件",
|
||||
"Cloned: '%s'": "副本: '%s'",
|
||||
"The cloned file '%s' was attached to this paste.": "副本 '%s' 已附加到此粘贴内容。",
|
||||
"Cloned: '%s'": "副本:“%s”",
|
||||
"The cloned file '%s' was attached to this paste.": "副本“%s”已附加到此粘贴内容。",
|
||||
"Attach a file": "添加一个附件",
|
||||
"alternatively drag & drop a file or paste an image from the clipboard": "拖放文件或从剪贴板粘贴图片",
|
||||
"File too large, to display a preview. Please download the attachment.": "文件过大。要显示预览,请下载附件。",
|
||||
"File too large, to display a preview. Please download the attachment.": "文件过大,无法显示预览。请下载附件。",
|
||||
"Remove attachment": "移除附件",
|
||||
"Your browser does not support uploading encrypted files. Please use a newer browser.": "您的浏览器不支持上传加密的文件,请使用更新的浏览器。",
|
||||
"Your browser does not support uploading encrypted files. Please use a newer browser.": "您的浏览器不支持上传加密的文件,请使用新版本的浏览器。",
|
||||
"Invalid attachment.": "无效的附件",
|
||||
"Options": "选项",
|
||||
"Shorten URL": "缩短链接",
|
||||
"Editor": "编辑",
|
||||
"Preview": "预览",
|
||||
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s 的 PATH 变量必须结束于 \"%s\"。 请修改你的 index.php 中的 PATH 变量。",
|
||||
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s 的 PATH 变量必须结束于“%s”。 请修改你的 index.php 中的 PATH 变量。",
|
||||
"Decrypt": "解密",
|
||||
"Enter password": "输入密码",
|
||||
"Loading…": "载入中…",
|
||||
"Decrypting paste…": "正在解密",
|
||||
"Preparing new paste…": "正在准备新的粘贴内容",
|
||||
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "如果这个消息一直存在,请参考 <a href=\"%s\">这里的 参考文档(英文版)</a>进行故障排除。",
|
||||
"+++ no paste text +++": "+++ 没有粘贴内容 +++",
|
||||
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "如果此消息一直存在,请参考 <a href=\"%s\">这里的 FAQ(英文版)</a>排除故障。",
|
||||
"+++ no paste text +++": "+++ 无粘贴内容 +++",
|
||||
"Could not get paste data: %s": "无法获取粘贴数据:%s",
|
||||
"QR code": "二维码",
|
||||
"This website is using an insecure HTTP connection! Please use it only for testing.": "该网站使用了不安全的 HTTP 连接!请仅将其用于测试。",
|
||||
"For more information <a href=\"%s\">see this FAQ entry</a>.": "有关更多信息,<a href=\"%s\">请参阅此常见问题解答</a>。",
|
||||
"For more information <a href=\"%s\">see this FAQ entry</a>.": "有关更多信息,请参阅<a href=\"%s\">此常见问题解答</a>。",
|
||||
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "您的浏览器可能需要 HTTPS 连接才能支持 WebCrypto API。 尝试<a href=\"%s\">切换到 HTTPS</a>。",
|
||||
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "您的浏览器不支持用于 zlib 压缩的 WebAssembly。 您可以创建未压缩的文档,但不能读取压缩的文档。",
|
||||
"waiting on user to provide a password": "请输入密码",
|
||||
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "无法解密数据。 您输入了错误的密码吗? 点顶部的按钮重试。",
|
||||
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "无法解密数据。您是否输入了错误的密码?按顶部的按钮重试。",
|
||||
"Retry": "重试",
|
||||
"Showing raw text…": "显示原始文字…",
|
||||
"Notice:": "注意:",
|
||||
"Notice:": "注意:",
|
||||
"This link will expire after %s.": "这个链接将会在 %s 过期。",
|
||||
"This link can only be accessed once, do not use back or refresh button in your browser.": "这个链接只能被访问一次,请勿使用浏览器中的返回和刷新按钮。",
|
||||
"Link:": "链接地址:",
|
||||
"This link can only be accessed once, do not use back or refresh button in your browser.": "此链接只能被访问一次,请勿使用浏览器中的返回和刷新按钮。",
|
||||
"Link:": "链接:",
|
||||
"Recipient may become aware of your timezone, convert time to UTC?": "收件人可能会知道您的时区,将时间转换为 UTC?",
|
||||
"Use Current Timezone": "使用当前时区",
|
||||
"Convert To UTC": "转换为 UTC",
|
||||
"Close": "关闭",
|
||||
"Encrypted note on PrivateBin": "PrivateBin 上的加密笔记",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "访问这个链接来查看该笔记。 将这个URL发送给任何人即可允许其访问该笔记。",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL."
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "访问此链接来查看该笔记。将此 URL 发送给任何人即可允许其访问该笔记。",
|
||||
"URL shortener may expose your decrypt key in URL.": "短链接服务可能会暴露您在 URL 中的解密密钥。",
|
||||
"Save paste": "保存内容"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "privatebin",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.5",
|
||||
"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": {
|
||||
|
|
|
@ -601,7 +601,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
* @prop {string[]}
|
||||
* @readonly
|
||||
*/
|
||||
const supportedLanguages = ['bg', 'cs', 'de', 'es', 'fr', 'he', 'hu', 'it', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh'];
|
||||
const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh'];
|
||||
|
||||
/**
|
||||
* built in language
|
||||
|
@ -767,7 +767,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
/**
|
||||
* per language functions to use to determine the plural form
|
||||
*
|
||||
* @see {@link http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html}
|
||||
* @see {@link https://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html}
|
||||
* @name I18n.getPluralForm
|
||||
* @function
|
||||
* @param {int} n
|
||||
|
@ -795,7 +795,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
return n % 10 === 1 && n % 100 !== 11 ? 0 : (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
|
||||
case 'sl':
|
||||
return n % 100 === 1 ? 1 : (n % 100 === 2 ? 2 : (n % 100 === 3 || n % 100 === 4 ? 3 : 0));
|
||||
// bg, ca, de, en, es, hu, it, nl, no, pt
|
||||
// bg, ca, de, en, es, et, hu, it, nl, no, pt
|
||||
default:
|
||||
return n !== 1 ? 1 : 0;
|
||||
}
|
||||
|
@ -3525,6 +3525,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$password,
|
||||
$passwordInput,
|
||||
$rawTextButton,
|
||||
$downloadTextButton,
|
||||
$qrCodeLink,
|
||||
$emailLink,
|
||||
$sendButton,
|
||||
|
@ -3666,6 +3667,30 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
newDoc.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* download text
|
||||
*
|
||||
* @name TopNav.downloadText
|
||||
* @private
|
||||
* @function
|
||||
*/
|
||||
function downloadText()
|
||||
{
|
||||
var filename='paste-' + Model.getPasteId() + '.txt';
|
||||
var text = PasteViewer.getText();
|
||||
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||
element.setAttribute('download', filename);
|
||||
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* saves the language in a cookie and reloads the page
|
||||
*
|
||||
|
@ -3676,7 +3701,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
*/
|
||||
function setLanguage(event)
|
||||
{
|
||||
document.cookie = 'lang=' + $(event.target).data('lang');
|
||||
document.cookie = 'lang=' + $(event.target).data('lang') + ';secure';
|
||||
UiHelper.reloadHome();
|
||||
}
|
||||
|
||||
|
@ -3892,6 +3917,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$newButton.removeClass('hidden');
|
||||
$cloneButton.removeClass('hidden');
|
||||
$rawTextButton.removeClass('hidden');
|
||||
$downloadTextButton.removeClass('hidden');
|
||||
$qrCodeLink.removeClass('hidden');
|
||||
|
||||
viewButtonsDisplayed = true;
|
||||
|
@ -3912,6 +3938,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$cloneButton.addClass('hidden');
|
||||
$newButton.addClass('hidden');
|
||||
$rawTextButton.addClass('hidden');
|
||||
$downloadTextButton.addClass('hidden');
|
||||
$qrCodeLink.addClass('hidden');
|
||||
me.hideEmailButton();
|
||||
|
||||
|
@ -4073,6 +4100,17 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$rawTextButton.addClass('hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* only hides the download text button
|
||||
*
|
||||
* @name TopNav.hideRawButton
|
||||
* @function
|
||||
*/
|
||||
me.hideDownloadButton = function()
|
||||
{
|
||||
$downloadTextButton.addClass('hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* only hides the qr code button
|
||||
*
|
||||
|
@ -4334,6 +4372,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$password = $('#password');
|
||||
$passwordInput = $('#passwordinput');
|
||||
$rawTextButton = $('#rawtextbutton');
|
||||
$downloadTextButton = $('#downloadtextbutton');
|
||||
$retryButton = $('#retrybutton');
|
||||
$sendButton = $('#sendbutton');
|
||||
$qrCodeLink = $('#qrcodelink');
|
||||
|
@ -4351,6 +4390,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
$sendButton.click(PasteEncrypter.sendPaste);
|
||||
$cloneButton.click(Controller.clonePaste);
|
||||
$rawTextButton.click(rawText);
|
||||
$downloadTextButton.click(downloadText);
|
||||
$retryButton.click(clickRetryButton);
|
||||
$fileRemoveButton.click(removeAttachment);
|
||||
$qrCodeLink.click(displayQrCode);
|
||||
|
@ -4689,6 +4729,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
TopNav.showEmailButton();
|
||||
|
||||
TopNav.hideRawButton();
|
||||
TopNav.hideDownloadButton();
|
||||
Editor.hide();
|
||||
|
||||
// parse and show text
|
||||
|
|
|
@ -14,7 +14,6 @@ namespace PrivateBin;
|
|||
|
||||
use Exception;
|
||||
use PDO;
|
||||
use PrivateBin\Persistence\DataStore;
|
||||
|
||||
/**
|
||||
* Configuration
|
||||
|
@ -55,7 +54,7 @@ class Configuration
|
|||
'urlshortener' => '',
|
||||
'qrcode' => true,
|
||||
'icon' => 'identicon',
|
||||
'cspheader' => 'default-src \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\' resource:; style-src \'self\'; font-src \'self\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
|
||||
'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\' resource:; style-src \'self\'; font-src \'self\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
|
||||
'zerobincompatibility' => false,
|
||||
'httpwarning' => true,
|
||||
'compression' => 'zlib',
|
||||
|
@ -81,12 +80,11 @@ class Configuration
|
|||
'traffic' => array(
|
||||
'limit' => 10,
|
||||
'header' => null,
|
||||
'dir' => 'data',
|
||||
'exemptedIp' => null,
|
||||
),
|
||||
'purge' => array(
|
||||
'limit' => 300,
|
||||
'batchsize' => 10,
|
||||
'dir' => 'data',
|
||||
),
|
||||
'model' => array(
|
||||
'class' => 'Filesystem',
|
||||
|
@ -103,22 +101,15 @@ class Configuration
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$basePaths = array();
|
||||
$config = array();
|
||||
$basePath = (getenv('CONFIG_PATH') !== false ? getenv('CONFIG_PATH') : PATH . 'cfg') . DIRECTORY_SEPARATOR;
|
||||
$configIni = $basePath . 'conf.ini';
|
||||
$configFile = $basePath . 'conf.php';
|
||||
|
||||
// rename INI files to avoid configuration leakage
|
||||
if (is_readable($configIni)) {
|
||||
DataStore::prependRename($configIni, $configFile, ';');
|
||||
|
||||
// cleanup sample, too
|
||||
$configIniSample = $configIni . '.sample';
|
||||
if (is_readable($configIniSample)) {
|
||||
DataStore::prependRename($configIniSample, $basePath . 'conf.sample.php', ';');
|
||||
$configPath = getenv('CONFIG_PATH');
|
||||
if ($configPath !== false && !empty($configPath)) {
|
||||
$basePaths[] = $configPath;
|
||||
}
|
||||
}
|
||||
|
||||
$basePaths[] = PATH . 'cfg';
|
||||
foreach ($basePaths as $basePath) {
|
||||
$configFile = $basePath . DIRECTORY_SEPARATOR . 'conf.php';
|
||||
if (is_readable($configFile)) {
|
||||
$config = parse_ini_file($configFile, true);
|
||||
foreach (array('main', 'model', 'model_options') as $section) {
|
||||
|
@ -126,6 +117,8 @@ class Configuration
|
|||
throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$opts = '_options';
|
||||
|
@ -152,6 +145,16 @@ class Configuration
|
|||
'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_PERSISTENT => true),
|
||||
);
|
||||
} elseif (
|
||||
$section == 'model_options' && in_array(
|
||||
$this->_configuration['model']['class'],
|
||||
array('GoogleCloudStorage')
|
||||
)
|
||||
) {
|
||||
$values = array(
|
||||
'bucket' => getenv('PRIVATEBIN_GCS_BUCKET') ? getenv('PRIVATEBIN_GCS_BUCKET') : null,
|
||||
'prefix' => 'pastes',
|
||||
);
|
||||
}
|
||||
|
||||
// "*_options" sections don't require all defaults to be set
|
||||
|
|
|
@ -162,7 +162,6 @@ class Controller
|
|||
$this->_model = new Model($this->_conf);
|
||||
$this->_request = new Request;
|
||||
$this->_urlBase = $this->_request->getRequestUri();
|
||||
ServerSalt::setPath($this->_conf->getKey('dir', 'traffic'));
|
||||
|
||||
// set default language
|
||||
$lang = $this->_conf->getKey('languagedefault');
|
||||
|
@ -170,7 +169,7 @@ class Controller
|
|||
// force default language, if language selection is disabled and a default is set
|
||||
if (!$this->_conf->getKey('languageselection') && strlen($lang) == 2) {
|
||||
$_COOKIE['lang'] = $lang;
|
||||
setcookie('lang', $lang);
|
||||
setcookie('lang', $lang, 0, '', '', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,9 +195,10 @@ class Controller
|
|||
*/
|
||||
private function _create()
|
||||
{
|
||||
try {
|
||||
// Ensure last paste from visitors IP address was more than configured amount of seconds ago.
|
||||
ServerSalt::setStore($this->_model->getStore());
|
||||
TrafficLimiter::setConfiguration($this->_conf);
|
||||
TrafficLimiter::setStore($this->_model->getStore());
|
||||
if (!TrafficLimiter::canPass()) {
|
||||
$this->_return_message(
|
||||
1, I18n::_(
|
||||
|
@ -208,10 +208,6 @@ class Controller
|
|||
);
|
||||
return;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->_return_message(1, I18n::_($e->getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->_request->getData();
|
||||
$isComment = array_key_exists('pasteid', $data) &&
|
||||
|
@ -346,10 +342,14 @@ class Controller
|
|||
header('Last-Modified: ' . $time);
|
||||
header('Vary: Accept');
|
||||
header('Content-Security-Policy: ' . $this->_conf->getKey('cspheader'));
|
||||
header('Cross-Origin-Resource-Policy: same-origin');
|
||||
header('Cross-Origin-Embedder-Policy: require-corp');
|
||||
header('Cross-Origin-Opener-Policy: same-origin');
|
||||
header('Permissions-Policy: interest-cohort=()');
|
||||
header('Referrer-Policy: no-referrer');
|
||||
header('X-Xss-Protection: 1; mode=block');
|
||||
header('X-Frame-Options: DENY');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('X-Frame-Options: deny');
|
||||
header('X-XSS-Protection: 1; mode=block');
|
||||
|
||||
// label all the expiration options
|
||||
$expire = array();
|
||||
|
@ -364,7 +364,7 @@ class Controller
|
|||
$languageselection = '';
|
||||
if ($this->_conf->getKey('languageselection')) {
|
||||
$languageselection = I18n::getLanguage();
|
||||
setcookie('lang', $languageselection);
|
||||
setcookie('lang', $languageselection, 0, '', '', true);
|
||||
}
|
||||
|
||||
$page = new View;
|
||||
|
|
|
@ -15,12 +15,12 @@ namespace PrivateBin\Data;
|
|||
/**
|
||||
* AbstractData
|
||||
*
|
||||
* Abstract model for PrivateBin data access, implemented as a singleton.
|
||||
* Abstract model for data access, implemented as a singleton.
|
||||
*/
|
||||
abstract class AbstractData
|
||||
{
|
||||
/**
|
||||
* singleton instance
|
||||
* Singleton instance
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
|
@ -29,9 +29,18 @@ abstract class AbstractData
|
|||
protected static $_instance = null;
|
||||
|
||||
/**
|
||||
* enforce singleton, disable constructor
|
||||
* cache for the traffic limiter
|
||||
*
|
||||
* Instantiate using {@link getInstance()}, privatebin is a singleton object.
|
||||
* @access private
|
||||
* @static
|
||||
* @var array
|
||||
*/
|
||||
protected static $_last_cache = array();
|
||||
|
||||
/**
|
||||
* Enforce singleton, disable constructor
|
||||
*
|
||||
* Instantiate using {@link getInstance()}, this object implements the singleton pattern.
|
||||
*
|
||||
* @access protected
|
||||
*/
|
||||
|
@ -40,9 +49,9 @@ abstract class AbstractData
|
|||
}
|
||||
|
||||
/**
|
||||
* enforce singleton, disable cloning
|
||||
* Enforce singleton, disable cloning
|
||||
*
|
||||
* Instantiate using {@link getInstance()}, privatebin is a singleton object.
|
||||
* Instantiate using {@link getInstance()}, this object implements the singleton pattern.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
|
@ -51,7 +60,7 @@ abstract class AbstractData
|
|||
}
|
||||
|
||||
/**
|
||||
* get instance of singleton
|
||||
* Get instance of singleton
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
|
@ -130,6 +139,46 @@ abstract class AbstractData
|
|||
*/
|
||||
abstract public function existsComment($pasteid, $parentid, $commentid);
|
||||
|
||||
/**
|
||||
* Purge outdated entries.
|
||||
*
|
||||
* @access public
|
||||
* @param string $namespace
|
||||
* @param int $time
|
||||
* @return void
|
||||
*/
|
||||
public function purgeValues($namespace, $time)
|
||||
{
|
||||
if ($namespace === 'traffic_limiter') {
|
||||
foreach (self::$_last_cache as $key => $last_submission) {
|
||||
if ($last_submission <= $time) {
|
||||
unset(self::$_last_cache[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a value.
|
||||
*
|
||||
* @access public
|
||||
* @param string $value
|
||||
* @param string $namespace
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function setValue($value, $namespace, $key = '');
|
||||
|
||||
/**
|
||||
* Load a value.
|
||||
*
|
||||
* @access public
|
||||
* @param string $namespace
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getValue($namespace, $key = '');
|
||||
|
||||
/**
|
||||
* Returns up to batch size number of paste ids that have expired
|
||||
*
|
||||
|
|
|
@ -198,6 +198,7 @@ class Database extends AbstractData
|
|||
$opendiscussion = $paste['adata'][2];
|
||||
$burnafterreading = $paste['adata'][3];
|
||||
}
|
||||
try {
|
||||
return self::_exec(
|
||||
'INSERT INTO ' . self::_sanitizeIdentifier('paste') .
|
||||
' VALUES(?,?,?,?,?,?,?,?,?)',
|
||||
|
@ -213,6 +214,9 @@ class Database extends AbstractData
|
|||
$attachmentname,
|
||||
)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -229,11 +233,14 @@ class Database extends AbstractData
|
|||
}
|
||||
|
||||
self::$_cache[$pasteid] = false;
|
||||
try {
|
||||
$paste = self::_select(
|
||||
'SELECT * FROM ' . self::_sanitizeIdentifier('paste') .
|
||||
' WHERE dataid = ?', array($pasteid), true
|
||||
);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$paste = false;
|
||||
}
|
||||
if ($paste === false) {
|
||||
return false;
|
||||
}
|
||||
|
@ -348,6 +355,7 @@ class Database extends AbstractData
|
|||
$meta[$key] = null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
return self::_exec(
|
||||
'INSERT INTO ' . self::_sanitizeIdentifier('comment') .
|
||||
' VALUES(?,?,?,?,?,?,?)',
|
||||
|
@ -361,6 +369,9 @@ class Database extends AbstractData
|
|||
$meta[$createdKey],
|
||||
)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -416,11 +427,83 @@ class Database extends AbstractData
|
|||
*/
|
||||
public function existsComment($pasteid, $parentid, $commentid)
|
||||
{
|
||||
try {
|
||||
return (bool) self::_select(
|
||||
'SELECT dataid FROM ' . self::_sanitizeIdentifier('comment') .
|
||||
' WHERE pasteid = ? AND parentid = ? AND dataid = ?',
|
||||
array($pasteid, $parentid, $commentid), true
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a value.
|
||||
*
|
||||
* @access public
|
||||
* @param string $value
|
||||
* @param string $namespace
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function setValue($value, $namespace, $key = '')
|
||||
{
|
||||
if ($namespace === 'traffic_limiter') {
|
||||
self::$_last_cache[$key] = $value;
|
||||
try {
|
||||
$value = Json::encode(self::$_last_cache);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return self::_exec(
|
||||
'UPDATE ' . self::_sanitizeIdentifier('config') .
|
||||
' SET value = ? WHERE id = ?',
|
||||
array($value, strtoupper($namespace))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a value.
|
||||
*
|
||||
* @access public
|
||||
* @param string $namespace
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
public function getValue($namespace, $key = '')
|
||||
{
|
||||
$configKey = strtoupper($namespace);
|
||||
$value = $this->_getConfig($configKey);
|
||||
if ($value === '') {
|
||||
// initialize the row, so that setValue can rely on UPDATE queries
|
||||
self::_exec(
|
||||
'INSERT INTO ' . self::_sanitizeIdentifier('config') .
|
||||
' VALUES(?,?)',
|
||||
array($configKey, '')
|
||||
);
|
||||
|
||||
// migrate filesystem based salt into database
|
||||
$file = 'data' . DIRECTORY_SEPARATOR . 'salt.php';
|
||||
if ($namespace === 'salt' && is_readable($file)) {
|
||||
$value = Filesystem::getInstance(array('dir' => 'data'))->getValue('salt');
|
||||
$this->setValue($value, 'salt');
|
||||
@unlink($file);
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
if ($value && $namespace === 'traffic_limiter') {
|
||||
try {
|
||||
self::$_last_cache = Json::decode($value);
|
||||
} catch (Exception $e) {
|
||||
self::$_last_cache = array();
|
||||
}
|
||||
if (array_key_exists($key, self::$_last_cache)) {
|
||||
return self::$_last_cache[$key];
|
||||
}
|
||||
}
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -563,16 +646,19 @@ class Database extends AbstractData
|
|||
* @access private
|
||||
* @static
|
||||
* @param string $key
|
||||
* @throws PDOException
|
||||
* @return string
|
||||
*/
|
||||
private static function _getConfig($key)
|
||||
{
|
||||
try {
|
||||
$row = self::_select(
|
||||
'SELECT value FROM ' . self::_sanitizeIdentifier('config') .
|
||||
' WHERE id = ?', array($key), true
|
||||
);
|
||||
return $row['value'];
|
||||
} catch (PDOException $e) {
|
||||
return '';
|
||||
}
|
||||
return $row ? $row['value'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
|
||||
namespace PrivateBin\Data;
|
||||
|
||||
use PrivateBin\Persistence\DataStore;
|
||||
use Exception;
|
||||
use PrivateBin\Json;
|
||||
|
||||
/**
|
||||
* Filesystem
|
||||
|
@ -21,6 +22,29 @@ use PrivateBin\Persistence\DataStore;
|
|||
*/
|
||||
class Filesystem extends AbstractData
|
||||
{
|
||||
/**
|
||||
* first line in paste or comment files, to protect their contents from browsing exposed data directories
|
||||
*
|
||||
* @const string
|
||||
*/
|
||||
const PROTECTION_LINE = '<?php http_response_code(403); /*';
|
||||
|
||||
/**
|
||||
* line in generated .htaccess files, to protect exposed directories from being browsable on apache web servers
|
||||
*
|
||||
* @const string
|
||||
*/
|
||||
const HTACCESS_LINE = 'Require all denied';
|
||||
|
||||
/**
|
||||
* path in which to persist something
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_path = 'data';
|
||||
|
||||
/**
|
||||
* get instance of singleton
|
||||
*
|
||||
|
@ -40,7 +64,7 @@ class Filesystem extends AbstractData
|
|||
is_array($options) &&
|
||||
array_key_exists('dir', $options)
|
||||
) {
|
||||
DataStore::setPath($options['dir']);
|
||||
self::$_path = $options['dir'];
|
||||
}
|
||||
return self::$_instance;
|
||||
}
|
||||
|
@ -63,7 +87,7 @@ class Filesystem extends AbstractData
|
|||
if (!is_dir($storagedir)) {
|
||||
mkdir($storagedir, 0700, true);
|
||||
}
|
||||
return DataStore::store($file, $paste);
|
||||
return self::_store($file, $paste);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,12 +99,13 @@ class Filesystem extends AbstractData
|
|||
*/
|
||||
public function read($pasteid)
|
||||
{
|
||||
if (!$this->exists($pasteid)) {
|
||||
if (
|
||||
!$this->exists($pasteid) ||
|
||||
!$paste = self::_get(self::_dataid2path($pasteid) . $pasteid . '.php')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return self::upgradePreV1Format(
|
||||
DataStore::get(self::_dataid2path($pasteid) . $pasteid . '.php')
|
||||
);
|
||||
return self::upgradePreV1Format($paste);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,7 +152,7 @@ class Filesystem extends AbstractData
|
|||
$pastePath = $basePath . '.php';
|
||||
// convert to PHP protected files if needed
|
||||
if (is_readable($basePath)) {
|
||||
DataStore::prependRename($basePath, $pastePath);
|
||||
self::_prependRename($basePath, $pastePath);
|
||||
|
||||
// convert comments, too
|
||||
$discdir = self::_dataid2discussionpath($pasteid);
|
||||
|
@ -136,7 +161,7 @@ class Filesystem extends AbstractData
|
|||
while (false !== ($filename = $dir->read())) {
|
||||
if (substr($filename, -4) !== '.php' && strlen($filename) >= 16) {
|
||||
$commentFilename = $discdir . $filename . '.php';
|
||||
DataStore::prependRename($discdir . $filename, $commentFilename);
|
||||
self::_prependRename($discdir . $filename, $commentFilename);
|
||||
}
|
||||
}
|
||||
$dir->close();
|
||||
|
@ -165,7 +190,7 @@ class Filesystem extends AbstractData
|
|||
if (!is_dir($storagedir)) {
|
||||
mkdir($storagedir, 0700, true);
|
||||
}
|
||||
return DataStore::store($file, $comment);
|
||||
return self::_store($file, $comment);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -187,7 +212,7 @@ class Filesystem extends AbstractData
|
|||
// - commentid is the comment identifier itself.
|
||||
// - parentid is the comment this comment replies to (It can be pasteid)
|
||||
if (is_file($discdir . $filename)) {
|
||||
$comment = DataStore::get($discdir . $filename);
|
||||
$comment = self::_get($discdir . $filename);
|
||||
$items = explode('.', $filename);
|
||||
// Add some meta information not contained in file.
|
||||
$comment['id'] = $items[1];
|
||||
|
@ -223,6 +248,97 @@ class Filesystem extends AbstractData
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a value.
|
||||
*
|
||||
* @access public
|
||||
* @param string $value
|
||||
* @param string $namespace
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function setValue($value, $namespace, $key = '')
|
||||
{
|
||||
switch ($namespace) {
|
||||
case 'purge_limiter':
|
||||
return self::_storeString(
|
||||
self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php',
|
||||
'<?php' . PHP_EOL . '$GLOBALS[\'purge_limiter\'] = ' . $value . ';'
|
||||
);
|
||||
case 'salt':
|
||||
return self::_storeString(
|
||||
self::$_path . DIRECTORY_SEPARATOR . 'salt.php',
|
||||
'<?php # |' . $value . '|'
|
||||
);
|
||||
case 'traffic_limiter':
|
||||
self::$_last_cache[$key] = $value;
|
||||
return self::_storeString(
|
||||
self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php',
|
||||
'<?php' . PHP_EOL . '$GLOBALS[\'traffic_limiter\'] = ' . var_export(self::$_last_cache, true) . ';'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a value.
|
||||
*
|
||||
* @access public
|
||||
* @param string $namespace
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
public function getValue($namespace, $key = '')
|
||||
{
|
||||
switch ($namespace) {
|
||||
case 'purge_limiter':
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php';
|
||||
if (is_readable($file)) {
|
||||
require $file;
|
||||
return $GLOBALS['purge_limiter'];
|
||||
}
|
||||
break;
|
||||
case 'salt':
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . 'salt.php';
|
||||
if (is_readable($file)) {
|
||||
$items = explode('|', file_get_contents($file));
|
||||
if (is_array($items) && count($items) == 3) {
|
||||
return $items[1];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'traffic_limiter':
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php';
|
||||
if (is_readable($file)) {
|
||||
require $file;
|
||||
self::$_last_cache = $GLOBALS['traffic_limiter'];
|
||||
if (array_key_exists($key, self::$_last_cache)) {
|
||||
return self::$_last_cache[$key];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* get the data
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @return array|false $data
|
||||
*/
|
||||
private static function _get($filename)
|
||||
{
|
||||
return Json::decode(
|
||||
substr(
|
||||
file_get_contents($filename),
|
||||
strlen(self::PROTECTION_LINE . PHP_EOL)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns up to batch size number of paste ids that have expired
|
||||
*
|
||||
|
@ -233,9 +349,8 @@ class Filesystem extends AbstractData
|
|||
protected function _getExpiredPastes($batchsize)
|
||||
{
|
||||
$pastes = array();
|
||||
$mainpath = DataStore::getPath();
|
||||
$firstLevel = array_filter(
|
||||
scandir($mainpath),
|
||||
scandir(self::$_path),
|
||||
'self::_isFirstLevelDir'
|
||||
);
|
||||
if (count($firstLevel) > 0) {
|
||||
|
@ -243,7 +358,7 @@ class Filesystem extends AbstractData
|
|||
for ($i = 0, $max = $batchsize * 10; $i < $max; ++$i) {
|
||||
$firstKey = array_rand($firstLevel);
|
||||
$secondLevel = array_filter(
|
||||
scandir($mainpath . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]),
|
||||
scandir(self::$_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]),
|
||||
'self::_isSecondLevelDir'
|
||||
);
|
||||
|
||||
|
@ -254,7 +369,7 @@ class Filesystem extends AbstractData
|
|||
}
|
||||
|
||||
$secondKey = array_rand($secondLevel);
|
||||
$path = $mainpath . DIRECTORY_SEPARATOR .
|
||||
$path = self::$_path . DIRECTORY_SEPARATOR .
|
||||
$firstLevel[$firstKey] . DIRECTORY_SEPARATOR .
|
||||
$secondLevel[$secondKey];
|
||||
if (!is_dir($path)) {
|
||||
|
@ -314,10 +429,9 @@ class Filesystem extends AbstractData
|
|||
*/
|
||||
private static function _dataid2path($dataid)
|
||||
{
|
||||
return DataStore::getPath(
|
||||
return self::$_path . DIRECTORY_SEPARATOR .
|
||||
substr($dataid, 0, 2) . DIRECTORY_SEPARATOR .
|
||||
substr($dataid, 2, 2) . DIRECTORY_SEPARATOR
|
||||
);
|
||||
substr($dataid, 2, 2) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -347,7 +461,7 @@ class Filesystem extends AbstractData
|
|||
private static function _isFirstLevelDir($element)
|
||||
{
|
||||
return self::_isSecondLevelDir($element) &&
|
||||
is_dir(DataStore::getPath($element));
|
||||
is_dir(self::$_path . DIRECTORY_SEPARATOR . $element);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -362,4 +476,97 @@ class Filesystem extends AbstractData
|
|||
{
|
||||
return (bool) preg_match('/^[a-f0-9]{2}$/', $element);
|
||||
}
|
||||
|
||||
/**
|
||||
* store the data
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
private static function _store($filename, array $data)
|
||||
{
|
||||
try {
|
||||
return self::_storeString(
|
||||
$filename,
|
||||
self::PROTECTION_LINE . PHP_EOL . Json::encode($data)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* store a string
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @param string $data
|
||||
* @return bool
|
||||
*/
|
||||
private static function _storeString($filename, $data)
|
||||
{
|
||||
// Create storage directory if it does not exist.
|
||||
if (!is_dir(self::$_path)) {
|
||||
if (!@mkdir(self::$_path, 0700)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
if (!is_file($file)) {
|
||||
$writtenBytes = 0;
|
||||
if ($fileCreated = @touch($file)) {
|
||||
$writtenBytes = @file_put_contents(
|
||||
$file,
|
||||
self::HTACCESS_LINE . PHP_EOL,
|
||||
LOCK_EX
|
||||
);
|
||||
}
|
||||
if (
|
||||
$fileCreated === false ||
|
||||
$writtenBytes === false ||
|
||||
$writtenBytes < strlen(self::HTACCESS_LINE . PHP_EOL)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$fileCreated = true;
|
||||
$writtenBytes = 0;
|
||||
if (!is_file($filename)) {
|
||||
$fileCreated = @touch($filename);
|
||||
}
|
||||
if ($fileCreated) {
|
||||
$writtenBytes = @file_put_contents($filename, $data, LOCK_EX);
|
||||
}
|
||||
if ($fileCreated === false || $writtenBytes === false || $writtenBytes < strlen($data)) {
|
||||
return false;
|
||||
}
|
||||
@chmod($filename, 0640); // protect file from access by other users on the host
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* rename a file, prepending the protection line at the beginning
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $srcFile
|
||||
* @param string $destFile
|
||||
* @return void
|
||||
*/
|
||||
private static function _prependRename($srcFile, $destFile)
|
||||
{
|
||||
// don't overwrite already converted file
|
||||
if (!is_readable($destFile)) {
|
||||
$handle = fopen($srcFile, 'r', false, stream_context_create());
|
||||
file_put_contents($destFile, self::PROTECTION_LINE . PHP_EOL);
|
||||
file_put_contents($destFile, $handle, FILE_APPEND);
|
||||
fclose($handle);
|
||||
}
|
||||
unlink($srcFile);
|
||||
}
|
||||
}
|
||||
|
|
346
lib/Data/GoogleCloudStorage.php
Normal file
346
lib/Data/GoogleCloudStorage.php
Normal file
|
@ -0,0 +1,346 @@
|
|||
<?php
|
||||
|
||||
namespace PrivateBin\Data;
|
||||
|
||||
use Exception;
|
||||
use Google\Cloud\Core\Exception\NotFoundException;
|
||||
use Google\Cloud\Storage\Bucket;
|
||||
use Google\Cloud\Storage\StorageClient;
|
||||
use PrivateBin\Json;
|
||||
|
||||
class GoogleCloudStorage extends AbstractData
|
||||
{
|
||||
/**
|
||||
* GCS client
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var StorageClient
|
||||
*/
|
||||
private static $_client = null;
|
||||
|
||||
/**
|
||||
* GCS bucket
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var Bucket
|
||||
*/
|
||||
private static $_bucket = null;
|
||||
|
||||
/**
|
||||
* object prefix
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_prefix = 'pastes';
|
||||
|
||||
/**
|
||||
* returns a Google Cloud Storage data backend.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param array $options
|
||||
* @return GoogleCloudStorage
|
||||
*/
|
||||
public static function getInstance(array $options)
|
||||
{
|
||||
// if needed initialize the singleton
|
||||
if (!(self::$_instance instanceof self)) {
|
||||
self::$_instance = new self;
|
||||
}
|
||||
|
||||
$bucket = null;
|
||||
if (getenv('PRIVATEBIN_GCS_BUCKET')) {
|
||||
$bucket = getenv('PRIVATEBIN_GCS_BUCKET');
|
||||
}
|
||||
if (is_array($options) && array_key_exists('bucket', $options)) {
|
||||
$bucket = $options['bucket'];
|
||||
}
|
||||
if (is_array($options) && array_key_exists('prefix', $options)) {
|
||||
self::$_prefix = $options['prefix'];
|
||||
}
|
||||
|
||||
if (empty(self::$_client)) {
|
||||
self::$_client = class_exists('StorageClientStub', false) ?
|
||||
new \StorageClientStub(array()) :
|
||||
new StorageClient(array('suppressKeyFileNotice' => true));
|
||||
}
|
||||
self::$_bucket = self::$_client->bucket($bucket);
|
||||
|
||||
return self::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the google storage object key for $pasteid in self::$_bucket.
|
||||
*
|
||||
* @access private
|
||||
* @param $pasteid string to get the key for
|
||||
* @return string
|
||||
*/
|
||||
private function _getKey($pasteid)
|
||||
{
|
||||
if (self::$_prefix != '') {
|
||||
return self::$_prefix . '/' . $pasteid;
|
||||
}
|
||||
return $pasteid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the payload in the self::$_bucket under the specified key.
|
||||
* The entire payload is stored as a JSON document. The metadata is replicated
|
||||
* as the GCS object's metadata except for the fields attachment, attachmentname
|
||||
* and salt.
|
||||
*
|
||||
* @param $key string to store the payload under
|
||||
* @param $payload array to store
|
||||
* @return bool true if successful, otherwise false.
|
||||
*/
|
||||
private function _upload($key, $payload)
|
||||
{
|
||||
$metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array();
|
||||
unset($metadata['attachment'], $metadata['attachmentname'], $metadata['salt']);
|
||||
foreach ($metadata as $k => $v) {
|
||||
$metadata[$k] = strval($v);
|
||||
}
|
||||
try {
|
||||
self::$_bucket->upload(Json::encode($payload), array(
|
||||
'name' => $key,
|
||||
'chunkSize' => 262144,
|
||||
'predefinedAcl' => 'private',
|
||||
'metadata' => array(
|
||||
'content-type' => 'application/json',
|
||||
'metadata' => $metadata,
|
||||
),
|
||||
));
|
||||
} catch (Exception $e) {
|
||||
error_log('failed to upload ' . $key . ' to ' . self::$_bucket->name() . ', ' .
|
||||
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function create($pasteid, array $paste)
|
||||
{
|
||||
if ($this->exists($pasteid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->_upload($this->_getKey($pasteid), $paste);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function read($pasteid)
|
||||
{
|
||||
try {
|
||||
$o = self::$_bucket->object($this->_getKey($pasteid));
|
||||
$data = $o->downloadAsString();
|
||||
return Json::decode($data);
|
||||
} catch (NotFoundException $e) {
|
||||
return false;
|
||||
} catch (Exception $e) {
|
||||
error_log('failed to read ' . $pasteid . ' from ' . self::$_bucket->name() . ', ' .
|
||||
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function delete($pasteid)
|
||||
{
|
||||
$name = $this->_getKey($pasteid);
|
||||
|
||||
try {
|
||||
foreach (self::$_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) {
|
||||
try {
|
||||
self::$_bucket->object($comment->name())->delete();
|
||||
} catch (NotFoundException $e) {
|
||||
// ignore if already deleted.
|
||||
}
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
// there are no discussions associated with the paste
|
||||
}
|
||||
|
||||
try {
|
||||
self::$_bucket->object($name)->delete();
|
||||
} catch (NotFoundException $e) {
|
||||
// ignore if already deleted
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function exists($pasteid)
|
||||
{
|
||||
$o = self::$_bucket->object($this->_getKey($pasteid));
|
||||
return $o->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createComment($pasteid, $parentid, $commentid, array $comment)
|
||||
{
|
||||
if ($this->existsComment($pasteid, $parentid, $commentid)) {
|
||||
return false;
|
||||
}
|
||||
$key = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
|
||||
return $this->_upload($key, $comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function readComments($pasteid)
|
||||
{
|
||||
$comments = array();
|
||||
$prefix = $this->_getKey($pasteid) . '/discussion/';
|
||||
try {
|
||||
foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $key) {
|
||||
$comment = JSON::decode(self::$_bucket->object($key->name())->downloadAsString());
|
||||
$comment['id'] = basename($key->name());
|
||||
$slot = $this->getOpenSlot($comments, (int) $comment['meta']['created']);
|
||||
$comments[$slot] = $comment;
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
// no comments found
|
||||
}
|
||||
return $comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function existsComment($pasteid, $parentid, $commentid)
|
||||
{
|
||||
$name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
|
||||
$o = self::$_bucket->object($name);
|
||||
return $o->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function purgeValues($namespace, $time)
|
||||
{
|
||||
$path = 'config/' . $namespace;
|
||||
try {
|
||||
foreach (self::$_bucket->objects(array('prefix' => $path)) as $object) {
|
||||
$name = $object->name();
|
||||
if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') {
|
||||
continue;
|
||||
}
|
||||
$info = $object->info();
|
||||
if (key_exists('metadata', $info) && key_exists('value', $info['metadata'])) {
|
||||
$value = $info['metadata']['value'];
|
||||
if (is_numeric($value) && intval($value) < $time) {
|
||||
try {
|
||||
$object->delete();
|
||||
} catch (NotFoundException $e) {
|
||||
// deleted by another instance.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
// no objects in the bucket yet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For GoogleCloudStorage, the value will also be stored in the metadata for the
|
||||
* namespaces traffic_limiter and purge_limiter.
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setValue($value, $namespace, $key = '')
|
||||
{
|
||||
if ($key === '') {
|
||||
$key = 'config/' . $namespace;
|
||||
} else {
|
||||
$key = 'config/' . $namespace . '/' . $key;
|
||||
}
|
||||
|
||||
$metadata = array('namespace' => $namespace);
|
||||
if ($namespace != 'salt') {
|
||||
$metadata['value'] = strval($value);
|
||||
}
|
||||
try {
|
||||
self::$_bucket->upload($value, array(
|
||||
'name' => $key,
|
||||
'chunkSize' => 262144,
|
||||
'predefinedAcl' => 'private',
|
||||
'metadata' => array(
|
||||
'content-type' => 'application/json',
|
||||
'metadata' => $metadata,
|
||||
),
|
||||
));
|
||||
} catch (Exception $e) {
|
||||
error_log('failed to set key ' . $key . ' to ' . self::$_bucket->name() . ', ' .
|
||||
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getValue($namespace, $key = '')
|
||||
{
|
||||
if ($key === '') {
|
||||
$key = 'config/' . $namespace;
|
||||
} else {
|
||||
$key = 'config/' . $namespace . '/' . $key;
|
||||
}
|
||||
try {
|
||||
$o = self::$_bucket->object($key);
|
||||
return $o->downloadAsString();
|
||||
} catch (NotFoundException $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _getExpiredPastes($batchsize)
|
||||
{
|
||||
$expired = array();
|
||||
|
||||
$now = time();
|
||||
$prefix = self::$_prefix;
|
||||
if ($prefix != '') {
|
||||
$prefix .= '/';
|
||||
}
|
||||
try {
|
||||
foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $object) {
|
||||
$metadata = $object->info()['metadata'];
|
||||
if ($metadata != null && array_key_exists('expire_date', $metadata)) {
|
||||
$expire_at = intval($metadata['expire_date']);
|
||||
if ($expire_at != 0 && $expire_at < $now) {
|
||||
array_push($expired, basename($object->name()));
|
||||
}
|
||||
}
|
||||
|
||||
if (count($expired) > $batchsize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
// no objects in the bucket yet
|
||||
}
|
||||
return $expired;
|
||||
}
|
||||
}
|
|
@ -52,6 +52,11 @@ class FormatV2
|
|||
}
|
||||
}
|
||||
|
||||
// Make sure adata is an array.
|
||||
if (!is_array($message['adata'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cipherParams = $isComment ? $message['adata'] : $message['adata'][0];
|
||||
|
||||
// Make sure some fields are base64 data:
|
||||
|
|
|
@ -305,7 +305,7 @@ class I18n
|
|||
/**
|
||||
* determines the plural form to use based on current language and given number
|
||||
*
|
||||
* From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
|
||||
* From: https://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
|
@ -334,7 +334,7 @@ class I18n
|
|||
return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
|
||||
case 'sl':
|
||||
return $n % 100 == 1 ? 1 : ($n % 100 == 2 ? 2 : ($n % 100 == 3 || $n % 100 == 4 ? 3 : 0));
|
||||
// bg, ca, de, en, es, hu, it, nl, no, pt
|
||||
// bg, ca, de, en, es, et, hu, it, nl, no, pt
|
||||
default:
|
||||
return $n != 1 ? 1 : 0;
|
||||
}
|
||||
|
|
|
@ -44,13 +44,13 @@ class Json
|
|||
* @static
|
||||
* @param string $input
|
||||
* @throws Exception
|
||||
* @return array
|
||||
* @return mixed
|
||||
*/
|
||||
public static function decode($input)
|
||||
{
|
||||
$array = json_decode($input, true);
|
||||
$output = json_decode($input, true);
|
||||
self::_detectError();
|
||||
return $array;
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,7 +54,7 @@ class Model
|
|||
*/
|
||||
public function getPaste($pasteId = null)
|
||||
{
|
||||
$paste = new Paste($this->_conf, $this->_getStore());
|
||||
$paste = new Paste($this->_conf, $this->getStore());
|
||||
if ($pasteId !== null) {
|
||||
$paste->setId($pasteId);
|
||||
}
|
||||
|
@ -67,8 +67,9 @@ class Model
|
|||
public function purge()
|
||||
{
|
||||
PurgeLimiter::setConfiguration($this->_conf);
|
||||
PurgeLimiter::setStore($this->getStore());
|
||||
if (PurgeLimiter::canPurge()) {
|
||||
$this->_getStore()->purge($this->_conf->getKey('batchsize', 'purge'));
|
||||
$this->getStore()->purge($this->_conf->getKey('batchsize', 'purge'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +78,7 @@ class Model
|
|||
*
|
||||
* @return Data\AbstractData
|
||||
*/
|
||||
private function _getStore()
|
||||
public function getStore()
|
||||
{
|
||||
if ($this->_store === null) {
|
||||
$this->_store = forward_static_call(
|
||||
|
|
|
@ -93,7 +93,7 @@ class Paste extends AbstractModel
|
|||
}
|
||||
|
||||
$this->_data['meta']['created'] = time();
|
||||
$this->_data['meta']['salt'] = serversalt::generate();
|
||||
$this->_data['meta']['salt'] = ServerSalt::generate();
|
||||
|
||||
// store paste
|
||||
if (
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
namespace PrivateBin\Persistence;
|
||||
|
||||
use Exception;
|
||||
use PrivateBin\Data\AbstractData;
|
||||
|
||||
/**
|
||||
* AbstractPersistence
|
||||
|
@ -22,104 +22,23 @@ use Exception;
|
|||
abstract class AbstractPersistence
|
||||
{
|
||||
/**
|
||||
* path in which to persist something
|
||||
* data storage to use to persist something
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
* @var AbstractData
|
||||
*/
|
||||
private static $_path = 'data';
|
||||
protected static $_store;
|
||||
|
||||
/**
|
||||
* set the path
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $path
|
||||
* @param AbstractData $store
|
||||
*/
|
||||
public static function setPath($path)
|
||||
public static function setStore(AbstractData $store)
|
||||
{
|
||||
self::$_path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the path
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public static function getPath($filename = null)
|
||||
{
|
||||
if (strlen($filename)) {
|
||||
return self::$_path . DIRECTORY_SEPARATOR . $filename;
|
||||
} else {
|
||||
return self::$_path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the file exists
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _exists($filename)
|
||||
{
|
||||
self::_initialize();
|
||||
return is_file(self::$_path . DIRECTORY_SEPARATOR . $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepares path for storage
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static function _initialize()
|
||||
{
|
||||
// Create storage directory if it does not exist.
|
||||
if (!is_dir(self::$_path)) {
|
||||
if (!@mkdir(self::$_path, 0700)) {
|
||||
throw new Exception('unable to create directory ' . self::$_path, 10);
|
||||
}
|
||||
}
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
if (!is_file($file)) {
|
||||
$writtenBytes = @file_put_contents(
|
||||
$file,
|
||||
'Require all denied' . PHP_EOL,
|
||||
LOCK_EX
|
||||
);
|
||||
if ($writtenBytes === false || $writtenBytes < 19) {
|
||||
throw new Exception('unable to write to file ' . $file, 11);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* store the data
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @param string $data
|
||||
* @throws Exception
|
||||
* @return string
|
||||
*/
|
||||
protected static function _store($filename, $data)
|
||||
{
|
||||
self::_initialize();
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . $filename;
|
||||
$writtenBytes = @file_put_contents($file, $data, LOCK_EX);
|
||||
if ($writtenBytes === false || $writtenBytes < strlen($data)) {
|
||||
throw new Exception('unable to write to file ' . $file, 13);
|
||||
}
|
||||
@chmod($file, 0640); // protect file access
|
||||
return $file;
|
||||
self::$_store = $store;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* PrivateBin
|
||||
*
|
||||
* a zero-knowledge paste bin
|
||||
*
|
||||
* @link https://github.com/PrivateBin/PrivateBin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 1.3.5
|
||||
*/
|
||||
|
||||
namespace PrivateBin\Persistence;
|
||||
|
||||
use Exception;
|
||||
use PrivateBin\Json;
|
||||
|
||||
/**
|
||||
* DataStore
|
||||
*
|
||||
* Handles data storage for Data\Filesystem.
|
||||
*/
|
||||
class DataStore extends AbstractPersistence
|
||||
{
|
||||
/**
|
||||
* first line in file, to protect its contents
|
||||
*
|
||||
* @const string
|
||||
*/
|
||||
const PROTECTION_LINE = '<?php http_response_code(403); /*';
|
||||
|
||||
/**
|
||||
* store the data
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
public static function store($filename, $data)
|
||||
{
|
||||
$path = self::getPath();
|
||||
if (strpos($filename, $path) === 0) {
|
||||
$filename = substr($filename, strlen($path));
|
||||
}
|
||||
try {
|
||||
self::_store(
|
||||
$filename,
|
||||
self::PROTECTION_LINE . PHP_EOL . Json::encode($data)
|
||||
);
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the data
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @return array|false $data
|
||||
*/
|
||||
public static function get($filename)
|
||||
{
|
||||
return Json::decode(
|
||||
substr(
|
||||
file_get_contents($filename),
|
||||
strlen(self::PROTECTION_LINE . PHP_EOL)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* rename a file, prepending the protection line at the beginning
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $srcFile
|
||||
* @param string $destFile
|
||||
* @param string $prefix (optional)
|
||||
* @return void
|
||||
*/
|
||||
public static function prependRename($srcFile, $destFile, $prefix = '')
|
||||
{
|
||||
// don't overwrite already converted file
|
||||
if (!is_readable($destFile)) {
|
||||
$handle = fopen($srcFile, 'r', false, stream_context_create());
|
||||
file_put_contents($destFile, $prefix . self::PROTECTION_LINE . PHP_EOL);
|
||||
file_put_contents($destFile, $handle, FILE_APPEND);
|
||||
fclose($handle);
|
||||
}
|
||||
unlink($srcFile);
|
||||
}
|
||||
}
|
|
@ -52,7 +52,6 @@ class PurgeLimiter extends AbstractPersistence
|
|||
public static function setConfiguration(Configuration $conf)
|
||||
{
|
||||
self::setLimit($conf->getKey('limit', 'purge'));
|
||||
self::setPath($conf->getKey('dir', 'purge'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,7 +59,6 @@ class PurgeLimiter extends AbstractPersistence
|
|||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @throws \Exception
|
||||
* @return bool
|
||||
*/
|
||||
public static function canPurge()
|
||||
|
@ -71,17 +69,14 @@ class PurgeLimiter extends AbstractPersistence
|
|||
}
|
||||
|
||||
$now = time();
|
||||
$file = 'purge_limiter.php';
|
||||
if (self::_exists($file)) {
|
||||
require self::getPath($file);
|
||||
$pl = $GLOBALS['purge_limiter'];
|
||||
$pl = (int) self::$_store->getValue('purge_limiter');
|
||||
if ($pl + self::$_limit >= $now) {
|
||||
return false;
|
||||
}
|
||||
$hasStored = self::$_store->setValue((string) $now, 'purge_limiter');
|
||||
if (!$hasStored) {
|
||||
error_log('failed to store the purge limiter, skipping purge cycle to avoid getting stuck in a purge loop');
|
||||
}
|
||||
|
||||
$content = '<?php' . PHP_EOL . '$GLOBALS[\'purge_limiter\'] = ' . $now . ';';
|
||||
self::_store($file, $content);
|
||||
return true;
|
||||
return $hasStored;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
namespace PrivateBin\Persistence;
|
||||
|
||||
use Exception;
|
||||
use PrivateBin\Data\AbstractData;
|
||||
|
||||
/**
|
||||
* ServerSalt
|
||||
|
@ -26,15 +26,6 @@ use Exception;
|
|||
*/
|
||||
class ServerSalt extends AbstractPersistence
|
||||
{
|
||||
/**
|
||||
* file where salt is saved to
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_file = 'salt.php';
|
||||
|
||||
/**
|
||||
* generated salt
|
||||
*
|
||||
|
@ -53,8 +44,7 @@ class ServerSalt extends AbstractPersistence
|
|||
*/
|
||||
public static function generate()
|
||||
{
|
||||
$randomSalt = bin2hex(random_bytes(256));
|
||||
return $randomSalt;
|
||||
return bin2hex(random_bytes(256));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,7 +52,6 @@ class ServerSalt extends AbstractPersistence
|
|||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @throws Exception
|
||||
* @return string
|
||||
*/
|
||||
public static function get()
|
||||
|
@ -71,20 +60,14 @@ class ServerSalt extends AbstractPersistence
|
|||
return self::$_salt;
|
||||
}
|
||||
|
||||
if (self::_exists(self::$_file)) {
|
||||
if (is_readable(self::getPath(self::$_file))) {
|
||||
$items = explode('|', file_get_contents(self::getPath(self::$_file)));
|
||||
}
|
||||
if (!isset($items) || !is_array($items) || count($items) != 3) {
|
||||
throw new Exception('unable to read file ' . self::getPath(self::$_file), 20);
|
||||
}
|
||||
self::$_salt = $items[1];
|
||||
$salt = self::$_store->getValue('salt');
|
||||
if ($salt) {
|
||||
self::$_salt = $salt;
|
||||
} else {
|
||||
self::$_salt = self::generate();
|
||||
self::_store(
|
||||
self::$_file,
|
||||
'<?php # |' . self::$_salt . '|'
|
||||
);
|
||||
if (!self::$_store->setValue(self::$_salt, 'salt')) {
|
||||
error_log('failed to store the server salt, delete tokens, traffic limiter and user icons won\'t work');
|
||||
}
|
||||
}
|
||||
return self::$_salt;
|
||||
}
|
||||
|
@ -94,11 +77,11 @@ class ServerSalt extends AbstractPersistence
|
|||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $path
|
||||
* @param AbstractData $store
|
||||
*/
|
||||
public static function setPath($path)
|
||||
public static function setStore(AbstractData $store)
|
||||
{
|
||||
self::$_salt = '';
|
||||
parent::setPath($path);
|
||||
parent::setStore($store);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PrivateBin
|
||||
*
|
||||
|
@ -12,6 +13,7 @@
|
|||
|
||||
namespace PrivateBin\Persistence;
|
||||
|
||||
use IPLib\Factory;
|
||||
use PrivateBin\Configuration;
|
||||
|
||||
/**
|
||||
|
@ -30,6 +32,15 @@ class TrafficLimiter extends AbstractPersistence
|
|||
*/
|
||||
private static $_limit = 10;
|
||||
|
||||
/**
|
||||
* listed ips are exempted from limits, defaults to null
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string|null
|
||||
*/
|
||||
private static $_exemptedIp = null;
|
||||
|
||||
/**
|
||||
* key to fetch IP address
|
||||
*
|
||||
|
@ -51,6 +62,18 @@ class TrafficLimiter extends AbstractPersistence
|
|||
self::$_limit = $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* set a list of ip(ranges) as string
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $exemptedIps
|
||||
*/
|
||||
public static function setExemptedIp($exemptedIp)
|
||||
{
|
||||
self::$_exemptedIp = $exemptedIp;
|
||||
}
|
||||
|
||||
/**
|
||||
* set configuration options of the traffic limiter
|
||||
*
|
||||
|
@ -61,7 +84,8 @@ class TrafficLimiter extends AbstractPersistence
|
|||
public static function setConfiguration(Configuration $conf)
|
||||
{
|
||||
self::setLimit($conf->getKey('limit', 'traffic'));
|
||||
self::setPath($conf->getKey('dir', 'traffic'));
|
||||
self::setExemptedIp($conf->getKey('exemptedIp', 'traffic'));
|
||||
|
||||
if (($option = $conf->getKey('header', 'traffic')) !== null) {
|
||||
$httpHeader = 'HTTP_' . $option;
|
||||
if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) {
|
||||
|
@ -83,6 +107,34 @@ class TrafficLimiter extends AbstractPersistence
|
|||
return hash_hmac($algo, $_SERVER[self::$_ipKey], ServerSalt::get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate $_ipKey against configured ipranges. If matched we will ignore the ip
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $ipRange
|
||||
* @return bool
|
||||
*/
|
||||
private static function matchIp($ipRange = null)
|
||||
{
|
||||
if (is_string($ipRange)) {
|
||||
$ipRange = trim($ipRange);
|
||||
}
|
||||
$address = Factory::addressFromString($_SERVER[self::$_ipKey]);
|
||||
$range = Factory::rangeFromString($ipRange);
|
||||
|
||||
// address could not be parsed, we might not be in IP space and try a string comparison instead
|
||||
if (is_null($address)) {
|
||||
return $_SERVER[self::$_ipKey] === $ipRange;
|
||||
}
|
||||
// range could not be parsed, possibly an invalid ip range given in config
|
||||
if (is_null($range)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $address->matches($range);
|
||||
}
|
||||
|
||||
/**
|
||||
* traffic limiter
|
||||
*
|
||||
|
@ -90,7 +142,6 @@ class TrafficLimiter extends AbstractPersistence
|
|||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @throws \Exception
|
||||
* @return bool
|
||||
*/
|
||||
public static function canPass()
|
||||
|
@ -100,35 +151,30 @@ class TrafficLimiter extends AbstractPersistence
|
|||
return true;
|
||||
}
|
||||
|
||||
$file = 'traffic_limiter.php';
|
||||
if (self::_exists($file)) {
|
||||
require self::getPath($file);
|
||||
$tl = $GLOBALS['traffic_limiter'];
|
||||
} else {
|
||||
$tl = array();
|
||||
// Check if $_ipKey is exempted from ratelimiting
|
||||
if (!is_null(self::$_exemptedIp)) {
|
||||
$exIp_array = explode(',', self::$_exemptedIp);
|
||||
foreach ($exIp_array as $ipRange) {
|
||||
if (self::matchIp($ipRange) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// purge file of expired hashes to keep it small
|
||||
$now = time();
|
||||
foreach ($tl as $key => $time) {
|
||||
if ($time + self::$_limit < $now) {
|
||||
unset($tl[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// this hash is used as an array key, hence a shorter algo is used
|
||||
$hash = self::getHash('sha256');
|
||||
if (array_key_exists($hash, $tl) && ($tl[$hash] + self::$_limit >= $now)) {
|
||||
$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[$hash] = time();
|
||||
$tl = time();
|
||||
$result = true;
|
||||
}
|
||||
self::_store(
|
||||
$file,
|
||||
'<?php' . PHP_EOL .
|
||||
'$GLOBALS[\'traffic_limiter\'] = ' . var_export($tl, true) . ';'
|
||||
);
|
||||
if (!self::$_store->setValue((string) $tl, 'traffic_limiter', $hash)) {
|
||||
error_log('failed to store the traffic limiter, it probably contains outdated information');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,8 @@ class Request
|
|||
case 'DELETE':
|
||||
case 'PUT':
|
||||
case 'POST':
|
||||
// it might be a creation or a deletion, the latter is detected below
|
||||
$this->_operation = 'create';
|
||||
$this->_params = Json::decode(
|
||||
file_get_contents(self::$_inputStream)
|
||||
);
|
||||
|
@ -125,15 +127,10 @@ class Request
|
|||
}
|
||||
|
||||
// prepare operation, depending on current parameters
|
||||
if (
|
||||
array_key_exists('ct', $this->_params) &&
|
||||
!empty($this->_params['ct'])
|
||||
) {
|
||||
$this->_operation = 'create';
|
||||
} elseif (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) {
|
||||
if (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) {
|
||||
if (array_key_exists('deletetoken', $this->_params) && !empty($this->_params['deletetoken'])) {
|
||||
$this->_operation = 'delete';
|
||||
} else {
|
||||
} elseif ($this->_operation != 'create') {
|
||||
$this->_operation = 'read';
|
||||
}
|
||||
} elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld'])) {
|
||||
|
@ -172,7 +169,7 @@ class Request
|
|||
$data['meta'] = $meta;
|
||||
}
|
||||
foreach ($required_keys as $key) {
|
||||
$data[$key] = $this->getParam($key);
|
||||
$data[$key] = $this->getParam($key, $key == 'v' ? 1 : '');
|
||||
}
|
||||
// forcing a cast to int or float
|
||||
$data['v'] = $data['v'] + 0;
|
||||
|
@ -288,7 +285,7 @@ class Request
|
|||
}
|
||||
krsort($mediaTypes);
|
||||
foreach ($mediaTypes as $acceptedQuality => $acceptedValues) {
|
||||
if ($acceptedQuality === 0.0) {
|
||||
if ($acceptedQuality === '0.0') {
|
||||
continue;
|
||||
}
|
||||
foreach ($acceptedValues as $acceptedValue) {
|
||||
|
|
|
@ -72,7 +72,7 @@ endif;
|
|||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.2.7.js" integrity="sha512-7Ka1I/nJuR2CL8wzIS5PJS4HgEMd0HJ6kfAl6fFhwFBB27rhztFbe0tS+Ex+Qg+5n4nZIT4lty4k4Di3+X9T4A==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-wuKnPu9+bTYhJ0HRhUmw0UxWYP5mbQehFNspkD9N4mTlxLkjRZXPnMt/nfT2/U62rRDUw1HL3SvveKJe2v4EBw==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-lJwDAY69TQuYQZ7FjUFPfhgYeZ2L6y5bmGt1hR+d3kMm2sddivGr7ZDdLLSe/CBgn1JrsKMj3th9dPyXN3dLHw==" crossorigin="anonymous"></script>
|
||||
<!-- icon -->
|
||||
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />
|
||||
|
@ -212,6 +212,9 @@ endif;
|
|||
<button id="rawtextbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
|
||||
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> <?php echo I18n::_('Raw text'), PHP_EOL; ?>
|
||||
</button>
|
||||
<button id="downloadtextbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
|
||||
<span class="glyphicon glyphicon glyphicon-download-alt" aria-hidden="true"></span> <?php echo I18n::_('Save paste'), PHP_EOL; ?>
|
||||
</button>
|
||||
<button id="emaillink" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
|
||||
<span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> <?php echo I18n::_('Email'), PHP_EOL; ?>
|
||||
</button>
|
||||
|
|
|
@ -50,7 +50,7 @@ endif;
|
|||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.2.7.js" integrity="sha512-7Ka1I/nJuR2CL8wzIS5PJS4HgEMd0HJ6kfAl6fFhwFBB27rhztFbe0tS+Ex+Qg+5n4nZIT4lty4k4Di3+X9T4A==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-wuKnPu9+bTYhJ0HRhUmw0UxWYP5mbQehFNspkD9N4mTlxLkjRZXPnMt/nfT2/U62rRDUw1HL3SvveKJe2v4EBw==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-lJwDAY69TQuYQZ7FjUFPfhgYeZ2L6y5bmGt1hR+d3kMm2sddivGr7ZDdLLSe/CBgn1JrsKMj3th9dPyXN3dLHw==" crossorigin="anonymous"></script>
|
||||
<!-- icon -->
|
||||
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
|
||||
|
@ -127,6 +127,7 @@ endif;
|
|||
<button id="sendbutton" class="hidden"><img src="img/icon_send.png" width="18" height="15" alt="" /><?php echo I18n::_('Send'); ?></button>
|
||||
<button id="clonebutton" class="hidden"><img src="img/icon_clone.png" width="15" height="17" alt="" /><?php echo I18n::_('Clone'); ?></button>
|
||||
<button id="rawtextbutton" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" /><?php echo I18n::_('Raw text'); ?></button>
|
||||
<button id="downloadtextbutton" class="hidden"><?php echo I18n::_('Save paste'), PHP_EOL; ?></button>
|
||||
<button id="emaillink" class="hidden"><img src="img/icon_email.png" width="15" height="15" alt="" /><?php echo I18n::_('Email'); ?></button>
|
||||
<?php
|
||||
if ($QRCODE):
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<?php
|
||||
|
||||
use Google\Cloud\Core\Exception\BadRequestException;
|
||||
use Google\Cloud\Core\Exception\NotFoundException;
|
||||
use Google\Cloud\Storage\Bucket;
|
||||
use Google\Cloud\Storage\Connection\ConnectionInterface;
|
||||
use Google\Cloud\Storage\StorageClient;
|
||||
use Google\Cloud\Storage\StorageObject;
|
||||
use PrivateBin\Persistence\ServerSalt;
|
||||
|
||||
error_reporting(E_ALL | E_STRICT);
|
||||
|
@ -21,6 +27,586 @@ if (!defined('CONF_SAMPLE')) {
|
|||
require PATH . 'vendor/autoload.php';
|
||||
Helper::updateSubresourceIntegrity();
|
||||
|
||||
/**
|
||||
* Class StorageClientStub provides a limited stub for performing the unit test
|
||||
*/
|
||||
class StorageClientStub extends StorageClient
|
||||
{
|
||||
private $_config = null;
|
||||
private $_connection = null;
|
||||
private $_buckets = array();
|
||||
|
||||
public function __construct(array $config = array())
|
||||
{
|
||||
$this->_config = $config;
|
||||
$this->_connection = new ConnectionInterfaceStub();
|
||||
}
|
||||
|
||||
public function bucket($name, $userProject = false)
|
||||
{
|
||||
if (!key_exists($name, $this->_buckets)) {
|
||||
$b = new BucketStub($this->_connection, $name, array(), $this);
|
||||
$this->_buckets[$name] = $b;
|
||||
}
|
||||
return $this->_buckets[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Google\Cloud\Core\Exception\NotFoundException
|
||||
*/
|
||||
public function deleteBucket($name)
|
||||
{
|
||||
if (key_exists($name, $this->_buckets)) {
|
||||
unset($this->_buckets[$name]);
|
||||
} else {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
public function buckets(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function registerStreamWrapper($protocol = null)
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function unregisterStreamWrapper($protocol = null)
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function signedUrlUploader($uri, $data, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function timestamp(\DateTimeInterface $timestamp, $nanoSeconds = null)
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getServiceAccount(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function hmacKeys(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function hmacKey($accessId, $projectId = null, array $metadata = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function createHmacKey($serviceAccountEmail, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function createBucket($name, array $options = array())
|
||||
{
|
||||
if (key_exists($name, $this->_buckets)) {
|
||||
throw new BadRequestException('already exists');
|
||||
}
|
||||
$b = new BucketStub($this->_connection, $name, array(), $this);
|
||||
$this->_buckets[$name] = $b;
|
||||
return $b;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class BucketStub stubs a GCS bucket.
|
||||
*/
|
||||
class BucketStub extends Bucket
|
||||
{
|
||||
public $_objects;
|
||||
private $_name;
|
||||
private $_info;
|
||||
private $_connection;
|
||||
private $_client;
|
||||
|
||||
public function __construct(ConnectionInterface $connection, $name, array $info = array(), $client = null)
|
||||
{
|
||||
$this->_name = $name;
|
||||
$this->_info = $info;
|
||||
$this->_connection = $connection;
|
||||
$this->_objects = array();
|
||||
$this->_client = $client;
|
||||
}
|
||||
|
||||
public function acl()
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function defaultAcl()
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function exists()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function upload($data, array $options = array())
|
||||
{
|
||||
if (!is_string($data) || !key_exists('name', $options)) {
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
$name = $options['name'];
|
||||
$generation = '1';
|
||||
$o = new StorageObjectStub($this->_connection, $name, $this, $generation, $options);
|
||||
$this->_objects[$options['name']] = $o;
|
||||
$o->setData($data);
|
||||
}
|
||||
|
||||
public function uploadAsync($data, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getResumableUploader($data, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getStreamableUploader($data, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function object($name, array $options = array())
|
||||
{
|
||||
if (key_exists($name, $this->_objects)) {
|
||||
return $this->_objects[$name];
|
||||
} else {
|
||||
return new StorageObjectStub($this->_connection, $name, $this, null, $options);
|
||||
}
|
||||
}
|
||||
|
||||
public function objects(array $options = array())
|
||||
{
|
||||
$prefix = key_exists('prefix', $options) ? $options['prefix'] : '';
|
||||
|
||||
return new CallbackFilterIterator(
|
||||
new ArrayIterator($this->_objects),
|
||||
function ($current, $key, $iterator) use ($prefix) {
|
||||
return substr($key, 0, strlen($prefix)) == $prefix;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function createNotification($topic, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function notification($id)
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function notifications(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function delete(array $options = array())
|
||||
{
|
||||
$this->_client->deleteBucket($this->_name);
|
||||
}
|
||||
|
||||
public function update(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function compose(array $sourceObjects, $name, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function info(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function reload(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function name()
|
||||
{
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
public static function lifecycle(array $lifecycle = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function currentLifecycle(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function isWritable($file = null)
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function iam()
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function lockRetentionPolicy(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function signedUrl($expires, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function generateSignedPostPolicyV4($objectName, $expires, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class StorageObjectStub stubs a GCS storage object.
|
||||
*/
|
||||
class StorageObjectStub extends StorageObject
|
||||
{
|
||||
private $_name;
|
||||
private $_data;
|
||||
private $_info;
|
||||
private $_bucket;
|
||||
private $_generation;
|
||||
private $_exists = false;
|
||||
private $_connection;
|
||||
|
||||
public function __construct(ConnectionInterface $connection, $name, $bucket, $generation = null, array $info = array(), $encryptionKey = null, $encryptionKeySHA256 = null)
|
||||
{
|
||||
$this->_name = $name;
|
||||
$this->_bucket = $bucket;
|
||||
$this->_generation = $generation;
|
||||
$this->_info = $info;
|
||||
$this->_connection = $connection;
|
||||
$timeCreated = new Datetime();
|
||||
$this->_info['metadata']['timeCreated'] = $timeCreated->format('Y-m-d\TH:i:s.u\Z');
|
||||
}
|
||||
|
||||
public function acl()
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function exists(array $options = array())
|
||||
{
|
||||
return key_exists($this->_name, $this->_bucket->_objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function delete(array $options = array())
|
||||
{
|
||||
if (key_exists($this->_name, $this->_bucket->_objects)) {
|
||||
unset($this->_bucket->_objects[$this->_name]);
|
||||
} else {
|
||||
throw new NotFoundException('key ' . $this->_name . ' not found.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function update(array $metadata, array $options = array())
|
||||
{
|
||||
if (!$this->_exists) {
|
||||
throw new NotFoundException('key ' . $this->_name . ' not found.');
|
||||
}
|
||||
$this->_info = $metadata;
|
||||
}
|
||||
|
||||
public function copy($destination, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function rewrite($destination, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function rename($name, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function downloadAsString(array $options = array())
|
||||
{
|
||||
if (!$this->_exists) {
|
||||
throw new NotFoundException('key ' . $this->_name . ' not found.');
|
||||
}
|
||||
return $this->_data;
|
||||
}
|
||||
|
||||
public function downloadToFile($path, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function downloadAsStream(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function downloadAsStreamAsync(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function signedUrl($expires, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function signedUploadUrl($expires, array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function beginSignedUploadSession(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function info(array $options = array())
|
||||
{
|
||||
return key_exists('metadata',$this->_info) ? $this->_info['metadata'] : array();
|
||||
}
|
||||
|
||||
public function reload(array $options = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function name()
|
||||
{
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
public function identity()
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function gcsUri()
|
||||
{
|
||||
return sprintf(
|
||||
'gs://%s/%s',
|
||||
$this->_bucket->name(),
|
||||
$this->_name
|
||||
);
|
||||
}
|
||||
|
||||
public function setData($data)
|
||||
{
|
||||
$this->_data = $data;
|
||||
$this->_exists = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class ConnectionInterfaceStub required for the stubs.
|
||||
*/
|
||||
class ConnectionInterfaceStub implements ConnectionInterface
|
||||
{
|
||||
public function deleteAcl(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getAcl(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function listAcl(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function insertAcl(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function patchAcl(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function deleteBucket(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getBucket(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function listBuckets(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function insertBucket(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getBucketIamPolicy(array $args)
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function setBucketIamPolicy(array $args)
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function testBucketIamPermissions(array $args)
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function patchBucket(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function deleteObject(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function copyObject(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function rewriteObject(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function composeObject(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getObject(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function listObjects(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function patchObject(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function downloadObject(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function insertObject(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getNotification(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function deleteNotification(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function insertNotification(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function listNotifications(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getServiceAccount(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function lockRetentionPolicy(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function createHmacKey(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function deleteHmacKey(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function getHmacKey(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function updateHmacKey(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
|
||||
public function listHmacKeys(array $args = array())
|
||||
{
|
||||
throw new BadMethodCallException('not supported by this stub');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Helper provides unit tests pastes and comments of various formats
|
||||
*/
|
||||
class Helper
|
||||
{
|
||||
/**
|
||||
|
@ -155,7 +741,11 @@ class Helper
|
|||
public static function getPastePost($version = 2, array $meta = array())
|
||||
{
|
||||
$example = self::getPaste($version, $meta);
|
||||
if ($version == 2) {
|
||||
$example['meta'] = array('expire' => $example['meta']['expire']);
|
||||
} else {
|
||||
unset($example['meta']['postdate']);
|
||||
}
|
||||
return $example;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@ class ConfigurationTest extends PHPUnit_Framework_TestCase
|
|||
$this->_minimalConfig = '[main]' . PHP_EOL . '[model]' . PHP_EOL . '[model_options]';
|
||||
$this->_options = Configuration::getDefaults();
|
||||
$this->_options['model_options']['dir'] = PATH . $this->_options['model_options']['dir'];
|
||||
$this->_options['traffic']['dir'] = PATH . $this->_options['traffic']['dir'];
|
||||
$this->_options['purge']['dir'] = PATH . $this->_options['purge']['dir'];
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_cfg';
|
||||
if (!is_dir($this->_path)) {
|
||||
mkdir($this->_path);
|
||||
|
@ -147,44 +145,6 @@ class ConfigurationTest extends PHPUnit_Framework_TestCase
|
|||
$this->assertEquals('Database', $conf->getKey('class', 'model'), 'old db class gets renamed');
|
||||
}
|
||||
|
||||
public function testHandleConfigFileRename()
|
||||
{
|
||||
$options = $this->_options;
|
||||
Helper::createIniFile(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini.sample', $options);
|
||||
|
||||
$options['main']['opendiscussion'] = true;
|
||||
$options['main']['fileupload'] = true;
|
||||
$options['main']['template'] = 'darkstrap';
|
||||
Helper::createIniFile(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', $options);
|
||||
|
||||
$conf = new Configuration;
|
||||
$this->assertFileExists(CONF, 'old configuration file gets converted');
|
||||
$this->assertFileNotExists(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', 'old configuration file gets removed');
|
||||
$this->assertFileNotExists(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini.sample', 'old configuration sample file gets removed');
|
||||
$this->assertTrue(
|
||||
$conf->getKey('opendiscussion') &&
|
||||
$conf->getKey('fileupload') &&
|
||||
$conf->getKey('template') === 'darkstrap',
|
||||
'configuration values get converted'
|
||||
);
|
||||
}
|
||||
|
||||
public function testRenameIniSample()
|
||||
{
|
||||
$iniSample = PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini.sample';
|
||||
|
||||
Helper::createIniFile(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', $this->_options);
|
||||
if (is_file(CONF)) {
|
||||
unlink(CONF);
|
||||
}
|
||||
rename(CONF_SAMPLE, $iniSample);
|
||||
new Configuration;
|
||||
$this->assertFileNotExists($iniSample, 'old sample file gets removed');
|
||||
$this->assertFileExists(CONF_SAMPLE, 'new sample file gets created');
|
||||
$this->assertFileExists(CONF, 'old configuration file gets converted');
|
||||
$this->assertFileNotExists(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', 'old configuration file gets removed');
|
||||
}
|
||||
|
||||
public function testConfigPath()
|
||||
{
|
||||
// setup
|
||||
|
@ -204,29 +164,4 @@ class ConfigurationTest extends PHPUnit_Framework_TestCase
|
|||
}
|
||||
putenv('CONFIG_PATH');
|
||||
}
|
||||
|
||||
public function testConfigPathIni()
|
||||
{
|
||||
// setup
|
||||
$configFile = $this->_path . DIRECTORY_SEPARATOR . 'conf.ini';
|
||||
$configMigrated = $this->_path . DIRECTORY_SEPARATOR . 'conf.php';
|
||||
$options = $this->_options;
|
||||
$options['main']['name'] = 'OtherBin';
|
||||
Helper::createIniFile($configFile, $options);
|
||||
$this->assertFileNotExists(CONF, 'configuration in the default location is non existing');
|
||||
|
||||
// test
|
||||
putenv('CONFIG_PATH=' . $this->_path);
|
||||
$conf = new Configuration;
|
||||
$this->assertEquals('OtherBin', $conf->getKey('name'), 'changing config path is supported for ini files as well');
|
||||
$this->assertFileExists($configMigrated, 'old configuration file gets converted');
|
||||
$this->assertFileNotExists($configFile, 'old configuration file gets removed');
|
||||
$this->assertFileNotExists(CONF, 'configuration is not created in the default location');
|
||||
|
||||
// cleanup environment
|
||||
if (is_file($configFile)) {
|
||||
unlink($configFile);
|
||||
}
|
||||
putenv('CONFIG_PATH');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -428,8 +428,6 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase
|
|||
Helper::confBackup();
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
|
||||
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
|
||||
ServerSalt::setPath($this->_path);
|
||||
TrafficLimiter::setPath($this->_path);
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
|
@ -449,8 +447,6 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase
|
|||
if ($this->_model->exists(Helper::getPasteId()))
|
||||
$this->_model->delete(Helper::getPasteId());
|
||||
$configuration['model_options']['dir'] = $this->_path;
|
||||
$configuration['traffic']['dir'] = $this->_path;
|
||||
$configuration['purge']['dir'] = $this->_path;
|
||||
Helper::createIniFile(CONF, $configuration);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase
|
|||
/* Setup Routine */
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
|
||||
$this->_data = Filesystem::getInstance(array('dir' => $this->_path));
|
||||
ServerSalt::setStore($this->_data);
|
||||
TrafficLimiter::setStore($this->_data);
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
|
@ -37,11 +39,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase
|
|||
$this->_data->delete(Helper::getPasteId());
|
||||
}
|
||||
$options = parse_ini_file(CONF_SAMPLE, true);
|
||||
$options['purge']['dir'] = $this->_path;
|
||||
$options['traffic']['dir'] = $this->_path;
|
||||
$options['model_options']['dir'] = $this->_path;
|
||||
Helper::createIniFile(CONF, $options);
|
||||
ServerSalt::setPath($this->_path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,6 +48,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testView()
|
||||
{
|
||||
$_SERVER['QUERY_STRING'] = Helper::getPasteId();
|
||||
$_GET[Helper::getPasteId()] = '';
|
||||
ob_start();
|
||||
new Controller;
|
||||
$content = ob_get_contents();
|
||||
|
@ -127,28 +128,6 @@ class ControllerTest extends PHPUnit_Framework_TestCase
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testHtaccess()
|
||||
{
|
||||
$htaccess = $this->_path . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
@unlink($htaccess);
|
||||
|
||||
$paste = Helper::getPasteJson();
|
||||
$file = tempnam(sys_get_temp_dir(), 'FOO');
|
||||
file_put_contents($file, $paste);
|
||||
Request::setInputStream($file);
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new Controller;
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertFileExists($htaccess, 'htaccess recreated');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 2
|
||||
|
@ -493,6 +472,29 @@ class ControllerTest extends PHPUnit_Framework_TestCase
|
|||
$this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testCreateInvalidFormat()
|
||||
{
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
Helper::createIniFile(CONF, $options);
|
||||
$file = tempnam(sys_get_temp_dir(), 'FOO');
|
||||
file_put_contents($file, Helper::getPasteJson(1));
|
||||
Request::setInputStream($file);
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new Controller;
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(1, $response['status'], 'outputs error status');
|
||||
$this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
|
@ -541,7 +543,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase
|
|||
ob_end_clean();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(1, $response['status'], 'outputs error status');
|
||||
$this->assertFalse($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'paste exists after posting data');
|
||||
$this->assertFalse($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment exists after posting data');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
use PrivateBin\Data\Database;
|
||||
use PrivateBin\Persistence\ServerSalt;
|
||||
use PrivateBin\Persistence\TrafficLimiter;
|
||||
|
||||
require_once 'ControllerTest.php';
|
||||
|
||||
|
@ -24,6 +26,8 @@ class ControllerWithDbTest extends ControllerTest
|
|||
}
|
||||
$this->_options['dsn'] = 'sqlite:' . $this->_path . DIRECTORY_SEPARATOR . 'tst.sq3';
|
||||
$this->_data = Database::getInstance($this->_options);
|
||||
ServerSalt::setStore($this->_data);
|
||||
TrafficLimiter::setStore($this->_data);
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
|
|
59
tst/ControllerWithGcsTest.php
Normal file
59
tst/ControllerWithGcsTest.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
use Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use GuzzleHttp\Client;
|
||||
use PrivateBin\Data\GoogleCloudStorage;
|
||||
use PrivateBin\Persistence\ServerSalt;
|
||||
use PrivateBin\Persistence\TrafficLimiter;
|
||||
|
||||
require_once 'ControllerTest.php';
|
||||
|
||||
class ControllerWithGcsTest extends ControllerTest
|
||||
{
|
||||
private static $_client;
|
||||
private static $_bucket;
|
||||
private $_options = array();
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
$httpClient = new Client(array('debug'=>false));
|
||||
$handler = HttpHandlerFactory::build($httpClient);
|
||||
|
||||
$name = 'pb-';
|
||||
$alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||
for ($i = 0; $i < 29; ++$i) {
|
||||
$name .= $alphabet[rand(0, strlen($alphabet) - 1)];
|
||||
}
|
||||
self::$_client = new StorageClientStub(array());
|
||||
self::$_bucket = self::$_client->createBucket($name);
|
||||
}
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
/* Setup Routine */
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
|
||||
if (!is_dir($this->_path)) {
|
||||
mkdir($this->_path);
|
||||
}
|
||||
$this->_options = array(
|
||||
'bucket' => self::$_bucket->name(),
|
||||
'prefix' => 'pastes',
|
||||
);
|
||||
$this->_data = GoogleCloudStorage::getInstance($this->_options);
|
||||
ServerSalt::setStore($this->_data);
|
||||
TrafficLimiter::setStore($this->_data);
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
// but then inject a db config
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['model'] = array(
|
||||
'class' => 'GoogleCloudStorage',
|
||||
);
|
||||
$options['model_options'] = $this->_options;
|
||||
Helper::createIniFile(CONF, $options);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
use PrivateBin\Controller;
|
||||
use PrivateBin\Data\Database;
|
||||
use PrivateBin\Data\Filesystem;
|
||||
use PrivateBin\Persistence\ServerSalt;
|
||||
|
||||
class DatabaseTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
@ -31,6 +33,19 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
|
|||
}
|
||||
}
|
||||
|
||||
public function testSaltMigration()
|
||||
{
|
||||
ServerSalt::setStore(Filesystem::getInstance(array('dir' => 'data')));
|
||||
$salt = ServerSalt::get();
|
||||
$file = 'data' . DIRECTORY_SEPARATOR . 'salt.php';
|
||||
$this->assertFileExists($file, 'ServerSalt got initialized and stored on disk');
|
||||
$this->assertNotEquals($salt, '');
|
||||
ServerSalt::setStore($this->_model);
|
||||
ServerSalt::get();
|
||||
$this->assertFileNotExists($file, 'legacy ServerSalt got removed');
|
||||
$this->assertEquals($salt, ServerSalt::get(), 'ServerSalt got preserved & migrated');
|
||||
}
|
||||
|
||||
public function testDatabaseBasedDataStoreWorks()
|
||||
{
|
||||
$this->_model->delete(Helper::getPasteId());
|
||||
|
@ -287,6 +302,48 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
|
|||
Helper::rmDir($this->_path);
|
||||
}
|
||||
|
||||
public function testCorruptMeta()
|
||||
{
|
||||
mkdir($this->_path);
|
||||
$path = $this->_path . DIRECTORY_SEPARATOR . 'meta-test.sq3';
|
||||
if (is_file($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
$this->_options['dsn'] = 'sqlite:' . $path;
|
||||
$this->_options['tbl'] = 'baz_';
|
||||
$model = Database::getInstance($this->_options);
|
||||
$paste = Helper::getPaste(1, array('expire_date' => 1344803344));
|
||||
unset($paste['meta']['formatter'], $paste['meta']['opendiscussion'], $paste['meta']['salt']);
|
||||
$model->delete(Helper::getPasteId());
|
||||
|
||||
$db = new PDO(
|
||||
$this->_options['dsn'],
|
||||
$this->_options['usr'],
|
||||
$this->_options['pwd'],
|
||||
$this->_options['opt']
|
||||
);
|
||||
$statement = $db->prepare('INSERT INTO baz_paste VALUES(?,?,?,?,?,?,?,?,?)');
|
||||
$statement->execute(
|
||||
array(
|
||||
Helper::getPasteId(),
|
||||
$paste['data'],
|
||||
$paste['meta']['postdate'],
|
||||
$paste['meta']['expire_date'],
|
||||
0,
|
||||
0,
|
||||
'{',
|
||||
null,
|
||||
null,
|
||||
)
|
||||
);
|
||||
$statement->closeCursor();
|
||||
|
||||
$this->assertTrue($model->exists(Helper::getPasteId()), 'paste exists after storing it');
|
||||
$this->assertEquals($paste, $model->read(Helper::getPasteId()));
|
||||
|
||||
Helper::rmDir($this->_path);
|
||||
}
|
||||
|
||||
public function testTableUpgrade()
|
||||
{
|
||||
mkdir($this->_path);
|
||||
|
|
|
@ -117,6 +117,7 @@ class FilesystemTest extends PHPUnit_Framework_TestCase
|
|||
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does not yet exist');
|
||||
$this->assertFalse($this->_model->create(Helper::getPasteId(), $paste), 'unable to store broken paste');
|
||||
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does still not exist');
|
||||
$this->assertFalse($this->_model->setValue('foo', 'non existing namespace'), 'rejects setting value in non existing namespace');
|
||||
}
|
||||
|
||||
public function testCommentErrorDetection()
|
||||
|
|
182
tst/Data/GoogleCloudStorageTest.php
Normal file
182
tst/Data/GoogleCloudStorageTest.php
Normal file
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
|
||||
use Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use GuzzleHttp\Client;
|
||||
use PrivateBin\Data\GoogleCloudStorage;
|
||||
|
||||
class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
private static $_client;
|
||||
private static $_bucket;
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
$httpClient = new Client(array('debug'=>false));
|
||||
$handler = HttpHandlerFactory::build($httpClient);
|
||||
|
||||
$name = 'pb-';
|
||||
$alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||
for ($i = 0; $i < 29; ++$i) {
|
||||
$name .= $alphabet[rand(0, strlen($alphabet) - 1)];
|
||||
}
|
||||
self::$_client = new StorageClientStub(array());
|
||||
self::$_bucket = self::$_client->createBucket($name);
|
||||
}
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
ini_set('error_log', stream_get_meta_data(tmpfile())['uri']);
|
||||
$this->_model = GoogleCloudStorage::getInstance(array(
|
||||
'bucket' => self::$_bucket->name(),
|
||||
'prefix' => 'pastes',
|
||||
));
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
foreach (self::$_bucket->objects() as $object) {
|
||||
$object->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
self::$_bucket->delete();
|
||||
}
|
||||
|
||||
public function testFileBasedDataStoreWorks()
|
||||
{
|
||||
$this->_model->delete(Helper::getPasteId());
|
||||
|
||||
// storing pastes
|
||||
$paste = Helper::getPaste(2, array('expire_date' => 1344803344));
|
||||
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does not yet exist');
|
||||
$this->assertTrue($this->_model->create(Helper::getPasteId(), $paste), 'store new paste');
|
||||
$this->assertTrue($this->_model->exists(Helper::getPasteId()), 'paste exists after storing it');
|
||||
$this->assertFalse($this->_model->create(Helper::getPasteId(), $paste), 'unable to store the same paste twice');
|
||||
$this->assertEquals($paste, $this->_model->read(Helper::getPasteId()));
|
||||
|
||||
// storing comments
|
||||
$this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment does not yet exist');
|
||||
$this->assertTrue($this->_model->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), Helper::getComment()), 'store comment');
|
||||
$this->assertTrue($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment exists after storing it');
|
||||
$this->assertFalse($this->_model->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), Helper::getComment()), 'unable to store the same comment twice');
|
||||
$comment = Helper::getComment();
|
||||
$comment['id'] = Helper::getCommentId();
|
||||
$comment['parentid'] = Helper::getPasteId();
|
||||
$this->assertEquals(
|
||||
array($comment['meta']['created'] => $comment),
|
||||
$this->_model->readComments(Helper::getPasteId())
|
||||
);
|
||||
|
||||
// deleting pastes
|
||||
$this->_model->delete(Helper::getPasteId());
|
||||
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste successfully deleted');
|
||||
$this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment was deleted with paste');
|
||||
$this->assertFalse($this->_model->read(Helper::getPasteId()), 'paste can no longer be found');
|
||||
}
|
||||
|
||||
/**
|
||||
* pastes a-g are expired and should get deleted, x never expires and y-z expire in an hour
|
||||
*/
|
||||
public function testPurge()
|
||||
{
|
||||
$expired = Helper::getPaste(2, array('expire_date' => 1344803344));
|
||||
$paste = Helper::getPaste(2, array('expire_date' => time() + 3600));
|
||||
$keys = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z');
|
||||
$ids = array();
|
||||
foreach ($keys as $key) {
|
||||
$ids[$key] = hash('fnv164', $key);
|
||||
$this->assertFalse($this->_model->exists($ids[$key]), "paste $key does not yet exist");
|
||||
if (in_array($key, array('x', 'y', 'z'))) {
|
||||
$this->assertTrue($this->_model->create($ids[$key], $paste), "store $key paste");
|
||||
} elseif ($key === 'x') {
|
||||
$this->assertTrue($this->_model->create($ids[$key], Helper::getPaste()), "store $key paste");
|
||||
} else {
|
||||
$this->assertTrue($this->_model->create($ids[$key], $expired), "store $key paste");
|
||||
}
|
||||
$this->assertTrue($this->_model->exists($ids[$key]), "paste $key exists after storing it");
|
||||
}
|
||||
$this->_model->purge(10);
|
||||
foreach ($ids as $key => $id) {
|
||||
if (in_array($key, array('x', 'y', 'z'))) {
|
||||
$this->assertTrue($this->_model->exists($id), "paste $key exists after purge");
|
||||
$this->_model->delete($id);
|
||||
} else {
|
||||
$this->assertFalse($this->_model->exists($id), "paste $key was purged");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testErrorDetection()
|
||||
{
|
||||
$this->_model->delete(Helper::getPasteId());
|
||||
$paste = Helper::getPaste(2, array('expire' => "Invalid UTF-8 sequence: \xB1\x31"));
|
||||
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does not yet exist');
|
||||
$this->assertFalse($this->_model->create(Helper::getPasteId(), $paste), 'unable to store broken paste');
|
||||
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does still not exist');
|
||||
}
|
||||
|
||||
public function testCommentErrorDetection()
|
||||
{
|
||||
$this->_model->delete(Helper::getPasteId());
|
||||
$comment = Helper::getComment(1, array('nickname' => "Invalid UTF-8 sequence: \xB1\x31"));
|
||||
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does not yet exist');
|
||||
$this->assertTrue($this->_model->create(Helper::getPasteId(), Helper::getPaste()), 'store new paste');
|
||||
$this->assertTrue($this->_model->exists(Helper::getPasteId()), 'paste exists after storing it');
|
||||
$this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment does not yet exist');
|
||||
$this->assertFalse($this->_model->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), $comment), 'unable to store broken comment');
|
||||
$this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment does still not exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testKeyValueStore()
|
||||
{
|
||||
$salt = bin2hex(random_bytes(256));
|
||||
$this->_model->setValue($salt, 'salt', '');
|
||||
$storedSalt = $this->_model->getValue('salt', '');
|
||||
$this->assertEquals($salt, $storedSalt);
|
||||
$this->_model->purgeValues('salt', time() + 60);
|
||||
$this->assertEquals('', $this->_model->getValue('salt', 'master'));
|
||||
|
||||
$client = hash_hmac('sha512', '127.0.0.1', $salt);
|
||||
$expire = time();
|
||||
$this->_model->setValue(strval($expire), 'traffic_limiter', $client);
|
||||
$storedExpired = $this->_model->getValue('traffic_limiter', $client);
|
||||
$this->assertEquals(strval($expire), $storedExpired);
|
||||
|
||||
$this->_model->purgeValues('traffic_limiter', time() - 60);
|
||||
$this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client));
|
||||
$this->_model->purgeValues('traffic_limiter', time() + 60);
|
||||
$this->assertEquals('', $this->_model->getValue('traffic_limiter', $client));
|
||||
|
||||
$purgeAt = $expire + (15 * 60);
|
||||
$this->_model->setValue(strval($purgeAt), 'purge_limiter', '');
|
||||
$storedPurgedAt = $this->_model->getValue('purge_limiter', '');
|
||||
$this->assertEquals(strval($purgeAt), $storedPurgedAt);
|
||||
$this->_model->purgeValues('purge_limiter', $purgeAt + 60);
|
||||
$this->assertEquals('', $this->_model->getValue('purge_limiter', ''));
|
||||
$this->assertEquals('', $this->_model->getValue('purge_limiter', 'at'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testKeyValuePurgeTrafficLimiter()
|
||||
{
|
||||
$salt = bin2hex(random_bytes(256));
|
||||
$client = hash_hmac('sha512', '127.0.0.1', $salt);
|
||||
$expire = time();
|
||||
$this->_model->setValue(strval($expire), 'traffic_limiter', $client);
|
||||
$storedExpired = $this->_model->getValue('traffic_limiter', $client);
|
||||
$this->assertEquals(strval($expire), $storedExpired);
|
||||
|
||||
$this->_model->purgeValues('traffic_limiter', time() - 60);
|
||||
$this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client));
|
||||
|
||||
$this->_model->purgeValues('traffic_limiter', time() + 60);
|
||||
$this->assertEquals('', $this->_model->getValue('traffic_limiter', $client));
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
|
|||
/* Setup Routine */
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
|
||||
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
|
||||
ServerSalt::setPath($this->_path);
|
||||
ServerSalt::setStore($this->_model);
|
||||
|
||||
$_POST = array();
|
||||
$_GET = array();
|
||||
|
@ -25,8 +25,6 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
|
|||
$this->_model->delete(Helper::getPasteId());
|
||||
}
|
||||
$options = parse_ini_file(CONF_SAMPLE, true);
|
||||
$options['purge']['dir'] = $this->_path;
|
||||
$options['traffic']['dir'] = $this->_path;
|
||||
$options['model_options']['dir'] = $this->_path;
|
||||
Helper::confBackup();
|
||||
Helper::createIniFile(CONF, $options);
|
||||
|
|
|
@ -25,7 +25,6 @@ class ModelTest extends PHPUnit_Framework_TestCase
|
|||
if (!is_dir($this->_path)) {
|
||||
mkdir($this->_path);
|
||||
}
|
||||
ServerSalt::setPath($this->_path);
|
||||
$options = parse_ini_file(CONF_SAMPLE, true);
|
||||
$options['purge']['limit'] = 0;
|
||||
$options['model'] = array(
|
||||
|
@ -39,6 +38,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
|
|||
);
|
||||
Helper::confBackup();
|
||||
Helper::createIniFile(CONF, $options);
|
||||
ServerSalt::setStore(Database::getInstance($options['model_options']));
|
||||
$this->_conf = new Configuration;
|
||||
$this->_model = new Model($this->_conf);
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
|
@ -102,6 +102,58 @@ class ModelTest extends PHPUnit_Framework_TestCase
|
|||
$this->assertEquals(array(), $paste->getComments(), 'comment was deleted with paste');
|
||||
}
|
||||
|
||||
public function testPasteV1()
|
||||
{
|
||||
$pasteData = Helper::getPaste(1);
|
||||
unset($pasteData['meta']['formatter']);
|
||||
|
||||
$path = $this->_path . DIRECTORY_SEPARATOR . 'v1-test.sq3';
|
||||
if (is_file($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
$options = parse_ini_file(CONF_SAMPLE, true);
|
||||
$options['purge']['limit'] = 0;
|
||||
$options['model'] = array(
|
||||
'class' => 'Database',
|
||||
);
|
||||
$options['model_options'] = array(
|
||||
'dsn' => 'sqlite:' . $path,
|
||||
'usr' => null,
|
||||
'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
);
|
||||
Helper::createIniFile(CONF, $options);
|
||||
$model = new Model(new Configuration);
|
||||
$model->getPaste('0000000000000000')->exists(); // triggers database table creation
|
||||
$model->getPaste(Helper::getPasteId())->delete(); // deletes the cache
|
||||
|
||||
$db = new PDO(
|
||||
$options['model_options']['dsn'],
|
||||
$options['model_options']['usr'],
|
||||
$options['model_options']['pwd'],
|
||||
$options['model_options']['opt']
|
||||
);
|
||||
$statement = $db->prepare('INSERT INTO paste VALUES(?,?,?,?,?,?,?,?,?)');
|
||||
$statement->execute(
|
||||
array(
|
||||
Helper::getPasteId(),
|
||||
$pasteData['data'],
|
||||
$pasteData['meta']['postdate'],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
json_encode($pasteData['meta']),
|
||||
null,
|
||||
null,
|
||||
)
|
||||
);
|
||||
$statement->closeCursor();
|
||||
|
||||
$paste = $model->getPaste(Helper::getPasteId());
|
||||
$this->assertNotEmpty($paste->getDeleteToken(), 'excercise the condition to load the data from storage');
|
||||
$this->assertEquals('plaintext', $paste->get()['meta']['formatter'], 'paste got created with default formatter');
|
||||
}
|
||||
|
||||
public function testCommentDefaults()
|
||||
{
|
||||
$comment = new Comment(
|
||||
|
@ -133,6 +185,97 @@ class ModelTest extends PHPUnit_Framework_TestCase
|
|||
$paste->store();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 76
|
||||
*/
|
||||
public function testStoreFail()
|
||||
{
|
||||
$path = $this->_path . DIRECTORY_SEPARATOR . 'model-store-test.sq3';
|
||||
if (is_file($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
$options = parse_ini_file(CONF_SAMPLE, true);
|
||||
$options['purge']['limit'] = 0;
|
||||
$options['model'] = array(
|
||||
'class' => 'Database',
|
||||
);
|
||||
$options['model_options'] = array(
|
||||
'dsn' => 'sqlite:' . $path,
|
||||
'usr' => null,
|
||||
'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
);
|
||||
Helper::createIniFile(CONF, $options);
|
||||
$model = new Model(new Configuration);
|
||||
|
||||
$pasteData = Helper::getPastePost();
|
||||
$model->getPaste(Helper::getPasteId())->delete();
|
||||
$model->getPaste(Helper::getPasteId())->exists();
|
||||
|
||||
$db = new PDO(
|
||||
$options['model_options']['dsn'],
|
||||
$options['model_options']['usr'],
|
||||
$options['model_options']['pwd'],
|
||||
$options['model_options']['opt']
|
||||
);
|
||||
$statement = $db->prepare('DROP TABLE paste');
|
||||
$statement->execute();
|
||||
$statement->closeCursor();
|
||||
|
||||
$paste = $model->getPaste();
|
||||
$paste->setData($pasteData);
|
||||
$paste->store();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 70
|
||||
*/
|
||||
public function testCommentStoreFail()
|
||||
{
|
||||
$path = $this->_path . DIRECTORY_SEPARATOR . 'model-test.sq3';
|
||||
if (is_file($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
$options = parse_ini_file(CONF_SAMPLE, true);
|
||||
$options['purge']['limit'] = 0;
|
||||
$options['model'] = array(
|
||||
'class' => 'Database',
|
||||
);
|
||||
$options['model_options'] = array(
|
||||
'dsn' => 'sqlite:' . $path,
|
||||
'usr' => null,
|
||||
'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
);
|
||||
Helper::createIniFile(CONF, $options);
|
||||
$model = new Model(new Configuration);
|
||||
|
||||
$pasteData = Helper::getPastePost();
|
||||
$commentData = Helper::getCommentPost();
|
||||
$model->getPaste(Helper::getPasteId())->delete();
|
||||
|
||||
$paste = $model->getPaste();
|
||||
$paste->setData($pasteData);
|
||||
$paste->store();
|
||||
$paste->exists();
|
||||
|
||||
$db = new PDO(
|
||||
$options['model_options']['dsn'],
|
||||
$options['model_options']['usr'],
|
||||
$options['model_options']['pwd'],
|
||||
$options['model_options']['opt']
|
||||
);
|
||||
$statement = $db->prepare('DROP TABLE comment');
|
||||
$statement->execute();
|
||||
$statement->closeCursor();
|
||||
|
||||
$comment = $paste->getComment(Helper::getPasteId());
|
||||
$comment->setData($commentData);
|
||||
$comment->store();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 69
|
||||
|
@ -195,6 +338,18 @@ class ModelTest extends PHPUnit_Framework_TestCase
|
|||
$paste->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 75
|
||||
*/
|
||||
public function testInvalidPasteFormat()
|
||||
{
|
||||
$pasteData = Helper::getPastePost();
|
||||
$pasteData['adata'][1] = 'format does not exist';
|
||||
$paste = $this->_model->getPaste();
|
||||
$paste->setData($pasteData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 60
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use PrivateBin\Data\Filesystem;
|
||||
use PrivateBin\Persistence\PurgeLimiter;
|
||||
|
||||
class PurgeLimiterTest extends PHPUnit_Framework_TestCase
|
||||
|
@ -13,7 +14,9 @@ class PurgeLimiterTest extends PHPUnit_Framework_TestCase
|
|||
if (!is_dir($this->_path)) {
|
||||
mkdir($this->_path);
|
||||
}
|
||||
PurgeLimiter::setPath($this->_path);
|
||||
PurgeLimiter::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_path))
|
||||
);
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use PrivateBin\Data\Filesystem;
|
||||
use PrivateBin\Persistence\ServerSalt;
|
||||
|
||||
class ServerSaltTest extends PHPUnit_Framework_TestCase
|
||||
|
@ -19,7 +20,9 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
|
|||
if (!is_dir($this->_path)) {
|
||||
mkdir($this->_path);
|
||||
}
|
||||
ServerSalt::setPath($this->_path);
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_path))
|
||||
);
|
||||
|
||||
$this->_otherPath = $this->_path . DIRECTORY_SEPARATOR . 'foo';
|
||||
|
||||
|
@ -40,46 +43,46 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
|
|||
public function testGeneration()
|
||||
{
|
||||
// generating new salt
|
||||
ServerSalt::setPath($this->_path);
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_path))
|
||||
);
|
||||
$salt = ServerSalt::get();
|
||||
|
||||
// try setting a different path and resetting it
|
||||
ServerSalt::setPath($this->_otherPath);
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_otherPath))
|
||||
);
|
||||
$this->assertNotEquals($salt, ServerSalt::get());
|
||||
ServerSalt::setPath($this->_path);
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_path))
|
||||
);
|
||||
$this->assertEquals($salt, ServerSalt::get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 11
|
||||
*/
|
||||
public function testPathShenanigans()
|
||||
{
|
||||
// try setting an invalid path
|
||||
chmod($this->_invalidPath, 0000);
|
||||
ServerSalt::setPath($this->_invalidPath);
|
||||
ServerSalt::get();
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
|
||||
ServerSalt::setStore($store);
|
||||
$salt = ServerSalt::get();
|
||||
ServerSalt::setStore($store);
|
||||
$this->assertNotEquals($salt, ServerSalt::get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 20
|
||||
*/
|
||||
public function testFileRead()
|
||||
{
|
||||
// try setting an invalid file
|
||||
chmod($this->_invalidPath, 0700);
|
||||
file_put_contents($this->_invalidFile, '');
|
||||
chmod($this->_invalidFile, 0000);
|
||||
ServerSalt::setPath($this->_invalidPath);
|
||||
ServerSalt::get();
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
|
||||
ServerSalt::setStore($store);
|
||||
$salt = ServerSalt::get();
|
||||
ServerSalt::setStore($store);
|
||||
$this->assertNotEquals($salt, ServerSalt::get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 13
|
||||
*/
|
||||
public function testFileWrite()
|
||||
{
|
||||
// try setting an invalid file
|
||||
|
@ -90,19 +93,24 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
|
|||
}
|
||||
file_put_contents($this->_invalidPath . DIRECTORY_SEPARATOR . '.htaccess', '');
|
||||
chmod($this->_invalidPath, 0500);
|
||||
ServerSalt::setPath($this->_invalidPath);
|
||||
ServerSalt::get();
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
|
||||
ServerSalt::setStore($store);
|
||||
$salt = ServerSalt::get();
|
||||
ServerSalt::setStore($store);
|
||||
$this->assertNotEquals($salt, ServerSalt::get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 10
|
||||
*/
|
||||
public function testPermissionShenanigans()
|
||||
{
|
||||
// try creating an invalid path
|
||||
chmod($this->_invalidPath, 0000);
|
||||
ServerSalt::setPath($this->_invalidPath . DIRECTORY_SEPARATOR . 'baz');
|
||||
ServerSalt::get();
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_invalidPath . DIRECTORY_SEPARATOR . 'baz'))
|
||||
);
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
|
||||
ServerSalt::setStore($store);
|
||||
$salt = ServerSalt::get();
|
||||
ServerSalt::setStore($store);
|
||||
$this->assertNotEquals($salt, ServerSalt::get());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use PrivateBin\Data\Filesystem;
|
||||
use PrivateBin\Persistence\ServerSalt;
|
||||
use PrivateBin\Persistence\TrafficLimiter;
|
||||
|
||||
class TrafficLimiterTest extends PHPUnit_Framework_TestCase
|
||||
|
@ -10,7 +12,9 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
|
|||
{
|
||||
/* Setup Routine */
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit';
|
||||
TrafficLimiter::setPath($this->_path);
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_path));
|
||||
ServerSalt::setStore($store);
|
||||
TrafficLimiter::setStore($store);
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
|
@ -19,11 +23,17 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
|
|||
Helper::rmDir($this->_path . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
public function testHtaccess()
|
||||
{
|
||||
$htaccess = $this->_path . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
@unlink($htaccess);
|
||||
$_SERVER['REMOTE_ADDR'] = 'foobar';
|
||||
TrafficLimiter::canPass();
|
||||
$this->assertFileExists($htaccess, 'htaccess recreated');
|
||||
}
|
||||
|
||||
public function testTrafficGetsLimited()
|
||||
{
|
||||
$this->assertEquals($this->_path, TrafficLimiter::getPath());
|
||||
$file = 'baz';
|
||||
$this->assertEquals($this->_path . DIRECTORY_SEPARATOR . $file, TrafficLimiter::getPath($file));
|
||||
TrafficLimiter::setLimit(4);
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$this->assertTrue(TrafficLimiter::canPass(), 'first request may pass');
|
||||
|
@ -35,5 +45,20 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
|
|||
$this->assertTrue(TrafficLimiter::canPass(), 'fourth request has different ip and may pass');
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$this->assertFalse(TrafficLimiter::canPass(), 'fifth request is to fast, may not pass');
|
||||
|
||||
// exempted IPs configuration
|
||||
TrafficLimiter::setExemptedIp('1.2.3.4,10.10.10.0/24,2001:1620:2057::/48');
|
||||
$this->assertFalse(TrafficLimiter::canPass(), 'still too fast and not exempted');
|
||||
$_SERVER['REMOTE_ADDR'] = '10.10.10.10';
|
||||
$this->assertTrue(TrafficLimiter::canPass(), 'IPv4 in exempted range');
|
||||
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv4 in exempted range');
|
||||
$_SERVER['REMOTE_ADDR'] = '2001:1620:2057:dead:beef::cafe:babe';
|
||||
$this->assertTrue(TrafficLimiter::canPass(), 'IPv6 in exempted range');
|
||||
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv6 in exempted range');
|
||||
TrafficLimiter::setExemptedIp('127.*,foobar');
|
||||
$this->assertFalse(TrafficLimiter::canPass(), 'request is to fast, invalid range');
|
||||
$_SERVER['REMOTE_ADDR'] = 'foobar';
|
||||
$this->assertTrue(TrafficLimiter::canPass(), 'non-IP address');
|
||||
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but non-IP address matches exempted range');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use PrivateBin\Data\Filesystem;
|
||||
use PrivateBin\Persistence\ServerSalt;
|
||||
use PrivateBin\Vizhash16x16;
|
||||
|
||||
|
@ -17,7 +18,7 @@ class Vizhash16x16Test extends PHPUnit_Framework_TestCase
|
|||
mkdir($this->_path);
|
||||
}
|
||||
$this->_file = $this->_path . DIRECTORY_SEPARATOR . 'vizhash.png';
|
||||
ServerSalt::setPath($this->_path);
|
||||
ServerSalt::setStore(Filesystem::getInstance(array('dir' => $this->_path)));
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
|
|
15
vendor/composer/autoload_classmap.php
vendored
15
vendor/composer/autoload_classmap.php
vendored
|
@ -6,6 +6,20 @@ $vendorDir = dirname(dirname(__FILE__));
|
|||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'IPLib\\Address\\AddressInterface' => $vendorDir . '/mlocati/ip-lib/src/Address/AddressInterface.php',
|
||||
'IPLib\\Address\\AssignedRange' => $vendorDir . '/mlocati/ip-lib/src/Address/AssignedRange.php',
|
||||
'IPLib\\Address\\IPv4' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv4.php',
|
||||
'IPLib\\Address\\IPv6' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv6.php',
|
||||
'IPLib\\Address\\Type' => $vendorDir . '/mlocati/ip-lib/src/Address/Type.php',
|
||||
'IPLib\\Factory' => $vendorDir . '/mlocati/ip-lib/src/Factory.php',
|
||||
'IPLib\\Range\\AbstractRange' => $vendorDir . '/mlocati/ip-lib/src/Range/AbstractRange.php',
|
||||
'IPLib\\Range\\Pattern' => $vendorDir . '/mlocati/ip-lib/src/Range/Pattern.php',
|
||||
'IPLib\\Range\\RangeInterface' => $vendorDir . '/mlocati/ip-lib/src/Range/RangeInterface.php',
|
||||
'IPLib\\Range\\Single' => $vendorDir . '/mlocati/ip-lib/src/Range/Single.php',
|
||||
'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\\RangesFromBounradyCalculator' => $vendorDir . '/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php',
|
||||
'Identicon\\Generator\\BaseGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php',
|
||||
'Identicon\\Generator\\GdGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/GdGenerator.php',
|
||||
'Identicon\\Generator\\GeneratorInterface' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/GeneratorInterface.php',
|
||||
|
@ -17,6 +31,7 @@ return array(
|
|||
'PrivateBin\\Data\\AbstractData' => $baseDir . '/lib/Data/AbstractData.php',
|
||||
'PrivateBin\\Data\\Database' => $baseDir . '/lib/Data/Database.php',
|
||||
'PrivateBin\\Data\\Filesystem' => $baseDir . '/lib/Data/Filesystem.php',
|
||||
'PrivateBin\\Data\\GoogleCloudStorage' => $baseDir . '/lib/Data/GoogleCloudStorage.php',
|
||||
'PrivateBin\\Filter' => $baseDir . '/lib/Filter.php',
|
||||
'PrivateBin\\FormatV2' => $baseDir . '/lib/FormatV2.php',
|
||||
'PrivateBin\\I18n' => $baseDir . '/lib/I18n.php',
|
||||
|
|
1
vendor/composer/autoload_psr4.php
vendored
1
vendor/composer/autoload_psr4.php
vendored
|
@ -8,4 +8,5 @@ $baseDir = dirname($vendorDir);
|
|||
return array(
|
||||
'PrivateBin\\' => array($baseDir . '/lib'),
|
||||
'Identicon\\' => array($vendorDir . '/yzalis/identicon/src/Identicon'),
|
||||
'IPLib\\' => array($vendorDir . '/mlocati/ip-lib/src'),
|
||||
);
|
||||
|
|
20
vendor/composer/autoload_static.php
vendored
20
vendor/composer/autoload_static.php
vendored
|
@ -18,6 +18,7 @@ class ComposerStaticInitDontChange
|
|||
'I' =>
|
||||
array (
|
||||
'Identicon\\' => 10,
|
||||
'IPLib\\' => 6,
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -30,9 +31,27 @@ class ComposerStaticInitDontChange
|
|||
array (
|
||||
0 => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon',
|
||||
),
|
||||
'IPLib\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/mlocati/ip-lib/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'IPLib\\Address\\AddressInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AddressInterface.php',
|
||||
'IPLib\\Address\\AssignedRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AssignedRange.php',
|
||||
'IPLib\\Address\\IPv4' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv4.php',
|
||||
'IPLib\\Address\\IPv6' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv6.php',
|
||||
'IPLib\\Address\\Type' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/Type.php',
|
||||
'IPLib\\Factory' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Factory.php',
|
||||
'IPLib\\Range\\AbstractRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/AbstractRange.php',
|
||||
'IPLib\\Range\\Pattern' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Pattern.php',
|
||||
'IPLib\\Range\\RangeInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/RangeInterface.php',
|
||||
'IPLib\\Range\\Single' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Single.php',
|
||||
'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\\RangesFromBounradyCalculator' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php',
|
||||
'Identicon\\Generator\\BaseGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php',
|
||||
'Identicon\\Generator\\GdGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/GdGenerator.php',
|
||||
'Identicon\\Generator\\GeneratorInterface' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/GeneratorInterface.php',
|
||||
|
@ -44,6 +63,7 @@ class ComposerStaticInitDontChange
|
|||
'PrivateBin\\Data\\AbstractData' => __DIR__ . '/../..' . '/lib/Data/AbstractData.php',
|
||||
'PrivateBin\\Data\\Database' => __DIR__ . '/../..' . '/lib/Data/Database.php',
|
||||
'PrivateBin\\Data\\Filesystem' => __DIR__ . '/../..' . '/lib/Data/Filesystem.php',
|
||||
'PrivateBin\\Data\\GoogleCloudStorage' => __DIR__ . '/../..' . '/lib/Data/GoogleCloudStorage.php',
|
||||
'PrivateBin\\Filter' => __DIR__ . '/../..' . '/lib/Filter.php',
|
||||
'PrivateBin\\FormatV2' => __DIR__ . '/../..' . '/lib/FormatV2.php',
|
||||
'PrivateBin\\I18n' => __DIR__ . '/../..' . '/lib/I18n.php',
|
||||
|
|
13
vendor/mlocati/ip-lib/ip-lib.php
vendored
Normal file
13
vendor/mlocati/ip-lib/ip-lib.php
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
spl_autoload_register(
|
||||
function ($class) {
|
||||
if (strpos($class, 'IPLib\\') !== 0) {
|
||||
return;
|
||||
}
|
||||
$file = __DIR__ . DIRECTORY_SEPARATOR . 'src' . str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('IPLib'))) . '.php';
|
||||
if (is_file($file)) {
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
);
|
125
vendor/mlocati/ip-lib/src/Address/AddressInterface.php
vendored
Normal file
125
vendor/mlocati/ip-lib/src/Address/AddressInterface.php
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Address;
|
||||
|
||||
use IPLib\Range\RangeInterface;
|
||||
|
||||
/**
|
||||
* Interface of all the IP address types.
|
||||
*/
|
||||
interface AddressInterface
|
||||
{
|
||||
/**
|
||||
* Get the short string representation of this address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* Get the number of bits representing this address type.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @example 32 for IPv4
|
||||
* @example 128 for IPv6
|
||||
*/
|
||||
public static function getNumberOfBits();
|
||||
|
||||
/**
|
||||
* Get the string representation of this address.
|
||||
*
|
||||
* @param bool $long set to true to have a long/full representation, false otherwise
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @example If $long is true, you'll get '0000:0000:0000:0000:0000:0000:0000:0001', '::1' otherwise.
|
||||
*/
|
||||
public function toString($long = false);
|
||||
|
||||
/**
|
||||
* Get the byte list of the IP address.
|
||||
*
|
||||
* @return int[]
|
||||
*
|
||||
* @example For localhost: for IPv4 you'll get array(127, 0, 0, 1), for IPv6 array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
|
||||
*/
|
||||
public function getBytes();
|
||||
|
||||
/**
|
||||
* Get the full bit list the IP address.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @example For localhost: For IPv4 you'll get '01111111000000000000000000000001' (32 digits), for IPv6 '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' (128 digits)
|
||||
*/
|
||||
public function getBits();
|
||||
|
||||
/**
|
||||
* Get the type of the IP address.
|
||||
*
|
||||
* @return int One of the \IPLib\Address\Type::T_... constants
|
||||
*/
|
||||
public function getAddressType();
|
||||
|
||||
/**
|
||||
* Get the default RFC reserved range type.
|
||||
*
|
||||
* @return int One of the \IPLib\Range\Type::T_... constants
|
||||
*/
|
||||
public static function getDefaultReservedRangeType();
|
||||
|
||||
/**
|
||||
* Get the RFC reserved ranges (except the ones of type getDefaultReservedRangeType).
|
||||
*
|
||||
* @return \IPLib\Address\AssignedRange[] ranges are sorted
|
||||
*/
|
||||
public static function getReservedRanges();
|
||||
|
||||
/**
|
||||
* Get the type of range of the IP address.
|
||||
*
|
||||
* @return int One of the \IPLib\Range\Type::T_... constants
|
||||
*/
|
||||
public function getRangeType();
|
||||
|
||||
/**
|
||||
* Get a string representation of this address than can be used when comparing addresses and ranges.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComparableString();
|
||||
|
||||
/**
|
||||
* Check if this address is contained in an range.
|
||||
*
|
||||
* @param \IPLib\Range\RangeInterface $range
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matches(RangeInterface $range);
|
||||
|
||||
/**
|
||||
* Get the address right after this IP address (if available).
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface|null
|
||||
*/
|
||||
public function getNextAddress();
|
||||
|
||||
/**
|
||||
* Get the address right before this IP address (if available).
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface|null
|
||||
*/
|
||||
public function getPreviousAddress();
|
||||
|
||||
/**
|
||||
* Get the Reverse DNS Lookup Address of this IP address.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @example for IPv4 it returns something like x.x.x.x.in-addr.arpa
|
||||
* @example for IPv6 it returns something like x.x.x.x..x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa
|
||||
*/
|
||||
public function getReverseDNSLookupName();
|
||||
}
|
138
vendor/mlocati/ip-lib/src/Address/AssignedRange.php
vendored
Normal file
138
vendor/mlocati/ip-lib/src/Address/AssignedRange.php
vendored
Normal file
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Address;
|
||||
|
||||
use IPLib\Range\RangeInterface;
|
||||
|
||||
/**
|
||||
* Represents an IP address range with an assigned range type.
|
||||
*/
|
||||
class AssignedRange
|
||||
{
|
||||
/**
|
||||
* The range definition.
|
||||
*
|
||||
* @var \IPLib\Range\RangeInterface
|
||||
*/
|
||||
protected $range;
|
||||
|
||||
/**
|
||||
* The range type.
|
||||
*
|
||||
* @var int one of the \IPLib\Range\Type::T_ constants
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The list of exceptions for this range type.
|
||||
*
|
||||
* @var \IPLib\Address\AssignedRange[]
|
||||
*/
|
||||
protected $exceptions;
|
||||
|
||||
/**
|
||||
* Initialize the instance.
|
||||
*
|
||||
* @param \IPLib\Range\RangeInterface $range the range definition
|
||||
* @param int $type The range type (one of the \IPLib\Range\Type::T_ constants)
|
||||
* @param \IPLib\Address\AssignedRange[] $exceptions the list of exceptions for this range type
|
||||
*/
|
||||
public function __construct(RangeInterface $range, $type, array $exceptions = array())
|
||||
{
|
||||
$this->range = $range;
|
||||
$this->type = $type;
|
||||
$this->exceptions = $exceptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range definition.
|
||||
*
|
||||
* @return \IPLib\Range\RangeInterface
|
||||
*/
|
||||
public function getRange()
|
||||
{
|
||||
return $this->range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range type.
|
||||
*
|
||||
* @return int one of the \IPLib\Range\Type::T_ constants
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of exceptions for this range type.
|
||||
*
|
||||
* @return \IPLib\Address\AssignedRange[]
|
||||
*/
|
||||
public function getExceptions()
|
||||
{
|
||||
return $this->exceptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assigned type for a specific address.
|
||||
*
|
||||
* @param \IPLib\Address\AddressInterface $address
|
||||
*
|
||||
* @return int|null return NULL of the address is outside this address; a \IPLib\Range\Type::T_ constant otherwise
|
||||
*/
|
||||
public function getAddressType(AddressInterface $address)
|
||||
{
|
||||
$result = null;
|
||||
if ($this->range->contains($address)) {
|
||||
foreach ($this->exceptions as $exception) {
|
||||
$result = $exception->getAddressType($address);
|
||||
if ($result !== null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($result === null) {
|
||||
$result = $this->type;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assigned type for a specific address range.
|
||||
*
|
||||
* @param \IPLib\Range\RangeInterface $range
|
||||
*
|
||||
* @return int|false|null return NULL of the range is fully outside this range; false if it's partly crosses this range (or it contains mixed types); a \IPLib\Range\Type::T_ constant otherwise
|
||||
*/
|
||||
public function getRangeType(RangeInterface $range)
|
||||
{
|
||||
$myStart = $this->range->getComparableStartString();
|
||||
$rangeEnd = $range->getComparableEndString();
|
||||
if ($myStart > $rangeEnd) {
|
||||
$result = null;
|
||||
} else {
|
||||
$myEnd = $this->range->getComparableEndString();
|
||||
$rangeStart = $range->getComparableStartString();
|
||||
if ($myEnd < $rangeStart) {
|
||||
$result = null;
|
||||
} elseif ($rangeStart < $myStart || $rangeEnd > $myEnd) {
|
||||
$result = false;
|
||||
} else {
|
||||
$result = null;
|
||||
foreach ($this->exceptions as $exception) {
|
||||
$result = $exception->getRangeType($range);
|
||||
if ($result !== null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($result === null) {
|
||||
$result = $this->getType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
465
vendor/mlocati/ip-lib/src/Address/IPv4.php
vendored
Normal file
465
vendor/mlocati/ip-lib/src/Address/IPv4.php
vendored
Normal file
|
@ -0,0 +1,465 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Address;
|
||||
|
||||
use IPLib\Range\RangeInterface;
|
||||
use IPLib\Range\Subnet;
|
||||
use IPLib\Range\Type as RangeType;
|
||||
|
||||
/**
|
||||
* An IPv4 address.
|
||||
*/
|
||||
class IPv4 implements AddressInterface
|
||||
{
|
||||
/**
|
||||
* The string representation of the address.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @example '127.0.0.1'
|
||||
*/
|
||||
protected $address;
|
||||
|
||||
/**
|
||||
* The byte list of the IP address.
|
||||
*
|
||||
* @var int[]|null
|
||||
*/
|
||||
protected $bytes;
|
||||
|
||||
/**
|
||||
* The type of the range of this IP address.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $rangeType;
|
||||
|
||||
/**
|
||||
* A string representation of this address than can be used when comparing addresses and ranges.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $comparableString;
|
||||
|
||||
/**
|
||||
* An array containing RFC designated address ranges.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private static $reservedRanges = null;
|
||||
|
||||
/**
|
||||
* Initializes the instance.
|
||||
*
|
||||
* @param string $address
|
||||
*/
|
||||
protected function __construct($address)
|
||||
{
|
||||
$this->address = $address;
|
||||
$this->bytes = null;
|
||||
$this->rangeType = null;
|
||||
$this->comparableString = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::__toString()
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getNumberOfBits()
|
||||
*/
|
||||
public static function getNumberOfBits()
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string and returns an IPv4 instance if the string is valid, or null otherwise.
|
||||
*
|
||||
* @param string|mixed $address the address to parse
|
||||
* @param bool $mayIncludePort set to false to avoid parsing addresses with ports
|
||||
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
|
||||
*
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false)
|
||||
{
|
||||
if (!is_string($address) || !strpos($address, '.')) {
|
||||
return null;
|
||||
}
|
||||
$rxChunk = '0?[0-9]{1,3}';
|
||||
if ($supportNonDecimalIPv4) {
|
||||
$rxChunk = "(?:0[Xx]0*[0-9A-Fa-f]{1,2})|(?:{$rxChunk})";
|
||||
}
|
||||
$rx = "0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})";
|
||||
if ($mayIncludePort) {
|
||||
$rx .= '(?::\d+)?';
|
||||
}
|
||||
$matches = null;
|
||||
if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
|
||||
return null;
|
||||
}
|
||||
$nums = array();
|
||||
for ($i = 1; $i <= 4; $i++) {
|
||||
$s = $matches[$i];
|
||||
if ($supportNonDecimalIPv4) {
|
||||
if (stripos($s, '0x') === 0) {
|
||||
$n = hexdec(substr($s, 2));
|
||||
} elseif ($s[0] === '0') {
|
||||
if (!preg_match('/^[0-7]+$/', $s)) {
|
||||
return null;
|
||||
}
|
||||
$n = octdec(substr($s, 1));
|
||||
} else {
|
||||
$n = (int) $s;
|
||||
}
|
||||
} else {
|
||||
$n = (int) $s;
|
||||
}
|
||||
if ($n < 0 || $n > 255) {
|
||||
return null;
|
||||
}
|
||||
$nums[] = (string) $n;
|
||||
}
|
||||
|
||||
return new static(implode('.', $nums));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise.
|
||||
*
|
||||
* @param int[]|array $bytes
|
||||
*
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromBytes(array $bytes)
|
||||
{
|
||||
$result = null;
|
||||
if (count($bytes) === 4) {
|
||||
$chunks = array_map(
|
||||
function ($byte) {
|
||||
return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false;
|
||||
},
|
||||
$bytes
|
||||
);
|
||||
if (in_array(false, $chunks, true) === false) {
|
||||
$result = new static(implode('.', $chunks));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::toString()
|
||||
*/
|
||||
public function toString($long = false)
|
||||
{
|
||||
if ($long) {
|
||||
return $this->getComparableString();
|
||||
}
|
||||
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the octal representation of this IP address.
|
||||
*
|
||||
* @param bool $long
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377'
|
||||
* @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377'
|
||||
*/
|
||||
public function toOctal($long = false)
|
||||
{
|
||||
$chunks = array();
|
||||
foreach ($this->getBytes() as $byte) {
|
||||
if ($long) {
|
||||
$chunks[] = sprintf('%04o', $byte);
|
||||
} else {
|
||||
$chunks[] = '0' . decoct($byte);
|
||||
}
|
||||
}
|
||||
|
||||
return implode('.', $chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hexadecimal representation of this IP address.
|
||||
*
|
||||
* @param bool $long
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff'
|
||||
* @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff'
|
||||
*/
|
||||
public function toHexadecimal($long = false)
|
||||
{
|
||||
$chunks = array();
|
||||
foreach ($this->getBytes() as $byte) {
|
||||
if ($long) {
|
||||
$chunks[] = sprintf('0x%02x', $byte);
|
||||
} else {
|
||||
$chunks[] = '0x' . dechex($byte);
|
||||
}
|
||||
}
|
||||
|
||||
return implode('.', $chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getBytes()
|
||||
*/
|
||||
public function getBytes()
|
||||
{
|
||||
if ($this->bytes === null) {
|
||||
$this->bytes = array_map(
|
||||
function ($chunk) {
|
||||
return (int) $chunk;
|
||||
},
|
||||
explode('.', $this->address)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getBits()
|
||||
*/
|
||||
public function getBits()
|
||||
{
|
||||
$parts = array();
|
||||
foreach ($this->getBytes() as $byte) {
|
||||
$parts[] = sprintf('%08b', $byte);
|
||||
}
|
||||
|
||||
return implode('', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getAddressType()
|
||||
*/
|
||||
public function getAddressType()
|
||||
{
|
||||
return Type::T_IPv4;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
|
||||
*/
|
||||
public static function getDefaultReservedRangeType()
|
||||
{
|
||||
return RangeType::T_PUBLIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getReservedRanges()
|
||||
*/
|
||||
public static function getReservedRanges()
|
||||
{
|
||||
if (self::$reservedRanges === null) {
|
||||
$reservedRanges = array();
|
||||
foreach (array(
|
||||
// RFC 5735
|
||||
'0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)),
|
||||
// RFC 5735
|
||||
'10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK),
|
||||
// RFC 6598
|
||||
'100.64.0.0/10' => array(RangeType::T_CGNAT),
|
||||
// RFC 5735
|
||||
'127.0.0.0/8' => array(RangeType::T_LOOPBACK),
|
||||
// RFC 5735
|
||||
'169.254.0.0/16' => array(RangeType::T_LINKLOCAL),
|
||||
// RFC 5735
|
||||
'172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK),
|
||||
// RFC 5735
|
||||
'192.0.0.0/24' => array(RangeType::T_RESERVED),
|
||||
// RFC 5735
|
||||
'192.0.2.0/24' => array(RangeType::T_RESERVED),
|
||||
// RFC 5735
|
||||
'192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY),
|
||||
// RFC 5735
|
||||
'192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK),
|
||||
// RFC 5735
|
||||
'198.18.0.0/15' => array(RangeType::T_RESERVED),
|
||||
// RFC 5735
|
||||
'198.51.100.0/24' => array(RangeType::T_RESERVED),
|
||||
// RFC 5735
|
||||
'203.0.113.0/24' => array(RangeType::T_RESERVED),
|
||||
// RFC 5735
|
||||
'224.0.0.0/4' => array(RangeType::T_MULTICAST),
|
||||
// RFC 5735
|
||||
'240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)),
|
||||
) as $range => $data) {
|
||||
$exceptions = array();
|
||||
if (isset($data[1])) {
|
||||
foreach ($data[1] as $exceptionRange => $exceptionType) {
|
||||
$exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType);
|
||||
}
|
||||
}
|
||||
$reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions);
|
||||
}
|
||||
self::$reservedRanges = $reservedRanges;
|
||||
}
|
||||
|
||||
return self::$reservedRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getRangeType()
|
||||
*/
|
||||
public function getRangeType()
|
||||
{
|
||||
if ($this->rangeType === null) {
|
||||
$rangeType = null;
|
||||
foreach (static::getReservedRanges() as $reservedRange) {
|
||||
$rangeType = $reservedRange->getAddressType($this);
|
||||
if ($rangeType !== null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
|
||||
}
|
||||
|
||||
return $this->rangeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IPv6 representation of this address (in 6to4 notation).
|
||||
*
|
||||
* @return \IPLib\Address\IPv6
|
||||
*/
|
||||
public function toIPv6()
|
||||
{
|
||||
$myBytes = $this->getBytes();
|
||||
|
||||
return IPv6::fromString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation).
|
||||
*
|
||||
* @return \IPLib\Address\IPv6
|
||||
*/
|
||||
public function toIPv6IPv4Mapped()
|
||||
{
|
||||
return IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getComparableString()
|
||||
*/
|
||||
public function getComparableString()
|
||||
{
|
||||
if ($this->comparableString === null) {
|
||||
$chunks = array();
|
||||
foreach ($this->getBytes() as $byte) {
|
||||
$chunks[] = sprintf('%03d', $byte);
|
||||
}
|
||||
$this->comparableString = implode('.', $chunks);
|
||||
}
|
||||
|
||||
return $this->comparableString;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::matches()
|
||||
*/
|
||||
public function matches(RangeInterface $range)
|
||||
{
|
||||
return $range->contains($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getNextAddress()
|
||||
*/
|
||||
public function getNextAddress()
|
||||
{
|
||||
$overflow = false;
|
||||
$bytes = $this->getBytes();
|
||||
for ($i = count($bytes) - 1; $i >= 0; $i--) {
|
||||
if ($bytes[$i] === 255) {
|
||||
if ($i === 0) {
|
||||
$overflow = true;
|
||||
break;
|
||||
}
|
||||
$bytes[$i] = 0;
|
||||
} else {
|
||||
$bytes[$i]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $overflow ? null : static::fromBytes($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getPreviousAddress()
|
||||
*/
|
||||
public function getPreviousAddress()
|
||||
{
|
||||
$overflow = false;
|
||||
$bytes = $this->getBytes();
|
||||
for ($i = count($bytes) - 1; $i >= 0; $i--) {
|
||||
if ($bytes[$i] === 0) {
|
||||
if ($i === 0) {
|
||||
$overflow = true;
|
||||
break;
|
||||
}
|
||||
$bytes[$i] = 255;
|
||||
} else {
|
||||
$bytes[$i]--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $overflow ? null : static::fromBytes($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
|
||||
*/
|
||||
public function getReverseDNSLookupName()
|
||||
{
|
||||
return implode(
|
||||
'.',
|
||||
array_reverse($this->getBytes())
|
||||
) . '.in-addr.arpa';
|
||||
}
|
||||
}
|
568
vendor/mlocati/ip-lib/src/Address/IPv6.php
vendored
Normal file
568
vendor/mlocati/ip-lib/src/Address/IPv6.php
vendored
Normal file
|
@ -0,0 +1,568 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Address;
|
||||
|
||||
use IPLib\Range\RangeInterface;
|
||||
use IPLib\Range\Subnet;
|
||||
use IPLib\Range\Type as RangeType;
|
||||
|
||||
/**
|
||||
* An IPv6 address.
|
||||
*/
|
||||
class IPv6 implements AddressInterface
|
||||
{
|
||||
/**
|
||||
* The long string representation of the address.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @example '0000:0000:0000:0000:0000:0000:0000:0001'
|
||||
*/
|
||||
protected $longAddress;
|
||||
|
||||
/**
|
||||
* The long string representation of the address.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @example '::1'
|
||||
*/
|
||||
protected $shortAddress;
|
||||
|
||||
/**
|
||||
* The byte list of the IP address.
|
||||
*
|
||||
* @var int[]|null
|
||||
*/
|
||||
protected $bytes;
|
||||
|
||||
/**
|
||||
* The word list of the IP address.
|
||||
*
|
||||
* @var int[]|null
|
||||
*/
|
||||
protected $words;
|
||||
|
||||
/**
|
||||
* The type of the range of this IP address.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $rangeType;
|
||||
|
||||
/**
|
||||
* An array containing RFC designated address ranges.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private static $reservedRanges = null;
|
||||
|
||||
/**
|
||||
* Initializes the instance.
|
||||
*
|
||||
* @param string $longAddress
|
||||
*/
|
||||
public function __construct($longAddress)
|
||||
{
|
||||
$this->longAddress = $longAddress;
|
||||
$this->shortAddress = null;
|
||||
$this->bytes = null;
|
||||
$this->words = null;
|
||||
$this->rangeType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::__toString()
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getNumberOfBits()
|
||||
*/
|
||||
public static function getNumberOfBits()
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string and returns an IPv6 instance if the string is valid, or null otherwise.
|
||||
*
|
||||
* @param string|mixed $address the address to parse
|
||||
* @param bool $mayIncludePort set to false to avoid parsing addresses with ports
|
||||
* @param bool $mayIncludeZoneID set to false to avoid parsing addresses with zone IDs (see RFC 4007)
|
||||
*
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromString($address, $mayIncludePort = true, $mayIncludeZoneID = true)
|
||||
{
|
||||
$result = null;
|
||||
if (is_string($address) && strpos($address, ':') !== false && strpos($address, ':::') === false) {
|
||||
$matches = null;
|
||||
if ($mayIncludePort && $address[0] === '[' && preg_match('/^\[(.+)]:\d+$/', $address, $matches)) {
|
||||
$address = $matches[1];
|
||||
}
|
||||
if ($mayIncludeZoneID) {
|
||||
$percentagePos = strpos($address, '%');
|
||||
if ($percentagePos > 0) {
|
||||
$address = substr($address, 0, $percentagePos);
|
||||
}
|
||||
}
|
||||
if (preg_match('/^((?:[0-9a-f]*:+)+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i', $address, $matches)) {
|
||||
$address6 = static::fromString($matches[1] . '0:0', false);
|
||||
if ($address6 !== null) {
|
||||
$address4 = IPv4::fromString($matches[2], false);
|
||||
if ($address4 !== null) {
|
||||
$bytes4 = $address4->getBytes();
|
||||
$address6->longAddress = substr($address6->longAddress, 0, -9) . sprintf('%02x%02x:%02x%02x', $bytes4[0], $bytes4[1], $bytes4[2], $bytes4[3]);
|
||||
$result = $address6;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (strpos($address, '::') === false) {
|
||||
$chunks = explode(':', $address);
|
||||
} else {
|
||||
$chunks = array();
|
||||
$parts = explode('::', $address);
|
||||
if (count($parts) === 2) {
|
||||
$before = ($parts[0] === '') ? array() : explode(':', $parts[0]);
|
||||
$after = ($parts[1] === '') ? array() : explode(':', $parts[1]);
|
||||
$missing = 8 - count($before) - count($after);
|
||||
if ($missing >= 0) {
|
||||
$chunks = $before;
|
||||
if ($missing !== 0) {
|
||||
$chunks = array_merge($chunks, array_fill(0, $missing, '0'));
|
||||
}
|
||||
$chunks = array_merge($chunks, $after);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($chunks) === 8) {
|
||||
$nums = array_map(
|
||||
function ($chunk) {
|
||||
return preg_match('/^[0-9A-Fa-f]{1,4}$/', $chunk) ? hexdec($chunk) : false;
|
||||
},
|
||||
$chunks
|
||||
);
|
||||
if (!in_array(false, $nums, true)) {
|
||||
$longAddress = implode(
|
||||
':',
|
||||
array_map(
|
||||
function ($num) {
|
||||
return sprintf('%04x', $num);
|
||||
},
|
||||
$nums
|
||||
)
|
||||
);
|
||||
$result = new static($longAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array of bytes and returns an IPv6 instance if the array is valid, or null otherwise.
|
||||
*
|
||||
* @param int[]|array $bytes
|
||||
*
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromBytes(array $bytes)
|
||||
{
|
||||
$result = null;
|
||||
if (count($bytes) === 16) {
|
||||
$address = '';
|
||||
for ($i = 0; $i < 16; $i++) {
|
||||
if ($i !== 0 && $i % 2 === 0) {
|
||||
$address .= ':';
|
||||
}
|
||||
$byte = $bytes[$i];
|
||||
if (is_int($byte) && $byte >= 0 && $byte <= 255) {
|
||||
$address .= sprintf('%02x', $byte);
|
||||
} else {
|
||||
$address = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($address !== null) {
|
||||
$result = new static($address);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array of words and returns an IPv6 instance if the array is valid, or null otherwise.
|
||||
*
|
||||
* @param int[]|array $words
|
||||
*
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromWords(array $words)
|
||||
{
|
||||
$result = null;
|
||||
if (count($words) === 8) {
|
||||
$chunks = array();
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
$word = $words[$i];
|
||||
if (is_int($word) && $word >= 0 && $word <= 0xffff) {
|
||||
$chunks[] = sprintf('%04x', $word);
|
||||
} else {
|
||||
$chunks = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($chunks !== null) {
|
||||
$result = new static(implode(':', $chunks));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::toString()
|
||||
*/
|
||||
public function toString($long = false)
|
||||
{
|
||||
if ($long) {
|
||||
$result = $this->longAddress;
|
||||
} else {
|
||||
if ($this->shortAddress === null) {
|
||||
if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) {
|
||||
$lastBytes = array_slice($this->getBytes(), -4);
|
||||
$this->shortAddress = '::ffff:' . implode('.', $lastBytes);
|
||||
} else {
|
||||
$chunks = array_map(
|
||||
function ($word) {
|
||||
return dechex($word);
|
||||
},
|
||||
$this->getWords()
|
||||
);
|
||||
$shortAddress = implode(':', $chunks);
|
||||
$matches = null;
|
||||
for ($i = 8; $i > 1; $i--) {
|
||||
$search = '(?:^|:)' . rtrim(str_repeat('0:', $i), ':') . '(?:$|:)';
|
||||
if (preg_match('/^(.*?)' . $search . '(.*)$/', $shortAddress, $matches)) {
|
||||
$shortAddress = $matches[1] . '::' . $matches[2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->shortAddress = $shortAddress;
|
||||
}
|
||||
}
|
||||
$result = $this->shortAddress;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getBytes()
|
||||
*/
|
||||
public function getBytes()
|
||||
{
|
||||
if ($this->bytes === null) {
|
||||
$bytes = array();
|
||||
foreach ($this->getWords() as $word) {
|
||||
$bytes[] = $word >> 8;
|
||||
$bytes[] = $word & 0xff;
|
||||
}
|
||||
$this->bytes = $bytes;
|
||||
}
|
||||
|
||||
return $this->bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getBits()
|
||||
*/
|
||||
public function getBits()
|
||||
{
|
||||
$parts = array();
|
||||
foreach ($this->getBytes() as $byte) {
|
||||
$parts[] = sprintf('%08b', $byte);
|
||||
}
|
||||
|
||||
return implode('', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the word list of the IP address.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getWords()
|
||||
{
|
||||
if ($this->words === null) {
|
||||
$this->words = array_map(
|
||||
function ($chunk) {
|
||||
return hexdec($chunk);
|
||||
},
|
||||
explode(':', $this->longAddress)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->words;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getAddressType()
|
||||
*/
|
||||
public function getAddressType()
|
||||
{
|
||||
return Type::T_IPv6;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
|
||||
*/
|
||||
public static function getDefaultReservedRangeType()
|
||||
{
|
||||
return RangeType::T_RESERVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getReservedRanges()
|
||||
*/
|
||||
public static function getReservedRanges()
|
||||
{
|
||||
if (self::$reservedRanges === null) {
|
||||
$reservedRanges = array();
|
||||
foreach (array(
|
||||
// RFC 4291
|
||||
'::/128' => array(RangeType::T_UNSPECIFIED),
|
||||
// RFC 4291
|
||||
'::1/128' => array(RangeType::T_LOOPBACK),
|
||||
// RFC 4291
|
||||
'100::/8' => array(RangeType::T_DISCARD, array('100::/64' => RangeType::T_DISCARDONLY)),
|
||||
//'2002::/16' => array(RangeType::),
|
||||
// RFC 4291
|
||||
'2000::/3' => array(RangeType::T_PUBLIC),
|
||||
// RFC 4193
|
||||
'fc00::/7' => array(RangeType::T_PRIVATENETWORK),
|
||||
// RFC 4291
|
||||
'fe80::/10' => array(RangeType::T_LINKLOCAL_UNICAST),
|
||||
// RFC 4291
|
||||
'ff00::/8' => array(RangeType::T_MULTICAST),
|
||||
// RFC 4291
|
||||
//'::/8' => array(RangeType::T_RESERVED),
|
||||
// RFC 4048
|
||||
//'200::/7' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'400::/6' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'800::/5' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'1000::/4' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'4000::/3' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'6000::/3' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'8000::/3' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'a000::/3' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'c000::/3' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'e000::/4' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'f000::/5' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'f800::/6' => array(RangeType::T_RESERVED),
|
||||
// RFC 4291
|
||||
//'fe00::/9' => array(RangeType::T_RESERVED),
|
||||
// RFC 3879
|
||||
//'fec0::/10' => array(RangeType::T_RESERVED),
|
||||
) as $range => $data) {
|
||||
$exceptions = array();
|
||||
if (isset($data[1])) {
|
||||
foreach ($data[1] as $exceptionRange => $exceptionType) {
|
||||
$exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType);
|
||||
}
|
||||
}
|
||||
$reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions);
|
||||
}
|
||||
self::$reservedRanges = $reservedRanges;
|
||||
}
|
||||
|
||||
return self::$reservedRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getRangeType()
|
||||
*/
|
||||
public function getRangeType()
|
||||
{
|
||||
if ($this->rangeType === null) {
|
||||
$ipv4 = $this->toIPv4();
|
||||
if ($ipv4 !== null) {
|
||||
$this->rangeType = $ipv4->getRangeType();
|
||||
} else {
|
||||
$rangeType = null;
|
||||
foreach (static::getReservedRanges() as $reservedRange) {
|
||||
$rangeType = $reservedRange->getAddressType($this);
|
||||
if ($rangeType !== null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->rangeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IPv4 representation of this address (if possible, otherwise returns null).
|
||||
*
|
||||
* @return \IPLib\Address\IPv4|null
|
||||
*/
|
||||
public function toIPv4()
|
||||
{
|
||||
if (strpos($this->longAddress, '2002:') === 0) {
|
||||
// 6to4
|
||||
return IPv4::fromBytes(array_slice($this->getBytes(), 2, 4));
|
||||
}
|
||||
if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) {
|
||||
// IPv4-mapped IPv6 addresses
|
||||
return IPv4::fromBytes(array_slice($this->getBytes(), -4));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this IPv6 address in the "mixed" IPv6 (first 12 bytes) + IPv4 (last 4 bytes) mixed syntax.
|
||||
*
|
||||
* @param bool $ipV6Long render the IPv6 part in "long" format?
|
||||
* @param bool $ipV4Long render the IPv4 part in "long" format?
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @example '::13.1.68.3'
|
||||
* @example '0000:0000:0000:0000:0000:0000:13.1.68.3' when $ipV6Long is true
|
||||
* @example '::013.001.068.003' when $ipV4Long is true
|
||||
* @example '0000:0000:0000:0000:0000:0000:013.001.068.003' when $ipV6Long and $ipV4Long are true
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc4291#section-2.2 point 3.
|
||||
*/
|
||||
public function toMixedIPv6IPv4String($ipV6Long = false, $ipV4Long = false)
|
||||
{
|
||||
$myBytes = $this->getBytes();
|
||||
$ipv6Bytes = array_merge(array_slice($myBytes, 0, 12), array(0xff, 0xff, 0xff, 0xff));
|
||||
$ipv6String = static::fromBytes($ipv6Bytes)->toString($ipV6Long);
|
||||
$ipv4Bytes = array_slice($myBytes, 12, 4);
|
||||
$ipv4String = IPv4::fromBytes($ipv4Bytes)->toString($ipV4Long);
|
||||
|
||||
return preg_replace('/((ffff:ffff)|(\d+(\.\d+){3}))$/i', $ipv4String, $ipv6String);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getComparableString()
|
||||
*/
|
||||
public function getComparableString()
|
||||
{
|
||||
return $this->longAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::matches()
|
||||
*/
|
||||
public function matches(RangeInterface $range)
|
||||
{
|
||||
return $range->contains($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getNextAddress()
|
||||
*/
|
||||
public function getNextAddress()
|
||||
{
|
||||
$overflow = false;
|
||||
$words = $this->getWords();
|
||||
for ($i = count($words) - 1; $i >= 0; $i--) {
|
||||
if ($words[$i] === 0xffff) {
|
||||
if ($i === 0) {
|
||||
$overflow = true;
|
||||
break;
|
||||
}
|
||||
$words[$i] = 0;
|
||||
} else {
|
||||
$words[$i]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $overflow ? null : static::fromWords($words);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getPreviousAddress()
|
||||
*/
|
||||
public function getPreviousAddress()
|
||||
{
|
||||
$overflow = false;
|
||||
$words = $this->getWords();
|
||||
for ($i = count($words) - 1; $i >= 0; $i--) {
|
||||
if ($words[$i] === 0) {
|
||||
if ($i === 0) {
|
||||
$overflow = true;
|
||||
break;
|
||||
}
|
||||
$words[$i] = 0xffff;
|
||||
} else {
|
||||
$words[$i]--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $overflow ? null : static::fromWords($words);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
|
||||
*/
|
||||
public function getReverseDNSLookupName()
|
||||
{
|
||||
return implode(
|
||||
'.',
|
||||
array_reverse(str_split(str_replace(':', '', $this->toString(true)), 1))
|
||||
) . '.ip6.arpa';
|
||||
}
|
||||
}
|
42
vendor/mlocati/ip-lib/src/Address/Type.php
vendored
Normal file
42
vendor/mlocati/ip-lib/src/Address/Type.php
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Address;
|
||||
|
||||
/**
|
||||
* Types of IP addresses.
|
||||
*/
|
||||
class Type
|
||||
{
|
||||
/**
|
||||
* IPv4 address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_IPv4 = 4;
|
||||
|
||||
/**
|
||||
* IPv6 address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_IPv6 = 6;
|
||||
|
||||
/**
|
||||
* Get the name of a type.
|
||||
*
|
||||
* @param int $type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getName($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case static::T_IPv4:
|
||||
return 'IP v4';
|
||||
case static::T_IPv6:
|
||||
return 'IP v6';
|
||||
default:
|
||||
return sprintf('Unknown type (%s)', $type);
|
||||
}
|
||||
}
|
||||
}
|
205
vendor/mlocati/ip-lib/src/Factory.php
vendored
Normal file
205
vendor/mlocati/ip-lib/src/Factory.php
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib;
|
||||
|
||||
use IPLib\Address\AddressInterface;
|
||||
use IPLib\Range\Subnet;
|
||||
use IPLib\Service\RangesFromBounradyCalculator;
|
||||
|
||||
/**
|
||||
* Factory methods to build class instances.
|
||||
*/
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* Parse an IP address string.
|
||||
*
|
||||
* @param string $address the address to parse
|
||||
* @param bool $mayIncludePort set to false to avoid parsing addresses with ports
|
||||
* @param bool $mayIncludeZoneID set to false to avoid parsing IPv6 addresses with zone IDs (see RFC 4007)
|
||||
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface|null
|
||||
*/
|
||||
public static function addressFromString($address, $mayIncludePort = true, $mayIncludeZoneID = true, $supportNonDecimalIPv4 = false)
|
||||
{
|
||||
$result = null;
|
||||
if ($result === null) {
|
||||
$result = Address\IPv4::fromString($address, $mayIncludePort, $supportNonDecimalIPv4);
|
||||
}
|
||||
if ($result === null) {
|
||||
$result = Address\IPv6::fromString($address, $mayIncludePort, $mayIncludeZoneID);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to an address instance.
|
||||
*
|
||||
* @param int[]|array $bytes
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface|null
|
||||
*/
|
||||
public static function addressFromBytes(array $bytes)
|
||||
{
|
||||
$result = null;
|
||||
if ($result === null) {
|
||||
$result = Address\IPv4::fromBytes($bytes);
|
||||
}
|
||||
if ($result === null) {
|
||||
$result = Address\IPv6::fromBytes($bytes);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an IP range string.
|
||||
*
|
||||
* @param string $range
|
||||
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
|
||||
*
|
||||
* @return \IPLib\Range\RangeInterface|null
|
||||
*/
|
||||
public static function rangeFromString($range, $supportNonDecimalIPv4 = false)
|
||||
{
|
||||
$result = null;
|
||||
if ($result === null) {
|
||||
$result = Range\Subnet::fromString($range, $supportNonDecimalIPv4);
|
||||
}
|
||||
if ($result === null) {
|
||||
$result = Range\Pattern::fromString($range, $supportNonDecimalIPv4);
|
||||
}
|
||||
if ($result === null) {
|
||||
$result = Range\Single::fromString($range, $supportNonDecimalIPv4);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the smallest address range that comprises two addresses.
|
||||
*
|
||||
* @param string|\IPLib\Address\AddressInterface $from
|
||||
* @param string|\IPLib\Address\AddressInterface $to
|
||||
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
|
||||
*
|
||||
* @return \IPLib\Range\RangeInterface|null return NULL if $from and/or $to are invalid addresses, or if both are NULL or empty strings, or if they are addresses of different types
|
||||
*/
|
||||
public static function rangeFromBoundaries($from, $to, $supportNonDecimalIPv4 = false)
|
||||
{
|
||||
list($from, $to) = self::parseBoundaries($from, $to, $supportNonDecimalIPv4);
|
||||
|
||||
return $from === false || $to === false ? null : static::rangeFromBoundaryAddresses($from, $to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of Range instances that exactly describes all the addresses between the two provided addresses.
|
||||
*
|
||||
* @param string|\IPLib\Address\AddressInterface $from
|
||||
* @param string|\IPLib\Address\AddressInterface $to
|
||||
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
|
||||
*
|
||||
* @return \IPLib\Range\Subnet[]|null return NULL if $from and/or $to are invalid addresses, or if both are NULL or empty strings, or if they are addresses of different types
|
||||
*/
|
||||
public static function rangesFromBoundaries($from, $to, $supportNonDecimalIPv4 = false)
|
||||
{
|
||||
list($from, $to) = self::parseBoundaries($from, $to, $supportNonDecimalIPv4);
|
||||
if (($from === false || $to === false) || ($from === null && $to === null)) {
|
||||
return null;
|
||||
}
|
||||
if ($from === null || $to === null) {
|
||||
$address = $from ? $from : $to;
|
||||
|
||||
return array(new Subnet($address, $address, $address->getNumberOfBits()));
|
||||
}
|
||||
$numberOfBits = $from->getNumberOfBits();
|
||||
if ($to->getNumberOfBits() !== $numberOfBits) {
|
||||
return null;
|
||||
}
|
||||
$calculator = new RangesFromBounradyCalculator($numberOfBits);
|
||||
|
||||
return $calculator->getRanges($from, $to);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \IPLib\Address\AddressInterface $from
|
||||
* @param \IPLib\Address\AddressInterface $to
|
||||
*
|
||||
* @return \IPLib\Range\RangeInterface|null
|
||||
*/
|
||||
protected static function rangeFromBoundaryAddresses(AddressInterface $from = null, AddressInterface $to = null)
|
||||
{
|
||||
if ($from === null && $to === null) {
|
||||
$result = null;
|
||||
} elseif ($to === null) {
|
||||
$result = Range\Single::fromAddress($from);
|
||||
} elseif ($from === null) {
|
||||
$result = Range\Single::fromAddress($to);
|
||||
} else {
|
||||
$result = null;
|
||||
$addressType = $from->getAddressType();
|
||||
if ($addressType === $to->getAddressType()) {
|
||||
$cmp = strcmp($from->getComparableString(), $to->getComparableString());
|
||||
if ($cmp === 0) {
|
||||
$result = Range\Single::fromAddress($from);
|
||||
} else {
|
||||
if ($cmp > 0) {
|
||||
list($from, $to) = array($to, $from);
|
||||
}
|
||||
$fromBytes = $from->getBytes();
|
||||
$toBytes = $to->getBytes();
|
||||
$numBytes = count($fromBytes);
|
||||
$sameBits = 0;
|
||||
for ($byteIndex = 0; $byteIndex < $numBytes; $byteIndex++) {
|
||||
$fromByte = $fromBytes[$byteIndex];
|
||||
$toByte = $toBytes[$byteIndex];
|
||||
if ($fromByte === $toByte) {
|
||||
$sameBits += 8;
|
||||
} else {
|
||||
$differentBitsInByte = decbin($fromByte ^ $toByte);
|
||||
$sameBits += 8 - strlen($differentBitsInByte);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$result = static::rangeFromString($from->toString(true) . '/' . (string) $sameBits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|\IPLib\Address\AddressInterface $from
|
||||
* @param string|\IPLib\Address\AddressInterface $to
|
||||
* @param bool $supportNonDecimalIPv4
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface[]|null[]|false[]
|
||||
*/
|
||||
private static function parseBoundaries($from, $to, $supportNonDecimalIPv4 = false)
|
||||
{
|
||||
$result = array();
|
||||
foreach (array('from', 'to') as $param) {
|
||||
$value = $$param;
|
||||
if (!($value instanceof AddressInterface)) {
|
||||
$value = (string) $value;
|
||||
if ($value === '') {
|
||||
$value = null;
|
||||
} else {
|
||||
$value = static::addressFromString($value, true, true, $supportNonDecimalIPv4);
|
||||
if ($value === null) {
|
||||
$value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$result[] = $value;
|
||||
}
|
||||
if ($result[0] && $result[1] && strcmp($result[0]->getComparableString(), $result[1]->getComparableString()) > 0) {
|
||||
$result = array($result[1], $result[0]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
95
vendor/mlocati/ip-lib/src/Range/AbstractRange.php
vendored
Normal file
95
vendor/mlocati/ip-lib/src/Range/AbstractRange.php
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Range;
|
||||
|
||||
use IPLib\Address\AddressInterface;
|
||||
use IPLib\Address\IPv4;
|
||||
use IPLib\Address\IPv6;
|
||||
use IPLib\Address\Type as AddressType;
|
||||
use IPLib\Factory;
|
||||
|
||||
abstract class AbstractRange implements RangeInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getRangeType()
|
||||
*/
|
||||
public function getRangeType()
|
||||
{
|
||||
if ($this->rangeType === null) {
|
||||
$addressType = $this->getAddressType();
|
||||
if ($addressType === AddressType::T_IPv6 && Subnet::get6to4()->containsRange($this)) {
|
||||
$this->rangeType = Factory::rangeFromBoundaries($this->fromAddress->toIPv4(), $this->toAddress->toIPv4())->getRangeType();
|
||||
} else {
|
||||
switch ($addressType) {
|
||||
case AddressType::T_IPv4:
|
||||
$defaultType = IPv4::getDefaultReservedRangeType();
|
||||
$reservedRanges = IPv4::getReservedRanges();
|
||||
break;
|
||||
case AddressType::T_IPv6:
|
||||
$defaultType = IPv6::getDefaultReservedRangeType();
|
||||
$reservedRanges = IPv6::getReservedRanges();
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('@todo'); // @codeCoverageIgnore
|
||||
}
|
||||
$rangeType = null;
|
||||
foreach ($reservedRanges as $reservedRange) {
|
||||
$rangeType = $reservedRange->getRangeType($this);
|
||||
if ($rangeType !== null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->rangeType = $rangeType === null ? $defaultType : $rangeType;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->rangeType === false ? null : $this->rangeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::contains()
|
||||
*/
|
||||
public function contains(AddressInterface $address)
|
||||
{
|
||||
$result = false;
|
||||
if ($address->getAddressType() === $this->getAddressType()) {
|
||||
$cmp = $address->getComparableString();
|
||||
$from = $this->getComparableStartString();
|
||||
if ($cmp >= $from) {
|
||||
$to = $this->getComparableEndString();
|
||||
if ($cmp <= $to) {
|
||||
$result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::containsRange()
|
||||
*/
|
||||
public function containsRange(RangeInterface $range)
|
||||
{
|
||||
$result = false;
|
||||
if ($range->getAddressType() === $this->getAddressType()) {
|
||||
$myStart = $this->getComparableStartString();
|
||||
$itsStart = $range->getComparableStartString();
|
||||
if ($itsStart >= $myStart) {
|
||||
$myEnd = $this->getComparableEndString();
|
||||
$itsEnd = $range->getComparableEndString();
|
||||
if ($itsEnd <= $myEnd) {
|
||||
$result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
275
vendor/mlocati/ip-lib/src/Range/Pattern.php
vendored
Normal file
275
vendor/mlocati/ip-lib/src/Range/Pattern.php
vendored
Normal file
|
@ -0,0 +1,275 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Range;
|
||||
|
||||
use IPLib\Address\AddressInterface;
|
||||
use IPLib\Address\IPv4;
|
||||
use IPLib\Address\IPv6;
|
||||
use IPLib\Address\Type as AddressType;
|
||||
|
||||
/**
|
||||
* Represents an address range in pattern format (only ending asterisks are supported).
|
||||
*
|
||||
* @example 127.0.*.*
|
||||
* @example ::/8
|
||||
*/
|
||||
class Pattern extends AbstractRange
|
||||
{
|
||||
/**
|
||||
* Starting address of the range.
|
||||
*
|
||||
* @var \IPLib\Address\AddressInterface
|
||||
*/
|
||||
protected $fromAddress;
|
||||
|
||||
/**
|
||||
* Final address of the range.
|
||||
*
|
||||
* @var \IPLib\Address\AddressInterface
|
||||
*/
|
||||
protected $toAddress;
|
||||
|
||||
/**
|
||||
* Number of ending asterisks.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $asterisksCount;
|
||||
|
||||
/**
|
||||
* The type of the range of this IP range.
|
||||
*
|
||||
* @var int|false|null false if this range crosses multiple range types, null if yet to be determined
|
||||
*/
|
||||
protected $rangeType;
|
||||
|
||||
/**
|
||||
* Initializes the instance.
|
||||
*
|
||||
* @param \IPLib\Address\AddressInterface $fromAddress
|
||||
* @param \IPLib\Address\AddressInterface $toAddress
|
||||
* @param int $asterisksCount
|
||||
*/
|
||||
public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $asterisksCount)
|
||||
{
|
||||
$this->fromAddress = $fromAddress;
|
||||
$this->toAddress = $toAddress;
|
||||
$this->asterisksCount = $asterisksCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::__toString()
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try get the range instance starting from its string representation.
|
||||
*
|
||||
* @param string|mixed $range
|
||||
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
|
||||
*
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromString($range, $supportNonDecimalIPv4 = false)
|
||||
{
|
||||
if (!is_string($range) || strpos($range, '*') === false) {
|
||||
return null;
|
||||
}
|
||||
if ($range === '*.*.*.*') {
|
||||
return new static(IPv4::fromString('0.0.0.0'), IPv4::fromString('255.255.255.255'), 4);
|
||||
}
|
||||
if ($range === '*:*:*:*:*:*:*:*') {
|
||||
return new static(IPv6::fromString('::'), IPv6::fromString('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 8);
|
||||
}
|
||||
$matches = null;
|
||||
if (strpos($range, '.') !== false && preg_match('/^[^*]+((?:\.\*)+)$/', $range, $matches)) {
|
||||
$asterisksCount = strlen($matches[1]) >> 1;
|
||||
if ($asterisksCount > 0) {
|
||||
$missingDots = 3 - substr_count($range, '.');
|
||||
if ($missingDots > 0) {
|
||||
$range .= str_repeat('.*', $missingDots);
|
||||
$asterisksCount += $missingDots;
|
||||
}
|
||||
}
|
||||
$fromAddress = IPv4::fromString(str_replace('*', '0', $range), true, $supportNonDecimalIPv4);
|
||||
if ($fromAddress === null) {
|
||||
return null;
|
||||
}
|
||||
$fixedBytes = array_slice($fromAddress->getBytes(), 0, -$asterisksCount);
|
||||
$otherBytes = array_fill(0, $asterisksCount, 255);
|
||||
$toAddress = IPv4::fromBytes(array_merge($fixedBytes, $otherBytes));
|
||||
|
||||
return new static($fromAddress, $toAddress, $asterisksCount);
|
||||
}
|
||||
if (strpos($range, ':') !== false && preg_match('/^[^*]+((?::\*)+)$/', $range, $matches)) {
|
||||
$asterisksCount = strlen($matches[1]) >> 1;
|
||||
$fromAddress = IPv6::fromString(str_replace('*', '0', $range));
|
||||
if ($fromAddress === null) {
|
||||
return null;
|
||||
}
|
||||
$fixedWords = array_slice($fromAddress->getWords(), 0, -$asterisksCount);
|
||||
$otherWords = array_fill(0, $asterisksCount, 0xffff);
|
||||
$toAddress = IPv6::fromWords(array_merge($fixedWords, $otherWords));
|
||||
|
||||
return new static($fromAddress, $toAddress, $asterisksCount);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::toString()
|
||||
*/
|
||||
public function toString($long = false)
|
||||
{
|
||||
if ($this->asterisksCount === 0) {
|
||||
return $this->fromAddress->toString($long);
|
||||
}
|
||||
switch (true) {
|
||||
case $this->fromAddress instanceof \IPLib\Address\IPv4:
|
||||
$chunks = explode('.', $this->fromAddress->toString());
|
||||
$chunks = array_slice($chunks, 0, -$this->asterisksCount);
|
||||
$chunks = array_pad($chunks, 4, '*');
|
||||
$result = implode('.', $chunks);
|
||||
break;
|
||||
case $this->fromAddress instanceof \IPLib\Address\IPv6:
|
||||
if ($long) {
|
||||
$chunks = explode(':', $this->fromAddress->toString(true));
|
||||
$chunks = array_slice($chunks, 0, -$this->asterisksCount);
|
||||
$chunks = array_pad($chunks, 8, '*');
|
||||
$result = implode(':', $chunks);
|
||||
} elseif ($this->asterisksCount === 8) {
|
||||
$result = '*:*:*:*:*:*:*:*';
|
||||
} else {
|
||||
$bytes = $this->toAddress->getBytes();
|
||||
$bytes = array_slice($bytes, 0, -$this->asterisksCount * 2);
|
||||
$bytes = array_pad($bytes, 16, 1);
|
||||
$address = IPv6::fromBytes($bytes);
|
||||
$before = substr($address->toString(false), 0, -strlen(':101') * $this->asterisksCount);
|
||||
$result = $before . str_repeat(':*', $this->asterisksCount);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('@todo'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getAddressType()
|
||||
*/
|
||||
public function getAddressType()
|
||||
{
|
||||
return $this->fromAddress->getAddressType();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getStartAddress()
|
||||
*/
|
||||
public function getStartAddress()
|
||||
{
|
||||
return $this->fromAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getEndAddress()
|
||||
*/
|
||||
public function getEndAddress()
|
||||
{
|
||||
return $this->toAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getComparableStartString()
|
||||
*/
|
||||
public function getComparableStartString()
|
||||
{
|
||||
return $this->fromAddress->getComparableString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getComparableEndString()
|
||||
*/
|
||||
public function getComparableEndString()
|
||||
{
|
||||
return $this->toAddress->getComparableString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::asSubnet()
|
||||
*/
|
||||
public function asSubnet()
|
||||
{
|
||||
switch ($this->getAddressType()) {
|
||||
case AddressType::T_IPv4:
|
||||
return new Subnet($this->getStartAddress(), $this->getEndAddress(), 8 * (4 - $this->asterisksCount));
|
||||
case AddressType::T_IPv6:
|
||||
return new Subnet($this->getStartAddress(), $this->getEndAddress(), 16 * (8 - $this->asterisksCount));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::asPattern()
|
||||
*/
|
||||
public function asPattern()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getSubnetMask()
|
||||
*/
|
||||
public function getSubnetMask()
|
||||
{
|
||||
if ($this->getAddressType() !== AddressType::T_IPv4) {
|
||||
return null;
|
||||
}
|
||||
switch ($this->asterisksCount) {
|
||||
case 0:
|
||||
$bytes = array(255, 255, 255, 255);
|
||||
break;
|
||||
case 4:
|
||||
$bytes = array(0, 0, 0, 0);
|
||||
break;
|
||||
default:
|
||||
$bytes = array_pad(array_fill(0, 4 - $this->asterisksCount, 255), 4, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
return IPv4::fromBytes($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
|
||||
*/
|
||||
public function getReverseDNSLookupName()
|
||||
{
|
||||
return $this->asterisksCount === 0 ? array($this->getStartAddress()->getReverseDNSLookupName()) : $this->asSubnet()->getReverseDNSLookupName();
|
||||
}
|
||||
}
|
120
vendor/mlocati/ip-lib/src/Range/RangeInterface.php
vendored
Normal file
120
vendor/mlocati/ip-lib/src/Range/RangeInterface.php
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Range;
|
||||
|
||||
use IPLib\Address\AddressInterface;
|
||||
|
||||
/**
|
||||
* Interface of all the range types.
|
||||
*/
|
||||
interface RangeInterface
|
||||
{
|
||||
/**
|
||||
* Get the short string representation of this address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* Get the string representation of this address.
|
||||
*
|
||||
* @param bool $long set to true to have a long/full representation, false otherwise
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @example If $long is true, you'll get '0000:0000:0000:0000:0000:0000:0000:0001/128', '::1/128' otherwise.
|
||||
*/
|
||||
public function toString($long = false);
|
||||
|
||||
/**
|
||||
* Get the type of the IP addresses contained in this range.
|
||||
*
|
||||
* @return int One of the \IPLib\Address\Type::T_... constants
|
||||
*/
|
||||
public function getAddressType();
|
||||
|
||||
/**
|
||||
* Get the type of range of the IP address.
|
||||
*
|
||||
* @return int One of the \IPLib\Range\Type::T_... constants
|
||||
*/
|
||||
public function getRangeType();
|
||||
|
||||
/**
|
||||
* Check if this range contains an IP address.
|
||||
*
|
||||
* @param \IPLib\Address\AddressInterface $address
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function contains(AddressInterface $address);
|
||||
|
||||
/**
|
||||
* Check if this range contains another range.
|
||||
*
|
||||
* @param \IPLib\Range\RangeInterface $range
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function containsRange(RangeInterface $range);
|
||||
|
||||
/**
|
||||
* Get the initial address contained in this range.
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface
|
||||
*/
|
||||
public function getStartAddress();
|
||||
|
||||
/**
|
||||
* Get the final address contained in this range.
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface
|
||||
*/
|
||||
public function getEndAddress();
|
||||
|
||||
/**
|
||||
* Get a string representation of the starting address of this range than can be used when comparing addresses and ranges.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComparableStartString();
|
||||
|
||||
/**
|
||||
* Get a string representation of the final address of this range than can be used when comparing addresses and ranges.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComparableEndString();
|
||||
|
||||
/**
|
||||
* Get the subnet mask representing this range (only for IPv4 ranges).
|
||||
*
|
||||
* @return \IPLib\Address\IPv4|null return NULL if the range is an IPv6 range, the subnet mask otherwise
|
||||
*/
|
||||
public function getSubnetMask();
|
||||
|
||||
/**
|
||||
* Get the subnet/CIDR representation of this range.
|
||||
*
|
||||
* @return \IPLib\Range\Subnet
|
||||
*/
|
||||
public function asSubnet();
|
||||
|
||||
/**
|
||||
* Get the pattern/asterisk representation (if applicable) of this range.
|
||||
*
|
||||
* @return \IPLib\Range\Pattern|null return NULL if this range can't be represented by a pattern notation
|
||||
*/
|
||||
public function asPattern();
|
||||
|
||||
/**
|
||||
* Get the Reverse DNS Lookup Addresses of this IP range.
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @example for IPv4 it returns something like array('x.x.x.x.in-addr.arpa', 'x.x.x.x.in-addr.arpa') (where the number of 'x.' ranges from 1 to 4)
|
||||
* @example for IPv6 it returns something like array('x.x.x.x..x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa', 'x.x.x.x..x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa') (where the number of 'x.' ranges from 1 to 32)
|
||||
*/
|
||||
public function getReverseDNSLookupName();
|
||||
}
|
226
vendor/mlocati/ip-lib/src/Range/Single.php
vendored
Normal file
226
vendor/mlocati/ip-lib/src/Range/Single.php
vendored
Normal file
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Range;
|
||||
|
||||
use IPLib\Address\AddressInterface;
|
||||
use IPLib\Address\IPv4;
|
||||
use IPLib\Address\Type as AddressType;
|
||||
use IPLib\Factory;
|
||||
|
||||
/**
|
||||
* Represents a single address (eg a range that contains just one address).
|
||||
*
|
||||
* @example 127.0.0.1
|
||||
* @example ::1
|
||||
*/
|
||||
class Single extends AbstractRange
|
||||
{
|
||||
/**
|
||||
* @var \IPLib\Address\AddressInterface
|
||||
*/
|
||||
protected $address;
|
||||
|
||||
/**
|
||||
* Initializes the instance.
|
||||
*
|
||||
* @param \IPLib\Address\AddressInterface $address
|
||||
*/
|
||||
protected function __construct(AddressInterface $address)
|
||||
{
|
||||
$this->address = $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::__toString()
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->address->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try get the range instance starting from its string representation.
|
||||
*
|
||||
* @param string|mixed $range
|
||||
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
|
||||
*
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromString($range, $supportNonDecimalIPv4 = false)
|
||||
{
|
||||
$result = null;
|
||||
$address = Factory::addressFromString($range, true, true, $supportNonDecimalIPv4);
|
||||
if ($address !== null) {
|
||||
$result = new static($address);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the range instance starting from an address instance.
|
||||
*
|
||||
* @param \IPLib\Address\AddressInterface $address
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function fromAddress(AddressInterface $address)
|
||||
{
|
||||
return new static($address);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::toString()
|
||||
*/
|
||||
public function toString($long = false)
|
||||
{
|
||||
return $this->address->toString($long);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getAddressType()
|
||||
*/
|
||||
public function getAddressType()
|
||||
{
|
||||
return $this->address->getAddressType();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getRangeType()
|
||||
*/
|
||||
public function getRangeType()
|
||||
{
|
||||
return $this->address->getRangeType();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::contains()
|
||||
*/
|
||||
public function contains(AddressInterface $address)
|
||||
{
|
||||
$result = false;
|
||||
if ($address->getAddressType() === $this->getAddressType()) {
|
||||
if ($address->toString(false) === $this->address->toString(false)) {
|
||||
$result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::containsRange()
|
||||
*/
|
||||
public function containsRange(RangeInterface $range)
|
||||
{
|
||||
$result = false;
|
||||
if ($range->getAddressType() === $this->getAddressType()) {
|
||||
if ($range->toString(false) === $this->toString(false)) {
|
||||
$result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getStartAddress()
|
||||
*/
|
||||
public function getStartAddress()
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getEndAddress()
|
||||
*/
|
||||
public function getEndAddress()
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getComparableStartString()
|
||||
*/
|
||||
public function getComparableStartString()
|
||||
{
|
||||
return $this->address->getComparableString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getComparableEndString()
|
||||
*/
|
||||
public function getComparableEndString()
|
||||
{
|
||||
return $this->address->getComparableString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::asSubnet()
|
||||
*/
|
||||
public function asSubnet()
|
||||
{
|
||||
$networkPrefixes = array(
|
||||
AddressType::T_IPv4 => 32,
|
||||
AddressType::T_IPv6 => 128,
|
||||
);
|
||||
|
||||
return new Subnet($this->address, $this->address, $networkPrefixes[$this->address->getAddressType()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::asPattern()
|
||||
*/
|
||||
public function asPattern()
|
||||
{
|
||||
return new Pattern($this->address, $this->address, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getSubnetMask()
|
||||
*/
|
||||
public function getSubnetMask()
|
||||
{
|
||||
if ($this->getAddressType() !== AddressType::T_IPv4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return IPv4::fromBytes(array(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
|
||||
*/
|
||||
public function getReverseDNSLookupName()
|
||||
{
|
||||
return array($this->getStartAddress()->getReverseDNSLookupName());
|
||||
}
|
||||
}
|
305
vendor/mlocati/ip-lib/src/Range/Subnet.php
vendored
Normal file
305
vendor/mlocati/ip-lib/src/Range/Subnet.php
vendored
Normal file
|
@ -0,0 +1,305 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Range;
|
||||
|
||||
use IPLib\Address\AddressInterface;
|
||||
use IPLib\Address\IPv4;
|
||||
use IPLib\Address\IPv6;
|
||||
use IPLib\Address\Type as AddressType;
|
||||
use IPLib\Factory;
|
||||
|
||||
/**
|
||||
* Represents an address range in subnet format (eg CIDR).
|
||||
*
|
||||
* @example 127.0.0.1/32
|
||||
* @example ::/8
|
||||
*/
|
||||
class Subnet extends AbstractRange
|
||||
{
|
||||
/**
|
||||
* Starting address of the range.
|
||||
*
|
||||
* @var \IPLib\Address\AddressInterface
|
||||
*/
|
||||
protected $fromAddress;
|
||||
|
||||
/**
|
||||
* Final address of the range.
|
||||
*
|
||||
* @var \IPLib\Address\AddressInterface
|
||||
*/
|
||||
protected $toAddress;
|
||||
|
||||
/**
|
||||
* Number of the same bits of the range.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $networkPrefix;
|
||||
|
||||
/**
|
||||
* The type of the range of this IP range.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $rangeType;
|
||||
|
||||
/**
|
||||
* The 6to4 address IPv6 address range.
|
||||
*
|
||||
* @var self|null
|
||||
*/
|
||||
private static $sixToFour;
|
||||
|
||||
/**
|
||||
* Initializes the instance.
|
||||
*
|
||||
* @param \IPLib\Address\AddressInterface $fromAddress
|
||||
* @param \IPLib\Address\AddressInterface $toAddress
|
||||
* @param int $networkPrefix
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $networkPrefix)
|
||||
{
|
||||
$this->fromAddress = $fromAddress;
|
||||
$this->toAddress = $toAddress;
|
||||
$this->networkPrefix = $networkPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::__toString()
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try get the range instance starting from its string representation.
|
||||
*
|
||||
* @param string|mixed $range
|
||||
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
|
||||
*
|
||||
* @return static|null
|
||||
*/
|
||||
public static function fromString($range, $supportNonDecimalIPv4 = false)
|
||||
{
|
||||
if (!is_string($range)) {
|
||||
return null;
|
||||
}
|
||||
$parts = explode('/', $range);
|
||||
if (count($parts) !== 2) {
|
||||
return null;
|
||||
}
|
||||
$address = Factory::addressFromString($parts[0], true, true, $supportNonDecimalIPv4);
|
||||
if ($address === null) {
|
||||
return null;
|
||||
}
|
||||
if (!preg_match('/^[0-9]{1,9}$/', $parts[1])) {
|
||||
return null;
|
||||
}
|
||||
$networkPrefix = (int) $parts[1];
|
||||
$addressBytes = $address->getBytes();
|
||||
$totalBytes = count($addressBytes);
|
||||
$numDifferentBits = $totalBytes * 8 - $networkPrefix;
|
||||
if ($numDifferentBits < 0) {
|
||||
return null;
|
||||
}
|
||||
$numSameBytes = $networkPrefix >> 3;
|
||||
$sameBytes = array_slice($addressBytes, 0, $numSameBytes);
|
||||
$differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0);
|
||||
$differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255);
|
||||
$startSameBits = $networkPrefix % 8;
|
||||
if ($startSameBits !== 0) {
|
||||
$varyingByte = $addressBytes[$numSameBytes];
|
||||
$differentBytesStart[0] = $varyingByte & bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT));
|
||||
$differentBytesEnd[0] = $differentBytesStart[0] + bindec(str_repeat('1', 8 - $startSameBits));
|
||||
}
|
||||
|
||||
return new static(
|
||||
Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart)),
|
||||
Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd)),
|
||||
$networkPrefix
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::toString()
|
||||
*/
|
||||
public function toString($long = false)
|
||||
{
|
||||
return $this->fromAddress->toString($long) . '/' . $this->networkPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getAddressType()
|
||||
*/
|
||||
public function getAddressType()
|
||||
{
|
||||
return $this->fromAddress->getAddressType();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getStartAddress()
|
||||
*/
|
||||
public function getStartAddress()
|
||||
{
|
||||
return $this->fromAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getEndAddress()
|
||||
*/
|
||||
public function getEndAddress()
|
||||
{
|
||||
return $this->toAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getComparableStartString()
|
||||
*/
|
||||
public function getComparableStartString()
|
||||
{
|
||||
return $this->fromAddress->getComparableString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getComparableEndString()
|
||||
*/
|
||||
public function getComparableEndString()
|
||||
{
|
||||
return $this->toAddress->getComparableString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::asSubnet()
|
||||
*/
|
||||
public function asSubnet()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pattern (asterisk) representation (if applicable) of this range.
|
||||
*
|
||||
* @return \IPLib\Range\Pattern|null return NULL if this range can't be represented by a pattern notation
|
||||
*/
|
||||
public function asPattern()
|
||||
{
|
||||
$address = $this->getStartAddress();
|
||||
$networkPrefix = $this->getNetworkPrefix();
|
||||
switch ($address->getAddressType()) {
|
||||
case AddressType::T_IPv4:
|
||||
return $networkPrefix % 8 === 0 ? new Pattern($address, $address, 4 - $networkPrefix / 8) : null;
|
||||
case AddressType::T_IPv6:
|
||||
return $networkPrefix % 16 === 0 ? new Pattern($address, $address, 8 - $networkPrefix / 16) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 6to4 address IPv6 address range.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function get6to4()
|
||||
{
|
||||
if (self::$sixToFour === null) {
|
||||
self::$sixToFour = self::fromString('2002::/16');
|
||||
}
|
||||
|
||||
return self::$sixToFour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subnet prefix.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNetworkPrefix()
|
||||
{
|
||||
return $this->networkPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getSubnetMask()
|
||||
*/
|
||||
public function getSubnetMask()
|
||||
{
|
||||
if ($this->getAddressType() !== AddressType::T_IPv4) {
|
||||
return null;
|
||||
}
|
||||
$bytes = array();
|
||||
$prefix = $this->getNetworkPrefix();
|
||||
while ($prefix >= 8) {
|
||||
$bytes[] = 255;
|
||||
$prefix -= 8;
|
||||
}
|
||||
if ($prefix !== 0) {
|
||||
$bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0'));
|
||||
}
|
||||
$bytes = array_pad($bytes, 4, 0);
|
||||
|
||||
return IPv4::fromBytes($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
|
||||
*/
|
||||
public function getReverseDNSLookupName()
|
||||
{
|
||||
switch ($this->getAddressType()) {
|
||||
case AddressType::T_IPv4:
|
||||
$unitSize = 8; // bytes
|
||||
$maxUnits = 4;
|
||||
$isHex = false;
|
||||
$rxUnit = '\d+';
|
||||
break;
|
||||
case AddressType::T_IPv6:
|
||||
$unitSize = 4; // nibbles
|
||||
$maxUnits = 32;
|
||||
$isHex = true;
|
||||
$rxUnit = '[0-9A-Fa-f]';
|
||||
break;
|
||||
}
|
||||
$totBits = $unitSize * $maxUnits;
|
||||
$prefixUnits = (int) ($this->networkPrefix / $unitSize);
|
||||
$extraBits = ($totBits - $this->networkPrefix) % $unitSize;
|
||||
if ($extraBits !== 0) {
|
||||
$prefixUnits += 1;
|
||||
}
|
||||
$numVariants = 1 << $extraBits;
|
||||
$result = array();
|
||||
$unitsToRemove = $maxUnits - $prefixUnits;
|
||||
$initialPointer = preg_replace("/^(({$rxUnit})\.){{$unitsToRemove}}/", '', $this->getStartAddress()->getReverseDNSLookupName());
|
||||
$chunks = explode('.', $initialPointer, 2);
|
||||
for ($index = 0; $index < $numVariants; $index++) {
|
||||
if ($index !== 0) {
|
||||
$chunks[0] = $isHex ? dechex(1 + hexdec($chunks[0])) : (string) (1 + (int) $chunks[0]);
|
||||
}
|
||||
$result[] = implode('.', $chunks);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
150
vendor/mlocati/ip-lib/src/Range/Type.php
vendored
Normal file
150
vendor/mlocati/ip-lib/src/Range/Type.php
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Range;
|
||||
|
||||
/**
|
||||
* Types of IP address classes.
|
||||
*/
|
||||
class Type
|
||||
{
|
||||
/**
|
||||
* Unspecified/unknown address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_UNSPECIFIED = 1;
|
||||
|
||||
/**
|
||||
* Reserved/internal use only.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_RESERVED = 2;
|
||||
|
||||
/**
|
||||
* Refer to source hosts on "this" network.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_THISNETWORK = 3;
|
||||
|
||||
/**
|
||||
* Internet host loopback address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_LOOPBACK = 4;
|
||||
|
||||
/**
|
||||
* Relay anycast address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_ANYCASTRELAY = 5;
|
||||
|
||||
/**
|
||||
* "Limited broadcast" destination address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_LIMITEDBROADCAST = 6;
|
||||
|
||||
/**
|
||||
* Multicast address assignments - Indentify a group of interfaces.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_MULTICAST = 7;
|
||||
|
||||
/**
|
||||
* "Link local" address, allocated for communication between hosts on a single link.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_LINKLOCAL = 8;
|
||||
|
||||
/**
|
||||
* Link local unicast / Linked-scoped unicast.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_LINKLOCAL_UNICAST = 9;
|
||||
|
||||
/**
|
||||
* Discard-Only address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_DISCARDONLY = 10;
|
||||
|
||||
/**
|
||||
* Discard address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_DISCARD = 11;
|
||||
|
||||
/**
|
||||
* For use in private networks.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_PRIVATENETWORK = 12;
|
||||
|
||||
/**
|
||||
* Public address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_PUBLIC = 13;
|
||||
|
||||
/**
|
||||
* Carrier-grade NAT address.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const T_CGNAT = 14;
|
||||
|
||||
/**
|
||||
* Get the name of a type.
|
||||
*
|
||||
* @param int $type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getName($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case static::T_UNSPECIFIED:
|
||||
return 'Unspecified/unknown address';
|
||||
case static::T_RESERVED:
|
||||
return 'Reserved/internal use only';
|
||||
case static::T_THISNETWORK:
|
||||
return 'Refer to source hosts on "this" network';
|
||||
case static::T_LOOPBACK:
|
||||
return 'Internet host loopback address';
|
||||
case static::T_ANYCASTRELAY:
|
||||
return 'Relay anycast address';
|
||||
case static::T_LIMITEDBROADCAST:
|
||||
return '"Limited broadcast" destination address';
|
||||
case static::T_MULTICAST:
|
||||
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';
|
||||
case static::T_LINKLOCAL_UNICAST:
|
||||
return 'Link local unicast / Linked-scoped unicast';
|
||||
case static::T_DISCARDONLY:
|
||||
return 'Discard only';
|
||||
case static::T_DISCARD:
|
||||
return 'Discard';
|
||||
case static::T_PRIVATENETWORK:
|
||||
return 'For use in private networks';
|
||||
case static::T_PUBLIC:
|
||||
return 'Public address';
|
||||
case static::T_CGNAT:
|
||||
return 'Carrier-grade NAT';
|
||||
default:
|
||||
return $type === null ? 'Unknown type' : sprintf('Unknown type (%s)', $type);
|
||||
}
|
||||
}
|
||||
}
|
118
vendor/mlocati/ip-lib/src/Service/BinaryMath.php
vendored
Normal file
118
vendor/mlocati/ip-lib/src/Service/BinaryMath.php
vendored
Normal file
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Service;
|
||||
|
||||
/**
|
||||
* Helper class to work with unsigned binary integers.
|
||||
*/
|
||||
class BinaryMath
|
||||
{
|
||||
/**
|
||||
* Trim the leading zeroes from a non-negative integer represented in binary form.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function reduce($value)
|
||||
{
|
||||
$value = ltrim($value, '0');
|
||||
|
||||
return $value === '' ? '0' : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two non-negative integers represented in binary form.
|
||||
*
|
||||
* @param string $a
|
||||
* @param string $b
|
||||
*
|
||||
* @return int 1 if $a is greater than $b, -1 if $b is greater than $b, 0 if they are the same
|
||||
*/
|
||||
public function compare($a, $b)
|
||||
{
|
||||
list($a, $b) = $this->toSameLength($a, $b);
|
||||
|
||||
return $a < $b ? -1 : ($a > $b ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 1 to a non-negative integer represented in binary form.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function increment($value)
|
||||
{
|
||||
$lastZeroIndex = strrpos($value, '0');
|
||||
if ($lastZeroIndex === false) {
|
||||
return '1' . str_repeat('0', strlen($value));
|
||||
}
|
||||
|
||||
return ltrim(substr($value, 0, $lastZeroIndex), '0') . '1' . str_repeat('0', strlen($value) - $lastZeroIndex - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the bitwise AND of two non-negative integers represented in binary form.
|
||||
*
|
||||
* @param string $operand1
|
||||
* @param string $operand2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function andX($operand1, $operand2)
|
||||
{
|
||||
$operand1 = $this->reduce($operand1);
|
||||
$operand2 = $this->reduce($operand2);
|
||||
$numBits = min(strlen($operand1), strlen($operand2));
|
||||
$operand1 = substr(str_pad($operand1, $numBits, '0', STR_PAD_LEFT), -$numBits);
|
||||
$operand2 = substr(str_pad($operand2, $numBits, '0', STR_PAD_LEFT), -$numBits);
|
||||
$result = '';
|
||||
for ($index = 0; $index < $numBits; $index++) {
|
||||
$result .= $operand1[$index] === '1' && $operand2[$index] === '1' ? '1' : '0';
|
||||
}
|
||||
|
||||
return $this->reduce($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the bitwise OR of two non-negative integers represented in binary form.
|
||||
*
|
||||
* @param string $operand1
|
||||
* @param string $operand2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function orX($operand1, $operand2)
|
||||
{
|
||||
list($operand1, $operand2, $numBits) = $this->toSameLength($operand1, $operand2);
|
||||
$result = '';
|
||||
for ($index = 0; $index < $numBits; $index++) {
|
||||
$result .= $operand1[$index] === '1' || $operand2[$index] === '1' ? '1' : '0';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zero-padding of two non-negative integers represented in binary form, so that they have the same length.
|
||||
*
|
||||
* @param string $num1
|
||||
* @param string $num2
|
||||
*
|
||||
* @return string[],int[] The first array element is $num1 (padded), the first array element is $num2 (padded), the third array element is the number of bits
|
||||
*/
|
||||
private function toSameLength($num1, $num2)
|
||||
{
|
||||
$num1 = $this->reduce($num1);
|
||||
$num2 = $this->reduce($num2);
|
||||
$numBits = max(strlen($num1), strlen($num2));
|
||||
|
||||
return array(
|
||||
str_pad($num1, $numBits, '0', STR_PAD_LEFT),
|
||||
str_pad($num2, $numBits, '0', STR_PAD_LEFT),
|
||||
$numBits,
|
||||
);
|
||||
}
|
||||
}
|
161
vendor/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php
vendored
Normal file
161
vendor/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
|
||||
namespace IPLib\Service;
|
||||
|
||||
use IPLib\Address\AddressInterface;
|
||||
use IPLib\Factory;
|
||||
use IPLib\Range\Subnet;
|
||||
|
||||
/**
|
||||
* Helper class to calculate the subnets describing all (and only all) the addresses between two bouundaries.
|
||||
*/
|
||||
class RangesFromBounradyCalculator
|
||||
{
|
||||
/**
|
||||
* The BinaryMath instance to be used to perform bitwise poerations.
|
||||
*
|
||||
* @var \IPLib\Service\BinaryMath
|
||||
*/
|
||||
private $math;
|
||||
|
||||
/**
|
||||
* The number of bits used to represent addresses.
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
* @example 32 for IPv4, 128 for IPv6
|
||||
*/
|
||||
private $numBits;
|
||||
|
||||
/**
|
||||
* The bit masks for every bit index.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $masks;
|
||||
|
||||
/**
|
||||
* The bit unmasks for every bit index.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $unmasks;
|
||||
|
||||
/**
|
||||
* Initializes the instance.
|
||||
*
|
||||
* @param int $numBits the number of bits used to represent addresses (32 for IPv4, 128 for IPv6)
|
||||
*/
|
||||
public function __construct($numBits)
|
||||
{
|
||||
$this->math = new BinaryMath();
|
||||
$this->setNumBits($numBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the subnets describing all (and only all) the addresses between two bouundaries.
|
||||
*
|
||||
* @param \IPLib\Address\AddressInterface $from
|
||||
* @param \IPLib\Address\AddressInterface $to
|
||||
*
|
||||
* @return \IPLib\Range\Subnet[]|null return NULL if the two addresses have an invalid number of bits (that is, different from the one passed to the constructor of this class)
|
||||
*/
|
||||
public function getRanges(AddressInterface $from, AddressInterface $to)
|
||||
{
|
||||
if ($from->getNumberOfBits() !== $this->numBits || $to->getNumberOfBits() !== $this->numBits) {
|
||||
return null;
|
||||
}
|
||||
if ($from->getComparableString() > $to->getComparableString()) {
|
||||
list($from, $to) = array($to, $from);
|
||||
}
|
||||
$result = array();
|
||||
$this->calculate($this->math->reduce($from->getBits()), $this->math->reduce($to->getBits()), $this->numBits, $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of bits used to represent addresses (32 for IPv4, 128 for IPv6).
|
||||
*
|
||||
* @param int $numBits
|
||||
*/
|
||||
private function setNumBits($numBits)
|
||||
{
|
||||
$numBits = (int) $numBits;
|
||||
$masks = array();
|
||||
$unmasks = array();
|
||||
for ($bit = 0; $bit < $numBits; $bit++) {
|
||||
$masks[$bit] = str_repeat('1', $numBits - $bit) . str_repeat('0', $bit);
|
||||
$unmasks[$bit] = $bit === 0 ? '0' : str_repeat('1', $bit);
|
||||
}
|
||||
$this->numBits = $numBits;
|
||||
$this->masks = $masks;
|
||||
$this->unmasks = $unmasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the subnets.
|
||||
*
|
||||
* @param string $start the start address (represented in reduced bit form)
|
||||
* @param string $end the end address (represented in reduced bit form)
|
||||
* @param int $position the number of bits in the mask we are comparing at this cycle
|
||||
* @param \IPLib\Range\Subnet[] $result found ranges will be added to this variable
|
||||
*/
|
||||
private function calculate($start, $end, $position, array &$result)
|
||||
{
|
||||
if ($start === $end) {
|
||||
$result[] = $this->subnetFromBits($start, $this->numBits);
|
||||
|
||||
return;
|
||||
}
|
||||
for ($index = $position - 1; $index >= 0; $index--) {
|
||||
$startMasked = $this->math->andX($start, $this->masks[$index]);
|
||||
$endMasked = $this->math->andX($end, $this->masks[$index]);
|
||||
if ($startMasked !== $endMasked) {
|
||||
$position = $index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($startMasked === $start && $this->math->andX($this->math->increment($end), $this->unmasks[$position]) === '0') {
|
||||
$result[] = $this->subnetFromBits($start, $this->numBits - 1 - $position);
|
||||
|
||||
return;
|
||||
}
|
||||
$middleAddress = $this->math->orX($start, $this->unmasks[$position]);
|
||||
$this->calculate($start, $middleAddress, $position, $result);
|
||||
$this->calculate($this->math->increment($middleAddress), $end, $position, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address instance starting from its bits.
|
||||
*
|
||||
* @param string $bits the bits of the address (represented in reduced bit form)
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface
|
||||
*/
|
||||
private function addressFromBits($bits)
|
||||
{
|
||||
$bits = str_pad($bits, $this->numBits, '0', STR_PAD_LEFT);
|
||||
$bytes = array();
|
||||
foreach (explode("\n", trim(chunk_split($bits, 8, "\n"))) as $byteBits) {
|
||||
$bytes[] = bindec($byteBits);
|
||||
}
|
||||
|
||||
return Factory::addressFromBytes($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an range instance starting from the bits if the address and the length of the network prefix.
|
||||
*
|
||||
* @param string $bits the bits of the address (represented in reduced bit form)
|
||||
* @param int $networkPrefix the length of the network prefix
|
||||
*
|
||||
* @return \IPLib\Range\Subnet
|
||||
*/
|
||||
private function subnetFromBits($bits, $networkPrefix)
|
||||
{
|
||||
$address = $this->addressFromBits($bits);
|
||||
|
||||
return new Subnet($address, $address, $networkPrefix);
|
||||
}
|
||||
}
|
|
@ -182,6 +182,7 @@ if (!is_callable('random_bytes')) {
|
|||
if (!in_array('com', $RandomCompat_disabled_classes)) {
|
||||
try {
|
||||
$RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
|
||||
/** @psalm-suppress TypeDoesNotContainType */
|
||||
if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
|
||||
// See random_bytes_com_dotnet.php
|
||||
require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_com_dotnet.php';
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue