diff --git a/.gitattributes b/.gitattributes index ef060615..60629c04 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..7a0b263a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,49 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# Currently can only check JS. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '28 22 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/refresh-php8.yml b/.github/workflows/refresh-php8.yml new file mode 100644 index 00000000..5160e227 --- /dev/null +++ b/.github/workflows/refresh-php8.yml @@ -0,0 +1,28 @@ +name: Refresh PHP 8 branch + +on: + push: + branches: [ master ] + schedule: + - cron: '42 2 * * *' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Checkout php8 branch + run: git checkout php8 + + - name: Merge master changes into php8 + run: git merge master + + - name: Push new changes + uses: github-actions-x/commit@v2.8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + push-branch: 'php8' + diff --git a/.github/workflows/snyk-scan.yml b/.github/workflows/snyk-scan.yml new file mode 100644 index 00000000..4730eec0 --- /dev/null +++ b/.github/workflows/snyk-scan.yml @@ -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 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e4cd7417..b11913d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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: composer install --prefer-dist --no-progress --no-suggest + run: composer install --prefer-dist --no-dev + PHPunit: runs-on: ubuntu-latest strategy: matrix: php-versions: ['7.3', '7.4', '8.0'] 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 diff --git a/.gitignore b/.gitignore index a4cd2bb6..65ef7189 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 00000000..cf69fbd8 --- /dev/null +++ b/.scrutinizer.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f566937..d964a57a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,36 @@ # PrivateBin version history * **1.4 (not yet released)** - * CHANGED: Upgrading libraries to: DOMpurify 2.0.14 + * ADDED: Translation for Estonian + * ADDED: new HTTP headers improving security (#765) + * ADDED: Download button for paste text (#774) + * ADDED: Opt-out of federated learning of cohorts (FLoC) (#776) + * ADDED: Configuration option to exempt IPs from the rate-limiter (#787) + * ADDED: Google Cloud Storage backend support (#795) + * CHANGED: Language selection cookie only transmitted over HTTPS (#472) + * CHANGED: Upgrading libraries to: random_compat 2.0.20 + * CHANGED: Removed automatic `.ini` configuration file migration (#808) + * CHANGED: Removed configurable `dir` for `traffic` & `purge` limiters (#419) + * CHANGED: Server salt, traffic and purge limiter now stored in the storage backend (#419) + * **1.3.5 (2021-04-05)** + * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan + * ADDED: Make the project info configurable (#681) + * 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) + * FIXED: PDF display in Firefox (#630) + * FIXED: Allow pasting into password input dialog (#630) + * FIXED: Display of expiration date in email (#630) + * FIXED: Allow display of durations in weeks (#630) + * FIXED: Avoid exposing burn-after-reading messages from cache (#630) + * FIXED: Only display the dropzone when it should (#630) + * FIXED: Detect delete token properly (#630) + * FIXED: Sanitize output from `Helper.urls2links()` (#630) + * FIXED: Avoid recreation of existing pasteurl element when calling URL shortener (#630) + * FIXED: Downloads in Chrome >= 83 (#634) + * FIXED: Display of empty files (#663) + * FIXED: Improve OpenGraph attributes (#651) + * FIXED: Reset to configured burn-after-reading, discussion and expiration settings (#682) + * FIXED: Italic segment of project information (#756) * **1.3.4 (2020-03-22)** * CHANGED: Minimum required PHP version is 5.6, due to a change in the identicon library and to use php's native hash_equals() * CHANGED: Upgrading libraries to: identicon 2.0.0 diff --git a/CREDITS.md b/CREDITS.md index fec83fa0..612749c0 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -13,7 +13,7 @@ Sébastien Sauvage - original idea and main developer * Alexey Gladkov - syntax highlighting * Greg Knaddison - robots.txt * MrKooky - HTML5 markup, CSS cleanup -* Simon Rupf - WebCrypto, unit tests, current docker containers, MVC, configuration, i18n +* Simon Rupf - WebCrypto, unit tests, containers images, database backend, MVC, configuration, i18n * Hexalyse - Password protection * Viktor Stanchev - File upload support * azlux - Tab character input support @@ -27,6 +27,8 @@ Sébastien Sauvage - original idea and main developer * Harald Leithner - base58 encoding of key * Haocen - lots of bugfixes and UI improvements * Lucas Savva - configurable config file location, NixOS packaging +* rodehoed - option to exempt ips from the rate-limiter +* Mark van Holsteijn - Google Cloud Storage backend ## Translations * Hexalyse - French @@ -45,4 +47,9 @@ Sébastien Sauvage - original idea and main developer * Péter Tabajdi - Hungarian * info-path - Czech * BigWax - Bulgarian -* AndriiZ - Ukrainian \ No newline at end of file +* AndriiZ - Ukrainian +* Yaron Shahrabani - Hebrew +* Moo - Lithuanian +* whenwesober - Indonesian +* retiolus - Catalan +* sarnane - Estonian diff --git a/INSTALL.md b/INSTALL.md index 93a12843..0e728237 100644 --- a/INSTALL.md +++ b/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 @@ -9,21 +9,21 @@ options](#configuration) to adjust as you see fit. **NOTE:** See [our FAQ](https://github.com/PrivateBin/PrivateBin/wiki/FAQ#how-can-i-securely-clonedownload-your-project) for information how to securely download the PrivateBin release files. +**NOTE:** There is a [ansible](https://ansible.com) role by @e1mo available to install and configure PrivateBin on your server. It's available on [ansible galaxy](https://galaxy.ansible.com/e1mo/privatebin) ([source code](https://git.sr.ht/~e1mo/ansible-role-privatebin)). + ### Minimal requirements -- PHP version 5.5 or above -- _one_ of the following sources of cryptographically safe randomness is required: - - PHP 7 or higher - - [Libsodium](https://download.libsodium.org/libsodium/content/installation/) and it's [PHP extension](https://paragonie.com/book/pecl-libsodium/read/00-intro.md#installing-libsodium) - - open_basedir access to `/dev/urandom` - - mcrypt extension - - com_dotnet extension - - Mcrypt needs to be able to access `/dev/urandom`. This means if `open_basedir` is set, it must include this file. +- PHP version 7.0 or above + - Or PHP version 5.6 AND _one_ of the following sources of cryptographically safe randomness: + - [Libsodium](https://download.libsodium.org/libsodium/content/installation/) and it's [PHP extension](https://paragonie.com/book/pecl-libsodium/read/00-intro.md#installing-libsodium) + - open_basedir access to `/dev/urandom` + - mcrypt extension (mcrypt needs to be able to access `/dev/urandom`. This means if `open_basedir` is set, it must include this file.) + - com_dotnet extension - GD extension -- some disk space or (optionally) a database supported by [PDO](https://secure.php.net/manual/book.pdo.php) +- zlib extension +- some disk space or (optionally) a database supported by [PDO](https://php.net/manual/book.pdo.php) - ability to create files and folders in the installation directory and the PATH defined in index.php -- A web browser with javascript support +- A web browser with JavaScript support ## Hardening and security @@ -74,7 +74,7 @@ Note that your PHP process will need read access to the config wherever it may b ### Transport security When setting up PrivateBin, also set up HTTPS, if you haven't already. Without HTTPS -PrivateBin is not secure, as the javascript files could be manipulated during transmission. +PrivateBin is not secure, as the JavaScript files could be manipulated during transmission. For more information on this, see our [FAQ entry on HTTPS setup](https://github.com/PrivateBin/PrivateBin/wiki/FAQ#how-should-i-setup-https). ### File-level permissions @@ -187,7 +187,24 @@ CREATE INDEX parent ON prefix_comment(pasteid); CREATE TABLE prefix_config ( id CHAR(16) NOT NULL, value TEXT, PRIMARY KEY (id) ); -INSERT INTO prefix_config VALUES('VERSION', '1.3.4'); +INSERT INTO prefix_config VALUES('VERSION', '1.3.5'); ``` -In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to be TEXT and not BLOB or MEDIUMBLOB. +In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to +be TEXT and not BLOB or MEDIUMBLOB. + +### Using Google Cloud Storage +If you want to deploy PrivateBin in a serverless manner in the Google Cloud, you +can choose the `GoogleCloudStorage` as backend. To use this backend, you create +a GCS bucket and specify the name as the model option `bucket`. Alternatively, +you can set the name through the environment variable PASTEBIN_GCS_BUCKET. + +The default prefix for pastes stored in the bucket is `pastes`. To change the +prefix, specify the option `prefix`. + +Google Cloud Storage buckets may be significantly slower than a `FileSystem` or +`Database` backend. The big advantage is that the deployment on Google Cloud +Platform using Google Cloud Run is easy and cheap. + +To use the Google Cloud Storage backend you have to install the suggested +library using the command `composer require google/cloud-storage`. diff --git a/Makefile b/Makefile index ca176d39..83a6b6de 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,16 @@ .PHONY: all coverage coverage-js coverage-php doc doc-js doc-php increment sign test test-js test-php help -CURRENT_VERSION = 1.3.4 -VERSION ?= 1.3.5 -VERSION_FILES = index.php cfg/ *.md css/ i18n/ img/ js/privatebin.js lib/ Makefile tpl/ tst/ +CURRENT_VERSION = 1.3.5 +VERSION ?= 1.3.6 +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") all: coverage doc ## Equivalent to running `make coverage doc`. +composer: ## Update composer dependencies (only production ones, optimize the autoloader) + composer update --no-dev --optimize-autoloader + coverage: coverage-js coverage-php ## Run all unit tests and generate code coverage reports. coverage-js: ## Run JS unit tests and generate code coverage reports. @@ -15,7 +18,7 @@ coverage-js: ## Run JS unit tests and generate code coverage reports. coverage-php: ## Run PHP unit tests and generate code coverage reports. cd tst && phpunit 2> /dev/null - cd log/php-coverage-report && sed -i "s#$(CURDIR)##g" *.html */*.html + cd tst/log/php-coverage-report && sed -i "s#$(CURDIR)##g" *.html */*.html doc: doc-js doc-php ## Generate all code documentation. diff --git a/README.md b/README.md index d35035f0..7d39952c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [![PrivateBin](https://cdn.rawgit.com/PrivateBin/assets/master/images/preview/logoSmall.png)](https://privatebin.info/) -*Current version: 1.3.4* +*Current version: 1.3.5* **PrivateBin** is a minimalist, open source online [pastebin](https://en.wikipedia.org/wiki/Pastebin) where the server has zero knowledge of pasted data. @@ -37,9 +37,7 @@ without losing any data. Otherwise you would also have to trust your internet provider, and any country the traffic passes through. Additionally the instance should be secured by - [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) and - ideally by [HPKP](https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning) using a - certificate. It can use traditional certificate authorities and/or use + [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security). It can use traditional certificate authorities and/or use [DNSSEC](https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions) protected [DANE](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities) diff --git a/SECURITY.md b/SECURITY.md index 1a5bf963..e00398b9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,8 +4,8 @@ | Version | Supported | | ------- | ------------------ | -| 1.3.4 | :heavy_check_mark: | -| < 1.3.4 | :x: | +| 1.3.5 | :heavy_check_mark: | +| < 1.3.5 | :x: | ## Reporting a Vulnerability diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index 6b9295e4..7194ee57 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -39,6 +39,10 @@ sizelimit = 10485760 ; template to include, default is "bootstrap" (tpl/bootstrap.php) template = "bootstrap" +; (optional) info text to display +; use single, instead of double quotes for HTML attributes +;info = "More information on the project page." + ; (optional) notice to display ; notice = "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service." @@ -83,7 +87,7 @@ languageselection = false ; async functions and display an error if not and for Chrome to enable ; webassembly support (used for zlib compression). You can remove it if Chrome ; doesn't need to be supported and old browsers don't need to be warned. -; cspheader = "default-src 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval' resource:; style-src 'self'; font-src 'self'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads" +; cspheader = "default-src 'none'; base-uri 'self'; form-action 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval' resource:; style-src 'self'; font-src 'self'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads" ; stay compatible with PrivateBin Alpha 0.19, less secure ; if enabled will use base64.js version 1.7 instead of 2.1.9 and sha1 instead of @@ -131,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 @@ -149,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 @@ -159,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 diff --git a/codacy-analysis.yml b/codacy-analysis.yml new file mode 100644 index 00000000..9850708b --- /dev/null +++ b/codacy-analysis.yml @@ -0,0 +1,49 @@ +# This workflow checks out code, performs a Codacy security scan +# and integrates the results with the +# GitHub Advanced Security code scanning feature. For more information on +# the Codacy security scan action usage and parameters, see +# https://github.com/codacy/codacy-analysis-cli-action. +# For more information on Codacy Analysis CLI in general, see +# https://github.com/codacy/codacy-analysis-cli. + +name: Codacy Security Scan + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '45 16 * * 1' + +jobs: + codacy-security-scan: + name: Codacy Security Scan + runs-on: ubuntu-latest + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout code + uses: actions/checkout@v2 + + # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis + - name: Run Codacy Analysis CLI + uses: codacy/codacy-analysis-cli-action@1.1.0 + with: + # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository + # You can also omit the token and run the tools that support default configurations + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + verbose: true + output: results.sarif + format: sarif + # Adjust severity of non-security issues + gh-code-scanning-compat: true + # Force 0 exit code to allow SARIF file generation + # This will handover control about PR rejection to the GitHub side + max-allowed-issues: 2147483647 + + # Upload the SARIF file generated in the previous step + - name: Upload SARIF results file + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: results.sarif diff --git a/composer.json b/composer.json index 88640d50..92816655 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,11 @@ "require" : { "php" : "^5.6.0 || ^7.0 || ^8.0", "paragonie/random_compat" : "<9.99", - "yzalis/identicon" : "^2.0.0" + "yzalis/identicon" : "^2.0.0", + "mlocati/ip-lib" : "1.14.0" + }, + "suggest" : { + "google/cloud-storage" : "1.23.1" }, "require-dev" : { "phpunit/phpunit" : "^9" @@ -39,4 +43,4 @@ "config" : { "autoloader-suffix" : "DontChange" } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index fa0c7b51..5bb6a15e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,88 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f8f346748370d0efd672270571c4aa74", + "content-hash": "217f0ba9bdac1014a332a8ba390be949", "packages": [ { - "name": "paragonie/random_compat", - "version": "v2.0.18", + "name": "mlocati/ip-lib", + "version": "1.14.0", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db" + "url": "https://github.com/mlocati/ip-lib.git", + "reference": "882bc0e115970a536b13bcfa59f312783fce08c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/882bc0e115970a536b13bcfa59f312783fce08c8", + "reference": "882bc0e115970a536b13bcfa59f312783fce08c8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "ext-pdo_sqlite": "*", + "phpunit/dbunit": "^1.4 || ^2 || ^3 || ^4", + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "IPLib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michele Locati", + "email": "mlocati@gmail.com", + "homepage": "https://github.com/mlocati", + "role": "Author" + } + ], + "description": "Handle IPv4, IPv6 addresses and ranges", + "homepage": "https://github.com/mlocati/ip-lib", + "keywords": [ + "IP", + "address", + "addresses", + "ipv4", + "ipv6", + "manage", + "managing", + "matching", + "network", + "networking", + "range", + "subnet" + ], + "funding": [ + { + "url": "https://github.com/sponsors/mlocati", + "type": "github" + }, + { + "url": "https://paypal.me/mlocati", + "type": "other" + } + ], + "time": "2020-12-31T11:30:02+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.20", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a", + "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a", "shasum": "" }, "require": { @@ -53,7 +121,7 @@ "pseudorandom", "random" ], - "time": "2019-01-03T20:59:08+00:00" + "time": "2021-04-17T09:33:01+00:00" }, { "name": "yzalis/identicon", @@ -105,42 +173,38 @@ "identicon", "image" ], + "abandoned": true, "time": "2019-10-14T09:30:57+00:00" } ], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", - "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", + "doctrine/coding-standard": "^8.0", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-shim": "^0.11", - "phpunit/phpunit": "^7.0" + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" @@ -154,7 +218,7 @@ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "homepage": "https://ocramius.github.io/" } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", @@ -177,20 +241,20 @@ "type": "tidelift" } ], - "time": "2020-05-29T17:27:14+00:00" + "time": "2020-11-10T18:47:58+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.1", + "version": "1.10.2", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", - "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", "shasum": "" }, "require": { @@ -1088,15 +1152,15 @@ "role": "lead" } ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-02T14:47:54+00:00" + "time": "2020-11-30T08:15:22+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1896,20 +1960,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.18.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", - "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-ctype": "For best performance" @@ -1917,7 +1981,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1968,20 +2032,20 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.2.0", + "name": "symfony/yaml", + "version": "v4.4.24", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "url": "https://github.com/symfony/yaml.git", + "reference": "8b6d1b97521e2f125039b3fcb4747584c6dfa0ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8b6d1b97521e2f125039b3fcb4747584c6dfa0ef", + "reference": "8b6d1b97521e2f125039b3fcb4747584c6dfa0ef", "shasum": "" }, "require": { @@ -2014,34 +2078,55 @@ "type": "github" } ], - "time": "2020-07-12T23:59:07+00:00" + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-16T09:52:47+00:00" }, { "name": "webmozart/assert", - "version": "1.9.1", + "version": "1.10.0", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", + "php": "^7.2 || ^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" + "vimeo/psalm": "<4.6.1 || 4.6.2" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "phpunit/phpunit": "^8.5.13" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -2063,7 +2148,7 @@ "check", "validate" ], - "time": "2020-07-08T17:02:28+00:00" + "time": "2021-03-09T10:59:23+00:00" } ], "aliases": [], diff --git a/css/bootstrap/privatebin.css b/css/bootstrap/privatebin.css index 72e420ed..de8b6797 100644 --- a/css/bootstrap/privatebin.css +++ b/css/bootstrap/privatebin.css @@ -6,7 +6,7 @@ * @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.4 + * @version 1.3.5 */ body { diff --git a/css/noscript.css b/css/noscript.css index e44670f0..f42e419f 100644 --- a/css/noscript.css +++ b/css/noscript.css @@ -6,7 +6,7 @@ * @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.4 + * @version 1.3.5 */ /* When there is no script at all other */ diff --git a/css/privatebin.css b/css/privatebin.css index a3ab5ea6..f852d396 100644 --- a/css/privatebin.css +++ b/css/privatebin.css @@ -6,7 +6,7 @@ * @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.4 + * @version 1.3.5 */ /* CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved. @@ -249,6 +249,10 @@ button img { padding: 1px 0 1px 0; } +#downloadtextbutton img { + padding: 1px 0 1px 0; +} + #remainingtime, #password { color: #94a3b4; display: inline; diff --git a/i18n/ar.json b/i18n/ar.json index 482d6706..ddca0d62 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", + "%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 project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "ar", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/bg.json b/i18n/bg.json index 7d536ed8..4a025389 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s е изчистен и изцяло достъпен като отворен код, онлайн \"paste\" услуга, където сървъра не знае подадената информация. Тя се шифрова/дешифрова във браузъра използвайки 256 битов 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.": "%s е изчистен и изцяло достъпен като отворен код, онлайн \"paste\" услуга, където сървъра не знае подадената информация. Тя се шифрова/дешифрова %sвъв браузъра%s използвайки 256 битов AES алгоритъм.", + "More information on the project page.": "Повече информация може да намерите на страницата на проекта (Английски).", "Because ignorance is bliss": "Невежеството е блаженство", "en": "bg", "Paste does not exist, has expired or has been deleted.": "Информацията не съществува, срокът и е изтекъл или е била изтрита.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/ca.json b/i18n/ca.json new file mode 100644 index 00000000..ef6b348d --- /dev/null +++ b/i18n/ca.json @@ -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 és un pastebin en línia de codi obert i minimalista on el servidor no té coneixement de les dades enganxades. Les dades estan encriptades/desxifrades %sen el navegador%s utilitzant AES de 256 bits.", + "More information on the project page.": "Més informació a la pàgina del projecte.", + "Because ignorance is bliss": "Perquè la ignorància és felicitat", + "en": "ca", + "Paste does not exist, has expired or has been deleted.": "El paste no existeix, ha caducat o s'ha eliminat.", + "%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.": [ + "Espereu %d segon entre cada entrada. (singular)", + "Espereu %d segons entre cada entrada. (1r plural)", + "Espereu %d segons entre cada entrada. (2n plural)", + "Please wait %d seconds between each post. (3er plural)" + ], + "Paste is limited to %s of encrypted data.": "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.": "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 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 %s (Hit [Ctrl]+[c] to copy)": "Your paste is %s (Hit [Ctrl]+[c] to copy)", + "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 this FAQ for information to troubleshoot.": "In case this message never disappears please have a look at this FAQ for information to troubleshoot.", + "+++ no paste text +++": "+++ no paste 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 see this FAQ entry.": "For more information see this FAQ entry.", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.", + "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.", + "Save paste": "Save paste" +} diff --git a/i18n/cs.json b/i18n/cs.json index e5540fc5..34ef19a8 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s je minimalistický open source 'pastebin' server, který neanalyzuje vložená data. Data jsou šifrována v prohlížeči pomocí 256 bitů AES. Více informací na stránce projetu.", + "%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 je minimalistický open source 'pastebin' server, který neanalyzuje vložená data. Data jsou šifrována %sv prohlížeči%s pomocí 256 bitů AES.", + "More information on the project page.": "Více informací na stránce projetu.", "Because ignorance is bliss": "Protože nevědomost je sladká", "en": "cs", "Paste does not exist, has expired or has been deleted.": "Vložený text neexistuje, expiroval nebo byl odstraněn.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/de.json b/i18n/de.json index b40d2911..7bc38d14 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s ist ein minimalistischer, quelloffener \"Pastebin\"-artiger Dienst, bei dem der Server keinerlei Kenntnis der Inhalte hat. Die Daten werden im Browser mit 256 Bit AES ver- und entschlüsselt. Weitere Informationen sind auf der Projektseite zu finden.", + "%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 ist ein minimalistischer, quelloffener \"Pastebin\"-artiger Dienst, bei dem der Server keinerlei Kenntnis der Inhalte hat. Die Daten werden %sim Browser%s mit 256 Bit AES ver- und entschlüsselt.", + "More information on the project page.": "Weitere Informationen sind auf der Projektseite zu finden.", "Because ignorance is bliss": "Unwissenheit ist ein Segen", "en": "de", "Paste does not exist, has expired or has been deleted.": "Diesen Text gibt es nicht, er ist abgelaufen oder wurde gelöscht.", @@ -182,5 +183,7 @@ "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." + "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.", + "Save paste": "Text speichern" } diff --git a/i18n/el.json b/i18n/el.json index c263340d..6e33978d 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", + "%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 project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "el", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/en.json b/i18n/en.json index ddac0a5f..a96bab5d 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", + "%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 project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "en", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/es.json b/i18n/es.json index e371c5c3..5aa2b380 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s es un \"pastebin\" en línea minimalista de código abierto, donde el servidor no tiene ningún conocimiento de los datos guardados. Los datos son cifrados/descifrados en el navegador usando 256 bits AES. Más información en la página del proyecto.", + "%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 es un \"pastebin\" en línea minimalista de código abierto, donde el servidor no tiene ningún conocimiento de los datos guardados. Los datos son cifrados/descifrados %sen el navegador%s usando 256 bits AES.", + "More information on the project page.": "Más información en la página del proyecto.", "Because ignorance is bliss": "Porque la ignorancia es dicha", "en": "es", "Paste does not exist, has expired or has been deleted.": "El \"paste\" no existe, ha caducado o ha sido eliminado.", @@ -182,5 +183,7 @@ "Convert To UTC": "Convertir A UTC", "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." + "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.": "El acortador de URL puede exponer su clave de descifrado en el URL.", + "Save paste": "Guardar \"paste\"" } diff --git a/i18n/et.json b/i18n/et.json new file mode 100644 index 00000000..9ab2840c --- /dev/null +++ b/i18n/et.json @@ -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 project page.": "Lisateave projekti lehel.", + "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 %s (Hit [Ctrl]+[c] to copy)": "Sinu kleebe on %s (Kopeerimiseks vajuta [Ctrl]+[c])", + "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 this FAQ for information to troubleshoot.": "Kui see sõnum ei kao, palun vaata seda KKK-d, et saada tõrkeotsinguks teavet..", + "+++ 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 see this FAQ entry.": "Lisateabe saamiseks vaata seda KKK sissekannet.", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Sinu brauser võib vajada HTTPS ühendust, et toetada WebCrypto API-d. Proovi üle minna HTTPS-ile.", + "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" +} diff --git a/i18n/fr.json b/i18n/fr.json index 429c3653..0e14d799 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s est un 'pastebin' (ou gestionnaire d'extraits de texte et de code source) minimaliste et open source, dans lequel le serveur n'a aucune connaissance des données envoyées. Les données sont chiffrées/déchiffrées dans le navigateur par un chiffrement AES 256 bits. Plus d'informations sur la page du projet.", + "%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 est un 'pastebin' (ou gestionnaire d'extraits de texte et de code source) minimaliste et open source, dans lequel le serveur n'a aucune connaissance des données envoyées. Les données sont chiffrées/déchiffrées %sdans le navigateur%s par un chiffrement AES 256 bits.", + "More information on the project page.": "Plus d'informations sur la page du projet.", "Because ignorance is bliss": "Parce que l'ignorance c'est le bonheur", "en": "fr", "Paste does not exist, has expired or has been deleted.": "Le paste n'existe pas, a expiré, ou a été supprimé.", @@ -182,5 +183,7 @@ "Convert To UTC": "Convertir en UTC", "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." + "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.", + "Save paste": "Sauver le paste" } diff --git a/i18n/he.json b/i18n/he.json index 52f8992d..046874e2 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -1,10 +1,11 @@ { "PrivateBin": "PrivateBin", - "%s 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 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", + "%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 project page.": "More information on the project page.", "Because ignorance is bliss": "כיוון שבורות היא ברכה", "en": "he", "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 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.": [ "נא להמתין שנייה אחת בין פרסום לפרסום.", @@ -22,7 +23,7 @@ "Wrong deletion token. Paste was not deleted.": "אסימון מחיקה שגוי. ההדבקה לא נמחקה.", "Paste was properly deleted.": "ההדבקה נמחקה כראוי.", "JavaScript is required for %s to work. Sorry for the inconvenience.": "צריך JavaScript כדי לאפשר ל־%s לפעול. סליחה על חוסר הנוחות.", - "%s requires a modern browser to work.": "%s requires a modern browser to work.", + "%s requires a modern browser to work.": "%s דורש דפדפן מודרני כדי לפעול.", "New": "חדש", "Send": "שליחה", "Clone": "שכפול", @@ -52,31 +53,31 @@ "%d hours (3rd plural)" ], "%d days": [ - "%d day (singular)", - "%d days (1st plural)", - "%d days (2nd plural)", - "%d days (3rd plural)" + "יום אחד", + "%d ימים", + "%d ימים", + "%d ימים" ], "%d weeks": [ - "%d week (singular)", - "%d weeks (1st plural)", - "%d weeks (2nd plural)", - "%d weeks (3rd plural)" + "שבוע אחד", + "%d שבועות", + "%d שבועות", + "%d שבועות" ], "%d months": [ - "%d month (singular)", - "%d months (1st plural)", - "%d months (2nd plural)", - "%d months (3rd plural)" + "חודש אחד", + "%d חודשים", + "%d חודשים", + "%d חודשים" ], "%d years": [ - "%d year (singular)", - "%d years (1st plural)", - "%d years (2nd plural)", - "%d years (3rd plural)" + "שנה אחת", + "%d שנים", + "%d שנים", + "%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.", + "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)", @@ -112,7 +113,7 @@ "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?": "לא ניתן לפענח את ההערה, מפתח שגוי?", - "Reply": "Reply", + "Reply": "תגובה", "Anonymous": "אלמוני", "Avatar generated from IP address": "התמונה הייצוגית נוצרה מכתובת ה־IP", "Add comment": "הוספת תגובה", @@ -138,12 +139,12 @@ "EiB": "EiB", "ZiB": "ZiB", "YiB": "YiB", - "Format": "Format", + "Format": "פורמט", "Plain Text": "טקסט פשוט", "Source Code": "קוד מקור", "Markdown": "Markdown", "Download attachment": "הורדת קובץ מצורף", - "Cloned: '%s'": "Cloned: '%s'", + "Cloned: '%s'": "שוכפל: '%s'", "The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.", "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", @@ -182,5 +183,7 @@ "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.": "נא לבקר בקישור כדי לצפות בהערה. מסירת הקישור לאנשים כלשהם תאפשר גם להם לגשת להערה." + "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": "Save paste" } diff --git a/i18n/hi.json b/i18n/hi.json index 5a3e6037..0cd2787a 100644 --- a/i18n/hi.json +++ b/i18n/hi.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", + "%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 project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "hi", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/hu.json b/i18n/hu.json index 3aa27584..1cff1a6f 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "A %s egy minimalista, nyílt forráskódú adattároló szoftver, ahol a szerver semmilyen információt nem tárol a feltett adatról. Azt ugyanis a böngésződ segítségével titkosítja és oldja fel 256 bit hosszú titkosítási kulcsú AES-t használva. További információt a projekt oldalán találsz.", + "%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.": "A %s egy minimalista, nyílt forráskódú adattároló szoftver, ahol a szerver semmilyen információt nem tárol a feltett adatról. Azt ugyanis a %sböngésződ%s segítségével titkosítja és oldja fel 256 bit hosszú titkosítási kulcsú AES-t használva.", + "More information on the project page.": "További információt a projekt oldalán találsz.", "Because ignorance is bliss": "A titok egyfajta hatalom.", "en": "hu", "Paste does not exist, has expired or has been deleted.": "A bejegyzés nem létezik, lejárt vagy törölve lett.", @@ -182,5 +183,7 @@ "Convert To UTC": "Átalakítás UTC időzónára", "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." + "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.", + "Save paste": "Save paste" } diff --git a/i18n/id.json b/i18n/id.json new file mode 100644 index 00000000..e58900a9 --- /dev/null +++ b/i18n/id.json @@ -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 adalah sebuah pastebin online sumber terbuka dan minimalis, dimana servernya tersebut tidak punya pengetahuan tentang data yang ditempelkan. Data tersebut di enkrip/dekrip %sdi dalam browser%s menggunakan metode enkrip AES 256 bit.", + "More information on the project page.": "Infomasi lebih lanjut pada halaman proyek.", + "Because ignorance is bliss": "Karena ketidaktahuan adalah kebahagiaan, gitu loh", + "en": "id", + "Paste does not exist, has expired or has been deleted.": "Paste tidak ada, telah kedaluwarsa atau telah dihapus.", + "%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.", + "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.", + "You are unlucky. Try again.": "Anda belum beruntung. Coba kembali ya Kaka.", + "Error saving comment. Sorry.": "Terjadi kesalahan saat menyimpan komentar. Maaf ya Kaka.", + "Error saving paste. Sorry.": "Terjadi kesalahan saat menyimpan paste. Maaf ya Kaka.", + "Invalid paste ID.": "ID paste tidak valid.", + "Paste is not of burn-after-reading type.": "Paste bukan tipe hapus-setelah-membaca.", + "Wrong deletion token. Paste was not deleted.": "Token penghapusan salah. Paste belum terhapus.", + "Paste was properly deleted.": "Paste telah dihapus dengan benar.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript diperlukan agar %s bekerja. Maaf untuk ketidaknyamanannya.", + "%s requires a modern browser to work.": "%s memerlukan sebuah browser modern untuk bekerja.", + "New": "Baru", + "Send": "Kirim", + "Clone": "Klon", + "Raw text": "Teks mentah", + "Expires": "Kadaluarsa", + "Burn after reading": "Hapus setelah membaca", + "Open discussion": "Diskusi terbuka", + "Password (recommended)": "Kata Sandi (direkomendasikan)", + "Discussion": "Diskusi", + "Toggle navigation": "Alihkan navigasi", + "%d seconds": [ + "%d detik", + "%d detik", + "%d detik", + "%d detik" + ], + "%d minutes": [ + "%d menit", + "%d menit", + "%d menit", + "%d menit" + ], + "%d hours": [ + "%d jam", + "%d jam", + "%d jam", + "%d jam" + ], + "%d days": [ + "%d hari", + "%d hari", + "%d hari", + "%d hari" + ], + "%d weeks": [ + "%d minggu", + "%d minggu", + "%d minggu", + "%d minggu" + ], + "%d months": [ + "%d bulan", + "%d bulan", + "%d bulan", + "%d bulan" + ], + "%d years": [ + "%d tahun", + "%d tahun", + "%d tahun", + "%d tahun" + ], + "Never": "Jangan pernah", + "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Catatan: Ini adalah layanan percobaan: Data mungkin bisa terhapus kapanpun juga. Anak-anak kucing akan mati jika Anda mengekploitasi layanan ini.", + "This document will expire in %d seconds.": [ + "Dokumen ini kadaluarsa dalam %d detik.", + "Dokumen ini kadaluarsa dalam %d detik.", + "Dokumen ini kadaluarsa dalam %d detik.", + "Dokumen ini kadaluarsa dalam %d detik." + ], + "This document will expire in %d minutes.": [ + "Dokumen ini akan kadaluarsa dalam %d menit.", + "Dokumen ini akan kadaluarsa dalam %d menit.", + "Dokumen ini akan kadaluarsa dalam %d menit.", + "Dokumen ini akan kadaluarsa dalam %d menit." + ], + "This document will expire in %d hours.": [ + "Dokumen ini akan kadaluarsa dalam %d jam.", + "Dokumen ini akan kadaluarsa dalam %d jam.", + "Dokumen ini akan kadaluarsa dalam %d jam.", + "Dokumen ini akan kadaluarsa dalam %d jam." + ], + "This document will expire in %d days.": [ + "Dokumen ini akan kadaluarsa dalam %d hari.", + "Dokumen ini akan kadaluarsa dalam %d hari.", + "Dokumen ini akan kadaluarsa dalam %d hari.", + "Dokumen ini akan kadaluarsa dalam %d hari." + ], + "This document will expire in %d months.": [ + "Dokumen ini akan kadaluarsa dalam %d bulan.", + "Dokumen ini akan kadaluarsa dalam %d bulan.", + "Dokumen ini akan kadaluarsa dalam %d bulan.", + "Dokumen ini akan kadaluarsa dalam %d bulan." + ], + "Please enter the password for this paste:": "Silahkan masukkan kata sandi untuk paste ini:", + "Could not decrypt data (Wrong key?)": "Tidak dapat mendekrip data (Salah kunci?)", + "Could not delete the paste, it was not stored in burn after reading mode.": "Tidak dapat menghapus paste, ini dikarenakan data tidak tersimpan dalam mode hapus setelah membaca.", + "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "HANYA UNTUK ANDA SAJA. Jangan tutup kolom jendela ini, pesan ini tidak akan dapat ditampilkan lagi.", + "Could not decrypt comment; Wrong key?": "Tidak dapat mendekrip komentar; Salah kunci?", + "Reply": "Balas", + "Anonymous": "Tanpa Nama", + "Avatar generated from IP address": "Avatar dihasilkan dari alamat IP", + "Add comment": "Tambah komentar", + "Optional nickname…": "Nama julukan tambahan…", + "Post comment": "Posting komentar", + "Sending comment…": "Mengirim komentar…", + "Comment posted.": "Komentar telah diposting.", + "Could not refresh display: %s": "Tidak dapat menyegarkan tampilan: %s", + "unknown status": "status tidak diketahui", + "server error or not responding": "kesalahan server atau server tidak merespon", + "Could not post comment: %s": "Tidak dapat memposting komentar: %s", + "Sending paste…": "Mengirim paste…", + "Your paste is %s (Hit [Ctrl]+[c] to copy)": "Paste Anda adalah %s(Tekan [Ctrl]+[c] untuk menyalin)", + "Delete data": "Hapus data", + "Could not create paste: %s": "Tidak dapat membuat 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?)": "Tidak dapat mendekripsi paste: Kunci dekripsi tidak ada di URL (Apakah Anda menggunakan redirector atau penyingkat URL yang menghapus bagian dari URL?)", + "B": "B", + "KiB": "KiB", + "MiB": "MiB", + "GiB": "GiB", + "TiB": "TiB", + "PiB": "PiB", + "EiB": "EiB", + "ZiB": "ZiB", + "YiB": "YiB", + "Format": "Format", + "Plain Text": "Teks Biasa", + "Source Code": "Kode Sumber", + "Markdown": "Markdown", + "Download attachment": "Unduh lampiran", + "Cloned: '%s'": "Diklon: '%s'", + "The cloned file '%s' was attached to this paste.": "Berkas yang di-klon '%s' telah dilampirkan pada paste ini.", + "Attach a file": "Lampirkan sebuah berkas", + "alternatively drag & drop a file or paste an image from the clipboard": "sebagai alternatif, seret & jatuhkan berkas atau tempel sebuah gambar dari papan klip", + "File too large, to display a preview. Please download the attachment.": "File terlalu besar untuk menampilkan pratinjau. Silakan unduh lampirannya.", + "Remove attachment": "Hapus lampiran", + "Your browser does not support uploading encrypted files. Please use a newer browser.": "Browser Anda tidak mendukung pengunggahan file terenkripsi. Harap gunakan browser yang lebih baru.", + "Invalid attachment.": "Lampiran tidak valid.", + "Options": "Pilihan", + "Shorten URL": "Pendekkan alamat URL", + "Editor": "Penyunting", + "Preview": "Pratinjau", + "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s memerlukan PATH berakhir dalam sebuah \"%s\". Silahkan perbarui PATH dalam index.php Anda.", + "Decrypt": "Dekrip", + "Enter password": "Masukkan kata sandi", + "Loading…": "Memuat…", + "Decrypting paste…": "Men-dekrip paste…", + "Preparing new paste…": "Menyiapkan paste baru…", + "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "Jika pesan ini tidak pernah menghilang, silahkan kunjungi dan lihat pada FAQ ini untuk informasi bagaimana menyelesaikan masalah tersebut.", + "+++ no paste text +++": "+++ tidak ada teks paste +++", + "Could not get paste data: %s": "Tidak dapat mengambil/menampilkan data paste: %s", + "QR code": "Kode QR", + "This website is using an insecure HTTP connection! Please use it only for testing.": "Situs web ini menggunakan koneksi HTTP yang tidak aman! Silahkan gunakan hanya untuk pengujian.", + "For more information see this FAQ entry.": "Untuk informasi lebih lanjut, lihat entri FAQ ini .", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Browser Anda mungkin memerlukan koneksi HTTPS untuk mendukung API Webcrypto. Coba beralih ke HTTPS .", + "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Browser Anda tidak mendukung Webassembly, yang digunakan untuk kompresi zlib. Anda dapat membuat dokumen yang tidak terkompresi, tetapi tidak akan dapat membaca berkas yang terkompresi.", + "waiting on user to provide a password": "menunggu pengguna untuk menyediakan kata sandi", + "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Tidak dapat mendekrip data. Apakah Anda memasukkan kata sandi yang salah? Silahkan coba lagi dengan tombol di bagian atas.", + "Retry": "Coba lagi", + "Showing raw text…": "Menampilkan teks mentah…", + "Notice:": "Pengumuman:", + "This link will expire after %s.": "Tautan ini akan kadaluarsa setelah %s.", + "This link can only be accessed once, do not use back or refresh button in your browser.": "Tautan ini hanya dapat diakses satu kali, jangan gunakan tombol Kembali atau tombol Segarkan di browser Anda.", + "Link:": "Tautan:", + "Recipient may become aware of your timezone, convert time to UTC?": "Penerima dapat mengetahui zona waktu Anda, ubah waktu menjadi UTC?", + "Use Current Timezone": "Gunakan Zonawaktu Saat Ini", + "Convert To UTC": "Konversi Ke UTC", + "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.": "Pemendek URL mungkin akan menampakkan kunci dekrip Anda dalam URL.", + "Save paste": "Simpan paste" +} diff --git a/i18n/it.json b/i18n/it.json index 6dc13a4e..659816e4 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s è un sistema di tipo \"Pastebin\" online, open source, minimalista. Il server non possiede alcuna conoscenza (\"Zero Knowledge\") del contenuto dei dati inviati. I dati sono cifrati/decifrati nel Browser con algoritmo AES a 256 Bit. Per ulteriori informazioni, vedi Sito del progetto.", + "%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 è un sistema di tipo \"Pastebin\" online, open source, minimalista. Il server non possiede alcuna conoscenza (\"Zero Knowledge\") del contenuto dei dati inviati. I dati sono cifrati/decifrati %snel Browser%s con algoritmo AES a 256 Bit.", + "More information on the project page.": "Per ulteriori informazioni, vedi Sito del progetto.", "Because ignorance is bliss": "Perché l'ignoranza è una benedizione (Because ignorance is bliss)", "en": "it", "Paste does not exist, has expired or has been deleted.": "Questo messaggio non esiste, è scaduto o è stato cancellato.", @@ -182,5 +183,7 @@ "Convert To UTC": "Converti a UTC", "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." + "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 può esporre la tua chiave decrittografata nell'URL.", + "Save paste": "Salva il messagio" } diff --git a/i18n/ja.json b/i18n/ja.json index f775dbb7..807a38c0 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", + "%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 project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "ja", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/ku.json b/i18n/ku.json index 3ae85137..ae72ebd8 100644 --- a/i18n/ku.json +++ b/i18n/ku.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", + "%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 project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "ku", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/la.json b/i18n/la.json index cdb4d0fd..2098982d 100644 --- a/i18n/la.json +++ b/i18n/la.json @@ -1,6 +1,7 @@ { "PrivateBin": "PrivatumVinariam", - "%s 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 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", + "%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 project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "la", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/languages.json b/i18n/languages.json index dace6f1e..a2aff4a9 100644 --- a/i18n/languages.json +++ b/i18n/languages.json @@ -63,6 +63,7 @@ "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"], diff --git a/i18n/lt.json b/i18n/lt.json new file mode 100644 index 00000000..10b62d84 --- /dev/null +++ b/i18n/lt.json @@ -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 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 project page.": "Daugiau informacijos rasite projekto puslapyje.", + "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.", + "%s requires php %s or above to work. Sorry.": "%s savo darbui reikalauja php %s arba naujesnės versijos. Apgailestaujame.", + "%s requires configuration section [%s] to be present in configuration file.": "%s reikalauja, kad konfigūracijos faile būtų [%s] konfigūracijos sekcija.", + "Please wait %d seconds between each post.": [ + "Tarp kiekvieno įrašo palaukite %d sekundę.", + "Tarp kiekvieno įrašo palaukite %d sekundes.", + "Tarp kiekvieno įrašo palaukite %d sekundžių.", + "Tarp kiekvieno įrašo palaukite %d sekundę." + ], + "Paste is limited to %s of encrypted data.": "Įdėjimas yra apribotas iki %s šifruotų duomenų.", + "Invalid data.": "Neteisingi duomenys.", + "You are unlucky. Try again.": "Jums nesiseka. Bandykite dar kartą.", + "Error saving comment. Sorry.": "Klaida įrašant komentarą. Apgailestaujame.", + "Error saving paste. Sorry.": "Klaida įrašant įdėjimą. Apgailestaujame.", + "Invalid paste ID.": "Neteisingas įdėjimo ID.", + "Paste is not of burn-after-reading type.": "Įdėjimo tipas nėra „Perskaičius sudeginti“.", + "Wrong deletion token. Paste was not deleted.": "Neteisingas ištrynimo prieigos raktas. Įdėjimas nebuvo ištrintas.", + "Paste was properly deleted.": "Įdėjimas buvo tinkamai ištrintas.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "%s darbui reikalinga JavaScript. Atsiprašome už nepatogumus.", + "%s requires a modern browser to work.": "%s savo darbui reikalauja šiuolaikinės naršyklės.", + "New": "Naujas", + "Send": "Siųsti", + "Clone": "Dubliuoti", + "Raw text": "Neapdorotas tekstas", + "Expires": "Baigs galioti po", + "Burn after reading": "Perskaičius sudeginti", + "Open discussion": "Atvira diskusija", + "Password (recommended)": "Slaptažodis (rekomenduojama)", + "Discussion": "Diskusija", + "Toggle navigation": "Perjungti naršymą", + "%d seconds": [ + "%d sekundės", + "%d sekundžių", + "%d sekundžių", + "%d sekundės" + ], + "%d minutes": [ + "%d minutės", + "%d minučių", + "%d minučių", + "%d minutės" + ], + "%d hours": [ + "%d valandos", + "%d valandų", + "%d valandų", + "%d valandos" + ], + "%d days": [ + "%d dienos", + "%d dienų", + "%d dienų", + "%d dienos" + ], + "%d weeks": [ + "%d savaitės", + "%d savaičių", + "%d savaičių", + "%d savaitės" + ], + "%d months": [ + "%d mėnesio", + "%d mėnesių", + "%d mėnesių", + "%d mėnesio" + ], + "%d years": [ + "%d metų", + "%d metų", + "%d metų", + "%d metų" + ], + "Never": "Niekada", + "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Pastaba: Tai yra bandomoji paslauga. Duomenys bet kuriuo metu gali būti ištrinti. Kačiukai mirs, jei piktnaudžiausite šia paslauga.", + "This document will expire in %d seconds.": [ + "Šis dokumentas nustos galioti po %d sekundės.", + "Šis dokumentas nustos galioti po %d sekundžių.", + "Šis dokumentas nustos galioti po %d sekundžių.", + "Šis dokumentas nustos galioti po %d sekundės." + ], + "This document will expire in %d minutes.": [ + "Šis dokumentas nustos galioti po %d minutės.", + "Šis dokumentas nustos galioti po %d minučių.", + "Šis dokumentas nustos galioti po %d minučių.", + "Šis dokumentas nustos galioti po %d minutės." + ], + "This document will expire in %d hours.": [ + "Šis dokumentas nustos galioti po %d valandos.", + "Šis dokumentas nustos galioti po %d valandų.", + "Šis dokumentas nustos galioti po %d valandų.", + "Šis dokumentas nustos galioti po %d valandos." + ], + "This document will expire in %d days.": [ + "Šis dokumentas nustos galioti po %d dienos.", + "Šis dokumentas nustos galioti po %d dienų.", + "Šis dokumentas nustos galioti po %d dienų.", + "Šis dokumentas nustos galioti po %d dienos." + ], + "This document will expire in %d months.": [ + "Šis dokumentas nustos galioti po %d mėnesio.", + "Šis dokumentas nustos galioti po %d mėnesių.", + "Šis dokumentas nustos galioti po %d mėnesių.", + "Šis dokumentas nustos galioti po %d mėnesio." + ], + "Please enter the password for this paste:": "Įveskite šio įdėjimo slaptažodį:", + "Could not decrypt data (Wrong key?)": "Nepavyko iššifruoti duomenų (Neteisingas raktas?)", + "Could not delete the paste, it was not stored in burn after reading mode.": "Nepavyko ištrinti įdėjimo, jis nebuvo saugomas „Perskaičius sudeginti“ veiksenoje.", + "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "SKIRTA TIK JŪSŲ AKIMS. Neužverkite šio lango, šis pranešimas negalės būti rodomas dar kartą.", + "Could not decrypt comment; Wrong key?": "Nepavyko iššifruoti komentaro; Neteisingas raktas?", + "Reply": "Atsakyti", + "Anonymous": "Anonimas", + "Avatar generated from IP address": "Avataras sukurtas iš IP adreso", + "Add comment": "Pridėti komentarą", + "Optional nickname…": "Nebūtinas slapyvardis…", + "Post comment": "Skelbti komentarą", + "Sending comment…": "Siunčiamas komentaras…", + "Comment posted.": "Komentaras paskelbtas.", + "Could not refresh display: %s": "Nepavyko įkelti rodinio iš naujo: %s", + "unknown status": "nežinoma būsena", + "server error or not responding": "serverio klaida arba jis neatsako", + "Could not post comment: %s": "Nepavyko paskelbti komentaro: %s", + "Sending paste…": "Siunčiamas įdėjimas…", + "Your paste is %s (Hit [Ctrl]+[c] to copy)": "Jūsų įdėjimas yra %s (Paspauskite [Vald]+[c] norėdami nukopijuoti)", + "Delete data": "Ištrinti duomenis", + "Could not create paste: %s": "Nepavyko sukurti įdėjimo: %s", + "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Nepavyksta iššifruoti įdėjimo: URL adrese trūksta iššifravimo rakto (Ar naudojote peradresavimo ar URL trumpinimo įrankį, kuris pašalina URL dalį?)", + "B": "B", + "KiB": "KiB", + "MiB": "MiB", + "GiB": "GiB", + "TiB": "TiB", + "PiB": "PiB", + "EiB": "EiB", + "ZiB": "ZiB", + "YiB": "YiB", + "Format": "Formatas", + "Plain Text": "Grynasis tekstas", + "Source Code": "Pirminis kodas", + "Markdown": "„Markdown“", + "Download attachment": "Atsisiųsti priedą", + "Cloned: '%s'": "Dubliuota: „%s“", + "The cloned file '%s' was attached to this paste.": "Dubliuotas failas „%s“ buvo pridėtas į šį įdėjimą.", + "Attach a file": "Pridėti failą", + "alternatively drag & drop a file or paste an image from the clipboard": "arba kitaip - tempkite failą arba įdėkite paveikslą iš iškarpinės", + "File too large, to display a preview. Please download the attachment.": "Failas per didelis, kad būtų rodoma peržiūra. Atsisiųskite priedą.", + "Remove attachment": "Šalinti priedą", + "Your browser does not support uploading encrypted files. Please use a newer browser.": "Jūsų naršyklė nepalaiko šifruotų failų įkėlimo. Naudokite naujesnę naršyklę.", + "Invalid attachment.": "Neteisingas priedas.", + "Options": "Parinktys", + "Shorten URL": "Sutrumpinti URL", + "Editor": "Redaktorius", + "Preview": "Peržiūra", + "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s reikalauja, kad PATH baigtųsi „%s“. Atnaujinkite PATH savo index.php.", + "Decrypt": "Iššifruoti", + "Enter password": "Įveskite slaptažodį", + "Loading…": "Įkeliama…", + "Decrypting paste…": "Iššifruojamas įdėjimas…", + "Preparing new paste…": "Ruošiamas naujas įdėjimas…", + "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "Jeigu šis pranešimas niekada nedingsta, pasižiūrėkite šį DUK skyrių, kuriame yra informacija apie nesklandumų šalinimą.", + "+++ no paste text +++": "+++ nėra įdėjimo teksto +++", + "Could not get paste data: %s": "Nepavyko gauti įdėjimo duomenų: %s", + "QR code": "QR kodas", + "This website is using an insecure HTTP connection! Please use it only for testing.": "Ši internetinė svetainė naudoja nesaugų HTTP ryšį! Naudokite ją tik bandymams.", + "For more information see this FAQ entry.": "Išsamesnei informacijai, žiūrėkite šį DUK įrašą.", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Jūsų naršyklei gali prireikti HTTPS ryšio, kad palaikytų „WebCrypto“ API. Pabandykite persijungti į HTTPS.", + "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Jūsų naršyklė nepalaiko „WebAssembly“, kuri naudojama zlib glaudinimui. Jūs galite kurti neglaudintus dokumentus, tačiau negalite skaityti glaudintų dokumentų.", + "waiting on user to provide a password": "laukiama, kol naudotojas pateiks slaptažodį", + "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Nepavyko iššifruoti duomenų. Ar įvedėte teisingą slaptažodį? Bandykite iš naujo pasinaudodami mygtuku viršuje.", + "Retry": "Bandyti dar kartą", + "Showing raw text…": "Rodomas neapdorotas tekstas…", + "Notice:": "Pranešimas:", + "This link will expire after %s.": "Ši nuoroda nustos galioti po %s.", + "This link can only be accessed once, do not use back or refresh button in your browser.": "Ši nuoroda gali būti atverta tik vieną kartą, nenaudokite savo naršyklėje mygtuko „Grįžti“ ar „Įkelti iš naujo“.", + "Link:": "Nuoroda:", + "Recipient may become aware of your timezone, convert time to UTC?": "Gavėjas gali sužinoti jūsų laiko juostą, konvertuoti laiką į suderintąjį pasaulinį laiką (UTC)?", + "Use Current Timezone": "Naudoti esamą laiko juostą", + "Convert To UTC": "Konvertuoti į UTC", + "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 trumpinimo įrankis gali atskleisti URL adrese jūsų iššifravimo raktą.", + "Save paste": "Įrašyti įdėjimą" +} diff --git a/i18n/nl.json b/i18n/nl.json index 7eab51a6..65c4e37c 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s is een minimalistische, open source online pastebin waarbij de server geen kennis heeft van de geplakte gegevens. Gegevens worden gecodeerd/gedecodeerd in de browser met behulp van 256 bits AES. Meer informatie is te vinden op de projectpagina.", + "%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 een minimalistische, open source online pastebin waarbij de server geen kennis heeft van de geplakte gegevens. Gegevens worden gecodeerd/gedecodeerd %s in de browser %s met behulp van 256 bits AES.", + "More information on the project page.": "Meer informatie is te vinden op de projectpagina.", "Because ignorance is bliss": "Onwetendheid is een zegen", "en": "nl", "Paste does not exist, has expired or has been deleted.": "Geplakte tekst bestaat niet, is verlopen of verwijderd.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/no.json b/i18n/no.json index 34389307..b0ca95a0 100644 --- a/i18n/no.json +++ b/i18n/no.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s er en minimalistisk, åpen kildekode, elektronisk tilgjengelig pastebin hvor serveren ikke har kunnskap om dataene som limes inn. Dataene krypteres/dekrypteres i nettleseren ved hjelp av 256 bits AES. Mer informasjon om prosjektet på prosjektsiden.", + "%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 er en minimalistisk, åpen kildekode, elektronisk tilgjengelig pastebin hvor serveren ikke har kunnskap om dataene som limes inn. Dataene krypteres/dekrypteres %si nettleseren%s ved hjelp av 256 bits AES.", + "More information on the project page.": "Mer informasjon om prosjektet på prosjektsiden.", "Because ignorance is bliss": "Fordi uvitenhet er lykke", "en": "no", "Paste does not exist, has expired or has been deleted.": "Innlegget eksisterer ikke, er utløpt eller har blitt slettet.", @@ -182,5 +183,7 @@ "Convert To UTC": "Konverter til UTC", "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." + "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 forkorter kan avsløre dekrypteringsnøkkelen.", + "Save paste": "Lagre utklipp" } diff --git a/i18n/oc.json b/i18n/oc.json index ba9e69d1..6a0a69ce 100644 --- a/i18n/oc.json +++ b/i18n/oc.json @@ -1,13 +1,14 @@ { "PrivateBin": "PrivateBin", - "%s 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 bits AES. More information on the project page.": "%s es un 'pastebin' (o gestionari d’extrachs de tèxte e còdi font) minimalista e open source, dins lo qual lo servidor a pas cap de coneissença de las donadas mandadas. Las donadas son chifradas/deschifradas dins lo navigator per un chiframent AES 256 bits. Mai informacions sus la pagina del projècte.", + "%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 es un 'pastebin' (o gestionari d’extrachs de tèxte e còdi font) minimalista e open source, dins lo qual lo servidor a pas cap de coneissença de las donadas mandadas. Las donadas son chifradas/deschifradas %sdins lo navigator%s per un chiframent AES 256 bits.", + "More information on the project page.": "Mai informacions sus la pagina del projècte.", "Because ignorance is bliss": "Perque lo bonaür es l’ignorància", "en": "oc", "Paste does not exist, has expired or has been deleted.": "Lo tèxte existís pas, a expirat, o es estat suprimit.", "%s requires php %s or above to work. Sorry.": "O planhèm, %s necessita php %s o superior per foncionar.", "%s requires configuration section [%s] to be present in configuration file.": "%s fa besonh de la seccion de configuracion [%s] dins lo fichièr de configuracion per foncionar.", "Please wait %d seconds between each post.": [ - "Mercés d'esperar %d segonde entre cada publicacion.", + "Mercés d'esperar %d segonda entre cada publicacion.", "Mercés d'esperar %d segondas entre cada publicacion.", "Mercés d'esperar %d segondas entre cada publicacion.", "Mercés d'esperar %d segondas entre cada publicacion." @@ -182,5 +183,7 @@ "Convert To UTC": "Convertir en UTC", "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." + "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.": "Los espleches d’acorchiment d’URL pòdon expausar la clau de deschiframent dins l’URL.", + "Save paste": "Enregistrar lo tèxt" } diff --git a/i18n/pl.json b/i18n/pl.json index 45c5c968..4b4a67be 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s jest minimalistycznym, otwartoźródłowym serwisem typu pastebin, w którym serwer nie ma jakichkolwiek informacji o tym, co jest wklejane. Dane są szyfrowane i deszyfrowane w przeglądarce z użyciem 256-bitowego klucza AES. Więcej informacji na stronie projektu.", + "%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 jest minimalistycznym, otwartoźródłowym serwisem typu pastebin, w którym serwer nie ma jakichkolwiek informacji o tym, co jest wklejane. Dane są szyfrowane i deszyfrowane %sw przeglądarce%s z użyciem 256-bitowego klucza AES.", + "More information on the project page.": "Więcej informacji na stronie projektu.", "Because ignorance is bliss": "Ponieważ ignorancja jest cnotą", "en": "pl", "Paste does not exist, has expired or has been deleted.": "Wklejka nie istnieje, wygasła albo została usunięta.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/pt.json b/i18n/pt.json index afff1370..0f6c1333 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s é um serviço minimalista e de código aberto do tipo \"pastebin\", em que o servidor tem zero conhecimento dos dados copiados. Os dados são cifrados e decifrados no navegador usando 256 bits AES. Mais informações na página do projeto.", + "%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 é um serviço minimalista e de código aberto do tipo \"pastebin\", em que o servidor tem zero conhecimento dos dados copiados. Os dados são cifrados e decifrados %sno navegador%s usando 256 bits AES.", + "More information on the project page.": "Mais informações na página do projeto.", "Because ignorance is bliss": "Porque a ignorância é uma benção", "en": "pt", "Paste does not exist, has expired or has been deleted.": "A cópia não existe, expirou ou já foi excluída.", @@ -182,5 +183,7 @@ "Convert To UTC": "Converter para UTC", "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." + "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.", + "Save paste": "Save paste" } diff --git a/i18n/ru.json b/i18n/ru.json index 5b7ace41..a9112330 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s это минималистичный Open Source проект для создания заметок, где сервер не знает ничего о сохраняемых данных. Данные шифруются/расшифровываются в браузере с использованием 256 битного шифрования 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.": "%s это минималистичный Open Source проект для создания заметок, где сервер не знает ничего о сохраняемых данных. Данные шифруются/расшифровываются %sв браузере%s с использованием 256 битного шифрования AES.", + "More information on the project page.": "Подробнее можно узнать на сайте проекта.", "Because ignorance is bliss": "Потому что неведение - благо", "en": "ru", "Paste does not exist, has expired or has been deleted.": "Запись не существует, просрочена или была удалена.", @@ -76,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 секунды.", @@ -182,5 +183,7 @@ "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.": "Посетите эту ссылку чтобы просмотреть запись. Передача ссылки кому либо позволит им получить доступ к записи тоже." + "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.": "Сервис сокращения ссылок может получить ваш ключ расшифровки из ссылки.", + "Save paste": "Сохранить запись" } diff --git a/i18n/sl.json b/i18n/sl.json index de7c2b3e..c4bb0cb7 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s je minimalističen, odprtokodni spletni 'pastebin', kjer server ne ve ničesar o prilepljenih podatkih. Podatki so zakodirani/odkodirani v brskalniku z uporabo 256 bitnega AES. Več informacij na spletni strani projekta..", + "%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 je minimalističen, odprtokodni spletni 'pastebin', kjer server ne ve ničesar o prilepljenih podatkih. Podatki so zakodirani/odkodirani %sv brskalniku%s z uporabo 256 bitnega AES.", + "More information on the project page.": "Več informacij na spletni strani projekta..", "Because ignorance is bliss": "Ker kar ne veš ne boli.", "en": "sl", "Paste does not exist, has expired or has been deleted.": "Prilepek ne obstaja, mu je potekla življenjska doba, ali pa je izbrisan.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/sv.json b/i18n/sv.json index 7de08313..f2286e02 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", + "%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 project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "sv", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/tr.json b/i18n/tr.json index 3902c3f7..0abbc37d 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -1,7 +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 in the browser using 256 bits AES. More information on the project page.": "%s 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 bits AES. More information on the project page.", - "Because ignorance is bliss": "Because ignorance is bliss", + "%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 project page.": "Daha fazla bilgi için proje sayfası'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.", @@ -13,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.", @@ -23,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)", @@ -58,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)" ], @@ -112,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 %s (Hit [Ctrl]+[c] to copy)": "Your paste is %s (Hit [Ctrl]+[c] to copy)", - "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", @@ -154,33 +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 this FAQ for information to troubleshoot.": "In case this message never disappears please have a look at this FAQ for information to troubleshoot.", "+++ 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 see this FAQ entry.": "For more information see this FAQ entry.", "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.", "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." + "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": "Save paste" } diff --git a/i18n/uk.json b/i18n/uk.json index 73ddd3ed..1916a244 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -1,6 +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 in the browser using 256 bits AES. More information on the project page.": "%s це мінімалістичний Open Source проєкт для створення нотаток, де сервер не знає нічого про дані, що зберігаються. Дані шифруються/розшифровуються у переглядачі з використанням 256-бітного шифрувания 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.": "%s це мінімалістичний Open Source проєкт для створення нотаток, де сервер не знає нічого про дані, що зберігаються. Дані шифруються/розшифровуються %sу переглядачі%s з використанням 256-бітного шифрувания AES.", + "More information on the project page.": "Подробиці можна дізнатися на сайті проєкту.", "Because ignorance is bliss": "Бо незнання - благо", "en": "uk", "Paste does not exist, has expired or has been deleted.": "Допис не існує, протермінований чи був видалений.", @@ -182,5 +183,7 @@ "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." + "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": "Save paste" } diff --git a/i18n/zh.json b/i18n/zh.json index 880f96bf..2ab63bf4 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -1,28 +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 in the browser using 256 bits AES. More information on the project page.": "%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 project page.": "更多信息请查看项目主页。", "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,7 +112,7 @@ "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?": "无法解密评论; 密钥错误?", + "Could not decrypt comment; Wrong key?": "无法解密评论;密钥错误?", "Reply": "回复", "Anonymous": "匿名", "Avatar generated from IP address": "由IP生成的头像", @@ -125,7 +126,7 @@ "server error or not responding": "服务器错误或无回应", "Could not post comment: %s": "无法发送评论: %s", "Sending paste…": "粘贴内容提交中…", - "Your paste is %s (Hit [Ctrl]+[c] to copy)": "您粘贴内容的链接是%s (按下 [Ctrl]+[c] 以复制)", + "Your paste is %s (Hit [Ctrl]+[c] to copy)": "您粘贴内容的链接是 %s (按下 [Ctrl]+[C] 以复制)", "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中缺失解密密钥(是否使用了重定向或者短链接导致密钥丢失?)", @@ -143,44 +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 this FAQ for information to troubleshoot.": "如果这个消息一直存在,请参考 这里的 FAQ (英文版)进行故障排除。", - "+++ no paste text +++": "+++ 没有粘贴内容 +++", + "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "如果此消息一直存在,请参考 这里的 FAQ(英文版)排除故障。", + "+++ 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 see this FAQ entry.": "有关更多信息,请参阅此常见问题解答。", - "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "您的浏览器可能需要HTTPS连接才能支持WebCrypto API。 尝试切换到HTTPS 。", - "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 see this FAQ entry.": "有关更多信息,请参阅此常见问题解答。", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "您的浏览器可能需要 HTTPS 连接才能支持 WebCrypto API。 尝试切换到 HTTPS。", + "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发送给任何人即可允许其访问该笔记。" + "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": "保存内容" } diff --git a/index.php b/index.php index a6d7cdf2..f846adb7 100644 --- a/index.php +++ b/index.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ // change this, if your php files and data is outside of your webservers document root diff --git a/js/common.js b/js/common.js index 750e63d0..b23ed221 100644 --- a/js/common.js +++ b/js/common.js @@ -17,7 +17,7 @@ require('./prettify'); global.prettyPrint = window.PR.prettyPrint; global.prettyPrintOne = window.PR.prettyPrintOne; global.showdown = require('./showdown-1.9.1'); -global.DOMPurify = require('./purify-2.0.14'); +global.DOMPurify = require('./purify-2.2.7'); global.baseX = require('./base-x-3.0.7').baseX; global.Legacy = require('./legacy').Legacy; require('./bootstrap-3.3.7'); diff --git a/js/kjua-0.6.0.js b/js/kjua-0.6.0.js deleted file mode 100644 index f70482d4..00000000 --- a/js/kjua-0.6.0.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! kjua v0.6.0 - https://larsjung.de/kjua/ */ -!function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define("kjua",[],r):"object"==typeof exports?exports.kjua=r():t.kjua=r()}("undefined"!=typeof self?self:this,function(){return function(e){var n={};function o(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,o),r.l=!0,r.exports}return o.m=e,o.c=n,o.d=function(t,r,e){o.o(t,r)||Object.defineProperty(t,r,{enumerable:!0,get:e})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(r,t){if(1&t&&(r=o(r)),8&t)return r;if(4&t&&"object"==typeof r&&r&&r.__esModule)return r;var e=Object.create(null);if(o.r(e),Object.defineProperty(e,"default",{enumerable:!0,value:r}),2&t&&"string"!=typeof r)for(var n in r)o.d(e,n,function(t){return r[t]}.bind(null,n));return e},o.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(r,"a",r),r},o.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},o.p="",o(o.s=0)}([function(t,r,e){var n=e(1),a=n.create_canvas,u=n.canvas_to_img,f=n.dpr,c=e(3),l=e(4),s=e(6);t.exports=function(t){var r=Object.assign({},c,t),e=l(r.text,r.ecLevel,r.minVersion,r.quiet),n=r.ratio||f,o=a(r.size,n),i=o.getContext("2d");return i.scale(n,n),s(e,i,r),"image"===r.render?u(o):o}},function(u,t,r){(function(t){function n(t){return i.createElement(t)}function e(t,r){return t.getAttribute(r)}function o(t,r,e){return t.setAttribute(r,e)}var r=t.window,i=r.document,a=r.devicePixelRatio||1;u.exports={create_canvas:function(t,r){var e=n("canvas");return o(e,"width",t*r),o(e,"height",t*r),e.style.width="".concat(t,"px"),e.style.height="".concat(t,"px"),e},canvas_to_img:function(t){var r=n("img");return o(r,"crossorigin","anonymous"),o(r,"src",t.toDataURL("image/png")),o(r,"width",e(t,"width")),o(r,"height",e(t,"height")),r.style.width=t.style.width,r.style.height=t.style.height,r},dpr:a}}).call(this,r(2))},function(t,r){var e;e=function(){return this}();try{e=e||new Function("return this")()}catch(t){"object"==typeof window&&(e=window)}t.exports=e},function(t,r){t.exports={render:"image",crisp:!0,minVersion:1,ecLevel:"L",size:200,ratio:null,fill:"#333",back:"#fff",text:"no text",rounded:0,quiet:0,mode:"plain",mSize:30,mPosX:50,mPosY:50,label:"no label",fontname:"sans",fontcolor:"#333",image:null}},function(t,r,e){function a(t){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var u=/code length overflow/i,f=e(5);f.stringToBytes=f.stringToBytesFuncs["UTF-8"];t.exports=function(){var t=0>e&1);l[Math.floor(e/3)][e%3+s-8-3]=n}for(e=0;e<18;e+=1){n=!t&&1==(r>>e&1);l[e%3+s-8-3][Math.floor(e/3)]=n}},v=function(t,r){for(var e=f<<3|r,n=w.getBCHTypeInfo(e),o=0;o<15;o+=1){var i=!t&&1==(n>>o&1);o<6?l[o][8]=i:o<8?l[o+1][8]=i:l[s-15+o][8]=i}for(o=0;o<15;o+=1){i=!t&&1==(n>>o&1);o<8?l[8][s-o-1]=i:o<9?l[8][15-o-1+1]=i:l[8][15-o-1]=i}l[s-8][8]=!t},d=function(t,r){for(var e=-1,n=s-1,o=7,i=0,a=w.getMaskFunction(r),u=s-1;0>>o&1)),a(n,u-f)&&(c=!c),l[n][u-f]=c,-1==(o-=1)&&(i+=1,o=7)}if((n+=e)<0||s<=n){n-=e,e=-e;break}}},p=function(t,r,e){for(var n=B.getRSBlocks(t,r),o=C(),i=0;i8*u)throw"code length overflow. ("+o.getLengthInBits()+">"+8*u+")";for(o.getLengthInBits()+4<=8*u&&o.put(0,4);o.getLengthInBits()%8!=0;)o.putBit(!1);for(;!(o.getLengthInBits()>=8*u||(o.put(236,8),o.getLengthInBits()>=8*u));)o.put(17,8);return function(t,r){for(var e=0,n=0,o=0,i=new Array(r.length),a=new Array(r.length),u=0;u',e+="";for(var n=0;n";for(var o=0;o';e+=""}return e+="",e+=""},h.createSvgTag=function(t,r){var e={};"object"==typeof t&&(t=(e=t).cellSize,r=e.margin),t=t||2,r=void 0===r?4*t:r;var n,o,i,a,u=h.getModuleCount()*t+2*r,f="";for(a="l"+t+",0 0,"+t+" -"+t+",0 0,-"+t+"z ",f+='>>8),r.push(255&o)):r.push(a)}}return r}};var a=1,u=2,o=4,f=8,y={L:1,M:0,Q:3,H:2},n=0,c=1,l=2,s=3,g=4,h=5,v=6,d=7,w=function(){function e(t){for(var r=0;0!=t;)r+=1,t>>>=1;return r}var r=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],t={};return t.getBCHTypeInfo=function(t){for(var r=t<<10;0<=e(r)-e(1335);)r^=1335<>>8)},writeBytes:function(t,r,e){r=r||0,e=e||t.length;for(var n=0;n>>7-t%8&1)},put:function(t,r){for(var e=0;e>>r-e-1&1))},getLengthInBits:function(){return n},putBit:function(t){var r=Math.floor(n/8);e.length<=r&&e.push(0),t&&(e[r]|=128>>>n%8),n+=1}};return o},x=function(t){var r=a,n=t,e={getMode:function(){return r},getLength:function(t){return n.length},write:function(t){for(var r=n,e=0;e+2>>8&255)+(255&n),t.put(n,13),e+=2}if(e=e.length){if(0==i)return-1;throw"unexpected end of file./"+i}var t=e.charAt(n);if(n+=1,"="==t)return i=0,-1;t.match(/^\s$/)||(o=o<<6|a(t.charCodeAt(0)),i+=6)}var r=o>>>i-8&255;return i-=8,r}},a=function(t){if(65<=t&&t<=90)return t-65;if(97<=t&&t<=122)return t-97+26;if(48<=t&&t<=57)return t-48+52;if(43==t)return 62;if(47==t)return 63;throw"c:"+t};return r},D=function(t,r,e){for(var n=function(t,r){var n=t,o=r,s=new Array(t*r),e={setPixel:function(t,r,e){s[r*n+t]=e},write:function(t){t.writeString("GIF87a"),t.writeShort(n),t.writeShort(o),t.writeByte(128),t.writeByte(0),t.writeByte(0),t.writeByte(0),t.writeByte(0),t.writeByte(0),t.writeByte(255),t.writeByte(255),t.writeByte(255),t.writeString(","),t.writeShort(0),t.writeShort(0),t.writeShort(n),t.writeShort(o),t.writeByte(0);var r=i(2);t.writeByte(2);for(var e=0;255>>r!=0)throw"length over";for(;8<=n+r;)e.writeByte(255&(t<>>=8-n,n=o=0;o|=t<>>o-6),o-=6},t.flush=function(){if(0>6,128|63&n):n<55296||57344<=n?r.push(224|n>>12,128|n>>6&63,128|63&n):(e++,n=65536+((1023&n)<<10|1023&t.charCodeAt(e)),r.push(240|n>>18,128|n>>12&63,128|n>>6&63,128|63&n))}return r}(t)},o=[],void 0===(i="function"==typeof(n=function(){return a})?n.apply(r,o):n)||(t.exports=i)},function(t,r,e){function c(t,r,e,n,o,i){t.isDark(o,i)&&r.rect(i*n,o*n,n,n)}var l=e(7),n=e(8);t.exports=function(t,r,e){!function(t,r){t.fillStyle=r.back,t.fillRect(0,0,r.size,r.size)}(r,e),function(t,r,e){if(t){var n=0>e&1);l[Math.floor(e/3)][e%3+s-8-3]=n}for(e=0;e<18;e+=1){n=!t&&1==(r>>e&1);l[e%3+s-8-3][Math.floor(e/3)]=n}},d=function(t,r){for(var e=f<<3|r,n=m.getBCHTypeInfo(e),o=0;o<15;o+=1){var i=!t&&1==(n>>o&1);o<6?l[o][8]=i:o<8?l[o+1][8]=i:l[s-15+o][8]=i}for(o=0;o<15;o+=1){i=!t&&1==(n>>o&1);o<8?l[8][s-o-1]=i:o<9?l[8][15-o-1+1]=i:l[8][15-o-1]=i}l[s-8][8]=!t},v=function(t,r){for(var e=-1,n=s-1,o=7,i=0,a=m.getMaskFunction(r),u=s-1;0>>o&1)),a(n,u-c)&&(f=!f),l[n][u-c]=f,-1==--o&&(i+=1,o=7))}if((n+=e)<0||s<=n){n-=e,e=-e;break}}},p=function(t,r,e){for(var n=S.getRSBlocks(t,r),o=M(),i=0;i8*u)throw"code length overflow. ("+o.getLengthInBits()+">"+8*u+")";for(o.getLengthInBits()+4<=8*u&&o.put(0,4);o.getLengthInBits()%8!=0;)o.putBit(!1);for(;!(o.getLengthInBits()>=8*u||(o.put(236,8),o.getLengthInBits()>=8*u));)o.put(17,8);return function(t,r){for(var e=0,n=0,o=0,i=new Array(r.length),a=new Array(r.length),u=0;u',e+="";for(var n=0;n";for(var o=0;o';e+=""}return e+="",e+=""},h.createSvgTag=function(t,r,e,n){var o={};"object"==typeof arguments[0]&&(t=(o=arguments[0]).cellSize,r=o.margin,e=o.alt,n=o.title),t=t||2,r=void 0===r?4*t:r,(e="string"==typeof e?{text:e}:e||{}).text=e.text||null,e.id=e.text?e.id||"qrcode-description":null,(n="string"==typeof n?{text:n}:n||{}).text=n.text||null,n.id=n.text?n.id||"qrcode-title":null;var i,a,u,f=h.getModuleCount()*t+2*r,c="",l="l"+t+",0 0,"+t+" -"+t+",0 0,-"+t+"z ";for(c+=''+y(n.text)+"":"",c+=e.text?''+y(e.text)+"":"",c+='',c+='":r+=">";break;case"&":r+="&";break;case'"':r+=""";break;default:r+=n}}return r};return h.createASCII=function(t,r){if((t=t||1)<2)return function(t){t=void 0===t?2:t;for(var r,e,n,o,i=+h.getModuleCount()+2*t,a=t,u=i-t,f={"██":"█","█ ":"▀"," █":"▄"," ":" "},c={"██":"▀","█ ":"▀"," █":" "," ":" "},l="",s=0;s>>8),r.push(255&n)):r.push(a)}return r}};var r,t,a=1,u=2,o=4,f=8,w={L:1,M:0,Q:3,H:2},e=0,n=1,c=2,l=3,s=4,g=5,h=6,d=7,m=(r=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],(t={}).getBCHTypeInfo=function(t){for(var r=t<<10;0<=v(r)-v(1335);)r^=1335<>>=1;return r}var p=function(){for(var r=new Array(256),e=new Array(256),t=0;t<8;t+=1)r[t]=1<>>8)},writeBytes:function(t,r,e){r=r||0,e=e||t.length;for(var n=0;n>>o-6),o-=6},t.flush=function(){if(0>>r!=0)throw"length over";for(;8<=u+r;)a.writeByte(255&(t<>>=8-u,u=f=0;f|=t<>>7-t%8&1)},put:function(t,r){for(var e=0;e>>r-e-1&1))},getLengthInBits:function(){return n},putBit:function(t){var r=Math.floor(n/8);e.length<=r&&e.push(0),t&&(e[r]|=128>>>n%8),n+=1}};return o},A=function(t){var r=a,n=t,e={getMode:function(){return r},getLength:function(t){return n.length},write:function(t){for(var r=n,e=0;e+2>>8&255)+(255&n),t.put(n,13),e+=2}if(e=e.length){if(0==i)return-1;throw"unexpected end of file./"+i}var t=e.charAt(n);if(n+=1,"="==t)return i=0,-1;t.match(/^\s$/)||(o=o<<6|a(t.charCodeAt(0)),i+=6)}var r=o>>>i-8&255;return i-=8,r}},a=function(t){if(65<=t&&t<=90)return t-65;if(97<=t&&t<=122)return t-97+26;if(48<=t&&t<=57)return t-48+52;if(43==t)return 62;if(47==t)return 63;throw"c:"+t};return r},P=function(t,r,e){for(var n=k(t,r),o=0;o>6,128|63&n):n<55296||57344<=n?r.push(224|n>>12,128|n>>6&63,128|63&n):(e++,n=65536+((1023&n)<<10|1023&t.charCodeAt(e)),r.push(240|n>>18,128|n>>12&63,128|n>>6&63,128|63&n))}return r}(t)},o=[],void 0===(i="function"==typeof(n=function(){return a})?n.apply(r,o):n)||(t.exports=i)},function(t,r,e){function c(t,r,e,n,o,i){t.is_dark(o,i)&&r.rect(i*n,o*n,n,n)}function a(t,r,e){var n,o;n=r,(o=e).back&&(n.fillStyle=o.back,n.fillRect(0,0,o.size,o.size)),function(t,r,e){if(t){var n=0 1 ? 1 : 0; + case 'he': + return n === 1 ? 0 : (n === 2 ? 1 : ((n < 0 || n > 10) && (n % 10 === 0) ? 2 : 3)); + case 'id': + return 0; + case 'lt': + return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20) ? 1 : 2); case 'pl': return n === 1 ? 0 : (n % 10 >= 2 && n %10 <=4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); case 'ru': @@ -789,7 +795,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { return n % 10 === 1 && n % 100 !== 11 ? 0 : (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); case 'sl': return n % 100 === 1 ? 1 : (n % 100 === 2 ? 2 : (n % 100 === 3 || n % 100 === 4 ? 3 : 0)); - // bg, 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; } @@ -3502,6 +3508,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { let createButtonsDisplayed = false, viewButtonsDisplayed = false, + burnAfterReadingDefault = false, + openDiscussionDefault = false, $attach, $burnAfterReading, $burnAfterReadingOption, @@ -3517,6 +3525,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $password, $passwordInput, $rawTextButton, + $downloadTextButton, $qrCodeLink, $emailLink, $sendButton, @@ -3658,6 +3667,30 @@ jQuery.PrivateBin = (function($, RawDeflate) { newDoc.close(); } + /** + * download text + * + * @name TopNav.downloadText + * @private + * @function + */ + function downloadText() + { + var filename='paste-' + Model.getPasteId() + '.txt'; + var text = PasteViewer.getText(); + + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + } + /** * saves the language in a cookie and reloads the page * @@ -3668,7 +3701,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ function setLanguage(event) { - document.cookie = 'lang=' + $(event.target).data('lang'); + document.cookie = 'lang=' + $(event.target).data('lang') + ';secure'; UiHelper.reloadHome(); } @@ -3884,6 +3917,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $newButton.removeClass('hidden'); $cloneButton.removeClass('hidden'); $rawTextButton.removeClass('hidden'); + $downloadTextButton.removeClass('hidden'); $qrCodeLink.removeClass('hidden'); viewButtonsDisplayed = true; @@ -3904,6 +3938,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $cloneButton.addClass('hidden'); $newButton.addClass('hidden'); $rawTextButton.addClass('hidden'); + $downloadTextButton.addClass('hidden'); $qrCodeLink.addClass('hidden'); me.hideEmailButton(); @@ -4065,6 +4100,17 @@ jQuery.PrivateBin = (function($, RawDeflate) { $rawTextButton.addClass('hidden'); }; + /** + * only hides the download text button + * + * @name TopNav.hideRawButton + * @function + */ + me.hideDownloadButton = function() + { + $downloadTextButton.addClass('hidden'); + }; + /** * only hides the qr code button * @@ -4146,13 +4192,18 @@ jQuery.PrivateBin = (function($, RawDeflate) { me.resetInput = function() { clearAttachmentInput(); + $burnAfterReading.prop('checked', burnAfterReadingDefault); + $openDiscussion.prop('checked', openDiscussionDefault); + if (openDiscussionDefault || !burnAfterReadingDefault) $openDiscussionOption.removeClass('buttondisabled'); + if (burnAfterReadingDefault || !openDiscussionDefault) $burnAfterReadingOption.removeClass('buttondisabled'); - $openDiscussion.prop('checked', false); - $burnAfterReading.prop('checked', false); - $openDiscussionOption.removeClass('buttondisabled'); - $burnAfterReadingOption.removeClass('buttondisabled'); - - // TODO: reset expiration time + pasteExpiration = Model.getExpirationDefault() || pasteExpiration; + $('#pasteExpiration>option').each(function() { + const $this = $(this); + if ($this.val() === pasteExpiration) { + $('#pasteExpirationDisplay').text($this.text()); + } + }); }; /** @@ -4321,6 +4372,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $password = $('#password'); $passwordInput = $('#passwordinput'); $rawTextButton = $('#rawtextbutton'); + $downloadTextButton = $('#downloadtextbutton'); $retryButton = $('#retrybutton'); $sendButton = $('#sendbutton'); $qrCodeLink = $('#qrcodelink'); @@ -4338,6 +4390,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $sendButton.click(PasteEncrypter.sendPaste); $cloneButton.click(Controller.clonePaste); $rawTextButton.click(rawText); + $downloadTextButton.click(downloadText); $retryButton.click(clickRetryButton); $fileRemoveButton.click(removeAttachment); $qrCodeLink.click(displayQrCode); @@ -4350,7 +4403,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { changeBurnAfterReading(); changeOpenDiscussion(); - // get default value from template or fall back to set value + // get default values from template or fall back to set value + burnAfterReadingDefault = me.getBurnAfterReading(); + openDiscussionDefault = me.getOpenDiscussion(); pasteExpiration = Model.getExpirationDefault() || pasteExpiration; createButtonsDisplayed = false; @@ -4674,6 +4729,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { TopNav.showEmailButton(); TopNav.hideRawButton(); + TopNav.hideDownloadButton(); Editor.hide(); // parse and show text diff --git a/js/purify-2.0.14.js b/js/purify-2.0.14.js deleted file mode 100644 index a794b186..00000000 --- a/js/purify-2.0.14.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! @license DOMPurify | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.0.8/LICENSE */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).DOMPurify=t()}(this,(function(){"use strict";var e=Object.hasOwnProperty,t=Object.setPrototypeOf,n=Object.isFrozen,r=Object.keys,o=Object.freeze,i=Object.seal,a=Object.create,l="undefined"!=typeof Reflect&&Reflect,c=l.apply,s=l.construct;c||(c=function(e,t,n){return e.apply(t,n)}),o||(o=function(e){return e}),i||(i=function(e){return e}),s||(s=function(e,t){return new(Function.prototype.bind.apply(e,[null].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t1?n-1:0),o=1;o/gm),U=i(/^data-[\-\w.\u00B7-\uFFFF]/),j=i(/^aria-[\-\w]+$/),P=i(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),G=i(/^(?:\w+script|data):/i),W=i(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g),B="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function q(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:K(),n=function(t){return e(t)};if(n.version="2.0.14",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var i=t.document,a=!1,l=t.document,c=t.DocumentFragment,s=t.HTMLTemplateElement,k=t.Node,L=t.NodeFilter,Y=t.NamedNodeMap,X=void 0===Y?t.NamedNodeMap||t.MozNamedAttrMap:Y,$=t.Text,J=t.Comment,Q=t.DOMParser,Z=t.trustedTypes;if("function"==typeof s){var ee=l.createElement("template");ee.content&&ee.content.ownerDocument&&(l=ee.content.ownerDocument)}var te=V(Z,i),ne=te&&He?te.createHTML(""):"",re=l,oe=re.implementation,ie=re.createNodeIterator,ae=re.getElementsByTagName,le=re.createDocumentFragment,ce=i.importNode,se=E(l).documentMode?l.documentMode:{},ue={};n.isSupported=oe&&void 0!==oe.createHTMLDocument&&9!==se;var de=z,fe=I,pe=U,me=j,ye=G,ge=W,he=P,ve=null,be=_({},[].concat(q(M),q(D),q(N),q(O),q(R))),Te=null,Ae=_({},[].concat(q(w),q(F),q(H),q(C))),xe=null,Se=null,ke=!0,Le=!0,_e=!1,Ee=!1,Me=!1,De=!1,Ne=!1,Oe=!1,Re=!1,we=!1,Fe=!1,He=!1,Ce=!0,ze=!0,Ie=!1,Ue={},je=_({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","plaintext","script","style","svg","template","thead","title","video","xmp"]),Pe=null,Ge=_({},["audio","video","img","source","image","track"]),We=null,Be=_({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),qe=null,Ke=l.createElement("form"),Ve=function(e){qe&&qe===e||(e&&"object"===(void 0===e?"undefined":B(e))||(e={}),e=E(e),ve="ALLOWED_TAGS"in e?_({},e.ALLOWED_TAGS):be,Te="ALLOWED_ATTR"in e?_({},e.ALLOWED_ATTR):Ae,We="ADD_URI_SAFE_ATTR"in e?_(E(Be),e.ADD_URI_SAFE_ATTR):Be,Pe="ADD_DATA_URI_TAGS"in e?_(E(Ge),e.ADD_DATA_URI_TAGS):Ge,xe="FORBID_TAGS"in e?_({},e.FORBID_TAGS):{},Se="FORBID_ATTR"in e?_({},e.FORBID_ATTR):{},Ue="USE_PROFILES"in e&&e.USE_PROFILES,ke=!1!==e.ALLOW_ARIA_ATTR,Le=!1!==e.ALLOW_DATA_ATTR,_e=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Ee=e.SAFE_FOR_JQUERY||!1,Me=e.SAFE_FOR_TEMPLATES||!1,De=e.WHOLE_DOCUMENT||!1,Re=e.RETURN_DOM||!1,we=e.RETURN_DOM_FRAGMENT||!1,Fe=e.RETURN_DOM_IMPORT||!1,He=e.RETURN_TRUSTED_TYPE||!1,Oe=e.FORCE_BODY||!1,Ce=!1!==e.SANITIZE_DOM,ze=!1!==e.KEEP_CONTENT,Ie=e.IN_PLACE||!1,he=e.ALLOWED_URI_REGEXP||he,Me&&(Le=!1),we&&(Re=!0),Ue&&(ve=_({},[].concat(q(R))),Te=[],!0===Ue.html&&(_(ve,M),_(Te,w)),!0===Ue.svg&&(_(ve,D),_(Te,F),_(Te,C)),!0===Ue.svgFilters&&(_(ve,N),_(Te,F),_(Te,C)),!0===Ue.mathMl&&(_(ve,O),_(Te,H),_(Te,C))),e.ADD_TAGS&&(ve===be&&(ve=E(ve)),_(ve,e.ADD_TAGS)),e.ADD_ATTR&&(Te===Ae&&(Te=E(Te)),_(Te,e.ADD_ATTR)),e.ADD_URI_SAFE_ATTR&&_(We,e.ADD_URI_SAFE_ATTR),ze&&(ve["#text"]=!0),De&&_(ve,["html","head","body"]),ve.table&&(_(ve,["tbody"]),delete xe.tbody),o&&o(e),qe=e)},Ye=function(e){m(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=ne}},Xe=function(e,t){try{m(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){m(n.removed,{attribute:null,from:t})}t.removeAttribute(e)},$e=function(e){var t=void 0,n=void 0;if(Oe)e=""+e;else{var r=h(e,/^[\r\n\t ]+/);n=r&&r[0]}var o=te?te.createHTML(e):e;try{t=(new Q).parseFromString(o,"text/html")}catch(e){}if(a&&_(xe,["title"]),!t||!t.documentElement){var i=(t=oe.createHTMLDocument("")).body;i.parentNode.removeChild(i.parentNode.firstElementChild),i.outerHTML=o}return e&&n&&t.body.insertBefore(l.createTextNode(n),t.body.childNodes[0]||null),ae.call(t,De?"html":"body")[0]};n.isSupported&&function(){try{var e=$e("</title><img>");A(/<\/title/,e.querySelector("title").innerHTML)&&(a=!0)}catch(e){}}();var Je=function(e){return ie.call(e.ownerDocument||e,e,L.SHOW_ELEMENT|L.SHOW_COMMENT|L.SHOW_TEXT,(function(){return L.FILTER_ACCEPT}),!1)},Qe=function(e){return!(e instanceof $||e instanceof J)&&!("string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof X&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute&&"string"==typeof e.namespaceURI)},Ze=function(e){return"object"===(void 0===k?"undefined":B(k))?e instanceof k:e&&"object"===(void 0===e?"undefined":B(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},et=function(e,t,r){ue[e]&&u(ue[e],(function(e){e.call(n,t,r,qe)}))},tt=function(e){var t=void 0;if(et("beforeSanitizeElements",e,null),Qe(e))return Ye(e),!0;if(h(e.nodeName,/[\u0080-\uFFFF]/))return Ye(e),!0;var r=g(e.nodeName);if(et("uponSanitizeElement",e,{tagName:r,allowedTags:ve}),("svg"===r||"math"===r)&&0!==e.querySelectorAll("p, br").length)return Ye(e),!0;if(!ve[r]||xe[r]){if(ze&&!je[r]&&"function"==typeof e.insertAdjacentHTML)try{var o=e.innerHTML;e.insertAdjacentHTML("AfterEnd",te?te.createHTML(o):o)}catch(e){}return Ye(e),!0}return"noscript"===r&&A(/<\/noscript/i,e.innerHTML)||"noembed"===r&&A(/<\/noembed/i,e.innerHTML)?(Ye(e),!0):(!Ee||e.firstElementChild||e.content&&e.content.firstElementChild||!A(/l&&e.setAttribute("id",a.value);else{if("INPUT"===e.nodeName&&"type"===i&&"file"===o&&s.keepAttr&&(Te[i]||!Se[i]))continue;"id"===m&&e.setAttribute(m,""),Xe(m,e)}if(s.keepAttr)if(Ee&&A(/\/>/i,o))Xe(m,e);else if(A(/svg|math/i,e.namespaceURI)&&A(x("1?n-1:0),o=1;o/gm),U=a(/^data-[\-\w.\u00B7-\uFFFF]/),j=a(/^aria-[\-\w]+$/),P=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),B=a(/^(?:\w+script|data):/i),W=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),G="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function q(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:K(),n=function(t){return e(t)};if(n.version="2.2.7",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var r=t.document,o=t.document,a=t.DocumentFragment,l=t.HTMLTemplateElement,c=t.Node,s=t.Element,u=t.NodeFilter,f=t.NamedNodeMap,x=void 0===f?t.NamedNodeMap||t.MozNamedAttrMap:f,Y=t.Text,X=t.Comment,$=t.DOMParser,Z=t.trustedTypes,J=s.prototype,Q=k(J,"cloneNode"),ee=k(J,"nextSibling"),te=k(J,"childNodes"),ne=k(J,"parentNode");if("function"==typeof l){var re=o.createElement("template");re.content&&re.content.ownerDocument&&(o=re.content.ownerDocument)}var oe=V(Z,r),ie=oe&&ze?oe.createHTML(""):"",ae=o,le=ae.implementation,ce=ae.createNodeIterator,se=ae.getElementsByTagName,ue=ae.createDocumentFragment,fe=r.importNode,me={};try{me=S(o).documentMode?o.documentMode:{}}catch(e){}var de={};n.isSupported="function"==typeof ne&&le&&void 0!==le.createHTMLDocument&&9!==me;var pe=z,ge=H,he=U,ye=j,ve=B,be=W,Te=P,Ae=null,xe=w({},[].concat(q(R),q(_),q(D),q(N),q(L))),we=null,Se=w({},[].concat(q(M),q(F),q(C),q(I))),ke=null,Re=null,_e=!0,De=!0,Ee=!1,Ne=!1,Oe=!1,Le=!1,Me=!1,Fe=!1,Ce=!1,Ie=!0,ze=!1,He=!0,Ue=!0,je=!1,Pe={},Be=w({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),We=null,Ge=w({},["audio","video","img","source","image","track"]),qe=null,Ke=w({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),Ve=null,Ye=o.createElement("form"),Xe=function(e){Ve&&Ve===e||(e&&"object"===(void 0===e?"undefined":G(e))||(e={}),e=S(e),Ae="ALLOWED_TAGS"in e?w({},e.ALLOWED_TAGS):xe,we="ALLOWED_ATTR"in e?w({},e.ALLOWED_ATTR):Se,qe="ADD_URI_SAFE_ATTR"in e?w(S(Ke),e.ADD_URI_SAFE_ATTR):Ke,We="ADD_DATA_URI_TAGS"in e?w(S(Ge),e.ADD_DATA_URI_TAGS):Ge,ke="FORBID_TAGS"in e?w({},e.FORBID_TAGS):{},Re="FORBID_ATTR"in e?w({},e.FORBID_ATTR):{},Pe="USE_PROFILES"in e&&e.USE_PROFILES,_e=!1!==e.ALLOW_ARIA_ATTR,De=!1!==e.ALLOW_DATA_ATTR,Ee=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Ne=e.SAFE_FOR_TEMPLATES||!1,Oe=e.WHOLE_DOCUMENT||!1,Fe=e.RETURN_DOM||!1,Ce=e.RETURN_DOM_FRAGMENT||!1,Ie=!1!==e.RETURN_DOM_IMPORT,ze=e.RETURN_TRUSTED_TYPE||!1,Me=e.FORCE_BODY||!1,He=!1!==e.SANITIZE_DOM,Ue=!1!==e.KEEP_CONTENT,je=e.IN_PLACE||!1,Te=e.ALLOWED_URI_REGEXP||Te,Ne&&(De=!1),Ce&&(Fe=!0),Pe&&(Ae=w({},[].concat(q(L))),we=[],!0===Pe.html&&(w(Ae,R),w(we,M)),!0===Pe.svg&&(w(Ae,_),w(we,F),w(we,I)),!0===Pe.svgFilters&&(w(Ae,D),w(we,F),w(we,I)),!0===Pe.mathMl&&(w(Ae,N),w(we,C),w(we,I))),e.ADD_TAGS&&(Ae===xe&&(Ae=S(Ae)),w(Ae,e.ADD_TAGS)),e.ADD_ATTR&&(we===Se&&(we=S(we)),w(we,e.ADD_ATTR)),e.ADD_URI_SAFE_ATTR&&w(qe,e.ADD_URI_SAFE_ATTR),Ue&&(Ae["#text"]=!0),Oe&&w(Ae,["html","head","body"]),Ae.table&&(w(Ae,["tbody"]),delete ke.tbody),i&&i(e),Ve=e)},$e=w({},["mi","mo","mn","ms","mtext"]),Ze=w({},["foreignobject","desc","title","annotation-xml"]),Je=w({},_);w(Je,D),w(Je,E);var Qe=w({},N);w(Qe,O);var et="http://www.w3.org/1998/Math/MathML",tt="http://www.w3.org/2000/svg",nt="http://www.w3.org/1999/xhtml",rt=function(e){var t=ne(e);t&&t.tagName||(t={namespaceURI:nt,tagName:"template"});var n=g(e.tagName),r=g(t.tagName);if(e.namespaceURI===tt)return t.namespaceURI===nt?"svg"===n:t.namespaceURI===et?"svg"===n&&("annotation-xml"===r||$e[r]):Boolean(Je[n]);if(e.namespaceURI===et)return t.namespaceURI===nt?"math"===n:t.namespaceURI===tt?"math"===n&&Ze[r]:Boolean(Qe[n]);if(e.namespaceURI===nt){if(t.namespaceURI===tt&&!Ze[r])return!1;if(t.namespaceURI===et&&!$e[r])return!1;var o=w({},["title","style","font","a","script"]);return!Qe[n]&&(o[n]||!Je[n])}return!1},ot=function(e){p(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=ie}catch(t){e.remove()}}},it=function(e,t){try{p(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!we[e])if(Fe||Ce)try{ot(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},at=function(e){var t=void 0,n=void 0;if(Me)e=""+e;else{var r=h(e,/^[\r\n\t ]+/);n=r&&r[0]}var i=oe?oe.createHTML(e):e;try{t=(new $).parseFromString(i,"text/html")}catch(e){}if(!t||!t.documentElement){var a=(t=le.createHTMLDocument("")).body;a.parentNode.removeChild(a.parentNode.firstElementChild),a.outerHTML=i}return e&&n&&t.body.insertBefore(o.createTextNode(n),t.body.childNodes[0]||null),se.call(t,Oe?"html":"body")[0]},lt=function(e){return ce.call(e.ownerDocument||e,e,u.SHOW_ELEMENT|u.SHOW_COMMENT|u.SHOW_TEXT,(function(){return u.FILTER_ACCEPT}),!1)},ct=function(e){return!(e instanceof Y||e instanceof X)&&!("string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof x&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute&&"string"==typeof e.namespaceURI&&"function"==typeof e.insertBefore)},st=function(e){return"object"===(void 0===c?"undefined":G(c))?e instanceof c:e&&"object"===(void 0===e?"undefined":G(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},ut=function(e,t,r){de[e]&&m(de[e],(function(e){e.call(n,t,r,Ve)}))},ft=function(e){var t=void 0;if(ut("beforeSanitizeElements",e,null),ct(e))return ot(e),!0;if(h(e.nodeName,/[\u0080-\uFFFF]/))return ot(e),!0;var r=g(e.nodeName);if(ut("uponSanitizeElement",e,{tagName:r,allowedTags:Ae}),!st(e.firstElementChild)&&(!st(e.content)||!st(e.content.firstElementChild))&&T(/<[/\w]/g,e.innerHTML)&&T(/<[/\w]/g,e.textContent))return ot(e),!0;if(!Ae[r]||ke[r]){if(Ue&&!Be[r]){var o=ne(e),i=te(e);if(i&&o)for(var a=i.length-1;a>=0;--a)o.insertBefore(Q(i[a],!0),ee(e))}return ot(e),!0}return e instanceof s&&!rt(e)?(ot(e),!0):"noscript"!==r&&"noembed"!==r||!T(/<\/no(script|embed)/i,e.innerHTML)?(Ne&&3===e.nodeType&&(t=e.textContent,t=y(t,pe," "),t=y(t,ge," "),e.textContent!==t&&(p(n.removed,{element:e.cloneNode()}),e.textContent=t)),ut("afterSanitizeElements",e,null),!1):(ot(e),!0)},mt=function(e,t,n){if(He&&("id"===t||"name"===t)&&(n in o||n in Ye))return!1;if(De&&T(he,t));else if(_e&&T(ye,t));else{if(!we[t]||Re[t])return!1;if(qe[t]);else if(T(Te,y(n,be,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==v(n,"data:")||!We[e]){if(Ee&&!T(ve,y(n,be,"")));else if(n)return!1}else;}return!0},dt=function(e){var t=void 0,r=void 0,o=void 0,i=void 0;ut("beforeSanitizeAttributes",e,null);var a=e.attributes;if(a){var l={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:we};for(i=a.length;i--;){var c=t=a[i],s=c.name,u=c.namespaceURI;if(r=b(t.value),o=g(s),l.attrName=o,l.attrValue=r,l.keepAttr=!0,l.forceKeepAttr=void 0,ut("uponSanitizeAttribute",e,l),r=l.attrValue,!l.forceKeepAttr&&(it(s,e),l.keepAttr))if(T(/\/>/i,r))it(s,e);else{Ne&&(r=y(r,pe," "),r=y(r,ge," "));var f=e.nodeName.toLowerCase();if(mt(f,o,r))try{u?e.setAttributeNS(u,s,r):e.setAttribute(s,r),d(n.removed)}catch(e){}}}ut("afterSanitizeAttributes",e,null)}},pt=function e(t){var n=void 0,r=lt(t);for(ut("beforeSanitizeShadowDOM",t,null);n=r.nextNode();)ut("uponSanitizeShadowNode",n,null),ft(n)||(n.content instanceof a&&e(n.content),dt(n));ut("afterSanitizeShadowDOM",t,null)};return n.sanitize=function(e,o){var i=void 0,l=void 0,s=void 0,u=void 0,f=void 0;if(e||(e="\x3c!--\x3e"),"string"!=typeof e&&!st(e)){if("function"!=typeof e.toString)throw A("toString is not a function");if("string"!=typeof(e=e.toString()))throw A("dirty is not a string, aborting")}if(!n.isSupported){if("object"===G(t.toStaticHTML)||"function"==typeof t.toStaticHTML){if("string"==typeof e)return t.toStaticHTML(e);if(st(e))return t.toStaticHTML(e.outerHTML)}return e}if(Le||Xe(o),n.removed=[],"string"==typeof e&&(je=!1),je);else if(e instanceof c)1===(l=(i=at("\x3c!----\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===l.nodeName||"HTML"===l.nodeName?i=l:i.appendChild(l);else{if(!Fe&&!Ne&&!Oe&&-1===e.indexOf("<"))return oe&&ze?oe.createHTML(e):e;if(!(i=at(e)))return Fe?null:ie}i&&Me&&ot(i.firstChild);for(var m=lt(je?e:i);s=m.nextNode();)3===s.nodeType&&s===u||ft(s)||(s.content instanceof a&&pt(s.content),dt(s),u=s);if(u=null,je)return e;if(Fe){if(Ce)for(f=ue.call(i.ownerDocument);i.firstChild;)f.appendChild(i.firstChild);else f=i;return Ie&&(f=fe.call(r,f,!0)),f}var d=Oe?i.outerHTML:i.innerHTML;return Ne&&(d=y(d,pe," "),d=y(d,ge," ")),oe&&ze?oe.createHTML(d):d},n.setConfig=function(e){Xe(e),Le=!0},n.clearConfig=function(){Ve=null,Le=!1},n.isValidAttribute=function(e,t,n){Ve||Xe({});var r=g(e),o=g(t);return mt(r,o,n)},n.addHook=function(e,t){"function"==typeof t&&(de[e]=de[e]||[],p(de[e],t))},n.removeHook=function(e){de[e]&&d(de[e])},n.removeHooks=function(e){de[e]&&(de[e]=[])},n.removeAllHooks=function(){de={}},n}()})); diff --git a/js/test/TopNav.js b/js/test/TopNav.js index e5a29c41..29639643 100644 --- a/js/test/TopNav.js +++ b/js/test/TopNav.js @@ -317,6 +317,131 @@ describe('TopNav', function () { ); }); + describe('resetInput', function () { + before(function () { + cleanup(); + }); + + it( + 'reset inputs to defaults (options off)', + function () { + var results = []; + $('body').html( + '' + ); + $.PrivateBin.TopNav.init(); + results.push( + !$.PrivateBin.TopNav.getBurnAfterReading() + ); + results.push( + !$.PrivateBin.TopNav.getOpenDiscussion() + ); + $('#burnafterreading').attr('checked', 'checked'); + $('#opendiscussion').attr('checked', 'checked'); + results.push( + $.PrivateBin.TopNav.getBurnAfterReading() + ); + results.push( + $.PrivateBin.TopNav.getOpenDiscussion() + ); + $.PrivateBin.TopNav.resetInput(); + results.push( + !$.PrivateBin.TopNav.getBurnAfterReading() + ); + results.push( + !$.PrivateBin.TopNav.getOpenDiscussion() + ); + cleanup(); + assert.ok(results.every(element => element)); + } + ); + + it( + 'reset inputs to defaults (burnafterreading on)', + function () { + var results = []; + $('body').html( + '' + ); + $.PrivateBin.TopNav.init(); + results.push( + $.PrivateBin.TopNav.getBurnAfterReading() + ); + results.push( + !$.PrivateBin.TopNav.getOpenDiscussion() + ); + $('#burnafterreading').removeAttr('checked'); + results.push( + !$.PrivateBin.TopNav.getBurnAfterReading() + ); + results.push( + !$.PrivateBin.TopNav.getOpenDiscussion() + ); + $.PrivateBin.TopNav.resetInput(); + results.push( + $.PrivateBin.TopNav.getBurnAfterReading() + ); + results.push( + !$.PrivateBin.TopNav.getOpenDiscussion() + ); + cleanup(); + assert.ok(results.every(element => element)); + } + ); + + it( + 'reset inputs to defaults (opendiscussion on)', + function () { + var results = []; + $('body').html( + '' + ); + $.PrivateBin.TopNav.init(); + results.push( + !$.PrivateBin.TopNav.getBurnAfterReading() + ); + results.push( + $.PrivateBin.TopNav.getOpenDiscussion() + ); + $('#opendiscussion').removeAttr('checked'); + $('#burnafterreading').prop('checked', true); + results.push( + $.PrivateBin.TopNav.getBurnAfterReading() + ); + results.push( + !$.PrivateBin.TopNav.getOpenDiscussion() + ); + $.PrivateBin.TopNav.resetInput(); + results.push( + !$.PrivateBin.TopNav.getBurnAfterReading() + ); + results.push( + $.PrivateBin.TopNav.getOpenDiscussion() + ); + cleanup(); + assert.ok(results.every(element => element)); + } + ); + }); + describe('getExpiration', function () { before(function () { cleanup(); diff --git a/lib/Configuration.php b/lib/Configuration.php index 06783706..d8ef346b 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -7,14 +7,13 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin; use Exception; use PDO; -use PrivateBin\Persistence\DataStore; /** * Configuration @@ -48,13 +47,14 @@ class Configuration 'syntaxhighlightingtheme' => null, 'sizelimit' => 10485760, 'template' => 'bootstrap', + 'info' => 'More information on the project page.', 'notice' => '', 'languageselection' => false, 'languagedefault' => '', 'urlshortener' => '', 'qrcode' => true, 'icon' => 'identicon', - 'cspheader' => 'default-src \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\' resource:; style-src \'self\'; font-src \'self\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads', + 'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\' resource:; style-src \'self\'; font-src \'self\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads', 'zerobincompatibility' => false, 'httpwarning' => true, 'compression' => 'zlib', @@ -78,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', @@ -102,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; } } @@ -151,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 diff --git a/lib/Controller.php b/lib/Controller.php index 744a5237..fb919ca1 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin; @@ -28,7 +28,7 @@ class Controller * * @const string */ - const VERSION = '1.3.4'; + const VERSION = '1.3.5'; /** * minimal required PHP version @@ -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; @@ -380,6 +380,7 @@ class Controller $page->assign('SYNTAXHIGHLIGHTINGTHEME', $this->_conf->getKey('syntaxhighlightingtheme')); $page->assign('FORMATTER', $formatters); $page->assign('FORMATTERDEFAULT', $this->_conf->getKey('defaultformatter')); + $page->assign('INFO', I18n::_(str_replace("'", '"', $this->_conf->getKey('info')))); $page->assign('NOTICE', I18n::_($this->_conf->getKey('notice'))); $page->assign('BURNAFTERREADINGSELECTED', $this->_conf->getKey('burnafterreadingselected')); $page->assign('PASSWORD', $this->_conf->getKey('password')); diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index 9c925838..591b91fd 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin\Data; @@ -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 * diff --git a/lib/Data/Database.php b/lib/Data/Database.php index aa05e95a..0c66d330 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin\Data; @@ -198,21 +198,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 +233,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; } @@ -348,19 +355,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; + } } /** @@ -416,13 +427,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 * @@ -563,16 +646,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'] : ''; } /** diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 3e9b237f..577ad34f 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -7,12 +7,13 @@ * @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.4 + * @version 1.3.5 */ 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 = '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', + ' 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); + } } diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php new file mode 100644 index 00000000..2e8e2c5d --- /dev/null +++ b/lib/Data/GoogleCloudStorage.php @@ -0,0 +1,346 @@ + 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; + } +} diff --git a/lib/Filter.php b/lib/Filter.php index 547e2395..0ad87d3c 100644 --- a/lib/Filter.php +++ b/lib/Filter.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin; diff --git a/lib/FormatV2.php b/lib/FormatV2.php index 31cc5b84..d2055f31 100644 --- a/lib/FormatV2.php +++ b/lib/FormatV2.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin; @@ -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: diff --git a/lib/I18n.php b/lib/I18n.php index aa68df9c..50bf0ccf 100644 --- a/lib/I18n.php +++ b/lib/I18n.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin; @@ -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 @@ -321,6 +321,12 @@ class I18n case 'oc': case 'zh': return $n > 1 ? 1 : 0; + case 'he': + return $n === 1 ? 0 : ($n === 2 ? 1 : (($n < 0 || $n > 10) && ($n % 10 === 0) ? 2 : 3)); + case 'id': + return 0; + case 'lt': + return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (($n % 10 >= 2 && $n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); case 'pl': return $n == 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); case 'ru': @@ -328,7 +334,7 @@ class I18n return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); case 'sl': return $n % 100 == 1 ? 1 : ($n % 100 == 2 ? 2 : ($n % 100 == 3 || $n % 100 == 4 ? 3 : 0)); - // bg, 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; } diff --git a/lib/Json.php b/lib/Json.php index 6916d27f..5f4efcf3 100644 --- a/lib/Json.php +++ b/lib/Json.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin; @@ -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; } /** diff --git a/lib/Model.php b/lib/Model.php index f5dd5577..8aebd794 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin; @@ -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( diff --git a/lib/Model/AbstractModel.php b/lib/Model/AbstractModel.php index b7273399..f2ab1daa 100644 --- a/lib/Model/AbstractModel.php +++ b/lib/Model/AbstractModel.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin\Model; diff --git a/lib/Model/Comment.php b/lib/Model/Comment.php index 68045aa9..4b3fc828 100644 --- a/lib/Model/Comment.php +++ b/lib/Model/Comment.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin\Model; diff --git a/lib/Model/Paste.php b/lib/Model/Paste.php index 0aa2a967..b855e6d5 100644 --- a/lib/Model/Paste.php +++ b/lib/Model/Paste.php @@ -7,7 +7,7 @@ * @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.4 + * @version 1.3.5 */ namespace PrivateBin\Model; @@ -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 ( diff --git a/lib/Persistence/AbstractPersistence.php b/lib/Persistence/AbstractPersistence.php index a4011d2d..4e61a8ee 100644 --- a/lib/Persistence/AbstractPersistence.php +++ b/lib/Persistence/AbstractPersistence.php @@ -7,12 +7,12 @@ * @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.4 + * @version 1.3.5 */ 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; } } diff --git a/lib/Persistence/DataStore.php b/lib/Persistence/DataStore.php deleted file mode 100644 index f60fc972..00000000 --- a/lib/Persistence/DataStore.php +++ /dev/null @@ -1,97 +0,0 @@ -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 = '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; } } diff --git a/lib/Persistence/ServerSalt.php b/lib/Persistence/ServerSalt.php index 7764129f..1095498c 100644 --- a/lib/Persistence/ServerSalt.php +++ b/lib/Persistence/ServerSalt.php @@ -7,12 +7,12 @@ * @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.4 + * @version 1.3.5 */ 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, - '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); } } diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 0e6a34b0..9e896c1d 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -1,4 +1,5 @@ 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, - '_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) { diff --git a/lib/View.php b/lib/View.php index b154ed86..81698047 100644 --- a/lib/View.php +++ b/lib/View.php @@ -7,7 +7,7 @@ * @link https://github.com/PrivateBin/PrivateBin * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License - * @version 1.3.4 + * @version 1.3.5 */ namespace PrivateBin; diff --git a/lib/Vizhash16x16.php b/lib/Vizhash16x16.php index 0292de3c..77584eb0 100644 --- a/lib/Vizhash16x16.php +++ b/lib/Vizhash16x16.php @@ -8,7 +8,7 @@ * @link http://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License - * @version 0.0.5 beta PrivateBin 1.3.4 + * @version 0.0.5 beta PrivateBin 1.3.5 */ namespace PrivateBin; diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index eef04448..1e4eae00 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -45,7 +45,7 @@ endif; - + - + - + @@ -212,6 +212,9 @@ endif; + @@ -422,7 +425,7 @@ if (strlen($LANGUAGESELECTION)) : ?>