mirror of
https://github.com/PrivateBin/PrivateBin.git
synced 2026-03-22 17:14:03 -04:00
Compare commits
291 Commits
1.3.5
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6731bc1214 | ||
|
|
94ef6392b0 | ||
|
|
438638b72b | ||
|
|
35698f0f99 | ||
|
|
0e3a7196f9 | ||
|
|
7277d2bb43 | ||
|
|
c8c6a67530 | ||
|
|
9443900f66 | ||
|
|
76bc8590a6 | ||
|
|
6cc47e6073 | ||
|
|
2d7f5e9a9f | ||
|
|
6307c01cc6 | ||
|
|
a200f8875c | ||
|
|
75eede9870 | ||
|
|
e822673e90 | ||
|
|
9df6754dfa | ||
|
|
f5f07b9288 | ||
|
|
75a86e2594 | ||
|
|
4758fd683a | ||
|
|
186dd82653 | ||
|
|
8faf0501f4 | ||
|
|
832f000576 | ||
|
|
6da1fa04c3 | ||
|
|
0c0be618e0 | ||
|
|
8b0ab8ee49 | ||
|
|
49d0b35dc8 | ||
|
|
86da334e8f | ||
|
|
b21d5afa2d | ||
|
|
5b5f1e3aa5 | ||
|
|
34923addd7 | ||
|
|
15374f99ae | ||
|
|
1d6bcb1f57 | ||
|
|
77dd0b027f | ||
|
|
902e7cf480 | ||
|
|
08d8922aad | ||
|
|
79abba5124 | ||
|
|
cadfb09193 | ||
|
|
401cd32d07 | ||
|
|
f3b14225ba | ||
|
|
3b472fdd41 | ||
|
|
bb3af09c6c | ||
|
|
57b03d438e | ||
|
|
1a7ac272eb | ||
|
|
1b2b183d7f | ||
|
|
1f872167c1 | ||
|
|
29ffd25c18 | ||
|
|
d43dfdefdf | ||
|
|
1d20eee169 | ||
|
|
53c0e4976b | ||
|
|
0333777a37 | ||
|
|
f4438a0103 | ||
|
|
55db9426b9 | ||
|
|
535f038daa | ||
|
|
0c4852c099 | ||
|
|
b8e8755fb1 | ||
|
|
0b6af67b99 | ||
|
|
56c54dd880 | ||
|
|
a8e1c33b54 | ||
|
|
0cc2b67753 | ||
|
|
4f051fe5a5 | ||
|
|
8d63921924 | ||
|
|
0be55e05bf | ||
|
|
b133c2e233 | ||
|
|
b54308a77e | ||
|
|
47deaeb7ca | ||
|
|
83336b2949 | ||
|
|
35ef64ff79 | ||
|
|
c725b4f0fe | ||
|
|
585d5db983 | ||
|
|
2182cdd44f | ||
|
|
041ef7f7a5 | ||
|
|
6a489d35ab | ||
|
|
ee99952d90 | ||
|
|
4f09873f94 | ||
|
|
ca999b15ed | ||
|
|
eef4f8be3f | ||
|
|
ff19d052b7 | ||
|
|
e53090a937 | ||
|
|
74e1f18ae0 | ||
|
|
02fcc0ba53 | ||
|
|
e36a94cfdd | ||
|
|
b80b318e38 | ||
|
|
1fff4bf4d7 | ||
|
|
20d39347c6 | ||
|
|
aa6e2f7631 | ||
|
|
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 |
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
|
||||
|
||||
37
.github/workflows/refresh-php8.yml
vendored
Normal file
37
.github/workflows/refresh-php8.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
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 config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git merge origin/master
|
||||
|
||||
- name: Push new changes
|
||||
uses: github-actions-x/commit@v2.8
|
||||
with:
|
||||
name: github-actions[bot]
|
||||
email: 41898282+github-actions[bot]@users.noreply.github.com
|
||||
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
|
||||
@@ -29,3 +29,9 @@ disabled:
|
||||
- short_array_syntax
|
||||
- single_line_after_imports
|
||||
- unalign_equals
|
||||
|
||||
finder:
|
||||
path:
|
||||
- "lib/"
|
||||
- "tpl/"
|
||||
- "tst/"
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,8 +1,20 @@
|
||||
# PrivateBin version history
|
||||
|
||||
* **1.4 (not yet released)**
|
||||
* ADDED: Translations for Estonian and Lojban
|
||||
* 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)
|
||||
* ADDED: Oracle database support (#868)
|
||||
* CHANGED: Language selection cookie only transmitted over HTTPS (#472)
|
||||
* CHANGED: Upgrading libraries to: base-x 4.0.0, DOMpurify 2.3.6, ip-lib 1.18.0, jQuery 3.6.0, random_compat 2.0.21 & Showdown 2.0.0
|
||||
* 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: Translations for Hebrew, Lithuanian, Indonesian and Catalan
|
||||
* ADDED: Make the project info configurable (#681)
|
||||
* CHANGED: Upgrading libraries to: DOMpurify 2.2.7, kjua 0.9.0 & random_compat 2.0.18
|
||||
* CHANGED: Open all links in new window (#630)
|
||||
@@ -54,7 +66,7 @@
|
||||
* CHANGED: Upgrading libraries to: DOMpurify 2.0.1
|
||||
* FIXED: Enabling browsers without WASM to create pastes and read uncompressed ones (#454)
|
||||
* FIXED: Cloning related issues (#489, #491, #493, #494)
|
||||
* FIXED: Enable file operation only when editing (#497)
|
||||
* FIXED: Enable file operation only when editing (#497)
|
||||
* FIXED: Clicking 'New' on a previously submitted paste does not blank address bar (#354)
|
||||
* FIXED: Clear address bar when create new paste from existing paste (#479)
|
||||
* FIXED: Discussion section not hiding when new/clone paste is clicked on (#484)
|
||||
@@ -217,7 +229,7 @@ encryption), i18n (translation, counterpart of i18n.php) and helper (stateless u
|
||||
* FIXED: 2 minor corrections to avoid notices in php log.
|
||||
* FIXED: Sources converted to UTF-8.
|
||||
* **Alpha 0.14 (2012-04-20):**
|
||||
* ADDED: GD presence is checked.
|
||||
* ADDED: GD presence is checked.
|
||||
* CHANGED: Traffic limiter data files moved to data/ (→easier rights management)
|
||||
* ADDED: "Burn after reading" implemented. Opening the URL will display the paste and immediately destroy it on server.
|
||||
* **Alpha 0.13 (2012-04-18):**
|
||||
@@ -225,16 +237,16 @@ encryption), i18n (translation, counterpart of i18n.php) and helper (stateless u
|
||||
* FIXED: $error not properly initialized in index.php
|
||||
* **Alpha 0.12 (2012-04-18):**
|
||||
* **DISCUSSIONS !** Now you can enable discussions on your pastes. Of course, posted comments and nickname are also encrypted and the server cannot see them.
|
||||
* This feature implies a change in storage format. You will have to delete all previous pastes in your ZeroBin.
|
||||
* This feature implies a change in storage format. You will have to delete all previous pastes in your ZeroBin.
|
||||
* Added [[php:vizhash_gd|Vizhash]] as avatars, so you can match posters IP addresses without revealing them. (Same image = same IP). Of course the IP address cannot be deduced from the Vizhash.
|
||||
* Remaining time before expiration is now displayed.
|
||||
* Explicit tags were added to CSS and jQuery selectors (eg. div#aaa instead of #aaa) to speed up browser.
|
||||
* Explicit tags were added to CSS and jQuery selectors (eg. div#aaa instead of #aaa) to speed up browser.
|
||||
* Better cleaning of the URL (to make sure the key is not broken by some stupid redirection service)
|
||||
* **Alpha 0.11 (2012-04-12):**
|
||||
* Automatically ignore parameters (such as &utm_source=...) added //after// the anchor by some stupid Web 2.0 services.
|
||||
* First public release.
|
||||
* **Alpha 0.10 (2012-04-12):**
|
||||
* IE9 does not seem to correctly support ''pre-wrap'' either. Special handling mode activated for all version of IE<10. (Note: **ALL other browsers** correctly support this feature.)
|
||||
* IE9 does not seem to correctly support ''pre-wrap'' either. Special handling mode activated for all version of IE<10. (Note: **ALL other browsers** correctly support this feature.)
|
||||
* **Alpha 0.9 (2012-04-11):**
|
||||
* Oh bummer... IE 8 is as shitty as IE6/7: Its does not seem to support ''white-space:pre-wrap'' correctly. I had to activate the special handling mode. I still have to test IE 9.
|
||||
* **Alpha 0.8 (2012-04-11):**
|
||||
|
||||
@@ -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,9 @@ 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
|
||||
* Austin Huang - Oracle database support
|
||||
|
||||
## Translations
|
||||
* Hexalyse - French
|
||||
@@ -50,3 +53,5 @@ Sébastien Sauvage - original idea and main developer
|
||||
* Moo - Lithuanian
|
||||
* whenwesober - Indonesian
|
||||
* retiolus - Catalan
|
||||
* sarnane - Estonian
|
||||
* foxsouns - Lojban
|
||||
|
||||
27
INSTALL.md
27
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,27 @@ 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
|
||||
need to be `TEXT` and not `BLOB` or `MEDIUMBLOB`. The key names in brackets,
|
||||
after `PRIMARY KEY`, need to be removed.
|
||||
|
||||
In **Oracle**, the `data`, `attachment`, `nickname` and `vizhash` columns need
|
||||
to be `CLOB` and not `BLOB` or `MEDIUMBLOB`, the `id` column in the `config`
|
||||
table needs to be `VARCHAR2(16)` and the `meta` column in the `paste` table
|
||||
and the `value` column in the `config` table need to be `VARCHAR2(4000)`.
|
||||
|
||||
### 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'; frame-ancestors 'none'; 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.21",
|
||||
"yzalis/identicon" : "2.0.0",
|
||||
"mlocati/ip-lib" : "1.18.0"
|
||||
},
|
||||
"suggest" : {
|
||||
"google/cloud-storage" : "1.26.1"
|
||||
},
|
||||
"require-dev" : {
|
||||
"phpunit/phpunit" : "^4.6 || ^5.0"
|
||||
@@ -39,4 +43,4 @@
|
||||
"config" : {
|
||||
"autoloader-suffix" : "DontChange"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
144
composer.lock
generated
144
composer.lock
generated
@@ -4,27 +4,94 @@
|
||||
"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": "fa52d4988bfe17d4b27e3a4789a1ec49",
|
||||
"packages": [
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.19",
|
||||
"name": "mlocati/ip-lib",
|
||||
"version": "1.18.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "446fc9faa5c2a9ddf65eb7121c0af7e857295241"
|
||||
"url": "https://github.com/mlocati/ip-lib.git",
|
||||
"reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2"
|
||||
},
|
||||
"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/c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
|
||||
"reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-pdo_sqlite": "*",
|
||||
"phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.5 || ^9.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": "2022-01-13T18:05:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.21",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "96c132c7f2f7bc3230723b66e89f8f150b29d5ae"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/96c132c7f2f7bc3230723b66e89f8f150b29d5ae",
|
||||
"reference": "96c132c7f2f7bc3230723b66e89f8f150b29d5ae",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*"
|
||||
"phpunit/phpunit": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
@@ -53,7 +120,7 @@
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"time": "2020-10-15T10:06:57+00:00"
|
||||
"time": "2022-02-16T17:07:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "yzalis/identicon",
|
||||
@@ -202,12 +269,12 @@
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DeepCopy\\": "src/DeepCopy/"
|
||||
},
|
||||
"files": [
|
||||
"src/DeepCopy/deep_copy.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"DeepCopy\\": "src/DeepCopy/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
@@ -280,16 +347,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-docblock",
|
||||
"version": "5.2.2",
|
||||
"version": "5.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
|
||||
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
|
||||
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
|
||||
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
|
||||
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -300,7 +367,8 @@
|
||||
"webmozart/assert": "^1.9.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~1.3.2"
|
||||
"mockery/mockery": "~1.3.2",
|
||||
"psalm/phar": "^4.8"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -328,20 +396,20 @@
|
||||
}
|
||||
],
|
||||
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
|
||||
"time": "2020-09-03T19:13:55+00:00"
|
||||
"time": "2021-10-19T17:43:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/type-resolver",
|
||||
"version": "1.4.0",
|
||||
"version": "1.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/TypeResolver.git",
|
||||
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
|
||||
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
|
||||
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706",
|
||||
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -349,7 +417,8 @@
|
||||
"phpdocumentor/reflection-common": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-tokenizer": "*"
|
||||
"ext-tokenizer": "*",
|
||||
"psalm/phar": "^4.8"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -373,7 +442,7 @@
|
||||
}
|
||||
],
|
||||
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
|
||||
"time": "2020-09-17T18:55:26+00:00"
|
||||
"time": "2022-01-04T19:58:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
@@ -1351,28 +1420,31 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.22.1",
|
||||
"version": "v1.24.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "c6c942b1ac76c82448322025e084cadc56048b4e"
|
||||
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
|
||||
},
|
||||
"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/30885182c981ab175d4d034db0f6f469898070ab",
|
||||
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.22-dev"
|
||||
"dev-main": "1.23-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -1423,20 +1495,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-01-07T16:49:33+00:00"
|
||||
"time": "2021-10-20T20:35:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v4.4.21",
|
||||
"version": "v4.4.37",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "3871c720871029f008928244e56cf43497da7e9d"
|
||||
"reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/3871c720871029f008928244e56cf43497da7e9d",
|
||||
"reference": "3871c720871029f008928244e56cf43497da7e9d",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/d7f637cc0f0cc14beb0984f2bb50da560b271311",
|
||||
"reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1491,7 +1563,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-03-05T17:58:50+00:00"
|
||||
"time": "2022-01-24T20:11:01+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"
|
||||
}
|
||||
|
||||
109
i18n/ca.json
109
i18n/ca.json
@@ -8,75 +8,75 @@
|
||||
"%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)",
|
||||
"Espereu %d segon entre cada entrada.",
|
||||
"Espereu %d segons entre cada entrada.",
|
||||
"Please wait %d seconds between each post. (2nd plural)",
|
||||
"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.",
|
||||
"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",
|
||||
"Wrong deletion token. Paste was not deleted.": "El token d'eliminació és incorrecte. El Paste no s'ha eliminat.",
|
||||
"Paste was properly deleted.": "El Paste s'ha esborrat correctament.",
|
||||
"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)",
|
||||
"%d segon",
|
||||
"%d segons",
|
||||
"%d seconds (2nd plural)",
|
||||
"%d seconds (3rd plural)"
|
||||
],
|
||||
"%d minutes": [
|
||||
"%d minute (singular)",
|
||||
"%d minutes (1st plural)",
|
||||
"%d minut",
|
||||
"%d minuts",
|
||||
"%d minutes (2nd plural)",
|
||||
"%d minutes (3rd plural)"
|
||||
],
|
||||
"%d hours": [
|
||||
"%d hour (singular)",
|
||||
"%d hours (1st plural)",
|
||||
"%d hora",
|
||||
"%d hores",
|
||||
"%d hours (2nd plural)",
|
||||
"%d hours (3rd plural)"
|
||||
],
|
||||
"%d days": [
|
||||
"%d day (singular)",
|
||||
"%d days (1st plural)",
|
||||
"%d dia",
|
||||
"%d dies",
|
||||
"%d days (2nd plural)",
|
||||
"%d days (3rd plural)"
|
||||
],
|
||||
"%d weeks": [
|
||||
"%d week (singular)",
|
||||
"%d weeks (1st plural)",
|
||||
"%d setmana",
|
||||
"%d setmanes",
|
||||
"%d weeks (2nd plural)",
|
||||
"%d weeks (3rd plural)"
|
||||
],
|
||||
"%d months": [
|
||||
"%d month (singular)",
|
||||
"%d months (1st plural)",
|
||||
"%d mes",
|
||||
"%d mesos",
|
||||
"%d months (2nd plural)",
|
||||
"%d months (3rd plural)"
|
||||
],
|
||||
"%d years": [
|
||||
"%d year (singular)",
|
||||
"%d years (1st plural)",
|
||||
"%d any",
|
||||
"%d anys",
|
||||
"%d years (2nd plural)",
|
||||
"%d years (3rd plural)"
|
||||
],
|
||||
"Never": "Never",
|
||||
"Never": "Mai",
|
||||
"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.": [
|
||||
"This document will expire in %d second. (singular)",
|
||||
@@ -108,26 +108,26 @@
|
||||
"This document will expire in %d months. (2nd plural)",
|
||||
"This document will expire in %d months. (3rd plural)"
|
||||
],
|
||||
"Please enter the password for this paste:": "Please enter the password for this paste:",
|
||||
"Could not decrypt data (Wrong key?)": "Could not decrypt data (Wrong key?)",
|
||||
"Please enter the password for this paste:": "Si us plau, introdueix la contrasenya per aquest paste:",
|
||||
"Could not decrypt data (Wrong key?)": "No s'han pogut desxifrar les dades (Clau incorrecte?)",
|
||||
"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",
|
||||
"Avatar generated from IP address": "Avatar generated from IP address",
|
||||
"Add comment": "Add comment",
|
||||
"Optional nickname…": "Optional nickname…",
|
||||
"Post comment": "Post comment",
|
||||
"Sending comment…": "Sending comment…",
|
||||
"Comment posted.": "Comment posted.",
|
||||
"Reply": "Respondre",
|
||||
"Anonymous": "Anònim",
|
||||
"Avatar generated from IP address": "Avatar generat a partir de l'adreça IP",
|
||||
"Add comment": "Afegir comentari",
|
||||
"Optional nickname…": "Pseudònim opcional…",
|
||||
"Post comment": "Publicar comentari",
|
||||
"Sending comment…": "Enviant comentari…",
|
||||
"Comment posted.": "Comentari publicat.",
|
||||
"Could not refresh display: %s": "Could not refresh display: %s",
|
||||
"unknown status": "unknown status",
|
||||
"unknown status": "estat desconegut",
|
||||
"server error or not responding": "server error or not responding",
|
||||
"Could not post comment: %s": "Could not post comment: %s",
|
||||
"Sending paste…": "Sending paste…",
|
||||
"Sending paste…": "Enviant 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": "Esborrar les dades",
|
||||
"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",
|
||||
@@ -140,10 +140,10 @@
|
||||
"ZiB": "ZiB",
|
||||
"YiB": "YiB",
|
||||
"Format": "Format",
|
||||
"Plain Text": "Plain Text",
|
||||
"Source Code": "Source Code",
|
||||
"Plain Text": "Text sense format",
|
||||
"Source Code": "Codi font",
|
||||
"Markdown": "Markdown",
|
||||
"Download attachment": "Download attachment",
|
||||
"Download attachment": "Baixar els adjunts",
|
||||
"Cloned: '%s'": "Cloned: '%s'",
|
||||
"The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.",
|
||||
"Attach a file": "Attach a file",
|
||||
@@ -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"
|
||||
}
|
||||
189
i18n/fi.json
Normal file
189
i18n/fi.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 minimalistinen, avoimen lähdekoodin online pastebin jossa palvelimella ei ole tietoa syötetystä datasta. Data salataan/puretaan %sselaimessa%s käyttäen 256-bittistä AES:ää.",
|
||||
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Enemmän tietoa <a href=\"https://privatebin.info/\">projektisivulla</a>.",
|
||||
"Because ignorance is bliss": "Koska tieto lisää tuskaa",
|
||||
"en": "fi",
|
||||
"Paste does not exist, has expired or has been deleted.": "Pastea ei ole olemassa, se on vanhentunut, tai se on poistettu.",
|
||||
"%s requires php %s or above to work. Sorry.": "%s tarvitsee php %s-versiota tai uudempaa toimiakseen. Anteeksi.",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s vaatii konfiguraatio-osion [%s] olevan läsnä konfiguraatiotiedostossa.",
|
||||
"Please wait %d seconds between each post.": [
|
||||
"Odotathan %d sekuntin jokaisen lähetyksen välillä.",
|
||||
"Odotathan %d sekuntia jokaisen lähetyksen välillä.",
|
||||
"Odotathan %d sekuntia jokaisen lähetyksen välillä.",
|
||||
"Odotathan %d sekuntia jokaisen lähetyksen välillä."
|
||||
],
|
||||
"Paste is limited to %s of encrypted data.": "Paste on rajoitettu kokoon %s salattua dataa.",
|
||||
"Invalid data.": "Virheellinen data.",
|
||||
"You are unlucky. Try again.": "Olet epäonnekas. Yritä uudelleen",
|
||||
"Error saving comment. Sorry.": "Virhe kommenttia tallentaessa. Anteeksi.",
|
||||
"Error saving paste. Sorry.": "Virhe pastea tallentaessa. Anteeksi.",
|
||||
"Invalid paste ID.": "Virheellinen paste ID.",
|
||||
"Paste is not of burn-after-reading type.": "Paste ei ole polta-lukemisen-jälkeen-tyyppiä",
|
||||
"Wrong deletion token. Paste was not deleted.": "Virheellinen poistotunniste. Pastea ei poistettu.",
|
||||
"Paste was properly deleted.": "Paste poistettiin kunnolla.",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScriptiä tarvitaan jotta %s toimisi. Anteeksi haitasta.",
|
||||
"%s requires a modern browser to work.": "%s tarvitsee modernia selainta toimiakseen.",
|
||||
"New": "Uusi",
|
||||
"Send": "Lähetä",
|
||||
"Clone": "Kloonaa",
|
||||
"Raw text": "Raaka teksti",
|
||||
"Expires": "Vanhenee",
|
||||
"Burn after reading": "Polta lukemisen jälkeen",
|
||||
"Open discussion": "Avaa keskustelu",
|
||||
"Password (recommended)": "Salasana (suositeltu)",
|
||||
"Discussion": "Keskustelu",
|
||||
"Toggle navigation": "Navigointi päällä/pois",
|
||||
"%d seconds": [
|
||||
"%d sekunti",
|
||||
"%d sekuntia",
|
||||
"%d sekuntia",
|
||||
"%d sekuntia"
|
||||
],
|
||||
"%d minutes": [
|
||||
"%d minuutti",
|
||||
"%d minuuttia",
|
||||
"%d minuuttia",
|
||||
"%d minuuttia"
|
||||
],
|
||||
"%d hours": [
|
||||
"%d tunti",
|
||||
"%d tuntia",
|
||||
"%d tuntia",
|
||||
"%d tuntia"
|
||||
],
|
||||
"%d days": [
|
||||
"%d päivä",
|
||||
"%d päivää",
|
||||
"%d päivää",
|
||||
"%d päivää"
|
||||
],
|
||||
"%d weeks": [
|
||||
"%d viikko",
|
||||
"%d viikkoa",
|
||||
"%d viikkoa",
|
||||
"%d viikkoa"
|
||||
],
|
||||
"%d months": [
|
||||
"%d kuukausi",
|
||||
"%d kuukautta",
|
||||
"%d kuukautta",
|
||||
"%d kuukautta"
|
||||
],
|
||||
"%d years": [
|
||||
"%d vuosi",
|
||||
"%d vuotta",
|
||||
"%d vuotta",
|
||||
"%d vuotta"
|
||||
],
|
||||
"Never": "Ei koskaan",
|
||||
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Huomaa: Tämä on testipalvelu: Data voidaan poistaa milloin tahansa. Kissanpennut kuolevat jos väärinkäytät tätä palvelua.",
|
||||
"This document will expire in %d seconds.": [
|
||||
"Tämä dokumentti vanhenee %d sekuntissa.",
|
||||
"Tämä dokumentti vanhenee %d sekunnissa.",
|
||||
"Tämä dokumentti vanhenee %d sekunnissa.",
|
||||
"Tämä dokumentti vanhenee %d sekunnissa."
|
||||
],
|
||||
"This document will expire in %d minutes.": [
|
||||
"Tämä dokumentti vanhenee %d minuutissa.",
|
||||
"Tämä dokumentti vanhenee %d minuutissa.",
|
||||
"Tämä dokumentti vanhenee %d minuutissa.",
|
||||
"Tämä dokumentti vanhenee %d minuutissa."
|
||||
],
|
||||
"This document will expire in %d hours.": [
|
||||
"Tämä dokumentti vanhenee %d tunnissa.",
|
||||
"Tämä dokumentti vanhenee %d tunnissa.",
|
||||
"Tämä dokumentti vanhenee %d tunnissa.",
|
||||
"Tämä dokumentti vanhenee %d tunnissa."
|
||||
],
|
||||
"This document will expire in %d days.": [
|
||||
"Tämä dokumentti vanhenee %d päivässä.",
|
||||
"Tämä dokumentti vanhenee %d päivässä.",
|
||||
"Tämä dokumentti vanhenee %d päivässä.",
|
||||
"Tämä dokumentti vanhenee %d päivässä."
|
||||
],
|
||||
"This document will expire in %d months.": [
|
||||
"Tämä dokumentti vanhenee %d kuukaudessa.",
|
||||
"Tämä dokumentti vanhenee %d kuukaudessa.",
|
||||
"Tämä dokumentti vanhenee %d kuukaudessa.",
|
||||
"Tämä dokumentti vanhenee %d kuukaudessa."
|
||||
],
|
||||
"Please enter the password for this paste:": "Syötä salasana tälle pastelle:",
|
||||
"Could not decrypt data (Wrong key?)": "Dataa ei voitu purkaa (Väärä avain?)",
|
||||
"Could not delete the paste, it was not stored in burn after reading mode.": "Pastea ei voitu poistaa, sitä ei säilytetty \"Polta lukemisen jälkeen\" -tilassa.",
|
||||
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "VAIN SINUN SILMILLESI. Älä sulje tätä ikkunaa, tätä viestiä ei voida näyttää uudelleen.",
|
||||
"Could not decrypt comment; Wrong key?": "Kommenttia ei voitu purkaa; väärä avain?",
|
||||
"Reply": "Vastaa",
|
||||
"Anonymous": "Anonyymi",
|
||||
"Avatar generated from IP address": "Avatar generoitu IP-osoitteesta",
|
||||
"Add comment": "Lisää kommentti",
|
||||
"Optional nickname…": "Valinnainen nimimerkki…",
|
||||
"Post comment": "Lähetä kommentti",
|
||||
"Sending comment…": "Lähetetään kommenttia…",
|
||||
"Comment posted.": "Kommentti lähetetty.",
|
||||
"Could not refresh display: %s": "Näyttöä ei voitu päivittää: %s",
|
||||
"unknown status": "tuntematon status",
|
||||
"server error or not responding": "palvelinvirhe tai palvelin ei vastaa",
|
||||
"Could not post comment: %s": "Kommenttia ei voitu lähettää: %s",
|
||||
"Sending paste…": "Lähetetään pastea…",
|
||||
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Pastesi on <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Paina [Ctrl]+[c] kopioidaksesi)</span>",
|
||||
"Delete data": "Poista data",
|
||||
"Could not create paste: %s": "Pastea ei voitu luoda: %s",
|
||||
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Pastea ei voitu purkaa: Purkausavain puuttuu URL:stä (Käytitkö uudelleenohjaajaa tai URL-lyhentäjää joka poistaa osan URL:stä?)",
|
||||
"B": "B",
|
||||
"KiB": "KiB",
|
||||
"MiB": "MiB",
|
||||
"GiB": "GiB",
|
||||
"TiB": "TiB",
|
||||
"PiB": "PiB",
|
||||
"EiB": "EiB",
|
||||
"ZiB": "ZiB",
|
||||
"YiB": "YiB",
|
||||
"Format": "Formaatti",
|
||||
"Plain Text": "Perusteksti",
|
||||
"Source Code": "Lähdekoodi",
|
||||
"Markdown": "Markdown",
|
||||
"Download attachment": "Lataa liite",
|
||||
"Cloned: '%s'": "Kloonattu: '%s'",
|
||||
"The cloned file '%s' was attached to this paste.": "Kloonattu tiedosto '%s' liitettiin tähän pasteen",
|
||||
"Attach a file": "Liitä tiedosto",
|
||||
"alternatively drag & drop a file or paste an image from the clipboard": "vaihtoehtoisesti vedä & pudota tiedosto tai liitä kuva leikepöydältä",
|
||||
"File too large, to display a preview. Please download the attachment.": "Tiedosto on liian iso esikatselun näyttämiseksi. Lataathan liitteen.",
|
||||
"Remove attachment": "Poista liite",
|
||||
"Your browser does not support uploading encrypted files. Please use a newer browser.": "Selaimesi ei tue salattujen tiedostojen lataamista. Käytäthän uudempaa selainta.",
|
||||
"Invalid attachment.": "Virheellinen liite.",
|
||||
"Options": "Asetukset",
|
||||
"Shorten URL": "Lyhennä URL",
|
||||
"Editor": "Muokkaaja",
|
||||
"Preview": "Esikatselu",
|
||||
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s vaatii PATH:in loppuvan \"%s\"-merkkiin. Päivitäthän PATH:in index.php:ssäsi.",
|
||||
"Decrypt": "Pura",
|
||||
"Enter password": "Syötä salasana",
|
||||
"Loading…": "Ladataan…",
|
||||
"Decrypting paste…": "Puretaan pastea…",
|
||||
"Preparing new paste…": "Valmistellaan uutta pastea",
|
||||
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "Jos tämä viesti ei katoa koskaan, katsothan <a href=\"%s\">tämän FAQ:n ongelmanratkaisutiedon löytämiseksi</a>.",
|
||||
"+++ no paste text +++": "+++ ei paste-tekstiä +++",
|
||||
"Could not get paste data: %s": "Paste-tietoja ei löydetty: %s",
|
||||
"QR code": "QR-koodi",
|
||||
"This website is using an insecure HTTP connection! Please use it only for testing.": "Tämä sivusto käyttää epäturvallista HTTP-yhteyttä! Käytäthän sitä vain testaukseen.",
|
||||
"For more information <a href=\"%s\">see this FAQ entry</a>.": "Lisätietoja varten <a href=\"%s\">lue tämä FAQ-kohta</a>.",
|
||||
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Selaimesi ehkä tarvitsee HTTPS-yhteyden tukeakseen WebCrypto API:a. Yritä <a href=\"%s\">vaihtamista HTTPS:ään</a>.",
|
||||
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Selaimesi ei tue WebAssemblyä jota käytetään zlib-pakkaamiseen. Voit luoda pakkaamattomia dokumentteja, mutta et voi lukea pakattuja dokumentteja.",
|
||||
"waiting on user to provide a password": "odotetaan käyttäjän antavan salasanan",
|
||||
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Dataa ei voitu purkaa. Syötitkö väärän salasanan? Yritä uudelleen ylhäällä olevalla painikkeella.",
|
||||
"Retry": "Yritä uudelleen",
|
||||
"Showing raw text…": "Näytetään raaka reksti…",
|
||||
"Notice:": "Huomautus:",
|
||||
"This link will expire after %s.": "Tämä linkki vanhenee ajan %s jälkeen.",
|
||||
"This link can only be accessed once, do not use back or refresh button in your browser.": "Tätä linkkiä voidaan käyttää vain kerran, älä käytä taaksepäin- tai päivityspainiketta selaimessasi.",
|
||||
"Link:": "Linkki:",
|
||||
"Recipient may become aware of your timezone, convert time to UTC?": "Vastaanottaja saattaa tulla tietoiseksi aikavyöhykkeestäsi, muutetaanko aika UTC:ksi?",
|
||||
"Use Current Timezone": "Käytä nykyistä aikavyöhykettä",
|
||||
"Convert To UTC": "Muuta UTC:ksi",
|
||||
"Close": "Sulje",
|
||||
"Encrypted note on PrivateBin": "Salattu viesti PrivateBinissä",
|
||||
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Käy tässä linkissä nähdäksesi viestin. URL:n antaminen kenellekään antaa heidänkin päästä katsomeen viestiä. ",
|
||||
"URL shortener may expose your decrypt key in URL.": "URL-lyhentäjä voi paljastaa purkuavaimesi URL:ssä.",
|
||||
"Save paste": "Tallenna paste"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
189
i18n/jbo.json
Normal file
189
i18n/jbo.json
Normal file
@@ -0,0 +1,189 @@
|
||||
{
|
||||
"PrivateBin": "sivlolnitvanku'a",
|
||||
"%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.": ".i la %s mupli lo sorcu lo'e se setca kibro .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i ba'e %sle brauzero%s ku mipri le do datni ku fi la'oi AES poi bitni li 256",
|
||||
"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": ".i ki'u le ka na djuno cu ka saxfri",
|
||||
"en": "jbo",
|
||||
"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.",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.",
|
||||
"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)"
|
||||
],
|
||||
"Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.",
|
||||
"Invalid data.": ".i le selru'e cu na drani",
|
||||
"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 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": "cnino",
|
||||
"Send": "benji",
|
||||
"Clone": "fukpi",
|
||||
"Raw text": "vlapoi nalselrucyzu'e",
|
||||
"Expires": "vimcu",
|
||||
"Burn after reading": "vimcu ba la tcidu",
|
||||
"Open discussion": "lo zbasu cu casnu",
|
||||
"Password (recommended)": "japyvla (nelti'i)",
|
||||
"Discussion": "casnu",
|
||||
"Toggle navigation": "Toggle navigation",
|
||||
"%d seconds": [
|
||||
"%d second (singular)",
|
||||
"%d seconds (1st plural)",
|
||||
"%d seconds (2nd plural)",
|
||||
"%d seconds (3rd plural)"
|
||||
],
|
||||
"%d minutes": [
|
||||
"%d minute (singular)",
|
||||
"%d minutes (1st plural)",
|
||||
"%d minutes (2nd plural)",
|
||||
"%d minutes (3rd plural)"
|
||||
],
|
||||
"%d hours": [
|
||||
"%d hour (singular)",
|
||||
"%d hours (1st plural)",
|
||||
"%d hours (2nd plural)",
|
||||
"%d hours (3rd plural)"
|
||||
],
|
||||
"%d days": [
|
||||
"%d day (singular)",
|
||||
"%d days (1st plural)",
|
||||
"%d days (2nd plural)",
|
||||
"%d days (3rd plural)"
|
||||
],
|
||||
"%d weeks": [
|
||||
"%d week (singular)",
|
||||
"%d weeks (1st plural)",
|
||||
"%d weeks (2nd plural)",
|
||||
"%d weeks (3rd plural)"
|
||||
],
|
||||
"%d months": [
|
||||
"%d month (singular)",
|
||||
"%d months (1st plural)",
|
||||
"%d months (2nd plural)",
|
||||
"%d months (3rd plural)"
|
||||
],
|
||||
"%d years": [
|
||||
"%d year (singular)",
|
||||
"%d years (1st plural)",
|
||||
"%d years (2nd plural)",
|
||||
"%d years (3rd plural)"
|
||||
],
|
||||
"Never": "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.": [
|
||||
"This document will expire in %d second. (singular)",
|
||||
"This document will expire in %d seconds. (1st plural)",
|
||||
"This document will expire in %d seconds. (2nd plural)",
|
||||
"This document will expire in %d seconds. (3rd plural)"
|
||||
],
|
||||
"This document will expire in %d minutes.": [
|
||||
"This document will expire in %d minute. (singular)",
|
||||
"This document will expire in %d minutes. (1st plural)",
|
||||
"This document will expire in %d minutes. (2nd plural)",
|
||||
"This document will expire in %d minutes. (3rd plural)"
|
||||
],
|
||||
"This document will expire in %d hours.": [
|
||||
"This document will expire in %d hour. (singular)",
|
||||
"This document will expire in %d hours. (1st plural)",
|
||||
"This document will expire in %d hours. (2nd plural)",
|
||||
"This document will expire in %d hours. (3rd plural)"
|
||||
],
|
||||
"This document will expire in %d days.": [
|
||||
"This document will expire in %d day. (singular)",
|
||||
"This document will expire in %d days. (1st plural)",
|
||||
"This document will expire in %d days. (2nd plural)",
|
||||
"This document will expire in %d days. (3rd plural)"
|
||||
],
|
||||
"This document will expire in %d months.": [
|
||||
"This document will expire in %d month. (singular)",
|
||||
"This document will expire in %d months. (1st plural)",
|
||||
"This document will expire in %d months. (2nd plural)",
|
||||
"This document will expire in %d months. (3rd plural)"
|
||||
],
|
||||
"Please enter the password for this paste:": "Please enter the password for this paste:",
|
||||
"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",
|
||||
"Anonymous": "Anonymous",
|
||||
"Avatar generated from IP address": "Avatar generated from IP address",
|
||||
"Add comment": "Add comment",
|
||||
"Optional nickname…": "Optional nickname…",
|
||||
"Post comment": "Post comment",
|
||||
"Sending comment…": "Sending comment…",
|
||||
"Comment posted.": "Comment posted.",
|
||||
"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",
|
||||
"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",
|
||||
"KiB": "KiB",
|
||||
"MiB": "MiB",
|
||||
"GiB": "GiB",
|
||||
"TiB": "TiB",
|
||||
"PiB": "PiB",
|
||||
"EiB": "EiB",
|
||||
"ZiB": "ZiB",
|
||||
"YiB": "YiB",
|
||||
"Format": "Format",
|
||||
"Plain Text": "Plain Text",
|
||||
"Source Code": "Source Code",
|
||||
"Markdown": "Markdown",
|
||||
"Download attachment": "Download attachment",
|
||||
"Cloned: '%s'": "Cloned: '%s'",
|
||||
"The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.",
|
||||
"Attach a file": "Attach a file",
|
||||
"alternatively drag & drop a file or paste an image from the clipboard": "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": "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.": "Invalid attachment.",
|
||||
"Options": "Options",
|
||||
"Shorten URL": "Shorten URL",
|
||||
"Editor": "Editor",
|
||||
"Preview": "Preview",
|
||||
"%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…",
|
||||
"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": "ky.bu ry termifra",
|
||||
"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": "refcfa",
|
||||
"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:": "urli:",
|
||||
"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": "galfi lo cabni la utc",
|
||||
"Close": "ganlo",
|
||||
"Encrypted note on PrivateBin": ".i lo lo notci ku mifra cu zvati sivlolnitvanku'a",
|
||||
"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.",
|
||||
"Save paste": "rejgau fukpi"
|
||||
}
|
||||
@@ -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"],
|
||||
@@ -89,6 +89,7 @@
|
||||
"ku": ["Kurdî", "Kurdish"],
|
||||
"kj": ["Kuanyama", "Kwanyama"],
|
||||
"la": ["lingua latina", "Latin"],
|
||||
"jbo":["jbobau", "Lojban"],
|
||||
"lb": ["Lëtzebuergesch", "Luxembourgish"],
|
||||
"lg": ["Luganda", "Ganda"],
|
||||
"li": ["Limburgs", "Limburgish"],
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
63
i18n/zh.json
63
i18n/zh.json
@@ -1,29 +1,29 @@
|
||||
{
|
||||
"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.": "保存评论时出现错误,抱歉。",
|
||||
"Error saving paste. Sorry.": "保存粘贴内容时出现错误,抱歉。",
|
||||
"Invalid paste ID.": "无效的ID。",
|
||||
"Invalid paste ID.": "无效的 ID。",
|
||||
"Paste is not of burn-after-reading type.": "粘贴内容不是阅后即焚类型。",
|
||||
"Wrong deletion token. Paste was not deleted.": "错误的删除token,粘贴内容没有被删除。",
|
||||
"Paste was properly deleted.": "粘贴内容已被正确删除。",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "%s需要JavaScript来进行加解密。 给你带来的不便敬请谅解。",
|
||||
"%s requires a modern browser to work.": "%s需要在现代浏览器上工作。",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "%s 需要 JavaScript 来进行加解密。 给你带来的不便敬请谅解。",
|
||||
"%s requires a modern browser to work.": "%s 需要在现代浏览器上工作。",
|
||||
"New": "新建",
|
||||
"Send": "送出",
|
||||
"Clone": "复制",
|
||||
@@ -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>。",
|
||||
"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。 您可以创建未压缩的文档,但不能读取压缩的文档。",
|
||||
"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>。",
|
||||
"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:": "链接地址:",
|
||||
"Recipient may become aware of your timezone, convert time to UTC?": "收件人可能会知道您的时区,将时间转换为UTC?",
|
||||
"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",
|
||||
"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."
|
||||
"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 中的解密密钥。",
|
||||
"Save paste": "保存内容"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
'use strict';
|
||||
// base-x encoding / decoding
|
||||
// based on https://github.com/cryptocoinjs/base-x 3.0.7
|
||||
// modification: removed Buffer dependency and node.modules entry
|
||||
// Copyright (c) 2018 base-x contributors
|
||||
// Copyright (c) 2014-2018 The Bitcoin Core developers (base58.cpp)
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
(function(){
|
||||
this.baseX = function base (ALPHABET) {
|
||||
if (ALPHABET.length >= 255) { throw new TypeError('Alphabet too long') }
|
||||
var BASE_MAP = new Uint8Array(256)
|
||||
BASE_MAP.fill(255)
|
||||
for (var j = 0; j < BASE_MAP.length; j++) {
|
||||
BASE_MAP[j] = 255
|
||||
}
|
||||
for (var i = 0; i < ALPHABET.length; i++) {
|
||||
var x = ALPHABET.charAt(i)
|
||||
var xc = x.charCodeAt(0)
|
||||
@@ -23,6 +22,13 @@ this.baseX = function base (ALPHABET) {
|
||||
var FACTOR = Math.log(BASE) / Math.log(256) // log(BASE) / log(256), rounded up
|
||||
var iFACTOR = Math.log(256) / Math.log(BASE) // log(256) / log(BASE), rounded up
|
||||
function encode (source) {
|
||||
if (source instanceof Uint8Array) {
|
||||
} else if (ArrayBuffer.isView(source)) {
|
||||
source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength)
|
||||
} else if (Array.isArray(source)) {
|
||||
source = Uint8Array.from(source)
|
||||
}
|
||||
if (!(source instanceof Uint8Array)) { throw new TypeError('Expected Uint8Array') }
|
||||
if (source.length === 0) { return '' }
|
||||
// Skip & count leading zeroes.
|
||||
var zeroes = 0
|
||||
@@ -62,10 +68,8 @@ this.baseX = function base (ALPHABET) {
|
||||
}
|
||||
function decodeUnsafe (source) {
|
||||
if (typeof source !== 'string') { throw new TypeError('Expected String') }
|
||||
if (source.length === 0) { return '' }
|
||||
if (source.length === 0) { return new Uint8Array() }
|
||||
var psz = 0
|
||||
// Skip leading spaces.
|
||||
if (source[psz] === ' ') { return }
|
||||
// Skip and count leading '1's.
|
||||
var zeroes = 0
|
||||
var length = 0
|
||||
@@ -92,14 +96,12 @@ this.baseX = function base (ALPHABET) {
|
||||
length = i
|
||||
psz++
|
||||
}
|
||||
// Skip trailing spaces.
|
||||
if (source[psz] === ' ') { return }
|
||||
// Skip leading zeroes in b256.
|
||||
var it4 = size - length
|
||||
while (it4 !== size && b256[it4] === 0) {
|
||||
it4++
|
||||
}
|
||||
var vch = []
|
||||
var vch = new Uint8Array(zeroes + (size - it4))
|
||||
var j = zeroes
|
||||
while (it4 !== size) {
|
||||
vch[j++] = b256[it4++]
|
||||
@@ -10,15 +10,15 @@ global.fs = require('fs');
|
||||
global.WebCrypto = require('@peculiar/webcrypto').Crypto;
|
||||
|
||||
// application libraries to test
|
||||
global.$ = global.jQuery = require('./jquery-3.4.1');
|
||||
global.$ = global.jQuery = require('./jquery-3.6.0');
|
||||
global.RawDeflate = require('./rawinflate-0.3').RawDeflate;
|
||||
global.zlib = require('./zlib-1.2.11').zlib;
|
||||
require('./prettify');
|
||||
global.prettyPrint = window.PR.prettyPrint;
|
||||
global.prettyPrintOne = window.PR.prettyPrintOne;
|
||||
global.showdown = require('./showdown-1.9.1');
|
||||
global.DOMPurify = require('./purify-2.2.7');
|
||||
global.baseX = require('./base-x-3.0.7').baseX;
|
||||
global.showdown = require('./showdown-2.0.0');
|
||||
global.DOMPurify = require('./purify-2.3.6');
|
||||
global.baseX = require('./base-x-4.0.0').baseX;
|
||||
global.Legacy = require('./legacy').Legacy;
|
||||
require('./bootstrap-3.3.7');
|
||||
require('./privatebin');
|
||||
|
||||
2
js/jquery-3.4.1.js
vendored
2
js/jquery-3.4.1.js
vendored
File diff suppressed because one or more lines are too long
2
js/jquery-3.6.0.js
vendored
Normal file
2
js/jquery-3.6.0.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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', 'jbo', '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
|
||||
@@ -785,6 +785,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||
case 'he':
|
||||
return n === 1 ? 0 : (n === 2 ? 1 : ((n < 0 || n > 10) && (n % 10 === 0) ? 2 : 3));
|
||||
case 'id':
|
||||
case 'jbo':
|
||||
return 0;
|
||||
case 'lt':
|
||||
return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
|
||||
@@ -795,7 +796,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 +3526,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||
$password,
|
||||
$passwordInput,
|
||||
$rawTextButton,
|
||||
$downloadTextButton,
|
||||
$qrCodeLink,
|
||||
$emailLink,
|
||||
$sendButton,
|
||||
@@ -3666,6 +3668,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 +3702,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 +3918,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||
$newButton.removeClass('hidden');
|
||||
$cloneButton.removeClass('hidden');
|
||||
$rawTextButton.removeClass('hidden');
|
||||
$downloadTextButton.removeClass('hidden');
|
||||
$qrCodeLink.removeClass('hidden');
|
||||
|
||||
viewButtonsDisplayed = true;
|
||||
@@ -3912,6 +3939,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||
$cloneButton.addClass('hidden');
|
||||
$newButton.addClass('hidden');
|
||||
$rawTextButton.addClass('hidden');
|
||||
$downloadTextButton.addClass('hidden');
|
||||
$qrCodeLink.addClass('hidden');
|
||||
me.hideEmailButton();
|
||||
|
||||
@@ -4073,6 +4101,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 +4373,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||
$password = $('#password');
|
||||
$passwordInput = $('#passwordinput');
|
||||
$rawTextButton = $('#rawtextbutton');
|
||||
$downloadTextButton = $('#downloadtextbutton');
|
||||
$retryButton = $('#retrybutton');
|
||||
$sendButton = $('#sendbutton');
|
||||
$qrCodeLink = $('#qrcodelink');
|
||||
@@ -4351,6 +4391,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 +4730,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||
TopNav.showEmailButton();
|
||||
|
||||
TopNav.hideRawButton();
|
||||
TopNav.hideDownloadButton();
|
||||
Editor.hide();
|
||||
|
||||
// parse and show text
|
||||
@@ -5363,8 +5405,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||
node.setAttribute('target', '_blank');
|
||||
}
|
||||
// set non-HTML/MathML links to xlink:show=new
|
||||
if (!node.hasAttribute('target')
|
||||
&& (node.hasAttribute('xlink:href')
|
||||
if (!node.hasAttribute('target')
|
||||
&& (node.hasAttribute('xlink:href')
|
||||
|| node.hasAttribute('href'))) {
|
||||
node.setAttribute('xlink:show', 'new');
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
js/purify-2.3.6.js
Normal file
2
js/purify-2.3.6.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2447
js/showdown-2.0.0.js
Normal file
2447
js/showdown-2.0.0.js
Normal file
File diff suppressed because it is too large
Load Diff
5157
js/showdown.js
Normal file
5157
js/showdown.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,6 @@ namespace PrivateBin;
|
||||
|
||||
use Exception;
|
||||
use PDO;
|
||||
use PrivateBin\Persistence\DataStore;
|
||||
|
||||
/**
|
||||
* Configuration
|
||||
@@ -45,7 +44,7 @@ class Configuration
|
||||
'fileupload' => false,
|
||||
'burnafterreadingselected' => false,
|
||||
'defaultformatter' => 'plaintext',
|
||||
'syntaxhighlightingtheme' => null,
|
||||
'syntaxhighlightingtheme' => '',
|
||||
'sizelimit' => 10485760,
|
||||
'template' => 'bootstrap',
|
||||
'info' => 'More information on the <a href=\'https://privatebin.info/\'>project page</a>.',
|
||||
@@ -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\'; frame-ancestors \'none\'; 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',
|
||||
@@ -79,14 +78,13 @@ class Configuration
|
||||
'markdown' => 'Markdown',
|
||||
),
|
||||
'traffic' => array(
|
||||
'limit' => 10,
|
||||
'header' => null,
|
||||
'dir' => 'data',
|
||||
'limit' => 10,
|
||||
'header' => null,
|
||||
'exemptedIp' => null,
|
||||
),
|
||||
'purge' => array(
|
||||
'limit' => 300,
|
||||
'batchsize' => 10,
|
||||
'dir' => 'data',
|
||||
),
|
||||
'model' => array(
|
||||
'class' => 'Filesystem',
|
||||
@@ -103,28 +101,23 @@ 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;
|
||||
}
|
||||
|
||||
if (is_readable($configFile)) {
|
||||
$config = parse_ini_file($configFile, true);
|
||||
foreach (array('main', 'model', 'model_options') as $section) {
|
||||
if (!array_key_exists($section, $config)) {
|
||||
throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2);
|
||||
$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) {
|
||||
if (!array_key_exists($section, $config)) {
|
||||
throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,20 +195,17 @@ class Controller
|
||||
*/
|
||||
private function _create()
|
||||
{
|
||||
try {
|
||||
// Ensure last paste from visitors IP address was more than configured amount of seconds ago.
|
||||
TrafficLimiter::setConfiguration($this->_conf);
|
||||
if (!TrafficLimiter::canPass()) {
|
||||
$this->_return_message(
|
||||
1, I18n::_(
|
||||
'Please wait %d seconds between each post.',
|
||||
$this->_conf->getKey('limit', 'traffic')
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->_return_message(1, I18n::_($e->getMessage()));
|
||||
// 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::_(
|
||||
'Please wait %d seconds between each post.',
|
||||
$this->_conf->getKey('limit', 'traffic')
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -97,6 +97,11 @@ class Database extends AbstractData
|
||||
self::$_type = strtolower(
|
||||
substr($options['dsn'], 0, strpos($options['dsn'], ':'))
|
||||
);
|
||||
// MySQL uses backticks to quote identifiers by default,
|
||||
// tell it to expect ANSI SQL double quotes
|
||||
if (self::$_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
|
||||
$options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET sql_mode='ANSI_QUOTES'";
|
||||
}
|
||||
$tableQuery = self::_getTableQuery(self::$_type);
|
||||
self::$_db = new PDO(
|
||||
$options['dsn'],
|
||||
@@ -198,21 +203,25 @@ class Database extends AbstractData
|
||||
$opendiscussion = $paste['adata'][2];
|
||||
$burnafterreading = $paste['adata'][3];
|
||||
}
|
||||
return self::_exec(
|
||||
'INSERT INTO ' . self::_sanitizeIdentifier('paste') .
|
||||
' VALUES(?,?,?,?,?,?,?,?,?)',
|
||||
array(
|
||||
$pasteid,
|
||||
$isVersion1 ? $paste['data'] : Json::encode($paste),
|
||||
$created,
|
||||
$expire_date,
|
||||
(int) $opendiscussion,
|
||||
(int) $burnafterreading,
|
||||
Json::encode($meta),
|
||||
$attachment,
|
||||
$attachmentname,
|
||||
)
|
||||
);
|
||||
try {
|
||||
return self::_exec(
|
||||
'INSERT INTO "' . self::_sanitizeIdentifier('paste') .
|
||||
'" VALUES(?,?,?,?,?,?,?,?,?)',
|
||||
array(
|
||||
$pasteid,
|
||||
$isVersion1 ? $paste['data'] : Json::encode($paste),
|
||||
$created,
|
||||
$expire_date,
|
||||
(int) $opendiscussion,
|
||||
(int) $burnafterreading,
|
||||
Json::encode($meta),
|
||||
$attachment,
|
||||
$attachmentname,
|
||||
)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,11 +238,14 @@ class Database extends AbstractData
|
||||
}
|
||||
|
||||
self::$_cache[$pasteid] = false;
|
||||
$paste = self::_select(
|
||||
'SELECT * FROM ' . self::_sanitizeIdentifier('paste') .
|
||||
' WHERE dataid = ?', array($pasteid), true
|
||||
);
|
||||
|
||||
try {
|
||||
$paste = self::_select(
|
||||
'SELECT * FROM "' . self::_sanitizeIdentifier('paste') .
|
||||
'" WHERE "dataid" = ?', array($pasteid), true
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$paste = false;
|
||||
}
|
||||
if ($paste === false) {
|
||||
return false;
|
||||
}
|
||||
@@ -290,12 +302,12 @@ class Database extends AbstractData
|
||||
public function delete($pasteid)
|
||||
{
|
||||
self::_exec(
|
||||
'DELETE FROM ' . self::_sanitizeIdentifier('paste') .
|
||||
' WHERE dataid = ?', array($pasteid)
|
||||
'DELETE FROM "' . self::_sanitizeIdentifier('paste') .
|
||||
'" WHERE "dataid" = ?', array($pasteid)
|
||||
);
|
||||
self::_exec(
|
||||
'DELETE FROM ' . self::_sanitizeIdentifier('comment') .
|
||||
' WHERE pasteid = ?', array($pasteid)
|
||||
'DELETE FROM "' . self::_sanitizeIdentifier('comment') .
|
||||
'" WHERE "pasteid" = ?', array($pasteid)
|
||||
);
|
||||
if (
|
||||
array_key_exists($pasteid, self::$_cache)
|
||||
@@ -348,19 +360,23 @@ class Database extends AbstractData
|
||||
$meta[$key] = null;
|
||||
}
|
||||
}
|
||||
return self::_exec(
|
||||
'INSERT INTO ' . self::_sanitizeIdentifier('comment') .
|
||||
' VALUES(?,?,?,?,?,?,?)',
|
||||
array(
|
||||
$commentid,
|
||||
$pasteid,
|
||||
$parentid,
|
||||
$data,
|
||||
$meta['nickname'],
|
||||
$meta[$iconKey],
|
||||
$meta[$createdKey],
|
||||
)
|
||||
);
|
||||
try {
|
||||
return self::_exec(
|
||||
'INSERT INTO "' . self::_sanitizeIdentifier('comment') .
|
||||
'" VALUES(?,?,?,?,?,?,?)',
|
||||
array(
|
||||
$commentid,
|
||||
$pasteid,
|
||||
$parentid,
|
||||
$data,
|
||||
$meta['nickname'],
|
||||
$meta[$iconKey],
|
||||
$meta[$createdKey],
|
||||
)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,13 +389,13 @@ class Database extends AbstractData
|
||||
public function readComments($pasteid)
|
||||
{
|
||||
$rows = self::_select(
|
||||
'SELECT * FROM ' . self::_sanitizeIdentifier('comment') .
|
||||
' WHERE pasteid = ?', array($pasteid)
|
||||
'SELECT * FROM "' . self::_sanitizeIdentifier('comment') .
|
||||
'" WHERE "pasteid" = ?', array($pasteid)
|
||||
);
|
||||
|
||||
// create comment list
|
||||
$comments = array();
|
||||
if (count($rows)) {
|
||||
if (is_array($rows) && count($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$i = $this->getOpenSlot($comments, (int) $row['postdate']);
|
||||
$data = Json::decode($row['data']);
|
||||
@@ -416,13 +432,85 @@ class Database extends AbstractData
|
||||
*/
|
||||
public function existsComment($pasteid, $parentid, $commentid)
|
||||
{
|
||||
return (bool) self::_select(
|
||||
'SELECT dataid FROM ' . self::_sanitizeIdentifier('comment') .
|
||||
' WHERE pasteid = ? AND parentid = ? AND dataid = ?',
|
||||
array($pasteid, $parentid, $commentid), true
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns up to batch size number of paste ids that have expired
|
||||
*
|
||||
@@ -434,11 +522,12 @@ class Database extends AbstractData
|
||||
{
|
||||
$pastes = array();
|
||||
$rows = self::_select(
|
||||
'SELECT dataid FROM ' . self::_sanitizeIdentifier('paste') .
|
||||
' WHERE expiredate < ? AND expiredate != ? LIMIT ?',
|
||||
'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') .
|
||||
'" WHERE "expiredate" < ? AND "expiredate" != ? ' .
|
||||
(self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'),
|
||||
array(time(), 0, $batchsize)
|
||||
);
|
||||
if (count($rows)) {
|
||||
if (is_array($rows) && count($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$pastes[] = $row['dataid'];
|
||||
}
|
||||
@@ -459,7 +548,17 @@ class Database extends AbstractData
|
||||
private static function _exec($sql, array $params)
|
||||
{
|
||||
$statement = self::$_db->prepare($sql);
|
||||
$result = $statement->execute($params);
|
||||
foreach ($params as $key => &$parameter) {
|
||||
$position = $key + 1;
|
||||
if (is_int($parameter)) {
|
||||
$statement->bindParam($position, $parameter, PDO::PARAM_INT);
|
||||
} elseif (strlen($parameter) >= 4000) {
|
||||
$statement->bindParam($position, $parameter, PDO::PARAM_STR, strlen($parameter));
|
||||
} else {
|
||||
$statement->bindParam($position, $parameter);
|
||||
}
|
||||
}
|
||||
$result = $statement->execute();
|
||||
$statement->closeCursor();
|
||||
return $result;
|
||||
}
|
||||
@@ -479,10 +578,24 @@ class Database extends AbstractData
|
||||
{
|
||||
$statement = self::$_db->prepare($sql);
|
||||
$statement->execute($params);
|
||||
$result = $firstOnly ?
|
||||
$statement->fetch(PDO::FETCH_ASSOC) :
|
||||
$statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($firstOnly) {
|
||||
$result = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
} elseif (self::$_type === 'oci') {
|
||||
// workaround for https://bugs.php.net/bug.php?id=46728
|
||||
$result = array();
|
||||
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
|
||||
$result[] = array_map('self::_sanitizeClob', $row);
|
||||
}
|
||||
} else {
|
||||
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
$statement->closeCursor();
|
||||
if (self::$_type === 'oci' && is_array($result)) {
|
||||
// returned CLOB values are streams, convert these into strings
|
||||
$result = $firstOnly ?
|
||||
array_map('self::_sanitizeClob', $result) :
|
||||
$result;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -515,14 +628,15 @@ class Database extends AbstractData
|
||||
{
|
||||
switch ($type) {
|
||||
case 'ibm':
|
||||
$sql = 'SELECT tabname FROM SYSCAT.TABLES ';
|
||||
$sql = 'SELECT "tabname" FROM "SYSCAT"."TABLES"';
|
||||
break;
|
||||
case 'informix':
|
||||
$sql = 'SELECT tabname FROM systables ';
|
||||
$sql = 'SELECT "tabname" FROM "systables"';
|
||||
break;
|
||||
case 'mssql':
|
||||
$sql = 'SELECT name FROM sysobjects '
|
||||
. "WHERE type = 'U' ORDER BY name";
|
||||
// U: tables created by the user
|
||||
$sql = 'SELECT "name" FROM "sysobjects" '
|
||||
. 'WHERE "type" = \'U\' ORDER BY "name"';
|
||||
break;
|
||||
case 'mysql':
|
||||
$sql = 'SHOW TABLES';
|
||||
@@ -531,23 +645,23 @@ class Database extends AbstractData
|
||||
$sql = 'SELECT table_name FROM all_tables';
|
||||
break;
|
||||
case 'pgsql':
|
||||
$sql = 'SELECT c.relname AS table_name '
|
||||
. 'FROM pg_class c, pg_user u '
|
||||
. "WHERE c.relowner = u.usesysid AND c.relkind = 'r' "
|
||||
. 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) '
|
||||
. "AND c.relname !~ '^(pg_|sql_)' "
|
||||
$sql = 'SELECT c."relname" AS "table_name" '
|
||||
. 'FROM "pg_class" c, "pg_user" u '
|
||||
. 'WHERE c."relowner" = u."usesysid" AND c."relkind" = \'r\' '
|
||||
. 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") '
|
||||
. "AND c.\"relname\" !~ '^(pg_|sql_)' "
|
||||
. 'UNION '
|
||||
. 'SELECT c.relname AS table_name '
|
||||
. 'FROM pg_class c '
|
||||
. "WHERE c.relkind = 'r' "
|
||||
. 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) '
|
||||
. 'AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) '
|
||||
. "AND c.relname !~ '^pg_'";
|
||||
. 'SELECT c."relname" AS "table_name" '
|
||||
. 'FROM "pg_class" c '
|
||||
. "WHERE c.\"relkind\" = 'r' "
|
||||
. 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") '
|
||||
. 'AND NOT EXISTS (SELECT 1 FROM "pg_user" WHERE "usesysid" = c."relowner") '
|
||||
. "AND c.\"relname\" !~ '^pg_'";
|
||||
break;
|
||||
case 'sqlite':
|
||||
$sql = "SELECT name FROM sqlite_master WHERE type='table' "
|
||||
. 'UNION ALL SELECT name FROM sqlite_temp_master '
|
||||
. "WHERE type='table' ORDER BY name";
|
||||
$sql = 'SELECT "name" FROM "sqlite_master" WHERE "type"=\'table\' '
|
||||
. 'UNION ALL SELECT "name" FROM "sqlite_temp_master" '
|
||||
. 'WHERE "type"=\'table\' ORDER BY "name"';
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
@@ -563,16 +677,19 @@ class Database extends AbstractData
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $key
|
||||
* @throws PDOException
|
||||
* @return string
|
||||
*/
|
||||
private static function _getConfig($key)
|
||||
{
|
||||
$row = self::_select(
|
||||
'SELECT value FROM ' . self::_sanitizeIdentifier('config') .
|
||||
' WHERE id = ?', array($key), true
|
||||
);
|
||||
return $row['value'];
|
||||
try {
|
||||
$row = self::_select(
|
||||
'SELECT "value" FROM "' . self::_sanitizeIdentifier('config') .
|
||||
'" WHERE "id" = ?', array($key), true
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
return '';
|
||||
}
|
||||
return $row ? $row['value'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -586,10 +703,14 @@ class Database extends AbstractData
|
||||
private static function _getPrimaryKeyClauses($key = 'dataid')
|
||||
{
|
||||
$main_key = $after_key = '';
|
||||
if (self::$_type === 'mysql') {
|
||||
$after_key = ", PRIMARY KEY ($key)";
|
||||
} else {
|
||||
$main_key = ' PRIMARY KEY';
|
||||
switch (self::$_type) {
|
||||
case 'mysql':
|
||||
case 'oci':
|
||||
$after_key = ", PRIMARY KEY (\"$key\")";
|
||||
break;
|
||||
default:
|
||||
$main_key = ' PRIMARY KEY';
|
||||
break;
|
||||
}
|
||||
return array($main_key, $after_key);
|
||||
}
|
||||
@@ -597,7 +718,7 @@ class Database extends AbstractData
|
||||
/**
|
||||
* get the data type, depending on the database driver
|
||||
*
|
||||
* PostgreSQL uses a different API for BLOBs then SQL, hence we use TEXT
|
||||
* PostgreSQL and OCI uses a different API for BLOBs then SQL, hence we use TEXT and CLOB
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
@@ -605,13 +726,20 @@ class Database extends AbstractData
|
||||
*/
|
||||
private static function _getDataType()
|
||||
{
|
||||
return self::$_type === 'pgsql' ? 'TEXT' : 'BLOB';
|
||||
switch (self::$_type) {
|
||||
case 'oci':
|
||||
return 'CLOB';
|
||||
case 'pgsql':
|
||||
return 'TEXT';
|
||||
default:
|
||||
return 'BLOB';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the attachment type, depending on the database driver
|
||||
*
|
||||
* PostgreSQL uses a different API for BLOBs then SQL, hence we use TEXT
|
||||
* PostgreSQL and OCI use different APIs for BLOBs then SQL, hence we use TEXT and CLOB
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
@@ -619,7 +747,33 @@ class Database extends AbstractData
|
||||
*/
|
||||
private static function _getAttachmentType()
|
||||
{
|
||||
return self::$_type === 'pgsql' ? 'TEXT' : 'MEDIUMBLOB';
|
||||
switch (self::$_type) {
|
||||
case 'oci':
|
||||
return 'CLOB';
|
||||
case 'pgsql':
|
||||
return 'TEXT';
|
||||
default:
|
||||
return 'MEDIUMBLOB';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the meta type, depending on the database driver
|
||||
*
|
||||
* OCI doesn't accept TEXT so it has to be VARCHAR2(4000)
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
private static function _getMetaType()
|
||||
{
|
||||
switch (self::$_type) {
|
||||
case 'oci':
|
||||
return 'VARCHAR2(4000)';
|
||||
default:
|
||||
return 'TEXT';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,17 +787,18 @@ class Database extends AbstractData
|
||||
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
|
||||
$dataType = self::_getDataType();
|
||||
$attachmentType = self::_getAttachmentType();
|
||||
$metaType = self::_getMetaType();
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE ' . self::_sanitizeIdentifier('paste') . ' ( ' .
|
||||
"dataid CHAR(16) NOT NULL$main_key, " .
|
||||
"data $attachmentType, " .
|
||||
'postdate INT, ' .
|
||||
'expiredate INT, ' .
|
||||
'opendiscussion INT, ' .
|
||||
'burnafterreading INT, ' .
|
||||
'meta TEXT, ' .
|
||||
"attachment $attachmentType, " .
|
||||
"attachmentname $dataType$after_key );"
|
||||
'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' .
|
||||
"\"dataid\" CHAR(16) NOT NULL$main_key, " .
|
||||
"\"data\" $attachmentType, " .
|
||||
'"postdate" INT, ' .
|
||||
'"expiredate" INT, ' .
|
||||
'"opendiscussion" INT, ' .
|
||||
'"burnafterreading" INT, ' .
|
||||
"\"meta\" $metaType, " .
|
||||
"\"attachment\" $attachmentType, " .
|
||||
"\"attachmentname\" $dataType$after_key )"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -658,19 +813,35 @@ class Database extends AbstractData
|
||||
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
|
||||
$dataType = self::_getDataType();
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE ' . self::_sanitizeIdentifier('comment') . ' ( ' .
|
||||
"dataid CHAR(16) NOT NULL$main_key, " .
|
||||
'pasteid CHAR(16), ' .
|
||||
'parentid CHAR(16), ' .
|
||||
"data $dataType, " .
|
||||
"nickname $dataType, " .
|
||||
"vizhash $dataType, " .
|
||||
"postdate INT$after_key );"
|
||||
);
|
||||
self::$_db->exec(
|
||||
'CREATE INDEX IF NOT EXISTS comment_parent ON ' .
|
||||
self::_sanitizeIdentifier('comment') . '(pasteid);'
|
||||
'CREATE TABLE "' . self::_sanitizeIdentifier('comment') . '" ( ' .
|
||||
"\"dataid\" CHAR(16) NOT NULL$main_key, " .
|
||||
'"pasteid" CHAR(16), ' .
|
||||
'"parentid" CHAR(16), ' .
|
||||
"\"data\" $dataType, " .
|
||||
"\"nickname\" $dataType, " .
|
||||
"\"vizhash\" $dataType, " .
|
||||
"\"postdate\" INT$after_key )"
|
||||
);
|
||||
if (self::$_type === 'oci') {
|
||||
self::$_db->exec(
|
||||
'declare
|
||||
already_exists exception;
|
||||
columns_indexed exception;
|
||||
pragma exception_init( already_exists, -955 );
|
||||
pragma exception_init(columns_indexed, -1408);
|
||||
begin
|
||||
execute immediate \'create index "comment_parent" on "' . self::_sanitizeIdentifier('comment') . '" ("pasteid")\';
|
||||
exception
|
||||
when already_exists or columns_indexed then
|
||||
NULL;
|
||||
end;'
|
||||
);
|
||||
} else {
|
||||
self::$_db->exec(
|
||||
'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' .
|
||||
self::_sanitizeIdentifier('comment') . '" ("pasteid")'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -682,17 +853,37 @@ class Database extends AbstractData
|
||||
private static function _createConfigTable()
|
||||
{
|
||||
list($main_key, $after_key) = self::_getPrimaryKeyClauses('id');
|
||||
$charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)';
|
||||
$textType = self::_getMetaType();
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE ' . self::_sanitizeIdentifier('config') .
|
||||
" ( id CHAR(16) NOT NULL$main_key, value TEXT$after_key );"
|
||||
'CREATE TABLE "' . self::_sanitizeIdentifier('config') .
|
||||
"\" ( \"id\" $charType NOT NULL$main_key, \"value\" $textType$after_key )"
|
||||
);
|
||||
self::_exec(
|
||||
'INSERT INTO ' . self::_sanitizeIdentifier('config') .
|
||||
' VALUES(?,?)',
|
||||
'INSERT INTO "' . self::_sanitizeIdentifier('config') .
|
||||
'" VALUES(?,?)',
|
||||
array('VERSION', Controller::VERSION)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* sanitizes CLOB values used with OCI
|
||||
*
|
||||
* From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param int|string|resource $value
|
||||
* @return int|string
|
||||
*/
|
||||
public static function _sanitizeClob($value)
|
||||
{
|
||||
if (is_resource($value)) {
|
||||
$value = stream_get_contents($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* sanitizes identifiers
|
||||
*
|
||||
@@ -721,43 +912,46 @@ class Database extends AbstractData
|
||||
case '0.21':
|
||||
// create the meta column if necessary (pre 0.21 change)
|
||||
try {
|
||||
self::$_db->exec('SELECT meta FROM ' . self::_sanitizeIdentifier('paste') . ' LIMIT 1;');
|
||||
self::$_db->exec(
|
||||
'SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" ' .
|
||||
(self::$_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1')
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
self::$_db->exec('ALTER TABLE ' . self::_sanitizeIdentifier('paste') . ' ADD COLUMN meta TEXT;');
|
||||
self::$_db->exec('ALTER TABLE "' . self::_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT');
|
||||
}
|
||||
// SQLite only allows one ALTER statement at a time...
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE ' . self::_sanitizeIdentifier('paste') .
|
||||
" ADD COLUMN attachment $attachmentType;"
|
||||
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
|
||||
"\" ADD COLUMN \"attachment\" $attachmentType"
|
||||
);
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . " ADD COLUMN attachmentname $dataType;"
|
||||
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachmentname\" $dataType"
|
||||
);
|
||||
// SQLite doesn't support MODIFY, but it allows TEXT of similar
|
||||
// size as BLOB, so there is no need to change it there
|
||||
if (self::$_type !== 'sqlite') {
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE ' . self::_sanitizeIdentifier('paste') .
|
||||
" ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType;"
|
||||
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
|
||||
"\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType"
|
||||
);
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE ' . self::_sanitizeIdentifier('comment') .
|
||||
" ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType, " .
|
||||
"MODIFY COLUMN nickname $dataType, MODIFY COLUMN vizhash $dataType;"
|
||||
'ALTER TABLE "' . self::_sanitizeIdentifier('comment') .
|
||||
"\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType, " .
|
||||
"MODIFY COLUMN \"nickname\" $dataType, MODIFY COLUMN \"vizhash\" $dataType"
|
||||
);
|
||||
} else {
|
||||
self::$_db->exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS paste_dataid ON ' .
|
||||
self::_sanitizeIdentifier('paste') . '(dataid);'
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "paste_dataid" ON "' .
|
||||
self::_sanitizeIdentifier('paste') . '" ("dataid")'
|
||||
);
|
||||
self::$_db->exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS comment_dataid ON ' .
|
||||
self::_sanitizeIdentifier('comment') . '(dataid);'
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "comment_dataid" ON "' .
|
||||
self::_sanitizeIdentifier('comment') . '" ("dataid")'
|
||||
);
|
||||
}
|
||||
self::$_db->exec(
|
||||
'CREATE INDEX IF NOT EXISTS comment_parent ON ' .
|
||||
self::_sanitizeIdentifier('comment') . '(pasteid);'
|
||||
'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' .
|
||||
self::_sanitizeIdentifier('comment') . '" ("pasteid")'
|
||||
);
|
||||
// no break, continue with updates for 0.22 and later
|
||||
case '1.3':
|
||||
@@ -766,15 +960,15 @@ class Database extends AbstractData
|
||||
// to change it there
|
||||
if (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') {
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE ' . self::_sanitizeIdentifier('paste') .
|
||||
" MODIFY COLUMN data $attachmentType;"
|
||||
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
|
||||
"\" MODIFY COLUMN \"data\" $attachmentType"
|
||||
);
|
||||
}
|
||||
// no break, continue with updates for all newer versions
|
||||
default:
|
||||
self::_exec(
|
||||
'UPDATE ' . self::_sanitizeIdentifier('config') .
|
||||
' SET value = ? WHERE id = ?',
|
||||
'UPDATE "' . self::_sanitizeIdentifier('config') .
|
||||
'" SET "value" = ? WHERE "id" = ?',
|
||||
array(Controller::VERSION, 'VERSION')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -195,7 +195,7 @@ class I18n
|
||||
if (count(self::$_availableLanguages) == 0) {
|
||||
$i18n = dir(self::_getPath());
|
||||
while (false !== ($file = $i18n->read())) {
|
||||
if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1) {
|
||||
if (preg_match('/^([a-z]{2,3}).json$/', $file, $match) === 1) {
|
||||
self::$_availableLanguages[] = $match[1];
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -324,6 +324,7 @@ class I18n
|
||||
case 'he':
|
||||
return $n === 1 ? 0 : ($n === 2 ? 1 : (($n < 0 || $n > 10) && ($n % 10 === 0) ? 2 : 3));
|
||||
case 'id':
|
||||
case 'jbo':
|
||||
return 0;
|
||||
case 'lt':
|
||||
return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (($n % 10 >= 2 && $n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
|
||||
@@ -334,7 +335,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'];
|
||||
if ($pl + self::$_limit >= $now) {
|
||||
return false;
|
||||
}
|
||||
$pl = (int) self::$_store->getValue('purge_limiter');
|
||||
if ($pl + self::$_limit >= $now) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = '<?php' . PHP_EOL . '$GLOBALS[\'purge_limiter\'] = ' . $now . ';';
|
||||
self::_store($file, $content);
|
||||
return true;
|
||||
$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');
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
// purge file of expired hashes to keep it small
|
||||
$now = time();
|
||||
foreach ($tl as $key => $time) {
|
||||
if ($time + self::$_limit < $now) {
|
||||
unset($tl[$key]);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
$result = true;
|
||||
$tl = time();
|
||||
$result = true;
|
||||
}
|
||||
if (!self::$_store->setValue((string) $tl, 'traffic_limiter', $hash)) {
|
||||
error_log('failed to store the traffic limiter, it probably contains outdated information');
|
||||
}
|
||||
self::_store(
|
||||
$file,
|
||||
'<?php' . PHP_EOL .
|
||||
'$GLOBALS[\'traffic_limiter\'] = ' . var_export($tl, true) . ';'
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,9 @@ class Request
|
||||
case 'DELETE':
|
||||
case 'PUT':
|
||||
case 'POST':
|
||||
$this->_params = Json::decode(
|
||||
// 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)
|
||||
);
|
||||
break;
|
||||
@@ -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) {
|
||||
|
||||
@@ -41,7 +41,7 @@ if ($SYNTAXHIGHLIGHTING) :
|
||||
endif;
|
||||
?>
|
||||
<noscript><link type="text/css" rel="stylesheet" href="css/noscript.css" /></noscript>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.4.1.js" integrity="sha512-bnIvzh6FU75ZKxp0GXLH9bewza/OIw6dLVh9ICg0gogclmYGguQJWl8U30WpbsGTqbIiAwxTsbe76DErLq5EDQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.6.0.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
if ($QRCODE) :
|
||||
?>
|
||||
@@ -55,7 +55,7 @@ if ($ZEROBINCOMPATIBILITY) :
|
||||
endif;
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/zlib-1.2.11.js" integrity="sha512-Yey/0yoaVmSbqMEyyff3DIu8kCPwpHvHf7tY1AuZ1lrX9NPCMg87PwzngMi+VNbe4ilCApmePeuKT869RTcyCQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/base-x-3.0.7.js" integrity="sha512-/Bi1AJIP0TtxEB+Jh6Hk809H1G7vn4iJV80qagslf0+Hm0UjUi1s3qNrn1kZULjzUYuaf6ck0ndLGJ7MxWLmgQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/base-x-4.0.0.js" integrity="sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/rawinflate-0.3.js" integrity="sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/bootstrap-3.3.7.js" integrity="sha512-iztkobsvnjKfAtTNdHkGVjAYTrrtlC7mGp/54c40wowO7LhURYl3gVzzcEqGl/qKXQltJ2HwMrdLcNUdo+N/RQ==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
@@ -66,13 +66,13 @@ if ($SYNTAXHIGHLIGHTING) :
|
||||
endif;
|
||||
if ($MARKDOWN) :
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-1.9.1.js" integrity="sha512-nRri7kqh3iRLdHbhtjfe8w9eAQPmt+ubH5U88UZyKbz6O9Q0q4haaXF0krOUclKmRJou/kKZYulgBHvHXPqOvg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.0.js" integrity="sha512-UB9jpMTOJLSnVzePuqlSGT34G70wEGqtIWabMeAh+Drnj4/uQ8rFkFn1zkN9vkWp/7nA51U2LmP23H5MJvBXsw==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
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/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" 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-PTOcxIWIPWCnb5vC4fmQDMqYGerwsu3AndVyPxn9NlQffIWYMPf/p28Z9SIygXsmcYjmTRmUiW5y7df63mNTfg==" 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>
|
||||
|
||||
11
tpl/page.php
11
tpl/page.php
@@ -20,7 +20,7 @@ if ($SYNTAXHIGHLIGHTING):
|
||||
endif;
|
||||
endif;
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.4.1.js" integrity="sha512-bnIvzh6FU75ZKxp0GXLH9bewza/OIw6dLVh9ICg0gogclmYGguQJWl8U30WpbsGTqbIiAwxTsbe76DErLq5EDQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.6.0.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
if ($QRCODE):
|
||||
?>
|
||||
@@ -34,7 +34,7 @@ if ($ZEROBINCOMPATIBILITY):
|
||||
endif;
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/zlib-1.2.11.js" integrity="sha512-Yey/0yoaVmSbqMEyyff3DIu8kCPwpHvHf7tY1AuZ1lrX9NPCMg87PwzngMi+VNbe4ilCApmePeuKT869RTcyCQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/base-x-3.0.7.js" integrity="sha512-/Bi1AJIP0TtxEB+Jh6Hk809H1G7vn4iJV80qagslf0+Hm0UjUi1s3qNrn1kZULjzUYuaf6ck0ndLGJ7MxWLmgQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/base-x-4.0.0.js" integrity="sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/rawinflate-0.3.js" integrity="sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
if ($SYNTAXHIGHLIGHTING):
|
||||
@@ -44,13 +44,13 @@ if ($SYNTAXHIGHLIGHTING):
|
||||
endif;
|
||||
if ($MARKDOWN):
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-1.9.1.js" integrity="sha512-nRri7kqh3iRLdHbhtjfe8w9eAQPmt+ubH5U88UZyKbz6O9Q0q4haaXF0krOUclKmRJou/kKZYulgBHvHXPqOvg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.0.js" integrity="sha512-UB9jpMTOJLSnVzePuqlSGT34G70wEGqtIWabMeAh+Drnj4/uQ8rFkFn1zkN9vkWp/7nA51U2LmP23H5MJvBXsw==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
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/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" 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-PTOcxIWIPWCnb5vC4fmQDMqYGerwsu3AndVyPxn9NlQffIWYMPf/p28Z9SIygXsmcYjmTRmUiW5y7df63mNTfg==" 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);
|
||||
$example['meta'] = array('expire' => $example['meta']['expire']);
|
||||
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);
|
||||
@@ -331,4 +388,16 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertEquals(Controller::VERSION, $result['value']);
|
||||
Helper::rmDir($this->_path);
|
||||
}
|
||||
|
||||
public function testOciClob()
|
||||
{
|
||||
$int = (int) random_bytes(1);
|
||||
$string = random_bytes(10);
|
||||
$clob = fopen('php://memory', 'r+');
|
||||
fwrite($clob, $string);
|
||||
rewind($clob);
|
||||
$this->assertEquals($int, Database::_sanitizeClob($int));
|
||||
$this->assertEquals($string, Database::_sanitizeClob($string));
|
||||
$this->assertEquals($string, Database::_sanitizeClob($clob));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ class I18nTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = '*';
|
||||
I18n::loadTranslations();
|
||||
$this->assertTrue(strlen(I18n::_('en')) == 2, 'browser language any');
|
||||
$this->assertTrue(strlen(I18n::_('en')) >= 2, 'browser language any');
|
||||
}
|
||||
|
||||
public function testVariableInjection()
|
||||
|
||||
@@ -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()
|
||||
|
||||
18
vendor/composer/autoload_classmap.php
vendored
18
vendor/composer/autoload_classmap.php
vendored
@@ -6,6 +6,22 @@ $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\\ParseStringFlag' => $vendorDir . '/mlocati/ip-lib/src/ParseStringFlag.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\\RangesFromBoundaryCalculator' => $vendorDir . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
|
||||
'IPLib\\Service\\UnsignedIntegerMath' => $vendorDir . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
|
||||
'Identicon\\Generator\\BaseGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php',
|
||||
'Identicon\\Generator\\GdGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/GdGenerator.php',
|
||||
'Identicon\\Generator\\GeneratorInterface' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/GeneratorInterface.php',
|
||||
@@ -17,6 +33,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',
|
||||
@@ -26,7 +43,6 @@ return array(
|
||||
'PrivateBin\\Model\\Comment' => $baseDir . '/lib/Model/Comment.php',
|
||||
'PrivateBin\\Model\\Paste' => $baseDir . '/lib/Model/Paste.php',
|
||||
'PrivateBin\\Persistence\\AbstractPersistence' => $baseDir . '/lib/Persistence/AbstractPersistence.php',
|
||||
'PrivateBin\\Persistence\\DataStore' => $baseDir . '/lib/Persistence/DataStore.php',
|
||||
'PrivateBin\\Persistence\\PurgeLimiter' => $baseDir . '/lib/Persistence/PurgeLimiter.php',
|
||||
'PrivateBin\\Persistence\\ServerSalt' => $baseDir . '/lib/Persistence/ServerSalt.php',
|
||||
'PrivateBin\\Persistence\\TrafficLimiter' => $baseDir . '/lib/Persistence/TrafficLimiter.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'),
|
||||
);
|
||||
|
||||
23
vendor/composer/autoload_static.php
vendored
23
vendor/composer/autoload_static.php
vendored
@@ -18,6 +18,7 @@ class ComposerStaticInitDontChange
|
||||
'I' =>
|
||||
array (
|
||||
'Identicon\\' => 10,
|
||||
'IPLib\\' => 6,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -30,9 +31,29 @@ 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\\ParseStringFlag' => __DIR__ . '/..' . '/mlocati/ip-lib/src/ParseStringFlag.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\\RangesFromBoundaryCalculator' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
|
||||
'IPLib\\Service\\UnsignedIntegerMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
|
||||
'Identicon\\Generator\\BaseGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php',
|
||||
'Identicon\\Generator\\GdGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/GdGenerator.php',
|
||||
'Identicon\\Generator\\GeneratorInterface' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/GeneratorInterface.php',
|
||||
@@ -44,6 +65,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',
|
||||
@@ -53,7 +75,6 @@ class ComposerStaticInitDontChange
|
||||
'PrivateBin\\Model\\Comment' => __DIR__ . '/../..' . '/lib/Model/Comment.php',
|
||||
'PrivateBin\\Model\\Paste' => __DIR__ . '/../..' . '/lib/Model/Paste.php',
|
||||
'PrivateBin\\Persistence\\AbstractPersistence' => __DIR__ . '/../..' . '/lib/Persistence/AbstractPersistence.php',
|
||||
'PrivateBin\\Persistence\\DataStore' => __DIR__ . '/../..' . '/lib/Persistence/DataStore.php',
|
||||
'PrivateBin\\Persistence\\PurgeLimiter' => __DIR__ . '/../..' . '/lib/Persistence/PurgeLimiter.php',
|
||||
'PrivateBin\\Persistence\\ServerSalt' => __DIR__ . '/../..' . '/lib/Persistence/ServerSalt.php',
|
||||
'PrivateBin\\Persistence\\TrafficLimiter' => __DIR__ . '/../..' . '/lib/Persistence/TrafficLimiter.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;
|
||||
}
|
||||
}
|
||||
);
|
||||
156
vendor/mlocati/ip-lib/src/Address/AddressInterface.php
vendored
Normal file
156
vendor/mlocati/ip-lib/src/Address/AddressInterface.php
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
<?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
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public static function getDefaultReservedRangeType();
|
||||
|
||||
/**
|
||||
* Get the RFC reserved ranges (except the ones of type getDefaultReservedRangeType).
|
||||
*
|
||||
* @return \IPLib\Address\AssignedRange[] ranges are sorted
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
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 at a certain distance from this address.
|
||||
*
|
||||
* @param int $n the distance of the address (can be negative)
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface|null return NULL if $n is not an integer or if the final address would be invalid
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @example passing 1 to the address 127.0.0.1 will result in 127.0.0.2
|
||||
* @example passing -1 to the address 127.0.0.1 will result in 127.0.0.0
|
||||
* @example passing -1 to the address 0.0.0.0 will result in NULL
|
||||
*/
|
||||
public function getAddressAtOffset($n);
|
||||
|
||||
/**
|
||||
* Get the address right after this IP address (if available).
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface|null
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getAddressAtOffset()
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public function getNextAddress();
|
||||
|
||||
/**
|
||||
* Get the address right before this IP address (if available).
|
||||
*
|
||||
* @return \IPLib\Address\AddressInterface|null
|
||||
*
|
||||
* @see \IPLib\Address\AddressInterface::getAddressAtOffset()
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public function getPreviousAddress();
|
||||
|
||||
/**
|
||||
* Get the Reverse DNS Lookup Address of this IP address.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.12.0
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user